Manifest   F
last analyzed

Complexity

Total Complexity 96

Size/Duplication

Total Lines 753
Duplicated Lines 0 %

Importance

Changes 40
Bugs 0 Features 0
Metric Value
eloc 320
c 40
b 0
f 0
dl 0
loc 753
rs 2
wmc 96

23 Methods

Rating   Name   Duplication   Size   Complexity  
B getModule() 0 29 10
A getCriticalCssTags() 0 25 3
A getCssRelPreloadPolyfill() 0 5 1
A getJsModuleTags() 0 27 5
A getCssModuleTags() 0 24 3
A getSafariNomoduleFix() 0 14 2
A getCssInlineTags() 0 16 3
A getCspNonceType() 0 7 2
B getFileContents() 0 83 10
A getJsonFile() 0 3 1
A reportError() 0 10 4
A getModuleEntry() 0 27 4
B getFileFromUri() 0 32 9
A invalidateCaches() 0 5 1
A jsonFileDecode() 0 9 2
A combinePaths() 0 24 3
A includeNonce() 0 18 5
A getNonce() 0 12 3
A getFile() 0 3 1
B getFileFromManifest() 0 46 11
A getModuleHash() 0 18 3
A getHttpResponseCode() 0 8 2
B getManifestFile() 0 40 8

How to fix   Complexity   

Complex Class

Complex classes like Manifest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manifest, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Twigpack plugin for Craft CMS 3.x
4
 *
5
 * Twigpack is the conduit between Twig and webpack, with manifest.json &
6
 * webpack-dev-server HMR support
7
 *
8
 * @link      https://nystudio107.com/
0 ignored issues
show
Coding Style introduced by
The tag in position 1 should be the @copyright tag
Loading history...
9
 * @copyright Copyright (c) 2018 nystudio107
0 ignored issues
show
Coding Style introduced by
@copyright tag must contain a year and the name of the copyright holder
Loading history...
10
 */
0 ignored issues
show
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 @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
11
12
namespace nystudio107\twigpack\helpers;
13
14
use Craft;
15
use craft\errors\DeprecationException;
16
use craft\helpers\Html;
17
use craft\helpers\Json as JsonHelper;
18
use craft\helpers\UrlHelper;
19
use GuzzleHttp\Client;
20
use GuzzleHttp\RequestOptions;
21
use nystudio107\twigpack\models\Settings;
22
use nystudio107\twigpack\Twigpack;
23
use Throwable;
24
use Twig\Error\LoaderError;
25
use yii\base\Exception;
26
use yii\caching\ChainedDependency;
27
use yii\caching\FileDependency;
28
use yii\caching\TagDependency;
29
use yii\web\NotFoundHttpException;
30
31
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
32
 * @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...
33
 * @package   Twigpack
0 ignored issues
show
Coding Style introduced by
Tag value for @package tag indented incorrectly; expected 1 spaces but found 3
Loading history...
34
 * @since     1.0.0
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...
35
 */
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...
36
class Manifest
37
{
38
    // Constants
39
    // =========================================================================
40
41
    public const CACHE_KEY = 'twigpack';
42
    public const CACHE_TAG = 'twigpack';
43
44
    public const DEVMODE_CACHE_DURATION = 1;
45
46
    public const CSP_HEADERS = [
47
        'Content-Security-Policy',
48
        'X-Content-Security-Policy',
49
        'X-WebKit-CSP',
50
    ];
51
52
    public const SUPPRESS_ERRORS_FOR_MODULES = [
53
        'styles.js',
54
    ];
55
56
    // Protected Static Properties
57
    // =========================================================================
58
59
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
60
     * @var array
61
     */
62
    protected static $files;
63
64
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
65
     * @var bool
66
     */
67
    protected static $isHot = false;
68
69
    // Public Static Methods
70
    // =========================================================================
71
72
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
73
     * @param array $config
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...
74
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
75
     * @param bool $async
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...
76
     * @param array $attributes additional HTML key/value pair attributes to add to the resulting tag
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
77
     *
78
     * @return string
79
     * @throws NotFoundHttpException
80
     */
81
    public static function getCssModuleTags(array $config, string $moduleName, bool $async, array $attributes = []): string
82
    {
83
        $legacyModule = self::getModule($config, $moduleName, 'legacy', true);
84
        if ($legacyModule === null) {
85
            return '';
86
        }
87
        $lines = [];
88
        if ($async) {
89
            $lines[] = Html::cssFile($legacyModule, 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...
90
                'rel' => 'stylesheet',
91
                'media' => 'print',
92
                'onload' => "this.media='all'",
93
            ], $attributes));
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...
94
            $lines[] = Html::cssFile($legacyModule, 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...
95
                'rel' => 'stylesheet',
96
                'noscript' => true,
97
            ], $attributes));
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...
98
        } else {
99
            $lines[] = Html::cssFile($legacyModule, 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...
100
                'rel' => 'stylesheet',
101
            ], $attributes));
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...
102
        }
