vendor/contao/core-bundle/src/Routing/Page/PageRegistry.php line 55

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\Page;
  11. use Contao\PageModel;
  12. use Doctrine\DBAL\Connection;
  13. use Symfony\Contracts\Service\ResetInterface;
  14. class PageRegistry implements ResetInterface
  15. {
  16. private const DISABLE_CONTENT_COMPOSITION = ['redirect', 'forward', 'logout'];
  17. private Connection $connection;
  18. private ?array $urlPrefixes = null;
  19. private ?array $urlSuffixes = null;
  20. /**
  21. * @var array<string,RouteConfig>
  22. */
  23. private array $routeConfigs = [];
  24. /**
  25. * @var array<string,DynamicRouteInterface>
  26. */
  27. private array $routeEnhancers = [];
  28. /**
  29. * @var array<string,ContentCompositionInterface|bool>
  30. */
  31. private array $contentComposition = [];
  32. public function __construct(Connection $connection)
  33. {
  34. $this->connection = $connection;
  35. }
  36. /**
  37. * Returns the route for a page.
  38. *
  39. * If no path is configured (is null), the route will accept
  40. * any parameters after the page alias (e.g. "en/page-alias/foo/bar.html").
  41. *
  42. * A route enhancer might enhance the route for a specific page.
  43. */
  44. public function getRoute(PageModel $pageModel): PageRoute
  45. {
  46. $type = $pageModel->type;
  47. $config = $this->routeConfigs[$type] ?? new RouteConfig();
  48. $defaults = $config->getDefaults();
  49. $requirements = $config->getRequirements();
  50. $options = $config->getOptions();
  51. $path = $config->getPath();
  52. if (false === $path) {
  53. $path = '';
  54. $options['compiler_class'] = UnroutablePageRouteCompiler::class;
  55. } elseif (null === $path) {
  56. if ($this->isParameterless($pageModel)) {
  57. $path = '/'.($pageModel->alias ?: $pageModel->id);
  58. } else {
  59. $path = '/'.($pageModel->alias ?: $pageModel->id).'{!parameters}';
  60. $defaults['parameters'] = '';
  61. $requirements['parameters'] = $pageModel->requireItem ? '/.+?' : '(/.+?)?';
  62. }
  63. }
  64. $route = new PageRoute($pageModel, $path, $defaults, $requirements, $options, $config->getMethods());
  65. if (null !== $config->getUrlSuffix()) {
  66. $route->setUrlSuffix($config->getUrlSuffix());
  67. }
  68. if (!isset($this->routeEnhancers[$type])) {
  69. return $route;
  70. }
  71. /** @var DynamicRouteInterface $enhancer */
  72. $enhancer = $this->routeEnhancers[$type];
  73. $enhancer->configurePageRoute($route);
  74. return $route;
  75. }
  76. public function getPathRegex(): array
  77. {
  78. $prefixes = [];
  79. foreach ($this->routeConfigs as $type => $config) {
  80. $regex = $config->getPathRegex();
  81. if (null !== $regex) {
  82. $prefixes[$type] = $regex;
  83. }
  84. }
  85. return $prefixes;
  86. }
  87. public function supportsContentComposition(PageModel $pageModel): bool
  88. {
  89. if (!isset($this->contentComposition[$pageModel->type])) {
  90. return !\in_array($pageModel->type, self::DISABLE_CONTENT_COMPOSITION, true);
  91. }
  92. $service = $this->contentComposition[$pageModel->type];
  93. if ($service instanceof ContentCompositionInterface) {
  94. return $service->supportsContentComposition($pageModel);
  95. }
  96. return (bool) $service;
  97. }
  98. /**
  99. * @return array<string>
  100. */
  101. public function getUrlPrefixes(): array
  102. {
  103. $this->initializePrefixAndSuffix();
  104. return $this->urlPrefixes;
  105. }
  106. /**
  107. * @return array<string>
  108. */
  109. public function getUrlSuffixes(): array
  110. {
  111. $this->initializePrefixAndSuffix();
  112. return $this->urlSuffixes;
  113. }
  114. /**
  115. * @param ContentCompositionInterface|bool $contentComposition
  116. */
  117. public function add(string $type, RouteConfig $config, ?DynamicRouteInterface $routeEnhancer = null, $contentComposition = true): self
  118. {
  119. // Override existing pages with the same identifier
  120. $this->routeConfigs[$type] = $config;
  121. if ($routeEnhancer) {
  122. $this->routeEnhancers[$type] = $routeEnhancer;
  123. }
  124. $this->contentComposition[$type] = $contentComposition;
  125. // Make sure to reset caches when a page type is added
  126. $this->urlPrefixes = null;
  127. $this->urlSuffixes = null;
  128. return $this;
  129. }
  130. public function remove(string $type): self
  131. {
  132. unset(
  133. $this->routeConfigs[$type],
  134. $this->routeEnhancers[$type],
  135. $this->contentComposition[$type]
  136. );
  137. $this->urlPrefixes = $this->urlSuffixes = null;
  138. return $this;
  139. }
  140. public function keys(): array
  141. {
  142. return array_keys($this->routeConfigs);
  143. }
  144. /**
  145. * Checks whether this is a routable page type (see #3415).
  146. */
  147. public function isRoutable(PageModel $page): bool
  148. {
  149. $type = $page->type;
  150. // Any legacy page without route config is routable by default
  151. if (!isset($this->routeConfigs[$type])) {
  152. return true;
  153. }
  154. // Check if page controller is routable
  155. return false !== $this->routeConfigs[$type]->getPath();
  156. }
  157. /**
  158. * @return array<string>
  159. */
  160. public function getUnroutableTypes(): array
  161. {
  162. $types = [];
  163. foreach ($this->routeConfigs as $type => $config) {
  164. if (false === $config->getPath()) {
  165. $types[] = $type;
  166. }
  167. }
  168. return $types;
  169. }
  170. public function reset(): void
  171. {
  172. $this->urlPrefixes = null;
  173. $this->urlSuffixes = null;
  174. }
  175. private function initializePrefixAndSuffix(): void
  176. {
  177. if (null !== $this->urlPrefixes || null !== $this->urlSuffixes) {
  178. return;
  179. }
  180. $results = $this->connection->fetchAllAssociative("SELECT urlPrefix, urlSuffix FROM tl_page WHERE type='root'");
  181. $urlSuffixes = [
  182. array_column($results, 'urlSuffix'),
  183. array_filter(array_map(
  184. static fn (RouteConfig $config) => $config->getUrlSuffix(),
  185. $this->routeConfigs,
  186. )),
  187. ];
  188. foreach ($this->routeConfigs as $config) {
  189. if (null !== ($suffix = $config->getUrlSuffix())) {
  190. $urlSuffixes[] = [$suffix];
  191. }
  192. }
  193. foreach ($this->routeEnhancers as $enhancer) {
  194. $urlSuffixes[] = $enhancer->getUrlSuffixes();
  195. }
  196. $this->urlSuffixes = array_values(array_unique(array_merge(...$urlSuffixes)));
  197. $this->urlPrefixes = array_values(array_unique(array_column($results, 'urlPrefix')));
  198. }
  199. private function isParameterless(PageModel $pageModel): bool
  200. {
  201. if ('redirect' === $pageModel->type) {
  202. return true;
  203. }
  204. return 'forward' === $pageModel->type && !$pageModel->alwaysForward;
  205. }
  206. }