[!!!][TASK] Remove Extbase mode for non-consistentTranslationOverlayHandling
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Compatibility / LegacyDomainResolver.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Frontend\Compatibility;
5
6 /*
7 * This file is part of the TYPO3 CMS project.
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Core\Cache\CacheManager;
21 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
22 use TYPO3\CMS\Core\Database\ConnectionPool;
23 use TYPO3\CMS\Core\Exception\Page\RootLineException;
24 use TYPO3\CMS\Core\Http\NormalizedParams;
25 use TYPO3\CMS\Core\SingletonInterface;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Core\Utility\RootlineUtility;
28
29 /**
30 * Resolves sys_domain entries when a Request object is given,
31 * or a pageId is given or a rootpage Id is given (= if there is a sys_domain record on that specific page).
32 * Always keeps the sorting in line.
33 *
34 * @internal this functionality is for compatibility reasons and might be removed in TYPO3 v10.0.
35 */
36 class LegacyDomainResolver implements SingletonInterface
37 {
38 /**
39 * Runtime cache of domains per processed page ids.
40 *
41 * @var array
42 */
43 protected $domainDataCache = [];
44
45 /**
46 * @var string
47 */
48 protected $cacheIdentifier = 'legacy-domains';
49
50 /**
51 * @var FrontendInterface
52 */
53 protected $cache;
54
55 /**
56 * all entries in sys_domain grouped by page (pid)
57 * @var array
58 */
59 protected $groupedDomainsPerPage;
60
61 public function __construct()
62 {
63 $this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
64 $this->populate();
65 }
66
67 /**
68 * Builds up all domain records from DB and all routes
69 */
70 protected function populate()
71 {
72 if ($data = $this->cache->get($this->cacheIdentifier)) {
73 // Due to the nature of PhpFrontend, the `<?php` and `#` wraps have to be removed
74 $data = preg_replace('/^<\?php\s*|\s*#$/', '', $data);
75 $this->groupedDomainsPerPage = json_decode($data, true);
76 } else {
77 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_domain');
78 $queryBuilder->getRestrictions()->removeAll();
79 $statement = $queryBuilder
80 ->select('*')
81 ->from('sys_domain')
82 ->orderBy('sorting', 'ASC')
83 ->execute();
84
85 while ($row = $statement->fetch()) {
86 $row['domainName'] = rtrim($row['domainName'], '/');
87 $this->groupedDomainsPerPage[(int)$row['pid']][] = $row;
88 }
89
90 $this->cache->set($this->cacheIdentifier, json_encode($this->groupedDomainsPerPage));
91 }
92 }
93
94 /**
95 * @return array
96 */
97 public function getGroupedDomainsPerPage(): array
98 {
99 return $this->groupedDomainsPerPage ?? [];
100 }
101
102 /**
103 * Obtains a sys_domain record that fits for a given page ID by traversing the rootline up and finding
104 * a suitable page with sys_domain records.
105 * As all sys_domains have been fetched already, the internal grouped list of sys_domains can be used directly.
106 *
107 * Usually used in the Frontend to find out the domain of a page to link to.
108 *
109 * Includes a runtime cache if a frontend request links to the same page multiple times.
110 *
111 * @param int $pageId Target page id
112 * @param ServerRequestInterface|null $currentRequest if given, the domain record is marked with "isCurrentDomain"
113 * @return array|null the sys_domain record if found
114 */
115 public function matchPageId(int $pageId, ServerRequestInterface $currentRequest = null): ?array
116 {
117 // Using array_key_exists() here, nice $result can be NULL
118 // (happens, if there's no domain records defined)
119 if (array_key_exists($pageId, $this->domainDataCache)) {
120 return $this->domainDataCache[$pageId];
121 }
122 try {
123 $this->domainDataCache[$pageId] = $this->resolveDomainEntry(
124 $pageId,
125 $currentRequest
126 );
127 } catch (RootLineException $e) {
128 $this->domainDataCache[$pageId] = null;
129 }
130 return $this->domainDataCache[$pageId];
131 }
132
133 /**
134 * Returns the full sys_domain record, based on a page record, which is assumed the "pid" of the sys_domain record.
135 * Since ordering is taken into account, this is the first sys_domain record on that page Id.
136 *
137 * @param int $pageId
138 * @return array|null
139 */
140 public function matchRootPageId(int $pageId): ?array
141 {
142 return !empty($this->groupedDomainsPerPage[$pageId]) ? reset($this->groupedDomainsPerPage[$pageId]) : null;
143 }
144
145 /**
146 * @param int $pageId
147 * @param ServerRequestInterface|null $currentRequest
148 * @return array|null
149 */
150 protected function resolveDomainEntry(int $pageId, ?ServerRequestInterface $currentRequest): ?array
151 {
152 $rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
153 // walk the rootline downwards from the target page
154 // to the root page, until a domain record is found
155 foreach ($rootLine as $pageInRootline) {
156 $pidInRootline = (int)$pageInRootline['uid'];
157 if (empty($this->groupedDomainsPerPage[$pidInRootline])) {
158 continue;
159 }
160
161 $domainEntriesOfPage = $this->groupedDomainsPerPage[$pidInRootline];
162 foreach ($domainEntriesOfPage as $domainEntry) {
163 if ($domainEntry['hidden']) {
164 continue;
165 }
166 // When no currentRequest is given, let's take the first non-hidden sys_domain page
167 if ($currentRequest === null) {
168 return $domainEntry;
169 }
170 // Otherwise the check should match against the current domain (and set "isCurrentDomain")
171 // Current domain is "forced", however, otherwise the first one is fine
172 if ($this->domainNameMatchesCurrentRequest($domainEntry['domainName'], $currentRequest)) {
173 $result = $domainEntry;
174 $result['isCurrentDomain'] = true;
175 return $result;
176 }
177 }
178 }
179 return null;
180 }
181
182 /**
183 * Whether the given domain name (potentially including a path segment) matches currently requested host or
184 * the host including the path segment
185 *
186 * @param string $domainName
187 * @param ServerRequestInterface|null $request
188 * @return bool
189 */
190 protected function domainNameMatchesCurrentRequest($domainName, ServerRequestInterface $request): bool
191 {
192 /** @var NormalizedParams $normalizedParams */
193 $normalizedParams = $request->getAttribute('normalizedParams');
194 if (!($normalizedParams instanceof NormalizedParams)) {
195 return false;
196 }
197 $currentDomain = $normalizedParams->getHttpHost();
198 // remove the script filename from the path segment.
199 $currentPathSegment = trim(preg_replace('|/[^/]*$|', '', $normalizedParams->getScriptName()));
200 return $currentDomain === $domainName || $currentDomain . $currentPathSegment === $domainName;
201 }
202 }