Completed
Branch FET/asset-manager (76ba99)
by
unknown
17:31 queued 29s
created

Registry::getI18nRegistry()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 1
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 $VID:$
110
     * @throws AssetRegistrationException
111
     * @throws InvalidDataTypeException
112
     * @throws Exception
113
     */
114
    public function registerScriptsAndStyles()
115
    {
116
        try {
117
            $this->registerScripts($this->assets->getJavascriptAssets());
118
            $this->registerStyles($this->assets->getStylesheetAssets());
119
        } catch (Exception $exception) {
120
            new ExceptionStackTraceDisplay($exception);
121
        }
122
    }
123
124
125
    /**
126
     * Registers JS assets with WP core
127
     *
128
     * @since $VID:$
129
     * @param JavascriptAsset[] $scripts
130
     * @throws AssetRegistrationException
131
     * @throws InvalidDataTypeException
132
     */
133
    public function registerScripts(array $scripts)
134
    {
135
        foreach ($scripts as $script) {
136
            // skip to next script if this has already been done
137
            if($script->isRegistered()) {
138
                continue;
139
            }
140
            do_action(
141
                'AHEE__EventEspresso_core_services_assets_Registry__registerScripts__before_script',
142
                $script
143
            );
144
            $registered = wp_register_script(
145
                $script->handle(),
146
                $script->source(),
147
                $script->dependencies(),
148
                $script->version(),
149
                $script->loadInFooter()
150
            );
151
            if(WP_DEBUG && ! $registered) {
152
                throw new AssetRegistrationException($script->handle());
153
            }
154
            $script->setRegistered($registered);
155
            if ($script->requiresTranslation()) {
156
                $this->registerTranslation($script->handle());
157
            }
158
            do_action(
159
                'AHEE__EventEspresso_core_services_assets_Registry__registerScripts__after_script',
160
                $script
161
            );
162
        }
163
    }
164
165
166
    /**
167
     * Registers CSS assets with WP core
168
     *
169
     * @since $VID:$
170
     * @param StylesheetAsset[] $styles
171
     * @throws InvalidDataTypeException
172
     */
173
    public function registerStyles(array $styles)
