diff --git a/src/wp-includes/class-wp-block-bindings-registry.php b/src/wp-includes/class-wp-block-bindings-registry.php new file mode 100644 index 0000000000000..baffd2f3dc900 --- /dev/null +++ b/src/wp-includes/class-wp-block-bindings-registry.php @@ -0,0 +1,194 @@ +is_registered( $source_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block binding source name. */ + sprintf( __( 'Block binding source "%s" already registered.' ), $source_name ), + '6.5.0' + ); + return false; + } + + $source = array_merge( + array( 'name' => $source_name ), + $source_properties + ); + + $this->sources[ $source_name ] = $source; + + return $source; + } + + /** + * Unregisters a block binding source. + * + * @since 6.5.0 + * + * @param string $source_name Block binding source name including namespace. + * @return array|false The unregistred block binding source on success and `false` otherwise. + */ + public function unregister( $source_name ) { + if ( ! $this->is_registered( $source_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block binding source name. */ + sprintf( __( 'Block binding "%s" not found.' ), $source_name ), + '6.5.0' + ); + return false; + } + + $unregistered_source = $this->sources[ $source_name ]; + unset( $this->sources[ $source_name ] ); + + return $unregistered_source; + } + + /** + * Retrieves the list of all registered block bindings sources. + * + * @since 6.5.0 + * + * @return array The array of registered sources. + */ + public function get_all_registered() { + return $this->sources; + } + + /** + * Retrieves a registered block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return array|null The registered block binding source, or `null` if it is not registered. + */ + public function get_registered( $source_name ) { + if ( ! $this->is_registered( $source_name ) ) { + return null; + } + + return $this->sources[ $source_name ]; + } + + /** + * Checks if a block binding source is registered. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return bool `true` if the block binding source is registered, `false` otherwise. + */ + public function is_registered( $source_name ) { + return isset( $this->sources[ $source_name ] ); + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.5.0 + * + * @return WP_Block_Bindings_Registry The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } +} diff --git a/src/wp-settings.php b/src/wp-settings.php index e30eeb493b86b..9904bf500371f 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -330,6 +330,7 @@ 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-editor-context.php'; +require ABSPATH . WPINC . '/class-wp-block-bindings-registry.php'; require ABSPATH . WPINC . '/class-wp-block-type.php'; require ABSPATH . WPINC . '/class-wp-block-pattern-categories-registry.php'; require ABSPATH . WPINC . '/class-wp-block-patterns-registry.php'; diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php new file mode 100644 index 0000000000000..8fcd9648ad846 --- /dev/null +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsRegistry.php @@ -0,0 +1,254 @@ + 'Test source', + ); + + /** + * Fake block bindings registry. + * + * @since 6.5.0 + * @var WP_Block_Bindings_Registry + */ + private $registry = null; + + /** + * Set up each test method. + * + * @since 6.5.0 + */ + public function set_up() { + parent::set_up(); + + $this->registry = new WP_Block_Bindings_Registry(); + } + + /** + * Tear down each test method. + * + * @since 6.5.0 + */ + public function tear_down() { + $this->registry = null; + + parent::tear_down(); + } + + /** + * Should reject numbers as block binding source name. + * + * @ticket 60282 + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_non_string_names() { + $result = $this->registry->register( 1, self::TEST_SOURCE_PROPERTIES ); + $this->assertFalse( $result ); + } + + /** + * Should reject block binding source name without a namespace. + * + * @ticket 60282 + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_names_without_namespace() { + $result = $this->registry->register( 'post-meta', self::TEST_SOURCE_PROPERTIES ); + $this->assertFalse( $result ); + } + + /** + * Should reject block binding source name with invalid characters. + * + * @ticket 60282 + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_characters() { + $result = $this->registry->register( 'still/_doing_it_wrong', array() ); + $this->assertFalse( $result ); + } + + /** + * Should reject block binding source name with uppercase characters. + * + * @ticket 60282 + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::register + */ + public function test_register_invalid_uppercase_characters() { + $result = $this->registry->register( 'Core/PostMeta', self::TEST_SOURCE_PROPERTIES ); + $this->assertFalse( $result ); + } + + /** + * Should accept valid block binding source. + * + * @covers WP_Block_Bindings_Registry::register + * + * @ticket 60282 + */ + 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 + ); + } + + /** + * Unregistering should fail if a block binding source is not registered. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::unregister + * + * @expectedIncorrectUsage WP_Block_Bindings_Registry::unregister + */ + public function test_unregister_not_registered_block() { + $result = $this->registry->unregister( 'test/unregistered' ); + $this->assertFalse( $result ); + } + + /** + * Should unregister existing block binding source. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::unregister + */ + public function test_unregister_block_source() { + $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 + ); + } + + /** + * Should find all registered sources. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * @covers WP_Block_Bindings_Registry::get_all_registered + */ + public function test_get_all_registered() { + $source_one_name = 'test/source-one'; + $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; + $this->registry->register( $source_two_name, $source_two_properties ); + + $source_three_name = 'test/source-three'; + $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 ), + ); + + $registered = $this->registry->get_all_registered(); + $this->assertSame( $expected, $registered ); + } + + /** + * Should not find source that's not registered. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * @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 ); + + $source = $this->registry->get_registered( 'test/unknown-source' ); + $this->assertNull( $source ); + } + + /** + * Should find registered block binding source by name. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * @covers WP_Block_Bindings_Registry::get_registered + */ + public function test_get_registered() { + $source_one_name = 'test/source-one'; + $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; + $this->registry->register( $source_two_name, $source_two_properties ); + + $source_three_name = 'test/source-three'; + $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 + ), + $result + ); + } + + /** + * Should return false for source that's not registered. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::is_registered + */ + public function test_is_registered_for_unknown_source() { + $result = $this->registry->is_registered( 'test/one' ); + $this->assertFalse( $result ); + } + + /** + * Should return true if source is registered. + * + * @ticket 60282 + * + * @covers WP_Block_Bindings_Registry::register + * @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 ); + + $result = $this->registry->is_registered( self::TEST_SOURCE_NAME ); + $this->assertTrue( $result ); + } +}