[TASK] Deprecate PageGenerator::renderContent
[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']) {
94 $pageRenderer->enableMoveJsFromHeaderToFooter();
95 }
96 if ($tsfe->config['config']['pageRendererTemplateFile']) {
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'];
105 if (trim($headerComment)) {
106 $pageRenderer->addInlineComment(TAB . str_replace(LF, LF . TAB, 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'];
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'];
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']) {
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 ($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']) {
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 (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 (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 (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.'], 'cssInline.');
383 if (trim($style)) {
384 self::addCssToPageRenderer($style, true, 'additionalTSFEInlineStyle');
385 }
386 // Javascript Libraries
387 if (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 (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 (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 (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 (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 (is_array($tsfe->pSetup['headerData.'])) {
587 $pageRenderer->addHeaderData($tsfe->cObj->cObjGet($tsfe->pSetup['headerData.'], 'headerData.'));
588 }
589 // Footerdata
590 if (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 (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.'], 'jsInline.');
679 if ($inline) {
680 $inlineJS .= LF . $inline . LF;
681 }
682 // Javascript inline code for Footer
683 $inlineFooterJs = $tsfe->cObj->cObjGet($tsfe->pSetup['jsFooterInline.'], 'jsFooterInline.');
684 // Should minify?
685 if ($tsfe->config['config']['compressJs']) {
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 (!$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 (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 (is_array($tsfe->pSetup['inlineSettings.'])) {
768 $pageRenderer->addInlineSettingArray('TS', $tsfe->pSetup['inlineSettings.']);
769 }
770 // Compression and concatenate settings
771 if ($tsfe->config['config']['compressCss']) {
772 $pageRenderer->enableCompressCss();
773 }
774 if ($tsfe->config['config']['compressJs']) {
775 $pageRenderer->enableCompressJavascript();
776 }
777 if ($tsfe->config['config']['concatenateCss']) {
778 $pageRenderer->enableConcatenateCss();
779 }
780 if ($tsfe->config['config']['concatenateJs']) {
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']) {
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']) {
801 $bodyTag = '';
802 } else {
803 $defBT = $tsfe->pSetup['bodyTagCObject'] ? $tsfe->cObj->cObjGetSingle($tsfe->pSetup['bodyTagCObject'], $tsfe->pSetup['bodyTagCObject.'], 'bodyTagCObject') : '<body>';
804 $bodyTag = $tsfe->pSetup['bodyTag'] ? $tsfe->pSetup['bodyTag'] : $defBT;
805 if (trim($tsfe->pSetup['bodyTagAdd'])) {
806 $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($tsfe->pSetup['bodyTagAdd']) . '>';
807 }
808 }
809 $pageRenderer->addBodyContent(LF . $bodyTag);
810 // Div-sections
811 if ($tsfe->divSection) {
812 $pageRenderer->addBodyContent(LF . $tsfe->divSection);
813 }
814 // Page content
815 $pageRenderer->addBodyContent(LF . $pageContent);
816 if (!empty($tsfe->config['INTincScript']) && is_array($tsfe->config['INTincScript'])) {
817 // Store the serialized pageRenderer in configuration
818 $tsfe->config['INTincScript_ext']['pageRenderer'] = serialize($pageRenderer);
819 // Render complete page, keep placeholders for JavaScript and CSS
820 $tsfe->content = $pageRenderer->renderPageWithUncachedObjects($tsfe->config['INTincScript_ext']['divKey']);
821 } else {
822 // Render complete page
823 $tsfe->content = $pageRenderer->render();
824 }
825 }
826
827 /*************************
828 *
829 * Helper functions
830 * Remember: Calls internally must still be done on the non-instantiated class: PageGenerator::inline2TempFile()
831 *
832 *************************/
833 /**
834 * Searches for placeholder created from *_INT cObjects, removes them from
835 * $searchString and merges them to $intObjects
836 *
837 * @param string $searchString The String which should be cleaned from int-object markers
838 * @param string $intObjects The String the found int-placeholders are moved to (for further processing)
839 */
840 protected static function stripIntObjectPlaceholder(&$searchString, &$intObjects)
841 {
842 $tempArray = [];
843 preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
844 $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
845 $intObjects = implode('', $tempArray[0]);
846 }
847
848 /**
849 * Writes string to a temporary file named after the md5-hash of the string
850 *
851 * @param string $str CSS styles / JavaScript to write to file.
852 * @param string $ext Extension: "css" or "js
853 * @return string <script> or <link> tag for the file.
854 */
855 public static function inline2TempFile($str, $ext)
856 {
857 // Create filename / tags:
858 $script = '';
859 switch ($ext) {
860 case 'js':
861 $script = 'typo3temp/assets/js/' . GeneralUtility::shortMD5($str) . '.js';
862 break;
863 case 'css':
864 $script = 'typo3temp/assets/css/' . GeneralUtility::shortMD5($str) . '.css';
865 break;
866 }
867 // Write file
868 if ($script && !@is_file(Environment::getPublicPath() . '/' . $script)) {
869 GeneralUtility::writeFileToTypo3tempDir(Environment::getPublicPath() . '/' . $script, $str);
870 }
871 return $script;
872 }
873
874 /**
875 * 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.
876 *
877 * @param string $haystack The string in which to find $needle
878 * @param string $needle The string to find in $haystack
879 * @return bool Returns TRUE if $needle matches or is found in $haystack
880 *
881 * @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.
882 */
883 public static function isAllowedLinkVarValue($haystack, $needle)
884 {
885 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);
886 $OK = false;
887 // Integer
888 if ($needle === 'int' || $needle === 'integer') {
889 if (MathUtility::canBeInterpretedAsInteger($haystack)) {
890 $OK = true;
891 }
892 } elseif (preg_match('/^\\/.+\\/[imsxeADSUXu]*$/', $needle)) {
893 // Regular expression, only "//" is allowed as delimiter
894 if (@preg_match($needle, $haystack)) {
895 $OK = true;
896 }
897 } elseif (strstr($needle, '-')) {
898 // Range
899 if (MathUtility::canBeInterpretedAsInteger($haystack)) {
900 $range = explode('-', $needle);
901 if ($range[0] <= $haystack && $range[1] >= $haystack) {
902 $OK = true;
903 }
904 }
905 } elseif (strstr($needle, '|')) {
906 // List
907 // Trim the input
908 $haystack = str_replace(' ', '', $haystack);
909 if (strstr('|' . $needle . '|', '|' . $haystack . '|')) {
910 $OK = true;
911 }
912 } elseif ((string)$needle === (string)$haystack) {
913 // String comparison
914 $OK = true;
915 }
916 return $OK;
917 }
918
919 /**
920 * Generate title for page.
921 * Takes the settings [config][noPageTitle], [config][pageTitleFirst], [config][titleTagFunction]
922 * [config][pageTitleSeparator] and [config][noPageTitle] into account.
923 * Furthermore $GLOBALS[TSFE]->altPageTitle is observed.
924 *
925 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, as TSFE->generatePageTitle() should be used instead.
926 */
927 public static function generatePageTitle()
928 {
929 trigger_error('This method will be removed in TYPO3 v10.0. Use $TSFE->generatePageTitle() instead.', E_USER_DEPRECATED);
930 $GLOBALS['TSFE']->generatePageTitle();
931 }
932
933 /**
934 * Generate meta tags from meta tag TypoScript
935 *
936 * @param array $metaTagTypoScript TypoScript configuration for meta tags (e.g. $GLOBALS['TSFE']->pSetup['meta.'])
937 * @param bool $xhtml Whether xhtml tag-style should be used. (e.g. pass $GLOBALS['TSFE']->xhtmlVersion here)
938 * @param ContentObjectRenderer $cObj
939 */
940 protected static function generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
941 {
942 $pageRenderer = static::getPageRenderer();
943
944 /** @var TypoScriptService $typoScriptService */
945 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
946 $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
947 foreach ($conf as $key => $properties) {
948 $replace = false;
949 if (is_array($properties)) {
950 $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
951 $value = trim($cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']));
952 if ($value === '' && !empty($properties['value'])) {
953 $value = $properties['value'];
954 $replace = false;
955 }
956 } else {
957 $value = $properties;
958 }
959
960 $attribute = 'name';
961 if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
962 $attribute = 'http-equiv';
963 }
964 if (is_array($properties) && !empty($properties['attribute'])) {
965 $attribute = $properties['attribute'];
966 }
967 if (is_array($properties) && !empty($properties['replace'])) {
968 $replace = true;
969 }
970
971 if (!is_array($value)) {
972 $value = (array)$value;
973 }
974 foreach ($value as $subValue) {
975 if (trim($subValue) !== '') {
976 $pageRenderer->setMetaTag($attribute, $key, $subValue, [], $replace);
977 }
978 }
979 }
980 }
981
982 /**
983 * @return PageRenderer
984 */
985 protected static function getPageRenderer()
986 {
987 return GeneralUtility::makeInstance(PageRenderer::class);
988 }
989
990 /**
991 * Adds inline CSS code, by respecting the inlineStyle2TempFile option
992 *
993 * @param string $cssStyles the inline CSS styling
994 * @param bool $excludeFromConcatenation option to see if it should be concatenated
995 * @param string $inlineBlockName the block name to add it
996 */
997 protected static function addCssToPageRenderer($cssStyles, $excludeFromConcatenation = false, $inlineBlockName = 'TSFEinlineStyle')
998 {
999 if (empty($GLOBALS['TSFE']->config['config']['inlineStyle2TempFile'])) {
1000 self::getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($GLOBALS['TSFE']->config['config']['compressCss']));
1001 } else {
1002 self::getPageRenderer()->addCssFile(
1003 self::inline2TempFile($cssStyles, 'css'),
1004 'stylesheet',
1005 'all',
1006 '',
1007 (bool)$GLOBALS['TSFE']->config['config']['compressCss'],
1008 false,
1009 '',
1010 $excludeFromConcatenation
1011 );
1012 }
1013 }
1014
1015 /**
1016 * Returns the currently configured "site language" if a site is configured (= resolved) in the current request.
1017 *
1018 * @internal
1019 */
1020 protected static function getCurrentSiteLanguage(): ?SiteLanguage
1021 {
1022 if ($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface
1023 && $GLOBALS['TYPO3_REQUEST']->getAttribute('language') instanceof SiteLanguage) {
1024 return $GLOBALS['TYPO3_REQUEST']->getAttribute('language');
1025 }
1026 return null;
1027 }
1028 }