vendor/sulu/sulu/src/Sulu/Bundle/WebsiteBundle/Routing/ContentRouteProvider.php line 104

  1. <?php
  2. /*
  3.  * This file is part of Sulu.
  4.  *
  5.  * (c) Sulu GmbH
  6.  *
  7.  * This source file is subject to the MIT license that is bundled
  8.  * with this source code in the file LICENSE.
  9.  */
  10. namespace Sulu\Bundle\WebsiteBundle\Routing;
  11. use PHPCR\RepositoryException;
  12. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  13. use Sulu\Bundle\PageBundle\Document\PageDocument;
  14. use Sulu\Component\Content\Compat\Structure\PageBridge;
  15. use Sulu\Component\Content\Compat\StructureManagerInterface;
  16. use Sulu\Component\Content\Document\Behavior\ExtensionBehavior;
  17. use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
  18. use Sulu\Component\Content\Document\Behavior\WebspaceBehavior;
  19. use Sulu\Component\Content\Document\RedirectType;
  20. use Sulu\Component\Content\Exception\ResourceLocatorMovedException;
  21. use Sulu\Component\Content\Exception\ResourceLocatorNotFoundException;
  22. use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyPoolInterface;
  23. use Sulu\Component\DocumentManager\DocumentManagerInterface;
  24. use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
  25. use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
  26. use Sulu\Component\Webspace\Analyzer\RequestAnalyzerInterface;
  27. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  28. use Symfony\Cmf\Component\Routing\RouteProviderInterface;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  31. use Symfony\Component\Routing\Route;
  32. use Symfony\Component\Routing\RouteCollection;
  33. use Webmozart\Assert\Assert;
  34. /**
  35.  * The PortalRouteProvider should load the dynamic routes created by Sulu.
  36.  */
  37. class ContentRouteProvider implements RouteProviderInterface
  38. {
  39.     /**
  40.      * @var DocumentManagerInterface
  41.      */
  42.     private $documentManager;
  43.     /**
  44.      * @var DocumentInspector
  45.      */
  46.     private $documentInspector;
  47.     /**
  48.      * @var ResourceLocatorStrategyPoolInterface
  49.      */
  50.     private $resourceLocatorStrategyPool;
  51.     /**
  52.      * @var StructureManagerInterface
  53.      */
  54.     private $structureManager;
  55.     /**
  56.      * @var WebspaceManagerInterface
  57.      */
  58.     private $webspaceManager;
  59.     /**
  60.      * @var RequestAnalyzerInterface
  61.      */
  62.     private $requestAnalyzer;
  63.     /**
  64.      * @var SecurityCheckerInterface|null
  65.      */
  66.     private $securityChecker;
  67.     /**
  68.      * @var array
  69.      */
  70.     private $defaultOptions;
  71.     public function __construct(
  72.         DocumentManagerInterface $documentManager,
  73.         DocumentInspector $documentInspector,
  74.         ResourceLocatorStrategyPoolInterface $resourceLocatorStrategyPool,
  75.         StructureManagerInterface $structureManager,
  76.         WebspaceManagerInterface $webspaceManager,
  77.         RequestAnalyzerInterface $requestAnalyzer,
  78.         ?SecurityCheckerInterface $securityChecker null,
  79.         array $defaultOptions = []
  80.     ) {
  81.         $this->documentManager $documentManager;
  82.         $this->documentInspector $documentInspector;
  83.         $this->resourceLocatorStrategyPool $resourceLocatorStrategyPool;
  84.         $this->structureManager $structureManager;
  85.         $this->webspaceManager $webspaceManager;
  86.         $this->requestAnalyzer $requestAnalyzer;
  87.         $this->securityChecker $securityChecker;
  88.         Assert::null($securityChecker'The security checker should be called by the SecurityListener not the ContentRouteProvider.'); // people who overwrite the ContentRouteProvider should make aware of that they also need to refactor this
  89.         $this->defaultOptions $defaultOptions;
  90.     }
  91.     public function getRouteCollectionForRequest(Request $request): RouteCollection
  92.     {
  93.         $collection = new RouteCollection();
  94.         if ('' === $request->getRequestFormat()) {
  95.             return $collection;
  96.         }
  97.         /** @var RequestAttributes $attributes */
  98.         $attributes $request->attributes->get('_sulu');
  99.         if (!$attributes) {
  100.             return $collection;
  101.         }
  102.         $matchType $attributes->getAttribute('matchType');
  103.         // no portal information without localization supported
  104.         if (null === $attributes->getAttribute('localization')
  105.             && RequestAnalyzerInterface::MATCH_TYPE_PARTIAL !== $matchType
  106.             && RequestAnalyzerInterface::MATCH_TYPE_REDIRECT !== $matchType
  107.         ) {
  108.             return $collection;
  109.         }
  110.         $resourceLocator $this->decodePathInfo($attributes->getAttribute('resourceLocator'));
  111.         $prefix $attributes->getAttribute('resourceLocatorPrefix');
  112.         $pathInfo $this->decodePathInfo($request->getPathInfo());
  113.         $htmlRedirect $pathInfo !== $prefix $resourceLocator
  114.                         && \in_array($request->getRequestFormat(), ['htm''html']);
  115.         if ($htmlRedirect
  116.             || RequestAnalyzerInterface::MATCH_TYPE_REDIRECT == $matchType
  117.             || RequestAnalyzerInterface::MATCH_TYPE_PARTIAL == $matchType
  118.         ) {
  119.             return $collection;
  120.         }
  121.         // just show the page
  122.         $portal $attributes->getAttribute('portal');
  123.         $locale $attributes->getAttribute('localization')->getLocale();
  124.         $resourceLocatorStrategy $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey(
  125.             $portal->getWebspace()->getKey()
  126.         );
  127.         try {
  128.             // load content by url ignore ending trailing slash
  129.             /** @var PageDocument $document */
  130.             $document $this->documentManager->find(
  131.                 $resourceLocatorStrategy->loadByResourceLocator(
  132.                     \rtrim($resourceLocator'/'),
  133.                     $portal->getWebspace()->getKey(),
  134.                     $locale
  135.                 ),
  136.                 $locale,
  137.                 [
  138.                     'load_ghost_content' => false,
  139.                 ]
  140.             );
  141.             if (!$document->getTitle()) {
  142.                 // If the title is empty the document does not exist in this locale
  143.                 // Necessary because of https://github.com/sulu/sulu/issues/2724, otherwise locale could be checked
  144.                 return $collection;
  145.             }
  146.             if (\preg_match('/\/$/'$resourceLocator) && ('/' !== $resourceLocator || $prefix)) {
  147.                 // redirect page to page without slash at the end
  148.                 $url $prefix \rtrim($resourceLocator'/');
  149.                 if ($request->getQueryString()) {
  150.                     $url .= '?' $request->getQueryString();
  151.                 }
  152.                 $collection->add('redirect_' \uniqid(), $this->getRedirectRoute($request$url));
  153.             } elseif (RedirectType::INTERNAL === $document->getRedirectType()) {
  154.                 $redirectTarget $document->getRedirectTarget();
  155.                 if (!$redirectTarget instanceof ResourceSegmentBehavior || !$redirectTarget instanceof WebspaceBehavior) {
  156.                     return $collection;
  157.                 }
  158.                 $redirectUrl $this->webspaceManager->findUrlByResourceLocator(
  159.                     $redirectTarget->getResourceSegment(),
  160.                     null,
  161.                     $document->getLocale(),
  162.                     $redirectTarget->getWebspaceName()
  163.                 );
  164.                 if ($request->getQueryString()) {
  165.                     $redirectUrl .= '?' $request->getQueryString();
  166.                 }
  167.                 $collection->add(
  168.                     $document->getStructureType() . '_' $document->getUuid(),
  169.                     $this->getRedirectRoute($request$redirectUrl)
  170.                 );
  171.             } elseif (RedirectType::EXTERNAL === $document->getRedirectType()) {
  172.                 $collection->add(
  173.                     $document->getStructureType() . '_' $document->getUuid(),
  174.                     $this->getRedirectRoute($request$document->getRedirectExternal())
  175.                 );
  176.             } elseif (!$this->checkResourceLocator($resourceLocator$prefix)) {
  177.                 return $collection;
  178.             } else {
  179.                 if ($document instanceof ExtensionBehavior) {
  180.                     $documentSegments $document->getExtensionsData()['excerpt']['segments'] ?? [];
  181.                     $documentSegmentKey $documentSegments[$portal->getWebspace()->getKey()] ?? null;
  182.                     $segment $this->requestAnalyzer->getSegment();
  183.                     if ($segment && $documentSegmentKey && $segment->getKey() !== $documentSegmentKey) {
  184.                         $this->requestAnalyzer->changeSegment($documentSegmentKey);
  185.                     }
  186.                 }
  187.                 // convert the page to a StructureBridge because of BC
  188.                 $metadata $this->documentInspector->getStructureMetadata($document);
  189.                 if (!$metadata) {
  190.                     return $collection;
  191.                 }
  192.                 /** @var PageBridge $structure */
  193.                 $structure $this->structureManager->wrapStructure(
  194.                     $this->documentInspector->getMetadata($document)->getAlias(),
  195.                     $metadata
  196.                 );
  197.                 $structure->setDocument($document);
  198.                 // show the page
  199.                 $collection->add(
  200.                     $document->getStructureType() . '_' $document->getUuid(),
  201.                     $this->getStructureRoute($request$structure)
  202.                 );
  203.             }
  204.         } catch (ResourceLocatorNotFoundException $exc) {
  205.             // just do not add any routes to the collection
  206.         } catch (ResourceLocatorMovedException $exc) {
  207.             $url $prefix $exc->getNewResourceLocator();
  208.             if ($request->getQueryString()) {
  209.                 $url .= '?' $request->getQueryString();
  210.             }
  211.             // old url resource was moved
  212.             $collection->add(
  213.                 $exc->getNewResourceLocatorUuid() . '_' \uniqid(),
  214.                 $this->getRedirectRoute($request$url)
  215.             );
  216.         } catch (RepositoryException $exc) {
  217.             // just do not add any routes to the collection
  218.         }
  219.         return $collection;
  220.     }
  221.     /**
  222.      * @param string $name
  223.      */
  224.     public function getRouteByName($name): Route
  225.     {
  226.         throw new RouteNotFoundException();
  227.     }
  228.     public function getRoutesByNames($names null): iterable
  229.     {
  230.         return [];
  231.     }
  232.     /**
  233.      * Checks if the resource locator is valid.
  234.      * A resource locator with a slash only is not allowed, the only exception is when it is a single language
  235.      * website, where the browser automatically adds the slash.
  236.      *
  237.      * @param string $resourceLocator
  238.      * @param string $resourceLocatorPrefix
  239.      *
  240.      * @return bool
  241.      */
  242.     private function checkResourceLocator($resourceLocator$resourceLocatorPrefix)
  243.     {
  244.         return !('/' === $resourceLocator && $resourceLocatorPrefix);
  245.     }
  246.     /**
  247.      * @param string $url
  248.      *
  249.      * @return Route
  250.      */
  251.     protected function getRedirectRoute(Request $request$url)
  252.     {
  253.         $requestFormat $request->getRequestFormat(null);
  254.         $formatSuffix $requestFormat '.' $requestFormat '';
  255.         $urlParts \explode('?'$url2);
  256.         $url $urlParts[0] . $formatSuffix;
  257.         if ($urlParts[1] ?? null) {
  258.             $url .= '?' $urlParts[1];
  259.         }
  260.         // redirect to linked page
  261.         return new Route(
  262.             $this->decodePathInfo($request->getPathInfo()),
  263.             [
  264.                 '_controller' => 'sulu_website.redirect_controller::redirectAction',
  265.                 'url' => $url,
  266.             ],
  267.             [],
  268.             $this->defaultOptions
  269.         );
  270.     }
  271.     /**
  272.      * @return Route
  273.      */
  274.     protected function getStructureRoute(Request $requestPageBridge $content)
  275.     {
  276.         return new Route(
  277.             $this->decodePathInfo($request->getPathInfo()),
  278.             [
  279.                 '_controller' => $content->getController(),
  280.                 'structure' => $content,
  281.                 'partial' => 'true' === $request->get('partial''false'),
  282.             ],
  283.             [],
  284.             $this->defaultOptions
  285.         );
  286.     }
  287.     /**
  288.      * Server encodes the url and symfony does not encode it
  289.      * Symfony decodes this data here https://github.com/symfony/symfony/blob/3.3/src/Symfony/Component/Routing/Matcher/UrlMatcher.php#L91.
  290.      *
  291.      * @param string $pathInfo
  292.      *
  293.      * @return string
  294.      */
  295.     private function decodePathInfo($pathInfo)
  296.     {
  297.         if (null === $pathInfo || '' === $pathInfo) {
  298.             return '';
  299.         }
  300.         return '/' \ltrim(\rawurldecode($pathInfo), '/');
  301.     }
  302. }