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
53 changes: 33 additions & 20 deletions src/wp-includes/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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' );
Expand Down Expand Up @@ -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 {
Expand All @@ -797,21 +806,21 @@ 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(
'selector' => $selector,
'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 ] ),
);
}
}
Expand Down Expand Up @@ -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'] ?? '';
Expand Down Expand Up @@ -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*<div\b[^>]*wp-block-group__inner-container(\s|")[^>]*>)((.|\S|\s)*)/U',
preg_quote( $tag_name, '/' )
Expand Down
107 changes: 107 additions & 0 deletions tests/phpunit/tests/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<div class="wp-block-group"></div>';
$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 = '<div class="wp-block-group"><p>Test</p></div>';
$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.'
);
}
}
Loading