Make WordPress Core

Changeset 60038


Ignore:
Timestamp:
03/18/2025 12:41:31 PM (4 weeks ago)
Author:
joemcgill
Message:

Editor: Fix layout support classes to be generated with a stable ID.

This fixes a bug reported in https://github.com/WordPress/gutenberg/issues/67308 related to the Interactivity API's client-side navigation feature by replacing the incrementally generated IDs with stable hashes derived from the block's layout style definition.

Fixes #62985.
Props darerodz.

Location:
trunk
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/block-supports/layout.php

    r58750 r60038  
    581581    // Child layout specific logic.
    582582    if ( $child_layout ) {
    583         $container_content_class   = wp_unique_prefixed_id( 'wp-container-content-' );
     583        /*
     584         * Generates a unique class for child block layout styles.
     585         *
     586         * To ensure consistent class generation across different page renders,
     587         * only properties that affect layout styling are used. These properties
     588         * come from `$block['attrs']['style']['layout']` and `$block['parentLayout']`.
     589         *
     590         * As long as these properties coincide, the generated class will be the same.
     591         */
     592        $container_content_class = wp_unique_id_from_values(
     593            array(
     594                'layout'       => array_intersect_key(
     595                    $block['attrs']['style']['layout'] ?? array(),
     596                    array_flip(
     597                        array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' )
     598                    )
     599                ),
     600                'parentLayout' => array_intersect_key(
     601                    $block['parentLayout'] ?? array(),
     602                    array_flip(
     603                        array( 'minimumColumnWidth', 'columnCount' )
     604                    )
     605                ),
     606            ),
     607            'wp-container-content-'
     608        );
     609
    584610        $child_layout_declarations = array();
    585611        $child_layout_styles       = array();
     
    707733    $layout_definitions = wp_get_layout_definitions();
    708734
    709     /*
    710      * Uses an incremental ID that is independent per prefix to make sure that
    711      * rendering different numbers of blocks doesn't affect the IDs of other
    712      * blocks. Makes the CSS class names stable across paginations
    713      * for features like the enhanced pagination of the Query block.
    714      */
    715     $container_class = wp_unique_prefixed_id(
    716         'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-'
    717     );
    718 
    719735    // Set the correct layout type for blocks using legacy content width.
    720736    if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) {
     
    806822            : null;
    807823        $has_block_gap_support = isset( $block_gap );
     824
     825        /*
     826         * Generates a unique ID based on all the data required to obtain the
     827         * corresponding layout style. Keeps the CSS class names the same
     828         * even for different blocks on different places, as long as they have
     829         * the same layout definition. Makes the CSS class names stable across
     830         * paginations for features like the enhanced pagination of the Query block.
     831         */
     832        $container_class = wp_unique_id_from_values(
     833            array(
     834                $used_layout,
     835                $has_block_gap_support,
     836                $gap_value,
     837                $should_skip_gap_serialization,
     838                $fallback_gap_value,
     839                $block_spacing,
     840            ),
     841            'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-'
     842        );
    808843
    809844        $style = wp_get_layout_style(
  • trunk/src/wp-includes/functions.php

    r59946 r60038  
    91759175    return hash_equals( $hash, wp_fast_hash( $message ) );
    91769176}
     9177
     9178/**
     9179 * Generates a unique ID based on the structure and values of a given array.
     9180 *
     9181 * This function serializes the array into a JSON string and generates a hash
     9182 * that serves as a unique identifier. Optionally, a prefix can be added to
     9183 * the generated ID for context or categorization.
     9184 *
     9185 * @since 6.8.0
     9186 *
     9187 * @param array  $data   The input array to generate an ID from.
     9188 * @param string $prefix Optional. A prefix to prepend to the generated ID. Default ''.
     9189 *
     9190 * @return string The generated unique ID for the array.
     9191 */
     9192function wp_unique_id_from_values( array $data, string $prefix = '' ): string {
     9193    if ( empty( $data ) ) {
     9194        _doing_it_wrong(
     9195            __FUNCTION__,
     9196            sprintf(
     9197                __( 'The $data argument must not be empty.' ),
     9198                gettype( $data )
     9199            ),
     9200            '6.8.0'
     9201        );
     9202    }
     9203    $serialized = wp_json_encode( $data );
     9204    $hash       = substr( md5( $serialized ), 0, 8 );
     9205    return $prefix . $hash;
     9206}
  • trunk/tests/phpunit/data/blocks/fixtures/core__columns.server.html

    r55956 r60038  
    11
    2 <div class="wp-block-columns has-3-columns is-layout-flex wp-container-1 wp-block-columns-is-layout-flex">
     2<div class="wp-block-columns has-3-columns is-layout-flex wp-container-1d6595d7 wp-block-columns-is-layout-flex">
    33   
    44    <div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
  • trunk/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html

    r55956 r60038  
    11
    2 <div class="wp-block-columns has-3-columns is-layout-flex wp-container-1 wp-block-columns-is-layout-flex">
     2<div class="wp-block-columns has-3-columns is-layout-flex wp-container-1d6595d7 wp-block-columns-is-layout-flex">
    33   
    44    <p class="layout-column-1">Column One, Paragraph One</p>
  • trunk/tests/phpunit/tests/block-supports/layout.php

    r58194 r60038  
    273273                    ),
    274274                ),
    275                 'expected_output' => '<p class="wp-container-content-1">Some text.</p>', // The generated classname number assumes `wp_unique_prefixed_id( 'wp-container-content-' )` will not have run previously in this test.
     275                'expected_output' => '<p class="wp-container-content-b7aa651c">Some text.</p>',
     276            ),
     277            'single wrapper block layout with flex type'   => array(
     278                'args'            => array(
     279                    'block_content' => '<div class="wp-block-group"></div>',
     280                    'block'         => array(
     281                        'blockName'    => 'core/group',
     282                        'attrs'        => array(
     283                            'layout' => array(
     284                                'type'        => 'flex',
     285                                'orientation' => 'horizontal',
     286                                'flexWrap'    => 'nowrap',
     287                            ),
     288                        ),
     289                        'innerBlocks'  => array(),
     290                        'innerHTML'    => '<div class="wp-block-group"></div>',
     291                        'innerContent' => array(
     292                            '<div class="wp-block-group"></div>',
     293                        ),
     294                    ),
     295                ),
     296                'expected_output' => '<div class="wp-block-group is-horizontal is-nowrap is-layout-flex wp-container-core-group-is-layout-67f0b8e2 wp-block-group-is-layout-flex"></div>',
     297            ),
     298            'single wrapper block layout with grid type'   => array(
     299                'args'            => array(
     300                    'block_content' => '<div class="wp-block-group"></div>',
     301                    'block'         => array(
     302                        'blockName'    => 'core/group',
     303                        'attrs'        => array(
     304                            'layout' => array(
     305                                'type' => 'grid',
     306                            ),
     307                        ),
     308                        'innerBlocks'  => array(),
     309                        'innerHTML'    => '<div class="wp-block-group"></div>',
     310                        'innerContent' => array(
     311                            '<div class="wp-block-group"></div>',
     312                        ),
     313                    ),
     314                ),
     315                'expected_output' => '<div class="wp-block-group is-layout-grid wp-container-core-group-is-layout-9649a0d9 wp-block-group-is-layout-grid"></div>',
    276316            ),
    277317            'skip classname output if block does not support layout and there are no child layout classes to be output' => array(
     
    464504        );
    465505    }
     506
     507    /**
     508     * Check that wp_render_layout_support_flag() renders consistent hashes
     509     * for the container class when the relevant layout properties are the same.
     510     *
     511     * @dataProvider data_layout_support_flag_renders_consistent_container_hash
     512     *
     513     * @covers ::wp_render_layout_support_flag
     514     *
     515     * @param array $block_attrs     Dataset to test.
     516     * @param array $expected_class  Class generated for the passed dataset.
     517     */
     518    public function test_layout_support_flag_renders_consistent_container_hash( $block_attrs, $expected_class ) {
     519        switch_theme( 'default' );
     520
     521        $block_content = '<div class="wp-block-group"></div>';
     522        $block         = array(
     523            'blockName'    => 'core/group',
     524            'innerBlocks'  => array(),
     525            'innerHTML'    => '<div class="wp-block-group"></div>',
     526            'innerContent' => array(
     527                '<div class="wp-block-group"></div>',
     528            ),
     529            'attrs'        => $block_attrs,
     530        );
     531
     532        /*
     533         * The `appearance-tools` theme support is temporarily added to ensure
     534         * that the block gap support is enabled during rendering, which is
     535         * necessary to compute styles for layouts with block gap values.
     536         */
     537        add_theme_support( 'appearance-tools' );
     538        $output = wp_render_layout_support_flag( $block_content, $block );
     539        remove_theme_support( 'appearance-tools' );
     540
     541        // Process the output and look for the expected class in the first rendered element.
     542        $processor = new WP_HTML_Tag_Processor( $output );
     543        $processor->next_tag();
     544
     545        $this->assertTrue(
     546            $processor->has_class( $expected_class ),
     547            "Expected class '$expected_class' not found in the rendered output, probably because of a different hash."
     548        );
     549    }
     550
     551    /**
     552     * Data provider for test_layout_support_flag_renders_consistent_container_hash.
     553     *
     554     * @return array
     555     */
     556    public function data_layout_support_flag_renders_consistent_container_hash() {
     557        return array(
     558            'default type block gap 12px'      => array(
     559                'block_attributes' => array(
     560                    'layout' => array(
     561                        'type' => 'default',
     562                    ),
     563                    'style'  => array(
     564                        'spacing' => array(
     565                            'blockGap' => '12px',
     566                        ),
     567                    ),
     568                ),
     569                'expected_class'   => 'wp-container-core-group-is-layout-c5c7d83f',
     570            ),
     571            'default type block gap 24px'      => array(
     572                'block_attributes' => array(
     573                    'layout' => array(
     574                        'type' => 'default',
     575                    ),
     576                    'style'  => array(
     577                        'spacing' => array(
     578                            'blockGap' => '24px',
     579                        ),
     580                    ),
     581                ),
     582                'expected_class'   => 'wp-container-core-group-is-layout-634f0b9d',
     583            ),
     584            'constrained type justified left'  => array(
     585                'block_attributes' => array(
     586                    'layout' => array(
     587                        'type'           => 'constrained',
     588                        'justifyContent' => 'left',
     589                    ),
     590                ),
     591                'expected_class'   => 'wp-container-core-group-is-layout-12dd3699',
     592            ),
     593            'constrained type justified right' => array(
     594                'block_attributes' => array(
     595                    'layout' => array(
     596                        'type'           => 'constrained',
     597                        'justifyContent' => 'right',
     598                    ),
     599                ),
     600                'expected_class'   => 'wp-container-core-group-is-layout-f1f2ed93',
     601            ),
     602            'flex type horizontal'             => array(
     603                'block_attributes' => array(
     604                    'layout' => array(
     605                        'type'        => 'flex',
     606                        'orientation' => 'horizontal',
     607                        'flexWrap'    => 'nowrap',
     608                    ),
     609                ),
     610                'expected_class'   => 'wp-container-core-group-is-layout-2487dcaa',
     611            ),
     612            'flex type vertical'               => array(
     613                'block_attributes' => array(
     614                    'layout' => array(
     615                        'type'        => 'flex',
     616                        'orientation' => 'vertical',
     617                    ),
     618                ),
     619                'expected_class'   => 'wp-container-core-group-is-layout-fe9cc265',
     620            ),
     621            'grid type'                        => array(
     622                'block_attributes' => array(
     623                    'layout' => array(
     624                        'type' => 'grid',
     625                    ),
     626                ),
     627                'expected_class'   => 'wp-container-core-group-is-layout-478b6e6b',
     628            ),
     629            'grid type 3 columns'              => array(
     630                'block_attributes' => array(
     631                    'layout' => array(
     632                        'type'        => 'grid',
     633                        'columnCount' => 3,
     634                    ),
     635                ),
     636                'expected_class'   => 'wp-container-core-group-is-layout-d3b710ac',
     637            ),
     638        );
     639    }
    466640}
Note: See TracChangeset for help on using the changeset viewer.