diff --git a/wp-admin/includes/post.php b/wp-admin/includes/post.php index ed73b8ff5f..3709ded67f 100644 --- a/wp-admin/includes/post.php +++ b/wp-admin/includes/post.php @@ -2207,6 +2207,7 @@ function taxonomy_meta_box_sanitize_cb_input( $taxonomy, $terms ) { * * @since 5.0.0 * @since 6.3.0 Added `selectors` field. + * @since 6.4.0 Added `block_hooks` field. * * @return array An associative array of registered block data. */ @@ -2221,6 +2222,7 @@ function get_block_editor_server_block_settings() { 'attributes' => 'attributes', 'provides_context' => 'providesContext', 'uses_context' => 'usesContext', + 'block_hooks' => 'blockHooks', 'selectors' => 'selectors', 'supports' => 'supports', 'category' => 'category', diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php index 7795a6969b..bef59a21db 100644 --- a/wp-includes/blocks.php +++ b/wp-includes/blocks.php @@ -342,6 +342,7 @@ function get_block_metadata_i18n_schema() { * @since 5.9.0 Added support for `variations` and `viewScript` fields. * @since 6.1.0 Added support for `render` field. * @since 6.3.0 Added `selectors` field. + * @since 6.4.0 Added support for `blockHooks` field. * * @param string $file_or_folder Path to the JSON file with metadata definition for * the block or path to the folder where the `block.json` file is located. @@ -513,6 +514,39 @@ function register_block_type_from_metadata( $file_or_folder, $args = array() ) { } } + if ( ! empty( $metadata['blockHooks'] ) ) { + /** + * Map camelCased position string (from block.json) to snake_cased block type position. + * + * @var array + */ + $position_mappings = array( + 'before' => 'before', + 'after' => 'after', + 'firstChild' => 'first_child', + 'lastChild' => 'last_child', + ); + + $settings['block_hooks'] = array(); + foreach ( $metadata['blockHooks'] as $anchor_block_name => $position ) { + // Avoid infinite recursion (hooking to itself). + if ( $metadata['name'] === $anchor_block_name ) { + _doing_it_wrong( + __METHOD__, + __( 'Cannot hook block to itself.', 'gutenberg' ), + '6.4.0' + ); + continue; + } + + if ( ! isset( $position_mappings[ $position ] ) ) { + continue; + } + + $settings['block_hooks'][ $anchor_block_name ] = $position_mappings[ $position ]; + } + } + if ( ! empty( $metadata['render'] ) ) { $template_path = wp_normalize_path( realpath( diff --git a/wp-includes/class-wp-block-type.php b/wp-includes/class-wp-block-type.php index e28bacd3b2..b8ffb92e55 100644 --- a/wp-includes/class-wp-block-type.php +++ b/wp-includes/class-wp-block-type.php @@ -173,6 +173,18 @@ class WP_Block_Type { */ public $provides_context = null; + /** + * Block hooks for this block type. + * + * A block hook is specified by a block type and a relative position. + * The hooked block will be automatically inserted in the given position + * next to the "anchor" block whenever the latter is encountered. + * + * @since 6.4.0 + * @var array[] + */ + public $block_hooks = array(); + /** * Block type editor only script handles. * @@ -254,6 +266,7 @@ class WP_Block_Type { * `editor_style_handles`, and `style_handles` properties. * Deprecated the `editor_script`, `script`, `view_script`, `editor_style`, and `style` properties. * @since 6.3.0 Added the `selectors` property. + * @since 6.4.0 Added the `block_hooks` property. * * @see register_block_type() * @@ -284,6 +297,7 @@ class WP_Block_Type { * @type array|null $attributes Block type attributes property schemas. * @type string[] $uses_context Context values inherited by blocks of this type. * @type string[]|null $provides_context Context provided by blocks of this type. + * @type array[] $block_hooks Block hooks. * @type string[] $editor_script_handles Block type editor only script handles. * @type string[] $script_handles Block type front end and editor script handles. * @type string[] $view_script_handles Block type front end only script handles. diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php index aaecc0dcac..cd174e4aaf 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php @@ -291,6 +291,7 @@ class WP_REST_Block_Types_Controller extends WP_REST_Controller { 'editor_style_handles', 'style_handles', 'variations', + 'block_hooks', ), $deprecated_fields ); @@ -706,6 +707,19 @@ class WP_REST_Block_Types_Controller extends WP_REST_Controller { ), 'keywords' => $keywords_definition, 'example' => $example_definition, + 'block_hooks' => array( + 'description' => __( 'This block is automatically inserted near any occurence of the block types used as keys of this map, into a relative position given by the corresponding value.' ), + 'type' => 'object', + 'patternProperties' => array( + '^[a-zA-Z0-9-]+/[a-zA-Z0-9-]+$' => array( + 'type' => 'string', + 'enum' => array( 'before', 'after', 'first_child', 'last_child' ), + ), + ), + 'default' => array(), + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), ), ); diff --git a/wp-includes/version.php b/wp-includes/version.php index 126a3e2370..05a9fe3eb3 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.4-alpha-56586'; +$wp_version = '6.4-alpha-56587'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.