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