Make WordPress Core


Ignore:
Timestamp:
04/24/2024 12:00:29 PM (3 months ago)
Author:
Bernhard Reiter
Message:

Block Hooks: Pass correct context to filters.

The $context argument passed to filters such as hooked_block_types, hooked_block, and hooked_block_{$hooked_block_type} allows them to conditionally insert a hooked block. If the anchor block is contained in a template or template part, $context will be set to a WP_Block_Template object reflecting that template or part.

The aforementioned filters are applied when hooked block insertion is run upon reading a template (or part) from the DB (and before sending the template/part content with hooked blocks inserted over the REST API to the client), but also upon writing to the DB, as that's when the ignoredHookedBlocks metadata attribute is set.

Prior to this changeset, the $context passed to Block Hooks related filters in the latter case reflected the template/part that was already stored in the database (if any), which is a bug; instead, it needs to reflect the template/part that will result from the incoming POST network request that will trigger a database update.

Those incoming changes are encapsulated in the $changes argument passed to the reset_pre_insert_template and reset_pre_insert_template_part filters, respectively, and thus to the inject_ignored_hooked_blocks_metadata_attributes function that is hooked to them. $changes is of type stdClass and only contains the fields that need to be updated. That means that in order to create a WP_Block_Template object, a two-step process is needed:

  • Emulate what the updated wp_template or wp_template_part post object in the database will look like by merging $changes on top of the existing $post object fetched from the DB, or from the theme's block template (part) file, if any.
  • Create a WP_Block_Template from the resulting object.

To achieve the latter, a new helper method (_build_block_template_object_from_post_object) is extracted from the existing _build_block_template_result_from_post function. (The latter cannot be used directly as it includes a few database calls that will fail if no post object for the template has existed yet in the database.)

While somewhat complicated to implement, the overall change allows for better separation of concerns and isolation of entities. This is visible e.g. in the fact that inject_ignored_hooked_blocks_metadata_attributes no longer requires a $request argument, which is reflected by unit tests no longer needing to create a $request object to pass to it, thus decoupling the function from the templates endpoint controller.

Unit tests for inject_ignored_hooked_blocks_metadata_attributes have been moved to a new, separate file. Test coverage has been added such that now, all three relevant scenarios are covered:

  • The template doesn't exist in the DB, nor is there a block theme template file for it.
  • The template doesn't exist in the DB, but there is a block theme template file for it.
  • The template already exists in the DB.

Those scenarios also correspond to the logical branching inside WP_REST_Templates_Controller::prepare_item_for_database, which is where inject_ignored_hooked_blocks_metadata_attributes gets its data from.

Reviewed by gziolo.
Merges [57919] to the to the 6.5 branch.

