Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background block supports: add support for top-level styles in theme.json #6482

Closed
49 changes: 13 additions & 36 deletions src/wp-includes/block-supports/background.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ function wp_register_background_support( $block_type ) {
*
* @since 6.4.0
* @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.
*
* @access private
*
* @param string $block_content Rendered block content.
Expand All @@ -54,52 +56,27 @@ function wp_render_background_support( $block_content, $block ) {

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

$background_image_source = isset( $block_attributes['style']['background']['backgroundImage']['source'] )
? $block_attributes['style']['background']['backgroundImage']['source']
: null;
$background_image_url = isset( $block_attributes['style']['background']['backgroundImage']['url'] )
? $block_attributes['style']['background']['backgroundImage']['url']
: null;

if ( ! $background_image_source && ! $background_image_url ) {
return $block_content;
}

$background_size = isset( $block_attributes['style']['background']['backgroundSize'] )
? $block_attributes['style']['background']['backgroundSize']
: 'cover';
$background_position = isset( $block_attributes['style']['background']['backgroundPosition'] )
? $block_attributes['style']['background']['backgroundPosition']
: null;
$background_repeat = isset( $block_attributes['style']['background']['backgroundRepeat'] )
? $block_attributes['style']['background']['backgroundRepeat']
: null;

$background_block_styles = array();
$background_styles = array();
$background_styles['backgroundImage'] = isset( $block_attributes['style']['background']['backgroundImage'] ) ? $block_attributes['style']['background']['backgroundImage'] : array();

if (
'file' === $background_image_source &&
$background_image_url
) {
// Set file based background URL.
$background_block_styles['backgroundImage']['url'] = $background_image_url;
// Only output the background size and repeat when an image url is set.
$background_block_styles['backgroundSize'] = $background_size;
$background_block_styles['backgroundRepeat'] = $background_repeat;
$background_block_styles['backgroundPosition'] = $background_position;
if ( ! empty( $background_styles['backgroundImage'] ) ) {
$background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover';
$background_styles['backgroundPosition'] = isset( $block_attributes['style']['background']['backgroundPosition'] ) ? $block_attributes['style']['background']['backgroundPosition'] : null;
$background_styles['backgroundRepeat'] = isset( $block_attributes['style']['background']['backgroundRepeat'] ) ? $block_attributes['style']['background']['backgroundRepeat'] : null;

// If the background size is set to `contain` and no position is set, set the position to `center`.
if ( 'contain' === $background_size && ! isset( $background_position ) ) {
$background_block_styles['backgroundPosition'] = 'center';
if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) {
$background_styles['backgroundPosition'] = 'center';
}
}

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

if ( ! empty( $styles['css'] ) ) {
// Inject background styles to the first element, presuming it's the wrapper, if it exists.
Expand Down
73 changes: 68 additions & 5 deletions src/wp-includes/class-wp-theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,18 @@ class WP_Theme_JSON {
* @since 6.3.0 Added `column-count` property.
* @since 6.4.0 Added `writing-mode` property.
* @since 6.5.0 Added `aspect-ratio` property.
* @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
*
* @var array
*/
const PROPERTIES_METADATA = array(
'aspect-ratio' => array( 'dimensions', 'aspectRatio' ),
'background' => array( 'color', 'gradient' ),
'background-color' => array( 'color', 'background' ),
'background-image' => array( 'background', 'backgroundImage' ),
'background-position' => array( 'background', 'backgroundPosition' ),
'background-repeat' => array( 'background', 'backgroundRepeat' ),
'background-size' => array( 'background', 'backgroundSize' ),
'border-radius' => array( 'border', 'radius' ),
'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ),
'border-top-right-radius' => array( 'border', 'radius', 'topRight' ),
Expand Down Expand Up @@ -283,9 +288,10 @@ class WP_Theme_JSON {
*
* Indirect properties are not output directly by `compute_style_properties`,
* but are used elsewhere in the processing of global styles. The indirect
* property is used to validate whether or not a style value is allowed.
* property is used to validate whether a style value is allowed.
*
* @since 6.2.0
* @since 6.6.0 Added background-image properties.
*
* @var array
*/
Expand All @@ -303,6 +309,9 @@ class WP_Theme_JSON {
array( 'layout', 'contentSize' ),
array( 'layout', 'wideSize' ),
),
'background-image' => array(
array( 'background', 'backgroundImage', 'url' ),
),
);

/**
Expand Down Expand Up @@ -482,10 +491,17 @@ class WP_Theme_JSON {
* @since 6.2.0 Added `outline`, and `minHeight` properties.
* @since 6.3.0 Added support for `typography.textColumns`.
* @since 6.5.0 Added support for `dimensions.aspectRatio`.
* @since 6.6.0 Added `background` sub properties to top-level only.
*
* @var array
*/
const VALID_STYLES = array(
'background' => array(
'backgroundImage' => 'top',
'backgroundPosition' => 'top',
'backgroundRepeat' => 'top',
'backgroundSize' => 'top',
),
'border' => array(
'color' => null,
'radius' => null,
Expand Down Expand Up @@ -2052,6 +2068,7 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
* @since 6.1.0 Added `$theme_json`, `$selector`, and `$use_root_padding` parameters.
* @since 6.5.0 Output a `min-height: unset` rule when `aspect-ratio` is set.
* @since 6.6.0 Passing current theme JSON settings to wp_get_typography_font_size_value().
* Process background properties using the Style Engine.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
*
* @param array $styles Styles to process.
* @param array $settings Theme settings.
Expand Down Expand Up @@ -2105,6 +2122,12 @@ protected static function compute_style_properties( $styles, $settings = array()
}
}

// Processes background styles.
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) {
$background_styles = wp_style_engine_get_styles( array( 'background' => $styles['background'] ) );
$value = isset( $background_styles['declarations'][ $css_property ] ) ? $background_styles['declarations'][ $css_property ] : $value;
}

// 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 ) ) {
Expand Down Expand Up @@ -2484,6 +2507,7 @@ private static function get_block_nodes( $theme_json ) {
* Gets the CSS rules for a particular block from theme.json.
*
* @since 6.1.0
* @since 6.6.0 Setting a min-height of HTML when root styles have a background gradient or image.
*
* @param array $block_metadata Metadata about the block to get styles for.
*
Expand All @@ -2495,6 +2519,7 @@ public function get_styles_for_block( $block_metadata ) {
$selector = $block_metadata['selector'];
$settings = isset( $this->theme_json['settings'] ) ? $this->theme_json['settings'] : array();
$feature_declarations = static::get_feature_declarations_for_node( $block_metadata, $node );
$is_root_selector = static::ROOT_BLOCK_SELECTOR === $selector;

// If there are style variations, generate the declarations for them, including any feature selectors the block may have.
$style_variation_declarations = array();
Expand Down Expand Up @@ -2577,15 +2602,53 @@ static function ( $pseudo_selector ) use ( $selector ) {
$block_rules = '';

/*
* 1. Separate the declarations that use the general selector
* 1. Bespoke declaration modifiers:
* - 'filter': Separate the declarations that use the general selector
* from the ones using the duotone selector.
* - 'background|background-image': set the html min-height to 100%
* to ensure the background covers the entire viewport.
*/
$declarations_duotone = array();
$declarations_duotone = array();
$should_set_root_min_height = false;

foreach ( $declarations as $index => $declaration ) {
if ( 'filter' === $declaration['name'] ) {
/*
* 'unset' filters happen when a filter is unset
* in the site-editor UI. Because the 'unset' value
* in the user origin overrides the value in the
* theme origin, we can skip rendering anything
* here as no filter needs to be applied anymore.
* So only add declarations to with values other
* than 'unset'.
*/
if ( 'unset' !== $declaration['value'] ) {
$declarations_duotone[] = $declaration;
}
unset( $declarations[ $index ] );
$declarations_duotone[] = $declaration;
}

if ( $is_root_selector && ( 'background-image' === $declaration['name'] || 'background' === $declaration['name'] ) ) {
$should_set_root_min_height = true;
}
}

/*
* If root styles has a background-image or a background (gradient) set,
* set the min-height to '100%'. Minus `--wp-admin--admin-bar--height` for logged-in view.
* Setting the CSS rule on the HTML tag ensures background gradients and images behave similarly,
* and matches the behavior of the site editor.
*/
if ( $should_set_root_min_height ) {
$block_rules .= static::to_ruleset(
'html',
array(
array(
'name' => 'min-height',
'value' => 'calc(100% - var(--wp-admin--admin-bar--height, 0px))',
),
)
);
}

// Update declarations if there are separators with only background color defined.
Expand All @@ -2603,7 +2666,7 @@ static function ( $pseudo_selector ) use ( $selector ) {

// 4. Generate Layout block gap styles.
if (
static::ROOT_BLOCK_SELECTOR !== $selector &&
! $is_root_selector &&
! empty( $block_metadata['name'] )
) {
$block_rules .= $this->get_layout_styles( $block_metadata );
Expand Down
16 changes: 6 additions & 10 deletions tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public function filter_set_theme_root() {
*
* @ticket 59357
* @ticket 60175
* @ticket 61123
*
* @covers ::wp_render_background_support
*
Expand Down Expand Up @@ -132,8 +133,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
'url' => 'https://example.com/image.jpg',
),
),
'expected_wrapper' => '<div class="has-background" style="background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
Expand All @@ -147,8 +147,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
'url' => 'https://example.com/image.jpg',
),
'backgroundRepeat' => 'no-repeat',
'backgroundSize' => 'contain',
Expand All @@ -164,8 +163,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
'url' => 'https://example.com/image.jpg',
),
),
'expected_wrapper' => '<div class="wp-block-test has-background" style="color: red;background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
Expand All @@ -179,8 +177,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
'url' => 'https://example.com/image.jpg',
),
),
'expected_wrapper' => '<div class="wp-block-test has-background" style="color: red;font-size: 15px;background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
Expand All @@ -194,8 +191,7 @@ public function data_background_block_support() {
),
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
'url' => 'https://example.com/image.jpg',
),
),
'expected_wrapper' => '<div>Content</div>',
Expand Down
67 changes: 67 additions & 0 deletions tests/phpunit/tests/theme/wpThemeJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -4984,6 +4984,73 @@ public function test_get_shadow_styles_for_blocks() {
$this->assertSame( $expected_styles, $theme_json->get_stylesheet() );
}

/**
* Tests that theme background image styles are correctly generated.
*
* @ticket 61123
*/
public function test_get_top_level_background_image_styles() {
$theme_json = new WP_Theme_JSON(
array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'background' => array(
'backgroundImage' => array(
'url' => 'http://example.org/image.png',
),
'backgroundSize' => 'contain',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
'blocks' => array(
'core/paragraph' => array(
'background' => array(
'backgroundImage' => array(
'url' => 'http://example.org/image.png',
),
'backgroundSize' => 'cover',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
),
),
'elements' => array(
'button' => array(
'background' => array(
'backgroundImage' => array(
'url' => 'http://example.org/image.png',
),
'backgroundSize' => 'cover',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
),
),
),
)
);

$expected_styles = "body { margin: 0; }.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; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}body{background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}";
$this->assertSame( $expected_styles, $theme_json->get_stylesheet(), 'Styles returned from "::get_stylesheet()" with top-level background styles type does not match expectations' );

$theme_json = new WP_Theme_JSON(
array(
'version' => WP_Theme_JSON::LATEST_SCHEMA,
'styles' => array(
'background' => array(
'backgroundImage' => "url('http://example.org/image.png')",
'backgroundSize' => 'contain',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
),
)
);

$expected_styles = "body { margin: 0; }.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; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}body{background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}";
$this->assertSame( $expected_styles, $theme_json->get_stylesheet(), 'Styles returned from "::get_stylesheet()" with top-level background image as string type does not match expectations' );
}

/**
* @ticket 57536
*/
Expand Down
Loading