From a68650ef1965b30622e84d79958bbfa4c5daa562 Mon Sep 17 00:00:00 2001 From: Jonny Harris Date: Mon, 25 Sep 2023 17:47:27 +0000 Subject: [PATCH] Editor: Introduce get_block_asset_url Utility Function. This commit introduces a valuable utility function, get_block_asset_url, designed to simplify the retrieval of block asset URLs, such as those for CSS and JavaScript files. This utility eliminates redundancy in both register_block_script_handle and register_block_style_handle. Additionally, `get_block_asset_url` incorporates an early exit mechanism to optimize performance. This update includes comprehensive unit tests, covering various scenarios, including asset registration from core (wp-includes), themes, child themes, plugins, and mu-plugins. Props spacedmonkey, joemcgill, flixos90, gziolo. Fixes #58525. git-svn-id: https://develop.svn.wordpress.org/trunk@56683 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/blocks.php | 125 ++++++++---------- .../blocks/example-block/block.json | 8 ++ .../blocks/example-block/editor-style-rtl.css | 1 + .../blocks/example-block/editor-style.css | 1 + .../blocks/example-block/index.asset.php | 6 + .../blocks/example-block/index.js | 1 + .../blocks/example-block/style-rtl.css | 1 + .../blocks/example-block/style.css | 1 + .../blocks/example-block/view.asset.php | 6 + .../blocks/example-block/view.js | 1 + .../phpunit/tests/blocks/getBlockAssetUrl.php | 116 ++++++++++++++++ 11 files changed, 196 insertions(+), 71 deletions(-) create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/block.json create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style-rtl.css create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style.css create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.asset.php create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.js create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style-rtl.css create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style.css create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.asset.php create mode 100644 tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.js create mode 100644 tests/phpunit/tests/blocks/getBlockAssetUrl.php diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index be387b89a983e..81c7d250fcc86 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -73,6 +73,54 @@ function generate_block_asset_handle( $block_name, $field_name, $index = 0 ) { 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( 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( 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 handle for the selected block metadata field. It detects * when a path to file was provided and finds a corresponding asset file @@ -107,7 +155,8 @@ function register_block_script_handle( $metadata, $field_name, $index = 0 ) { return $script_handle; } - $script_asset_raw_path = dirname( $metadata['file'] ) . '/' . substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ); + $path = dirname( $metadata['file'] ); + $script_asset_raw_path = $path . '/' . substr_replace( $script_path, '.asset.php', - strlen( '.js' ) ); $script_handle = generate_block_asset_handle( $metadata['name'], $field_name, $index ); $script_asset_path = wp_normalize_path( realpath( $script_asset_raw_path ) @@ -128,44 +177,8 @@ function register_block_script_handle( $metadata, $field_name, $index = 0 ) { 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 ) ); - } - - // Cache $template_path_norm and $stylesheet_path_norm to avoid unnecessary additional calls. - static $template_path_norm = ''; - static $stylesheet_path_norm = ''; - if ( ! $template_path_norm || ! $stylesheet_path_norm ) { - $template_path_norm = wp_normalize_path( get_template_directory() ); - $stylesheet_path_norm = wp_normalize_path( get_stylesheet_directory() ); - } - - $script_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $script_path ) ); - - $is_core_block = isset( $metadata['file'] ) && str_starts_with( $metadata['file'], $wpinc_path_norm ); - - /* - * Determine if the block script was registered in a theme, by checking if the script path starts with either - * the parent (template) or child (stylesheet) directory path. - */ - $is_parent_theme_block = str_starts_with( $script_path_norm, trailingslashit( $template_path_norm ) ); - $is_child_theme_block = str_starts_with( $script_path_norm, trailingslashit( $stylesheet_path_norm ) ); - $is_theme_block = ( $is_parent_theme_block || $is_child_theme_block ); - - $script_uri = ''; - if ( $is_core_block ) { - $script_uri = includes_url( str_replace( $wpinc_path_norm, '', $script_path_norm ) ); - } elseif ( $is_theme_block ) { - // Get the script path deterministically based on whether or not it was registered in a parent or child theme. - $script_uri = $is_parent_theme_block - ? get_theme_file_uri( str_replace( $template_path_norm, '', $script_path_norm ) ) - : get_theme_file_uri( str_replace( $stylesheet_path_norm, '', $script_path_norm ) ); - } else { - // Fallback to plugins_url(). - $script_uri = plugins_url( $script_path, $metadata['file'] ); - } + $script_path_norm = wp_normalize_path( realpath( $path . '/' . $script_path ) ); + $script_uri = get_block_asset_url( $script_path_norm ); $script_args = array(); if ( 'viewScript' === $field_name ) { @@ -255,37 +268,7 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { } $style_path_norm = wp_normalize_path( realpath( dirname( $metadata['file'] ) . '/' . $style_path ) ); - $has_style_file = '' !== $style_path_norm; - - if ( $has_style_file ) { - $style_uri = plugins_url( $style_path, $metadata['file'] ); - - // Cache $template_path_norm and $stylesheet_path_norm to avoid unnecessary additional calls. - static $template_path_norm = ''; - static $stylesheet_path_norm = ''; - if ( ! $template_path_norm || ! $stylesheet_path_norm ) { - $template_path_norm = wp_normalize_path( get_template_directory() ); - $stylesheet_path_norm = wp_normalize_path( get_stylesheet_directory() ); - } - - // Determine if the block style was registered in a theme, by checking if the script path starts with either - // the parent (template) or child (stylesheet) directory path. - $is_parent_theme_block = str_starts_with( $style_path_norm, trailingslashit( $template_path_norm ) ); - $is_child_theme_block = str_starts_with( $style_path_norm, trailingslashit( $stylesheet_path_norm ) ); - $is_theme_block = ( $is_parent_theme_block || $is_child_theme_block ); - - if ( $is_core_block ) { - // All possible $style_path variants for core blocks are hard-coded above. - $style_uri = includes_url( 'blocks/' . str_replace( 'core/', '', $metadata['name'] ) . '/' . $style_path ); - } elseif ( $is_theme_block ) { - // Get the script path deterministically based on whether or not it was registered in a parent or child theme. - $style_uri = $is_parent_theme_block - ? get_theme_file_uri( str_replace( $template_path_norm, '', $style_path_norm ) ) - : get_theme_file_uri( str_replace( $stylesheet_path_norm, '', $style_path_norm ) ); - } - } else { - $style_uri = false; - } + $style_uri = get_block_asset_url( $style_path_norm ); $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; $result = wp_register_style( @@ -298,7 +281,7 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { return false; } - if ( $has_style_file ) { + if ( $style_uri ) { wp_style_add_data( $style_handle_name, 'path', $style_path_norm ); if ( $is_core_block ) { diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/block.json b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/block.json new file mode 100644 index 0000000000000..419a332b587b6 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/block.json @@ -0,0 +1,8 @@ +{ + "apiVersion": 2, + "title": "Example Theme Block", + "name": "block-theme/example-block", + "description": "Custom block registered from within a theme", + "editorScript": "file:./index.js", + "style": "file:./style.css" +} diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style-rtl.css b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style-rtl.css new file mode 100644 index 0000000000000..2572f27aaf2d9 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style-rtl.css @@ -0,0 +1 @@ +/* Test CSS file - RTL version */ diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style.css b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style.css new file mode 100644 index 0000000000000..5bbe1134f7048 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/editor-style.css @@ -0,0 +1 @@ +/* Test CSS file */ diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.asset.php b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.asset.php new file mode 100644 index 0000000000000..0314b6de8d090 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.asset.php @@ -0,0 +1,6 @@ + array( 'wp-element', 'wp-blocks' ), + 'version' => 'test', +); diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.js b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.js new file mode 100644 index 0000000000000..0bdf0f5ad91f7 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/index.js @@ -0,0 +1 @@ +/* Test JavaScript file. */ diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style-rtl.css b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style-rtl.css new file mode 100644 index 0000000000000..2572f27aaf2d9 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style-rtl.css @@ -0,0 +1 @@ +/* Test CSS file - RTL version */ diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style.css b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style.css new file mode 100644 index 0000000000000..5bbe1134f7048 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/style.css @@ -0,0 +1 @@ +/* Test CSS file */ diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.asset.php b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.asset.php new file mode 100644 index 0000000000000..0314b6de8d090 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.asset.php @@ -0,0 +1,6 @@ + array( 'wp-element', 'wp-blocks' ), + 'version' => 'test', +); diff --git a/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.js b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.js new file mode 100644 index 0000000000000..0bdf0f5ad91f7 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child/blocks/example-block/view.js @@ -0,0 +1 @@ +/* Test JavaScript file. */ diff --git a/tests/phpunit/tests/blocks/getBlockAssetUrl.php b/tests/phpunit/tests/blocks/getBlockAssetUrl.php new file mode 100644 index 0000000000000..ca4c0e25df2cd --- /dev/null +++ b/tests/phpunit/tests/blocks/getBlockAssetUrl.php @@ -0,0 +1,116 @@ +orig_theme_dir = $wp_theme_directories; + $wp_theme_directories = array( WP_CONTENT_DIR . '/themes', realpath( DIR_TESTDATA . '/themedir1' ) ); + + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + global $wp_theme_directories; + + $wp_theme_directories = $this->orig_theme_dir; + + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + parent::tear_down(); + } + + /** + * @ticket 58525 + */ + public function test_core_block() { + $path = ABSPATH . WPINC . '/blocks/file/view.min.js'; + $url = get_block_asset_url( $path ); + + $this->assertStringNotContainsString( ABSPATH . WPINC, 'The return block asset url should not contain include path.' ); + $this->assertSame( includes_url( '/blocks/file/view.min.js' ), $url, 'The return block asset url should match includes url.' ); + } + + /** + * @ticket 58525 + */ + public function test_parent_theme() { + switch_theme( 'block-theme' ); + + $path = wp_normalize_path( realpath( DIR_TESTDATA . '/themedir1/block-theme/blocks/example-block/view.js' ) ); + $url = get_block_asset_url( $path ); + + $this->assertSame( get_template_directory_uri() . '/blocks/example-block/view.js', $url ); + + } + + /** + * @ticket 58525 + */ + public function test_child_theme() { + switch_theme( 'block-theme-child' ); + + $path = wp_normalize_path( realpath( DIR_TESTDATA . '/themedir1/block-theme-child/blocks/example-block/view.js' ) ); + $url = get_block_asset_url( $path ); + + $this->assertSame( get_stylesheet_directory_uri() . '/blocks/example-block/view.js', $url ); + + } + + /** + * @ticket 58525 + */ + public function test_plugin() { + $path = WP_PLUGIN_DIR . '/test-plugin/blocks/example-block/view.js'; + $url = get_block_asset_url( $path ); + + $this->assertStringNotContainsString( WP_PLUGIN_DIR, $url, 'The return block asset url should not contain plugin path.' ); + $this->assertSame( plugins_url( 'view.js', $path ), $url, 'The return block asset url should match plugin url.' ); + $this->assertStringStartsWith( WP_PLUGIN_URL, $url, 'The return block asset url should contain the url that support with the mu plugin url.' ); + + } + + /** + * @ticket 58525 + */ + public function test_muplugin() { + $path = WPMU_PLUGIN_DIR . '/test-plugin/example-block/view.js'; + $url = get_block_asset_url( $path ); + + $this->assertStringNotContainsString( WPMU_PLUGIN_DIR, $url, 'The return block asset url should not contain plugin path.' ); + $this->assertSame( plugins_url( 'view.js', $path ), $url, 'The return block asset url should match plugin url.' ); + $this->assertStringStartsWith( WPMU_PLUGIN_URL, $url, 'The return block asset url should contain the url that support with the mu plugin url.' ); + } + + /** + * @ticket 58525 + */ + public function test_empty() { + $url = get_block_asset_url( '' ); + + $this->assertFalse( $url ); + } + +}