diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index f318e7c492da7..2e14013fdd11a 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -39,9 +39,10 @@ function wp_register_layout_support( $block_type ) { * @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 Whether the theme has support for the block gap. + * @param string $gap_value The block gap value to apply. * @return string CSS style. */ -function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false ) { +function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null ) { $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; $style = ''; @@ -72,8 +73,9 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false $style .= "$selector .alignleft { float: left; margin-right: 2em; }"; $style .= "$selector .alignright { float: right; margin-left: 2em; }"; if ( $has_block_gap_support ) { - $style .= "$selector > * { margin-top: 0; margin-bottom: 0; }"; - $style .= "$selector > * + * { margin-top: var( --wp--style--block-gap ); margin-bottom: 0; }"; + $gap_style = $gap_value ? $gap_value : 'var( --wp--style--block-gap )'; + $style .= "$selector > * { margin-top: 0; margin-bottom: 0; }"; + $style .= "$selector > * + * { margin-top: $gap_style; margin-bottom: 0; }"; } } elseif ( 'flex' === $layout_type ) { $layout_orientation = isset( $layout['orientation'] ) ? $layout['orientation'] : 'horizontal'; @@ -96,7 +98,8 @@ function wp_get_layout_style( $selector, $layout, $has_block_gap_support = false $style = "$selector {"; $style .= 'display: flex;'; if ( $has_block_gap_support ) { - $style .= 'gap: var( --wp--style--block-gap, 0.5em );'; + $gap_style = $gap_value ? $gap_value : 'var( --wp--style--block-gap, 0.5em )'; + $style .= "gap: $gap_style;"; } else { $style .= 'gap: 0.5em;'; } @@ -156,8 +159,13 @@ function wp_render_layout_support_flag( $block_content, $block ) { $used_layout = $default_layout; } - $id = uniqid(); - $style = wp_get_layout_style( ".wp-container-$id", $used_layout, $has_block_gap_support ); + $id = uniqid(); + $gap_value = _wp_array_get( $block, array( 'attrs', 'style', 'spacing', 'blockGap' ) ); + // Skip if gap value contains unsupported characters. + // Regex for CSS value borrowed from `safecss_filter_attr`, and used here + // because we only want to match against the value, not the CSS attribute. + $gap_value = preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value; + $style = gutenberg_get_layout_style( ".wp-container-$id", $used_layout, $has_block_gap_support, $gap_value ); // This assumes the hook only applies to blocks with a single wrapper. // I think this is a reasonable limitation for that particular hook. $content = preg_replace( diff --git a/src/wp-includes/block-supports/spacing.php b/src/wp-includes/block-supports/spacing.php index 86def2fa8efe0..a65bae2f087c8 100644 --- a/src/wp-includes/block-supports/spacing.php +++ b/src/wp-includes/block-supports/spacing.php @@ -95,59 +95,6 @@ function wp_skip_spacing_serialization( $block_type ) { $spacing_support['__experimentalSkipSerialization']; } -/** - * Renders the spacing gap support to the block wrapper, to ensure - * that the CSS variable is rendered in all environments. - * - * @since 5.9.0 - * @access private - * - * @param string $block_content Rendered block content. - * @param array $block Block object. - * @return string Filtered block content. - */ -function wp_render_spacing_gap_support( $block_content, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $has_gap_support = block_has_support( $block_type, array( 'spacing', 'blockGap' ), false ); - if ( ! $has_gap_support || ! isset( $block['attrs']['style']['spacing']['blockGap'] ) ) { - return $block_content; - } - - $gap_value = $block['attrs']['style']['spacing']['blockGap']; - - // Skip if gap value contains unsupported characters. - // Regex for CSS value borrowed from `safecss_filter_attr`, and used here - // because we only want to match against the value, not the CSS attribute. - if ( preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ) { - return $block_content; - } - - $style = sprintf( - '--wp--style--block-gap: %s', - esc_attr( $gap_value ) - ); - - // Attempt to update an existing style attribute on the wrapper element. - $injected_style = preg_replace( - '/^([^>.]+?)(' . preg_quote( 'style="', '/' ) . ')(?=.+?>)/', - '$1$2' . $style . '; ', - $block_content, - 1 - ); - - // If there is no existing style attribute, add one to the wrapper element. - if ( $injected_style === $block_content ) { - $injected_style = preg_replace( - '/<([a-zA-Z0-9]+)([ >])/', - '<$1 style="' . $style . '"$2', - $block_content, - 1 - ); - }; - - return $injected_style; -} - // Register the block support. WP_Block_Supports::get_instance()->register( 'spacing', @@ -156,5 +103,3 @@ function wp_render_spacing_gap_support( $block_content, $block ) { 'apply' => 'wp_apply_spacing_support', ) ); - -add_filter( 'render_block', 'wp_render_spacing_gap_support', 10, 2 ); diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index c3b1d055875d3..5c643cf138a67 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -317,7 +317,7 @@ class WP_Theme_JSON { 'spacing' => array( 'margin' => null, 'padding' => null, - 'blockGap' => null, + 'blockGap' => 'top', ), 'typography' => array( 'fontFamily' => null, @@ -472,17 +472,28 @@ private static function sanitize( $input, $valid_block_names, $valid_element_nam $output = array_intersect_key( $input, array_flip( self::VALID_TOP_LEVEL_KEYS ) ); + // Some styles are only meant to be available at the top-level (e.g.: blockGap), + // hence, the schema for blocks & elements should not have them. + $styles_non_top_level = self::VALID_STYLES; + foreach ( array_keys( $styles_non_top_level ) as $section ) { + foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { + if ( 'top' === $styles_non_top_level[ $section ][ $prop ] ) { + unset( $styles_non_top_level[ $section ][ $prop ] ); + } + } + } + // Build the schema based on valid block & element names. $schema = array(); $schema_styles_elements = array(); foreach ( $valid_element_names as $element ) { - $schema_styles_elements[ $element ] = self::VALID_STYLES; + $schema_styles_elements[ $element ] = $styles_non_top_level; } $schema_styles_blocks = array(); $schema_settings_blocks = array(); foreach ( $valid_block_names as $block ) { $schema_settings_blocks[ $block ] = self::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = self::VALID_STYLES; + $schema_styles_blocks[ $block ] = $styles_non_top_level; $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; } $schema['styles'] = self::VALID_STYLES; diff --git a/tests/phpunit/tests/block-supports/spacing.php b/tests/phpunit/tests/block-supports/spacing.php deleted file mode 100644 index 9215716708e32..0000000000000 --- a/tests/phpunit/tests/block-supports/spacing.php +++ /dev/null @@ -1,138 +0,0 @@ -Test'; - private $test_gap_block_value = array(); - private $test_gap_block_args = array(); - - function set_up() { - parent::set_up(); - - $this->test_gap_block_value = array( - 'blockName' => 'test/test-block', - 'attrs' => array( - 'style' => array( - 'spacing' => array( - 'blockGap' => '3em', - ), - ), - ), - ); - - $this->test_gap_block_args = array( - 'api_version' => 2, - 'supports' => array( - 'spacing' => array( - 'blockGap' => true, - ), - ), - ); - } - - function tear_down() { - unregister_block_type( 'test/test-block' ); - - parent::tear_down(); - } - - function test_spacing_gap_block_support_renders_block_inline_style() { - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - $this->sample_block_content, - $this->test_gap_block_value - ); - - $this->assertSame( - '
Test
', - $render_output - ); - } - - function test_spacing_gap_block_support_renders_block_inline_style_with_inner_tag() { - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - '

Test

', - $this->test_gap_block_value - ); - - $this->assertSame( - '

Test

', - $render_output - ); - } - - function test_spacing_gap_block_support_renders_block_inline_style_with_no_other_attributes() { - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - '

Test

', - $this->test_gap_block_value - ); - - $this->assertSame( - '

Test

', - $render_output - ); - } - - function test_spacing_gap_block_support_renders_appended_block_inline_style() { - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - '

Test

', - $this->test_gap_block_value - ); - - $this->assertSame( - '

Test

', - $render_output - ); - } - - function test_spacing_gap_block_support_does_not_render_style_when_support_is_false() { - $this->test_gap_block_args['supports']['spacing']['blockGap'] = false; - - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - $this->sample_block_content, - $this->test_gap_block_value - ); - - $this->assertEquals( - $this->sample_block_content, - $render_output - ); - } - - function test_spacing_gap_block_support_does_not_render_style_when_gap_is_null() { - $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = null; - $this->test_gap_block_args['supports']['spacing']['blockGap'] = true; - - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - $this->sample_block_content, - $this->test_gap_block_value - ); - - $this->assertEquals( - $this->sample_block_content, - $render_output - ); - } - - function test_spacing_gap_block_support_does_not_render_style_when_gap_is_illegal_value() { - $this->test_gap_block_value['attrs']['style']['spacing']['blockGap'] = '" javascript="alert("hello");'; - $this->test_gap_block_args['supports']['spacing']['blockGap'] = true; - - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = wp_render_spacing_gap_support( - $this->sample_block_content, - $this->test_gap_block_value - ); - - $this->assertEquals( - $this->sample_block_content, - $render_output - ); - } -} diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index eefa1ca26a7b9..6d92c3741b15a 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -419,18 +419,11 @@ public function test_get_stylesheet_renders_enabled_protected_properties() { 'spacing' => array( 'blockGap' => '1em', ), - 'blocks' => array( - 'core/columns' => array( - 'spacing' => array( - 'blockGap' => '24px', - ), - ), - ), ), ) ); - $expected = 'body { margin: 0; }body{--wp--style--block-gap: 1em;}.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; }.wp-site-blocks > * { margin-top: 0; margin-bottom: 0; }.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); }.wp-block-columns{--wp--style--block-gap: 24px;}'; + $expected = 'body { margin: 0; }body{--wp--style--block-gap: 1em;}.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; }.wp-site-blocks > * { margin-top: 0; margin-bottom: 0; }.wp-site-blocks > * + * { margin-top: var( --wp--style--block-gap ); }'; $this->assertSame( $expected, $theme_json->get_stylesheet() ); $this->assertSame( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -2348,4 +2341,47 @@ public function test_get_editor_settings_custom_units_can_be_filtered() { $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); } + /** + * @ticket 54487 + */ + public function test_sanitization() { + $theme_json = new WP_Theme_JSON( + array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'blockGap' => 'valid value', + ), + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'margin' => 'valid value', + 'blockGap' => 'invalid value', + ), + ), + ), + ), + ) + ); + + $actual = $theme_json->get_raw_data(); + $expected = array( + 'version' => 2, + 'styles' => array( + 'spacing' => array( + 'blockGap' => 'valid value', + ), + 'blocks' => array( + 'core/group' => array( + 'spacing' => array( + 'margin' => 'valid value', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + }