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

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