Make WordPress Core

Changeset 60038


Ignore:
Timestamp:
03/18/2025 12:41:31 PM (19 months 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.