diff --git a/src/wp-includes/block-bindings.php b/src/wp-includes/block-bindings.php index 4fc58c3325ba8..a4dc1626e64dd 100644 --- a/src/wp-includes/block-bindings.php +++ b/src/wp-includes/block-bindings.php @@ -38,7 +38,7 @@ * The callback has a mixed return type; it may return a string to override * the block's original value, null, false to remove an attribute, etc. * } - * @return array|false Source when the registration was successful, or `false` on failure. + * @return WP_Block_Bindings_Source|false Source when the registration was successful, or `false` on failure. */ function register_block_bindings_source( string $source_name, array $source_properties ) { return WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_properties ); @@ -50,7 +50,7 @@ function register_block_bindings_source( string $source_name, array $source_prop * @since 6.5.0 * * @param string $source_name Block bindings source name including namespace. - * @return array|false The unregistered block bindings source on success and `false` otherwise. + * @return WP_Block_Bindings_Source|false The unregistered block bindings source on success and `false` otherwise. */ function unregister_block_bindings_source( string $source_name ) { return WP_Block_Bindings_Registry::get_instance()->unregister( $source_name ); @@ -61,7 +61,7 @@ function unregister_block_bindings_source( string $source_name ) { * * @since 6.5.0 * - * @return array The array of registered block bindings sources. + * @return WP_Block_Bindings_Source[] The array of registered block bindings sources. */ function get_all_registered_block_bindings_sources() { return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); @@ -73,7 +73,7 @@ function get_all_registered_block_bindings_sources() { * @since 6.5.0 * * @param string $source_name The name of the source. - * @return array|null The registered block bindings source, or `null` if it is not registered. + * @return WP_Block_Bindings_Source|null The registered block bindings source, or `null` if it is not registered. */ function get_block_bindings_source( string $source_name ) { return WP_Block_Bindings_Registry::get_instance()->get_registered( $source_name ); diff --git a/src/wp-includes/class-wp-block-bindings-registry.php b/src/wp-includes/class-wp-block-bindings-registry.php index 28e8b1b60186e..bdfc024959fc2 100644 --- a/src/wp-includes/class-wp-block-bindings-registry.php +++ b/src/wp-includes/class-wp-block-bindings-registry.php @@ -20,7 +20,7 @@ final class WP_Block_Bindings_Registry { * Holds the registered block bindings sources, keyed by source identifier. * * @since 6.5.0 - * @var array + * @var WP_Block_Bindings_Source[] */ private $sources = array(); @@ -61,7 +61,7 @@ final class WP_Block_Bindings_Registry { * The callback has a mixed return type; it may return a string to override * the block's original value, null, false to remove an attribute, etc. * } - * @return array|false Source when the registration was successful, or `false` on failure. + * @return WP_Block_Bindings_Source|false Source when the registration was successful, or `false` on failure. */ public function register( string $source_name, array $source_properties ) { if ( ! is_string( $source_name ) ) { @@ -102,8 +102,38 @@ public function register( string $source_name, array $source_properties ) { return false; } - $source = array_merge( - array( 'name' => $source_name ), + /* Validate that the source properties contain the label */ + if ( ! isset( $source_properties['label'] ) ) { + _doing_it_wrong( + __METHOD__, + __( 'The $source_properties must contain a "label".' ), + '6.5.0' + ); + return false; + } + + /* Validate that the source properties contain the get_value_callback */ + if ( ! isset( $source_properties['get_value_callback'] ) ) { + _doing_it_wrong( + __METHOD__, + __( 'The $source_properties must contain a "get_value_callback".' ), + '6.5.0' + ); + return false; + } + + /* Validate that the get_value_callback is a valid callback */ + if ( ! is_callable( $source_properties['get_value_callback'] ) ) { + _doing_it_wrong( + __METHOD__, + __( 'The "get_value_callback" parameter must be a valid callback.' ), + '6.5.0' + ); + return false; + } + + $source = new WP_Block_Bindings_Source( + $source_name, $source_properties ); @@ -118,7 +148,7 @@ public function register( string $source_name, array $source_properties ) { * @since 6.5.0 * * @param string $source_name Block bindings source name including namespace. - * @return array|false The unregistered block bindings source on success and `false` otherwise. + * @return WP_Block_Bindings_Source|false The unregistered block bindings source on success and `false` otherwise. */ public function unregister( string $source_name ) { if ( ! $this->is_registered( $source_name ) ) { @@ -142,7 +172,7 @@ public function unregister( string $source_name ) { * * @since 6.5.0 * - * @return array The array of registered sources. + * @return WP_Block_Bindings_Source[] The array of registered sources. */ public function get_all_registered() { return $this->sources; @@ -154,7 +184,7 @@ public function get_all_registered() { * @since 6.5.0 * * @param string $source_name The name of the source. - * @return array|null The registered block bindings source, or `null` if it is not registered. + * @return WP_Block_Bindings_Source|null The registered block bindings source, or `null` if it is not registered. */ public function get_registered( string $source_name ) { if ( ! $this->is_registered( $source_name ) ) { diff --git a/src/wp-includes/class-wp-block-bindings-source.php b/src/wp-includes/class-wp-block-bindings-source.php new file mode 100644 index 0000000000000..c2a3d4f8ae2b8 --- /dev/null +++ b/src/wp-includes/class-wp-block-bindings-source.php @@ -0,0 +1,88 @@ +name = $name; + $this->label = $source_properties['label']; + $this->get_value_callback = $source_properties['get_value_callback']; + } + + /** + * Retrieves the value from the source. + * + * @since 6.5.0 + * + * @param array $source_args Array containing source arguments used to look up the override value, i.e. {"key": "foo"}. + * @param WP_Block $block_instance The block instance. + * @param string $attribute_name The name of the target attribute. + * + * @return mixed The value of the source. + */ + public function get_value( array $source_args, $block_instance, string $attribute_name ) { + return call_user_func_array( $this->get_value_callback, array( $source_args, $block_instance, $attribute_name ) ); + } + + /** + * Wakeup magic method. + * + * @since 6.5.0 + */ + public function __wakeup() { + throw new \LogicException( __CLASS__ . ' should never be unserialized' ); + } +} diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 80757baabce39..de69fda943c2a 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -270,9 +270,8 @@ private function process_block_bindings( $block_content ) { continue; } - $source_callback = $block_binding_source['get_value_callback']; - $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array(); - $source_value = call_user_func_array( $source_callback, array( $source_args, $this, $attribute_name ) ); + $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array(); + $source_value = $block_binding_source->get_value( $source_args, $this, $attribute_name ); // If the value is not null, process the HTML based on the block and the attribute. if ( ! is_null( $source_value ) ) { diff --git a/src/wp-settings.php b/src/wp-settings.php index 22683b37d1f5d..b77a5348e7cfb 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -329,6 +329,7 @@ require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-posts.php'; require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-taxonomies.php'; require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-users.php'; +require ABSPATH . WPINC . '/class-wp-block-bindings-source.php'; require ABSPATH . WPINC . '/class-wp-block-bindings-registry.php'; require ABSPATH . WPINC . '/class-wp-block-editor-context.php'; require ABSPATH . WPINC . '/class-wp-block-type.php'; diff --git a/tests/phpunit/tests/block-bindings/register.php b/tests/phpunit/tests/block-bindings/register.php index 800db70a70da6..49dd203d30ca6 100644 --- a/tests/phpunit/tests/block-bindings/register.php +++ b/tests/phpunit/tests/block-bindings/register.php @@ -11,10 +11,24 @@ */ class Tests_Block_Bindings_Register extends WP_UnitTestCase { - const TEST_SOURCE_NAME = 'test/source'; - const TEST_SOURCE_PROPERTIES = array( - 'label' => 'Test source', - ); + public static $test_source_name = 'test/source'; + public static $test_source_properties = array(); + + /** + * Set up before each test. + * + * @since 6.5.0 + */ + public function set_up() { + parent::set_up(); + + self::$test_source_properties = array( + 'label' => 'Test source', + 'get_value_callback' => function () { + return 'test-value'; + }, + ); + } /** * Tear down after each test. @@ -39,24 +53,25 @@ public function tear_down() { * @covers ::register_block_bindings_source * @covers ::get_all_registered_block_bindings_sources * @covers ::get_block_bindings_source + * @covers WP_Block_Bindings_Source::__construct */ public function test_get_all_registered() { $source_one_name = 'test/source-one'; - $source_one_properties = self::TEST_SOURCE_PROPERTIES; + $source_one_properties = self::$test_source_properties; register_block_bindings_source( $source_one_name, $source_one_properties ); $source_two_name = 'test/source-two'; - $source_two_properties = self::TEST_SOURCE_PROPERTIES; + $source_two_properties = self::$test_source_properties; register_block_bindings_source( $source_two_name, $source_two_properties ); $source_three_name = 'test/source-three'; - $source_three_properties = self::TEST_SOURCE_PROPERTIES; + $source_three_properties = self::$test_source_properties; register_block_bindings_source( $source_three_name, $source_three_properties ); $expected = array( - $source_one_name => array_merge( array( 'name' => $source_one_name ), $source_one_properties ), - $source_two_name => array_merge( array( 'name' => $source_two_name ), $source_two_properties ), - $source_three_name => array_merge( array( 'name' => $source_three_name ), $source_three_properties ), + $source_one_name => new WP_Block_Bindings_Source( $source_one_name, $source_one_properties ), + $source_two_name => new WP_Block_Bindings_Source( $source_two_name, $source_two_properties ), + $source_three_name => new WP_Block_Bindings_Source( $source_three_name, $source_three_properties ), 'core/post-meta' => get_block_bindings_source( 'core/post-meta' ), 'core/pattern-overrides' => get_block_bindings_source( 'core/pattern-overrides' ), ); @@ -72,15 +87,16 @@ public function test_get_all_registered() { * * @covers ::register_block_bindings_source * @covers ::unregister_block_bindings_source + * @covers WP_Block_Bindings_Source::__construct */ public function test_unregister_block_source() { - register_block_bindings_source( self::TEST_SOURCE_NAME, self::TEST_SOURCE_PROPERTIES ); + register_block_bindings_source( self::$test_source_name, self::$test_source_properties ); - $result = unregister_block_bindings_source( self::TEST_SOURCE_NAME ); - $this->assertSame( - array_merge( - array( 'name' => self::TEST_SOURCE_NAME ), - self::TEST_SOURCE_PROPERTIES + $result = unregister_block_bindings_source( self::$test_source_name ); + $this->assertEquals( + new WP_Block_Bindings_Source( + self::$test_source_name, + self::$test_source_properties ), $result ); diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php index e3f205e88ce80..554058f4606c8 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php @@ -13,10 +13,8 @@ */ class Tests_Blocks_wpBlockBindingsRegistry extends WP_UnitTestCase { - const TEST_SOURCE_NAME = 'test/source'; - const TEST_SOURCE_PROPERTIES = array( - 'label' => 'Test source', - ); + public static $test_source_name = 'test/source'; + public static $test_source_properties = array(); /** * Fake block bindings registry. @@ -35,6 +33,13 @@ public function set_up() { parent::set_up(); $this->registry = new WP_Block_Bindings_Registry(); + + self::$test_source_properties = array( + 'label' => 'Test source', + 'get_value_callback' => function () { + return 'test-value'; + }, + ); } /** @@ -58,7 +63,7 @@ public function tear_down() { * @expectedIncorrectUsage WP_Block_Bindings_Registry::register */ public function test_register_invalid_non_string_names() { - $result = $this->registry->register( 1, self::TEST_SOURCE_PROPERTIES ); + $result = $this->registry->register( 1, self::$test_source_properties ); $this->assertFalse( $result ); } @@ -72,7 +77,7 @@ public function test_register_invalid_non_string_names() { * @expectedIncorrectUsage WP_Block_Bindings_Registry::register */ public function test_register_invalid_names_without_namespace() { - $result = $this->registry->register( 'post-meta', self::TEST_SOURCE_PROPERTIES ); + $result = $this->registry->register( 'post-meta', self::$test_source_properties ); $this->assertFalse( $result ); } @@ -100,7 +105,60 @@ public function test_register_invalid_characters() { * @expectedIncorrectUsage WP_Block_Bindings_Registry::register */ public function test_register_invalid_uppercase_characters() { - $result = $this->registry->register( 'Core/PostMeta', self::TEST_SOURCE_PROPERTIES ); + $result = $this->registry->register( 'Core/PostMeta', self::$test_source_properties ); + $this->assertFalse( $result ); + } + + /** + * Should reject block bindings registration without a label. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_missing_label() { + + // Remove the label from the properties. + unset( self::$test_source_properties['label'] ); + + $result = $this->registry->register( self::$test_source_name, self::$test_source_properties ); + $this->assertFalse( $result ); + } + + /** + * Should reject block bindings registration without a get_value_callback. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_missing_get_value_callback() { + + // Remove the get_value_callback from the properties. + unset( self::$test_source_properties['get_value_callback'] ); + + $result = $this->registry->register( self::$test_source_name, self::$test_source_properties ); + $this->assertFalse( $result ); + } + + /** + * Should reject block bindings registration if `get_value_callback` is not a callable. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_incorrect_callback_type() { + + self::$test_source_properties['get_value_callback'] = 'not-a-callback'; + + $result = $this->registry->register( self::$test_source_name, self::$test_source_properties ); $this->assertFalse( $result ); } @@ -110,13 +168,14 @@ public function test_register_invalid_uppercase_characters() { * @ticket 60282 * * @covers WP_Block_Bindings_Registry::register + * @covers WP_Block_Bindings_Source::__construct */ public function test_register_block_binding_source() { - $result = $this->registry->register( self::TEST_SOURCE_NAME, self::TEST_SOURCE_PROPERTIES ); - $this->assertSame( - array_merge( - array( 'name' => self::TEST_SOURCE_NAME ), - self::TEST_SOURCE_PROPERTIES + $result = $this->registry->register( self::$test_source_name, self::$test_source_properties ); + $this->assertEquals( + new WP_Block_Bindings_Source( + self::$test_source_name, + self::$test_source_properties ), $result ); @@ -143,15 +202,16 @@ public function test_unregister_not_registered_block() { * * @covers WP_Block_Bindings_Registry::register * @covers WP_Block_Bindings_Registry::unregister + * WP_Block_Bindings_Source::__construct */ public function test_unregister_block_source() { - $this->registry->register( self::TEST_SOURCE_NAME, self::TEST_SOURCE_PROPERTIES ); + $this->registry->register( self::$test_source_name, self::$test_source_properties ); - $result = $this->registry->unregister( self::TEST_SOURCE_NAME ); - $this->assertSame( - array_merge( - array( 'name' => self::TEST_SOURCE_NAME ), - self::TEST_SOURCE_PROPERTIES + $result = $this->registry->unregister( self::$test_source_name ); + $this->assertEquals( + new WP_Block_Bindings_Source( + self::$test_source_name, + self::$test_source_properties ), $result ); @@ -164,28 +224,29 @@ public function test_unregister_block_source() { * * @covers WP_Block_Bindings_Registry::register * @covers WP_Block_Bindings_Registry::get_all_registered + * WP_Block_Bindings_Source::__construct */ public function test_get_all_registered() { $source_one_name = 'test/source-one'; - $source_one_properties = self::TEST_SOURCE_PROPERTIES; + $source_one_properties = self::$test_source_properties; $this->registry->register( $source_one_name, $source_one_properties ); $source_two_name = 'test/source-two'; - $source_two_properties = self::TEST_SOURCE_PROPERTIES; + $source_two_properties = self::$test_source_properties; $this->registry->register( $source_two_name, $source_two_properties ); $source_three_name = 'test/source-three'; - $source_three_properties = self::TEST_SOURCE_PROPERTIES; + $source_three_properties = self::$test_source_properties; $this->registry->register( $source_three_name, $source_three_properties ); $expected = array( - $source_one_name => array_merge( array( 'name' => $source_one_name ), $source_one_properties ), - $source_two_name => array_merge( array( 'name' => $source_two_name ), $source_two_properties ), - $source_three_name => array_merge( array( 'name' => $source_three_name ), $source_three_properties ), + $source_one_name => new WP_Block_Bindings_Source( $source_one_name, $source_one_properties ), + $source_two_name => new WP_Block_Bindings_Source( $source_two_name, $source_two_properties ), + $source_three_name => new WP_Block_Bindings_Source( $source_three_name, $source_three_properties ), ); $registered = $this->registry->get_all_registered(); - $this->assertSame( $expected, $registered ); + $this->assertEquals( $expected, $registered ); } /** @@ -197,7 +258,7 @@ public function test_get_all_registered() { * @covers WP_Block_Bindings_Registry::get_registered */ public function test_get_registered_rejects_unknown_source_name() { - $this->registry->register( self::TEST_SOURCE_NAME, self::TEST_SOURCE_PROPERTIES ); + $this->registry->register( self::$test_source_name, self::$test_source_properties ); $source = $this->registry->get_registered( 'test/unknown-source' ); $this->assertNull( $source ); @@ -210,26 +271,26 @@ public function test_get_registered_rejects_unknown_source_name() { * * @covers WP_Block_Bindings_Registry::register * @covers WP_Block_Bindings_Registry::get_registered + * @covers WP_Block_Bindings_Source::__construct */ public function test_get_registered() { $source_one_name = 'test/source-one'; - $source_one_properties = self::TEST_SOURCE_PROPERTIES; + $source_one_properties = self::$test_source_properties; $this->registry->register( $source_one_name, $source_one_properties ); $source_two_name = 'test/source-two'; - $source_two_properties = self::TEST_SOURCE_PROPERTIES; + $source_two_properties = self::$test_source_properties; $this->registry->register( $source_two_name, $source_two_properties ); $source_three_name = 'test/source-three'; - $source_three_properties = self::TEST_SOURCE_PROPERTIES; + $source_three_properties = self::$test_source_properties; $this->registry->register( $source_three_name, $source_three_properties ); - $result = $this->registry->get_registered( 'test/source-two' ); - $this->assertSame( - array_merge( - array( 'name' => $source_two_name ), - $source_two_properties - ), + $expected = new WP_Block_Bindings_Source( $source_two_name, $source_two_properties ); + $result = $this->registry->get_registered( 'test/source-two' ); + + $this->assertEquals( + $expected, $result ); } @@ -255,9 +316,9 @@ public function test_is_registered_for_unknown_source() { * @covers WP_Block_Bindings_Registry::is_registered */ public function test_is_registered_for_known_source() { - $this->registry->register( self::TEST_SOURCE_NAME, self::TEST_SOURCE_PROPERTIES ); + $this->registry->register( self::$test_source_name, self::$test_source_properties ); - $result = $this->registry->is_registered( self::TEST_SOURCE_NAME ); + $result = $this->registry->is_registered( self::$test_source_name ); $this->assertTrue( $result ); } }