vendor/contao/core-bundle/src/Routing/Route404Provider.php line 48

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\ContaoCoreBundle;
  12. use Contao\CoreBundle\Exception\NoRootPageFoundException;
  13. use Contao\CoreBundle\Framework\ContaoFramework;
  14. use Contao\PageModel;
  15. use Symfony\Cmf\Component\Routing\RouteProviderInterface;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\Routing\Exception\RouteNotFoundException;
  18. use Symfony\Component\Routing\Route;
  19. use Symfony\Component\Routing\RouteCollection;
  20. class Route404Provider implements RouteProviderInterface
  21. {
  22.     /**
  23.      * @var ContaoFramework
  24.      */
  25.     private $framework;
  26.     /**
  27.      * @var bool
  28.      */
  29.     private $prependLocale;
  30.     public function __construct(ContaoFramework $frameworkbool $prependLocale)
  31.     {
  32.         $this->framework $framework;
  33.         $this->prependLocale $prependLocale;
  34.     }
  35.     public function getRouteCollectionForRequest(Request $request): RouteCollection
  36.     {
  37.         $this->framework->initialize(true);
  38.         $collection = new RouteCollection();
  39.         $routes $this->getRoutes($request->getLanguages());
  40.         foreach ($routes as $name => $route) {
  41.             $collection->add($name$route);
  42.         }
  43.         return $collection;
  44.     }
  45.     public function getRouteByName($name): Route
  46.     {
  47.         throw new RouteNotFoundException('This router cannot load routes by name');
  48.     }
  49.     public function getRoutesByNames($names): array
  50.     {
  51.         // Support console and web inspector profiling
  52.         if (null === $names) {
  53.             return $this->getRoutes();
  54.         }
  55.         return [];
  56.     }
  57.     private function getRoutes(array $languages null): array
  58.     {
  59.         $this->framework->initialize(true);
  60.         /** @var PageModel $pageModel */
  61.         $pageModel $this->framework->getAdapter(PageModel::class);
  62.         $pages $pageModel->findByType('error_404');
  63.         if (null === $pages) {
  64.             return [];
  65.         }
  66.         $routes = [];
  67.         foreach ($pages as $page) {
  68.             $this->addRoutesForPage($page$routes);
  69.         }
  70.         $this->sortRoutes($routes$languages);
  71.         return $routes;
  72.     }
  73.     private function addRoutesForPage(PageModel $page, array &$routes): void
  74.     {
  75.         try {
  76.             $page->loadDetails();
  77.             if (!$page->rootId) {
  78.                 return;
  79.             }
  80.         } catch (NoRootPageFoundException $e) {
  81.             return;
  82.         }
  83.         $defaults = [
  84.             '_token_check' => true,
  85.             '_controller' => 'Contao\FrontendIndex::renderPage',
  86.             '_scope' => ContaoCoreBundle::SCOPE_FRONTEND,
  87.             '_locale' => $page->rootLanguage,
  88.             'pageModel' => $page,
  89.         ];
  90.         $requirements = ['_url_fragment' => '.*'];
  91.         $path '/{_url_fragment}';
  92.         $routes['tl_page.'.$page->id.'.error_404'] = new Route(
  93.             $path,
  94.             $defaults,
  95.             $requirements,
  96.             ['utf8' => true],
  97.             $page->domain,
  98.             $page->rootUseSSL 'https' null
  99.         );
  100.         if (!$this->prependLocale) {
  101.             return;
  102.         }
  103.         $path '/{_locale}'.$path;
  104.         $requirements['_locale'] = $page->rootLanguage;
  105.         $routes['tl_page.'.$page->id.'.error_404.locale'] = new Route(
  106.             $path,
  107.             $defaults,
  108.             $requirements,
  109.             ['utf8' => true],
  110.             $page->domain,
  111.             $page->rootUseSSL 'https' null
  112.         );
  113.     }
  114.     /**
  115.      * Sorts routes so that the FinalMatcher will correctly resolve them.
  116.      *
  117.      * 1. Sort locale-aware routes first, so e.g. /de/not-found.html renders the german error page
  118.      * 2. Then sort by hostname, so the ones with empty host are only taken if no hostname matches
  119.      * 3. Lastly pages must be sorted by accept language and fallback, so the best language matches first
  120.      */
  121.     private function sortRoutes(array &$routes, array $languages null): void
  122.     {
  123.         // Convert languages array so key is language and value is priority
  124.         if (null !== $languages) {
  125.             foreach ($languages as &$language) {
  126.                 $language str_replace('_''-'$language);
  127.                 if (=== \strlen($language)) {
  128.                     $lng substr($language02);
  129.                     // Append the language if only language plus dialect is given (see #430)
  130.                     if (!\in_array($lng$languagestrue)) {
  131.                         $languages[] = $lng;
  132.                     }
  133.                 }
  134.             }
  135.             unset($language);
  136.             $languages array_flip(array_values($languages));
  137.         }
  138.         uasort(
  139.             $routes,
  140.             static function (Route $aRoute $b) use ($languages$routes) {
  141.                 $localeA '.locale' === substr(array_search($a$routestrue), -7);
  142.                 $localeB '.locale' === substr(array_search($b$routestrue), -7);
  143.                 if ($localeA && !$localeB) {
  144.                     return -1;
  145.                 }
  146.                 if ($localeB && !$localeA) {
  147.                     return 1;
  148.                 }
  149.                 if ('' !== $a->getHost() && '' === $b->getHost()) {
  150.                     return -1;
  151.                 }
  152.                 if ('' === $a->getHost() && '' !== $b->getHost()) {
  153.                     return 1;
  154.                 }
  155.                 /** @var PageModel $pageA */
  156.                 $pageA $a->getDefault('pageModel');
  157.                 /** @var PageModel $pageB */
  158.                 $pageB $b->getDefault('pageModel');
  159.                 if (!$pageA instanceof PageModel || !$pageB instanceof PageModel) {
  160.                     return 0;
  161.                 }
  162.                 if (null !== $languages && $pageA->rootLanguage !== $pageB->rootLanguage) {
  163.                     $langA $languages[$pageA->rootLanguage] ?? null;
  164.                     $langB $languages[$pageB->rootLanguage] ?? null;
  165.                     if (null === $langA && null === $langB) {
  166.                         if ($pageA->rootIsFallback) {
  167.                             return -1;
  168.                         }
  169.                         if ($pageB->rootIsFallback) {
  170.                             return 1;
  171.                         }
  172.                         return $pageA->rootSorting <=> $pageB->rootSorting;
  173.                     }
  174.                     if (null === $langA && null !== $langB) {
  175.                         return 1;
  176.                     }
  177.                     if (null !== $langA && null === $langB) {
  178.                         return -1;
  179.                     }
  180.                     return $langA $langB ? -1;
  181.                 }
  182.                 return strnatcasecmp((string) $pageB->alias, (string) $pageA->alias);
  183.             }
  184.         );
  185.     }
  186. }