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

PageRenderer::getTemplateForPart()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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