Completed
Branch TASK/update-about-page (5cee29)
by
unknown
34:34 queued 26:08
created

Registry::getCssUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace EventEspresso\core\services\assets;
4
5
use EE_Error;
6
use EventEspresso\core\domain\values\assets\Asset;
7
use EventEspresso\core\domain\values\assets\JavascriptAsset;
8
use EventEspresso\core\domain\values\assets\StylesheetAsset;
9
use EventEspresso\core\exceptions\ExceptionStackTraceDisplay;
10
use EventEspresso\core\exceptions\InvalidDataTypeException;
11
use EventEspresso\core\exceptions\InvalidFilePathException;
12
use Exception;
13
use InvalidArgumentException;
14
15
/**
16
 * Used for registering assets used in EE.
17
 *
18
 * @package    EventEspresso
19
 * @subpackage services\assets
20
 * @author     Darren Ethier
21
 * @since      4.9.24.rc.004
22
 */
23
class Registry
24
{
25
26
    const FILE_NAME_BUILD_MANIFEST = 'build-manifest.json';
27
28
    /**
29
     * @var AssetCollection $assets
30
     */
31
    protected $assets;
32
33
    /**
34
     * @var I18nRegistry
35
     */
36
    private $i18n_registry;
37
38
    /**
39
     * This holds the jsdata data object that will be exposed on pages that enqueue the `eejs-core` script.
40
     *
41
     * @var array
42
     */
43
    protected $jsdata = array();
44
45
    /**
46
     * This keeps track of all scripts with registered data.  It is used to prevent duplicate data objects setup in the
47
     * page source.
48
     *
49
     * @var array
50
     */
51
    private $script_handles_with_data = array();
52
53
54
    /**
55
     * Holds the manifest data obtained from registered manifest files.
56
     * Manifests are maps of asset chunk name to actual built asset file names.
57
     * Shape of this array is:
58
     * array(
59
     *  'some_namespace_slug' => array(
60
     *      'some_chunk_name' => array(
61
     *          'js' => 'filename.js'
62
     *          'css' => 'filename.js'
63
     *      ),
64
     *      'url_base' => 'https://baseurl.com/to/assets
65
     *  )
66
     * )
67
     *
68
     * @var array
69
     */
70
    private $manifest_data = array();
71
72
73
    /**
74
     * Registry constructor.
75
     * Hooking into WP actions for script registry.
76
     *
77
     * @param AssetCollection $assets
78
     * @param I18nRegistry    $i18n_registry
79
     */
80
    public function __construct(AssetCollection $assets, I18nRegistry $i18n_registry)
81
    {
82
        $this->assets = $assets;
83
        $this->i18n_registry = $i18n_registry;
84
        add_action('wp_enqueue_scripts', array($this, 'registerManifestFiles'), 1);
85
        add_action('admin_enqueue_scripts', array($this, 'registerManifestFiles'), 1);
86
        add_action('wp_enqueue_scripts', array($this, 'registerScriptsAndStyles'), 3);
87
        add_action('admin_enqueue_scripts', array($this, 'registerScriptsAndStyles'), 3);
88
        add_action('wp_enqueue_scripts', array($this, 'enqueueData'), 4);
89
        add_action('admin_enqueue_scripts', array($this, 'enqueueData'), 4);
90
        add_action('wp_print_footer_scripts', array($this, 'enqueueData'), 1);
91
        add_action('admin_print_footer_scripts', array($this, 'enqueueData'), 1);
92
    }
93
94
95
    /**
96
     * For classes that have Registry as a dependency, this provides a handy way to register script handles for i18n
97
     * translation handling.
98
     *
99
     * @return I18nRegistry
100
     */
101
    public function getI18nRegistry()
102
    {
103
        return $this->i18n_registry;
104
    }
105
106
107
    /**
108
     * Callback for the wp_enqueue_scripts actions used to register assets.
109
     *
110
     * @since 4.9.62.p
111
     * @throws Exception
112
     */
113
    public function registerScriptsAndStyles()
114
    {
115
        try {
116
            $this->registerScripts($this->assets->getJavascriptAssets());
117
            $this->registerStyles($this->assets->getStylesheetAssets());
118
        } catch (Exception $exception) {
119
            new ExceptionStackTraceDisplay($exception);
120
        }
121
    }
122
123
124
    /**
125
     * Registers JS assets with WP core
126
     *
127
     * @since 4.9.62.p
128
     * @param JavascriptAsset[] $scripts
129
     * @throws AssetRegistrationException
130
     * @throws InvalidDataTypeException
131
     */
132
    public function registerScripts(array $scripts)
133
    {
134
        foreach ($scripts as $script) {
135
            // skip to next script if this has already been done
136
            if ($script->isRegistered()) {
137
                continue;
138
            }
139
            do_action(
140
                'AHEE__EventEspresso_core_services_assets_Registry__registerScripts__before_script',
141
                $script
142
            );
143
            $registered = wp_register_script(
144
                $script->handle(),
145
                $script->source(),
146
                $script->dependencies(),
147
                $script->version(),
148
                $script->loadInFooter()
149
            );
150
            if (! $registered && $this->debug()) {
151
                throw new AssetRegistrationException($script->handle());
152
            }
153
            $script->setRegistered($registered);
154
            if ($script->requiresTranslation()) {
155
                $this->registerTranslation($script->handle());
156
            }
157
            do_action(
158
                'AHEE__EventEspresso_core_services_assets_Registry__registerScripts__after_script',
159
                $script
160
            );
161
        }
162
    }
163
164
165
    /**
166
     * Registers CSS assets with WP core
167
     *
168
     * @since 4.9.62.p
169
     * @param StylesheetAsset[] $styles
170
     * @throws InvalidDataTypeException
171
     */
172
    public function registerStyles(array $styles)
173
    {
174
        foreach ($styles as $style) {
175
            // skip to next style if this has already been done
176
            if ($style->isRegistered()) {
177
                continue;
178
            }
179
            do_action(
180
                'AHEE__EventEspresso_core_services_assets_Registry__registerStyles__before_style',
181
                $style
182
            );
183
            wp_register_style(
184
                $style->handle(),
185
                $style->source(),
186
                $style->dependencies(),
187
                $style->version(),
188
                $style->media()
189
            );
190
            $style->setRegistered();
191
            do_action(
192
                'AHEE__EventEspresso_core_services_assets_Registry__registerStyles__after_style',
193
                $style
194
            );
195
        }
196
    }
197
198
199
    /**
200
     * Call back for the script print in frontend and backend.
201
     * Used to call wp_localize_scripts so that data can be added throughout the runtime until this later hook point.
202
     *
203
     * @since 4.9.31.rc.015
204
     */
205
    public function enqueueData()
206
    {
207
        $this->removeAlreadyRegisteredDataForScriptHandles();
208
        wp_add_inline_script(
209
            'eejs-core',
210
            'var eejsdata=' . wp_json_encode(array('data' => $this->jsdata)),
211
            'before'
212
        );
213
        $scripts = $this->assets->getJavascriptAssetsWithData();
214
        foreach ($scripts as $script) {
215
            $this->addRegisteredScriptHandlesWithData($script->handle());
216
            if ($script->hasInlineDataCallback()) {
217
                $localize = $script->inlineDataCallback();
218
                $localize();
219
            }
220
        }
221
    }
222
223
224
    /**
225
     * Used to add data to eejs.data object.
226
     * Note:  Overriding existing data is not allowed.
227
     * Data will be accessible as a javascript object when you list `eejs-core` as a dependency for your javascript.
228
     * If the data you add is something like this:
229
     *  $this->addData( 'my_plugin_data', array( 'foo' => 'gar' ) );
230
     * It will be exposed in the page source as:
231
     *  eejs.data.my_plugin_data.foo == gar
232
     *
233
     * @param string       $key   Key used to access your data
234
     * @param string|array $value Value to attach to key
235
     * @throws InvalidArgumentException
236
     */
237
    public function addData($key, $value)
238
    {
239
        if ($this->verifyDataNotExisting($key)) {
240
            $this->jsdata[ $key ] = $value;
241
        }
242
    }
243
244
245
    /**
246
     * Similar to addData except this allows for users to push values to an existing key where the values on key are
247
     * elements in an array.
248
     *
249
     * When you use this method, the value you include will be merged with the array on $key.
250
     * So if the $key was 'test' and you added a value of ['my_data'] then it would be represented in the javascript
251
     * object like this, eejs.data.test = [ my_data,
252
     * ]
253
     * If there has already been a scalar value attached to the data object given key (via addData for instance), then
254
     * this will throw an exception.
255
     *
256
     * Caution: Only add data using this method if you are okay with the potential for additional data added on the same
257
     * key potentially overriding the existing data on merge (specifically with associative arrays).
258
     *
259
     * @param string       $key   Key to attach data to.
260
     * @param string|array $value Value being registered.
261
     * @throws InvalidArgumentException
262
     */
263
    public function pushData($key, $value)
264
    {
265
        if (isset($this->jsdata[ $key ])
266
            && ! is_array($this->jsdata[ $key ])
267
        ) {
268
            if (! $this->debug()) {
269
                return;
270
            }
271
            throw new InvalidArgumentException(
272
                sprintf(
273
                    __(
274
                        'The value for %1$s is already set and it is not an array. The %2$s method can only be used to
275
                         push values to this data element when it is an array.',
276
                        'event_espresso'
277
                    ),
278
                    $key,
279
                    __METHOD__
280
                )
281
            );
282
        }
283
        if ( ! isset( $this->jsdata[ $key ] ) ) {
284
            $this->jsdata[ $key ] = is_array($value) ? $value : [$value];
285
        } else {
286
            $this->jsdata[ $key ] = array_merge( $this->jsdata[$key], (array) $value);
287
        }
288
    }
289
290
291
    /**
292
     * Used to set content used by javascript for a template.
293
     * Note: Overrides of existing registered templates are not allowed.
294
     *
295
     * @param string $template_reference
296
     * @param string $template_content
297
     * @throws InvalidArgumentException
298
     */
299
    public function addTemplate($template_reference, $template_content)
300
    {
301
        if (! isset($this->jsdata['templates'])) {
302
            $this->jsdata['templates'] = array();
303
        }
304
        //no overrides allowed.
305 View Code Duplication
        if (isset($this->jsdata['templates'][ $template_reference ])) {
306
            if (! $this->debug()) {
307
                return;
308
            }
309
            throw new InvalidArgumentException(
310
                sprintf(
311
                    __(
312
                        'The %1$s key already exists for the templates array in the js data array.  No overrides are allowed.',
313
                        'event_espresso'
314
                    ),
315
                    $template_reference
316
                )
317
            );
318
        }
319
        $this->jsdata['templates'][ $template_reference ] = $template_content;
320
    }
321
322
323
    /**
324
     * Retrieve the template content already registered for the given reference.
325
     *
326
     * @param string $template_reference
327
     * @return string
328
     */
329
    public function getTemplate($template_reference)
330
    {
331
        return isset($this->jsdata['templates'][ $template_reference ])
332
            ? $this->jsdata['templates'][ $template_reference ]
333
            : '';
334
    }
335
336
337
    /**
338
     * Retrieve registered data.
339
     *
340
     * @param string $key Name of key to attach data to.
341
     * @return mixed                If there is no for the given key, then false is returned.
342
     */
343
    public function getData($key)
344
    {
345
        return isset($this->jsdata[ $key ])
346
            ? $this->jsdata[ $key ]
347
            : false;
348
    }
349
350
351
    /**
352
     * Verifies whether the given data exists already on the jsdata array.
353
     * Overriding data is not allowed.
354
     *
355
     * @param string $key Index for data.
356
     * @return bool        If valid then return true.
357
     * @throws InvalidArgumentException if data already exists.
358
     */
359
    protected function verifyDataNotExisting($key)
360
    {
361
        if (isset($this->jsdata[ $key ])) {
362
            if (! $this->debug()) {
363
                return false;
364
            }
365
            if (is_array($this->jsdata[ $key ])) {
366
                throw new InvalidArgumentException(
367
                    sprintf(
368
                        __(
369
                            'The value for %1$s already exists in the Registry::eejs object.
370
                            Overrides are not allowed. Since the value of this data is an array, you may want to use the
371
                            %2$s method to push your value to the array.',
372
                            'event_espresso'
373
                        ),
374
                        $key,
375
                        'pushData()'
376
                    )
377
                );
378
            }
379
            throw new InvalidArgumentException(
380
                sprintf(
381
                    __(
382
                        'The value for %1$s already exists in the Registry::eejs object. Overrides are not
383
                        allowed.  Consider attaching your value to a different key',
384
                        'event_espresso'
385
                    ),
386
                    $key
387
                )
388
            );
389
        }
390
        return true;
391
    }
392
393
394
    /**
395
     * Get the actual asset path for asset manifests.
396
     * If there is no asset path found for the given $chunk_name, then the $chunk_name is returned.
397
     *
398
     * @param string $namespace  The namespace associated with the manifest file hosting the map of chunk_name to actual
399
     *                           asset file location.
400
     * @param string $chunk_name
401
     * @param string $asset_type
402
     * @return string
403
     * @since 4.9.59.p
404
     */
405
    public function getAssetUrl($namespace, $chunk_name, $asset_type)
406
    {
407
        $url = isset(
408
            $this->manifest_data[ $namespace ][ $chunk_name . '.' . $asset_type ],
409
            $this->manifest_data[ $namespace ]['url_base']
410
        )
411
            ? $this->manifest_data[ $namespace ]['url_base']
412
              . $this->manifest_data[ $namespace ][ $chunk_name . '.' . $asset_type ]
413
            : $chunk_name;
414
        return apply_filters(
415
            'FHEE__EventEspresso_core_services_assets_Registry__getAssetUrl',
416
            $url,
417
            $namespace,
418
            $chunk_name,
419
            $asset_type
420
        );
421
    }
422
423
424
425
    /**
426
     * Return the url to a js file for the given namespace and chunk name.
427
     *
428
     * @param string $namespace
429
     * @param string $chunk_name
430
     * @return string
431
     */
432
    public function getJsUrl($namespace, $chunk_name)
433
    {
434
        return $this->getAssetUrl($namespace, $chunk_name, Asset::TYPE_JS);
435
    }
436
437
438
    /**
439
     * Return the url to a css file for the given namespace and chunk name.
440
     *
441
     * @param string $namespace
442
     * @param string $chunk_name
443
     * @return string
444
     */
445
    public function getCssUrl($namespace, $chunk_name)
446
    {
447
        return $this->getAssetUrl($namespace, $chunk_name, Asset::TYPE_CSS);
448
    }
449
450
451
    /**
452
     * @since 4.9.62.p
453
     * @throws InvalidArgumentException
454
     * @throws InvalidFilePathException
455
     */
456
    public function registerManifestFiles()
457
    {
458
        $manifest_files = $this->assets->getManifestFiles();
459
        foreach ($manifest_files as $manifest_file) {
460
            $this->registerManifestFile(
461
                $manifest_file->assetNamespace(),
462
                $manifest_file->urlBase(),
463
                $manifest_file->filepath() . Registry::FILE_NAME_BUILD_MANIFEST
464
            );
465
        }
466
    }
467
468
469
    /**
470
     * Used to register a js/css manifest file with the registered_manifest_files property.
471
     *
472
     * @param string $namespace     Provided to associate the manifest file with a specific namespace.
473
     * @param string $url_base      The url base for the manifest file location.
474
     * @param string $manifest_file The absolute path to the manifest file.
475
     * @throws InvalidArgumentException
476
     * @throws InvalidFilePathException
477
     * @since 4.9.59.p
478
     */
479
    public function registerManifestFile($namespace, $url_base, $manifest_file)
480
    {
481 View Code Duplication
        if (isset($this->manifest_data[ $namespace ])) {
482
            if (! $this->debug()) {
483
                return;
484
            }
485
            throw new InvalidArgumentException(
486
                sprintf(
487
                    esc_html__(
488
                        'The namespace for this manifest file has already been registered, choose a namespace other than %s',
489
                        'event_espresso'
490
                    ),
491
                    $namespace
492
                )
493
            );
494
        }
495
        if (filter_var($url_base, FILTER_VALIDATE_URL) === false) {
496
            if (is_admin()) {
497
                EE_Error::add_error(
498
                    sprintf(
499
                        esc_html__(
500
                            'The url given for %1$s assets is invalid.  The url provided was: "%2$s". This usually happens when another plugin or theme on a site is using the "%3$s" filter or has an invalid url set for the "%4$s" constant',
501
                            'event_espresso'
502
                        ),
503
                        'Event Espresso',
504
                        $url_base,
505
                        'plugins_url',
506
                        'WP_PLUGIN_URL'
507
                    ),
508
                    __FILE__,
509
                    __FUNCTION__,
510
                    __LINE__
511
                );
512
            }
513
            return;
514
        }
515
        $this->manifest_data[ $namespace ] = $this->decodeManifestFile($manifest_file);
516
        if (! isset($this->manifest_data[ $namespace ]['url_base'])) {
517
            $this->manifest_data[ $namespace ]['url_base'] = trailingslashit($url_base);
518
        }
519
    }
520
521
522
    /**
523
     * Decodes json from the provided manifest file.
524
     *
525
     * @since 4.9.59.p
526
     * @param string $manifest_file Path to manifest file.
527
     * @return array
528
     * @throws InvalidFilePathException
529
     */
530
    private function decodeManifestFile($manifest_file)
531
    {
532
        if (! file_exists($manifest_file)) {
533
            throw new InvalidFilePathException($manifest_file);
534
        }
535
        return json_decode(file_get_contents($manifest_file), true);
536
    }
537
538
539
    /**
540
     * This is used to set registered script handles that have data.
541
     *
542
     * @param string $script_handle
543
     */
544
    private function addRegisteredScriptHandlesWithData($script_handle)
545
    {
546
        $this->script_handles_with_data[ $script_handle ] = $script_handle;
547
    }
548
549
550
    /**i
551
     * Checks WP_Scripts for all of each script handle registered internally as having data and unsets from the
552
     * Dependency stored in WP_Scripts if its set.
553
     */
554
    private function removeAlreadyRegisteredDataForScriptHandles()
555
    {
556
        if (empty($this->script_handles_with_data)) {
557
            return;
558
        }
559
        foreach ($this->script_handles_with_data as $script_handle) {
560
            $this->removeAlreadyRegisteredDataForScriptHandle($script_handle);
561
        }
562
    }
563
564
565
    /**
566
     * Removes any data dependency registered in WP_Scripts if its set.
567
     *
568
     * @param string $script_handle
569
     */
570
    private function removeAlreadyRegisteredDataForScriptHandle($script_handle)
571
    {
572
        if (isset($this->script_handles_with_data[ $script_handle ])) {
573
            global $wp_scripts;
574
            $unset_handle = false;
575 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'data')) {
576
                unset($wp_scripts->registered[ $script_handle ]->extra['data']);
577
                $unset_handle = true;
578
            }
579
            //deal with inline_scripts
580 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'before')) {
581
                unset($wp_scripts->registered[ $script_handle ]->extra['before']);
582
                $unset_handle = true;
583
            }
584
            if ($wp_scripts->get_data($script_handle, 'after')) {
585
                unset($wp_scripts->registered[ $script_handle ]->extra['after']);
586
            }
587
            if ($unset_handle) {
588
                unset($this->script_handles_with_data[ $script_handle ]);
589
            }
590
        }
591
    }
592
593
594
    /**
595
     * register translations for a registered script
596
     *
597
     * @param string $handle
598
     */
599
    public function registerTranslation($handle)
600
    {
601
        $this->i18n_registry->registerScriptI18n($handle);
602
    }
603
604
605
    /**
606
     * @since 4.9.63.p
607
     * @return bool
608
     */
609
    private function debug()
610
    {
611
        return apply_filters(
612
            'FHEE__EventEspresso_core_services_assets_Registry__debug',
613
            defined('EE_DEBUG') && EE_DEBUG
614
        );
615
    }
616
}
617