Make WordPress Core


Ignore:
Timestamp:
04/03/2024 03:09:38 PM (4 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.

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

File:
1 edited

Legend:

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

    r57790 r57919  
    726726
    727727/**
    728  * Builds a unified template object based a post Object.
    729  *
    730  * @since 5.9.0
    731  * @since 6.3.0 Added `modified` property to template objects.
    732  * @since 6.4.0 Added support for a revision post to be passed to this function.
     728 * Builds a block template object from a post object.
     729 *
     730 * This is a helper function that creates a block template object from a given post object.
     731 * It is self-sufficient in that it only uses information passed as arguments; it does not
     732 * query the database for additional information.
     733 *
     734 * @since 6.5.1
    733735 * @access private
    734736 *
    735  * @param WP_Post $post Template post.
     737 * @param WP_Post $post  Template post.
     738 * @param array   $terms Additional terms to inform the template object.
     739 * @param array   $meta  Additional meta fields to inform the template object.
    736740 * @return WP_Block_Template|WP_Error Template or error object.
    737741 */
    738 function _build_block_template_result_from_post( $post ) {
     742function _build_block_template_object_from_post_object( $post, $terms = array(), $meta = array() ) {
     743    if ( empty( $terms['wp_theme'] ) ) {
     744        return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
     745    }
     746    $theme = $terms['wp_theme'];
     747
    739748    $default_template_types = get_default_block_template_types();
    740749
    741     $post_id = wp_is_post_revision( $post );
    742     if ( ! $post_id ) {
    743         $post_id = $post;
    744     }
    745     $parent_post = get_post( $post_id );
    746 
    747     $terms = get_the_terms( $parent_post, 'wp_theme' );
    748 
    749     if ( is_wp_error( $terms ) ) {
    750         return $terms;
    751     }
    752 
    753     if ( ! $terms ) {
    754         return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
    755     }
    756 
    757     $theme          = $terms[0]->name;
    758750    $template_file  = _get_block_template_file( $post->post_type, $post->post_name );
    759751    $has_theme_file = get_stylesheet() === $theme && null !== $template_file;
    760752
    761     $origin           = get_post_meta( $parent_post->ID, 'origin', true );
    762     $is_wp_suggestion = get_post_meta( $parent_post->ID, 'is_wp_suggestion', true );
    763 
    764753    $template                 = new WP_Block_Template();
    765754    $template->wp_id          = $post->ID;
    766     $template->id             = $theme . '//' . $parent_post->post_name;
     755    $template->id             = $theme . '//' . $post->post_name;
    767756    $template->theme          = $theme;
    768757    $template->content        = $post->post_content;
    769758    $template->slug           = $post->post_name;
    770759    $template->source         = 'custom';
    771     $template->origin         = ! empty( $origin ) ? $origin : null;
     760    $template->origin         = ! empty( $meta['origin'] ) ? $meta['origin'] : null;
    772761    $template->type           = $post->post_type;
    773762    $template->description    = $post->post_excerpt;
     
    775764    $template->status         = $post->post_status;
    776765    $template->has_theme_file = $has_theme_file;
    777     $template->is_custom      = empty( $is_wp_suggestion );
     766    $template->is_custom      = empty( $meta['is_wp_suggestion'] );
    778767    $template->author         = $post->post_author;
    779768    $template->modified       = $post->post_modified;
    780769
    781     if ( 'wp_template' === $parent_post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
     770    if ( 'wp_template' === $post->post_type && $has_theme_file && isset( $template_file['postTypes'] ) ) {
    782771        $template->post_types = $template_file['postTypes'];
    783772    }
    784773
    785     if ( 'wp_template' === $parent_post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
     774    if ( 'wp_template' === $post->post_type && isset( $default_template_types[ $template->slug ] ) ) {
    786775        $template->is_custom = false;
    787776    }
     777
     778    if ( 'wp_template_part' === $post->post_type && isset( $terms['wp_template_part_area'] ) ) {
     779        $template->area = $terms['wp_template_part_area'];
     780    }
     781
     782    return $template;
     783}
     784
     785/**
     786 * Builds a unified template object based a post Object.
     787 *
     788 * @since 5.9.0
     789 * @since 6.3.0 Added `modified` property to template objects.
     790 * @since 6.4.0 Added support for a revision post to be passed to this function.
     791 * @access private
     792 *
     793 * @param WP_Post $post Template post.
     794 * @return WP_Block_Template|WP_Error Template or error object.
     795 */
     796function _build_block_template_result_from_post( $post ) {
     797    $post_id = wp_is_post_revision( $post );
     798    if ( ! $post_id ) {
     799        $post_id = $post;
     800    }
     801    $parent_post     = get_post( $post_id );
     802    $post->post_name = $parent_post->post_name;
     803    $post->post_type = $parent_post->post_type;
     804
     805    $terms = get_the_terms( $parent_post, 'wp_theme' );
     806
     807    if ( is_wp_error( $terms ) ) {
     808        return $terms;
     809    }
     810
     811    if ( ! $terms ) {
     812        return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.' ) );
     813    }
     814
     815    $terms = array(
     816        'wp_theme' => $terms[0]->name,
     817    );
    788818
    789819    if ( 'wp_template_part' === $parent_post->post_type ) {
    790820        $type_terms = get_the_terms( $parent_post, 'wp_template_part_area' );
    791821        if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
    792             $template->area = $type_terms[0]->name;
    793         }
     822            $terms['wp_template_part_area'] = $type_terms[0]->name;
     823        }
     824    }
     825
     826    $meta = array(
     827        'origin'           => get_post_meta( $parent_post->ID, 'origin', true ),
     828        'is_wp_suggestion' => get_post_meta( $parent_post->ID, 'is_wp_suggestion', true ),
     829    );
     830
     831    $template = _build_block_template_object_from_post_object( $post, $terms, $meta );
     832
     833    if ( is_wp_error( $template ) ) {
     834        return $template;
    794835    }
    795836
     
    14451486 * @access private
    14461487 *
    1447  * @param stdClass        $post    An object representing a template or template part
    1448  *                                 prepared for inserting or updating the database.
    1449  * @param WP_REST_Request $request Request object.
    1450  * @return stdClass The updated object representing a template or template part.
    1451  */
    1452 function inject_ignored_hooked_blocks_metadata_attributes( $post, $request ) {
    1453     $filter_name = current_filter();
    1454     if ( ! str_starts_with( $filter_name, 'rest_pre_insert_' ) ) {
    1455         return $post;
    1456     }
    1457     $post_type = str_replace( 'rest_pre_insert_', '', $filter_name );
     1488 * @param stdClass        $changes    An object representing a template or template part
     1489 *                                    prepared for inserting or updating the database.
     1490 * @param WP_REST_Request $deprecated Deprecated. Not used.
     1491 * @return stdClass|WP_Error The updated object representing a template or template part.
     1492 */
     1493function inject_ignored_hooked_blocks_metadata_attributes( $changes, $deprecated = null ) {
     1494    if ( null !== $deprecated ) {
     1495        _deprecated_argument( __FUNCTION__, '6.5.1' );
     1496    }
    14581497
    14591498    $hooked_blocks = get_hooked_blocks();
    14601499    if ( empty( $hooked_blocks ) && ! has_filter( 'hooked_block_types' ) ) {
    1461         return $post;
    1462     }
    1463 
    1464     // At this point, the post has already been created.
    1465     // We need to build the corresponding `WP_Block_Template` object as context argument for the visitor.
    1466     // To that end, we need to suppress hooked blocks from getting inserted into the template.
    1467     add_filter( 'hooked_block_types', '__return_empty_array', 99999, 0 );
    1468     $template = $request['id'] ? get_block_template( $request['id'], $post_type ) : null;
    1469     remove_filter( 'hooked_block_types', '__return_empty_array', 99999 );
     1500        return $changes;
     1501    }
     1502
     1503    $meta  = isset( $changes->meta_input ) ? $changes->meta_input : array();
     1504    $terms = isset( $changes->tax_input ) ? $changes->tax_input : array();
     1505
     1506    if ( empty( $changes->ID ) ) {
     1507        // There's no post object for this template in the database for this template yet.
     1508        $post = $changes;
     1509    } else {
     1510        // Find the existing post object.
     1511        $post = get_post( $changes->ID );
     1512
     1513        // If the post is a revision, use the parent post's post_name and post_type.
     1514        $post_id = wp_is_post_revision( $post );
     1515        if ( $post_id ) {
     1516            $parent_post     = get_post( $post_id );
     1517            $post->post_name = $parent_post->post_name;
     1518            $post->post_type = $parent_post->post_type;
     1519        }
     1520
     1521        // Apply the changes to the existing post object.
     1522        $post = (object) array_merge( (array) $post, (array) $changes );
     1523
     1524        $type_terms        = get_the_terms( $changes->ID, 'wp_theme' );
     1525        $terms['wp_theme'] = ! is_wp_error( $type_terms ) && ! empty( $type_terms ) ? $type_terms[0]->name : null;
     1526    }
     1527
     1528    // Required for the WP_Block_Template. Update the post object with the current time.
     1529    $post->post_modified = current_time( 'mysql' );
     1530
     1531    // If the post_author is empty, set it to the current user.
     1532    if ( empty( $post->post_author ) ) {
     1533        $post->post_author = get_current_user_id();
     1534    }
     1535
     1536    if ( 'wp_template_part' === $post->post_type && ! isset( $terms['wp_template_part_area'] ) ) {
     1537        $area_terms                     = get_the_terms( $changes->ID, 'wp_template_part_area' );
     1538        $terms['wp_template_part_area'] = ! is_wp_error( $area_terms ) && ! empty( $area_terms ) ? $area_terms[0]->name : null;
     1539    }
     1540
     1541    $template = _build_block_template_object_from_post_object( new WP_Post( $post ), $terms, $meta );
     1542
     1543    if ( is_wp_error( $template ) ) {
     1544        return $template;
     1545    }
    14701546
    14711547    $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' );
    14721548    $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'set_ignored_hooked_blocks_metadata' );
    14731549
    1474     $blocks  = parse_blocks( $post->post_content );
    1475     $content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
    1476 
    1477     $post->post_content = $content;
    1478     return $post;
    1479 }
     1550    $blocks                = parse_blocks( $changes->post_content );
     1551    $changes->post_content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
     1552
     1553    return $changes;
     1554}
Note: See TracChangeset for help on using the changeset viewer.