diff --git a/app/config/admin/services.yml b/app/config/admin/services.yml index caad57d5e04e0..9b43c04b87c65 100644 --- a/app/config/admin/services.yml +++ b/app/config/admin/services.yml @@ -1,10 +1,15 @@ # Dedicated services for Admin app parameters: - # All admin context listeners should be executed before the router listener, this way even if no symfony route is found they context are initialized + # We set priorities lower than the RouterListener (32) so that the LegacyRouterChecker class is called first. + # The correct behaviour of Context Listeners on legacy routes depends on LegacyRouterChecker. + employee_listener_priority: 31 + language_listener_priority: 30 + default_listener_priority: 29 + # All admin context listeners should be executed before the router listener, this way even if no symfony route is found the contexts are initialized correctly # and can be used in admin legacy pages as well (router listener priority is 32) - employee_listener_priority: 35 - language_listener_priority: 34 - default_listener_priority: 33 + legacy_employee_listener_priority: 35 + legacy_language_listener_priority: 34 + legacy_default_listener_priority: 33 services: _defaults: @@ -75,26 +80,79 @@ services: # Employee context must have a higher priority because Shop and Language depend on it PrestaShopBundle\EventListener\Admin\Context\EmployeeContextListener: + arguments: + $isSymfonyLayout: true tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%employee_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\EmployeeContextListener.legacy: + class: PrestaShopBundle\EventListener\Admin\Context\EmployeeContextListener + arguments: + $isSymfonyLayout: false + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_employee_listener_priority%' } + + PrestaShopBundle\EventListener\Admin\Context\DefaultLanguageContextListener: + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_language_listener_priority%' } + # Language depends on Employee so its priority is lower, however it has higher priority than Currency and Country because they depend on Language PrestaShopBundle\EventListener\Admin\Context\LanguageContextListener: + arguments: + $isSymfonyLayout: true tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%language_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\LanguageContextListener.legacy: + class: PrestaShopBundle\EventListener\Admin\Context\LanguageContextListener + arguments: + $isSymfonyLayout: false + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_language_listener_priority%' } + + PrestaShopBundle\EventListener\Admin\Context\DefaultShopContextListener: + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_default_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\ShopContextListener: + arguments: + $isSymfonyLayout: true tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%default_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\ShopContextListener.legacy: + class: PrestaShopBundle\EventListener\Admin\Context\ShopContextListener + arguments: + $isSymfonyLayout: false + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_default_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\CurrencyContextListener: + arguments: + $isSymfonyLayout: true tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%default_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\CurrencyContextListener.legacy: + class: PrestaShopBundle\EventListener\Admin\Context\CurrencyContextListener + arguments: + $isSymfonyLayout: false + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_default_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\CountryContextListener: + arguments: + $isSymfonyLayout: true tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%default_listener_priority%' } + PrestaShopBundle\EventListener\Admin\Context\CountryContextListener.legacy: + class: PrestaShopBundle\EventListener\Admin\Context\CountryContextListener + arguments: + $isSymfonyLayout: false + tags: + - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: '%legacy_default_listener_priority%' } + # This listener must run after the router listener since it's based on the request attributes that are defined by the router PrestaShopBundle\EventListener\Admin\Context\LegacyControllerContextListener: tags: diff --git a/classes/controller/AdminController.php b/classes/controller/AdminController.php index 049da14c80a74..e7a873026b94a 100644 --- a/classes/controller/AdminController.php +++ b/classes/controller/AdminController.php @@ -4395,7 +4395,7 @@ protected function setAllowAnonymous($value) * * @return bool */ - protected function isAnonymousAllowed() + public function isAnonymousAllowed() { return $this->allowAnonymous; } diff --git a/src/Adapter/Security/Admin.php b/src/Adapter/Security/Admin.php index 074229adbbda0..d8f67ca267093 100644 --- a/src/Adapter/Security/Admin.php +++ b/src/Adapter/Security/Admin.php @@ -28,8 +28,8 @@ use Context; use PrestaShop\PrestaShop\Adapter\LegacyContext; +use PrestaShopBundle\Routing\LegacyControllerConstants; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -89,7 +89,8 @@ public function __construct( */ public function onKernelRequest(RequestEvent $event): void { - if ($this->security->getUser() !== null) { + $publicLegacyRoute = $event->getRequest()->attributes->get(LegacyControllerConstants::ANONYMOUS_ATTRIBUTE); + if ($this->security->getUser() !== null || $publicLegacyRoute) { return; } diff --git a/src/Core/Context/LegacyControllerContextBuilder.php b/src/Core/Context/LegacyControllerContextBuilder.php index 5e96c00d3d218..7a883b4b49764 100644 --- a/src/Core/Context/LegacyControllerContextBuilder.php +++ b/src/Core/Context/LegacyControllerContextBuilder.php @@ -88,6 +88,12 @@ public function build(): LegacyControllerContext public function buildLegacyContext(): void { + // In legacy pages the AdminController class already sets the context's controller, which is a more accurate + // candidate than our facade meant for backward compatibility, so we leave it untouched + if ($this->contextStateManager->getContext()->controller) { + return; + } + $this->assertArguments(); if (null === $this->_legacyControllerContext) { diff --git a/src/PrestaShopBundle/Controller/Admin/LegacyController.php b/src/PrestaShopBundle/Controller/Admin/LegacyController.php index 3a3e1804f1ec6..2f0c970f77a38 100644 --- a/src/PrestaShopBundle/Controller/Admin/LegacyController.php +++ b/src/PrestaShopBundle/Controller/Admin/LegacyController.php @@ -32,7 +32,7 @@ use PrestaShop\PrestaShop\Core\ConfigurationInterface; use PrestaShop\PrestaShop\Core\Exception\CoreException; use PrestaShopBundle\Entity\Repository\TabRepository; -use PrestaShopBundle\Routing\LegacyRouterChecker; +use PrestaShopBundle\Routing\LegacyControllerConstants; use PrestaShopBundle\Twig\Layout\MenuBuilder; use PrestaShopBundle\Twig\Layout\SmartyVariablesFiller; use SmartyException; @@ -84,11 +84,11 @@ public function legacyPageAction(Request $request): Response // These parameters have already been set as request attributes by LegacyRouterChecker $dispatcherHookParameters = [ 'controller_type' => Dispatcher::FC_ADMIN, - 'controller_class' => $request->attributes->get(LegacyRouterChecker::LEGACY_CONTROLLER_CLASS_ATTRIBUTE), - 'is_module' => $request->attributes->get(LegacyRouterChecker::LEGACY_CONTROLLER_IS_MODULE_ATTRIBUTE), + 'controller_class' => $request->attributes->get(LegacyControllerConstants::CLASS_ATTRIBUTE), + 'is_module' => $request->attributes->get(LegacyControllerConstants::IS_MODULE_ATTRIBUTE), ]; - $adminController = $this->initController($dispatcherHookParameters); + $adminController = $this->initController($request, $dispatcherHookParameters); // Redirect if necessary after post process if (!empty($adminController->getRedirectAfter())) { // After each request the cookie must be written to save its modified state during AdminController workflow @@ -196,30 +196,8 @@ protected function renderAjaxController(AdminController $adminController, string } elseif (method_exists($adminController, 'displayAjax')) { $adminController->displayAjax(); } - $outputContent = ob_get_clean(); - // The output of the controller is either directly echoed or it can be properly appended in AdminController::content - // it depends on the implementation of the controllers. - if (!empty($outputContent) && !empty($adminController->content)) { - // Sometimes they are both done and are equal, in which case we only return one content to avoid duplicates. - if ($outputContent === $adminController->content) { - $responseContent = $outputContent; - } else { - // In case both contents are present and are different we concatenate them, first the echoed output and then the controller - // one since it is displayed last by smarty in the original workflow - // This concatenation approach is theoretical and naive and was not tested because of the lack of use cases, so it may need - // to be improved in the future - $responseContent = $outputContent . $adminController->content; - } - } elseif (!empty($outputContent)) { - $responseContent = $outputContent; - } elseif (!empty($adminController->content)) { - $responseContent = $adminController->content; - } else { - $responseContent = ''; - } - - return new Response($responseContent); + return new Response(ob_get_clean()); } /** @@ -228,17 +206,16 @@ protected function renderAjaxController(AdminController $adminController, string * * Note: some legacy controllers may already use die at this point (to echo content and finish the process) when postProcess is called. * + * @param Request $request * @param array $dispatcherHookParameters * * @return AdminController */ - protected function initController(array $dispatcherHookParameters): AdminController + protected function initController(Request $request, array $dispatcherHookParameters): AdminController { - $controllerClass = $dispatcherHookParameters['controller_class']; - - // Loading controller + // Retrieving the controller instantiated in LegacyRouterChecker /** @var AdminController $adminController */ - $adminController = new $controllerClass(); + $adminController = $request->attributes->get(LegacyControllerConstants::INSTANCE_ATTRIBUTE); // Fill default smarty variables as they can be used in partial templates rendered in init methods $this->assignSmartyVariables->fillDefault(); @@ -248,7 +225,6 @@ protected function initController(array $dispatcherHookParameters): AdminControl // This part comes from AdminController::run method, it has been stripped from permission checks since the permission is already // handled by this Symfony controller - $adminController->init(); $adminController->setMedia(false); $adminController->postProcess(); diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/CountryContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/CountryContextListener.php index e061f5d517fce..0f778872064a6 100644 --- a/src/PrestaShopBundle/EventListener/Admin/Context/CountryContextListener.php +++ b/src/PrestaShopBundle/EventListener/Admin/Context/CountryContextListener.php @@ -30,6 +30,8 @@ use PrestaShop\PrestaShop\Core\ConfigurationInterface; use PrestaShop\PrestaShop\Core\Context\CountryContextBuilder; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; /** @@ -39,7 +41,9 @@ class CountryContextListener { public function __construct( private readonly CountryContextBuilder $countryContextBuilder, - private readonly ConfigurationInterface $configuration + private readonly ConfigurationInterface $configuration, + private readonly FeatureFlagStateCheckerInterface $featureFlagStateChecker, + private readonly bool $isSymfonyLayout, ) { } @@ -49,6 +53,10 @@ public function onKernelRequest(RequestEvent $event): void return; } + if ($this->isSymfonyLayout !== $this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_SYMFONY_LAYOUT)) { + return; + } + $this->countryContextBuilder->setCountryId((int) $this->configuration->get('PS_COUNTRY_DEFAULT')); } } diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListener.php index 41e5f53d24522..916fa46f8394b 100644 --- a/src/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListener.php +++ b/src/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListener.php @@ -30,6 +30,8 @@ use PrestaShop\PrestaShop\Core\ConfigurationInterface; use PrestaShop\PrestaShop\Core\Context\CurrencyContextBuilder; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; /** @@ -40,6 +42,8 @@ class CurrencyContextListener public function __construct( private readonly CurrencyContextBuilder $currencyContextBuilder, private readonly ConfigurationInterface $configuration, + private readonly FeatureFlagStateCheckerInterface $featureFlagStateChecker, + private readonly bool $isSymfonyLayout, ) { } @@ -49,6 +53,10 @@ public function onKernelRequest(RequestEvent $event): void return; } + if ($this->isSymfonyLayout !== $this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_SYMFONY_LAYOUT)) { + return; + } + $this->currencyContextBuilder->setCurrencyId((int) $this->configuration->get('PS_CURRENCY_DEFAULT')); } } diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/DefaultLanguageContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/DefaultLanguageContextListener.php new file mode 100644 index 0000000000000..3371e2a9a14eb --- /dev/null +++ b/src/PrestaShopBundle/EventListener/Admin/Context/DefaultLanguageContextListener.php @@ -0,0 +1,58 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PrestaShopBundle\EventListener\Admin\Context; + +use PrestaShop\PrestaShop\Core\ConfigurationInterface; +use PrestaShop\PrestaShop\Core\Context\LanguageContextBuilder; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * Listener dedicated to set up default Language context for the Back-Office/Admin application. + * We need to initialize the LanguageContext earlier because it's used by components in the Symfony + * layout that will be displayed in the Not Found legacy page + */ +class DefaultLanguageContextListener +{ + public function __construct( + private readonly LanguageContextBuilder $languageContextBuilder, + private readonly ConfigurationInterface $configuration, + ) { + } + + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + + $defaultLanguageId = (int) $this->configuration->get('PS_LANG_DEFAULT'); + $this->languageContextBuilder->setDefaultLanguageId($defaultLanguageId); + $this->languageContextBuilder->setLanguageId($defaultLanguageId); + } +} diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/DefaultShopContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/DefaultShopContextListener.php new file mode 100644 index 0000000000000..dc8edba59dfed --- /dev/null +++ b/src/PrestaShopBundle/EventListener/Admin/Context/DefaultShopContextListener.php @@ -0,0 +1,62 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) + */ + +declare(strict_types=1); + +namespace PrestaShopBundle\EventListener\Admin\Context; + +use PrestaShop\PrestaShop\Core\Context\ShopContextBuilder; +use PrestaShop\PrestaShop\Core\Domain\Configuration\ShopConfigurationInterface; +use PrestaShop\PrestaShop\Core\Domain\Shop\Exception\ShopException; +use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint; +use Symfony\Component\HttpKernel\Event\RequestEvent; + +/** + * Listener dedicated to set up default Shop context for the Back-Office/Admin application. + * We need to initialize the ShopContext earlier because it's used by components in the Symfony + * layout that will be displayed in the Not Found legacy page + */ +class DefaultShopContextListener +{ + public function __construct( + private readonly ShopContextBuilder $shopContextBuilder, + private readonly ShopConfigurationInterface $configuration, + ) { + } + + /** + * @throws ShopException + */ + public function onKernelRequest(RequestEvent $event): void + { + if (!$event->isMainRequest()) { + return; + } + $shopConstraint = ShopConstraint::shop((int) $this->configuration->get('PS_SHOP_DEFAULT', null, ShopConstraint::allShops())); + $this->shopContextBuilder->setShopId($shopConstraint->getShopId()->getValue()); + $this->shopContextBuilder->setShopConstraint($shopConstraint); + } +} diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListener.php index 6387bcdcc55a1..c17c94ba9162d 100644 --- a/src/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListener.php +++ b/src/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListener.php @@ -30,6 +30,8 @@ use PrestaShop\PrestaShop\Adapter\LegacyContext; use PrestaShop\PrestaShop\Core\Context\EmployeeContextBuilder; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface; use PrestaShopBundle\Security\Admin\Employee; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -43,6 +45,8 @@ public function __construct( private readonly EmployeeContextBuilder $employeeContextBuilder, private readonly LegacyContext $legacyContext, private readonly Security $security, + private readonly FeatureFlagStateCheckerInterface $featureFlagStateChecker, + private readonly bool $isSymfonyLayout, ) { } @@ -52,6 +56,10 @@ public function onKernelRequest(RequestEvent $event): void return; } + if ($this->isSymfonyLayout !== $this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_SYMFONY_LAYOUT)) { + return; + } + if (!empty($this->legacyContext->getContext()->cookie->id_employee)) { $this->employeeContextBuilder->setEmployeeId((int) $this->legacyContext->getContext()->cookie->id_employee); } elseif ($this->security->getUser() instanceof Employee) { diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListener.php index 1fa4f0e648b8c..41fd9aec7bc9c 100644 --- a/src/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListener.php +++ b/src/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListener.php @@ -28,9 +28,10 @@ namespace PrestaShopBundle\EventListener\Admin\Context; -use PrestaShop\PrestaShop\Core\ConfigurationInterface; use PrestaShop\PrestaShop\Core\Context\EmployeeContext; use PrestaShop\PrestaShop\Core\Context\LanguageContextBuilder; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; /** @@ -41,7 +42,8 @@ class LanguageContextListener public function __construct( private readonly LanguageContextBuilder $languageContextBuilder, private readonly EmployeeContext $employeeContext, - private readonly ConfigurationInterface $configuration, + private readonly FeatureFlagStateCheckerInterface $featureFlagStateChecker, + private readonly bool $isSymfonyLayout, ) { } @@ -51,15 +53,12 @@ public function onKernelRequest(RequestEvent $event): void return; } - $defaultLanguageId = (int) $this->configuration->get('PS_LANG_DEFAULT'); - $this->languageContextBuilder->setDefaultLanguageId($defaultLanguageId); - + if ($this->isSymfonyLayout !== $this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_SYMFONY_LAYOUT)) { + return; + } if ($this->employeeContext->getEmployee()) { // Use the employee language if available $this->languageContextBuilder->setLanguageId($this->employeeContext->getEmployee()->getLanguageId()); - } else { - // If not use the default language of the shop - $this->languageContextBuilder->setLanguageId($defaultLanguageId); } } } diff --git a/src/PrestaShopBundle/EventListener/Admin/Context/ShopContextListener.php b/src/PrestaShopBundle/EventListener/Admin/Context/ShopContextListener.php index 9d28c7b360405..9c575a222ed42 100644 --- a/src/PrestaShopBundle/EventListener/Admin/Context/ShopContextListener.php +++ b/src/PrestaShopBundle/EventListener/Admin/Context/ShopContextListener.php @@ -35,8 +35,11 @@ use PrestaShop\PrestaShop\Core\Domain\Configuration\ShopConfigurationInterface; use PrestaShop\PrestaShop\Core\Domain\Shop\Exception\ShopException; use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopConstraint; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagSettings; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface; use PrestaShop\PrestaShop\Core\Util\Url\UrlCleaner; use PrestaShopBundle\Controller\Attribute\AllShopContext; +use PrestaShopBundle\Routing\LegacyControllerConstants; use ReflectionClass; use ReflectionException; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -57,6 +60,8 @@ public function __construct( private readonly LegacyContext $legacyContext, private readonly MultistoreFeature $multistoreFeature, private readonly RouterInterface $router, + private readonly FeatureFlagStateCheckerInterface $featureFlagStateChecker, + private readonly bool $isSymfonyLayout, ) { } @@ -69,6 +74,11 @@ public function onKernelRequest(RequestEvent $event): void if (!$event->isMainRequest()) { return; } + + if ($this->isSymfonyLayout !== $this->featureFlagStateChecker->isEnabled(FeatureFlagSettings::FEATURE_FLAG_SYMFONY_LAYOUT)) { + return; + } + $psSslEnabled = (bool) $this->configuration->get('PS_SSL_ENABLED', null, ShopConstraint::allShops()); $this->shopContextBuilder->setSecureMode($psSslEnabled && $event->getRequest()->isSecure()); @@ -116,6 +126,13 @@ private function getMultiShopConstraint(Request $request): ShopConstraint } $shopConstraint = ShopConstraint::allShops(); + // Check if the displayed legacy controller forces All shops mode (check already performed by LegacyRouterChecker) + $isAllShopContext = $request->attributes->get(LegacyControllerConstants::IS_ALL_SHOP_CONTEXT_ATTRIBUTE); + + if ($isAllShopContext) { + return $shopConstraint; + } + $cookieShopConstraint = $this->getShopConstraintFromCookie(); if ($cookieShopConstraint) { if ($cookieShopConstraint->getShopGroupId()) { diff --git a/src/PrestaShopBundle/EventListener/Admin/TokenizedUrlsListener.php b/src/PrestaShopBundle/EventListener/Admin/TokenizedUrlsListener.php index b82b54bbd39db..7ac8ac2cd9d23 100644 --- a/src/PrestaShopBundle/EventListener/Admin/TokenizedUrlsListener.php +++ b/src/PrestaShopBundle/EventListener/Admin/TokenizedUrlsListener.php @@ -28,6 +28,7 @@ use PrestaShop\PrestaShop\Core\Feature\TokenInUrls; use PrestaShop\PrestaShop\Core\Util\Url\UrlCleaner; +use PrestaShopBundle\Routing\LegacyControllerConstants; use PrestaShopBundle\Security\Admin\UserTokenManager; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\KernelEvent; @@ -50,8 +51,9 @@ public function __construct( public function onKernelRequest(KernelEvent $event) { $request = $event->getRequest(); + $publicLegacyRoute = $event->getRequest()->attributes->get(LegacyControllerConstants::ANONYMOUS_ATTRIBUTE); - if (TokenInUrls::isDisabled()) { + if (TokenInUrls::isDisabled() || $publicLegacyRoute) { return; } diff --git a/src/PrestaShopBundle/Routing/LegacyControllerConstants.php b/src/PrestaShopBundle/Routing/LegacyControllerConstants.php new file mode 100644 index 0000000000000..ea26287f57e08 --- /dev/null +++ b/src/PrestaShopBundle/Routing/LegacyControllerConstants.php @@ -0,0 +1,14 @@ +attributes->set(self::LEGACY_CONTROLLER_CLASS_ATTRIBUTE, $controllerClass); - $request->attributes->set(self::LEGACY_CONTROLLER_IS_MODULE_ATTRIBUTE, $isModule); + // We load the controller early in the process (during router matching actually), because the controller + // configuration has many impacts on the contexts, the security listeners, ... And the relevant data can + // only be retrieved once the legacy class is instantiated to access its public configuration + // But for performance issues we only instantiate (and init) the controller here once and then store it (along + // with other related attributes) in the request attributes so they can be retrieved easily by the code depending on them + $adminController = new $controllerClass(); + $adminController->init(); + + $request->attributes->set(LegacyControllerConstants::INSTANCE_ATTRIBUTE, $adminController); + $request->attributes->set(LegacyControllerConstants::ANONYMOUS_ATTRIBUTE, $adminController->isAnonymousAllowed()); + $request->attributes->set(LegacyControllerConstants::IS_ALL_SHOP_CONTEXT_ATTRIBUTE, $adminController->multishop_context === ShopConstraint::ALL_SHOPS); + $request->attributes->set(LegacyControllerConstants::CLASS_ATTRIBUTE, $controllerClass); + $request->attributes->set(LegacyControllerConstants::IS_MODULE_ATTRIBUTE, $isModule); return true; } diff --git a/tests/UI/pages/BO/BObasePage.ts b/tests/UI/pages/BO/BObasePage.ts index 55e0a22e91ce3..7babb4dfc9b74 100644 --- a/tests/UI/pages/BO/BObasePage.ts +++ b/tests/UI/pages/BO/BObasePage.ts @@ -256,9 +256,13 @@ export default class BOBasePage extends CommonPage { private readonly helpDocumentURL: string; - private readonly invalidTokenContinueLink: string; + private readonly invalidTokenContinueLinkLegacy: string; - private readonly invalidTokenCancelLink: string; + private readonly invalidTokenCancelLinkLegacy: string; + + private readonly invalidTokenContinueLinkSymfony: string; + + private readonly invalidTokenCancelLinkSymfony: string; public readonly debugModeToolbar: string; @@ -613,8 +617,10 @@ export default class BOBasePage extends CommonPage { this.helpDocumentURL = `${this.rightSidebar} div.quicknav-scroller._fullspace object`; // Invalid token block - this.invalidTokenContinueLink = 'a.btn-continue'; - this.invalidTokenCancelLink = 'a.btn-cancel'; + this.invalidTokenContinueLinkLegacy = 'a.btn-continue'; + this.invalidTokenCancelLinkLegacy = 'a.btn-cancel'; + this.invalidTokenContinueLinkSymfony = '#security-compromised-page #csrf-white-container div a:nth-child(1)'; + this.invalidTokenCancelLinkSymfony = '#security-compromised-page #csrf-white-container div a:nth-child(2)'; } /* @@ -1161,10 +1167,18 @@ export default class BOBasePage extends CommonPage { */ async navigateToPageWithInvalidToken(page: Page, url: string, continueToPage: boolean = true): Promise { await this.goTo(page, url); - if (await this.elementVisible(page, this.invalidTokenContinueLink, 10000)) { + // Legacy Layout + if (await this.elementVisible(page, this.invalidTokenContinueLinkLegacy, 10000)) { + await this.clickAndWaitForURL( + page, + continueToPage ? this.invalidTokenContinueLinkLegacy : this.invalidTokenCancelLinkLegacy, + ); + } + // Symfony Layout + if (await this.elementVisible(page, this.invalidTokenContinueLinkSymfony, 10000)) { await this.clickAndWaitForURL( page, - continueToPage ? this.invalidTokenContinueLink : this.invalidTokenCancelLink, + continueToPage ? this.invalidTokenContinueLinkSymfony : this.invalidTokenCancelLinkSymfony, ); } } diff --git a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CountryContextListenerTest.php b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CountryContextListenerTest.php index 4d38cc54b66d0..9ca90cf155600 100644 --- a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CountryContextListenerTest.php +++ b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CountryContextListenerTest.php @@ -48,6 +48,8 @@ public function testKernelRequest(): void $listener = new CountryContextListener( $countryContextBuilder, $this->mockConfiguration(['PS_COUNTRY_DEFAULT' => 42]), + $this->mockFeatureFlagStateChecker(), + true, ); $event = $this->createRequestEvent(new Request()); diff --git a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListenerTest.php b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListenerTest.php index d4cb44eff8269..0a93688d0c88f 100644 --- a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListenerTest.php +++ b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/CurrencyContextListenerTest.php @@ -48,6 +48,8 @@ public function testKernelRequest(): void $listener = new CurrencyContextListener( $currencyContextBuilder, $this->mockConfiguration(['PS_CURRENCY_DEFAULT' => 42]), + $this->mockFeatureFlagStateChecker(), + true, ); $event = $this->createRequestEvent(new Request()); diff --git a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListenerTest.php b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListenerTest.php index cabfc0761c9ed..e82f9dc50b3cc 100644 --- a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListenerTest.php +++ b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/EmployeeContextListenerTest.php @@ -46,7 +46,9 @@ public function testFindEmployee(): void $listener = new EmployeeContextListener( $employeeBuilder, $this->mockLegacyContext(['id_employee' => 42]), - $this->createMock(Security::class) + $this->createMock(Security::class), + $this->mockFeatureFlagStateChecker(), + true, ); $event = $this->createRequestEvent(new Request()); @@ -63,7 +65,9 @@ public function testEmployeeNotFound(): void $listener = new EmployeeContextListener( $employeeBuilder, $this->mockLegacyContext(['id_employee' => null]), - $this->createMock(Security::class) + $this->createMock(Security::class), + $this->mockFeatureFlagStateChecker(), + true, ); $listener->onKernelRequest($event); $this->assertEquals(null, $this->getPrivateField($employeeBuilder, 'employeeId')); @@ -81,7 +85,9 @@ public function testEmployeeFromSymfonySecurity(): void $listener = new EmployeeContextListener( $employeeBuilder, $this->mockLegacyContext(['id_employee' => null]), - $securityMock + $securityMock, + $this->mockFeatureFlagStateChecker(), + true, ); $event = $this->createRequestEvent(new Request()); diff --git a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListenerTest.php b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListenerTest.php index ccaef27af1201..b94c80a031c7e 100644 --- a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListenerTest.php +++ b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/LanguageContextListenerTest.php @@ -36,6 +36,7 @@ use PrestaShop\PrestaShop\Core\Context\LanguageContextBuilder; use PrestaShop\PrestaShop\Core\Language\LanguageRepositoryInterface; use PrestaShop\PrestaShop\Core\Localization\Locale\RepositoryInterface; +use PrestaShopBundle\EventListener\Admin\Context\DefaultLanguageContextListener; use PrestaShopBundle\EventListener\Admin\Context\LanguageContextListener; use Symfony\Component\HttpFoundation\Request; use Tests\Unit\PrestaShopBundle\EventListener\ContextEventListenerTestCase; @@ -56,7 +57,8 @@ public function testContextEmployeeLanguage(): void $listener = new LanguageContextListener( $languageContextBuilder, $this->mockEmployeeContext(self::EMPLOYEE_CONTEXT_LANGUAGE_ID), - $this->mockConfiguration(), + $this->mockFeatureFlagStateChecker(), + true, ); $event = $this->createRequestEvent(new Request()); @@ -72,9 +74,8 @@ public function testDefaultConfigurationLanguage(): void $this->createMock(ContextStateManager::class), $this->createMock(ObjectModelLanguageRepository::class) ); - $listener = new LanguageContextListener( + $listener = new DefaultLanguageContextListener( $languageContextBuilder, - $this->mockEmployeeContext(null), $this->mockConfiguration(['PS_LANG_DEFAULT' => self::DEFAULT_CONFIGURATION_LANGUAGE_ID]), ); diff --git a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/ShopContextListenerTest.php b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/ShopContextListenerTest.php index cf841b07cae7f..9643db9451929 100644 --- a/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/ShopContextListenerTest.php +++ b/tests/Unit/PrestaShopBundle/EventListener/Admin/Context/ShopContextListenerTest.php @@ -61,7 +61,9 @@ public function testSingleShop(): void $this->mockConfiguration(['PS_SHOP_DEFAULT' => self::DEFAULT_SHOP_ID, 'PS_SSL_ENABLED' => self::PS_SSL_ENABLED]), $this->mockLegacyContext(['shopContext' => '']), $this->mockMultistoreFeature(false), - $this->mockRouter() + $this->mockRouter(), + $this->mockFeatureFlagStateChecker(), + true, ); $listener->onKernelRequest($event); @@ -94,7 +96,9 @@ public function testMultiShop(string $cookieValue, ?array $employeeData, ShopCon $this->mockConfiguration(['PS_SHOP_DEFAULT' => self::DEFAULT_SHOP_ID, 'PS_SSL_ENABLED' => self::PS_SSL_ENABLED]), $this->mockLegacyContext(['shopContext' => $cookieValue]), $this->mockMultistoreFeature(true), - $this->mockRouter() + $this->mockRouter(), + $this->mockFeatureFlagStateChecker(), + true, ); $listener->onKernelRequest($event); @@ -169,7 +173,9 @@ public function testMultiShopRedirection(string $switchParameterValue, ?string $ $this->mockConfiguration(['PS_SHOP_DEFAULT' => self::DEFAULT_SHOP_ID, 'PS_SSL_ENABLED' => self::PS_SSL_ENABLED]), $mockContext, $this->mockMultistoreFeature(true), - $this->mockRouter() + $this->mockRouter(), + $this->mockFeatureFlagStateChecker(), + true, ); // Check that initially the cookie has a null value diff --git a/tests/Unit/PrestaShopBundle/EventListener/ContextEventListenerTestCase.php b/tests/Unit/PrestaShopBundle/EventListener/ContextEventListenerTestCase.php index da48809efbc5f..3f1b80435dae7 100644 --- a/tests/Unit/PrestaShopBundle/EventListener/ContextEventListenerTestCase.php +++ b/tests/Unit/PrestaShopBundle/EventListener/ContextEventListenerTestCase.php @@ -34,6 +34,7 @@ use PrestaShop\PrestaShop\Adapter\LegacyContext; use PrestaShop\PrestaShop\Adapter\Shop\Repository\ShopRepository; use PrestaShop\PrestaShop\Core\Domain\Shop\ValueObject\ShopId; +use PrestaShop\PrestaShop\Core\FeatureFlag\FeatureFlagStateCheckerInterface; use ReflectionProperty; use Shop; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -82,6 +83,17 @@ protected function mockLegacyContext(array $cookieValues): LegacyContext|MockObj return $legacyContext; } + protected function mockFeatureFlagStateChecker(): FeatureFlagStateCheckerInterface|MockObject + { + $featureFlagStateChecker = $this->createMock(FeatureFlagStateCheckerInterface::class); + $featureFlagStateChecker + ->method('isEnabled') + ->willReturn(true) + ; + + return $featureFlagStateChecker; + } + protected function createRequestEvent(Request $request): RequestEvent { return new RequestEvent(static::createKernel(), $request, HttpKernelInterface::MAIN_REQUEST);