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

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