Props tomjcafferkey, bernhard-reiter, gziolo, swissspidy.
Fixes #60754.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/6.5/src/wp-includes/block-template-utils.php

    r57947 r58041  
    724724
    725725/**
    726  * Builds a unified template object based a post Object.
    727  *
    728  * @since 5.9.0
    729  * @since 6.3.0 Added `modified` property to template objects.
    730  * @since 6.4.0 Added support for a revision post to be passed to this function.
     726 * Builds a block template object from a post object.
     727 *
     728 * This is a helper function that creates a block template object from a given post object.
     729 * It is self-sufficient in that it only uses information passed as arguments; it does not
     730 * query the database for additional information.
     731 *
     732 * @since 6.5.1
    731733 * @access private
    732734 *
    733  * @param WP_Post $post Template post.
     735 * @param WP_Post $post  Template post.
     736 * @param array   $terms Additional terms to inform the template object.
     737 * @param array   $meta  Additional meta fields to inform the template object.
    734738 * @return WP_Block_Template|WP_Error Template or error object.
    735739 */
    736 function _build_block_template_result_from_post( $post ) {
     740function _build_block_template_object_from_post_object( $post, $terms = array(), $meta = array() ) {
     741    if ( empty( $terms['wp_theme'] ) ) {
     742        return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
     743    }
     744    $theme = $terms['wp_theme'];
     745
    737746    $default_template_types = get_default_block_template_types();
    738747
    739     $post_id = wp_is_post_revision( $post );
    740     if ( ! $post_id ) {
    741         $post_id = $post;
    742     }
    743     $parent_post = get_post( $post_id );
    744 
    745     $terms = get_the_terms( $parent_post, 'wp_theme' );
    746 
    747     if ( is_wp_error( $terms ) ) {
    748         return $terms;
    749     }
    750 
    751     if ( ! $terms ) {
    752         return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
    753     }
    754 
    755     $theme          = $terms[0]->name;
    756748    $template_file  = _get_block_template_file( $post->post_type, $post->post_name );
    757749    $has_theme_file = get_stylesheet() === $theme && null !== $template_file;
    758750
    759     $origin           = get_post_meta( $parent_post->ID, 'origin', true );
    760     $is_wp_suggestion = get_post_meta( $parent_post->ID, 'is_wp_suggestion', true );
    761 
    762751    $template                 = new WP_Block_Template();
    763752    $template->wp_id          = $post->ID;
    764     $template->id             = $theme . '//' . $parent_post->post_name;
     753    $template->id             = $theme . '//' . $post->post_name;
    765754    $template->theme          = $theme;
    766755    $template->content        = $post->post_content;
    767756    $template->slug           = $post->post_name;
    768757    $template->source         = 'custom';
    769     $template->origin         = ! empty( $origin ) ? $origin : null;
     758    $template->origin         = ! empty( $meta['origin'] ) ? $meta['origin'] : null;
    770759    $template->type           = $post->post_type;
    771760    $template->description    = $post->post_excerpt;
     
    773762    $template->status         = $post->post_status;
    774763    $template->has_theme_file = $has_theme_file;
    775     $template->is_custom      = empty( $is_wp_suggestion );
     764    $template->is_custom      = empty( $meta['is_wp_suggestion'] );
    776765    $template->author         = $post->post_author;
    777766    $template->modified       = $post->post_modified;
    778767
    779     if ( 'wp_template' === $parent_post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
     768    if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
    780769        $template->post_types = $template_file['postTypes'];
    781770    }
    782771
    783     if ( 'wp_template' === $parent_post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
     772    if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
    784773        $template->is_custom = false;
    785774    }
     775
     776    if ( 'wp_template_part' === $post->post_type && isset( $terms['wp_template_part_area'] ) ) {
     777        $template->area = $terms['wp_template_part_area'];
     778    }
     779
     780    return $template;
     781}
     782
     783/**
     784 * Builds a unified template object based a post Object.
     785 *
     786 * @since 5.9.0
     787 * @since 6.3.0 Added `modified` property to template objects.
     788 * @since 6.4.0 Added support for a revision post to be passed to this function.
     789 * @access private
     790 *
     791 * @param WP_Post $post Template post.
     792 * @return WP_Block_Template|WP_Error Template or error object.
     793 */
     794function _build_block_template_result_from_post( $post ) {
     795    $post_id = wp_is_post_revision( $post );
     796    if ( ! $post_id ) {
     797        $post_id = $post;
     798    }
     799    $parent_post     = get_post( $post_id );
     800    $post->post_name = $parent_post->post_name;
     801    $post->post_type = $parent_post->post_type;
     802
     803    $terms = get_the_terms( $parent_post, 'wp_theme' );
     804
     805    if ( is_wp_error( $terms ) ) {
     806        return $terms;
     807    }
     808
     809    if ( ! $terms ) {
     810        return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
     811    }
     812
     813    $terms = array(
     814        'wp_theme' => $terms[0]->name,
     815    );
    786816
    787817    if ( 'wp_template_part' === $parent_post->post_type ) {
    788818        $type_terms = get_the_terms( $parent_post, 'wp_template_part_area' );
    789819        if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
    790             $template->area = $type_terms[0]->name;
    791         }
     820            $terms['wp_template_part_area'] = $type_terms[0]->name;
     821        }
     822    }
     823
     824    $meta = array(
     825        'origin'           => get_post_meta( $parent_post->ID, 'origin', true ),
     826        'is_wp_suggestion' => get_post_meta( $parent_post->ID, 'is_wp_suggestion', true ),
     827    );
     828
     829    $template = _build_block_template_object_from_post_object( $post, $terms, $meta );
     830
     831    if ( is_wp_error( $template ) ) {
     832        return $template;
    792833    }
    793834
     
    14431484 * @access private
    14441485 *
    1445  * @param stdClass        $post    An object representing a template or template part
    1446  *                                 prepared for inserting or updating the database.
    1447  * @param WP_REST_Request $request Request object.
    1448  * @return stdClass The updated object representing a template or template part.
    1449  */
    1450 function inject_ignored_hooked_blocks_metadata_attributes( $post, $request ) {
    1451     $filter_name = current_filter();
    1452     if ( ! str_starts_with( $filter_name, 'rest_pre_insert_' ) ) {
    1453         return $post;
    1454     }
    1455     $post_type = str_replace( 'rest_pre_insert_', '', $filter_name );
     1486 * @param stdClass        $changes    An object representing a template or template part
     1487 *                                    prepared for inserting or updating the database.
     1488 * @param WP_REST_Request $deprecated Deprecated. Not used.
     1489 * @return stdClass|WP_Error The updated object representing a template or template part.
     1490 */
     1491function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated = null ) {
     1492    if ( null !== $deprecated ) {
     1493        _deprecated_argument( __FUNCTION__, '6.5.1' );
     1494    }
    14561495
    14571496    $hooked_blocks = get_hooked_blocks();
    14581497    if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
    1459         return $post;
    1460     }
    1461 
    1462     // At this point, the post has already been created.
    1463     // We need to build the corresponding `WP_Block_Template` object as context argument for the visitor.
    1464     // To that end, we need to suppress hooked blocks from getting inserted into the template.
    1465     add_filter( 'hooked_block_types', '__return_empty_array', 99999, 0 );
    1466     $template = $request['id'] ? get_block_template( $request['id'], $post_type ) : null;
    1467     remove_filter( 'hooked_block_types', '__return_empty_array', 99999 );
     1498        return $changes;
     1499    }
     1500
     1501    $meta  = isset( $changes->meta_input ) ? $changes->meta_input : array();
     1502    $terms = isset( $changes->tax_input ) ? $changes->tax_input : array();
     1503
     1504    if ( empty( $changes->ID ) ) {
     1505        // There's no post object for this template in the database for this template yet.
     1506        $post = $changes;
     1507    } else {
     1508        // Find the existing post object.
     1509        $post = get_post( $changes->ID );
     1510
     1511        // If the post is a revision, use the parent post's post_name and post_type.
     1512        $post_id = wp_is_post_revision( $post );
     1513        if ( $post_id ) {
     1514            $parent_post     = get_post( $post_id );
     1515            $post->post_name = $parent_post->post_name;
     1516            $post->post_type = $parent_post->post_type;
     1517        }
     1518
     1519        // Apply the changes to the existing post object.
     1520        $post = (object) array_merge( (array) $post, (array) $changes );
     1521
     1522        $type_terms        = get_the_terms( $changes->ID, 'wp_theme' );
     1523        $terms['wp_theme'] = ! is_wp_error( $type_terms ) && ! empty( $type_terms ) ? $type_terms[0]->name : null;
     1524    }
     1525
     1526    // Required for the WP_Block_Template. Update the post object with the current time.
     1527    $post->post_modified = current_time( 'mysql' );
     1528
     1529    // If the post_author is empty, set it to the current user.
     1530    if ( empty( $post->post_author ) ) {
     1531        $post->post_author = get_current_user_id();
     1532    }
     1533
     1534    if ( 'wp_template_part' === $post->post_type && ! isset( $terms['wp_template_part_area'] ) ) {
     1535        $area_terms                     = get_the_terms( $changes->ID, 'wp_template_part_area' );
     1536        $terms['wp_template_part_area'] = ! is_wp_error( $area_terms ) && ! empty( $area_terms ) ? $area_terms[0]->name : null;
     1537    }
     1538
     1539    $template = _build_block_template_object_from_post_object( new WP_Post( $post ), $terms, $meta );
     1540
     1541    if ( is_wp_error( $template ) ) {
     1542        return $template;
     1543    }
    14681544
    14691545    $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' );
    14701546    $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' );
    14711547
    1472     $blocks  = parse_blocks( $post->post_content );
    1473     $content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
    1474 
    1475     $post->post_content = $content;
    1476     return $post;
    1477 }
     1548    $blocks                = parse_blocks( $changes->post_content );
     1549    $changes->post_content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
     1550
     1551    return $changes;
     1552}
Note: See TracChangeset for help on using the changeset viewer.