PluginProbe
AI / trunk
AI vtrunk
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / Asset_Loader.php

Asset_Loader.php in AI trunk, at includes/Asset_Loader.php

274 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Asset Loader Utility Class
4 *
5 * Provides static methods to enqueue scripts and styles with WordPress.
6 * Automatically handles .asset.php metadata files generated by the plugin build step.
7 *
8 * Usage:
9 * Asset_Loader::enqueue_script( 'my-handle', 'my-script' );
10 * Asset_Loader::enqueue_style( 'my-style', 'my-stylesheet' );
11 *
12 * @package WordPress\AI
13 */
14
15 declare( strict_types=1 );
16
17 namespace WordPress\AI;
18
19 // Exit if accessed directly.
20 defined( 'ABSPATH' ) || exit;
21
22 /**
23 * Class Asset_Loader
24 *
25 * A utility class for registering and enqueuing assets (scripts and styles)
26 * with support for asset metadata files.
27 *
28 * @internal This class is intended for internal plugin use only and should not be used directly by external code.
29 * Breaking changes may be made without a major version bump or prior notice.
30 *
31 * @since 0.1.0
32 */
33 final class Asset_Loader {
34 /**
35 * The path to the directory containing the built assets.
36 */
37 private const ASSET_DIR = WPAI_PLUGIN_DIR . 'build-scripts/';
38
39 /**
40 * The URL to the directory containing the built assets.
41 */
42 private const ASSET_URL = WPAI_PLUGIN_URL . 'build-scripts/';
43
44 /**
45 * The prefix to use for all asset handles to avoid conflicts with other plugins or themes.
46 */
47 private const HANDLE_PREFIX = 'ai_';
48
49 /**
50 * Global data to be localized.
51 *
52 * @since 1.0.0
53 * @var array<string, array<string, mixed>>
54 */
55 private static array $global_data = array();
56
57 /**
58 * Registers data to be localized onto the first plugin script enqueued in the current request.
59 *
60 * Use this for plugin-wide data that any script may need but that should only be
61 * output when at least one plugin script is present on the page.
62 *
63 * @since 1.0.0
64 *
65 * @param string $object_name The name of the JavaScript object (without the 'ai' prefix).
66 * @param array<string,mixed> $data The data to localize.
67 */
68 public static function add_global_data( string $object_name, array $data ): void {
69 self::$global_data[ $object_name ] = $data;
70 }
71
72 /**
73 * Enqueue a script using a script path and its asset metadata.
74 *
75 * @since 0.1.0
76 *
77 * @param string $handle The handle for the script.
78 * @param string $file_name The script file name without the .js extension.
79 * @param array{ include_core_abilities?: bool } $extra_args Additional arguments.
80 */
81 public static function enqueue_script( string $handle, string $file_name, array $extra_args = array() ): void {
82 $script_url = self::ASSET_URL . $file_name . '.js';
83 $asset_data = self::get_asset_file_data( $file_name );
84
85 // Bail if there's nothing to enqueue.
86 if ( ! $asset_data ) {
87 return;
88 }
89
90 $args = array(
91 'in_footer' => true,
92 'strategy' => 'defer',
93 );
94
95 if ( $extra_args['include_core_abilities'] ?? false ) {
96 $args['module_dependencies'] = array(
97 '@wordpress/abilities',
98 '@wordpress/core-abilities',
99 );
100 }
101
102 wp_enqueue_script(
103 self::HANDLE_PREFIX . $handle,
104 $script_url,
105 $asset_data['dependencies'],
106 $asset_data['version'],
107 $args
108 );
109
110 // Localize global data.
111 foreach ( self::$global_data as $object_name => $data ) {
112 wp_add_inline_script(
113 self::HANDLE_PREFIX . $handle,
114 sprintf( 'window.ai%s=%s;', $object_name, wp_json_encode( $data ) ),
115 'before'
116 );
117 }
118 self::$global_data = array();
119 wp_set_script_translations( self::HANDLE_PREFIX . $handle, 'ai' );
120 }
121
122 /**
123 * Enqueue a style using a style path and its asset metadata.
124 *
125 * @since 0.1.0
126 *
127 * @param string $handle The handle for the style.
128 * @param string $file_name The script file name.
129 * @param string[] $dependencies Optional. An array of registered style handles this style depends on. Default empty array.
130 */
131 public static function enqueue_style( string $handle, string $file_name, array $dependencies = array() ): void {
132 $handle = self::HANDLE_PREFIX . $handle;
133 $style_path = self::ASSET_DIR . $file_name . '.css';
134 $style_url = self::ASSET_URL . $file_name . '.css';
135 $asset_data = self::get_asset_file_data( $file_name );
136
137 // Bail if there's nothing to enqueue.
138 if ( ! $asset_data ) {
139 return;
140 }
141
142 wp_enqueue_style(
143 $handle,
144 $style_url,
145 $dependencies, // *.asset.php dependencies are only for scripts.
146 $asset_data['version']
147 );
148
149 wp_style_add_data( $handle, 'path', $style_path );
150
151 $rtl_style_path = str_replace( '.css', '-rtl.css', $style_path );
152 if ( ! file_exists( $rtl_style_path ) ) {
153 self::log_and_display_error(
154 sprintf(
155 /* translators: %1$s: The RTL style filename. */
156 __( 'RTL stylesheet "%1$s" is missing and will not be available.', 'ai' ),
157 basename( $rtl_style_path ),
158 )
159 );
160 return;
161 }
162
163 wp_style_add_data( $handle, 'rtl', 'replace' );
164 if ( ! is_rtl() ) {
165 return;
166 }
167
168 wp_style_add_data( $handle, 'path', $rtl_style_path );
169 }
170
171 /**
172 * Localize data for an enqueued script.
173 *
174 * This method allows passing PHP data to JavaScript using `wp_localize_script()`.
175 * It must be called after the script has been enqueued using `enqueue_script()`.
176 *
177 * @since 0.1.0
178 *
179 * @param string $handle The script handle used in `enqueue_script()` (without prefix).
180 * @param string $object_name The name of the JavaScript object to contain the data.
181 * @param array<string, mixed> $data The data to localize.
182 */
183 public static function localize_script( string $handle, string $object_name, array $data ): void {
184 wp_localize_script(
185 self::HANDLE_PREFIX . $handle,
186 'ai' . $object_name,
187 $data
188 );
189 }
190
191 /**
192 * Get the asset file data for a given asset filename.
193 *
194 * @param string $filename Path of the asset relative to the assets directory, excluding the file extension.
195 *
196 * @return ?array{
197 * version:string,
198 * dependencies:array<string>,
199 * ...
200 * } The asset file array, or null if the asset file does not exist or is invalid.
201 */
202 private static function get_asset_file_data( string $filename ): ?array {
203 $asset_file = self::ASSET_DIR . $filename . '.asset.php';
204
205 // Bail if the asset file does not exist.
206 if ( ! file_exists( $asset_file ) ) {
207 self::log_and_display_error(
208 sprintf(
209 /* translators: %1$s: The asset filename. */
210 __( 'Asset file for "%1$s" is missing and cannot be registered.', 'ai' ),
211 $filename,
212 ),
213 );
214 return null;
215 }
216
217 // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- The file is checked above.
218 $asset = require $asset_file;
219
220 if ( ! is_array( $asset ) ) {
221 self::log_and_display_error(
222 sprintf(
223 /* translators: %1$s: The asset filename. */
224 __( 'Asset file for "%1$s" is invalid and cannot be registered.', 'ai' ),
225 $filename,
226 ),
227 );
228 return null;
229 }
230
231 // Fallback to filemtime if version is not set in the asset file.
232 if ( ! isset( $asset['version'] ) ) {
233 $asset['version'] = filemtime( $asset_file );
234 }
235
236 // Fallback to empty dependencies if not set in the asset file.
237 if ( ! isset( $asset['dependencies'] ) ) {
238 $asset['dependencies'] = array();
239 }
240
241 return $asset;
242 }
243
244 /**
245 * Logs a _doing_it_wrong() and displays an admin notice with the provided message.
246 *
247 * Messages are escaped with esc_html() before being logged or displayed.
248 *
249 * @param string $message The message to display in the admin notice.
250 */
251 private static function log_and_display_error( string $message ): void {
252 _doing_it_wrong( self::class, esc_html( $message ), '0.8.0' );
253
254 $hooks = array(
255 'admin_notices',
256 'network_admin_notices',
257 );
258
259 foreach ( $hooks as $hook ) {
260 add_action(
261 $hook,
262 static function () use ( $message ) {
263 wp_admin_notice(
264 esc_html( $message ),
265 array(
266 'type' => 'error',
267 )
268 );
269 }
270 );
271 }
272 }
273 }
274