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