Passed
Push — release-11.5.x ( 385fe8...cd49eb )
by Rafael
53:22 queued 14:05
created

SiteRepository::getAvailableTYPO3ManagedSites()   A

Complexity

Conditions 6
Paths 14

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6.105

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 22
ccs 12
cts 14
cp 0.8571
rs 9.2222
c 0
b 0
f 0
cc 6
nc 14
nop 1
crap 6.105
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace ApacheSolrForTypo3\Solr\Domain\Site;
19
20
use ApacheSolrForTypo3\Solr\Domain\Index\Queue\RecordMonitor\Helper\RootPageResolver;
21
use ApacheSolrForTypo3\Solr\FrontendEnvironment;
22
use ApacheSolrForTypo3\Solr\System\Cache\TwoLevelCache;
23
use ApacheSolrForTypo3\Solr\System\Configuration\ExtensionConfiguration;
24
use ApacheSolrForTypo3\Solr\System\Records\Pages\PagesRepository;
25
use ApacheSolrForTypo3\Solr\System\Util\SiteUtility;
26
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
27
use InvalidArgumentException;
28
use Throwable;
29
use TYPO3\CMS\Backend\Utility\BackendUtility;
30
use TYPO3\CMS\Core\Registry;
31
use TYPO3\CMS\Core\Site\Entity\Site as CoreSite;
32
use TYPO3\CMS\Core\Site\SiteFinder;
33
use TYPO3\CMS\Core\Utility\GeneralUtility;
34
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
35
36
/**
37
 * SiteRepository
38
 *
39
 * Responsible to retrieve instances of Site objects
40
 *
41
 * @author Thomas Hohn <[email protected]>
42
 */
