From 9d068853650bf72b7984781139a95c32a3a56cdf Mon Sep 17 00:00:00 2001 From: im3dabasia Date: Fri, 24 Jul 2026 11:51:08 +0530 Subject: [PATCH] feat: Add backport PR changes for GB 80501 --- src/wp-includes/block-supports/layout.php | 53 +++++---- tests/phpunit/tests/block-supports/layout.php | 107 ++++++++++++++++++ 2 files changed, 140 insertions(+), 20 deletions(-) diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index 896f6d25543cf..83b6b1c7f269f 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -190,8 +190,9 @@ function wp_get_child_layout_style_rules( $selector, $child_layout, $parent_layo ); } - $minimum_column_width = $parent_layout['minimumColumnWidth'] ?? null; - $column_count = $parent_layout['columnCount'] ?? null; + $minimum_column_width_attr = $parent_layout['minimumColumnWidth'] ?? null; + $minimum_column_width = is_string( $minimum_column_width_attr ) ? $minimum_column_width_attr : null; + $column_count = $parent_layout['columnCount'] ?? null; /* * If columnSpan or columnStart is set, and the parent grid is responsive, i.e. if it has a minimumColumnWidth set, @@ -548,9 +549,14 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false } } } elseif ( 'constrained' === $layout_type ) { - $content_size = $layout_for_styles['contentSize'] ?? ''; - $wide_size = $layout_for_styles['wideSize'] ?? ''; - $justify_content = $layout_for_styles['justifyContent'] ?? 'center'; + // The schemas and editor UI only produce strings here, so treat a non-string + // value as absent rather than casting it — it couldn't render as valid CSS anyway. + $content_size_attr = $layout_for_styles['contentSize'] ?? null; + $content_size = is_string( $content_size_attr ) ? $content_size_attr : ''; + $wide_size_attr = $layout_for_styles['wideSize'] ?? null; + $wide_size = is_string( $wide_size_attr ) ? $wide_size_attr : ''; + $justify_content_attr = $layout_for_styles['justifyContent'] ?? null; + $justify_content = is_string( $justify_content_attr ) ? $justify_content_attr : 'center'; // Check if viewport-specific ("override") values exist. Null values are valid and mean the user cleared a value inherited from the default viewport. $has_justify_content_override = null !== $viewport_overrides && $has_viewport_property_override( 'justifyContent' ); @@ -771,23 +777,26 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false } } + $flex_justify_content = $layout_for_styles['justifyContent'] ?? null; + $flex_vertical_alignment = $layout_for_styles['verticalAlignment'] ?? null; + if ( 'horizontal' === $layout_orientation ) { /* * Add this style only if is not empty for backwards compatibility, * since we intend to convert blocks that had flex layout implemented * by custom css. */ - if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) { + if ( $should_output_flex_justification && ! empty( $flex_justify_content ) && is_string( $flex_justify_content ) && array_key_exists( $flex_justify_content, $justify_content_options ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'justify-content' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ), + 'declarations' => array( 'justify-content' => $justify_content_options[ $flex_justify_content ] ), ); } - if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) { + if ( $should_output_flex_alignment && ! empty( $flex_vertical_alignment ) && is_string( $flex_vertical_alignment ) && array_key_exists( $flex_vertical_alignment, $vertical_alignment_options ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'align-items' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ), + 'declarations' => array( 'align-items' => $vertical_alignment_options[ $flex_vertical_alignment ] ), ); } } else { @@ -797,10 +806,10 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false 'declarations' => array( 'flex-direction' => 'column' ), ); } - if ( $should_output_flex_justification && ! empty( $layout_for_styles['justifyContent'] ) && array_key_exists( $layout_for_styles['justifyContent'], $justify_content_options ) ) { + if ( $should_output_flex_justification && ! empty( $flex_justify_content ) && is_string( $flex_justify_content ) && array_key_exists( $flex_justify_content, $justify_content_options ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'align-items' => $justify_content_options[ $layout_for_styles['justifyContent'] ] ), + 'declarations' => array( 'align-items' => $justify_content_options[ $flex_justify_content ] ), ); } elseif ( $should_output_flex_justification ) { $layout_styles[] = array( @@ -808,10 +817,10 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false 'declarations' => array( 'align-items' => 'flex-start' ), ); } - if ( $should_output_flex_alignment && ! empty( $layout_for_styles['verticalAlignment'] ) && array_key_exists( $layout_for_styles['verticalAlignment'], $vertical_alignment_options ) ) { + if ( $should_output_flex_alignment && ! empty( $flex_vertical_alignment ) && is_string( $flex_vertical_alignment ) && array_key_exists( $flex_vertical_alignment, $vertical_alignment_options ) ) { $layout_styles[] = array( 'selector' => $selector, - 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $layout_for_styles['verticalAlignment'] ] ), + 'declarations' => array( 'justify-content' => $vertical_alignment_options[ $flex_vertical_alignment ] ), ); } } @@ -1108,20 +1117,23 @@ function wp_render_layout_support_flag( $block_content, $block ) { * not intended to provide an extended set of classes to match all block layout attributes * here. */ - if ( ! empty( $block['attrs']['layout']['orientation'] ) ) { - $class_names[] = 'is-' . sanitize_title( $block['attrs']['layout']['orientation'] ); + $orientation = $block['attrs']['layout']['orientation'] ?? null; + if ( ! empty( $orientation ) && is_string( $orientation ) ) { + $class_names[] = 'is-' . sanitize_title( $orientation ); } - if ( ! empty( $block['attrs']['layout']['justifyContent'] ) ) { - $class_names[] = 'is-content-justification-' . sanitize_title( $block['attrs']['layout']['justifyContent'] ); + $justify_content = $block['attrs']['layout']['justifyContent'] ?? null; + if ( ! empty( $justify_content ) && is_string( $justify_content ) ) { + $class_names[] = 'is-content-justification-' . sanitize_title( $justify_content ); } - if ( ! empty( $block['attrs']['layout']['flexWrap'] ) && 'nowrap' === $block['attrs']['layout']['flexWrap'] ) { + $flex_wrap = $block['attrs']['layout']['flexWrap'] ?? null; + if ( ! empty( $flex_wrap ) && 'nowrap' === $flex_wrap ) { $class_names[] = 'is-nowrap'; } // Get classname for layout type. - if ( isset( $used_layout['type'] ) ) { + if ( isset( $used_layout['type'] ) && is_string( $used_layout['type'] ) ) { $layout_classname = $layout_definitions[ $used_layout['type'] ]['className'] ?? ''; } else { $layout_classname = $layout_definitions['default']['className'] ?? ''; @@ -1448,7 +1460,8 @@ function wp_add_parent_layout_to_parsed_block( $parsed_block, $source_block, $pa * @return string Filtered block content. */ function wp_restore_group_inner_container( $block_content, $block ) { - $tag_name = $block['attrs']['tagName'] ?? 'div'; + $tag_name_attr = $block['attrs']['tagName'] ?? null; + $tag_name = is_string( $tag_name_attr ) ? $tag_name_attr : 'div'; $group_with_inner_container_regex = sprintf( '/(^\s*<%1$s\b[^>]*wp-block-group(\s|")[^>]*>)(\s*]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U', preg_quote( $tag_name, '/' ) diff --git a/tests/phpunit/tests/block-supports/layout.php b/tests/phpunit/tests/block-supports/layout.php index c9a8dd34b1371..64c3de103b415 100644 --- a/tests/phpunit/tests/block-supports/layout.php +++ b/tests/phpunit/tests/block-supports/layout.php @@ -1124,4 +1124,111 @@ public function test_layout_support_flag_with_non_string_class_name() { 'Layout support should render the expected markup when className is not a string' ); } + + /** + * Tests that a constrained layout with non-string contentSize/wideSize/justifyContent + * values (e.g. from hand-edited, imported, or AI-generated content) does not cause a + * fatal error in the explode() calls. + * + * @covers ::wp_get_layout_style + */ + public function test_wp_get_layout_style_with_non_string_constrained_sizes() { + $layout_styles = wp_get_layout_style( + '.wp-layout', + array( + 'type' => 'constrained', + 'contentSize' => array( '800px' ), + 'wideSize' => array( '1200px' ), + 'justifyContent' => array( 'center' ), + ) + ); + + $this->assertIsString( $layout_styles, 'Constrained layout should not fatal when sizes are not strings.' ); + $this->assertStringNotContainsString( 'Array', $layout_styles, 'A non-string size value should not leak into the output.' ); + } + + /** + * Tests that a flex layout with non-string justifyContent/verticalAlignment values + * does not cause a fatal error in the array_key_exists() calls. + * + * @covers ::wp_get_layout_style + */ + public function test_wp_get_layout_style_with_non_string_flex_alignment() { + $layout_styles = wp_get_layout_style( + '.wp-layout', + array( + 'type' => 'flex', + 'orientation' => 'horizontal', + 'justifyContent' => array( 'right' ), + 'verticalAlignment' => array( 'center' ), + ) + ); + + $this->assertIsString( $layout_styles, 'Flex layout should not fatal when alignment values are not strings.' ); + } + + /** + * Tests that a responsive grid child with a non-string parent minimumColumnWidth + * does not cause a fatal error in the explode() call. + * + * @covers ::wp_get_child_layout_style_rules + */ + public function test_wp_get_child_layout_style_rules_with_non_string_minimum_column_width() { + $actual_output = wp_get_child_layout_style_rules( + '.wp-container-content-test', + array( 'columnSpan' => '2' ), + array( 'minimumColumnWidth' => array( '12rem' ) ), + null + ); + + $this->assertIsArray( $actual_output, 'Child layout rules should not fatal when minimumColumnWidth is not a string.' ); + } + + /** + * Tests that layout classname generation does not fatal when the layout type, + * orientation, or justifyContent attributes are not strings. + * + * @covers ::wp_render_layout_support_flag + */ + public function test_layout_support_flag_with_non_string_layout_values() { + $block_content = '
'; + $block = array( + 'blockName' => 'core/group', + 'attrs' => array( + 'layout' => array( + 'type' => array( 'constrained' ), + 'orientation' => array( 'horizontal' ), + 'justifyContent' => array( 'center' ), + ), + ), + ); + + $this->assertIsString( + wp_render_layout_support_flag( $block_content, $block ), + 'Layout support should not fatal when layout values are not strings.' + ); + } + + /** + * Tests that restoring the group inner container does not fatal when the tagName + * attribute is not a string (which would break the preg_quote() calls). + * + * @covers ::wp_restore_group_inner_container + */ + public function test_restore_group_inner_container_with_non_string_tag_name() { + // The "default" theme doesn't have theme.json support, so the preg_quote() path runs. + switch_theme( 'default' ); + $block_content = '

Test

'; + $block = array( + 'blockName' => 'core/group', + 'attrs' => array( + 'tagName' => array( 'div' ), + ), + ); + + $this->assertIsString( + wp_restore_group_inner_container( $block_content, $block ), + 'Group inner container restore should not fatal when tagName is not a string.' + ); + } }