0fb79a45c0613dc7616de9a206b8d94f1a1bc770
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Middleware / SiteResolver.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Frontend\Middleware;
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
18 use Psr\Http\Message\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use Psr\Http\Server\MiddlewareInterface;
21 use Psr\Http\Server\RequestHandlerInterface;
22 use TYPO3\CMS\Core\Database\ConnectionPool;
23 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
24 use TYPO3\CMS\Core\Http\NormalizedParams;
25 use TYPO3\CMS\Core\Site\Entity\Site;
26 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
27 use TYPO3\CMS\Core\Site\SiteFinder;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29
30 /**
31 * Identifies if a site is configured for the request, based on "id" and "L" GET/POST parameters, or the requested
32 * string.
33 *
34 * If a site is found, the request is populated with the found language+site objects. If none is found, the main magic
35 * is handled by the PageResolver middleware.
36 *
37 * In addition to that, TSFE gets the $domainStartPage information resolved and added.
38 */
39 class SiteResolver implements MiddlewareInterface
40 {
41 /**
42 * Resolve the site/language information by checking the page ID or the URL.
43 *
44 * @param ServerRequestInterface $request
45 * @param RequestHandlerInterface $handler
46 * @return ResponseInterface
47 */
48 public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
49 {
50 $finder = GeneralUtility::makeInstance(SiteFinder::class);
51
52 $site = null;
53 $language = null;
54
55 $pageId = $request->getQueryParams()['id'] ?? $request->getParsedBody()['id'] ?? 0;
56 $languageId = $request->getQueryParams()['L'] ?? $request->getParsedBody()['L'] ?? null;
57
58 // 1. Check if we have a _GET/_POST parameter for "id", then a site information can be resolved based.
59 if ($pageId > 0 && $languageId !== null) {
60 // Loop over the whole rootline without permissions to get the actual site information
61 try {
62 $site = $finder->getSiteByPageId((int)$pageId);
63 $language = $site->getLanguageById((int)$languageId);
64 } catch (SiteNotFoundException $e) {
65 }
66 }
67 if (!($language instanceof SiteLanguage)) {
68 // 2. Check if there is a site language, if not, just don't do anything
69 $language = $finder->getSiteLanguageByBase((string)$request->getUri());
70 // @todo: use exception for getSiteLanguageByBase
71 if ($language) {
72 $site = $language->getSite();
73 }
74 }
75
76 // Add language+site information to the PSR-7 request object.
77 if ($language instanceof SiteLanguage && $site instanceof Site) {
78 $request = $request->withAttribute('site', $site);
79 $request = $request->withAttribute('language', $language);
80 $queryParams = $request->getQueryParams();
81 // necessary to calculate the proper hash base
82 $queryParams['L'] = $language->getLanguageId();
83 $request = $request->withQueryParams($queryParams);
84 $_GET['L'] = $queryParams['L'];
85 // At this point, we later get further route modifiers
86 // for bw-compat we update $GLOBALS[TYPO3_REQUEST] to be used later in TSFE.
87 $GLOBALS['TYPO3_REQUEST'] = $request;
88 }
89
90 // Now resolve the root page of the site, the page_id of the current domain
91 if ($site instanceof Site) {
92 $GLOBALS['TSFE']->domainStartPage = $site->getRootPageId();
93 } else {
94 $GLOBALS['TSFE']->domainStartPage = $this->findDomainRecord($request->getAttribute('normalizedParams'), (bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch']);
95 }
96
97 return $handler->handle($request);
98 }
99
100 /**
101 * Looking up a domain record based on server parameters HTTP_HOST
102 *
103 * @param NormalizedParams $requestParams used to get sanitized information of the current request
104 * @param bool $recursive If set, it looks "recursively" meaning that a domain like "123.456.typo3.com" would find a domain record like "typo3.com" if "123.456.typo3.com" or "456.typo3.com" did not exist.
105 * @return int|null Returns the page id of the page where the domain record was found or null if no sys_domain record found.
106 * previously found at \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::findDomainRecord()
107 */
108 protected function findDomainRecord(NormalizedParams $requestParams, $recursive = false): ?int
109 {
110 if ($recursive) {
111 $pageUid = 0;
112 $host = explode('.', $requestParams->getHttpHost());
113 while (count($host)) {
114 $pageUid = $this->getRootPageIdFromDomainRecord(implode('.', $host), $requestParams->getScriptName());
115 if ($pageUid) {
116 return $pageUid;
117 }
118 array_shift($host);
119 }
120 return $pageUid;
121 }
122 return $this->getRootPageIdFromDomainRecord($requestParams->getHttpHost(), $requestParams->getScriptName());
123 }
124
125 /**
126 * Will find the page ID carrying the domain record matching the input domain.
127 *
128 * @param string $domain Domain name to search for. Eg. "www.typo3.com". Typical the HTTP_HOST value.
129 * @param string $path Path for the current script in domain. Eg. "/somedir/subdir". Typ. supplied by \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('SCRIPT_NAME')
130 * @return int|null If found, returns integer with page UID where found. Otherwise null.
131 * previously found at PageRepository::getDomainStartPage
132 */
133 protected function getRootPageIdFromDomainRecord(string $domain, string $path = ''): ?int
134 {
135 list($domain) = explode(':', $domain);
136 $domain = strtolower(preg_replace('/\\.$/', '', $domain));
137 // Removing extra trailing slashes
138 $path = trim(preg_replace('/\\/[^\\/]*$/', '', $path));
139 // Appending to domain string
140 $domain .= $path;
141 $domain = preg_replace('/\\/*$/', '', $domain);
142 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
143 $queryBuilder->getRestrictions()->removeAll();
144 $row = $queryBuilder
145 ->select(
146 'pid'
147 )
148 ->from('sys_domain')
149 ->where(
150 $queryBuilder->expr()->eq(
151 'hidden',
152 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
153 ),
154 $queryBuilder->expr()->orX(
155 $queryBuilder->expr()->eq(
156 'domainName',
157 $queryBuilder->createNamedParameter($domain, \PDO::PARAM_STR)
158 ),
159 $queryBuilder->expr()->eq(
160 'domainName',
161 $queryBuilder->createNamedParameter($domain . '/', \PDO::PARAM_STR)
162 )
163 )
164 )
165 ->setMaxResults(1)
166 ->execute()
167 ->fetch();
168 return $row ? (int)$row['pid'] : null;
169 }
170 }