43
class SiteRepository
44
{
45
    /**
46
     * Rootpage resolver
47
     *
48
     * @var RootPageResolver
49
     */
50
    protected RootPageResolver $rootPageResolver;
51
52
    /**
53
     * @var TwoLevelCache
54
     */
55
    protected TwoLevelCache $runtimeCache;
56
57
    /**
58
     * @var Registry
59
     */
60
    protected Registry $registry;
61
62
    /**
63
     * @var SiteFinder
64
     */
65
    protected SiteFinder $siteFinder;
66
67
    /**
68
     * @var ExtensionConfiguration
69
     */
70
    protected ExtensionConfiguration $extensionConfiguration;
71
72
    /**
73
     * @var FrontendEnvironment
74
     */
75
    protected FrontendEnvironment $frontendEnvironment;
76
77
    /**
78
     * SiteRepository constructor.
79
     *
80
     * @param RootPageResolver|null $rootPageResolver
81
     * @param TwoLevelCache|null $twoLevelCache
82
     * @param Registry|null $registry
83
     * @param SiteFinder|null $siteFinder
84
     * @param ExtensionConfiguration|null $extensionConfiguration
85
     * @param FrontendEnvironment|null $frontendEnvironment
86
     */
87 238
    public function __construct(
88
        RootPageResolver $rootPageResolver = null,
89
        TwoLevelCache $twoLevelCache = null,
90
        Registry $registry = null,
91
        SiteFinder $siteFinder = null,
92
        ExtensionConfiguration $extensionConfiguration = null,
93
        FrontendEnvironment $frontendEnvironment = null
94
    ) {
95 238
        $this->rootPageResolver = $rootPageResolver ?? GeneralUtility::makeInstance(RootPageResolver::class);
96 238
        $this->runtimeCache = $twoLevelCache ?? GeneralUtility::makeInstance(TwoLevelCache::class, /** @scrutinizer ignore-type */'runtime');
97 238
        $this->registry = $registry ?? GeneralUtility::makeInstance(Registry::class);
98 238
        $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
99 238
        $this->extensionConfiguration = $extensionConfiguration ?? GeneralUtility::makeInstance(ExtensionConfiguration::class);
100 238
        $this->frontendEnvironment = $frontendEnvironment ?? GeneralUtility::makeInstance(FrontendEnvironment::class);
101
    }
102
103
    /**
104
     * Gets the Site for a specific page ID.
105
     *
106
     * @param int $pageId The page ID to get a Site object for.
107
     * @param string $mountPointIdentifier
108
     * @return SiteInterface Site for the given page ID.
109
     * @throws DBALDriverException
110
     */
111 156
    public function getSiteByPageId(int $pageId, string $mountPointIdentifier = '')
112
    {
113 156
        $rootPageId = $this->rootPageResolver->getRootPageId($pageId, false, $mountPointIdentifier);
114 156
        return $this->getSiteByRootPageId($rootPageId);
115
    }
116
117
    /**
118
     * Gets the Site for a specific root page-id.
119
     *
120
     * @param int $rootPageId Root page Id to get a Site object for.
121
     * @return SiteInterface Site for the given page-id.
122
     * @throws DBALDriverException
123
     */
124 181
    public function getSiteByRootPageId(int $rootPageId)
125
    {
126 181
        $cacheId = 'SiteRepository' . '_' . 'getSiteByPageId' . '_' . $rootPageId;
127
128 181
        $methodResult = $this->runtimeCache->get($cacheId);
129 181
        if (!empty($methodResult)) {
130 149
            return $methodResult;
131
        }
132
133 181
        $methodResult = $this->buildSite($rootPageId);
134 174
        $this->runtimeCache->set($cacheId, $methodResult);
135
136 174
        return $methodResult;
137
    }
138
139
    /**
140
     * Returns the first available Site.
141
     *
142
     * @param bool $stopOnInvalidSite
143
     * @return Site|null
144
     * @throws DBALDriverException
145
     * @throws Throwable
146
     */
147 21
    public function getFirstAvailableSite(bool $stopOnInvalidSite = false): ?Site
148
    {
149 21
        $sites = $this->getAvailableSites($stopOnInvalidSite);
150 21
        return array_shift($sites);
151
    }
152
153
    /**
154
     * Gets all available TYPO3 sites with Solr configured.
155
     *
156
     * @param bool $stopOnInvalidSite
157
     * @return Site[] An array of available sites
158
     * @throws DBALDriverException
159
     * @throws Throwable
160
     */
161 104
    public function getAvailableSites(bool $stopOnInvalidSite = false): array
162
    {
163 104
        $cacheId = 'SiteRepository' . '_' . 'getAvailableSites';
164
165 104
        $sites = $this->runtimeCache->get($cacheId);
166 104
        if (!empty($sites)) {
167 22
            return $sites;
168
        }
169
170 104
        $sites = $this->getAvailableTYPO3ManagedSites($stopOnInvalidSite);
171 104
        $this->runtimeCache->set($cacheId, $sites);
172
173 104
        return $sites ?? [];
174
    }
175
176
    /**
177
     * @param bool $stopOnInvalidSite
178
     * @return array
179
     * @throws DBALDriverException
180
     * @throws Throwable
181
     */
182 104
    protected function getAvailableTYPO3ManagedSites(bool $stopOnInvalidSite): array
183
    {
184 104
        $typo3ManagedSolrSites = [];
185 104
        $typo3Sites = $this->siteFinder->getAllSites();
186 104
        foreach ($typo3Sites as $typo3Site) {
187
            try {
188 104
                $rootPageId = $typo3Site->getRootPageId();
189 104
                if (isset($typo3ManagedSolrSites[$rootPageId])) {
190
                    //get each site only once
191
                    continue;
192
                }
193 104
                $typo3ManagedSolrSite = $this->buildSite($rootPageId);
194 104
                if ($typo3ManagedSolrSite->isEnabled()) {
195 104
                    $typo3ManagedSolrSites[$rootPageId] = $typo3ManagedSolrSite;
196
                }
197 22
            } catch (Throwable $e) {
198 22
                if ($stopOnInvalidSite) {
199
                    throw $e;
200
                }
201
            }
202
        }
203 104
        return $typo3ManagedSolrSites;
204
    }
205
206
    /**
207
     * Creates an instance of the Site object.
208
     *
209
     * @param int $rootPageId
210
     * @return SiteInterface
211
     * @throws DBALDriverException
212
     */
213 204
    protected function buildSite(int $rootPageId)
214
    {
215 204
        $rootPageRecord = BackendUtility::getRecord('pages', $rootPageId);
216 204
        if (empty($rootPageRecord)) {
217 27
            throw new InvalidArgumentException(
218 27
                "The rootPageRecord for the given rootPageRecord ID '$rootPageId' could not be found in the database and can therefore not be used as site root rootPageRecord.",
219 27
                1487326416
220 27
            );
221
        }
222
223 198
        $this->validateRootPageRecord($rootPageId, $rootPageRecord);
224
225 197
        return $this->buildTypo3ManagedSite($rootPageRecord);
226
    }
227
228
    /**
229
     * @param string $domain
230
     * @return string
231
     */
232 197
    protected function getSiteHashForDomain(string $domain): string
233
    {
234
        /** @var $siteHashService SiteHashService */
235 197
        $siteHashService = GeneralUtility::makeInstance(SiteHashService::class);
236 197
        return $siteHashService->getSiteHashForDomain($domain);
237
    }
238
239
    /**
240
     * @param int $rootPageId
241
     * @param array $rootPageRecord
242
     * @throws InvalidArgumentException
243
     */
244 198
    protected function validateRootPageRecord(int $rootPageId, array $rootPageRecord)
245
    {
246 198
        if (!Site::isRootPage($rootPageRecord)) {
247 3
            throw new InvalidArgumentException(
248 3
                "The rootPageRecord for the given rootPageRecord ID '$rootPageId' is not marked as root rootPageRecord and can therefore not be used as site root rootPageRecord.",
249 3
                1309272922
250 3
            );
251
        }
252
    }
253
254
    /**
255
     * Builds a TYPO3 managed site with TypoScript configuration.
256
     *
257
     * @param array $rootPageRecord
258
     *
259
     * @return Site
260
     *
261
     * @throws DBALDriverException
262
     */
263 197
    protected function buildTypo3ManagedSite(array $rootPageRecord): ?Site
264
    {
265 197
        $typo3Site = $this->getTypo3Site($rootPageRecord['uid']);
266 197
        if (!$typo3Site instanceof CoreSite) {
267
            return null;
268
        }
269
270 197
        $domain = $typo3Site->getBase()->getHost();
271
272 197
        $siteHash = $this->getSiteHashForDomain($domain);
273 197
        $defaultLanguage = $typo3Site->getDefaultLanguage()->getLanguageId();
274 197
        $pageRepository = GeneralUtility::makeInstance(PagesRepository::class);
275 197
        $availableLanguageIds = array_map(function ($language) {
276 197
            return $language->getLanguageId();
277 197
        }, $typo3Site->getLanguages());
278
279
        // Try to get first instantiable TSFE for one of site languages, to get TypoScript with `plugin.tx_solr.index.*`,
280
        // to be able to collect indexing configuration,
281
        // which are required for BE-Modules/CLI-Commands or RecordMonitor within BE/TCE-commands.
282
        // If TSFE for none of languages can be initialized, then the \ApacheSolrForTypo3\Solr\Domain\Site\Site object unusable at all,
283
        // so the rest of the steps in this method are not necessary, and therefore the null will be returned.
284 197
        $tsfeFactory = GeneralUtility::makeInstance(FrontendEnvironment\Tsfe::class);
285 197
        $tsfeToUseForTypoScriptConfiguration = $tsfeFactory->getTsfeByPageIdAndLanguageFallbackChain($typo3Site->getRootPageId(), ...$availableLanguageIds);
286 197
        if (!$tsfeToUseForTypoScriptConfiguration instanceof TypoScriptFrontendController) {
287
            return null;
288
        }
289
290 197
        $solrConnectionConfigurations = [];
291
292 197
        foreach ($availableLanguageIds as $languageUid) {
293 197
            $solrConnection = SiteUtility::getSolrConnectionConfiguration($typo3Site, $languageUid);
294 197
            if ($solrConnection !== null) {
295 197
                $solrConnectionConfigurations[$languageUid] = $solrConnection;
296
            }
297
        }
298
299 197
        $solrConfiguration = $this->frontendEnvironment->getSolrConfigurationFromPageId(
300 197
            $rootPageRecord['uid'],
301 197
            $tsfeToUseForTypoScriptConfiguration->getLanguage()->getLanguageId()
302 197
        );
303
304 197
        return GeneralUtility::makeInstance(
305 197
            Site::class,
306
            /** @scrutinizer ignore-type */
307 197
            $solrConfiguration,
308
            /** @scrutinizer ignore-type */
309 197
            $rootPageRecord,
310
            /** @scrutinizer ignore-type */
311 197
            $domain,
312
            /** @scrutinizer ignore-type */
313 197
            $siteHash,
314
            /** @scrutinizer ignore-type */
315 197
            $pageRepository,
316
            /** @scrutinizer ignore-type */
317 197
            $defaultLanguage,
318
            /** @scrutinizer ignore-type */
319 197
            $availableLanguageIds,
320
            /** @scrutinizer ignore-type */
321 197
            $solrConnectionConfigurations,
322
            /** @scrutinizer ignore-type */
323 197
            $typo3Site
324 197
        );
325
    }
326
327
    /**
328
     * Returns {@link \TYPO3\CMS\Core\Site\Entity\Site}.
329
     *
330
     * @param int $pageUid
331
     * @return CoreSite|null
332
     */
333 197
    protected function getTypo3Site(int $pageUid): ?CoreSite
334
    {
335
        try {
336 197
            return $this->siteFinder->getSiteByPageId($pageUid);
337
        } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
338
        }
339
        return null;
340
    }
341
}
342