Completed
Push — master ( 4dc794...0f9475 )
by
unknown
15:26
created

RequestHandler   F

Complexity

Total Complexity 240

Size/Duplication

Total Lines 1029
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 584
dl 0
loc 1029
rs 2
c 1
b 0
f 0
wmc 240

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A resetGlobalsToCurrentRequest() 0 15 5
A handle() 0 46 5
A generatePageContent() 0 24 3
C generateMetaTagHtml() 0 36 15
A generateHrefLangTags() 0 15 3
A generatePageBodyContent() 0 10 4
F processHtmlBasedRenderingSettings() 0 653 183
A getPageRenderer() 0 3 1
A addCssToPageRenderer() 0 14 2
A stripIntObjectPlaceholder() 0 6 1
B generateHtmlTag() 0 24 9
A getLanguageService() 0 3 1
B displayPreviewInfoMessage() 0 31 7

How to fix   Complexity   

Complex Class

Complex classes like RequestHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RequestHandler, and based on these observations, apply Extract Interface, too.

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 TYPO3\CMS\Frontend\Http;
19
20
use Psr\EventDispatcher\EventDispatcherInterface;
21
use Psr\Http\Message\ResponseInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
use Psr\Http\Server\RequestHandlerInterface;
24
use TYPO3\CMS\Core\Core\Environment;
25
use TYPO3\CMS\Core\Http\Response;
26
use TYPO3\CMS\Core\Information\Typo3Information;
27
use TYPO3\CMS\Core\Localization\LanguageService;
28
use TYPO3\CMS\Core\Page\AssetCollector;
29
use TYPO3\CMS\Core\Page\PageRenderer;
30
use TYPO3\CMS\Core\Resource\Exception;
31
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
32
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
33
use TYPO3\CMS\Core\Type\File\ImageInfo;
34
use TYPO3\CMS\Core\TypoScript\TypoScriptService;
35
use TYPO3\CMS\Core\Utility\GeneralUtility;
36
use TYPO3\CMS\Core\Utility\PathUtility;
37
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
38
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
39
use TYPO3\CMS\Frontend\Event\ModifyHrefLangTagsEvent;
40
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
41
42
/**
43
 * This is the main entry point of the TypoScript driven standard front-end.
44
 *
45
 * "handle()" is called when all PSR-15 middlewares have been set up the PSR-7 ServerRequest object and the following
46
 * things have been evaluated
47
 * - correct page ID, page type (typeNum), rootline, MP etc.
48
 * - info if is cached content already available
49
 * - proper language
50
 * - proper TypoScript which should be processed.
51
 *
52
 * Then, this class is able to render the actual HTTP body part built via TypoScript. Here this is split into two parts:
53
 * - Everything included in <body>, done via page.10, page.20 etc.
54
 * - Everything around.
55
 *
56
 * If the content has been built together within the cache (cache_pages), it is fetched directly, and
57
 * any so-called "uncached" content is generated again.
58
 *
59
 * Some further hooks allow to post-processing the content.
60
 *
61
 * Then the right HTTP response headers are compiled together and sent as well.
62
 */
