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

AssetCollector::updateState()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
namespace TYPO3\CMS\Core\Page;
19
20
use TYPO3\CMS\Core\SingletonInterface;
21
use TYPO3\CMS\Core\Utility\ArrayUtility;
22
23
/**
24
 * The Asset Collector is responsible for keeping track of
25
 * - everything within <script> tags: javascript files and inline javascript code
26
 * - inline CSS and CSS files
27
 *
28
 * The goal of the asset collector is to:
29
 * - utilize a single "runtime-based" store for adding assets of certain kinds that are added to the output
30
 * - allow to deal with assets from non-cacheable plugins and cacheable content in the Frontend
31
 * - reduce the "power" and flexibility (I'd say it's a burden) of the "god class" PageRenderer.
32
 * - reduce the burden of storing everything in PageRenderer
33
 *
34
 * As a side-effect this allows to:
35
 * - Add a single CSS snippet or CSS file per content block, but assure that the CSS is only added once to the output.
36
 *
37
 * Note on the implementation:
38
 * - We use a Singleton to make use of the AssetCollector throughout Frontend process (similar to PageRenderer).
39
 * - Although this is not optimal, I don't see any other way to do so in the current code.
40
 *
41
 * https://developer.wordpress.org/reference/functions/wp_enqueue_style/
42
 */
