Skip to content
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
81 changes: 62 additions & 19 deletions src/wp-includes/block-supports/layout.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php
/**
* Layout block support flag.
Expand Down Expand Up @@ -80,16 +80,36 @@
*
* @since 7.1.0
*
* @param string|array|null $gap_value Block gap value.
* @return string|array|null Sanitized block gap value.
* Numeric zero is converted to a string because it is valid CSS without a unit.
* Other non-string values are rejected.
*
* @param mixed $gap_value Block gap value.
* @return string|string[]|null Sanitized block gap value.
*/
function wp_sanitize_block_gap_value( $gap_value ) {
if ( is_array( $gap_value ) ) {
foreach ( $gap_value as $key => $value ) {
$gap_value[ $key ] = ! is_scalar( $value ) || ( $value && preg_match( '%[\\\(&=}]|/\*%', (string) $value ) ) ? null : $value;
$sanitized_value = wp_sanitize_block_gap_value( $value );
if ( ! is_string( $sanitized_value ) ) {
unset( $gap_value[ $key ] );
continue;
}
$gap_value[ $key ] = $sanitized_value;
}

return $gap_value;
return empty( $gap_value ) ? null : $gap_value;
}

if ( ( is_int( $gap_value ) || is_float( $gap_value ) ) && 0.0 === (float) $gap_value ) {
return '0';
}

if ( ! is_string( $gap_value ) ) {
return null;
}

if ( '' === trim( $gap_value ) ) {
return null;
}

return $gap_value && preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
Expand Down Expand Up @@ -478,15 +498,18 @@
* @since 7.1.0 Added options array with options to process responsive styles.
* @access private
*
* @param string $selector CSS selector.
* @param array $layout Layout object. The one that is passed has already checked
* the existence of default block layout.
* @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false.
* @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null.
* @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false.
* @param string|array $fallback_gap_value Optional. The block gap value to apply. If it's an array expected properties are "top" and/or "left". Default '0.5em'.
* @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
* @param array $options {
* @param string $selector CSS selector.
* @param array $layout Layout object. The one that is passed has already checked
* the existence of default block layout.
* @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false.
* @param string|string[]|int|float|null $gap_value Optional. The block gap value to apply. Only zero is accepted as a
* numeric value. Default null.
* @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the
* editor. Default false.
* @param string|string[]|int|float|null $fallback_gap_value Optional. The fallback block gap value to apply. Only zero is accepted
* as a numeric value. Default '0.5em'.
* @param array|null $block_spacing Optional. Custom spacing set on the block. Default null.
* @param array $options {
* Optional. Extra options for internal callers. Default empty array.
*
* @type array $viewport_overrides An array of layout property overrides for the sake of style generation,
Expand All @@ -497,6 +520,10 @@
* @return string CSS styles on success. Else, empty string.
*/
function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null, $should_skip_gap_serialization = false, $fallback_gap_value = '0.5em', $block_spacing = null, $options = array() ) {
// Normalize here as well as at external data boundaries because this function has direct callers.
$gap_value = wp_sanitize_block_gap_value( $gap_value );
$fallback_gap_value = wp_sanitize_block_gap_value( $fallback_gap_value ) ?? '0.5em';

$base_layout = is_array( $layout ) ? $layout : array();
$viewport_overrides = $options['viewport_overrides'] ?? null;
$layout_for_styles = null === $viewport_overrides ? $base_layout : array_replace( $base_layout, $viewport_overrides );
Expand Down Expand Up @@ -763,7 +790,7 @@
}
$gap_value = trim( $combined_gap_value );

if ( null !== $gap_value && ! $should_skip_gap_serialization ) {
if ( '' !== $gap_value && ! $should_skip_gap_serialization ) {
$layout_styles[] = array(
'selector' => $selector,
'declarations' => array( 'gap' => $gap_value ),
Expand Down Expand Up @@ -817,7 +844,7 @@
}
} elseif ( 'grid' === $layout_type ) {
/*
* If the gap value is an array, we use the "left" value because it represents the vertical gap, which
* If the gap value is an array, we use the "left" value because it represents the horizontal gap, which
* is the relevant one for computation of responsive grid columns.
*/
if ( is_array( $fallback_gap_value ) ) {
Expand Down Expand Up @@ -846,10 +873,12 @@
$slug = _wp_to_kebab_case( substr( $process_value, $index_to_splice ) );
$process_value = "var(--wp--preset--spacing--$slug)";
}
if ( ! is_array( $gap_value ) || 'left' === $gap_side ) {
$responsive_gap_value = $process_value;
}
$combined_gap_value .= "$process_value ";
}
$gap_value = trim( $combined_gap_value );
$responsive_gap_value = $gap_value;
$gap_value = trim( $combined_gap_value );
}

// Ensure 0 values have a unit so they work in calc().
Expand Down Expand Up @@ -1153,7 +1182,9 @@
if ( ! current_theme_supports( 'disable-layout-styles' ) ) {

$gap_value = wp_sanitize_block_gap_value( $style_attr['spacing']['blockGap'] ?? null );
$fallback_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? '0.5em';
$fallback_gap_value = wp_sanitize_block_gap_value(
$block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? null
) ?? '0.5em';
$block_spacing = $style_attr['spacing'] ?? null;

/*
Expand Down Expand Up @@ -1187,7 +1218,19 @@
}
}

$global_block_gap_value = $variation_block_gap_value ?? $global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? $global_styles['spacing']['blockGap'] ?? null;
$global_block_gap_candidates = array(
$variation_block_gap_value,
$global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? null,
$global_styles['spacing']['blockGap'] ?? null,
);
$global_block_gap_value = null;
foreach ( $global_block_gap_candidates as $candidate_gap_value ) {
$candidate_gap_value = wp_sanitize_block_gap_value( $candidate_gap_value );
if ( null !== $candidate_gap_value ) {
$global_block_gap_value = $candidate_gap_value;
break;
}
}

if ( null !== $global_block_gap_value ) {
$fallback_gap_value = $global_block_gap_value;
Expand Down
143 changes: 135 additions & 8 deletions tests/phpunit/tests/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,150 @@ public function filter_set_theme_root() {
/**
* @ticket 65667
*
* @dataProvider data_sanitize_block_gap_value
*
* @covers ::wp_sanitize_block_gap_value
*/
public function test_sanitize_block_gap_value_rejects_nested_array_values() {
$this->assertSame(
array(
'top' => null,
'left' => '2rem',
),
wp_sanitize_block_gap_value(
public function test_sanitize_block_gap_value_normalizes_zero_and_rejects_other_non_string_values( $gap_value, $expected ) {
$this->assertSame( $expected, wp_sanitize_block_gap_value( $gap_value ) );
}

/**
* Data provider for test_sanitize_block_gap_value_normalizes_zero_and_rejects_other_non_string_values().
*
* @return array[] Test data.
*/
public function data_sanitize_block_gap_value() {
return array(
'string value' => array( '1rem', '1rem' ),
'empty string' => array( '', null ),
'whitespace-only string' => array( " \t\n", null ),
'integer zero' => array( 0, '0' ),
'floating-point zero' => array( 0.0, '0' ),
'non-zero integer' => array( 1, null ),
'boolean value' => array( true, null ),
'object value' => array( new stdClass(), null ),
'nested array value' => array(
array(
'top' => array( '1rem' ),
'left' => '2rem',
)
),
array( 'left' => '2rem' ),
),
'empty sanitized array' => array( array( array( '1rem' ) ), null ),
);
}

/**
* @dataProvider data_wp_get_layout_style
*
* @covers ::wp_get_layout_style
*/
public function test_wp_get_layout_style( $args, $expected_output ) {
$this->assertSame(
$expected_output,
wp_get_layout_style(
$args['selector'],
$args['layout'],
$args['has_block_gap_support'],
$args['gap_value'],
false,
$args['fallback_gap_value'] ?? '0.5em'
)
);
}

/**
* Data provider for test_wp_get_layout_style().
*
* @return array[] Test data.
*/
public function data_wp_get_layout_style() {
return array(
'flex layout uses the default for malformed gap values' => array(
'args' => array(
'selector' => '.wp-layout',
'layout' => array( 'type' => 'flex' ),
'has_block_gap_support' => true,
'gap_value' => array( 'left' => '2rem' ),
'fallback_gap_value' => array(
'top' => array( '1rem' ),
'left' => new stdClass(),
),
),
'expected_output' => '.wp-layout{gap:0.5em 2rem;}',
),
'flex layout ignores an empty block gap' => array(
'args' => array(
'selector' => '.wp-layout',
'layout' => array( 'type' => 'flex' ),
'has_block_gap_support' => true,
'gap_value' => '',
),
'expected_output' => '',
),
'grid layout uses the default for malformed gap values' => array(
'args' => array(
'selector' => '.wp-layout',
'layout' => array( 'type' => 'grid' ),
'has_block_gap_support' => true,
'gap_value' => array( 'left' => '2rem' ),
'fallback_gap_value' => array(
'top' => array( '1rem' ),
'left' => new stdClass(),
),
),
'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(min(12rem, 100%), 1fr));container-type:inline-size;gap:0.5em 2rem;}',
),
'grid layout uses horizontal gap for responsive columns' => array(
'args' => array(
'selector' => '.wp-layout',
'layout' => array(
'type' => 'grid',
'columnCount' => 3,
'minimumColumnWidth' => '12rem',
),
'has_block_gap_support' => true,
'gap_value' => array(
'top' => '2rem',
'left' => '3rem',
),
),
'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(max(min(12rem, 100%), (100% - (3rem * (3 - 1))) /3), 1fr));container-type:inline-size;gap:2rem 3rem;}',
),
'grid layout uses fallback when horizontal gap is missing' => array(
'args' => array(
'selector' => '.wp-layout',
'layout' => array(
'type' => 'grid',
'columnCount' => 3,
'minimumColumnWidth' => '12rem',
),
'has_block_gap_support' => true,
'gap_value' => array( 'top' => '2rem' ),
'fallback_gap_value' => '1.2rem',
),
'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(max(min(12rem, 100%), (100% - (1.2rem * (3 - 1))) /3), 1fr));container-type:inline-size;gap:2rem 1.2rem;}',
),
'grid layout preserves zero horizontal gap' => array(
'args' => array(
'selector' => '.wp-layout',
'layout' => array(
'type' => 'grid',
'columnCount' => 3,
'minimumColumnWidth' => '12rem',
),
'has_block_gap_support' => true,
'gap_value' => array(
'top' => '2rem',
'left' => '0',
),
),
'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(max(min(12rem, 100%), (100% - (0px * (3 - 1))) /3), 1fr));container-type:inline-size;gap:2rem 0;}',
),
);
}

/**
* @ticket 55505
*/
Expand Down
Loading