diff --git a/README.md b/README.md index 70045c8..093703c 100644 --- a/README.md +++ b/README.md @@ -427,6 +427,79 @@ Asset::add( 'my-asset', 'css/some-asset.css' ) ->register(); ``` +If you specify an object name using dot notation, then the object will be printed on the page "merging" it with other, pre-existing objects. +In the following example, the `boomshakalaka.project` object will be created and then the `firstScriptData` and `secondScriptData` objects will be added to it: + +```php +use Boomshakalaka\StellarWP\Assets\Asset; + +Asset::add( 'my-first-script', 'js/first-script.js' ) + ->add_localize_script( + 'boomshakalaka.project.firstScriptData', + [ + 'animal' => 'cat', + 'color' => 'orange', + ] + ) + ->register(); + +Asset::add( 'my-second-script', 'js/second-script.js' ) + ->add_localize_script( + 'boomshakalaka.project.secondScriptData', + [ + 'animal' => 'dog', + 'color' => 'green', + ] + ) + ->register(); + +Asset::add( 'my-second-script-mod', 'js/second-script-mod.js' ) + ->add_localize_script( + 'boomshakalaka.project.secondScriptData', + [ + 'animal' => 'horse' + ] + ) + ->register(); +``` + +The resulting output will be: + +```html + + + + + + +``` + +Note the `my-second-script-mod` handle is overriding a specific nested +key, `boomshakalaka.project.secondScriptData.animal`, in the `boomshakalaka.project.secondScriptData` object while +preserving the other keys. + ### Output content before/after a JS asset is output There may be times when you wish to output markup or text immediately before or immediately after outputting the JS diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index ab82dcb..d93b479 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -115,7 +115,7 @@ class Asset { /** * The asset wp_localize_script objects for this asset. * - * @var array + * @var array */ protected array $wp_localize_script_objects = []; @@ -217,6 +217,13 @@ class Asset { */ protected ?string $version = null; + /** + * An array of objects to localized using dot-notation and namespaces. + * + * @var array + */ + protected array $custom_localize_script_objects = []; + /** * Constructor. * @@ -412,7 +419,12 @@ public function call_after_enqueue( $callable ) { * @return static */ public function add_localize_script( string $object_name, array $data ) { - $this->wp_localize_script_objects[ $object_name ] = $data; + if ( str_contains( $object_name, '.' ) ) { + $this->custom_localize_script_objects[] = [ $object_name, $data ]; + } else { + $this->wp_localize_script_objects[ $object_name ] = $data; + } + return $this; } @@ -520,6 +532,15 @@ public function get_localize_scripts(): array { return $this->wp_localize_script_objects; } + /** + * Get the asset wp_localize_script_objects. + * + * @return array A set of data to localized using dot-notation. + */ + public function get_custom_localize_scripts(): array { + return $this->custom_localize_script_objects; + } + /** * Get the asset media setting. * diff --git a/src/Assets/Assets.php b/src/Assets/Assets.php index fb0da65..afdf42e 100755 --- a/src/Assets/Assets.php +++ b/src/Assets/Assets.php @@ -48,11 +48,39 @@ class Assets { protected $localized = []; /** - * Singleton instance. + * Constructor. + * + * @param string|null $base_path Base path to the directory. + * @param string|null $assets_url Directory to the assets. + * + * @since 1.0.0 + * + */ + public function __construct( ?string $base_path = null, ?string $assets_url = null ) { + $this->base_path = $base_path ?: Config::get_path(); + $this->assets_url = $assets_url ?: trailingslashit( get_site_url() . $this->base_path ); + $this->version = Config::get_version(); + $this->controller = new Controller( $this ); + $this->controller->register(); + } + + /** + * Helper method to get the instance object. Alias of ::init(). * + * @return Assets * @since 1.0.0 * + */ + public static function instance(): Assets { + return static::init(); + } + + /** + * Singleton instance. + * * @return Assets + * @since 1.0.0 + * */ public static function init(): Assets { if ( ! isset( static::$instance ) ) { @@ -63,42 +91,66 @@ public static function init(): Assets { } /** - * Helper method to get the instance object. Alias of ::init(). + * Create an asset. + * + * @param string $slug The asset slug. + * @param string $file The asset file path. + * @param string|null $version The asset version. + * @param string|null $plugin_path The path to the root of the plugin. + */ + public static function asset( string $slug, string $file, string $version = null, string $plugin_path = null ) { + return static::init()->add( new Asset( $slug, $file, $version, $plugin_path ) ); + } + + /** + * Register an Asset and attach a callback to the required action to display it correctly. * + * @param Asset $asset Register an asset. + * + * @return Asset|false The registered object or false on error. * @since 1.0.0 * - * @return Assets */ - public static function instance(): Assets { - return static::init(); + public function add( Asset $asset ) { + // Prevent weird stuff here. + $slug = $asset->get_slug(); + + if ( $this->exists( $slug ) ) { + return $this->get( $slug ); + } + + // Set the Asset on the array of notices. + $this->assets[ $slug ] = $asset; + + // Return the Slug because it might be modified. + return $asset; } /** - * Constructor. + * Checks if an Asset exists. + * + * @param string|array $slug Slug of the Asset. * + * @return bool * @since 1.0.0 * - * @param string|null $base_path Base path to the directory. - * @param string|null $assets_url Directory to the assets. */ - public function __construct( ?string $base_path = null, ?string $assets_url = null ) { - $this->base_path = $base_path ?: Config::get_path(); - $this->assets_url = $assets_url ?: trailingslashit( get_site_url() . $this->base_path ); - $this->version = Config::get_version(); - $this->controller = new Controller( $this ); - $this->controller->register(); + public function exists( $slug ) { + $slug = sanitize_key( $slug ); + + return isset( $this->assets[ $slug ] ); } /** * Depending on how certain scripts are loaded and how much cross-compatibility is required we need to be able to * create noConflict backups and restore other scripts, which normally need to be printed directly on the scripts. * - * @since 1.0.0 - * - * @param string $tag Tag we are filtering. + * @param string $tag Tag we are filtering. * @param string $handle Which is the ID/Handle of the tag we are about to print. * * @return string Script tag with the before and after strings attached to it. + * @since 1.0.0 + * */ public function filter_print_before_after_script( $tag, $handle ): string { // Only filter for our own filters. @@ -134,15 +186,137 @@ public function filter_print_before_after_script( $tag, $handle ): string { } /** - * Handles adding localization data, when attached to `script_loader_tag` which allows dependencies to load in their - * localization data as well. + * Get the Asset Object configuration. + * + * @param string|array $slug Slug of the Asset. + * @param boolean $sort If we should do any sorting before returning. + * + * @return array|Asset Array of asset objects, single asset object, or null if looking for a single asset but + * it was not in the array of objects. + * @since 1.0.0 + * + */ + public function get( $slug = null, $sort = true ) { + $obj = $this; + + if ( is_null( $slug ) ) { + if ( $sort ) { + $cache_key_count = __METHOD__ . ':count'; + // Sorts by priority. + $cache_count = $this->get_var( $cache_key_count, 0 ); + $count = count( $this->assets ); + + if ( $count !== $cache_count ) { + uasort( $this->assets, static function ( $a, $b ) use ( $obj ) { + return $obj->sort_by_priority( $a, $b, 'get_priority' ); + } ); + $this->set_var( $cache_key_count, $count ); + } + } + + return $this->assets; + } + + // If slug is an array we return all of those. + if ( is_array( $slug ) ) { + $assets = []; + foreach ( $slug as $asset_slug ) { + $asset_slug = sanitize_key( $asset_slug ); + // Skip empty assets. + if ( empty( $this->assets[ $asset_slug ] ) ) { + continue; + } + + $assets[ $asset_slug ] = $this->assets[ $asset_slug ]; + } + + if ( empty( $assets ) ) { + return []; + } + + if ( $sort ) { + // Sorts by priority. + uasort( $assets, static function ( $a, $b ) use ( $obj ) { + return $obj->sort_by_priority( $a, $b, 'get_priority' ); + } ); + } + + return $assets; + } + + // Prevent weird stuff here. + $slug = sanitize_key( $slug ); + + if ( ! empty( $this->assets[ $slug ] ) ) { + return $this->assets[ $slug ]; + } + + return []; + } + + /** + * Gets a memoized value. + * + * @param string $var Var name. + * @param mixed|null $default Default value. + * + * @return mixed|null + */ + public function get_var( string $var, $default = null ) { + return $this->memoized[ $var ] ?? $default; + } + + /** + * Sorting function based on Priority + * + * @param object|array $b Second subject to compare. + * @param object|array $a First Subject to compare. + * @param string $method Method to use for sorting. * + * @return int * @since 1.0.0 * - * @param string $tag Tag we are filtering. + */ + public function sort_by_priority( $a, $b, $method = null ) { + if ( is_array( $a ) ) { + $a_priority = $a['priority']; + } else { + $a_priority = $method ? $a->$method() : $a->priority; + } + + if ( is_array( $b ) ) { + $b_priority = $b['priority']; + } else { + $b_priority = $method ? $b->$method() : $b->priority; + } + + if ( (int) $a_priority === (int) $b_priority ) { + return 0; + } + + return (int) $a_priority > (int) $b_priority ? 1 : - 1; + } + + /** + * Sets a memoized value. + * + * @param string $var Var name. + * @param mixed|null $value The value. + */ + public function set_var( string $var, $value = null ) { + $this->memoized[ $var ] = $value; + } + + /** + * Handles adding localization data, when attached to `script_loader_tag` which allows dependencies to load in their + * localization data as well. + * + * @param string $tag Tag we are filtering. * @param string $handle Which is the ID/Handle of the tag we are about to print. * * @return string Script tag with the localization variable HTML attached to it. + * @since 1.0.0 + * */ public function filter_add_localization_data( $tag, $handle ) { // Only filter for own filters. @@ -155,56 +329,95 @@ public function filter_add_localization_data( $tag, $handle ) { return $tag; } + $localize_scripts = $asset->get_localize_scripts(); + $custom_localize_scripts = $asset->get_custom_localize_scripts(); + // Only localize on JS and if we have data. - if ( empty( $asset->get_localize_scripts() ) ) { + if ( empty( $localize_scripts ) && empty( $custom_localize_scripts ) ) { return $tag; } - global $wp_scripts; + $localization_html = ''; - $localization = $asset->get_localize_scripts(); + if ( count( $localize_scripts ) ) { + global $wp_scripts; - /** - * Check to ensure we haven't already localized it before. - * - * @since 1.0.0 + $localization = $localize_scripts; + + /** + * Check to ensure we haven't already localized it before. + * + * @since 1.0.0 + */ + foreach ( $localization as $key => $localize ) { + + if ( in_array( $key, $this->localized ) ) { + continue; + } + + // If we have a Callable as the Localize data we execute it. + if ( is_callable( $localize ) ) { + $localize = call_user_func( $localize, $asset ); + } + + wp_localize_script( $asset->get_slug(), $key, $localize ); + + $this->localized[] = $key; + } + + // Fetch the HTML for all the localized data. + ob_start(); + $wp_scripts->print_extra_script( $asset->get_slug(), true ); + $localization_html = ob_get_clean(); + + // After printing it remove data;| + $wp_scripts->add_data( $asset->get_slug(), 'data', '' ); + } + + /* + * Print the dot.notation namespaced objects for the asset. */ - foreach ( $localization as $key => $localize ) { + foreach ( $custom_localize_scripts as [$object_name, $data] ) { + $localized_key = "{$asset->get_slug()}::{$object_name}"; - if ( in_array( $key, $this->localized ) ) { + if ( in_array( $localized_key, $this->localized, true ) ) { continue; } - // If we have a Callable as the Localize data we execute it. - if ( is_callable( $localize ) ) { - $localize = call_user_func( $localize, $asset ); + $frags = explode( '.', $object_name ); + $js_data = ''; + $var_name = ''; + foreach ( $frags as $i => $frag ) { + $var_name = ltrim( $var_name . '.' . $frag, '.' ); + if ( isset( $frags[ $i + 1 ] ) ) { + $js_data .= PHP_EOL . sprintf( 'window.%1$s = window.%1$s || {};', $var_name ); + } else { + $json_data = wp_json_encode( $data ); + $js_data .= PHP_EOL . sprintf( 'window.%1$s = Object.assign(window.%1$s || {}, %2$s);', $var_name, $json_data ); + } } - wp_localize_script( $asset->get_slug(), $key, $localize ); + $localization_html .= sprintf( + '', + $asset->get_slug(), + $js_data + ); - $this->localized[] = $key; + $this->localized[] = $localized_key; } - // Fetch the HTML for all the localized data. - ob_start(); - $wp_scripts->print_extra_script( $asset->get_slug(), true ); - $localization_html = ob_get_clean(); - - // After printing it remove data;| - $wp_scripts->add_data( $asset->get_slug(), 'data', '' ); - return $localization_html . $tag; } /** * Filters the Script tags to attach Async and/or Defer based on the rules we set in our Asset class. * - * @since 1.0.0 - * - * @param string $tag Tag we are filtering. + * @param string $tag Tag we are filtering. * @param string $handle Which is the ID/Handle of the tag we are about to print. * * @return string Script tag with the defer and/or async attached. + * @since 1.0.0 + * */ public function filter_tag_async_defer( $tag, $handle ) { // Only filter for our own filters. @@ -241,12 +454,12 @@ public function filter_tag_async_defer( $tag, $handle ) { /** * Filters the Script tags to attach type=module based on the rules we set in our Asset class. * - * @since 1.0.0 - * - * @param string $tag Tag we are filtering. + * @param string $tag Tag we are filtering. * @param string $handle Which is the ID/Handle of the tag we are about to print. * * @return string Script tag with the type=module + * @since 1.0.0 + * */ public function filter_modify_to_module( $tag, $handle ) { // Only filter for own own filters. @@ -277,118 +490,87 @@ public function filter_modify_to_module( $tag, $handle ) { } /** - * Register the Assets on the correct hooks. + * Enqueues registered assets based on their groups. + * + * @param string|array $groups Which groups will be enqueued. + * @param bool $should_enqueue_no_matter_what Whether to ignore conditional requirements when enqueuing. * * @since 1.0.0 * - * @param array|Asset|null $assets Array of asset objects, single asset object, or null. + * @uses Assets::enqueue() * - * @return void */ - public function register_in_wp( $assets = null ) { - if ( is_null( $assets ) ) { - $assets = $this->get(); - } - - if ( ! is_array( $assets ) ) { - $assets = [ $assets ]; - } + public function enqueue_group( $groups, bool $should_enqueue_no_matter_what = false ) { + $assets = $this->get( null, false ); + $enqueue = []; foreach ( $assets as $asset ) { - // Asset is already registered. - if ( $asset->is_registered() ) { + if ( empty( $asset->get_groups() ) ) { continue; } - if ( 'js' === $asset->get_type() ) { - // Script is already registered. - if ( wp_script_is( $asset->get_slug(), 'registered' ) ) { - continue; - } - - $dependencies = $asset->get_dependencies(); + $intersect = array_intersect( (array) $groups, $asset->get_groups() ); - // If the asset is a callable, we call the function, - // passing it the asset and expecting back an array of dependencies. - if ( is_callable( $asset->get_dependencies() ) ) { - $dependencies = call_user_func( $asset->get_dependencies(), [ $asset ] ); - } - - wp_register_script( $asset->get_slug(), $asset->get_url(), $dependencies, $asset->get_version(), $asset->is_in_footer() ); - - // Register that this asset is actually registered on the WP methods. - // @phpstan-ignore-next-line - if ( wp_script_is( $asset->get_slug(), 'registered' ) ) { - $asset->set_as_registered(); - } - } else { - // Style is already registered. - if ( wp_style_is( $asset->get_slug(), 'registered' ) ) { - continue; - } - - wp_register_style( $asset->get_slug(), $asset->get_url(), $asset->get_dependencies(), $asset->get_version(), $asset->get_media() ); - - // Register that this asset is actually registered on the WP methods. - // @phpstan-ignore-next-line - if ( wp_style_is( $asset->get_slug(), 'registered' ) ) { - $asset->set_as_registered(); - } - - $style_data = $asset->get_style_data(); - if ( $style_data ) { - foreach ( $style_data as $datum_key => $datum_value ) { - wp_style_add_data( $asset->get_slug(), $datum_key, $datum_value ); - } - } - } - - // If we don't have an action we don't even register the action to enqueue. - if ( empty( $asset->get_action() ) ) { + if ( empty( $intersect ) ) { continue; } - - // Now add an action to enqueue the registered assets. - foreach ( (array) $asset->get_action() as $action ) { - // Enqueue the registered assets at the appropriate time. - if ( did_action( $action ) > 0 ) { - $this->enqueue(); - } else { - add_action( $action, [ $this, 'enqueue' ], $asset->get_priority(), 0 ); - } - } + $enqueue[] = $asset->get_slug(); } + + $this->enqueue( $enqueue, $should_enqueue_no_matter_what ); } /** - * Enqueues registered assets based on their groups. + * Enqueues registered assets. * - * @since 1.0.0 + * This method is called on whichever action (if any) was declared during registration. + * + * It can also be called directly with a list of asset slugs to forcibly enqueue, which may be + * useful where an asset is required in a situation not anticipated when it was originally + * registered. * - * @param string|array $groups Which groups will be enqueued. - * @param bool $should_enqueue_no_matter_what Whether to ignore conditional requirements when enqueuing. + * @param string|array $assets_to_enqueue Which assets will be enqueued. + * @param bool $should_enqueue_no_matter_what Whether to ignore conditional requirements when enqueuing. * - * @uses Assets::enqueue() + * @since 1.0.0 * */ - public function enqueue_group( $groups, bool $should_enqueue_no_matter_what = false ) { - $assets = $this->get( null, false ); - $enqueue = []; + public function enqueue( $assets_to_enqueue = null, bool $should_enqueue_no_matter_what = false ) { + $assets_to_enqueue = array_filter( (array) $assets_to_enqueue ); + if ( ! empty( $assets_to_enqueue ) ) { + $assets = (array) $this->get( $assets_to_enqueue ); + } else { + $assets = $this->get(); + } foreach ( $assets as $asset ) { - if ( empty( $asset->get_groups() ) ) { - continue; + $slug = $asset->get_slug(); + + // Should this asset be enqueued regardless of the current filter/any conditional requirements? + $must_enqueue = in_array( $slug, $assets_to_enqueue ); + $actions = $asset->get_action(); + + if ( empty( $actions ) && $must_enqueue ) { + $this->do_enqueue( $asset, $must_enqueue ); } - $intersect = array_intersect( (array) $groups, $asset->get_groups() ); + foreach ( $asset->get_action() as $action ) { + $in_filter = current_filter() === $action; + $did_action = did_action( $action ) > 0; - if ( empty( $intersect ) ) { - continue; + // Skip if we are not on the correct filter (unless we are forcibly enqueuing). + if ( ! $in_filter && ! $must_enqueue && ! $did_action ) { + continue; + } + + // If any single conditional returns true, then we need to enqueue the asset. + if ( empty( $action ) && ! $must_enqueue ) { + continue; + } + + $this->do_enqueue( $asset, $should_enqueue_no_matter_what ); } - $enqueue[] = $asset->get_slug(); } - - $this->enqueue( $enqueue, $should_enqueue_no_matter_what ); } /** @@ -400,10 +582,11 @@ public function enqueue_group( $groups, bool $should_enqueue_no_matter_what = fa * useful where an asset is required in a situation not anticipated when it was originally * registered. * + * @param Asset $asset Asset to enqueue. + * @param bool $force_enqueue Whether to ignore conditional requirements when enqueuing. + * * @since 1.0.0 * - * @param Asset $asset Asset to enqueue. - * @param bool $force_enqueue Whether to ignore conditional requirements when enqueuing. */ protected function do_enqueue( Asset $asset, bool $force_enqueue = false ): void { $hook_prefix = Config::get_hook_prefix(); @@ -431,20 +614,22 @@ protected function do_enqueue( Asset $asset, bool $force_enqueue = false ): void /** * Allows developers to hook-in and prevent an asset from being loaded. * + * @param bool $enqueue If we should enqueue or not a given asset. + * @param object $asset Which asset we are dealing with. + * * @since 1.0.0 * - * @param bool $enqueue If we should enqueue or not a given asset. - * @param object $asset Which asset we are dealing with. */ $enqueue = (bool) apply_filters( "stellarwp/assets/{$hook_prefix}/enqueue", $enqueue, $asset ); /** * Allows developers to hook-in and prevent an asset from being loaded. * + * @param bool $enqueue If we should enqueue or not a given asset. + * @param object $asset Which asset we are dealing with. + * * @since 1.0.0 * - * @param bool $enqueue If we should enqueue or not a given asset. - * @param object $asset Which asset we are dealing with. */ $enqueue = (bool) apply_filters( "stellarwp/assets/{$hook_prefix}/enqueue_{$slug}", $enqueue, $asset ); @@ -482,101 +667,97 @@ protected function do_enqueue( Asset $asset, bool $force_enqueue = false ): void } /** - * Enqueues registered assets. - * - * This method is called on whichever action (if any) was declared during registration. + * Register the Assets on the correct hooks. * - * It can also be called directly with a list of asset slugs to forcibly enqueue, which may be - * useful where an asset is required in a situation not anticipated when it was originally - * registered. + * @param array|Asset|null $assets Array of asset objects, single asset object, or null. * + * @return void * @since 1.0.0 * - * @param string|array $assets_to_enqueue Which assets will be enqueued. - * @param bool $should_enqueue_no_matter_what Whether to ignore conditional requirements when enqueuing. */ - public function enqueue( $assets_to_enqueue = null, bool $should_enqueue_no_matter_what = false ) { - $assets_to_enqueue = array_filter( (array) $assets_to_enqueue ); - if ( ! empty( $assets_to_enqueue ) ) { - $assets = (array) $this->get( $assets_to_enqueue ); - } else { + public function register_in_wp( $assets = null ) { + if ( is_null( $assets ) ) { $assets = $this->get(); } + if ( ! is_array( $assets ) ) { + $assets = [ $assets ]; + } + foreach ( $assets as $asset ) { - $slug = $asset->get_slug(); + // Asset is already registered. + if ( $asset->is_registered() ) { + continue; + } - // Should this asset be enqueued regardless of the current filter/any conditional requirements? - $must_enqueue = in_array( $slug, $assets_to_enqueue ); - $actions = $asset->get_action(); + if ( 'js' === $asset->get_type() ) { + // Script is already registered. + if ( wp_script_is( $asset->get_slug(), 'registered' ) ) { + continue; + } - if ( empty( $actions ) && $must_enqueue ) { - $this->do_enqueue( $asset, $must_enqueue ); - } + $dependencies = $asset->get_dependencies(); - foreach ( $asset->get_action() as $action ) { - $in_filter = current_filter() === $action; - $did_action = did_action( $action ) > 0; + // If the asset is a callable, we call the function, + // passing it the asset and expecting back an array of dependencies. + if ( is_callable( $asset->get_dependencies() ) ) { + $dependencies = call_user_func( $asset->get_dependencies(), [ $asset ] ); + } - // Skip if we are not on the correct filter (unless we are forcibly enqueuing). - if ( ! $in_filter && ! $must_enqueue && ! $did_action ) { + wp_register_script( $asset->get_slug(), $asset->get_url(), $dependencies, $asset->get_version(), $asset->is_in_footer() ); + + // Register that this asset is actually registered on the WP methods. + // @phpstan-ignore-next-line + if ( wp_script_is( $asset->get_slug(), 'registered' ) ) { + $asset->set_as_registered(); + } + } else { + // Style is already registered. + if ( wp_style_is( $asset->get_slug(), 'registered' ) ) { continue; } - // If any single conditional returns true, then we need to enqueue the asset. - if ( empty( $action ) && ! $must_enqueue ) { - continue; + wp_register_style( $asset->get_slug(), $asset->get_url(), $asset->get_dependencies(), $asset->get_version(), $asset->get_media() ); + + // Register that this asset is actually registered on the WP methods. + // @phpstan-ignore-next-line + if ( wp_style_is( $asset->get_slug(), 'registered' ) ) { + $asset->set_as_registered(); } - $this->do_enqueue( $asset, $should_enqueue_no_matter_what ); + $style_data = $asset->get_style_data(); + if ( $style_data ) { + foreach ( $style_data as $datum_key => $datum_value ) { + wp_style_add_data( $asset->get_slug(), $datum_key, $datum_value ); + } + } } - } - } - /** - * Register an Asset and attach a callback to the required action to display it correctly. - * - * @since 1.0.0 - * - * @param Asset $asset Register an asset. - * - * @return Asset|false The registered object or false on error. - */ - public function add( Asset $asset ) { - // Prevent weird stuff here. - $slug = $asset->get_slug(); + // If we don't have an action we don't even register the action to enqueue. + if ( empty( $asset->get_action() ) ) { + continue; + } - if ( $this->exists( $slug ) ) { - return $this->get( $slug ); + // Now add an action to enqueue the registered assets. + foreach ( (array) $asset->get_action() as $action ) { + // Enqueue the registered assets at the appropriate time. + if ( did_action( $action ) > 0 ) { + $this->enqueue(); + } else { + add_action( $action, [ $this, 'enqueue' ], $asset->get_priority(), 0 ); + } + } } - - // Set the Asset on the array of notices. - $this->assets[ $slug ] = $asset; - - // Return the Slug because it might be modified. - return $asset; - } - - /** - * Create an asset. - * - * @param string $slug The asset slug. - * @param string $file The asset file path. - * @param string|null $version The asset version. - * @param string|null $plugin_path The path to the root of the plugin. - */ - public static function asset( string $slug, string $file, string $version = null, string $plugin_path = null ) { - return static::init()->add( new Asset( $slug, $file, $version, $plugin_path ) ); } /** * Removes an Asset from been registered and enqueue. * - * @since 1.0.0 - * * @param string $slug Slug of the Asset. * * @return bool + * @since 1.0.0 + * */ public function remove( $slug ) { if ( ! $this->exists( $slug ) ) { @@ -594,101 +775,8 @@ public function remove( $slug ) { } unset( $this->assets[ $slug ] ); - return true; - } - - /** - * Get the Asset Object configuration. - * - * @since 1.0.0 - * - * @param string|array $slug Slug of the Asset. - * @param boolean $sort If we should do any sorting before returning. - * - * @return array|Asset Array of asset objects, single asset object, or null if looking for a single asset but - * it was not in the array of objects. - */ - public function get( $slug = null, $sort = true ) { - $obj = $this; - - if ( is_null( $slug ) ) { - if ( $sort ) { - $cache_key_count = __METHOD__ . ':count'; - // Sorts by priority. - $cache_count = $this->get_var( $cache_key_count, 0 ); - $count = count( $this->assets ); - - if ( $count !== $cache_count ) { - uasort( $this->assets, static function( $a, $b ) use ( $obj ) { - return $obj->sort_by_priority( $a, $b, 'get_priority' ); - } ); - $this->set_var( $cache_key_count, $count ); - } - } - return $this->assets; - } - - // If slug is an array we return all of those. - if ( is_array( $slug ) ) { - $assets = []; - foreach ( $slug as $asset_slug ) { - $asset_slug = sanitize_key( $asset_slug ); - // Skip empty assets. - if ( empty( $this->assets[ $asset_slug ] ) ) { - continue; - } - $assets[ $asset_slug ] = $this->assets[ $asset_slug ]; - } - - if ( empty( $assets ) ) { - return []; - } - - if ( $sort ) { - // Sorts by priority. - uasort( $assets, static function( $a, $b ) use ( $obj ) { - return $obj->sort_by_priority( $a, $b, 'get_priority' ); - } ); - } - - return $assets; - } - - // Prevent weird stuff here. - $slug = sanitize_key( $slug ); - - if ( ! empty( $this->assets[ $slug ] ) ) { - return $this->assets[ $slug ]; - } - - return []; - } - - /** - * Gets a memoized value. - * - * @param string $var Var name. - * @param mixed|null $default Default value. - * - * @return mixed|null - */ - public function get_var( string $var, $default = null ) { - return $this->memoized[ $var ] ?? $default; - } - - /** - * Checks if an Asset exists. - * - * @since 1.0.0 - * - * @param string|array $slug Slug of the Asset. - * - * @return bool - */ - public function exists( $slug ) { - $slug = sanitize_key( $slug ); - return isset( $this->assets[ $slug ] ); + return true; } /** @@ -696,23 +784,23 @@ public function exists( $slug ) { * * The method will force the scripts and styles to print overriding their registration and conditional. * - * @since 1.0.0 - * * @param string|array $group Which group(s) should be enqueued. - * @param bool $echo Whether to print the group(s) tag(s) to the page or not; default to `true` to + * @param bool $echo Whether to print the group(s) tag(s) to the page or not; default to `true` to * print the HTML `script` (JS) and `link` (CSS) tags to the page. * * @return string The `script` and `link` HTML tags produced for the group(s). + * @since 1.0.0 + * */ public function print_group( $group, $echo = true ) { $all_assets = $this->get(); $groups = (array) $group; - $to_print = array_filter( $all_assets, static function( Asset $asset ) use ( $groups ) { + $to_print = array_filter( $all_assets, static function ( Asset $asset ) use ( $groups ) { $asset_groups = $asset->get_groups(); return ! empty( $asset_groups ) && array_intersect( $asset_groups, $groups ); } ); - $by_type = array_reduce( $to_print, static function( array $acc, Asset $asset ) { + $by_type = array_reduce( $to_print, static function ( array $acc, Asset $asset ) { $acc[ $asset->get_type() ][] = $asset->get_slug(); return $acc; @@ -741,45 +829,4 @@ public function print_group( $group, $echo = true ) { return $tags; } - /** - * Sets a memoized value. - * - * @param string $var Var name. - * @param mixed|null $value The value. - */ - public function set_var( string $var, $value = null ) { - $this->memoized[ $var ] = $value; - } - - /** - * Sorting function based on Priority - * - * @since 1.0.0 - * - * @param object|array $b Second subject to compare. - * @param object|array $a First Subject to compare. - * @param string $method Method to use for sorting. - * - * @return int - */ - public function sort_by_priority( $a, $b, $method = null ) { - if ( is_array( $a ) ) { - $a_priority = $a['priority']; - } else { - $a_priority = $method ? $a->$method() : $a->priority; - } - - if ( is_array( $b ) ) { - $b_priority = $b['priority']; - } else { - $b_priority = $method ? $b->$method() : $b->priority; - } - - if ( (int) $a_priority === (int) $b_priority ) { - return 0; - } - - return (int) $a_priority > (int) $b_priority ? 1 : -1; - } - } diff --git a/tests/wpunit/AssetsTest.php b/tests/wpunit/AssetsTest.php index b82b83a..3345662 100644 --- a/tests/wpunit/AssetsTest.php +++ b/tests/wpunit/AssetsTest.php @@ -1,4 +1,5 @@ assertFalse( Assets::init()->exists( 'my-script' ) ); $this->assertFalse( wp_script_is( 'my-script', 'enqueued' ) ); } + + /** + * It should localize data correctly + * + * @test + */ + public function should_localize_data_correctly(): void { + Asset::add( 'my-first-script', 'first-script.js' ) + ->add_localize_script( 'boomshakalakaProjectFirstScriptData', [ + 'animal' => 'cat', + 'color' => 'orange', + ] ) + ->register(); + Asset::add( 'my-second-script', 'second-script.js' ) + ->add_localize_script( 'boomshakalakaProjectSecondScriptData', [ + 'animal' => 'dog', + 'color' => 'green', + ] ) + ->register(); + Asset::add( 'my-second-script-mod', 'second-script-mod.js' ) + ->add_localize_script( 'boomshakalakaProjectSecondScriptModData', [ + 'animal' => 'horse' + ] ) + ->register(); + + $this->assertEquals( <<< SCRIPT + + +SCRIPT, + apply_filters( 'script_loader_tag', '', 'my-first-script' ) + ); + $this->assertEquals( <<< SCRIPT + + +SCRIPT, + apply_filters( 'script_loader_tag', '', 'my-second-script' ) + ); + + $this->assertEquals( <<< SCRIPT + + +SCRIPT, + apply_filters( 'script_loader_tag', '', 'my-second-script-mod' ) + ); + } + + /** + * It should localize dot notation data correctly + * + * @test + */ + public function should_localize_dot_notation_data_correctly(): void { + Asset::add( 'my-first-ns-script', 'first-script.js' ) + ->add_localize_script( 'boomshakalaka.project.firstScriptData', [ + 'animal' => 'cat', + 'color' => 'orange', + ] ) + ->register(); + Asset::add( 'my-second-ns-script', 'second-script.js' ) + ->add_localize_script( 'boomshakalaka.project.secondScriptData', [ + 'animal' => 'dog', + 'color' => 'green', + ] ) + ->register(); + Asset::add( 'my-second-ns-script-mod', 'second-script-mod.js' ) + ->add_localize_script( 'boomshakalaka.project.secondScriptData', [ + 'animal' => 'horse' + ] ) + ->register(); + + $this->assertEquals( <<< SCRIPT + +SCRIPT, + apply_filters( 'script_loader_tag', '', 'my-first-ns-script' ) + ); + $this->assertEquals( <<< SCRIPT + +SCRIPT, + apply_filters( 'script_loader_tag', '', 'my-second-ns-script' ) + ); + + $this->assertEquals( <<< SCRIPT + +SCRIPT, + apply_filters( 'script_loader_tag', '', 'my-second-ns-script-mod' ) + ); + } + + /** + * It should allow localizing data in normal and namespaced form for same script + * + * @test + */ + public function should_allow_localizing_data_in_normal_and_namespaced_form_for_same_script(): void { + Asset::add( 'my-test-script', 'test-script.js' ) + ->add_localize_script( 'boomshakalakaProjectTestScriptData', [ + 'animal' => 'cat', + 'color' => 'orange', + ] ) + ->add_localize_script( 'boomshakalaka.project.testScriptData', [ + 'animal' => 'dog', + 'color' => 'green', + ] ) + ->register(); + + $apply_filters = apply_filters( 'script_loader_tag', '', 'my-test-script' ); + $this->assertEquals( <<< SCRIPT + + +SCRIPT, + $apply_filters + ); + } }