Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout Support: Replace incremental IDs with hashes #8353

Draft
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 46 additions & 11 deletions src/wp-includes/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.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 {
$serialized = wp_json_encode( $data );
$hash = substr( md5( $serialized ), 0, 8 );
return $prefix . $hash;
}
Original file line number Diff line number Diff line change
@@ -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">

Expand Down
Original file line number Diff line number Diff line change
@@ -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>

Expand Down
176 changes: 175 additions & 1 deletion tests/phpunit/tests/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,47 @@ public function data_layout_support_flag_renders_classnames_on_wrapper() {
),
),
),
'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(
Expand Down Expand Up @@ -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 = '<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',
),
);
}
}
Loading