63
class RequestHandler implements RequestHandlerInterface
64
{
65
    /**
66
     * Instance of the timetracker
67
     * @var TimeTracker
68
     */
69
    protected $timeTracker;
70
71
    /**
72
     * @var EventDispatcherInterface
73
     */
74
    protected $eventDispatcher;
75
76
    public function __construct(EventDispatcherInterface $eventDispatcher)
77
    {
78
        $this->eventDispatcher = $eventDispatcher;
79
    }
80
81
    /**
82
     * Sets the global GET and POST to the values, so if people access $_GET and $_POST
83
     * Within hooks starting NOW (e.g. cObject), they get the "enriched" data from query params.
84
     *
85
     * This needs to be run after the request object has been enriched with modified GET/POST variables.
86
     *
87
     * @param ServerRequestInterface $request
88
     * @internal this safety net will be removed in TYPO3 v10.0.
89
     */
90
    protected function resetGlobalsToCurrentRequest(ServerRequestInterface $request)
91
    {
92
        if ($request->getQueryParams() !== $_GET) {
93
            $queryParams = $request->getQueryParams();
94
            $_GET = $queryParams;
95
            $GLOBALS['HTTP_GET_VARS'] = $_GET;
96
        }
97
        if ($request->getMethod() === 'POST') {
98
            $parsedBody = $request->getParsedBody();
99
            if (is_array($parsedBody) && $parsedBody !== $_POST) {
100
                $_POST = $parsedBody;
101
                $GLOBALS['HTTP_POST_VARS'] = $_POST;
102
            }
103
        }
104
        $GLOBALS['TYPO3_REQUEST'] = $request;
105
    }
106
107
    /**
108
     * Handles a frontend request, after finishing running middlewares
109
     *
110
     * @param ServerRequestInterface $request
111
     * @return ResponseInterface
112
     */
113
    public function handle(ServerRequestInterface $request): ResponseInterface
114
    {
115
        // Fetch the initialized time tracker object
116
        $this->timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
117
        /** @var TypoScriptFrontendController $controller */
118
        $controller = $GLOBALS['TSFE'];
119
120
        $this->resetGlobalsToCurrentRequest($request);
121
122
        // Generate page
123
        if ($controller->isGeneratePage()) {
124
            $this->timeTracker->push('Page generation');
125
            $controller->generatePage_preProcessing();
126
            $controller->preparePageContentGeneration($request);
127
128
            // Content generation
129
            $this->timeTracker->incStackPointer();
130
            $this->timeTracker->push($controller->sPre, 'PAGE');
131
132
            $controller->content = $this->generatePageContent($controller, $request);
133
134
            $this->timeTracker->pull($this->timeTracker->LR ? $controller->content : '');
135
            $this->timeTracker->decStackPointer();
136
137
            $controller->generatePage_postProcessing();
138
            $this->timeTracker->pull();
139
        }
140
        $controller->releaseLocks();
141
142
        // Render non-cached page parts by replacing placeholders which are taken from cache or added during page generation
143
        if ($controller->isINTincScript()) {
144
            if (!$controller->isGeneratePage()) {
145
                // When page was generated, this was already called. Avoid calling this twice.
146
                $controller->preparePageContentGeneration($request);
147
            }
148
            $this->timeTracker->push('Non-cached objects');
149
            $controller->INTincScript();
150
            $this->timeTracker->pull();
151
        }
152
153
        // Create a default Response object and add headers and body to it
154
        $response = new Response();
155
        $response = $controller->applyHttpHeadersToResponse($response);
156
        $this->displayPreviewInfoMessage($controller);
157
        $response->getBody()->write($controller->content);
158
        return $response;
159
    }
160
161
    /**
162
     * Generates the main body part for the page, and if "config.disableAllHeaderCode" is not active, triggers
163
     * pageRenderer to evaluate includeCSS, headTag etc. TypoScript processing to populate the pageRenderer.
164
     *
165
     * @param TypoScriptFrontendController $controller
166
     * @param ServerRequestInterface $request
167
     * @return string
168
     */
169
    protected function generatePageContent(TypoScriptFrontendController $controller, ServerRequestInterface $request): string
170
    {
171
        // Generate the main content between the <body> tags
172
        // This has to be done first, as some additional TSFE-related code could have been written
173
        $pageContent = $this->generatePageBodyContent($controller);
174
        // If 'disableAllHeaderCode' is set, all the pageRenderer settings are not evaluated
175
        if ($controller->config['config']['disableAllHeaderCode'] ?? false) {
176
            return $pageContent;
177
        }
178
        // Now, populate pageRenderer with all additional data
179
        $this->processHtmlBasedRenderingSettings($controller, $controller->getLanguage(), $request);
180
        $pageRenderer = $this->getPageRenderer();
181
        // Add previously generated page content within the <body> tag afterwards
182
        $pageRenderer->addBodyContent(LF . $pageContent);
183
        if ($controller->isINTincScript()) {
184
            // Store the serialized pageRenderer in configuration
185
            $controller->config['INTincScript_ext']['pageRenderer'] = serialize($pageRenderer);
186
            // Store the serialized AssetCollector in configuration
187
            $controller->config['INTincScript_ext']['assetCollector'] = serialize(GeneralUtility::makeInstance(AssetCollector::class));
188
            // Render complete page, keep placeholders for JavaScript and CSS
189
            return $pageRenderer->renderPageWithUncachedObjects($controller->config['INTincScript_ext']['divKey']);
190
        }
191
        // Render complete page
192
        return $pageRenderer->render();
193
    }
194
195
    /**
196
     * Generates the main content part within <body> tags (except JS files/CSS files), this means:
197
     * render everything that can be cached, otherwise put placeholders for COA_INT/USER_INT objects
198
     * in the content that is processed later-on.
199
     *
200
     * @param TypoScriptFrontendController $controller
201
     * @return string
202
     */
203
    protected function generatePageBodyContent(TypoScriptFrontendController $controller): string
204
    {
205
        $pageContent = $controller->cObj->cObjGet($controller->pSetup) ?: '';
0 ignored issues
show
Bug introduced by
It seems like $controller->pSetup can also be of type string; however, parameter $setup of TYPO3\CMS\Frontend\Conte...jectRenderer::cObjGet() does only seem to accept array, 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

205
        $pageContent = $controller->cObj->cObjGet(/** @scrutinizer ignore-type */ $controller->pSetup) ?: '';
Loading history...
206
        if ($controller->pSetup['wrap'] ?? false) {
207
            $pageContent = $controller->cObj->wrap($pageContent, $controller->pSetup['wrap']);
208
        }
209
        if ($controller->pSetup['stdWrap.'] ?? false) {
210
            $pageContent = $controller->cObj->stdWrap($pageContent, $controller->pSetup['stdWrap.']);
211
        }
212
        return $pageContent;
213
    }
214
215
    /**
216
     * At this point, the cacheable content has just been generated (thus, all content is available but hasn't been added
217
     * to PageRenderer yet). The method is called after the "main" page content, since some JS may be inserted at that point
218
     * that has been registered by cacheable plugins.
219
     * PageRenderer is now populated with all <head> data and additional JavaScript/CSS/FooterData/HeaderData that can be cached.
220
     * Once finished, the content is added to the >addBodyContent() functionality.
221
     *
222
     * @param TypoScriptFrontendController $controller
223
     * @param SiteLanguage $siteLanguage
224
     * @param ServerRequestInterface $request
225
     */
226
    protected function processHtmlBasedRenderingSettings(TypoScriptFrontendController $controller, SiteLanguage $siteLanguage, ServerRequestInterface $request): void
227
    {
228
        $pageRenderer = $this->getPageRenderer();
229
        if ($controller->config['config']['moveJsFromHeaderToFooter'] ?? false) {
230
            $pageRenderer->enableMoveJsFromHeaderToFooter();
231
        }
232
        if ($controller->config['config']['pageRendererTemplateFile'] ?? false) {
233
            try {
234
                $file = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($controller->config['config']['pageRendererTemplateFile']);
235
                $pageRenderer->setTemplateFile($file);
236
            } catch (Exception $e) {
237
                // do nothing
238
            }
239
        }
240
        $headerComment = trim($controller->config['config']['headerComment'] ?? '');
241
        if ($headerComment) {
242
            $pageRenderer->addInlineComment("\t" . str_replace(LF, LF . "\t", $headerComment) . LF);
243
        }
244
        // Setting charset:
245
        $theCharset = $controller->metaCharset;
246
        $htmlTagAttributes = [];
247
        $htmlLang = $siteLanguage->getHreflang() ?: '';
248
249
        if ($siteLanguage->getDirection()) {
250
            $htmlTagAttributes['dir'] = htmlspecialchars($siteLanguage->getDirection());
251
        }
252
        // Setting document type:
253
        $docTypeParts = [];
254
        $xmlDocument = true;
255
        // Part 1: XML prologue
256
        switch ((string)($controller->config['config']['xmlprologue'] ?? '')) {
257
            case 'none':
258
                $xmlDocument = false;
259
                break;
260
            case 'xml_10':
261
                $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
262
                break;
263
            case 'xml_11':
264
                $docTypeParts[] = '<?xml version="1.1" encoding="' . $theCharset . '"?>';
265
                break;
266
            case '':
267
                if ($controller->xhtmlVersion) {
268
                    $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
269
                } else {
270
                    $xmlDocument = false;
271
                }
272
                break;
273
            default:
274
                $docTypeParts[] = $controller->config['config']['xmlprologue'];
275
        }
276
        // Part 2: DTD
277
        $doctype = $controller->config['config']['doctype'] ?? null;
278
        $defaultTypeAttributeForJavaScript = 'text/javascript';
279
        if ($doctype) {
280
            switch ($doctype) {
281
                case 'xhtml_trans':
282
                    $docTypeParts[] = '<!DOCTYPE html
283
    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
284
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
285
                    break;
286
                case 'xhtml_strict':
287
                    $docTypeParts[] = '<!DOCTYPE html
288
    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
289
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
290
                    break;
291
                case 'xhtml_basic':
292
                    $docTypeParts[] = '<!DOCTYPE html
293
    PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
294
    "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">';
295
                    break;
296
                case 'xhtml_11':
297
                    $docTypeParts[] = '<!DOCTYPE html
298
    PUBLIC "-//W3C//DTD XHTML 1.1//EN"
299
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
300
                    break;
301
                case 'xhtml+rdfa_10':
302
                    $docTypeParts[] = '<!DOCTYPE html
303
    PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"
304
    "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">';
305
                    break;
306
                case 'html5':
307
                    $defaultTypeAttributeForJavaScript = '';
308
                    $docTypeParts[] = '<!DOCTYPE html>';
309
                    if ($xmlDocument) {
310
                        $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
311
                    } else {
312
                        $pageRenderer->setMetaCharsetTag('<meta charset="|">');
313
                    }
314
                    break;
315
                case 'none':
316
                    break;
317
                default:
318
                    $docTypeParts[] = $doctype;
319
            }
320
        } else {
321
            $docTypeParts[] = '<!DOCTYPE html>';
322
            if ($xmlDocument) {
323
                $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
324
            } else {
325
                $pageRenderer->setMetaCharsetTag('<meta charset="|">');
326
            }
327
            $defaultTypeAttributeForJavaScript = '';
328
        }
329
        if ($htmlLang) {
330
            if ($controller->xhtmlVersion) {
331
                $htmlTagAttributes['xml:lang'] = $htmlLang;
332
            }
333
            if ($controller->xhtmlVersion < 110 || $doctype === 'html5') {
334
                $htmlTagAttributes['lang'] = $htmlLang;
335
            }
336
        }
337
        if ($controller->xhtmlVersion || $doctype === 'html5' && $xmlDocument) {
338
            // We add this to HTML5 to achieve a slightly better backwards compatibility
339
            $htmlTagAttributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
340
            if (is_array($controller->config['config']['namespaces.'])) {
341
                foreach ($controller->config['config']['namespaces.'] as $prefix => $uri) {
342
                    // $uri gets htmlspecialchared later
343
                    $htmlTagAttributes['xmlns:' . htmlspecialchars($prefix)] = $uri;
344
                }
345
            }
346
        }
347
        // Swap XML and doctype order around (for MSIE / Opera standards compliance)
348
        if ($controller->config['config']['doctypeSwitch'] ?? false) {
349
            $docTypeParts = array_reverse($docTypeParts);
350
        }
351
        // Adding doctype parts:
352
        if (!empty($docTypeParts)) {
353
            $pageRenderer->setXmlPrologAndDocType(implode(LF, $docTypeParts));
354
        }
355
        // Begin header section:
356
        $htmlTag = $this->generateHtmlTag($htmlTagAttributes, $controller->config['config'] ?? [], $controller->cObj);
0 ignored issues
show
Bug introduced by
It seems like $controller->cObj can also be of type string; however, parameter $cObj of TYPO3\CMS\Frontend\Http\...dler::generateHtmlTag() does only seem to accept TYPO3\CMS\Frontend\Conte...t\ContentObjectRenderer, 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

356
        $htmlTag = $this->generateHtmlTag($htmlTagAttributes, $controller->config['config'] ?? [], /** @scrutinizer ignore-type */ $controller->cObj);
Loading history...
357
        $pageRenderer->setHtmlTag($htmlTag);
358
        // Head tag:
359
        $headTag = $controller->pSetup['headTag'] ?? '<head>';
360
        if (isset($controller->pSetup['headTag.'])) {
361
            $headTag = $controller->cObj->stdWrap($headTag, $controller->pSetup['headTag.']);
362
        }
363
        $pageRenderer->setHeadTag($headTag);
364
        // Setting charset meta tag:
365
        $pageRenderer->setCharSet($theCharset);
366
        $pageRenderer->addInlineComment(GeneralUtility::makeInstance(Typo3Information::class)->getInlineHeaderComment());
367
        if ($controller->baseUrl) {
368
            $pageRenderer->setBaseUrl($controller->baseUrl);
369
        }
370
        if ($controller->pSetup['shortcutIcon'] ?? false) {
371
            try {
372
                $favIcon = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($controller->pSetup['shortcutIcon']);
373
                $iconFileInfo = GeneralUtility::makeInstance(ImageInfo::class, Environment::getPublicPath() . '/' . $favIcon);
374
                if ($iconFileInfo->isFile()) {
375
                    $iconMimeType = $iconFileInfo->getMimeType();
376
                    if ($iconMimeType) {
377
                        $iconMimeType = ' type="' . $iconMimeType . '"';
378
                        $pageRenderer->setIconMimeType($iconMimeType);
379
                    }
380
                    $pageRenderer->setFavIcon(PathUtility::getAbsoluteWebPath($controller->absRefPrefix . $favIcon));
381
                }
382
            } catch (Exception $e) {
383
                // do nothing
384
            }
385
        }
386
        // Including CSS files
387
        if (is_array($controller->tmpl->setup['plugin.'] ?? null)) {
388
            $stylesFromPlugins = '';
389
            foreach ($controller->tmpl->setup['plugin.'] as $key => $iCSScode) {
390
                if (is_array($iCSScode)) {
391
                    if ($iCSScode['_CSS_DEFAULT_STYLE'] && empty($controller->config['config']['removeDefaultCss'])) {
392
                        if (isset($iCSScode['_CSS_DEFAULT_STYLE.'])) {
393
                            $cssDefaultStyle = $controller->cObj->stdWrap($iCSScode['_CSS_DEFAULT_STYLE'], $iCSScode['_CSS_DEFAULT_STYLE.']);
394
                        } else {
395
                            $cssDefaultStyle = $iCSScode['_CSS_DEFAULT_STYLE'];
396
                        }
397
                        $stylesFromPlugins .= '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssDefaultStyle . LF;
398
                    }
399
                    if ($iCSScode['_CSS_PAGE_STYLE'] && empty($controller->config['config']['removePageCss'])) {
400
                        $cssPageStyle = implode(LF, $iCSScode['_CSS_PAGE_STYLE']);
401
                        if (isset($iCSScode['_CSS_PAGE_STYLE.'])) {
402
                            $cssPageStyle = $controller->cObj->stdWrap($cssPageStyle, $iCSScode['_CSS_PAGE_STYLE.']);
403
                        }
404
                        $cssPageStyle = '/* specific page styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssPageStyle;
405
                        $this->addCssToPageRenderer($controller, $cssPageStyle, true, 'InlinePageCss');
406
                    }
407
                }
408
            }
409
            if (!empty($stylesFromPlugins)) {
410
                $this->addCssToPageRenderer($controller, $stylesFromPlugins, false, 'InlineDefaultCss');
411
            }
412
        }
413
        /**********************************************************************/
414
        /* config.includeCSS / config.includeCSSLibs
415
        /**********************************************************************/
416
        if (is_array($controller->pSetup['includeCSS.'] ?? null)) {
417
            foreach ($controller->pSetup['includeCSS.'] as $key => $CSSfile) {
418
                if (!is_array($CSSfile)) {
419
                    $cssFileConfig = &$controller->pSetup['includeCSS.'][$key . '.'];
420
                    if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
421
                        continue;
422
                    }
423
                    if ($cssFileConfig['external']) {
424
                        $ss = $CSSfile;
425
                    } else {
426
                        try {
427
                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
428
                        } catch (Exception $e) {
429
                            $ss = null;
430
                        }
431
                    }
432
                    if ($ss) {
433
                        if ($cssFileConfig['import']) {
434
                            if (!$cssFileConfig['external'] && $ss[0] !== '/') {
435
                                // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
436
                                $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
437
                            }
438
                            $cssMedia = !empty($cssFileConfig['media']) ? ' ' . htmlspecialchars($cssFileConfig['media']) : '';
439
                            $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '")' . $cssMedia . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
440
                        } else {
441
                            $pageRenderer->addCssFile(
442
                                $ss,
443
                                $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
444
                                $cssFileConfig['media'] ?: 'all',
445
                                $cssFileConfig['title'] ?: '',
446
                                $cssFileConfig['external']  || (bool)$cssFileConfig['inline'] ? false : empty($cssFileConfig['disableCompression']),
447
                                (bool)$cssFileConfig['forceOnTop'],
448
                                $cssFileConfig['allWrap'],
449
                                (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
450
                                $cssFileConfig['allWrap.']['splitChar'],
451
                                $cssFileConfig['inline']
452
                            );
453
                            unset($cssFileConfig);
454
                        }
455
                    }
456
                }
457
            }
458
        }
459
        if (is_array($controller->pSetup['includeCSSLibs.'] ?? null)) {
460
            foreach ($controller->pSetup['includeCSSLibs.'] as $key => $CSSfile) {
461
                if (!is_array($CSSfile)) {
462
                    $cssFileConfig = &$controller->pSetup['includeCSSLibs.'][$key . '.'];
463
                    if (isset($cssFileConfig['if.']) && !$controller->cObj->checkIf($cssFileConfig['if.'])) {
464
                        continue;
465
                    }
466
                    if ($cssFileConfig['external']) {
467
                        $ss = $CSSfile;
468
                    } else {
469
                        try {
470
                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
471
                        } catch (Exception $e) {
472
                            $ss = null;
473
                        }
474
                    }
475
                    if ($ss) {
476
                        if ($cssFileConfig['import']) {
477
                            if (!$cssFileConfig['external'] && $ss[0] !== '/') {
478
                                // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
479
                                $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
480
                            }
481
                            $cssMedia = !empty($cssFileConfig['media']) ? ' ' . htmlspecialchars($cssFileConfig['media']) : '';
482
                            $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '")' . $cssMedia . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
483
                        } else {
484
                            $pageRenderer->addCssLibrary(
485
                                $ss,
486
                                $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
487
                                $cssFileConfig['media'] ?: 'all',
488
                                $cssFileConfig['title'] ?: '',
489
                                $cssFileConfig['external'] || (bool)$cssFileConfig['inline'] ? false : empty($cssFileConfig['disableCompression']),
490
                                (bool)$cssFileConfig['forceOnTop'],
491
                                $cssFileConfig['allWrap'],
492
                                (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
493
                                $cssFileConfig['allWrap.']['splitChar'],
494
                                $cssFileConfig['inline']
495
                            );
496
                            unset($cssFileConfig);
497
                        }
498
                    }
499
                }
500
            }
501
        }
502
503
        // CSS_inlineStyle from TS
504
        $style = trim($controller->pSetup['CSS_inlineStyle'] ?? '');
505
        $style .= $controller->cObj->cObjGet($controller->pSetup['cssInline.'] ?? null, 'cssInline.');
506
        if (trim($style)) {
507
            $this->addCssToPageRenderer($controller, $style, true, 'additionalTSFEInlineStyle');
508
        }
509
        // JavaScript library files
510
        if (is_array($controller->pSetup['includeJSLibs.'] ?? null)) {
511
            foreach ($controller->pSetup['includeJSLibs.'] as $key => $JSfile) {
512
                if (!is_array($JSfile)) {
513
                    if (isset($controller->pSetup['includeJSLibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSLibs.'][$key . '.']['if.'])) {
514
                        continue;
515
                    }
516
                    if ($controller->pSetup['includeJSLibs.'][$key . '.']['external']) {
517
                        $ss = $JSfile;
518
                    } else {
519
                        try {
520
                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
521
                        } catch (Exception $e) {
522
                            $ss = null;
523
                        }
524
                    }
525
                    if ($ss) {
526
                        $jsFileConfig = &$controller->pSetup['includeJSLibs.'][$key . '.'];
527
                        $type = $jsFileConfig['type'] ?? $defaultTypeAttributeForJavaScript;
528
                        $crossOrigin = $jsFileConfig['crossorigin'];
529
                        if (!$crossOrigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
530
                            $crossOrigin = 'anonymous';
531
                        }
532
                        $pageRenderer->addJsLibrary(
533
                            $key,
534
                            $ss,
535
                            $type,
536
                            $jsFileConfig['external'] ? false : empty($jsFileConfig['disableCompression']),
537
                            (bool)$jsFileConfig['forceOnTop'],
538
                            $jsFileConfig['allWrap'],
539
                            (bool)$jsFileConfig['excludeFromConcatenation'],
540
                            $jsFileConfig['allWrap.']['splitChar'],
541
                            (bool)$jsFileConfig['async'],
542
                            $jsFileConfig['integrity'],
543
                            (bool)$jsFileConfig['defer'],
544
                            $crossOrigin,
545
                            (bool)$jsFileConfig['nomodule']
546
                        );
547
                        unset($jsFileConfig);
548
                    }
549
                }
550
            }
551
        }
552
        if (is_array($controller->pSetup['includeJSFooterlibs.'] ?? null)) {
553
            foreach ($controller->pSetup['includeJSFooterlibs.'] as $key => $JSfile) {
554
                if (!is_array($JSfile)) {
555
                    if (isset($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooterlibs.'][$key . '.']['if.'])) {
556
                        continue;
557
                    }
558
                    if ($controller->pSetup['includeJSFooterlibs.'][$key . '.']['external']) {
559
                        $ss = $JSfile;
560
                    } else {
561
                        try {
562
                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
563
                        } catch (Exception $e) {
564
                            $ss = null;
565
                        }
566
                    }
567
                    if ($ss) {
568
                        $jsFileConfig = &$controller->pSetup['includeJSFooterlibs.'][$key . '.'];
569
                        $type = $jsFileConfig['type'] ?? $defaultTypeAttributeForJavaScript;
570
                        $crossorigin = $jsFileConfig['crossorigin'];
571
                        if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
572
                            $crossorigin = 'anonymous';
573
                        }
574
                        $pageRenderer->addJsFooterLibrary(
575
                            $key,
576
                            $ss,
577
                            $type,
578
                            $jsFileConfig['external'] ? false : empty($jsFileConfig['disableCompression']),
579
                            (bool)$jsFileConfig['forceOnTop'],
580
                            $jsFileConfig['allWrap'],
581
                            (bool)$jsFileConfig['excludeFromConcatenation'],
582
                            $jsFileConfig['allWrap.']['splitChar'],
583
                            (bool)$jsFileConfig['async'],
584
                            $jsFileConfig['integrity'],
585
                            (bool)$jsFileConfig['defer'],
586
                            $crossorigin,
587
                            (bool)$jsFileConfig['nomodule']
588
                        );
589
                        unset($jsFileConfig);
590
                    }
591
                }
592
            }
593
        }
594
        // JavaScript files
595
        if (is_array($controller->pSetup['includeJS.'] ?? null)) {
596
            foreach ($controller->pSetup['includeJS.'] as $key => $JSfile) {
597
                if (!is_array($JSfile)) {
598
                    if (isset($controller->pSetup['includeJS.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJS.'][$key . '.']['if.'])) {
599
                        continue;
600
                    }
601
                    if ($controller->pSetup['includeJS.'][$key . '.']['external']) {
602
                        $ss = $JSfile;
603
                    } else {
604
                        try {
605
                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
606
                        } catch (Exception $e) {
607
                            $ss = null;
608
                        }
609
                    }
610
                    if ($ss) {
611
                        $jsConfig = &$controller->pSetup['includeJS.'][$key . '.'];
612
                        $type = $jsConfig['type'] ?? $defaultTypeAttributeForJavaScript;
613
                        $crossorigin = $jsConfig['crossorigin'];
614
                        if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
615
                            $crossorigin = 'anonymous';
616
                        }
617
                        $pageRenderer->addJsFile(
618
                            $ss,
619
                            $type,
620
                            $jsConfig['external'] ? false : empty($jsConfig['disableCompression']),
621
                            (bool)$jsConfig['forceOnTop'],
622
                            $jsConfig['allWrap'],
623
                            (bool)$jsConfig['excludeFromConcatenation'],
624
                            $jsConfig['allWrap.']['splitChar'],
625
                            (bool)$jsConfig['async'],
626
                            $jsConfig['integrity'],
627
                            (bool)$jsConfig['defer'],
628
                            $crossorigin,
629
                            (bool)$jsConfig['nomodule']
630
                        );
631
                        unset($jsConfig);
632
                    }
633
                }
634
            }
635
        }
636
        if (is_array($controller->pSetup['includeJSFooter.'] ?? null)) {
637
            foreach ($controller->pSetup['includeJSFooter.'] as $key => $JSfile) {
638
                if (!is_array($JSfile)) {
639
                    if (isset($controller->pSetup['includeJSFooter.'][$key . '.']['if.']) && !$controller->cObj->checkIf($controller->pSetup['includeJSFooter.'][$key . '.']['if.'])) {
640
                        continue;
641
                    }
642
                    if ($controller->pSetup['includeJSFooter.'][$key . '.']['external']) {
643
                        $ss = $JSfile;
644
                    } else {
645
                        try {
646
                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
647
                        } catch (Exception $e) {
648
                            $ss = null;
649
                        }
650
                    }
651
                    if ($ss) {
652
                        $jsConfig = &$controller->pSetup['includeJSFooter.'][$key . '.'];
653
                        $type = $jsConfig['type'] ?? $defaultTypeAttributeForJavaScript;
654
                        $crossorigin = $jsConfig['crossorigin'];
655
                        if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
656
                            $crossorigin = 'anonymous';
657
                        }
658
                        $pageRenderer->addJsFooterFile(
659
                            $ss,
660
                            $type,
661
                            $jsConfig['external'] ? false : empty($jsConfig['disableCompression']),
662
                            (bool)$jsConfig['forceOnTop'],
663
                            $jsConfig['allWrap'],
664
                            (bool)$jsConfig['excludeFromConcatenation'],
665
                            $jsConfig['allWrap.']['splitChar'],
666
                            (bool)$jsConfig['async'],
667
                            $jsConfig['integrity'],
668
                            (bool)$jsConfig['defer'],
669
                            $crossorigin,
670
                            (bool)$jsConfig['nomodule']
671
                        );
672
                        unset($jsConfig);
673
                    }
674
                }
675
            }
676
        }
677
        // Headerdata
678
        if (is_array($controller->pSetup['headerData.'] ?? null)) {
679
            $pageRenderer->addHeaderData($controller->cObj->cObjGet($controller->pSetup['headerData.'], 'headerData.'));
680
        }
681
        // Footerdata
682
        if (is_array($controller->pSetup['footerData.'] ?? null)) {
683
            $pageRenderer->addFooterData($controller->cObj->cObjGet($controller->pSetup['footerData.'], 'footerData.'));
684
        }
685
        $controller->generatePageTitle();
686
687
        // @internal hook for EXT:seo, will be gone soon, do not use it in your own extensions
688
        $_params = ['page' => $controller->page];
689
        $_ref = null;
690
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
691
            GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
692
        }