43
class AssetCollector implements SingletonInterface
44
{
45
    /**
46
     * @var array
47
     */
48
    protected $javaScripts = [];
49
50
    /**
51
     * @var array
52
     */
53
    protected $inlineJavaScripts = [];
54
55
    /**
56
     * @var array
57
     */
58
    protected $styleSheets = [];
59
60
    /**
61
     * @var array
62
     */
63
    protected $inlineStyleSheets = [];
64
65
    /**
66
     * @var array
67
     */
68
    protected $media = [];
69
70
    /**
71
     * @param string $identifier
72
     * @param string $source URI to JavaScript file (allows EXT: syntax)
73
     * @param array $attributes additional HTML <script> tag attributes
74
     * @param array $options ['priority' => true] means rendering before other tags
75
     * @return AssetCollector
76
     */
77
    public function addJavaScript(string $identifier, string $source, array $attributes = [], array $options = []): self
78
    {
79
        $existingAttributes = $this->javaScripts[$identifier]['attributes'] ?? [];
80
        ArrayUtility::mergeRecursiveWithOverrule($existingAttributes, $attributes);
81
        $existingOptions = $this->javaScripts[$identifier]['options'] ?? [];
82
        ArrayUtility::mergeRecursiveWithOverrule($existingOptions, $options);
83
        $this->javaScripts[$identifier] = [
84
            'source' => $source,
85
            'attributes' => $existingAttributes,
86
            'options' => $existingOptions
87
        ];
88
        return $this;
89
    }
90
91
    /**
92
     * @param string $identifier
93
     * @param string $source JavaScript code
94
     * @param array $attributes additional HTML <script> tag attributes
95
     * @param array $options ['priority' => true] means rendering before other tags
96
     * @return AssetCollector
97
     */
98
    public function addInlineJavaScript(string $identifier, string $source, array $attributes = [], array $options = []): self
99
    {
100
        $existingAttributes = $this->inlineJavaScripts[$identifier]['attributes'] ?? [];
101
        ArrayUtility::mergeRecursiveWithOverrule($existingAttributes, $attributes);
102
        $existingOptions = $this->inlineJavaScripts[$identifier]['options'] ?? [];
103
        ArrayUtility::mergeRecursiveWithOverrule($existingOptions, $options);
104
        $this->inlineJavaScripts[$identifier] = [
105
            'source' => $source,
106
            'attributes' => $existingAttributes,
107
            'options' => $existingOptions
108
        ];
109
        return $this;
110
    }
111
112
    /**
113
     * @param string $identifier
114
     * @param string $source URI to stylesheet file (allows EXT: syntax)
115
     * @param array $attributes additional HTML <link> tag attributes
116
     * @param array $options ['priority' => true] means rendering before other tags
117
     * @return AssetCollector
118
     */
119
    public function addStyleSheet(string $identifier, string $source, array $attributes = [], array $options = []): self
120
    {
121
        $existingAttributes = $this->styleSheets[$identifier]['attributes'] ?? [];
122
        ArrayUtility::mergeRecursiveWithOverrule($existingAttributes, $attributes);
123
        $existingOptions = $this->styleSheets[$identifier]['options'] ?? [];
124
        ArrayUtility::mergeRecursiveWithOverrule($existingOptions, $options);
125
        $this->styleSheets[$identifier] = [
126
            'source' => $source,
127
            'attributes' => $existingAttributes,
128
            'options' => $existingOptions
129
        ];
130
        return $this;
131
    }
132
133
    /**
134
     * @param string $identifier
135
     * @param string $source stylesheet code
136
     * @param array $attributes additional HTML <link> tag attributes
137
     * @param array $options ['priority' => true] means rendering before other tags
138
     * @return AssetCollector
139
     */
140
    public function addInlineStyleSheet(string $identifier, string $source, array $attributes = [], array $options = []): self
141
    {
142
        $existingAttributes = $this->inlineStyleSheets[$identifier]['attributes'] ?? [];
143
        ArrayUtility::mergeRecursiveWithOverrule($existingAttributes, $attributes);
144
        $existingOptions = $this->inlineStyleSheets[$identifier]['options'] ?? [];
145
        ArrayUtility::mergeRecursiveWithOverrule($existingOptions, $options);
146
        $this->inlineStyleSheets[$identifier] = [
147
            'source' => $source,
148
            'attributes' => $existingAttributes,
149
            'options' => $existingOptions
150
        ];
151
        return $this;
152
    }
153
154
    /**
155
     * @param string $fileName
156
     * @param array $additionalInformation One dimensional hash map (array with non numerical keys) with scalar values
157
     * @return AssetCollector
158
     */
159
    public function addMedia(string $fileName, array $additionalInformation): self
160
    {
161
        $existingAdditionalInformation = $this->media[$fileName] ?? [];
162
        ArrayUtility::mergeRecursiveWithOverrule($existingAdditionalInformation, $this->ensureAllValuesAreSerializable($additionalInformation));
163
        $this->media[$fileName] = $existingAdditionalInformation;
164
        return $this;
165
    }
166
167
    private function ensureAllValuesAreSerializable(array $additionalInformation): array
168
    {
169
        // Currently just filtering all non scalar values
170
        return array_filter($additionalInformation, 'is_scalar');
171
    }
172
173
    public function removeJavaScript(string $identifier): self
174
    {
175
        unset($this->javaScripts[$identifier]);
176
        return $this;
177
    }
178
179
    public function removeInlineJavaScript(string $identifier): self
180
    {
181
        unset($this->inlineJavaScripts[$identifier]);
182
        return $this;
183
    }
184
185
    public function removeStyleSheet(string $identifier): self
186
    {
187
        unset($this->styleSheets[$identifier]);
188
        return $this;
189
    }
190
191
    public function removeInlineStyleSheet(string $identifier): self
192
    {
193
        unset($this->inlineStyleSheets[$identifier]);
194
        return $this;
195
    }
196
197
    public function removeMedia(string $identifier): self
198
    {
199
        unset($this->media[$identifier]);
200
        return $this;
201
    }
202
203
    public function getMedia(): array
204
    {
205
        return $this->media;
206
    }
207
208
    public function getJavaScripts(?bool $priority = null): array
209
    {
210
        return $this->filterAssetsPriority($this->javaScripts, $priority);
211
    }
212
213
    public function getInlineJavaScripts(?bool $priority = null): array
214
    {
215
        return $this->filterAssetsPriority($this->inlineJavaScripts, $priority);
216
    }
217
218
    public function getStyleSheets(?bool $priority = null): array
219
    {
220
        return $this->filterAssetsPriority($this->styleSheets, $priority);
221
    }
222
223
    public function getInlineStyleSheets(?bool $priority = null): array
224
    {
225
        return $this->filterAssetsPriority($this->inlineStyleSheets, $priority);
226
    }
227
228
    /**
229
     * @param array $assets Takes a reference to prevent a deep copy. The variable is not changed (const).
230
     * @param bool|null $priority null: no filter; else filters for the given priority
231
     * @return array
232
     */
233
    protected function filterAssetsPriority(array &$assets, ?bool $priority): array
234
    {
235
        if ($priority === null) {
236
            return $assets;
237
        }
238
        $currentPriorityAssets = [];
239
        foreach ($assets as $identifier => $asset) {
240
            if ($priority === ($asset['options']['priority'] ?? false)) {
241
                $currentPriorityAssets[$identifier] = $asset;
242
            }
243
        }
244
        return $currentPriorityAssets;
245
    }
246
247
    /**
248
     * @param array $newState
249
     * @internal
250
     */
251
    public function updateState(array $newState): void
252
    {
253
        foreach ($newState as $var => $value) {
254
            $this->{$var} = $value;
255
        }
256
    }
257
258
    /**
259
     * @return array
260
     * @internal
261
     */
262
    public function getState(): array
263
    {
264
        $state = [];
265
        foreach (get_object_vars($this) as $var => $value) {
266
            $state[$var] = $value;
267
        }
268
        return $state;
269
    }
270
}
271