Make WordPress Core

Changeset 59838


Ignore:
Timestamp:
02/19/2025 02:50:08 PM (2 months ago)
Author:
Bernhard Reiter
Message:

Block Hooks: Add function to encapsulate wrapping in ad-hoc parent.

Introduce a new function, apply_block_hooks_to_content_from_post_object, to colocate the logic used to temporarily wrap content in a parent block (with ignoredHookedBlocks information fetched from post meta) alongside the call to apply_block_hooks_to_content. Fetching that information from post meta is required for all block types that get their content from post objects, i.e. Post Content, Synced Pattern, and Navigation blocks.

Additionally, the newly introduced function contains logic to ensure that insertion of a hooked block into the first_child or last_child position of a given Post Content block works, even if that block only contains "classic" markup (i.e. no blocks).

Props bernhard-reiter, gziolo, mamaduka.
Fixes #61074, #62716.

Location:
trunk
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/blocks.php

    r59761 r59838  
    11421142
    11431143/**
     1144 * Run the Block Hooks algorithm on a post object's content.
     1145 *
     1146 * This function is different from `apply_block_hooks_to_content` in that
     1147 * it takes ignored hooked block information from the post's metadata into
     1148 * account. This ensures that any blocks hooked as first or last child
     1149 * of the block that corresponds to the post type are handled correctly.
     1150 *
     1151 * @since 6.8.0
     1152 * @access private
     1153 *
     1154 * @param string       $content  Serialized content.
     1155 * @param WP_Post|null $post     A post object that the content belongs to. If set to `null`,
     1156 *                               `get_post()` will be called to use the current post as context.
     1157 *                               Default: `null`.
     1158 * @param callable     $callback A function that will be called for each block to generate
     1159 *                               the markup for a given list of blocks that are hooked to it.
     1160 *                               Default: 'insert_hooked_blocks'.
     1161 * @return string The serialized markup.
     1162 */
     1163function apply_block_hooks_to_content_from_post_object( $content, WP_Post $post = null, $callback = 'insert_hooked_blocks' ) {
     1164    // Default to the current post if no context is provided.
     1165    if ( null === $post ) {
     1166        $post = get_post();
     1167    }
     1168
     1169    if ( ! $post instanceof WP_Post ) {
     1170        return apply_block_hooks_to_content( $content, $post, $callback );
     1171    }
     1172
     1173    /*
     1174     * If the content was created using the classic editor or using a single Classic block
     1175     * (`core/freeform`), it might not contain any block markup at all.
     1176     * However, we still might need to inject hooked blocks in the first child or last child
     1177     * positions of the parent block. To be able to apply the Block Hooks algorithm, we wrap
     1178     * the content in a `core/freeform` wrapper block.
     1179     */
     1180    if ( ! has_blocks( $content ) ) {
     1181        $original_content = $content;
     1182
     1183        $content_wrapped_in_classic_block = get_comment_delimited_block_content(
     1184            'core/freeform',
     1185            array(),
     1186            $content
     1187        );
     1188
     1189        $content = $content_wrapped_in_classic_block;
     1190    }
     1191
     1192    $attributes = array();
     1193
     1194    // If context is a post object, `ignoredHookedBlocks` information is stored in its post meta.
     1195    $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
     1196    if ( ! empty( $ignored_hooked_blocks ) ) {
     1197        $ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
     1198        $attributes['metadata'] = array(
     1199            'ignoredHookedBlocks' => $ignored_hooked_blocks,
     1200        );
     1201    }
     1202
     1203    /*
     1204     * We need to wrap the content in a temporary wrapper block with that metadata
     1205     * so the Block Hooks algorithm can insert blocks that are hooked as first or last child
     1206     * of the wrapper block.
     1207     * To that end, we need to determine the wrapper block type based on the post type.
     1208     */
     1209    if ( 'wp_navigation' === $post->post_type ) {
     1210        $wrapper_block_type = 'core/navigation';
     1211    } elseif ( 'wp_block' === $post->post_type ) {
     1212        $wrapper_block_type = 'core/block';
     1213    } else {
     1214        $wrapper_block_type = 'core/post-content';
     1215    }
     1216
     1217    $content = get_comment_delimited_block_content(
     1218        $wrapper_block_type,
     1219        $attributes,
     1220        $content
     1221    );
     1222
     1223    // Apply Block Hooks.
     1224    $content = apply_block_hooks_to_content( $content, $post, $callback );
     1225
     1226    // Finally, we need to remove the temporary wrapper block.
     1227    $content = remove_serialized_parent_block( $content );
     1228
     1229    // If we wrapped the content in a `core/freeform` block, we also need to remove that.
     1230    if ( ! empty( $content_wrapped_in_classic_block ) ) {
     1231        /*
     1232         * We cannot simply use remove_serialized_parent_block() here,
     1233         * as that function assumes that the block wrapper is at the top level.
     1234         * However, there might now be a hooked block inserted next to it
     1235         * (as first or last child of the parent).
     1236         */
     1237        $content = str_replace( $content_wrapped_in_classic_block, $original_content, $content );
     1238    }
     1239
     1240    return $content;
     1241}
     1242
     1243/**
    11441244 * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the inner blocks.
    11451245 *
     
    12981398    }
    12991399
    1300     $attributes            = array();
    1301     $ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
    1302     if ( ! empty( $ignored_hooked_blocks ) ) {
    1303         $ignored_hooked_blocks  = json_decode( $ignored_hooked_blocks, true );
    1304         $attributes['metadata'] = array(
    1305             'ignoredHookedBlocks' => $ignored_hooked_blocks,
    1306         );
    1307     }
    1308 
    1309     if ( 'wp_navigation' === $post->post_type ) {
    1310         $wrapper_block_type = 'core/navigation';
    1311     } elseif ( 'wp_block' === $post->post_type ) {
    1312         $wrapper_block_type = 'core/block';
    1313     } else {
    1314         $wrapper_block_type = 'core/post-content';
    1315     }
    1316 
    1317     $content = get_comment_delimited_block_content(
    1318         $wrapper_block_type,
    1319         $attributes,
    1320         $response->data['content']['raw']
    1321     );
    1322 
    1323     $content = apply_block_hooks_to_content(
    1324         $content,
     1400    $response->data['content']['raw'] = apply_block_hooks_to_content_from_post_object(
     1401        $response->data['content']['raw'],
    13251402        $post,
    13261403        'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
    13271404    );
    13281405
    1329     // Remove mock block wrapper.
    1330     $content = remove_serialized_parent_block( $content );
    1331 
    1332     $response->data['content']['raw'] = $content;
    1333 
    13341406    // If the rendered content was previously empty, we leave it like that.
    13351407    if ( empty( $response->data['content']['rendered'] ) ) {
     
    13381410
    13391411    // `apply_block_hooks_to_content` is called above. Ensure it is not called again as a filter.
    1340     $priority = has_filter( 'the_content', 'apply_block_hooks_to_content' );
     1412    $priority = has_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object' );
    13411413    if ( false !== $priority ) {
    1342         remove_filter( 'the_content', 'apply_block_hooks_to_content', $priority );
     1414        remove_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', $priority );
    13431415    }
    13441416
    13451417    /** This filter is documented in wp-includes/post-template.php */
    1346     $response->data['content']['rendered'] = apply_filters( 'the_content', $content );
     1418    $response->data['content']['rendered'] = apply_filters(
     1419        'the_content',
     1420        $response->data['content']['raw']
     1421    );
    13471422
    13481423    // Restore the filter if it was set initially.
    13491424    if ( false !== $priority ) {
    1350         add_filter( 'the_content', 'apply_block_hooks_to_content', $priority );
     1425        add_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', $priority );
    13511426    }
    13521427
  • trunk/src/wp-includes/default-filters.php

    r59837 r59838  
    198198add_filter( 'the_title', 'trim' );
    199199
    200 add_filter( 'the_content', 'apply_block_hooks_to_content', 8 ); // BEFORE do_blocks().
     200add_filter( 'the_content', 'apply_block_hooks_to_content_from_post_object', 8 ); // BEFORE do_blocks().
    201201add_filter( 'the_content', 'do_blocks', 9 );
    202202add_filter( 'the_content', 'wptexturize' );
Note: See TracChangeset for help on using the changeset viewer.