693
694
        $this->generateHrefLangTags($controller, $request);
695
        $this->generateMetaTagHtml(
696
            $controller->pSetup['meta.'] ?? [],
697
            $controller->cObj
0 ignored issues
show
Bug introduced by
It seems like $controller->cObj can also be of type string; however, parameter $cObj of TYPO3\CMS\Frontend\Http\...::generateMetaTagHtml() does only seem to accept TYPO3\CMS\Frontend\Conte...t\ContentObjectRenderer, 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

697
            /** @scrutinizer ignore-type */ $controller->cObj
Loading history...
698
        );
699
700
        $controller->INTincScript_loadJSCode();
701
        $scriptJsCode = '';
702
703
        if ($controller->spamProtectEmailAddresses && $controller->spamProtectEmailAddresses !== 'ascii') {
704
            $scriptJsCode = '
705
			/* decrypt helper function */
706
		function decryptCharcode(n,start,end,offset) {
707
			n = n + offset;
708
			if (offset > 0 && n > end) {
709
				n = start + (n - end - 1);
710
			} else if (offset < 0 && n < start) {
711
				n = end - (start - n - 1);
712
			}
713
			return String.fromCharCode(n);
714
		}
715
			/* decrypt string */
716
		function decryptString(enc,offset) {
717
			var dec = "";
718
			var len = enc.length;
719
			for(var i=0; i < len; i++) {
720
				var n = enc.charCodeAt(i);
721
				if (n >= 0x2B && n <= 0x3A) {
722
					dec += decryptCharcode(n,0x2B,0x3A,offset);	/* 0-9 . , - + / : */
723
				} else if (n >= 0x40 && n <= 0x5A) {
724
					dec += decryptCharcode(n,0x40,0x5A,offset);	/* A-Z @ */
725
				} else if (n >= 0x61 && n <= 0x7A) {
726
					dec += decryptCharcode(n,0x61,0x7A,offset);	/* a-z */
727
				} else {
728
					dec += enc.charAt(i);
729
				}
730
			}
731
			return dec;
732
		}
733
			/* decrypt spam-protected emails */
734
		function linkTo_UnCryptMailto(s) {
735
			location.href = decryptString(s,' . $controller->spamProtectEmailAddresses * -1 . ');
736
		}
737
		';
738
        }
