[!!!][TASK] Remove sys_domain and LegacyDomains
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Site / PseudoSiteFinder.php
1 <?php
2 declare(strict_types = 1);
3
4 namespace TYPO3\CMS\Core\Site;
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 TYPO3\CMS\Core\Cache\CacheManager;
20 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
21 use TYPO3\CMS\Core\Database\ConnectionPool;
22 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23 use TYPO3\CMS\Core\Database\Query\Restriction\FrontendWorkspaceRestriction;
24 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
25 use TYPO3\CMS\Core\Exception\Page\PageNotFoundException;
26 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
27 use TYPO3\CMS\Core\Site\Entity\NullSite;
28 use TYPO3\CMS\Core\Site\Entity\PseudoSite;
29 use TYPO3\CMS\Core\Site\Entity\SiteInterface;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Utility\RootlineUtility;
32
33 /**
34 * Methods related to "pseudo-sites" = sites that do not have a configuration yet.
35 * @internal this class will likely be removed in TYPO3 v10.0. Please use SiteMatcher and not the PseudoSiteFinder directly to make use of caching etc.
36 */
37 class PseudoSiteFinder
38 {
39 /**
40 * @var string
41 */
42 protected $cacheIdentifier = 'pseudo-sites';
43
44 /**
45 * @var FrontendInterface
46 */
47 protected $cache;
48
49 /**
50 * @var PseudoSite[]
51 */
52 protected $pseudoSites = [];
53
54 public function __construct()
55 {
56 $this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_core');
57 }
58
59 /**
60 * Fetches all site root pages, all sys_language records and forms pseudo-sites,
61 * but only for the pagetree's that do not have a site configuration available.
62 */
63 protected function populate()
64 {
65 $data = $this->cache->get($this->cacheIdentifier);
66 if (empty($data)) {
67 $allLanguages = $this->getAllLanguageRecords();
68 $availablePages = $this->getAllRootPagesWithoutSiteConfiguration();
69 $this->cache->set($this->cacheIdentifier, json_encode([$allLanguages, $availablePages]));
70 } else {
71 // Due to the nature of PhpFrontend, the `<?php` and `#` wraps have to be removed
72 $data = preg_replace('/^<\?php\s*|\s*#$/', '', $data);
73 list($allLanguages, $availablePages) = json_decode($data, true);
74 }
75
76 $this->pseudoSites = [];
77 foreach ($availablePages as $row) {
78 $rootPageId = (int)$row['uid'];
79 $site = new PseudoSite($rootPageId, [
80 'domains' => [],
81 'languages' => $allLanguages
82 ]);
83 $this->pseudoSites[$rootPageId] = $site;
84 }
85
86 // Now lets an empty Pseudo-Site for visiting things on pid=0
87 $this->pseudoSites[0] = new NullSite($allLanguages);
88 }
89
90 /**
91 * Returns all pseudo sites, including one for "pid=0".
92 *
93 * @return PseudoSite[]
94 */
95 public function findAll(): array
96 {
97 if (empty($this->pseudoSites)) {
98 $this->populate();
99 }
100 return $this->pseudoSites;
101 }
102
103 /**
104 * Fetches all "sys_language" records.
105 *
106 * @return array
107 */
108 protected function getAllLanguageRecords(): array
109 {
110 $languageRecords = [
111 0 => [
112 'languageId' => 0,
113 'title' => 'Default',
114 'flag' => '',
115 ],
116 ];
117
118 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
119 $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
120 $statement = $queryBuilder
121 ->select('*')
122 ->from('sys_language')
123 ->orderBy('sorting')
124 ->execute();
125 while ($row = $statement->fetch()) {
126 $uid = (int)$row['uid'];
127 $languageRecords[$uid] = [
128 'languageId' => $uid,
129 'title' => $row['title'],
130 'iso-639-1' => $row['language_isocode'] ?? '',
131 'flag' => 'flags-' . $row['flag'],
132 'enabled' => !$row['hidden'],
133 ];
134 }
135
136 return $languageRecords;
137 }
138
139 /**
140 * Traverses the rootline of a page up until a PseudoSite was found.
141 * The main use-case here is in the TYPO3 Backend when the middleware tries to detect
142 * a PseudoSite
143 *
144 * @param int $pageId
145 * @param array $rootLine
146 * @return SiteInterface
147 * @throws SiteNotFoundException
148 */
149 public function getSiteByPageId(int $pageId, array $rootLine = null): SiteInterface
150 {
151 $this->findAll();
152 if (isset($this->pseudoSites[$pageId])) {
153 return $this->pseudoSites[$pageId];
154 }
155 if (!is_array($rootLine)) {
156 try {
157 $rootLine = GeneralUtility::makeInstance(RootlineUtility::class, $pageId)->get();
158 } catch (PageNotFoundException $e) {
159 $rootLine = [];
160 }
161 }
162 foreach ($rootLine as $pageInRootLine) {
163 if (isset($this->pseudoSites[(int)$pageInRootLine['uid']])) {
164 return $this->pseudoSites[(int)$pageInRootLine['uid']];
165 }
166 }
167 throw new SiteNotFoundException('No pseudo-site found in root line of page ' . $pageId, 1534710048);
168 }
169
170 /**
171 * Find a site by given root page id
172 *
173 * @param int $rootPageId the page ID (default language)
174 * @return SiteInterface
175 * @throws SiteNotFoundException
176 */
177 public function getSiteByRootPageId(int $rootPageId): SiteInterface
178 {
179 if (empty($this->pseudoSites)) {
180 $this->populate();
181 }
182 if (isset($this->pseudoSites[$rootPageId])) {
183 return $this->pseudoSites[$rootPageId];
184 }
185 throw new SiteNotFoundException('No pseudo-site found for root page id ' . $rootPageId, 1521668982);
186 }
187
188 /**
189 * Loads all sites with a configuration, and takes their rootPageId.
190 *
191 * @return array
192 */
193 protected function getExistingSiteConfigurationRootPageIds(): array
194 {
195 $usedPageIds = [];
196 $finder = GeneralUtility::makeInstance(SiteFinder::class);
197 $sites = $finder->getAllSites();
198 foreach ($sites as $site) {
199 $usedPageIds[] = $site->getRootPageId();
200 }
201 return $usedPageIds;
202 }
203
204 /**
205 * Do a SQL query for root pages (pid=0 or is_siteroot=1) that do not have a site configuration
206 * @return array
207 */
208 protected function getAllRootPagesWithoutSiteConfiguration(): array
209 {
210 $usedPageIds = $this->getExistingSiteConfigurationRootPageIds();
211 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
212 $queryBuilder->getRestrictions()->removeAll()
213 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
214 ->add(GeneralUtility::makeInstance(FrontendWorkspaceRestriction::class, 0, false));
215 $queryBuilder
216 ->select('uid')
217 ->from('pages')
218 ->where(
219 $queryBuilder->expr()->eq('sys_language_uid', 0),
220 $queryBuilder->expr()->orX(
221 $queryBuilder->expr()->eq('pid', 0),
222 $queryBuilder->expr()->eq('is_siteroot', 1)
223 )
224 )
225 ->orderBy('pid')
226 ->addOrderBy('sorting');
227
228 if (!empty($usedPageIds)) {
229 $queryBuilder->andWhere($queryBuilder->expr()->notIn('uid', $usedPageIds));
230 }
231 $availablePages = $queryBuilder->execute()->fetchAll();
232 return is_array($availablePages) ? $availablePages : [];
233 }
234 }