‪TYPO3CMS  10.4
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 Symfony\Component\Routing\Exception\NoConfigurationException;
22 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
23 use Symfony\Component\Routing\Matcher\UrlMatcher;
24 use Symfony\Component\Routing\RequestContext;
35 
51 {
55  protected ‪$finder;
56 
62  public function ‪__construct(‪SiteFinder ‪$finder = null)
63  {
64  $this->finder = ‪$finder ?? GeneralUtility::makeInstance(SiteFinder::class);
65  }
66 
74  public function ‪refresh()
75  {
78  GeneralUtility::makeInstance(CacheManager::class)->getCache('rootline')->flush();
79  }
80 
91  public function ‪matchRequest(ServerRequestInterface $request): ‪RouteResultInterface
92  {
93  $site = new ‪NullSite();
94  $language = null;
95  $defaultLanguage = null;
96 
97  $pageId = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0;
98 
99  // First, check if we have a _GET/_POST parameter for "id", then a site information can be resolved based.
100  if ($pageId > 0) {
101  // Loop over the whole rootline without permissions to get the actual site information
102  try {
103  $site = $this->finder->getSiteByPageId((int)$pageId);
104  // If a "L" parameter is given, we take that one into account.
105  $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
106  if ($languageId !== null) {
107  $language = $site->getLanguageById((int)$languageId);
108  } else {
109  // Use this later below
110  $defaultLanguage = $site->getDefaultLanguage();
111  }
112  } catch (‪SiteNotFoundException $e) {
113  // No site found by the given page
114  } catch (\InvalidArgumentException $e) {
115  // The language fetched by getLanguageById() was not available, now the PSR-15 middleware
116  // redirects to the default page.
117  }
118  }
119 
120  // No language found at this point means that the URL was not used with a valid "?id=1&L=2" parameter
121  // which resulted in a site / language combination that was found. Now, the matching is done
122  // on the incoming URL.
123  if (!($language instanceof ‪SiteLanguage)) {
124  $collection = $this->‪getRouteCollectionForAllSites();
125  $context = new RequestContext(
126  '',
127  $request->getMethod(),
128  (string)‪HttpUtility::idn_to_ascii($request->getUri()->getHost()),
129  $request->getUri()->getScheme(),
130  // Ports are only necessary for URL generation in Symfony which is not used by TYPO3
131  80,
132  443,
133  $request->getUri()->getPath()
134  );
135  $matcher = new UrlMatcher($collection, $context);
136  try {
137  $result = $matcher->match($request->getUri()->getPath());
138  return new ‪SiteRouteResult(
139  $request->getUri(),
140  $result['site'],
141  // if no language is found, this usually results due to "/" called instead of "/fr/"
142  // but it could also be the reason that "/index.php?id=23" was called, so the default
143  // language is used as a fallback here then.
144  $result['language'] ?? $defaultLanguage,
145  $result['tail']
146  );
147  } catch (NoConfigurationException | ResourceNotFoundException $e) {
148  // At this point we discard a possible found site via ?id=123
149  // Because ?id=123 _can_ only work if the actual domain/site base works
150  // so www.domain-without-site-configuration/index.php?id=123 (where 123 is a page referring
151  // to a page within a site configuration will never be resolved here) properly
152  $site = new ‪NullSite();
153  }
154  }
155 
156  return new ‪SiteRouteResult($request->getUri(), $site, $language);
157  }
158 
167  public function ‪matchByPageId(int $pageId, array $rootLine = null): ‪SiteInterface
168  {
169  try {
170  return $this->finder->getSiteByPageId($pageId, $rootLine);
171  } catch (‪SiteNotFoundException $e) {
172  return new ‪NullSite();
173  }
174  }
175 
182  {
183  $groupedRoutes = [];
184  foreach ($this->finder->getAllSites() as $site) {
185  // Add the site as entrypoint
186  // @todo Find a way to test only this basic route against chinese characters, as site languages kicking
187  // always in. Do the rawurldecode() here to to be consistent with language preparations.
188  $uri = $site->getBase();
189  $route = new ‪Route(
190  (rawurldecode($uri->getPath()) ?: '/') . '{tail}',
191  ['site' => $site, 'language' => null, 'tail' => ''],
192  array_filter(['tail' => '.*', 'port' => (string)$uri->getPort()]),
193  ['utf8' => true],
194  // @todo Verify if host should here covered with idn_to_ascii() to be consistent with preparation for languages.
195  $uri->getHost() ?: '',
196  $uri->getScheme() === '' ? [] : [$uri->getScheme()]
197  );
198  $identifier = 'site_' . $site->getIdentifier();
199  $groupedRoutes[($uri->getScheme() ?: '-') . ($uri->getHost() ?: '-')][$uri->getPath() ?: '/'][$identifier] = $route;
200  // Add all languages
201  foreach ($site->getAllLanguages() as $siteLanguage) {
202  $uri = $siteLanguage->getBase();
203  $route = new ‪Route(
204  (rawurldecode($uri->getPath()) ?: '/') . '{tail}',
205  ['site' => $site, 'language' => $siteLanguage, 'tail' => ''],
206  array_filter(['tail' => '.*', 'port' => (string)$uri->getPort()]),
207  ['utf8' => true],
208  (string)(‪HttpUtility::idn_to_ascii($uri->getHost()) ?: ''),
209  $uri->getScheme() === '' ? [] : [$uri->getScheme()]
210  );
211  $identifier = 'site_' . $site->getIdentifier() . '_' . $siteLanguage->getLanguageId();
212  $groupedRoutes[($uri->getScheme() ?: '-') . ($uri->getHost() ?: '-')][$uri->getPath() ?: '/'][$identifier] = $route;
213  }
214  }
215  return $this->‪createRouteCollectionFromGroupedRoutes($groupedRoutes);
216  }
217 
225  protected function ‪createRouteCollectionFromGroupedRoutes(array $groupedRoutes): ‪RouteCollection
226  {
227  $collection = new ‪RouteCollection();
228  // Ensure more generic routes containing '-' in host identifier, processed at last
229  krsort($groupedRoutes);
230  foreach ($groupedRoutes as $groupedRoutesPerHost) {
231  krsort($groupedRoutesPerHost);
232  foreach ($groupedRoutesPerHost as $groupedRoutesPerPath) {
233  krsort($groupedRoutesPerPath);
234  foreach ($groupedRoutesPerPath as $identifier => $route) {
235  $collection->add($identifier, $route);
236  }
237  }
238  }
239  return $collection;
240  }
241 }
‪TYPO3\CMS\Core\Site\Entity\SiteInterface
Definition: SiteInterface.php:26
‪TYPO3\CMS\Core\Utility\HttpUtility\idn_to_ascii
‪static string bool idn_to_ascii(string $domain)
Definition: HttpUtility.php:195
‪TYPO3\CMS\Core\Routing\RouteResultInterface
Definition: RouteResultInterface.php:24
‪TYPO3\CMS\Core\Routing\RouteCollection
Definition: RouteCollection.php:28
‪TYPO3\CMS\Core\Utility\RootlineUtility
Definition: RootlineUtility.php:39
‪TYPO3\CMS\Core\Utility\RootlineUtility\purgeCaches
‪static purgeCaches()
Definition: RootlineUtility.php:160
‪TYPO3\CMS\Core\Site\Entity\NullSite
Definition: NullSite.php:32
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:26
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\Routing
‪TYPO3\CMS\Core\Routing\SiteMatcher\__construct
‪__construct(SiteFinder $finder=null)
Definition: SiteMatcher.php:61
‪TYPO3\CMS\Core\Site\Entity\SiteLanguage
Definition: SiteLanguage.php:26
‪TYPO3\CMS\Core\Routing\SiteRouteResult
Definition: SiteRouteResult.php:30
‪TYPO3\CMS\Core\Routing\SiteMatcher\getRouteCollectionForAllSites
‪RouteCollection getRouteCollectionForAllSites()
Definition: SiteMatcher.php:180
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:35
‪TYPO3\CMS\Core\Routing\SiteMatcher\$finder
‪SiteFinder $finder
Definition: SiteMatcher.php:54
‪TYPO3\CMS\Core\Routing\SiteMatcher\refresh
‪refresh()
Definition: SiteMatcher.php:73
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:23
‪TYPO3\CMS\Core\Routing\SiteMatcher\createRouteCollectionFromGroupedRoutes
‪RouteCollection createRouteCollectionFromGroupedRoutes(array $groupedRoutes)
Definition: SiteMatcher.php:224
‪TYPO3\CMS\Core\Routing\Route
Definition: Route.php:32
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchRequest
‪RouteResultInterface matchRequest(ServerRequestInterface $request)
Definition: SiteMatcher.php:90
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:24
‪TYPO3\CMS\Core\Routing\SiteMatcher\matchByPageId
‪SiteInterface matchByPageId(int $pageId, array $rootLine=null)
Definition: SiteMatcher.php:166
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪TYPO3\CMS\Core\Routing\SiteMatcher
Definition: SiteMatcher.php:51