2 declare(strict_types
= 1);
4 namespace TYPO3\CMS\Core\Site
;
7 * This file is part of the TYPO3 CMS project.
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.
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
16 * The TYPO3 project - inspiring people to share!
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
;
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.
37 class PseudoSiteFinder
42 protected $cacheIdentifier = 'pseudo-sites';
45 * @var FrontendInterface
52 protected $pseudoSites = [];
54 public function __construct()
56 $this->cache
= GeneralUtility
::makeInstance(CacheManager
::class)->getCache('cache_core');
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.
63 protected function populate()
65 $data = $this->cache
->get($this->cacheIdentifier
);
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]));
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);
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
84 unset($groupedDomains[$rootPageId]);
85 $this->pseudoSites
[$rootPageId] = $site;
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
94 $this->pseudoSites
[$rootPageId] = $site;
97 // Now lets an empty Pseudo-Site for visiting things on pid=0
98 $this->pseudoSites
[0] = new NullSite($allLanguages);
102 * Returns all pseudo sites, including one for "pid=0".
104 * @return PseudoSite[]
106 public function findAll(): array
108 if (empty($this->pseudoSites
)) {
111 return $this->pseudoSites
;
115 * Fetches all "sys_language" records.
119 protected function getAllLanguageRecords(): array
124 'title' => 'Default',
129 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable('sys_language');
130 $queryBuilder->getRestrictions()->removeByType(HiddenRestriction
::class);
131 $statement = $queryBuilder
133 ->from('sys_language')
136 while ($row = $statement->fetch()) {
138 $languageRecords[$uid] = [
139 'languageId' => $uid,
140 'title' => $row['title'],
141 'iso' => $row['language_isocode'] ??
'',
142 'flag' => 'flags-' . $row['flag'],
146 return $languageRecords;
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
155 * @param array $rootLine
156 * @return SiteInterface
157 * @throws SiteNotFoundException
159 public function getSiteByPageId(int $pageId, array $rootLine = null): SiteInterface
163 return $this->pseudoSites
[0];
165 if (!is_array($rootLine)) {
167 $rootLine = GeneralUtility
::makeInstance(RootlineUtility
::class, $pageId)->get();
168 } catch (PageNotFoundException
$e) {
172 foreach ($rootLine as $pageInRootLine) {
173 if (isset($this->pseudoSites
[(int)$pageInRootLine['uid']])) {
174 return $this->pseudoSites
[(int)$pageInRootLine['uid']];
177 throw new SiteNotFoundException('No pseudo-site found in root line of page ' . $pageId, 1534710048);
181 * Loads all sites with a configuration, and takes their rootPageId.
185 protected function getExistingSiteConfigurationRootPageIds(): array
188 $finder = GeneralUtility
::makeInstance(SiteFinder
::class);
189 $sites = $finder->getAllSites();
190 foreach ($sites as $site) {
191 $usedPageIds[] = $site->getRootPageId();
197 * Do a SQL query for root pages (pid=0 or is_siteroot=1) that do not have a site configuration
200 protected function getAllRootPagesWithoutSiteConfiguration(): array
202 $usedPageIds = $this->getExistingSiteConfigurationRootPageIds();
203 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable('pages');
204 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility
::makeInstance(DeletedRestriction
::class));
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)
216 ->addOrderBy('sorting');
218 if (!empty($usedPageIds)) {
219 $queryBuilder->andWhere($queryBuilder->expr()->notIn('uid', $usedPageIds));
221 $availablePages = $queryBuilder->execute()->fetchAll();
222 return is_array($availablePages) ?
$availablePages : [];