739
        // Add inline JS
740
        $inlineJS = '';
741
        // defined in php
742
        if (is_array($controller->inlineJS)) {
0 ignored issues
show
introduced by
The condition is_array($controller->inlineJS) is always false.
Loading history...
743
            foreach ($controller->inlineJS as $key => $val) {
744
                if (!is_array($val)) {
745
                    $inlineJS .= LF . $val . LF;
746
                }
747
            }
748
        }
749
        // defined in TS with page.inlineJS
750
        // Javascript inline code
751
        $inline = $controller->cObj->cObjGet($controller->pSetup['jsInline.'] ?? null, 'jsInline.');
752
        if ($inline) {
753
            $inlineJS .= LF . $inline . LF;
754
        }
755
        // Javascript inline code for Footer
756
        $inlineFooterJs = $controller->cObj->cObjGet($controller->pSetup['jsFooterInline.'] ?? null, 'jsFooterInline.');
757
        // Should minify?
758
        if ($controller->config['config']['compressJs'] ?? false) {
759
            $pageRenderer->enableCompressJavascript();
760
            $minifyErrorScript = ($minifyErrorInline = '');
761
            $scriptJsCode = GeneralUtility::minifyJavaScript($scriptJsCode, $minifyErrorScript);
762
            if ($minifyErrorScript) {
763
                $this->timeTracker->setTSlogMessage($minifyErrorScript, 3);
764
            }
765
            if ($inlineJS) {
766
                $inlineJS = GeneralUtility::minifyJavaScript($inlineJS, $minifyErrorInline);
767
                if ($minifyErrorInline) {
768
                    $this->timeTracker->setTSlogMessage($minifyErrorInline, 3);
769
                }
770
            }
771
            if ($inlineFooterJs) {
772
                $inlineFooterJs = GeneralUtility::minifyJavaScript($inlineFooterJs, $minifyErrorInline);
773
                if ($minifyErrorInline) {
774
                    $this->timeTracker->setTSlogMessage($minifyErrorInline, 3);
775
                }
776
            }
777
        }
