From 73b9a4956f1650a90e2d9efac4286869f0db6699 Mon Sep 17 00:00:00 2001 From: Bernhard Reiter Date: Tue, 2 Jul 2024 10:03:15 +0000 Subject: [PATCH] Block Hooks: Allow child insertion into Template Part block. The Block Hooks mechanism was previously extended to allow insertion of a block as a Navigation block's first or last child. This was implemented by storing the `ignoredHookedBlocks` array in the corresponding `wp_navigation` post's post meta (instead of a metadata attribute on the anchor block). This changeset extends that mechanism to Template Part blocks, by storing said metadata in the corresponding `wp_template_part` post's post meta, thus allowing extenders to use Block Hooks to insert a block as a Template Part block's first or last child, respectively. Props tomjcafferkey, bernhard-reiter. Fixes #60854. Built from https://develop.svn.wordpress.org/trunk@58614 git-svn-id: http://core.svn.wordpress.org/trunk@58047 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/block-template-utils.php | 83 +++++++++++++++++++++++++--- wp-includes/blocks.php | 17 ++++++ wp-includes/version.php | 2 +- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/wp-includes/block-template-utils.php b/wp-includes/block-template-utils.php index 2681acb568..6a4df84a11 100644 --- a/wp-includes/block-template-utils.php +++ b/wp-includes/block-template-utils.php @@ -606,15 +606,35 @@ function _build_block_template_result_from_file( $template_file, $template_type $template->area = $template_file['area']; } + $hooked_blocks = get_hooked_blocks(); + $has_hooked_blocks = ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ); $before_block_visitor = '_inject_theme_attribute_in_template_part_block'; $after_block_visitor = null; - $hooked_blocks = get_hooked_blocks(); - if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { + + if ( $has_hooked_blocks ) { $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); } - $blocks = parse_blocks( $template->content ); - $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + + if ( 'wp_template_part' === $template->type && $has_hooked_blocks ) { + /** + * In order for hooked blocks to be inserted at positions first_child and last_child in a template part, + * we need to wrap its content a mock template part block and traverse it. + */ + $content = get_comment_delimited_block_content( + 'core/template-part', + array(), + $template->content + ); + $content = traverse_and_serialize_blocks( parse_blocks( $content ), $before_block_visitor, $after_block_visitor ); + $template->content = remove_serialized_parent_block( $content ); + } else { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + $before_block_visitor, + $after_block_visitor + ); + } return $template; } @@ -998,8 +1018,28 @@ function _build_block_template_result_from_post( $post ) { if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) { $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); $after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' ); - $blocks = parse_blocks( $template->content ); - $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor ); + if ( 'wp_template_part' === $template->type ) { + $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ); + $attributes = ! empty( $existing_ignored_hooked_blocks ) ? array( 'metadata' => array( 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ) ) ) : array(); + + /** + * In order for hooked blocks to be inserted at positions first_child and last_child in a template part, + * we need to wrap its content a mock template part block and traverse it. + */ + $content = get_comment_delimited_block_content( + 'core/template-part', + $attributes, + $template->content + ); + $content = traverse_and_serialize_blocks( parse_blocks( $content ), $before_block_visitor, $after_block_visitor ); + $template->content = remove_serialized_parent_block( $content ); + } else { + $template->content = traverse_and_serialize_blocks( + parse_blocks( $template->content ), + $before_block_visitor, + $after_block_visitor + ); + } } return $template; @@ -1611,7 +1651,36 @@ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated return $template; } - $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' ); + if ( 'wp_template_part' === $post->post_type ) { + $attributes = array(); + $existing_ignored_hooked_blocks = isset( $post->ID ) ? get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ) : ''; + + if ( ! empty( $existing_ignored_hooked_blocks ) ) { + $attributes['metadata'] = array( + 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ), + ); + } + + $content = get_comment_delimited_block_content( + 'core/template-part', + $attributes, + $changes->post_content + ); + $content = apply_block_hooks_to_content( $content, $template, 'set_ignored_hooked_blocks_metadata' ); + $changes->post_content = remove_serialized_parent_block( $content ); + + $wrapper_block_markup = extract_serialized_parent_block( $content ); + $wrapper_block = parse_blocks( $wrapper_block_markup )[0]; + $ignored_hooked_blocks = $wrapper_block['attrs']['metadata']['ignoredHookedBlocks'] ?? array(); + if ( ! empty( $ignored_hooked_blocks ) ) { + if ( ! isset( $changes->meta_input ) ) { + $changes->meta_input = array(); + } + $changes->meta_input['_wp_ignored_hooked_blocks'] = wp_json_encode( $ignored_hooked_blocks ); + } + } else { + $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' ); + } return $changes; } diff --git a/wp-includes/blocks.php b/wp-includes/blocks.php index 2edcaa8c1b..3b1fc25d48 100644 --- a/wp-includes/blocks.php +++ b/wp-includes/blocks.php @@ -1045,6 +1045,23 @@ function remove_serialized_parent_block( $serialized_block ) { return substr( $serialized_block, $start, $end - $start ); } +/** + * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the wrapper block. + * + * @since 6.7.0 + * @access private + * + * @see remove_serialized_parent_block() + * + * @param string $serialized_block The serialized markup of a block and its inner blocks. + * @return string The serialized markup of the wrapper block. + */ +function extract_serialized_parent_block( $serialized_block ) { + $start = strpos( $serialized_block, '-->' ) + strlen( '-->' ); + $end = strrpos( $serialized_block, '