Make WordPress Core

Changeset 58614


Ignore:
Timestamp:
07/02/2024 10:01:17 AM (2 months ago)
Author:
Bernhard Reiter
Message:

Block Hooks: Allow child insertion into Template Part block.

The Block Hooks mechanism was previously extended to allow insertion of a block as a Navigation block's first or last child. This was implemented by storing the ignoredHookedBlocks array in the corresponding wp_navigation post's post meta (instead of a metadata attribute on the anchor block).

This changeset extends that mechanism to Template Part blocks, by storing said metadata in the corresponding wp_template_part post's post meta, thus allowing extenders to use Block Hooks to insert a block as a Template Part block's first or last child, respectively.

Props tomjcafferkey, bernhard-reiter.
Fixes #60854.

Location:
trunk
Files:
5 edited

Legend:

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

    r58409 r58614  
    607607    }
    608608
     609    $hooked_blocks        = get_hooked_blocks();
     610    $has_hooked_blocks    = ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' );
    609611    $before_block_visitor = '_inject_theme_attribute_in_template_part_block';
    610612    $after_block_visitor  = null;
    611     $hooked_blocks        = get_hooked_blocks();
    612     if ( ! empty( $hooked_blocks ) || has_filter( 'hooked_block_types' ) ) {
     613
     614    if ( $has_hooked_blocks ) {
    613615        $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
    614616        $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
    615617    }
    616     $blocks            = parse_blocks( $template->content );
    617     $template->content = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
     618
     619    if ( 'wp_template_part' === $template->type && $has_hooked_blocks ) {
     620        /**
     621         * In order for hooked blocks to be inserted at positions first_child and last_child in a template part,
     622         * we need to wrap its content a mock template part block and traverse it.
     623         */
     624        $content           = get_comment_delimited_block_content(
     625            'core/template-part',
     626            array(),
     627            $template->content
     628        );
     629        $content           = traverse_and_serialize_blocks( parse_blocks( $content ), $before_block_visitor, $after_block_visitor );
     630        $template->content = remove_serialized_parent_block( $content );
     631    } else {
     632        $template->content = traverse_and_serialize_blocks(
     633            parse_blocks( $template->content ),
     634            $before_block_visitor,
     635            $after_block_visitor
     636        );
     637    }
    618638
    619639    return $template;
     
    9991019        $before_block_visitor = make_before_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
    10001020        $after_block_visitor  = make_after_block_visitor( $hooked_blocks, $template, 'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata' );
    1001         $blocks               = parse_blocks( $template->content );
    1002         $template->content    = traverse_and_serialize_blocks( $blocks, $before_block_visitor, $after_block_visitor );
     1021        if ( 'wp_template_part' === $template->type ) {
     1022            $existing_ignored_hooked_blocks = get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true );
     1023            $attributes                     = ! empty( $existing_ignored_hooked_blocks ) ? array( 'metadata' => array( 'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ) ) ) : array();
     1024
     1025            /**
     1026             * In order for hooked blocks to be inserted at positions first_child and last_child in a template part,
     1027             * we need to wrap its content a mock template part block and traverse it.
     1028             */
     1029            $content           = get_comment_delimited_block_content(
     1030                'core/template-part',
     1031                $attributes,
     1032                $template->content
     1033            );
     1034            $content           = traverse_and_serialize_blocks( parse_blocks( $content ), $before_block_visitor, $after_block_visitor );
     1035            $template->content = remove_serialized_parent_block( $content );
     1036        } else {
     1037            $template->content = traverse_and_serialize_blocks(
     1038                parse_blocks( $template->content ),
     1039                $before_block_visitor,
     1040                $after_block_visitor
     1041            );
     1042        }
    10031043    }
    10041044
     
    16121652    }
    16131653
    1614     $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' );
     1654    if ( 'wp_template_part' === $post->post_type ) {
     1655        $attributes                     = array();
     1656        $existing_ignored_hooked_blocks = isset( $post->ID ) ? get_post_meta( $post->ID, '_wp_ignored_hooked_blocks', true ) : '';
     1657
     1658        if ( ! empty( $existing_ignored_hooked_blocks ) ) {
     1659            $attributes['metadata'] = array(
     1660                'ignoredHookedBlocks' => json_decode( $existing_ignored_hooked_blocks, true ),
     1661            );
     1662        }
     1663
     1664        $content = get_comment_delimited_block_content(
     1665            'core/template-part',
     1666            $attributes,
     1667            $changes->post_content
     1668        );
     1669        $content = apply_block_hooks_to_content( $content, $template, 'set_ignored_hooked_blocks_metadata' );
     1670        $changes->post_content = remove_serialized_parent_block( $content );
     1671
     1672        $wrapper_block_markup  = extract_serialized_parent_block( $content );
     1673        $wrapper_block         = parse_blocks( $wrapper_block_markup )[0];
     1674        $ignored_hooked_blocks = $wrapper_block['attrs']['metadata']['ignoredHookedBlocks'] ?? array();
     1675        if ( ! empty( $ignored_hooked_blocks ) ) {
     1676            if ( ! isset( $changes->meta_input ) ) {
     1677                $changes->meta_input = array();
     1678            }
     1679            $changes->meta_input['_wp_ignored_hooked_blocks'] = wp_json_encode( $ignored_hooked_blocks );
     1680        }
     1681    } else {
     1682        $changes->post_content = apply_block_hooks_to_content( $changes->post_content, $template, 'set_ignored_hooked_blocks_metadata' );
     1683    }
    16151684
    16161685    return $changes;
  • trunk/src/wp-includes/blocks.php

    r58578 r58614  
    10471047
    10481048/**
     1049 * Accepts the serialized markup of a block and its inner blocks, and returns serialized markup of the wrapper block.
     1050 *
     1051 * @since 6.7.0
     1052 * @access private
     1053 *
     1054 * @see remove_serialized_parent_block()
     1055 *
     1056 * @param string $serialized_block The serialized markup of a block and its inner blocks.
     1057 * @return string The serialized markup of the wrapper block.
     1058 */
     1059function extract_serialized_parent_block( $serialized_block ) {
     1060    $start = strpos( $serialized_block, '-->' ) + strlen( '-->' );
     1061    $end   = strrpos( $serialized_block, '<!--' );
     1062    return substr( $serialized_block, 0, $start ) . substr( $serialized_block, $end );
     1063}
     1064
     1065/**
    10491066 * Updates the wp_postmeta with the list of ignored hooked blocks where the inner blocks are stored as post content.
    10501067 * Currently only supports `wp_navigation` post types.
  • trunk/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromFile.php

    r57266 r58614  
    88 */
    99class Tests_Block_Templates_BuildBlockTemplateResultFromFile extends WP_Block_Templates_UnitTestCase {
     10    /**
     11     * Tear down each test method.
     12     *
     13     * @since 6.7.0
     14     */
     15    public function tear_down() {
     16        $registry = WP_Block_Type_Registry::get_instance();
     17
     18        if ( $registry->is_registered( 'tests/my-block' ) ) {
     19            $registry->unregister( 'tests/my-block' );
     20        }
     21
     22        parent::tear_down();
     23    }
    1024
    1125    /**
     
    179193        $this->assertEmpty( $template->post_types );
    180194    }
     195
     196    /**
     197     * @ticket 60506
     198     */
     199    public function test_should_inject_hooked_block_into_template_part() {
     200        register_block_type(
     201            'tests/my-block',
     202            array(
     203                'block_hooks' => array(
     204                    'core/paragraph' => 'after',
     205                ),
     206            )
     207        );
     208
     209        $template_part = _build_block_template_result_from_file(
     210            array(
     211                'slug'      => 'header',
     212                'postTypes' => array( 'post' ),
     213                'path'      => DIR_TESTDATA . '/templates/template.html',
     214            ),
     215            'wp_template_part'
     216        );
     217        $this->assertStringEndsWith( '<!-- wp:tests/my-block /-->', $template_part->content );
     218    }
     219
     220    /*
     221     * @ticket 60506
     222     * @ticket 60854
     223     */
     224    public function test_should_injected_hooked_block_into_template_part_first_child() {
     225        register_block_type(
     226            'tests/my-block',
     227            array(
     228                'block_hooks' => array(
     229                    'core/template-part' => 'first_child',
     230                ),
     231            )
     232        );
     233
     234        $template_part = _build_block_template_result_from_file(
     235            array(
     236                'slug'      => 'header',
     237                'postTypes' => array( 'post' ),
     238                'path'      => DIR_TESTDATA . '/templates/template.html',
     239            ),
     240            'wp_template_part'
     241        );
     242        $this->assertStringStartsWith( '<!-- wp:tests/my-block /-->', $template_part->content );
     243    }
     244
     245    /*
     246     * @ticket 60506
     247     * @ticket 60854
     248     */
     249    public function test_should_injected_hooked_block_into_template_part_last_child() {
     250        register_block_type(
     251            'tests/my-block',
     252            array(
     253                'block_hooks' => array(
     254                    'core/template-part' => 'last_child',
     255                ),
     256            )
     257        );
     258
     259        $template_part = _build_block_template_result_from_file(
     260            array(
     261                'slug'      => 'header',
     262                'postTypes' => array( 'post' ),
     263                'path'      => DIR_TESTDATA . '/templates/template.html',
     264            ),
     265            'wp_template_part'
     266        );
     267        $this->assertStringEndsWith( '<!-- wp:tests/my-block /-->', $template_part->content );
     268    }
    181269}
  • trunk/tests/phpunit/tests/block-templates/buildBlockTemplateResultFromPost.php

    r57627 r58614  
    112112    }
    113113
     114    /*
     115     * @ticket 59646
     116     * @ticket 60506
     117     * @ticket 60854
     118     */
     119    public function test_should_injected_hooked_block_into_template_part_first_child() {
     120        register_block_type(
     121            'tests/my-block',
     122            array(
     123                'block_hooks' => array(
     124                    'core/template-part' => 'first_child',
     125                ),
     126            )
     127        );
     128
     129        $template_part = _build_block_template_result_from_post(
     130            self::$template_part_post,
     131            'wp_template_part'
     132        );
     133        $this->assertStringStartsWith( '<!-- wp:tests/my-block /-->', $template_part->content );
     134    }
     135
     136    /*
     137     * @ticket 59646
     138     * @ticket 60506
     139     * @ticket 60854
     140     */
     141    public function test_should_injected_hooked_block_into_template_part_last_child() {
     142        register_block_type(
     143            'tests/my-block',
     144            array(
     145                'block_hooks' => array(
     146                    'core/template-part' => 'last_child',
     147                ),
     148            )
     149        );
     150
     151        $template_part = _build_block_template_result_from_post(
     152            self::$template_part_post,
     153            'wp_template_part'
     154        );
     155        $this->assertStringEndsWith( '<!-- wp:tests/my-block /-->', $template_part->content );
     156    }
     157
    114158    /**
    115159     * @ticket 59646
  • trunk/tests/phpunit/tests/block-templates/injectIgnoredHookedBlocksMetadataAttributes.php

    r58042 r58614  
    1818            unregister_block_type( 'tests/hooked-block' );
    1919        }
     20        delete_post_meta( self::$template_part_post->ID, '_wp_ignored_hooked_blocks' );
    2021
    2122        parent::tear_down();
     
    3940        inject_ignored_hooked_blocks_metadata_attributes( $changes );
    4041
    41         $args              = $action->get_args();
    42         $anchor_block_type = end( $args )[2];
    43         $context           = end( $args )[3];
    44 
    45         $this->assertSame( 'tests/anchor-block', $anchor_block_type );
    46 
    47         $this->assertInstanceOf( 'WP_Block_Template', $context );
    48 
     42        $args               = $action->get_args();
     43        $relative_positions = array_column( $args, 1 );
     44        $anchor_block_types = array_column( $args, 2 );
     45        $contexts           = array_column( $args, 3 );
     46
     47        $this->assertSame(
     48            array(
     49                'before',
     50                'after',
     51            ),
     52            $relative_positions,
     53            'The relative positions passed to the hooked_block_types filter are incorrect.'
     54        );
     55
     56        $this->assertSame(
     57            array(
     58                'tests/anchor-block',
     59                'tests/anchor-block',
     60            ),
     61            $anchor_block_types,
     62            'The anchor block types passed to the hooked_block_types filter are incorrect.'
     63        );
     64
     65        $context = $contexts[0];
     66        $this->assertSame(
     67            array_fill( 0, count( $contexts ), $context ),
     68            $contexts,
     69            'The context passed to the hooked_block_types filter should be the same for all calls.'
     70        );
    4971        $this->assertSame(
    5072            $changes->post_type,
     
    7092    /**
    7193     * @ticket 60754
     94     * @ticket 60854
    7295     */
    7396    public function test_hooked_block_types_filter_with_newly_created_template_part() {
     
    86109        inject_ignored_hooked_blocks_metadata_attributes( $changes );
    87110
    88         $args              = $action->get_args();
    89         $anchor_block_type = end( $args )[2];
    90         $context           = end( $args )[3];
    91 
    92         $this->assertSame( 'tests/anchor-block', $anchor_block_type );
    93 
    94         $this->assertInstanceOf( 'WP_Block_Template', $context );
    95 
     111        $args               = $action->get_args();
     112        $relative_positions = array_column( $args, 1 );
     113        $anchor_block_types = array_column( $args, 2 );
     114        $contexts           = array_column( $args, 3 );
     115
     116        $this->assertSame(
     117            array(
     118                'before',
     119                'after',
     120                'first_child',
     121                'before',
     122                'after',
     123                'last_child',
     124            ),
     125            $relative_positions,
     126            'The relative positions passed to the hooked_block_types filter are incorrect.'
     127        );
     128
     129        $this->assertSame(
     130            array(
     131                'core/template-part',
     132                'core/template-part',
     133                'core/template-part',
     134                'tests/anchor-block',
     135                'tests/anchor-block',
     136                'core/template-part',
     137            ),
     138            $anchor_block_types,
     139            'The anchor block types passed to the hooked_block_types filter are incorrect.'
     140        );
     141
     142        $context = $contexts[0];
     143        $this->assertSame(
     144            array_fill( 0, count( $contexts ), $context ),
     145            $contexts,
     146            'The context passed to the hooked_block_types filter should be the same for all calls.'
     147        );
     148        $this->assertInstanceOf(
     149            'WP_Block_Template',
     150            $context,
     151            'The context passed to the hooked_block_types filter is not an instance of WP_Block_Template.'
     152        );
    96153        $this->assertSame(
    97154            $changes->post_type,
     
    141198        inject_ignored_hooked_blocks_metadata_attributes( $changes );
    142199
    143         $args              = $action->get_args();
    144         $anchor_block_type = end( $args )[2];
    145         $context           = end( $args )[3];
    146 
    147         $this->assertSame( 'tests/anchor-block', $anchor_block_type );
    148 
    149         $this->assertInstanceOf( 'WP_Block_Template', $context );
    150 
     200        $args               = $action->get_args();
     201        $relative_positions = array_column( $args, 1 );
     202        $anchor_block_types = array_column( $args, 2 );
     203        $contexts           = array_column( $args, 3 );
     204
     205        $this->assertSame(
     206            array(
     207                'before',
     208                'after',
     209            ),
     210            $relative_positions,
     211            'The relative positions passed to the hooked_block_types filter are incorrect.'
     212        );
     213
     214        $this->assertSame(
     215            array(
     216                'tests/anchor-block',
     217                'tests/anchor-block',
     218            ),
     219            $anchor_block_types,
     220            'The anchor block types passed to the hooked_block_types filter are incorrect.'
     221        );
     222
     223        $context = $contexts[0];
     224        $this->assertSame(
     225            array_fill( 0, count( $contexts ), $context ),
     226            $contexts,
     227            'The context passed to the hooked_block_types filter should be the same for all calls.'
     228        );
    151229        $this->assertSame(
    152230            $changes->post_name,
     
    182260    /**
    183261     * @ticket 60754
     262     * @ticket 60854
    184263     */
    185264    public function test_hooked_block_types_filter_with_existing_template_part_file() {
     
    202281        inject_ignored_hooked_blocks_metadata_attributes( $changes );
    203282
    204         $args              = $action->get_args();
    205         $anchor_block_type = end( $args )[2];
    206         $context           = end( $args )[3];
    207 
    208         $this->assertSame( 'tests/anchor-block', $anchor_block_type );
    209 
    210         $this->assertInstanceOf( 'WP_Block_Template', $context );
    211 
     283        $args               = $action->get_args();
     284        $relative_positions = array_column( $args, 1 );
     285        $anchor_block_types = array_column( $args, 2 );
     286        $contexts           = array_column( $args, 3 );
     287
     288        $this->assertSame(
     289            array(
     290                'before',
     291                'after',
     292                'first_child',
     293                'before',
     294                'after',
     295                'last_child',
     296            ),
     297            $relative_positions,
     298            'The relative positions passed to the hooked_block_types filter are incorrect.'
     299        );
     300
     301        $this->assertSame(
     302            array(
     303                'core/template-part',
     304                'core/template-part',
     305                'core/template-part',
     306                'tests/anchor-block',
     307                'tests/anchor-block',
     308                'core/template-part',
     309            ),
     310            $anchor_block_types,
     311            'The anchor block types passed to the hooked_block_types filter are incorrect.'
     312        );
     313
     314        $context = $contexts[0];
     315        $this->assertSame(
     316            array_fill( 0, count( $contexts ), $context ),
     317            $contexts,
     318            'The context passed to the hooked_block_types filter should be the same for all calls.'
     319        );
     320        $this->assertInstanceOf(
     321            'WP_Block_Template',
     322            $context,
     323            'The context passed to the hooked_block_types filter is not an instance of WP_Block_Template.'
     324        );
    212325        $this->assertSame(
    213326            $changes->post_name,
     
    260373        inject_ignored_hooked_blocks_metadata_attributes( $changes );
    261374
    262         $args              = $action->get_args();
    263         $anchor_block_type = end( $args )[2];
    264         $context           = end( $args )[3];
    265 
    266         $this->assertSame( 'tests/anchor-block', $anchor_block_type );
    267 
    268         $this->assertInstanceOf( 'WP_Block_Template', $context );
    269 
     375        $args               = $action->get_args();
     376        $relative_positions = array_column( $args, 1 );
     377        $anchor_block_types = array_column( $args, 2 );
     378        $contexts           = array_column( $args, 3 );
     379
     380        $this->assertSame(
     381            array(
     382                'before',
     383                'after',
     384            ),
     385            $relative_positions,
     386            'The relative positions passed to the hooked_block_types filter are incorrect.'
     387        );
     388
     389        $this->assertSame(
     390            array(
     391                'tests/anchor-block',
     392                'tests/anchor-block',
     393            ),
     394            $anchor_block_types,
     395            'The anchor block types passed to the hooked_block_types filter are incorrect.'
     396        );
     397
     398        $context = $contexts[0];
     399        $this->assertSame(
     400            array_fill( 0, count( $contexts ), $context ),
     401            $contexts,
     402            'The context passed to the hooked_block_types filter should be the same for all calls.'
     403        );
    270404        $this->assertSame(
    271405            $changes->post_name,
     
    303437    /**
    304438     * @ticket 60754
     439     * @ticket 60854
    305440     */
    306441    public function test_hooked_block_types_filter_with_existing_template_part_post() {
     
    319454        inject_ignored_hooked_blocks_metadata_attributes( $changes );
    320455
    321         $args              = $action->get_args();
    322         $anchor_block_type = end( $args )[2];
    323         $context           = end( $args )[3];
    324 
    325         $this->assertSame( 'tests/anchor-block', $anchor_block_type );
    326 
    327         $this->assertInstanceOf( 'WP_Block_Template', $context );
    328 
     456        $args               = $action->get_args();
     457        $relative_positions = array_column( $args, 1 );
     458        $anchor_block_types = array_column( $args, 2 );
     459        $contexts           = array_column( $args, 3 );
     460
     461        $this->assertSame(
     462            array(
     463                'before',
     464                'after',
     465                'first_child',
     466                'before',
     467                'after',
     468                'last_child',
     469            ),
     470            $relative_positions,
     471            'The relative positions passed to the hooked_block_types filter are incorrect.'
     472        );
     473
     474        $this->assertSame(
     475            array(
     476                'core/template-part',
     477                'core/template-part',
     478                'core/template-part',
     479                'tests/anchor-block',
     480                'tests/anchor-block',
     481                'core/template-part',
     482            ),
     483            $anchor_block_types,
     484            'The anchor block types passed to the hooked_block_types filter are incorrect.'
     485        );
     486
     487        $context = $contexts[0];
     488        $this->assertSame(
     489            array_fill( 0, count( $contexts ), $context ),
     490            $contexts,
     491            'The context passed to the hooked_block_types filter should be the same for all calls.'
     492        );
     493        $this->assertInstanceOf(
     494            'WP_Block_Template',
     495            $context,
     496            'The context passed to the hooked_block_types filter is not an instance of WP_Block_Template.'
     497        );
    329498        $this->assertSame(
    330499            $changes->post_name,
     
    420589        );
    421590    }
     591
     592    /**
     593     * @ticket 60854
     594     */
     595    public function test_inject_ignored_hooked_blocks_metadata_attributes_into_template_part_postmeta() {
     596        register_block_type(
     597            'tests/hooked-block',
     598            array(
     599                'block_hooks' => array(
     600                    'core/template-part' => 'last_child',
     601                ),
     602            )
     603        );
     604
     605        $id       = self::TEST_THEME . '//' . 'my_template_part';
     606        $template = get_block_template( $id, 'wp_template_part' );
     607
     608        $changes               = new stdClass();
     609        $changes->ID           = $template->wp_id;
     610        $changes->post_content = '<!-- wp:tests/anchor-block -->Hello<!-- /wp:tests/anchor-block -->';
     611
     612        $post = inject_ignored_hooked_blocks_metadata_attributes( $changes );
     613        $this->assertSame(
     614            array( 'tests/hooked-block' ),
     615            json_decode( $post->meta_input['_wp_ignored_hooked_blocks'], true ),
     616            'The hooked block was not injected into the wp_template_part\'s _wp_ignored_hooked_blocks postmeta.'
     617        );
     618        $this->assertSame(
     619            $changes->post_content,
     620            $post->post_content,
     621            'The template part\'s post content was modified.'
     622        );
     623    }
    422624}
Note: See TracChangeset for help on using the changeset viewer.