778
        if (!isset($controller->config['config']['removeDefaultJS']) || !$controller->config['config']['removeDefaultJS']) {
779
            // include default and inlineJS
780
            if ($scriptJsCode) {
781
                $pageRenderer->addJsInlineCode('_scriptCode', $scriptJsCode, $controller->config['config']['compressJs']);
782
            }
783
            if ($inlineJS) {
784
                $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $controller->config['config']['compressJs']);
785
            }
786
            if ($inlineFooterJs) {
787
                $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $controller->config['config']['compressJs']);
788
            }
789
        } elseif ($controller->config['config']['removeDefaultJS'] === 'external') {
790
            /*
791
             * This keeps inlineJS from *_INT Objects from being moved to external files.
792
             * At this point in frontend rendering *_INT Objects only have placeholders instead
793
             * of actual content so moving these placeholders to external files would
794
             *     a) break the JS file (syntax errors due to the placeholders)
795
             *     b) the needed JS would never get included to the page
796
             * Therefore inlineJS from *_INT Objects must not be moved to external files but
797
             * kept internal.
798
             */
799
            $inlineJSint = '';
800
            $this->stripIntObjectPlaceholder($inlineJS, $inlineJSint);
801
            if ($inlineJSint) {
802
                $pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $controller->config['config']['compressJs']);
803
            }
