forked from gocodebox/lifterlms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclass-llms-assets.php
704 lines (591 loc) · 23 KB
/
class-llms-assets.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
<?php
/**
* Methods for static asset registration and enqueueing
*
* These methods require assets to be "defined" in a structured format.
*
* A defined asset is then enqueued or registered with the WordPress core using this derivative
* API that requires only script handles.
*
* This API also aims to reduce redundancy in asset registrations by allowing "partial" definitions
* which are filled with default values. For example, every asset in LifterLMS shares the same base
* plugin url. Using this API we define that URL one time, instead of defining it over and over for
* each individual asset.
*
* @package LifterLMS/Classes
*
* @since 4.4.0
* @version 7.2.0
*/
defined( 'ABSPATH' ) || exit;
/**
* LLMS_Assets Class
*
* @since 4.4.0
* @since 4.9.0 Added new default values related to script localization.
* @since 5.5.0 Added new script default for `asset_file`.
*/
class LLMS_Assets {
/**
* An ID used to identify the originating package (plugin or theme) of the asset handler instance.
*
* @var string
*/
protected $package_id = '';
/**
* Determines if SCRIPT_DEBUG is enabled.
*
* @var boolean
*/
protected $debugging_assets = false;
/**
* List of default asset definitions.
*
* @since 7.2.0 Use `LLMS_ASSETS_VERSION` for default asset version.
*
* @var array[]
*/
protected $defaults = array(
// Base defaults shared by all asset types.
'base' => array(
'base_file' => LLMS_PLUGIN_FILE,
'base_url' => LLMS_PLUGIN_URL,
'suffix' => LLMS_ASSETS_SUFFIX,
'dependencies' => array(),
'version' => LLMS_ASSETS_VERSION,
),
// Script specific defaults.
'script' => array(
'path' => 'assets/js',
'extension' => '.js',
'in_footer' => true,
'translate' => false,
'asset_file' => false,
),
// Stylesheet specific defaults.
'style' => array(
'path' => 'assets/css',
'extension' => '.css',
'media' => 'all',
'rtl' => true,
),
);
protected $inline = array();
/**
* List of defined scripts.
*
* The full list of core script definitions can be found at includes/assets/llms-assets-scripts.php
*
* @var array[]
*/
protected $scripts = array();
/**
* List of defined stylesheets.
*
* The full list of core stylesheet definitions can be found at includes/assets/llms-assets-styles.php
*
* @var array[]
*/
protected $styles = array();
/**
* Constructor
*
* @since 4.4.0
* @since 4.9.0 Replace defaults instead of merging them.
*
* @param string $package_id An ID used to identify the originating package (plugin or theme) of the asset handler instance.
* @param array[] $defaults Array of asset definitions values. Accepts a partial list of values that is merged with the default defaults.
*/
public function __construct( $package_id, $defaults = array() ) {
$this->package_id = $package_id;
$this->defaults = array_replace_recursive( $this->defaults, $defaults );
/**
* Filter asset debug mode.
*
* Asset debug mode is used only to help debug inline assets although the asset suffix is also controlled by the same
* WP Core constants.g
*
* @since 4.4.0
*
* @param bool $debugging Whether or not debugging is enabled. Returns `true` when `SCRIPT_DEBUG` is on, and `false` otherwise.
* @param string $package_id An ID used to identify the originating plugin or theme that defined the asset.
*/
$this->debugging_assets = apply_filters( 'llms_assets_debug', ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ), $this->package_id );
}
/**
* Define a list of assets by type so they can be enqueued or registered later.
*
* If an asset is already defined, redefining it will overwrite the previous definition.
*
* @since 4.4.0
*
* @param string $type Asset type. Accepts 'scripts' or 'styles'.
* @param array[] $assets List of assets to define. The array key is the asset's handle. Each array value is an array of asset definitions.
* @return array[] Returns the updated list of defined assets.
*/
public function define( $type, $assets ) {
if ( ! in_array( $type, array( 'scripts', 'styles' ), true ) ) {
return false;
}
foreach ( $assets as $handle => $definition ) {
$this->{$type}[ $handle ] = $definition;
}
return $this->$type;
}
/**
* Enqueue an inline script or style
*
* @since 4.4.0
*
* @param string $handle Inline asset ID.
* @param string $asset The inline script or CSS rule. This should *not* be wrapped in <script> or <style> tags.
* @param string $location Output location of the inline asset. Accepts "style" (for stylesheets in the headr), "header" (for
* scripts in the header), or "footer" (for scripts in the footer).
* @param int|float $priority Output priority of the inline asset.
* @return float Returns the priority of the enqueued script
*/
public function enqueue_inline( $handle, $asset, $location, $priority = 10 ) {
// If script already exists, remove it and re-enqueue.
if ( $this->is_inline_enqueued( $handle ) ) {
unset( $this->inline[ $handle ] );
}
$priority = $this->get_inline_priority( $priority, $this->get_definitions_inline( $location ) );
$this->inline[ $handle ] = compact( 'handle', 'asset', 'location', 'priority' );
return $priority;
}
/**
* Enqueue (and maybe register) a defined script
*
* If the script has not yet been registered, it will be automatically registered.
*
* The script *must* be defined in one of the following places:
*
* + The script definition list found at includes/assets/llms-assets-scripts.php
* + Added to the definitions list via the `LLMS_Assets::define()` method.
* + Added to the definition list via the `llms_get_script_asset_definitions` filter
* + Added "just in time" via the `llms_get_script_asset` filter.
*
* If the script is *not defined* this function will return `false` because registration
* will fail.
*
* @since 4.4.0
*
* @param string $handle The script's handle.
* @return boolean
*/
public function enqueue_script( $handle ) {
// Script was not registered and registration failed.
if ( ! wp_script_is( $handle, 'registered' ) && ! $this->register_script( $handle ) ) {
return false;
}
wp_enqueue_script( $handle );
return wp_script_is( $handle, 'enqueued' );
}
/**
* Enqueue (and maybe register) a defined stylesheet
*
* If the stylesheet has not yet been registered, it will be automatically registered.
*
* The stylesheet *must* be defined in one of the following places:
*
* + The stylesheet definition list found at includes/assets/llms-assets-styles.php
* + Added to the definitions list via the `LLMS_Assets::define()` method.
* + Added to the definition list via the `llms_get_style_asset_definitions` filter
* + Added "just in time" via the `llms_get_style_asset` filter.
*
* If the stylesheet is *not defined* this function will return `false` because registration
* will fail.
*
* @since 4.4.0
*
* @param string $handle The stylesheets's handle.
* @return boolean
*/
public function enqueue_style( $handle ) {
// Style was not registered and registration failed.
if ( ! wp_style_is( $handle, 'registered' ) && ! $this->register_style( $handle ) ) {
return false;
}
wp_enqueue_style( $handle );
return wp_style_is( $handle, 'enqueued' );
}
/**
* Retrieve an asset definition by type and handle
*
* Locates the asset by type and handle and merges a potentially impartial asset definition
* with default values from the `get_defaults()` method.
*
* @since 4.4.0
* @since 4.4.1 Replace truthy check with an strict check against `false` to ensure assets defined with an empty array signifying all default values should be used.
* @since 5.5.0 Load dependency and version info from an asset.php file when `$asset_file` is `true`.
*
* @param string $type The asset type. Accepts either "script" or "style".
* @param string $handle The asset handle.
* @return array|false {
* An asset definition array or `false` if an asset definition could not be located.
*
* @type string $file_name The file name of the asset. Excludes the path, suffix, and extension, eg: 'llms' for 'llms.js'. Defaults to the asset's handle.
* @type string $base_url The base URL used to locate the asset on the server. Defaults to `LLMS_PLUGIN_URL`.
* @type string $path The relative path to the asset within the plugin directory. Defaults to `assets/js` for scripts and `assets/css` for styles.
* @type string $extension The filename extension for the asset. Defaults to `.js` for scripts and `.css` for styles.
* @type string $suffix The file suffix for the asset, for example `.min` for minified files. Defaults to `LLMS_ASSETS_SUFFIX`.
* @type string[] $dependencies An array of asset handles the asset depends on. These assets do not necessarily need to be assets defined by LifterLMS, for example WP Core scripts, such as `jquery`, can be used.
* @type string $version The asset version. Defaults to `LLMS_ASSETS_VERSION`.
* @type string $package_id An ID used to identify the originating plugin or theme that defined the asset.
* @type boolean $in_footer (For `script` assets only) Whether or not the script should be output in the footer. Defaults to `true`.
* @type boolean $translate (For `script` assets only) Whether or not script translations should be set. Defaults to `false`.
* @type boolean $asset_file (For `script` assets only) Whether or not the script has an asset file (generated via the @wordpress/dependency-extraction-webpack-plugin).
* @type boolean $rtl (For `style` assets only) Whether or not to automatically add RTL style data for the stylesheet. Defaults to `true`.
* @type boolean $media (For `style` assets only) The stylesheet's media type. Defaults to `all`.
* }
*/
protected function get( $type, $handle ) {
$list = $this->get_definitions( $type );
$asset = isset( $list[ $handle ] ) ? $list[ $handle ] : false;
/**
* Filter static asset data prior to preparing the definition
*
* The definition is "prepared" by merging its data with the default data and preparing its src.
*
* The dynamic portion of this filter, `{$type}`, refers to the asset type. Either "script" or "style".
*
* @since 4.4.0
*
* @param array|false $asset Array of asset data or `false` if the asset has not been defined with LifterLMS.
* @param string $handle The asset handle.
* @param string $package_id An ID used to identify the originating plugin or theme that defined the asset.
*/
$asset = apply_filters( "llms_get_{$type}_asset_before_prep", $asset, $handle, $this->package_id );
if ( false !== $asset && is_array( $asset ) ) {
$asset = wp_parse_args( $asset, $this->get_defaults( $type ) );
$asset['handle'] = $handle;
$asset['package_id'] = $this->package_id;
$asset['file_name'] = ! empty( $asset['file_name'] ) ? $asset['file_name'] : $handle;
$asset['src'] = ! empty( $asset['src'] ) ? $asset['src'] : implode(
'',
array(
trailingslashit( $asset['base_url'] ),
trailingslashit( $asset['path'] ),
$asset['file_name'],
$asset['suffix'],
$asset['extension'],
)
);
$asset = $this->merge_asset_file( $asset );
}
/**
* Filter static asset data prior to enqueueing or registering it with the WordPress core
*
* The dynamic portion of this filter, `{$type}`, refers to the asset type. Either "script" or "style".
*
* @since 4.4.0
*
* @param array|false $asset Array of asset data or `false` if the asset has not been defined with LifterLMS.
* @param string $handle The asset handle.
*/
return apply_filters( "llms_get_{$type}_asset", $asset, $handle );
}
/**
* Retrieves an array of definition values based on asset type.
*
* @since 4.4.0
*
* @param string $type The asset type. Accepts either "script" or "style".
* @return array
*/
protected function get_defaults( $type ) {
$type_defaults = isset( $this->defaults[ $type ] ) ? $this->defaults[ $type ] : array();
$defaults = array_merge( $this->defaults['base'], $type_defaults );
/**
* Filter the default values used to register or enqueue an asset
*
* The dynamic portion of this filter, `{$type}`, refers to the asset type. Either "script" or "style".
*
* @since 4.4.0
*
* @param array $defaults Default definition values.
* @param string $package_id An ID used to identify the originating plugin or theme that defined the asset.
*/
return apply_filters( "llms_get_{$type}_asset_defaults", $defaults, $this->package_id );
}
/**
* Retrieve the asset definition list for a given asset type.
*
* @since 4.4.0
*
* @param string $type The asset type. Accepts either "script" or "style".
* @return array[]
*/
protected function get_definitions( $type ) {
switch ( $type ) {
case 'script':
$list = $this->scripts;
break;
case 'style':
$list = $this->styles;
break;
default:
$list = array();
}
/**
* Filter the definition list of static assets for the given type
*
* The dynamic portion of this filter, `{$type}`, refers to the asset type. Either "script" or "style".
*
* @since 4.4.0
*
* @param array[] $list The definition list.
* @param string $package_id An ID used to identify the originating plugin or theme that defined the asset.
*/
return apply_filters( "llms_get_{$type}_asset_definitions", $list, $this->package_id );
}
/**
* Retrieve a list of inline asset definitions by location.
*
* @since 4.4.0
*
* @param string $location Location of scripts to output. Accepts "style", "header", or "footer".
* Inline header styles are output using "style".
* Inline scripts are output using either "header" or "footer", output in their respective locations.
* @return array[]
*/
protected function get_definitions_inline( $location ) {
$assets = array();
foreach ( $this->inline as $handle => $definition ) {
if ( $location === $definition['location'] ) {
$assets[ $handle ] = $definition;
}
}
// Sort by priority.
uasort(
$assets,
function ( $a, $b ) {
if ( $a['priority'] === $b['priority'] ) {
return 0;
}
return $a['priority'] < $b['priority'] ? -1 : 1;
}
);
return $assets;
}
/**
* Auto-increment inline asset priority to prevent duplicates.
*
* This ensures that inline assets are always enqueued with a unique priority for their requested
* location.
*
* @since 4.4.0
* @since 7.0.0 When increasing priorities, round to the nearest two decimals.
*
* @param float $priority Requested enqueue priority.
* @param array $inline_assets List of existing inline assets for the requested location.
* @return float
*/
protected function get_inline_priority( $priority, $inline_assets = array() ) {
$priority = floatval( $priority );
if ( $inline_assets ) {
$priorities = wp_list_pluck( $inline_assets, 'priority' );
while ( in_array( $priority, $priorities, true ) ) {
$priority = round( $priority + 0.01, 2 );
}
}
return $priority;
}
/**
* Determines if an inline asset is enqueued
*
* @since 4.4.0
*
* @param string $handle Inline asset handle.
* @return boolean
*/
public function is_inline_enqueued( $handle ) {
return in_array( $handle, array_keys( $this->inline ), true );
}
/**
* Retrieve dependency and version info from a script asset's asset.php file
*
* Loads the asset.php file (generated via the @wordpress/dependency-extraction-webpack-plugin) and merges it
* into an existing asset array.
*
* @since 5.5.0
*
* @param array $asset An asset definition array.
* @return array
*/
protected function merge_asset_file( $asset ) {
if ( empty( $asset['asset_file'] ) ) {
return $asset;
}
$asset_file_path = plugin_dir_path( $asset['base_file'] ) . trailingslashit( $asset['path'] ) . $asset['file_name'] . '.asset.php';
if ( file_exists( $asset_file_path ) ) {
$info = include $asset_file_path;
$asset['dependencies'] = array_merge( $asset['dependencies'], $info['dependencies'] );
$asset['version'] = $info['version'];
}
return $asset;
}
/**
* Output inline scripts
*
* @since 4.4.0
*
* @param string $location Location of scripts to output. Accepts "style", "header", or "footer".
* Inline header styles are output using "style".
* Inline scripts are output using either "header" or "footer", output in their respective locations.
* @return void
*/
public function output_inline( $location ) {
$defs = self::get_definitions_inline( $location );
if ( $defs ) {
$assets = array();
foreach ( $defs as $def ) {
$assets[] = $this->prepare_inline_asset_for_output( $def, $location );
}
$open = 'style' === $location ? '<style id="llms-inline-styles" type="text/css">' : sprintf( '<script id="llms-inline-%s-scripts" type="text/javascript">', $location );
$close = 'style' === $location ? '</style>' : '</script>';
echo $open . implode( '', $assets ) . $close;
}
}
/**
* Prepares an inline asset definition for being output.
*
* When `$this->debugging_assets` is `true` this will add line breaks between each inline asset
* and output the asset's handle as a comment before the asset's script/style so that the
* inline assets can be quickly located and reviewed in the generated source of the page.
*
* @since 4.4.0
*
* @param array $asset The inline asset definition array.
* @param string $location The location of the asset. Accepts "header", "footer", or "style".
* @return string
*/
protected function prepare_inline_asset_for_output( $asset, $location ) {
$before = '';
$after = '';
// Output inline asset handles and add line breaks when debugging.
if ( $this->debugging_assets ) {
// Setup the comment template.
$before = 'style' === $location ? '/* %s. */' : '// %s.';
// Add line breaks.
$before .= "\n";
$after = "\n";
}
return sprintf( $before, $asset['handle'] ) . $asset['asset'] . $after;
}
/**
* Registers a defined script with WordPress
*
* The script *must* be defined in one of the following places:
*
* + The script definition list found at includes/assets/llms-assets-scripts.php
* + Added to the definition list via the `llms_get_script_asset_definitions` filter
* + Added "just in time" via the `llms_get_script_asset` filter.
*
* If the script is *not defined* this function will return `false`.
*
* @since 4.4.0
* @since 4.9.0 Automatically set script translations when `translate=true`.
* @since 5.5.0 Automatically register all of the asset's dependencies.
*
* @param string $handle The script's handle.
* @return boolean
*/
public function register_script( $handle ) {
$script = $this->get( 'script', $handle );
if ( $script ) {
array_map( array( $this, 'register_script' ), $script['dependencies'] );
$reg = wp_register_script( $handle, $script['src'], $script['dependencies'], $script['version'], $script['in_footer'] );
if ( $reg && $script['translate'] ) {
$this->set_script_translations( $script );
}
return $reg;
}
return false;
}
/**
* Register a defined stylesheet
*
* If the stylesheet has not yet been registered, it will be automatically registered.
*
* The stylesheet *must* be defined in one of the following places:
*
* + The stylesheet definition list found at includes/assets/llms-assets-styles.php
* + Added to the definition list via the `llms_get_style_asset_definitions` filter
* + Added "just in time" via the `llms_get_style_asset` filter.
*
* If the stylesheet is *not defined* this function will return `false`.
*
* This method will also automatically add RTL style data unless explicitly told not to do so.
*
* The RTL stylesheet should have the same name (and suffix) with `-rtl` included prior to the suffix, for example
* `llms.css` (or `llms.min.css`) would add the RTL stylesheet `llms-rtl.css` (or `llms-rtl.min.css`).
*
* @since 4.4.0
* @since 5.5.0 Automatically register all of the asset's dependencies.
*
* @param string $handle The stylesheets's handle.
* @return boolean
*/
public function register_style( $handle ) {
$style = $this->get( 'style', $handle );
if ( $style ) {
array_map( array( $this, 'register_style' ), $style['dependencies'] );
$reg = wp_register_style( $handle, $style['src'], $style['dependencies'], $style['version'], $style['media'] );
if ( $reg && $style['rtl'] ) {
wp_style_add_data( $handle, 'rtl', 'replace' );
wp_style_add_data( $handle, 'suffix', $style['suffix'] );
}
return $reg;
}
return false;
}
/**
* Load JSON format localization files for a registered script
*
* This method mimics the behavior of PO/MO pot files loaded for PHP localization.
*
* Language files can be found in the following locations (The first loaded file takes priority):
*
* 1. wp-content/languages/{$textdomain}/{$textdomain}-{$locale}-{$file_md5_hash}.json
*
* This is recommended "safe" location where custom language files can be stored. A file
* stored in this directory will never be automatically overwritten.
*
* 2. wp-content/languages/plugins/{$textdomain}-{$locale}-{$file_md5_hash}.json
*
* This is the default directory where WordPress will download language files from the
* WordPress GlotPress server during updates. If you store a custom language file in this
* directory it will be overwritten during updates.
*
* 3. wp-content/plugins/{$textdomain}/languages/{$textdomain}-{$locale}-{$file_md5_hash}.json
*
* This is the the LifterLMS plugin directory. A language file stored in this directory will
* be removed from the server during a LifterLMS plugin update.
*
* @since 4.9.0
*
* @param array $script An asset definition array from the return of `LLMS_Assets::get()`.
* @return void
*/
protected function set_script_translations( $script ) {
$plugin_data = get_plugin_data( $script['base_file'], false, false );
$domain = $plugin_data['TextDomain'];
// Setup the script's filename based on the md5 of it's relative path.
$relative_path = sprintf( '%1$s/%2$s%3$s', $script['path'], $script['file_name'], $script['extension'] );
$file = sprintf( '%1$s-%2$s-%3$s.json', $domain, llms_get_locale(), md5( $relative_path ) );
// Possible directories where the language files may be found.
$dirs = array(
llms_l10n_get_safe_directory(),
WP_LANG_DIR . '/plugins', // Default language directory.
trailingslashit( plugin_dir_path( $script['base_file'] ) ) . untrailingslashit( ltrim( $plugin_data['DomainPath'], '/' ) ), // Language directory within the plugin.
);
foreach ( $dirs as $dir ) {
// If the file exists, set the script translations.
if ( file_exists( sprintf( '%1$s/%2$s', $dir, $file ) ) ) {
wp_set_script_translations( $script['handle'], $domain, $dir );
break;
}
}
}
}