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