804
            if (trim($scriptJsCode . $inlineJS)) {
805
                $pageRenderer->addJsFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($scriptJsCode . $inlineJS), $defaultTypeAttributeForJavaScript, $controller->config['config']['compressJs']);
806
            }
807
            if ($inlineFooterJs) {
808
                $inlineFooterJSint = '';
809
                $this->stripIntObjectPlaceholder($inlineFooterJs, $inlineFooterJSint);
810
                if ($inlineFooterJSint) {
811
                    $pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $controller->config['config']['compressJs']);
812
                }
813
                $pageRenderer->addJsFooterFile(GeneralUtility::writeJavaScriptContentToTemporaryFile($inlineFooterJs), $defaultTypeAttributeForJavaScript, $controller->config['config']['compressJs']);
814
            }
815
        } else {
816
            // Include only inlineJS
817
            if ($inlineJS) {
818
                $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $controller->config['config']['compressJs']);
819
            }
820
            if ($inlineFooterJs) {
821
                $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $controller->config['config']['compressJs']);
822
            }
823
        }
824
        if (isset($controller->pSetup['inlineLanguageLabelFiles.']) && is_array($controller->pSetup['inlineLanguageLabelFiles.'])) {
825
            foreach ($controller->pSetup['inlineLanguageLabelFiles.'] as $key => $languageFile) {
826
                if (is_array($languageFile)) {
827
                    continue;
828
                }
829
                $languageFileConfig = &$controller->pSetup['inlineLanguageLabelFiles.'][$key . '.'];
830
                if (isset($languageFileConfig['if.']) && !$controller->cObj->checkIf($languageFileConfig['if.'])) {
831
                    continue;
832
                }
833
                $pageRenderer->addInlineLanguageLabelFile(
834
                    $languageFile,
835
                    $languageFileConfig['selectionPrefix'] ?: '',
836
                    $languageFileConfig['stripFromSelectionName'] ?: ''
837
                );
838
            }
839
        }
