Passed
Push — v5 ( 848feb...023ea9 )
by Andrew
29:11 queued 22:46
created

ManifestHelper::extractIntegrity()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Vite plugin for Craft CMS
4
 *
5
 * Allows the use of the Vite.js next generation frontend tooling with Craft CMS
6
 *
7
 * @link      https://nystudio107.com
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
8
 * @copyright Copyright (c) 2021 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
9
 */
0 ignored issues
show
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
10
11
namespace nystudio107\pluginvite\helpers;
12
13
use Craft;
14
use craft\helpers\Json as JsonHelper;
15
16
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
17
 * @author    nystudio107
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @package tag
Loading history...
Coding Style introduced by
Content of the @author tag must be in the form "Display Name <[email protected]>"
Loading history...
Coding Style introduced by
Tag value for @author tag indented incorrectly; expected 2 spaces but found 4
Loading history...
18
 * @package   Vite
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
19
 * @since     1.0.5
0 ignored issues
show
Coding Style introduced by
The tag in position 3 should be the @author tag
Loading history...
Coding Style introduced by
Tag value for @since tag indented incorrectly; expected 3 spaces but found 5
Loading history...
20
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
21
class ManifestHelper
22
{
23
    // Constants
24
    // =========================================================================
25
26
    public const LEGACY_EXTENSION = '-legacy.';
27
28
    // Protected Static Properties
29
    // =========================================================================
30
31
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
32
     * @var array|null
33
     */
34
    protected static $manifest;
35
36
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
37
     * @var array|null
38
     */
39
    protected static $assetFiles;
40
41
    // Public Static Methods
42
    // =========================================================================
43
44
    /**
45
     * Fetch and memoize the manifest file
46
     *
47
     * @param string $manifestPath
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
48
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
49
    public static function fetchManifest(string $manifestPath)
50
    {
51
        // Grab the manifest
52
        $pathOrUrl = (string)Craft::parseEnv($manifestPath);
0 ignored issues
show
Deprecated Code introduced by
The function Craft::parseEnv() has been deprecated: in 3.7.29. [[App::parseEnv()]] should be used instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

52
        $pathOrUrl = (string)/** @scrutinizer ignore-deprecated */ Craft::parseEnv($manifestPath);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
53
        $manifest = FileHelper::fetch($pathOrUrl, [JsonHelper::class, 'decodeIfJson']);
54
        // If no manifest file is found, log it
55
        if ($manifest === null) {
56
            Craft::error('Manifest not found at ' . $manifestPath, __METHOD__);
57
        }
58
        // Ensure we're dealing with an array
59
        self::$manifest = (array)$manifest;
60
    }
61
62
    /**
63
     * Return an array of tags from the manifest, for both modern and legacy builds
64
     *
65
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
66
     * @param bool $asyncCss
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
67
     * @param array $scriptTagAttrs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
68
     * @param array $cssTagAttrs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
69
     *
70
     * @return array
71
     */
72
    public static function manifestTags(string $path, bool $asyncCss = true, array $scriptTagAttrs = [], array $cssTagAttrs = []): array
73
    {
74
        // Get the modern tags for this $path
75
        return self::extractManifestTags($path, $asyncCss, $scriptTagAttrs, $cssTagAttrs);
76
    }
77
78
    /**
79
     * Return an array of data describing the  script, module link, and CSS link tags for the
80
     * script from the manifest.json file
81
     *
82
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
83
     * @param bool $asyncCss
0 ignored issues
show
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
84
     * @param array $scriptTagAttrs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
85
     * @param array $cssTagAttrs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
86
     * @param bool $legacy
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
87
     *
88
     * @return array
89
     */
90
    public static function extractManifestTags(string $path, bool $asyncCss = true, array $scriptTagAttrs = [], array $cssTagAttrs = [], bool $legacy = false): array
