Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Hooks API: Move ignoredHookedBlocks metadata injection logic #6604

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 1 addition & 5 deletions src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -1599,11 +1599,7 @@ function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated
return $template;
}

$before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' );
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' );

$blocks = parse_blocks( $changes->post_content );
$changes->post_content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
$changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' );

return $changes;
}
118 changes: 114 additions & 4 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -1003,14 +1003,124 @@ function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_po
}

/**
* Returns the markup for blocks hooked to the given anchor block in a specific relative position and then
* adds a list of hooked block types to an anchor block's ignored hooked block types.
* Runs the hooked blocks algorithm on the given content.
*
* This function is meant for internal use only.
* @since 6.6.0
* @access private
*
* @param string $content Serialized content.
* @param WP_Block_Template|WP_Post|array $context A block template, template part, `wp_navigation` post object,
* or pattern that the blocks belong to.
* @param callable $callback A function that will be called for each block to generate
* the markup for a given list of blocks that are hooked to it.
* Default: 'insert_hooked_blocks'.
* @return string The serialized markup.
*/
function apply_block_hooks_to_content( $content, $context, $callback = 'insert_hooked_blocks' ) {
$hooked_blocks = get_hooked_blocks();
if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
return $content;
}

$blocks = parse_blocks( $content );

$before_block_visitor = make_before_block_visitor( $hooked_blocks, $context, $callback );
$after_block_visitor = make_after_block_visitor( $hooked_blocks, $context, $callback );

return traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
}

/**
* Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
*
* @since 6.6.0
* @access private
*
* @param string $serialized_block The serialized markup of a block and its inner blocks.
* @return string The serialized markup of the inner blocks.
*/
function remove_serialized_parent_block( $serialized_block ) {
$start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
$end = strrpos( $serialized_block, '<!--' );
return substr( $serialized_block, $start, $end - $start );
}

/**
* Updates the wp_postmeta with the list of ignored hooked blocks where the inner blocks are stored as post content.
* Currently only supports `wp_navigation` post types.
*
* @since 6.6.0
* @access private
*
* @param stdClass $post Post object.
* @return stdClass The updated post object.
*/
function update_ignored_hooked_blocks_postmeta( $post ) {
tjcafferkey marked this conversation as resolved.
Show resolved Hide resolved
/*
* In this scenario the user has likely tried to create a navigation via the REST API.
* In which case we won't have a post ID to work with and store meta against.
*/
if ( empty( $post->ID ) ) {
return $post;
}

/**
* Skip meta generation when consumers intentionally update specific Navigation fields
* and omit the content update.
*/
if ( ! isset( $post->post_content ) ) {
return $post;
}

/**
* Skip meta generation when the post content is not a navigation block.
*/
if ( ! isset( $post->post_type ) || 'wp_navigation' !== $post->post_type ) {
tjcafferkey marked this conversation as resolved.
Show resolved Hide resolved
return $post;
}

$attributes = array();

$ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
if ( ! empty( $ignored_hooked_blocks ) ) {
$ignored_hooked_blocks = json_decode( $ignored_hooked_blocks, true );
$attributes['metadata'] = array(
'ignoredHookedBlocks' => $ignored_hooked_blocks,
);
}

$markup = get_comment_delimited_block_content(
'core/navigation',
$attributes,
$post->post_content
);

$serialized_block = apply_block_hooks_to_content( $markup, get_post( $post->ID ), 'set_ignored_hooked_blocks_metadata' );
tjcafferkey marked this conversation as resolved.
Show resolved Hide resolved
$root_block = parse_blocks( $serialized_block )[0];

$ignored_hooked_blocks = isset( $root_block['attrs']['metadata']['ignoredHookedBlocks'] )
? $root_block['attrs']['metadata']['ignoredHookedBlocks']
: array();

if ( ! empty( $ignored_hooked_blocks ) ) {
$existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
if ( ! empty( $existing_ignored_hooked_blocks ) ) {
$existing_ignored_hooked_blocks = json_decode( $existing_ignored_hooked_blocks, true );
$ignored_hooked_blocks = array_unique( array_merge( $ignored_hooked_blocks, $existing_ignored_hooked_blocks ) );
}
update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
}

$post->post_content = remove_serialized_parent_block( $serialized_block );
return $post;
}

