diff --git a/src/wp-admin/includes/upgrade.php b/src/wp-admin/includes/upgrade.php index dd318200573ea..a49217601fd1a 100644 --- a/src/wp-admin/includes/upgrade.php +++ b/src/wp-admin/includes/upgrade.php @@ -654,6 +654,8 @@ function wp_upgrade() { update_site_meta( get_current_blog_id(), 'db_last_updated', microtime() ); } + delete_transient( 'wp_core_block_css_files' ); + /** * Fires after a site is fully upgraded. * diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 3578b1d25ee78..5fa7f1d6d0ddc 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -186,6 +186,20 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { 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 ) ); @@ -197,14 +211,6 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { 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_path = remove_block_asset_path_prefix( $style_handle ); $is_style_handle = $style_handle === $style_path; // Allow only passing style handles for core blocks. @@ -246,10 +252,9 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { $style_uri = false; } - $style_handle = generate_block_asset_handle( $metadata['name'], $field_name, $index ); - $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; - $result = wp_register_style( - $style_handle, + $version = ! $is_core_block && isset( $metadata['version'] ) ? $metadata['version'] : false; + $result = wp_register_style( + $style_handle_name, $style_uri, array(), $version @@ -259,7 +264,7 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { } if ( $has_style_file ) { - wp_style_add_data( $style_handle, 'path', $style_path_norm ); + 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 ); @@ -268,13 +273,13 @@ function register_block_style_handle( $metadata, $field_name, $index = 0 ) { } if ( is_rtl() && file_exists( $rtl_file ) ) { - wp_style_add_data( $style_handle, 'rtl', 'replace' ); - wp_style_add_data( $style_handle, 'suffix', $suffix ); - wp_style_add_data( $style_handle, 'path', $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; + return $style_handle_name; } /** @@ -320,7 +325,7 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { */ static $core_blocks_meta; if ( ! $core_blocks_meta ) { - $core_blocks_meta = require_once ABSPATH . WPINC . '/blocks/blocks-json.php'; + $core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php'; } $metadata_file = ( ! str_ends_with( $file_or_folder, 'block.json' ) ) ? diff --git a/src/wp-includes/blocks/index.php b/src/wp-includes/blocks/index.php index 654e2fd02ca2e..51cf0c91e8329 100644 --- a/src/wp-includes/blocks/index.php +++ b/src/wp-includes/blocks/index.php @@ -12,6 +12,92 @@ require BLOCKS_PATH . 'widget-group.php'; require BLOCKS_PATH . 'require-dynamic-blocks.php'; +/** + * Registers core block style handles. + * + * While {@see register_block_style_handle()} is typically used for that, the way it is + * implemented is inefficient for core block styles. Registering those style handles here + * avoids unnecessary logic and filesystem lookups in the other function. + * + * @since 6.3.0 + */ +function register_core_block_style_handles() { + if ( ! wp_should_load_separate_core_block_assets() ) { + return; + } + + static $core_blocks_meta; + if ( ! $core_blocks_meta ) { + $core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php'; + } + + $includes_url = includes_url(); + $includes_path = ABSPATH . WPINC . '/'; + $suffix = wp_scripts_get_suffix(); + $wp_styles = wp_styles(); + $style_fields = array( + 'style' => 'style', + 'editorStyle' => 'editor', + ); + + /* + * Ignore transient cache when the development mode is set to 'core'. Why? To avoid interfering with + * the core developer's workflow. + */ + if ( 'core' !== wp_get_development_mode() ) { + $transient_name = 'wp_core_block_css_files'; + $files = get_transient( $transient_name ); + if ( ! $files ) { + $files = glob( __DIR__ . '/**/**.css' ); + set_transient( $transient_name, $files ); + } + } else { + $files = glob( __DIR__ . '/**/**.css' ); + } + + foreach ( $core_blocks_meta as $name => $schema ) { + /** This filter is documented in wp-includes/blocks.php */ + $schema = apply_filters( 'block_type_metadata', $schema ); + + // Backfill these properties similar to `register_block_type_from_metadata()`. + if ( ! isset( $schema['style'] ) ) { + $schema['style'] = "wp-block-{$name}"; + } + if ( ! isset( $schema['editorStyle'] ) ) { + $schema['editorStyle'] = "wp-block-{$name}-editor"; + } + + foreach ( $style_fields as $style_field => $filename ) { + $style_handle = $schema[ $style_field ]; + if ( is_array( $style_handle ) ) { + continue; + } + + $style_path = "blocks/{$name}/{$filename}{$suffix}.css"; + $path = $includes_path . $style_path; + + if ( ! in_array( $path, $files, true ) ) { + $wp_styles->add( + $style_handle, + false + ); + continue; + } + + $wp_styles->add( $style_handle, $includes_url . $style_path ); + $wp_styles->add_data( $style_handle, 'path', $path ); + + $rtl_file = str_replace( "{$suffix}.css", "-rtl{$suffix}.css", $path ); + if ( is_rtl() && in_array( $rtl_file, $files, true ) ) { + $wp_styles->add_data( $style_handle, 'rtl', 'replace' ); + $wp_styles->add_data( $style_handle, 'suffix', $suffix ); + $wp_styles->add_data( $style_handle, 'path', $rtl_file ); + } + } + } +} +add_action( 'init', 'register_core_block_style_handles', 9 ); + /** * Registers core block types using metadata files. * Dynamic core blocks are registered separately. diff --git a/tests/phpunit/tests/blocks/register.php b/tests/phpunit/tests/blocks/register.php index 5711d9c1a781c..f3617f5fad841 100644 --- a/tests/phpunit/tests/blocks/register.php +++ b/tests/phpunit/tests/blocks/register.php @@ -397,6 +397,7 @@ public function data_register_block_style_handle_uses_correct_core_stylesheet() */ public function test_handle_passed_register_block_style_handle() { $metadata = array( + 'name' => 'test-block', 'style' => 'test-style-handle', ); $result = register_block_style_handle( $metadata, 'style' ); @@ -406,6 +407,7 @@ public function test_handle_passed_register_block_style_handle() { public function test_handles_passed_register_block_style_handles() { $metadata = array( + 'name' => 'test-block', 'style' => array( 'test-style-handle', 'test-style-handle-2' ), ); @@ -523,6 +525,26 @@ public function test_success_register_block_style_handle_in_theme() { $this->assertFalse( wp_styles()->get_data( $expected_style_handle, 'rtl' ) ); } + /** + * @ticket 58528 + * + * @covers ::register_block_style_handle + */ + public function test_success_register_block_style_handle_exists() { + $expected_style_handle = 'block-theme-example-block-editor-style'; + wp_register_style( $expected_style_handle, false ); + switch_theme( 'block-theme' ); + + $metadata = array( + 'file' => wp_normalize_path( get_theme_file_path( 'blocks/example-block/block.json' ) ), + 'name' => 'block-theme/example-block', + 'editorStyle' => 'file:./editor-style.css', + ); + $result = register_block_style_handle( $metadata, 'editorStyle' ); + + $this->assertSame( $expected_style_handle, $result ); + } + /** * Tests that the function returns false when the `block.json` is not found * in the WordPress core. diff --git a/tests/phpunit/tests/blocks/registerCoreBlockStyleHandles.php b/tests/phpunit/tests/blocks/registerCoreBlockStyleHandles.php new file mode 100644 index 0000000000000..75c05f8f5911b --- /dev/null +++ b/tests/phpunit/tests/blocks/registerCoreBlockStyleHandles.php @@ -0,0 +1,124 @@ + 'style', + 'editorStyle' => 'editor', + ); + + public function set_up() { + parent::set_up(); + + $this->old_wp_styles = $GLOBALS['wp_styles']; + + $this->includes_url = includes_url(); + + remove_action( 'wp_default_styles', 'wp_default_styles' ); + + if ( empty( $GLOBALS['wp_styles'] ) ) { + $GLOBALS['wp_styles'] = null; + } + } + + public function tear_down() { + $GLOBALS['wp_styles'] = $this->old_wp_styles; + + add_action( 'wp_default_styles', 'wp_default_styles' ); + + parent::tear_down(); + } + + /** + * @ticket 58528 + * + * @dataProvider data_block_data + */ + public function test_wp_should_load_separate_core_block_assets_false( $name, $schema ) { + register_core_block_style_handles(); + + foreach ( self::STYLE_FIELDS as $style_field => $filename ) { + $style_handle = $schema[ $style_field ]; + if ( is_array( $style_handle ) ) { + continue; + } + + $this->assertArrayNotHasKey( $style_handle, $GLOBALS['wp_styles']->registered, 'The key should not exist, as this style should not be registered' ); + } + } + + + /** + * @ticket 58528 + * + * @dataProvider data_block_data + */ + public function test_wp_should_load_separate_core_block_assets_true( $name, $schema ) { + add_filter( 'should_load_separate_core_block_assets', '__return_true' ); + register_core_block_style_handles(); + + $wp_styles = $GLOBALS['wp_styles']; + + foreach ( self::STYLE_FIELDS as $style_field => $filename ) { + $style_handle = $schema[ $style_field ]; + if ( is_array( $style_handle ) ) { + continue; + } + + $this->assertArrayHasKey( $style_handle, $wp_styles->registered, 'The key should exist, as this style should be registered' ); + if ( false === $wp_styles->registered[ $style_handle ]->src ) { + $this->assertEmpty( $wp_styles->registered[ $style_handle ]->extra, 'If source is false, not style path should be set' ); + } else { + $this->assertStringContainsString( $this->includes_url, $wp_styles->registered[ $style_handle ]->src, 'Source of style should contain the includes url' ); + $this->assertNotEmpty( $wp_styles->registered[ $style_handle ]->extra, 'The path of the style should exist' ); + $this->assertArrayHasKey( 'path', $wp_styles->registered[ $style_handle ]->extra, 'The path key of the style should exist in extra array' ); + $this->assertNotEmpty( $wp_styles->registered[ $style_handle ]->extra['path'], 'The path key of the style should not be empty' ); + } + } + } + + + public function data_block_data() { + $core_blocks_meta = require ABSPATH . WPINC . '/blocks/blocks-json.php'; + + // Remove this blocks for now, as they are registered elsewhere. + unset( $core_blocks_meta['archives'] ); + unset( $core_blocks_meta['widget-group'] ); + + $data = array(); + foreach ( $core_blocks_meta as $name => $schema ) { + if ( ! isset( $schema['style'] ) ) { + $schema['style'] = "wp-block-$name"; + } + if ( ! isset( $schema['editorStyle'] ) ) { + $schema['editorStyle'] = "wp-block-{$name}-editor"; + } + + $data[ $name ] = array( $name, $schema ); + } + + return $data; + } +}