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