vendor/pimcore/pimcore/lib/Routing/Dynamic/DocumentRouteHandler.php line 179

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * Pimcore
  5.  *
  6.  * This source file is available under two different licenses:
  7.  * - GNU General Public License version 3 (GPLv3)
  8.  * - Pimcore Commercial License (PCL)
  9.  * Full copyright and license information is available in
  10.  * LICENSE.md which is distributed with this source code.
  11.  *
  12.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  13.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  14.  */
  15. namespace Pimcore\Routing\Dynamic;
  16. use Pimcore\Config;
  17. use Pimcore\Controller\Config\ConfigNormalizer;
  18. use Pimcore\Http\Request\Resolver\SiteResolver;
  19. use Pimcore\Http\RequestHelper;
  20. use Pimcore\Model\Document;
  21. use Pimcore\Routing\DocumentRoute;
  22. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  23. use Symfony\Component\Routing\RouteCollection;
  24. class DocumentRouteHandler implements DynamicRouteHandlerInterface
  25. {
  26.     /**
  27.      * @var Document\Service
  28.      */
  29.     private $documentService;
  30.     /**
  31.      * @var SiteResolver
  32.      */
  33.     private $siteResolver;
  34.     /**
  35.      * @var RequestHelper
  36.      */
  37.     private $requestHelper;
  38.     /**
  39.      * @var ConfigNormalizer
  40.      */
  41.     private $configNormalizer;
  42.     /**
  43.      * Determines if unpublished documents should be matched, even when not in admin mode. This
  44.      * is mainly needed for maintencance jobs/scripts.
  45.      *
  46.      * @var bool
  47.      */
  48.     private $forceHandleUnpublishedDocuments false;
  49.     /**
  50.      * @var array
  51.      */
  52.     private $directRouteDocumentTypes = ['page''snippet''email''newsletter''printpage''printcontainer'];
  53.     /**
  54.      * @var Config
  55.      */
  56.     private $config;
  57.     /**
  58.      * @param Document\Service $documentService
  59.      * @param SiteResolver $siteResolver
  60.      * @param RequestHelper $requestHelper
  61.      * @param ConfigNormalizer $configNormalizer
  62.      * @param Config $config
  63.      */
  64.     public function __construct(
  65.         Document\Service $documentService,
  66.         SiteResolver $siteResolver,
  67.         RequestHelper $requestHelper,
  68.         ConfigNormalizer $configNormalizer,
  69.         Config $config
  70.     ) {
  71.         $this->documentService $documentService;
  72.         $this->siteResolver $siteResolver;
  73.         $this->requestHelper $requestHelper;
  74.         $this->configNormalizer $configNormalizer;
  75.         $this->config $config;
  76.     }
  77.     public function setForceHandleUnpublishedDocuments(bool $handle)
  78.     {
  79.         $this->forceHandleUnpublishedDocuments $handle;
  80.     }
  81.     /**
  82.      * @return array
  83.      */
  84.     public function getDirectRouteDocumentTypes()
  85.     {
  86.         return $this->directRouteDocumentTypes;
  87.     }
  88.     /**
  89.      * @param string $type
  90.      */
  91.     public function addDirectRouteDocumentType($type)
  92.     {
  93.         if (!in_array($type$this->directRouteDocumentTypes)) {
  94.             $this->directRouteDocumentTypes[] = $type;
  95.         }
  96.     }
  97.     /**
  98.      * @inheritDoc
  99.      */
  100.     public function getRouteByName(string $name)
  101.     {
  102.         if (preg_match('/^document_(\d+)$/'$name$match)) {
  103.             $document Document::getById($match[1]);
  104.             if ($this->isDirectRouteDocument($document)) {
  105.                 return $this->buildRouteForDocument($document);
  106.             }
  107.         }
  108.         throw new RouteNotFoundException(sprintf("Route for name '%s' was not found"$name));
  109.     }
  110.     /**
  111.      * @inheritDoc
  112.      */
  113.     public function matchRequest(RouteCollection $collectionDynamicRequestContext $context)
  114.     {
  115.         $document Document::getByPath($context->getPath());
  116.         // check for a pretty url inside a site
  117.         if (!$document && $this->siteResolver->isSiteRequest($context->getRequest())) {
  118.             $site $this->siteResolver->getSite($context->getRequest());
  119.             $sitePrettyDocId $this->documentService->getDao()->getDocumentIdByPrettyUrlInSite($site$context->getOriginalPath());
  120.             if ($sitePrettyDocId) {
  121.                 if ($sitePrettyDoc Document::getById($sitePrettyDocId)) {
  122.                     $document $sitePrettyDoc;
  123.                     // TODO set pretty path via siteResolver?
  124.                     // undo the modification of the path by the site detection (prefixing with site root path)
  125.                     // this is not necessary when using pretty-urls and will cause problems when validating the
  126.                     // prettyUrl later (redirecting to the prettyUrl in the case the page was called by the real path)
  127.                     $context->setPath($context->getOriginalPath());
  128.                 }
  129.             }
  130.         }
  131.         // check for a parent hardlink with children
  132.         if (!$document instanceof Document) {
  133.             $hardlinkedParentDocument $this->documentService->getNearestDocumentByPath($context->getPath(), true);
  134.             if ($hardlinkedParentDocument instanceof Document\Hardlink) {
  135.                 if ($hardLinkedDocument Document\Hardlink\Service::getChildByPath($hardlinkedParentDocument$context->getPath())) {
  136.                     $document $hardLinkedDocument;
  137.                 }
  138.             }
  139.         }
  140.         if ($document && $document instanceof Document) {
  141.             if ($route $this->buildRouteForDocument($document$context)) {
  142.                 $collection->add($route->getRouteKey(), $route);
  143.             }
  144.         }
  145.     }
  146.     /**
  147.      * Build a route for a document. Context is only set from match mode, not when generating URLs.
  148.      *
  149.      * @param Document $document
  150.      * @param DynamicRequestContext|null $context
  151.      *
  152.      * @return DocumentRoute|null
  153.      */
  154.     public function buildRouteForDocument(Document $documentDynamicRequestContext $context null)
  155.     {
  156.         // check for direct hardlink
  157.         if ($document instanceof Document\Hardlink) {
  158.             $document Document\Hardlink\Service::wrap($document);
  159.             if (!$document) {
  160.                 return null;
  161.             }
  162.         }
  163.         $route = new DocumentRoute($document->getFullPath());
  164.         $route->setOption('utf8'true);
  165.         // coming from matching -> set route path the currently matched one
  166.         if (null !== $context) {
  167.             $route->setPath($context->getOriginalPath());
  168.         }
  169.         $route->setDefault('_locale'$document->getProperty('language'));
  170.         $route->setDocument($document);
  171.         if ($this->isDirectRouteDocument($document)) {
  172.             /** @var Document\PageSnippet $document */
  173.             $route $this->handleDirectRouteDocument($document$route$context);
  174.         } elseif ($document->getType() === 'link') {
  175.             /** @var Document\Link $document */
  176.             $route $this->handleLinkDocument($document$route);
  177.         }
  178.         return $route;
  179.     }
  180.     /**
  181.      * Handle route params for link document
  182.      *
  183.      * @param Document\Link $document
  184.      * @param DocumentRoute $route
  185.      *
  186.      * @return DocumentRoute
  187.      */
  188.     private function handleLinkDocument(Document\Link $documentDocumentRoute $route)
  189.     {
  190.         $route->setDefault('_controller''Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction');
  191.         $route->setDefault('path'$document->getHref());
  192.         $route->setDefault('permanent'true);
  193.         return $route;
  194.     }
  195.     /**
  196.      * Handle direct route documents (not link)
  197.      *
  198.      * @param Document\PageSnippet $document
  199.      * @param DocumentRoute $route
  200.      * @param DynamicRequestContext|null $context
  201.      *
  202.      * @return DocumentRoute|null
  203.      */
  204.     private function handleDirectRouteDocument(
  205.         Document\PageSnippet $document,
  206.         DocumentRoute $route,
  207.         DynamicRequestContext $context null
  208.     ) {
  209.         // if we have a request we're currently in match mode (not generating URLs) -> only match when frontend request by admin
  210.         try {
  211.             $request null;
  212.             if ($context) {
  213.                 $request $context->getRequest();
  214.             }
  215.             $isAdminRequest $this->requestHelper->isFrontendRequestByAdmin($request);
  216.         } catch (\LogicException $e) {
  217.             // catch logic exception here - when the exception fires, it is no admin request
  218.             $isAdminRequest false;
  219.         }
  220.         // abort if document is not published and the request is no admin request
  221.         // and matching unpublished documents was not forced
  222.         if (!$document->isPublished()) {
  223.             if (!($isAdminRequest || $this->forceHandleUnpublishedDocuments)) {
  224.                 return null;
  225.             }
  226.         }
  227.         if (!$isAdminRequest && null !== $context) {
  228.             // check for redirects (pretty URL, SEO) when not in admin mode and while matching (not generating route)
  229.             if ($redirectRoute $this->handleDirectRouteRedirect($document$route$context)) {
  230.                 return $redirectRoute;
  231.             }
  232.         }
  233.         return $this->buildRouteForPageSnippetDocument($document$route);
  234.     }
  235.     /**
  236.      * Handle document redirects (pretty url, SEO without trailing slash)
  237.      *
  238.      * @param Document\PageSnippet $document
  239.      * @param DocumentRoute $route
  240.      * @param DynamicRequestContext|null $context
  241.      *
  242.      * @return DocumentRoute|null
  243.      */
  244.     private function handleDirectRouteRedirect(
  245.         Document\PageSnippet $document,
  246.         DocumentRoute $route,
  247.         DynamicRequestContext $context null
  248.     ) {
  249.         $redirectTargetUrl $context->getOriginalPath();
  250.         // check for a pretty url, and if the document is called by that, otherwise redirect to pretty url
  251.         if ($document instanceof Document\Page && !$document instanceof Document\Hardlink\Wrapper\WrapperInterface) {
  252.             if ($prettyUrl $document->getPrettyUrl()) {
  253.                 if (rtrim(strtolower($prettyUrl), ' /') !== rtrim(strtolower($context->getOriginalPath()), '/')) {
  254.                     $redirectTargetUrl $prettyUrl;
  255.                 }
  256.             }
  257.         }
  258.         // check for a trailing slash in path, if exists, redirect to this page without the slash
  259.         // the only reason for this is: SEO, Analytics, ... there is no system specific reason, pimcore would work also with a trailing slash without problems
  260.         // use $originalPath because of the sites
  261.         // only do redirecting with GET requests
  262.         if ($context->getRequest()->getMethod() === 'GET') {
  263.             if (($this->config['documents']['allow_trailing_slash'] ?? null) === 'no') {
  264.                 if ($redirectTargetUrl !== '/' && substr($redirectTargetUrl, -1) === '/') {
  265.                     $redirectTargetUrl rtrim($redirectTargetUrl'/');
  266.                 }
  267.             }
  268.             // only allow the original key of a document to be the URL (lowercase/uppercase)
  269.             if ($redirectTargetUrl !== '/' && rtrim($redirectTargetUrl'/') !== rawurldecode($document->getFullPath())) {
  270.                 $redirectTargetUrl $document->getFullPath();
  271.             }
  272.         }
  273.         if (null !== $redirectTargetUrl && $redirectTargetUrl !== $context->getOriginalPath()) {
  274.             $route->setDefault('_controller''Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction');
  275.             $route->setDefault('path'$redirectTargetUrl);
  276.             $route->setDefault('permanent'true);
  277.             return $route;
  278.         }
  279.         return null;
  280.     }
  281.     /**
  282.      * Handle page snippet route (controller, action, view)
  283.      *
  284.      * @param Document\PageSnippet $document
  285.      * @param DocumentRoute $route
  286.      *
  287.      * @return DocumentRoute
  288.      */
  289.     private function buildRouteForPageSnippetDocument(Document\PageSnippet $documentDocumentRoute $route)
  290.     {
  291.         $controller $this->configNormalizer->formatControllerReference(
  292.             $document->getModule(),
  293.             $document->getController(),
  294.             $document->getAction()
  295.         );
  296.         $route->setDefault('_controller'$controller);
  297.         if ($document->getTemplate()) {
  298.             $template $this->configNormalizer->normalizeTemplateName($document->getTemplate());
  299.             $route->setDefault('_template'$template);
  300.         }
  301.         return $route;
  302.     }
  303.     /**
  304.      * Check if document is can be used to generate a route
  305.      *
  306.      * @param Document\PageSnippet $document
  307.      *
  308.      * @return bool
  309.      */
  310.     private function isDirectRouteDocument($document)
  311.     {
  312.         if ($document instanceof Document\PageSnippet) {
  313.             if (in_array($document->getType(), $this->getDirectRouteDocumentTypes())) {
  314.                 return true;
  315.             }
  316.         }
  317.         return false;
  318.     }
  319. }