Completed
Branch Gutenberg/components-form-sele... (fd4b83)
by
unknown
49:15 queued 33:22
created

Registry::pushData()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 3
nop 2
dl 0
loc 22
rs 8.9197
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
     * When you use this method, the value you include will be appended to the end of an array on $key.
249
     * So if the $key was 'test' and you added a value of 'my_data' then it would be represented in the javascript
250
     * object like this, eejs.data.test = [ my_data,
251
     * ]
252
     * If there has already been a scalar value attached to the data object given key, then
253
     * this will throw an exception.
254
     *
255
     * @param string       $key   Key to attach data to.
256
     * @param string|array $value Value being registered.
257
     * @throws InvalidArgumentException
258
     */
259
    public function pushData($key, $value)
260
    {
261
        if (isset($this->jsdata[ $key ])
262
            && ! is_array($this->jsdata[ $key ])
263
        ) {
264
            if (! $this->debug()) {
265
                return;
266
            }
267
            throw new InvalidArgumentException(
268
                sprintf(
269
                    __(
270
                        'The value for %1$s is already set and it is not an array. The %2$s method can only be used to
271
                         push values to this data element when it is an array.',
272
                        'event_espresso'
273
                    ),
274
                    $key,
275
                    __METHOD__
276
                )
277
            );
278
        }
279
        $this->jsdata[ $key ][] = $value;
280
    }
281
282
283
    /**
284
     * Used to set content used by javascript for a template.
285
     * Note: Overrides of existing registered templates are not allowed.
286
     *
287
     * @param string $template_reference
288
     * @param string $template_content
289
     * @throws InvalidArgumentException
290
     */
291
    public function addTemplate($template_reference, $template_content)
292
    {
293
        if (! isset($this->jsdata['templates'])) {
294
            $this->jsdata['templates'] = array();
295
        }
296
        //no overrides allowed.
297 View Code Duplication
        if (isset($this->jsdata['templates'][ $template_reference ])) {
298
            if (! $this->debug()) {
299
                return;
300
            }
301
            throw new InvalidArgumentException(
302
                sprintf(
303
                    __(
304
                        'The %1$s key already exists for the templates array in the js data array.  No overrides are allowed.',
305
                        'event_espresso'
306
                    ),
307
                    $template_reference
308
                )
309
            );
310
        }
311
        $this->jsdata['templates'][ $template_reference ] = $template_content;
312
    }
313
314
315
    /**
316
     * Retrieve the template content already registered for the given reference.
317
     *
318
     * @param string $template_reference
319
     * @return string
320
     */
321
    public function getTemplate($template_reference)
322
    {
323
        return isset($this->jsdata['templates'][ $template_reference ])
324
            ? $this->jsdata['templates'][ $template_reference ]
325
            : '';
326
    }
327
328
329
    /**
330
     * Retrieve registered data.
331
     *
332
     * @param string $key Name of key to attach data to.
333
     * @return mixed                If there is no for the given key, then false is returned.
334
     */
335
    public function getData($key)
336
    {
337
        return isset($this->jsdata[ $key ])
338
            ? $this->jsdata[ $key ]
339
            : false;
340
    }
341
342
343
    /**
344
     * Verifies whether the given data exists already on the jsdata array.
345
     * Overriding data is not allowed.
346
     *
347
     * @param string $key Index for data.
348
     * @return bool        If valid then return true.
349
     * @throws InvalidArgumentException if data already exists.
350
     */
351
    protected function verifyDataNotExisting($key)
