Commit 3739c054f8 for wordpress.org

commit 3739c054f8ffa9e201e114a81c48533f33d49da2
Author: westonruter <westonruter@git.wordpress.org>
Date:   Thu Nov 12 17:08:01 2026 +0000

    Editor: Improve Style Engine types and avoid a fatal error.

    A selector made only of whitespace passed the empty check in `WP_Style_Engine::store_css_rule()`, but the rules store trims it and returns no rule, so `add_declarations()` was called on null. This fatal error is reachable from `wp_style_engine_get_stylesheet_from_css_rules()` when a `context` is passed, and the rule is now skipped instead.

    Raw style values are documented as `mixed`, since `parse_block_styles()` passes whatever is found at a definition's path to every definition, so spacing values arrive as arrays and settings such as `textColumns` as integers. A `Style_Definition` type alias describes each entry of `BLOCK_STYLE_DEFINITIONS_METADATA`. Non-string values are now left out of the returned declarations so the documented `string[]` return type holds; they were already dropped from the compiled CSS, which is unchanged.

    These changes are synced from the Style Engine package in Gutenberg.

    Developed in https://github.com/WordPress/wordpress-develop/pull/15823.
    Follow-up to r54156, r56614, r58089.

    Props westonruter, ramonopoly.
    See #65817.

    Built from https://develop.svn.wordpress.org/trunk@65438


    git-svn-id: http://core.svn.wordpress.org/trunk@64577 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-includes/style-engine/class-wp-style-engine.php b/wp-includes/style-engine/class-wp-style-engine.php
index 81499d15e5..2ccfdefa95 100644
--- a/wp-includes/style-engine/class-wp-style-engine.php
+++ b/wp-includes/style-engine/class-wp-style-engine.php
@@ -28,6 +28,14 @@
  * @since 6.7.0 Added support for typography.writingMode.
  * @since 7.0.0 Added support for typography.textIndent.
  * @since 7.2.0 Added support for typography.textShadow.
