From b92477dcbe0fc99fb1222e968b479596d8dc12ab Mon Sep 17 00:00:00 2001 From: David Date: Wed, 19 Feb 2025 12:22:03 +0100 Subject: [PATCH 1/3] Backport changes from WordPress/gutenberg#68210 --- src/wp-includes/block-supports/layout.php | 57 ++++-- src/wp-includes/functions.php | 20 ++ tests/phpunit/tests/block-supports/layout.php | 176 +++++++++++++++++- 3 files changed, 241 insertions(+), 12 deletions(-) diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index d3718930d5062..71ade08f83ef4 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -580,7 +580,33 @@ function wp_render_layout_support_flag( $block_content, $block ) { // Child layout specific logic. if ( $child_layout ) { - $container_content_class = wp_unique_prefixed_id( 'wp-container-content-' ); + /* + * Generates a unique class for child block layout styles. + * + * To ensure consistent class generation across different page renders, + * only properties that affect layout styling are used. These properties + * come from `$block['attrs']['style']['layout']` and `$block['parentLayout']`. + * + * As long as these properties coincide, the generated class will be the same. + */ + $container_content_class = wp_unique_id_from_values( + array( + 'layout' => array_intersect_key( + $block['attrs']['style']['layout'] ?? array(), + array_flip( + array( 'selfStretch', 'flexSize', 'columnStart', 'columnSpan', 'rowStart', 'rowSpan' ) + ) + ), + 'parentLayout' => array_intersect_key( + $block['parentLayout'] ?? array(), + array_flip( + array( 'minimumColumnWidth', 'columnCount' ) + ) + ), + ), + 'wp-container-content-' + ); + $child_layout_declarations = array(); $child_layout_styles = array(); @@ -706,16 +732,6 @@ function wp_render_layout_support_flag( $block_content, $block ) { $class_names = array(); $layout_definitions = wp_get_layout_definitions(); - /* - * Uses an incremental ID that is independent per prefix to make sure that - * rendering different numbers of blocks doesn't affect the IDs of other - * blocks. Makes the CSS class names stable across paginations - * for features like the enhanced pagination of the Query block. - */ - $container_class = wp_unique_prefixed_id( - 'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-' - ); - // Set the correct layout type for blocks using legacy content width. if ( isset( $used_layout['inherit'] ) && $used_layout['inherit'] || isset( $used_layout['contentSize'] ) && $used_layout['contentSize'] ) { $used_layout['type'] = 'constrained'; @@ -806,6 +822,25 @@ function wp_render_layout_support_flag( $block_content, $block ) { : null; $has_block_gap_support = isset( $block_gap ); + /* + * Generates a unique ID based on all the data required to obtain the + * corresponding layout style. Keeps the CSS class names the same + * even for different blocks on different places, as long as they have + * the same layout definition. Makes the CSS class names stable across + * paginations for features like the enhanced pagination of the Query block. + */ + $container_class = wp_unique_id_from_values( + array( + $used_layout, + $has_block_gap_support, + $gap_value, + $should_skip_gap_serialization, + $fallback_gap_value, + $block_spacing, + ), + 'wp-container-' . sanitize_title( $block['blockName'] ) . '-is-layout-' + ); + $style = wp_get_layout_style( ".$container_class", $used_layout, diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 9a6938ed640de..586a425f69113 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -9173,3 +9173,23 @@ function wp_verify_fast_hash( return hash_equals( $hash, wp_fast_hash( $message ) ); } + +/** + * Generates a unique ID based on the structure and values of a given array. + * + * This function serializes the array into a JSON string and generates a hash + * that serves as a unique identifier. Optionally, a prefix can be added to + * the generated ID for context or categorization. + * + * @since 6.9.0 + * + * @param array $data The input array to generate an ID from. + * @param string $prefix Optional. A prefix to prepend to the generated ID. Default ''. + * + * @return string The generated unique ID for the array. + */ +function wp_unique_id_from_values( array $data, string $prefix = '' ): string { + $serialized = wp_json_encode( $data ); + $hash = substr( md5( $serialized ), 0, 8 ); + return $prefix . $hash; +} diff --git a/tests/phpunit/tests/block-supports/layout.php b/tests/phpunit/tests/block-supports/layout.php index de6d9e6e76060..38c9cce4afaca 100644 --- a/tests/phpunit/tests/block-supports/layout.php +++ b/tests/phpunit/tests/block-supports/layout.php @@ -272,7 +272,47 @@ public function data_layout_support_flag_renders_classnames_on_wrapper() { ), ), ), - 'expected_output' => '

Some text.

', // The generated classname number assumes `wp_unique_prefixed_id( 'wp-container-content-' )` will not have run previously in this test. + 'expected_output' => '

Some text.

