Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: WordPress/gutenberg
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 6a973d6a6d961a53d4c94bb118f861d24bcc367b
Choose a base ref
..
head repository: WordPress/gutenberg
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: d01dacaef44191e222176c6fae74069a253b3fad
Choose a head ref
Showing with 257 additions and 23 deletions.
  1. +3 −0 backport-changelog/6.8/8353.md
  2. +64 −11 lib/block-supports/layout.php
  3. +15 −11 packages/e2e-tests/mu-plugins/enable-templates-ui.php
  4. +175 −1 phpunit/block-supports/layout-test.php
3 changes: 3 additions & 0 deletions backport-changelog/6.8/8353.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/8353

* https://github.com/WordPress/gutenberg/pull/68210
75 changes: 64 additions & 11 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
@@ -582,6 +582,24 @@ function gutenberg_incremental_id_per_prefix( $prefix = '' ) {
return $prefix . (string) ++$id_counters[ $prefix ];
}

/**
* 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.
*
* @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 gutenberg_unique_id_from_values( array $data, string $prefix = '' ): string {
$serialized = wp_json_encode( $data );
$hash = substr( md5( $serialized ), 0, 8 );
return $prefix . $hash;
}

/**
* Renders the layout config to the block wrapper.
*
@@ -603,7 +621,33 @@ function gutenberg_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 = gutenberg_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();

@@ -755,16 +799,6 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$class_names = array();
$layout_definitions = gutenberg_get_layout_definitions();

/*
* We use 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. We need this to make the CSS class names stable across paginations
* for features like the enhanced pagination of the Query block.
*/
$container_class = gutenberg_incremental_id_per_prefix(
'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';
@@ -838,6 +872,25 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
$block_gap = $global_settings['spacing']['blockGap'] ?? null;
$has_block_gap_support = isset( $block_gap );

/*
* We generate a unique ID based on all the data required to obtain the
* corresponding layout style. This way, the CSS class names keep the same
* even for different blocks with the same layout definition. We need this to
* make the CSS class names stable across paginations for features like the
* enhanced pagination of the Query block.
*/
$container_class = gutenberg_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 = gutenberg_get_layout_style(
".$container_class",
$used_layout,
26 changes: 15 additions & 11 deletions packages/e2e-tests/mu-plugins/enable-templates-ui.php
Original file line number Diff line number Diff line change
@@ -10,15 +10,19 @@
/**
* Enable Templates & Template Parts post type UI during e2e testing.
*/
add_filter(
'register_post_type_args',
static function ( $args, $name ) {
if ( in_array( $name, array( 'wp_template', 'wp_template_part' ), true ) ) {
$args['show_ui'] = wp_is_block_theme();
}

return $args;
},
20,
2
);

function gutenberg_enable_templates_ui() {
add_filter(
'register_post_type_args',
static function ( $args, $name ) {
if ( in_array( $name, array( 'wp_template', 'wp_template_part' ), true ) ) {
$args['show_ui'] = wp_is_block_theme();
}
return $args;
},
20,
2
);
}
add_action( 'setup_theme', 'gutenberg_enable_templates_ui' );
176 changes: 175 additions & 1 deletion phpunit/block-supports/layout-test.php
Original file line number Diff line number Diff line change
@@ -501,7 +501,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>',
),
);
}
@@ -593,4 +633,138 @@ public function data_restore_group_inner_container() {
),
);
}

/**
* Check that gutenberg_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 ::gutenberg_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 = gutenberg_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',
),
);
}
}