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