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

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
  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 start with "/" and must not contain "auto_item" (see #4012)
  41.         if (!str_starts_with($pathInfo'/') || 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.         // Only create "root" routes for root pages or pages with root alias
  145.         if ('root' !== $page->type && 'index' !== $page->alias && '/' !== $page->alias) {
  146.             return;
  147.         }
  148.         $urlPrefix $route->getUrlPrefix();
  149.         // Do not create a ".root" route for root pages without prefix if `disableLanguageRedirect` is enabled
  150.         if ('root' === $page->type && !$urlPrefix && !$this->legacyRouting && $page->disableLanguageRedirect) {
  151.             return;
  152.         }
  153.         $routes['tl_page.'.$page->id.'.root'] = new Route(
  154.             $urlPrefix '/'.$urlPrefix.'/' '/',
  155.             $route->getDefaults(),
  156.             [],
  157.             $route->getOptions(),
  158.             $route->getHost(),
  159.             $route->getSchemes(),
  160.             $route->getMethods()
  161.         );
  162.         // Do not create ".fallback" route if `disableLanguageRedirect` is enabled
  163.         if (!$urlPrefix || (!$this->legacyRouting && $page->loadDetails()->disableLanguageRedirect)) {
  164.             return;
  165.         }
  166.         $routes['tl_page.'.$page->id.'.fallback'] = new Route(
  167.             '/',
  168.             array_merge(
  169.                 $route->getDefaults(),
  170.                 [
  171.                     '_controller' => 'Symfony\Bundle\FrameworkBundle\Controller\RedirectController::urlRedirectAction',
  172.                     'path' => '/'.$urlPrefix.'/',
  173.                     'permanent' => false,
  174.                 ]
  175.             ),
  176.             [],
  177.             $route->getOptions(),
  178.             $route->getHost(),
  179.             $route->getSchemes(),
  180.             $route->getMethods()
  181.         );
  182.     }
  183.     /**
  184.      * Sorts routes so that the FinalMatcher will correctly resolve them.
  185.      *
  186.      * 1. The ones with hostname should come first, so the ones with empty host are only taken if no hostname matches
  187.      * 2. Root pages come last, so non-root pages with index alias (= identical path) match first
  188.      * 3. Root/Index pages must be sorted by accept language and fallback, so the best language matches first
  189.      * 4. Pages with longer alias (folder page) must come first to match if applicable
  190.      */
  191.     private function sortRoutes(array &$routes, ?array $languages null): void
  192.     {
  193.         // Convert languages array so key is language and value is priority
  194.         if (null !== $languages) {
  195.             $languages $this->convertLanguagesForSorting($languages);
  196.         }
  197.         uasort(
  198.             $routes,
  199.             function (Route $aRoute $b) use ($languages$routes) {
  200.                 $nameA array_search($a$routestrue);
  201.                 $nameB array_search($b$routestrue);
  202.                 $fallbackA === substr_compare($nameA'.fallback', -9);
  203.                 $fallbackB === substr_compare($nameB'.fallback', -9);
  204.                 if ($fallbackA && !$fallbackB) {
  205.                     return 1;
  206.                 }
  207.                 if ($fallbackB && !$fallbackA) {
  208.                     return -1;
  209.                 }
  210.                 if ('/' === $a->getPath() && '/' !== $b->getPath()) {
  211.                     return -1;
  212.                 }
  213.                 if ('/' === $b->getPath() && '/' !== $a->getPath()) {
  214.                     return 1;
  215.                 }
  216.                 return $this->compareRoutes($a$b$languages);
  217.             }
  218.         );
  219.     }
  220.     /**
  221.      * @return array<PageModel>
  222.      */
  223.     private function findRootPages(string $httpHost): array
  224.     {
  225.         if (
  226.             $this->legacyRouting
  227.             && !empty($GLOBALS['TL_HOOKS']['getRootPageFromUrl'])
  228.             && \is_array($GLOBALS['TL_HOOKS']['getRootPageFromUrl'])
  229.         ) {
  230.             $system $this->framework->getAdapter(System::class);
  231.             foreach ($GLOBALS['TL_HOOKS']['getRootPageFromUrl'] as $callback) {
  232.                 $page $system->importStatic($callback[0])->{$callback[1]}();
  233.                 if ($page instanceof PageModel) {
  234.                     return [$page];
  235.                 }
  236.             }
  237.         }
  238.         $models = [];
  239.         $pageModel $this->framework->getAdapter(PageModel::class);
  240.         $pages $pageModel->findBy(["(tl_page.type='root' AND (tl_page.dns=? OR tl_page.dns=''))"], $httpHost);
  241.         if ($pages instanceof Collection) {
  242.             $models $pages->getModels();
  243.         }
  244.         /** @var Collection|array<PageModel> $pages */
  245.         $pages $pageModel->findBy(['tl_page.alias=? OR tl_page.alias=?'], ['index''/']);
  246.         if ($pages instanceof Collection) {
  247.             foreach ($pages as $page) {
  248.                 if ($this->pageRegistry->isRoutable($page)) {
  249.                     $models[] = $page;
  250.                 }
  251.             }
  252.         }
  253.         return $models;
  254.     }
  255. }