PageRenderer::setBaseUrl()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Core\Page;
17
18
use Psr\Http\Message\ServerRequestInterface;
19
use TYPO3\CMS\Backend\Routing\Router;
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Core\Cache\CacheManager;
22
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
23
use TYPO3\CMS\Core\Core\Environment;
24
use TYPO3\CMS\Core\Http\ApplicationType;
25
use TYPO3\CMS\Core\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