1<?php
2/**
3 * Copyright since 2007 PrestaShop SA and Contributors
4 * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5 *
6 * NOTICE OF LICENSE
7 *
8 * This source file is subject to the Open Software License (OSL 3.0)
9 * that is bundled with this package in the file LICENSE.md.
10 * It is also available through the world-wide-web at this URL:
11 * https://opensource.org/licenses/OSL-3.0
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@prestashop.com so we can send you a copy immediately.
15 *
16 * DISCLAIMER
17 *
18 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
19 * versions in the future. If you wish to customize PrestaShop for your
20 * needs please refer to https://devdocs.prestashop.com/ for more information.
21 *
22 * @author    PrestaShop SA and Contributors <contact@prestashop.com>
23 * @copyright Since 2007 PrestaShop SA and Contributors
24 * @license   https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
25 */
26
27namespace PrestaShopBundle\Controller\Admin\Configure\ShopParameters;
28
29use Exception;
30use PrestaShop\PrestaShop\Core\Domain\Meta\Exception\MetaConstraintException;
31use PrestaShop\PrestaShop\Core\Domain\Meta\Exception\MetaNotFoundException;
32use PrestaShop\PrestaShop\Core\Domain\ShowcaseCard\Query\GetShowcaseCardIsClosed;
33use PrestaShop\PrestaShop\Core\Domain\ShowcaseCard\ValueObject\ShowcaseCard;
34use PrestaShop\PrestaShop\Core\Form\FormHandlerInterface;
35use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Builder\FormBuilderInterface;
36use PrestaShop\PrestaShop\Core\Form\IdentifiableObject\Handler;
37use PrestaShop\PrestaShop\Core\Search\Filters\MetaFilters;
38use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
39use PrestaShopBundle\Security\Annotation\AdminSecurity;
40use PrestaShopBundle\Security\Annotation\DemoRestricted;
41use Symfony\Component\Form\FormInterface;
42use Symfony\Component\HttpFoundation\RedirectResponse;
43use Symfony\Component\HttpFoundation\Request;
44use Symfony\Component\HttpFoundation\Response;
45
46/**
47 * Class MetaController is responsible for page display and all actions used in Configure -> Shop parameters ->
48 * Traffic & Seo -> Seo & Urls tab.
49 */
50class MetaController extends FrameworkBundleAdminController
51{
52    /**
53     * Shows index Meta page.
54     *
55     * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
56     *
57     * @param MetaFilters $filters
58     * @param Request $request
59     *
60     * @return Response
61     */
62    public function indexAction(MetaFilters $filters, Request $request)
63    {
64        $setUpUrlsForm = $this->getSetUpUrlsFormHandler()->getForm();
65        $shopUrlsForm = $this->getShopUrlsFormHandler()->getForm();
66        $seoOptionsForm = $this->getSeoOptionsFormHandler()->getForm();
67        $isRewriteSettingEnabled = $this->get('prestashop.adapter.legacy.configuration')->getBoolean('PS_REWRITING_SETTINGS');
68
69        $urlSchemaForm = null;
70        if ($isRewriteSettingEnabled) {
71            $urlSchemaForm = $this->getUrlSchemaFormHandler()->getForm();
72        }
73
74        return $this->renderForm($request, $filters, $setUpUrlsForm, $shopUrlsForm, $seoOptionsForm, $urlSchemaForm);
75    }
76
77    /**
78     * @deprecated since 1.7.8 and will be removed in next major. Use CommonController:searchGridAction instead
79     *
80     * Used for applying filtering actions.
81     *
82     * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
83     * @DemoRestricted(redirectRoute="admin_metas_index")
84     *
85     * @param Request $request
86     *
87     * @return RedirectResponse
88     */
89    public function searchAction(Request $request)
90    {
91        $definitionFactory = $this->get('prestashop.core.grid.definition.factory.meta');
92        $definitionFactory = $definitionFactory->getDefinition();
93
94        $gridFilterFormFactory = $this->get('prestashop.core.grid.filter.form_factory');
95        $searchParametersForm = $gridFilterFormFactory->create($definitionFactory);
96        $searchParametersForm->handleRequest($request);
97
98        $filters = [];
99        if ($searchParametersForm->isSubmitted()) {
100            $filters = $searchParametersForm->getData();
101        }
102
103        return $this->redirectToRoute('admin_metas_index', ['filters' => $filters]);
104    }
105
106    /**
107     * Points to the form where new record of meta list can be created.
108     *
109     * @AdminSecurity("is_granted('create', request.get('_legacy_controller'))", message="You do not have permission to add this.")
110     *
111     * @param Request $request
112     *
113     * @return Response
114     */
115    public function createAction(Request $request)
116    {
117        $data = [];
118        $metaForm = $this->getMetaFormBuilder()->getForm($data);
119        $metaForm->handleRequest($request);
120
121        try {
122            $result = $this->getMetaFormHandler()->handle($metaForm);
123
124            if (null !== $result->getIdentifiableObjectId()) {
125                $this->addFlash('success', $this->trans('Successful creation.', 'Admin.Notifications.Success'));
126
127                return $this->redirectToRoute('admin_metas_index');
128            }
129        } catch (Exception $exception) {
130            $this->addFlash('error', $this->handleException($exception));
131        }
132
133        return $this->render('@PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/create.html.twig', [
134            'meta_form' => $metaForm->createView(),
135            'multistoreInfoTip' => $this->trans(
136                'Note that this feature is available in all shops context only. It will be added to all your stores.',
137                'Admin.Notifications.Info'
138            ),
139            'multistoreIsUsed' => $this->get('prestashop.adapter.multistore_feature')->isUsed(),
140        ]
141        );
142    }
143
144    /**
145     * Redirects to page where list record can be edited.
146     *
147     * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", message="You do not have permission to edit this.")
148     *
149     * @param int $metaId
150     * @param Request $request
151     *
152     * @return Response
153     */
154    public function editAction($metaId, Request $request)
155    {
156        try {
157            $metaForm = $this->getMetaFormBuilder()->getFormFor($metaId);
158            $metaForm->handleRequest($request);
159
160            $result = $this->getMetaFormHandler()->handleFor($metaId, $metaForm);
161
162            if ($result->isSubmitted() && $result->isValid()) {
163                $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
164
165                return $this->redirectToRoute('admin_metas_index');
166            }
167        } catch (Exception $e) {
168            $this->addFlash('error', $this->handleException($e));
169
170            return $this->redirectToRoute('admin_metas_index');
171        }
172
173        return $this->render('@PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/edit.html.twig', [
174            'meta_form' => $metaForm->createView(),
175        ]
176        );
177    }
178
179    /**
180     * Removes single element from meta list.
181     *
182     * @AdminSecurity("is_granted('delete', request.get('_legacy_controller'))", message="You do not have permission to delete this.")
183     * @DemoRestricted(redirectRoute="admin_metas_index")
184     *
185     * @param int $metaId
186     *
187     * @return RedirectResponse
188     */
189    public function deleteAction($metaId)
190    {
191        $metaEraser = $this->get('prestashop.adapter.meta.meta_eraser');
192        $errors = $metaEraser->erase([$metaId]);
193
194        if (!empty($errors)) {
195            $this->flashErrors($errors);
196        } else {
197            $this->addFlash(
198                'success',
199                $this->trans('Successful deletion.', 'Admin.Notifications.Success')
200            );
201        }
202
203        return $this->redirectToRoute('admin_metas_index');
204    }
205
206    /**
207     * Removes multiple records from meta list.
208     *
209     * @AdminSecurity("is_granted('delete', request.get('_legacy_controller'))", message="You do not have permission to delete this.")
210     * @DemoRestricted(redirectRoute="admin_metas_index")
211     *
212     * @param Request $request
213     *
214     * @return RedirectResponse
215     */
216    public function deleteBulkAction(Request $request)
217    {
218        $metaToDelete = $request->request->get('meta_bulk');
219
220        $metaEraser = $this->get('prestashop.adapter.meta.meta_eraser');
221        $errors = $metaEraser->erase($metaToDelete);
222
223        if (!empty($errors)) {
224            $this->flashErrors($errors);
225        } else {
226            $this->addFlash(
227                'success',
228                $this->trans('The selection has been successfully deleted.', 'Admin.Notifications.Success')
229            );
230        }
231
232        return $this->redirectToRoute('admin_metas_index');
233    }
234
235    /**
236     * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", message="You do not have permission to edit this.")
237     * @DemoRestricted(redirectRoute="admin_metas_index")
238     *
239     * @param MetaFilters $filters
240     * @param Request $request
241     *
242     * @return Response|RedirectResponse
243     */
244    public function processSetUpUrlsFormAction(MetaFilters $filters, Request $request)
245    {
246        $formProcessResult = $this->processForm(
247            $request,
248            $this->getSetUpUrlsFormHandler(),
249            'SetUpUrls'
250        );
251
252        if ($formProcessResult instanceof RedirectResponse) {
253            return $formProcessResult;
254        }
255
256        $shopUrlsForm = $this->getShopUrlsFormHandler()->getForm();
257        $seoOptionsForm = $this->getSeoOptionsFormHandler()->getForm();
258        $isRewriteSettingEnabled = $this->get('prestashop.adapter.legacy.configuration')->getBoolean('PS_REWRITING_SETTINGS');
259
260        $urlSchemaForm = null;
261        if ($isRewriteSettingEnabled) {
262            $urlSchemaForm = $this->getUrlSchemaFormHandler()->getForm();
263        }
264
265        return $this->renderForm($request, $filters, $formProcessResult, $shopUrlsForm, $seoOptionsForm, $urlSchemaForm);
266    }
267
268    /**
269     * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", message="You do not have permission to edit this.")
270     * @DemoRestricted(redirectRoute="admin_metas_index")
271     *
272     * @param MetaFilters $filters
273     * @param Request $request
274     *
275     * @return Response|RedirectResponse
276     */
277    public function processShopUrlsFormAction(MetaFilters $filters, Request $request)
278    {
279        $formProcessResult = $this->processForm(
280            $request,
281            $this->getShopUrlsFormHandler(),
282            'ShopUrls'
283        );
284
285        if ($formProcessResult instanceof RedirectResponse) {
286            return $formProcessResult;
287        }
288
289        $setUpUrlsForm = $this->getSetUpUrlsFormHandler()->getForm();
290        $seoOptionsForm = $this->getSeoOptionsFormHandler()->getForm();
291        $isRewriteSettingEnabled = $this->get('prestashop.adapter.legacy.configuration')->getBoolean('PS_REWRITING_SETTINGS');
292
293        $urlSchemaForm = null;
294        if ($isRewriteSettingEnabled) {
295            $urlSchemaForm = $this->getUrlSchemaFormHandler()->getForm();
296        }
297
298        return $this->renderForm($request, $filters, $setUpUrlsForm, $formProcessResult, $seoOptionsForm, $urlSchemaForm);
299    }
300
301    /**
302     * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", message="You do not have permission to edit this.")
303     * @DemoRestricted(redirectRoute="admin_metas_index")
304     *
305     * @param MetaFilters $filters
306     * @param Request $request
307     *
308     * @return Response|RedirectResponse
309     */
310    public function processUrlSchemaFormAction(MetaFilters $filters, Request $request)
311    {
312        $formProcessResult = $this->processForm(
313            $request,
314            $this->getUrlSchemaFormHandler(),
315            'UrlSchema'
316        );
317
318        if ($formProcessResult instanceof RedirectResponse) {
319            return $formProcessResult;
320        }
321
322        $setUpUrlsForm = $this->getSetUpUrlsFormHandler()->getForm();
323        $shopUrlsForm = $this->getShopUrlsFormHandler()->getForm();
324        $seoOptionsForm = $this->getSeoOptionsFormHandler()->getForm();
325
326        return $this->renderForm($request, $filters, $setUpUrlsForm, $shopUrlsForm, $seoOptionsForm, $formProcessResult);
327    }
328
329    /**
330     * @AdminSecurity("is_granted('update', request.get('_legacy_controller'))", message="You do not have permission to edit this.")
331     * @DemoRestricted(redirectRoute="admin_metas_index")
332     *
333     * @param MetaFilters $filters
334     * @param Request $request
335     *
336     * @return Response|RedirectResponse
337     */
338    public function processSeoOptionsFormAction(MetaFilters $filters, Request $request)
339    {
340        $formProcessResult = $this->processForm(
341            $request,
342            $this->getSeoOptionsFormHandler(),
343            'SeoOptions'
344        );
345
346        if ($formProcessResult instanceof RedirectResponse) {
347            return $formProcessResult;
348        }
349
350        $setUpUrlsForm = $this->getSetUpUrlsFormHandler()->getForm();
351        $shopUrlsForm = $this->getShopUrlsFormHandler()->getForm();
352        $isRewriteSettingEnabled = $this->get('prestashop.adapter.legacy.configuration')->getBoolean('PS_REWRITING_SETTINGS');
353
354        $urlSchemaForm = null;
355        if ($isRewriteSettingEnabled) {
356            $urlSchemaForm = $this->getUrlSchemaFormHandler()->getForm();
357        }
358
359        return $this->renderForm($request, $filters, $setUpUrlsForm, $shopUrlsForm, $formProcessResult, $urlSchemaForm);
360    }
361
362    /**
363     * Generates robots.txt file for Front Office.
364     *
365     * @AdminSecurity("is_granted(['create', 'update', 'delete'], request.get('_legacy_controller'))")
366     * @DemoRestricted(redirectRoute="admin_metas_index")
367     *
368     * @return RedirectResponse
369     */
370    public function generateRobotsFileAction()
371    {
372        $robotsTextFileGenerator = $this->get('prestashop.adapter.file.robots_text_file_generator');
373
374        $rootDir = $this->get('prestashop.adapter.legacy.configuration')->get('_PS_ROOT_DIR_');
375
376        if (!$robotsTextFileGenerator->generateFile()) {
377            $this->addFlash(
378                'error',
379                $this->trans(
380                    'Cannot write into file: %filename%. Please check write permissions.',
381                    'Admin.Notifications.Error',
382                    [
383                        '%filename%' => $rootDir . '/robots.txt',
384                    ]
385                )
386            );
387
388            return $this->redirectToRoute('admin_metas_index');
389        }
390
391        $this->addFlash(
392            'success',
393            $this->trans('Successful update.', 'Admin.Notifications.Success')
394        );
395
396        return $this->redirectToRoute('admin_metas_index');
397    }
398
399    /**
400     * @param Request $request
401     * @param MetaFilters $filters
402     * @param FormInterface $setUpUrlsForm
403     * @param FormInterface $shopUrlsForm
404     * @param FormInterface $seoOptionsForm
405     * @param FormInterface|null $urlSchemaForm
406     *
407     * @return Response
408     */
409    protected function renderForm(
410        Request $request,
411        MetaFilters $filters,
412        FormInterface $setUpUrlsForm,
413        FormInterface $shopUrlsForm,
414        FormInterface $seoOptionsForm,
415        ?FormInterface $urlSchemaForm = null
416    ): Response {
417        $seoUrlsGridFactory = $this->get('prestashop.core.grid.factory.meta');
418
419        $context = $this->get('prestashop.adapter.shop.context');
420
421        $isShopContext = $context->isShopContext();
422        $isShopFeatureActive = $this->get('prestashop.adapter.multistore_feature')->isActive();
423
424        $isGridDisplayed = !($isShopFeatureActive && !$isShopContext);
425
426        $presentedGrid = null;
427        if ($isGridDisplayed) {
428            $grid = $seoUrlsGridFactory->getGrid($filters);
429
430            $gridPresenter = $this->get('prestashop.core.grid.presenter.grid_presenter');
431            $presentedGrid = $gridPresenter->present($grid);
432        }
433
434        $tools = $this->get('prestashop.adapter.tools');
435        $urlFileChecker = $this->get('prestashop.core.util.url.url_file_checker');
436        $hostingInformation = $this->get('prestashop.adapter.hosting_information');
437        $defaultRoutesProvider = $this->get('prestashop.adapter.data_provider.default_route');
438        $helperBlockLinkProvider = $this->get('prestashop.core.util.helper_card.documentation_link_provider');
439        $metaDataProvider = $this->get('prestashop.adapter.meta.data_provider');
440
441        $showcaseCardIsClosed = $this->getQueryBus()->handle(
442            new GetShowcaseCardIsClosed((int) $this->getContext()->employee->id, ShowcaseCard::SEO_URLS_CARD)
443        );
444
445        $doesMainShopUrlExist = $this->get('prestashop.adapter.shop.shop_url')->doesMainShopUrlExist();
446
447        return $this->render(
448            '@PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/index.html.twig',
449            [
450                'layoutHeaderToolbarBtn' => [
451                    'add' => [
452                        'href' => $this->generateUrl('admin_metas_create'),
453                        'desc' => $this->trans('Add a new page', 'Admin.Shopparameters.Feature'),
454                        'icon' => 'add_circle_outline',
455                    ],
456                ],
457                'grid' => $presentedGrid,
458                'setUpUrlsForm' => $setUpUrlsForm->createView(),
459                'shopUrlsForm' => $shopUrlsForm->createView(),
460                'urlSchemaForm' => $urlSchemaForm !== null ? $urlSchemaForm->createView() : null,
461                'seoOptionsForm' => $seoOptionsForm->createView(),
462                'robotsForm' => $this->createFormBuilder()->getForm()->createView(),
463                'routeKeywords' => $defaultRoutesProvider->getKeywords(),
464                'isGridDisplayed' => $isGridDisplayed,
465                'isModRewriteActive' => $tools->isModRewriteActive(),
466                'isShopContext' => $isShopContext,
467                'isHtaccessFileValid' => $urlFileChecker->isHtaccessFileWritable(),
468                'isRobotsTextFileValid' => $urlFileChecker->isRobotsFileWritable(),
469                'isShopFeatureActive' => $isShopFeatureActive,
470                'isHostMode' => $hostingInformation->isHostMode(),
471                'doesMainShopUrlExist' => $doesMainShopUrlExist,
472                'enableSidebar' => true,
473                'help_link' => $this->generateSidebarLink($request->attributes->get('_legacy_controller')),
474                'helperDocLink' => $helperBlockLinkProvider->getLink('meta'),
475                'indexPageId' => $metaDataProvider->getIdByPage('index'),
476                'metaShowcaseCardName' => ShowcaseCard::SEO_URLS_CARD,
477                'showcaseCardIsClosed' => $showcaseCardIsClosed,
478            ]
479        );
480    }
481
482    /**
483     * Process the Meta configuration form.
484     *
485     * @param Request $request
486     * @param FormHandlerInterface $formHandler
487     * @param string $hookName
488     *
489     * @return FormInterface|RedirectResponse
490     */
491    protected function processForm(Request $request, FormHandlerInterface $formHandler, string $hookName)
492    {
493        $this->dispatchHook(
494            'actionAdminShopParametersMetaControllerPostProcess' . $hookName . 'Before',
495            ['controller' => $this]
496        );
497
498        $this->dispatchHook('actionAdminAdminShopParametersMetaControllerPostProcessBefore', ['controller' => $this]);
499
500        $form = $formHandler->getForm();
501        $form->handleRequest($request);
502
503        if ($form->isSubmitted() && $form->isValid()) {
504            $data = $form->getData();
505            $saveErrors = $formHandler->save($data);
506
507            if (0 === count($saveErrors)) {
508                $this->addFlash('success', $this->trans('Update successful', 'Admin.Notifications.Success'));
509
510                return $this->redirectToRoute('admin_metas_index');
511            } else {
512                $this->flashErrors($saveErrors);
513            }
514        }
515
516        return $form;
517    }
518
519    /**
520     * Gets form builder.
521     *
522     * @return FormBuilderInterface
523     */
524    private function getMetaFormBuilder()
525    {
526        return $this->get('prestashop.core.form.builder.meta_form_builder');
527    }
528
529    /**
530     * @return Handler\FormHandlerInterface
531     */
532    private function getMetaFormHandler()
533    {
534        return $this->get('prestashop.core.form.identifiable_object.meta_form_handler');
535    }
536
537    /**
538     * Handles exception by its type and status code or by its type only and returns error message.
539     *
540     * @param Exception $exception
541     *
542     * @return string
543     *
544     * @todo use FrameworkAdminBundleController::getErrorMessageForException() instead
545     */
546    private function handleException(Exception $exception)
547    {
548        if (0 !== $exception->getCode()) {
549            return $this->getExceptionByClassAndErrorCode($exception);
550        }
551
552        return $this->getExceptionByType($exception);
553    }
554
555    /**
556     * Gets exception by class and error code.
557     *
558     * @param Exception $exception
559     *
560     * @return string
561     */
562    private function getExceptionByClassAndErrorCode(Exception $exception)
563    {
564        $exceptionDictionary = [
565            MetaConstraintException::class => [
566                MetaConstraintException::INVALID_URL_REWRITE => $this->trans(
567                        'The %s field is not valid',
568                        'Admin.Notifications.Error',
569                        [
570                            sprintf(
571                                '"%s"',
572                                $this->trans('Rewritten URL', 'Admin.Shopparameters.Feature')
573                            ),
574                        ]
575                    ),
576                MetaConstraintException::INVALID_PAGE_NAME => $this->trans(
577                        'The %s field is required.',
578                        'Admin.Notifications.Error',
579                        [
580                            sprintf(
581                                '"%s"',
582                                $this->trans('Page name', 'Admin.Shopparameters.Feature')
583                            ),
584                        ]
585                    ),
586                MetaConstraintException::INVALID_PAGE_TITLE => $this->trans(
587                        'The %s field is not valid',
588                        'Admin.Notifications.Error',
589                        [
590                            sprintf(
591                                '"%s"',
592                                $this->trans('Page title', 'Admin.Shopparameters.Feature')
593                            ),
594                        ]
595                    ),
596                MetaConstraintException::INVALID_META_DESCRIPTION => $this->trans(
597                        'The %s field is not valid',
598                        'Admin.Notifications.Error',
599                        [
600                            sprintf(
601                                '"%s"',
602                                $this->trans('Meta description', 'Admin.Global')
603                            ),
604                        ]
605                    ),
606                MetaConstraintException::INVALID_META_KEYWORDS => $this->trans(
607                        'The %s field is not valid',
608                        'Admin.Notifications.Error',
609                        [
610                            sprintf(
611                                '"%s"',
612                                $this->trans('Meta keywords', 'Admin.Global')
613                            ),
614                        ]
615                    ),
616            ],
617        ];
618
619        $exceptionClass = get_class($exception);
620        $exceptionCode = $exception->getCode();
621        if (isset($exceptionDictionary[$exceptionClass][$exceptionCode])) {
622            return $exceptionDictionary[$exceptionClass][$exceptionCode];
623        }
624
625        return $this->getFallbackErrorMessage($exceptionClass, $exceptionCode);
626    }
627
628    /**
629     * Gets exception by class type.
630     *
631     * @param Exception $exception
632     *
633     * @return string
634     */
635    private function getExceptionByType(Exception $exception)
636    {
637        $exceptionDictionary = [
638            MetaNotFoundException::class => $this->trans(
639                'The object cannot be loaded (or found)',
640                'Admin.Notifications.Error'
641            ),
642        ];
643
644        $exceptionClass = get_class($exception);
645        if (isset($exceptionDictionary[$exceptionClass])) {
646            return $exceptionDictionary[$exceptionClass];
647        }
648
649        return $this->getFallbackErrorMessage($exceptionClass, $exception->getCode());
650    }
651
652    /**
653     * @return FormHandlerInterface
654     */
655    protected function getSetUpUrlsFormHandler(): FormHandlerInterface
656    {
657        return $this->get('prestashop.admin.meta_settings.set_up_urls.form_handler');
658    }
659
660    /**
661     * @return FormHandlerInterface
662     */
663    protected function getShopUrlsFormHandler(): FormHandlerInterface
664    {
665        return $this->get('prestashop.admin.meta_settings.shop_urls.form_handler');
666    }
667
668    /**
669     * @return FormHandlerInterface
670     */
671    protected function getUrlSchemaFormHandler(): FormHandlerInterface
672    {
673        return $this->get('prestashop.admin.meta_settings.url_schema.form_handler');
674    }
675
676    /**
677     * @return FormHandlerInterface
678     */
679    protected function getSeoOptionsFormHandler(): FormHandlerInterface
680    {
681        return $this->get('prestashop.admin.meta_settings.seo_options.form_handler');
682    }
683}
684