From 6d8ee2af8b081f89a7801dfec363f91ea8abfb69 Mon Sep 17 00:00:00 2001
From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>
Date: Fri, 11 Sep 2026 19:06:20 +1000
Subject: [PATCH 1/2] Editor: Add a background clip block support
Adds a background.backgroundClip block support, so a block can set which
box its background is painted into: border-box, padding-box, content-box,
or the text itself. Clipping to the text is what gives blocks a gradient
text fill.
Registers the property in the style engine and theme.json, allows the
declarations through KSES, and skips the has-background class when the
background is clipped to the text.
Props aaronrobertshaw.
See #66094.
---
src/wp-includes/block-supports/background.php | 25 ++++-
src/wp-includes/class-wp-theme-json.php | 34 ++++++
src/wp-includes/kses.php | 5 +
.../style-engine/class-wp-style-engine.php | 49 +++++++++
.../wpRenderBackgroundSupport.php | 103 ++++++++++++++++++
.../tests/style-engine/styleEngine.php | 44 ++++++++
6 files changed, 257 insertions(+), 3 deletions(-)
diff --git a/src/wp-includes/block-supports/background.php b/src/wp-includes/block-supports/background.php
index 3b3e277c377f2..b2161973ecc6c 100644
--- a/src/wp-includes/block-supports/background.php
+++ b/src/wp-includes/block-supports/background.php
@@ -44,6 +44,7 @@ function wp_register_background_support( $block_type ) {
* @since 6.6.0 Removed requirement for `backgroundImage.source`. A file/url is the default.
* @since 6.7.0 Added support for `backgroundAttachment` output.
* @since 7.1.0 Added support for `background.gradient` output.
+ * @since 7.2.0 Added support for `background.backgroundClip` output.
*
* @access private
*
@@ -56,9 +57,10 @@ function wp_render_background_support( $block_content, $block ) {
$block_attributes = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array();
$has_background_image_support = block_has_support( $block_type, array( 'background', 'backgroundImage' ), false );
$has_background_gradient_support = block_has_support( $block_type, array( 'background', 'gradient' ), false );
+ $has_background_clip_support = block_has_support( $block_type, array( 'background', 'backgroundClip' ), false );
if (
- ( ! $has_background_image_support && ! $has_background_gradient_support ) ||
+ ( ! $has_background_image_support && ! $has_background_gradient_support && ! $has_background_clip_support ) ||
! isset( $block_attributes['style']['background'] )
) {
return $block_content;
@@ -67,8 +69,9 @@ function wp_render_background_support( $block_content, $block ) {
// Check serialization skip for each feature individually.
$skip_background_image = ! $has_background_image_support || wp_should_skip_block_supports_serialization( $block_type, 'background', 'backgroundImage' );
$skip_background_gradient = ! $has_background_gradient_support || wp_should_skip_block_supports_serialization( $block_type, 'background', 'gradient' );
+ $skip_background_clip = ! $has_background_clip_support || wp_should_skip_block_supports_serialization( $block_type, 'background', 'backgroundClip' );
- if ( $skip_background_image && $skip_background_gradient ) {
+ if ( $skip_background_image && $skip_background_gradient && $skip_background_clip ) {
return $block_content;
}
@@ -95,6 +98,10 @@ function wp_render_background_support( $block_content, $block ) {
$background_styles['gradient'] = $block_attributes['style']['background']['gradient'] ?? null;
}
+ if ( ! $skip_background_clip ) {
+ $background_styles['backgroundClip'] = $block_attributes['style']['background']['backgroundClip'] ?? null;
+ }
+
$styles = wp_style_engine_get_styles( array( 'background' => $background_styles ) );
if ( ! empty( $styles['css'] ) ) {
@@ -111,7 +118,19 @@ function wp_render_background_support( $block_content, $block ) {
}
$tags->set_attribute( 'style', $updated_style );
- $tags->add_class( 'has-background' );
+
+ /*
+ * A background clipped to the text paints the glyphs rather than the
+ * block's box, so there is no block background to announce. Read the
+ * clip from the serialized styles so an unsupported or skipped value
+ * left in the block's attributes cannot suppress the class.
+ */
+ $background_clip = $background_styles['backgroundClip'] ?? null;
+ $has_background = ! empty( $background_styles['backgroundImage'] ) || ! empty( $background_styles['gradient'] );
+
+ if ( $has_background && 'text' !== $background_clip ) {
+ $tags->add_class( 'has-background' );
+ }
}
return $tags->get_updated_html();
diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php
index 837499fb636f4..14eb7ed1df04f 100644
--- a/src/wp-includes/class-wp-theme-json.php
+++ b/src/wp-includes/class-wp-theme-json.php
@@ -259,6 +259,7 @@ class WP_Theme_JSON {
* @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
* Added `text-indent` property.
* @since 7.1.0 Added `min-width` and `text-shadow`.
+ * @since 7.2.0 Added `background-clip`.
* @var array
*/
const PROPERTIES_METADATA = array(
@@ -270,6 +271,7 @@ class WP_Theme_JSON {
'background-repeat' => array( 'background', 'backgroundRepeat' ),
'background-size' => array( 'background', 'backgroundSize' ),
'background-attachment' => array( 'background', 'backgroundAttachment' ),
+ 'background-clip' => array( 'background', 'backgroundClip' ),
'border-radius' => array( 'border', 'radius' ),
'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ),
'border-top-right-radius' => array( 'border', 'radius', 'topRight' ),
@@ -428,6 +430,7 @@ class WP_Theme_JSON {
* Added support for `dimensions.width` and `dimensions.height`.
* Added support for `typography.textIndent`.
* @since 7.1.0 Added `viewport` property.
+ * @since 7.2.0 Added `background.backgroundClip` property.
* Added support for `background.gradient`, `dimensions.minWidth` and `blockVisibility.allowEditing`.
* @since 7.2.0 Added support for `typography.textShadow`, `typography.textShadowPresets`,
* and `typography.defaultTextShadowPresets`.
@@ -437,6 +440,7 @@ class WP_Theme_JSON {
'appearanceTools' => null,
'useRootPaddingAwareAlignments' => null,
'background' => array(
+ 'backgroundClip' => null,
'backgroundImage' => null,
'backgroundSize' => null,
'gradient' => null,
@@ -582,10 +586,12 @@ class WP_Theme_JSON {
* @since 7.0.0 Added support for `dimensions.width` and `dimensions.height`.
* @since 7.1.0 Added support for `background.gradient`,`dimensions.minWidth`,
* and `typography.textShadow`.
+ * @since 7.2.0 Added support for `background.backgroundClip`.
* @var array
*/
const VALID_STYLES = array(
'background' => array(
+ 'backgroundClip' => null,
'backgroundImage' => null,
'backgroundPosition' => null,
'backgroundRepeat' => null,
@@ -3192,6 +3198,34 @@ protected static function compute_style_properties( $styles, $settings = array()
'name' => $css_property,
'value' => $value,
);
+
+ /*
+ * Background clipping needs vendor prefixed properties for
+ * cross-browser support. The `text` value clips the background to
+ * the text and makes it visible through a transparent fill. The box
+ * values only reset the fill color, to cancel any inherited text
+ * clipping. `-webkit-background-clip` is an alias of
+ * `background-clip` in Chromium, so resetting it would discard the
+ * value set above. Values outside the set the style engine accepts
+ * get neither, so a typo cannot reset the fill color.
+ */
+ if ( 'background-clip' === $css_property ) {
+ if ( 'text' === $value ) {
+ $declarations[] = array(
+ 'name' => '-webkit-background-clip',
+ 'value' => 'text',
+ );
+ $declarations[] = array(
+ 'name' => '-webkit-text-fill-color',
+ 'value' => 'transparent',
+ );
+ } elseif ( in_array( $value, array( 'border-box', 'padding-box', 'content-box' ), true ) ) {
+ $declarations[] = array(
+ 'name' => '-webkit-text-fill-color',
+ 'value' => 'currentColor',
+ );
+ }
+ }
}
// If a variable value is added to the root, the corresponding property should be removed.
diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php
index 0a7ea7ff3087e..9d22ee45858ff 100644
--- a/src/wp-includes/kses.php
+++ b/src/wp-includes/kses.php
@@ -3684,6 +3684,7 @@ function kses_init() {
* @since 6.6.0 Added support for `grid-column`, `grid-row`, and `container-type`.
* @since 6.9.0 Added support for `white-space`.
* @since 7.1.0 Extended gradient support to allow any single-level nested function.
+ * @since 7.2.0 Added support for `background-clip`.
* Added support for transform functions, `clip-path` basic shapes,
* and URLs in the SVG element reference properties.
* @since 7.2.0 Added support for CSS anchor positioning properties and `text-shadow`.
@@ -3712,6 +3713,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
*
* @since 2.8.1
* @since 7.1.0 Added support for SVG presentation attributes.
+ * @since 7.2.0 Added support for `background-clip` and its vendor prefixed companions.
*
* @param string[] $attr Array of allowed CSS attributes.
*/
@@ -3726,6 +3728,9 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
'background-size',
'background-attachment',
'background-blend-mode',
+ 'background-clip',
+ '-webkit-background-clip',
+ '-webkit-text-fill-color',
'border',
'border-radius',
diff --git a/src/wp-includes/style-engine/class-wp-style-engine.php b/src/wp-includes/style-engine/class-wp-style-engine.php
index b2988c7140b07..58b79592d8aa1 100644
--- a/src/wp-includes/style-engine/class-wp-style-engine.php
+++ b/src/wp-includes/style-engine/class-wp-style-engine.php
@@ -50,6 +50,7 @@ final class WP_Style_Engine {
*
* @since 6.1.0
* @since 7.1.0 Added `background.gradient` property.
+ * @since 7.2.0 Added `background.backgroundClip` property.
* @var array
*/
const BLOCK_STYLE_DEFINITIONS_METADATA = array(
@@ -97,6 +98,10 @@ final class WP_Style_Engine {
'has-background' => true,
),
),
+ 'backgroundClip' => array(
+ 'value_func' => array( self::class, 'get_background_clip_css_declarations' ),
+ 'path' => array( 'background', 'backgroundClip' ),
+ ),
),
'color' => array(
'text' => array(
@@ -768,6 +773,50 @@ protected static function get_url_or_value_css_declaration( $style_value, $style
return $css_declarations;
}
+ /**
+ * Style value parser that returns the CSS declarations for background clipping.
+ *
+ * For the `text` value the background is clipped to the block's text, which
+ * requires the vendor prefixed properties and a transparent fill color.
+ *
+ * @since 7.2.0
+ *
+ * @param string $style_value A single raw style value from $block_styles array.
+ * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
+ * @return string[] An associative array of CSS definitions, e.g., array( "$property" => "$value", "$property" => "$value" ).
+ */
+ protected static function get_background_clip_css_declarations( $style_value, $style_definition ) {
+ if ( empty( $style_value ) || ! is_string( $style_value ) ) {
+ return array();
+ }
+
+ $valid_values = array( 'border-box', 'padding-box', 'content-box', 'text' );
+
+ if ( ! in_array( $style_value, $valid_values, true ) ) {
+ return array();
+ }
+
+ $css_declarations = array(
+ 'background-clip' => $style_value,
+ );
+
+ if ( 'text' === $style_value ) {
+ $css_declarations['-webkit-background-clip'] = 'text';
+ $css_declarations['-webkit-text-fill-color'] = 'transparent';
+ } else {
+ /*
+ * Only the fill color is restored. `-webkit-background-clip` is an
+ * alias of `background-clip` in Chromium, so resetting it here would
+ * discard the value set above. The fill color is inherited, so it
+ * needs its initial value rather than `unset`, which would take a
+ * transparent fill from an ancestor clipping to text.
+ */
+ $css_declarations['-webkit-text-fill-color'] = 'currentColor';
+ }
+
+ return $css_declarations;
+ }
+
/**
* Returns compiled CSS from CSS declarations.
*
diff --git a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
index 6f0a6cc023db7..9ebde814b0437 100644
--- a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
+++ b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
@@ -70,6 +70,7 @@ public function filter_set_theme_root() {
* @ticket 61720
* @ticket 61858
* @ticket 64974
+ * @ticket 66094
*
* @covers ::wp_render_background_support
*
@@ -282,6 +283,108 @@ public function data_background_block_support() {
'expected_wrapper' => '
Content
',
'wrapper' => 'Content
',
),
+ 'background clip alone does not add has-background' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-is-output',
+ 'background_settings' => array(
+ 'backgroundClip' => true,
+ ),
+ 'background_style' => array(
+ 'backgroundClip' => 'border-box',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clip text style is applied with vendor prefixes' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-text-is-output',
+ 'background_settings' => array(
+ 'backgroundClip' => true,
+ ),
+ 'background_style' => array(
+ 'backgroundClip' => 'text',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clip alone does not add has-background for block with only backgroundClip support' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-only-support',
+ 'background_settings' => array(
+ 'backgroundClip' => true,
+ ),
+ 'background_style' => array(
+ 'backgroundClip' => 'padding-box',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clip style is not applied if the block does not support it' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-not-supported',
+ 'background_settings' => array(
+ 'backgroundClip' => false,
+ ),
+ 'background_style' => array(
+ 'backgroundClip' => 'text',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clip keeps has-background when a background is painted into a box' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-with-gradient',
+ 'background_settings' => array(
+ 'gradient' => true,
+ 'backgroundClip' => true,
+ ),
+ 'background_style' => array(
+ 'gradient' => 'linear-gradient(135deg,rgb(255,0,0) 0%,rgb(0,0,255) 100%)',
+ 'backgroundClip' => 'padding-box',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clipped to text drops has-background from a gradient' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-text-with-gradient',
+ 'background_settings' => array(
+ 'gradient' => true,
+ 'backgroundClip' => true,
+ ),
+ 'background_style' => array(
+ 'gradient' => 'linear-gradient(135deg,rgb(255,0,0) 0%,rgb(0,0,255) 100%)',
+ 'backgroundClip' => 'text',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clipped to text keeps has-background when the block does not support the clip' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-text-without-clip-support',
+ 'background_settings' => array(
+ 'gradient' => true,
+ 'backgroundClip' => false,
+ ),
+ 'background_style' => array(
+ 'gradient' => 'linear-gradient(135deg,rgb(255,0,0) 0%,rgb(0,0,255) 100%)',
+ 'backgroundClip' => 'text',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
+ 'background clip style is appended to existing styles' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-clip-appends-styles',
+ 'background_settings' => array(
+ 'backgroundClip' => true,
+ ),
+ 'background_style' => array(
+ 'backgroundClip' => 'content-box',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
);
}
diff --git a/tests/phpunit/tests/style-engine/styleEngine.php b/tests/phpunit/tests/style-engine/styleEngine.php
index fe858e07cb6a7..e53252a0715e3 100644
--- a/tests/phpunit/tests/style-engine/styleEngine.php
+++ b/tests/phpunit/tests/style-engine/styleEngine.php
@@ -25,6 +25,7 @@ class Tests_wpStyleEngine extends WP_UnitTestCase {
* @ticket 63799
* @ticket 64974
* @ticket 65037
+ * @ticket 66094
*
* @covers ::wp_style_engine_get_styles
*
@@ -667,6 +668,49 @@ public function data_wp_style_engine_get_styles() {
),
),
),
+
+ 'inline_background_clip_border_box' => array(
+ 'block_styles' => array(
+ 'background' => array(
+ 'backgroundClip' => 'border-box',
+ ),
+ ),
+ 'options' => array(),
+ 'expected_output' => array(
+ 'css' => 'background-clip:border-box;-webkit-text-fill-color:currentColor;',
+ 'declarations' => array(
+ 'background-clip' => 'border-box',
+ '-webkit-text-fill-color' => 'currentColor',
+ ),
+ ),
+ ),
+
+ 'inline_background_clip_text_with_vendor_prefixes' => array(
+ 'block_styles' => array(
+ 'background' => array(
+ 'backgroundClip' => 'text',
+ ),
+ ),
+ 'options' => array(),
+ 'expected_output' => array(
+ 'css' => 'background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent;',
+ 'declarations' => array(
+ 'background-clip' => 'text',
+ '-webkit-background-clip' => 'text',
+ '-webkit-text-fill-color' => 'transparent',
+ ),
+ ),
+ ),
+
+ 'inline_background_clip_invalid_value' => array(
+ 'block_styles' => array(
+ 'background' => array(
+ 'backgroundClip' => 'invalid-value',
+ ),
+ ),
+ 'options' => array(),
+ 'expected_output' => array(),
+ ),
);
}
From fee78182a6d0517c7eeeded05e255c1df70a41a8 Mon Sep 17 00:00:00 2001
From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com>
Date: Thu, 8 Oct 2026 15:13:29 +0800
Subject: [PATCH 2/2] Block Supports: Add PHP helpers for block support classes
and styles.
---
src/wp-includes/block-supports/background.php | 114 +++++++----
src/wp-includes/block-supports/border.php | 150 +++++++++------
src/wp-includes/block-supports/colors.php | 74 +++++---
src/wp-includes/block-supports/shadow.php | 23 ++-
src/wp-includes/block-supports/spacing.php | 57 ++++--
tests/phpunit/tests/block-supports/border.php | 177 ++++++++++++++++++
tests/phpunit/tests/block-supports/colors.php | 74 ++++++++
.../phpunit/tests/block-supports/spacing.php | 63 +++++++
.../wpGetBackgroundClassesAndStyles.php | 172 +++++++++++++++++
.../wpGetBorderClassesAndStyles.php | 131 +++++++++++++
.../wpGetColorClassesAndStyles.php | 118 ++++++++++++
.../wpGetShadowClassesAndStyles.php | 67 +++++++
.../wpGetSpacingClassesAndStyles.php | 112 +++++++++++
.../wpRenderBackgroundSupport.php | 18 ++
14 files changed, 1211 insertions(+), 139 deletions(-)
create mode 100644 tests/phpunit/tests/block-supports/wpGetBackgroundClassesAndStyles.php
create mode 100644 tests/phpunit/tests/block-supports/wpGetBorderClassesAndStyles.php
create mode 100644 tests/phpunit/tests/block-supports/wpGetColorClassesAndStyles.php
create mode 100644 tests/phpunit/tests/block-supports/wpGetShadowClassesAndStyles.php
create mode 100644 tests/phpunit/tests/block-supports/wpGetSpacingClassesAndStyles.php
diff --git a/src/wp-includes/block-supports/background.php b/src/wp-includes/block-supports/background.php
index b2161973ecc6c..f7a603f9bab81 100644
--- a/src/wp-includes/block-supports/background.php
+++ b/src/wp-includes/block-supports/background.php
@@ -34,6 +34,60 @@ function wp_register_background_support( $block_type ) {
}
}
+/**
+ * Returns background classes and inline styles for block attributes, like the JS
+ * `getBackgroundClassesAndStyles()`. Does not check block support or skipped
+ * serialization.
+ *
+ * @since 7.2.0
+ *
+ * @param array $block_attributes Block attributes.
+ * @return array Array with `class` and `style` keys, each present only when non-empty.
+ */
+function wp_get_background_classes_and_styles( $block_attributes ) {
+ $background = $block_attributes['style']['background'] ?? null;
+
+ if ( ! is_array( $background ) ) {
+ return array();
+ }
+
+ $background_styles = array(
+ 'backgroundImage' => $background['backgroundImage'] ?? null,
+ 'backgroundSize' => $background['backgroundSize'] ?? null,
+ 'backgroundPosition' => $background['backgroundPosition'] ?? null,
+ 'backgroundRepeat' => $background['backgroundRepeat'] ?? null,
+ 'backgroundAttachment' => $background['backgroundAttachment'] ?? null,
+ 'gradient' => $background['gradient'] ?? null,
+ 'backgroundClip' => $background['backgroundClip'] ?? null,
+ );
+
+ if ( ! empty( $background_styles['backgroundImage'] ) ) {
+ $background_styles['backgroundSize'] ??= 'cover';
+ if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) {
+ $background_styles['backgroundPosition'] = '50% 50%';
+ }
+ }
+
+ $styles = wp_style_engine_get_styles( array( 'background' => $background_styles ) );
+
+ if ( empty( $styles['css'] ) ) {
+ return array();
+ }
+
+ $classes_and_styles = array();
+
+ $has_background = ! empty( $background_styles['backgroundImage'] ) || ! empty( $background_styles['gradient'] );
+
+ // A text clip paints the glyphs, not the box, so there is no block background.
+ if ( $has_background && 'text' !== $background_styles['backgroundClip'] ) {
+ $classes_and_styles['class'] = 'has-background';
+ }
+
+ $classes_and_styles['style'] = $styles['css'];
+
+ return $classes_and_styles;
+}
+
/**
* Renders the background styles to the block wrapper.
* This block support uses the `render_block` hook to ensure that
@@ -75,36 +129,31 @@ function wp_render_background_support( $block_content, $block ) {
return $block_content;
}
- $background_styles = array();
-
- if ( ! $skip_background_image ) {
- $background_styles['backgroundImage'] = $block_attributes['style']['background']['backgroundImage'] ?? null;
- $background_styles['backgroundSize'] = $block_attributes['style']['background']['backgroundSize'] ?? null;
- $background_styles['backgroundPosition'] = $block_attributes['style']['background']['backgroundPosition'] ?? null;
- $background_styles['backgroundRepeat'] = $block_attributes['style']['background']['backgroundRepeat'] ?? null;
- $background_styles['backgroundAttachment'] = $block_attributes['style']['background']['backgroundAttachment'] ?? null;
-
- if ( ! empty( $background_styles['backgroundImage'] ) ) {
- $background_styles['backgroundSize'] ??= 'cover';
-
- // If the background size is set to `contain` and no position is set, set the position to `center`.
- if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) {
- $background_styles['backgroundPosition'] = '50% 50%';
- }
+ // The helper ignores a non-array background, so only an array needs filtering.
+ if ( is_array( $block_attributes['style']['background'] ) ) {
+ if ( $skip_background_image ) {
+ unset(
+ $block_attributes['style']['background']['backgroundImage'],
+ $block_attributes['style']['background']['backgroundSize'],
+ $block_attributes['style']['background']['backgroundPosition'],
+ $block_attributes['style']['background']['backgroundRepeat'],
+ $block_attributes['style']['background']['backgroundAttachment']
+ );
}
- }
- if ( ! $skip_background_gradient ) {
- $background_styles['gradient'] = $block_attributes['style']['background']['gradient'] ?? null;
- }
+ if ( $skip_background_gradient ) {
+ unset( $block_attributes['style']['background']['gradient'] );
+ }
- if ( ! $skip_background_clip ) {
- $background_styles['backgroundClip'] = $block_attributes['style']['background']['backgroundClip'] ?? null;
+ // Serialized values only, so a skipped clip cannot drop `has-background`.
+ if ( $skip_background_clip ) {
+ unset( $block_attributes['style']['background']['backgroundClip'] );
+ }
}
- $styles = wp_style_engine_get_styles( array( 'background' => $background_styles ) );
+ $styles = wp_get_background_classes_and_styles( $block_attributes );
- if ( ! empty( $styles['css'] ) ) {
+ if ( ! empty( $styles['style'] ) ) {
// Inject background styles to the first element, presuming it's the wrapper, if it exists.
$tags = new WP_HTML_Tag_Processor( $block_content );
@@ -112,24 +161,15 @@ function wp_render_background_support( $block_content, $block ) {
$existing_style = $tags->get_attribute( 'style' );
if ( is_string( $existing_style ) && '' !== $existing_style ) {
$separator = str_ends_with( $existing_style, ';' ) ? '' : ';';
- $updated_style = "{$existing_style}{$separator}{$styles['css']}";
+ $updated_style = "{$existing_style}{$separator}{$styles['style']}";
} else {
- $updated_style = $styles['css'];
+ $updated_style = $styles['style'];
}
$tags->set_attribute( 'style', $updated_style );
- /*
- * A background clipped to the text paints the glyphs rather than the
- * block's box, so there is no block background to announce. Read the
- * clip from the serialized styles so an unsupported or skipped value
- * left in the block's attributes cannot suppress the class.
- */
- $background_clip = $background_styles['backgroundClip'] ?? null;
- $has_background = ! empty( $background_styles['backgroundImage'] ) || ! empty( $background_styles['gradient'] );
-
- if ( $has_background && 'text' !== $background_clip ) {
- $tags->add_class( 'has-background' );
+ if ( ! empty( $styles['class'] ) ) {
+ $tags->add_class( $styles['class'] );
}
}
diff --git a/src/wp-includes/block-supports/border.php b/src/wp-includes/block-supports/border.php
index 89a3ef175e234..e5afd5518998a 100644
--- a/src/wp-includes/block-supports/border.php
+++ b/src/wp-includes/block-supports/border.php
@@ -52,76 +52,110 @@ function wp_apply_border_support( $block_type, $block_attributes ) {
return array();
}
- $border_block_styles = array();
- $has_border_color_support = wp_has_border_feature_support( $block_type, 'color' );
- $has_border_width_support = wp_has_border_feature_support( $block_type, 'width' );
-
- // Border radius.
- if (
- wp_has_border_feature_support( $block_type, 'radius' ) &&
- isset( $block_attributes['style']['border']['radius'] ) &&
- ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'radius' )
- ) {
- $border_radius = $block_attributes['style']['border']['radius'];
-
- if ( is_numeric( $border_radius ) ) {
- $border_radius .= 'px';
- }
+ $has_border_color_support = wp_has_border_feature_support( $block_type, 'color' );
+ $has_border_radius_support = wp_has_border_feature_support( $block_type, 'radius' );
+ $has_border_style_support = wp_has_border_feature_support( $block_type, 'style' );
+ $has_border_width_support = wp_has_border_feature_support( $block_type, 'width' );
+ $skip_color = wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' );
+ $skip_radius = wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'radius' );
+ $skip_style = wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' );
+ $skip_width = wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' );
+
+ // The helper reads only `style.border`, so a non-array value counts as no border styles.
+ if ( ! isset( $block_attributes['style']['border'] ) || ! is_array( $block_attributes['style']['border'] ) ) {
+ unset( $block_attributes['style'] );
+ }
+
+ if ( ! $has_border_radius_support || $skip_radius ) {
+ unset( $block_attributes['style']['border']['radius'] );
+ }
- $border_block_styles['radius'] = $border_radius;
+ if ( ! $has_border_style_support || $skip_style ) {
+ unset( $block_attributes['style']['border']['style'] );
}
- // Border style.
- if (
- wp_has_border_feature_support( $block_type, 'style' ) &&
- isset( $block_attributes['style']['border']['style'] ) &&
- ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' )
- ) {
- $border_block_styles['style'] = $block_attributes['style']['border']['style'];
+ if ( ! $has_border_width_support || $skip_width ) {
+ unset( $block_attributes['style']['border']['width'] );
}
- // Border width.
- if (
- $has_border_width_support &&
- isset( $block_attributes['style']['border']['width'] ) &&
- ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' )
- ) {
- $border_width = $block_attributes['style']['border']['width'];
+ if ( ! $has_border_color_support || $skip_color ) {
+ unset( $block_attributes['style']['border']['color'], $block_attributes['borderColor'] );
+ }
+
+ // Sides are filtered by skipped serialization only, not by feature support.
+ foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) {
+ $has_side = isset( $block_attributes['style']['border'][ $side ] ) && is_array( $block_attributes['style']['border'][ $side ] );
+
+ if ( ! $has_side || ( ! $has_border_color_support && ! $has_border_width_support ) ) {
+ unset( $block_attributes['style']['border'][ $side ] );
+ continue;
+ }
+
+ if ( $skip_width ) {
+ unset( $block_attributes['style']['border'][ $side ]['width'] );
+ }
- // This check handles original unitless implementation.
- if ( is_numeric( $border_width ) ) {
- $border_width .= 'px';
+ if ( $skip_color ) {
+ unset( $block_attributes['style']['border'][ $side ]['color'] );
}
- $border_block_styles['width'] = $border_width;
- }
-
- // Border color.
- if (
- $has_border_color_support &&
- ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' )
- ) {
- $preset_border_color = array_key_exists( 'borderColor', $block_attributes ) ? "var:preset|color|{$block_attributes['borderColor']}" : null;
- $custom_border_color = $block_attributes['style']['border']['color'] ?? null;
- $border_block_styles['color'] = $preset_border_color ? $preset_border_color : $custom_border_color;
- }
-
- // Generates styles for individual border sides.
- if ( $has_border_color_support || $has_border_width_support ) {
- foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) {
- $border = $block_attributes['style']['border'][ $side ] ?? null;
- $border_side_values = array(
- 'width' => isset( $border['width'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'width' ) ? $border['width'] : null,
- 'color' => isset( $border['color'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'color' ) ? $border['color'] : null,
- 'style' => isset( $border['style'] ) && ! wp_should_skip_block_supports_serialization( $block_type, '__experimentalBorder', 'style' ) ? $border['style'] : null,
- );
- $border_block_styles[ $side ] = $border_side_values;
+ if ( $skip_style ) {
+ unset( $block_attributes['style']['border'][ $side ]['style'] );
}
}
- // Collect classes and styles.
+ return wp_get_border_classes_and_styles( $block_attributes );
+}
+
+/**
+ * Returns border classes and inline styles for block attributes, like the JS
+ * `getBorderClassesAndStyles()`. Does not check block support or skipped
+ * serialization.
+ *
+ * @since 7.2.0
+ *
+ * @param array $block_attributes Block attributes.
+ * @return array Array with `class` and `style` keys, each present only when non-empty.
+ */
+function wp_get_border_classes_and_styles( $block_attributes ) {
+ if ( ! is_array( $block_attributes ) ) {
+ return array();
+ }
+
+ $border = isset( $block_attributes['style']['border'] ) && is_array( $block_attributes['style']['border'] )
+ ? $block_attributes['style']['border']
+ : array();
+ $border_styles = array();
+
+ // Unitless radius and width are from the original implementation.
+ if ( isset( $border['radius'] ) ) {
+ $border_styles['radius'] = is_numeric( $border['radius'] ) ? "{$border['radius']}px" : $border['radius'];
+ }
+
+ if ( isset( $border['style'] ) ) {
+ $border_styles['style'] = $border['style'];
+ }
+
+ if ( isset( $border['width'] ) ) {
+ $border_styles['width'] = is_numeric( $border['width'] ) ? "{$border['width']}px" : $border['width'];
+ }
+
+ $preset_border_color = array_key_exists( 'borderColor', $block_attributes ) ? "var:preset|color|{$block_attributes['borderColor']}" : null;
+ $border_styles['color'] = $preset_border_color ? $preset_border_color : ( $border['color'] ?? null );
+
+ foreach ( array( 'top', 'right', 'bottom', 'left' ) as $side ) {
+ if ( ! isset( $border[ $side ] ) || ! is_array( $border[ $side ] ) ) {
+ continue;
+ }
+ $border_styles[ $side ] = array(
+ 'width' => $border[ $side ]['width'] ?? null,
+ 'color' => $border[ $side ]['color'] ?? null,
+ 'style' => $border[ $side ]['style'] ?? null,
+ );
+ }
+
$attributes = array();
- $styles = wp_style_engine_get_styles( array( 'border' => $border_block_styles ) );
+ $styles = wp_style_engine_get_styles( array( 'border' => $border_styles ) );
if ( ! empty( $styles['classnames'] ) ) {
$attributes['class'] = $styles['classnames'];
diff --git a/src/wp-includes/block-supports/colors.php b/src/wp-includes/block-supports/colors.php
index 567a5422490b3..ea2749684c242 100644
--- a/src/wp-includes/block-supports/colors.php
+++ b/src/wp-includes/block-supports/colors.php
@@ -97,37 +97,61 @@ function wp_apply_colors_support( $block_type, $block_attributes ) {
( isset( $color_support['background'] ) && $color_support['background'] ) ||
( is_array( $color_support ) && ! isset( $color_support['background'] ) );
$has_gradients_support = $color_support['gradients'] ?? false;
- $color_block_styles = array();
+ $skip_text = wp_should_skip_block_supports_serialization( $block_type, 'color', 'text' );
+ $skip_background = wp_should_skip_block_supports_serialization( $block_type, 'color', 'background' );
+ $skip_gradients = wp_should_skip_block_supports_serialization( $block_type, 'color', 'gradients' );
- // Text colors.
- if ( $has_text_colors_support && ! wp_should_skip_block_supports_serialization( $block_type, 'color', 'text' ) ) {
- $preset_text_color = array_key_exists( 'textColor', $block_attributes ) ? "var:preset|color|{$block_attributes['textColor']}" : null;
- $custom_text_color = $block_attributes['style']['color']['text'] ?? null;
- $color_block_styles['text'] = $preset_text_color ? $preset_text_color : $custom_text_color;
+ // background.php owns the CSS when a background gradient is set, so skip color.gradient.
+ $has_background_gradient_support = block_has_support( $block_type, array( 'background', 'gradient' ), false );
+ $has_background_gradient_value = ! empty( $block_attributes['style']['background']['gradient'] );
+ $has_background_gradient = $has_background_gradient_support && $has_background_gradient_value;
+
+ // The helper reads only `style.color`, so a non-array value counts as no color styles.
+ if ( ! isset( $block_attributes['style']['color'] ) || ! is_array( $block_attributes['style']['color'] ) ) {
+ unset( $block_attributes['style'] );
}
- // Background colors.
- if ( $has_background_colors_support && ! wp_should_skip_block_supports_serialization( $block_type, 'color', 'background' ) ) {
- $preset_background_color = array_key_exists( 'backgroundColor', $block_attributes ) ? "var:preset|color|{$block_attributes['backgroundColor']}" : null;
- $custom_background_color = $block_attributes['style']['color']['background'] ?? null;
- $color_block_styles['background'] = $preset_background_color ? $preset_background_color : $custom_background_color;
+ if ( ! $has_text_colors_support || $skip_text ) {
+ unset( $block_attributes['textColor'], $block_attributes['style']['color']['text'] );
}
- // Gradients.
- // Suppress color.gradient CSS when background.gradient is supported and
- // explicitly set. background.php owns CSS generation in that case, and
- // emitting the background shorthand here would conflict with it.
- $has_background_gradient_support = block_has_support( $block_type, array( 'background', 'gradient' ), false );
- $has_background_gradient_value = ! empty( $block_attributes['style']['background']['gradient'] );
+ if ( ! $has_background_colors_support || $skip_background ) {
+ unset( $block_attributes['backgroundColor'], $block_attributes['style']['color']['background'] );
+ }
- if (
- $has_gradients_support &&
- ! wp_should_skip_block_supports_serialization( $block_type, 'color', 'gradients' ) &&
- ! ( $has_background_gradient_support && $has_background_gradient_value )
- ) {
- $preset_gradient_color = array_key_exists( 'gradient', $block_attributes ) ? "var:preset|gradient|{$block_attributes['gradient']}" : null;
- $custom_gradient_color = $block_attributes['style']['color']['gradient'] ?? null;
- $color_block_styles['gradient'] = $preset_gradient_color ? $preset_gradient_color : $custom_gradient_color;
+ if ( ! $has_gradients_support || $skip_gradients || $has_background_gradient ) {
+ unset( $block_attributes['gradient'], $block_attributes['style']['color']['gradient'] );
+ }
+
+ return wp_get_color_classes_and_styles( $block_attributes );
+}
+
+/**
+ * Returns color classes and inline styles for block attributes, like the JS
+ * `getColorClassesAndStyles()`. Does not check block support or skipped
+ * serialization.
+ *
+ * @since 7.2.0
+ *
+ * @param array $block_attributes Block attributes.
+ * @return array Array with `class` and `style` keys, each present only when non-empty.
+ */
+function wp_get_color_classes_and_styles( $block_attributes ) {
+ if ( ! is_array( $block_attributes ) ) {
+ return array();
+ }
+
+ $color_block_styles = array();
+ $preset_attributes = array(
+ 'text' => array( 'textColor', 'color' ),
+ 'background' => array( 'backgroundColor', 'color' ),
+ 'gradient' => array( 'gradient', 'gradient' ),
+ );
+
+ foreach ( $preset_attributes as $feature => list( $preset_attribute, $preset_type ) ) {
+ $preset_value = array_key_exists( $preset_attribute, $block_attributes ) ? "var:preset|{$preset_type}|{$block_attributes[ $preset_attribute ]}" : null;
+ $custom_value = $block_attributes['style']['color'][ $feature ] ?? null;
+ $color_block_styles[ $feature ] = $preset_value ?? $custom_value;
}
$attributes = array();
diff --git a/src/wp-includes/block-supports/shadow.php b/src/wp-includes/block-supports/shadow.php
index 4c09ce9d08f0f..9469b68f55307 100644
--- a/src/wp-includes/block-supports/shadow.php
+++ b/src/wp-includes/block-supports/shadow.php
@@ -60,13 +60,28 @@ function wp_apply_shadow_support( $block_type, $block_attributes ) {
return array();
}
- $shadow_block_styles = array();
+ return wp_get_shadow_classes_and_styles( $block_attributes );
+}
- $custom_shadow = $block_attributes['style']['shadow'] ?? null;
- $shadow_block_styles['shadow'] = $custom_shadow;
+/**
+ * Returns shadow classes and inline styles for block attributes, like the JS
+ * `getShadowClassesAndStyles()`. Does not check block support or skipped
+ * serialization.
+ *
+ * @since 7.2.0
+ *
+ * @param array $block_attributes Block attributes.
+ * @return array Array with `class` and `style` keys, each present only when non-empty.
+ */
+function wp_get_shadow_classes_and_styles( $block_attributes ) {
+ $shadow = $block_attributes['style']['shadow'] ?? null;
$attributes = array();
- $styles = wp_style_engine_get_styles( $shadow_block_styles );
+ $styles = wp_style_engine_get_styles( array( 'shadow' => $shadow ) );
+
+ if ( ! empty( $styles['classnames'] ) ) {
+ $attributes['class'] = $styles['classnames'];
+ }
if ( ! empty( $styles['css'] ) ) {
$attributes['style'] = $styles['css'];
diff --git a/src/wp-includes/block-supports/spacing.php b/src/wp-includes/block-supports/spacing.php
index a778eebeb475a..e7064007360bc 100644
--- a/src/wp-includes/block-supports/spacing.php
+++ b/src/wp-includes/block-supports/spacing.php
@@ -49,28 +49,55 @@ function wp_apply_spacing_support( $block_type, $block_attributes ) {
return array();
}
- $attributes = array();
+ if ( ! isset( $block_attributes['style']['spacing'] ) || ! is_array( $block_attributes['style']['spacing'] ) ) {
+ return array();
+ }
+
$has_padding_support = block_has_support( $block_type, array( 'spacing', 'padding' ), false );
$has_margin_support = block_has_support( $block_type, array( 'spacing', 'margin' ), false );
- $block_styles = $block_attributes['style'] ?? null;
+ $skip_padding = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'padding' );
+ $skip_margin = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'margin' );
- if ( ! $block_styles ) {
- return $attributes;
+ if ( ! $has_padding_support || $skip_padding ) {
+ unset( $block_attributes['style']['spacing']['padding'] );
}
- $skip_padding = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'padding' );
- $skip_margin = wp_should_skip_block_supports_serialization( $block_type, 'spacing', 'margin' );
- $spacing_block_styles = array(
- 'padding' => null,
- 'margin' => null,
- );
- if ( $has_padding_support && ! $skip_padding ) {
- $spacing_block_styles['padding'] = $block_styles['spacing']['padding'] ?? null;
+ if ( ! $has_margin_support || $skip_margin ) {
+ unset( $block_attributes['style']['spacing']['margin'] );
+ }
+
+ return wp_get_spacing_classes_and_styles( $block_attributes );
+}
+
+/**
+ * Returns spacing classes and inline styles for block attributes, like the JS
+ * `getSpacingClassesAndStyles()`. Does not check block support or skipped
+ * serialization.
+ *
+ * @since 7.2.0
+ *
+ * @param array $block_attributes Block attributes.
+ * @return array Array with `class` and `style` keys, each present only when non-empty.
+ */
+function wp_get_spacing_classes_and_styles( $block_attributes ) {
+ $spacing = $block_attributes['style']['spacing'] ?? null;
+ if ( ! is_array( $spacing ) ) {
+ return array();
}
- if ( $has_margin_support && ! $skip_margin ) {
- $spacing_block_styles['margin'] = $block_styles['spacing']['margin'] ?? null;
+
+ $styles = wp_style_engine_get_styles(
+ array(
+ 'spacing' => array(
+ 'padding' => $spacing['padding'] ?? null,
+ 'margin' => $spacing['margin'] ?? null,
+ ),
+ )
+ );
+ $attributes = array();
+
+ if ( ! empty( $styles['classnames'] ) ) {
+ $attributes['class'] = $styles['classnames'];
}
- $styles = wp_style_engine_get_styles( array( 'spacing' => $spacing_block_styles ) );
if ( ! empty( $styles['css'] ) ) {
$attributes['style'] = $styles['css'];
diff --git a/tests/phpunit/tests/block-supports/border.php b/tests/phpunit/tests/block-supports/border.php
index cd1c1def70e26..2bab64e9741c6 100644
--- a/tests/phpunit/tests/block-supports/border.php
+++ b/tests/phpunit/tests/block-supports/border.php
@@ -21,6 +21,35 @@ public function tear_down() {
parent::tear_down();
}
+ /**
+ * Registers a new block for testing border support.
+ *
+ * @param string $block_name Name for the test block.
+ * @param array $supports Array defining block support configuration.
+ * @return WP_Block_Type The block type for the newly registered test block.
+ */
+ private function register_bordered_block_with_support( $block_name, $supports = array() ) {
+ $this->test_block_name = $block_name;
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'attributes' => array(
+ 'borderColor' => array(
+ 'type' => 'string',
+ ),
+ 'style' => array(
+ 'type' => 'object',
+ ),
+ ),
+ 'supports' => $supports,
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+
+ return $registry->get_registered( $this->test_block_name );
+ }
+
/**
* @ticket 55505
*/
@@ -159,4 +188,152 @@ public function test_radius_with_individual_skipped_serialization_block_supports
$this->assertSame( $expected, $actual );
}
+
+ /**
+ * @ticket 66269
+ */
+ public function test_border_color_preset_with_skipped_color_serialization() {
+ $block_type = $this->register_bordered_block_with_support(
+ 'test/border-color-preset-with-skipped-color-serialization',
+ array(
+ '__experimentalBorder' => array(
+ 'color' => true,
+ 'width' => true,
+ '__experimentalSkipSerialization' => array( 'color' ),
+ ),
+ )
+ );
+ $block_attrs = array(
+ 'borderColor' => 'red',
+ 'style' => array( 'border' => array( 'width' => '1px' ) ),
+ );
+
+ $this->assertSame(
+ array( 'style' => 'border-width:1px;' ),
+ wp_apply_border_support( $block_type, $block_attrs )
+ );
+ }
+
+ /**
+ * @ticket 66269
+ */
+ public function test_split_borders_with_color_support_only_keeps_side_style() {
+ $block_type = $this->register_bordered_block_with_support(
+ 'test/split-borders-with-color-support-only',
+ array(
+ '__experimentalBorder' => array(
+ 'color' => true,
+ ),
+ )
+ );
+ $block_attrs = array(
+ 'style' => array(
+ 'border' => array(
+ 'style' => 'solid',
+ 'top' => array(
+ 'color' => '#72aee6',
+ 'style' => 'dashed',
+ ),
+ ),
+ ),
+ );
+ $actual = wp_apply_border_support( $block_type, $block_attrs );
+ $expected = array(
+ 'style' => 'border-top-color:#72aee6;border-top-style:dashed;',
+ );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * @ticket 66269
+ */
+ public function test_split_borders_with_width_support_only_keeps_side_color() {
+ $block_type = $this->register_bordered_block_with_support(
+ 'test/split-borders-with-width-support-only',
+ array(
+ '__experimentalBorder' => array(
+ 'width' => true,
+ ),
+ )
+ );
+ $block_attrs = array(
+ 'borderColor' => 'red',
+ 'style' => array(
+ 'border' => array(
+ 'top' => array(
+ 'color' => '#72aee6',
+ 'width' => '2px',
+ ),
+ ),
+ ),
+ );
+ $actual = wp_apply_border_support( $block_type, $block_attrs );
+ $expected = array(
+ 'style' => 'border-top-width:2px;border-top-color:#72aee6;',
+ );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * @ticket 66269
+ */
+ public function test_split_borders_without_color_or_width_support_are_dropped() {
+ $block_type = $this->register_bordered_block_with_support(
+ 'test/split-borders-without-color-or-width-support',
+ array(
+ '__experimentalBorder' => array(
+ 'radius' => true,
+ 'style' => true,
+ ),
+ )
+ );
+ $block_attrs = array(
+ 'style' => array(
+ 'border' => array(
+ 'radius' => '5px',
+ 'top' => array(
+ 'style' => 'dashed',
+ 'width' => '2px',
+ ),
+ ),
+ ),
+ );
+ $actual = wp_apply_border_support( $block_type, $block_attrs );
+ $expected = array(
+ 'style' => 'border-radius:5px;',
+ );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * @ticket 66269
+ */
+ public function test_non_array_border_and_sides_are_ignored() {
+ $block_type = $this->register_bordered_block_with_support(
+ 'test/non-array-border-and-sides',
+ array(
+ '__experimentalBorder' => true,
+ )
+ );
+
+ $this->assertSame( array(), wp_apply_border_support( $block_type, array( 'style' => 'invalid' ) ) );
+ $this->assertSame( array(), wp_apply_border_support( $block_type, array( 'style' => array( 'border' => 'invalid' ) ) ) );
+ $this->assertSame(
+ array( 'style' => 'border-bottom-width:1px;' ),
+ wp_apply_border_support(
+ $block_type,
+ array(
+ 'style' => array(
+ 'border' => array(
+ 'top' => 'invalid',
+ 'bottom' => array( 'width' => '1px' ),
+ ),
+ ),
+ )
+ )
+ );
+ }
}
diff --git a/tests/phpunit/tests/block-supports/colors.php b/tests/phpunit/tests/block-supports/colors.php
index 30e4985c63ed9..1f4deead2ee8c 100644
--- a/tests/phpunit/tests/block-supports/colors.php
+++ b/tests/phpunit/tests/block-supports/colors.php
@@ -299,4 +299,78 @@ public function test_color_gradient_emitted_when_background_gradient_is_not_supp
$this->assertSame( $expected, $actual );
}
+
+ /**
+ * @ticket 66269
+ */
+ public function test_color_support_true_applies_text_and_background_colors() {
+ $this->test_block_name = 'test/color-support-true';
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'attributes' => array(
+ 'textColor' => array(
+ 'type' => 'string',
+ ),
+ 'backgroundColor' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'supports' => array(
+ 'color' => true,
+ ),
+ )
+ );
+
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $this->test_block_name );
+
+ $block_atts = array(
+ 'textColor' => 'contrast',
+ 'backgroundColor' => 'base',
+ );
+
+ $actual = wp_apply_colors_support( $block_type, $block_atts );
+ $expected = array(
+ 'class' => 'has-text-color has-contrast-color has-background has-base-background-color',
+ );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * @ticket 66269
+ */
+ public function test_color_text_support_disabled_skips_text_color() {
+ $this->test_block_name = 'test/color-text-support-disabled';
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'attributes' => array(
+ 'textColor' => array(
+ 'type' => 'string',
+ ),
+ ),
+ 'supports' => array(
+ 'color' => array(
+ 'text' => false,
+ ),
+ ),
+ )
+ );
+
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $this->test_block_name );
+
+ $block_atts = array(
+ 'textColor' => 'contrast',
+ );
+
+ $actual = wp_apply_colors_support( $block_type, $block_atts );
+ $expected = array();
+
+ $this->assertSame( $expected, $actual );
+ }
}
diff --git a/tests/phpunit/tests/block-supports/spacing.php b/tests/phpunit/tests/block-supports/spacing.php
index 2cfcd76d99af8..ac50818491655 100644
--- a/tests/phpunit/tests/block-supports/spacing.php
+++ b/tests/phpunit/tests/block-supports/spacing.php
@@ -164,4 +164,67 @@ public function test_margin_with_individual_skipped_serialization_block_supports
$this->assertSame( $expected, $actual );
}
+
+ /**
+ * @ticket 66269
+ */
+ public function test_unsupported_padding_is_not_applied() {
+ $this->test_block_name = 'test/unsupported-padding-is-not-applied';
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'attributes' => array(
+ 'style' => array(
+ 'type' => 'object',
+ ),
+ ),
+ 'supports' => array(
+ 'spacing' => array(
+ 'margin' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $this->test_block_name );
+ $block_atts = array(
+ 'style' => array(
+ 'spacing' => array(
+ 'margin' => '1px',
+ 'padding' => '2px',
+ ),
+ ),
+ );
+
+ $actual = wp_apply_spacing_support( $block_type, $block_atts );
+ $expected = array(
+ 'style' => 'margin:1px;',
+ );
+
+ $this->assertSame( $expected, $actual );
+ }
+
+ /**
+ * @ticket 66269
+ */
+ public function test_spacing_without_style_attribute() {
+ $this->test_block_name = 'test/spacing-without-style-attribute';
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'supports' => array(
+ 'spacing' => array(
+ 'margin' => true,
+ 'padding' => true,
+ ),
+ ),
+ )
+ );
+ $registry = WP_Block_Type_Registry::get_instance();
+ $block_type = $registry->get_registered( $this->test_block_name );
+
+ $this->assertSame( array(), wp_apply_spacing_support( $block_type, array() ) );
+ }
}
diff --git a/tests/phpunit/tests/block-supports/wpGetBackgroundClassesAndStyles.php b/tests/phpunit/tests/block-supports/wpGetBackgroundClassesAndStyles.php
new file mode 100644
index 0000000000000..45ee6e62df673
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/wpGetBackgroundClassesAndStyles.php
@@ -0,0 +1,172 @@
+assertSame( $expected, wp_get_background_classes_and_styles( $block_attributes ) );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_get_background_classes_and_styles() {
+ return array(
+ 'custom image gets default size' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'backgroundImage' => array( 'url' => 'https://example.com/image.jpg' ),
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-background',
+ 'style' => "background-image:url('https://example.com/image.jpg');background-size:cover;",
+ ),
+ ),
+ 'contain without position is centered' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'backgroundImage' => array( 'url' => 'https://example.com/image.jpg' ),
+ 'backgroundSize' => 'contain',
+ 'backgroundRepeat' => 'no-repeat',
+ 'backgroundAttachment' => 'fixed',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-background',
+ 'style' => "background-image:url('https://example.com/image.jpg');background-position:50% 50%;background-repeat:no-repeat;background-size:contain;background-attachment:fixed;",
+ ),
+ ),
+ 'preset gradient' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'gradient' => 'var:preset|gradient|vivid-cyan-blue-to-vivid-purple',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-background',
+ 'style' => 'background-image:var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple);',
+ ),
+ ),
+ 'gradient clipped to text has no class' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'gradient' => 'linear-gradient(red, blue)',
+ 'backgroundClip' => 'text',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'background-image:linear-gradient(red, blue);background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent;',
+ ),
+ ),
+ 'clip alone has no class' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'backgroundClip' => 'padding-box',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'background-clip:padding-box;-webkit-text-fill-color:currentColor;',
+ ),
+ ),
+ 'image clipped to text has no class' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'backgroundImage' => array( 'url' => 'https://example.com/image.jpg' ),
+ 'backgroundClip' => 'text',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => "background-image:url('https://example.com/image.jpg');background-size:cover;background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent;",
+ ),
+ ),
+ 'image without url gets default size' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'backgroundImage' => array( 'id' => 1 ),
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-background',
+ 'style' => 'background-size:cover;',
+ ),
+ ),
+ 'numeric position is not output' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'backgroundImage' => array( 'url' => 'https://example.com/image.jpg' ),
+ 'backgroundPosition' => 50,
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-background',
+ 'style' => "background-image:url('https://example.com/image.jpg');background-size:cover;",
+ ),
+ ),
+ 'zero gradient outputs nothing' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(
+ 'gradient' => 0,
+ ),
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'empty background' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => array(),
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'no style attribute' => array(
+ 'block_attributes' => array(),
+ 'expected' => array(),
+ ),
+ 'background is not an array' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'background' => 'linear-gradient(red, blue)',
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'attributes are not an array' => array(
+ 'block_attributes' => 'style',
+ 'expected' => array(),
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/wpGetBorderClassesAndStyles.php b/tests/phpunit/tests/block-supports/wpGetBorderClassesAndStyles.php
new file mode 100644
index 0000000000000..1707f2d71d27e
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/wpGetBorderClassesAndStyles.php
@@ -0,0 +1,131 @@
+assertSame( $expected, wp_get_border_classes_and_styles( $block_attributes ) );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_get_border_classes_and_styles() {
+ return array(
+ 'custom' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'border' => array(
+ 'radius' => '10px',
+ 'style' => 'solid',
+ 'width' => '2px',
+ 'color' => '#ff0000',
+ 'top' => array(
+ 'style' => 'dashed',
+ 'color' => '#00ff00',
+ 'width' => '3px',
+ ),
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-border-color',
+ 'style' => 'border-color:#ff0000;border-radius:10px;border-style:solid;border-width:2px;border-top-width:3px;border-top-color:#00ff00;border-top-style:dashed;',
+ ),
+ ),
+ 'preset wins over custom' => array(
+ 'block_attributes' => array(
+ 'borderColor' => 'red',
+ 'style' => array(
+ 'border' => array(
+ 'color' => '#ff0000',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-border-color has-red-border-color',
+ ),
+ ),
+ 'preset only' => array(
+ 'block_attributes' => array(
+ 'borderColor' => 'red',
+ ),
+ 'expected' => array(
+ 'class' => 'has-border-color has-red-border-color',
+ ),
+ ),
+ 'numeric' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'border' => array(
+ 'radius' => 5,
+ 'width' => 2,
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'border-radius:5px;border-width:2px;',
+ ),
+ ),
+ 'zero' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'border' => array(
+ 'radius' => 0,
+ 'width' => 0,
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'border-radius:0px;border-width:0px;',
+ ),
+ ),
+ 'empty' => array(
+ 'block_attributes' => array(),
+ 'expected' => array(),
+ ),
+ 'malformed border' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'border' => 'solid',
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'malformed side' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'border' => array(
+ 'top' => '1px solid',
+ ),
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'malformed style' => array(
+ 'block_attributes' => array(
+ 'style' => 'border:1px solid',
+ ),
+ 'expected' => array(),
+ ),
+ 'malformed non-array' => array(
+ 'block_attributes' => 'border',
+ 'expected' => array(),
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/wpGetColorClassesAndStyles.php b/tests/phpunit/tests/block-supports/wpGetColorClassesAndStyles.php
new file mode 100644
index 0000000000000..df998dac26409
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/wpGetColorClassesAndStyles.php
@@ -0,0 +1,118 @@
+assertSame( $expected, wp_get_color_classes_and_styles( $block_attributes ) );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_get_color_classes_and_styles() {
+ return array(
+ 'custom' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'color' => array(
+ 'text' => '#d92828',
+ 'background' => '#ffffff',
+ 'gradient' => 'linear-gradient(135deg,rgb(6,147,227) 0%,rgb(155,81,224) 100%)',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-text-color has-background',
+ 'style' => 'color:#d92828;background-color:#ffffff;background:linear-gradient(135deg,rgb(6,147,227) 0%,rgb(155,81,224) 100%);',
+ ),
+ ),
+ 'preset' => array(
+ 'block_attributes' => array(
+ 'textColor' => 'contrast',
+ 'backgroundColor' => 'base',
+ 'gradient' => 'vivid-cyan-blue-to-vivid-purple',
+ ),
+ 'expected' => array(
+ 'class' => 'has-text-color has-contrast-color has-background has-base-background-color has-vivid-cyan-blue-to-vivid-purple-gradient-background',
+ ),
+ ),
+ 'preset wins over custom' => array(
+ 'block_attributes' => array(
+ 'textColor' => 'contrast',
+ 'style' => array(
+ 'color' => array(
+ 'text' => '#d92828',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-text-color has-contrast-color',
+ ),
+ ),
+ 'null preset wins over custom' => array(
+ 'block_attributes' => array(
+ 'textColor' => null,
+ 'style' => array(
+ 'color' => array(
+ 'text' => '#d92828',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'class' => 'has-text-color',
+ ),
+ ),
+ 'numeric' => array(
+ 'block_attributes' => array(
+ 'textColor' => 1,
+ ),
+ 'expected' => array(
+ 'class' => 'has-text-color has-1-color',
+ ),
+ ),
+ 'zero' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'color' => array(
+ 'text' => '0',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'color:0;',
+ ),
+ ),
+ 'empty' => array(
+ 'block_attributes' => array(),
+ 'expected' => array(),
+ ),
+ 'malformed color' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'color' => '#d92828',
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'malformed attributes' => array(
+ 'block_attributes' => 'color',
+ 'expected' => array(),
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/wpGetShadowClassesAndStyles.php b/tests/phpunit/tests/block-supports/wpGetShadowClassesAndStyles.php
new file mode 100644
index 0000000000000..9034c53b71570
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/wpGetShadowClassesAndStyles.php
@@ -0,0 +1,67 @@
+assertSame( $expected, wp_get_shadow_classes_and_styles( $block_attributes ) );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_get_shadow_classes_and_styles() {
+ return array(
+ 'custom shadow' => array(
+ 'block_attributes' => array( 'style' => array( 'shadow' => '1px 1px 1px #000' ) ),
+ 'expected' => array( 'style' => 'box-shadow:1px 1px 1px #000;' ),
+ ),
+ 'preset shadow' => array(
+ 'block_attributes' => array( 'style' => array( 'shadow' => 'var:preset|shadow|natural' ) ),
+ 'expected' => array( 'style' => 'box-shadow:var(--wp--preset--shadow--natural);' ),
+ ),
+ 'numeric shadow' => array(
+ 'block_attributes' => array( 'style' => array( 'shadow' => 5 ) ),
+ 'expected' => array(),
+ ),
+ 'zero shadow' => array(
+ 'block_attributes' => array( 'style' => array( 'shadow' => 0 ) ),
+ 'expected' => array(),
+ ),
+ 'empty shadow' => array(
+ 'block_attributes' => array( 'style' => array( 'shadow' => '' ) ),
+ 'expected' => array(),
+ ),
+ 'no style attribute' => array(
+ 'block_attributes' => array(),
+ 'expected' => array(),
+ ),
+ 'non-array style' => array(
+ 'block_attributes' => array( 'style' => 'box-shadow:1px 1px 1px #000' ),
+ 'expected' => array(),
+ ),
+ 'array shadow' => array(
+ 'block_attributes' => array( 'style' => array( 'shadow' => array( '1px 1px 1px #000' ) ) ),
+ 'expected' => array(),
+ ),
+ 'non-array attributes' => array(
+ 'block_attributes' => null,
+ 'expected' => array(),
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/wpGetSpacingClassesAndStyles.php b/tests/phpunit/tests/block-supports/wpGetSpacingClassesAndStyles.php
new file mode 100644
index 0000000000000..cc10e9b24e908
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/wpGetSpacingClassesAndStyles.php
@@ -0,0 +1,112 @@
+assertSame( $expected, wp_get_spacing_classes_and_styles( $block_attributes ) );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_get_spacing_classes_and_styles() {
+ return array(
+ 'custom values' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => array(
+ 'padding' => array(
+ 'top' => '1px',
+ 'left' => '2em',
+ ),
+ 'margin' => '3px',
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'padding-top:1px;padding-left:2em;margin:3px;',
+ ),
+ ),
+ 'presets' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => array(
+ 'padding' => 'var:preset|spacing|40',
+ 'margin' => array( 'bottom' => 'var:preset|spacing|20' ),
+ ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'padding:var(--wp--preset--spacing--40);margin-bottom:var(--wp--preset--spacing--20);',
+ ),
+ ),
+ 'block gap is ignored' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => array( 'blockGap' => '2em' ),
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'numeric values' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => array( 'padding' => array( 'top' => 10 ) ),
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'zero' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => array( 'margin' => '0' ),
+ ),
+ ),
+ 'expected' => array(
+ 'style' => 'margin:0;',
+ ),
+ ),
+ 'empty spacing' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => array(),
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'no style' => array(
+ 'block_attributes' => array(),
+ 'expected' => array(),
+ ),
+ 'malformed spacing' => array(
+ 'block_attributes' => array(
+ 'style' => array(
+ 'spacing' => '10px',
+ ),
+ ),
+ 'expected' => array(),
+ ),
+ 'malformed style' => array(
+ 'block_attributes' => array(
+ 'style' => 'padding:10px',
+ ),
+ 'expected' => array(),
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
index 9ebde814b0437..730a33d9e5ed5 100644
--- a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
+++ b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
@@ -71,6 +71,7 @@ public function filter_set_theme_root() {
* @ticket 61858
* @ticket 64974
* @ticket 66094
+ * @ticket 66269
*
* @covers ::wp_render_background_support
*
@@ -269,6 +270,23 @@ public function data_background_block_support() {
'expected_wrapper' => 'Content
',
'wrapper' => 'Content
',
),
+ 'background gradient is applied without the image when the image skips serialization' => array(
+ 'theme_name' => 'block-theme-child-with-fluid-typography',
+ 'block_name' => 'test/background-image-skipped-gradient-applied',
+ 'background_settings' => array(
+ 'backgroundImage' => true,
+ 'gradient' => true,
+ '__experimentalSkipSerialization' => array( 'backgroundImage' ),
+ ),
+ 'background_style' => array(
+ 'backgroundImage' => array(
+ 'url' => 'https://example.com/image.jpg',
+ ),
+ 'gradient' => 'linear-gradient(135deg,rgb(255,0,0) 0%,rgb(0,0,255) 100%)',
+ ),
+ 'expected_wrapper' => 'Content
',
+ 'wrapper' => 'Content
',
+ ),
'background image style is not applied if the block does not support background image' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
'block_name' => 'test/background-rules-are-not-output',