‪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;
35 
51 {
52  public function ‪__construct(
53  protected readonly ‪Features $features,
54  protected readonly ‪SiteFinder ‪$finder,
55  protected readonly ‪RequestContextFactory $requestContextFactory
56  ) {}
57 
65  public function ‪refresh()
66  {
68  $cacheManager = GeneralUtility::makeInstance(CacheManager::class);
69  $cacheManager->getCache('runtime')->flushByTag(‪RootlineUtility::RUNTIME_CACHE_TAG);
70  $cacheManager->getCache('rootline')->flush();
71  }
72 
82  public function ‪matchRequest(ServerRequestInterface $request): ‪RouteResultInterface
83  {
84  // Remove script file name (index.php) from request uri
85  $uri = $this->‪canonicalizeUri($request->getUri(), $request);
86  $pageId = $this->‪resolvePageIdQueryParam($request);
87  $languageId = $this->‪resolveLanguageIdQueryParam($request);
88 
89  $routeResult = $this->‪matchSiteByUri($uri, $request);
90 
91  // Allow insecure pageId based site resolution if explicitly enabled and only if both, ?id= and ?L= are defined
92  // (pageId based site resolution without L parameter has always been prohibited, so we do not support that)
93  if (
94  $this->features->isFeatureEnabled('security.frontend.allowInsecureSiteResolutionByQueryParameters') &&
95  $pageId !== null && $languageId !== null
96  ) {
97  return $this->‪matchSiteByQueryParams($pageId, $languageId, $routeResult, $uri);
98  }
99 
100  // Allow the default language to be resolved in case all languages use a prefix
101  // and therefore did not match based on path if an explicit pageId is given,
102  // (example "https://www.example.com/?id=.." was entered, but all languages have "https://www.example.com/lang-key/")
103  // @todo remove this fallback, in order for SiteBaseRedirectResolver to produce a redirect instead (requires functionals to be adapted)
104  if ($pageId !== null && $routeResult->getLanguage() === null) {
105  $routeResult = $routeResult->withLanguage($routeResult->getSite()->getDefaultLanguage());
106  }
107 
108  // adjust the language aspect if it was given by query param `&L` (and ?id is given)
109  // @todo remove, this is added for backwards (and functional tests) compatibility reasons
110  if ($languageId !== null && $pageId !== null) {
111  try {
112  // override/set language by `&L=` query param
113  $routeResult = $routeResult->withLanguage($routeResult->getSite()->getLanguageById($languageId));
114  } catch (\InvalidArgumentException) {
115  // ignore; language id not available
116  }
117  }
118 
119  return $routeResult;
120  }
121 
128  public function ‪matchByPageId(int $pageId, array $rootLine = null): ‪SiteInterface
129  {
130  try {
131  return $this->finder->getSiteByPageId($pageId, $rootLine);
132  } catch (‪SiteNotFoundException) {
133  return new ‪NullSite();
134  }
135  }
136 
141  {
142  $collection = new ‪RouteCollection();
143  foreach ($this->finder->getAllSites() as $site) {
144  // Add the site as entrypoint
145  // @todo Find a way to test only this basic route against chinese characters, as site languages kicking
146  // always in. Do the rawurldecode() here to to be consistent with language preparations.
147 
148  $uri = $site->getBase();
149  $route = new ‪Route(
150  (rawurldecode($uri->getPath()) ?: '/') . '{tail}',
151  ['site' => $site, 'language' => null, 'tail' => ''],
152  array_filter(['tail' => '.*', 'port' => (string)$uri->getPort()]),
153  ['utf8' => true, 'fallback' => true],
154  // @todo Verify if host should here covered with idn_to_ascii() to be consistent with preparation for languages.
155  $uri->getHost() ?: '',
156  $uri->getScheme() === '' ? [] : [$uri->getScheme()]
157  );
158  ‪$identifier = 'site_' . $site->getIdentifier();
159  $collection->add(‪$identifier, $route);
160 
161  // Add all languages
162  foreach ($site->getAllLanguages() as $siteLanguage) {
163  $uri = $siteLanguage->getBase();
164  $route = new ‪Route(
165  (rawurldecode($uri->getPath()) ?: '/') . '{tail}',
166  ['site' => $site, 'language' => $siteLanguage, 'tail' => ''],
167  array_filter(['tail' => '.*', 'port' => (string)$uri->getPort()]),
168  ['utf8' => true],
169  (string)idn_to_ascii($uri->getHost()),
170  $uri->getScheme() === '' ? [] : [$uri->getScheme()]
171  );
172  ‪$identifier = 'site_' . $site->getIdentifier() . '_' . $siteLanguage->getLanguageId();
173  $collection->add(‪$identifier, $route);
174  }
175  }
176  return $collection;
177  }
178 
182  protected function ‪resolvePageIdQueryParam(ServerRequestInterface $request): ?int
183  {
184  $pageId = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? null;
185  if ($pageId === null) {
186  return null;
187  }
188  return (int)$pageId <= 0 ? null : (int)$pageId;
189  }
190 
194  protected function ‪resolveLanguageIdQueryParam(ServerRequestInterface $request): ?int
195  {
196  $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
197  if ($languageId === null) {
198  return null;
199  }
200  return (int)$languageId < 0 ? null : (int)$languageId;
201  }
202 
206  protected function ‪canonicalizeUri(UriInterface $uri, ServerRequestInterface $request): UriInterface
207  {
208  if ($uri->getPath() === '') {
209  return $uri;
210  }
211 
212  $normalizedParams = $request->getAttribute('normalizedParams');
213  if (!$normalizedParams instanceof ‪NormalizedParams) {
214  return $uri;
215  }
216 
217  $urlPath = ltrim($uri->getPath(), '/');
218  $scriptName = ltrim($normalizedParams->getScriptName(), '/');
219  $scriptPath = ltrim($normalizedParams->getSitePath(), '/');
220  if ($scriptName !== '' && str_starts_with($urlPath, $scriptName)) {
221  $urlPath = '/' . $scriptPath . substr($urlPath, mb_strlen($scriptName));
222  $uri = $uri->withPath($urlPath);
223  }
224 
225  return $uri;
226  }
227 
228  protected function ‪matchSiteByUri(UriInterface $uri, ServerRequestInterface $request): ‪SiteRouteResult
229  {
230  $collection = $this->‪getRouteCollectionForAllSites();
231  $requestContext = $this->requestContextFactory->fromUri($uri, $request->getMethod());
232  $matcher = new ‪BestUrlMatcher($collection, $requestContext);
233  try {
235  $match = $matcher->match($uri->getPath());
236  return new ‪SiteRouteResult(
237  $uri,
238  $match['site'],
239  $match['language'],
240  $match['tail']
241  );
242  } catch (NoConfigurationException | ResourceNotFoundException) {
243  return new ‪SiteRouteResult($uri, new ‪NullSite(), null, '');
244  }
245  }
246 
247  protected function ‪matchSiteByQueryParams(
248  int $pageId,
249  int $languageId,
250  ‪SiteRouteResult $fallback,
251  UriInterface $uri,
252  ): ‪SiteRouteResult {
253  try {
254  $site = $this->finder->getSiteByPageId($pageId);
255  } catch (‪SiteNotFoundException) {
256  return $fallback;
257  }
258 
259  try {
260  // override/set language by `&L=` query param
261  $language = $site->getLanguageById($languageId);
262  } catch (\InvalidArgumentException) {
263  return $fallback;
264  }
265 
266  return new ‪SiteRouteResult($uri, $site, $language);
267  }
268 }
‪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:228
‪TYPO3\CMS\Core\Routing\SiteMatcher\resolvePageIdQueryParam
‪positive int resolvePageIdQueryParam(ServerRequestInterface $request)
Definition: SiteMatcher.php:182
‪TYPO3\CMS\Core\Routing\RouteResultInterface
Definition: RouteResultInterface.php:23
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchByPageId
‪matchByPageId(int $pageId, array $rootLine=null)
Definition: SiteMatcher.php:128
‪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:82
‪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\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:247
‪TYPO3\CMS\Core\Routing\SiteMatcher\refresh
‪refresh()
Definition: SiteMatcher.php:65
‪TYPO3\CMS\Core\Routing\SiteMatcher\getRouteCollectionForAllSites
‪getRouteCollectionForAllSites()
Definition: SiteMatcher.php:140
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Core\Routing\SiteMatcher\canonicalizeUri
‪canonicalizeUri(UriInterface $uri, ServerRequestInterface $request)
Definition: SiteMatcher.php:206
‪TYPO3\CMS\Core\Routing\Route
Definition: Route.php:32
‪TYPO3\CMS\Core\Routing\SiteMatcher\__construct
‪__construct(protected readonly Features $features, protected readonly SiteFinder $finder, protected readonly RequestContextFactory $requestContextFactory)
Definition: SiteMatcher.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Core\Routing\SiteMatcher\resolveLanguageIdQueryParam
‪positive int resolveLanguageIdQueryParam(ServerRequestInterface $request)
Definition: SiteMatcher.php:194
‪TYPO3\CMS\Core\Routing\SiteMatcher
Definition: SiteMatcher.php:51
‪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