352
    {
353
        if (isset($this->jsdata[ $key ])) {
354
            if (! $this->debug()) {
355
                return false;
356
            }
357
            if (is_array($this->jsdata[ $key ])) {
358
                throw new InvalidArgumentException(
359
                    sprintf(
360
                        __(
361
                            'The value for %1$s already exists in the Registry::eejs object.
362
                            Overrides are not allowed. Since the value of this data is an array, you may want to use the
363
                            %2$s method to push your value to the array.',
364
                            'event_espresso'
365
                        ),
366
                        $key,
367
                        'pushData()'
368
                    )
369
                );
370
            }
371
            throw new InvalidArgumentException(
372
                sprintf(
373
                    __(
374
                        'The value for %1$s already exists in the Registry::eejs object. Overrides are not
375
                        allowed.  Consider attaching your value to a different key',
376
                        'event_espresso'
377
                    ),
378
                    $key
379
                )
380
            );
381
        }
382
        return true;
383
    }
384
385
386
    /**
387
     * Get the actual asset path for asset manifests.
388
     * If there is no asset path found for the given $chunk_name, then the $chunk_name is returned.
389
     *
390
     * @param string $namespace  The namespace associated with the manifest file hosting the map of chunk_name to actual
391
     *                           asset file location.
392
     * @param string $chunk_name
393
     * @param string $asset_type
394
     * @return string
395
     * @since 4.9.59.p
396
     */
397
    public function getAssetUrl($namespace, $chunk_name, $asset_type)
398
    {
399
        $url = isset(
400
            $this->manifest_data[ $namespace ][ $chunk_name . '.' . $asset_type ],
401
            $this->manifest_data[ $namespace ]['url_base']
402
        )
403
            ? $this->manifest_data[ $namespace ]['url_base']
404
              . $this->manifest_data[ $namespace ][ $chunk_name . '.' . $asset_type ]
405
            : $chunk_name;
406
        return apply_filters(
407
            'FHEE__EventEspresso_core_services_assets_Registry__getAssetUrl',
408
            $url,
409
            $namespace,
410
            $chunk_name,
411
            $asset_type
412
        );
413
    }
414
415
416
417
    /**
418
     * Return the url to a js file for the given namespace and chunk name.
419
     *
420
     * @param string $namespace
421
     * @param string $chunk_name
422
     * @return string
423
     */
424
    public function getJsUrl($namespace, $chunk_name)
425
    {
426
        return $this->getAssetUrl($namespace, $chunk_name, Asset::TYPE_JS);
427
    }
428
429
430
    /**
431
     * Return the url to a css file for the given namespace and chunk name.
432
     *
433
     * @param string $namespace
434
     * @param string $chunk_name
435
     * @return string
436
     */
437
    public function getCssUrl($namespace, $chunk_name)
438
    {
439
        return $this->getAssetUrl($namespace, $chunk_name, Asset::TYPE_CSS);
440
    }
441
442
443
    /**
444
     * @since 4.9.62.p
445
     * @throws InvalidArgumentException
446
     * @throws InvalidFilePathException
447
     */
448
    public function registerManifestFiles()
449
    {
450
        $manifest_files = $this->assets->getManifestFiles();
451
        foreach ($manifest_files as $manifest_file) {
452
            $this->registerManifestFile(
453
                $manifest_file->assetNamespace(),
454
                $manifest_file->urlBase(),
455
                $manifest_file->filepath() . Registry::FILE_NAME_BUILD_MANIFEST
456
            );
457
        }
458
    }
459
460
461
    /**
462
     * Used to register a js/css manifest file with the registered_manifest_files property.
463
     *
464
     * @param string $namespace     Provided to associate the manifest file with a specific namespace.
465
     * @param string $url_base      The url base for the manifest file location.
466
     * @param string $manifest_file The absolute path to the manifest file.
467
     * @throws InvalidArgumentException
468
     * @throws InvalidFilePathException
469
     * @since 4.9.59.p
470
     */
471
    public function registerManifestFile($namespace, $url_base, $manifest_file)
