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

PageRenderer::addJsFile()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
nc 4
nop 11
dl 0
loc 19
rs 9.4285
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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