vendor/pimcore/pimcore/lib/Routing/RedirectHandler.php line 96

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Routing;
  15. use Pimcore\Cache;
  16. use Pimcore\Config;
  17. use Pimcore\Event\Model\RedirectEvent;
  18. use Pimcore\Event\RedirectEvents;
  19. use Pimcore\Event\Traits\RecursionBlockingEventDispatchHelperTrait;
  20. use Pimcore\Http\Request\Resolver\SiteResolver;
  21. use Pimcore\Http\RequestHelper;
  22. use Pimcore\Model\Document;
  23. use Pimcore\Model\Redirect;
  24. use Pimcore\Model\Site;
  25. use Pimcore\Routing\Redirect\RedirectUrlPartResolver;
  26. use Pimcore\Tool;
  27. use Psr\Log\LoggerAwareInterface;
  28. use Psr\Log\LoggerAwareTrait;
  29. use Symfony\Component\HttpFoundation\RedirectResponse;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use Symfony\Component\HttpFoundation\Response;
  32. use Symfony\Component\Lock\LockFactory;
  33. use Symfony\Component\Lock\LockInterface;
  34. /**
  35.  * @internal
  36.  */
  37. final class RedirectHandler implements LoggerAwareInterface
  38. {
  39.     use LoggerAwareTrait;
  40.     use RecursionBlockingEventDispatchHelperTrait;
  41.     const RESPONSE_HEADER_NAME_ID 'X-Pimcore-Redirect-ID';
  42.     /**
  43.      * @var RequestHelper
  44.      */
  45.     private $requestHelper;
  46.     /**
  47.      * @var SiteResolver
  48.      */
  49.     private $siteResolver;
  50.     /**
  51.      * @var null|Redirect[]
  52.      */
  53.     private $redirects;
  54.     /**
  55.      * @var Config
  56.      */
  57.     private $config;
  58.     /**
  59.      * @var null|LockInterface
  60.      */
  61.     private $lock null;
  62.     /**
  63.      * @param RequestHelper $requestHelper
  64.      * @param SiteResolver $siteResolver
  65.      * @param Config $config
  66.      * @param LockFactory $lockFactory
  67.      */
  68.     public function __construct(RequestHelper $requestHelperSiteResolver $siteResolverConfig $configLockFactory $lockFactory)
  69.     {
  70.         $this->requestHelper $requestHelper;
  71.         $this->siteResolver $siteResolver;
  72.         $this->config $config;
  73.         $this->lock $lockFactory->createLock(self::class);
  74.     }
  75.     /**
  76.      * @param Request $request
  77.      * @param bool $override
  78.      * @param Site|null $sourceSite
  79.      *
  80.      * @return Response|null
  81.      *
  82.      * @throws \Exception
  83.      */
  84.     public function checkForRedirect(Request $request$override false$sourceSite null)
  85.     {
  86.         // not for admin requests
  87.         if ($this->requestHelper->isFrontendRequestByAdmin($request)) {
  88.             return null;
  89.         }
  90.         // get current site if available
  91.         if (!$sourceSite && $this->siteResolver->isSiteRequest($request)) {
  92.             $sourceSite $this->siteResolver->getSite($request);
  93.         }
  94.         if ($redirect Redirect::getByExactMatch($request$sourceSite$override)) {
  95.             if (null !== $response $this->buildRedirectResponse($redirect$request)) {
  96.                 return $response;
  97.             }
  98.         }
  99.         $partResolver = new RedirectUrlPartResolver($request);
  100.         foreach ($this->getRegexFilteredRedirects($override) as $redirect) {
  101.             if (null !== $response $this->matchRegexRedirect($redirect$request$partResolver$sourceSite)) {
  102.                 return $response;
  103.             }
  104.         }
  105.         return null;
  106.     }
  107.     /**
  108.      * @param Redirect $redirect
  109.      * @param Request $request
  110.      * @param RedirectUrlPartResolver $partResolver
  111.      * @param Site|null $sourceSite
  112.      *
  113.      * @return Response|null
  114.      *
  115.      * @throws \Exception
  116.      */
  117.     private function matchRegexRedirect(
  118.         Redirect $redirect,
  119.         Request $request,
  120.         RedirectUrlPartResolver $partResolver,
  121.         Site $sourceSite null
  122.     ) {
  123.         if (empty($redirect->getType())) {
  124.             return null;
  125.         }
  126.         $matchPart $partResolver->getRequestUriPart($redirect->getType());
  127.         $matches = [];
  128.         $doesMatch false;
  129.         if ($redirect->isRegex()) {
  130.             $doesMatch = (bool)@preg_match($redirect->getSource(), $matchPart$matches);
  131.         } else {
  132.             $source str_replace('+'' '$redirect->getSource()); // see #2202
  133.             $doesMatch $source === $matchPart;
  134.         }
  135.         if (!$doesMatch) {
  136.             return null;
  137.         }
  138.         // check for a site
  139.         if ($redirect->getSourceSite() || $sourceSite) {
  140.             if (!$sourceSite || $sourceSite->getId() !== $redirect->getSourceSite()) {
  141.                 return null;
  142.             }
  143.         }
  144.         return $this->buildRedirectResponse($redirect$request$matches);
  145.     }
  146.     /**
  147.      * @param Redirect $redirect
  148.      * @param Request $request
  149.      * @param array $matches
  150.      *
  151.      * @return Response|null
  152.      *
  153.      * @throws \Exception
  154.      */
  155.     protected function buildRedirectResponse(Redirect $redirectRequest $request$matches = [])
  156.     {
  157.         $this->dispatchEvent(new RedirectEvent($redirect), RedirectEvents::PRE_BUILD);
  158.         $target $redirect->getTarget();
  159.         if (is_numeric($target)) {
  160.             $d Document::getById((int) $target);
  161.             if ($d instanceof Document\Page || $d instanceof Document\Link || $d instanceof Document\Hardlink) {
  162.                 $target $d->getFullPath();
  163.             } else {
  164.                 $this->logger->error('Target of redirect {redirect} not found (Document-ID: {document})', [
  165.                     'redirect' => $redirect->getId(),
  166.                     'document' => $target,
  167.                 ]);
  168.                 return null;
  169.             }
  170.         }
  171.         $url $target;
  172.         if ($redirect->isRegex()) {
  173.             array_shift($matches);
  174.             // support for pcre backreferences
  175.             $url replace_pcre_backreferences($url$matches);
  176.         }
  177.         if (!preg_match('@http(s)?://@i'$url)) {
  178.             if ($redirect->getTargetSite()) {
  179.                 if ($targetSite Site::getById($redirect->getTargetSite())) {
  180.                     // if the target site is specified and and the target-path is starting at root (not absolute to site)
  181.                     // the root-path will be replaced so that the page can be shown
  182.                     $url preg_replace('@^' $targetSite->getRootPath() . '/@''/'$url);
  183.                     $url $request->getScheme() . '://' $targetSite->getMainDomain() . $url;
  184.                 } else {
  185.                     $this->logger->error('Site with ID {targetSite} not found', [
  186.                         'redirect' => $redirect->getId(),
  187.                         'targetSite' => $redirect->getTargetSite(),
  188.                     ]);
  189.                     return null;
  190.                 }
  191.             } else {
  192.                 $site Site::getByDomain($request->getHost());
  193.                 if ($site instanceof Site) {
  194.                     $redirectDomain $request->getHost();
  195.                 } else {
  196.                     $redirectDomain $this->config['general']['domain'];
  197.                 }
  198.                 if ($redirectDomain) {
  199.                     // prepend the host and scheme to avoid infinite loops when using "domain" redirects
  200.                     $url $request->getScheme().'://'.$redirectDomain.$url;
  201.                 }
  202.             }
  203.         }
  204.         // pass-through parameters if specified
  205.         $queryString $request->getQueryString();
  206.         if ($redirect->getPassThroughParameters() && !empty($queryString)) {
  207.             $glue '?';
  208.             if (strpos($url'?')) {
  209.                 $glue '&';
  210.             }
  211.             $url .= $glue;
  212.             $url .= $queryString;
  213.         }
  214.         $statusCode $redirect->getStatusCode() ?: Response::HTTP_MOVED_PERMANENTLY;
  215.         $response = new Response(null$statusCode);
  216.         if ($response->isRedirect()) {
  217.             $response = new RedirectResponse($url$statusCode);
  218.         }
  219.         $response->headers->set(self::RESPONSE_HEADER_NAME_ID, (string) $redirect->getId());
  220.         // log all redirects to the redirect log
  221.         \Pimcore\Log\Simple::log(
  222.             'redirect',
  223.             Tool::getAnonymizedClientIp() . " \t Custom-Redirect ID: " $redirect->getId() . ', Source: ' $_SERVER['REQUEST_URI'] . ' -> ' $url
  224.         );
  225.         return $response;
  226.     }
  227.     /**
  228.      * @return Redirect[]
  229.      */
  230.     private function getRegexRedirects()
  231.     {
  232.         if (is_array($this->redirects)) {
  233.             return $this->redirects;
  234.         }
  235.         $cacheKey 'system_route_redirect';
  236.         if (($this->redirects Cache::load($cacheKey)) === false) {
  237.             // acquire lock to avoid concurrent redirect cache warm-up
  238.             $this->lock->acquire(true);
  239.             //check again if redirects are cached to avoid re-warming cache
  240.             if (($this->redirects Cache::load($cacheKey)) === false) {
  241.                 try {
  242.                     $list = new Redirect\Listing();
  243.                     $list->setCondition('active = 1 AND regex = 1');
  244.                     $list->setOrder('DESC');
  245.                     $list->setOrderKey('priority');
  246.                     $this->redirects $list->load();
  247.                     Cache::save($this->redirects$cacheKey, ['system''redirect''route'], null998true);
  248.                 } catch (\Exception $e) {
  249.                     $this->logger->error('Failed to load redirects');
  250.                 }
  251.             }
  252.             $this->lock->release();
  253.         }
  254.         if (!is_array($this->redirects)) {
  255.             $this->logger->warning('Failed to load redirects', [
  256.                 'redirects' => $this->redirects,
  257.             ]);
  258.             $this->redirects = [];
  259.         }
  260.         return $this->redirects;
  261.     }
  262.     /**
  263.      * @param bool $override
  264.      *
  265.      * @return Redirect[]
  266.      */
  267.     private function getRegexFilteredRedirects($override false)
  268.     {
  269.         $now time();
  270.         return array_filter($this->getRegexRedirects(), function (Redirect $redirect) use ($override$now) {
  271.             // this is the case when maintenance did't deactivate the redirect yet but it is already expired
  272.             if (!empty($redirect->getExpiry()) && $redirect->getExpiry() < $now) {
  273.                 return false;
  274.             }
  275.             if ($override) {
  276.                 // if override is true the priority has to be 99 which means that overriding is ok
  277.                 return (int)$redirect->getPriority() === 99;
  278.             } else {
  279.                 return (int)$redirect->getPriority() !== 99;
  280.             }
  281.         });
  282.     }
  283. }