vendor/contao/core-bundle/src/Routing/RouteProvider.php line 63

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of Contao.
  5.  *
  6.  * (c) Leo Feyer
  7.  *
  8.  * @license LGPL-3.0-or-later
  9.  */
  10. namespace Contao\CoreBundle\Routing;
  11. use Contao\CoreBundle\Exception\NoRootPageFoundException;
  12. use Contao\CoreBundle\Framework\ContaoFramework;
  13. use Contao\CoreBundle\Routing\Page\PageRegistry;
  14. use Contao\CoreBundle\Routing\Page\PageRoute;
  15. use Contao\Model\Collection;
  16. use Contao\PageModel;
  17. use Contao\System;
  18. use Symfony\Cmf\Component\Routing\Candidates\CandidatesInterface;
  19. use Symfony\Component\HttpFoundation\Request;
  20. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  21. use Symfony\Component\Routing\Route;
  22. use Symfony\Component\Routing\RouteCollection;
  23. class RouteProvider extends AbstractPageRouteProvider
  24. {
  25.     private bool $legacyRouting;
  26.     private bool $prependLocale;
  27.     /**
  28.      * @internal Do not inherit from this class; decorate the "contao.routing.route_provider" service instead
  29.      */
  30.     public function __construct(ContaoFramework $frameworkCandidatesInterface $candidatesPageRegistry $pageRegistrybool $legacyRoutingbool $prependLocale)
  31.     {
  32.         parent::__construct($framework$candidates$pageRegistry);
  33.         $this->legacyRouting $legacyRouting;
  34.         $this->prependLocale $prependLocale;
  35.     }
  36.     public function getRouteCollectionForRequest(Request $request): RouteCollection
  37.     {
  38.         $this->framework->initialize(true);
  39.         $pathInfo rawurldecode($request->getPathInfo());
  40.         // The request string must not contain "auto_item" (see #4012)
  41.         if (false !== strpos($pathInfo'/auto_item/')) {
  42.             return new RouteCollection();
  43.         }
  44.         $routes = [];
  45.         if ('/' === $pathInfo || ($this->legacyRouting && $this->prependLocale && preg_match('@^/([a-z]{2}(-[A-Z]{2})?)/$@'$pathInfo))) {
  46.             $this->addRoutesForRootPages($this->findRootPages($request->getHttpHost()), $routes);
  47.             return $this->createCollectionForRoutes($routes$request->getLanguages());
  48.         }
  49.         $pages $this->findCandidatePages($request);
  50.         if (empty($pages)) {
  51.             return new RouteCollection();
  52.         }
  53.         $this->addRoutesForPages($pages$routes);
  54.         return $this->createCollectionForRoutes($routes$request->getLanguages());
  55.     }
  56.     public function getRouteByName($name): Route
  57.     {
  58.         $this->framework->initialize(true);
  59.         $ids $this->getPageIdsFromNames([$name]);
  60.         if (empty($ids)) {
  61.             throw new RouteNotFoundException('Route name does not match a page ID');
  62.         }
  63.         $pageModel $this->framework->getAdapter(PageModel::class);
  64.         $page $pageModel->findByPk($ids[0]);
  65.         if (null === $page || !$this->pageRegistry->isRoutable($page)) {
  66.             throw new RouteNotFoundException(sprintf('Page ID "%s" not found'$ids[0]));
  67.         }
  68.         $routes = [];
  69.         $this->addRoutesForPage($page$routes);
  70.         if (!\array_key_exists($name$routes)) {
  71.             throw new RouteNotFoundException('Route "'.$name.'" not found');
  72.         }
  73.         return $routes[$name];
  74.     }
  75.     public function getRoutesByNames($names): array
  76.     {
  77.         $this->framework->initialize(true);
  78.         $pageModel $this->framework->getAdapter(PageModel::class);
  79.         if (null === $names) {
  80.             $pages $pageModel->findAll();
  81.         } else {
  82.             $ids $this->getPageIdsFromNames($names);
  83.             if (empty($ids)) {
  84.                 return [];
  85.             }
  86.             $pages $pageModel->findBy('tl_page.id IN ('.implode(','$ids).')', []);
  87.         }
  88.         if (!$pages instanceof Collection) {
  89.             return [];
  90.         }
  91.         $routes = [];
  92.         /** @var array<PageModel> $models */
  93.         $models $pages->getModels();
  94.         $models array_filter($models, fn (PageModel $page): bool => $this->pageRegistry->isRoutable($page));
  95.         $this->addRoutesForPages($models$routes);
  96.         $this->sortRoutes($routes);
  97.         return $routes;
  98.     }
  99.     /**
  100.      * @param iterable<PageModel> $pages
  101.      */
  102.     private function addRoutesForPages(iterable $pages, array &$routes): void
  103.     {
  104.         foreach ($pages as $page) {
  105.             $this->addRoutesForPage($page$routes);
  106.         }
  107.     }
  108.     /**
  109.      * @param array<PageModel> $pages
  110.      */
  111.     private function addRoutesForRootPages(array $pages, array &$routes): void
  112.     {
  113.         foreach ($pages as $page) {
  114.             $route $this->pageRegistry->getRoute($page);
  115.             $this->addRoutesForRootPage($route$routes);
  116.         }
  117.     }
  118.     private function createCollectionForRoutes(array $routes, array $languages): RouteCollection
  119.     {
  120.         $this->sortRoutes($routes$languages);
  121.         $collection = new RouteCollection();
  122.         foreach ($routes as $name => $route) {
  123.             $collection->add($name$route);
  124.         }
  125.         return $collection;
  126.     }
  127.     private function addRoutesForPage(PageModel $page, array &$routes): void
  128.     {
  129.         try {
  130.             $page->loadDetails();
  131.             if (!$page->rootId) {
  132.                 return;
  133.             }
  134.         } catch (NoRootPageFoundException $e) {
  135.             return;
  136.         }
  137.         $route $this->pageRegistry->getRoute($page);
  138.         $routes['tl_page.'.$page->id] = $route;
  139.         $this->addRoutesForRootPage($route$routes);
  140.     }
  141.     private function addRoutesForRootPage(PageRoute $route, array &$routes): void
  142.     {
  143.         $page $route->getPageModel();
  144.         if ('root' !== $page->type && 'index' !== $page->alias && '/' !== $page->alias) {
  145.             return;
  146.         }
  147.         $urlPrefix $route->getUrlPrefix();
  148.         $routes['tl_page.'.$page->id.'.root'] = new Route(
  149.             $urlPrefix '/'.$urlPrefix.'/' '/',
  150.             $route->getDefaults(),
  151.             [],
  152.             $route->getOptions(),
  153.             $route->getHost(),
  154.             $route->getSchemes(),
  155.             $route->getMethods()
  156.         );
  157.         if (!$urlPrefix || (!$this->legacyRouting && $page->loadDetails()->disableLanguageRedirect)) {
  158.             return;
  159.         }
  160.         $routes['tl_page.'.$page->id.'.fallback'] = new Route(
  161.             '/',
  162.             array_merge(
  163.                 $route->getDefaults(),
  164.                 [
  165.                     '_controller' => 'Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction',
  166.                     'path' => '/'.$urlPrefix.'/',
  167.                     'permanent' => false,
  168.                 ]
  169.             ),
  170.             [],
  171.             $route->getOptions(),
  172.             $route->getHost(),
  173.             $route->getSchemes(),
  174.             $route->getMethods()
  175.         );
  176.     }
  177.     /**
  178.      * Sorts routes so that the FinalMatcher will correctly resolve them.
  179.      *
  180.      * 1. The ones with hostname should come first, so the ones with empty host are only taken if no hostname matches
  181.      * 2. Root pages come last, so non-root pages with index alias (= identical path) match first
  182.      * 3. Root/Index pages must be sorted by accept language and fallback, so the best language matches first
  183.      * 4. Pages with longer alias (folder page) must come first to match if applicable
  184.      */
  185.     private function sortRoutes(array &$routes, array $languages null): void
  186.     {
  187.         // Convert languages array so key is language and value is priority
  188.         if (null !== $languages) {
  189.             $languages $this->convertLanguagesForSorting($languages);
  190.         }
  191.         uasort(
  192.             $routes,
  193.             function (Route $aRoute $b) use ($languages$routes) {
  194.                 $nameA array_search($a$routestrue);
  195.                 $nameB array_search($b$routestrue);
  196.                 $fallbackA === substr_compare($nameA'.fallback', -9);
  197.                 $fallbackB === substr_compare($nameB'.fallback', -9);
  198.                 if ($fallbackA && !$fallbackB) {
  199.                     return 1;
  200.                 }
  201.                 if ($fallbackB && !$fallbackA) {
  202.                     return -1;
  203.                 }
  204.                 if ('/' === $a->getPath() && '/' !== $b->getPath()) {
  205.                     return -1;
  206.                 }
  207.                 if ('/' === $b->getPath() && '/' !== $a->getPath()) {
  208.                     return 1;
  209.                 }
  210.                 return $this->compareRoutes($a$b$languages);
  211.             }
  212.         );
  213.     }
  214.     /**
  215.      * @return array<PageModel>
  216.      */
  217.     private function findRootPages(string $httpHost): array
  218.     {
  219.         if (
  220.             $this->legacyRouting
  221.             && !empty($GLOBALS['TL_HOOKS']['getRootPageFromUrl'])
  222.             && \is_array($GLOBALS['TL_HOOKS']['getRootPageFromUrl'])
  223.         ) {
  224.             $system $this->framework->getAdapter(System::class);
  225.             foreach ($GLOBALS['TL_HOOKS']['getRootPageFromUrl'] as $callback) {
  226.                 $page $system->importStatic($callback[0])->{$callback[1]}();
  227.                 if ($page instanceof PageModel) {
  228.                     return [$page];
  229.                 }
  230.             }
  231.         }
  232.         $models = [];
  233.         $pageModel $this->framework->getAdapter(PageModel::class);
  234.         $pages $pageModel->findBy(["(tl_page.type='root' AND (tl_page.dns=? OR tl_page.dns=''))"], $httpHost);
  235.         if ($pages instanceof Collection) {
  236.             $models $pages->getModels();
  237.         }
  238.         $pages $pageModel->findBy(['tl_page.alias=? OR tl_page.alias=?'], ['index''/']);
  239.         if ($pages instanceof Collection) {
  240.             foreach ($pages as $page) {
  241.                 if ($this->pageRegistry->isRoutable($page)) {
  242.                     $models[] = $page;
  243.                 }
  244.             }
  245.         }
  246.         return $models;
  247.     }
  248. }