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

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