Changeset 63330
- Timestamp:
- 08/21/2026 05:42:32 AM (24 hours ago)
- Location:
- trunk
- Files:
-
- 2 edited
-
src/wp-includes/block-supports/layout.php (modified) (14 diffs)
-
tests/phpunit/tests/block-supports/layout.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/block-supports/layout.php
r62864 r63330 160 160 } 161 161 162 $column_start = $child_layout['columnStart'] ?? null; 163 $column_span = $child_layout['columnSpan'] ?? null; 162 /* 163 * Grid line numbers and spans are whole numbers. The editor stores them as numbers, but 164 * content saved by WordPress 6.3 to 6.6 stored them as numeric strings, and that 165 * migration only runs when a block is parsed in JavaScript, so the front end still sees 166 * strings. Accept any numeric value and cast it, and treat anything else as absent 167 * because it can't render as valid CSS. 168 */ 169 $column_start_attr = $child_layout['columnStart'] ?? null; 170 $column_start = is_numeric( $column_start_attr ) ? (int) $column_start_attr : null; 171 $column_span_attr = $child_layout['columnSpan'] ?? null; 172 $column_span = is_numeric( $column_span_attr ) ? (int) $column_span_attr : null; 164 173 if ( null === $viewport_overrides || $has_viewport_property_override( 'columnStart' ) || $has_viewport_property_override( 'columnSpan' ) ) { 165 174 if ( $column_start && $column_span ) { … … 172 181 } 173 182 174 $row_start = $child_layout['rowStart'] ?? null; 175 $row_span = $child_layout['rowSpan'] ?? null; 183 $row_start_attr = $child_layout['rowStart'] ?? null; 184 $row_start = is_numeric( $row_start_attr ) ? (int) $row_start_attr : null; 185 $row_span_attr = $child_layout['rowSpan'] ?? null; 186 $row_span = is_numeric( $row_span_attr ) ? (int) $row_span_attr : null; 176 187 if ( null === $viewport_overrides || $has_viewport_property_override( 'rowStart' ) || $has_viewport_property_override( 'rowSpan' ) ) { 177 188 if ( $row_start && $row_span ) { … … 191 202 } 192 203 193 $minimum_column_width = $parent_layout['minimumColumnWidth'] ?? null; 194 $column_count = $parent_layout['columnCount'] ?? null; 204 $minimum_column_width_attr = $parent_layout['minimumColumnWidth'] ?? null; 205 $minimum_column_width = is_string( $minimum_column_width_attr ) ? $minimum_column_width_attr : null; 206 $column_count = $parent_layout['columnCount'] ?? null; 195 207 196 208 /* … … 549 561 } 550 562 } elseif ( 'constrained' === $layout_type ) { 551 $content_size = $layout_for_styles['contentSize'] ?? ''; 552 $wide_size = $layout_for_styles['wideSize'] ?? ''; 553 $justify_content = $layout_for_styles['justifyContent'] ?? 'center'; 563 // The schemas and editor UI only produce strings here, so treat a non-string 564 // value as absent rather than casting it — it couldn't render as valid CSS anyway. 565 $content_size_attr = $layout_for_styles['contentSize'] ?? null; 566 $content_size = is_string( $content_size_attr ) ? $content_size_attr : ''; 567 $wide_size_attr = $layout_for_styles['wideSize'] ?? null; 568 $wide_size = is_string( $wide_size_attr ) ? $wide_size_attr : ''; 569 $justify_content_attr = $layout_for_styles['justifyContent'] ?? null; 570 $justify_content = is_string( $justify_content_attr ) ? $justify_content_attr : 'center'; 554 571 555 572 // Check if viewport-specific ("override") values exist. Null values are valid and mean the user cleared a value inherited from the default viewport. … … 772 789 } 773 790 791 $flex_justify_content = $layout_for_styles['justifyContent'] ?? null; 792 $flex_vertical_alignment = $layout_for_styles['verticalAlignment'] ?? null; 793 774 794 if ( 'horizontal' === $layout_orientation ) { 775 795 /* … … 778 798 * by custom css. 779 799 */ 780 if ( $should_output_flex_justification && ! empty( $ layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {800 if ( $should_output_flex_justification && ! empty( $flex_justify_content ) && is_string( $flex_justify_content ) && array_key_exists( $flex_justify_content, $justify_content_options ) ) { 781 801 $layout_styles[] = array( 782 802 'selector' => $selector, 783 'declarations' => array( 'justify-content' => $justify_content_options[ $ layout_for_styles['justifyContent']] ),803 'declarations' => array( 'justify-content' => $justify_content_options[ $flex_justify_content ] ), 784 804 ); 785 805 } 786 806 787 if ( $should_output_flex_alignment && ! empty( $ layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {807 if ( $should_output_flex_alignment && ! empty( $flex_vertical_alignment ) && is_string( $flex_vertical_alignment ) && array_key_exists( $flex_vertical_alignment, $vertical_alignment_options ) ) { 788 808 $layout_styles[] = array( 789 809 'selector' => $selector, 790 'declarations' => array( 'align-items' => $vertical_alignment_options[ $ layout_for_styles['verticalAlignment']] ),810 'declarations' => array( 'align-items' => $vertical_alignment_options[ $flex_vertical_alignment ] ), 791 811 ); 792 812 } … … 798 818 ); 799 819 } 800 if ( $should_output_flex_justification && ! empty( $ layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) {820 if ( $should_output_flex_justification && ! empty( $flex_justify_content ) && is_string( $flex_justify_content ) && array_key_exists( $flex_justify_content, $justify_content_options ) ) { 801 821 $layout_styles[] = array( 802 822 'selector' => $selector, 803 'declarations' => array( 'align-items' => $justify_content_options[ $ layout_for_styles['justifyContent']] ),823 'declarations' => array( 'align-items' => $justify_content_options[ $flex_justify_content ] ), 804 824 ); 805 825 } elseif ( $should_output_flex_justification ) { … … 809 829 ); 810 830 } 811 if ( $should_output_flex_alignment && ! empty( $ layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) {831 if ( $should_output_flex_alignment && ! empty( $flex_vertical_alignment ) && is_string( $flex_vertical_alignment ) && array_key_exists( $flex_vertical_alignment, $vertical_alignment_options ) ) { 812 832 $layout_styles[] = array( 813 833 'selector' => $selector, 814 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $ layout_for_styles['verticalAlignment']] ),834 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $flex_vertical_alignment ] ), 815 835 ); 816 836 } 817 837 } 818 838 } elseif ( 'grid' === $layout_type ) { 839 /* 840 * Column and row counts are whole numbers, for the same reason as the grid line 841 * numbers in wp_get_child_layout_style_rules(). 842 */ 843 $column_count_attr = $layout_for_styles['columnCount'] ?? null; 844 $column_count = is_numeric( $column_count_attr ) ? (int) $column_count_attr : null; 845 $row_count_attr = $layout_for_styles['rowCount'] ?? null; 846 $row_count = is_numeric( $row_count_attr ) ? (int) $row_count_attr : null; 847 819 848 /* 820 849 * If the gap value is an array, we use the "left" value because it represents the vertical gap, which … … 863 892 */ 864 893 $should_output_grid_columns = null === $viewport_overrides || $has_viewport_property_override( 'minimumColumnWidth' ) || $has_viewport_property_override( 'columnCount' ) || $has_viewport_property_override( 'autoFit' ); 865 $uses_gap_in_grid_columns = ! empty( $ layout_for_styles['columnCount']) && ! empty( $layout_for_styles['minimumColumnWidth'] );894 $uses_gap_in_grid_columns = ! empty( $column_count ) && ! empty( $layout_for_styles['minimumColumnWidth'] ); 866 895 if ( $has_block_gap_override && $uses_gap_in_grid_columns ) { 867 896 $should_output_grid_columns = true; 868 897 } 869 898 870 $should_output_grid_rows = ( null === $viewport_overrides || $has_viewport_property_override( 'rowCount' ) ) && ! empty( $ layout_for_styles['columnCount'] ) && ! empty( $layout_for_styles['rowCount']);899 $should_output_grid_rows = ( null === $viewport_overrides || $has_viewport_property_override( 'rowCount' ) ) && ! empty( $column_count ) && ! empty( $row_count ); 871 900 $grid_declarations = array(); 872 901 … … 877 906 $auto_placement = ! empty( $layout_for_styles['autoFit'] ) ? 'auto-fit' : 'auto-fill'; 878 907 879 if ( $should_output_grid_columns && ! empty( $ layout_for_styles['columnCount']) && ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {880 $max_value = 'max(min(' . $layout_for_styles['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $ layout_for_styles['columnCount'] . ' - 1))) /' . $layout_for_styles['columnCount']. ')';908 if ( $should_output_grid_columns && ! empty( $column_count ) && ! empty( $layout_for_styles['minimumColumnWidth'] ) ) { 909 $max_value = 'max(min(' . $layout_for_styles['minimumColumnWidth'] . ', 100%), (100% - (' . $responsive_gap_value . ' * (' . $column_count . ' - 1))) /' . $column_count . ')'; 881 910 $grid_declarations['grid-template-columns'] = 'repeat(' . $auto_placement . ', minmax(' . $max_value . ', 1fr))'; 882 } elseif ( $should_output_grid_columns && ! empty( $ layout_for_styles['columnCount']) ) {883 $grid_declarations['grid-template-columns'] = 'repeat(' . $ layout_for_styles['columnCount']. ', minmax(0, 1fr))';911 } elseif ( $should_output_grid_columns && ! empty( $column_count ) ) { 912 $grid_declarations['grid-template-columns'] = 'repeat(' . $column_count . ', minmax(0, 1fr))'; 884 913 } elseif ( $should_output_grid_columns ) { 885 914 $minimum_column_width = ! empty( $layout_for_styles['minimumColumnWidth'] ) ? $layout_for_styles['minimumColumnWidth'] : '12rem'; … … 889 918 if ( ! empty( $grid_declarations ) ) { 890 919 $base_has_container_type = empty( $base_layout['columnCount'] ) || ( ! empty( $base_layout['columnCount'] ) && ! empty( $base_layout['minimumColumnWidth'] ) ); 891 if ( empty( $ layout_for_styles['columnCount']) || ! empty( $layout_for_styles['minimumColumnWidth'] ) ) {920 if ( empty( $column_count ) || ! empty( $layout_for_styles['minimumColumnWidth'] ) ) { 892 921 if ( null === $viewport_overrides || ! $base_has_container_type ) { 893 922 $grid_declarations['container-type'] = 'inline-size'; … … 903 932 $layout_styles[] = array( 904 933 'selector' => $selector, 905 'declarations' => array( 'grid-template-rows' => 'repeat(' . $ layout_for_styles['rowCount']. ', minmax(1rem, auto))' ),934 'declarations' => array( 'grid-template-rows' => 'repeat(' . $row_count . ', minmax(1rem, auto))' ), 906 935 ); 907 936 } … … 1124 1153 * here. 1125 1154 */ 1126 if ( ! empty( $block['attrs']['layout']['orientation'] ) ) { 1127 $class_names[] = 'is-' . sanitize_title( $block['attrs']['layout']['orientation'] ); 1128 } 1129 1130 if ( ! empty( $block['attrs']['layout']['justifyContent'] ) ) { 1131 $class_names[] = 'is-content-justification-' . sanitize_title( $block['attrs']['layout']['justifyContent'] ); 1132 } 1133 1134 if ( ! empty( $block['attrs']['layout']['flexWrap'] ) && 'nowrap' === $block['attrs']['layout']['flexWrap'] ) { 1155 $orientation = $block['attrs']['layout']['orientation'] ?? null; 1156 if ( ! empty( $orientation ) && is_string( $orientation ) ) { 1157 $class_names[] = 'is-' . sanitize_title( $orientation ); 1158 } 1159 1160 $justify_content = $block['attrs']['layout']['justifyContent'] ?? null; 1161 if ( ! empty( $justify_content ) && is_string( $justify_content ) ) { 1162 $class_names[] = 'is-content-justification-' . sanitize_title( $justify_content ); 1163 } 1164 1165 $flex_wrap = $block['attrs']['layout']['flexWrap'] ?? null; 1166 if ( ! empty( $flex_wrap ) && 'nowrap' === $flex_wrap ) { 1135 1167 $class_names[] = 'is-nowrap'; 1136 1168 } 1137 1169 1138 1170 // Get classname for layout type. 1139 if ( isset( $used_layout['type'] ) ) {1171 if ( isset( $used_layout['type'] ) && is_string( $used_layout['type'] ) ) { 1140 1172 $layout_classname = $layout_definitions[ $used_layout['type'] ]['className'] ?? ''; 1141 1173 } else { … … 1464 1496 */ 1465 1497 function wp_restore_group_inner_container( $block_content, $block ) { 1466 $tag_name = $block['attrs']['tagName'] ?? 'div'; 1498 $tag_name_attr = $block['attrs']['tagName'] ?? null; 1499 $tag_name = is_string( $tag_name_attr ) ? $tag_name_attr : 'div'; 1467 1500 $group_with_inner_container_regex = sprintf( 1468 1501 '/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U', -
trunk/tests/phpunit/tests/block-supports/layout.php
r62864 r63330 1189 1189 ); 1190 1190 } 1191 1192 /** 1193 * Tests that a constrained layout with non-string contentSize/wideSize/justifyContent 1194 * values (e.g. from hand-edited, imported, or AI-generated content) does not cause a 1195 * fatal error in the explode() calls. 1196 * 1197 * @ticket 65774 1198 * @covers ::wp_get_layout_style 1199 */ 1200 public function test_wp_get_layout_style_with_non_string_constrained_sizes() { 1201 $layout_styles = wp_get_layout_style( 1202 '.wp-layout', 1203 array( 1204 'type' => 'constrained', 1205 'contentSize' => array( '800px' ), 1206 'wideSize' => array( '1200px' ), 1207 'justifyContent' => array( 'center' ), 1208 ) 1209 ); 1210 1211 $this->assertIsString( $layout_styles, 'Constrained layout should not fatal when sizes are not strings.' ); 1212 $this->assertStringNotContainsString( 'Array', $layout_styles, 'A non-string size value should not leak into the output.' ); 1213 } 1214 1215 /** 1216 * Tests that a flex layout with non-string justifyContent/verticalAlignment values 1217 * does not cause a fatal error in the array_key_exists() calls. 1218 * 1219 * @ticket 65774 1220 * @covers ::wp_get_layout_style 1221 */ 1222 public function test_wp_get_layout_style_with_non_string_flex_alignment() { 1223 $layout_styles = wp_get_layout_style( 1224 '.wp-layout', 1225 array( 1226 'type' => 'flex', 1227 'orientation' => 'horizontal', 1228 'justifyContent' => array( 'right' ), 1229 'verticalAlignment' => array( 'center' ), 1230 ) 1231 ); 1232 1233 $this->assertIsString( $layout_styles, 'Flex layout should not fatal when alignment values are not strings.' ); 1234 } 1235 1236 /** 1237 * Tests that a responsive grid child with a non-string parent minimumColumnWidth 1238 * does not cause a fatal error in the explode() call. 1239 * 1240 * @ticket 65774 1241 * @covers ::wp_get_child_layout_style_rules 1242 */ 1243 public function test_wp_get_child_layout_style_rules_with_non_string_minimum_column_width() { 1244 $actual_output = wp_get_child_layout_style_rules( 1245 '.wp-container-content-test', 1246 array( 'columnSpan' => '2' ), 1247 array( 'minimumColumnWidth' => array( '12rem' ) ), 1248 null 1249 ); 1250 1251 $this->assertIsArray( $actual_output, 'Child layout rules should not fatal when minimumColumnWidth is not a string.' ); 1252 } 1253 1254 /** 1255 * Tests that layout classname generation does not fatal when the layout type, 1256 * orientation, or justifyContent attributes are not strings. 1257 * 1258 * @ticket 65774 1259 * @covers ::wp_render_layout_support_flag 1260 */ 1261 public function test_layout_support_flag_with_non_string_layout_values() { 1262 $block_content = '<div class="wp-block-group"></div>'; 1263 $block = array( 1264 'blockName' => 'core/group', 1265 'attrs' => array( 1266 'layout' => array( 1267 'type' => array( 'constrained' ), 1268 'orientation' => array( 'horizontal' ), 1269 'justifyContent' => array( 'center' ), 1270 ), 1271 ), 1272 ); 1273 1274 $this->assertIsString( 1275 wp_render_layout_support_flag( $block_content, $block ), 1276 'Layout support should not fatal when layout values are not strings.' 1277 ); 1278 } 1279 1280 /** 1281 * Tests that restoring the group inner container does not fatal when the tagName 1282 * attribute is not a string (which would break the preg_quote() calls). 1283 * 1284 * @ticket 65774 1285 * @covers ::wp_restore_group_inner_container 1286 */ 1287 public function test_restore_group_inner_container_with_non_string_tag_name() { 1288 // The "default" theme doesn't have theme.json support, so the preg_quote() path runs. 1289 switch_theme( 'default' ); 1290 $block_content = '<div class="wp-block-group"><p>Test</p></div>'; 1291 $block = array( 1292 'blockName' => 'core/group', 1293 'attrs' => array( 1294 'tagName' => array( 'div' ), 1295 ), 1296 ); 1297 1298 $this->assertIsString( 1299 wp_restore_group_inner_container( $block_content, $block ), 1300 'Group inner container restore should not fatal when tagName is not a string.' 1301 ); 1302 } 1303 1304 /** 1305 * Tests that non-numeric grid placement values are dropped rather than being 1306 * interpolated into the `grid-column` and `grid-row` declarations. 1307 * 1308 * @ticket 65774 1309 * @covers ::wp_get_child_layout_style_rules 1310 */ 1311 public function test_get_child_layout_style_rules_with_non_numeric_grid_placement() { 1312 $actual_output = wp_get_child_layout_style_rules( 1313 '.wp-container-content-test', 1314 array( 1315 'columnStart' => array( 2 ), 1316 'columnSpan' => array( 3 ), 1317 'rowStart' => array( 1 ), 1318 'rowSpan' => array( 2 ), 1319 ), 1320 array(), 1321 null 1322 ); 1323 1324 $this->assertSame( 1325 array(), 1326 $actual_output, 1327 'Non-numeric grid placement values should not produce any child layout rules.' 1328 ); 1329 } 1330 1331 /** 1332 * Tests that grid placement values saved as numeric strings (WordPress 6.3 to 6.6) 1333 * produce the same declarations as numbers. 1334 * 1335 * @ticket 65774 1336 * @covers ::wp_get_child_layout_style_rules 1337 */ 1338 public function test_get_child_layout_style_rules_with_numeric_string_grid_placement() { 1339 $expected_output = array( 1340 array( 1341 'selector' => '.wp-container-content-test', 1342 'declarations' => array( 1343 'grid-column' => '2 / span 3', 1344 'grid-row' => '1 / span 2', 1345 ), 1346 ), 1347 ); 1348 1349 $actual_output = wp_get_child_layout_style_rules( 1350 '.wp-container-content-test', 1351 array( 1352 'columnStart' => '2', 1353 'columnSpan' => '3', 1354 'rowStart' => '1', 1355 'rowSpan' => '2', 1356 ), 1357 array( 'columnCount' => '3' ), 1358 null 1359 ); 1360 1361 $this->assertSame( $expected_output, $actual_output ); 1362 } 1363 1364 /** 1365 * Tests that non-numeric grid counts are treated as absent instead of leaking into the 1366 * CSS. The rowCount case keeps columnCount valid, because the row track rule is only 1367 * reached when there is a column count. 1368 * 1369 * @dataProvider data_get_layout_style_with_non_numeric_grid_counts 1370 * 1371 * @ticket 65774 1372 * @covers ::wp_get_layout_style 1373 * 1374 * @param array $layout Grid layout values. 1375 * @param string $expected_output The expected output. 1376 */ 1377 public function test_get_layout_style_with_non_numeric_grid_counts( $layout, $expected_output ) { 1378 $this->assertSame( $expected_output, wp_get_layout_style( '.wp-layout', $layout ) ); 1379 } 1380 1381 /** 1382 * Data provider for test_get_layout_style_with_non_numeric_grid_counts(). 1383 * 1384 * @return array 1385 */ 1386 public function data_get_layout_style_with_non_numeric_grid_counts() { 1387 return array( 1388 'non-numeric columnCount falls back to the responsive default' => array( 1389 'layout' => array( 1390 'type' => 'grid', 1391 'columnCount' => array( 3 ), 1392 ), 1393 'expected_output' => '.wp-layout{grid-template-columns:repeat(auto-fill, minmax(min(12rem, 100%), 1fr));container-type:inline-size;}', 1394 ), 1395 'non-numeric rowCount drops the row track rule' => array( 1396 'layout' => array( 1397 'type' => 'grid', 1398 'columnCount' => 3, 1399 'rowCount' => array( 2 ), 1400 ), 1401 'expected_output' => '.wp-layout{grid-template-columns:repeat(3, minmax(0, 1fr));}', 1402 ), 1403 ); 1404 } 1405 1406 /** 1407 * Tests that grid counts saved as numeric strings (WordPress 6.3 to 6.6) produce the 1408 * same CSS as numbers. 1409 * 1410 * @ticket 65774 1411 * @covers ::wp_get_layout_style 1412 */ 1413 public function test_get_layout_style_with_numeric_string_grid_counts() { 1414 $layout_styles = wp_get_layout_style( 1415 '.wp-layout', 1416 array( 1417 'type' => 'grid', 1418 'columnCount' => '3', 1419 'rowCount' => '2', 1420 ) 1421 ); 1422 1423 $this->assertSame( 1424 '.wp-layout{grid-template-columns:repeat(3, minmax(0, 1fr));grid-template-rows:repeat(2, minmax(1rem, auto));}', 1425 $layout_styles 1426 ); 1427 } 1191 1428 }
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)