91
    {
92
        if (self::$manifest === null) {
93
            return [];
94
        }
95
        $tags = [];
96
        // Set the async CSS args
97
        $asyncCssOptions = [];
98
        if ($asyncCss) {
99
            $asyncCssOptions = [
100
                'media' => 'print',
101
                'onload' => "this.media='all'",
102
            ];
103
        }
104
        // Set the script args
105
        $scriptOptions = [
106
            'type' => 'module',
107
            'crossorigin' => true,
108
        ];
109
        if ($legacy) {
110
            $scriptOptions = [
111
                'nomodule' => true,
112
            ];
113
        }
114
        // Iterate through the manifest
115
        foreach (self::$manifest as $manifestKey => $entry) {
116
            // If it's not an entry, skip it
117
            if (!isset($entry['isEntry']) || !$entry['isEntry']) {
118
                continue;
119
            }
120
            // If there's no file, skip it
121
            if (!isset($entry['file'])) {
122
                continue;
123
            }
124
            // If the $path isn't in the $manifestKey, and vice versus, skip it
125
            if (strpos($manifestKey, $path) === false && strpos($path, $manifestKey) === false) {
126
                continue;
127
            }
128
            // Handle optional `integrity` tags
129
            $integrityAttributes = [];
130
            if (isset($entry['integrity'])) {
131
                $integrityAttributes = [
132
                    'integrity' => $entry['integrity'],
133
                ];
134
            }
135
            // Add an onload event so listeners can know when the event has fired
136
            $tagOptions = array_merge(
137
                $scriptOptions,
138
                [
139
                    'onload' => "e=new CustomEvent('vite-script-loaded', {detail:{path: '$manifestKey'}});document.dispatchEvent(e);",
140
                ],
141
                $integrityAttributes,
142
                $scriptTagAttrs
143
            );
144
            // Include the entry script
145
            $tags[$manifestKey] = [
146
                'type' => 'file',
147
                'url' => $entry['file'],
148
                'options' => $tagOptions,
149
            ];
150
            // Include any imports
151
            $importFiles = [];
152
            // Only include import tags for the non-legacy scripts
153
            if (!$legacy) {
154
                self::extractImportFiles(self::$manifest, $manifestKey, $importFiles);
155
                foreach ($importFiles as $importKey => $importFile) {
156
                    $tags[$importFile] = [
157
                        'crossorigin' => $tagOptions['crossorigin'] ?? true,
158
                        'type' => 'import',
159
                        'url' => $importFile,
160
                        'integrity' => self::$manifest[$importKey]['integrity'] ?? '',
161
                    ];
162
                }
163
            }
164
            // Include any CSS tags
165
            $cssFiles = [];
166
            self::extractCssFiles(self::$manifest, $manifestKey, $cssFiles);
167
            foreach ($cssFiles as $cssFile) {
168
                $tags[$cssFile] = [
169
                    'type' => 'css',
170
                    'url' => $cssFile,
171
                    'options' => array_merge([
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
172
                        'rel' => 'stylesheet',
173
                    ], $asyncCssOptions, $cssTagAttrs),
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
174
                ];
175
            }
176
        }
177
178
        return $tags;
179
    }
180
181
    /**
182
     * Return an array of tags from the manifest, for both modern and legacy builds
183
     *
184
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
185
     * @param bool $asyncCss
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 3 spaces after parameter type; 1 found
Loading history...
186
     * @param array $scriptTagAttrs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
187
     * @param array $cssTagAttrs
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
188
     *
189
     * @return array
190
     */
191
    public static function legacyManifestTags(string $path, bool $asyncCss = true, array $scriptTagAttrs = [], array $cssTagAttrs = []): array
192
    {
193
        // Get the legacy tags for this $path
194
        $parts = pathinfo($path);
195
        $legacyPath = $parts['dirname']
196
            . '/'
197
            . $parts['filename']
198
            . self::LEGACY_EXTENSION
199
            . $parts['extension'];
200
201
        return self::extractManifestTags($legacyPath, $asyncCss, $scriptTagAttrs, $cssTagAttrs, true);
202
    }
203
204
    /**
205
     * Extract an entry file URL from all of the entries in the manifest
206
     *
207
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
208
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
209
     */
210
    public static function extractEntry(string $path): string
