Completed
Branch BUG/chicken-is-a-required-ques... (f96ad9)
by
unknown
42:30 queued 28:14
created

Registry::localizeAccountingJs()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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