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

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