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