1<?php
2namespace TYPO3\CMS\Frontend\Page;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use Psr\Http\Message\ServerRequestInterface;
18use TYPO3\CMS\Core\Core\Environment;
19use TYPO3\CMS\Core\Page\PageRenderer;
20use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
21use TYPO3\CMS\Core\TimeTracker\TimeTracker;
22use TYPO3\CMS\Core\Type\File\ImageInfo;
23use TYPO3\CMS\Core\TypoScript\TypoScriptService;
24use TYPO3\CMS\Core\Utility\GeneralUtility;
25use TYPO3\CMS\Core\Utility\MathUtility;
26use TYPO3\CMS\Core\Utility\PathUtility;
27use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
28use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
29use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
30
31/**
32 * Class for starting TypoScript page generation
33 *
34 * The class is not instantiated as an objects but called directly with the "::" operator.
35 * @deprecated this class will be removed in TYPO3 v10.0, as all functionality has been built in RequestHandler already.
36 */
37class PageGenerator
38{
39    /**
40     * Do not render title tag
41     * Typoscript setting: [config][noPageTitle]
42     * @deprecated will not be used anymore, and will be removed in TYPO3 v10.0.
43     */
44    const NO_PAGE_TITLE = 2;
45
46    /**
47     * Rendering the page content
48     * @deprecated since TYPO3 v9.4 will be removed in TYPO3 v10.0. This functionality is now within RequestHandler.
49     */
50    public static function renderContent()
51    {
52        trigger_error('PageGenerator::renderContent() will be removed in TYPO3 v10.0. This logic is now built in TYPO3s Frontend RequestHandler.', E_USER_DEPRECATED);
53        /** @var TypoScriptFrontendController $tsfe */
54        $tsfe = $GLOBALS['TSFE'];
55
56        /** @var TimeTracker $timeTracker */
57        $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
58
59        // PAGE CONTENT
60        $timeTracker->incStackPointer();
61        $timeTracker->push($tsfe->sPre, 'PAGE');
62        $pageContent = $tsfe->cObj->cObjGet($tsfe->pSetup);
63        if ($tsfe->pSetup['wrap']) {
64            $pageContent = $tsfe->cObj->wrap($pageContent, $tsfe->pSetup['wrap']);
65        }
66        if ($tsfe->pSetup['stdWrap.']) {
67            $pageContent = $tsfe->cObj->stdWrap($pageContent, $tsfe->pSetup['stdWrap.']);
68        }
69        // PAGE HEADER (after content - maybe JS is inserted!
70        // if 'disableAllHeaderCode' is set, all the header-code is discarded!
71        if ($tsfe->config['config']['disableAllHeaderCode']) {
72            $tsfe->content = $pageContent;
73        } else {
74            self::renderContentWithHeader($pageContent);
75        }
76        $timeTracker->pull($timeTracker->LR ? $tsfe->content : '');
77        $timeTracker->decStackPointer();
78    }
79
80    /**
81     * Rendering normal HTML-page with header by wrapping the generated content ($pageContent) in body-tags and setting the header accordingly.
82     *
83     * @param string $pageContent The page content which TypoScript objects has generated
84     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. This functionality is now within TYPO3's Frontend Request Handler.
85     */
86    public static function renderContentWithHeader($pageContent)
87    {
88        trigger_error('PageGenerator::renderContentWithHeader() will be removed in TYPO3 v10.0. This logic is now built in TYPO3s Frontend RequestHandler.', E_USER_DEPRECATED);
89        /** @var TypoScriptFrontendController $tsfe */
90        $tsfe = $GLOBALS['TSFE'];
91
92        /** @var TimeTracker $timeTracker */
93        $timeTracker = GeneralUtility::makeInstance(TimeTracker::class);
94
95        $pageRenderer = static::getPageRenderer();
96        if ($tsfe->config['config']['moveJsFromHeaderToFooter'] ?? false) {
97            $pageRenderer->enableMoveJsFromHeaderToFooter();
98        }
99        if ($tsfe->config['config']['pageRendererTemplateFile'] ?? false) {
100            try {
101                $file = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($tsfe->config['config']['pageRendererTemplateFile']);
102                $pageRenderer->setTemplateFile($file);
103            } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
104                // do nothing
105            }
106        }
107        $headerComment = $tsfe->config['config']['headerComment'] ?? null;
108        if (trim($headerComment)) {
109            $pageRenderer->addInlineComment("\t" . str_replace(LF, LF . "\t", trim($headerComment)) . LF);
110        }
111        // Setting charset:
112        $theCharset = $tsfe->metaCharset;
113        // Reset the content variables:
114        $tsfe->content = '';
115        $htmlTagAttributes = [];
116        $htmlLang = $tsfe->config['config']['htmlTag_langKey'] ?? ($tsfe->sys_language_isocode ?: 'en');
117        // Set content direction
118        // More info: http://www.tau.ac.il/~danon/Hebrew/HTML_and_Hebrew.html)
119        $direction = $tsfe->config['config']['htmlTag_dir'] ?? null;
120        if (self::getCurrentSiteLanguage()) {
121            $direction = self::getCurrentSiteLanguage()->getDirection();
122            $htmlLang = self::getCurrentSiteLanguage()->getTwoLetterIsoCode();
123        }
124        if ($direction) {
125            $htmlTagAttributes['dir'] = htmlspecialchars($direction);
126        }
127        // Setting document type:
128        $docTypeParts = [];
129        $xmlDocument = true;
130        // Part 1: XML prologue
131        switch ((string)($tsfe->config['config']['xmlprologue'] ?? '')) {
132            case 'none':
133                $xmlDocument = false;
134                break;
135            case 'xml_10':
136                $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
137                break;
138            case 'xml_11':
139                $docTypeParts[] = '<?xml version="1.1" encoding="' . $theCharset . '"?>';
140                break;
141            case '':
142                if ($tsfe->xhtmlVersion) {
143                    $docTypeParts[] = '<?xml version="1.0" encoding="' . $theCharset . '"?>';
144                } else {
145                    $xmlDocument = false;
146                }
147                break;
148            default:
149                $docTypeParts[] = $tsfe->config['config']['xmlprologue'];
150        }
151        // Part 2: DTD
152        $doctype = $tsfe->config['config']['doctype'] ?? null;
153        if ($doctype) {
154            switch ($doctype) {
155                case 'xhtml_trans':
156                    $docTypeParts[] = '<!DOCTYPE html
157    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
158    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
159                    break;
160                case 'xhtml_strict':
161                    $docTypeParts[] = '<!DOCTYPE html
162    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
163    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
164                    break;
165                case 'xhtml_basic':
166                    $docTypeParts[] = '<!DOCTYPE html
167    PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
168    "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">';
169                    break;
170                case 'xhtml_11':
171                    $docTypeParts[] = '<!DOCTYPE html
172    PUBLIC "-//W3C//DTD XHTML 1.1//EN"
173    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
174                    break;
175                case 'xhtml+rdfa_10':
176                    $docTypeParts[] = '<!DOCTYPE html
177    PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN"
178    "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">';
179                    break;
180                case 'html5':
181                    $docTypeParts[] = '<!DOCTYPE html>';
182                    if ($xmlDocument) {
183                        $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
184                    } else {
185                        $pageRenderer->setMetaCharsetTag('<meta charset="|">');
186                    }
187                    break;
188                case 'none':
189                    break;
190                default:
191                    $docTypeParts[] = $doctype;
192            }
193        } else {
194            $docTypeParts[] = '<!DOCTYPE html>';
195            if ($xmlDocument) {
196                $pageRenderer->setMetaCharsetTag('<meta charset="|" />');
197            } else {
198                $pageRenderer->setMetaCharsetTag('<meta charset="|">');
199            }
200        }
201        if ($tsfe->xhtmlVersion) {
202            $htmlTagAttributes['xml:lang'] = $htmlLang;
203        }
204        if ($tsfe->xhtmlVersion < 110 || $doctype === 'html5') {
205            $htmlTagAttributes['lang'] = $htmlLang;
206        }
207        if ($tsfe->xhtmlVersion || $doctype === 'html5' && $xmlDocument) {
208            // We add this to HTML5 to achieve a slightly better backwards compatibility
209            $htmlTagAttributes['xmlns'] = 'http://www.w3.org/1999/xhtml';
210            if (is_array($tsfe->config['config']['namespaces.'])) {
211                foreach ($tsfe->config['config']['namespaces.'] as $prefix => $uri) {
212                    // $uri gets htmlspecialchared later
213                    $htmlTagAttributes['xmlns:' . htmlspecialchars($prefix)] = $uri;
214                }
215            }
216        }
217        // Swap XML and doctype order around (for MSIE / Opera standards compliance)
218        if ($tsfe->config['config']['doctypeSwitch'] ?? false) {
219            $docTypeParts = array_reverse($docTypeParts);
220        }
221        // Adding doctype parts:
222        if (!empty($docTypeParts)) {
223            $pageRenderer->setXmlPrologAndDocType(implode(LF, $docTypeParts));
224        }
225        // Begin header section:
226        if (is_array($tsfe->config['config']['htmlTag.']['attributes.'] ?? null)) {
227            $_attr = '';
228            foreach ($tsfe->config['config']['htmlTag.']['attributes.'] as $attributeName => $value) {
229                $_attr .= ' ' . htmlspecialchars($attributeName) . ($value !== '' ? '="' . htmlspecialchars((string)$value) . '"' : '');
230                // If e.g. "htmlTag.attributes.dir" is set, make sure it is not added again with "implodeAttributes()"
231                if (isset($htmlTagAttributes[$attributeName])) {
232                    unset($htmlTagAttributes[$attributeName]);
233                }
234            }
235            $_attr = GeneralUtility::implodeAttributes($htmlTagAttributes) . $_attr;
236        } elseif (($tsfe->config['config']['htmlTag_setParams'] ?? '') === 'none') {
237            $_attr = '';
238        } elseif (isset($tsfe->config['config']['htmlTag_setParams'])) {
239            $_attr = $tsfe->config['config']['htmlTag_setParams'];
240        } else {
241            $_attr = GeneralUtility::implodeAttributes($htmlTagAttributes);
242        }
243        $htmlTag = '<html' . ($_attr ? ' ' . $_attr : '') . '>';
244        if (isset($tsfe->config['config']['htmlTag_stdWrap.'])) {
245            $htmlTag = $tsfe->cObj->stdWrap($htmlTag, $tsfe->config['config']['htmlTag_stdWrap.']);
246        }
247        $pageRenderer->setHtmlTag($htmlTag);
248        // Head tag:
249        $headTag = $tsfe->pSetup['headTag'] ?? '<head>';
250        if (isset($tsfe->pSetup['headTag.'])) {
251            $headTag = $tsfe->cObj->stdWrap($headTag, $tsfe->pSetup['headTag.']);
252        }
253        $pageRenderer->setHeadTag($headTag);
254        // Setting charset meta tag:
255        $pageRenderer->setCharSet($theCharset);
256        $pageRenderer->addInlineComment('	This website is powered by TYPO3 - inspiring people to share!
257	TYPO3 is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.
258	TYPO3 is copyright ' . TYPO3_copyright_year . ' of Kasper Skaarhoj. Extensions are copyright of their respective owners.
259	Information and contribution at ' . TYPO3_URL_GENERAL . '
260');
261        if ($tsfe->baseUrl) {
262            $pageRenderer->setBaseUrl($tsfe->baseUrl);
263        }
264        if ($tsfe->pSetup['shortcutIcon'] ?? false) {
265            try {
266                $favIcon = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($tsfe->pSetup['shortcutIcon']);
267                $iconFileInfo = GeneralUtility::makeInstance(ImageInfo::class, Environment::getPublicPath() . '/' . $favIcon);
268                if ($iconFileInfo->isFile()) {
269                    $iconMimeType = $iconFileInfo->getMimeType();
270                    if ($iconMimeType) {
271                        $iconMimeType = ' type="' . $iconMimeType . '"';
272                        $pageRenderer->setIconMimeType($iconMimeType);
273                    }
274                    $pageRenderer->setFavIcon(PathUtility::getAbsoluteWebPath($tsfe->absRefPrefix . $favIcon));
275                }
276            } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
277                // do nothing
278            }
279        }
280        // Including CSS files
281        if (isset($tsfe->tmpl->setup['plugin.']) && is_array($tsfe->tmpl->setup['plugin.'])) {
282            $stylesFromPlugins = '';
283            foreach ($tsfe->tmpl->setup['plugin.'] as $key => $iCSScode) {
284                if (is_array($iCSScode)) {
285                    if ($iCSScode['_CSS_DEFAULT_STYLE'] && empty($tsfe->config['config']['removeDefaultCss'])) {
286                        if (isset($iCSScode['_CSS_DEFAULT_STYLE.'])) {
287                            $cssDefaultStyle = $tsfe->cObj->stdWrap($iCSScode['_CSS_DEFAULT_STYLE'], $iCSScode['_CSS_DEFAULT_STYLE.']);
288                        } else {
289                            $cssDefaultStyle = $iCSScode['_CSS_DEFAULT_STYLE'];
290                        }
291                        $stylesFromPlugins .= '/* default styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssDefaultStyle . LF;
292                    }
293                    if ($iCSScode['_CSS_PAGE_STYLE'] && empty($tsfe->config['config']['removePageCss'])) {
294                        $cssPageStyle = implode(LF, $iCSScode['_CSS_PAGE_STYLE']);
295                        if (isset($iCSScode['_CSS_PAGE_STYLE.'])) {
296                            $cssPageStyle = $tsfe->cObj->stdWrap($cssPageStyle, $iCSScode['_CSS_PAGE_STYLE.']);
297                        }
298                        $cssPageStyle = '/* specific page styles for extension "' . substr($key, 0, -1) . '" */' . LF . $cssPageStyle;
299                        self::addCssToPageRenderer($cssPageStyle, true, 'InlinePageCss');
300                    }
301                }
302            }
303            if (!empty($stylesFromPlugins)) {
304                self::addCssToPageRenderer($stylesFromPlugins, false, 'InlineDefaultCss');
305            }
306        }
307        /**********************************************************************/
308        /* config.includeCSS / config.includeCSSLibs
309        /**********************************************************************/
310        if (isset($tsfe->pSetup['includeCSS.']) && is_array($tsfe->pSetup['includeCSS.'])) {
311            foreach ($tsfe->pSetup['includeCSS.'] as $key => $CSSfile) {
312                if (!is_array($CSSfile)) {
313                    $cssFileConfig = &$tsfe->pSetup['includeCSS.'][$key . '.'];
314                    if (isset($cssFileConfig['if.']) && !$tsfe->cObj->checkIf($cssFileConfig['if.'])) {
315                        continue;
316                    }
317                    if ($cssFileConfig['external']) {
318                        $ss = $CSSfile;
319                    } else {
320                        try {
321                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
322                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
323                            $ss = null;
324                        }
325                    }
326                    if ($ss) {
327                        if ($cssFileConfig['import']) {
328                            if (!$cssFileConfig['external'] && $ss[0] !== '/') {
329                                // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
330                                $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
331                            }
332                            $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '") ' . htmlspecialchars($cssFileConfig['media']) . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
333                        } else {
334                            $pageRenderer->addCssFile(
335                                $ss,
336                                $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
337                                $cssFileConfig['media'] ?: 'all',
338                                $cssFileConfig['title'] ?: '',
339                                $cssFileConfig['external'] ? false : empty($cssFileConfig['disableCompression']),
340                                (bool)$cssFileConfig['forceOnTop'],
341                                $cssFileConfig['allWrap'],
342                                (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
343                                $cssFileConfig['allWrap.']['splitChar'],
344                                $cssFileConfig['inline']
345                            );
346                            unset($cssFileConfig);
347                        }
348                    }
349                }
350            }
351        }
352        if (isset($tsfe->pSetup['includeCSSLibs.']) && is_array($tsfe->pSetup['includeCSSLibs.'])) {
353            foreach ($tsfe->pSetup['includeCSSLibs.'] as $key => $CSSfile) {
354                if (!is_array($CSSfile)) {
355                    $cssFileConfig = &$tsfe->pSetup['includeCSSLibs.'][$key . '.'];
356                    if (isset($cssFileConfig['if.']) && !$tsfe->cObj->checkIf($cssFileConfig['if.'])) {
357                        continue;
358                    }
359                    if ($cssFileConfig['external']) {
360                        $ss = $CSSfile;
361                    } else {
362                        try {
363                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($CSSfile);
364                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
365                            $ss = null;
366                        }
367                    }
368                    if ($ss) {
369                        if ($cssFileConfig['import']) {
370                            if (!$cssFileConfig['external'] && $ss[0] !== '/') {
371                                // To fix MSIE 6 that cannot handle these as relative paths (according to Ben v Ende)
372                                $ss = GeneralUtility::dirname(GeneralUtility::getIndpEnv('SCRIPT_NAME')) . '/' . $ss;
373                            }
374                            $pageRenderer->addCssInlineBlock('import_' . $key, '@import url("' . htmlspecialchars($ss) . '") ' . htmlspecialchars($cssFileConfig['media']) . ';', empty($cssFileConfig['disableCompression']), (bool)$cssFileConfig['forceOnTop']);
375                        } else {
376                            $pageRenderer->addCssLibrary(
377                                $ss,
378                                $cssFileConfig['alternate'] ? 'alternate stylesheet' : 'stylesheet',
379                                $cssFileConfig['media'] ?: 'all',
380                                $cssFileConfig['title'] ?: '',
381                                $cssFileConfig['external'] ? false : empty($cssFileConfig['disableCompression']),
382                                (bool)$cssFileConfig['forceOnTop'],
383                                $cssFileConfig['allWrap'],
384                                (bool)$cssFileConfig['excludeFromConcatenation'] || (bool)$cssFileConfig['inline'],
385                                $cssFileConfig['allWrap.']['splitChar'],
386                                $cssFileConfig['inline']
387                            );
388                            unset($cssFileConfig);
389                        }
390                    }
391                }
392            }
393        }
394
395        // CSS_inlineStyle from TS
396        $style = trim($tsfe->pSetup['CSS_inlineStyle'] ?? '');
397        $style .= $tsfe->cObj->cObjGet($tsfe->pSetup['cssInline.'] ?? null, 'cssInline.');
398        if (trim($style)) {
399            self::addCssToPageRenderer($style, true, 'additionalTSFEInlineStyle');
400        }
401        // Javascript Libraries
402        if (isset($tsfe->pSetup['javascriptLibs.']) && is_array($tsfe->pSetup['javascriptLibs.'])) {
403            // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, the setting page.javascriptLibs has been deprecated and will be removed in TYPO3 v10.0.
404            trigger_error('The setting page.javascriptLibs will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
405
406            // Include jQuery into the page renderer
407            if (!empty($tsfe->pSetup['javascriptLibs.']['jQuery'])) {
408                // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, the setting page.javascriptLibs.jQuery has been deprecated and will be removed in TYPO3 v10.0.
409                trigger_error('The setting page.javascriptLibs.jQuery will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
410
411                $jQueryTS = $tsfe->pSetup['javascriptLibs.']['jQuery.'];
412                // Check if version / source is set, if not set variable to "NULL" to use the default of the page renderer
413                $version = $jQueryTS['version'] ?? null;
414                $source = $jQueryTS['source'] ?? null;
415                // When "noConflict" is not set or "1" enable the default jQuery noConflict mode, otherwise disable the namespace
416                if (!isset($jQueryTS['noConflict']) || !empty($jQueryTS['noConflict'])) {
417                    $namespace = 'noConflict';
418                } else {
419                    $namespace = PageRenderer::JQUERY_NAMESPACE_NONE;
420                }
421                $pageRenderer->loadJquery($version, $source, $namespace, true);
422            }
423        }
424        // JavaScript library files
425        if (isset($tsfe->pSetup['includeJSLibs.']) && is_array($tsfe->pSetup['includeJSLibs.'])) {
426            foreach ($tsfe->pSetup['includeJSLibs.'] as $key => $JSfile) {
427                if (!is_array($JSfile)) {
428                    if (isset($tsfe->pSetup['includeJSLibs.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJSLibs.'][$key . '.']['if.'])) {
429                        continue;
430                    }
431                    if ($tsfe->pSetup['includeJSLibs.'][$key . '.']['external']) {
432                        $ss = $JSfile;
433                    } else {
434                        try {
435                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
436                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
437                            $ss = null;
438                        }
439                    }
440                    if ($ss) {
441                        $jsFileConfig = &$tsfe->pSetup['includeJSLibs.'][$key . '.'];
442                        $type = $jsFileConfig['type'];
443                        if (!$type) {
444                            $type = 'text/javascript';
445                        }
446                        $crossorigin = $jsFileConfig['crossorigin'];
447                        if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
448                            $crossorigin = 'anonymous';
449                        }
450                        $pageRenderer->addJsLibrary(
451                            $key,
452                            $ss,
453                            $type,
454                            $jsFileConfig['external'] ? false : empty($jsFileConfig['disableCompression']),
455                            (bool)$jsFileConfig['forceOnTop'],
456                            $jsFileConfig['allWrap'],
457                            (bool)$jsFileConfig['excludeFromConcatenation'],
458                            $jsFileConfig['allWrap.']['splitChar'],
459                            (bool)$jsFileConfig['async'],
460                            $jsFileConfig['integrity'],
461                            (bool)$jsFileConfig['defer'],
462                            $crossorigin
463                        );
464                        unset($jsFileConfig);
465                    }
466                }
467            }
468        }
469        if (isset($tsfe->pSetup['includeJSFooterlibs.']) && is_array($tsfe->pSetup['includeJSFooterlibs.'])) {
470            foreach ($tsfe->pSetup['includeJSFooterlibs.'] as $key => $JSfile) {
471                if (!is_array($JSfile)) {
472                    if (isset($tsfe->pSetup['includeJSFooterlibs.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJSFooterlibs.'][$key . '.']['if.'])) {
473                        continue;
474                    }
475                    if ($tsfe->pSetup['includeJSFooterlibs.'][$key . '.']['external']) {
476                        $ss = $JSfile;
477                    } else {
478                        try {
479                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
480                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
481                            $ss = null;
482                        }
483                    }
484                    if ($ss) {
485                        $jsFileConfig = &$tsfe->pSetup['includeJSFooterlibs.'][$key . '.'];
486                        $type = $jsFileConfig['type'];
487                        if (!$type) {
488                            $type = 'text/javascript';
489                        }
490                        $crossorigin = $jsFileConfig['crossorigin'];
491                        if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
492                            $crossorigin = 'anonymous';
493                        }
494                        $pageRenderer->addJsFooterLibrary(
495                            $key,
496                            $ss,
497                            $type,
498                            $jsFileConfig['external'] ? false : empty($jsFileConfig['disableCompression']),
499                            (bool)$jsFileConfig['forceOnTop'],
500                            $jsFileConfig['allWrap'],
501                            (bool)$jsFileConfig['excludeFromConcatenation'],
502                            $jsFileConfig['allWrap.']['splitChar'],
503                            (bool)$jsFileConfig['async'],
504                            $jsFileConfig['integrity'],
505                            (bool)$jsFileConfig['defer'],
506                            $crossorigin
507                        );
508                        unset($jsFileConfig);
509                    }
510                }
511            }
512        }
513        // JavaScript files
514        if (isset($tsfe->pSetup['includeJS.']) && is_array($tsfe->pSetup['includeJS.'])) {
515            foreach ($tsfe->pSetup['includeJS.'] as $key => $JSfile) {
516                if (!is_array($JSfile)) {
517                    if (isset($tsfe->pSetup['includeJS.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJS.'][$key . '.']['if.'])) {
518                        continue;
519                    }
520                    if ($tsfe->pSetup['includeJS.'][$key . '.']['external']) {
521                        $ss = $JSfile;
522                    } else {
523                        try {
524                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
525                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
526                            $ss = null;
527                        }
528                    }
529                    if ($ss) {
530                        $jsConfig = &$tsfe->pSetup['includeJS.'][$key . '.'];
531                        $type = $jsConfig['type'];
532                        if (!$type) {
533                            $type = 'text/javascript';
534                        }
535                        $crossorigin = $jsConfig['crossorigin'];
536                        if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
537                            $crossorigin = 'anonymous';
538                        }
539                        $pageRenderer->addJsFile(
540                            $ss,
541                            $type,
542                            $jsConfig['external'] ? false : empty($jsConfig['disableCompression']),
543                            (bool)$jsConfig['forceOnTop'],
544                            $jsConfig['allWrap'],
545                            (bool)$jsConfig['excludeFromConcatenation'],
546                            $jsConfig['allWrap.']['splitChar'],
547                            (bool)$jsConfig['async'],
548                            $jsConfig['integrity'],
549                            (bool)$jsConfig['defer'],
550                            $crossorigin
551                        );
552                        unset($jsConfig);
553                    }
554                }
555            }
556        }
557        if (isset($tsfe->pSetup['includeJSFooter.']) && is_array($tsfe->pSetup['includeJSFooter.'])) {
558            foreach ($tsfe->pSetup['includeJSFooter.'] as $key => $JSfile) {
559                if (!is_array($JSfile)) {
560                    if (isset($tsfe->pSetup['includeJSFooter.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJSFooter.'][$key . '.']['if.'])) {
561                        continue;
562                    }
563                    if ($tsfe->pSetup['includeJSFooter.'][$key . '.']['external']) {
564                        $ss = $JSfile;
565                    } else {
566                        try {
567                            $ss = GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($JSfile);
568                        } catch (\TYPO3\CMS\Core\Resource\Exception $e) {
569                            $ss = null;
570                        }
571                    }
572                    if ($ss) {
573                        $jsConfig = &$tsfe->pSetup['includeJSFooter.'][$key . '.'];
574                        $type = $jsConfig['type'];
575                        if (!$type) {
576                            $type = 'text/javascript';
577                        }
578                        $crossorigin = $jsConfig['crossorigin'];
579                        if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
580                            $crossorigin = 'anonymous';
581                        }
582                        $pageRenderer->addJsFooterFile(
583                            $ss,
584                            $type,
585                            $jsConfig['external'] ? false : empty($jsConfig['disableCompression']),
586                            (bool)$jsConfig['forceOnTop'],
587                            $jsConfig['allWrap'],
588                            (bool)$jsConfig['excludeFromConcatenation'],
589                            $jsConfig['allWrap.']['splitChar'],
590                            (bool)$jsConfig['async'],
591                            $jsConfig['integrity'],
592                            (bool)$jsConfig['defer'],
593                            $crossorigin
594                        );
595                        unset($jsConfig);
596                    }
597                }
598            }
599        }
600        // Headerdata
601        if (isset($tsfe->pSetup['headerData.']) && is_array($tsfe->pSetup['headerData.'])) {
602            $pageRenderer->addHeaderData($tsfe->cObj->cObjGet($tsfe->pSetup['headerData.'], 'headerData.'));
603        }
604        // Footerdata
605        if (isset($tsfe->pSetup['footerData.']) && is_array($tsfe->pSetup['footerData.'])) {
606            $pageRenderer->addFooterData($tsfe->cObj->cObjGet($tsfe->pSetup['footerData.'], 'footerData.'));
607        }
608        $tsfe->generatePageTitle();
609
610        // @internal hook for EXT:seo, will be gone soon, do not use it in your own extensions
611        $_params = ['page' => $tsfe->page];
612        $_ref = '';
613        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['TYPO3\CMS\Frontend\Page\PageGenerator']['generateMetaTags'] ?? [] as $_funcRef) {
614            GeneralUtility::callUserFunction($_funcRef, $_params, $_ref);
615        }
616
617        static::generateMetaTagHtml(
618            $tsfe->pSetup['meta.'] ?? [],
619            $tsfe->cObj
620        );
621
622        unset($tsfe->additionalHeaderData['JSCode']);
623        if (isset($tsfe->config['INTincScript']) && is_array($tsfe->config['INTincScript'])) {
624            $tsfe->additionalHeaderData['JSCode'] = $tsfe->JSCode;
625            // Storing the JSCode vars...
626            $tsfe->config['INTincScript_ext']['divKey'] = $tsfe->uniqueHash();
627            $tsfe->config['INTincScript_ext']['additionalHeaderData'] = $tsfe->additionalHeaderData;
628            // Storing the header-data array
629            $tsfe->config['INTincScript_ext']['additionalFooterData'] = $tsfe->additionalFooterData;
630            // Storing the footer-data array
631            $tsfe->config['INTincScript_ext']['additionalJavaScript'] = $tsfe->additionalJavaScript;
632            // Storing the JS-data array
633            $tsfe->config['INTincScript_ext']['additionalCSS'] = $tsfe->additionalCSS;
634            // Storing the Style-data array
635            $tsfe->additionalHeaderData = ['<!--HD_' . $tsfe->config['INTincScript_ext']['divKey'] . '-->'];
636            // Clearing the array
637            $tsfe->additionalFooterData = ['<!--FD_' . $tsfe->config['INTincScript_ext']['divKey'] . '-->'];
638            // Clearing the array
639            $tsfe->divSection .= '<!--TDS_' . $tsfe->config['INTincScript_ext']['divKey'] . '-->';
640        } else {
641            $tsfe->INTincScript_loadJSCode();
642        }
643        $scriptJsCode = '';
644
645        if ($tsfe->spamProtectEmailAddresses && $tsfe->spamProtectEmailAddresses !== 'ascii') {
646            $scriptJsCode = '
647			// decrypt helper function
648		function decryptCharcode(n,start,end,offset) {
649			n = n + offset;
650			if (offset > 0 && n > end) {
651				n = start + (n - end - 1);
652			} else if (offset < 0 && n < start) {
653				n = end - (start - n - 1);
654			}
655			return String.fromCharCode(n);
656		}
657			// decrypt string
658		function decryptString(enc,offset) {
659			var dec = "";
660			var len = enc.length;
661			for(var i=0; i < len; i++) {
662				var n = enc.charCodeAt(i);
663				if (n >= 0x2B && n <= 0x3A) {
664					dec += decryptCharcode(n,0x2B,0x3A,offset);	// 0-9 . , - + / :
665				} else if (n >= 0x40 && n <= 0x5A) {
666					dec += decryptCharcode(n,0x40,0x5A,offset);	// A-Z @
667				} else if (n >= 0x61 && n <= 0x7A) {
668					dec += decryptCharcode(n,0x61,0x7A,offset);	// a-z
669				} else {
670					dec += enc.charAt(i);
671				}
672			}
673			return dec;
674		}
675			// decrypt spam-protected emails
676		function linkTo_UnCryptMailto(s) {
677			location.href = decryptString(s,' . $tsfe->spamProtectEmailAddresses * -1 . ');
678		}
679		';
680        }
681        // Add inline JS
682        $inlineJS = '';
683        // defined in php
684        if (is_array($tsfe->inlineJS)) {
685            foreach ($tsfe->inlineJS as $key => $val) {
686                if (!is_array($val)) {
687                    $inlineJS .= LF . $val . LF;
688                }
689            }
690        }
691        // defined in TS with page.inlineJS
692        // Javascript inline code
693        $inline = $tsfe->cObj->cObjGet($tsfe->pSetup['jsInline.'] ?? null, 'jsInline.');
694        if ($inline) {
695            $inlineJS .= LF . $inline . LF;
696        }
697        // Javascript inline code for Footer
698        $inlineFooterJs = $tsfe->cObj->cObjGet($tsfe->pSetup['jsFooterInline.'] ?? null, 'jsFooterInline.');
699        // Should minify?
700        if ($tsfe->config['config']['compressJs'] ?? false) {
701            $pageRenderer->enableCompressJavascript();
702            $minifyErrorScript = ($minifyErrorInline = '');
703            $scriptJsCode = GeneralUtility::minifyJavaScript($scriptJsCode, $minifyErrorScript);
704            if ($minifyErrorScript) {
705                $timeTracker->setTSlogMessage($minifyErrorScript, 3);
706            }
707            if ($inlineJS) {
708                $inlineJS = GeneralUtility::minifyJavaScript($inlineJS, $minifyErrorInline);
709                if ($minifyErrorInline) {
710                    $timeTracker->setTSlogMessage($minifyErrorInline, 3);
711                }
712            }
713            if ($inlineFooterJs) {
714                $inlineFooterJs = GeneralUtility::minifyJavaScript($inlineFooterJs, $minifyErrorInline);
715                if ($minifyErrorInline) {
716                    $timeTracker->setTSlogMessage($minifyErrorInline, 3);
717                }
718            }
719        }
720        if (!isset($tsfe->config['config']['removeDefaultJS']) || !$tsfe->config['config']['removeDefaultJS']) {
721            // include default and inlineJS
722            if ($scriptJsCode) {
723                $pageRenderer->addJsInlineCode('_scriptCode', $scriptJsCode, $tsfe->config['config']['compressJs']);
724            }
725            if ($inlineJS) {
726                $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $tsfe->config['config']['compressJs']);
727            }
728            if ($inlineFooterJs) {
729                $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $tsfe->config['config']['compressJs']);
730            }
731        } elseif ($tsfe->config['config']['removeDefaultJS'] === 'external') {
732            /*
733             * This keeps inlineJS from *_INT Objects from being moved to external files.
734             * At this point in frontend rendering *_INT Objects only have placeholders instead
735             * of actual content so moving these placeholders to external files would
736             *     a) break the JS file (syntax errors due to the placeholders)
737             *     b) the needed JS would never get included to the page
738             * Therefore inlineJS from *_INT Objects must not be moved to external files but
739             * kept internal.
740             */
741            $inlineJSint = '';
742            self::stripIntObjectPlaceholder($inlineJS, $inlineJSint);
743            if ($inlineJSint) {
744                $pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $tsfe->config['config']['compressJs']);
745            }
746            if (trim($scriptJsCode . $inlineJS)) {
747                $pageRenderer->addJsFile(self::inline2TempFile($scriptJsCode . $inlineJS, 'js'), 'text/javascript', $tsfe->config['config']['compressJs']);
748            }
749            if ($inlineFooterJs) {
750                $inlineFooterJSint = '';
751                self::stripIntObjectPlaceholder($inlineFooterJs, $inlineFooterJSint);
752                if ($inlineFooterJSint) {
753                    $pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $tsfe->config['config']['compressJs']);
754                }
755                $pageRenderer->addJsFooterFile(self::inline2TempFile($inlineFooterJs, 'js'), 'text/javascript', $tsfe->config['config']['compressJs']);
756            }
757        } else {
758            // Include only inlineJS
759            if ($inlineJS) {
760                $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $tsfe->config['config']['compressJs']);
761            }
762            if ($inlineFooterJs) {
763                $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $tsfe->config['config']['compressJs']);
764            }
765        }
766        if (isset($tsfe->pSetup['inlineLanguageLabelFiles.']) && is_array($tsfe->pSetup['inlineLanguageLabelFiles.'])) {
767            foreach ($tsfe->pSetup['inlineLanguageLabelFiles.'] as $key => $languageFile) {
768                if (is_array($languageFile)) {
769                    continue;
770                }
771                $languageFileConfig = &$tsfe->pSetup['inlineLanguageLabelFiles.'][$key . '.'];
772                if (isset($languageFileConfig['if.']) && !$tsfe->cObj->checkIf($languageFileConfig['if.'])) {
773                    continue;
774                }
775                $pageRenderer->addInlineLanguageLabelFile(
776                    $languageFile,
777                    $languageFileConfig['selectionPrefix'] ?: '',
778                    $languageFileConfig['stripFromSelectionName'] ?: ''
779                );
780            }
781        }
782        if (isset($tsfe->pSetup['inlineSettings.']) && is_array($tsfe->pSetup['inlineSettings.'])) {
783            $pageRenderer->addInlineSettingArray('TS', $tsfe->pSetup['inlineSettings.']);
784        }
785        // Compression and concatenate settings
786        if ($tsfe->config['config']['compressCss'] ?? false) {
787            $pageRenderer->enableCompressCss();
788        }
789        if ($tsfe->config['config']['compressJs'] ?? false) {
790            $pageRenderer->enableCompressJavascript();
791        }
792        if ($tsfe->config['config']['concatenateCss'] ?? false) {
793            $pageRenderer->enableConcatenateCss();
794        }
795        if ($tsfe->config['config']['concatenateJs'] ?? false) {
796            $pageRenderer->enableConcatenateJavascript();
797        }
798        // Backward compatibility for old configuration
799        // @deprecated - remove this option in TYPO3 v10.0.
800        if ($tsfe->config['config']['concatenateJsAndCss'] ?? false) {
801            trigger_error('Setting config.concatenateJsAndCss is deprecated in favor of config.concatenateJs and config.concatenateCss, and will have no effect anymore in TYPO3 v10.0.', E_USER_DEPRECATED);
802            $pageRenderer->enableConcatenateCss();
803            $pageRenderer->enableConcatenateJavascript();
804        }
805        // Add header data block
806        if ($tsfe->additionalHeaderData) {
807            $pageRenderer->addHeaderData(implode(LF, $tsfe->additionalHeaderData));
808        }
809        // Add footer data block
810        if ($tsfe->additionalFooterData) {
811            $pageRenderer->addFooterData(implode(LF, $tsfe->additionalFooterData));
812        }
813        // Header complete, now add content
814        // Bodytag:
815        if ($tsfe->config['config']['disableBodyTag'] ?? false) {
816            $bodyTag = '';
817        } else {
818            $defBT = (isset($tsfe->pSetup['bodyTagCObject']) && $tsfe->pSetup['bodyTagCObject'])
819                ? $tsfe->cObj->cObjGetSingle($tsfe->pSetup['bodyTagCObject'], $tsfe->pSetup['bodyTagCObject.'], 'bodyTagCObject')
820                : '<body>';
821            $bodyTag = (isset($tsfe->pSetup['bodyTag']) && $tsfe->pSetup['bodyTag'])
822                ? $tsfe->pSetup['bodyTag']
823                : $defBT;
824            if (trim($tsfe->pSetup['bodyTagAdd'] ?? '')) {
825                $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($tsfe->pSetup['bodyTagAdd']) . '>';
826            }
827        }
828        $pageRenderer->addBodyContent(LF . $bodyTag);
829        // Div-sections
830        if ($tsfe->divSection) {
831            $pageRenderer->addBodyContent(LF . $tsfe->divSection);
832        }
833        // Page content
834        $pageRenderer->addBodyContent(LF . $pageContent);
835        if (!empty($tsfe->config['INTincScript']) && is_array($tsfe->config['INTincScript'])) {
836            // Store the serialized pageRenderer in configuration
837            $tsfe->config['INTincScript_ext']['pageRenderer'] = serialize($pageRenderer);
838            // Render complete page, keep placeholders for JavaScript and CSS
839            $tsfe->content = $pageRenderer->renderPageWithUncachedObjects($tsfe->config['INTincScript_ext']['divKey']);
840        } else {
841            // Render complete page
842            $tsfe->content = $pageRenderer->render();
843        }
844    }
845
846    /*************************
847     *
848     * Helper functions
849     * Remember: Calls internally must still be done on the non-instantiated class: PageGenerator::inline2TempFile()
850     *
851     *************************/
852    /**
853     * Searches for placeholder created from *_INT cObjects, removes them from
854     * $searchString and merges them to $intObjects
855     *
856     * @param string $searchString The String which should be cleaned from int-object markers
857     * @param string $intObjects The String the found int-placeholders are moved to (for further processing)
858     */
859    protected static function stripIntObjectPlaceholder(&$searchString, &$intObjects)
860    {
861        $tempArray = [];
862        preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
863        $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
864        $intObjects = implode('', $tempArray[0]);
865    }
866
867    /**
868     * Writes string to a temporary file named after the md5-hash of the string
869     *
870     * @param string $str CSS styles / JavaScript to write to file.
871     * @param string $ext Extension: "css" or "js
872     * @return string <script> or <link> tag for the file.
873     * @deprecated since TYPO3 v9.4, will be removed in TYPO3 v10.0. This functionality is now within TYPO3's Frontend Request Handler.
874     */
875    public static function inline2TempFile($str, $ext)
876    {
877        trigger_error('PageGenerator::inline2TempFile() will be removed in TYPO3 v10.0. This logic is now built in TYPO3s Frontend RequestHandler.', E_USER_DEPRECATED);
878        // Create filename / tags:
879        $script = '';
880        switch ($ext) {
881            case 'js':
882                $script = 'typo3temp/assets/js/' . GeneralUtility::shortMD5($str) . '.js';
883                break;
884            case 'css':
885                $script = 'typo3temp/assets/css/' . GeneralUtility::shortMD5($str) . '.css';
886                break;
887        }
888        // Write file
889        if ($script && !@is_file(Environment::getPublicPath() . '/' . $script)) {
890            GeneralUtility::writeFileToTypo3tempDir(Environment::getPublicPath() . '/' . $script, $str);
891        }
892        return $script;
893    }
894
895    /**
896     * Checks if the value defined in "config.linkVars" contains an allowed value. Otherwise, return FALSE which means the value will not be added to any links.
897     *
898     * @param string $haystack The string in which to find $needle
899     * @param string $needle The string to find in $haystack
900     * @return bool Returns TRUE if $needle matches or is found in $haystack
901     *
902     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, is now called within TSFE itself, if needed outside the regular calculations, reimplement the method on your own.
903     */
904    public static function isAllowedLinkVarValue($haystack, $needle)
905    {
906        trigger_error('The method will be removed in TYPO3 v10.0, if needed outside of linkVar calculation, re-implement the method in your own extension.', E_USER_DEPRECATED);
907        $OK = false;
908        // Integer
909        if ($needle === 'int' || $needle === 'integer') {
910            if (MathUtility::canBeInterpretedAsInteger($haystack)) {
911                $OK = true;
912            }
913        } elseif (preg_match('/^\\/.+\\/[imsxeADSUXu]*$/', $needle)) {
914            // Regular expression, only "//" is allowed as delimiter
915            if (@preg_match($needle, $haystack)) {
916                $OK = true;
917            }
918        } elseif (strstr($needle, '-')) {
919            // Range
920            if (MathUtility::canBeInterpretedAsInteger($haystack)) {
921                $range = explode('-', $needle);
922                if ($range[0] <= $haystack && $range[1] >= $haystack) {
923                    $OK = true;
924                }
925            }
926        } elseif (strstr($needle, '|')) {
927            // List
928            // Trim the input
929            $haystack = str_replace(' ', '', $haystack);
930            if (strstr('|' . $needle . '|', '|' . $haystack . '|')) {
931                $OK = true;
932            }
933        } elseif ((string)$needle === (string)$haystack) {
934            // String comparison
935            $OK = true;
936        }
937        return $OK;
938    }
939
940    /**
941     * Generate title for page.
942     * Takes the settings [config][noPageTitle], [config][pageTitleFirst], [config][titleTagFunction]
943     * [config][pageTitleSeparator] and [config][noPageTitle] into account.
944     * Furthermore $GLOBALS[TSFE]->altPageTitle is observed.
945     *
946     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, as TSFE->generatePageTitle() should be used instead.
947     */
948    public static function generatePageTitle()
949    {
950        trigger_error('This method will be removed in TYPO3 v10.0. Use $TSFE->generatePageTitle() instead.', E_USER_DEPRECATED);
951        $GLOBALS['TSFE']->generatePageTitle();
952    }
953
954    /**
955     * Generate meta tags from meta tag TypoScript
956     *
957     * @param array $metaTagTypoScript TypoScript configuration for meta tags (e.g. $GLOBALS['TSFE']->pSetup['meta.'])
958     * @param ContentObjectRenderer $cObj
959     */
960    protected static function generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
961    {
962        $pageRenderer = static::getPageRenderer();
963
964        /** @var TypoScriptService $typoScriptService */
965        $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
966        $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
967        foreach ($conf as $key => $properties) {
968            $replace = false;
969            if (is_array($properties)) {
970                $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
971                $value = trim($cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']));
972                if ($value === '' && !empty($properties['value'])) {
973                    $value = $properties['value'];
974                    $replace = false;
975                }
976            } else {
977                $value = $properties;
978            }
979
980            $attribute = 'name';
981            if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
982                $attribute = 'http-equiv';
983            }
984            if (is_array($properties) && !empty($properties['attribute'])) {
985                $attribute = $properties['attribute'];
986            }
987            if (is_array($properties) && !empty($properties['replace'])) {
988                $replace = true;
989            }
990
991            if (!is_array($value)) {
992                $value = (array)$value;
993            }
994            foreach ($value as $subValue) {
995                if (trim($subValue) !== '') {
996                    $pageRenderer->setMetaTag($attribute, $key, $subValue, [], $replace);
997                }
998            }
999        }
1000    }
1001
1002    /**
1003     * @return PageRenderer
1004     */
1005    protected static function getPageRenderer()
1006    {
1007        return GeneralUtility::makeInstance(PageRenderer::class);
1008    }
1009
1010    /**
1011     * Adds inline CSS code, by respecting the inlineStyle2TempFile option
1012     *
1013     * @param string $cssStyles the inline CSS styling
1014     * @param bool $excludeFromConcatenation option to see if it should be concatenated
1015     * @param string $inlineBlockName the block name to add it
1016     */
1017    protected static function addCssToPageRenderer($cssStyles, $excludeFromConcatenation = false, $inlineBlockName = 'TSFEinlineStyle')
1018    {
1019        if (empty($GLOBALS['TSFE']->config['config']['inlineStyle2TempFile'])) {
1020            self::getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($GLOBALS['TSFE']->config['config']['compressCss']));
1021        } else {
1022            self::getPageRenderer()->addCssFile(
1023                self::inline2TempFile($cssStyles, 'css'),
1024                'stylesheet',
1025                'all',
1026                '',
1027                (bool)$GLOBALS['TSFE']->config['config']['compressCss'],
1028                false,
1029                '',
1030                $excludeFromConcatenation
1031            );
1032        }
1033    }
1034
1035    /**
1036     * Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
1037     *
1038     * @internal
1039     */
1040    protected static function getCurrentSiteLanguage(): ?SiteLanguage
1041    {
1042        if (isset($GLOBALS['TYPO3_REQUEST'])
1043            && $GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
1044            && $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage) {
1045            return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
1046        }
1047        return null;
1048    }
1049}
1050