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