[!!!][TASK] Remove deprecated functionality within PageRenderer
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Page / PageRenderer.php
1 <?php
2 namespace TYPO3\CMS\Core\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\Backend\Routing\Router;
18 use TYPO3\CMS\Backend\Routing\UriBuilder;
19 use TYPO3\CMS\Core\Cache\CacheManager;
20 use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
21 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
22 use TYPO3\CMS\Core\Core\Environment;
23 use TYPO3\CMS\Core\Localization\LocalizationFactory;
24 use TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry;
25 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
26 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Core\Utility\PathUtility;
29 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
30
31 /**
32 * TYPO3 pageRender class
33 * This class render the HTML of a webpage, usable for BE and FE
34 */
35 class PageRenderer implements \TYPO3\CMS\Core\SingletonInterface
36 {
37 // Constants for the part to be rendered
38 const PART_COMPLETE = 0;
39 const PART_HEADER = 1;
40 const PART_FOOTER = 2;
41
42 /**
43 * @var bool
44 */
45 protected $compressJavascript = false;
46
47 /**
48 * @var bool
49 */
50 protected $compressCss = false;
51
52 /**
53 * @var bool
54 */
55 protected $removeLineBreaksFromTemplate = false;
56
57 /**
58 * @var bool
59 */
60 protected $concatenateJavascript = false;
61
62 /**
63 * @var bool
64 */
65 protected $concatenateCss = false;
66
67 /**
68 * @var bool
69 */
70 protected $moveJsFromHeaderToFooter = false;
71
72 /**
73 * @var \TYPO3\CMS\Core\Localization\Locales
74 */
75 protected $locales;
76
77 /**
78 * The language key
79 * Two character string or 'default'
80 *
81 * @var string
82 */
83 protected $lang;
84
85 /**
86 * List of language dependencies for actual language. This is used for local variants of a language
87 * that depend on their "main" language, like Brazilian Portuguese or Canadian French.
88 *
89 * @var array
90 */
91 protected $languageDependencies = [];
92
93 /**
94 * @var \TYPO3\CMS\Core\Resource\ResourceCompressor
95 */
96 protected $compressor;
97
98 // Arrays containing associative array for the included files
99 /**
100 * @var array
101 */
102 protected $jsFiles = [];
103
104 /**
105 * @var array
106 */
107 protected $jsFooterFiles = [];
108
109 /**
110 * @var array
111 */
112 protected $jsLibs = [];
113
114 /**
115 * @var array
116 */
117 protected $jsFooterLibs = [];
118
119 /**
120 * @var array
121 */
122 protected $cssFiles = [];
123
124 /**
125 * @var array
126 */
127 protected $cssLibs = [];
128
129 /**
130 * The title of the page
131 *
132 * @var string
133 */
134 protected $title;
135
136 /**
137 * Charset for the rendering
138 *
139 * @var string
140 */
141 protected $charSet;
142
143 /**
144 * @var string
145 */
146 protected $favIcon;
147
148 /**
149 * @var string
150 */
151 protected $baseUrl;
152
153 /**
154 * @var bool
155 */
156 protected $renderXhtml = true;
157
158 // Static header blocks
159 /**
160 * @var string
161 */
162 protected $xmlPrologAndDocType = '';
163
164 /**
165 * @var array
166 */
167 protected $metaTags = [];
168
169 /**
170 * META Tags added via the API
171 *
172 * @var array
173 */
174 protected $metaTagsByAPI = [];
175
176 /**
177 * @var array
178 */
179 protected $inlineComments = [];
180
181 /**
182 * @var array
183 */
184 protected $headerData = [];
185
186 /**
187 * @var array
188 */
189 protected $footerData = [];
190
191 /**
192 * @var string
193 */
194 protected $titleTag = '<title>|</title>';
195
196 /**
197 * @var string
198 */
199 protected $metaCharsetTag = '<meta http-equiv="Content-Type" content="text/html; charset=|" />';
200
201 /**
202 * @var string
203 */
204 protected $htmlTag = '<html>';
205
206 /**
207 * @var string
208 */
209 protected $headTag = '<head>';
210
211 /**
212 * @var string
213 */
214 protected $baseUrlTag = '<base href="|" />';
215
216 /**
217 * @var string
218 */
219 protected $iconMimeType = '';
220
221 /**
222 * @var string
223 */
224 protected $shortcutTag = '<link rel="shortcut icon" href="%1$s"%2$s />';
225
226 // Static inline code blocks
227 /**
228 * @var array
229 */
230 protected $jsInline = [];
231
232 /**
233 * @var array
234 */
235 protected $jsFooterInline = [];
236
237 /**
238 * @var array
239 */
240 protected $extOnReadyCode = [];
241
242 /**
243 * @var array
244 */
245 protected $cssInline = [];
246
247 /**
248 * @var string
249 */
250 protected $bodyContent;
251
252 /**
253 * @var string
254 */
255 protected $templateFile;
256
257 // Paths to contributed libraries
258
259 /**
260 * default path to the requireJS library, relative to the typo3/ directory
261 * @var string
262 */
263 protected $requireJsPath = 'EXT:core/Resources/Public/JavaScript/Contrib/';
264
265 // Internal flags for JS-libraries
266 /**
267 * if set, the requireJS library is included
268 * @var bool
269 */
270 protected $addRequireJs = false;
271
272 /**
273 * inline configuration for requireJS
274 * @var array
275 */
276 protected $requireJsConfig = [];
277
278 /**
279 * @var array
280 */
281 protected $inlineLanguageLabels = [];
282
283 /**
284 * @var array
285 */
286 protected $inlineLanguageLabelFiles = [];
287
288 /**
289 * @var array
290 */
291 protected $inlineSettings = [];
292
293 /**
294 * @var array
295 */
296 protected $inlineJavascriptWrap = [];
297
298 /**
299 * @var array
300 */
301 protected $inlineCssWrap = [];
302
303 /**
304 * Saves error messages generated during compression
305 *
306 * @var string
307 */
308 protected $compressError = '';
309
310 /**
311 * Is empty string for HTML and ' /' for XHTML rendering
312 *
313 * @var string
314 */
315 protected $endingSlash = '';
316
317 /**
318 * @var MetaTagManagerRegistry
319 */
320 protected $metaTagRegistry;
321
322 /**
323 * @var FrontendInterface
324 */
325 protected static $cache = null;
326
327 /**
328 * @param string $templateFile Declare the used template file. Omit this parameter will use default template
329 */
330 public function __construct($templateFile = '')
331 {
332 $this->reset();
333 $this->locales = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Localization\Locales::class);
334 if ($templateFile !== '') {
335 $this->templateFile = $templateFile;
336 }
337 $this->inlineJavascriptWrap = [
338 '<script type="text/javascript">' . LF . '/*<![CDATA[*/' . LF,
339 '/*]]>*/' . LF . '</script>' . LF
340 ];
341 $this->inlineCssWrap = [
342 '<style type="text/css">' . LF . '/*<![CDATA[*/' . LF . '<!-- ' . LF,
343 '-->' . LF . '/*]]>*/' . LF . '</style>' . LF
344 ];
345
346 $this->metaTagRegistry = GeneralUtility::makeInstance(MetaTagManagerRegistry::class);
347 $this->setMetaTag('name', 'generator', 'TYPO3 CMS');
348 }
349
350 /**
351 * @param FrontendInterface $cache
352 */
353 public static function setCache(FrontendInterface $cache)
354 {
355 static::$cache = $cache;
356 }
357
358 /**
359 * Reset all vars to initial values
360 */
361 protected function reset()
362 {
363 $this->templateFile = 'EXT:core/Resources/Private/Templates/PageRenderer.html';
364 $this->jsFiles = [];
365 $this->jsFooterFiles = [];
366 $this->jsInline = [];
367 $this->jsFooterInline = [];
368 $this->jsLibs = [];
369 $this->cssFiles = [];
370 $this->cssInline = [];
371 $this->metaTags = [];
372 $this->metaTagsByAPI = [];
373 $this->inlineComments = [];
374 $this->headerData = [];
375 $this->footerData = [];
376 $this->extOnReadyCode = [];
377 }
378
379 /*****************************************************/
380 /* */
381 /* Public Setters */
382 /* */
383 /* */
384 /*****************************************************/
385 /**
386 * Sets the title
387 *
388 * @param string $title title of webpage
389 */
390 public function setTitle($title)
391 {
392 $this->title = $title;
393 }
394
395 /**
396 * Enables/disables rendering of XHTML code
397 *
398 * @param bool $enable Enable XHTML
399 */
400 public function setRenderXhtml($enable)
401 {
402 $this->renderXhtml = $enable;
403 }
404
405 /**
406 * Sets xml prolog and docType
407 *
408 * @param string $xmlPrologAndDocType Complete tags for xml prolog and docType
409 */
410 public function setXmlPrologAndDocType($xmlPrologAndDocType)
411 {
412 $this->xmlPrologAndDocType = $xmlPrologAndDocType;
413 }
414
415 /**
416 * Sets meta charset
417 *
418 * @param string $charSet Used charset
419 */
420 public function setCharSet($charSet)
421 {
422 $this->charSet = $charSet;
423 }
424
425 /**
426 * Sets language
427 *
428 * @param string $lang Used language
429 */
430 public function setLanguage($lang)
431 {
432 $this->lang = $lang;
433 $this->languageDependencies = [];
434
435 // Language is found. Configure it:
436 if (in_array($this->lang, $this->locales->getLocales())) {
437 $this->languageDependencies[] = $this->lang;
438 foreach ($this->locales->getLocaleDependencies($this->lang) as $language) {
439 $this->languageDependencies[] = $language;
440 }
441 }
442 }
443
444 /**
445 * Set the meta charset tag
446 *
447 * @param string $metaCharsetTag
448 */
449 public function setMetaCharsetTag($metaCharsetTag)
450 {
451 $this->metaCharsetTag = $metaCharsetTag;
452 }
453
454 /**
455 * Sets html tag
456 *
457 * @param string $htmlTag Html tag
458 */
459 public function setHtmlTag($htmlTag)
460 {
461 $this->htmlTag = $htmlTag;
462 }
463
464 /**
465 * Sets HTML head tag
466 *
467 * @param string $headTag HTML head tag
468 */
469 public function setHeadTag($headTag)
470 {
471 $this->headTag = $headTag;
472 }
473
474 /**
475 * Sets favicon
476 *
477 * @param string $favIcon
478 */
479 public function setFavIcon($favIcon)
480 {
481 $this->favIcon = $favIcon;
482 }
483
484 /**
485 * Sets icon mime type
486 *
487 * @param string $iconMimeType
488 */
489 public function setIconMimeType($iconMimeType)
490 {
491 $this->iconMimeType = $iconMimeType;
492 }
493
494 /**
495 * Sets HTML base URL
496 *
497 * @param string $baseUrl HTML base URL
498 */
499 public function setBaseUrl($baseUrl)
500 {
501 $this->baseUrl = $baseUrl;
502 }
503
504 /**
505 * Sets template file
506 *
507 * @param string $file
508 */
509 public function setTemplateFile($file)
510 {
511 $this->templateFile = $file;
512 }
513
514 /**
515 * Sets Content for Body
516 *
517 * @param string $content
518 */
519 public function setBodyContent($content)
520 {
521 $this->bodyContent = $content;
522 }
523
524 /**
525 * Sets path to requireJS library (relative to typo3 directory)
526 *
527 * @param string $path Path to requireJS library
528 */
529 public function setRequireJsPath($path)
530 {
531 $this->requireJsPath = $path;
532 }
533
534 /*****************************************************/
535 /* */
536 /* Public Enablers / Disablers */
537 /* */
538 /* */
539 /*****************************************************/
540 /**
541 * Enables MoveJsFromHeaderToFooter
542 */
543 public function enableMoveJsFromHeaderToFooter()
544 {
545 $this->moveJsFromHeaderToFooter = true;
546 }
547
548 /**
549 * Disables MoveJsFromHeaderToFooter
550 */
551 public function disableMoveJsFromHeaderToFooter()
552 {
553 $this->moveJsFromHeaderToFooter = false;
554 }
555
556 /**
557 * Enables compression of javascript
558 */
559 public function enableCompressJavascript()
560 {
561 $this->compressJavascript = true;
562 }
563
564 /**
565 * Disables compression of javascript
566 */
567 public function disableCompressJavascript()
568 {
569 $this->compressJavascript = false;
570 }
571
572 /**
573 * Enables compression of css
574 */
575 public function enableCompressCss()
576 {
577 $this->compressCss = true;
578 }
579
580 /**
581 * Disables compression of css
582 */
583 public function disableCompressCss()
584 {
585 $this->compressCss = false;
586 }
587
588 /**
589 * Enables concatenation of js files
590 */
591 public function enableConcatenateJavascript()
592 {
593 $this->concatenateJavascript = true;
594 }
595
596 /**
597 * Disables concatenation of js files
598 */
599 public function disableConcatenateJavascript()
600 {
601 $this->concatenateJavascript = false;
602 }
603
604 /**
605 * Enables concatenation of css files
606 */
607 public function enableConcatenateCss()
608 {
609 $this->concatenateCss = true;
610 }
611
612 /**
613 * Disables concatenation of css files
614 */
615 public function disableConcatenateCss()
616 {
617 $this->concatenateCss = false;
618 }
619
620 /**
621 * Sets removal of all line breaks in template
622 */
623 public function enableRemoveLineBreaksFromTemplate()
624 {
625 $this->removeLineBreaksFromTemplate = true;
626 }
627
628 /**
629 * Unsets removal of all line breaks in template
630 */
631 public function disableRemoveLineBreaksFromTemplate()
632 {
633 $this->removeLineBreaksFromTemplate = false;
634 }
635
636 /**
637 * Enables Debug Mode
638 * This is a shortcut to switch off all compress/concatenate features to enable easier debug
639 */
640 public function enableDebugMode()
641 {
642 $this->compressJavascript = false;
643 $this->compressCss = false;
644 $this->concatenateCss = false;
645 $this->concatenateJavascript = false;
646 $this->removeLineBreaksFromTemplate = false;
647 }
648
649 /*****************************************************/
650 /* */
651 /* Public Getters */
652 /* */
653 /* */
654 /*****************************************************/
655 /**
656 * Gets the title
657 *
658 * @return string $title Title of webpage
659 */
660 public function getTitle()
661 {
662 return $this->title;
663 }
664
665 /**
666 * Gets the charSet
667 *
668 * @return string $charSet
669 */
670 public function getCharSet()
671 {
672 return $this->charSet;
673 }
674
675 /**
676 * Gets the language
677 *
678 * @return string $lang
679 */
680 public function getLanguage()
681 {
682 return $this->lang;
683 }
684
685 /**
686 * Returns rendering mode XHTML or HTML
687 *
688 * @return bool TRUE if XHTML, FALSE if HTML
689 */
690 public function getRenderXhtml()
691 {
692 return $this->renderXhtml;
693 }
694
695 /**
696 * Gets html tag
697 *
698 * @return string $htmlTag Html tag
699 */
700 public function getHtmlTag()
701 {
702 return $this->htmlTag;
703 }
704
705 /**
706 * Get meta charset
707 *
708 * @return string
709 */
710 public function getMetaCharsetTag()
711 {
712 return $this->metaCharsetTag;
713 }
714
715 /**
716 * Gets head tag
717 *
718 * @return string $tag Head tag
719 */
720 public function getHeadTag()
721 {
722 return $this->headTag;
723 }
724
725 /**
726 * Gets favicon
727 *
728 * @return string $favIcon
729 */
730 public function getFavIcon()
731 {
732 return $this->favIcon;
733 }
734
735 /**
736 * Gets icon mime type
737 *
738 * @return string $iconMimeType
739 */
740 public function getIconMimeType()
741 {
742 return $this->iconMimeType;
743 }
744
745 /**
746 * Gets HTML base URL
747 *
748 * @return string $url
749 */
750 public function getBaseUrl()
751 {
752 return $this->baseUrl;
753 }
754
755 /**
756 * Gets template file
757 *
758 * @return string
759 */
760 public function getTemplateFile()
761 {
762 return $this->templateFile;
763 }
764
765 /**
766 * Gets MoveJsFromHeaderToFooter
767 *
768 * @return bool
769 */
770 public function getMoveJsFromHeaderToFooter()
771 {
772 return $this->moveJsFromHeaderToFooter;
773 }
774
775 /**
776 * Gets compress of javascript
777 *
778 * @return bool
779 */
780 public function getCompressJavascript()
781 {
782 return $this->compressJavascript;
783 }
784
785 /**
786 * Gets compress of css
787 *
788 * @return bool
789 */
790 public function getCompressCss()
791 {
792 return $this->compressCss;
793 }
794
795 /**
796 * Gets concatenate of js files
797 *
798 * @return bool
799 */
800 public function getConcatenateJavascript()
801 {
802 return $this->concatenateJavascript;
803 }
804
805 /**
806 * Gets concatenate of css files
807 *
808 * @return bool
809 */
810 public function getConcatenateCss()
811 {
812 return $this->concatenateCss;
813 }
814
815 /**
816 * Gets remove of empty lines from template
817 *
818 * @return bool
819 */
820 public function getRemoveLineBreaksFromTemplate()
821 {
822 return $this->removeLineBreaksFromTemplate;
823 }
824
825 /**
826 * Gets content for body
827 *
828 * @return string
829 */
830 public function getBodyContent()
831 {
832 return $this->bodyContent;
833 }
834
835 /**
836 * Gets the inline language labels.
837 *
838 * @return array The inline language labels
839 */
840 public function getInlineLanguageLabels()
841 {
842 return $this->inlineLanguageLabels;
843 }
844
845 /**
846 * Gets the inline language files
847 *
848 * @return array
849 */
850 public function getInlineLanguageLabelFiles()
851 {
852 return $this->inlineLanguageLabelFiles;
853 }
854
855 /*****************************************************/
856 /* */
857 /* Public Functions to add Data */
858 /* */
859 /* */
860 /*****************************************************/
861
862 /**
863 * Sets a given meta tag
864 *
865 * @param string $type The type of the meta tag. Allowed values are property, name or http-equiv
866 * @param string $name The name of the property to add
867 * @param string $content The content of the meta tag
868 * @param array $subProperties Subproperties of the meta tag (like e.g. og:image:width)
869 * @param bool $replace Replace earlier set meta tag
870 * @throws \InvalidArgumentException
871 */
872 public function setMetaTag(string $type, string $name, string $content, array $subProperties = [], $replace = true)
873 {
874 // Lowercase all the things
875 $type = strtolower($type);
876 $name = strtolower($name);
877 if (!in_array($type, ['property', 'name', 'http-equiv'], true)) {
878 throw new \InvalidArgumentException(
879 'When setting a meta tag the only types allowed are property, name or http-equiv. "' . $type . '" given.',
880 1496402460
881 );
882 }
883
884 $manager = $this->metaTagRegistry->getManagerForProperty($name);
885 $manager->addProperty($name, $content, $subProperties, $replace, $type);
886 }
887
888 /**
889 * Returns the requested meta tag
890 *
891 * @param string $type
892 * @param string $name
893 *
894 * @return array
895 */
896 public function getMetaTag(string $type, string $name): array
897 {
898 // Lowercase all the things
899 $type = strtolower($type);
900 $name = strtolower($name);
901
902 $manager = $this->metaTagRegistry->getManagerForProperty($name);
903 $propertyContent = $manager->getProperty($name, $type);
904
905 if (!empty($propertyContent[0])) {
906 return [
907 'type' => $type,
908 'name' => $name,
909 'content' => $propertyContent[0]['content']
910 ];
911 }
912 return [];
913 }
914
915 /**
916 * Unset the requested meta tag
917 *
918 * @param string $type
919 * @param string $name
920 */
921 public function removeMetaTag(string $type, string $name)
922 {
923 // Lowercase all the things
924 $type = strtolower($type);
925 $name = strtolower($name);
926
927 $manager = $this->metaTagRegistry->getManagerForProperty($name);
928 $manager->removeProperty($name, $type);
929 }
930
931 /**
932 * Adds inline HTML comment
933 *
934 * @param string $comment
935 */
936 public function addInlineComment($comment)
937 {
938 if (!in_array($comment, $this->inlineComments)) {
939 $this->inlineComments[] = $comment;
940 }
941 }
942
943 /**
944 * Adds header data
945 *
946 * @param string $data Free header data for HTML header
947 */
948 public function addHeaderData($data)
949 {
950 if (!in_array($data, $this->headerData)) {
951 $this->headerData[] = $data;
952 }
953 }
954
955 /**
956 * Adds footer data
957 *
958 * @param string $data Free header data for HTML header
959 */
960 public function addFooterData($data)
961 {
962 if (!in_array($data, $this->footerData)) {
963 $this->footerData[] = $data;
964 }
965 }
966
967 /**
968 * Adds JS Library. JS Library block is rendered on top of the JS files.
969 *
970 * @param string $name Arbitrary identifier
971 * @param string $file File name
972 * @param string $type Content Type
973 * @param bool $compress Flag if library should be compressed
974 * @param bool $forceOnTop Flag if added library should be inserted at begin of this block
975 * @param string $allWrap
976 * @param bool $excludeFromConcatenation
977 * @param string $splitChar The char used to split the allWrap value, default is "|"
978 * @param bool $async Flag if property 'async="async"' should be added to JavaScript tags
979 * @param string $integrity Subresource Integrity (SRI)
980 * @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
981 * @param string $crossorigin CORS settings attribute
982 */
983 public function addJsLibrary($name, $file, $type = 'text/javascript', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
984 {
985 if (!$type) {
986 $type = 'text/javascript';
987 }
988 if (!in_array(strtolower($name), $this->jsLibs)) {
989 $this->jsLibs[strtolower($name)] = [
990 'file' => $file,
991 'type' => $type,
992 'section' => self::PART_HEADER,
993 'compress' => $compress,
994 'forceOnTop' => $forceOnTop,
995 'allWrap' => $allWrap,
996 'excludeFromConcatenation' => $excludeFromConcatenation,
997 'splitChar' => $splitChar,
998 'async' => $async,
999 'integrity' => $integrity,
1000 'defer' => $defer,
1001 'crossorigin' => $crossorigin,
1002 ];
1003 }
1004 }
1005
1006 /**
1007 * Adds JS Library to Footer. JS Library block is rendered on top of the Footer JS files.
1008 *
1009 * @param string $name Arbitrary identifier
1010 * @param string $file File name
1011 * @param string $type Content Type
1012 * @param bool $compress Flag if library should be compressed
1013 * @param bool $forceOnTop Flag if added library should be inserted at begin of this block
1014 * @param string $allWrap
1015 * @param bool $excludeFromConcatenation
1016 * @param string $splitChar The char used to split the allWrap value, default is "|"
1017 * @param bool $async Flag if property 'async="async"' should be added to JavaScript tags
1018 * @param string $integrity Subresource Integrity (SRI)
1019 * @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
1020 * @param string $crossorigin CORS settings attribute
1021 */
1022 public function addJsFooterLibrary($name, $file, $type = 'text/javascript', $compress = false, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
1023 {
1024 if (!$type) {
1025 $type = 'text/javascript';
1026 }
1027 $name .= '_jsFooterLibrary';
1028 if (!in_array(strtolower($name), $this->jsLibs)) {
1029 $this->jsLibs[strtolower($name)] = [
1030 'file' => $file,
1031 'type' => $type,
1032 'section' => self::PART_FOOTER,
1033 'compress' => $compress,
1034 'forceOnTop' => $forceOnTop,
1035 'allWrap' => $allWrap,
1036 'excludeFromConcatenation' => $excludeFromConcatenation,
1037 'splitChar' => $splitChar,
1038 'async' => $async,
1039 'integrity' => $integrity,
1040 'defer' => $defer,
1041 'crossorigin' => $crossorigin,
1042 ];
1043 }
1044 }
1045
1046 /**
1047 * Adds JS file
1048 *
1049 * @param string $file File name
1050 * @param string $type Content Type
1051 * @param bool $compress
1052 * @param bool $forceOnTop
1053 * @param string $allWrap
1054 * @param bool $excludeFromConcatenation
1055 * @param string $splitChar The char used to split the allWrap value, default is "|"
1056 * @param bool $async Flag if property 'async="async"' should be added to JavaScript tags
1057 * @param string $integrity Subresource Integrity (SRI)
1058 * @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
1059 * @param string $crossorigin CORS settings attribute
1060 */
1061 public function addJsFile($file, $type = 'text/javascript', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
1062 {
1063 if (!$type) {
1064 $type = 'text/javascript';
1065 }
1066 if (!isset($this->jsFiles[$file])) {
1067 $this->jsFiles[$file] = [
1068 'file' => $file,
1069 'type' => $type,
1070 'section' => self::PART_HEADER,
1071 'compress' => $compress,
1072 'forceOnTop' => $forceOnTop,
1073 'allWrap' => $allWrap,
1074 'excludeFromConcatenation' => $excludeFromConcatenation,
1075 'splitChar' => $splitChar,
1076 'async' => $async,
1077 'integrity' => $integrity,
1078 'defer' => $defer,
1079 'crossorigin' => $crossorigin,
1080 ];
1081 }
1082 }
1083
1084 /**
1085 * Adds JS file to footer
1086 *
1087 * @param string $file File name
1088 * @param string $type Content Type
1089 * @param bool $compress
1090 * @param bool $forceOnTop
1091 * @param string $allWrap
1092 * @param bool $excludeFromConcatenation
1093 * @param string $splitChar The char used to split the allWrap value, default is "|"
1094 * @param bool $async Flag if property 'async="async"' should be added to JavaScript tags
1095 * @param string $integrity Subresource Integrity (SRI)
1096 * @param bool $defer Flag if property 'defer="defer"' should be added to JavaScript tags
1097 * @param string $crossorigin CORS settings attribute
1098 */
1099 public function addJsFooterFile($file, $type = 'text/javascript', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $async = false, $integrity = '', $defer = false, $crossorigin = '')
1100 {
1101 if (!$type) {
1102 $type = 'text/javascript';
1103 }
1104 if (!isset($this->jsFiles[$file])) {
1105 $this->jsFiles[$file] = [
1106 'file' => $file,
1107 'type' => $type,
1108 'section' => self::PART_FOOTER,
1109 'compress' => $compress,
1110 'forceOnTop' => $forceOnTop,
1111 'allWrap' => $allWrap,
1112 'excludeFromConcatenation' => $excludeFromConcatenation,
1113 'splitChar' => $splitChar,
1114 'async' => $async,
1115 'integrity' => $integrity,
1116 'defer' => $defer,
1117 'crossorigin' => $crossorigin,
1118 ];
1119 }
1120 }
1121
1122 /**
1123 * Adds JS inline code
1124 *
1125 * @param string $name
1126 * @param string $block
1127 * @param bool $compress
1128 * @param bool $forceOnTop
1129 */
1130 public function addJsInlineCode($name, $block, $compress = true, $forceOnTop = false)
1131 {
1132 if (!isset($this->jsInline[$name]) && !empty($block)) {
1133 $this->jsInline[$name] = [
1134 'code' => $block . LF,
1135 'section' => self::PART_HEADER,
1136 'compress' => $compress,
1137 'forceOnTop' => $forceOnTop
1138 ];
1139 }
1140 }
1141
1142 /**
1143 * Adds JS inline code to footer
1144 *
1145 * @param string $name
1146 * @param string $block
1147 * @param bool $compress
1148 * @param bool $forceOnTop
1149 */
1150 public function addJsFooterInlineCode($name, $block, $compress = true, $forceOnTop = false)
1151 {
1152 if (!isset($this->jsInline[$name]) && !empty($block)) {
1153 $this->jsInline[$name] = [
1154 'code' => $block . LF,
1155 'section' => self::PART_FOOTER,
1156 'compress' => $compress,
1157 'forceOnTop' => $forceOnTop
1158 ];
1159 }
1160 }
1161
1162 /**
1163 * Adds CSS file
1164 *
1165 * @param string $file
1166 * @param string $rel
1167 * @param string $media
1168 * @param string $title
1169 * @param bool $compress
1170 * @param bool $forceOnTop
1171 * @param string $allWrap
1172 * @param bool $excludeFromConcatenation
1173 * @param string $splitChar The char used to split the allWrap value, default is "|"
1174 * @param bool $inline
1175 */
1176 public function addCssFile($file, $rel = 'stylesheet', $media = 'all', $title = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $inline = false)
1177 {
1178 if (!isset($this->cssFiles[$file])) {
1179 $this->cssFiles[$file] = [
1180 'file' => $file,
1181 'rel' => $rel,
1182 'media' => $media,
1183 'title' => $title,
1184 'compress' => $compress,
1185 'forceOnTop' => $forceOnTop,
1186 'allWrap' => $allWrap,
1187 'excludeFromConcatenation' => $excludeFromConcatenation,
1188 'splitChar' => $splitChar,
1189 'inline' => $inline
1190 ];
1191 }
1192 }
1193
1194 /**
1195 * Adds CSS file
1196 *
1197 * @param string $file
1198 * @param string $rel
1199 * @param string $media
1200 * @param string $title
1201 * @param bool $compress
1202 * @param bool $forceOnTop
1203 * @param string $allWrap
1204 * @param bool $excludeFromConcatenation
1205 * @param string $splitChar The char used to split the allWrap value, default is "|"
1206 * @param bool $inline
1207 */
1208 public function addCssLibrary($file, $rel = 'stylesheet', $media = 'all', $title = '', $compress = true, $forceOnTop = false, $allWrap = '', $excludeFromConcatenation = false, $splitChar = '|', $inline = false)
1209 {
1210 if (!isset($this->cssLibs[$file])) {
1211 $this->cssLibs[$file] = [
1212 'file' => $file,
1213 'rel' => $rel,
1214 'media' => $media,
1215 'title' => $title,
1216 'compress' => $compress,
1217 'forceOnTop' => $forceOnTop,
1218 'allWrap' => $allWrap,
1219 'excludeFromConcatenation' => $excludeFromConcatenation,
1220 'splitChar' => $splitChar,
1221 'inline' => $inline
1222 ];
1223 }
1224 }
1225
1226 /**
1227 * Adds CSS inline code
1228 *
1229 * @param string $name
1230 * @param string $block
1231 * @param bool $compress
1232 * @param bool $forceOnTop
1233 */
1234 public function addCssInlineBlock($name, $block, $compress = false, $forceOnTop = false)
1235 {
1236 if (!isset($this->cssInline[$name]) && !empty($block)) {
1237 $this->cssInline[$name] = [
1238 'code' => $block,
1239 'compress' => $compress,
1240 'forceOnTop' => $forceOnTop
1241 ];
1242 }
1243 }
1244
1245 /**
1246 * Call function if you need the requireJS library
1247 * this automatically adds the JavaScript path of all loaded extensions in the requireJS path option
1248 * so it resolves names like TYPO3/CMS/MyExtension/MyJsFile to EXT:MyExtension/Resources/Public/JavaScript/MyJsFile.js
1249 * when using requireJS
1250 */
1251 public function loadRequireJs()
1252 {
1253 $this->addRequireJs = true;
1254 if (!empty($this->requireJsConfig)) {
1255 return;
1256 }
1257
1258 $loadedExtensions = ExtensionManagementUtility::getLoadedExtensionListArray();
1259 $isDevelopment = GeneralUtility::getApplicationContext()->isDevelopment();
1260 $cacheIdentifier = 'requireJS_' . md5(implode(',', $loadedExtensions) . ($isDevelopment ? ':dev' : '') . GeneralUtility::getIndpEnv('TYPO3_REQUEST_SCRIPT'));
1261 /** @var FrontendInterface $cache */
1262 $cache = static::$cache ?? GeneralUtility::makeInstance(CacheManager::class)->getCache('assets');
1263 $this->requireJsConfig = $cache->get($cacheIdentifier);
1264
1265 // if we did not get a configuration from the cache, compute and store it in the cache
1266 if (empty($this->requireJsConfig)) {
1267 $this->requireJsConfig = $this->computeRequireJsConfig($isDevelopment, $loadedExtensions);
1268 $cache->set($cacheIdentifier, $this->requireJsConfig);
1269 }
1270 }
1271
1272 /**
1273 * Computes the RequireJS configuration, mainly consisting of the paths to the core and all extension JavaScript
1274 * resource folders plus some additional generic configuration.
1275 *
1276 * @param bool $isDevelopment
1277 * @param array $loadedExtensions
1278 * @return array The RequireJS configuration
1279 */
1280 protected function computeRequireJsConfig($isDevelopment, array $loadedExtensions)
1281 {
1282 // load all paths to map to package names / namespaces
1283 $requireJsConfig = [];
1284
1285 // In order to avoid browser caching of JS files, adding a GET parameter to the files loaded via requireJS
1286 if ($isDevelopment) {
1287 $requireJsConfig['urlArgs'] = 'bust=' . $GLOBALS['EXEC_TIME'];
1288 } else {
1289 $requireJsConfig['urlArgs'] = 'bust=' . GeneralUtility::hmac(TYPO3_version . Environment::getProjectPath());
1290 }
1291 $corePath = ExtensionManagementUtility::extPath('core', 'Resources/Public/JavaScript/Contrib/');
1292 $corePath = PathUtility::getAbsoluteWebPath($corePath);
1293 // first, load all paths for the namespaces, and configure contrib libs.
1294 $requireJsConfig['paths'] = [
1295 'jquery' => $corePath . '/jquery/jquery',
1296 'jquery-ui' => $corePath . 'jquery-ui',
1297 'datatables' => $corePath . 'jquery.dataTables',
1298 'nprogress' => $corePath . 'nprogress',
1299 'moment' => $corePath . 'moment',
1300 'cropper' => $corePath . 'cropper.min',
1301 'imagesloaded' => $corePath . 'imagesloaded.pkgd.min',
1302 'bootstrap' => $corePath . 'bootstrap/bootstrap',
1303 'twbs/bootstrap-datetimepicker' => $corePath . 'bootstrap-datetimepicker',
1304 'autosize' => $corePath . 'autosize',
1305 'taboverride' => $corePath . 'taboverride.min',
1306 'twbs/bootstrap-slider' => $corePath . 'bootstrap-slider.min',
1307 'jquery/autocomplete' => $corePath . 'jquery.autocomplete',
1308 'd3' => $corePath . 'd3/d3'
1309 ];
1310 $requireJsConfig['waitSeconds'] = 30;
1311 foreach ($loadedExtensions as $packageName) {
1312 $fullJsPath = 'EXT:' . $packageName . '/Resources/Public/JavaScript/';
1313 $fullJsPath = GeneralUtility::getFileAbsFileName($fullJsPath);
1314 $fullJsPath = PathUtility::getAbsoluteWebPath($fullJsPath);
1315 $fullJsPath = rtrim($fullJsPath, '/');
1316 if ($fullJsPath) {
1317 $requireJsConfig['paths']['TYPO3/CMS/' . GeneralUtility::underscoredToUpperCamelCase($packageName)] = $fullJsPath;
1318 }
1319 }
1320
1321 // check if additional AMD modules need to be loaded if a single AMD module is initialized
1322 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['RequireJS']['postInitializationModules'] ?? false)) {
1323 $this->addInlineSettingArray(
1324 'RequireJS.PostInitializationModules',
1325 $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['RequireJS']['postInitializationModules']
1326 );
1327 }
1328
1329 return $requireJsConfig;
1330 }
1331
1332 /**
1333 * Add additional configuration to require js.
1334 *
1335 * Configuration will be merged recursive with overrule.
1336 *
1337 * To add another path mapping deliver the following configuration:
1338 * 'paths' => array(
1339 * 'EXTERN/mybootstrapjs' => 'sysext/.../twbs/bootstrap.min',
1340 * ),
1341 *
1342 * @param array $configuration The configuration that will be merged with existing one.
1343 */
1344 public function addRequireJsConfiguration(array $configuration)
1345 {
1346 if (TYPO3_MODE === 'BE') {
1347 // Load RequireJS in backend context at first. Doing this in FE could break the output
1348 $this->loadRequireJs();
1349 }
1350 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($this->requireJsConfig, $configuration);
1351 }
1352
1353 /**
1354 * includes an AMD-compatible JS file by resolving the ModuleName, and then requires the file via a requireJS request,
1355 * additionally allowing to execute JavaScript code afterwards
1356 *
1357 * this function only works for AMD-ready JS modules, used like "define('TYPO3/CMS/Backend/FormEngine..."
1358 * in the JS file
1359 *
1360 * TYPO3/CMS/Backend/FormEngine =>
1361 * "TYPO3": Vendor Name
1362 * "CMS": Product Name
1363 * "Backend": Extension Name
1364 * "FormEngine": FileName in the Resources/Public/JavaScript folder
1365 *
1366 * @param string $mainModuleName Must be in the form of "TYPO3/CMS/PackageName/ModuleName" e.g. "TYPO3/CMS/Backend/FormEngine"
1367 * @param string $callBackFunction loaded right after the requireJS loading, must be wrapped in function() {}
1368 */
1369 public function loadRequireJsModule($mainModuleName, $callBackFunction = null)
1370 {
1371 $inlineCodeKey = $mainModuleName;
1372 // make sure requireJS is initialized
1373 $this->loadRequireJs();
1374
1375 // execute the main module, and load a possible callback function
1376 $javaScriptCode = 'require(["' . $mainModuleName . '"]';
1377 if ($callBackFunction !== null) {
1378 $inlineCodeKey .= sha1($callBackFunction);
1379 $javaScriptCode .= ', ' . $callBackFunction;
1380 }
1381 $javaScriptCode .= ');';
1382 $this->addJsInlineCode('RequireJS-Module-' . $inlineCodeKey, $javaScriptCode);
1383 }
1384
1385 /**
1386 * Adds Javascript Inline Label. This will occur in TYPO3.lang - object
1387 * The label can be used in scripts with TYPO3.lang.<key>
1388 *
1389 * @param string $key
1390 * @param string $value
1391 */
1392 public function addInlineLanguageLabel($key, $value)
1393 {
1394 $this->inlineLanguageLabels[$key] = $value;
1395 }
1396
1397 /**
1398 * Adds Javascript Inline Label Array. This will occur in TYPO3.lang - object
1399 * The label can be used in scripts with TYPO3.lang.<key>
1400 * Array will be merged with existing array.
1401 *
1402 * @param array $array
1403 */
1404 public function addInlineLanguageLabelArray(array $array)
1405 {
1406 $this->inlineLanguageLabels = array_merge($this->inlineLanguageLabels, $array);
1407 }
1408
1409 /**
1410 * Gets labels to be used in JavaScript fetched from a locallang file.
1411 *
1412 * @param string $fileRef Input is a file-reference (see GeneralUtility::getFileAbsFileName). That file is expected to be a 'locallang.xlf' file containing a valid XML TYPO3 language structure.
1413 * @param string $selectionPrefix Prefix to select the correct labels (default: '')
1414 * @param string $stripFromSelectionName String to be removed from the label names in the output. (default: '')
1415 */
1416 public function addInlineLanguageLabelFile($fileRef, $selectionPrefix = '', $stripFromSelectionName = '')
1417 {
1418 $index = md5($fileRef . $selectionPrefix . $stripFromSelectionName);
1419 if ($fileRef && !isset($this->inlineLanguageLabelFiles[$index])) {
1420 $this->inlineLanguageLabelFiles[$index] = [
1421 'fileRef' => $fileRef,
1422 'selectionPrefix' => $selectionPrefix,
1423 'stripFromSelectionName' => $stripFromSelectionName
1424 ];
1425 }
1426 }
1427
1428 /**
1429 * Adds Javascript Inline Setting. This will occur in TYPO3.settings - object
1430 * The label can be used in scripts with TYPO3.setting.<key>
1431 *
1432 * @param string $namespace
1433 * @param string $key
1434 * @param mixed $value
1435 */
1436 public function addInlineSetting($namespace, $key, $value)
1437 {
1438 if ($namespace) {
1439 if (strpos($namespace, '.')) {
1440 $parts = explode('.', $namespace);
1441 $a = &$this->inlineSettings;
1442 foreach ($parts as $part) {
1443 $a = &$a[$part];
1444 }
1445 $a[$key] = $value;
1446 } else {
1447 $this->inlineSettings[$namespace][$key] = $value;
1448 }
1449 } else {
1450 $this->inlineSettings[$key] = $value;
1451 }
1452 }
1453
1454 /**
1455 * Adds Javascript Inline Setting. This will occur in TYPO3.settings - object
1456 * The label can be used in scripts with TYPO3.setting.<key>
1457 * Array will be merged with existing array.
1458 *
1459 * @param string $namespace
1460 * @param array $array
1461 */
1462 public function addInlineSettingArray($namespace, array $array)
1463 {
1464 if ($namespace) {
1465 if (strpos($namespace, '.')) {
1466 $parts = explode('.', $namespace);
1467 $a = &$this->inlineSettings;
1468 foreach ($parts as $part) {
1469 $a = &$a[$part];
1470 }
1471 $a = array_merge((array)$a, $array);
1472 } else {
1473 $this->inlineSettings[$namespace] = array_merge((array)$this->inlineSettings[$namespace], $array);
1474 }
1475 } else {
1476 $this->inlineSettings = array_merge($this->inlineSettings, $array);
1477 }
1478 }
1479
1480 /**
1481 * Adds content to body content
1482 *
1483 * @param string $content
1484 */
1485 public function addBodyContent($content)
1486 {
1487 $this->bodyContent .= $content;
1488 }
1489
1490 /*****************************************************/
1491 /* */
1492 /* Render Functions */
1493 /* */
1494 /*****************************************************/
1495 /**
1496 * Render the section (Header or Footer)
1497 *
1498 * @param int $part Section which should be rendered: self::PART_COMPLETE, self::PART_HEADER or self::PART_FOOTER
1499 * @return string Content of rendered section
1500 */
1501 public function render($part = self::PART_COMPLETE)
1502 {
1503 $this->prepareRendering();
1504 list($jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs) = $this->renderJavaScriptAndCss();
1505 $metaTags = implode(LF, array_merge($this->metaTags, $this->renderMetaTagsFromAPI()));
1506 $markerArray = $this->getPreparedMarkerArray($jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs, $metaTags);
1507 $template = $this->getTemplateForPart($part);
1508
1509 // The page renderer needs a full reset, even when only rendering one part of the page
1510 // This means that you can only register footer files *after* the header has been already rendered.
1511 // In case you render the footer part first, header files can only be added *after* the footer has been rendered
1512 $this->reset();
1513 $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1514 return trim($templateService->substituteMarkerArray($template, $markerArray, '###|###'));
1515 }
1516
1517 /**
1518 * Renders metaTags based on tags added via the API
1519 *
1520 * @return array
1521 */
1522 protected function renderMetaTagsFromAPI()
1523 {
1524 $metaTags = [];
1525 $metaTagManagers = $this->metaTagRegistry->getAllManagers();
1526 try {
1527 $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_pages');
1528 } catch (NoSuchCacheException $e) {
1529 $cache = null;
1530 }
1531
1532 foreach ($metaTagManagers as $manager => $managerObject) {
1533 $cacheIdentifier = $this->getTypoScriptFrontendController()->newHash . '-metatag-' . $manager;
1534
1535 $existingCacheEntry = false;
1536 if ($cache instanceof FrontendInterface && $properties = $cache->get($cacheIdentifier)) {
1537 $existingCacheEntry = true;
1538 } else {
1539 $properties = $managerObject->renderAllProperties();
1540 }
1541
1542 if (!empty($properties)) {
1543 $metaTags[] = $properties;
1544
1545 if ($cache instanceof FrontendInterface && !$existingCacheEntry && ($this->getTypoScriptFrontendController()->page['uid'] ?? false)) {
1546 $cache->set(
1547 $cacheIdentifier,
1548 $properties,
1549 ['pageId_' . $this->getTypoScriptFrontendController()->page['uid']],
1550 $this->getTypoScriptFrontendController()->get_cache_timeout()
1551 );
1552 }
1553 }
1554 }
1555 return $metaTags;
1556 }
1557
1558 /**
1559 * Render the page but not the JavaScript and CSS Files
1560 *
1561 * @param string $substituteHash The hash that is used for the placehoder markers
1562 * @internal
1563 * @return string Content of rendered section
1564 */
1565 public function renderPageWithUncachedObjects($substituteHash)
1566 {
1567 $this->prepareRendering();
1568 $markerArray = $this->getPreparedMarkerArrayForPageWithUncachedObjects($substituteHash);
1569 $template = $this->getTemplateForPart(self::PART_COMPLETE);
1570 $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
1571 return trim($templateService->substituteMarkerArray($template, $markerArray, '###|###'));
1572 }
1573
1574 /**
1575 * Renders the JavaScript and CSS files that have been added during processing
1576 * of uncached content objects (USER_INT, COA_INT)
1577 *
1578 * @param string $cachedPageContent
1579 * @param string $substituteHash The hash that is used for the placehoder markers
1580 * @internal
1581 * @return string
1582 */
1583 public function renderJavaScriptAndCssForProcessingOfUncachedContentObjects($cachedPageContent, $substituteHash)
1584 {
1585 $this->prepareRendering();
1586 list($jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs) = $this->renderJavaScriptAndCss();
1587 $title = $this->title ? str_replace('|', htmlspecialchars($this->title), $this->titleTag) : '';
1588 $markerArray = [
1589 '<!-- ###TITLE' . $substituteHash . '### -->' => $title,
1590 '<!-- ###CSS_LIBS' . $substituteHash . '### -->' => $cssLibs,
1591 '<!-- ###CSS_INCLUDE' . $substituteHash . '### -->' => $cssFiles,
1592 '<!-- ###CSS_INLINE' . $substituteHash . '### -->' => $cssInline,
1593 '<!-- ###JS_INLINE' . $substituteHash . '### -->' => $jsInline,
1594 '<!-- ###JS_INCLUDE' . $substituteHash . '### -->' => $jsFiles,
1595 '<!-- ###JS_LIBS' . $substituteHash . '### -->' => $jsLibs,
1596 '<!-- ###META' . $substituteHash . '### -->' => implode(LF, array_merge($this->metaTags, $this->renderMetaTagsFromAPI())),
1597 '<!-- ###HEADERDATA' . $substituteHash . '### -->' => implode(LF, $this->headerData),
1598 '<!-- ###FOOTERDATA' . $substituteHash . '### -->' => implode(LF, $this->footerData),
1599 '<!-- ###JS_LIBS_FOOTER' . $substituteHash . '### -->' => $jsFooterLibs,
1600 '<!-- ###JS_INCLUDE_FOOTER' . $substituteHash . '### -->' => $jsFooterFiles,
1601 '<!-- ###JS_INLINE_FOOTER' . $substituteHash . '### -->' => $jsFooterInline
1602 ];
1603 foreach ($markerArray as $placeHolder => $content) {
1604 $cachedPageContent = str_replace($placeHolder, $content, $cachedPageContent);
1605 }
1606 $this->reset();
1607 return $cachedPageContent;
1608 }
1609
1610 /**
1611 * Remove ending slashes from static header block
1612 * if the page is being rendered as html (not xhtml)
1613 * and define property $this->endingSlash for further use
1614 */
1615 protected function prepareRendering()
1616 {
1617 if ($this->getRenderXhtml()) {
1618 $this->endingSlash = ' /';
1619 } else {
1620 $this->metaCharsetTag = str_replace(' />', '>', $this->metaCharsetTag);
1621 $this->baseUrlTag = str_replace(' />', '>', $this->baseUrlTag);
1622 $this->shortcutTag = str_replace(' />', '>', $this->shortcutTag);
1623 $this->endingSlash = '';
1624 }
1625 }
1626
1627 /**
1628 * Renders all JavaScript and CSS
1629 *
1630 * @return array<string>
1631 */
1632 protected function renderJavaScriptAndCss()
1633 {
1634 $this->executePreRenderHook();
1635 $mainJsLibs = $this->renderMainJavaScriptLibraries();
1636 if ($this->concatenateJavascript || $this->concatenateCss) {
1637 // Do the file concatenation
1638 $this->doConcatenate();
1639 }
1640 if ($this->compressCss || $this->compressJavascript) {
1641 // Do the file compression
1642 $this->doCompress();
1643 }
1644 $this->executeRenderPostTransformHook();
1645 $cssLibs = $this->renderCssLibraries();
1646 $cssFiles = $this->renderCssFiles();
1647 $cssInline = $this->renderCssInline();
1648 list($jsLibs, $jsFooterLibs) = $this->renderAdditionalJavaScriptLibraries();
1649 list($jsFiles, $jsFooterFiles) = $this->renderJavaScriptFiles();
1650 list($jsInline, $jsFooterInline) = $this->renderInlineJavaScript();
1651 $jsLibs = $mainJsLibs . $jsLibs;
1652 if ($this->moveJsFromHeaderToFooter) {
1653 $jsFooterLibs = $jsLibs . LF . $jsFooterLibs;
1654 $jsLibs = '';
1655 $jsFooterFiles = $jsFiles . LF . $jsFooterFiles;
1656 $jsFiles = '';
1657 $jsFooterInline = $jsInline . LF . $jsFooterInline;
1658 $jsInline = '';
1659 }
1660 $this->executePostRenderHook($jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs);
1661 return [$jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs];
1662 }
1663
1664 /**
1665 * Fills the marker array with the given strings and trims each value
1666 *
1667 * @param string $jsLibs
1668 * @param string $jsFiles
1669 * @param string $jsFooterFiles
1670 * @param string $cssLibs
1671 * @param string $cssFiles
1672 * @param string $jsInline
1673 * @param string $cssInline
1674 * @param string $jsFooterInline
1675 * @param string $jsFooterLibs
1676 * @param string $metaTags
1677 * @return array Marker array
1678 */
1679 protected function getPreparedMarkerArray($jsLibs, $jsFiles, $jsFooterFiles, $cssLibs, $cssFiles, $jsInline, $cssInline, $jsFooterInline, $jsFooterLibs, $metaTags)
1680 {
1681 $markerArray = [
1682 'XMLPROLOG_DOCTYPE' => $this->xmlPrologAndDocType,
1683 'HTMLTAG' => $this->htmlTag,
1684 'HEADTAG' => $this->headTag,
1685 'METACHARSET' => $this->charSet ? str_replace('|', htmlspecialchars($this->charSet), $this->metaCharsetTag) : '',
1686 'INLINECOMMENT' => $this->inlineComments ? LF . LF . '<!-- ' . LF . implode(LF, $this->inlineComments) . '-->' . LF . LF : '',
1687 'BASEURL' => $this->baseUrl ? str_replace('|', $this->baseUrl, $this->baseUrlTag) : '',
1688 'SHORTCUT' => $this->favIcon ? sprintf($this->shortcutTag, htmlspecialchars($this->favIcon), $this->iconMimeType) : '',
1689 'CSS_LIBS' => $cssLibs,
1690 'CSS_INCLUDE' => $cssFiles,
1691 'CSS_INLINE' => $cssInline,
1692 'JS_INLINE' => $jsInline,
1693 'JS_INCLUDE' => $jsFiles,
1694 'JS_LIBS' => $jsLibs,
1695 'TITLE' => $this->title ? str_replace('|', htmlspecialchars($this->title), $this->titleTag) : '',
1696 'META' => $metaTags,
1697 'HEADERDATA' => $this->headerData ? implode(LF, $this->headerData) : '',
1698 'FOOTERDATA' => $this->footerData ? implode(LF, $this->footerData) : '',
1699 'JS_LIBS_FOOTER' => $jsFooterLibs,
1700 'JS_INCLUDE_FOOTER' => $jsFooterFiles,
1701 'JS_INLINE_FOOTER' => $jsFooterInline,
1702 'BODY' => $this->bodyContent
1703 ];
1704 $markerArray = array_map('trim', $markerArray);
1705 return $markerArray;
1706 }
1707
1708 /**
1709 * Fills the marker array with the given strings and trims each value
1710 *
1711 * @param string $substituteHash The hash that is used for the placehoder markers
1712 * @return array Marker array
1713 */
1714 protected function getPreparedMarkerArrayForPageWithUncachedObjects($substituteHash)
1715 {
1716 $markerArray = [
1717 'XMLPROLOG_DOCTYPE' => $this->xmlPrologAndDocType,
1718 'HTMLTAG' => $this->htmlTag,
1719 'HEADTAG' => $this->headTag,
1720 'METACHARSET' => $this->charSet ? str_replace('|', htmlspecialchars($this->charSet), $this->metaCharsetTag) : '',
1721 'INLINECOMMENT' => $this->inlineComments ? LF . LF . '<!-- ' . LF . implode(LF, $this->inlineComments) . '-->' . LF . LF : '',
1722 'BASEURL' => $this->baseUrl ? str_replace('|', $this->baseUrl, $this->baseUrlTag) : '',
1723 'SHORTCUT' => $this->favIcon ? sprintf($this->shortcutTag, htmlspecialchars($this->favIcon), $this->iconMimeType) : '',
1724 'META' => '<!-- ###META' . $substituteHash . '### -->',
1725 'BODY' => $this->bodyContent,
1726 'TITLE' => '<!-- ###TITLE' . $substituteHash . '### -->',
1727 'CSS_LIBS' => '<!-- ###CSS_LIBS' . $substituteHash . '### -->',
1728 'CSS_INCLUDE' => '<!-- ###CSS_INCLUDE' . $substituteHash . '### -->',
1729 'CSS_INLINE' => '<!-- ###CSS_INLINE' . $substituteHash . '### -->',
1730 'JS_INLINE' => '<!-- ###JS_INLINE' . $substituteHash . '### -->',
1731 'JS_INCLUDE' => '<!-- ###JS_INCLUDE' . $substituteHash . '### -->',
1732 'JS_LIBS' => '<!-- ###JS_LIBS' . $substituteHash . '### -->',
1733 'HEADERDATA' => '<!-- ###HEADERDATA' . $substituteHash . '### -->',
1734 'FOOTERDATA' => '<!-- ###FOOTERDATA' . $substituteHash . '### -->',
1735 'JS_LIBS_FOOTER' => '<!-- ###JS_LIBS_FOOTER' . $substituteHash . '### -->',
1736 'JS_INCLUDE_FOOTER' => '<!-- ###JS_INCLUDE_FOOTER' . $substituteHash . '### -->',
1737 'JS_INLINE_FOOTER' => '<!-- ###JS_INLINE_FOOTER' . $substituteHash . '### -->'
1738 ];
1739 $markerArray = array_map('trim', $markerArray);
1740 return $markerArray;
1741 }
1742
1743 /**
1744 * Reads the template file and returns the requested part as string
1745 *
1746 * @param int $part
1747 * @return string
1748 */
1749 protected function getTemplateForPart($part)
1750 {
1751 $templateFile = GeneralUtility::getFileAbsFileName($this->templateFile);
1752 if (is_file($templateFile)) {
1753 $template = file_get_contents($templateFile);
1754 if ($this->removeLineBreaksFromTemplate) {
1755 $template = strtr($template, [LF => '', CR => '']);
1756 }
1757 if ($part !== self::PART_COMPLETE) {
1758 $templatePart = explode('###BODY###', $template);
1759 $template = $templatePart[$part - 1];
1760 }
1761 } else {
1762 $template = '';
1763 }
1764 return $template;
1765 }
1766
1767 /**
1768 * Helper function for render the main JavaScript libraries,
1769 * currently: RequireJS
1770 *
1771 * @return string Content with JavaScript libraries
1772 */
1773 protected function renderMainJavaScriptLibraries()
1774 {
1775 $out = '';
1776
1777 // Include RequireJS
1778 if ($this->addRequireJs) {
1779 // load the paths of the requireJS configuration
1780 $out .= GeneralUtility::wrapJS('var require = ' . json_encode($this->requireJsConfig)) . LF;
1781 // directly after that, include the require.js file
1782 $out .= '<script src="' . $this->processJsFile($this->requireJsPath . 'require.js') . '" type="text/javascript"></script>' . LF;
1783 }
1784
1785 $this->loadJavaScriptLanguageStrings();
1786 if (TYPO3_MODE === 'BE') {
1787 $this->addAjaxUrlsToInlineSettings();
1788 }
1789 $inlineSettings = '';
1790 $languageLabels = $this->parseLanguageLabelsForJavaScript();
1791 if (!empty($languageLabels)) {
1792 $inlineSettings .= 'TYPO3.lang = ' . json_encode($languageLabels) . ';';
1793 }
1794 $inlineSettings .= $this->inlineSettings ? 'TYPO3.settings = ' . json_encode($this->inlineSettings) . ';' : '';
1795
1796 if ($inlineSettings !== '') {
1797 // make sure the global TYPO3 is available
1798 $inlineSettings = 'var TYPO3 = TYPO3 || {};' . CRLF . $inlineSettings;
1799 $out .= $this->inlineJavascriptWrap[0] . $inlineSettings . $this->inlineJavascriptWrap[1];
1800 }
1801
1802 return $out;
1803 }
1804
1805 /**
1806 * Converts the language labels for usage in JavaScript
1807 *
1808 * @return array
1809 */
1810 protected function parseLanguageLabelsForJavaScript(): array
1811 {
1812 if (empty($this->inlineLanguageLabels)) {
1813 return [];
1814 }
1815
1816 $labels = [];
1817 foreach ($this->inlineLanguageLabels as $key => $translationUnit) {
1818 if (is_array($translationUnit)) {
1819 $translationUnit = current($translationUnit);
1820 $labels[$key] = $translationUnit['target'] ?? $translationUnit['source'];
1821 } else {
1822 $labels[$key] = $translationUnit;
1823 }
1824 }
1825
1826 return $labels;
1827 }
1828
1829 /**
1830 * Load the language strings into JavaScript
1831 */
1832 protected function loadJavaScriptLanguageStrings()
1833 {
1834 if (!empty($this->inlineLanguageLabelFiles)) {
1835 foreach ($this->inlineLanguageLabelFiles as $languageLabelFile) {
1836 $this->includeLanguageFileForInline($languageLabelFile['fileRef'], $languageLabelFile['selectionPrefix'], $languageLabelFile['stripFromSelectionName']);
1837 }
1838 }
1839 $this->inlineLanguageLabelFiles = [];
1840 // Convert settings back to UTF-8 since json_encode() only works with UTF-8:
1841 if ($this->getCharSet() && $this->getCharSet() !== 'utf-8' && is_array($this->inlineSettings)) {
1842 $this->convertCharsetRecursivelyToUtf8($this->inlineSettings, $this->getCharSet());
1843 }
1844 }
1845
1846 /**
1847 * Small helper function to convert charsets for arrays into utf-8
1848 *
1849 * @param mixed $data given by reference (string/array usually)
1850 * @param string $fromCharset convert FROM this charset
1851 */
1852 protected function convertCharsetRecursivelyToUtf8(&$data, string $fromCharset)
1853 {
1854 foreach ($data as $key => $value) {
1855 if (is_array($data[$key])) {
1856 $this->convertCharsetRecursivelyToUtf8($data[$key], $fromCharset);
1857 } elseif (is_string($data[$key])) {
1858 $data[$key] = mb_convert_encoding($data[$key], 'utf-8', $fromCharset);
1859 }
1860 }
1861 }
1862
1863 /**
1864 * Make URLs to all backend ajax handlers available as inline setting.
1865 */
1866 protected function addAjaxUrlsToInlineSettings()
1867 {
1868 $ajaxUrls = [];
1869 // Add the ajax-based routes
1870 /** @var UriBuilder $uriBuilder */
1871 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1872 /** @var Router $router */
1873 $router = GeneralUtility::makeInstance(Router::class);
1874 $routes = $router->getRoutes();
1875 foreach ($routes as $routeIdentifier => $route) {
1876 if ($route->getOption('ajax')) {
1877 $uri = (string)$uriBuilder->buildUriFromRoute($routeIdentifier);
1878 // use the shortened value in order to use this in JavaScript
1879 $routeIdentifier = str_replace('ajax_', '', $routeIdentifier);
1880 $ajaxUrls[$routeIdentifier] = $uri;
1881 }
1882 }
1883
1884 $this->inlineSettings['ajaxUrls'] = $ajaxUrls;
1885 }
1886
1887 /**
1888 * Render CSS library files
1889 *
1890 * @return string
1891 */
1892 protected function renderCssLibraries()
1893 {
1894 $cssFiles = '';
1895 if (!empty($this->cssLibs)) {
1896 foreach ($this->cssLibs as $file => $properties) {
1897 $tag = $this->createCssTag($properties, $file);
1898 if ($properties['forceOnTop']) {
1899 $cssFiles = $tag . $cssFiles;
1900 } else {
1901 $cssFiles .= $tag;
1902 }
1903 }
1904 }
1905 return $cssFiles;
1906 }
1907
1908 /**
1909 * Render CSS files
1910 *
1911 * @return string
1912 */
1913 protected function renderCssFiles()
1914 {
1915 $cssFiles = '';
1916 if (!empty($this->cssFiles)) {
1917 foreach ($this->cssFiles as $file => $properties) {
1918 $tag = $this->createCssTag($properties, $file);
1919 if ($properties['forceOnTop']) {
1920 $cssFiles = $tag . $cssFiles;
1921 } else {
1922 $cssFiles .= $tag;
1923 }
1924 }
1925 }
1926 return $cssFiles;
1927 }
1928
1929 /**
1930 * Create link (inline=0) or style (inline=1) tag
1931 *
1932 * @param array $properties
1933 * @param string $file
1934 * @return string
1935 */
1936 private function createCssTag(array $properties, string $file): string
1937 {
1938 if ($properties['inline'] && @is_file($file)) {
1939 $tag = $this->createInlineCssTagFromFile($file, $properties);
1940 } else {
1941 $href = $this->getStreamlinedFileName($file);
1942 $tag = '<link rel="' . htmlspecialchars($properties['rel'])
1943 . '" type="text/css" href="' . htmlspecialchars($href)
1944 . '" media="' . htmlspecialchars($properties['media']) . '"'
1945 . ($properties['title'] ? ' title="' . htmlspecialchars($properties['title']) . '"' : '')
1946 . $this->endingSlash . '>';
1947 }
1948 if ($properties['allWrap']) {
1949 $wrapArr = explode($properties['splitChar'] ?: '|', $properties['allWrap'], 2);
1950 $tag = $wrapArr[0] . $tag . $wrapArr[1];
1951 }
1952 $tag .= LF;
1953
1954 return $tag;
1955 }
1956
1957 /**
1958 * Render inline CSS
1959 *
1960 * @return string
1961 */
1962 protected function renderCssInline()
1963 {
1964 $cssInline = '';
1965 if (!empty($this->cssInline)) {
1966 foreach ($this->cssInline as $name => $properties) {
1967 $cssCode = '/*' . htmlspecialchars($name) . '*/' . LF . $properties['code'] . LF;
1968 if ($properties['forceOnTop']) {
1969 $cssInline = $cssCode . $cssInline;
1970 } else {
1971 $cssInline .= $cssCode;
1972 }
1973 }
1974 $cssInline = $this->inlineCssWrap[0] . $cssInline . $this->inlineCssWrap[1];
1975 }
1976 return $cssInline;
1977 }
1978
1979 /**
1980 * Render JavaScipt libraries
1981 *
1982 * @return array<string> jsLibs and jsFooterLibs strings
1983 */
1984 protected function renderAdditionalJavaScriptLibraries()
1985 {
1986 $jsLibs = '';
1987 $jsFooterLibs = '';
1988 if (!empty($this->jsLibs)) {
1989 foreach ($this->jsLibs as $properties) {
1990 $properties['file'] = $this->getStreamlinedFileName($properties['file']);
1991 $async = $properties['async'] ? ' async="async"' : '';
1992 $defer = $properties['defer'] ? ' defer="defer"' : '';
1993 $integrity = $properties['integrity'] ? ' integrity="' . htmlspecialchars($properties['integrity']) . '"' : '';
1994 $crossorigin = $properties['crossorigin'] ? ' crossorigin="' . htmlspecialchars($properties['crossorigin']) . '"' : '';
1995 $tag = '<script src="' . htmlspecialchars($properties['file']) . '" type="' . htmlspecialchars($properties['type']) . '"' . $async . $defer . $integrity . $crossorigin . '></script>';
1996 if ($properties['allWrap']) {
1997 $wrapArr = explode($properties['splitChar'] ?: '|', $properties['allWrap'], 2);
1998 $tag = $wrapArr[0] . $tag . $wrapArr[1];
1999 }
2000 $tag .= LF;
2001 if ($properties['forceOnTop']) {
2002 if ($properties['section'] === self::PART_HEADER) {
2003 $jsLibs = $tag . $jsLibs;
2004 } else {
2005 $jsFooterLibs = $tag . $jsFooterLibs;
2006 }
2007 } else {
2008 if ($properties['section'] === self::PART_HEADER) {
2009 $jsLibs .= $tag;
2010 } else {
2011 $jsFooterLibs .= $tag;
2012 }
2013 }
2014 }
2015 }
2016 if ($this->moveJsFromHeaderToFooter) {
2017 $jsFooterLibs = $jsLibs . LF . $jsFooterLibs;
2018 $jsLibs = '';
2019 }
2020 return [$jsLibs, $jsFooterLibs];
2021 }
2022
2023 /**
2024 * Render JavaScript files
2025 *
2026 * @return array<string> jsFiles and jsFooterFiles strings
2027 */
2028 protected function renderJavaScriptFiles()
2029 {
2030 $jsFiles = '';
2031 $jsFooterFiles = '';
2032 if (!empty($this->jsFiles)) {
2033 foreach ($this->jsFiles as $file => $properties) {
2034 $file = $this->getStreamlinedFileName($file);
2035 $async = $properties['async'] ? ' async="async"' : '';
2036 $defer = $properties['defer'] ? ' defer="defer"' : '';
2037 $integrity = $properties['integrity'] ? ' integrity="' . htmlspecialchars($properties['integrity']) . '"' : '';
2038 $crossorigin = $properties['crossorigin'] ? ' crossorigin="' . htmlspecialchars($properties['crossorigin']) . '"' : '';
2039 $tag = '<script src="' . htmlspecialchars($file) . '" type="' . htmlspecialchars($properties['type']) . '"' . $async . $defer . $integrity . $crossorigin . '></script>';
2040 if ($properties['allWrap']) {
2041 $wrapArr = explode($properties['splitChar'] ?: '|', $properties['allWrap'], 2);
2042 $tag = $wrapArr[0] . $tag . $wrapArr[1];
2043 }
2044 $tag .= LF;
2045 if ($properties['forceOnTop']) {
2046 if ($properties['section'] === self::PART_HEADER) {
2047 $jsFiles = $tag . $jsFiles;
2048 } else {
2049 $jsFooterFiles = $tag . $jsFooterFiles;
2050 }
2051 } else {
2052 if ($properties['section'] === self::PART_HEADER) {
2053 $jsFiles .= $tag;
2054 } else {
2055 $jsFooterFiles .= $tag;
2056 }
2057 }
2058 }
2059 }
2060 if ($this->moveJsFromHeaderToFooter) {
2061 $jsFooterFiles = $jsFiles . $jsFooterFiles;
2062 $jsFiles = '';
2063 }
2064 return [$jsFiles, $jsFooterFiles];
2065 }
2066
2067 /**
2068 * Render inline JavaScript
2069 *
2070 * @return array<string> jsInline and jsFooterInline string
2071 */
2072 protected function renderInlineJavaScript()
2073 {
2074 $jsInline = '';
2075 $jsFooterInline = '';
2076 if (!empty($this->jsInline)) {
2077 foreach ($this->jsInline as $name => $properties) {
2078 $jsCode = '/*' . htmlspecialchars($name) . '*/' . LF . $properties['code'] . LF;
2079 if ($properties['forceOnTop']) {
2080 if ($properties['section'] === self::PART_HEADER) {
2081 $jsInline = $jsCode . $jsInline;
2082 } else {
2083 $jsFooterInline = $jsCode . $jsFooterInline;
2084 }
2085 } else {
2086 if ($properties['section'] === self::PART_HEADER) {
2087 $jsInline .= $jsCode;
2088 } else {
2089 $jsFooterInline .= $jsCode;
2090 }
2091 }
2092 }
2093 }
2094 if ($jsInline) {
2095 $jsInline = $this->inlineJavascriptWrap[0] . $jsInline . $this->inlineJavascriptWrap[1];
2096 }
2097 if ($jsFooterInline) {
2098 $jsFooterInline = $this->inlineJavascriptWrap[0] . $jsFooterInline . $this->inlineJavascriptWrap[1];
2099 }
2100 if ($this->moveJsFromHeaderToFooter) {
2101 $jsFooterInline = $jsInline . $jsFooterInline;
2102 $jsInline = '';
2103 }
2104 return [$jsInline, $jsFooterInline];
2105 }
2106
2107 /**
2108 * Include language file for inline usage
2109 *
2110 * @param string $fileRef
2111 * @param string $selectionPrefix
2112 * @param string $stripFromSelectionName
2113 * @throws \RuntimeException
2114 */
2115 protected function includeLanguageFileForInline($fileRef, $selectionPrefix = '', $stripFromSelectionName = '')
2116 {
2117 if (!isset($this->lang) || !isset($this->charSet)) {
2118 throw new \RuntimeException('Language and character encoding are not set.', 1284906026);
2119 }
2120 $labelsFromFile = [];
2121 $allLabels = $this->readLLfile($fileRef);
2122 if ($allLabels !== false) {
2123 // Merge language specific translations:
2124 if ($this->lang !== 'default' && isset($allLabels[$this->lang])) {
2125 $labels = array_merge($allLabels['default'], $allLabels[$this->lang]);
2126 } else {
2127 $labels = $allLabels['default'];
2128 }
2129 // Iterate through all locallang labels:
2130 foreach ($labels as $label => $value) {
2131 // If $selectionPrefix is set, only respect labels that start with $selectionPrefix
2132 if ($selectionPrefix === '' || strpos($label, $selectionPrefix) === 0) {
2133 // Remove substring $stripFromSelectionName from label
2134 $label = str_replace($stripFromSelectionName, '', $label);
2135 $labelsFromFile[$label] = $value;
2136 }
2137 }
2138 $this->inlineLanguageLabels = array_merge($this->inlineLanguageLabels, $labelsFromFile);
2139 }
2140 }
2141
2142 /**
2143 * Reads a locallang file.
2144 *
2145 * @param string $fileRef Reference to a relative filename to include.
2146 * @return array Returns the $LOCAL_LANG array found in the file. If no array found, returns empty array.
2147 */
2148 protected function readLLfile($fileRef)
2149 {
2150 /** @var LocalizationFactory $languageFactory */
2151 $languageFactory = GeneralUtility::makeInstance(LocalizationFactory::class);
2152
2153 if ($this->lang !== 'default') {
2154 $languages = array_reverse($this->languageDependencies);
2155 // At least we need to have English
2156 if (empty($languages)) {
2157 $languages[] = 'default';
2158 }
2159 } else {
2160 $languages = ['default'];
2161 }
2162
2163 $localLanguage = [];
2164 foreach ($languages as $language) {
2165 $tempLL = $languageFactory->getParsedData($fileRef, $language);
2166
2167 $localLanguage['default'] = $tempLL['default'];
2168 if (!isset($localLanguage[$this->lang])) {
2169 $localLanguage[$this->lang] = $localLanguage['default'];
2170 }
2171 if ($this->lang !== 'default' && isset($tempLL[$language])) {
2172 // Merge current language labels onto labels from previous language
2173 // This way we have a labels with fall back applied
2174 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($localLanguage[$this->lang], $tempLL[$language], true, false);
2175 }
2176 }
2177
2178 return $localLanguage;
2179 }
2180
2181 /*****************************************************/
2182 /* */
2183 /* Tools */
2184 /* */
2185 /*****************************************************/
2186 /**
2187 * Concatenate files into one file
2188 * registered handler
2189 */
2190 protected function doConcatenate()
2191 {
2192 $this->doConcatenateCss();
2193 $this->doConcatenateJavaScript();
2194 }
2195
2196 /**
2197 * Concatenate JavaScript files according to the configuration.
2198 */
2199 protected function doConcatenateJavaScript()
2200 {
2201 if ($this->concatenateJavascript) {
2202 if (!empty($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['jsConcatenateHandler'])) {
2203 // use external concatenation routine
2204 $params = [
2205 'jsLibs' => &$this->jsLibs,
2206 'jsFiles' => &$this->jsFiles,
2207 'jsFooterFiles' => &$this->jsFooterFiles,
2208 'headerData' => &$this->headerData,
2209 'footerData' => &$this->footerData
2210 ];
2211 GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['jsConcatenateHandler'], $params, $this);
2212 } else {
2213 $this->jsLibs = $this->getCompressor()->concatenateJsFiles($this->jsLibs);
2214 $this->jsFiles = $this->getCompressor()->concatenateJsFiles($this->jsFiles);
2215 $this->jsFooterFiles = $this->getCompressor()->concatenateJsFiles($this->jsFooterFiles);
2216 }
2217 }
2218 }
2219
2220 /**
2221 * Concatenate CSS files according to configuration.
2222 */
2223 protected function doConcatenateCss()
2224 {
2225 if ($this->concatenateCss) {
2226 if (!empty($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssConcatenateHandler'])) {
2227 // use external concatenation routine
2228 $params = [
2229 'cssFiles' => &$this->cssFiles,
2230 'cssLibs' => &$this->cssLibs,
2231 'headerData' => &$this->headerData,
2232 'footerData' => &$this->footerData
2233 ];
2234 GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssConcatenateHandler'], $params, $this);
2235 } else {
2236 $cssOptions = [];
2237 if (TYPO3_MODE === 'BE') {
2238 $cssOptions = ['baseDirectories' => $GLOBALS['TBE_TEMPLATE']->getSkinStylesheetDirectories()];
2239 }
2240 $this->cssLibs = $this->getCompressor()->concatenateCssFiles($this->cssLibs, $cssOptions);
2241 $this->cssFiles = $this->getCompressor()->concatenateCssFiles($this->cssFiles, $cssOptions);
2242 }
2243 }
2244 }
2245
2246 /**
2247 * Compresses inline code
2248 */
2249 protected function doCompress()
2250 {
2251 $this->doCompressJavaScript();
2252 $this->doCompressCss();
2253 }
2254
2255 /**
2256 * Compresses CSS according to configuration.
2257 */
2258 protected function doCompressCss()
2259 {
2260 if ($this->compressCss) {
2261 if (!empty($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssCompressHandler'])) {
2262 // Use external compression routine
2263 $params = [
2264 'cssInline' => &$this->cssInline,
2265 'cssFiles' => &$this->cssFiles,
2266 'cssLibs' => &$this->cssLibs,
2267 'headerData' => &$this->headerData,
2268 'footerData' => &$this->footerData
2269 ];
2270 GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssCompressHandler'], $params, $this);
2271 } else {
2272 $this->cssLibs = $this->getCompressor()->compressCssFiles($this->cssLibs);
2273 $this->cssFiles = $this->getCompressor()->compressCssFiles($this->cssFiles);
2274 }
2275 }
2276 }
2277
2278 /**
2279 * Compresses JavaScript according to configuration.
2280 */
2281 protected function doCompressJavaScript()
2282 {
2283 if ($this->compressJavascript) {
2284 if (!empty($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['jsCompressHandler'])) {
2285 // Use external compression routine
2286 $params = [
2287 'jsInline' => &$this->jsInline,
2288 'jsFooterInline' => &$this->jsFooterInline,
2289 'jsLibs' => &$this->jsLibs,
2290 'jsFiles' => &$this->jsFiles,
2291 'jsFooterFiles' => &$this->jsFooterFiles,
2292 'headerData' => &$this->headerData,
2293 'footerData' => &$this->footerData
2294 ];
2295 GeneralUtility::callUserFunction($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['jsCompressHandler'], $params, $this);
2296 } else {
2297 // Traverse the arrays, compress files
2298 if (!empty($this->jsInline)) {
2299 foreach ($this->jsInline as $name => $properties) {
2300 if ($properties['compress']) {
2301 $error = '';
2302 $this->jsInline[$name]['code'] = GeneralUtility::minifyJavaScript($properties['code'], $error);
2303 if ($error) {
2304 $this->compressError .= 'Error with minify JS Inline Block "' . $name . '": ' . $error . LF;
2305 }
2306 }
2307 }
2308 }
2309 $this->jsLibs = $this->getCompressor()->compressJsFiles($this->jsLibs);
2310 $this->jsFiles = $this->getCompressor()->compressJsFiles($this->jsFiles);
2311 $this->jsFooterFiles = $this->getCompressor()->compressJsFiles($this->jsFooterFiles);
2312 }
2313 }
2314 }
2315
2316 /**
2317 * Returns instance of \TYPO3\CMS\Core\Resource\ResourceCompressor
2318 *
2319 * @return \TYPO3\CMS\Core\Resource\ResourceCompressor
2320 */
2321 protected function getCompressor()
2322 {
2323 if ($this->compressor === null) {
2324 $this->compressor = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Resource\ResourceCompressor::class);
2325 }
2326 return $this->compressor;
2327 }
2328
2329 /**
2330 * Processes a Javascript file dependent on the current context
2331 *
2332 * Adds the version number for Frontend, compresses the file for Backend
2333 *
2334 * @param string $filename Filename
2335 * @return string New filename
2336 */
2337 protected function processJsFile($filename)
2338 {
2339 $filename = $this->getStreamlinedFileName($filename, false);
2340 if ($this->compressJavascript) {
2341 $filename = $this->getCompressor()->compressJsFile($filename);
2342 } elseif (TYPO3_MODE === 'FE') {
2343 $filename = GeneralUtility::createVersionNumberedFilename($filename);
2344 }
2345 return $this->getAbsoluteWebPath($filename);
2346 }
2347
2348 /**
2349 * This function acts as a wrapper to allow relative and paths starting with EXT: to be dealt with
2350 * in this very case to always return the absolute web path to be included directly before output.
2351 *
2352 * This is mainly added so the EXT: syntax can be resolved for PageRenderer in one central place,
2353 * and hopefully removed in the future by one standard API call.
2354 *
2355 * @param string $file the filename to process
2356 * @param bool $prepareForOutput whether the file should be prepared as version numbered file and prefixed as absolute webpath
2357 * @return string
2358 * @internal
2359 */
2360 protected function getStreamlinedFileName($file, $prepareForOutput = true)
2361 {
2362 if (strpos($file, 'EXT:') === 0) {
2363 $file = GeneralUtility::getFileAbsFileName($file);
2364 // as the path is now absolute, make it "relative" to the current script to stay compatible
2365 $file = PathUtility::getRelativePathTo($file);
2366 $file = rtrim($file, '/');
2367 } else {
2368 $file = GeneralUtility::resolveBackPath($file);
2369 }
2370 if ($prepareForOutput) {
2371 $file = GeneralUtility::createVersionNumberedFilename($file);
2372 $file = $this->getAbsoluteWebPath($file);
2373 }
2374 return $file;
2375 }
2376
2377 /**
2378 * Gets absolute web path of filename for backend disposal.
2379 * Resolving the absolute path in the frontend with conflict with
2380 * applying config.absRefPrefix in frontend rendering process.
2381 *
2382 * @param string $file
2383 * @return string
2384 * @see TypoScriptFrontendController::setAbsRefPrefix()
2385 */
2386 protected function getAbsoluteWebPath(string $file): string
2387 {
2388 if (TYPO3_MODE === 'FE') {
2389 return $file;
2390 }
2391 return PathUtility::getAbsoluteWebPath($file);
2392 }
2393
2394 /**
2395 * Returns global frontend controller
2396 *
2397 * @return TypoScriptFrontendController
2398 */
2399 protected function getTypoScriptFrontendController()
2400 {
2401 return $GLOBALS['TSFE'];
2402 }
2403
2404 /*****************************************************/
2405 /* */
2406 /* Hooks */
2407 /* */
2408 /*****************************************************/
2409 /**
2410 * Execute PreRenderHook for possible manipulation
2411 */
2412 protected function executePreRenderHook()
2413 {
2414 $hooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_pagerenderer.php']['render-preProcess'] ?? false;
2415 if (!$hooks) {
2416 return;
2417 }
2418 $params = [
2419 'jsLibs' => &$this->jsLibs,
2420 'jsFooterLibs' => &$this->jsFooterLibs,
2421 'jsFiles' => &$this->jsFiles,
2422 'jsFooterFiles' => &$this->jsFooterFiles,
2423 'cssLibs' => &$this->cssLibs,
2424 'cssFiles' => &$this->cssFiles,
2425 'headerData' => &$this->headerData,
2426 'footerData' => &$this->footerData,
2427 'jsInline' => &$this->jsInline,
2428 'jsFooterInline' => &$this->jsFooterInline,
2429 'cssInline' => &$this->cssInline
2430 ];
2431 foreach ($hooks as $hook) {
2432 GeneralUtility::callUserFunction($hook, $params, $this);
2433 }
2434 }
2435
2436 /**
2437 * PostTransform for possible manipulation of concatenated and compressed files
2438 */
2439 protected function executeRenderPostTransformHook()
2440 {
2441 $hooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_pagerenderer.php']['render-postTransform'] ?? false;
2442 if (!$hooks) {
2443 return;
2444 }
2445 $params = [
2446 'jsLibs' => &$this->jsLibs,
2447 'jsFooterLibs' => &$this->jsFooterLibs,
2448 'jsFiles' => &$this->jsFiles,
2449 'jsFooterFiles' => &$this->jsFooterFiles,
2450 'cssLibs' => &$this->cssLibs,
2451 'cssFiles' => &$this->cssFiles,
2452 'headerData' => &$this->headerData,
2453 'footerData' => &$this->footerData,
2454 'jsInline' => &$this->jsInline,
2455 'jsFooterInline' => &$this->jsFooterInline,
2456 'cssInline' => &$this->cssInline
2457 ];
2458 foreach ($hooks as $hook) {
2459 GeneralUtility::callUserFunction($hook, $params, $this);
2460 }
2461 }
2462
2463 /**
2464 * Execute postRenderHook for possible manipulation
2465 *
2466 * @param string $jsLibs
2467 * @param string $jsFiles
2468 * @param string $jsFooterFiles
2469 * @param string $cssLibs
2470 * @param string $cssFiles
2471 * @param string $jsInline
2472 * @param string $cssInline
2473 * @param string $jsFooterInline
2474 * @param string $jsFooterLibs
2475 */
2476 protected function executePostRenderHook(&$jsLibs, &$jsFiles, &$jsFooterFiles, &$cssLibs, &$cssFiles, &$jsInline, &$cssInline, &$jsFooterInline, &$jsFooterLibs)
2477 {
2478 $hooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_pagerenderer.php']['render-postProcess'] ?? false;
2479 if (!$hooks) {
2480 return;
2481 }
2482 $params = [
2483 'jsLibs' => &$jsLibs,
2484 'jsFiles' => &$jsFiles,
2485 'jsFooterFiles' => &$jsFooterFiles,
2486 'cssLibs' => &$cssLibs,
2487 'cssFiles' => &$cssFiles,
2488 'headerData' => &$this->headerData,
2489 'footerData' => &$this->footerData,
2490 'jsInline' => &$jsInline,
2491 'cssInline' => &$cssInline,
2492 'xmlPrologAndDocType' => &$this->xmlPrologAndDocType,
2493 'htmlTag' => &$this->htmlTag,
2494 'headTag' => &$this->headTag,
2495 'charSet' => &$this->charSet,
2496 'metaCharsetTag' => &$this->metaCharsetTag,
2497 'shortcutTag' => &$this->shortcutTag,
2498 'inlineComments' => &$this->inlineComments,
2499 'baseUrl' => &$this->baseUrl,
2500 'baseUrlTag' => &$this->baseUrlTag,
2501 'favIcon' => &$this->favIcon,
2502 'iconMimeType' => &$this->iconMimeType,
2503 'titleTag' => &$this->titleTag,
2504 'title' => &$this->title,
2505 'metaTags' => &$this->metaTags,
2506 'jsFooterInline' => &$jsFooterInline,
2507 'jsFooterLibs' => &$jsFooterLibs,
2508 'bodyContent' => &$this->bodyContent
2509 ];
2510 foreach ($hooks as $hook) {
2511 GeneralUtility::callUserFunction($hook, $params, $this);
2512 }
2513 }
2514
2515 /**
2516 * Creates an CSS inline tag
2517 *
2518 * @param string $file the filename to process
2519 * @param array $properties
2520 * @return string
2521 */
2522 protected function createInlineCssTagFromFile(string $file, array $properties): string
2523 {
2524 $cssInline = file_get_contents($file);
2525
2526 return '<style type="text/css"'
2527 . ' media="' . htmlspecialchars($properties['media']) . '"'
2528 . ($properties['title'] ? ' title="' . htmlspecialchars($properties['title']) . '"' : '')
2529 . '>' . LF
2530 . '/*<![CDATA[*/' . LF . '<!-- ' . LF
2531 . $cssInline
2532 . '-->' . LF . '/*]]>*/' . LF . '</style>' . LF;
2533 }
2534 }