diff -r 48c4eec2b7e6 -r 8c2e4d02f4ef wp/wp-includes/class-wp-theme-json.php --- a/wp/wp-includes/class-wp-theme-json.php Fri Sep 05 18:40:08 2025 +0200 +++ b/wp/wp-includes/class-wp-theme-json.php Fri Sep 05 18:52:52 2025 +0200 @@ -226,7 +226,7 @@ * @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. - * + * @since 6.7.0 Added `background-attachment` property. * @var array */ const PROPERTIES_METADATA = array( @@ -237,6 +237,7 @@ 'background-position' => array( 'background', 'backgroundPosition' ), 'background-repeat' => array( 'background', 'backgroundRepeat' ), 'background-size' => array( 'background', 'backgroundSize' ), + 'background-attachment' => array( 'background', 'backgroundAttachment' ), 'border-radius' => array( 'border', 'radius' ), 'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ), 'border-top-right-radius' => array( 'border', 'radius', 'topRight' ), @@ -305,7 +306,6 @@ * * @since 6.2.0 * @since 6.6.0 Added background-image properties. - * * @var array */ const INDIRECT_PROPERTIES_METADATA = array( @@ -337,6 +337,7 @@ * setting key. * * @since 5.9.0 + * @var array */ const PROTECTED_PROPERTIES = array( 'spacing.blockGap' => array( 'spacing', 'blockGap' ), @@ -469,11 +470,10 @@ ), ); - /* + /** * The valid properties for fontFamilies under settings key. * * @since 6.5.0 - * * @var array */ const FONT_FAMILY_SCHEMA = array( @@ -515,15 +515,15 @@ * @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', + 'backgroundImage' => null, + 'backgroundPosition' => null, + 'backgroundRepeat' => null, + 'backgroundSize' => null, + 'backgroundAttachment' => null, ), 'border' => array( 'color' => null, @@ -578,7 +578,7 @@ /** * Defines which pseudo selectors are enabled for which elements. * - * The order of the selectors should be: link, any-link, visited, hover, focus, active. + * The order of the selectors should be: link, any-link, visited, hover, focus, focus-visible, active. * This is to ensure the user action (hover, focus and active) styles have a higher * specificity than the visited styles, which in turn have a higher specificity than * the unvisited styles. @@ -588,10 +588,12 @@ * * @since 6.1.0 * @since 6.2.0 Added support for ':link' and ':any-link'. + * @since 6.8.0 Added support for ':focus-visible'. + * @var array */ const VALID_ELEMENT_PSEUDO_SELECTORS = array( - 'link' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ), - 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':active' ), + 'link' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ), + 'button' => array( ':link', ':any-link', ':visited', ':hover', ':focus', ':focus-visible', ':active' ), ); /** @@ -756,9 +758,10 @@ } $this->theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin ); - $valid_block_names = array_keys( static::get_blocks_metadata() ); + $blocks_metadata = static::get_blocks_metadata(); + $valid_block_names = array_keys( $blocks_metadata ); $valid_element_names = array_keys( static::ELEMENTS ); - $valid_variations = static::get_valid_block_style_variations(); + $valid_variations = static::get_valid_block_style_variations( $blocks_metadata ); $this->theme_json = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations ); $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); @@ -935,7 +938,6 @@ * @return array The sanitized output. */ protected static function sanitize( $input, $valid_block_names, $valid_element_names, $valid_variations ) { - $output = array(); if ( ! is_array( $input ) ) { @@ -1316,6 +1318,8 @@ * - `variables`: only the CSS Custom Properties for presets & custom ones. * - `styles`: only the styles section in theme.json. * - `presets`: only the classes for the presets. + * - `base-layout-styles`: only the base layout styles. + * - `custom-css`: only the custom CSS. * @param string[] $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options { * Optional. An array of options for now used for internal purposes only (may change without notice). @@ -1423,6 +1427,12 @@ $stylesheet .= $this->get_preset_classes( $setting_nodes, $origins ); } + // Load the custom CSS last so it has the highest specificity. + if ( in_array( 'custom-css', $types, true ) ) { + // Add the global styles root CSS. + $stylesheet .= _wp_array_get( $this->theme_json, array( 'styles', 'css' ) ); + } + return $stylesheet; } @@ -1488,10 +1498,12 @@ * Returns the global styles custom CSS. * * @since 6.2.0 + * @deprecated 6.7.0 Use {@see 'get_stylesheet'} instead. * * @return string The global styles custom CSS. */ public function get_custom_css() { + _deprecated_function( __METHOD__, '6.7.0', 'get_stylesheet' ); // Add the global styles root CSS. $stylesheet = isset( $this->theme_json['styles']['css'] ) ? $this->theme_json['styles']['css'] : ''; @@ -2284,7 +2296,7 @@ * * array( * 'name' => 'property_name', - * 'value' => 'property_value, + * 'value' => 'property_value', * ) * * @since 5.8.0 @@ -2292,6 +2304,7 @@ * @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 Pass current theme JSON settings to wp_get_typography_font_size_value(), and process background properties. + * @since 6.7.0 `ref` resolution of background properties, and assigning custom default values. * * @param array $styles Styles to process. * @param array $settings Theme settings. @@ -2299,26 +2312,32 @@ * @param array $theme_json Theme JSON array. * @param string $selector The style block selector. * @param boolean $use_root_padding Whether to add custom properties at root level. - * @return array Returns the modified $declarations. + * @return array Returns the modified $declarations. */ protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) { + if ( empty( $styles ) ) { + return array(); + } + if ( null === $properties ) { $properties = static::PROPERTIES_METADATA; } - - $declarations = array(); - if ( empty( $styles ) ) { - return $declarations; - } - + $declarations = array(); $root_variable_duplicates = array(); + $root_style_length = strlen( '--wp--style--root--' ); foreach ( $properties as $css_property => $value_path ) { - $value = static::get_property_value( $styles, $value_path, $theme_json ); - - if ( str_starts_with( $css_property, '--wp--style--root--' ) && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { + if ( ! is_array( $value_path ) ) { continue; } + + $is_root_style = str_starts_with( $css_property, '--wp--style--root--' ); + if ( $is_root_style && ( static::ROOT_BLOCK_SELECTOR !== $selector || ! $use_root_padding ) ) { + continue; + } + + $value = static::get_property_value( $styles, $value_path, $theme_json ); + /* * Root-level padding styles don't currently support strings with CSS shorthand values. * This may change: https://github.com/WordPress/gutenberg/issues/40132. @@ -2327,33 +2346,48 @@ continue; } - if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) { - $root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) ); + if ( $is_root_style && $use_root_padding ) { + $root_variable_duplicates[] = substr( $css_property, $root_style_length ); + } + + /* + * Processes background image styles. + * If the value is a URL, it will be converted to a CSS `url()` value. + * 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 ( empty( $value ) && static::ROOT_BLOCK_SELECTOR !== $selector && ! empty( $styles['background']['backgroundImage']['id'] ) ) { + if ( 'background-size' === $css_property ) { + $value = 'cover'; + } + // If the background size is set to `contain` and no position is set, set the position to `center`. + if ( 'background-position' === $css_property ) { + $background_size = $styles['background']['backgroundSize'] ?? null; + $value = 'contain' === $background_size ? '50% 50%' : null; + } + } + + // 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 ) ) { + continue; } /* * Look up protected properties, keyed by value path. * Skip protected properties that are explicitly set to `null`. */ - if ( is_array( $value_path ) ) { - $path_string = implode( '.', $value_path ); - if ( - isset( static::PROTECTED_PROPERTIES[ $path_string ] ) && - _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null - ) { - continue; - } - } - - // Processes background styles. - 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 ) ) { + $path_string = implode( '.', $value_path ); + if ( + isset( static::PROTECTED_PROPERTIES[ $path_string ] ) && + _wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null + ) { continue; } @@ -2410,6 +2444,7 @@ * to the standard form "--wp--preset--color--secondary". * This is already done by the sanitize method, * so every property will be in the standard form. + * @since 6.7.0 Added support for background image refs. * * @param array $styles Styles subtree. * @param array $path Which property to process. @@ -2426,14 +2461,18 @@ /* * This converts references to a path to the value at that path - * where the values is an array with a "ref" key, pointing to a path. + * where the value is an array with a "ref" key, pointing to a path. * For example: { "ref": "style.color.background" } => "#fff". + * In the case of backgroundImage, if both a ref and a URL are present in the value, + * the URL takes precedence and the ref is ignored. */ if ( is_array( $value ) && isset( $value['ref'] ) ) { $value_path = explode( '.', $value['ref'] ); $ref_value = _wp_array_get( $theme_json, $value_path ); + // Background Image refs can refer to a string or an array containing a URL string. + $ref_value_url = $ref_value['url'] ?? null; // Only use the ref value if we find anything. - if ( ! empty( $ref_value ) && is_string( $ref_value ) ) { + if ( ! empty( $ref_value ) && ( is_string( $ref_value ) || is_string( $ref_value_url ) ) ) { $value = $ref_value; } @@ -2656,69 +2695,101 @@ * @since 6.1.0 * @since 6.3.0 Refactored and stabilized selectors API. * @since 6.6.0 Added optional selectors and options for generating block nodes. + * @since 6.7.0 Added $include_node_paths_only option. * * @param array $theme_json The theme.json converted to an array. * @param array $selectors Optional list of selectors per block. * @param array $options { * Optional. An array of options for now used for internal purposes only (may change without notice). * - * @type bool $include_block_style_variations Includes nodes for block style variations. Default false. + * @type bool $include_block_style_variations Include nodes for block style variations. Default false. + * @type bool $include_node_paths_only Return only block nodes node paths. Default false. * } * @return array The block nodes in theme.json. */ private static function get_block_nodes( $theme_json, $selectors = array(), $options = array() ) { - $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; - $nodes = array(); - if ( ! isset( $theme_json['styles'] ) ) { + $nodes = array(); + + if ( ! isset( $theme_json['styles']['blocks'] ) ) { return $nodes; } - // Blocks. - if ( ! isset( $theme_json['styles']['blocks'] ) ) { - return $nodes; + $include_variations = $options['include_block_style_variations'] ?? false; + $include_node_paths_only = $options['include_node_paths_only'] ?? false; + + // If only node paths are to be returned, skip selector assignment. + if ( ! $include_node_paths_only ) { + $selectors = empty( $selectors ) ? static::get_blocks_metadata() : $selectors; } foreach ( $theme_json['styles']['blocks'] as $name => $node ) { - $selector = null; - if ( isset( $selectors[ $name ]['selector'] ) ) { - $selector = $selectors[ $name ]['selector']; - } - - $duotone_selector = null; - if ( isset( $selectors[ $name ]['duotone'] ) ) { - $duotone_selector = $selectors[ $name ]['duotone']; - } - - $feature_selectors = null; - if ( isset( $selectors[ $name ]['selectors'] ) ) { - $feature_selectors = $selectors[ $name ]['selectors']; + $node_path = array( 'styles', 'blocks', $name ); + if ( $include_node_paths_only ) { + $variation_paths = array(); + if ( $include_variations && isset( $node['variations'] ) ) { + foreach ( $node['variations'] as $variation => $variation_node ) { + $variation_paths[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), + ); + } + } + $node = array( + 'path' => $node_path, + ); + if ( ! empty( $variation_paths ) ) { + $node['variations'] = $variation_paths; + } + $nodes[] = $node; + } else { + $selector = null; + if ( isset( $selectors[ $name ]['selector'] ) ) { + $selector = $selectors[ $name ]['selector']; + } + + $duotone_selector = null; + if ( isset( $selectors[ $name ]['duotone'] ) ) { + $duotone_selector = $selectors[ $name ]['duotone']; + } + + $feature_selectors = null; + if ( isset( $selectors[ $name ]['selectors'] ) ) { + $feature_selectors = $selectors[ $name ]['selectors']; + } + + $variation_selectors = array(); + if ( $include_variations && isset( $node['variations'] ) ) { + foreach ( $node['variations'] as $variation => $node ) { + $variation_selectors[] = array( + 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), + 'selector' => $selectors[ $name ]['styleVariations'][ $variation ], + ); + } + } + + $nodes[] = array( + 'name' => $name, + 'path' => $node_path, + 'selector' => $selector, + 'selectors' => $feature_selectors, + 'duotone' => $duotone_selector, + 'features' => $feature_selectors, + 'variations' => $variation_selectors, + 'css' => $selector, + ); } - $variation_selectors = array(); - $include_variations = $options['include_block_style_variations'] ?? false; - if ( $include_variations && isset( $node['variations'] ) ) { - foreach ( $node['variations'] as $variation => $node ) { - $variation_selectors[] = array( - 'path' => array( 'styles', 'blocks', $name, 'variations', $variation ), - 'selector' => $selectors[ $name ]['styleVariations'][ $variation ], - ); - } - } - - $nodes[] = array( - 'name' => $name, - 'path' => array( 'styles', 'blocks', $name ), - 'selector' => $selector, - 'selectors' => $feature_selectors, - 'duotone' => $duotone_selector, - 'features' => $feature_selectors, - 'variations' => $variation_selectors, - ); - if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) { foreach ( $theme_json['styles']['blocks'][ $name ]['elements'] as $element => $node ) { + $node_path = array( 'styles', 'blocks', $name, 'elements', $element ); + if ( $include_node_paths_only ) { + $nodes[] = array( + 'path' => $node_path, + ); + continue; + } + $nodes[] = array( - 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), + 'path' => $node_path, 'selector' => $selectors[ $name ]['elements'][ $element ], ); @@ -2726,8 +2797,16 @@ if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] ) ) { foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { + $node_path = array( 'styles', 'blocks', $name, 'elements', $element ); + if ( $include_node_paths_only ) { + $nodes[] = array( + 'path' => $node_path, + ); + continue; + } + $nodes[] = array( - 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), + 'path' => $node_path, 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), ); } @@ -2749,7 +2828,6 @@ * Fixed custom CSS output in block style variations. * * @param array $block_metadata Metadata about the block to get styles for. - * * @return string Styles for the block. */ public function get_styles_for_block( $block_metadata ) { @@ -2773,8 +2851,14 @@ // Combine selectors with style variation's selector and add to overall style variation declarations. foreach ( $variation_declarations as $current_selector => $new_declarations ) { - // If current selector includes block classname, remove it but leave the whitespace in. - $shortened_selector = str_replace( $block_metadata['selector'] . ' ', ' ', $current_selector ); + /* + * Clean up any whitespace between comma separated selectors. + * This prevents these spaces breaking compound selectors such as: + * - `.wp-block-list:not(.wp-block-list .wp-block-list)` + * - `.wp-block-image img, .wp-block-image.my-class img` + */ + $clean_current_selector = preg_replace( '/,\s+/', ',', $current_selector ); + $shortened_selector = str_replace( $block_metadata['selector'], '', $clean_current_selector ); // Prepend the variation selector to the current selector. $split_selectors = explode( ',', $shortened_selector ); @@ -2822,7 +2906,11 @@ array_filter( $element_pseudo_allowed, static function ( $pseudo_selector ) use ( $selector ) { - return str_contains( $selector, $pseudo_selector ); + /* + * Check if the pseudo selector is in the current selector, + * ensuring it is not followed by a dash (e.g., :focus should not match :focus-visible). + */ + return preg_match( '/' . preg_quote( $pseudo_selector, '/' ) . '(?!-)/', $selector ) === 1; } ) ); @@ -2949,7 +3037,7 @@ } } - // 7. Generate and append any custom CSS rules pertaining to nested block style variations. + // 7. Generate and append any custom CSS rules. if ( isset( $node['css'] ) && ! $is_root_selector ) { $block_rules .= $this->process_blocks_custom_css( $node['css'], $selector ); } @@ -2974,9 +3062,9 @@ $use_root_padding = isset( $this->theme_json['settings']['useRootPaddingAwareAlignments'] ) && true === $this->theme_json['settings']['useRootPaddingAwareAlignments']; /* - * If there are content and wide widths in theme.json, output them - * as custom properties on the body element so all blocks can use them. - */ + * If there are content and wide widths in theme.json, output them + * as custom properties on the body element so all blocks can use them. + */ if ( isset( $settings['layout']['contentSize'] ) || isset( $settings['layout']['wideSize'] ) ) { $content_size = isset( $settings['layout']['contentSize'] ) ? $settings['layout']['contentSize'] : $settings['layout']['wideSize']; $content_size = static::is_safe_css_declaration( 'max-width', $content_size ) ? $content_size : 'initial'; @@ -2987,13 +3075,13 @@ } /* - * Reset default browser margin on the body element. - * This is set on the body selector **before** generating the ruleset - * from the `theme.json`. This is to ensure that if the `theme.json` declares - * `margin` in its `spacing` declaration for the `body` element then these - * user-generated values take precedence in the CSS cascade. - * @link https://github.com/WordPress/gutenberg/issues/36147. - */ + * Reset default browser margin on the body element. + * This is set on the body selector **before** generating the ruleset + * from the `theme.json`. This is to ensure that if the `theme.json` declares + * `margin` in its `spacing` declaration for the `body` element then these + * user-generated values take precedence in the CSS cascade. + * @link https://github.com/WordPress/gutenberg/issues/36147. + */ $css .= ':where(body) { margin: 0; }'; if ( $use_root_padding ) { @@ -3071,6 +3159,7 @@ * * @since 5.8.0 * @since 5.9.0 Duotone preset also has origins. + * @since 6.7.0 Replace background image objects during merge. * * @param WP_Theme_JSON $incoming Data to merge. */ @@ -3194,6 +3283,33 @@ } } } + + /* + * Style values are merged at the leaf level, however + * some values provide exceptions, namely style values that are + * objects and represent unique definitions for the style. + */ + $style_nodes = static::get_block_nodes( + $this->theme_json, + array(), + array( 'include_node_paths_only' => true ) + ); + + // Add top-level styles. + $style_nodes[] = array( 'path' => array( 'styles' ) ); + + foreach ( $style_nodes as $style_node ) { + $path = $style_node['path']; + /* + * Background image styles should be replaced, not merged, + * as they themselves are specific object definitions for the style. + */ + $background_image_path = array_merge( $path, static::PROPERTIES_METADATA['background-image'] ); + $content = _wp_array_get( $incoming_data, $background_image_path, null ); + if ( isset( $content ) ) { + _wp_array_set( $this->theme_json, $background_image_path, $content ); + } + } } /** @@ -3222,7 +3338,7 @@ continue; } foreach ( $duotone_presets[ $origin ] as $duotone_preset ) { - $filters .= wp_get_duotone_filter_svg( $duotone_preset ); + $filters .= WP_Duotone::get_filter_svg_from_preset( $duotone_preset ); } } } @@ -3379,8 +3495,8 @@ * @since 6.6.0 Updated to allow variation element styles and $origin parameter. * * @param array $theme_json Structure to sanitize. - * @param string $origin Optional. What source of data this object represents. - * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. + * @param string $origin Optional. What source of data this object represents. + * One of 'blocks', 'default', 'theme', or 'custom'. Default 'theme'. * @return array Sanitized structure. */ public static function remove_insecure_properties( $theme_json, $origin = 'theme' ) { @@ -3392,9 +3508,10 @@ $theme_json = WP_Theme_JSON_Schema::migrate( $theme_json, $origin ); - $valid_block_names = array_keys( static::get_blocks_metadata() ); + $blocks_metadata = static::get_blocks_metadata(); + $valid_block_names = array_keys( $blocks_metadata ); $valid_element_names = array_keys( static::ELEMENTS ); - $valid_variations = static::get_valid_block_style_variations(); + $valid_variations = static::get_valid_block_style_variations( $blocks_metadata ); $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names, $valid_variations ); @@ -3446,26 +3563,12 @@ $variation_output = static::remove_insecure_styles( $variation_input ); - // Process a variation's elements and element pseudo selector styles. + if ( isset( $variation_input['blocks'] ) ) { + $variation_output['blocks'] = static::remove_insecure_inner_block_styles( $variation_input['blocks'] ); + } + if ( isset( $variation_input['elements'] ) ) { - foreach ( $valid_element_names as $element_name ) { - $element_input = $variation_input['elements'][ $element_name ] ?? null; - if ( $element_input ) { - $element_output = static::remove_insecure_styles( $element_input ); - - if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { - foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { - if ( isset( $element_input[ $pseudo_selector ] ) ) { - $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); - } - } - } - - if ( ! empty( $element_output ) ) { - _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output ); - } - } - } + $variation_output['elements'] = static::remove_insecure_element_styles( $variation_input['elements'] ); } if ( ! empty( $variation_output ) ) { @@ -3504,6 +3607,59 @@ } /** + * Remove insecure element styles within a variation or block. + * + * @since 6.8.0 + * + * @param array $elements The elements to process. + * @return array The sanitized elements styles. + */ + protected static function remove_insecure_element_styles( $elements ) { + $sanitized = array(); + $valid_element_names = array_keys( static::ELEMENTS ); + + foreach ( $valid_element_names as $element_name ) { + $element_input = $elements[ $element_name ] ?? null; + if ( $element_input ) { + $element_output = static::remove_insecure_styles( $element_input ); + + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { + if ( isset( $element_input[ $pseudo_selector ] ) ) { + $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); + } + } + } + + $sanitized[ $element_name ] = $element_output; + } + } + return $sanitized; + } + + /** + * Remove insecure styles from inner blocks and their elements. + * + * @since 6.8.0 + * + * @param array $blocks The block styles to process. + * @return array Sanitized block type styles. + */ + protected static function remove_insecure_inner_block_styles( $blocks ) { + $sanitized = array(); + foreach ( $blocks as $block_type => $block_input ) { + $block_output = static::remove_insecure_styles( $block_input ); + + if ( isset( $block_input['elements'] ) ) { + $block_output['elements'] = static::remove_insecure_element_styles( $block_input['elements'] ); + } + + $sanitized[ $block_type ] = $block_output; + } + return $sanitized; + } + + /** * Processes a setting node and returns the same node * without the insecure settings. * @@ -4112,6 +4268,7 @@ * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. * * @since 6.3.0 + * * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. * @return string The converted variable. */ @@ -4137,7 +4294,8 @@ * It is recursive and modifies the input in-place. * * @since 6.3.0 - * @param array $tree Input to process. + * + * @param array $tree Input to process. * @return array The modified $tree. */ private static function resolve_custom_css_format( $tree ) { @@ -4161,7 +4319,6 @@ * * @param object $block_type The block type. * @param string $root_selector The block's root selector. - * * @return array The custom selectors set by the block. */ protected static function get_block_selectors( $block_type, $root_selector ) { @@ -4220,9 +4377,8 @@ * * @param object $metadata The related block metadata containing selectors. * @param object $node A merged theme.json node for block or variation. - * * @return array The style declarations for the node's features with custom - * selectors. + * selectors. */ protected function get_feature_declarations_for_node( $metadata, &$node ) { $declarations = array(); @@ -4380,8 +4536,8 @@ * Resolves the values of CSS variables in the given styles. * * @since 6.3.0 + * * @param WP_Theme_JSON $theme_json The theme json resolver. - * * @return WP_Theme_JSON The $theme_json with resolved variables. */ public static function resolve_variables( $theme_json ) { @@ -4441,12 +4597,15 @@ * Collects valid block style variations keyed by block type. * * @since 6.6.0 - * + * @since 6.8.0 Added the `$blocks_metadata` parameter. + * + * @param array $blocks_metadata Optional. List of metadata per block. Default is the metadata for all blocks. * @return array Valid block style variations by block type. */ - protected static function get_valid_block_style_variations() { + protected static function get_valid_block_style_variations( $blocks_metadata = array() ) { $valid_variations = array(); - foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) { + $blocks_metadata = empty( $blocks_metadata ) ? static::get_blocks_metadata() : $blocks_metadata; + foreach ( $blocks_metadata as $block_name => $block_meta ) { if ( ! isset( $block_meta['styleVariations'] ) ) { continue; }