840
        if (isset($controller->pSetup['inlineSettings.']) && is_array($controller->pSetup['inlineSettings.'])) {
841
            $pageRenderer->addInlineSettingArray('TS', $controller->pSetup['inlineSettings.']);
842
        }
843
        // Compression and concatenate settings
844
        if ($controller->config['config']['compressCss'] ?? false) {
845
            $pageRenderer->enableCompressCss();
846
        }
847
        if ($controller->config['config']['compressJs'] ?? false) {
848
            $pageRenderer->enableCompressJavascript();
849
        }
850
        if ($controller->config['config']['concatenateCss'] ?? false) {
851
            $pageRenderer->enableConcatenateCss();
852
        }
853
        if ($controller->config['config']['concatenateJs'] ?? false) {
854
            $pageRenderer->enableConcatenateJavascript();
855
        }
856
        // Add header data block
857
        if ($controller->additionalHeaderData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $controller->additionalHeaderData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
858
            $pageRenderer->addHeaderData(implode(LF, $controller->additionalHeaderData));
859
        }
860
        // Add footer data block
861
        if ($controller->additionalFooterData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $controller->additionalFooterData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
862
            $pageRenderer->addFooterData(implode(LF, $controller->additionalFooterData));
863
        }
864
        // Header complete, now the body tag is added so the regular content can be applied later-on
865
        if ($controller->config['config']['disableBodyTag'] ?? false) {
866
            $bodyTag = '';
867
        } else {
868
            $defBT = (isset($controller->pSetup['bodyTagCObject']) && $controller->pSetup['bodyTagCObject'])
869
                ? $controller->cObj->cObjGetSingle($controller->pSetup['bodyTagCObject'], $controller->pSetup['bodyTagCObject.'], 'bodyTagCObject')
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
870
                : '<body>';
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
871
            $bodyTag = (isset($controller->pSetup['bodyTag']) && $controller->pSetup['bodyTag'])
872
                ? $controller->pSetup['bodyTag']
0 ignored issues
show
Coding Style introduced by
Expected 1 space before "?"; newline found
Loading history...
873
                : $defBT;
0 ignored issues
show
Coding Style introduced by
Expected 1 space before ":"; newline found
Loading history...
874
            if (trim($controller->pSetup['bodyTagAdd'] ?? '')) {
875
                $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($controller->pSetup['bodyTagAdd']) . '>';
876
            }
877
        }
878
        $pageRenderer->addBodyContent(LF . $bodyTag);
879
    }
880
881
    /*************************
882
     *
883
     * Helper functions
884
     *
885
     *************************/
886
887
    /**
888
     * Searches for placeholder created from *_INT cObjects, removes them from
889
     * $searchString and merges them to $intObjects
890
     *
891
     * @param string $searchString The String which should be cleaned from int-object markers
892
     * @param string $intObjects The String the found int-placeholders are moved to (for further processing)
893
     */
894
    protected function stripIntObjectPlaceholder(&$searchString, &$intObjects)
895
    {
896
        $tempArray = [];
897
        preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
898
        $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
899
        $intObjects = implode('', $tempArray[0]);
900
    }
901
902
    /**
903
     * Generate meta tags from meta tag TypoScript
904
     *
905
     * @param array $metaTagTypoScript TypoScript configuration for meta tags (e.g. $GLOBALS['TSFE']->pSetup['meta.'])
906
     * @param ContentObjectRenderer $cObj
907
     */
908
    protected function generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
909
    {
910
        $pageRenderer = $this->getPageRenderer();
911
912
        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
913
        $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
914
        foreach ($conf as $key => $properties) {
915
            $replace = false;
916
            if (is_array($properties)) {
917
                $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
918
                $value = trim($cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']) ?? '');
919
                if ($value === '' && !empty($properties['value'])) {
920
                    $value = $properties['value'];
921
                    $replace = false;
922
                }
923
            } else {
924
                $value = $properties;
925
            }
926
927
            $attribute = 'name';
928
            if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
929
                $attribute = 'http-equiv';
930
            }
931
            if (is_array($properties) && !empty($properties['attribute'])) {
932
                $attribute = $properties['attribute'];
933
            }
934
            if (is_array($properties) && !empty($properties['replace'])) {
935
                $replace = true;
936
            }
937
938
            if (!is_array($value)) {
939
                $value = (array)$value;
940
            }
941
            foreach ($value as $subValue) {
942
                if (trim($subValue ?? '') !== '') {
943
                    $pageRenderer->setMetaTag($attribute, $key, $subValue, [], $replace);
944
                }
945
            }
946
        }
947
    }
948
949
    /**
950
     * @return PageRenderer
951
     */
952
    protected function getPageRenderer(): PageRenderer
953
    {
954
        return GeneralUtility::makeInstance(PageRenderer::class);
955
    }
956
957
    /**
958
     * Adds inline CSS code, by respecting the inlineStyle2TempFile option
959
     *
960
     * @param TypoScriptFrontendController $controller
961
     * @param string $cssStyles the inline CSS styling
962
     * @param bool $excludeFromConcatenation option to see if it should be concatenated
963
     * @param string $inlineBlockName the block name to add it
964
     */
965
    protected function addCssToPageRenderer(TypoScriptFrontendController $controller, string $cssStyles, bool $excludeFromConcatenation, string $inlineBlockName)
