From cc2133fc3448de9bc050c0a7484f9a08c206847c Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Sat, 28 Oct 2023 01:00:14 +0000 Subject: [PATCH] Blocks: Parse the arguments earlier in `register_block_type_from_metadata()`. This makes it possible to register a block by passing an array of arguments, without the presence of a `block.json` file. Follow-up to [48141], [49948]. Props aristath, spacedmonkey, mukesh27, costdev, audrasjb, oglekler, felipeelia, hellofromTonya. Fixes #56865. git-svn-id: https://develop.svn.wordpress.org/trunk@57026 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/blocks.php | 90 +++++++++--------- tests/phpunit/tests/blocks/register.php | 120 ++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 42 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 01cc2070ac..ce5853d32d 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -352,13 +352,14 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { $file_or_folder; $is_core_block = str_starts_with( $file_or_folder, ABSPATH . WPINC ); - - if ( ! $is_core_block && ! file_exists( $metadata_file ) ) { + // If the block is not a core block, the metadata file must exist. + $metadata_file_exists = $is_core_block || file_exists( $metadata_file ); + if ( ! $metadata_file_exists && empty( $args['name'] ) ) { return false; } // Try to get metadata from the static cache for core blocks. - $metadata = false; + $metadata = array(); if ( $is_core_block ) { $core_block_name = str_replace( ABSPATH . WPINC . '/blocks/', '', $file_or_folder ); if ( ! empty( $core_blocks_meta[ $core_block_name ] ) ) { @@ -367,14 +368,15 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { } // If metadata is not found in the static cache, read it from the file. - if ( ! $metadata ) { + if ( $metadata_file_exists && empty( $metadata ) ) { $metadata = wp_json_file_decode( $metadata_file, array( 'associative' => true ) ); } - if ( ! is_array( $metadata ) || empty( $metadata['name'] ) ) { + if ( ! is_array( $metadata ) || ( empty( $metadata['name'] ) && empty( $args['name'] ) ) ) { return false; } - $metadata['file'] = wp_normalize_path( realpath( $metadata_file ) ); + + $metadata['file'] = $metadata_file_exists ? wp_normalize_path( realpath( $metadata_file ) ) : null; /** * Filters the metadata provided for registering a block type. @@ -404,6 +406,7 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { $settings = array(); $property_mappings = array( 'apiVersion' => 'api_version', + 'name' => 'name', 'title' => 'title', 'category' => 'category', 'parent' => 'parent', @@ -426,18 +429,50 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { foreach ( $property_mappings as $key => $mapped_key ) { if ( isset( $metadata[ $key ] ) ) { $settings[ $mapped_key ] = $metadata[ $key ]; - if ( $textdomain && isset( $i18n_schema->$key ) ) { + if ( $metadata_file_exists && $textdomain && isset( $i18n_schema->$key ) ) { $settings[ $mapped_key ] = translate_settings_using_i18n_schema( $i18n_schema->$key, $settings[ $key ], $textdomain ); } } } + if ( ! empty( $metadata['render'] ) ) { + $template_path = wp_normalize_path( + realpath( + dirname( $metadata['file'] ) . '/' . + remove_block_asset_path_prefix( $metadata['render'] ) + ) + ); + if ( $template_path ) { + /** + * Renders the block on the server. + * + * @since 6.1.0 + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the block content. + */ + $settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) { + ob_start(); + require $template_path; + return ob_get_clean(); + }; + } + } + + $settings = array_merge( $settings, $args ); + $script_fields = array( 'editorScript' => 'editor_script_handles', 'script' => 'script_handles', 'viewScript' => 'view_script_handles', ); foreach ( $script_fields as $metadata_field_name => $settings_field_name ) { + if ( ! empty( $settings[ $metadata_field_name ] ) ) { + $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; + } if ( ! empty( $metadata[ $metadata_field_name ] ) ) { $scripts = $metadata[ $metadata_field_name ]; $processed_scripts = array(); @@ -470,6 +505,9 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { 'style' => 'style_handles', ); foreach ( $style_fields as $metadata_field_name => $settings_field_name ) { + if ( ! empty( $settings[ $metadata_field_name ] ) ) { + $metadata[ $metadata_field_name ] = $settings[ $metadata_field_name ]; + } if ( ! empty( $metadata[ $metadata_field_name ] ) ) { $styles = $metadata[ $metadata_field_name ]; $processed_styles = array(); @@ -530,33 +568,6 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { } } - if ( ! empty( $metadata['render'] ) ) { - $template_path = wp_normalize_path( - realpath( - dirname( $metadata['file'] ) . '/' . - remove_block_asset_path_prefix( $metadata['render'] ) - ) - ); - if ( $template_path ) { - /** - * Renders the block on the server. - * - * @since 6.1.0 - * - * @param array $attributes Block attributes. - * @param string $content Block default content. - * @param WP_Block $block Block instance. - * - * @return string Returns the block content. - */ - $settings['render_callback'] = static function ( $attributes, $content, $block ) use ( $template_path ) { - ob_start(); - require $template_path; - return ob_get_clean(); - }; - } - } - /** * Filters the settings determined from the block type metadata. * @@ -565,14 +576,9 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { * @param array $settings Array of determined settings for registering a block type. * @param array $metadata Metadata provided for registering a block type. */ - $settings = apply_filters( - 'block_type_metadata_settings', - array_merge( - $settings, - $args - ), - $metadata - ); + $settings = apply_filters( 'block_type_metadata_settings', $settings, $metadata ); + + $metadata['name'] = ! empty( $settings['name'] ) ? $settings['name'] : $metadata['name']; return WP_Block_Type_Registry::get_instance()->register( $metadata['name'], diff --git a/tests/phpunit/tests/blocks/register.php b/tests/phpunit/tests/blocks/register.php index 3e55206037..525d7498ae 100644 --- a/tests/phpunit/tests/blocks/register.php +++ b/tests/phpunit/tests/blocks/register.php @@ -599,6 +599,126 @@ class Tests_Blocks_Register extends WP_UnitTestCase { $this->assertFalse( $result ); } + /** + * Tests registering a block using arguments instead of a block.json file. + * + * @ticket 56865 + * + * @covers ::register_block_type_from_metadata + */ + public function test_register_block_type_from_metadata_with_arguments() { + $result = register_block_type_from_metadata( + '', + array( + 'api_version' => 2, + 'name' => 'tests/notice-from-array', + 'title' => 'Notice from array', + 'category' => 'common', + 'icon' => 'star', + 'description' => 'Shows warning, error or success notices… (registered from an array)', + 'keywords' => array( + 'alert', + 'message', + ), + 'textdomain' => 'notice-from-array', + ) + ); + + $this->assertInstanceOf( 'WP_Block_Type', $result, 'The block was not registered' ); + $this->assertSame( 2, $result->api_version, 'The API version is incorrect' ); + $this->assertSame( 'tests/notice-from-array', $result->name, 'The block name is incorrect' ); + $this->assertSame( 'Notice from array', $result->title, 'The block title is incorrect' ); + $this->assertSame( 'common', $result->category, 'The block category is incorrect' ); + $this->assertSame( 'star', $result->icon, 'The block icon is incorrect' ); + $this->assertSame( + 'Shows warning, error or success notices… (registered from an array)', + $result->description, + 'The block description is incorrect' + ); + $this->assertSameSets( array( 'alert', 'message' ), $result->keywords, 'The block keywords are incorrect' ); + } + + /** + * Tests that defined $args can properly override the block.json file. + * + * @ticket 56865 + * + * @covers ::register_block_type_from_metadata + */ + public function test_block_registers_with_args_override() { + $result = register_block_type_from_metadata( + DIR_TESTDATA . '/blocks/notice', + array( + 'name' => 'tests/notice-with-overrides', + 'title' => 'Overriden title', + 'style' => array( 'tests-notice-style-overridden' ), + ) + ); + + $this->assertInstanceOf( 'WP_Block_Type', $result, 'The block was not registered' ); + $this->assertSame( 2, $result->api_version, 'The API version is incorrect' ); + $this->assertSame( 'tests/notice-with-overrides', $result->name, 'The block name was not overridden' ); + $this->assertSame( 'Overriden title', $result->title, 'The block title was not overridden' ); + $this->assertSameSets( + array( 'tests-notice-editor-script' ), + $result->editor_script_handles, + 'The block editor script is incorrect' + ); + $this->assertSameSets( + array( 'tests-notice-style-overridden' ), + $result->style_handles, + 'The block style was not overridden' + ); + $this->assertIsCallable( $result->render_callback ); + } + + /** + * Tests that when the `name` is missing, `register_block_type_from_metadata()` + * will return `false`. + * + * @ticket 56865 + * + * @covers ::register_block_type_from_metadata + * + * @dataProvider data_register_block_registers_with_args_override_returns_false_when_name_is_missing + * + * @param string $file The metadata file. + * @param array $args Array of block type arguments. + */ + public function test_block_registers_with_args_override_returns_false_when_name_is_missing( $file, $args ) { + $this->assertFalse( register_block_type_from_metadata( $file, $args ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_register_block_registers_with_args_override_returns_false_when_name_is_missing() { + return array( + 'no block.json file and no name argument' => array( + 'file' => '', // No block.json file. + 'args' => array( + 'title' => 'Overriden title', + 'style' => array( 'tests-notice-style-overridden' ), + ), + ), + 'existing file and args not an array' => array( + // A file that exists but is empty. This will bypass the file_exists() check. + 'file' => DIR_TESTDATA . '/blocks/notice/block.js', + 'args' => false, + ), + 'existing file and args[name] missing' => array( + // A file that exists but is empty. This will bypass the file_exists() check. + 'file' => DIR_TESTDATA . '/blocks/notice/block.js', + 'args' => array( + 'title' => 'Overriden title', + 'style' => array( 'tests-notice-style-overridden' ), + ), + ), + ); + } + /** * Tests that the function returns the registered block when the `block.json` * is found in the fixtures directory.