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

Navigation Block: Fix erroneous escaping of ampersands (etc) #59561

Merged
merged 9 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions packages/block-library/src/navigation/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -1458,13 +1458,18 @@ function block_core_navigation_set_ignored_hooked_blocks_metadata( $inner_blocks
/**
* Updates the post meta with the list of ignored hooked blocks when the navigation is created or updated via the REST API.
*
* @param WP_Post $post Post object.
* @param stdClass $post Post object.
*/
function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
// We run the Block Hooks mechanism to inject the `metadata.ignoredHookedBlocks` attribute into
// all anchor blocks. For the root level, we create a mock Navigation and extract them from there.
$blocks = parse_blocks( $post->post_content );
$markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, $post );

// Block Hooks logic requires a `WP_Post` object (rather than the `stdClass` with the updates that
// we're getting from the `rest_pre_insert_wp_navigation` filter) as its second argument (to be
// used as context for hooked blocks insertion).
// We thus have to look it up from the DB,based on `$post->ID`.
$markup = block_core_navigation_set_ignored_hooked_blocks_metadata( $blocks, get_post( $post->ID ) );

$root_nav_block = parse_blocks( $markup )[0];
$ignored_hooked_blocks = isset( $root_nav_block['attrs']['metadata']['ignoredHookedBlocks'] )
Expand All @@ -1480,14 +1485,8 @@ function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {
update_post_meta( $post->ID, '_wp_ignored_hooked_blocks', json_encode( $ignored_hooked_blocks ) );
}

$serialized_inner_blocks = block_core_navigation_remove_serialized_parent_block( $markup );

wp_update_post(
array(
'ID' => $post->ID,
'post_content' => $serialized_inner_blocks,
)
);
$post->post_content = block_core_navigation_remove_serialized_parent_block( $markup );
return $post;
}

// Before adding our filter, we verify if it's already added in Core.
Expand All @@ -1497,8 +1496,15 @@ function block_core_navigation_update_ignore_hooked_blocks_meta( $post ) {

// Injection of hooked blocks into the Navigation block relies on some functions present in WP >= 6.5
// that are not present in Gutenberg's WP 6.5 compatibility layer.
if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
add_action( 'rest_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10, 3 );
if ( function_exists( 'set_ignored_hooked_blocks_metadata' ) && ! has_filter( 'rest_pre_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
add_filter( 'rest_pre_insert_wp_navigation', 'block_core_navigation_update_ignore_hooked_blocks_meta', 10 );
}

// Previous versions of Gutenberg and WordPress 6.5 Betas were attaching the block_core_navigation_update_ignore_hooked_blocks_meta
// function to the `rest_insert_wp_navigation` _action_ (rather than the `rest_pre_insert_wp_navigation` _filter_).
// To avoid collisions, we need to remove the filter from that action if it's present.
if ( has_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback ) ) {
remove_filter( 'rest_insert_wp_navigation', $rest_insert_wp_navigation_core_callback, 10 );
}

/**
Expand Down
95 changes: 95 additions & 0 deletions phpunit/blocks/block-navigation-block-hooks-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php
/**
* Navigation block block hooks tests.
*
* @package WordPress
* @subpackage Blocks
*/

/**
* Tests for the Navigation block.
*
* @group blocks
*/
class Block_Navigation_Block_Hooks_Test extends WP_UnitTestCase {
/**
* Original markup.
*
* @var string
*/
protected static $original_markup;

/**
* Post object.
*
* @var object
*/
protected static $navigation_post;

/**
* Setup method.
*/
public static function wpSetUpBeforeClass() {
//self::$original_markup = '<!-- wp:navigation-link {"label":"News & About","type":"page","id":2,"url":"http://localhost:8888/?page_id=2","kind":"post-type"} /-->';

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();
}

/**
* @covers ::gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta
*/
public function test_block_core_navigation_update_ignore_hooked_blocks_meta_preserves_entities() {
if ( ! function_exists( 'set_ignored_hooked_blocks_metadata' ) ) {
$this->markTestSkipped( 'Test skipped on WordPress versions that do not included required Block Hooks functionalit.' );
}

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 = gutenberg_block_core_navigation_update_ignore_hooked_blocks_meta( $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.'
);
}
}
Loading