Passed
Pull Request — release-11.5.x (#3206)
by Michael
41:07
created

Tsfe::initializeTsfe()   B

Complexity

Conditions 6
Paths 31

Size

Total Lines 104
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 51
CRAP Score 6.0019

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 55
dl 0
loc 104
ccs 51
cts 53
cp 0.9623
rs 8.3595
c 6
b 0
f 0
cc 6
nc 31
nop 3
crap 6.0019

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
namespace ApacheSolrForTypo3\Solr\FrontendEnvironment;
3
4
use ApacheSolrForTypo3\Solr\System\Configuration\ConfigurationPageResolver;
5
use Doctrine\DBAL\Driver\Exception as DBALDriverException;
6
use Throwable;
7
use TYPO3\CMS\Backend\Utility\BackendUtility;
8
use TYPO3\CMS\Core\Context\Context;
9
use TYPO3\CMS\Core\Context\LanguageAspectFactory;
10
use TYPO3\CMS\Core\Context\TypoScriptAspect;
11
use TYPO3\CMS\Core\Context\VisibilityAspect;
12
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
13
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
14
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
15
use TYPO3\CMS\Core\Localization\Locales;
16
use TYPO3\CMS\Core\Routing\PageArguments;
17
use TYPO3\CMS\Core\SingletonInterface;
18
use TYPO3\CMS\Core\Site\SiteFinder;
19
use TYPO3\CMS\Core\TypoScript\TemplateService;
20
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
21
use TYPO3\CMS\Core\Utility\GeneralUtility;
22
use TYPO3\CMS\Core\Context\UserAspect;
23
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
24
use TYPO3\CMS\Core\Http\ServerRequest;
25
26
class Tsfe implements SingletonInterface
27
{
28
29
    /**
30
     * @var TypoScriptFrontendController[]
31
     */
32
    protected array $tsfeCache = [];
33
34
    /**
35
     * @var ServerRequest[]
36
     */
37
    protected array $serverRequestCache = [];
38
39
    /**
40
     * @var SiteFinder
41
     */
42
    protected SiteFinder $siteFinder;
43
44
    /**
45
     * Initializes isolated TypoScriptFrontendController for Indexing and backend actions.
46
     *
47
     * @param SiteFinder|null $siteFinder
48
     */
49 208
    public function __construct(?SiteFinder $siteFinder = null)
50
    {
51 208
        $this->siteFinder = $siteFinder ?? GeneralUtility::makeInstance(SiteFinder::class);
52
    }
53
54
    /**
55
     * Initializes the TSFE for a given page ID and language.
56
     *
57
     * @param int $pageId
58
     * @param int $language
59
     *
60
     * @param int|null $rootPageId
61
     *
62
     * @throws DBALDriverException
63
     * @throws Exception\Exception
64
     * @throws SiteNotFoundException
65
     *
66
     * @todo: Move whole caching stuff from this method and let return TSFE.
67
     */
68 208
    protected function initializeTsfe(int $pageId, int $language = 0, ?int $rootPageId = null)
69
    {
70 208
        $cacheIdentifier = $this->getCacheIdentifier($pageId, $language, $rootPageId);
71
72
        // Handle spacer and sys-folders, since they are not accessible in frontend, and TSFE can not be fully initialized on them.
73
        // Apart from this, the plugin.tx_solr.index.queue.[indexConfig].additionalPageIds is handled as well.
74 208
        $pidToUse = $this->getPidToUseForTsfeInitialization($pageId, $rootPageId);
75 208
        if ($pidToUse !== $pageId) {
76 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

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

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

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