‪TYPO3CMS  ‪main
SiteMatcher.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ServerRequestInterface;
21 use Psr\Http\Message\UriInterface;
22 use Symfony\Component\Routing\Exception\NoConfigurationException;
23 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
36 
52 {
53  public function ‪__construct(
54  protected readonly ‪Features $features,
55  protected readonly ‪SiteFinder ‪$finder,
56  protected readonly ‪RequestContextFactory $requestContextFactory
57  ) {}
58 
66  public function ‪refresh()
67  {
69  $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
70  $cacheManager->getCache('runtime')->flushByTag(‪RootlineUtility::RUNTIME_CACHE_TAG);
71  $cacheManager->getCache('rootline')->flush();
72  }
73 
83  public function ‪matchRequest(ServerRequestInterface $request): ‪RouteResultInterface
84  {
85  // Remove script file name (index.php) from request uri
86  $uri = $this->‪canonicalizeUri($request->getUri(), $request);
87  $pageId = $this->‪resolvePageIdQueryParam($request);
88  $languageId = $this->‪resolveLanguageIdQueryParam($request);
89 
90  $routeResult = $this->‪matchSiteByUri($uri, $request);
91 
92  // Allow insecure pageId based site resolution if explicitly enabled and only if both, ?id= and ?L= are defined
93  // (pageId based site resolution without L parameter has always been prohibited, so we do not support that)
94  if (
95  $this->features->isFeatureEnabled('security.frontend.allowInsecureSiteResolutionByQueryParameters') &&
96  $pageId !== null && $languageId !== null
97  ) {
98  return $this->‪matchSiteByQueryParams($pageId, $languageId, $routeResult, $uri);
99  }
100 
101  // Allow the default language to be resolved in case all languages use a prefix
102  // and therefore did not match based on path if an explicit pageId is given,
103  // (example "https://www.example.com/?id=.." was entered, but all languages have "https://www.example.com/lang-key/")
104  // @todo remove this fallback, in order for SiteBaseRedirectResolver to produce a redirect instead (requires functionals to be adapted)
105  if ($pageId !== null && $routeResult->getLanguage() === null) {
106  $routeResult = $routeResult->withLanguage($routeResult->getSite()->getDefaultLanguage());
107  }
108 
109  // adjust the language aspect if it was given by query param `&L` (and ?id is given)
110  // @todo remove, this is added for backwards (and functional tests) compatibility reasons
111  if ($languageId !== null && $pageId !== null) {
112  try {
113  // override/set language by `&L=` query param
114  $routeResult = $routeResult->withLanguage($routeResult->getSite()->getLanguageById($languageId));
115  } catch (\InvalidArgumentException) {
116  // ignore; language id not available
117  }
118  }
119 
120  return $routeResult;
121  }
122 
129  public function ‪matchByPageId(int $pageId, array $rootLine = null): ‪SiteInterface
130  {
131  try {
132  return $this->finder->getSiteByPageId($pageId, $rootLine);
133  } catch (‪SiteNotFoundException) {
134  return new ‪NullSite();
135  }
136  }
137 
142  {
143  $collection = new ‪RouteCollection();
144  foreach ($this->finder->getAllSites() as $site) {
145  // Add the site as entrypoint
146  // @todo Find a way to test only this basic route against chinese characters, as site languages kicking
147  // always in. Do the rawurldecode() here to to be consistent with language preparations.
148 
149  $uri = $site->getBase();
150  $route = new ‪Route(
151  (rawurldecode($uri->getPath()) ?: '/') . '{tail}',
152  ['site' => $site, 'language' => null, 'tail' => ''],
153  array_filter(['tail' => '.*', 'port' => (string)$uri->getPort()]),
154  ['utf8' => true, 'fallback' => true],
155  // @todo Verify if host should here covered with idn_to_ascii() to be consistent with preparation for languages.
156  $uri->getHost() ?: '',
157  $uri->getScheme() === '' ? [] : [$uri->getScheme()]
158  );
159  ‪$identifier = 'site_' . $site->getIdentifier();
160  $collection->add(‪$identifier, $route);
161 
162  // Add all languages
163  foreach ($site->getAllLanguages() as $siteLanguage) {
164  $uri = $siteLanguage->getBase();
165  $route = new ‪Route(
166  (rawurldecode($uri->getPath()) ?: '/') . '{tail}',
167  ['site' => $site, 'language' => $siteLanguage, 'tail' => ''],
168  array_filter(['tail' => '.*', 'port' => (string)$uri->getPort()]),
169  ['utf8' => true],
170  (string)idn_to_ascii($uri->getHost()),
171  $uri->getScheme() === '' ? [] : [$uri->getScheme()]
172  );
173  ‪$identifier = 'site_' . $site->getIdentifier() . '_' . $siteLanguage->getLanguageId();
174  $collection->add(‪$identifier, $route);
175  }
176  }
177  return $collection;
178  }
179 
183  protected function ‪resolvePageIdQueryParam(ServerRequestInterface $request): ?int
184  {
185  $pageId = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? null;
186  if ($pageId === null) {
187  return null;
188  }
190  return null;
191  }
192  return (int)$pageId <= 0 ? null : (int)$pageId;
193  }
194 
198  protected function ‪resolveLanguageIdQueryParam(ServerRequestInterface $request): ?int
199  {
200  $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
201  if ($languageId === null) {
202  return null;
203  }
204  if (!‪MathUtility::canBeInterpretedAsInteger($languageId)) {
205  return null;
206  }
207  return (int)$languageId < 0 ? null : (int)$languageId;
208  }
209 
213  protected function ‪canonicalizeUri(UriInterface $uri, ServerRequestInterface $request): UriInterface
214  {
215  if ($uri->getPath() === '') {
216  return $uri;
217  }
218 
219  $normalizedParams = $request->getAttribute('normalizedParams');
220  if (!$normalizedParams instanceof ‪NormalizedParams) {
221  return $uri;
222  }
223 
224  $urlPath = ltrim($uri->getPath(), '/');
225  $scriptName = ltrim($normalizedParams->getScriptName(), '/');
226  $scriptPath = ltrim($normalizedParams->getSitePath(), '/');
227  if ($scriptName !== '' && str_starts_with($urlPath, $scriptName)) {
228  $urlPath = '/' . $scriptPath . substr($urlPath, mb_strlen($scriptName));
229  $uri = $uri->withPath($urlPath);
230  }
231 
232  return $uri;
233  }
234 
235  protected function ‪matchSiteByUri(UriInterface $uri, ServerRequestInterface $request): ‪SiteRouteResult
236  {
237  $collection = $this->‪getRouteCollectionForAllSites();
238  $requestContext = $this->requestContextFactory->fromUri($uri, $request->getMethod());
239  $matcher = new ‪BestUrlMatcher($collection, $requestContext);
240  try {
242  $match = $matcher->match($uri->getPath());
243  return new ‪SiteRouteResult(
244  $uri,
245  $match['site'],
246  $match['language'],
247  $match['tail']
248  );
249  } catch (NoConfigurationException | ResourceNotFoundException) {
250  return new ‪SiteRouteResult($uri, new ‪NullSite(), null, '');
251  }
252  }
253 
254  protected function ‪matchSiteByQueryParams(
255  int $pageId,
256  int $languageId,
257  ‪SiteRouteResult $fallback,
258  UriInterface $uri,
259  ): ‪SiteRouteResult {
260  try {
261  $site = $this->finder->getSiteByPageId($pageId);
262  } catch (‪SiteNotFoundException) {
263  return $fallback;
264  }
265 
266  try {
267  // override/set language by `&L=` query param
268  $language = $site->getLanguageById($languageId);
269  } catch (\InvalidArgumentException) {
270  return $fallback;
271  }
272 
273  return new ‪SiteRouteResult($uri, $site, $language);
274  }
275 }
‪TYPO3\CMS\Core\Site\Entity\SiteInterface
Definition: SiteInterface.php:26
‪TYPO3\CMS\Core\Utility\RootlineUtility\RUNTIME_CACHE_TAG
‪const RUNTIME_CACHE_TAG
Definition: RootlineUtility.php:42
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchSiteByUri
‪matchSiteByUri(UriInterface $uri, ServerRequestInterface $request)
Definition: SiteMatcher.php:235
‪TYPO3\CMS\Core\Routing\SiteMatcher\resolvePageIdQueryParam
‪positive int resolvePageIdQueryParam(ServerRequestInterface $request)
Definition: SiteMatcher.php:183
‪TYPO3\CMS\Core\Routing\RouteResultInterface
Definition: RouteResultInterface.php:23
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchByPageId
‪matchByPageId(int $pageId, array $rootLine=null)
Definition: SiteMatcher.php:129
‪TYPO3\CMS\Core\Routing\RouteCollection
Definition: RouteCollection.php:30
‪TYPO3\CMS\Core\Utility\RootlineUtility
Definition: RootlineUtility.php:40
‪TYPO3\CMS\Core\Site\Entity\NullSite
Definition: NullSite.php:32
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:25
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchRequest
‪matchRequest(ServerRequestInterface $request)
Definition: SiteMatcher.php:83
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\Routing
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:27
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Core\Routing\SiteRouteResult
Definition: SiteRouteResult.php:30
‪TYPO3\CMS\Core\Routing\BestUrlMatcher
Definition: BestUrlMatcher.php:29
‪TYPO3\CMS\Core\Configuration\Features
Definition: Features.php:56
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchSiteByQueryParams
‪matchSiteByQueryParams(int $pageId, int $languageId, SiteRouteResult $fallback, UriInterface $uri,)
Definition: SiteMatcher.php:254
‪TYPO3\CMS\Core\Routing\SiteMatcher\refresh
‪refresh()
Definition: SiteMatcher.php:66
‪TYPO3\CMS\Core\Routing\SiteMatcher\getRouteCollectionForAllSites
‪getRouteCollectionForAllSites()
Definition: SiteMatcher.php:141
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Core\Routing\SiteMatcher\canonicalizeUri
‪canonicalizeUri(UriInterface $uri, ServerRequestInterface $request)
Definition: SiteMatcher.php:213
‪TYPO3\CMS\Core\Routing\Route
Definition: Route.php:32
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\Routing\SiteMatcher\__construct
‪__construct(protected readonly Features $features, protected readonly SiteFinder $finder, protected readonly RequestContextFactory $requestContextFactory)
Definition: SiteMatcher.php:53
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Routing\SiteMatcher\resolveLanguageIdQueryParam
‪positive int resolveLanguageIdQueryParam(ServerRequestInterface $request)
Definition: SiteMatcher.php:198
‪TYPO3\CMS\Core\Routing\SiteMatcher
Definition: SiteMatcher.php:52
‪TYPO3\CMS\Core\Http\NormalizedParams
Definition: NormalizedParams.php:38
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Routing\RequestContextFactory
Definition: RequestContextFactory.php:30