Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions src/wp-includes/block-supports/background.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function wp_register_background_support( $block_type ) {
* @since 6.5.0 Added support for `backgroundPosition` and `backgroundRepeat` output.
* @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.
*
* @access private
*
Expand All @@ -51,34 +52,49 @@ function wp_register_background_support( $block_type ) {
* @return string Filtered block content.
*/
function wp_render_background_support( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$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 );
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$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 );

if (
! $has_background_image_support ||
wp_should_skip_block_supports_serialization( $block_type, 'background', 'backgroundImage' ) ||
( ! $has_background_image_support && ! $has_background_gradient_support ) ||
! isset( $block_attributes['style']['background'] )
) {
return $block_content;
}

$background_styles = array();
$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;
// 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' );

if ( ! empty( $background_styles['backgroundImage'] ) ) {
$background_styles['backgroundSize'] = $background_styles['backgroundSize'] ?? 'cover';
if ( $skip_background_image && $skip_background_gradient ) {
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 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%';
if ( ! empty( $background_styles['backgroundImage'] ) ) {
$background_styles['backgroundSize'] = $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%';
}
}
}

if ( ! $skip_background_gradient ) {
$background_styles['gradient'] = $block_attributes['style']['background']['gradient'] ?? null;
}

$styles = wp_style_engine_get_styles( array( 'background' => $background_styles ) );

if ( ! empty( $styles['css'] ) ) {
Expand Down
12 changes: 11 additions & 1 deletion src/wp-includes/block-supports/colors.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,17 @@ function wp_apply_colors_support( $block_type, $block_attributes ) {
}

// Gradients.
if ( $has_gradients_support && ! wp_should_skip_block_supports_serialization( $block_type, 'color', '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_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;
Expand Down
28 changes: 23 additions & 5 deletions src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ class WP_Theme_JSON {
*
* @since 6.2.0
* @since 6.6.0 Added background-image properties.
* @since 7.1.0 Added `background.gradient` to `background-image` paths.
* @var array
*/
const INDIRECT_PROPERTIES_METADATA = array(
Expand All @@ -348,6 +349,7 @@ class WP_Theme_JSON {
),
'background-image' => array(
array( 'background', 'backgroundImage', 'url' ),
array( 'background', 'gradient' ),
),
);

Expand Down Expand Up @@ -412,6 +414,7 @@ class WP_Theme_JSON {
* @since 7.0.0 Added type markers to the schema for boolean values.
* Added support for `dimensions.width` and `dimensions.height`.
* Added support for `typography.textIndent`.
* @since 7.1.0 Added support for `background.gradient`.
* @var array
*/
const VALID_SETTINGS = array(
Expand All @@ -420,6 +423,7 @@ class WP_Theme_JSON {
'background' => array(
'backgroundImage' => null,
'backgroundSize' => null,
'gradient' => null,
),
'border' => array(
'color' => null,
Expand Down Expand Up @@ -549,6 +553,7 @@ class WP_Theme_JSON {
* @since 6.5.0 Added support for `dimensions.aspectRatio`.
* @since 6.6.0 Added `background` sub properties to top-level only.
* @since 7.0.0 Added support for `dimensions.width` and `dimensions.height`.
* @since 7.1.0 Added `background.gradient`.
* @var array
*/
const VALID_STYLES = array(
Expand All @@ -558,6 +563,7 @@ class WP_Theme_JSON {
'backgroundRepeat' => null,
'backgroundSize' => null,
'backgroundAttachment' => null,
'gradient' => null,
),
'border' => array(
'color' => null,
Expand Down Expand Up @@ -791,11 +797,13 @@ public static function get_element_class_name( $element ) {
* @since 6.4.0 Added `background.backgroundImage`.
* @since 6.5.0 Added `background.backgroundSize` and `dimensions.aspectRatio`.
* @since 7.0.0 Added `dimensions.width` and `dimensions.height`.
* @since 7.1.0 Added `background.gradient`.
* @var array
*/
const APPEARANCE_TOOLS_OPT_INS = array(
array( 'background', 'backgroundImage' ),
array( 'background', 'backgroundSize' ),
array( 'background', 'gradient' ),
array( 'border', 'color' ),
array( 'border', 'radius' ),
array( 'border', 'style' ),
Expand Down Expand Up @@ -2434,11 +2442,21 @@ protected static function compute_style_properties( $styles, $settings = array()
* For uploaded image (images with a database ID), apply size and position defaults,
* equal to those applied in block supports in lib/background.php.
*/
if ( 'background-image' === $css_property && ! empty( $value ) ) {
$background_styles = wp_style_engine_get_styles(
array( 'background' => array( 'backgroundImage' => $value ) )
);
$value = $background_styles['declarations'][ $css_property ];
if ( 'background-image' === $css_property ) {
$background_image_input = array();
if ( ! empty( $value ) ) {
$background_image_input['backgroundImage'] = $value;
}
$gradient_value = $styles['background']['gradient'] ?? null;
if ( ! empty( $gradient_value ) ) {
$background_image_input['gradient'] = $gradient_value;
}
if ( ! empty( $background_image_input ) ) {
$background_styles = wp_style_engine_get_styles(
array( 'background' => $background_image_input )
);
$value = $background_styles['declarations'][ $css_property ] ?? null;
}
}
if ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) {
if ( 'background-size' === $css_property ) {
Expand Down
55 changes: 55 additions & 0 deletions src/wp-includes/kses.php
Original file line number Diff line number Diff line change
Expand Up @@ -2885,6 +2885,61 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
return $css;
}

/**
* Allow combined gradient and url() background-image values in inline styles.
*
* WordPress's safecss_filter_attr() handles gradient and url() values
* separately for background-image, but fails when both appear in a single
* comma-separated declaration. The url() portion is stripped from the test
* string before the filter runs, but the gradient regex in core expects the
* gradient to be the only value and doesn't match when a trailing comma and
* whitespace remain. This leaves parentheses in the test string, which
* triggers the unsafe-character check.
*
* This filter catches that case: the test string still contains a valid
* gradient function with only commas and whitespace remaining after the
* url() was removed.
*
* @since 7.1.0
*
* @param bool $allow_css Whether the CSS is allowed.
* @param string $css_test_string The CSS declaration to test.
* @return bool Whether the CSS is allowed.
*/
function wp_kses_allow_background_image_combined( $allow_css, $css_test_string ) {
if ( $allow_css ) {
return $allow_css;
}

/*
* The test string at this point has url() values already removed by
* safecss_filter_attr. What remains is a gradient with comma/whitespace
* residue where the url() was stripped. Two possible forms:
*
* Gradient first: "background-image:<gradient>(...), "
* URL first: "background-image:, <gradient>(...)"
*
* A trailing or leading comma (with optional whitespace) must be present
* to confirm a url() was actually removed. Without that residue, the
* gradient alone would already pass core's own check.
*/
$gradient_pattern = '(?:linear|radial|conic|repeating-linear|repeating-radial|repeating-conic)-gradient\((?:[^()]|\([^()]*\))*\)';
$var_pattern = 'var\(--[a-zA-Z0-9_-]+(?:--[a-zA-Z0-9_-]+)*\)';
$value_pattern = "(?:$gradient_pattern|$var_pattern)";

// Gradient/var first, then comma+whitespace residue from stripped url().
$pattern_gradient_first = '/^background-image\s*:\s*' . $value_pattern . '\s*,[\s,]*$/';
// Stripped url() first (comma+whitespace residue), then gradient/var.
$pattern_url_first = '/^background-image\s*:[\s,]*,\s*' . $value_pattern . '\s*$/';

if ( preg_match( $pattern_gradient_first, $css_test_string ) || preg_match( $pattern_url_first, $css_test_string ) ) {
return true;
}

return $allow_css;
}
add_filter( 'safecss_filter_attr_allow_css', 'wp_kses_allow_background_image_combined', 10, 2 );

/**
* Helper function to add global attributes to a tag in the allowed HTML list.
*
Expand Down
20 changes: 20 additions & 0 deletions src/wp-includes/style-engine/class-wp-style-engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class WP_Style_Engine {
* - value_func => (string) the name of a function to generate a CSS definition array for a particular style object. The output of this function should be `array( "$property" => "$value", ... )`.
*
* @since 6.1.0
* @since 7.1.0 Added `background.gradient` property.
* @var array
*/
const BLOCK_STYLE_DEFINITIONS_METADATA = array(
Expand Down Expand Up @@ -83,6 +84,18 @@ final class WP_Style_Engine {
),
'path' => array( 'background', 'backgroundAttachment' ),
),
'gradient' => array(
'property_keys' => array(
'default' => 'background-image',
),
'css_vars' => array(
'gradient' => '--wp--preset--gradient--$slug',
),
'path' => array( 'background', 'gradient' ),
'classnames' => array(
'has-background' => true,
),
),
),
'color' => array(
'text' => array(
Expand Down Expand Up @@ -489,6 +502,13 @@ public static function parse_block_styles( $block_styles, $options ) {

$css_declarations = static::get_css_declarations( $style_value, $style_definition, $options );
if ( ! empty( $css_declarations ) ) {
/*
* Combine background gradient and background image into a single
* comma-separated background-image value, matching the JS style engine.
*/
if ( isset( $css_declarations['background-image'] ) && isset( $parsed_styles['declarations']['background-image'] ) ) {
$css_declarations['background-image'] = $css_declarations['background-image'] . ', ' . $parsed_styles['declarations']['background-image'];
}
$parsed_styles['declarations'] = array_merge( $parsed_styles['declarations'], $css_declarations );
}
}
Expand Down
Loading
Loading