Skip to content

Commit dfff16f

Browse files
committed
Style Engine: Preserve important state declarations
Before, state styles appended `!important` directly to CSS values before Style Engine sanitization. For gradient values, this caused `safecss_filter_attr(`) to reject otherwise valid declarations, so responsive/state gradient CSS might not have been emitted This commit stores `!important` as declaration metadata in the Style Engine, preserves it through rule generation and optimization, and appends it only after sanitization. Props andrewserong, ramonopoly, wildworks. Fixes #65561. git-svn-id: https://develop.svn.wordpress.org/trunk@62720 602fd350-edb4-49c9-b593-d223f7449a82
1 parent c94fa58 commit dfff16f

10 files changed

Lines changed: 337 additions & 46 deletions

File tree

src/wp-includes/block-supports/states.php

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,13 @@ function wp_get_state_declarations_with_background_resets( $declarations ) {
122122

123123
/*
124124
* When the state sets a solid background-color but no gradient of its own,
125-
* emit `background-image: unset !important` to clear any gradient (whether
126-
* stored as the `background` shorthand or as `background-image`) that was
127-
* applied to the default / normal state via an inline style attribute.
125+
* emit `background-image: unset` to clear any gradient (whether stored as
126+
* the `background` shorthand or as `background-image`) that was applied to
127+
* the default / normal state via an inline style attribute. The declaration
128+
* is marked important when the state rule is registered with the style engine.
128129
*/
129130
if ( $has_background_color && ! $has_background && ! $has_background_image ) {
130-
$declarations['background-image'] = 'unset !important';
131+
$declarations['background-image'] = 'unset';
131132
}
132133

133134
return $declarations;
@@ -677,26 +678,49 @@ function wp_render_block_states_support( $block_content, $block ) {
677678
*/
678679
$style_rules = array();
679680
foreach ( $css_rules as $rule ) {
680-
$declarations = $rule['declarations'];
681-
foreach ( $declarations as $property => $value ) {
682-
$declarations[ $property ] = is_string( $value ) && str_contains( $value, '!important' )
683-
? $value
684-
: $value . ' !important';
681+
$declarations = $rule['declarations'];
682+
$important_declaration_values = wp_get_state_declarations_with_background_resets( $declarations );
683+
$important_declarations = new WP_Style_Engine_CSS_Declarations();
684+
foreach ( $important_declaration_values as $property => $value ) {
685+
$important_declarations->add_declaration(
686+
$property,
687+
$value,
688+
array(
689+
'important' => true,
690+
)
691+
);
692+
}
693+
$selector = wp_build_state_selector(
694+
".$unique_class",
695+
$rule['selector'],
696+
$rule['state']
697+
);
698+
$important_style_rule = array(
699+
'selector' => $selector,
700+
'declarations' => $important_declarations,
701+
);
702+
if ( ! empty( $rule['rules_group'] ) ) {
703+
$important_style_rule['rules_group'] = $rule['rules_group'];
685704
}
686-
$declarations = wp_get_state_declarations_with_fallback_border_styles( $declarations );
687-
$declarations = wp_get_state_declarations_with_background_resets( $declarations );
688-
$style_rule = array(
689-
'selector' => wp_build_state_selector(
690-
".$unique_class",
691-
$rule['selector'],
692-
$rule['state']
693-
),
694-
'declarations' => $declarations,
705+
$style_rules[] = $important_style_rule;
706+
707+
$fallback_declarations = wp_get_state_declarations_with_fallback_border_styles( $declarations );
708+
foreach ( array_keys( $declarations ) as $property ) {
709+
unset( $fallback_declarations[ $property ] );
710+
}
711+
712+
if ( empty( $fallback_declarations ) ) {
713+
continue;
714+
}
715+
716+
$fallback_style_rule = array(
717+
'selector' => $selector,
718+
'declarations' => $fallback_declarations,
695719
);
696720
if ( ! empty( $rule['rules_group'] ) ) {
697-
$style_rule['rules_group'] = $rule['rules_group'];
721+
$fallback_style_rule['rules_group'] = $rule['rules_group'];
698722
}
699-
$style_rules[] = $style_rule;
723+
$style_rules[] = $fallback_style_rule;
700724
}
701725

702726
wp_style_engine_get_stylesheet_from_css_rules(

src/wp-includes/style-engine.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,18 @@ function wp_style_engine_get_styles( $block_styles, $options = array() ) {
114114
*
115115
* @since 6.1.0
116116
* @since 6.6.0 Added support for `$rules_group` in the `$css_rules` array.
117+
* @since 7.1.0 Extended `declarations` in `$css_rules` array to accept `WP_Style_Engine_CSS_Declarations` object.
117118
*
118119
* @param array $css_rules {
119120
* Required. A collection of CSS rules.
120121
*
121122
* @type array ...$0 {
122-
* @type string $rules_group A parent CSS selector in the case of nested CSS,
123-
* or a CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
124-
* @type string $selector A CSS selector.
125-
* @type string[] $declarations An associative array of CSS definitions,
126-
* e.g. `array( "$property" => "$value", "$property" => "$value" )`.
123+
* @type string $rules_group A parent CSS selector in the case of nested CSS,
124+
* or a CSS nested @rule, such as `@media (min-width: 80rem)` or `@layer module`.
125+
* @type string $selector A CSS selector.
126+
* @type string[]|WP_Style_Engine_CSS_Declarations $declarations An associative array of CSS definitions,
127+
* e.g. `array( "$property" => "$value", "$property" => "$value" )`,
128+
* or a WP_Style_Engine_CSS_Declarations object.
127129
* }
128130
* }
129131
* @param array $options {
@@ -153,16 +155,21 @@ function wp_style_engine_get_stylesheet_from_css_rules( $css_rules, $options = a
153155

154156
$css_rule_objects = array();
155157
foreach ( $css_rules as $css_rule ) {
156-
if ( empty( $css_rule['selector'] ) || empty( $css_rule['declarations'] ) || ! is_array( $css_rule['declarations'] ) ) {
158+
$declarations = $css_rule['declarations'] ?? null;
159+
if (
160+
empty( $css_rule['selector'] ) ||
161+
empty( $declarations ) ||
162+
( ! is_array( $declarations ) && ! $declarations instanceof WP_Style_Engine_CSS_Declarations )
163+
) {
157164
continue;
158165
}
159166

160167
$rules_group = $css_rule['rules_group'] ?? null;
161168
if ( ! empty( $options['context'] ) ) {
162-
WP_Style_Engine::store_css_rule( $options['context'], $css_rule['selector'], $css_rule['declarations'], $rules_group );
169+
WP_Style_Engine::store_css_rule( $options['context'], $css_rule['selector'], $declarations, $rules_group );
163170
}
164171

165-
$css_rule_objects[] = new WP_Style_Engine_CSS_Rule( $css_rule['selector'], $css_rule['declarations'], $rules_group );
172+
$css_rule_objects[] = new WP_Style_Engine_CSS_Rule( $css_rule['selector'], $declarations, $rules_group );
166173
}
167174

168175
if ( empty( $css_rule_objects ) ) {

src/wp-includes/style-engine/class-wp-style-engine-css-declarations.php

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ class WP_Style_Engine_CSS_Declarations {
2626
*/
2727
protected $declarations = array();
2828

29+
/**
30+
* CSS declaration options keyed by property name.
31+
*
32+
* @since 7.1.0
33+
*
34+
* @var array
35+
*/
36+
protected $declaration_options = array();
37+
2938
/**
3039
* Constructor for this object.
3140
*
@@ -46,12 +55,18 @@ public function __construct( $declarations = array() ) {
4655
* Adds a single declaration.
4756
*
4857
* @since 6.1.0
58+
* @since 7.1.0 Added the `$options` parameter.
4959
*
5060
* @param string $property The CSS property.
5161
* @param string $value The CSS value.
62+
* @param array $options {
63+
* Optional. An array of options. Default empty array.
64+
*
65+
* @type bool $important Whether to output the declaration with !important. Default false.
66+
* }
5267
* @return WP_Style_Engine_CSS_Declarations Returns the object to allow chaining methods.
5368
*/
54-
public function add_declaration( $property, $value ) {
69+
public function add_declaration( $property, $value, $options = array() ) {
5570
// Sanitizes the property.
5671
$property = $this->sanitize_property( $property );
5772
// Bails early if the property is empty.
@@ -70,8 +85,21 @@ public function add_declaration( $property, $value ) {
7085
return $this;
7186
}
7287

88+
$options = wp_parse_args(
89+
$options,
90+
array(
91+
'important' => false,
92+
)
93+
);
94+
$options = array_filter( $options );
95+
7396
// Adds the declaration property/value pair.
7497
$this->declarations[ $property ] = $value;
98+
if ( $options ) {
99+
$this->declaration_options[ $property ] = $options;
100+
} else {
101+
unset( $this->declaration_options[ $property ] );
102+
}
75103

76104
return $this;
77105
}
@@ -86,6 +114,7 @@ public function add_declaration( $property, $value ) {
86114
*/
87115
public function remove_declaration( $property ) {
88116
unset( $this->declarations[ $property ] );
117+
unset( $this->declaration_options[ $property ] );
89118
return $this;
90119
}
91120

@@ -130,21 +159,52 @@ public function get_declarations() {
130159
return $this->declarations;
131160
}
132161

162+
/**
163+
* Gets declaration options keyed by property name.
164+
*
165+
* @since 7.1.0
166+
*
167+
* @return array Declaration options keyed by property name.
168+
*/
169+
public function get_declaration_options() {
170+
return $this->declaration_options;
171+
}
172+
133173
/**
134174
* Filters a CSS property + value pair.
135175
*
136176
* @since 6.1.0
177+
* @since 7.1.0 Added the `$options` parameter.
137178
*
138179
* @param string $property The CSS property.
139180
* @param string $value The value to be filtered.
140181
* @param string $spacer Optional. The spacer between the colon and the value.
141182
* Default empty string.
183+
* @param array $options {
184+
* Optional. An array of options. Default empty array.
185+
*
186+
* @type bool $important Whether to output the declaration with !important. Default false.
187+
* }
142188
* @return string The filtered declaration or an empty string.
143189
*/
144-
protected static function filter_declaration( $property, $value, $spacer = '' ) {
190+
protected static function filter_declaration( $property, $value, $spacer = '', $options = array() ) {
145191
$filtered_value = wp_strip_all_tags( $value, true );
146192
if ( '' !== $filtered_value ) {
147-
return safecss_filter_attr( "{$property}:{$spacer}{$filtered_value}" );
193+
$options = wp_parse_args(
194+
$options,
195+
array(
196+
'important' => false,
197+
)
198+
);
199+
200+
$filtered_declaration = safecss_filter_attr( "{$property}:{$spacer}{$filtered_value}" );
201+
202+
// Only append !important in the presence of an option value and when sanitization returns a single declaration.
203+
if ( true === $options['important'] && '' !== $filtered_declaration && ! str_contains( $filtered_declaration, ';' ) ) {
204+
return "$filtered_declaration !important";
205+
}
206+
207+
return $filtered_declaration;
148208
}
149209
return '';
150210
}
@@ -169,7 +229,7 @@ public function get_declarations_string( $should_prettify = false, $indent_count
169229
$spacer = $should_prettify ? ' ' : '';
170230

171231
foreach ( $declarations_array as $property => $value ) {
172-
$filtered_declaration = static::filter_declaration( $property, $value, $spacer );
232+
$filtered_declaration = static::filter_declaration( $property, $value, $spacer, $this->declaration_options[ $property ] ?? array() );
173233
if ( $filtered_declaration ) {
174234
$declarations_output .= "{$indent}{$filtered_declaration};$suffix";
175235
}

src/wp-includes/style-engine/class-wp-style-engine-css-rule.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public function set_selector( $selector ) {
8181
* Sets the declarations.
8282
*
8383
* @since 6.1.0
84+
* @since 7.1.0 Preserves declaration options when declarations are provided as a `WP_Style_Engine_CSS_Declarations` object.
8485
*
8586
* @param string[]|WP_Style_Engine_CSS_Declarations $declarations An array of declarations (property => value pairs),
8687
* or a WP_Style_Engine_CSS_Declarations object.
@@ -89,15 +90,23 @@ public function set_selector( $selector ) {
8990
public function add_declarations( $declarations ) {
9091
$is_declarations_object = ! is_array( $declarations );
9192
$declarations_array = $is_declarations_object ? $declarations->get_declarations() : $declarations;
93+
$declaration_options = $is_declarations_object ? $declarations->get_declaration_options() : array();
9294

9395
if ( null === $this->declarations ) {
9496
if ( $is_declarations_object ) {
9597
$this->declarations = $declarations;
9698
return $this;
9799
}
98-
$this->declarations = new WP_Style_Engine_CSS_Declarations( $declarations_array );
100+
$this->declarations = new WP_Style_Engine_CSS_Declarations();
101+
}
102+
103+
foreach ( $declarations_array as $property => $value ) {
104+
$this->declarations->add_declaration(
105+
$property,
106+
$value,
107+
$declaration_options[ $property ] ?? array()
108+
);
99109
}
100-
$this->declarations->add_declarations( $declarations_array );
101110

102111
return $this;
103112
}

src/wp-includes/style-engine/class-wp-style-engine-processor.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,23 @@ private function combine_rules_selectors() {
151151
// Build an array of selectors along with the JSON-ified styles to make comparisons easier.
152152
$selectors_json = array();
153153
foreach ( $this->css_rules as $rule ) {
154-
$declarations = $rule->get_declarations()->get_declarations();
154+
$declarations = $rule->get_declarations()->get_declarations();
155+
$declaration_options = $rule->get_declarations()->get_declaration_options();
155156
ksort( $declarations );
156-
$selectors_json[ $rule->get_selector() ] = wp_json_encode( $declarations );
157+
// Declaration options are keyed by property and are part of the rule identity.
158+
ksort( $declaration_options );
159+
foreach ( $declaration_options as $property => $options ) {
160+
if ( is_array( $options ) ) {
161+
ksort( $options );
162+
$declaration_options[ $property ] = $options;
163+
}
164+
}
165+
$selectors_json[ $rule->get_selector() ] = wp_json_encode(
166+
array(
167+
'declarations' => $declarations,
168+
'declaration_options' => $declaration_options,
169+
)
170+
);
157171
}
158172

159173
// Combine selectors that have the same styles.

src/wp-includes/style-engine/class-wp-style-engine.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -425,15 +425,18 @@ protected static function is_valid_style_value( $style_value ) {
425425
*
426426
* @since 6.1.0
427427
* @since 6.6.0 Added the `$rules_group` parameter.
428-
*
429-
* @param string $store_name A valid store key.
430-
* @param string $css_selector When a selector is passed, the function will return
431-
* a full CSS rule `$selector { ...rules }`
432-
* otherwise a concatenated string of properties and values.
433-
* @param string[] $css_declarations An associative array of CSS definitions,
434-
* e.g. `array( "$property" => "$value", "$property" => "$value" )`.
435-
* @param string $rules_group Optional. A parent CSS selector in the case of nested CSS, or a CSS nested @rule,
436-
* such as `@media (min-width: 80rem)` or `@layer module`.
428+
* @since 7.1.0 Extended `$css_declarations` parameter to accept `WP_Style_Engine_CSS_Declarations` object.
429+
*
430+
* @param string $store_name A valid store key.
431+
* @param string $css_selector When a selector is passed, the function will return
432+
* a full CSS rule `$selector { ...rules }`
433+
* otherwise a concatenated string of properties and values.
434+
* @param string[]|WP_Style_Engine_CSS_Declarations $css_declarations An associative array of CSS definitions,
435+
* e.g. `array( "$property" => "$value", "$property" => "$value" )`,
436+
* or a WP_Style_Engine_CSS_Declarations object.
437+
* @param string $rules_group Optional. A parent CSS selector in the case of nested CSS,
438+
* or a CSS nested @rule, such as `@media (min-width: 80rem)`
439+
* or `@layer module`.
437440
*/
438441
public static function store_css_rule( $store_name, $css_selector, $css_declarations, $rules_group = '' ) {
439442
if ( empty( $store_name ) || empty( $css_selector ) || empty( $css_declarations ) ) {

0 commit comments

Comments
 (0)