vendor/contao/core-bundle/src/Routing/Matcher/LegacyMatcher.php line 43

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\Matcher;
  11. use Contao\Config;
  12. use Contao\CoreBundle\Framework\ContaoFramework;
  13. use Contao\CoreBundle\Util\LocaleUtil;
  14. use Contao\Input;
  15. use Contao\PageModel;
  16. use Contao\System;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  19. use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
  20. class LegacyMatcher implements RequestMatcherInterface
  21. {
  22. private ContaoFramework $framework;
  23. private RequestMatcherInterface $requestMatcher;
  24. private string $urlSuffix;
  25. private bool $prependLocale;
  26. /**
  27. * @internal
  28. */
  29. public function __construct(ContaoFramework $framework, RequestMatcherInterface $requestMatcher, string $urlSuffix, bool $prependLocale)
  30. {
  31. $this->framework = $framework;
  32. $this->requestMatcher = $requestMatcher;
  33. $this->urlSuffix = $urlSuffix;
  34. $this->prependLocale = $prependLocale;
  35. }
  36. public function matchRequest(Request $request): array
  37. {
  38. $this->framework->initialize(true);
  39. $pathInfo = rawurldecode($request->getPathInfo());
  40. if (
  41. '/' === $pathInfo
  42. || empty($GLOBALS['TL_HOOKS']['getPageIdFromUrl'])
  43. || !\is_array($GLOBALS['TL_HOOKS']['getPageIdFromUrl'])
  44. || ($this->prependLocale && preg_match('@^/([a-z]{2}(-[A-Z]{2})?)/$@', $pathInfo))
  45. ) {
  46. return $this->requestMatcher->matchRequest($request);
  47. }
  48. $locale = null;
  49. $fragments = null;
  50. try {
  51. $match = $this->requestMatcher->matchRequest($request);
  52. $fragments = $this->createFragmentsFromMatch($match);
  53. $locale = isset($match['_locale']) ? LocaleUtil::formatAsLanguageTag($match['_locale']) : null;
  54. } catch (ResourceNotFoundException $e) {
  55. // continue and parse fragments from path
  56. }
  57. if (null === $fragments) {
  58. $pathInfo = $this->parseSuffixAndLanguage($pathInfo, $locale);
  59. $fragments = $this->createFragmentsFromPath($pathInfo);
  60. }
  61. if ($this->prependLocale) {
  62. if (null === $locale) {
  63. throw new ResourceNotFoundException('Locale is missing');
  64. }
  65. $input = $this->framework->getAdapter(Input::class);
  66. $input->setGet('language', $locale);
  67. }
  68. trigger_deprecation('contao/core-bundle', '4.0', 'Using the "getPageIdFromUrl" hook has been deprecated and will no longer work in Contao 5.0.');
  69. $fragments = $this->executeLegacyHook($fragments);
  70. $pathInfo = $this->createPathFromFragments($fragments, $locale);
  71. return $this->requestMatcher->matchRequest($this->rebuildRequest($pathInfo, $request));
  72. }
  73. private function createFragmentsFromMatch(array $match): array
  74. {
  75. $page = $match['pageModel'] ?? null;
  76. $parameters = $match['parameters'] ?? '';
  77. if (!$page instanceof PageModel) {
  78. throw new ResourceNotFoundException('Resource not found');
  79. }
  80. if ('' === $parameters) {
  81. return [$page->alias ?: $page->id];
  82. }
  83. $config = $this->framework->getAdapter(Config::class);
  84. $fragments = [...[$page->alias ?: $page->id], ...explode('/', substr($parameters, 1))];
  85. // Add the second fragment as auto_item if the number of fragments is even
  86. if ($config->get('useAutoItem') && 0 === \count($fragments) % 2) {
  87. array_splice($fragments, 1, 0, ['auto_item']);
  88. }
  89. return $fragments;
  90. }
  91. private function createFragmentsFromPath(string $pathInfo): array
  92. {
  93. $config = $this->framework->getAdapter(Config::class);
  94. $fragments = explode('/', $pathInfo);
  95. // Add the second fragment as auto_item if the number of fragments is even
  96. if ($config->get('useAutoItem') && 0 === \count($fragments) % 2) {
  97. array_splice($fragments, 1, 0, ['auto_item']);
  98. }
  99. return $fragments;
  100. }
  101. private function executeLegacyHook(array $fragments): array
  102. {
  103. $system = $this->framework->getAdapter(System::class);
  104. foreach ($GLOBALS['TL_HOOKS']['getPageIdFromUrl'] as $callback) {
  105. $fragments = $system->importStatic($callback[0])->{$callback[1]}($fragments);
  106. }
  107. // Return if the alias is empty (see #4702 and #4972)
  108. if ('' === $fragments[0]) {
  109. throw new ResourceNotFoundException('Page alias is empty');
  110. }
  111. return $fragments;
  112. }
  113. private function createPathFromFragments(array $fragments, ?string $locale): string
  114. {
  115. $config = $this->framework->getAdapter(Config::class);
  116. if (isset($fragments[1]) && 'auto_item' === $fragments[1] && $config->get('useAutoItem')) {
  117. unset($fragments[1]);
  118. }
  119. $pathInfo = implode('/', $fragments).$this->urlSuffix;
  120. if ($this->prependLocale) {
  121. $pathInfo = $locale.'/'.$pathInfo;
  122. }
  123. return '/'.$pathInfo;
  124. }
  125. private function parseSuffixAndLanguage(string $pathInfo, ?string &$locale): string
  126. {
  127. $suffixLength = \strlen($this->urlSuffix);
  128. if (0 !== $suffixLength) {
  129. if (substr($pathInfo, -$suffixLength) !== $this->urlSuffix) {
  130. throw new ResourceNotFoundException('URL suffix does not match');
  131. }
  132. $pathInfo = substr($pathInfo, 0, -$suffixLength);
  133. }
  134. if (0 === strncmp($pathInfo, '/', 1)) {
  135. $pathInfo = substr($pathInfo, 1);
  136. }
  137. if ($this->prependLocale) {
  138. $matches = [];
  139. if (!preg_match('@^([a-z]{2}(-[A-Z]{2})?)/(.+)$@', $pathInfo, $matches)) {
  140. throw new ResourceNotFoundException('Locale does not match');
  141. }
  142. [, $locale,, $pathInfo] = $matches;
  143. }
  144. return $pathInfo;
  145. }
  146. /**
  147. * @see ChainRouter::rebuildRequest()
  148. */
  149. private function rebuildRequest(string $pathinfo, Request $request): Request
  150. {
  151. $server = $request->server->all();
  152. if ($baseUrl = $request->getBaseUrl()) {
  153. $pathinfo = $baseUrl.$pathinfo;
  154. }
  155. if ($qs = $request->getQueryString()) {
  156. $pathinfo .= '?'.$qs;
  157. }
  158. $server['REQUEST_URI'] = $pathinfo;
  159. return $request->duplicate(null, null, null, null, null, $server);
  160. }
  161. }