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