174
    {
175
        foreach ($styles as $style) {
176
            // skip to next style if this has already been done
177
            if ($style->isRegistered()) {
178
                continue;
179
            }
180
            do_action(
181
                'AHEE__EventEspresso_core_services_assets_Registry__registerStyles__before_style',
182
                $style
183
            );
184
            wp_enqueue_style(
185
                $style->handle(),
186
                $style->source(),
187
                $style->dependencies(),
188
                $style->version(),
189
                $style->media()
190
            );
191
            $style->setRegistered();
192
            do_action(
193
                'AHEE__EventEspresso_core_services_assets_Registry__registerStyles__after_style',
194
                $style
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->hasLocalizationCallback()) {
217
                $localize = $script->localizationCallback();
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 View Code Duplication
        if (isset($this->jsdata[ $key ])
262
            && ! is_array($this->jsdata[ $key ])
263
        ) {
264
            throw new InvalidArgumentException(
265
                sprintf(
266
                    __(
267
                        'The value for %1$s is already set and it is not an array. The %2$s method can only be used to
268
                         push values to this data element when it is an array.',
269
                        'event_espresso'
270
                    ),
271
                    $key,
272
                    __METHOD__
273
                )
274
            );
275
        }
276
        $this->jsdata[ $key ][] = $value;
277
    }
278
279
280
    /**
281
     * Used to set content used by javascript for a template.
282
     * Note: Overrides of existing registered templates are not allowed.
283
     *
284
     * @param string $template_reference
285
     * @param string $template_content
286
     * @throws InvalidArgumentException
287
     */
288
    public function addTemplate($template_reference, $template_content)
289
    {
290
        if (! isset($this->jsdata['templates'])) {
291
            $this->jsdata['templates'] = array();
292
        }
293
        //no overrides allowed.
294
        if (isset($this->jsdata['templates'][ $template_reference ])) {
295
            throw new InvalidArgumentException(
296
                sprintf(
297
                    __(
298
                        'The %1$s key already exists for the templates array in the js data array.  No overrides are allowed.',
299
                        'event_espresso'
300
                    ),
301
                    $template_reference
302
                )
303
            );
304
        }
305
        $this->jsdata['templates'][ $template_reference ] = $template_content;
306
    }
307
308
309
    /**
310
     * Retrieve the template content already registered for the given reference.
311
     *
312
     * @param string $template_reference
313
     * @return string
314
     */
315
    public function getTemplate($template_reference)
316
    {
317
        return isset($this->jsdata['templates'][ $template_reference ])
318
            ? $this->jsdata['templates'][ $template_reference ]
319
            : '';
320
    }
321
322
323
    /**
324
     * Retrieve registered data.
325
     *
326
     * @param string $key Name of key to attach data to.
327
     * @return mixed                If there is no for the given key, then false is returned.
328
     */
329
    public function getData($key)
330
    {
331
        return isset($this->jsdata[ $key ])
332
            ? $this->jsdata[ $key ]
333
            : false;
334
    }
335
336
337
    /**
338
     * Verifies whether the given data exists already on the jsdata array.
339
     * Overriding data is not allowed.
340
     *
341
     * @param string $key Index for data.
342
     * @return bool        If valid then return true.
343
     * @throws InvalidArgumentException if data already exists.
344
     */
345
    protected function verifyDataNotExisting($key)
346
    {
347
        if (isset($this->jsdata[ $key ])) {
348 View Code Duplication
            if (is_array($this->jsdata[ $key ])) {
349
                throw new InvalidArgumentException(
350
                    sprintf(
351
                        __(
352
                            'The value for %1$s already exists in the Registry::eejs object.
353
                            Overrides are not allowed. Since the value of this data is an array, you may want to use the
354
                            %2$s method to push your value to the array.',
355
                            'event_espresso'
356
                        ),
357
                        $key,
358
                        'pushData()'
359
                    )
360
                );
361
            }
362
            throw new InvalidArgumentException(
363
                sprintf(
364
                    __(
365
                        'The value for %1$s already exists in the Registry::eejs object. Overrides are not
366
                        allowed.  Consider attaching your value to a different key',
367
                        'event_espresso'
368
                    ),
369
                    $key
370
                )
371
            );
372
        }
373
        return true;
374
    }
375
376
377
    /**
378
     * Get the actual asset path for asset manifests.
379
     * If there is no asset path found for the given $chunk_name, then the $chunk_name is returned.
380
     *
381
     * @param string $namespace  The namespace associated with the manifest file hosting the map of chunk_name to actual
382
     *                           asset file location.
383
     * @param string $chunk_name
384
     * @param string $asset_type
385
     * @return string
386
     * @since 4.9.59.p
387
     */
388
    public function getAssetUrl($namespace, $chunk_name, $asset_type)
389
    {
390
        $url = isset(
391
            $this->manifest_data[ $namespace ][ $chunk_name ][ $asset_type ],
392
            $this->manifest_data[ $namespace ]['url_base']
393
        )
394
            ? $this->manifest_data[ $namespace ]['url_base']
395
              . $this->manifest_data[ $namespace ][ $chunk_name ][ $asset_type ]
396
            : $chunk_name;
397
        return apply_filters(
398
            'FHEE__EventEspresso_core_services_assets_Registry__getAssetUrl',
399
            $url,
400
            $namespace,
401
            $chunk_name,
402
            $asset_type
403
        );
404
    }
405
406
407
    /**
408
     * Return the url to a js file for the given namespace and chunk name.
409
     *
410
     * @param string $namespace
411
     * @param string $chunk_name
412
     * @return string
413
     */
414
    public function getJsUrl($namespace, $chunk_name)
415
    {
416
        return $this->getAssetUrl($namespace, $chunk_name, Asset::TYPE_JS);
417
    }
418
419
420
    /**
421
     * Return the url to a css file for the given namespace and chunk name.
422
     *
423
     * @param string $namespace
424
     * @param string $chunk_name
425
     * @return string
426
     */
427
    public function getCssUrl($namespace, $chunk_name)
428
    {
429
        return $this->getAssetUrl($namespace, $chunk_name, Asset::TYPE_CSS);
430
    }
431
432
433
    /**
434
     * @since $VID:$
435
     * @throws InvalidArgumentException
436
     * @throws InvalidFilePathException
437
     */
438
    public function registerManifestFiles()
439
    {
440
        $manifest_files = $this->assets->getManifestFiles();
441
        foreach ($manifest_files as $manifest_file) {
442
            $this->registerManifestFile(
443
                $manifest_file->assetNamespace(),
444
                $manifest_file->urlBase(),
445
                $manifest_file->filepath() . Registry::FILE_NAME_BUILD_MANIFEST
446
            );
447
        }
448
    }
449
450
451
    /**
452
     * Used to register a js/css manifest file with the registered_manifest_files property.
453
     *
454
     * @param string $namespace     Provided to associate the manifest file with a specific namespace.
455
     * @param string $url_base      The url base for the manifest file location.
456
     * @param string $manifest_file The absolute path to the manifest file.
457
     * @throws InvalidArgumentException
458
     * @throws InvalidFilePathException
459
     * @since 4.9.59.p
460
     */
461
    public function registerManifestFile($namespace, $url_base, $manifest_file)
462
    {
463
        if (isset($this->manifest_data[ $namespace ])) {
464
            throw new InvalidArgumentException(
465
                sprintf(
466
                    esc_html__(
467
                        'The namespace for this manifest file has already been registered, choose a namespace other than %s',
468
                        'event_espresso'
469
                    ),
470
                    $namespace
471
                )
472
            );
473
        }
474
        if (filter_var($url_base, FILTER_VALIDATE_URL) === false) {
475
            if (is_admin()) {
476
                EE_Error::add_error(
477
                    sprintf(
478
                        esc_html__(
479
                            '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',
480
                            'event_espresso'
481
                        ),
482
                        'Event Espresso',
483
                        $url_base,
484
                        'plugins_url',
485
                        'WP_PLUGIN_URL'
486
                    ),
487
                    __FILE__,
488
                    __FUNCTION__,
489
                    __LINE__
490
                );
491
            }
492
            return;
493
        }
494
        $this->manifest_data[ $namespace ] = $this->decodeManifestFile($manifest_file);
495
        if (! isset($this->manifest_data[ $namespace ]['url_base'])) {
496
            $this->manifest_data[ $namespace ]['url_base'] = trailingslashit($url_base);
497
        }
498
    }
499
500
501
    /**
502
     * Decodes json from the provided manifest file.
503
     *
504
     * @since 4.9.59.p
505
     * @param string $manifest_file Path to manifest file.
506
     * @return array
507
     * @throws InvalidFilePathException
508
     */
509
    private function decodeManifestFile($manifest_file)
510
    {
511
        if (! file_exists($manifest_file)) {
512
            throw new InvalidFilePathException($manifest_file);
513
        }
514
        return json_decode(file_get_contents($manifest_file), true);
515
    }
516
517
518
    /**
519
     * This is used to set registered script handles that have data.
520
     *
521
     * @param string $script_handle
522
     */
523
    private function addRegisteredScriptHandlesWithData($script_handle)
524
    {
525
        $this->script_handles_with_data[ $script_handle ] = $script_handle;
526
    }
527
528
529
    /**i
530
     * Checks WP_Scripts for all of each script handle registered internally as having data and unsets from the
531
     * Dependency stored in WP_Scripts if its set.
532
     */
533
    private function removeAlreadyRegisteredDataForScriptHandles()
534
    {
535
        if (empty($this->script_handles_with_data)) {
536
            return;
537
        }
538
        foreach ($this->script_handles_with_data as $script_handle) {
539
            $this->removeAlreadyRegisteredDataForScriptHandle($script_handle);
540
        }
541
    }
542
543
544
    /**
545
     * Removes any data dependency registered in WP_Scripts if its set.
546
     *
547
     * @param string $script_handle
548
     */
549
    private function removeAlreadyRegisteredDataForScriptHandle($script_handle)
550
    {
551
        if (isset($this->script_handles_with_data[ $script_handle ])) {
552
            global $wp_scripts;
553
            $unset_handle = false;
554 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'data')) {
555
                unset($wp_scripts->registered[ $script_handle ]->extra['data']);
556
                $unset_handle = true;
557
            }
558
            //deal with inline_scripts
559 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'before')) {
560
                unset($wp_scripts->registered[ $script_handle ]->extra['before']);
561
                $unset_handle = true;
562
            }
563
            if ($wp_scripts->get_data($script_handle, 'after')) {
564
                unset($wp_scripts->registered[ $script_handle ]->extra['after']);
565
            }
566
            if ($unset_handle) {
567
                unset($this->script_handles_with_data[ $script_handle ]);
568
            }
569
        }
570
    }
571
572
573
    /**
574
     * register translations for a registered script
575
     *
576
     * @param string $handle
577
     */
578
    public function registerTranslation($handle)
579
    {
580
        $this->i18n_registry->registerScriptI18n($handle);
581
    }
582
}
583