Passed
Pull Request — main (#3275)
by Rafael
44:35
created

Tsfe::initializeTsfe()   C

Complexity

Conditions 9
Paths 155

Size

Total Lines 119
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 42
CRAP Score 9.0076

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 64
c 10
b 0
f 0
dl 0
loc 119
ccs 42
cts 44
cp 0.9545
rs 6.8632
cc 9
nc 155
nop 3
crap 9.0076

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\Error\Http\InternalServerErrorException;
34
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
35
use TYPO3\CMS\Core\Http\ServerRequest;
36
use TYPO3\CMS\Core\Localization\Locales;
37 141
use TYPO3\CMS\Core\Routing\PageArguments;
38
use TYPO3\CMS\Core\SingletonInterface;
39
use TYPO3\CMS\Core\Site\SiteFinder;
40 141
use TYPO3\CMS\Core\TypoScript\TemplateService;
41 141
use TYPO3\CMS\Core\Utility\GeneralUtility;
42 141
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
43 141
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
44 141
45 141
/**
46
 * Class Tsfe is a factory class for TSFE(TypoScriptFrontendController) objects.
47
 */
48
class Tsfe implements SingletonInterface
49
{
50 17
    /**
51
     * @var TypoScriptFrontendController[]
52 17
     */
53 17
    protected array $tsfeCache = [];
54 17
55
    /**
56
     * @var ServerRequest[]
57
     */
58 17
    protected array $serverRequestCache = [];
59
60
    /**
61
     * @var SiteFinder
62
     */
63
    protected SiteFinder $siteFinder;
64
65
    /**
66
     * Initializes isolated TypoScriptFrontendController for Indexing and backend actions.
67
     *
68
     * @param SiteFinder|null $siteFinder
69
     */
70
    public function __construct(?SiteFinder $siteFinder = null)
71 22
    {
72
        $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
73
    }
74
75 22
    /**
76
     * Initializes the TSFE for a given page ID and language.
77 22
     *
78
     * @param int $pageId
79
     * @param int $language
80 22
     *
81 22
     * @param int|null $rootPageId
82
     *
83
     * @throws DBALDriverException
84 22
     * @throws Exception\Exception
85 22
     * @throws SiteNotFoundException
86 22
     *
87
     *
88 22
     * @todo: Move whole caching stuff from this method and let return TSFE.
89
     */
90 22
    protected function initializeTsfe(int $pageId, int $language = 0, ?int $rootPageId = null)
91 22
    {
92 22
        $cacheIdentifier = $this->getCacheIdentifier($pageId, $language, $rootPageId);
93 22
94 22
        // Handle spacer and sys-folders, since they are not accessible in frontend, and TSFE can not be fully initialized on them.
95 22
        // Apart from this, the plugin.tx_solr.index.queue.[indexConfig].additionalPageIds is handled as well.
96
        $pidToUse = $this->getPidToUseForTsfeInitialization($pageId, $rootPageId);
97 22
        if ($pidToUse !== $pageId) {
98
            $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

98
            $this->initializeTsfe(/** @scrutinizer ignore-type */ $pidToUse, $language, $rootPageId);
Loading history...
99
            $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

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

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