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

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