Skip to content

Commit

Permalink
Block Hooks: Fix truncation of post content (WordPress#68926)
Browse files Browse the repository at this point in the history
* Block Hooks: Repurpose GB compat function
* Block Hooks: Absorb temporary wrapper block functionality into apply_block_hooks_to_content
* Remove now-obsolete logic from Post Content block
* Remove now-obsolete logic from Synced Pattern block
* Whitespace
* Use in gutenberg_insert_hooked_blocks_into_rest_response
* Remove now-obsolete variable
* Use in Navigation block
* Navigation block: Remove now-obsolete Block Hooks helpers
* Rename function
* Replace filters correctly
* Rename context to post
* Add type annotation
* Add PHPDoc
* Add backport changelog

Unlinked contributors: theMasudRana, jacobzymet.

Co-authored-by: ockham <[email protected]>
Co-authored-by: gziolo <[email protected]>
Co-authored-by: Mamaduka <[email protected]>
Co-authored-by: t-hamano <[email protected]>
Co-authored-by: richtabor <[email protected]>
Co-authored-by: rinkalpagdar <[email protected]>
Co-authored-by: PaulREnglish <[email protected]>
Co-authored-by: ryelle <[email protected]>
  • Loading branch information
9 people authored Jan 31, 2025
1 parent bfe6dd8 commit abd963b
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 151 deletions.
3 changes: 3 additions & 0 deletions backport-changelog/6.8/8212.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/8212

* https://github.com/WordPress/gutenberg/pull/68926
122 changes: 81 additions & 41 deletions lib/compat/wordpress-6.8/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,79 @@
* @package gutenberg
*/

function gutenberg_apply_block_hooks_to_post_content( $content ) {
// The `the_content` filter does not provide the post that the content is coming from.
// However, we can infer it by calling `get_post()`, which will return the current post
// if no post ID is provided.
return apply_block_hooks_to_content( $content, get_post(), 'insert_hooked_blocks' );
if ( ! function_exists( 'apply_block_hooks_to_content_from_post_object' ) ) {
/**
* Run the Block Hooks algorithm on a post object's content.
*
* This function is different from `apply_block_hooks_to_content` in that
* it takes ignored hooked block information from the post's metadata into
* account. This ensures that any blocks hooked as first or last child
* of the block that corresponds to the post type are handled correctly.
*
* @since 6.8.0
* @access private
*
* @param string $content Serialized content.
* @param WP_Post|null $post A post object that the content belongs to. If set to `null`,
* `get_post()` will be called to use the current post as context.
* Default: `null`.
* @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_from_post_object( $content, WP_Post $post = null, $callback = 'insert_hooked_blocks' ) {
// Default to the current post if no context is provided.
if ( null === $post ) {
$post = get_post();
}

if ( ! $post instanceof WP_Post ) {
return apply_block_hooks_to_content( $content, $post, $callback );
}

$attributes = array();

// If context is a post object, `ignoredHookedBlocks` information is stored in its post meta.
$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,
);
}

// We need to wrap the content in a temporary wrapper block with that metadata
// so the Block Hooks algorithm can insert blocks that are hooked as first or last child
// of the wrapper block.
// To that end, we need to determine the wrapper block type based on the post type.
if ( 'wp_navigation' === $post->post_type ) {
$wrapper_block_type = 'core/navigation';
} elseif ( 'wp_block' === $post->post_type ) {
$wrapper_block_type = 'core/block';
} else {
$wrapper_block_type = 'core/post-content';
}

$content = get_comment_delimited_block_content(
$wrapper_block_type,
$attributes,
$content
);

// Apply Block Hooks.
$content = apply_block_hooks_to_content( $content, $post, $callback );

// Finally, we need to remove the temporary wrapper block.
$content = remove_serialized_parent_block( $content );

return $content;
}
// We need to apply this filter before `do_blocks` (which is hooked to `the_content` at priority 9).
add_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', 8 );
// Remove apply_block_hooks_to_content filter (previously added in Core).
remove_filter( 'the_content', 'apply_block_hooks_to_content', 8 );
}
// We need to apply this filter before `do_blocks` (which is hooked to `the_content` at priority 9).
add_filter( 'the_content', 'gutenberg_apply_block_hooks_to_post_content', 8 );

/**
* Hooks into the REST API response for the Posts endpoint and adds the first and last inner blocks.
Expand All @@ -29,57 +94,32 @@ function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) {
return $response;
}

$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,
);
}

if ( 'wp_navigation' === $post->post_type ) {
$wrapper_block_type = 'core/navigation';
} elseif ( 'wp_block' === $post->post_type ) {
$wrapper_block_type = 'core/block';
} else {
$wrapper_block_type = 'core/post-content';
}

$content = get_comment_delimited_block_content(
$wrapper_block_type,
$attributes,
$response->data['content']['raw']
);

$content = apply_block_hooks_to_content(
$content,
$response->data['content']['raw'] = apply_block_hooks_to_content_from_post_object(
$response->data['content']['raw'],
$post,
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
);

// Remove mock block wrapper.
$content = remove_serialized_parent_block( $content );

$response->data['content']['raw'] = $content;

// If the rendered content was previously empty, we leave it like that.
if ( empty( $response->data['content']['rendered'] ) ) {
return $response;
}

// No need to inject hooked blocks twice.
$priority = has_filter( 'the_content', 'apply_block_hooks_to_content' );
$priority = has_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object' );
if ( false !== $priority ) {
remove_filter( 'the_content', 'apply_block_hooks_to_content', $priority );
remove_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', $priority );
}

/** This filter is documented in wp-includes/post-template.php */
$response->data['content']['rendered'] = apply_filters( 'the_content', $content );
$response->data['content']['rendered'] = apply_filters(
'the_content',
$response->data['content']['raw']
);

// Add back the filter.
if ( false !== $priority ) {
add_filter( 'the_content', 'apply_block_hooks_to_content', $priority );
add_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', $priority );
}

return $response;
Expand Down
19 changes: 1 addition & 18 deletions packages/block-library/src/block/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,25 +87,8 @@ function render_block_core_block( $attributes ) {
add_filter( 'render_block_context', $filter_block_context, 1 );
}

$ignored_hooked_blocks = get_post_meta( $attributes['ref'], '_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,
);
}

// Wrap in "Block" block so the Block Hooks algorithm can insert blocks
// that are hooked as first or last child of `core/block`.
$content = get_comment_delimited_block_content(
'core/block',
$attributes,
$content
);
// Apply Block Hooks.
$content = apply_block_hooks_to_content( $content, $reusable_block );
// Remove block wrapper.
$content = remove_serialized_parent_block( $content );
$content = apply_block_hooks_to_content_from_post_object( $content, $reusable_block );

$content = do_blocks( $content );
unset( $seen_refs[ $attributes['ref'] ] );
Expand Down
80 changes: 11 additions & 69 deletions packages/block-library/src/navigation/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,12 @@ private static function get_inner_blocks_from_navigation_post( $attributes ) {
// it encounters whitespace. This code strips it.
$blocks = block_core_navigation_filter_out_empty_blocks( $parsed_blocks );

// Run Block Hooks algorithm to inject hooked blocks.
$markup = block_core_navigation_insert_hooked_blocks( $blocks, $navigation_post );
$root_nav_block = parse_blocks( $markup )[0];

$blocks = isset( $root_nav_block['innerBlocks'] ) ? $root_nav_block['innerBlocks'] : $blocks;
// Re-serialize, and run Block Hooks algorithm to inject hooked blocks.
// TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
// before the parse_blocks() call further above, to avoid the extra serialization/parsing.
$markup = serialize_blocks( $blocks );
$markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
$blocks = parse_blocks( $markup );

// TODO - this uses the full navigation block attributes for the
// context which could be refined.
Expand Down Expand Up @@ -1077,12 +1078,11 @@ function block_core_navigation_get_fallback_blocks() {

// Run Block Hooks algorithm to inject hooked blocks.
// We have to run it here because we need the post ID of the Navigation block to track ignored hooked blocks.
$markup = block_core_navigation_insert_hooked_blocks( $fallback_blocks, $navigation_post );
$blocks = parse_blocks( $markup );

if ( isset( $blocks[0]['innerBlocks'] ) ) {
$fallback_blocks = $blocks[0]['innerBlocks'];
}
// TODO: See if we can move the apply_block_hooks_to_content_from_post_object() call
// before the parse_blocks() call further above, to avoid the extra serialization/parsing.
$markup = serialize_blocks( $fallback_blocks );
$markup = apply_block_hooks_to_content_from_post_object( $markup, $navigation_post );
$fallback_blocks = parse_blocks( $markup );
}

/**
Expand Down Expand Up @@ -1436,61 +1436,3 @@ function block_core_navigation_get_most_recently_published_navigation() {

return null;
}

/**
* Mock a parsed block for the Navigation block given its inner blocks and the `wp_navigation` post object.
* The `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is queried to add the `metadata.ignoredHookedBlocks` attribute.
*
* @since 6.5.0
*
* @param array $inner_blocks Parsed inner blocks of a Navigation block.
* @param WP_Post $post `wp_navigation` post object corresponding to the block.
*
* @return array the normalized parsed blocks.
*/
function block_core_navigation_mock_parsed_block( $inner_blocks, $post ) {
$attributes = array();

if ( isset( $post->ID ) ) {
$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,
);
}
}

$mock_anchor_parent_block = array(
'blockName' => 'core/navigation',
'attrs' => $attributes,
'innerBlocks' => $inner_blocks,
'innerContent' => array_fill( 0, count( $inner_blocks ), null ),
);

return $mock_anchor_parent_block;
}

/**
* Insert hooked blocks into a Navigation block.
*
* Given a Navigation block's inner blocks and its corresponding `wp_navigation` post object,
* this function inserts hooked blocks into it, and returns the serialized inner blocks in a
* mock Navigation block wrapper.
*
* If there are any hooked blocks that need to be inserted as the Navigation block's first or last
* children, the `wp_navigation` post's `_wp_ignored_hooked_blocks` meta is checked to see if any
* of those hooked blocks should be exempted from insertion.
*
* @since 6.5.0
*
* @param array $inner_blocks Parsed inner blocks of a Navigation block.
* @param WP_Post $post `wp_navigation` post object corresponding to the block.
* @return string Serialized inner blocks in mock Navigation block wrapper, with hooked blocks inserted, if any.
*/
function block_core_navigation_insert_hooked_blocks( $inner_blocks, $post ) {
$mock_navigation_block = block_core_navigation_mock_parsed_block( $inner_blocks, $post );

$mock_navigation_block_markup = serialize_block( $mock_navigation_block );
return apply_block_hooks_to_content( $mock_navigation_block_markup, $post, 'insert_hooked_blocks' );
}
23 changes: 0 additions & 23 deletions packages/block-library/src/post-content/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,33 +46,10 @@ function render_block_core_post_content( $attributes, $content, $block ) {
$content .= wp_link_pages( array( 'echo' => 0 ) );
}

$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,
);
}

// Wrap in Post Content block so the Block Hooks algorithm can insert blocks
// that are hooked as first or last child of `core/post-content`.
$content = get_comment_delimited_block_content(
'core/post-content',
$attributes,
$content
);

// We need to remove the `core/post-content` block wrapper after the Block Hooks algorithm,
// but before `do_blocks` runs, as it would otherwise attempt to render the same block again --
// thus recursing infinitely.
add_filter( 'the_content', 'remove_serialized_parent_block', 8 );

/** This filter is documented in wp-includes/post-template.php */
$content = apply_filters( 'the_content', str_replace( ']]>', ']]&gt;', $content ) );
unset( $seen_ids[ $post_id ] );

remove_filter( 'the_content', 'remove_serialized_parent_block', 8 );

if ( empty( $content ) ) {
return '';
}
Expand Down

0 comments on commit abd963b

Please sign in to comment.