103
104
        return implode("\r\n", $lines);
105
    }
106
107
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
108
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
109
     * @param array $attributes additional HTML key/value pair attributes to add to the resulting tag
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
110
     *
111
     * @return string
112
     */
113
    public static function getCssInlineTags(string $path, array $attributes = []): string
114
    {
115
        $result = self::getFile($path);
116
        if ($result) {
117
            $config = [];
118
            $nonce = self::getNonce();
119
            if ($nonce !== null) {
120
                $config['nonce'] = $nonce;
121
                self::includeNonce($nonce, 'style-src');
122
            }
123
            $result = Html::style($result, array_merge($config, $attributes));
124
125
            return $result;
126
        }
127
128
        return '';
129
    }
130
131
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
132
     * @param array $config
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
133
     * @param string|null $name
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
134
     * @param array $attributes additional HTML key/value pair attributes to add to the resulting tag
0 ignored issues
show
Coding Style introduced by
Expected 7 spaces after parameter type; 1 found
Loading history...
135
     *
136
     * @return string
137
     * @throws LoaderError
138
     */
139
    public static function getCriticalCssTags(array $config, ?string $name = null, array $attributes = []): string
140
    {
141
        // Resolve the template name
142
        $template = Craft::$app->getView()->resolveTemplate($name ?? Twigpack::$templateName ?? '');
143
        if ($template) {
144
            $name = self::combinePaths(
145
                pathinfo($template, PATHINFO_DIRNAME),
0 ignored issues
show
Bug introduced by
It seems like pathinfo($template, nyst...lpers\PATHINFO_DIRNAME) can also be of type array; however, parameter $paths of nystudio107\twigpack\hel...anifest::combinePaths() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

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

145
                /** @scrutinizer ignore-type */ pathinfo($template, PATHINFO_DIRNAME),
Loading history...
146
                pathinfo($template, PATHINFO_FILENAME)
147
            );
148
            $dirPrefix = 'templates/';
149
            if (defined('CRAFT_TEMPLATES_PATH')) {
150
                $dirPrefix = CRAFT_TEMPLATES_PATH;
151
            }
152
            $name = strstr($name, $dirPrefix);
153
            $name = (string)str_replace($dirPrefix, '', $name);
154
            $path = self::combinePaths(
155
                    $config['localFiles']['basePath'],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 20.
Loading history...
156
                    $config['localFiles']['criticalPrefix'],
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 20.
Loading history...
157
                    $name
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 20.
Loading history...
158
                ) . $config['localFiles']['criticalSuffix'];
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
159
160
            return self::getCssInlineTags($path, $attributes);
161
        }
162
163
        return '';
164
    }
165
166
    /**
167
     * Returns the uglified loadCSS rel=preload Polyfill as per:
168
     * https://github.com/filamentgroup/loadCSS#how-to-use-loadcss-recommended-example
169
     *
170
     * @return string
0 ignored issues
show
Coding Style introduced by
Tag value for @return tag indented incorrectly; expected 5 spaces but found 1
Loading history...
171
     * @throws DeprecationException
0 ignored issues
show
Coding Style introduced by
Tag value for @throws tag indented incorrectly; expected 5 spaces but found 1
Loading history...
172
     * @deprecated in 1.2.0
173
     */
174
    public static function getCssRelPreloadPolyfill(): string
175
    {
176
        Craft::$app->getDeprecator()->log('craft.twigpack.includeCssRelPreloadPolyfill()', 'craft.twigpack.includeCssRelPreloadPolyfill() has been deprecated, this function now does nothing. You can safely remove craft.twigpack.includeCssRelPreloadPolyfill() from your templates.');
177
178
        return '';
179
    }
