vendor/contao/core-bundle/src/EventListener/StoreRefererListener.php line 45

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\EventListener;
  11. use Contao\CoreBundle\Routing\ScopeMatcher;
  12. use Contao\User;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  15. use Symfony\Component\Security\Core\Security;
  16. /**
  17.  * @internal
  18.  */
  19. class StoreRefererListener
  20. {
  21.     /**
  22.      * @var Security
  23.      */
  24.     private $security;
  25.     /**
  26.      * @var ScopeMatcher
  27.      */
  28.     private $scopeMatcher;
  29.     public function __construct(Security $securityScopeMatcher $scopeMatcher)
  30.     {
  31.         $this->security $security;
  32.         $this->scopeMatcher $scopeMatcher;
  33.     }
  34.     /**
  35.      * Stores the referer in the session.
  36.      */
  37.     public function __invoke(ResponseEvent $event): void
  38.     {
  39.         if (!$this->scopeMatcher->isBackendMasterRequest($event)) {
  40.             return;
  41.         }
  42.         $request $event->getRequest();
  43.         if (!$request->isMethod(Request::METHOD_GET)) {
  44.             return;
  45.         }
  46.         $response $event->getResponse();
  47.         if (200 !== $response->getStatusCode()) {
  48.             return;
  49.         }
  50.         $user $this->security->getUser();
  51.         if (!$user instanceof User) {
  52.             return;
  53.         }
  54.         if (!$this->canModifyBackendSession($request)) {
  55.             return;
  56.         }
  57.         if (!$request->hasSession()) {
  58.             throw new \RuntimeException('The request did not contain a session.');
  59.         }
  60.         $session $request->getSession();
  61.         $key $request->query->has('popup') ? 'popupReferer' 'referer';
  62.         $refererId $request->attributes->get('_contao_referer_id');
  63.         $referers $this->prepareBackendReferer($refererId$session->get($key));
  64.         $ref $request->query->get('ref''');
  65.         // Move current to last if the referer is in both the URL and the session
  66.         if ('' !== $ref && isset($referers[$ref])) {
  67.             $referers[$refererId] = array_merge($referers[$ref], $referers[$refererId]);
  68.             $referers[$refererId]['last'] = $referers[$ref]['current'];
  69.         }
  70.         // Set new current referer
  71.         $referers[$refererId]['current'] = $this->getRelativeRequestUri($request);
  72.         // Makes testing easier
  73.         ksort($referers[$refererId]);
  74.         ksort($referers);
  75.         $session->set($key$referers);
  76.     }
  77.     private function canModifyBackendSession(Request $request): bool
  78.     {
  79.         return !$request->query->has('act')
  80.             && !$request->query->has('key')
  81.             && !$request->query->has('token')
  82.             && !$request->query->has('state')
  83.             && 'feRedirect' !== $request->query->get('do')
  84.             && 'contao_backend' === $request->attributes->get('_route')
  85.             && !$request->isXmlHttpRequest();
  86.     }
  87.     /**
  88.      * @return array<string,array<string,string>>
  89.      */
  90.     private function prepareBackendReferer(string $refererId, array $referers null): array
  91.     {
  92.         if (!\is_array($referers)) {
  93.             $referers = [];
  94.         }
  95.         if (!isset($referers[$refererId]) || !\is_array($referers[$refererId])) {
  96.             $referers[$refererId] = end($referers) ?: ['last' => ''];
  97.         }
  98.         // Make sure we never have more than 25 different referer URLs
  99.         while (\count($referers) >= 25) {
  100.             array_shift($referers);
  101.         }
  102.         return $referers;
  103.     }
  104.     /**
  105.      * Returns the current request URI relative to the base path.
  106.      */
  107.     private function getRelativeRequestUri(Request $request): string
  108.     {
  109.         return (string) substr($request->getRequestUri(), \strlen($request->getBasePath()) + 1);
  110.     }
  111. }