vendor/shopware/platform/src/Storefront/Framework/Routing/StorefrontSubscriber.php line 353

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