mirror of
git://develop.git.wordpress.org/
synced 2025-03-23 05:20:01 +01:00
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. git-svn-id: https://develop.svn.wordpress.org/trunk@60038 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
79fd25fa56
commit
8b632c94a5
@ -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,
|
||||
|
@ -9174,3 +9174,33 @@ 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.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 ''.
|
||||
*
|
||||
* @return string The generated unique ID for the array.
|
||||
*/
|
||||
function wp_unique_id_from_values( array $data, string $prefix = '' ): string {
|
||||
if ( empty( $data ) ) {
|
||||
_doing_it_wrong(
|
||||
__FUNCTION__,
|
||||
sprintf(
|
||||
__( 'The $data argument must not be empty.' ),
|
||||
gettype( $data )
|
||||
),
|
||||
'6.8.0'
|
||||
);
|
||||
}
|
||||
$serialized = wp_json_encode( $data );
|
||||
$hash = substr( md5( $serialized ), 0, 8 );
|
||||
return $prefix . $hash;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
<div class="wp-block-columns has-3-columns is-layout-flex wp-container-1 wp-block-columns-is-layout-flex">
|
||||
<div class="wp-block-columns has-3-columns is-layout-flex wp-container-1d6595d7 wp-block-columns-is-layout-flex">
|
||||
|
||||
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
<div class="wp-block-columns has-3-columns is-layout-flex wp-container-1 wp-block-columns-is-layout-flex">
|
||||
<div class="wp-block-columns has-3-columns is-layout-flex wp-container-1d6595d7 wp-block-columns-is-layout-flex">
|
||||
|
||||
<p class="layout-column-1">Column One, Paragraph One</p>
|
||||
|
||||
|
@ -272,7 +272,47 @@ class Tests_Block_Supports_Layout extends WP_UnitTestCase {
|
||||
),
|
||||
),
|
||||
),
|
||||
'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.
|
||||
'expected_output' => '<p class="wp-container-content-b7aa651c">Some text.</p>',
|
||||
),
|
||||
'single wrapper block layout with flex type' => array(
|
||||
'args' => array(
|
||||
'block_content' => '<div class="wp-block-group"></div>',
|
||||
'block' => array(
|
||||
'blockName' => 'core/group',
|
||||
'attrs' => array(
|
||||
'layout' => array(
|
||||
'type' => 'flex',
|
||||
'orientation' => 'horizontal',
|
||||
'flexWrap' => 'nowrap',
|
||||
),
|
||||
),
|
||||
'innerBlocks' => array(),
|
||||
'innerHTML' => '<div class="wp-block-group"></div>',
|
||||
'innerContent' => array(
|
||||
'<div class="wp-block-group"></div>',
|
||||
),
|
||||
),
|
||||
),
|
||||
'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>',
|
||||
),
|
||||
'single wrapper block layout with grid type' => array(
|
||||
'args' => array(
|
||||
'block_content' => '<div class="wp-block-group"></div>',
|
||||
'block' => array(
|
||||
'blockName' => 'core/group',
|
||||
'attrs' => array(
|
||||
'layout' => array(
|
||||
'type' => 'grid',
|
||||
),
|
||||
),
|
||||
'innerBlocks' => array(),
|
||||
'innerHTML' => '<div class="wp-block-group"></div>',
|
||||
'innerContent' => array(
|
||||
'<div class="wp-block-group"></div>',
|
||||
),
|
||||
),
|
||||
),
|
||||
'expected_output' => '<div class="wp-block-group is-layout-grid wp-container-core-group-is-layout-9649a0d9 wp-block-group-is-layout-grid"></div>',
|
||||
),
|
||||
'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 @@ class Tests_Block_Supports_Layout extends WP_UnitTestCase {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = '<div class="wp-block-group"></div>';
|
||||
$block = array(
|
||||
'blockName' => 'core/group',
|
||||
'innerBlocks' => array(),
|
||||
'innerHTML' => '<div class="wp-block-group"></div>',
|
||||
'innerContent' => array(
|
||||
'<div class="wp-block-group"></div>',
|
||||
),
|
||||
'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',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
172
tests/phpunit/tests/functions/wpUniqueIdFromValues.php
Normal file
172
tests/phpunit/tests/functions/wpUniqueIdFromValues.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test cases for the `wp_unique_id_from_values()` function.
|
||||
*
|
||||
* @package WordPress\UnitTests
|
||||
*
|
||||
* @since 6.8.0
|
||||
*
|
||||
* @group functions.php
|
||||
* @covers ::wp_unique_id_from_values
|
||||
*/
|
||||
class Tests_Functions_WpUniqueIdFromValues extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Test that the function returns consistent ids for the passed params.
|
||||
*
|
||||
* @ticket 62985
|
||||
*
|
||||
* @dataProvider data_wp_unique_id_from_values
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public function test_wp_unique_id_from_values( $expected, $data, $prefix ) {
|
||||
$output1 = wp_unique_id_from_values( $data );
|
||||
$output2 = wp_unique_id_from_values( $data, $prefix );
|
||||
$this->assertSame( $expected, $output1 );
|
||||
$this->assertSame( $prefix . $expected, $output2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_wp_unique_id_from_values() {
|
||||
return array(
|
||||
'string' => array(
|
||||
'expected' => '469f5989',
|
||||
'data' => array(
|
||||
'value' => 'text',
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'integer' => array(
|
||||
'expected' => 'b2f0842e',
|
||||
'data' => array(
|
||||
'value' => 123,
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'float' => array(
|
||||
'expected' => 'a756f54d',
|
||||
'data' => array(
|
||||
'value' => 1.23,
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'boolean' => array(
|
||||
'expected' => 'bdae8be3',
|
||||
'data' => array(
|
||||
'value' => true,
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'object' => array(
|
||||
'expected' => '477bd670',
|
||||
'data' => array(
|
||||
'value' => new StdClass(),
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'null' => array(
|
||||
'expected' => 'a860dd95',
|
||||
'data' => array(
|
||||
'value' => null,
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'multiple values' => array(
|
||||
'expected' => 'ef258a5d',
|
||||
'data' => array(
|
||||
'value1' => 'text',
|
||||
'value2' => 123,
|
||||
'value3' => 1.23,
|
||||
'value4' => true,
|
||||
'value5' => new StdClass(),
|
||||
'value6' => null,
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
'nested arrays' => array(
|
||||
'expected' => '4345cae5',
|
||||
'data' => array(
|
||||
'list1' => array(
|
||||
'value1' => 'text',
|
||||
'value2' => 123,
|
||||
'value3' => 1.23,
|
||||
),
|
||||
'list2' => array(
|
||||
'value4' => true,
|
||||
'value5' => new StdClass(),
|
||||
'value6' => null,
|
||||
),
|
||||
),
|
||||
'prefix' => 'my-prefix-',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that passing an empty array is not allowed.
|
||||
*
|
||||
* @ticket 62985
|
||||
*
|
||||
* @expectedIncorrectUsage wp_unique_id_from_values
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public function test_wp_unique_id_from_values_empty_array() {
|
||||
wp_unique_id_from_values( array(), 'my-prefix-' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that passing non-array data throws an error.
|
||||
*
|
||||
* @ticket 62985
|
||||
*
|
||||
* @dataProvider data_wp_unique_id_from_values_invalid_data
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public function test_wp_unique_id_from_values_invalid_data( $data, $prefix ) {
|
||||
$this->expectException( TypeError::class );
|
||||
|
||||
wp_unique_id_from_values( $data, $prefix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data_wp_unique_id_from_values_invalid_data() {
|
||||
return array(
|
||||
'string' => array(
|
||||
'data' => 'text',
|
||||
'prefix' => '',
|
||||
),
|
||||
'integer' => array(
|
||||
'data' => 123,
|
||||
'prefix' => '',
|
||||
),
|
||||
'float' => array(
|
||||
'data' => 1.23,
|
||||
'prefix' => '',
|
||||
),
|
||||
'boolean' => array(
|
||||
'data' => true,
|
||||
'prefix' => '',
|
||||
),
|
||||
'object' => array(
|
||||
'data' => new StdClass(),
|
||||
'prefix' => '',
|
||||
),
|
||||
'null' => array(
|
||||
'data' => null,
|
||||
'prefix' => '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user