-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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: Apply to Post Content (on frontend and in editor) #67272
Changes from 9 commits
5dfd1fb
e057209
bc4ad06
5a78e0c
705d3f6
7261f2f
fbb6866
f0908fb
9266a18
87964c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
https://github.com/WordPress/wordpress-develop/pull/7898 | ||
|
||
* https://github.com/WordPress/gutenberg/pull/67272 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,3 +144,165 @@ function gutenberg_stabilize_experimental_block_supports( $args ) { | |
} | ||
|
||
add_filter( 'register_block_type_args', 'gutenberg_stabilize_experimental_block_supports', PHP_INT_MAX, 1 ); | ||
|
||
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' ); | ||
} | ||
// 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. | ||
* | ||
* @since 6.6.0 | ||
* @since 6.8.0 Support non-`wp_navigation` post types. | ||
* | ||
* @param WP_REST_Response $response The response object. | ||
* @param WP_Post $post Post object. | ||
* @return WP_REST_Response The response object. | ||
*/ | ||
function gutenberg_insert_hooked_blocks_into_rest_response( $response, $post ) { | ||
if ( empty( $response->data['content']['raw'] ) || empty( $response->data['content']['rendered'] ) ) { | ||
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'; | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noting that this |
||
$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, | ||
$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; | ||
|
||
// No need to inject hooked blocks twice. | ||
$priority = has_filter( 'the_content', 'apply_block_hooks_to_content' ); | ||
if ( false !== $priority ) { | ||
remove_filter( 'the_content', 'apply_block_hooks_to_content', $priority ); | ||
} | ||
|
||
/** This filter is documented in wp-includes/post-template.php */ | ||
$response->data['content']['rendered'] = apply_filters( 'the_content', $content ); | ||
|
||
// Add back the filter. | ||
if ( false !== $priority ) { | ||
add_filter( 'the_content', 'apply_block_hooks_to_content', $priority ); | ||
} | ||
|
||
return $response; | ||
} | ||
add_filter( 'rest_prepare_page', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); | ||
add_filter( 'rest_prepare_post', 'gutenberg_insert_hooked_blocks_into_rest_response', 10, 2 ); | ||
|
||
/** | ||
* Updates the wp_postmeta with the list of ignored hooked blocks | ||
* where the inner blocks are stored as post content. | ||
* | ||
* @since 6.6.0 | ||
* @since 6.8.0 Support non-`wp_navigation` post types. | ||
ockham marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* @access private | ||
* | ||
* @param stdClass $post Post object. | ||
* @return stdClass The updated post object. | ||
*/ | ||
function gutenberg_update_ignored_hooked_blocks_postmeta( $post ) { | ||
/* | ||
* In this scenario the user has likely tried to create a new post object 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 fields | ||
* and omit the content update. | ||
*/ | ||
if ( ! isset( $post->post_content ) ) { | ||
return $post; | ||
} | ||
|
||
/* | ||
* Skip meta generation if post type is not set. | ||
*/ | ||
if ( ! isset( $post->post_type ) ) { | ||
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, | ||
); | ||
} | ||
|
||
if ( 'wp_navigation' === $post->post_type ) { | ||
$wrapper_block_type = 'core/navigation'; | ||
} else { | ||
$wrapper_block_type = 'core/post-content'; | ||
} | ||
|
||
$markup = get_comment_delimited_block_content( | ||
$wrapper_block_type, | ||
$attributes, | ||
$post->post_content | ||
); | ||
|
||
$existing_post = get_post( $post->ID ); | ||
// Merge the existing post object with the updated post object to pass to the block hooks algorithm for context. | ||
$context = (object) array_merge( (array) $existing_post, (array) $post ); | ||
$context = new WP_Post( $context ); // Convert to WP_Post object. | ||
$serialized_block = apply_block_hooks_to_content( $markup, $context, 'set_ignored_hooked_blocks_metadata' ); | ||
$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 ) ); | ||
} | ||
|
||
if ( ! isset( $post->meta_input ) ) { | ||
$post->meta_input = array(); | ||
} | ||
$post->meta_input['_wp_ignored_hooked_blocks'] = json_encode( $ignored_hooked_blocks ); | ||
} | ||
|
||
$post->post_content = remove_serialized_parent_block( $serialized_block ); | ||
return $post; | ||
} | ||
add_filter( 'rest_pre_insert_page', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); | ||
add_filter( 'rest_pre_insert_post', 'gutenberg_update_ignored_hooked_blocks_postmeta' ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,10 +46,33 @@ 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 ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The trick for the first and last child of the Post Content block works like a charm! |
||
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( ']]>', ']]>', $content ) ); | ||
unset( $seen_ids[ $post_id ] ); | ||
|
||
remove_filter( 'the_content', 'remove_serialized_parent_block', 8 ); | ||
|
||
if ( empty( $content ) ) { | ||
return ''; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As simple as that?
apply_block_hooks_to_content
util is really powerful at this point. What risks do you see with this approach? What if there are synced patterns inside the rendered content? I assume they all are annotated with block hooks to ignore at this point, so double processing should be a non-issue if we ignore the additional CPU usage.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was also really happy when I saw how straight-forward this was.
apply_block_hooks_to_content
was introduced by @tjcafferkey, and it really is a great high-level util to insert Block Hooks into a piece of block markup 😄Yeah, intuitively I'd say this shouldn't duplicate any hooked blocks, but I do want to give it some good smoke testing to be sure.