> */ private static array $global_data = array(); /** * Registers data to be localized onto the first plugin script enqueued in the current request. * * Use this for plugin-wide data that any script may need but that should only be * output when at least one plugin script is present on the page. * * @since 1.0.0 * * @param string $object_name The name of the JavaScript object (without the 'ai' prefix). * @param array $data The data to localize. */ public static function add_global_data( string $object_name, array $data ): void { self::$global_data[ $object_name ] = $data; } /** * Enqueue a script using a script path and its asset metadata. * * @since 0.1.0 * * @param string $handle The handle for the script. * @param string $file_name The script file name without the .js extension. * @param array{ include_core_abilities?: bool } $extra_args Additional arguments. */ public static function enqueue_script( string $handle, string $file_name, array $extra_args = array() ): void { $script_url = self::ASSET_URL . $file_name . '.js'; $asset_data = self::get_asset_file_data( $file_name ); // Bail if there's nothing to enqueue. if ( ! $asset_data ) { return; } $args = array( 'in_footer' => true, 'strategy' => 'defer', ); if ( $extra_args['include_core_abilities'] ?? false ) { $args['module_dependencies'] = array( '@wordpress/abilities', '@wordpress/core-abilities', ); } wp_enqueue_script( self::HANDLE_PREFIX . $handle, $script_url, $asset_data['dependencies'], $asset_data['version'], $args ); // Localize global data. foreach ( self::$global_data as $object_name => $data ) { wp_add_inline_script( self::HANDLE_PREFIX . $handle, sprintf( 'window.ai%s=%s;', $object_name, wp_json_encode( $data ) ), 'before' ); } self::$global_data = array(); wp_set_script_translations( self::HANDLE_PREFIX . $handle, 'ai' ); } /** * Enqueue a style using a style path and its asset metadata. * * @since 0.1.0 * * @param string $handle The handle for the style. * @param string $file_name The script file name. * @param string[] $dependencies Optional. An array of registered style handles this style depends on. Default empty array. */ public static function enqueue_style( string $handle, string $file_name, array $dependencies = array() ): void { $handle = self::HANDLE_PREFIX . $handle; $style_path = self::ASSET_DIR . $file_name . '.css'; $style_url = self::ASSET_URL . $file_name . '.css'; $asset_data = self::get_asset_file_data( $file_name ); // Bail if there's nothing to enqueue. if ( ! $asset_data ) { return; } wp_enqueue_style( $handle, $style_url, $dependencies, // *.asset.php dependencies are only for scripts. $asset_data['version'] ); wp_style_add_data( $handle, 'path', $style_path ); $rtl_style_path = str_replace( '.css', '-rtl.css', $style_path ); if ( ! file_exists( $rtl_style_path ) ) { self::log_and_display_error( sprintf( /* translators: %1$s: The RTL style filename. */ __( 'RTL stylesheet "%1$s" is missing and will not be available.', 'ai' ), basename( $rtl_style_path ), ) ); return; } wp_style_add_data( $handle, 'rtl', 'replace' ); if ( ! is_rtl() ) { return; } wp_style_add_data( $handle, 'path', $rtl_style_path ); } /** * Localize data for an enqueued script. * * This method allows passing PHP data to JavaScript using `wp_localize_script()`. * It must be called after the script has been enqueued using `enqueue_script()`. * * @since 0.1.0 * * @param string $handle The script handle used in `enqueue_script()` (without prefix). * @param string $object_name The name of the JavaScript object to contain the data. * @param array $data The data to localize. */ public static function localize_script( string $handle, string $object_name, array $data ): void { wp_localize_script( self::HANDLE_PREFIX . $handle, 'ai' . $object_name, $data ); } /** * Get the asset file data for a given asset filename. * * @param string $filename Path of the asset relative to the assets directory, excluding the file extension. * * @return ?array{ * version:string, * dependencies:array, * ... * } The asset file array, or null if the asset file does not exist or is invalid. */ private static function get_asset_file_data( string $filename ): ?array { $asset_file = self::ASSET_DIR . $filename . '.asset.php'; // Bail if the asset file does not exist. if ( ! file_exists( $asset_file ) ) { self::log_and_display_error( sprintf( /* translators: %1$s: The asset filename. */ __( 'Asset file for "%1$s" is missing and cannot be registered.', 'ai' ), $filename, ), ); return null; } // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- The file is checked above. $asset = require $asset_file; if ( ! is_array( $asset ) ) { self::log_and_display_error( sprintf( /* translators: %1$s: The asset filename. */ __( 'Asset file for "%1$s" is invalid and cannot be registered.', 'ai' ), $filename, ), ); return null; } // Fallback to filemtime if version is not set in the asset file. if ( ! isset( $asset['version'] ) ) { $asset['version'] = filemtime( $asset_file ); } // Fallback to empty dependencies if not set in the asset file. if ( ! isset( $asset['dependencies'] ) ) { $asset['dependencies'] = array(); } return $asset; } /** * Logs a _doing_it_wrong() and displays an admin notice with the provided message. * * Messages are escaped with esc_html() before being logged or displayed. * * @param string $message The message to display in the admin notice. */ private static function log_and_display_error( string $message ): void { _doing_it_wrong( self::class, esc_html( $message ), '0.8.0' ); $hooks = array( 'admin_notices', 'network_admin_notices', ); foreach ( $hooks as $hook ) { add_action( $hook, static function () use ( $message ) { wp_admin_notice( esc_html( $message ), array( 'type' => 'error', ) ); } ); } } }