Changeset 64238
- Timestamp:
- 10/07/2026 10:53:31 PM (17 hours ago)
- Location:
- trunk
- Files:
-
- 4 edited
-
src/wp-includes/block-supports/layout.php (modified) (7 diffs)
-
src/wp-includes/class-wp-theme-json.php (modified) (4 diffs)
-
tests/phpunit/tests/block-supports/layout.php (modified) (1 diff)
-
tests/phpunit/tests/theme/wpThemeJson.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/block-supports/layout.php
r63887 r64238 81 81 * @since 7.1.0 82 82 * 83 * @param string|array|null $gap_value Block gap value. 84 * @return string|array|null Sanitized block gap value. 83 * Numeric zero is converted to a string because it is valid CSS without a unit. 84 * Other non-string values are rejected. 85 * 86 * @param mixed $gap_value Block gap value. 87 * @return string|string[]|null Sanitized block gap value. 85 88 */ 86 89 function wp_sanitize_block_gap_value( $gap_value ) { 87 90 if ( is_array( $gap_value ) ) { 88 91 foreach ( $gap_value as $key => $value ) { 89 $gap_value[ $key ] = ! is_scalar( $value ) || ( $value && preg_match( '%[\\\(&=}]|/\*%', (string) $value ) ) ? null : $value; 90 } 91 92 return $gap_value; 92 $sanitized_value = wp_sanitize_block_gap_value( $value ); 93 if ( ! is_string( $sanitized_value ) ) { 94 unset( $gap_value[ $key ] ); 95 continue; 96 } 97 $gap_value[ $key ] = $sanitized_value; 98 } 99 100 return empty( $gap_value ) ? null : $gap_value; 101 } 102 103 if ( ( is_int( $gap_value ) || is_float( $gap_value ) ) && 0.0 === (float) $gap_value ) { 104 return '0'; 105 } 106 107 if ( ! is_string( $gap_value ) ) { 108 return null; 109 } 110 111 if ( '' === trim( $gap_value ) ) { 112 return null; 93 113 } 94 114 … … 491 511 * @access private 492 512 * 493 * @param string $selector CSS selector. 494 * @param array $layout Layout object. The one that is passed has already checked 495 * the existence of default block layout. 496 * @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false. 497 * @param string|string[]|null $gap_value Optional. The block gap value to apply. Default null. 498 * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the editor. Default false. 499 * @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'. 500 * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null. 501 * @param array $options { 513 * @param string $selector CSS selector. 514 * @param array $layout Layout object. The one that is passed has already checked 515 * the existence of default block layout. 516 * @param bool $has_block_gap_support Optional. Whether the theme has support for the block gap. Default false. 517 * @param string|string[]|int|float|null $gap_value Optional. The block gap value to apply. Only zero is accepted as a 518 * numeric value. Default null. 519 * @param bool $should_skip_gap_serialization Optional. Whether to skip applying the user-defined value set in the 520 * editor. Default false. 521 * @param string|string[]|int|float|null $fallback_gap_value Optional. The fallback block gap value to apply. Only zero is accepted 522 * as a numeric value. Default '0.5em'. 523 * @param array|null $block_spacing Optional. Custom spacing set on the block. Default null. 524 * @param array $options { 502 525 * Optional. Extra options for internal callers. Default empty array. 503 526 * … … 510 533 */ 511 534 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() ) { 535 // Normalize here as well as at external data boundaries because this function has direct callers. 536 $gap_value = wp_sanitize_block_gap_value( $gap_value ); 537 $fallback_gap_value = wp_sanitize_block_gap_value( $fallback_gap_value ) ?? '0.5em'; 538 512 539 $base_layout = is_array( $layout ) ? $layout : array(); 513 540 $viewport_overrides = $options['viewport_overrides'] ?? null; … … 858 885 859 886 /* 860 * If the gap value is an array, we use the "left" value because it represents the vertical gap, which887 * If the gap value is an array, we use the "left" value because it represents the horizontal gap, which 861 888 * is the relevant one for computation of responsive grid columns. 862 889 */ … … 887 914 $process_value = "var(--wp--preset--spacing--$slug)"; 888 915 } 916 if ( ! is_array( $gap_value ) || 'left' === $gap_side ) { 917 $responsive_gap_value = $process_value; 918 } 889 919 $combined_gap_value .= "$process_value "; 890 920 } 891 $gap_value = trim( $combined_gap_value ); 892 $responsive_gap_value = $gap_value; 921 $gap_value = trim( $combined_gap_value ); 893 922 } 894 923 … … 1197 1226 1198 1227 $gap_value = wp_sanitize_block_gap_value( $style_attr['spacing']['blockGap'] ?? null ); 1199 $fallback_gap_value = $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? '0.5em'; 1228 $fallback_gap_value = wp_sanitize_block_gap_value( 1229 $block_type->supports['spacing']['blockGap']['__experimentalDefault'] ?? null 1230 ) ?? '0.5em'; 1200 1231 $block_spacing = $style_attr['spacing'] ?? null; 1201 1232 … … 1231 1262 } 1232 1263 1233 $global_block_gap_value = $variation_block_gap_value ?? $global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? $global_styles['spacing']['blockGap'] ?? null; 1264 $global_block_gap_candidates = array( 1265 $variation_block_gap_value, 1266 $global_styles['blocks'][ $block_name ]['spacing']['blockGap'] ?? null, 1267 $global_styles['spacing']['blockGap'] ?? null, 1268 ); 1269 $global_block_gap_value = null; 1270 foreach ( $global_block_gap_candidates as $candidate_gap_value ) { 1271 $candidate_gap_value = wp_sanitize_block_gap_value( $candidate_gap_value ); 1272 if ( null !== $candidate_gap_value ) { 1273 $global_block_gap_value = $candidate_gap_value; 1274 break; 1275 } 1276 } 1234 1277 1235 1278 if ( null !== $global_block_gap_value ) { -
trunk/src/wp-includes/class-wp-theme-json.php
r64093 r64238 2318 2318 */ 2319 2319 if ( $has_block_gap_support || $has_fallback_gap_support ) { 2320 $block_gap_value = null; 2320 $block_gap_value = null; 2321 $block_gap_row_value = null; 2321 2322 // Use a fallback gap value if block gap support is not available. 2322 2323 if ( ! $has_block_gap_support ) { … … 2328 2329 $block_gap_value = static::get_property_value( $node, array( 'spacing', 'blockGap' ) ); 2329 2330 } 2331 $block_gap_row_value = $block_gap_value; 2330 2332 2331 2333 // Support split row / column values and concatenate to a shorthand value. 2332 2334 if ( is_array( $block_gap_value ) ) { 2333 if ( isset( $block_gap_value['top'] ) && isset( $block_gap_value['left'] ) ) { 2334 $gap_row = static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) ); 2335 $gap_column = static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) ); 2336 $block_gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column; 2335 $has_block_gap_row_value = isset( $block_gap_value['top'] ); 2336 $has_block_gap_column_value = isset( $block_gap_value['left'] ); 2337 2338 if ( $has_block_gap_row_value || $has_block_gap_column_value ) { 2339 $block_gap_row_value = $has_block_gap_row_value 2340 ? static::get_property_value( $node, array( 'spacing', 'blockGap', 'top' ) ) 2341 : '0'; 2342 $block_gap_column_value = $has_block_gap_column_value 2343 ? static::get_property_value( $node, array( 'spacing', 'blockGap', 'left' ) ) 2344 : '0'; 2345 $block_gap_value = $block_gap_row_value === $block_gap_column_value 2346 ? $block_gap_row_value 2347 : $block_gap_row_value . ' ' . $block_gap_column_value; 2337 2348 } else { 2338 // Skip outputting gap value if not all sides are provided. 2339 $block_gap_value = null; 2349 // Skip outputting a gap value if neither supported axis is provided. 2350 $block_gap_value = null; 2351 $block_gap_row_value = null; 2340 2352 } 2341 2353 } … … 2349 2361 } 2350 2362 2351 $class_name = $layout_definition['className'] ?? false; 2352 $spacing_rules = $layout_definition['spacingStyles'] ?? array(); 2363 $class_name = $layout_definition['className'] ?? false; 2364 $spacing_rules = $layout_definition['spacingStyles'] ?? array(); 2365 $layout_gap_value = in_array( $layout_definition_key, array( 'default', 'constrained' ), true ) 2366 ? $block_gap_row_value 2367 : $block_gap_value; 2353 2368 2354 2369 if ( … … 2365 2380 // Iterate over each of the styling rules and substitute non-string values such as `null` with the real `blockGap` value. 2366 2381 foreach ( $spacing_rule['rules'] as $css_property => $css_value ) { 2367 $current_css_value = is_string( $css_value ) ? $css_value : $ block_gap_value;2382 $current_css_value = is_string( $css_value ) ? $css_value : $layout_gap_value; 2368 2383 if ( static::is_safe_css_declaration( $css_property, $current_css_value ) ) { 2369 2384 $declarations[] = array( -
trunk/tests/phpunit/tests/block-supports/layout.php
r64057 r64238 85 85 /** 86 86 * @ticket 65667 87 * @ticket 66263 88 * 89 * @dataProvider data_sanitize_block_gap_value 87 90 * 88 91 * @covers ::wp_sanitize_block_gap_value 89 92 */ 90 public function test_sanitize_block_gap_value_rejects_nested_array_values() { 91 $this->assertSame( 92 array( 93 'top' => null, 94 'left' => '2rem', 95 ), 96 wp_sanitize_block_gap_value( 93 public function test_sanitize_block_gap_value_normalizes_zero_and_rejects_other_non_string_values( $gap_value, $expected ) { 94 $this->assertSame( $expected, wp_sanitize_block_gap_value( $gap_value ) ); 95 } 96 97 /** 98 * Data provider for test_sanitize_block_gap_value_normalizes_zero_and_rejects_other_non_string_values(). 99 * 100 * @return array[] Test data. 101 */ 102 public function data_sanitize_block_gap_value() { 103 return array( 104 'string value' => array( '1rem', '1rem' ), 105 'empty string' => array( '', null ), 106 'whitespace-only string' => array( " \t\n", null ), 107 'integer zero' => array( 0, '0' ), 108 'floating-point zero' => array( 0.0, '0' ), 109 'non-zero integer' => array( 1, null ), 110 'boolean value' => array( true, null ), 111 'object value' => array( new stdClass(), null ), 112 'nested array value' => array( 97 113 array( 98 114 'top' => array( '1rem' ), 99 115 'left' => '2rem', 100 ) 116 ), 117 array( 'left' => '2rem' ), 118 ), 119 'empty sanitized array' => array( array( array( '1rem' ) ), null ), 120 ); 121 } 122 123 /** 124 * @ticket 66263 125 * 126 * @dataProvider data_wp_get_layout_style 127 * 128 * @covers ::wp_get_layout_style 129 */ 130 public function test_wp_get_layout_style( $args, $expected_output ) { 131 $this->assertSame( 132 $expected_output, 133 wp_get_layout_style( 134 $args['selector'], 135 $args['layout'], 136 $args['has_block_gap_support'], 137 $args['gap_value'], 138 false, 139 $args['fallback_gap_value'] ?? '0.5em' 101 140 ) 141 ); 142 } 143 144 /** 145 * Data provider for test_wp_get_layout_style(). 146 * 147 * @return array[] Test data. 148 */ 149 public function data_wp_get_layout_style() { 150 return array( 151 'flex layout uses the default for malformed gap values' => array( 152 'args' => array( 153 'selector' => '.wp-layout', 154 'layout' => array( 'type' => 'flex' ), 155 'has_block_gap_support' => true, 156 'gap_value' => array( 'left' => '2rem' ), 157 'fallback_gap_value' => array( 158 'top' => array( '1rem' ), 159 'left' => new stdClass(), 160 ), 161 ), 162 'expected_output' => '.wp-layout{gap:0.5em 2rem;}', 163 ), 164 'flex layout ignores an empty block gap' => array( 165 'args' => array( 166 'selector' => '.wp-layout', 167 'layout' => array( 'type' => 'flex' ), 168 'has_block_gap_support' => true, 169 'gap_value' => '', 170 ), 171 'expected_output' => '', 172 ), 173 'grid layout uses the default for malformed gap values' => array( 174 'args' => array( 175 'selector' => '.wp-layout', 176 'layout' => array( 'type' => 'grid' ), 177 'has_block_gap_support' => true, 178 'gap_value' => array( 'left' => '2rem' ), 179 'fallback_gap_value' => array( 180 'top' => array( '1rem' ), 181 'left' => new stdClass(), 182 ), 183 ), 184 'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(min(12rem, 100%), 1fr));container-type:inline-size;gap:0.5em 2rem;}', 185 ), 186 'grid layout uses horizontal gap for responsive columns' => array( 187 'args' => array( 188 'selector' => '.wp-layout', 189 'layout' => array( 190 'type' => 'grid', 191 'columnCount' => 3, 192 'minimumColumnWidth' => '12rem', 193 ), 194 'has_block_gap_support' => true, 195 'gap_value' => array( 196 'top' => '2rem', 197 'left' => '3rem', 198 ), 199 ), 200 '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;}', 201 ), 202 'grid layout uses fallback when horizontal gap is missing' => array( 203 'args' => array( 204 'selector' => '.wp-layout', 205 'layout' => array( 206 'type' => 'grid', 207 'columnCount' => 3, 208 'minimumColumnWidth' => '12rem', 209 ), 210 'has_block_gap_support' => true, 211 'gap_value' => array( 'top' => '2rem' ), 212 'fallback_gap_value' => '1.2rem', 213 ), 214 '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;}', 215 ), 216 'grid layout preserves zero horizontal gap' => array( 217 'args' => array( 218 'selector' => '.wp-layout', 219 'layout' => array( 220 'type' => 'grid', 221 'columnCount' => 3, 222 'minimumColumnWidth' => '12rem', 223 ), 224 'has_block_gap_support' => true, 225 'gap_value' => array( 226 'top' => '2rem', 227 'left' => '0', 228 ), 229 ), 230 '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;}', 231 ), 102 232 ); 103 233 } -
trunk/tests/phpunit/tests/theme/wpThemeJson.php
r64104 r64238 2097 2097 ':root { --wp--style--global--content-size: 640px;--wp--style--global--wide-size: 1200px; }:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.wp-site-blocks) > * { margin-block-start: 1em; margin-block-end: 0; }:where(.wp-site-blocks) > :first-child { margin-block-start: 0; }:where(.wp-site-blocks) > :last-child { margin-block-end: 0; }:root { --wp--style--block-gap: 1em; }:root :where(.is-layout-flow) > :first-child{margin-block-start: 0;}:root :where(.is-layout-flow) > :last-child{margin-block-end: 0;}:root :where(.is-layout-flow) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-constrained) > :first-child{margin-block-start: 0;}:root :where(.is-layout-constrained) > :last-child{margin-block-end: 0;}:root :where(.is-layout-constrained) > *{margin-block-start: 1em;margin-block-end: 0;}:root :where(.is-layout-flex){gap: 1em;}:root :where(.is-layout-grid){gap: 1em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}', 2098 2098 $theme_json->get_stylesheet( array( 'styles' ) ) 2099 ); 2100 } 2101 2102 /** 2103 * @ticket 66263 2104 * 2105 * @dataProvider data_get_stylesheet_with_axial_block_gap 2106 * 2107 * @param array $block_gap Block gap value. 2108 * @param string $expected_row_gap Expected row gap value. 2109 * @param string $expected_column_gap Expected column gap value. 2110 */ 2111 public function test_get_stylesheet_generates_layout_styles_with_axial_block_gap( $block_gap, $expected_row_gap, $expected_column_gap ) { 2112 $theme_json = new WP_Theme_JSON( 2113 array( 2114 'version' => WP_Theme_JSON::LATEST_SCHEMA, 2115 'settings' => array( 2116 'spacing' => array( 2117 'blockGap' => true, 2118 ), 2119 ), 2120 'styles' => array( 2121 'blocks' => array( 2122 'core/group' => array( 2123 'spacing' => array( 2124 'blockGap' => $block_gap, 2125 ), 2126 ), 2127 ), 2128 ), 2129 ), 2130 'default' 2131 ); 2132 2133 $stylesheet = $theme_json->get_stylesheet( array( 'styles' ), null, array( 'skip_root_layout_styles' => true ) ); 2134 2135 $this->assertMatchesRegularExpression( '/:where\(\.wp-block-group-is-layout-flow\) > \*\{margin-block-start:\s*' . preg_quote( $expected_row_gap, '/' ) . ';margin-block-end:\s*0;\}/', $stylesheet ); 2136 $this->assertMatchesRegularExpression( '/:where\(\.wp-block-group-is-layout-constrained\) > \*\{margin-block-start:\s*' . preg_quote( $expected_row_gap, '/' ) . ';margin-block-end:\s*0;\}/', $stylesheet ); 2137 $this->assertMatchesRegularExpression( '/:where\(\.wp-block-group-is-layout-flex\)\{gap:\s*' . preg_quote( $expected_row_gap, '/' ) . ' ' . preg_quote( $expected_column_gap, '/' ) . ';\}/', $stylesheet ); 2138 $this->assertMatchesRegularExpression( '/:where\(\.wp-block-group-is-layout-grid\)\{gap:\s*' . preg_quote( $expected_row_gap, '/' ) . ' ' . preg_quote( $expected_column_gap, '/' ) . ';\}/', $stylesheet ); 2139 } 2140 2141 /** 2142 * Data provider for test_get_stylesheet_generates_layout_styles_with_axial_block_gap(). 2143 * 2144 * @return array[] Test data. 2145 */ 2146 public function data_get_stylesheet_with_axial_block_gap() { 2147 return array( 2148 'different row and column gaps' => array( 2149 array( 2150 'top' => '1em', 2151 'left' => '2em', 2152 ), 2153 '1em', 2154 '2em', 2155 ), 2156 'row gap only' => array( 2157 array( 'top' => '1em' ), 2158 '1em', 2159 '0', 2160 ), 2161 'column gap only' => array( 2162 array( 'left' => '2em' ), 2163 '0', 2164 '2em', 2165 ), 2099 2166 ); 2100 2167 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)