diff --git a/config/allowed_files.php b/config/allowed_files.php index 6afab2cd..109ab181 100644 --- a/config/allowed_files.php +++ b/config/allowed_files.php @@ -31,6 +31,7 @@ 'composer.lock', 'config/allowed_files.php', 'config/index.php', + 'config/routes.yml', 'config/services.yml', 'controllers/admin/AdminEverBlockConfigurationController.php', 'controllers/admin/AdminEverBlockController.php', @@ -99,6 +100,14 @@ 'src/Command/PrettyBlocksCommand.php', 'src/Command/SearchReplaceCommand.php', 'src/Command/index.php', + 'src/Controller/Admin/EverblockConfigurationController.php', + 'src/Controller/Admin/index.php', + 'src/Controller/index.php', + 'src/Form/Type/EverblockConfigurationType.php', + 'src/Form/Type/index.php', + 'src/Form/index.php', + 'src/Service/Configuration/EverblockConfigurationManager.php', + 'src/Service/Configuration/index.php', 'src/Service/EverblockCache.php', 'src/Service/EverblockPrettyBlocks.php', 'src/Service/EverblockPreviewBuilder.php', @@ -198,6 +207,8 @@ 'views/img/svg/package.svg', 'views/img/svg/percent.svg', 'views/img/svg/phone.svg', + 'views/templates/admin/symfony/configuration.html.twig', + 'views/templates/admin/symfony/index.php', 'views/img/svg/pinterest.svg', 'views/img/svg/plus.svg', 'views/img/svg/qr.svg', diff --git a/config/routes.yml b/config/routes.yml new file mode 100644 index 00000000..7d0520c9 --- /dev/null +++ b/config/routes.yml @@ -0,0 +1,7 @@ +everblock_admin_configuration: + path: /everblock/configuration + methods: [GET, POST] + defaults: + _controller: 'Everblock\\Tools\\Controller\\Admin\\EverblockConfigurationController::index' + _legacy_controller: AdminEverBlockConfiguration + _legacy_link: AdminEverBlockConfiguration diff --git a/config/services.yml b/config/services.yml index 884e869c..f5f16257 100644 --- a/config/services.yml +++ b/config/services.yml @@ -3,6 +3,19 @@ services: autowire: true autoconfigure: true + Everblock\Tools\Controller\: + resource: '../src/Controller' + public: true + tags: ['controller.service_arguments'] + + Everblock\Tools\Form\: + resource: '../src/Form' + public: true + + Everblock\Tools\Service\Configuration\: + resource: '../src/Service/Configuration' + public: true + everblock.tools.import: class: Everblock\Tools\Command\ImportFileCommand public: true @@ -44,4 +57,3 @@ services: public: true tags: - { name: 'console.command' } - diff --git a/src/Controller/Admin/EverblockConfigurationController.php b/src/Controller/Admin/EverblockConfigurationController.php new file mode 100644 index 00000000..e0242796 --- /dev/null +++ b/src/Controller/Admin/EverblockConfigurationController.php @@ -0,0 +1,68 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +namespace Everblock\Tools\Controller\Admin; + +use Everblock\Tools\Form\Type\EverblockConfigurationType; +use Everblock\Tools\Service\Configuration\EverblockConfigurationManager; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatorInterface; + +class EverblockConfigurationController extends AbstractController +{ + private EverblockConfigurationManager $configurationManager; + private TranslatorInterface $translator; + + public function __construct(EverblockConfigurationManager $configurationManager, TranslatorInterface $translator) + { + $this->configurationManager = $configurationManager; + $this->translator = $translator; + } + + public function index(Request $request): Response + { + $configuration = $this->configurationManager->getConfiguration(); + + $form = $this->createForm(EverblockConfigurationType::class, $configuration); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->configurationManager->updateFromForm($form->getData()); + $this->addFlash( + 'success', + $this->translator->trans('Configuration updated successfully.', [], 'Modules.Everblock.Admin') + ); + + return $this->redirectToRoute('everblock_admin_configuration'); + } + + return $this->render( + '@Modules/everblock/views/templates/admin/symfony/configuration.html.twig', + [ + 'form' => $form->createView(), + 'configuration' => $configuration, + ] + ); + } +} diff --git a/src/Controller/Admin/index.php b/src/Controller/Admin/index.php new file mode 100644 index 00000000..b27c0f3a --- /dev/null +++ b/src/Controller/Admin/index.php @@ -0,0 +1,29 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/Controller/index.php b/src/Controller/index.php new file mode 100644 index 00000000..b27c0f3a --- /dev/null +++ b/src/Controller/index.php @@ -0,0 +1,29 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/Form/Type/EverblockConfigurationType.php b/src/Form/Type/EverblockConfigurationType.php new file mode 100644 index 00000000..847d6ae0 --- /dev/null +++ b/src/Form/Type/EverblockConfigurationType.php @@ -0,0 +1,83 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +namespace Everblock\Tools\Form\Type; + +use PrestaShopBundle\Form\Admin\Type\TranslateType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\GreaterThan; +use Symfony\Component\Validator\Constraints\NotBlank; + +class EverblockConfigurationType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('pages_base_url', TextType::class, [ + 'label' => 'Pages base URL', + 'help' => 'Base path used for the guide pages routes.', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('pages_per_page', IntegerType::class, [ + 'label' => 'Items per page', + 'help' => 'Number of guides to display on the listing page.', + 'constraints' => [ + new NotBlank(), + new GreaterThan(0), + ], + ]) + ->add('faq_base_url', TextType::class, [ + 'label' => 'FAQ base URL', + 'help' => 'Base path used for the FAQ tag routes.', + 'constraints' => [ + new NotBlank(), + ], + ]) + ->add('faq_per_page', IntegerType::class, [ + 'label' => 'FAQ per page', + 'help' => 'Number of FAQs to display for each tag page.', + 'constraints' => [ + new NotBlank(), + new GreaterThan(0), + ], + ]) + ->add('google_reviews_cta_label', TranslateType::class, [ + 'label' => 'Google reviews CTA label', + 'type' => TextType::class, + 'help' => 'Label used for the Google reviews call-to-action button.', + 'required' => false, + ]); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => null, + ]); + } +} diff --git a/src/Form/Type/index.php b/src/Form/Type/index.php new file mode 100644 index 00000000..b27c0f3a --- /dev/null +++ b/src/Form/Type/index.php @@ -0,0 +1,29 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/Form/index.php b/src/Form/index.php new file mode 100644 index 00000000..b27c0f3a --- /dev/null +++ b/src/Form/index.php @@ -0,0 +1,29 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/src/Service/Configuration/EverblockConfigurationManager.php b/src/Service/Configuration/EverblockConfigurationManager.php new file mode 100644 index 00000000..c42a77ab --- /dev/null +++ b/src/Service/Configuration/EverblockConfigurationManager.php @@ -0,0 +1,120 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +namespace Everblock\Tools\Service\Configuration; + +use PrestaShop\PrestaShop\Adapter\Configuration as ConfigurationAdapter; +use PrestaShop\PrestaShop\Adapter\Language\LanguageDataProvider; +use PrestaShop\PrestaShop\Adapter\Shop\Context as ShopContext; + +class EverblockConfigurationManager +{ + private const PAGES_BASE_URL = 'EVERBLOCK_PAGES_BASE_URL'; + private const PAGES_PER_PAGE = 'EVERBLOCK_PAGES_PER_PAGE'; + private const FAQ_BASE_URL = 'EVERBLOCK_FAQ_BASE_URL'; + private const FAQ_PER_PAGE = 'EVERBLOCK_FAQ_PER_PAGE'; + private const GOOGLE_REVIEWS_CTA_LABEL = 'EVERBLOCK_GOOGLE_REVIEWS_CTA_LABEL'; + + private ConfigurationAdapter $configuration; + private ShopContext $shopContext; + private LanguageDataProvider $languageProvider; + + public function __construct( + ConfigurationAdapter $configuration, + ShopContext $shopContext, + LanguageDataProvider $languageProvider + ) { + $this->configuration = $configuration; + $this->shopContext = $shopContext; + $this->languageProvider = $languageProvider; + } + + /** + * @return array + */ + public function getConfiguration(): array + { + [$shopGroupId, $shopId] = $this->getShopContextIds(); + + return [ + 'pages_base_url' => (string) $this->configuration->get(self::PAGES_BASE_URL, null, $shopGroupId, $shopId), + 'pages_per_page' => (int) $this->configuration->get(self::PAGES_PER_PAGE, null, $shopGroupId, $shopId), + 'faq_base_url' => (string) $this->configuration->get(self::FAQ_BASE_URL, null, $shopGroupId, $shopId), + 'faq_per_page' => (int) $this->configuration->get(self::FAQ_PER_PAGE, null, $shopGroupId, $shopId), + 'google_reviews_cta_label' => $this->getLocalizedConfiguration(self::GOOGLE_REVIEWS_CTA_LABEL, $shopGroupId, $shopId), + ]; + } + + /** + * @param array $data + */ + public function updateFromForm(array $data): void + { + [$shopGroupId, $shopId] = $this->getShopContextIds(); + + $this->configuration->set(self::PAGES_BASE_URL, (string) $data['pages_base_url'], $shopGroupId, $shopId); + $this->configuration->set(self::PAGES_PER_PAGE, (int) $data['pages_per_page'], $shopGroupId, $shopId); + $this->configuration->set(self::FAQ_BASE_URL, (string) $data['faq_base_url'], $shopGroupId, $shopId); + $this->configuration->set(self::FAQ_PER_PAGE, (int) $data['faq_per_page'], $shopGroupId, $shopId); + $this->configuration->set( + self::GOOGLE_REVIEWS_CTA_LABEL, + $data['google_reviews_cta_label'], + $shopGroupId, + $shopId + ); + } + + /** + * @return array + */ + private function getLocalizedConfiguration(string $key, ?int $shopGroupId, ?int $shopId): array + { + $values = []; + $languages = $this->languageProvider->getLanguages(false); + + foreach ($languages as $language) { + $languageId = (int) $language['id_lang']; + $values[$languageId] = (string) $this->configuration->get( + $key, + $languageId, + $shopGroupId, + $shopId + ); + } + + return $values; + } + + /** + * @return array{0: int|null, 1: int|null} + */ + private function getShopContextIds(): array + { + $shopId = $this->shopContext->getContextShopID(); + $shopGroupId = $this->shopContext->getContextShopGroupID(); + + return [ + $shopGroupId > 0 ? $shopGroupId : null, + $shopId > 0 ? $shopId : null, + ]; + } +} diff --git a/src/Service/Configuration/index.php b/src/Service/Configuration/index.php new file mode 100644 index 00000000..b27c0f3a --- /dev/null +++ b/src/Service/Configuration/index.php @@ -0,0 +1,29 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit; diff --git a/views/templates/admin/symfony/configuration.html.twig b/views/templates/admin/symfony/configuration.html.twig new file mode 100644 index 00000000..8d73b596 --- /dev/null +++ b/views/templates/admin/symfony/configuration.html.twig @@ -0,0 +1,21 @@ +{% extends '@PrestaShop/Admin/layout.html.twig' %} + +{% block content %} +
+

+ {{ 'Everblock configuration'|trans({}, 'Modules.Everblock.Admin') }} +

+
+ {{ form_start(form) }} +
+ {{ form_widget(form) }} +
+ + {{ form_end(form) }} +
+
+{% endblock %} diff --git a/views/templates/admin/symfony/index.php b/views/templates/admin/symfony/index.php new file mode 100644 index 00000000..b27c0f3a --- /dev/null +++ b/views/templates/admin/symfony/index.php @@ -0,0 +1,29 @@ + + * @copyright 2019-2025 Team Ever + * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + */ + +header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); +header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); + +header('Cache-Control: no-store, no-cache, must-revalidate'); +header('Cache-Control: post-check=0, pre-check=0', false); +header('Pragma: no-cache'); + +header('Location: ../'); +exit;