[TASK] Deprecate page.javascriptLibs.*
[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 // @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.
355 trigger_error('The setting page.javascriptLibs has been deprecated and will be removed in TYPO3 CMS 10', E_USER_DEPRECATED);
356
357 // Include jQuery into the page renderer
358 if (!empty($tsfe->pSetup['javascriptLibs.']['jQuery'])) {
359 // @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.
360 trigger_error('The setting page.javascriptLibs.jQuery has been deprecated and will be removed in TYPO3 CMS 10', E_USER_DEPRECATED);
361
362 $jQueryTS = $tsfe->pSetup['javascriptLibs.']['jQuery.'];
363 // Check if version / source is set, if not set variable to "NULL" to use the default of the page renderer
364 $version = $jQueryTS['version'] ?? null;
365 $source = $jQueryTS['source'] ?? null;
366 // When "noConflict" is not set or "1" enable the default jQuery noConflict mode, otherwise disable the namespace
367 if (!isset($jQueryTS['noConflict']) || !empty($jQueryTS['noConflict'])) {
368 $namespace = 'noConflict';
369 } else {
370 $namespace = PageRenderer::JQUERY_NAMESPACE_NONE;
371 }
372 $pageRenderer->loadJquery($version, $source, $namespace);
373 }
374 }
375 // JavaScript library files
376 if (is_array($tsfe->pSetup['includeJSLibs.'])) {
377 foreach ($tsfe->pSetup['includeJSLibs.'] as $key => $JSfile) {
378 if (!is_array($JSfile)) {
379 if (isset($tsfe->pSetup['includeJSLibs.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJSLibs.'][$key . '.']['if.'])) {
380 continue;
381 }
382 $ss = $tsfe->pSetup['includeJSLibs.'][$key . '.']['external'] ? $JSfile : $tsfe->tmpl->getFileName($JSfile);
383 if ($ss) {
384 $jsFileConfig = &$tsfe->pSetup['includeJSLibs.'][$key . '.'];
385 $type = $jsFileConfig['type'];
386 if (!$type) {
387 $type = 'text/javascript';
388 }
389 $crossorigin = $jsFileConfig['crossorigin'];
390 if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
391 $crossorigin = 'anonymous';
392 }
393 $pageRenderer->addJsLibrary(
394 $key,
395 $ss,
396 $type,
397 empty($jsFileConfig['disableCompression']),
398 (bool)$jsFileConfig['forceOnTop'],
399 $jsFileConfig['allWrap'],
400 (bool)$jsFileConfig['excludeFromConcatenation'],
401 $jsFileConfig['allWrap.']['splitChar'],
402 (bool)$jsFileConfig['async'],
403 $jsFileConfig['integrity'],
404 (bool)$jsFileConfig['defer'],
405 $crossorigin
406 );
407 unset($jsFileConfig);
408 }
409 }
410 }
411 }
412 if (is_array($tsfe->pSetup['includeJSFooterlibs.'])) {
413 foreach ($tsfe->pSetup['includeJSFooterlibs.'] as $key => $JSfile) {
414 if (!is_array($JSfile)) {
415 if (isset($tsfe->pSetup['includeJSFooterlibs.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJSFooterlibs.'][$key . '.']['if.'])) {
416 continue;
417 }
418 $ss = $tsfe->pSetup['includeJSFooterlibs.'][$key . '.']['external'] ? $JSfile : $tsfe->tmpl->getFileName($JSfile);
419 if ($ss) {
420 $jsFileConfig = &$tsfe->pSetup['includeJSFooterlibs.'][$key . '.'];
421 $type = $jsFileConfig['type'];
422 if (!$type) {
423 $type = 'text/javascript';
424 }
425 $crossorigin = $jsFileConfig['crossorigin'];
426 if (!$crossorigin && $jsFileConfig['integrity'] && $jsFileConfig['external']) {
427 $crossorigin = 'anonymous';
428 }
429 $pageRenderer->addJsFooterLibrary(
430 $key,
431 $ss,
432 $type,
433 empty($jsFileConfig['disableCompression']),
434 (bool)$jsFileConfig['forceOnTop'],
435 $jsFileConfig['allWrap'],
436 (bool)$jsFileConfig['excludeFromConcatenation'],
437 $jsFileConfig['allWrap.']['splitChar'],
438 (bool)$jsFileConfig['async'],
439 $jsFileConfig['integrity'],
440 (bool)$jsFileConfig['defer'],
441 $crossorigin
442 );
443 unset($jsFileConfig);
444 }
445 }
446 }
447 }
448 // JavaScript files
449 if (is_array($tsfe->pSetup['includeJS.'])) {
450 foreach ($tsfe->pSetup['includeJS.'] as $key => $JSfile) {
451 if (!is_array($JSfile)) {
452 if (isset($tsfe->pSetup['includeJS.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJS.'][$key . '.']['if.'])) {
453 continue;
454 }
455 $ss = $tsfe->pSetup['includeJS.'][$key . '.']['external'] ? $JSfile : $tsfe->tmpl->getFileName($JSfile);
456 if ($ss) {
457 $jsConfig = &$tsfe->pSetup['includeJS.'][$key . '.'];
458 $type = $jsConfig['type'];
459 if (!$type) {
460 $type = 'text/javascript';
461 }
462 $crossorigin = $jsConfig['crossorigin'];
463 if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
464 $crossorigin = 'anonymous';
465 }
466 $pageRenderer->addJsFile(
467 $ss,
468 $type,
469 empty($jsConfig['disableCompression']),
470 (bool)$jsConfig['forceOnTop'],
471 $jsConfig['allWrap'],
472 (bool)$jsConfig['excludeFromConcatenation'],
473 $jsConfig['allWrap.']['splitChar'],
474 (bool)$jsConfig['async'],
475 $jsConfig['integrity'],
476 (bool)$jsConfig['defer'],
477 $crossorigin
478 );
479 unset($jsConfig);
480 }
481 }
482 }
483 }
484 if (is_array($tsfe->pSetup['includeJSFooter.'])) {
485 foreach ($tsfe->pSetup['includeJSFooter.'] as $key => $JSfile) {
486 if (!is_array($JSfile)) {
487 if (isset($tsfe->pSetup['includeJSFooter.'][$key . '.']['if.']) && !$tsfe->cObj->checkIf($tsfe->pSetup['includeJSFooter.'][$key . '.']['if.'])) {
488 continue;
489 }
490 $ss = $tsfe->pSetup['includeJSFooter.'][$key . '.']['external'] ? $JSfile : $tsfe->tmpl->getFileName($JSfile);
491 if ($ss) {
492 $jsConfig = &$tsfe->pSetup['includeJSFooter.'][$key . '.'];
493 $type = $jsConfig['type'];
494 if (!$type) {
495 $type = 'text/javascript';
496 }
497 $crossorigin = $jsConfig['crossorigin'];
498 if (!$crossorigin && $jsConfig['integrity'] && $jsConfig['external']) {
499 $crossorigin = 'anonymous';
500 }
501 $pageRenderer->addJsFooterFile(
502 $ss,
503 $type,
504 empty($jsConfig['disableCompression']),
505 (bool)$jsConfig['forceOnTop'],
506 $jsConfig['allWrap'],
507 (bool)$jsConfig['excludeFromConcatenation'],
508 $jsConfig['allWrap.']['splitChar'],
509 (bool)$jsConfig['async'],
510 $jsConfig['integrity'],
511 (bool)$jsConfig['defer'],
512 $crossorigin
513 );
514 unset($jsConfig);
515 }
516 }
517 }
518 }
519 // Headerdata
520 if (is_array($tsfe->pSetup['headerData.'])) {
521 $pageRenderer->addHeaderData($tsfe->cObj->cObjGet($tsfe->pSetup['headerData.'], 'headerData.'));
522 }
523 // Footerdata
524 if (is_array($tsfe->pSetup['footerData.'])) {
525 $pageRenderer->addFooterData($tsfe->cObj->cObjGet($tsfe->pSetup['footerData.'], 'footerData.'));
526 }
527 $tsfe->generatePageTitle();
528
529 static::generateMetaTagHtml(
530 $tsfe->pSetup['meta.'] ?? [],
531 $tsfe->cObj
532 );
533
534 unset($tsfe->additionalHeaderData['JSCode']);
535 if (is_array($tsfe->config['INTincScript'])) {
536 $tsfe->additionalHeaderData['JSCode'] = $tsfe->JSCode;
537 // Storing the JSCode vars...
538 $tsfe->config['INTincScript_ext']['divKey'] = $tsfe->uniqueHash();
539 $tsfe->config['INTincScript_ext']['additionalHeaderData'] = $tsfe->additionalHeaderData;
540 // Storing the header-data array
541 $tsfe->config['INTincScript_ext']['additionalFooterData'] = $tsfe->additionalFooterData;
542 // Storing the footer-data array
543 $tsfe->config['INTincScript_ext']['additionalJavaScript'] = $tsfe->additionalJavaScript;
544 // Storing the JS-data array
545 $tsfe->config['INTincScript_ext']['additionalCSS'] = $tsfe->additionalCSS;
546 // Storing the Style-data array
547 $tsfe->additionalHeaderData = ['<!--HD_' . $tsfe->config['INTincScript_ext']['divKey'] . '-->'];
548 // Clearing the array
549 $tsfe->additionalFooterData = ['<!--FD_' . $tsfe->config['INTincScript_ext']['divKey'] . '-->'];
550 // Clearing the array
551 $tsfe->divSection .= '<!--TDS_' . $tsfe->config['INTincScript_ext']['divKey'] . '-->';
552 } else {
553 $tsfe->INTincScript_loadJSCode();
554 }
555 $scriptJsCode = '';
556
557 if ($tsfe->spamProtectEmailAddresses && $tsfe->spamProtectEmailAddresses !== 'ascii') {
558 $scriptJsCode = '
559 // decrypt helper function
560 function decryptCharcode(n,start,end,offset) {
561 n = n + offset;
562 if (offset > 0 && n > end) {
563 n = start + (n - end - 1);
564 } else if (offset < 0 && n < start) {
565 n = end - (start - n - 1);
566 }
567 return String.fromCharCode(n);
568 }
569 // decrypt string
570 function decryptString(enc,offset) {
571 var dec = "";
572 var len = enc.length;
573 for(var i=0; i < len; i++) {
574 var n = enc.charCodeAt(i);
575 if (n >= 0x2B && n <= 0x3A) {
576 dec += decryptCharcode(n,0x2B,0x3A,offset); // 0-9 . , - + / :
577 } else if (n >= 0x40 && n <= 0x5A) {
578 dec += decryptCharcode(n,0x40,0x5A,offset); // A-Z @
579 } else if (n >= 0x61 && n <= 0x7A) {
580 dec += decryptCharcode(n,0x61,0x7A,offset); // a-z
581 } else {
582 dec += enc.charAt(i);
583 }
584 }
585 return dec;
586 }
587 // decrypt spam-protected emails
588 function linkTo_UnCryptMailto(s) {
589 location.href = decryptString(s,' . $tsfe->spamProtectEmailAddresses * -1 . ');
590 }
591 ';
592 }
593 // Add inline JS
594 $inlineJS = '';
595 // defined in php
596 if (is_array($tsfe->inlineJS)) {
597 foreach ($tsfe->inlineJS as $key => $val) {
598 if (!is_array($val)) {
599 $inlineJS .= LF . $val . LF;
600 }
601 }
602 }
603 // defined in TS with page.inlineJS
604 // Javascript inline code
605 $inline = $tsfe->cObj->cObjGet($tsfe->pSetup['jsInline.'], 'jsInline.');
606 if ($inline) {
607 $inlineJS .= LF . $inline . LF;
608 }
609 // Javascript inline code for Footer
610 $inlineFooterJs = $tsfe->cObj->cObjGet($tsfe->pSetup['jsFooterInline.'], 'jsFooterInline.');
611 // Should minify?
612 if ($tsfe->config['config']['compressJs']) {
613 $pageRenderer->enableCompressJavascript();
614 $minifyErrorScript = ($minifyErrorInline = '');
615 $scriptJsCode = GeneralUtility::minifyJavaScript($scriptJsCode, $minifyErrorScript);
616 if ($minifyErrorScript) {
617 $timeTracker->setTSlogMessage($minifyErrorScript, 3);
618 }
619 if ($inlineJS) {
620 $inlineJS = GeneralUtility::minifyJavaScript($inlineJS, $minifyErrorInline);
621 if ($minifyErrorInline) {
622 $timeTracker->setTSlogMessage($minifyErrorInline, 3);
623 }
624 }
625 if ($inlineFooterJs) {
626 $inlineFooterJs = GeneralUtility::minifyJavaScript($inlineFooterJs, $minifyErrorInline);
627 if ($minifyErrorInline) {
628 $timeTracker->setTSlogMessage($minifyErrorInline, 3);
629 }
630 }
631 }
632 if (!$tsfe->config['config']['removeDefaultJS']) {
633 // include default and inlineJS
634 if ($scriptJsCode) {
635 $pageRenderer->addJsInlineCode('_scriptCode', $scriptJsCode, $tsfe->config['config']['compressJs']);
636 }
637 if ($inlineJS) {
638 $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $tsfe->config['config']['compressJs']);
639 }
640 if ($inlineFooterJs) {
641 $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $tsfe->config['config']['compressJs']);
642 }
643 } elseif ($tsfe->config['config']['removeDefaultJS'] === 'external') {
644 /*
645 * This keeps inlineJS from *_INT Objects from being moved to external files.
646 * At this point in frontend rendering *_INT Objects only have placeholders instead
647 * of actual content so moving these placeholders to external files would
648 * a) break the JS file (syntax errors due to the placeholders)
649 * b) the needed JS would never get included to the page
650 * Therefore inlineJS from *_INT Objects must not be moved to external files but
651 * kept internal.
652 */
653 $inlineJSint = '';
654 self::stripIntObjectPlaceholder($inlineJS, $inlineJSint);
655 if ($inlineJSint) {
656 $pageRenderer->addJsInlineCode('TS_inlineJSint', $inlineJSint, $tsfe->config['config']['compressJs']);
657 }
658 if (trim($scriptJsCode . $inlineJS)) {
659 $pageRenderer->addJsFile(self::inline2TempFile($scriptJsCode . $inlineJS, 'js'), 'text/javascript', $tsfe->config['config']['compressJs']);
660 }
661 if ($inlineFooterJs) {
662 $inlineFooterJSint = '';
663 self::stripIntObjectPlaceholder($inlineFooterJs, $inlineFooterJSint);
664 if ($inlineFooterJSint) {
665 $pageRenderer->addJsFooterInlineCode('TS_inlineFooterJSint', $inlineFooterJSint, $tsfe->config['config']['compressJs']);
666 }
667 $pageRenderer->addJsFooterFile(self::inline2TempFile($inlineFooterJs, 'js'), 'text/javascript', $tsfe->config['config']['compressJs']);
668 }
669 } else {
670 // Include only inlineJS
671 if ($inlineJS) {
672 $pageRenderer->addJsInlineCode('TS_inlineJS', $inlineJS, $tsfe->config['config']['compressJs']);
673 }
674 if ($inlineFooterJs) {
675 $pageRenderer->addJsFooterInlineCode('TS_inlineFooter', $inlineFooterJs, $tsfe->config['config']['compressJs']);
676 }
677 }
678 if (is_array($tsfe->pSetup['inlineLanguageLabelFiles.'])) {
679 foreach ($tsfe->pSetup['inlineLanguageLabelFiles.'] as $key => $languageFile) {
680 if (is_array($languageFile)) {
681 continue;
682 }
683 $languageFileConfig = &$tsfe->pSetup['inlineLanguageLabelFiles.'][$key . '.'];
684 if (isset($languageFileConfig['if.']) && !$tsfe->cObj->checkIf($languageFileConfig['if.'])) {
685 continue;
686 }
687 $pageRenderer->addInlineLanguageLabelFile(
688 $languageFile,
689 $languageFileConfig['selectionPrefix'] ?: '',
690 $languageFileConfig['stripFromSelectionName'] ?: ''
691 );
692 }
693 }
694 if (is_array($tsfe->pSetup['inlineSettings.'])) {
695 $pageRenderer->addInlineSettingArray('TS', $tsfe->pSetup['inlineSettings.']);
696 }
697 // Compression and concatenate settings
698 if ($tsfe->config['config']['compressCss']) {
699 $pageRenderer->enableCompressCss();
700 }
701 if ($tsfe->config['config']['compressJs']) {
702 $pageRenderer->enableCompressJavascript();
703 }
704 if ($tsfe->config['config']['concatenateCss']) {
705 $pageRenderer->enableConcatenateCss();
706 }
707 if ($tsfe->config['config']['concatenateJs']) {
708 $pageRenderer->enableConcatenateJavascript();
709 }
710 // Backward compatibility for old configuration
711 if ($tsfe->config['config']['concatenateJsAndCss']) {
712 $pageRenderer->enableConcatenateFiles();
713 }
714 // Add header data block
715 if ($tsfe->additionalHeaderData) {
716 $pageRenderer->addHeaderData(implode(LF, $tsfe->additionalHeaderData));
717 }
718 // Add footer data block
719 if ($tsfe->additionalFooterData) {
720 $pageRenderer->addFooterData(implode(LF, $tsfe->additionalFooterData));
721 }
722 // Header complete, now add content
723 // Bodytag:
724 if ($tsfe->config['config']['disableBodyTag']) {
725 $bodyTag = '';
726 } else {
727 $defBT = $tsfe->pSetup['bodyTagCObject'] ? $tsfe->cObj->cObjGetSingle($tsfe->pSetup['bodyTagCObject'], $tsfe->pSetup['bodyTagCObject.'], 'bodyTagCObject') : '<body>';
728 $bodyTag = $tsfe->pSetup['bodyTag'] ? $tsfe->pSetup['bodyTag'] : $defBT;
729 if (trim($tsfe->pSetup['bodyTagAdd'])) {
730 $bodyTag = preg_replace('/>$/', '', trim($bodyTag)) . ' ' . trim($tsfe->pSetup['bodyTagAdd']) . '>';
731 }
732 }
733 $pageRenderer->addBodyContent(LF . $bodyTag);
734 // Div-sections
735 if ($tsfe->divSection) {
736 $pageRenderer->addBodyContent(LF . $tsfe->divSection);
737 }
738 // Page content
739 $pageRenderer->addBodyContent(LF . $pageContent);
740 if (!empty($tsfe->config['INTincScript']) && is_array($tsfe->config['INTincScript'])) {
741 // Store the serialized pageRenderer in configuration
742 $tsfe->config['INTincScript_ext']['pageRenderer'] = serialize($pageRenderer);
743 // Render complete page, keep placeholders for JavaScript and CSS
744 $tsfe->content = $pageRenderer->renderPageWithUncachedObjects($tsfe->config['INTincScript_ext']['divKey']);
745 } else {
746 // Render complete page
747 $tsfe->content = $pageRenderer->render();
748 }
749 }
750
751 /*************************
752 *
753 * Helper functions
754 * Remember: Calls internally must still be done on the non-instantiated class: PageGenerator::inline2TempFile()
755 *
756 *************************/
757 /**
758 * Searches for placeholder created from *_INT cObjects, removes them from
759 * $searchString and merges them to $intObjects
760 *
761 * @param string $searchString The String which should be cleaned from int-object markers
762 * @param string $intObjects The String the found int-placeholders are moved to (for further processing)
763 */
764 protected static function stripIntObjectPlaceholder(&$searchString, &$intObjects)
765 {
766 $tempArray = [];
767 preg_match_all('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', $searchString, $tempArray);
768 $searchString = preg_replace('/\\<\\!--INT_SCRIPT.[a-z0-9]*--\\>/', '', $searchString);
769 $intObjects = implode('', $tempArray[0]);
770 }
771
772 /**
773 * Writes string to a temporary file named after the md5-hash of the string
774 *
775 * @param string $str CSS styles / JavaScript to write to file.
776 * @param string $ext Extension: "css" or "js
777 * @return string <script> or <link> tag for the file.
778 */
779 public static function inline2TempFile($str, $ext)
780 {
781 // Create filename / tags:
782 $script = '';
783 switch ($ext) {
784 case 'js':
785 $script = 'typo3temp/assets/js/' . GeneralUtility::shortMD5($str) . '.js';
786 break;
787 case 'css':
788 $script = 'typo3temp/assets/css/' . GeneralUtility::shortMD5($str) . '.css';
789 break;
790 }
791 // Write file:
792 if ($script) {
793 if (!@is_file(PATH_site . $script)) {
794 GeneralUtility::writeFileToTypo3tempDir(PATH_site . $script, $str);
795 }
796 }
797 return $script;
798 }
799
800 /**
801 * 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.
802 *
803 * @param string $haystack The string in which to find $needle
804 * @param string $needle The string to find in $haystack
805 * @return bool Returns TRUE if $needle matches or is found in $haystack
806 *
807 * @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.
808 */
809 public static function isAllowedLinkVarValue($haystack, $needle)
810 {
811 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);
812 $OK = false;
813 // Integer
814 if ($needle === 'int' || $needle === 'integer') {
815 if (MathUtility::canBeInterpretedAsInteger($haystack)) {
816 $OK = true;
817 }
818 } elseif (preg_match('/^\\/.+\\/[imsxeADSUXu]*$/', $needle)) {
819 // Regular expression, only "//" is allowed as delimiter
820 if (@preg_match($needle, $haystack)) {
821 $OK = true;
822 }
823 } elseif (strstr($needle, '-')) {
824 // Range
825 if (MathUtility::canBeInterpretedAsInteger($haystack)) {
826 $range = explode('-', $needle);
827 if ($range[0] <= $haystack && $range[1] >= $haystack) {
828 $OK = true;
829 }
830 }
831 } elseif (strstr($needle, '|')) {
832 // List
833 // Trim the input
834 $haystack = str_replace(' ', '', $haystack);
835 if (strstr('|' . $needle . '|', '|' . $haystack . '|')) {
836 $OK = true;
837 }
838 } elseif ((string)$needle === (string)$haystack) {
839 // String comparison
840 $OK = true;
841 }
842 return $OK;
843 }
844
845 /**
846 * Generate title for page.
847 * Takes the settings [config][noPageTitle], [config][pageTitleFirst], [config][titleTagFunction]
848 * [config][pageTitleSeparator] and [config][noPageTitle] into account.
849 * Furthermore $GLOBALS[TSFE]->altPageTitle is observed.
850 *
851 * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0, as TSFE->generatePageTitle() should be used instead.
852 */
853 public static function generatePageTitle()
854 {
855 trigger_error('This method will be removed in TYPO3 v10.0. Use $TSFE->generatePageTitle() instead.', E_USER_DEPRECATED);
856 $GLOBALS['TSFE']->generatePageTitle();
857 }
858
859 /**
860 * Generate meta tags from meta tag TypoScript
861 *
862 * @param array $metaTagTypoScript TypoScript configuration for meta tags (e.g. $GLOBALS['TSFE']->pSetup['meta.'])
863 * @param bool $xhtml Whether xhtml tag-style should be used. (e.g. pass $GLOBALS['TSFE']->xhtmlVersion here)
864 * @param ContentObjectRenderer $cObj
865 */
866 protected static function generateMetaTagHtml(array $metaTagTypoScript, ContentObjectRenderer $cObj)
867 {
868 $pageRenderer = static::getPageRenderer();
869
870 /** @var TypoScriptService $typoScriptService */
871 $typoScriptService = GeneralUtility::makeInstance(TypoScriptService::class);
872 $conf = $typoScriptService->convertTypoScriptArrayToPlainArray($metaTagTypoScript);
873 foreach ($conf as $key => $properties) {
874 if (is_array($properties)) {
875 $nodeValue = $properties['_typoScriptNodeValue'] ?? '';
876 $value = trim($cObj->stdWrap($nodeValue, $metaTagTypoScript[$key . '.']));
877 if ($value === '' && !empty($properties['value'])) {
878 $value = $properties['value'];
879 }
880 } else {
881 $value = $properties;
882 }
883
884 $attribute = 'name';
885 if ((is_array($properties) && !empty($properties['httpEquivalent'])) || strtolower($key) === 'refresh') {
886 $attribute = 'http-equiv';
887 }
888 if (is_array($properties) && !empty($properties['attribute'])) {
889 $attribute = $properties['attribute'];
890 }
891
892 if (!is_array($value)) {
893 $value = (array)$value;
894 }
895 foreach ($value as $subValue) {
896 if (trim($subValue) !== '') {
897 $pageRenderer->setMetaTag($attribute, $key, $subValue);
898 }
899 }
900 }
901 }
902
903 /**
904 * @return PageRenderer
905 */
906 protected static function getPageRenderer()
907 {
908 return GeneralUtility::makeInstance(PageRenderer::class);
909 }
910
911 /**
912 * Adds inline CSS code, by respecting the inlineStyle2TempFile option
913 *
914 * @param string $cssStyles the inline CSS styling
915 * @param bool $excludeFromConcatenation option to see if it should be concatenated
916 * @param string $inlineBlockName the block name to add it
917 */
918 protected static function addCssToPageRenderer($cssStyles, $excludeFromConcatenation = false, $inlineBlockName = 'TSFEinlineStyle')
919 {
920 if (empty($GLOBALS['TSFE']->config['config']['inlineStyle2TempFile'])) {
921 self::getPageRenderer()->addCssInlineBlock($inlineBlockName, $cssStyles, !empty($GLOBALS['TSFE']->config['config']['compressCss']));
922 } else {
923 self::getPageRenderer()->addCssFile(
924 self::inline2TempFile($cssStyles, 'css'),
925 'stylesheet',
926 'all',
927 '',
928 (bool)$GLOBALS['TSFE']->config['config']['compressCss'],
929 false,
930 '',
931 $excludeFromConcatenation
932 );
933 }
934 }
935 }