Passed
Push — master ( 96d328...9dd5e1 )
by
unknown
18:01
created

PageRenderer::updateState()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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