Completed
Branch BUG/11419/ical-line-endings (79c085)
by
unknown
25:29 queued 12:47
created

Registry::getCssUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 2
c 1
b 0
f 1
nc 1
nop 2
dl 0
loc 4
rs 10
1
<?php
2
3
namespace EventEspresso\core\services\assets;
4
5
use EE_Currency_Config;
6
use EE_Error;
7
use EE_Registry;
8
use EE_Template_Config;
9
use EEH_Qtip_Loader;
10
use EventEspresso\core\domain\DomainInterface;
11
use EventEspresso\core\exceptions\InvalidFilePathException;
12
use InvalidArgumentException;
13
14
defined('EVENT_ESPRESSO_VERSION') || exit;
15
16
17
18
/**
19
 * Used for registering assets used in EE.
20
 *
21
 * @package    EventEspresso
22
 * @subpackage services\assets
23
 * @author     Darren Ethier
24
 * @since      4.9.24.rc.004
25
 */
26
class Registry
27
{
28
29
    const ASSET_TYPE_CSS = 'css';
30
    const ASSET_TYPE_JS = 'js';
31
    const ASSET_NAMESPACE = 'core';
32
33
    /**
34
     * @var EE_Template_Config $template_config
35
     */
36
    protected $template_config;
37
38
    /**
39
     * @var EE_Currency_Config $currency_config
40
     */
41
    protected $currency_config;
42
43
    /**
44
     * This holds the jsdata data object that will be exposed on pages that enqueue the `eejs-core` script.
45
     *
46
     * @var array
47
     */
48
    protected $jsdata = array();
49
50
51
    /**
52
     * This keeps track of all scripts with registered data.  It is used to prevent duplicate data objects setup in the
53
     * page source.
54
     * @var array
55
     */
56
    protected $script_handles_with_data = array();
57
58
59
    /**
60
     * @var DomainInterface
61
     */
62
    protected $domain;
63
64
65
    /**
66
     * @var I18nRegistry
67
     */
68
    private $i18n_registry;
69
70
71
72
    /**
73
     * Holds the manifest data obtained from registered manifest files.
74
     * Manifests are maps of asset chunk name to actual built asset file names.
75
     * Shape of this array is:
76
     *
77
     * array(
78
     *  'some_namespace_slug' => array(
79
     *      'some_chunk_name' => array(
80
     *          'js' => 'filename.js'
81
     *          'css' => 'filename.js'
82
     *      ),
83
     *      'url_base' => 'https://baseurl.com/to/assets
84
     *  )
85
     * )
86
     *
87
     * @var array
88
     */
89
    private $manifest_data = array();
90
91
92
    /**
93
     * Registry constructor.
94
     * Hooking into WP actions for script registry.
95
     *
96
     * @param EE_Template_Config $template_config
97
     * @param EE_Currency_Config $currency_config
98
     * @param I18nRegistry       $i18n_registry
99
     * @param DomainInterface    $domain
100
     * @throws InvalidArgumentException
101
     * @throws InvalidFilePathException
102
     */
103
    public function __construct(
104
        EE_Template_Config $template_config,
105
        EE_Currency_Config $currency_config,
106
        I18nRegistry $i18n_registry,
107
        DomainInterface $domain
108
    ) {
109
        $this->template_config = $template_config;
110
        $this->currency_config = $currency_config;
111
        $this->domain = $domain;
112
        $this->i18n_registry = $i18n_registry;
113
        $this->registerManifestFile(
114
            self::ASSET_NAMESPACE,
115
            $this->domain->distributionAssetsUrl(),
116
            $this->domain->distributionAssetsPath() . 'build-manifest.json'
117
        );
118
        add_action('wp_enqueue_scripts', array($this, 'scripts'), 1);
119
        add_action('admin_enqueue_scripts', array($this, 'scripts'), 1);
120
        add_action('wp_enqueue_scripts', array($this, 'enqueueData'), 2);
121
        add_action('admin_enqueue_scripts', array($this, 'enqueueData'), 2);
122
        add_action('wp_print_footer_scripts', array($this, 'enqueueData'), 1);
123
        add_action('admin_print_footer_scripts', array($this, 'enqueueData'), 1);
124
    }
125
126
127
    /**
128
     * For classes that have Registry as a dependency, this provides a handy way to register script handles for i18n
129
     * translation handling.
130
     *
131
     * @return I18nRegistry
132
     */
133
    public function getI18nRegistry()
134
    {
135
        return $this->i18n_registry;
136
    }
137
138
    /**
139
     * Callback for the WP script actions.
140
     * Used to register globally accessible core scripts.
141
     * Also used to add the eejs.data object to the source for any js having eejs-core as a dependency.
142
     *
143
     */
144
    public function scripts()
145
    {
146
        global $wp_version;
147
        wp_register_script(
148
            'ee-manifest',
149
            $this->getJsUrl(self::ASSET_NAMESPACE, 'manifest'),
150
            array(),
151
            null,
152
            true
153
        );
154
        wp_register_script(
155
            'eejs-core',
156
            $this->getJsUrl(self::ASSET_NAMESPACE, 'eejs'),
157
            array('ee-manifest'),
158
            null,
159
            true
160
        );
161
        wp_register_script(
162
            'ee-vendor-react',
163
            $this->getJsUrl(self::ASSET_NAMESPACE, 'reactVendor'),
164
            array('eejs-core'),
165
            null,
166
            true
167
        );
168
        //only run this if WordPress 4.4.0 > is in use.
169
        if (version_compare($wp_version, '4.4.0', '>')) {
170
            //js.api
171
            wp_register_script(
172
                'eejs-api',
173
                EE_LIBRARIES_URL . 'rest_api/assets/js/eejs-api.min.js',
174
                array('underscore', 'eejs-core'),
175
                EVENT_ESPRESSO_VERSION,
176
                true
177
            );
178
            $this->jsdata['eejs_api_nonce'] = wp_create_nonce('wp_rest');
179
            $this->jsdata['paths'] = array('rest_route' => rest_url('ee/v4.8.36/'));
180
        }
181
        if (! is_admin()) {
182
            $this->loadCoreCss();
183
        }
184
        $this->registerTranslationsForHandles(array('eejs-core'));
185
        $this->loadCoreJs();
186
        $this->loadJqueryValidate();
187
        $this->loadAccountingJs();
188
        $this->loadQtipJs();
189
        $this->registerAdminAssets();
190
    }
191
192
193
194
    /**
195
     * Call back for the script print in frontend and backend.
196
     * Used to call wp_localize_scripts so that data can be added throughout the runtime until this later hook point.
197
     *
198
     * @since 4.9.31.rc.015
199
     */
200
    public function enqueueData()
201
    {
202
        $this->removeAlreadyRegisteredDataForScriptHandles();
203
        wp_add_inline_script(
204
            'eejs-core',
205
            'var eejsdata=' . wp_json_encode(array('data' => $this->jsdata)),
206
            'before'
207
        );
208
        wp_localize_script('espresso_core', 'eei18n', EE_Registry::$i18n_js_strings);
209
        $this->localizeAccountingJs();
210
        $this->addRegisteredScriptHandlesWithData('eejs-core');
211
        $this->addRegisteredScriptHandlesWithData('espresso_core');
212
    }
213
214
215
216
    /**
217
     * Used to add data to eejs.data object.
218
     * Note:  Overriding existing data is not allowed.
219
     * Data will be accessible as a javascript object when you list `eejs-core` as a dependency for your javascript.
220
     * If the data you add is something like this:
221
     *  $this->addData( 'my_plugin_data', array( 'foo' => 'gar' ) );
222
     * It will be exposed in the page source as:
223
     *  eejs.data.my_plugin_data.foo == gar
224
     *
225
     * @param string       $key   Key used to access your data
226
     * @param string|array $value Value to attach to key
227
     * @throws InvalidArgumentException
228
     */
229
    public function addData($key, $value)
230
    {
231
        if ($this->verifyDataNotExisting($key)) {
232
            $this->jsdata[$key] = $value;
233
        }
234
    }
235
236
237
238
    /**
239
     * Similar to addData except this allows for users to push values to an existing key where the values on key are
240
     * elements in an array.
241
     * When you use this method, the value you include will be appended to the end of an array on $key.
242
     * So if the $key was 'test' and you added a value of 'my_data' then it would be represented in the javascript
243
     * object like this, eejs.data.test = [ my_data,
244
     * ]
245
     * If there has already been a scalar value attached to the data object given key, then
246
     * this will throw an exception.
247
     *
248
     * @param string       $key   Key to attach data to.
249
     * @param string|array $value Value being registered.
250
     * @throws InvalidArgumentException
251
     */
252
    public function pushData($key, $value)
253
    {
254 View Code Duplication
        if (isset($this->jsdata[$key])
255
            && ! is_array($this->jsdata[$key])
256
        ) {
257
            throw new invalidArgumentException(
258
                sprintf(
259
                    __(
260
                        'The value for %1$s is already set and it is not an array. The %2$s method can only be used to
261
                         push values to this data element when it is an array.',
262
                        'event_espresso'
263
                    ),
264
                    $key,
265
                    __METHOD__
266
                )
267
            );
268
        }
269
        $this->jsdata[$key][] = $value;
270
    }
271
272
273
274
    /**
275
     * Used to set content used by javascript for a template.
276
     * Note: Overrides of existing registered templates are not allowed.
277
     *
278
     * @param string $template_reference
279
     * @param string $template_content
280
     * @throws InvalidArgumentException
281
     */
282
    public function addTemplate($template_reference, $template_content)
283
    {
284
        if (! isset($this->jsdata['templates'])) {
285
            $this->jsdata['templates'] = array();
286
        }
287
        //no overrides allowed.
288
        if (isset($this->jsdata['templates'][$template_reference])) {
289
            throw new invalidArgumentException(
290
                sprintf(
291
                    __(
292
                        'The %1$s key already exists for the templates array in the js data array.  No overrides are allowed.',
293
                        'event_espresso'
294
                    ),
295
                    $template_reference
296
                )
297
            );
298
        }
299
        $this->jsdata['templates'][$template_reference] = $template_content;
300
    }
301
302
303
304
    /**
305
     * Retrieve the template content already registered for the given reference.
306
     *
307
     * @param string $template_reference
308
     * @return string
309
     */
310
    public function getTemplate($template_reference)
311
    {
312
        return isset($this->jsdata['templates'], $this->jsdata['templates'][$template_reference])
313
            ? $this->jsdata['templates'][$template_reference]
314
            : '';
315
    }
316
317
318
319
    /**
320
     * Retrieve registered data.
321
     *
322
     * @param string $key Name of key to attach data to.
323
     * @return mixed                If there is no for the given key, then false is returned.
324
     */
325
    public function getData($key)
326
    {
327
        return isset($this->jsdata[$key])
328
            ? $this->jsdata[$key]
329
            : false;
330
    }
331
332
333
    /**
334
     * Get the actual asset path for asset manifests.
335
     * If there is no asset path found for the given $chunk_name, then the $chunk_name is returned.
336
     * @param string $namespace  The namespace associated with the manifest file hosting the map of chunk_name to actual
337
     *                           asset file location.
338
     * @param string $chunk_name
339
     * @param string $asset_type
340
     * @return string
341
     * @since 4.9.59.p
342
     */
343
    public function getAssetUrl($namespace, $chunk_name, $asset_type)
344
    {
345
        $url = isset(
346
            $this->manifest_data[$namespace][$chunk_name][$asset_type],
347
            $this->manifest_data[$namespace]['url_base']
348
        )
349
            ? $this->manifest_data[$namespace]['url_base']
350
              . $this->manifest_data[$namespace][$chunk_name][$asset_type]
351
            : $chunk_name;
352
        return apply_filters(
353
            'FHEE__EventEspresso_core_services_assets_Registry__getAssetUrl',
354
            $url,
355
            $namespace,
356
            $chunk_name,
357
            $asset_type
358
        );
359
    }
360
361
362
    /**
363
     * Return the url to a js file for the given namespace and chunk name.
364
     *
365
     * @param string $namespace
366
     * @param string $chunk_name
367
     * @return string
368
     */
369
    public function getJsUrl($namespace, $chunk_name)
370
    {
371
        return $this->getAssetUrl($namespace, $chunk_name, self::ASSET_TYPE_JS);
372
    }
373
374
375
    /**
376
     * Return the url to a css file for the given namespace and chunk name.
377
     *
378
     * @param string $namespace
379
     * @param string $chunk_name
380
     * @return string
381
     */
382
    public function getCssUrl($namespace, $chunk_name)
383
    {
384
        return $this->getAssetUrl($namespace, $chunk_name, self::ASSET_TYPE_CSS);
385
    }
386
387
388
    /**
389
     * Used to register a js/css manifest file with the registered_manifest_files property.
390
     *
391
     * @param string $namespace     Provided to associate the manifest file with a specific namespace.
392
     * @param string $url_base      The url base for the manifest file location.
393
     * @param string $manifest_file The absolute path to the manifest file.
394
     * @throws InvalidArgumentException
395
     * @throws InvalidFilePathException
396
     * @since 4.9.59.p
397
     */
398
    public function registerManifestFile($namespace, $url_base, $manifest_file)
399
    {
400
        if (isset($this->manifest_data[$namespace])) {
401
            throw new InvalidArgumentException(
402
                sprintf(
403
                    esc_html__(
404
                        'The namespace for this manifest file has already been registered, choose a namespace other than %s',
405
                        'event_espresso'
406
                    ),
407
                    $namespace
408
                )
409
            );
410
        }
411
        if (filter_var($url_base, FILTER_VALIDATE_URL) === false) {
412
            if (is_admin()) {
413
                EE_Error::add_error(
414
                    sprintf(
415
                        esc_html__(
416
                            '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',
417
                            'event_espresso'
418
                        ),
419
                        'Event Espresso',
420
                        $url_base,
421
                        'plugins_url',
422
                        'WP_PLUGIN_URL'
423
                    ),
424
                    __FILE__,
425
                    __FUNCTION__,
426
                    __LINE__
427
                );
428
            }
429
            return;
430
        }
431
        $this->manifest_data[$namespace] = $this->decodeManifestFile($manifest_file);
432
        if (! isset($this->manifest_data[$namespace]['url_base'])) {
433
            $this->manifest_data[$namespace]['url_base'] = trailingslashit($url_base);
434
        }
435
    }
436
437
438
439
    /**
440
     * Decodes json from the provided manifest file.
441
     *
442
     * @since 4.9.59.p
443
     * @param string $manifest_file Path to manifest file.
444
     * @return array
445
     * @throws InvalidFilePathException
446
     */
447
    private function decodeManifestFile($manifest_file)
448
    {
449
        if (! file_exists($manifest_file)) {
450
            throw new InvalidFilePathException($manifest_file);
451
        }
452
        return json_decode(file_get_contents($manifest_file), true);
453
    }
454
455
456
457
    /**
458
     * Verifies whether the given data exists already on the jsdata array.
459
     * Overriding data is not allowed.
460
     *
461
     * @param string $key Index for data.
462
     * @return bool        If valid then return true.
463
     * @throws InvalidArgumentException if data already exists.
464
     */
465
    protected function verifyDataNotExisting($key)
466
    {
467
        if (isset($this->jsdata[$key])) {
468 View Code Duplication
            if (is_array($this->jsdata[$key])) {
469
                throw new InvalidArgumentException(
470
                    sprintf(
471
                        __(
472
                            'The value for %1$s already exists in the Registry::eejs object.
473
                            Overrides are not allowed. Since the value of this data is an array, you may want to use the
474
                            %2$s method to push your value to the array.',
475
                            'event_espresso'
476
                        ),
477
                        $key,
478
                        'pushData()'
479
                    )
480
                );
481
            }
482
            throw new InvalidArgumentException(
483
                sprintf(
484
                    __(
485
                        'The value for %1$s already exists in the Registry::eejs object. Overrides are not
486
                        allowed.  Consider attaching your value to a different key',
487
                        'event_espresso'
488
                    ),
489
                    $key
490
                )
491
            );
492
        }
493
        return true;
494
    }
495
496
497
498
    /**
499
     * registers core default stylesheets
500
     */
501
    private function loadCoreCss()
502
    {
503
        if ($this->template_config->enable_default_style) {
504
            $default_stylesheet_path = is_readable(EVENT_ESPRESSO_UPLOAD_DIR . 'css/style.css')
505
                ? EVENT_ESPRESSO_UPLOAD_DIR . 'css/espresso_default.css'
506
                : EE_GLOBAL_ASSETS_URL . 'css/espresso_default.css';
507
            wp_register_style(
508
                'espresso_default',
509
                $default_stylesheet_path,
510
                array('dashicons'),
511
                EVENT_ESPRESSO_VERSION
512
            );
513
            //Load custom style sheet if available
514
            if ($this->template_config->custom_style_sheet !== null) {
515
                wp_register_style(
516
                    'espresso_custom_css',
517
                    EVENT_ESPRESSO_UPLOAD_URL . 'css/' . $this->template_config->custom_style_sheet,
518
                    array('espresso_default'),
519
                    EVENT_ESPRESSO_VERSION
520
                );
521
            }
522
        }
523
    }
524
525
526
527
    /**
528
     * registers core default javascript
529
     */
530
    private function loadCoreJs()
531
    {
532
        // load core js
533
        wp_register_script(
534
            'espresso_core',
535
            EE_GLOBAL_ASSETS_URL . 'scripts/espresso_core.js',
536
            array('jquery'),
537
            EVENT_ESPRESSO_VERSION,
538
            true
539
        );
540
    }
541
542
543
544
    /**
545
     * registers jQuery Validate for form validation
546
     */
547
    private function loadJqueryValidate()
548
    {
549
        // register jQuery Validate and additional methods
550
        wp_register_script(
551
            'jquery-validate',
552
            EE_GLOBAL_ASSETS_URL . 'scripts/jquery.validate.min.js',
553
            array('jquery'),
554
            '1.15.0',
555
            true
556
        );
557
        wp_register_script(
558
            'jquery-validate-extra-methods',
559
            EE_GLOBAL_ASSETS_URL . 'scripts/jquery.validate.additional-methods.min.js',
560
            array('jquery', 'jquery-validate'),
561
            '1.15.0',
562
            true
563
        );
564
    }
565
566
567
568
    /**
569
     * registers accounting.js for performing client-side calculations
570
     */
571
    private function loadAccountingJs()
572
    {
573
        //accounting.js library
574
        // @link http://josscrowcroft.github.io/accounting.js/
575
        wp_register_script(
576
            'ee-accounting-core',
577
            EE_THIRD_PARTY_URL . 'accounting/accounting.js',
578
            array('underscore'),
579
            '0.3.2',
580
            true
581
        );
582
        wp_register_script(
583
            'ee-accounting',
584
            EE_GLOBAL_ASSETS_URL . 'scripts/ee-accounting-config.js',
585
            array('ee-accounting-core'),
586
            EVENT_ESPRESSO_VERSION,
587
            true
588
        );
589
    }
590
591
592
593
    /**
594
     * registers accounting.js for performing client-side calculations
595
     */
596
    private function localizeAccountingJs()
597
    {
598
        wp_localize_script(
599
            'ee-accounting',
600
            'EE_ACCOUNTING_CFG',
601
            array(
602
                'currency' => array(
603
                    'symbol'    => $this->currency_config->sign,
604
                    'format'    => array(
605
                        'pos'  => $this->currency_config->sign_b4 ? '%s%v' : '%v%s',
606
                        'neg'  => $this->currency_config->sign_b4 ? '- %s%v' : '- %v%s',
607
                        'zero' => $this->currency_config->sign_b4 ? '%s--' : '--%s',
608
                    ),
609
                    'decimal'   => $this->currency_config->dec_mrk,
610
                    'thousand'  => $this->currency_config->thsnds,
611
                    'precision' => $this->currency_config->dec_plc,
612
                ),
613
                'number'   => array(
614
                    'precision' => $this->currency_config->dec_plc,
615
                    'thousand'  => $this->currency_config->thsnds,
616
                    'decimal'   => $this->currency_config->dec_mrk,
617
                ),
618
            )
619
        );
620
        $this->addRegisteredScriptHandlesWithData('ee-accounting');
621
    }
622
623
624
625
    /**
626
     * registers assets for cleaning your ears
627
     */
628
    private function loadQtipJs()
629
    {
630
        // qtip is turned OFF by default, but prior to the wp_enqueue_scripts hook,
631
        // can be turned back on again via: add_filter('FHEE_load_qtip', '__return_true' );
632
        if (apply_filters('FHEE_load_qtip', false)) {
633
            EEH_Qtip_Loader::instance()->register_and_enqueue();
634
        }
635
    }
636
637
638
    /**
639
     * This is used to set registered script handles that have data.
640
     * @param string $script_handle
641
     */
642
    private function addRegisteredScriptHandlesWithData($script_handle)
643
    {
644
        $this->script_handles_with_data[$script_handle] = $script_handle;
645
    }
646
647
648
    /**i
649
     * Checks WP_Scripts for all of each script handle registered internally as having data and unsets from the
650
     * Dependency stored in WP_Scripts if its set.
651
     */
652
    private function removeAlreadyRegisteredDataForScriptHandles()
653
    {
654
        if (empty($this->script_handles_with_data)) {
655
            return;
656
        }
657
        foreach ($this->script_handles_with_data as $script_handle) {
658
            $this->removeAlreadyRegisteredDataForScriptHandle($script_handle);
659
        }
660
    }
661
662
663
    /**
664
     * Removes any data dependency registered in WP_Scripts if its set.
665
     * @param string $script_handle
666
     */
667
    private function removeAlreadyRegisteredDataForScriptHandle($script_handle)
668
    {
669
        if (isset($this->script_handles_with_data[$script_handle])) {
670
            global $wp_scripts;
671
            $unset_handle = false;
672 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'data')) {
673
                unset($wp_scripts->registered[$script_handle]->extra['data']);
674
                $unset_handle = true;
675
            }
676
            //deal with inline_scripts
677 View Code Duplication
            if ($wp_scripts->get_data($script_handle, 'before')) {
678
                unset($wp_scripts->registered[$script_handle]->extra['before']);
679
                $unset_handle = true;
680
            }
681
            if ($wp_scripts->get_data($script_handle, 'after')) {
682
                unset($wp_scripts->registered[$script_handle]->extra['after']);
683
            }
684
            if ($unset_handle) {
685
                unset($this->script_handles_with_data[$script_handle]);
686
            }
687
        }
688
    }
689
690
691
    /**
692
     * Registers assets that are used in the WordPress admin.
693
     */
694
    private function registerAdminAssets()
695
    {
696
        wp_register_script(
697
            'ee-wp-plugins-page',
698
            $this->getJsUrl(self::ASSET_NAMESPACE, 'wp-plugins-page'),
699
            array(
700
                'jquery',
701
                'ee-vendor-react'
702
            ),
703
            null,
704
            true
705
        );
706
        wp_register_style(
707
            'ee-wp-plugins-page',
708
            $this->getCssUrl(self::ASSET_NAMESPACE, 'wp-plugins-page'),
709
            array(),
710
            null
711
        );
712
        $this->registerTranslationsForHandles(array('ee-wp-plugins-page'));
713
    }
714
715
716
    /**
717
     * All handles that are registered via the registry that might have translations have their translations registered
718
     *
719
     * @param array $handles_to_register
720
     */
721
    private function registerTranslationsForHandles(array $handles_to_register)
722
    {
723
        foreach($handles_to_register as $handle) {
724
            $this->i18n_registry->registerScriptI18n($handle);
725
        }
726
    }
727
}
728