180
181
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
182
     * @param array $config
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...
183
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
184
     * @param bool $async
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...
185
     * @param array $attributes additional HTML key/value pair attributes to add to the resulting tag
0 ignored issues
show
Coding Style introduced by
Expected 2 spaces after parameter type; 1 found
Loading history...
186
     *
187
     * @return null|string
188
     * @throws NotFoundHttpException
189
     */
190
    public static function getJsModuleTags(array $config, string $moduleName, bool $async, array $attributes = []): ?string
191
    {
192
        $legacyModule = self::getModule($config, $moduleName, 'legacy', true);
193
        if ($legacyModule === null) {
194
            return '';
195
        }
196
        $modernModule = '';
197
        if ($async) {
198
            $modernModule = self::getModule($config, $moduleName, 'modern', true);
199
            if ($modernModule === null) {
200
                return '';
201
            }
202
        }
203
        $lines = [];
204
        if ($async) {
205
            $lines[] = Html::jsFile($modernModule, 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...
206
                'type' => 'module',
207
            ], $attributes));
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...
208
            $lines[] = Html::jsFile($legacyModule, 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...
209
                'nomodule' => true,
210
            ], $attributes));
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...
211
        } else {
212
            $lines[] = Html::jsFile($legacyModule, 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...
213
            ], $attributes));
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...
214
        }
215
216
        return implode("\r\n", $lines);
217
    }
218
219
    /**
220
     * Safari 10.1 supports modules, but does not support the `nomodule`
221
     * attribute - it will load <script nomodule> anyway. This snippet solve
222
     * this problem, but only for script tags that load external code, e.g.:
223
     * <script nomodule src="nomodule.js"></script>
224
     *
225
     * Again: this will **not* # prevent inline script, e.g.:
226
     * <script nomodule>alert('no modules');</script>.
227
     *
228
     * This workaround is possible because Safari supports the non-standard
229
     * 'beforeload' event. This allows us to trap the module and nomodule load.
230
     *
231
     * Note also that `nomodule` is supported in later versions of Safari -
232
     * it's just 10.1 that omits this attribute.
233
     *
234
     * c.f.: https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
235
     *
236
     * @param array $attributes additional HTML key/value pair attributes to add to the resulting tag
237
     *
238
     * @return string
239
     */
240
    public static function getSafariNomoduleFix(array $attributes = []): string
241
    {
242
        $code = /** @lang JavaScript */
0 ignored issues
show
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
243
            <<<EOT
244
!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();
245
EOT;
246
        $config = [];
247
        $nonce = self::getNonce();
248
        if ($nonce !== null) {
249
            $config['nonce'] = $nonce;
250
            self::includeNonce($nonce, 'script-src');
251
        }
252
253
        return Html::script($code, array_merge($config, $attributes));
254
    }
255
256
    /**
257
     * Return the URI to a module
258
     *
259
     * @param array $config
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...
260
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
261
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
262
     * @param bool $soft
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...
263
     *
264
     * @return null|string
265
     * @throws NotFoundHttpException
266
     */
267
    public static function getModule(array $config, string $moduleName, string $type = 'modern', bool $soft = false): ?string
268
    {
269
        // Get the module entry
270
        $module = self::getModuleEntry($config, $moduleName, $type, $soft);
271
        if ($module !== null) {
272
            $prefix = self::$isHot
273
                ? $config['devServer']['publicPath']
274
                : $config['server']['publicPath'];
275
            $useAbsoluteUrl = $config['useAbsoluteUrl'];
276
            // If the module isn't a full URL, prefix it as required
277
            if ($useAbsoluteUrl && !UrlHelper::isAbsoluteUrl($module)) {
278
                $module = self::combinePaths($prefix, $module);
279
            }
280
            // Resolve any aliases
281
            $alias = Craft::getAlias($module, false);
282
            if ($alias) {
283
                $module = $alias;
284
            }
285
            // Make sure it's a full URL, as required
286
            try {
287
                if ($useAbsoluteUrl && !UrlHelper::isAbsoluteUrl($module) && !is_file($module)) {
288
                    $module = UrlHelper::siteUrl($module);
289
                }
290
            } catch (Exception $e) {
291
                Craft::error($e->getMessage(), __METHOD__);
292
            }
293
        }
294
295
        return $module;
296
    }
297
298
    /**
299
     * Return the HASH value from to module
300
     *
301
     * @param array $config
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...
302
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
303
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
304
     * @param bool $soft
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...
305
     *
306
     * @return null|string
307
     */
308
    public static function getModuleHash(array $config, string $moduleName, string $type = 'modern', bool $soft = false): ?string
309
    {
310
        $moduleHash = '';
311
        try {
312
            // Get the module entry
313
            $module = self::getModuleEntry($config, $moduleName, $type, $soft);
314
            if ($module !== null) {
315
                // Extract only the Hash Value
316
                $modulePath = pathinfo($module);
317
                $moduleFilename = $modulePath['filename'];
318
                $moduleHash = substr($moduleFilename, strpos($moduleFilename, ".") + 1);
319
            }
320
        } catch (Exception $e) {
321
            // return empty string if no module is found
322
            return '';
323
        }
324
325
        return $moduleHash;
326
    }
327
328
    /**
329
     * Return a module's raw entry from the manifest
330
     *
331
     * @param array $config
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...
332
     * @param string $moduleName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
333
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
334
     * @param bool $soft
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...
335
     *
336
     * @return null|string
337
     * @throws NotFoundHttpException
338
     */
339
    public static function getModuleEntry(
340
        array  $config,
341
        string $moduleName,
342
        string $type = 'modern',
343
        bool   $soft = false,
344
    ): ?string {
345
        $module = null;
346
        // Get the manifest file
347
        $manifest = self::getManifestFile($config, $type);
348
        if ($manifest !== null) {
349
            // Make sure it exists in the manifest
350
            if (empty($manifest[$moduleName])) {
351
                // Don't report errors for any files in SUPPRESS_ERRORS_FOR_MODULES
352
                if (!in_array($moduleName, self::SUPPRESS_ERRORS_FOR_MODULES)) {
353
                    self::reportError(Craft::t(
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...
354
                        'twigpack',
355
                        'Module does not exist in the manifest: {moduleName}',
356
                        ['moduleName' => $moduleName]
357
                    ), $soft);
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...
358
                }
359
360
                return null;
361
            }
362
            $module = $manifest[$moduleName];
363
        }
364
365
        return $module;
366
    }
367
368
    /**
369
     * Return a JSON-decoded manifest file
370
     *
371
     * @param array $config
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...
372
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
373
     *
374
     * @return null|array
375
     * @throws NotFoundHttpException
376
     */
377
    public static function getManifestFile(array $config, string $type = 'modern'): ?array
378
    {
379
        $manifest = null;
380
        // Determine whether we should use the devServer for HMR or not
381
        $devMode = Craft::$app->getConfig()->getGeneral()->devMode;
382
        self::$isHot = ($devMode && $config['useDevServer']);
383
        // Try to get the manifest
384
        while ($manifest === null) {
385
            $manifestPath = self::$isHot
386
                ? $config['devServer']['manifestPath']
387
                : $config['server']['manifestPath'];
388
            // If this is a dev-server, use the defined build type
389
            $thisType = $type;
390
            if (self::$isHot) {
391
                $thisType = $config['devServerBuildType'] === 'combined'
392
                    ? $thisType
393
                    : $config['devServerBuildType'];
394
            }
395
            // Normalize the path
396
            $path = self::combinePaths($manifestPath, $config['manifest'][$thisType]);
397
            $manifest = self::getJsonFile($path);
398
            // If the manifest isn't found, and it was hot, fall back on non-hot
399
            if ($manifest === null) {
400
                // We couldn't find a manifest; throw an error
401
                self::reportError(Craft::t(
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...
402
                    'twigpack',
403
                    'Manifest file not found at: {manifestPath}',
404
                    ['manifestPath' => $manifestPath]
405
                ), true);
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...
406
                if (self::$isHot) {
407
                    // Try again, but not with home module replacement
408
                    self::$isHot = false;
409
                } else {
410
                    // Give up and return null
411
                    return null;
412
                }
413
            }
414
        }
415
416
        return $manifest;
417
    }
418
419
    /**
420
     * Returns the contents of a file from a URI path
421
     *
422
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
423
     *
424
     * @return string
425
     */
426
    public static function getFile(string $path): string
427
    {
428
        return self::getFileFromUri($path, null, true) ?? '';
429
    }
