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

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) && $this->isDocumentSupported($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 childs
  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.         // check if document should be handled (not legacy)
  157.         if (!$this->isDocumentSupported($document)) {
  158.             return null;
  159.         }
  160.         $route = new DocumentRoute($document->getFullPath());
  161.         // coming from matching -> set route path the currently matched one
  162.         if (null !== $context) {
  163.             $route->setPath($context->getOriginalPath());
  164.         }
  165.         $route->setDefault('_locale'$document->getProperty('language'));
  166.         $route->setDocument($document);
  167.         if ($this->isDirectRouteDocument($document)) {
  168.             /** @var Document\PageSnippet $document */
  169.             $route $this->handleDirectRouteDocument($document$route$context);
  170.         } elseif ($document->getType() === 'link') {
  171.             /** @var Document\Link $document */
  172.             $route $this->handleLinkDocument($document$route);
  173.         }
  174.         return $route;
  175.     }
  176.     /**
  177.      * Handle route params for link document
  178.      *
  179.      * @param Document\Link $document
  180.      * @param DocumentRoute $route
  181.      *
  182.      * @return DocumentRoute
  183.      */
  184.     private function handleLinkDocument(Document\Link $documentDocumentRoute $route)
  185.     {
  186.         $route->setDefault('_controller''FrameworkBundle:Redirect:urlRedirect');
  187.         $route->setDefault('path'$document->getHref());
  188.         $route->setDefault('permanent'true);
  189.         return $route;
  190.     }
  191.     /**
  192.      * Handle direct route documents (not link)
  193.      *
  194.      * @param Document\PageSnippet $document
  195.      * @param DocumentRoute $route
  196.      * @param DynamicRequestContext|null $context
  197.      *
  198.      * @return DocumentRoute|null
  199.      */
  200.     private function handleDirectRouteDocument(
  201.         Document\PageSnippet $document,
  202.         DocumentRoute $route,
  203.         DynamicRequestContext $context null
  204.     ) {
  205.         // if we have a request we're currently in match mode (not generating URLs) -> only match when frontend request by admin
  206.         try {
  207.             $request null;
  208.             if ($context) {
  209.                 $request $context->getRequest();
  210.             }
  211.             $isAdminRequest $this->requestHelper->isFrontendRequestByAdmin($request);
  212.         } catch (\LogicException $e) {
  213.             // catch logic exception here - when the exception fires, it is no admin request
  214.             $isAdminRequest false;
  215.         }
  216.         // abort if document is not published and the request is no admin request
  217.         // and matching unpublished documents was not forced
  218.         if (!$document->isPublished()) {
  219.             if (!($isAdminRequest || $this->forceHandleUnpublishedDocuments)) {
  220.                 return null;
  221.             }
  222.         }
  223.         if (!$isAdminRequest && null !== $context) {
  224.             // check for redirects (pretty URL, SEO) when not in admin mode and while matching (not generating route)
  225.             if ($redirectRoute $this->handleDirectRouteRedirect($document$route$context)) {
  226.                 return $redirectRoute;
  227.             }
  228.         }
  229.         return $this->buildRouteForPageSnippetDocument($document$route);
  230.     }
  231.     /**
  232.      * Handle document redirects (pretty url, SEO without trailing slash)
  233.      *
  234.      * @param Document\PageSnippet $document
  235.      * @param DocumentRoute $route
  236.      * @param DynamicRequestContext|null $context
  237.      *
  238.      * @return DocumentRoute|null
  239.      */
  240.     private function handleDirectRouteRedirect(
  241.         Document\PageSnippet $document,
  242.         DocumentRoute $route,
  243.         DynamicRequestContext $context null
  244.     ) {
  245.         $redirectTargetUrl $context->getOriginalPath();
  246.         // check for a pretty url, and if the document is called by that, otherwise redirect to pretty url
  247.         if ($document instanceof Document\Page && !$document instanceof Document\Hardlink\Wrapper\WrapperInterface) {
  248.             if ($prettyUrl $document->getPrettyUrl()) {
  249.                 if (rtrim(strtolower($prettyUrl), ' /') !== rtrim(strtolower($context->getOriginalPath()), '/')) {
  250.                     $redirectTargetUrl $prettyUrl;
  251.                 }
  252.             }
  253.         }
  254.         // check for a trailing slash in path, if exists, redirect to this page without the slash
  255.         // the only reason for this is: SEO, Analytics, ... there is no system specific reason, pimcore would work also with a trailing slash without problems
  256.         // use $originalPath because of the sites
  257.         // only do redirecting with GET requests
  258.         if ($context->getRequest()->getMethod() === 'GET') {
  259.             $config Config::getSystemConfig();
  260.             if ($config->documents->allowtrailingslash) {
  261.                 if ($config->documents->allowtrailingslash === 'no') {
  262.                     if ($redirectTargetUrl !== '/' && substr($redirectTargetUrl, -1) === '/') {
  263.                         $redirectTargetUrl rtrim($redirectTargetUrl'/');
  264.                     }
  265.                 }
  266.             }
  267.             // only allow the original key of a document to be the URL (lowercase/uppercase)
  268.             if ($redirectTargetUrl !== '/' && rtrim($redirectTargetUrl'/') !== rawurldecode($document->getFullPath())) {
  269.                 $redirectTargetUrl $document->getFullPath();
  270.             }
  271.         }
  272.         if (null !== $redirectTargetUrl && $redirectTargetUrl !== $context->getOriginalPath()) {
  273.             $route->setDefault('_controller''FrameworkBundle:Redirect:urlRedirect');
  274.             $route->setDefault('path'$redirectTargetUrl);
  275.             $route->setDefault('permanent'true);
  276.             return $route;
  277.         }
  278.     }
  279.     /**
  280.      * Handle page snippet route (controller, action, view)
  281.      *
  282.      * @param Document\PageSnippet $document
  283.      * @param DocumentRoute $route
  284.      *
  285.      * @return DocumentRoute
  286.      */
  287.     private function buildRouteForPageSnippetDocument(Document\PageSnippet $documentDocumentRoute $route)
  288.     {
  289.         $controller $this->configNormalizer->formatControllerReference(
  290.             $document->getModule(),
  291.             $document->getController(),
  292.             $document->getAction()
  293.         );
  294.         $route->setDefault('_controller'$controller);
  295.         if ($document->getTemplate()) {
  296.             $template $this->configNormalizer->normalizeTemplateName($document->getTemplate());
  297.             $route->setDefault('_template'$template);
  298.         }
  299.         return $route;
  300.     }
  301.     /**
  302.      * Check if document is can be used to generate a route
  303.      *
  304.      * @param $document
  305.      *
  306.      * @return bool
  307.      */
  308.     private function isDirectRouteDocument($document)
  309.     {
  310.         if ($document instanceof Document\PageSnippet) {
  311.             if (in_array($document->getType(), $this->getDirectRouteDocumentTypes())) {
  312.                 return true;
  313.             }
  314.         }
  315.         return false;
  316.     }
  317.     /**
  318.      * @param Document $document
  319.      *
  320.      * @return bool
  321.      */
  322.     private function isDocumentSupported(Document $document)
  323.     {
  324.         return !$document->doRenderWithLegacyStack();
  325.     }
  326. }