PageRenderer::getPreparedMarkerArray()   B
last analyzed

Complexity

Conditions 8
Paths 128

Size

Total Lines 27
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 24
nc 128
nop 10
dl 0
loc 27
rs 8.2111
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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