', + ), + 'single wrapper block layout with flex type' => array( + 'args' => array( + 'block_content' => '
', + 'block' => array( + 'blockName' => 'core/group', + 'attrs' => array( + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'flexWrap' => 'nowrap', + ), + ), + 'innerBlocks' => array(), + 'innerHTML' => '
', + 'innerContent' => array( + '
', + ), + ), + ), + 'expected_output' => '
', + ), + 'single wrapper block layout with grid type' => array( + 'args' => array( + 'block_content' => '
', + 'block' => array( + 'blockName' => 'core/group', + 'attrs' => array( + 'layout' => array( + 'type' => 'grid', + ), + ), + 'innerBlocks' => array(), + 'innerHTML' => '
', + 'innerContent' => array( + '
', + ), + ), + ), + 'expected_output' => '
', ), 'skip classname output if block does not support layout and there are no child layout classes to be output' => array( 'args' => array( @@ -463,4 +503,138 @@ public function data_wp_add_parent_layout_to_parsed_block() { ), ); } + + /** + * Check that wp_render_layout_support_flag() renders consistent hashes + * for the container class when the relevant layout properties are the same. + * + * @dataProvider data_layout_support_flag_renders_consistent_container_hash + * + * @covers ::wp_render_layout_support_flag + * + * @param array $block_attrs Dataset to test. + * @param array $expected_class Class generated for the passed dataset. + */ + public function test_layout_support_flag_renders_consistent_container_hash( $block_attrs, $expected_class ) { + switch_theme( 'default' ); + + $block_content = '
'; + $block = array( + 'blockName' => 'core/group', + 'innerBlocks' => array(), + 'innerHTML' => '
', + 'innerContent' => array( + '
', + ), + 'attrs' => $block_attrs, + ); + + /* + * The `appearance-tools` theme support is temporarily added to ensure + * that the block gap support is enabled during rendering, which is + * necessary to compute styles for layouts with block gap values. + */ + add_theme_support( 'appearance-tools' ); + $output = wp_render_layout_support_flag( $block_content, $block ); + remove_theme_support( 'appearance-tools' ); + + // Process the output and look for the expected class in the first rendered element. + $processor = new WP_HTML_Tag_Processor( $output ); + $processor->next_tag(); + + $this->assertTrue( + $processor->has_class( $expected_class ), + "Expected class '$expected_class' not found in the rendered output, probably because of a different hash." + ); + } + + /** + * Data provider for test_layout_support_flag_renders_consistent_container_hash. + * + * @return array + */ + public function data_layout_support_flag_renders_consistent_container_hash() { + return array( + 'default type block gap 12px' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'default', + ), + 'style' => array( + 'spacing' => array( + 'blockGap' => '12px', + ), + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-c5c7d83f', + ), + 'default type block gap 24px' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'default', + ), + 'style' => array( + 'spacing' => array( + 'blockGap' => '24px', + ), + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-634f0b9d', + ), + 'constrained type justified left' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'constrained', + 'justifyContent' => 'left', + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-12dd3699', + ), + 'constrained type justified right' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'constrained', + 'justifyContent' => 'right', + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-f1f2ed93', + ), + 'flex type horizontal' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'flexWrap' => 'nowrap', + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-2487dcaa', + ), + 'flex type vertical' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'flex', + 'orientation' => 'vertical', + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-fe9cc265', + ), + 'grid type' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'grid', + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-478b6e6b', + ), + 'grid type 3 columns' => array( + 'block_attributes' => array( + 'layout' => array( + 'type' => 'grid', + 'columnCount' => 3, + ), + ), + 'expected_class' => 'wp-container-core-group-is-layout-d3b710ac', + ), + ); + } } From 299aa6c46a90a14c02c8a84638cd9a29db392e87 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 19 Feb 2025 12:52:29 +0100 Subject: [PATCH 2/3] Fix WP version in wp_unique_id_from_values PHPDoc --- src/wp-includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 586a425f69113..4da5c5773a1a1 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -9181,7 +9181,7 @@ function wp_verify_fast_hash( * that serves as a unique identifier. Optionally, a prefix can be added to * the generated ID for context or categorization. * - * @since 6.9.0 + * @since 6.8.0 * * @param array $data The input array to generate an ID from. * @param string $prefix Optional. A prefix to prepend to the generated ID. Default ''. From fb808008598ec24812ecab4b72ab0487e5254630 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 19 Feb 2025 13:21:01 +0100 Subject: [PATCH 3/3] Update fixtures manually --- tests/phpunit/data/blocks/fixtures/core__columns.server.html | 2 +- .../data/blocks/fixtures/core__columns__deprecated.server.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/data/blocks/fixtures/core__columns.server.html b/tests/phpunit/data/blocks/fixtures/core__columns.server.html index e35ab2763e833..02d855cbd6c38 100644 --- a/tests/phpunit/data/blocks/fixtures/core__columns.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__columns.server.html @@ -1,5 +1,5 @@ -
+
diff --git a/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html b/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html index c61aabf6822a1..6b695d15963de 100644 --- a/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html +++ b/tests/phpunit/data/blocks/fixtures/core__columns__deprecated.server.html @@ -1,5 +1,5 @@ -
+

Column One, Paragraph One