966
    {
967
        if (empty($controller->config['config']['inlineStyle2TempFile'] ?? false)) {
968
            $this->getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($controller->config['config']['compressCss'] ?? false));
969
        } else {
970
            $this->getPageRenderer()->addCssFile(
971
                GeneralUtility::writeStyleSheetContentToTemporaryFile($cssStyles),
972
                'stylesheet',
973
                'all',
974
                '',
975
                (bool)($controller->config['config']['compressCss'] ?? false),
976
                false,
977
                '',
978
                $excludeFromConcatenation
979
            );
980
        }
981
    }
982
983
    /**
984
     * Generates the <html> tag by evaluating TypoScript configuration, usually found via:
985
     *
986
     * - Adding extra attributes in addition to pre-generated ones (e.g. "dir")
987
     *     config.htmlTag.attributes.no-js = 1
988
     *     config.htmlTag.attributes.empty-attribute =
989
     *
990
     * - Adding one full string (no stdWrap!) to the "<html $htmlTagAttributes {config.htmlTag_setParams}>" tag
991
     *     config.htmlTag_setParams = string|"none"
992
     *
993
     *   If config.htmlTag_setParams = none is set, even the pre-generated values are not added at all anymore.
994
     *
995
     * - "config.htmlTag_stdWrap" always applies over the whole compiled tag.
996
     *
997
     * @param array $htmlTagAttributes pre-generated attributes by doctype/direction etc. values.
998
     * @param array $configuration the TypoScript configuration "config." array
999
     * @param ContentObjectRenderer $cObj
1000
     * @return string the full <html> tag as string
1001
     */
1002
    protected function generateHtmlTag(array $htmlTagAttributes, array $configuration, ContentObjectRenderer $cObj): string
1003
    {
1004
        if (is_array($configuration['htmlTag.']['attributes.'] ?? null)) {
1005
            $attributeString = '';
1006
            foreach ($configuration['htmlTag.']['attributes.'] as $attributeName => $value) {
1007
                $attributeString .= ' ' . htmlspecialchars($attributeName) . ($value !== '' ? '="' . htmlspecialchars((string)$value) . '"' : '');
1008
                // If e.g. "htmlTag.attributes.dir" is set, make sure it is not added again with "implodeAttributes()"
1009
                if (isset($htmlTagAttributes[$attributeName])) {
1010
                    unset($htmlTagAttributes[$attributeName]);
1011
                }
1012
            }
1013
            $attributeString = ltrim(GeneralUtility::implodeAttributes($htmlTagAttributes) . $attributeString);
1014
        } elseif (($configuration['htmlTag_setParams'] ?? '') === 'none') {
1015
            $attributeString = '';
1016
        } elseif (isset($configuration['htmlTag_setParams'])) {
1017
            $attributeString = $configuration['htmlTag_setParams'];
1018
        } else {
1019
            $attributeString = GeneralUtility::implodeAttributes($htmlTagAttributes);
1020
        }
1021
        $htmlTag = '<html' . ($attributeString ? ' ' . $attributeString : '') . '>';
1022
        if (isset($configuration['htmlTag_stdWrap.'])) {
1023
            $htmlTag = $cObj->stdWrap($htmlTag, $configuration['htmlTag_stdWrap.']);
1024
        }
1025
        return $htmlTag;
1026
    }
1027
1028
    protected function generateHrefLangTags(TypoScriptFrontendController $controller, ServerRequestInterface $request): void
1029
    {
1030
        $hrefLangs = $this->eventDispatcher->dispatch(
1031
            new ModifyHrefLangTagsEvent($request)
1032
        )->getHrefLangs();
1033
        if (count($hrefLangs) > 1) {
1034
            $data = [];
1035
            foreach ($hrefLangs as $hrefLang => $href) {
1036
                $data[] = sprintf('<link %s/>', GeneralUtility::implodeAttributes([
1037
                    'rel' => 'alternate',
1038
                    'hreflang' => $hrefLang,
1039
                    'href' => $href,
1040
                ], true));
1041
            }
1042
            $controller->additionalHeaderData[] = implode(LF, $data);
1043
        }
1044
    }
1045
1046
    /**
1047
     * Include the preview block in case we're looking at a hidden page in the LIVE workspace
1048
     *
1049
     * @param TypoScriptFrontendController $controller
1050
     * @internal this method might get moved to a PSR-15 middleware at some point
1051
     */
1052
    protected function displayPreviewInfoMessage(TypoScriptFrontendController $controller)
1053
    {
1054
        $isInPreviewMode = $controller->getContext()->hasAspect('frontend.preview')
1055
            && $controller->getContext()->getPropertyFromAspect('frontend.preview', 'isPreview');
1056
        if (!$isInPreviewMode || $controller->doWorkspacePreview() || ($controller->config['config']['disablePreviewNotification'] ?? false)) {
1057
            return;
1058
        }
1059
        if ($controller->config['config']['message_preview']) {
1060
            $message = $controller->config['config']['message_preview'];
1061
        } else {
1062
            $label = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_tsfe.xlf:preview');
1063
            $styles = [];
1064
            $styles[] = 'position: fixed';
1065
            $styles[] = 'top: 15px';
1066
            $styles[] = 'right: 15px';
1067
            $styles[] = 'padding: 8px 18px';
1068
            $styles[] = 'background: #fff3cd';
1069
            $styles[] = 'border: 1px solid #ffeeba';
1070
            $styles[] = 'font-family: sans-serif';
1071
            $styles[] = 'font-size: 14px';
1072
            $styles[] = 'font-weight: bold';
1073
            $styles[] = 'color: #856404';
1074
            $styles[] = 'z-index: 20000';
1075
            $styles[] = 'user-select: none';
1076
            $styles[] = 'pointer-events: none';
1077
            $styles[] = 'text-align: center';
1078
            $styles[] = 'border-radius: 2px';
1079
            $message = '<div id="typo3-preview-info" style="' . implode(';', $styles) . '">' . htmlspecialchars($label) . '</div>';
1080
        }
1081
        if (!empty($message)) {
1082
            $controller->content = str_ireplace('</body>', $message . '</body>', $controller->content);
1083
        }
1084
    }
1085
1086
    /**
1087
     * @return LanguageService
1088
     */
1089
    protected function getLanguageService()
1090
    {
1091
        return $GLOBALS['LANG'];
1092
    }
1093
}
1094