diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 2c62af810d969..7c852a9dbff81 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -173,6 +173,17 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) { $class_name = wp_unique_id( 'wp-container-' ); $gap_value = _wp_array_get( $block, array( 'attrs', 'style', 'spacing', 'blockGap' ) ); + + // If there is no block-level value for blockGap, + // but a global styles value available for blockGap, + // use the latter. + if ( empty( $gap_value ) ) { + $spacing_global_styles = gutenberg_get_global_styles( array( 'blocks', $block['blockName'], 'spacing' ) ); + if ( isset( $spacing_global_styles['blockGap'] ) && ! empty( $spacing_global_styles['blockGap'] ) ) { + $gap_value = $spacing_global_styles['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. diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php index 1a0945b175422..78aebb5869cc8 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-6-0.php @@ -289,6 +289,65 @@ class WP_Theme_JSON_6_0 extends WP_Theme_JSON_5_9 { ), ); + /** + * Sanitizes the input according to the schemas. + * + * @param array $input Structure to sanitize. + * @param array $valid_block_names List of valid block names. + * @param array $valid_element_names List of valid element names. + * @return array The sanitized output. + */ + protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { + $output = array(); + + if ( ! is_array( $input ) ) { + return $output; + } + + $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); + + // 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 ] = static::VALID_STYLES; + } + $schema_styles_blocks = array(); + $schema_settings_blocks = array(); + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = static::VALID_STYLES; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + } + $schema['styles'] = static::VALID_STYLES; + $schema['styles']['blocks'] = $schema_styles_blocks; + $schema['styles']['elements'] = $schema_styles_elements; + $schema['settings'] = static::VALID_SETTINGS; + $schema['settings']['blocks'] = $schema_settings_blocks; + + // Remove anything that's not present in the schema. + foreach ( array( 'styles', 'settings' ) as $subtree ) { + if ( ! isset( $input[ $subtree ] ) ) { + continue; + } + + if ( ! is_array( $input[ $subtree ] ) ) { + unset( $output[ $subtree ] ); + continue; + } + + $result = static::remove_keys_not_in_schema( $input[ $subtree ], $schema[ $subtree ] ); + + if ( empty( $result ) ) { + unset( $output[ $subtree ] ); + } else { + $output[ $subtree ] = $result; + } + } + + return $output; + } + /** * Returns the current theme's wanted patterns(slugs) to be * registered from Pattern Directory. @@ -328,7 +387,7 @@ protected function get_block_classes( $style_nodes ) { $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); $selector = $metadata['selector']; $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - $declarations = static::compute_style_properties( $node, $settings ); + $declarations = static::compute_style_properties( $node, $settings, null, $metadata['selector'] ); // 1. Separate the ones who use the general selector // and the ones who use the duotone selector. @@ -377,6 +436,69 @@ protected function get_block_classes( $style_nodes ) { return $block_rules; } + /** + * Given a styles array, it extracts the style properties + * and adds them to the $declarations array following the format: + * + * ```php + * array( + * 'name' => 'property_name', + * 'value' => 'property_value, + * ) + * ``` + * + * @param array $styles Styles to process. + * @param array $settings Theme settings. + * @param array $properties Properties metadata. + * @param string|null $selector Current selector. + * @return array Returns the modified $declarations. + */ + protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $selector = null ) { + if ( null === $properties ) { + $properties = static::PROPERTIES_METADATA; + } + + $declarations = array(); + if ( empty( $styles ) ) { + return $declarations; + } + + foreach ( $properties as $css_property => $value_path ) { + // Some styles such as blockGap are only meant to be available at the top level (ROOT_BLOCK_SELECTOR), + // hence we only output styles at the top level. + if ( 'top' === _wp_array_get( self::VALID_STYLES, array( $value_path[0], $value_path[1] ), null ) && static::ROOT_BLOCK_SELECTOR !== $selector ) { + continue; + } + + $value = static::get_property_value( $styles, $value_path ); + + // Look up protected properties, keyed by value path. + // Skip protected properties that are explicitly set to `null`. + if ( is_array( $value_path ) ) { + $path_string = implode( '.', $value_path ); + if ( + array_key_exists( $path_string, static::PROTECTED_PROPERTIES ) && + _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null + ) { + continue; + } + } + + // Skip if empty and not "0" or value represents array of longhand values. + $has_missing_value = empty( $value ) && ! is_numeric( $value ); + if ( $has_missing_value || is_array( $value ) ) { + continue; + } + + $declarations[] = array( + 'name' => $css_property, + 'value' => $value, + ); + } + + return $declarations; + } + /** * Merge new incoming data. * diff --git a/lib/compat/wordpress-6.0/theme.json b/lib/compat/wordpress-6.0/theme.json index 7691aa4a64e6a..b68c4e51db5bd 100644 --- a/lib/compat/wordpress-6.0/theme.json +++ b/lib/compat/wordpress-6.0/theme.json @@ -186,7 +186,7 @@ "text": true }, "spacing": { - "blockGap": null, + "blockGap": true, "margin": false, "padding": false, "units": [ "px", "em", "rem", "vh", "vw", "%" ] @@ -240,6 +240,13 @@ } }, "styles": { - "spacing": { "blockGap": "24px" } + "spacing": { "blockGap": "24px" }, + "blocks": { + "core/columns": { + "spacing": { + "blockGap": "2em" + } + } + } } } diff --git a/packages/edit-site/src/components/global-styles/dimensions-panel.js b/packages/edit-site/src/components/global-styles/dimensions-panel.js index 80c6c25e1d64c..db628050f0b47 100644 --- a/packages/edit-site/src/components/global-styles/dimensions-panel.js +++ b/packages/edit-site/src/components/global-styles/dimensions-panel.js @@ -43,13 +43,8 @@ function useHasMargin( name ) { function useHasGap( name ) { const supports = getSupportedGlobalStylesPanels( name ); const [ settings ] = useSetting( 'spacing.blockGap', name ); - // Do not show the gap control panel for block-level global styles - // as they do not work on the frontend. - // See: https://github.com/WordPress/gutenberg/pull/39845. - // We can revert this condition when they're working again. - return !! name - ? false - : settings && supports.includes( '--wp--style--block-gap' ); + + return settings && supports.includes( '--wp--style--block-gap' ); } function filterValuesBySides( values, sides ) { diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 89240870399dc..a30bffbda9608 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -385,6 +385,38 @@ function test_get_stylesheet_skips_disabled_protected_properties() { $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); } + function test_get_stylesheet_skips_top_level_properties_at_block_level() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'spacing' => array( + 'padding' => true, + 'blockGap' => true, + ), + ), + 'styles' => array( + 'spacing' => array( + 'padding' => '1vw', + 'blockGap' => '1em', + ), + 'blocks' => array( + 'core/columns' => array( + 'spacing' => array( + 'padding' => '0.5rem', + 'blockGap' => '24px', + ), + ), + ), + ), + ) + ); + + $expected = 'body { margin: 0; }body{padding: 1vw;--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-block-start: 0; margin-block-end: 0; }.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }.wp-block-columns{padding: 0.5rem;}'; + $this->assertEquals( $expected, $theme_json->get_stylesheet() ); + $this->assertEquals( $expected, $theme_json->get_stylesheet( array( 'styles' ) ) ); + } + function test_get_stylesheet_renders_enabled_protected_properties() { $theme_json = new WP_Theme_JSON_Gutenberg( array( @@ -2253,7 +2285,7 @@ function test_sanitization() { 'core/group' => array( 'spacing' => array( 'margin' => 'valid value', - 'blockGap' => 'invalid value', + 'blockGap' => 'valid value', ), ), ), @@ -2271,7 +2303,8 @@ function test_sanitization() { 'blocks' => array( 'core/group' => array( 'spacing' => array( - 'margin' => 'valid value', + 'margin' => 'valid value', + 'blockGap' => 'valid value', ), ), ),