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

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