Passed
Pull Request — main (#3451)
by Rafael
50:21 queued 10:58
created

Tsfe::initializeTsfe()   B

Complexity

Conditions 8
Paths 59

Size

Total Lines 117
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 64
CRAP Score 8

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 63
dl 0
loc 117
ccs 64
cts 64
cp 1
rs 7.5628
c 9
b 0
f 0
cc 8
nc 59
nop 3
crap 8

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
// @todo: self::getAbsRefPrefixFromTSFE() returns false instead of string.
4
//        Solve that issue and activate strict types.
5
//declare(strict_types=1);
6
7
/*
8
 * This file is part of the TYPO3 CMS project.
9
 *
10
 * It is free software; you can redistribute it and/or modify it under
11
 * the terms of the GNU General Public License, either version 2
12
 * of the License, or any later version.
13
 *
14
 * For the full copyright and license information, please read the
15
 * LICENSE.txt file that was distributed with this source code.
16
 *
17
 * The TYPO3 project - inspiring people to share!
18
 */
19
20
namespace ApacheSolrForTypo3\Solr\FrontendEnvironment;
21
22
use ApacheSolrForTypo3\Solr\System\Configuration\ConfigurationPageResolver;
23
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
24
use Throwable;
25
use TYPO3\CMS\Backend\Utility\BackendUtility;
26
use TYPO3\CMS\Core\Context\Context;
27
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
28
use TYPO3\CMS\Core\Context\TypoScriptAspect;
29
use TYPO3\CMS\Core\Context\UserAspect;
30
use TYPO3\CMS\Core\Context\VisibilityAspect;
31
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
32
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
33
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
34
use TYPO3\CMS\Core\Http\ServerRequest;
35
use TYPO3\CMS\Core\Localization\Locales;
36
use TYPO3\CMS\Core\Routing\PageArguments;
37
use TYPO3\CMS\Core\SingletonInterface;
38
use TYPO3\CMS\Core\Site\SiteFinder;
39
use TYPO3\CMS\Core\TypoScript\TemplateService;
40
use TYPO3\CMS\Core\Utility\GeneralUtility;
41
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
42
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
43
44
/**
45
 * Class Tsfe is a factory class for TSFE(TypoScriptFrontendController) objects.
46
 */
47
class Tsfe implements SingletonInterface
48
{
49
    /**
50
     * @var TypoScriptFrontendController[]
51
     */
52
    protected array $tsfeCache = [];
53
54
    /**
55
     * @var ServerRequest[]
56
     */
57
    protected array $serverRequestCache = [];
58
59
    /**
60
     * @var SiteFinder
61
     */
62
    protected SiteFinder $siteFinder;
63
64
    /**
65
     * Initializes isolated TypoScriptFrontendController for Indexing and backend actions.
66
     *
67
     * @param SiteFinder|null $siteFinder
68
     */
69 201
    public function __construct(?SiteFinder $siteFinder = null)
70
    {
71 201
        $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
72
    }
73
74
    /**
75
     * Initializes the TSFE for a given page ID and language.
76
     *
77
     * @param int $pageId
78
     * @param int $language
79
     *
80
     * @param int|null $rootPageId
81
     *
82
     * @throws DBALDriverException
83
     * @throws Exception\Exception
84
     * @throws SiteNotFoundException
85
     *
86
     *
87
     * @todo: Move whole caching stuff from this method and let return TSFE.
88
     */
89 201
    protected function initializeTsfe(int $pageId, int $language = 0, ?int $rootPageId = null)
90
    {
91 201
        $cacheIdentifier = $this->getCacheIdentifier($pageId, $language, $rootPageId);
92
93
        // Handle spacer and sys-folders, since they are not accessible in frontend, and TSFE can not be fully initialized on them.
94
        // Apart from this, the plugin.tx_solr.index.queue.[indexConfig].additionalPageIds is handled as well.
95 201
        $pidToUse = $this->getPidToUseForTsfeInitialization($pageId, $rootPageId);
96 201
        if ($pidToUse !== $pageId) {
97 4
            $this->initializeTsfe($pidToUse, $language, $rootPageId);
0 ignored issues
show
Bug introduced by
It seems like $pidToUse can also be of type null; however, parameter $pageId of ApacheSolrForTypo3\Solr\...\Tsfe::initializeTsfe() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

97
            $this->initializeTsfe(/** @scrutinizer ignore-type */ $pidToUse, $language, $rootPageId);
Loading history...
98 4
            $reusedCacheIdentifier = $this->getCacheIdentifier($pidToUse, $language, $rootPageId);
0 ignored issues
show
Bug introduced by
It seems like $pidToUse can also be of type null; however, parameter $pageId of ApacheSolrForTypo3\Solr\...e::getCacheIdentifier() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

98
            $reusedCacheIdentifier = $this->getCacheIdentifier(/** @scrutinizer ignore-type */ $pidToUse, $language, $rootPageId);
Loading history...
99 4
            $this->serverRequestCache[$cacheIdentifier] = $this->serverRequestCache[$reusedCacheIdentifier];
100 4
            $this->tsfeCache[$cacheIdentifier] = $this->tsfeCache[$reusedCacheIdentifier];
101
//            if ($rootPageId === null) {
102
//                // @Todo: Resolve and set TSFE object for $rootPageId.
103
//            }
104 4
            return;
105
        }
106
107
        /* @var Context $context */
108 201
        $context = clone GeneralUtility::makeInstance(Context::class);
109 201
        $site = $this->siteFinder->getSiteByPageId($pageId);
110
        // $siteLanguage and $languageAspect takes the language id into account.
111
        //   See: $site->getLanguageById($language);
112
        //   Therefore the whole TSFE stack is initialized and must be used as is.
113
        //   Note: ServerRequest, Context, Language, cObj of TSFE MUST NOT be changed or touched in any way,
114
        //         Otherwise the caching of TSFEs makes no sense anymore.
115
        //         If you want something to change in TSFE object, please use cloned one!
116 201
        $siteLanguage = $site->getLanguageById($language);
117 201
        $languageAspect = LanguageAspectFactory::createFromSiteLanguage($siteLanguage);
118 201
        $context->setAspect('language', $languageAspect);
119
120 201
        $serverRequest = $this->serverRequestCache[$cacheIdentifier] ?? null;
121 201
        if (!isset($this->serverRequestCache[$cacheIdentifier])) {
122 201
            $serverRequest = GeneralUtility::makeInstance(ServerRequest::class);
123 201
            $this->serverRequestCache[$cacheIdentifier] = $serverRequest =
124 201
                $serverRequest->withAttribute('site', $site)
125 201
                ->withAttribute('language', $siteLanguage)
126 201
                ->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_FE)
127 201
                ->withUri($site->getBase());
128
        }
129
130 201
        if (!isset($this->tsfeCache[$cacheIdentifier])) {
131
            // TYPO3 by default enables a preview mode if a backend user is logged in,
132
            // the VisibilityAspect is configured to show hidden elements.
133
            // Due to this setting hidden relations/translations might be indexed
134
            // when running the Solr indexer via the TYPO3 backend.
135
            // To avoid this, the VisibilityAspect is adapted for indexing.
136 201
            $context->setAspect(
137 201
                'visibility',
138 201
                GeneralUtility::makeInstance(
139 201
                    VisibilityAspect::class,
140 201
                    false,
141 201
                    false
142 201
                )
143 201
            );
144
145
            /** @var FrontendUserAuthentication $feUser */
146 201
            $feUser = GeneralUtility::makeInstance(FrontendUserAuthentication::class);
147
            // for certain situations we need to trick TSFE into granting us
148
            // access to the page in any case to make getPageAndRootline() work
149
            // see http://forge.typo3.org/issues/42122
150 201
            $pageRecord = BackendUtility::getRecord('pages', $pageId, 'fe_group');
151 201
            if (!empty($pageRecord['fe_group'])) {
152 1
                $userGroups = explode(',', $pageRecord['fe_group']);
153
            } else {
154 201
                $userGroups = [0, -1];
155
            }
156 201
            $feUser->user = ['uid' => 0, 'username' => '', 'usergroup' => implode(',', $userGroups) ];
157 201
            $feUser->fetchGroupData();
158 201
            $context->setAspect('frontend.user', GeneralUtility::makeInstance(UserAspect::class, $feUser, $userGroups));
159
160
            /* @var PageArguments $pageArguments */
161 201
            $pageArguments = GeneralUtility::makeInstance(PageArguments::class, $pageId, 0, []);
162
163
            /* @var TypoScriptFrontendController $tsfe */
164 201
            $tsfe = GeneralUtility::makeInstance(TypoScriptFrontendController::class, $context, $site, $siteLanguage, $pageArguments, $feUser);
165
166
            // @extensionScannerIgnoreLine
167
            /** Done in {@link \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::settingLanguage} */
168
            //$tsfe->sys_page = GeneralUtility::makeInstance(PageRepository::class);
169
170 201
            $template = GeneralUtility::makeInstance(TemplateService::class, $context, null, $tsfe);
171 201
            $template->tt_track = false;
172 201
            $tsfe->tmpl = $template;
173 201
            $context->setAspect('typoscript', GeneralUtility::makeInstance(TypoScriptAspect::class, true));
174 201
            $tsfe->no_cache = true;
175
176 201
            $backedUpBackendUser = $GLOBALS['BE_USER'] ?? null;
177
            try {
178 201
                $tsfe->determineId($serverRequest);
179 201
                $serverRequest->withAttribute('frontend.controller', $tsfe);
180 201
                $tsfe->no_cache = false;
181 201
                $tsfe->getConfigArray($serverRequest);
182
183 201
                $tsfe->newCObj($serverRequest);
184 201
                $tsfe->absRefPrefix = self::getAbsRefPrefixFromTSFE($tsfe);
0 ignored issues
show
Bug Best Practice introduced by
The method ApacheSolrForTypo3\Solr\...tAbsRefPrefixFromTSFE() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

184
                /** @scrutinizer ignore-call */ 
185
                $tsfe->absRefPrefix = self::getAbsRefPrefixFromTSFE($tsfe);
Loading history...
185 201
                $tsfe->calculateLinkVars([]);
186 6
            } catch (Throwable $exception) {
187
                // @todo: logging
188 6
                $this->serverRequestCache[$cacheIdentifier] = null;
189 6
                $this->tsfeCache[$cacheIdentifier] = null;
190
                // Restore backend user, happens when initializeTsfe() is called from Backend context
191 6
                if ($backedUpBackendUser) {
192 3
                    $GLOBALS['BE_USER'] = $backedUpBackendUser;
193
                }
194 6
                return;
195
            }
196
            // Restore backend user, happens when initializeTsfe() is called from Backend context
197 201
            if ($backedUpBackendUser) {
198 37
                $GLOBALS['BE_USER'] = $backedUpBackendUser;
199
            }
200
201 201
            $this->tsfeCache[$cacheIdentifier] = $tsfe;
202
        }
203
204
        // @todo: Not right place for that action, move on more convenient place: indexing a single item+id+lang.
205 201
        Locales::setSystemLocaleFromSiteLanguage($siteLanguage);
206
    }
207
208
    /**
209
     * Returns TypoScriptFrontendController with sand cast context.
210
     *
211
     * @param int $pageId
212
     * @param int $language
213
     *
214
     * @param int|null $rootPageId
215
     *
216
     * @return TypoScriptFrontendController
217
     *
218
     * @throws SiteNotFoundException
219
     * @throws DBALDriverException
220
     * @throws Exception\Exception
221
     */
222 201
    public function getTsfeByPageIdAndLanguageId(int $pageId, int $language = 0, ?int $rootPageId = null): ?TypoScriptFrontendController
223
    {
224 201
        $this->assureIsInitialized($pageId, $language, $rootPageId);
225 201
        return $this->tsfeCache[$this->getCacheIdentifier($pageId, $language, $rootPageId)];
226
    }
227
228
    /**
229
     * Returns TypoScriptFrontendController for first available language id in fallback chain.
230
     *
231
     * Is usable for BE-Modules/CLI-Commands stack only, where the rendered TypoScript configuration
232
     * of EXT:solr* stack is wanted and the language id does not matter.
233
     *
234
     * NOTE: This method MUST NOT be used on indexing context.
235
     *
236
     * @param int $pageId
237
     * @param int ...$languageFallbackChain
238
     * @return TypoScriptFrontendController|null
239
     */
240 199
    public function getTsfeByPageIdAndLanguageFallbackChain(int $pageId, int ...$languageFallbackChain): ?TypoScriptFrontendController
241
    {
242 199
        foreach ($languageFallbackChain as $languageId) {
243
            try {
244 199
                $tsfe = $this->getTsfeByPageIdAndLanguageId($pageId, $languageId);
245 199
                if ($tsfe instanceof TypoScriptFrontendController) {
246 199
                    return $tsfe;
247
                }
248
            } catch (Throwable $e) {
249
                // no needs to log or do anything, the method MUST not return anything if it can't.
250
                continue;
251
            }
252
        }
253 1
        return null;
254
    }
255
256
    /**
257
     * Returns TSFE for first initializable site language.
258
     *
259
     * Is usable for BE-Modules/CLI-Commands stack only, where the rendered TypoScript configuration
260
     * of EXT:solr* stack is wanted and the language id does not matter.
261
     *
262
     * @param int $pageId
263
     * @return TypoScriptFrontendController|null
264
     */
265 55
    public function getTsfeByPageIdIgnoringLanguage(int $pageId): ?TypoScriptFrontendController
266
    {
267
        try {
268 55
            $typo3Site = $this->siteFinder->getSiteByPageId($pageId);
269
        } catch (Throwable $e) {
270
            return null;
271
        }
272 55
        $availableLanguageIds = array_map(function ($siteLanguage) {
273 55
            return $siteLanguage->getLanguageId();
274 55
        }, $typo3Site->getLanguages());
275
276 55
        if (empty($availableLanguageIds)) {
277
            return null;
278
        }
279 55
        return $this->getTsfeByPageIdAndLanguageFallbackChain($pageId, ...$availableLanguageIds);
280
    }
281
282
    /**
283
     * Returns TypoScriptFrontendController with sand cast context.
284
     *
285
     * @param int $pageId
286
     * @param int $language
287
     *
288
     * @param int|null $rootPageId
289
     *
290
     * @return ServerRequest
291
     *
292
     * @throws SiteNotFoundException
293
     * @throws DBALDriverException
294
     * @throws Exception\Exception
295
     * @noinspection PhpUnused
296
     */
297
    public function getServerRequestForTsfeByPageIdAndLanguageId(int $pageId, int $language = 0, ?int $rootPageId = null): ?ServerRequest
298
    {
299
        $this->assureIsInitialized($pageId, $language, $rootPageId);
300
        return $this->serverRequestCache[$this->getCacheIdentifier($pageId, $language, $rootPageId)];
301
    }
302
303
    /**
304
     * Initializes the TSFE, ServerRequest, Context if not already done.
305
     *
306
     * @param int $pageId
307
     * @param int $language
308
     *
309
     * @param int|null $rootPageId
310
     *
311
     * @throws DBALDriverException
312
     * @throws SiteNotFoundException
313
     * @throws Exception\Exception
314
     */
315 201
    protected function assureIsInitialized(int $pageId, int $language, ?int $rootPageId = null): void
316
    {
317 201
        $cacheIdentifier = $this->getCacheIdentifier($pageId, $language, $rootPageId);
318 201
        if (!array_key_exists($cacheIdentifier, $this->tsfeCache)) {
319 201
            $this->initializeTsfe($pageId, $language, $rootPageId);
320 201
            return;
321
        }
322 198
        if ($this->tsfeCache[$cacheIdentifier] instanceof TypoScriptFrontendController) {
323 198
            $this->tsfeCache[$cacheIdentifier]->newCObj($this->serverRequestCache[$cacheIdentifier]);
324
        }
325
    }
326
327
    /**
328
     * Returns the cache identifier for cached TSFE and ServerRequest objects.
329
     *
330
     * @param int $pageId
331
     * @param int $language
332
     *
333
     * @param int|null $rootPageId
334
     *
335
     * @return string
336
     */
337 201
    protected function getCacheIdentifier(int $pageId, int $language, ?int $rootPageId = null): string
338
    {
339 201
        return 'root:' . ($rootPageId ?? 'null') . '|page:' . $pageId . '|lang:' . $language;
340
    }
341
342
    /**
343
     * The TSFE can not be initialized for Spacer and sys-folders.
344
     * See: "Spacer and sys folders is not accessible in frontend" on {@link \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController::getPageAndRootline()}
345
     *
346
     * Note: The requested $pidToUse can be one of configured plugin.tx_solr.index.queue.[indexConfig].additionalPageIds.
347
     *
348
     * @param int $pidToUse
349
     *
350
     * @param int|null $rootPageId
351
     *
352
     * @return int
353
     * @throws DBALDriverException
354
     * @throws Exception\Exception
355
     */
356 201
    protected function getPidToUseForTsfeInitialization(int $pidToUse, ?int $rootPageId = null): ?int
357
    {
358 201
        $incomingPidToUse = $pidToUse;
359 201
        $incomingRootPageId = $rootPageId;
360
361
        // handle plugin.tx_solr.index.queue.[indexConfig].additionalPageIds
362 201
        if (isset($rootPageId) && !$this->isRequestedPageAPartOfRequestedSite($pidToUse)) {
363 20
            return $rootPageId;
364
        }
365 201
        $pageRecord = BackendUtility::getRecord('pages', $pidToUse);
366 201
        $isSpacerOrSysfolder = ($pageRecord['doktype'] ?? null) == PageRepository::DOKTYPE_SPACER || ($pageRecord['doktype'] ?? null) == PageRepository::DOKTYPE_SYSFOLDER;
367 201
        if ($isSpacerOrSysfolder === false) {
368 201
            return $pidToUse;
369
        }
370
        /* @var ConfigurationPageResolver $configurationPageResolve */
371 3
        $configurationPageResolver = GeneralUtility::makeInstance(ConfigurationPageResolver::class);
372 3
        $askedPid = $pidToUse;
373 3
        $pidToUse = $configurationPageResolver->getClosestPageIdWithActiveTemplate($pidToUse);
374 3
        if (!isset($pidToUse) && !isset($rootPageId)) {
375 1
            throw new Exception\Exception(
376 1
                "The closest page with active template to page \"$askedPid\" could not be resolved and alternative rootPageId is not provided.",
377 1
                1637339439
378 1
            );
379
        }
380 2
        if (isset($rootPageId)) {
381
            return $rootPageId;
382
        }
383
384
        // Check for recursion that can happen if the root page is a sysfolder with a typoscript template
385 2
        if ($pidToUse === $incomingPidToUse && $rootPageId === $incomingRootPageId) {
386
            throw new Exception\Exception(
387
                "Infinite recursion detected while looking for the closest page with active template to page \"$askedPid\" . Please note that the page with active template (usually the root page of the current tree) MUST NOT be a sysfolder.",
388
                1637339476
389
            );
390
        }
391
392 2
        return $this->getPidToUseForTsfeInitialization($pidToUse, $rootPageId);
393
    }
394
395
    /**
396
     * Checks if the requested page belongs to site of given root page.
397
     *
398
     * @param int $pageId
399
     * @param int|null $rootPageId
400
     *
401
     * @return bool
402
     */
403 20
    protected function isRequestedPageAPartOfRequestedSite(int $pageId, ?int $rootPageId = null): bool
404
    {
405 20
        if (!isset($rootPageId)) {
406 20
            return false;
407
        }
408
        try {
409
            $site = $this->siteFinder->getSiteByPageId($pageId);
410
        } catch (SiteNotFoundException $e) {
411
            return false;
412
        }
413
        return $rootPageId === $site->getRootPageId();
414
    }
415
416
    /**
417
     * Resolves the configured absRefPrefix to a valid value and resolved if absRefPrefix
418
     * is set to "auto".
419
     */
420 201
    private function getAbsRefPrefixFromTSFE(TypoScriptFrontendController $TSFE): string
421
    {
422 201
        $absRefPrefix = '';
423 201
        if (empty($TSFE->config['config']['absRefPrefix'])) {
424
            return $absRefPrefix;
425
        }
426
427 201
        $absRefPrefix = trim($TSFE->config['config']['absRefPrefix']);
428 201
        if ($absRefPrefix === 'auto') {
429 201
            $absRefPrefix = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
430
        }
431
432 201
        return $absRefPrefix;
433
    }
434
}
435