PageRenderer::executePostRenderHook()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 36
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 32
dl 0
loc 36
rs 9.408
c 0
b 0
f 0
cc 3
nc 3
nop 9

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