472
    {
473 View Code Duplication
        if (isset($this->manifest_data[ $namespace ])) {
474
            if (! $this->debug()) {
475
                return;
476
            }
477
            throw new InvalidArgumentException(
478
                sprintf(
479
                    esc_html__(
480
                        'The namespace for this manifest file has already been registered, choose a namespace other than %s',
481
                        'event_espresso'
482
                    ),
483
                    $namespace
484
                )
485
            );
486
        }
487
        if (filter_var($url_base, FILTER_VALIDATE_URL) === false) {
488
            if (is_admin()) {
489
                EE_Error::add_error(
490
                    sprintf(
491
                        esc_html__(
492
                            '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',
493
                            'event_espresso'
494
                        ),
495
                        'Event Espresso',
496
                        $url_base,
497
                        'plugins_url',
498
                        'WP_PLUGIN_URL'
499
                    ),
500
                    __FILE__,
501
                    __FUNCTION__,
502
                    __LINE__
503
                );
504
            }
505
            return;
506
        }
507
        $this->manifest_data[ $namespace ] = $this->decodeManifestFile($manifest_file);
508
        if (! isset($this->manifest_data[ $namespace ]['url_base'])) {
509
            $this->manifest_data[ $namespace ]['url_base'] = trailingslashit($url_base);
510
        }
511
    }
512
513
514
    /**
515
     * Decodes json from the provided manifest file.
516
     *
517
     * @since 4.9.59.p
518
     * @param string $manifest_file Path to manifest file.
519
     * @return array
520
     * @throws InvalidFilePathException
521
     */
522
    private function decodeManifestFile($manifest_file)
523
    {
524
        if (! file_exists($manifest_file)) {
525
            throw new InvalidFilePathException($manifest_file);
526
        }
527
        return json_decode(file_get_contents($manifest_file), true);
528
    }
529
530
531
    /**
532
     * This is used to set registered script handles that have data.
533
     *
534
     * @param string $script_handle
535
     */
536
    private function addRegisteredScriptHandlesWithData($script_handle)
537
    {
538
        $this->script_handles_with_data[ $script_handle ] = $script_handle;
539
    }
540
541
542
    /**i
543
     * Checks WP_Scripts for all of each script handle registered internally as having data and unsets from the
544
     * Dependency stored in WP_Scripts if its set.
545
     */
546
    private function removeAlreadyRegisteredDataForScriptHandles()
547
    {
548
        if (empty($this->script_handles_with_data)) {
549
            return;
550
        }
551
        foreach ($this->script_handles_with_data as $script_handle) {
552
            $this->removeAlreadyRegisteredDataForScriptHandle($script_handle);
553
        }
554
    }
555
556
557
    /**
558
     * Removes any data dependency registered in WP_Scripts if its set.
559
     *
560
     * @param string $script_handle
561
     */
562
    private function removeAlreadyRegisteredDataForScriptHandle($script_handle)
563
    {
564
        if (isset($this->script_handles_with_data[ $script_handle ])) {
565
            global $wp_scripts;
566
            $unset_handle = false;
567 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'data')) {
568
                unset($wp_scripts->registered[ $script_handle ]->extra['data']);
569
                $unset_handle = true;
570
            }
571
            //deal with inline_scripts
572 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'before')) {
573
                unset($wp_scripts->registered[ $script_handle ]->extra['before']);
574
                $unset_handle = true;
575
            }
576
            if ($wp_scripts->get_data($script_handle, 'after')) {
577
                unset($wp_scripts->registered[ $script_handle ]->extra['after']);
578
            }
579
            if ($unset_handle) {
580
                unset($this->script_handles_with_data[ $script_handle ]);
581
            }
582
        }
583
    }
584
585
586
    /**
587
     * register translations for a registered script
588
     *
589
     * @param string $handle
590
     */
591
    public function registerTranslation($handle)
592
    {
593
        $this->i18n_registry->registerScriptI18n($handle);
594
    }
595
596
597
    /**
598
     * @since $VID:$
599
     * @return bool
600
     */
601
    private function debug()
602
    {
603
        return apply_filters(
604
            'FHEE__EventEspresso_core_services_assets_Registry__debug',
605
            defined('EE_DEBUG') && EE_DEBUG
606
        );
607
    }
608
}
609