From 5929f23e5dc563c361a0c76ba0616ec4854983e3 Mon Sep 17 00:00:00 2001 From: Bernie Reiter <96308+ockham@users.noreply.github.com> Date: Tue, 5 Mar 2024 13:59:24 +0100 Subject: [PATCH] Navigation Block: Fix erroneous escaping of ampersands (etc) (#59561) Fix erroneous escaping of ampersands to `u0026amp;`. This is done by using the [`rest_pre_insert_{$this->post_type}`](https://developer.wordpress.org/reference/hooks/rest_pre_insert_this-post_type/) _filter_ rather than the `rest_insert_wp_navigation` _action_. This avoids calling `wp_update_post` twice, which was the original reason of the issue, as it removed the backslash from the already-encoded entity. Unlinked contributors: kylekelly. Co-authored-by: ockham Co-authored-by: t-hamano Co-authored-by: tjcafferkey Co-authored-by: youknowriad Co-authored-by: draganescu Co-authored-by: annezazu Co-authored-by: fabiankaegy Co-authored-by: getdave Co-authored-by: swissspidy --- .../block-library/src/navigation/index.php | 30 +++--- .../block-navigation-block-hooks-test.php | 95 +++++++++++++++++++ 2 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 phpunit/blocks/block-navigation-block-hooks-test.php diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 1d73d09bbd1fb..18637d9ad3ee4 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -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'] ) @@ -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. @@ -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 ); } /** diff --git a/phpunit/blocks/block-navigation-block-hooks-test.php b/phpunit/blocks/block-navigation-block-hooks-test.php new file mode 100644 index 0000000000000..e0e7bd45cfc17 --- /dev/null +++ b/phpunit/blocks/block-navigation-block-hooks-test.php @@ -0,0 +1,95 @@ +'; + + 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 = ''; + $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.' + ); + } +}