<?php /** * Functions related to registering and parsing blocks. * * @package WordPress * @subpackage Blocks * @since 5.0.0 */ /** * Removes the block asset's path prefix if provided. * * @since 5.5.0 * * @param string $asset_handle_or_path Asset handle or prefixed path. * @return string Path without the prefix or the original value. */ function remove_block_asset_path_prefix( $asset_handle_or_path ) { $path_prefix = 'file:'; if ( ! str_starts_with( $asset_handle_or_path, $path_prefix ) ) { return $asset_handle_or_path; } $path = substr( $asset_handle_or_path, strlen( $path_prefix ) ); if ( str_starts_with( $path, './' ) ) { $path = substr( $path, 2 ); } return $path; } /** * Generates the name for an asset based on the name of the block * and the field name provided. * * @since 5.5.0 * @since 6.1.0 Added `$index` parameter. * @since 6.5.0 Added support for `viewScriptModule` field. * * @param string $block_name Name of the block. * @param string $field_name Name of the metadata field. * @param int $index Optional. Index of the asset when multiple items passed. * Default 0. * @return string Generated asset name for the block's field. */ function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) { if ( str_starts_with( $block_name, 'core/' ) ) { $asset_handle = str_replace( 'core/', 'wp-block-', $block_name ); if ( str_starts_with( $field_name, 'editor' ) ) { $asset_handle .= '-editor'; } if ( str_starts_with( $field_name, 'view' ) ) { $asset_handle .= '-view'; } if ( str_ends_with( strtolower( $field_name ), 'scriptmodule' ) ) { $asset_handle .= '-script-module'; } if ( $index > 0 ) { $asset_handle .= '-' . ( $index + 1 ); } return $asset_handle; } $field_mappings = array( 'editorScript' => 'editor-script', 'editorStyle' => 'editor-style', 'script' => 'script', 'style' => 'style', 'viewScript' => 'view-script', 'viewScriptModule' => 'view-script-module', 'viewStyle' => 'view-style', ); $asset_handle = str_replace( '/', '-', $block_name ) . '-' . $field_mappings[ $field_name ]; if ( $index > 0 ) { $asset_handle .= '-' . ( $index + 1 ); } return $asset_handle; } /** * Gets the URL to a block asset. * * @since 6.4.0 * * @param string $path A normalized path to a block asset. * @return string|false The URL to the block asset or false on failure. */ function get_block_asset_url( $path ) { if ( empty( $path ) ) { return false; } // Path needs to be normalized to work in Windows env. static $wpinc_path_norm = ''; if ( ! $wpinc_path_norm ) { $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); } if ( str_starts_with( $path, $wpinc_path_norm ) ) { return includes_url( str_replace( $wpinc_path_norm, '', $path ) ); } static $template_paths_norm = array(); $template = get_template(); if ( ! isset( $template_paths_norm[ $template ] ) ) { $template_paths_norm[ $template ] = wp_normalize_path( realpath( get_template_directory() ) ); } if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $template ] ) ) ) { return get_theme_file_uri( str_replace( $template_paths_norm[ $template ], '', $path ) ); } if ( is_child_theme() ) { $stylesheet = get_stylesheet(); if ( ! isset( $template_paths_norm[ $stylesheet ] ) ) { $template_paths_norm[ $stylesheet ] = wp_normalize_path( realpath( get_stylesheet_directory() ) ); } if ( str_starts_with( $path, trailingslashit( $template_paths_norm[ $stylesheet ] ) ) ) { return get_theme_file_uri( str_replace( $template_paths_norm[ $stylesheet ], '', $path ) ); } } return plugins_url( basename( $path ), $path ); } /** * Finds a script module ID for the selected block metadata field. It detects * when a path to file was provided and optionally finds a corresponding asset * file with details necessary to register the script module under with an * automatically generated module ID. It returns unprocessed script module * ID otherwise. * * @since 6.5.0 * * @param array $metadata Block metadata. * @param string $field_name Field name to pick from metadata. * @param int $index Optional. Index of the script module ID to register when multiple * items passed. Default 0. * @return string|false Script module ID or false on failure. */ function register_block_script_module_id( $metadata, $field_name, $index = 0 ) { if ( empty( $metadata[ $field_name ] ) ) { return false; } $module_id = $metadata[ $field_name ]; if ( is_array( $module_id ) ) { if ( empty( $module_id[ $index ] ) ) { return false; } $module_id = $module_id[ $index ]; } $module_path = remove_block_asset_path_prefix( $module_id ); if ( $module_id === $module_path ) { return $module_id; } $path = dirname( $metadata['file'] ); $module_asset_raw_path = $path . '/' . substr_replace( $module_path, '.asset.php', - strlen( '.js' ) ); $module_id = generate_block_asset_handle( $metadata['name'], $field_name, $index ); $module_asset_path = wp_normalize_path( realpath( $module_asset_raw_path ) ); $module_path_norm = wp_normalize_path( realpath( $path . '/' . $module_path ) ); $module_uri = get_block_asset_url( $module_path_norm ); $module_asset = ! empty( $module_asset_path ) ? require $module_asset_path : array(); $module_dependencies = isset( $module_asset['dependencies'] ) ? $module_asset['dependencies'] : array(); $block_version = isset( $metadata['version'] ) ? $metadata['version'] : false; $module_version = isset( $module_asset['version'] ) ? $module_asset['version'] : $block_version; wp_register_script_module( $module_id, $module_uri, $module_dependencies, $module_version ); return $module_id; } /** * Finds a script handle for the selected block metadata field. It detects * when a path to file was provided and optionally finds a corresponding asset * file with details necessary to register the script under automatically * generated handle name. It returns unprocessed script handle otherwise. * * @since 5.5.0 * @since 6.1.0 Added `$index` parameter. * @since 6.5.0 The asset file is optional. Added script handle support in the asset file. * * @param array $metadata Block metadata. * @param string $field_name Field name to pick from metadata. * @param int $index Optional. Index of the script to register when multiple items passed. * Default 0. * @return string|false Script handle provided directly or created through * script's registration, or false on failure. */ function register_block_script_handle( $metadata, $field_name, $index = 0 ) { if ( empty( $metadata[ $field_name ] ) ) { return false; } $script_handle_or_path = $metadata[ $field_name ]; if ( is_array( $script_handle_or_path ) ) { if ( empty( $script_handle_or_path[ $index ] ) ) { return false; } $script_handle_or_path = $script_handle_or_path[ $index ]; } $script_path = remove_block_asset_path_prefix( $script_handle_or_path ); if ( $script_handle_or_path === $script_path ) { return $script_handle_or_path; } $path = dirname( $metadata['file'] ); $script_asset_raw_path = $path . '/' . substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ); $script_asset_path = wp_normalize_path( realpath( $script_asset_raw_path ) ); // Asset file for blocks is optional. See https://core.trac.wordpress.org/ticket/60460. $script_asset = ! empty( $script_asset_path ) ? require $script_asset_path : array(); $script_handle = isset( $script_asset['handle'] ) ? $script_asset['handle'] : generate_block_asset_handle( $metadata['name'], $field_name, $index ); if ( wp_script_is( $script_handle, 'registered' ) ) { return $script_handle; } $script_path_norm = wp_normalize_path( realpath( $path . '/' . $script_path ) ); $script_uri = get_block_asset_url( $script_path_norm ); $script_dependencies = isset( $script_asset['dependencies'] ) ? $script_asset['dependencies'] : array(); $block_version = isset( $metadata['version'] ) ? $metadata['version'] : false; $script_version = isset( $script_asset['version'] ) ? $script_asset['version'] : $block_version; $script_args = array(); if ( 'viewScript' === $field_name && $script_uri ) { $script_args['strategy'] = 'defer'; } $result = wp_register_script( $script_handle, $script_uri, $script_dependencies, $script_version, $script_args ); if ( ! $result ) { return false; } if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $script_dependencies, true ) ) { wp_set_script_translations( $script_handle, $metadata['textdomain'] ); } return $script_handle; } /** * Finds a style handle for the block metadata field. It detects when a path * to file was provided and registers the style under automatically * generated handle name. It returns unprocessed style handle otherwise. * * @since 5.5.0 * @since 6.1.0 Added `$index` parameter. * * @param array $metadata Block metadata. * @param string $field_name Field name to pick from metadata. * @param int $index Optional. Index of the style to register when multiple items passed. * Default 0. * @return string|false Style handle provided directly or created through * style's registration, or false on failure. */ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { if ( empty( $metadata[ $field_name ] ) ) { return false; } $style_handle = $metadata[ $field_name ]; if ( is_array( $style_handle ) ) { if ( empty( $style_handle[ $index ] ) ) { return false; } $style_handle = $style_handle[ $index ]; } $style_handle_name = generate_block_asset_handle( $metadata['name'], $field_name, $index ); // If the style handle is already registered, skip re-registering. if ( wp_style_is( $style_handle_name, 'registered' ) ) { return $style_handle_name; } static $wpinc_path_norm = ''; if ( ! $wpinc_path_norm ) { $wpinc_path_norm = wp_normalize_path( realpath( ABSPATH . WPINC ) ); } $is_core_block = isset( $metadata['file'] ) && str_starts_with( $metadata['file'], $wpinc_path_norm ); // Skip registering individual styles for each core block when a bundled version provided. if ( $is_core_block && ! wp_should_load_separate_core_block_assets() ) { return false; } $style_path = remove_block_asset_path_prefix( $style_handle ); $is_style_handle = $style_handle === $style_path; // Allow only passing style handles for core blocks. if ( $is_core_block && ! $is_style_handle ) { return false; } // Return the style handle unless it's the first item for every core block that requires special treatment. if ( $is_style_handle && ! ( $is_core_block && 0 === $index ) ) { return $style_handle; } // Check whether styles should have a ".min" suffix or not. $suffix = SCRIPT_DEBUG ? '' : '.min'; if ( $is_core_block ) { $style_path = ( 'editorStyle' === $field_name ) ? "editor{$suffix}.css" : "style{$suffix}.css"; } $style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) ); $style_uri = get_block_asset_url( $style_path_norm ); $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; $result = wp_register_style( $style_handle_name, $style_uri, array(), $version ); if ( ! $result ) { return false; } if ( $style_uri ) { wp_style_add_data( $style_handle_name, 'path', $style_path_norm ); if ( $is_core_block ) { $rtl_file = str_replace( "{$suffix}.css", "-rtl{$suffix}.css", $style_path_norm ); } else { $rtl_file = str_replace( '.css', '-rtl.css', $style_path_norm ); } if ( is_rtl() && file_exists( $rtl_file ) ) { wp_style_add_data( $style_handle_name, 'rtl', 'replace' ); wp_style_add_data( $style_handle_name, 'suffix', $suffix ); wp_style_add_data( $style_handle_name, 'path', $rtl_file ); } } return $style_handle_name; } /** * Gets i18n schema for block's metadata read from `block.json` fil