+ *
+ * @phpstan-type Style_Definition array{
+ *     property_keys?: array{ default: string, individual?: string },
+ *     path: non-empty-list<string>,
+ *     css_vars?: array<string, string>,
+ *     classnames?: array<string, string|true>,
+ *     value_func?: callable( mixed, array<string, mixed>, array<mixed> ): array<string, string>,
+ * }
  */
 #[AllowDynamicProperties]
 final class WP_Style_Engine {
@@ -59,7 +67,8 @@ 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
+	 * @var array<string, array<string, array>>
+	 * @phpstan-var array<string, array<string, Style_Definition>>
 	 */
 	const BLOCK_STYLE_DEFINITIONS_METADATA = array(
 		'background' => array(
@@ -403,7 +412,7 @@ final class WP_Style_Engine {
 	 *
 	 * @since 6.1.0
 	 *
-	 * @param string $style_value  A single CSS preset value.
+	 * @param mixed  $style_value  A single CSS preset value.
 	 * @param string $property_key The CSS property that is the second element of the preset string.
 	 *                             Used for matching.
 	 * @return string The slug, or empty string if not found.
@@ -448,7 +457,7 @@ final class WP_Style_Engine {
 	 *
 	 * @since 6.1.0
 	 *
-	 * @param string $style_value A single CSS preset value.
+	 * @param mixed $style_value A single raw style value.
 	 * @return bool
 	 */
 	protected static function is_valid_style_value( $style_value ) {
@@ -473,11 +482,21 @@ final class WP_Style_Engine {
 	 *                                                                    or a CSS nested @rule, such as `@media (min-width: 80rem)`
 	 *                                                                    or `@layer module`.
 	 */
-	public static function store_css_rule( $store_name, $css_selector, $css_declarations, $rules_group = '' ) {
+	public static function store_css_rule( $store_name, $css_selector, $css_declarations, $rules_group = '' ): void {
 		if ( empty( $store_name ) || empty( $css_selector ) || empty( $css_declarations ) ) {
 			return;
 		}
-		static::get_store( $store_name )->add_rule( $css_selector, $rules_group )->add_declarations( $css_declarations );
+
+		$store = static::get_store( $store_name );
+		if ( ! $store ) {
+			return;
+		}
+
+		// The rule is null when the selector is only whitespace.
+		$rule = $store->add_rule( $css_selector, $rules_group );
+		if ( $rule ) {
+			$rule->add_declarations( $css_declarations );
+		}
 	}

 	/**
@@ -495,12 +514,12 @@ final class WP_Style_Engine {
 	/**
 	 * Returns classnames and CSS based on the values in a styles object.
 	 *
-	 * Return values are parsed based on the instructions in BLOCK_STYLE_DEFINITIONS_METADATA.
+	 * Return values are parsed based on the instructions in {@see self::BLOCK_STYLE_DEFINITIONS_METADATA}.
 	 *
 	 * @since 6.1.0
 	 *
-	 * @param array $block_styles The style object.
-	 * @param array $options      {
+	 * @param array<string, mixed> $block_styles The style object.
+	 * @param array                $options      {
 	 *     Optional. An array of options. Default empty array.
 	 *
 	 *     @type bool        $convert_vars_to_classnames Whether to skip converting incoming CSS var patterns,
@@ -567,10 +586,13 @@ final class WP_Style_Engine {
 	 *
 	 * @since 6.1.0
 	 *
-	 * @param string $style_value      A single raw style value or CSS preset property
+	 * @param mixed  $style_value      A single raw style value or CSS preset property
 	 *                                 from the `$block_styles` array.
-	 * @param array  $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
+	 * @param array  $style_definition A single style definition from {@see self::BLOCK_STYLE_DEFINITIONS_METADATA}.
 	 * @return string[] An array of CSS classnames, or empty array if there are none.
+	 *
+	 * @phpstan-param Style_Definition $style_definition
+	 * @phpstan-return list<string>
 	 */
 	protected static function get_classnames( $style_value, $style_definition ) {
 		if ( empty( $style_value ) ) {
@@ -589,7 +611,7 @@ final class WP_Style_Engine {

 				if ( $slug ) {
 					/*
-					 * Right now we expect a classname pattern to be stored in BLOCK_STYLE_DEFINITIONS_METADATA.
+					 * Right now we expect a classname pattern to be stored in self::BLOCK_STYLE_DEFINITIONS_METADATA.
 					 * One day, if there are no stored schemata, we could allow custom patterns or
 					 * generate classnames based on other properties
 					 * such as a path or a value or a prefix passed in options.
@@ -608,7 +630,7 @@ final class WP_Style_Engine {
 	 * @since 6.1.0
 	 *
 	 * @param mixed $style_value      A single raw style value from $block_styles array.
-	 * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
+	 * @param array $style_definition A single style definition from {@see self::BLOCK_STYLE_DEFINITIONS_METADATA}.
 	 * @param array $options          {
 	 *     Optional. An array of options. Default empty array.
 	 *
@@ -617,12 +639,18 @@ final class WP_Style_Engine {
 	 *                                            to `var( --wp--preset--* )` values. Default false.
 	 * }
 	 * @return string[] An associative array of CSS definitions, e.g. `array( "$property" => "$value", "$property" => "$value" )`.
+	 *
+	 * @phpstan-param Style_Definition $style_definition
 	 */
 	protected static function get_css_declarations( $style_value, $style_definition, $options = array() ) {
 		if ( isset( $style_definition['value_func'] ) && is_callable( $style_definition['value_func'] ) ) {
 			return call_user_func( $style_definition['value_func'], $style_value, $style_definition, $options );
 		}

+		if ( ! isset( $style_definition['property_keys'] ) ) {
+			return array();
+		}
+
 		$css_declarations     = array();
 		$style_property_keys  = $style_definition['property_keys'];
 		$should_skip_css_vars = isset( $options['convert_vars_to_classnames'] ) && true === $options['convert_vars_to_classnames'];
@@ -659,7 +687,7 @@ final class WP_Style_Engine {

 				$individual_property = sprintf( $style_property_keys['individual'], _wp_to_kebab_case( $key ) );

-				if ( $individual_property && static::is_valid_style_value( $value ) ) {
+				if ( $individual_property && is_string( $value ) && static::is_valid_style_value( $value ) ) {
 					$css_declarations[ $individual_property ] = $value;
 				}
 			}
@@ -667,7 +695,10 @@ final class WP_Style_Engine {
 			return $css_declarations;
 		}

-		$css_declarations[ $style_property_keys['default'] ] = $style_value;
+		// CSS declarations only accept string values.
+		if ( is_string( $style_value ) ) {
+			$css_declarations[ $style_property_keys['default'] ] = $style_value;
+		}
 		return $css_declarations;
 	}

@@ -689,8 +720,8 @@ final class WP_Style_Engine {
 	 *
 	 * @since 6.1.0
 	 *
-	 * @param array $style_value                    A single raw style value from `$block_styles` array.
-	 * @param array $individual_property_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA
+	 * @param mixed $style_value                    A single raw style value from `$block_styles` array.
+	 * @param array $individual_property_definition A single style definition from {@see self::BLOCK_STYLE_DEFINITIONS_METADATA}
 	 *                                              representing an individual property of a CSS property,
 	 *                                              e.g. 'top' in 'border-top'.
 	 * @param array $options                        {
@@ -701,6 +732,8 @@ final class WP_Style_Engine {
 	 *                                            to `var( --wp--preset--* )` values. Default false.
 	 * }
 	 * @return string[] An associative array of CSS definitions, e.g. `array( "$property" => "$value", "$property" => "$value" )`.
+	 *
+	 * @phpstan-param Style_Definition $individual_property_definition
 	 */
 	protected static function get_individual_property_css_declarations( $style_value, $individual_property_definition, $options = array() ) {
 		if ( ! is_array( $style_value ) || empty( $style_value ) || empty( $individual_property_definition['path'] ) ) {
@@ -721,17 +754,17 @@ final class WP_Style_Engine {
 		$css_declarations        = array();

 		foreach ( $style_value as $css_property => $value ) {
-			if ( empty( $value ) ) {
+			// CSS declarations only accept string values.
+			if ( empty( $value ) || ! is_string( $value ) ) {
 				continue;
 			}

-			// Build a path to the individual rules in definitions.
-			$style_definition_path = array( $definition_group_key, $css_property );
-			$style_definition      = _wp_array_get( static::BLOCK_STYLE_DEFINITIONS_METADATA, $style_definition_path, null );
+			// Get the corresponding style definition from the same group, e.g. `border.color`.
+			$style_definition = static::BLOCK_STYLE_DEFINITIONS_METADATA[ $definition_group_key ][ $css_property ] ?? null;

 			if ( $style_definition && isset( $style_definition['property_keys']['individual'] ) ) {
 				// Set a CSS var if there is a valid preset value.
-				if ( is_string( $value ) && str_contains( $value, 'var:' )
+				if ( str_contains( $value, 'var:' )
 					&& ! $should_skip_css_vars && ! empty( $individual_property_definition['css_vars'] )
 				) {
 					$value = static::get_css_var_value( $value, $individual_property_definition['css_vars'] );
@@ -753,9 +786,12 @@ final class WP_Style_Engine {
 	 *
 	 * @since 6.4.0
 	 *
-	 * @param array $style_value      A single raw style value from $block_styles array.
-	 * @param array $style_definition A single style definition from BLOCK_STYLE_DEFINITIONS_METADATA.
+	 * @param mixed $style_value      A single raw style value from $block_styles array.
+	 * @param array $style_definition A single style definition from {@see self::BLOCK_STYLE_DEFINITIONS_METADATA}.
 	 * @return string[] An associative array of CSS definitions, e.g., array( "$property" => "$value", "$property" => "$value" ).
+	 *
+	 * @phpstan-param Style_Definition $style_definition
+	 * @phpstan-return array<string, string>
 	 */
 	protected static function get_url_or_value_css_declaration( $style_value, $style_definition ) {
 		if ( empty( $style_value ) ) {
@@ -767,7 +803,7 @@ final class WP_Style_Engine {
 		if ( isset( $style_definition['property_keys']['default'] ) ) {
 			$value = null;

-			if ( ! empty( $style_value['url'] ) ) {
+			if ( is_array( $style_value ) && ! empty( $style_value['url'] ) && is_string( $style_value['url'] ) ) {
 				$value = "url('" . $style_value['url'] . "')";
 			} elseif ( is_string( $style_value ) ) {
 				$value = $style_value;
@@ -789,10 +825,11 @@ final class WP_Style_Engine {
 	 *
 	 * @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.
+	 * @param mixed $style_value      A single raw style value from $block_styles array.
+	 * @param array $style_definition A single style definition from {@see self::BLOCK_STYLE_DEFINITIONS_METADATA}.
 	 * @return array<string, string> An associative array of CSS definitions, mapping properties to values.
 	 *
+	 * @phpstan-param Style_Definition $style_definition
 	 * @phpstan-return array<non-falsy-string, non-falsy-string>
 	 */
 	protected static function get_background_clip_css_declarations( $style_value, $style_definition ) {
diff --git a/wp-includes/version.php b/wp-includes/version.php
index a2799374f4..8cb5de9d60 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-beta5-65437';
+$wp_version = '7.2-beta5-65438';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.