0bc5d659b1a8a429b3e55c4b42196d1e47869021
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Page / PageGenerator.php
1 <?php
2 namespace 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
17 use Psr\Http\Message\ServerRequestInterface;
18 use TYPO3\CMS\Core\Core\Environment;
19 use TYPO3\CMS\Core\Page\PageRenderer;
20 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
21 use TYPO3\CMS\Core\TimeTracker\TimeTracker;
22 use TYPO3\CMS\Core\Type\File\ImageInfo;
23 use TYPO3\CMS\Core\TypoScript\TypoScriptService;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\MathUtility;
26 use TYPO3\CMS\Core\Utility\PathUtility;
27 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
28 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
29 use 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 */
37 class 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 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 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 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 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 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 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 }