211
    {
212
        foreach (self::$manifest as $entryKey => $entry) {
213
            if (strpos($entryKey, $path) !== false) {
214
                return $entry['file'] ?? '';
215
            }
216
            // Check CSS
217
            $styles = $entry['css'] ?? [];
218
            foreach ($styles as $style) {
219
                $styleKey = self::filenameWithoutHash($style);
220
                if (strpos($styleKey, $path) !== false) {
221
                    return $style;
222
                }
223
            }
224
            // Check assets
225
            $assets = $entry['assets'] ?? [];
226
            foreach ($assets as $asset) {
227
                $assetKey = self::filenameWithoutHash($asset);
228
                if (strpos($assetKey, $path) !== false) {
229
                    return $asset;
230
                }
231
            }
232
        }
233
234
        return '';
235
    }
236
237
    /**
238
     * Extract an integrity hash for the given $path from the entries in the manifest
239
     *
240
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
241
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
242
     */
243
    public static function extractIntegrity(string $path): string
244
    {
245
        foreach (self::$manifest as $entryKey => $entry) {
246
            if (strpos($entryKey, $path) !== false) {
247
                return $entry['integrity'] ?? '';
248
            }
249
        }
250
251
        return '';
252
    }
253
254
    /**
255
     * Extract any asset files from all of the entries in the manifest
256
     *
257
     * @return array
258
     */
259
    public static function extractAssetFiles(): array
260
    {
261
        // Used the memoized version if available
262
        if (self::$assetFiles !== null) {
263
            return self::$assetFiles;
264
        }
265
        $assetFiles = [];
266
        foreach (self::$manifest as $entry) {
267
            $assets = $entry['assets'] ?? [];
268
            foreach ($assets as $asset) {
269
                $assetKey = self::filenameWithoutHash($asset);
270
                $assetFiles[$assetKey] = $asset;
271
            }
272
        }
273
        self::$assetFiles = $assetFiles;
274
275
        return $assetFiles;
276
    }
277
278
    // Protected Static Methods
279
    // =========================================================================
280
281
    /**
282
     * Extract any import files from entries recursively
283
     *
284
     * @param array $manifest
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
285
     * @param string $manifestKey
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
286
     * @param array $importFiles
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
287
     *
288
     * @return array
289
     */
290
    protected static function extractImportFiles(array $manifest, string $manifestKey, array &$importFiles): array
291
    {
292
        $entry = $manifest[$manifestKey] ?? null;
293
        if (!$entry) {
294
            return [];
295
        }
296
297
        $imports = $entry['imports'] ?? [];
298
        foreach ($imports as $import) {
299
            $importFiles[$import] = $manifest[$import]['file'];
300
            self::extractImportFiles($manifest, $import, $importFiles);
301
        }
302
303
        return $importFiles;
304
    }
305
306
    /**
307
     * Extract any CSS files from entries recursively
308
     *
309
     * @param array $manifest
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
310
     * @param string $manifestKey
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
311
     * @param array $cssFiles
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
Coding Style introduced by
Missing parameter comment
Loading history...
312
     *
313
     * @return array
314
     */
315
    protected static function extractCssFiles(array $manifest, string $manifestKey, array &$cssFiles): array
316
    {
317
        $entry = $manifest[$manifestKey] ?? null;
318
        if (!$entry) {
319
            return [];
320
        }
321
        $cssFiles = array_merge($cssFiles, $entry['css'] ?? []);
322
        $imports = $entry['imports'] ?? [];
323
        foreach ($imports as $import) {
324
            self::extractCssFiles($manifest, $import, $cssFiles);
325
        }
326
327
        return $cssFiles;
328
    }
329
330
    /**
331
     * Return a file name from the passed in $path, with any version hash removed from it
332
     *
333
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
334
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag @return cannot be grouped with parameter tags in a doc comment
Loading history...
335
     */
336
    protected static function filenameWithoutHash(string $path): string
337
    {
338
        $pathInfo = pathinfo($path);
339
        $filename = $pathInfo['filename'];
340
        $extension = $pathInfo['extension'];
341
        $hashPos = strrpos($filename, '.') ?: strlen($filename);
342
        $hash = substr($filename, $hashPos);
343
        // Vite 5 now uses a `-` to separate the version hash, so account for that as well
344
        if (empty($hash) && str_contains($filename, '-')) {
345
            $hash = substr($filename, strrpos($filename, '-'));
346
        }
347
        $filename = str_replace($hash, '', $filename);
348
349
        return implode('.', [$filename, $extension]);
350
    }
351
}
352