Passed
Push — master ( c8254c...d4fa3b )
by
unknown
14:19
created

PageRenderer::addJsFooterFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
c 0
b 0
f 0
nc 2
nop 12
dl 0
loc 17
rs 9.7666

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