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