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