diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index b4cfb0f110caa9..1b460219d5fda6 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -95,7 +95,7 @@ Display content in multiple columns, with blocks added to each column. - **Name:** core/columns - **Category:** design -- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), spacing (blockGap, margin, padding), ~~html~~ +- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), spacing (margin, padding), ~~html~~ - **Attributes:** isStackedOnMobile, verticalAlignment ## Comment Author Avatar @@ -365,7 +365,7 @@ A collection of blocks that allow visitors to get around your site. - **Name:** core/navigation - **Category:** theme -- **Supports:** align (full, wide), anchor, inserter, spacing (blockGap, units), typography (fontSize, lineHeight), ~~html~~ +- **Supports:** align (full, wide), anchor, inserter, spacing (units), typography (fontSize, lineHeight), ~~html~~ - **Attributes:** __unstableLocation, backgroundColor, customBackgroundColor, customOverlayBackgroundColor, customOverlayTextColor, customTextColor, openSubmenusOnClick, overlayBackgroundColor, overlayMenu, overlayTextColor, ref, rgbBackgroundColor, rgbTextColor, showSubmenuIcon, textColor ## Navigation Area diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index fd2a84c84ea5f7..5ed5f2f16ae754 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -31,10 +31,11 @@ function gutenberg_register_layout_support( $block_type ) { * @param string $selector CSS selector. * @param array $layout Layout object. The one that is passed has already checked the existance of default block layout. * @param boolean $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 gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false ) { +function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support = false, $gap_value = null ) { $layout_type = isset( $layout['type'] ) ? $layout['type'] : 'default'; $style = ''; @@ -65,8 +66,9 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $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'; @@ -89,7 +91,8 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support $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;'; } @@ -145,8 +148,13 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $used_layout = $default_layout; } - $id = uniqid(); - $style = gutenberg_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/lib/block-supports/spacing.php b/lib/block-supports/spacing.php index ca7b77f43864b9..78f5b59f90fb04 100644 --- a/lib/block-supports/spacing.php +++ b/lib/block-supports/spacing.php @@ -90,57 +90,6 @@ function gutenberg_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. - * - * @param string $block_content Rendered block content. - * @param array $block Block object. - * @return string Filtered block content. - */ -function gutenberg_render_spacing_gap_support( $block_content, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $has_gap_support = gutenberg_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', @@ -149,5 +98,3 @@ function gutenberg_render_spacing_gap_support( $block_content, $block ) { 'apply' => 'gutenberg_apply_spacing_support', ) ); - -add_filter( 'render_block', 'gutenberg_render_spacing_gap_support', 10, 2 ); diff --git a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php index c223b9ac96f414..83d9508fb59565 100644 --- a/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-5.9/class-wp-theme-json-gutenberg.php @@ -285,7 +285,7 @@ class WP_Theme_JSON_Gutenberg { 'spacing' => array( 'margin' => null, 'padding' => null, - 'blockGap' => null, + 'blockGap' => 'top', ), 'typography' => array( 'fontFamily' => null, @@ -428,17 +428,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/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index a1f17c525c0c6a..b2cfde0b5e06cc 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -215,6 +215,7 @@ export const withLayoutStyles = createHigherOrderComponent( , element ) } diff --git a/packages/block-editor/src/hooks/style.js b/packages/block-editor/src/hooks/style.js index 8b0fbd6820e27b..5882d5275937be 100644 --- a/packages/block-editor/src/hooks/style.js +++ b/packages/block-editor/src/hooks/style.js @@ -74,6 +74,7 @@ function compileStyleValue( uncompiledValue ) { * @return {Object} Flattened CSS variables declaration. */ export function getInlineStyles( styles = {} ) { + const ignoredStyles = [ 'spacing.blockGap' ]; const output = {}; Object.keys( STYLE_PROPERTY ).forEach( ( propKey ) => { const path = STYLE_PROPERTY[ propKey ].value; @@ -93,7 +94,7 @@ export function getInlineStyles( styles = {} ) { output[ name ] = compileStyleValue( value ); } } ); - } else { + } else if ( ! ignoredStyles.includes( path.join( '.' ) ) ) { output[ propKey ] = compileStyleValue( get( styles, path ) ); } } diff --git a/packages/block-editor/src/hooks/test/style.js b/packages/block-editor/src/hooks/test/style.js index e8c3264eeba6b1..ac2b0690dd498e 100644 --- a/packages/block-editor/src/hooks/test/style.js +++ b/packages/block-editor/src/hooks/test/style.js @@ -30,7 +30,6 @@ describe( 'getInlineStyles', () => { }, } ) ).toEqual( { - '--wp--style--block-gap': '1em', backgroundColor: 'black', borderColor: '#21759b', borderRadius: '10px', @@ -104,7 +103,6 @@ describe( 'getInlineStyles', () => { }, } ) ).toEqual( { - '--wp--style--block-gap': '1em', margin: '10px', padding: '20px', } ); diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index 84125108515ad3..092da1e036a10f 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -84,10 +84,12 @@ export default { ); }, - save: function FlexLayoutStyle( { selector, layout } ) { + save: function FlexLayoutStyle( { selector, layout, style } ) { const { orientation = 'horizontal' } = layout; const blockGapSupport = useSetting( 'spacing.blockGap' ); const hasBlockGapStylesSupport = blockGapSupport !== null; + const blockGapValue = + style?.spacing?.blockGap ?? 'var( --wp--style--block-gap, 0.5em )'; const justifyContent = justifyContentMap[ layout.justifyContent ] || justifyContentMap.left; @@ -110,11 +112,7 @@ export default { ; + return ; }, getOrientation() { return 'vertical'; diff --git a/packages/block-library/src/columns/block.json b/packages/block-library/src/columns/block.json index dfbfb24e2251b0..c556d90a0d2d25 100644 --- a/packages/block-library/src/columns/block.json +++ b/packages/block-library/src/columns/block.json @@ -24,11 +24,10 @@ "link": true }, "spacing": { - "blockGap": true, "margin": [ "top", "bottom" ], "padding": true, "__experimentalDefaultControls": { - "blockGap": true + "padding": true } } }, diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index 96a049e9f0bad0..6759cde277fa27 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -92,11 +92,7 @@ } }, "spacing": { - "blockGap": true, - "units": [ "px", "em", "rem", "vh", "vw" ], - "__experimentalDefaultControls": { - "blockGap": true - } + "units": [ "px", "em", "rem", "vh", "vw" ] }, "__experimentalLayout": { "allowSwitching": false, diff --git a/phpunit/block-supports/spacing-test.php b/phpunit/block-supports/spacing-test.php deleted file mode 100644 index 1038cfcb5ca97d..00000000000000 --- a/phpunit/block-supports/spacing-test.php +++ /dev/null @@ -1,142 +0,0 @@ -Test'; - private $test_gap_block_value = array(); - private $test_gap_block_args = array(); - - function setUp() { - parent::setUp(); - - $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 tearDown() { - unregister_block_type( 'test/test-block' ); - - parent::tearDown(); - } - - function test_spacing_gap_block_support_renders_block_inline_style() { - register_block_type( 'test/test-block', $this->test_gap_block_args ); - $render_output = gutenberg_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 = gutenberg_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 = gutenberg_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 = gutenberg_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 = gutenberg_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 = gutenberg_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 = gutenberg_render_spacing_gap_support( - $this->sample_block_content, - $this->test_gap_block_value - ); - - $this->assertEquals( - $this->sample_block_content, - $render_output - ); - } -} diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 2fe7077420208f..229fed0bc6f3eb 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -398,18 +398,11 @@ 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->assertEquals( $expected, $theme_json->get_stylesheet() ); $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } @@ -2248,4 +2241,44 @@ function test_get_editor_settings_custom_units_can_be_filtered() { $this->assertEqualSetsWithIndex( $expected, $actual['settings']['spacing'] ); } + function test_sanitization() { + $theme_json = new WP_Theme_JSON_Gutenberg( + 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 ); + } + }