430
431
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
432
     * @param array $config
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...
433
     * @param string $fileName
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
434
     * @param string $type
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
435
     *
436
     * @return string
437
     */
438
    public static function getFileFromManifest(array $config, string $fileName, string $type = 'legacy'): string
439
    {
440
        $path = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $path is dead and can be removed.
Loading history...
441
        try {
442
            $path = self::getModuleEntry($config, $fileName, $type, true);
443
        } catch (NotFoundHttpException $e) {
444
            Craft::error($e->getMessage(), __METHOD__);
445
        }
446
        if ($path !== null) {
447
            // Determine whether we should use the devServer for HMR or not
448
            $devMode = Craft::$app->getConfig()->getGeneral()->devMode;
449
            if ($devMode) {
450
                $devServerPrefix = $config['devServer']['publicPath'];
451
                // If we're using the devserver, swap in the deverserver path
452
                if (UrlHelper::isAbsoluteUrl($path) && self::$isHot) {
453
                    $path = parse_url($path, PHP_URL_PATH);
454
                }
455
                $devServerPath = self::combinePaths(
456
                    $devServerPrefix,
457
                    $path
458
                );
459
                $devServerFile = self::getFileFromUri($devServerPath, null);
460
                if ($devServerFile) {
461
                    return $devServerFile;
462
                }
463
            }
464
            // Otherwise, try not-hot files
465
            $localPrefix = $config['localFiles']['basePath'];
466
            $localPath = self::combinePaths(
467
                $localPrefix,
468
                $path
469
            );
470
            $alias = Craft::getAlias($localPath, false);
471
            if ($alias && is_string($alias)) {
472
                $localPath = $alias;
473
            }
474
            try {
475
                if (is_file($localPath)) {
476
                    return self::getFile($localPath);
477
                }
478
            } catch (Exception $e) {
479
                Craft::error($e->getMessage(), __METHOD__);
480
            }
481
        }
482
483
        return '';
484
    }
485
486
    /**
487
     * Invalidate all of the manifest caches
488
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
489
    public static function invalidateCaches(): void
490
    {
491
        $cache = Craft::$app->getCache();
492
        TagDependency::invalidate($cache, self::CACHE_TAG);
493
        Craft::info('All manifest caches cleared', __METHOD__);
494
    }
495
496
    /**
497
     * Return the contents of a JSON file from a URI path
498
     *
499
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
500
     *
501
     * @return null|array
502
     */
503
    protected static function getJsonFile(string $path): ?array
504
    {
505
        return self::getFileFromUri($path, [self::class, 'jsonFileDecode']);
506
    }
507
508
    // Protected Static Methods
509
    // =========================================================================
510
511
    /**
512
     * Return the contents of a file from a URI path
513
     *
514
     * @param string $path
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 8 spaces after parameter type; 1 found
Loading history...
515
     * @param callable|null $callback
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
516
     * @param bool $pathOnly
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 10 spaces after parameter type; 1 found
Loading history...
517
     *
518
     * @return null|mixed
519
     */
520
    protected static function getFileFromUri(string $path, callable $callback = null, bool $pathOnly = false): mixed
521
    {
522
        // Resolve any aliases
523
        $alias = Craft::getAlias($path, false);
524
        if ($alias && is_string($alias)) {
525
            $path = $alias;
526
        }
527
        // If we only want the file via path, make sure it exists
528
        try {
529
            if ($pathOnly && !is_file($path)) {
530
                Craft::warning(Craft::t(
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...
531
                    'twigpack',
532
                    'File does not exist: {path}',
533
                    ['path' => $path]
534
                ), __METHOD__);
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...
535
536
                return '';
537
            }
538
        } catch (Exception $e) {
539
            Craft::error($e->getMessage(), __METHOD__);
540
        }
541
542
        // Make sure it's a full URL
543
        try {
544
            if (!UrlHelper::isAbsoluteUrl($path) && !is_file($path)) {
545
                $path = UrlHelper::siteUrl($path);
546
            }
547
        } catch (Exception $e) {
548
            Craft::error($e->getMessage(), __METHOD__);
549
        }
550
551
        return self::getFileContents($path, $callback);
552
    }
553
554
    /**
555
     * Return the contents of a file from the passed in path
556
     *
557
     * @param string $path