/*
* Returns the markup for blocks hooked to the given anchor block in a specific relative position and then
* adds a list of hooked block types to an anchor block's ignored hooked block types.
*
* This function is meant for internal use only.
*
* @param array $parsed_anchor_block The anchor block, in parsed block array format.
* @param string $relative_position The relative position of the hooked blocks.
* Can be one of 'before', 'after', 'first_child', or 'last_child'.
Expand All @@ -1019,7 +1129,7 @@ function set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_po
* @return string
*/
function insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( &$parsed_anchor_block, $relative_position, $hooked_blocks, $context ) {
$markup = insert_hooked_blocks( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );
$markup = insert_hooked_blocks( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );
$markup .= set_ignored_hooked_blocks_metadata( $parsed_anchor_block, $relative_position, $hooked_blocks, $context );

return $markup;
Expand Down
3 changes: 3 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -757,4 +757,7 @@
add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes' );
add_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes' );

// Update ignoredHookedBlocks postmeta for wp_navigation post type.
add_filter( 'rest_pre_insert_wp_navigation', 'update_ignored_hooked_blocks_postmeta' );
tjcafferkey marked this conversation as resolved.
Show resolved Hide resolved

unset( $filter, $action );
196 changes: 196 additions & 0 deletions tests/phpunit/tests/blocks/updateIgnoredHookedBlocksPostMeta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php
/**
* Tests for update_ignored_hooked_blocks_postmeta
*
* @package WordPress
* @subpackage Blocks
*
* @since 6.6.0
*
* @group blocks
ockham marked this conversation as resolved.
Show resolved Hide resolved
* @covers ::update_ignored_hooked_blocks_postmeta
*/
class Tests_Blocks_UpdateIgnoredHookedBlocksPostMeta extends WP_UnitTestCase {
/**
* Post object.
*
* @var object
*/
protected static $navigation_post;

/**
* Setup method.
*/
public static function wpSetUpBeforeClass() {
self::$navigation_post = self::factory()->post->create_and_get(
array(
'post_type' => 'wp_navigation',
'post_title' => 'Navigation Menu',
'post_content' => 'Original content',
)
);
}

/**
* Tear down each test method.
*/
public function tear_down() {
$registry = WP_Block_Type_Registry::get_instance();

if ( $registry->is_registered( 'tests/my-block' ) ) {
$registry->unregister( 'tests/my-block' );
}

parent::tear_down();
}

/**
* @ticket 60759
*/
public function test_update_ignored_hooked_blocks_postmeta_preserves_entities() {
register_block_type(
'tests/my-block',
array(
'block_hooks' => array(
'core/navigation' => 'last_child',
),
)
);

$original_markup = '<!-- wp:navigation-link {"label":"News & About","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->';
$post = new stdClass();
$post->ID = self::$navigation_post->ID;
$post->post_content = $original_markup;
$post->post_type = 'wp_navigation';

$post = update_ignored_hooked_blocks_postmeta( $post );

// We expect the '&' character to be replaced with its unicode representation.
$expected_markup = str_replace( '&', '\u0026', $original_markup );

$this->assertSame(
$expected_markup,
$post->post_content,
'Post content did not match expected markup with entities escaped.'
);
$this->assertSame(
array( 'tests/my-block' ),
json_decode( get_post_meta( self::$navigation_post->ID, '_wp_ignored_hooked_blocks', true ), true ),
'Block was not added to ignored hooked blocks metadata.'
);
}

/**
* @ticket 60759
*/
public function test_update_ignored_hooked_blocks_postmeta_dont_modify_no_post_id() {
register_block_type(
'tests/my-block',
array(
'block_hooks' => array(
'core/navigation' => 'last_child',
),
)
);

$original_markup = '<!-- wp:navigation-link {"label":"News","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->';
$post = new stdClass();
$post->post_content = $original_markup;
$post->post_type = 'wp_navigation';

$post = update_ignored_hooked_blocks_postmeta( $post );

$this->assertSame(
$original_markup,
$post->post_content,
'Post content did not match the original markup.'
);
}

/**
* @ticket 60759
*/
public function test_update_ignored_hooked_blocks_postmeta_retains_content_if_not_set() {
register_block_type(
'tests/my-block',
array(
'block_hooks' => array(
'core/navigation' => 'last_child',
),
)
);

$post = new stdClass();
$post->ID = self::$navigation_post->ID;
$post->post_title = 'Navigation Menu with changes';
$post->post_type = 'wp_navigation';

$post = update_ignored_hooked_blocks_postmeta( $post );

$this->assertSame(
'Navigation Menu with changes',
$post->post_title,
'Post title was changed.'
);

$this->assertFalse(
isset( $post->post_content ),
'Post content should not be set.'
);
}

/**
* @ticket 60759
*/
public function test_update_ignored_hooked_blocks_postmeta_dont_modify_if_not_navigation() {
register_block_type(
'tests/my-block',
array(
'block_hooks' => array(
'core/navigation' => 'last_child',
),
)
);

$original_markup = '<!-- wp:navigation-link {"label":"News","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->';
$post = new stdClass();
$post->ID = self::$navigation_post->ID;
$post->post_content = $original_markup;
$post->post_type = 'post';

$post = update_ignored_hooked_blocks_postmeta( $post );

$this->assertSame(
$original_markup,
$post->post_content,
'Post content did not match the original markup.'
);
}

/**
* @ticket 60759
*/
public function test_update_ignored_hooked_blocks_postmeta_dont_modify_if_no_post_type() {
register_block_type(
'tests/my-block',
array(
'block_hooks' => array(
'core/navigation' => 'last_child',
),
)
);

$original_markup = '<!-- wp:navigation-link {"label":"News","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->';
$post = new stdClass();
$post->ID = self::$navigation_post->ID;
$post->post_content = $original_markup;

$post = update_ignored_hooked_blocks_postmeta( $post );

$this->assertSame(
$original_markup,
$post->post_content,
'Post content did not match the original markup.'
);
}
}