custom/plugins/MaxiaTaxSwitch6/src/Storefront/Subscriber/StorefrontSubscriber.php line 267

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Maxia\MaxiaTaxSwitch6\Storefront\Subscriber;
  3. use Maxia\MaxiaTaxSwitch6\Storefront\Events\GenerateCacheHashEvent;
  4. use Maxia\MaxiaTaxSwitch6\Storefront\Service\TaxSwitchService;
  5. use Shopware\Core\Checkout\Cart\SalesChannel\CartService;
  6. use Shopware\Core\Framework\Event\BeforeSendResponseEvent;
  7. use Shopware\Core\Framework\Struct\ArrayEntity;
  8. use Shopware\Core\Framework\Util\Random;
  9. use Shopware\Core\PlatformRequest;
  10. use Shopware\Core\SalesChannelRequest;
  11. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
  12. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
  13. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  14. use Shopware\Storefront\Event\StorefrontRenderEvent;
  15. use Shopware\Storefront\Framework\Cache\CacheResponseSubscriber;
  16. use Shopware\Storefront\Framework\Cache\Event\HttpCacheGenerateKeyEvent;
  17. use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
  18. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  19. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  20. use Symfony\Component\HttpFoundation\Cookie;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\HttpFoundation\RequestStack;
  23. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  24. use Symfony\Component\HttpKernel\KernelEvents;
  25. /**
  26.  * @package Maxia\MaxiaAdvBlockPrices6\Subscriber
  27.  */
  28. class StorefrontSubscriber implements EventSubscriberInterface
  29. {
  30.     public static function getSubscribedEvents(): array
  31.     {
  32.         return [
  33.             StorefrontRenderEvent::class => 'addContextExtension',
  34.             KernelEvents::RESPONSE => [
  35.                 ['updateContextHash', -2005],
  36.                 ['updateCookie', -1000]
  37.             ],
  38.             BeforeSendResponseEvent::class => [
  39.                 ['removeDuplicateSession', -100],
  40.             ],
  41.             HttpCacheGenerateKeyEvent::class => 'updateHttpCacheKey'
  42.         ];
  43.     }
  44.     /** @var RequestStack */
  45.     private $requestStack;
  46.     /** @var SalesChannelContextServiceInterface */
  47.     private $contextService;
  48.     /** @var TagAwareAdapterInterface */
  49.     private $cache;
  50.     /** @var TaxSwitchService */
  51.     private $taxSwitchService;
  52.     /**
  53.      * @param RequestStack $requestStack
  54.      * @param CartService $cartService
  55.      * @param TaxSwitchService $taxSwitchService
  56.      */
  57.     public function __construct(
  58.         RequestStack $requestStack,
  59.         EventDispatcherInterface $eventDispatcher,
  60.         SalesChannelContextServiceInterface $contextService,
  61.         TagAwareAdapterInterface $cache,
  62.         TaxSwitchService $taxSwitchService
  63.     ) {
  64.         $this->requestStack $requestStack;
  65.         $this->eventDispatcher $eventDispatcher;
  66.         $this->contextService $contextService;
  67.         $this->cache $cache;
  68.         $this->taxSwitchService $taxSwitchService;
  69.     }
  70.     /**
  71.      * @param StorefrontRenderEvent $event
  72.      */
  73.     public function addContextExtension(StorefrontRenderEvent $event)
  74.     {
  75.         $context $event->getSalesChannelContext();
  76.         $entity = new ArrayEntity($this->taxSwitchService->getContext($context));
  77.         $context->addExtension('maxiaTaxSwitch'$entity);
  78.     }
  79.     /**
  80.      * Add isNet parameter to the HTTP cache key.
  81.      *
  82.      * @param HttpCacheGenerateKeyEvent $event
  83.      * @return void
  84.      */
  85.     public function updateHttpCacheKey(HttpCacheGenerateKeyEvent $event)
  86.     {
  87.         $request $event->getRequest();
  88.         $config $this->getCachedPluginConfig($request);
  89.         if (!$config || !$config['pluginEnabled']) {
  90.             return;
  91.         }
  92.         $isNet $config['preselection'] === 'net';
  93.         $overrideSetting false;
  94.         if ($request->query->has($config['urlParameterName']) && $config['urlParameterActive']) {
  95.             $isNet = (bool)$request->query->get($config['urlParameterName'], $isNet);
  96.             $overrideSetting true;
  97.         } else if ($request->cookies->has(TaxSwitchService::COOKIE_NAME)) {
  98.             $isNet = (bool)$request->cookies->get(TaxSwitchService::COOKIE_NAME$isNet);
  99.         }
  100.         if (!$request->cookies->has(CacheResponseSubscriber::CONTEXT_CACHE_COOKIE) || $overrideSetting) {
  101.             $value $isNet $config['netContextHash'] : $config['grossContextHash'];
  102.             $request->cookies->set(CacheResponseSubscriber::CONTEXT_CACHE_COOKIE$value);
  103.         }
  104.     }
  105.     /**
  106.      * Loads some basic plugin settings relevant for generating the HTTP cache key.
  107.      *
  108.      * @param Request $request
  109.      * @return array|null
  110.      */
  111.     protected function getCachedPluginConfig(Request $request): ?array
  112.     {
  113.         $salesChannelId $request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
  114.         $language $request->headers->get(PlatformRequest::HEADER_LANGUAGE_ID);
  115.         $currencyId $request->attributes->get(SalesChannelRequest::ATTRIBUTE_DOMAIN_CURRENCY_ID);
  116.         if (!$salesChannelId) {
  117.             return null;
  118.         }
  119.         $cacheKey 'maxia-tax-switch-config-' implode('-', [
  120.             $salesChannelId$language$currencyId
  121.         ]);
  122.         $cacheItem $this->cache->getItem($cacheKey);
  123.         if (!$cacheItem->isHit()) {
  124.             $contextToken Random::getAlphanumericString(32);
  125.             $context $this->contextService
  126.                 ->get(new SalesChannelContextServiceParameters($salesChannelId$contextToken$language$currencyId));
  127.             $config $this->taxSwitchService->getConfig($context);
  128.             $result = [
  129.                 'pluginEnabled' => $config['pluginEnabled'],
  130.                 'preselection' => $config['preselection'],
  131.                 'urlParameterActive' => $config['urlParameterActive'],
  132.                 'urlParameterName' => $config['urlParameterName'],
  133.                 'cookieRequired' => $config['cookieRequired'],
  134.                 'netContextHash' => $this->buildCacheHash($contexttrue),
  135.                 'grossContextHash' => $this->buildCacheHash($contextfalse)
  136.             ];
  137.             $cacheItem->set(json_encode($result));
  138.             $this->cache->save($cacheItem);
  139.         } else {
  140.             $result json_decode($cacheItem->get(), true);
  141.         }
  142.         return $result;
  143.     }
  144.     /**
  145.      * Updates the sw-cache-hash cookie, taking the tax switch setting into account.
  146.      *
  147.      * @param ResponseEvent $event
  148.      */
  149.     public function updateContextHash(ResponseEvent $event): void
  150.     {
  151.         $master $this->getMainRequest();
  152.         $response $event->getResponse();
  153.         if (!$master ||
  154.             !$master->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST) ||
  155.             !$master->attributes->get(TaxSwitchService::TAX_SWITCH_ACTIVE_ATTR)) {
  156.             return;
  157.         }
  158.         /** @var SalesChannelContext $context */
  159.         $context $master->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  160.         if (!$context) {
  161.             return;
  162.         }
  163.         $isNet $this->taxSwitchService->getDisplayNet($context);
  164.         if ($isNet === null) {
  165.             return;
  166.         }
  167.         $cookieName CacheResponseSubscriber::CONTEXT_CACHE_COOKIE;
  168.         $currentHash $master->cookies->get($cookieName);
  169.         $newHash $this->buildCacheHash($context$isNet);
  170.         // remove context cookie from headers if already set
  171.         $responseHash null;
  172.         $headers $response->headers->all();
  173.         if (isset($headers['set-cookie'])) {
  174.             foreach ($headers['set-cookie'] as $key => $value) {
  175.                 if (preg_match('/' preg_quote($cookieName'/') . '=(.+);/'$value$matches)) {
  176.                     unset($headers['set-cookie'][$key]);
  177.                     $response->headers->set('set-cookie'$headers['set-cookie']);
  178.                     $responseHash $matches[1];
  179.                     break;
  180.                 }
  181.             }
  182.         }
  183.         // update cookie if needed
  184.         if ($currentHash !== $newHash || ($responseHash && $responseHash !== $newHash)) {
  185.             $cookie Cookie::create($cookieName$newHash);
  186.             $cookie->setSecureDefault($master->isSecure());
  187.             $response->headers->setCookie($cookie);
  188.         }
  189.     }
  190.     /**
  191.      * Returns the value for the sw-cache-hash cookie.
  192.      * @todo Extend SW implementation when possible
  193.      *
  194.      * @param SalesChannelContext $context
  195.      * @param bool $isNet
  196.      * @return string
  197.      */
  198.     protected function buildCacheHash(SalesChannelContext $contextbool $isNet): string
  199.     {
  200.         $data = [
  201.             $context->getRuleIds(),
  202.             $context->getContext()->getVersionId(),
  203.             $context->getCurrency()->getId(),
  204.             $context->getCustomer() ? 'logged-in' 'not-logged-in',
  205.             $isNet
  206.         ];
  207.         $acrisCacheHashExtension $context->getExtension('acris_cache_hash');
  208.         if ($acrisCacheHashExtension) {
  209.             $acrisCacheHash md5(json_encode($acrisCacheHashExtension->all()));
  210.             $data[] = $acrisCacheHash;
  211.         }
  212.         $event = new GenerateCacheHashEvent($data$context);
  213.         $this->eventDispatcher->dispatch($event);
  214.         return md5(json_encode($event->getParameters()));
  215.     }
  216.     /**
  217.      * Updates the tax switch cookie, if the setting has been changed.
  218.      * Will be ignored, if the cookie was not set beforehand on the client side.
  219.      *
  220.      * @param ResponseEvent $event
  221.      */
  222.     public function updateCookie(ResponseEvent $event): void
  223.     {
  224.         $master $this->getMainRequest();
  225.         if (!$master || !$master->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)) {
  226.             return;
  227.         }
  228.         if (!$master
  229.             || !$master->attributes->get(TaxSwitchService::TAX_SWITCH_ACTIVE_ATTR)
  230.             || !$master->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)
  231.             || !$master->attributes->get(TaxSwitchService::STATE_CHANGED_ATTR)
  232.             || $master->cookies->get(TaxSwitchService::COOKIE_NAME) === null)
  233.         {
  234.             return;
  235.         }
  236.         /** @var SalesChannelContext $context */
  237.         $context $master->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
  238.         if (!$context) {
  239.             return;
  240.         }
  241.         $isNet $this->taxSwitchService->getDisplayNet($context);
  242.         if ($isNet === null) {
  243.             return;
  244.         }
  245.         $cookie Cookie::create(TaxSwitchService::COOKIE_NAME, (string)((int)$isNet))
  246.             ->withHttpOnly(false)
  247.             ->withExpires((new \DateTime())->add(new \DateInterval('P30D')));
  248.         $cookie->setSecureDefault($master->isSecure());
  249.         $event->getResponse()->headers->setCookie($cookie);
  250.     }
  251.     /**
  252.      * Remove the duplicate session cookie as this can result in the wrong tax setting being read
  253.      * https://issues.shopware.com/issues/NEXT-10238
  254.      * @todo Remove when possible
  255.      *
  256.      * @param BeforeSendResponseEvent $event
  257.      */
  258.     public function removeDuplicateSession(BeforeSendResponseEvent $event): void
  259.     {
  260.         if (!$event->getRequest()->attributes->get(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)) {
  261.             return;
  262.         }
  263.         $hasSessionCookie false;
  264.         if (session_id() && headers_list()) {
  265.             $cookies array_filter(headers_list(), function ($item) {
  266.                 return strpos($item'session-='.session_id()) !== false;
  267.             });
  268.             $hasSessionCookie count($cookies) > 0;
  269.         }
  270.         $response $event->getResponse();
  271.         $headers $response->headers->all();
  272.         if (isset($headers['set-cookie'])) {
  273.             foreach ($headers['set-cookie'] as $key => $value) {
  274.                 if (str_starts_with($value'PHPSESSID=')) {
  275.                     unset($headers['set-cookie'][$key]);
  276.                 }
  277.                 if ($hasSessionCookie && str_starts_with($value'session-=')) {
  278.                     if (strpos($valuesession_id()) === false) {
  279.                         unset($headers['set-cookie'][$key]);
  280.                     }
  281.                 }
  282.             }
  283.             if ($headers['set-cookie']) {
  284.                 $response->headers->set('set-cookie'$headers['set-cookie']);
  285.                 $event->setResponse($response);
  286.             }
  287.         }
  288.     }
  289.     /**
  290.      * @return \Symfony\Component\HttpFoundation\Request|null
  291.      */
  292.     protected function getMainRequest()
  293.     {
  294.         return method_exists($this->requestStack'getMainRequest')
  295.             ? $this->requestStack->getMainRequest()
  296.             : $this->requestStack->getMasterRequest();
  297.     }
  298. }