vendor/shopware/storefront/Framework/Routing/StorefrontSubscriber.php line 427

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Routing;
  3. use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
  4. use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
  5. use Shopware\Core\Checkout\Customer\Event\CustomerLogoutEvent;
  6. use Shopware\Core\Content\Seo\HreflangLoaderInterface;
  7. use Shopware\Core\Content\Seo\HreflangLoaderParameter;
  8. use Shopware\Core\Framework\App\ActiveAppsLoader;
  9. use Shopware\Core\Framework\App\Exception\AppUrlChangeDetectedException;
  10. use Shopware\Core\Framework\App\ShopId\ShopIdProvider;
  11. use Shopware\Core\Framework\Event\BeforeSendResponseEvent;
  12. use Shopware\Core\Framework\Feature;
  13. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  14. use Shopware\Core\Framework\Routing\Event\SalesChannelContextResolvedEvent;
  15. use Shopware\Core\Framework\Routing\KernelListenerPriorities;
  16. use Shopware\Core\Framework\Util\Random;
  17. use Shopware\Core\PlatformRequest;
  18. use Shopware\Core\SalesChannelRequest;
  19. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  20. use Shopware\Core\System\SystemConfig\SystemConfigService;
  21. use Shopware\Storefront\Event\StorefrontRenderEvent;
  22. use Shopware\Storefront\Framework\Csrf\CsrfPlaceholderHandler;
  23. use Shopware\Storefront\Framework\Routing\NotFound\NotFoundSubscriber;
  24. use Shopware\Storefront\Theme\StorefrontPluginRegistryInterface;
  25. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  26. use Symfony\Component\HttpFoundation\RedirectResponse;
  27. use Symfony\Component\HttpFoundation\RequestStack;
  28. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  29. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  30. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  31. use Symfony\Component\HttpKernel\Event\RequestEvent;
  32. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  33. use Symfony\Component\HttpKernel\KernelEvents;
  34. use Symfony\Component\Routing\RouterInterface;
  35. /**
  36.  * @deprecated tag:v6.5.0 - reason:becomes-internal - EventSubscribers will become internal in v6.5.0
  37.  */
  38. class StorefrontSubscriber implements EventSubscriberInterface
  39. {
  40.     private RequestStack $requestStack;
  41.     private RouterInterface $router;
  42.     private CsrfPlaceholderHandler $csrfPlaceholderHandler;
  43.     private MaintenanceModeResolver $maintenanceModeResolver;
  44.     private HreflangLoaderInterface $hreflangLoader;
  45.     private ShopIdProvider $shopIdProvider;
  46.     private ActiveAppsLoader $activeAppsLoader;
  47.     private SystemConfigService $systemConfigService;
  48.     private StorefrontPluginRegistryInterface $themeRegistry;
  49.     private NotFoundSubscriber $notFoundSubscriber;
  50.     /**
  51.      * @internal
  52.      */
  53.     public function __construct(
  54.         RequestStack $requestStack,
  55.         RouterInterface $router,
  56.         CsrfPlaceholderHandler $csrfPlaceholderHandler,
  57.         HreflangLoaderInterface $hreflangLoader,
  58.         MaintenanceModeResolver $maintenanceModeResolver,
  59.         ShopIdProvider $shopIdProvider,
  60.         ActiveAppsLoader $activeAppsLoader,
  61.         SystemConfigService $systemConfigService,
  62.         StorefrontPluginRegistryInterface $themeRegistry,
  63.         NotFoundSubscriber $notFoundSubscriber
  64.     ) {
  65.         $this->requestStack $requestStack;
  66.         $this->router $router;
  67.         $this->csrfPlaceholderHandler $csrfPlaceholderHandler;
  68.         $this->maintenanceModeResolver $maintenanceModeResolver;
  69.         $this->hreflangLoader $hreflangLoader;
  70.         $this->shopIdProvider $shopIdProvider;
  71.         $this->activeAppsLoader $activeAppsLoader;
  72.         $this->systemConfigService $systemConfigService;
  73.         $this->themeRegistry $themeRegistry;
  74.         $this->notFoundSubscriber $notFoundSubscriber;
  75.     }
  76.     public static function getSubscribedEvents(): array
  77.     {
  78.         if (Feature::isActive('v6.5.0.0')) {
  79.             return [
  80.                 KernelEvents::REQUEST => [
  81.                     ['startSession'40],
  82.                     ['maintenanceResolver'],
  83.                 ],
  84.                 KernelEvents::EXCEPTION => [
  85.                     ['customerNotLoggedInHandler'],
  86.                     ['maintenanceResolver'],
  87.                 ],
  88.                 KernelEvents::CONTROLLER => [
  89.                     ['preventPageLoadingFromXmlHttpRequest'KernelListenerPriorities::KERNEL_CONTROLLER_EVENT_SCOPE_VALIDATE],
  90.                 ],
  91.                 CustomerLoginEvent::class => [
  92.                     'updateSessionAfterLogin',
  93.                 ],
  94.                 CustomerLogoutEvent::class => [
  95.                     'updateSessionAfterLogout',
  96.                 ],
  97.                 BeforeSendResponseEvent::class => [
  98.                     ['setCanonicalUrl'],
  99.                 ],
  100.                 StorefrontRenderEvent::class => [
  101.                     ['addHreflang'],
  102.                     ['addShopIdParameter'],
  103.                     ['addIconSetConfig'],
  104.                 ],
  105.                 SalesChannelContextResolvedEvent::class => [
  106.                     ['replaceContextToken'],
  107.                 ],
  108.             ];
  109.         }
  110.         return [
  111.             KernelEvents::REQUEST => [
  112.                 ['startSession'40],
  113.                 ['maintenanceResolver'],
  114.             ],
  115.             KernelEvents::EXCEPTION => [
  116.                 ['showHtmlExceptionResponse', -100],
  117.                 ['customerNotLoggedInHandler'],
  118.                 ['maintenanceResolver'],
  119.             ],
  120.             KernelEvents::CONTROLLER => [
  121.                 ['preventPageLoadingFromXmlHttpRequest'KernelListenerPriorities::KERNEL_CONTROLLER_EVENT_SCOPE_VALIDATE],
  122.             ],
  123.             CustomerLoginEvent::class => [
  124.                 'updateSessionAfterLogin',
  125.             ],
  126.             CustomerLogoutEvent::class => [
  127.                 'updateSessionAfterLogout',
  128.             ],
  129.             BeforeSendResponseEvent::class => [
  130.                 ['replaceCsrfToken'],
  131.                 ['setCanonicalUrl'],
  132.             ],
  133.             StorefrontRenderEvent::class => [
  134.                 ['addHreflang'],
  135.                 ['addShopIdParameter'],
  136.                 ['addIconSetConfig'],
  137.             ],
  138.             SalesChannelContextResolvedEvent::class => [
  139.                 ['replaceContextToken'],
  140.             ],
  141.         ];
  142.     }
  143.     public function startSession(): void
  144.     {
  145.         $master $this->requestStack->getMainRequest();
  146.         if (!$master) {
  147.             return;
  148.         }
  149.         if (!$master->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)) {
  150.             return;
  151.         }
  152.         if (!$master->hasSession()) {
  153.             return;
  154.         }
  155.         $session $master->getSession();
  156.         if (!$session->isStarted()) {
  157.             $session->setName('session-');
  158.             $session->start();
  159.             $session->set('sessionId'$session->getId());
  160.         }
  161.         $salesChannelId $master->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
  162.         if ($salesChannelId === null) {
  163.             /** @var SalesChannelContext|null $salesChannelContext */
  164.             $salesChannelContext $master->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  165.             if ($salesChannelContext !== null) {
  166.                 $salesChannelId $salesChannelContext->getSalesChannel()->getId();
  167.             }
  168.         }
  169.         if ($this->shouldRenewToken($session$salesChannelId)) {
  170.             $token Random::getAlphanumericString(32);
  171.             $session->set(PlatformRequest::HEADER_CONTEXT_TOKEN$token);
  172.             $session->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID$salesChannelId);
  173.         }
  174.         $master->headers->set(
  175.             PlatformRequest::HEADER_CONTEXT_TOKEN,
  176.             $session->get(PlatformRequest::HEADER_CONTEXT_TOKEN)
  177.         );
  178.     }
  179.     public function updateSessionAfterLogin(CustomerLoginEvent $event): void
  180.     {
  181.         $token $event->getContextToken();
  182.         $this->updateSession($token);
  183.     }
  184.     public function updateSessionAfterLogout(): void
  185.     {
  186.         $newToken Random::getAlphanumericString(32);
  187.         $this->updateSession($newTokentrue);
  188.     }
  189.     public function updateSession(string $tokenbool $destroyOldSession false): void
  190.     {
  191.         $master $this->requestStack->getMainRequest();
  192.         if (!$master) {
  193.             return;
  194.         }
  195.         if (!$master->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)) {
  196.             return;
  197.         }
  198.         if (!$master->hasSession()) {
  199.             return;
  200.         }
  201.         $session $master->getSession();
  202.         $session->migrate($destroyOldSession);
  203.         $session->set('sessionId'$session->getId());
  204.         $session->set(PlatformRequest::HEADER_CONTEXT_TOKEN$token);
  205.         $master->headers->set(PlatformRequest::HEADER_CONTEXT_TOKEN$token);
  206.     }
  207.     /**
  208.      * @deprecated tag:v6.5.0 - reason:remove-subscriber - Use `NotFoundSubscriber::onError` instead
  209.      */
  210.     public function showHtmlExceptionResponse(ExceptionEvent $event): void
  211.     {
  212.         $this->notFoundSubscriber->onError($event);
  213.     }
  214.     public function customerNotLoggedInHandler(ExceptionEvent $event): void
  215.     {
  216.         if (!$event->getRequest()->attributes->has(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)) {
  217.             return;
  218.         }
  219.         if (!$event->getThrowable() instanceof CustomerNotLoggedInException) {
  220.             return;
  221.         }
  222.         $request $event->getRequest();
  223.         $parameters = [
  224.             'redirectTo' => $request->attributes->get('_route'),
  225.             'redirectParameters' => json_encode($request->attributes->get('_route_params')),
  226.         ];
  227.         $redirectResponse = new RedirectResponse($this->router->generate('frontend.account.login.page'$parameters));
  228.         $event->setResponse($redirectResponse);
  229.     }
  230.     public function maintenanceResolver(RequestEvent $event): void
  231.     {
  232.         if ($this->maintenanceModeResolver->shouldRedirect($event->getRequest())) {
  233.             $event->setResponse(
  234.                 new RedirectResponse(
  235.                     $this->router->generate('frontend.maintenance.page'),
  236.                     RedirectResponse::HTTP_TEMPORARY_REDIRECT
  237.                 )
  238.             );
  239.         }
  240.     }
  241.     public function preventPageLoadingFromXmlHttpRequest(ControllerEvent $event): void
  242.     {
  243.         if (!$event->getRequest()->isXmlHttpRequest()) {
  244.             return;
  245.         }
  246.         /** @var RouteScope|list<string> $scope */
  247.         $scope $event->getRequest()->attributes->get(PlatformRequest::ATTRIBUTE_ROUTE_SCOPE, []);
  248.         if ($scope instanceof RouteScope) {
  249.             $scope $scope->getScopes();
  250.         }
  251.         if (!\in_array(StorefrontRouteScope::ID$scopetrue)) {
  252.             return;
  253.         }
  254.         $controller $event->getController();
  255.         // happens if Controller is a closure
  256.         if (!\is_array($controller)) {
  257.             return;
  258.         }
  259.         $isAllowed $event->getRequest()->attributes->getBoolean('XmlHttpRequest'false);
  260.         if ($isAllowed) {
  261.             return;
  262.         }
  263.         throw new AccessDeniedHttpException('PageController can\'t be requested via XmlHttpRequest.');
  264.     }
  265.     // used to switch session token - when the context token expired
  266.     public function replaceContextToken(SalesChannelContextResolvedEvent $event): void
  267.     {
  268.         $context $event->getSalesChannelContext();
  269.         // only update session if token expired and switched
  270.         if ($event->getUsedToken() === $context->getToken()) {
  271.             return;
  272.         }
  273.         $this->updateSession($context->getToken());
  274.     }
  275.     public function setCanonicalUrl(BeforeSendResponseEvent $event): void
  276.     {
  277.         if (!$event->getResponse()->isSuccessful()) {
  278.             return;
  279.         }
  280.         if ($canonical $event->getRequest()->attributes->get(SalesChannelRequest::ATTRIBUTE_CANONICAL_LINK)) {
  281.             $canonical sprintf('<%s>; rel="canonical"'$canonical);
  282.             $event->getResponse()->headers->set('Link'$canonical);
  283.         }
  284.     }
  285.     /**
  286.      * @deprecated tag:v6.5.0 - replaceCsrfToken method will be removed as the csrf system will be removed in favor for the samesite approach
  287.      */
  288.     public function replaceCsrfToken(BeforeSendResponseEvent $event): void
  289.     {
  290.         if (Feature::isActive('v6.5.0.0')) {
  291.             return;
  292.         }
  293.         Feature::triggerDeprecationOrThrow(
  294.             'v6.5.0.0',
  295.             Feature::deprecatedMethodMessage(__CLASS____METHOD__'v6.5.0.0')
  296.         );
  297.         $event->setResponse(
  298.             $this->csrfPlaceholderHandler->replaceCsrfToken($event->getResponse(), $event->getRequest())
  299.         );
  300.     }
  301.     public function addHreflang(StorefrontRenderEvent $event): void
  302.     {
  303.         $request $event->getRequest();
  304.         $route $request->attributes->get('_route');
  305.         if ($route === null) {
  306.             return;
  307.         }
  308.         $routeParams $request->attributes->get('_route_params', []);
  309.         $salesChannelContext $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  310.         $parameter = new HreflangLoaderParameter($route$routeParams$salesChannelContext);
  311.         $event->setParameter('hrefLang'$this->hreflangLoader->load($parameter));
  312.     }
  313.     public function addShopIdParameter(StorefrontRenderEvent $event): void
  314.     {
  315.         if (!$this->activeAppsLoader->getActiveApps()) {
  316.             return;
  317.         }
  318.         try {
  319.             $shopId $this->shopIdProvider->getShopId();
  320.         } catch (AppUrlChangeDetectedException $e) {
  321.             return;
  322.         }
  323.         $event->setParameter('appShopId'$shopId);
  324.     }
  325.     public function addIconSetConfig(StorefrontRenderEvent $event): void
  326.     {
  327.         $request $event->getRequest();
  328.         // get name if theme is not inherited
  329.         $theme $request->attributes->get(SalesChannelRequest::ATTRIBUTE_THEME_NAME);
  330.         if (!$theme) {
  331.             // get theme name from base theme because for inherited themes the name is always null
  332.             $theme $request->attributes->get(SalesChannelRequest::ATTRIBUTE_THEME_BASE_NAME);
  333.         }
  334.         if (!$theme) {
  335.             return;
  336.         }
  337.         $themeConfig $this->themeRegistry->getConfigurations()->getByTechnicalName($theme);
  338.         if (!$themeConfig) {
  339.             return;
  340.         }
  341.         $iconConfig = [];
  342.         foreach ($themeConfig->getIconSets() as $pack => $path) {
  343.             $iconConfig[$pack] = [
  344.                 'path' => $path,
  345.                 'namespace' => $theme,
  346.             ];
  347.         }
  348.         $event->setParameter('themeIconConfig'$iconConfig);
  349.     }
  350.     private function shouldRenewToken(SessionInterface $session, ?string $salesChannelId null): bool
  351.     {
  352.         if (!$session->has(PlatformRequest::HEADER_CONTEXT_TOKEN) || $salesChannelId === null) {
  353.             return true;
  354.         }
  355.         if ($this->systemConfigService->get('core.systemWideLoginRegistration.isCustomerBoundToSalesChannel')) {
  356.             return $session->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID) !== $salesChannelId;
  357.         }
  358.         return false;
  359.     }
  360. }