: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @type float $increment The value used with the operator to generate the other sizes.
* @return array The spacing sizes presets or an empty array if some spacing scale values are missing or invalid.
private static function compute_spacing_sizes( $spacing_scale ) {
* This condition is intentionally missing some checks on ranges for the values in order to
* keep backwards compatibility with the previous implementation.
! isset( $spacing_scale['steps'] ) ||
! is_numeric( $spacing_scale['steps'] ) ||
0 === $spacing_scale['steps'] ||
! isset( $spacing_scale['mediumStep'] ) ||
! is_numeric( $spacing_scale['mediumStep'] ) ||
! isset( $spacing_scale['unit'] ) ||
! isset( $spacing_scale['operator'] ) ||
( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ||
! isset( $spacing_scale['increment'] ) ||
! is_numeric( $spacing_scale['increment'] )
$unit = '%' === $spacing_scale['unit'] ? '%' : sanitize_title( $spacing_scale['unit'] );
$current_step = $spacing_scale['mediumStep'];
$steps_mid_point = round( $spacing_scale['steps'] / 2, 0 );
for ( $below_midpoint_count = $steps_mid_point - 1; $spacing_scale['steps'] > 1 && $slug > 0 && $below_midpoint_count > 0; $below_midpoint_count-- ) {
if ( '+' === $spacing_scale['operator'] ) {
$current_step -= $spacing_scale['increment'];
} elseif ( $spacing_scale['increment'] > 1 ) {
$current_step /= $spacing_scale['increment'];
$current_step *= $spacing_scale['increment'];
if ( $current_step <= 0 ) {
$remainder = $below_midpoint_count;
/* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Small. */
'name' => $below_midpoint_count === $steps_mid_point - 1 ? __( 'Small' ) : sprintf( __( '%sX-Small' ), (string) $x_small_count ),
'slug' => (string) $slug,
'size' => round( $current_step, 2 ) . $unit,
if ( $below_midpoint_count === $steps_mid_point - 2 ) {
if ( $below_midpoint_count < $steps_mid_point - 2 ) {
$below_sizes = array_reverse( $below_sizes );
'name' => __( 'Medium' ),
'size' => $spacing_scale['mediumStep'] . $unit,
$current_step = $spacing_scale['mediumStep'];
$steps_above = ( $spacing_scale['steps'] - $steps_mid_point ) + $remainder;
for ( $above_midpoint_count = 0; $above_midpoint_count < $steps_above; $above_midpoint_count++ ) {
$current_step = '+' === $spacing_scale['operator']
? $current_step + $spacing_scale['increment']
: ( $spacing_scale['increment'] >= 1 ? $current_step * $spacing_scale['increment'] : $current_step / $spacing_scale['increment'] );
/* translators: %s: Digit to indicate multiple of sizing, eg. 2X-Large. */
'name' => 0 === $above_midpoint_count ? __( 'Large' ) : sprintf( __( '%sX-Large' ), (string) $x_large_count ),
'slug' => (string) $slug,
'size' => round( $current_step, 2 ) . $unit,
if ( 1 === $above_midpoint_count ) {
if ( $above_midpoint_count > 1 ) {
$spacing_sizes = $below_sizes;
foreach ( $above_sizes as $above_sizes_item ) {
$spacing_sizes[] = $above_sizes_item;
* This is used to convert the internal representation of variables to the CSS representation.
* For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`.
* @param string $value The variable such as var:preset|color|vivid-green-cyan to convert.
* @return string The converted variable.
private static function convert_custom_properties( $value ) {
$prefix_len = strlen( $prefix );
if ( str_starts_with( $value, $prefix ) ) {
$unwrapped_name = str_replace(
substr( $value, $prefix_len )
$value = "var(--wp--$unwrapped_name)";
* Given a tree, converts the internal representation of variables to the CSS representation.
* It is recursive and modifies the input in-place.
* @param array $tree Input to process.
* @return array The modified $tree.
private static function resolve_custom_css_format( $tree ) {
foreach ( $tree as $key => $data ) {
if ( is_string( $data ) && str_starts_with( $data, $prefix ) ) {
$tree[ $key ] = self::convert_custom_properties( $data );
} elseif ( is_array( $data ) ) {
$tree[ $key ] = self::resolve_custom_css_format( $data );
* Returns the selectors metadata for a block.
* @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 ) {
if ( ! empty( $block_type->selectors ) ) {
return $block_type->selectors;
$selectors = array( 'root' => $root_selector );
foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) {
$feature_selector = wp_get_block_css_selector( $block_type, $key );
if ( null !== $feature_selector ) {
$selectors[ $feature ] = array( 'root' => $feature_selector );
* Generates all the element selectors for a block.
* @param string $root_selector The block's root CSS selector.
* @return array The block's element selectors.
protected static function get_block_element_selectors( $root_selector ) {
* Assign defaults, then override those that the block sets by itself.
* If the block selector is compounded, will append the element to each
* individual block selector.
$block_selectors = explode( ',', $root_selector );
$element_selectors = array();
foreach ( static::ELEMENTS as $el_name => $el_selector ) {
$element_selector = array();
foreach ( $block_selectors as $selector ) {
if ( $selector === $el_selector ) {
$element_selector = array( $el_selector );
$element_selector[] = static::prepend_to_selector( $el_selector, $selector . ' ' );
$element_selectors[ $el_name ] = implode( ',', $element_selector );
return $element_selectors;
* Generates style declarations for a node's features e.g., color, border,
* typography etc. that have custom selectors in their related block's
* @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
protected function get_feature_declarations_for_node( $metadata, &$node ) {
if ( ! isset( $metadata['selectors'] ) ) {
$settings = isset( $this->theme_json['settings'] )
? $this->theme_json['settings']
foreach ( $metadata['selectors'] as $feature => $feature_selectors ) {
* Skip if this is the block's root selector or the block doesn't
* have any styles for the feature.
if ( 'root' === $feature || empty( $node[ $feature ] ) ) {
if ( is_array( $feature_selectors ) ) {
foreach ( $feature_selectors as $subfeature => $subfeature_selector ) {
if ( 'root' === $subfeature || empty( $node[ $feature ][ $subfeature ] ) ) {
* Create temporary node containing only the subfeature data
* to leverage existing `compute_style_properties` function.
$subfeature_node = array(
$subfeature => $node[ $feature ][ $subfeature ],
// Generate style declarations.
$new_declarations = static::compute_style_properties( $subfeature_node, $settings, null, $this->theme_json );
// Merge subfeature declarations into feature declarations.
if ( isset( $declarations[ $subfeature_selector ] ) ) {
foreach ( $new_declarations as $new_declaration ) {
$declarations[ $subfeature_selector ][] = $new_declaration;
$declarations[ $subfeature_selector ] = $new_declarations;
* Remove the subfeature from the block's node now its
* styles will be included under its own selector not the
unset( $node[ $feature ][ $subfeature ] );
* Now subfeatures have been processed and removed we can process
* feature root selector or simple string selector.
is_string( $feature_selectors ) ||
( isset( $feature_selectors['root'] ) && $feature_selectors['root'] )
$feature_selector = is_string( $feature_selectors ) ? $feature_selectors : $feature_selectors['root'];
* Create temporary node containing only the feature data
* to leverage existing `compute_style_properties` function.
$feature_node = array( $feature => $node[ $feature ] );
// Generate the style declarations.
$new_declarations = static::compute_style_properties( $feature_node, $settings, null, $this->theme_json );
* Merge new declarations with any that already exist for
* the feature selector. This may occur when multiple block
* support features use the same custom selector.
if ( isset( $declarations[ $feature_selector ] ) ) {
foreach ( $new_declarations as $new_declaration ) {
$declarations[ $feature_selector ][] = $new_declaration;
$declarations[ $feature_selector ] = $new_declarations;
* Remove the feature from the block's node now its styles
* will be included under its own selector not the block's.
unset( $node[ $feature ] );
* Replaces CSS variables with their values in place.
* @since 6.5.0 Check for empty style before processing its value.
* @param array $styles CSS declarations to convert.
* @param array $values key => value pairs to use for replacement.
private static function convert_variables_to_value( $styles, $values ) {
foreach ( $styles as $key => $style ) {
if ( is_array( $style ) ) {
$styles[ $key ] = self::convert_variables_to_value( $style, $values );
if ( 0 <= strpos( $style, 'var(' ) ) {
// find all the variables in the string in the form of var(--variable-name, fallback), with fallback in the second capture group.
$has_matches = preg_match_all( '/var\(([^),]+)?,?\s?(\S+)?\)/', $style, $var_parts );
$resolved_style = $styles[ $key ];
foreach ( $var_parts[1] as $index => $var_part ) {
$key_in_values = 'var(' . $var_part . ')';
$rule_to_replace = $var_parts[0][ $index ]; // the css rule to replace e.g. var(--wp--preset--color--vivid-green-cyan).
$fallback = $var_parts[2][ $index ]; // the fallback value.
$resolved_style = str_replace(
isset( $values[ $key_in_values ] ) ? $values[ $key_in_values ] : $rule_to_replace,
isset( $values[ $fallback ] ) ? $values[ $fallback ] : $fallback,
$styles[ $key ] = $resolved_style;
* Resolves the values of CSS variables in the given styles.
* @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 ) {
$settings = $theme_json->get_settings();
$styles = $theme_json->get_raw_data()['styles'];
$preset_vars = static::compute_preset_vars( $settings, static::VALID_ORIGINS );
$theme_vars = static::compute_theme_vars( $settings );
array_merge( $preset_vars, $theme_vars ),
function ( $carry, $item ) {
$carry[ "var({$name})" ] = $item['value'];
$theme_json->theme_json['styles'] = self::convert_variables_to_value( $styles, $vars );
* Generates a selector for a block style variation.
* @param string $variation_name Name of the block style variation.
* @param string $block_selector CSS selector for the block.
* @return string Block selector with block style variation selector added to it.
protected static function get_block_style_variation_selector( $variation_name, $block_selector ) {
$variation_class = ".is-style-$variation_name";
if ( ! $block_selector ) {
$selector_parts = explode( ',', $block_selector );
foreach ( $selector_parts as $part ) {
$result[] = preg_replace_callback(
'/((?::\([^)]+\))?\s*)([^\s:]+)/',
function ( $matches ) use ( $variation_class ) {
return $matches[1] . $matches[2] . $variation_class;
return implode( ',', $result );
* Collects valid block style variations keyed by block type.
* @return array Valid block style variations by block type.
protected static function get_valid_block_style_variations() {
$valid_variations = array();
foreach ( self::get_blocks_metadata() as $block_name => $block_meta ) {
if ( ! isset( $block_meta['styleVariations'] ) ) {
$valid_variations[ $block_name ] = array_keys( $block_meta['styleVariations'] );
return $valid_variations;