Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

PageRenderer::enableConcatenateFiles()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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