Asset::enqueue()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Flynt\Utils;
4
5
// TODO: add async & defer (see https://matthewhorne.me/defer-async-wordpress-scripts/); also add to cdn fallback
6
7
class Asset
8
{
9
    const DEFAULT_OPTIONS = [
10
        'dependencies' => [],
11
        'version' => null,
12
        'inFooter' => true,
13
        'media' => 'all',
14
        'cdn' => []
15
    ];
16
17
    protected static $assetManifest;
18
    protected static $loadFromCdn = false;
19
20
    /**
21
     * Gets the asset's url.
22
     *
23
     * @since 0.1.0
24
     *
25
     * @param string $asset The filename of the required asset.
26
     *
27
     * @return string|boolean Returns the url or false if the asset is not found.
28
     */
29
    public static function requireUrl($asset)
30
    {
31
        return self::get('url', $asset);
32
    }
33
34
    /**
35
     * Gets the asset's absolute path.
36
     *
37
     * @since 0.1.0
38
     *
39
     * @param string $asset The filename of the required asset.
40
     *
41
     * @return string|boolean Returns the absolute path or false if the asset is not found.
42
     */
43
    public static function requirePath($asset)
44
    {
45
        return self::get('path', $asset);
46
    }
47
48
    /**
49
     * Registers an asset.
50
     * It can register a script or a style.
51
     *
52
     * If the Asset utility has `loadFromCdn` set to true, you can use the cdn option to load an asset from the CDN,
53
     * the path option will then become the local fallback file, using the 'check' in the 'cdn' array.
54
     *
55
     * @since 0.1.0
56
     * @since 0.2.0 Supports CDN parameter.
57
     *
58
     * @param array $options Options must specify a type, a name and a path.
59
     *              $options = [
60
     *                 'type'          => (string) The type of asset to register (script|style).
61
     *                 'name'          => (string) Should be unique as it is used to identify the script in the whole system.
62
     *                 'path'          => (string) The path.
63
     *                 'dependencies'  => (array) An array of registered script handles this script depends on.
64
     *                 'version'       => (string/boolean) String specifying script version number, if it has one.
65
     *                 'inFooter'      => (boolean) If the type is 'script', whether to enqueue the script before </body> instead of in the <head>. Default 'false'.
66
     *                 'media'         => (string) If the type is 'style', string specifying the media for which this stylesheet has been defined (all/screen/handheld/print).
67
     *                 'cdn'           => [
68
     *                      'url'   => (string) The CDN url.
69
     *                      'check' => (string/boolean) Javascript boolean value that loads the local fallback if it is false.
70
     *                 ]
71
     *              ]
72
     *
73
     * @return boolean
74
     */
75
    public static function register($options)
76
    {
77
        return self::add('register', $options);
78
    }
79
80
    /**
81
     * Enqueues an asset.
82
     * It can enqueue a script or a style.
83
     *
84
     * @since 0.1.0
85
     * @since 0.2.0 Supports CDN parameter.
86
     *
87
     * @param array $options Options must specify a type, a name and a path.
88
     *              $options = [
89
     *                 'type'          => (string) The type of asset to register (script|style).
90
     *                 'name'          => (string) Should be unique as it is used to identify the script in the whole system.
91
     *                 'path'          => (string) The path.
92
     *                 'dependencies'  => (array) An array of registered script handles this script depends on.
93
     *                 'version'       => (string/boolean) String specifying script version number, if it has one.
94
     *                 'inFooter'      => (boolean) If the type is 'script', whether to enqueue the script before </body> instead of in the <head>. Default 'false'.
95
     *                 'media'         => (string) If the type is 'style', string specifying the media for which this stylesheet has been defined (all/screen/handheld/print).
96
     *                 'cdn'           => [
97
     *                      'url'   => (string) The CDN url.
98
     *                      'check' => (string/boolean) Javascript boolean value that loads the local fallback if it is false.
99
     *                 ]
100
     *              ]
101
     *
102
     * @return boolean
103
     */
104
    public static function enqueue($options)
105
    {
106
        return self::add('enqueue', $options);
107
    }
108
109
    /**
110
     * Getter and setter for the loadFromCdn setting.
111
     *
112
     * @param boolean $load (optional) Value to set the parameter to.
113
     *
114
     * @since 0.2.0
115
     */
116
    public static function loadFromCdn($load = null)
117
    {
118
        if (!isset($load)) {
119
            return self::$loadFromCdn;
120
        }
121
        self::$loadFromCdn = (bool) $load;
122
    }
123
124
    /**
125
     * Gets the contents of an asset.
126
     *
127
     * Useful for loading SVGs or other files inline.
128
     *
129
     * @since 0.2.0
130
     *
131
     * @param string $asset Asset path (relative to the theme directory).
132
     * @return string|boolean Returns the file contents or false in case of failure.
133
     */
134
    public static function getContents($asset)
135
    {
136
        $file = self::requirePath($asset);
137
        if (file_exists($file)) {
138
            return file_get_contents($file);
139
        } else {
140
            trigger_error("File not found at: ${asset}", E_USER_WARNING);
141
            return false;
142
        }
143
    }
144
145
    protected static function get($returnType, $asset)
146
    {
147
        $distPath = get_template_directory() . '/dist';
148
149
        if (!isset(self::$assetManifest)) {
150
            $manifestPath = $distPath . '/rev-manifest.json';
151
            if (is_file($manifestPath)) {
152
                self::$assetManifest = json_decode(file_get_contents($manifestPath), true);
153
            } else {
154
                self::$assetManifest = [];
155
            }
156
        }
157
158
        if (array_key_exists($asset, self::$assetManifest)) {
159
            $assetSuffix = self::$assetManifest[$asset];
160
        } else {
161
            $assetSuffix = $asset;
162
        }
163
164
        if ('path' == $returnType) {
165
            return $distPath . '/' . $assetSuffix;
166
        } else if ('url' == $returnType) {
167
            $distUrl = get_template_directory_uri() . '/dist';
168
            return $distUrl . '/' . $assetSuffix;
169
        }
170
171
        return false;
172
    }
173
174
    protected static function add($funcType, $options)
175
    {
176
        $options = array_merge(self::DEFAULT_OPTIONS, $options);
177
178
        if (!array_key_exists('name', $options)) {
179
            trigger_error('Cannot add asset: Name not provided!', E_USER_WARNING);
180
            return false;
181
        }
182
183
        if (!array_key_exists('path', $options)) {
184
            trigger_error('Cannot add asset: Path not provided!', E_USER_WARNING);
185
            return false;
186
        }
187
188
        $funcName = "wp_{$funcType}_{$options['type']}";
189
        $lastVar = $options['type'] === 'script' ? $options['inFooter'] : $options['media'];
190
191
        // allow external urls
192
        $path = $options['path'];
193
        if (!(StringHelpers::startsWith('http://', $path))
194
            && !(StringHelpers::startsWith('https://', $path))
195
            && !(StringHelpers::startsWith('//', $path))
196
        ) {
197
            $path = Asset::requireUrl($path);
198
        }
199
200
        if ('script' === $options['type']
201
            && true === self::$loadFromCdn
202
            && !empty($options['cdn'])
203
            && !empty($options['cdn']['check'])
204
            && !empty($options['cdn']['url'])
205
        ) {
206
            // if the script isn't registered or enqueued yet
207
            if (!wp_script_is($options['name'], 'registered')
208
                && !wp_script_is($options['name'], 'enqueued')
209
            ) {
210
                $localPath = $path;
211
                $path = $options['cdn']['url'];
212
            } else {
213
                // script already registered / enqueued
214
                // get registered script and compare paths
215
                $scriptPath = wp_scripts()->registered[$options['name']]->src;
216
217
                // deregister script and set cdn options to re-register down below
218
                if ($options['cdn']['url'] !== $scriptPath) {
219
                    wp_deregister_script($options['name']);
220
                    $localPath = $path;
221
                    $path = $options['cdn']['url'];
222
                }
223
            }
224
        }
225
226
        if (function_exists($funcName)) {
227
            $funcName(
228
                $options['name'],
229
                $path,
230
                $options['dependencies'],
231
                $options['version'],
232
                $lastVar
233
            );
234
235
            if (isset($localPath)) {
236
                $cdnCheck = $options['cdn']['check'];
237
                wp_add_inline_script(
238
                    $options['name'],
239
                    "${cdnCheck}||document.write(\"<script src=\\\"${localPath}\\\"><\/script>\")"
240
                );
241
            }
242
243
            return true;
244
        }
245
246
        return false;
247
    }
248
}
249