: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
} elseif ( empty( $inner ) ) {
$selectors_scoped[] = $outer;
$result = implode( ', ', $selectors_scoped );
* Scopes the selectors for a given style node.
* This includes the primary selector, i.e. `$node['selector']`, as well as any custom
* selectors for features and subfeatures, e.g. `$node['selectors']['border']` etc.
* @param string $scope Selector to scope to.
* @param array $node Style node with selectors to scope.
* @return array Node with updated selectors.
protected static function scope_style_node_selectors( $scope, $node ) {
$node['selector'] = static::scope_selector( $scope, $node['selector'] );
if ( empty( $node['selectors'] ) ) {
foreach ( $node['selectors'] as $feature => $selector ) {
if ( is_string( $selector ) ) {
$node['selectors'][ $feature ] = static::scope_selector( $scope, $selector );
if ( is_array( $selector ) ) {
foreach ( $selector as $subfeature => $subfeature_selector ) {
$node['selectors'][ $feature ][ $subfeature ] = static::scope_selector( $scope, $subfeature_selector );
* Gets preset values keyed by slugs based on settings and metadata.
* 'fontFamilies' => array(
* 'fontFamily' => '"Helvetica Neue", sans-serif',
* 'colors' => 'Georgia, serif',
* 'path' => array( 'typography', 'fontFamilies' ),
* 'value_key' => 'fontFamily',
* $values_by_slug = get_settings_values_by_slug();
* // $values_by_slug === array(
* // 'sans-serif' => '"Helvetica Neue", sans-serif',
* // 'serif' => 'Georgia, serif',
* @since 6.6.0 Passing $settings to the callbacks defined in static::PRESETS_METADATA.
* @param array $settings Settings to process.
* @param array $preset_metadata One of the PRESETS_METADATA values.
* @param string[] $origins List of origins to process.
* @return array Array of presets where each key is a slug and each value is the preset value.
protected static function get_settings_values_by_slug( $settings, $preset_metadata, $origins ) {
$preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() );
foreach ( $origins as $origin ) {
if ( ! isset( $preset_per_origin[ $origin ] ) ) {
foreach ( $preset_per_origin[ $origin ] as $preset ) {
$slug = _wp_to_kebab_case( $preset['slug'] );
if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) {
$value_key = $preset_metadata['value_key'];
$value = $preset[ $value_key ];
isset( $preset_metadata['value_func'] ) &&
is_callable( $preset_metadata['value_func'] )
$value_func = $preset_metadata['value_func'];
$value = call_user_func( $value_func, $preset, $settings );
// If we don't have a value, then don't add it to the result.
$result[ $slug ] = $value;
* Similar to get_settings_values_by_slug, but doesn't compute the value.
* @param array $settings Settings to process.
* @param array $preset_metadata One of the PRESETS_METADATA values.
* @param string[] $origins List of origins to process.
* @return array Array of presets where the key and value are both the slug.
protected static function get_settings_slugs( $settings, $preset_metadata, $origins = null ) {
if ( null === $origins ) {
$origins = static::VALID_ORIGINS;
$preset_per_origin = _wp_array_get( $settings, $preset_metadata['path'], array() );
foreach ( $origins as $origin ) {
if ( ! isset( $preset_per_origin[ $origin ] ) ) {
foreach ( $preset_per_origin[ $origin ] as $preset ) {
$slug = _wp_to_kebab_case( $preset['slug'] );
// Use the array as a set so we don't get duplicates.
$result[ $slug ] = $slug;
* Transforms a slug into a CSS Custom Property.
* @param string $input String to replace.
* @param string $slug The slug value to use to generate the custom property.
* @return string The CSS Custom Property. Something along the lines of `--wp--preset--color--black`.
protected static function replace_slug_in_string( $input, $slug ) {
return strtr( $input, array( '$slug' => $slug ) );
* Given the block settings, extracts the CSS Custom Properties
* for the presets and adds them to the $declarations array
* 'name' => 'property_name',
* 'value' => 'property_value,
* @since 5.9.0 Added the `$origins` parameter.
* @param array $settings Settings to process.
* @param string[] $origins List of origins to process.
* @return array The modified $declarations.
protected static function compute_preset_vars( $settings, $origins ) {
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
if ( empty( $preset_metadata['css_vars'] ) ) {
$values_by_slug = static::get_settings_values_by_slug( $settings, $preset_metadata, $origins );
foreach ( $values_by_slug as $slug => $value ) {
'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ),
* Given an array of settings, extracts the CSS Custom Properties
* for the custom values and adds them to the $declarations
* array following the format:
* 'name' => 'property_name',
* 'value' => 'property_value,
* @param array $settings Settings to process.
* @return array The modified $declarations.
protected static function compute_theme_vars( $settings ) {
$custom_values = isset( $settings['custom'] ) ? $settings['custom'] : array();
$css_vars = static::flatten_tree( $custom_values );
foreach ( $css_vars as $key => $value ) {
'name' => '--wp--custom--' . $key,
* Given a tree, it creates a flattened one
* by merging the keys and binding the leaf values
* It also transforms camelCase names into kebab-case
* and substitutes '/' by '-'.
* This is thought to be useful to generate
* CSS Custom Properties from a tree,
* although there's nothing in the implementation
* of this function that requires that format.
* For example, assuming the given prefix is '--wp'
* and the token is '--', for this input tree:
* 'some/property': 'value',
* 'sub-property': 'value'
* it'll return this output:
* '--wp--some-property': 'value',
* '--wp--nested-property--sub-property': 'value'
* @param array $tree Input tree to process.
* @param string $prefix Optional. Prefix to prepend to each variable. Default empty string.
* @param string $token Optional. Token to use between levels. Default '--'.
* @return array The flattened tree.
protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) {
foreach ( $tree as $property => $value ) {
$new_key = $prefix . str_replace(
strtolower( _wp_to_kebab_case( $property ) )
if ( is_array( $value ) ) {
$new_prefix = $new_key . $token;
$flattened_subtree = static::flatten_tree( $value, $new_prefix, $token );
foreach ( $flattened_subtree as $subtree_key => $subtree_value ) {
$result[ $subtree_key ] = $subtree_value;
$result[ $new_key ] = $value;
* Given a styles array, it extracts the style properties
* and adds them to the $declarations array following the format:
* 'name' => 'property_name',
* 'value' => 'property_value,
* @since 5.9.0 Added the `$settings` and `$properties` parameters.
* @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.
* @param array $styles Styles to process.
* @param array $settings Theme settings.
* @param array $properties Properties metadata.
* @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.
protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null, $selector = null, $use_root_padding = null ) {
if ( null === $properties ) {
$properties = static::PROPERTIES_METADATA;
if ( empty( $styles ) ) {
$root_variable_duplicates = array();
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 ) ) {
* Root-level padding styles don't currently support strings with CSS shorthand values.
* This may change: https://github.com/WordPress/gutenberg/issues/40132.
if ( '--wp--style--root--padding' === $css_property && is_string( $value ) ) {
if ( str_starts_with( $css_property, '--wp--style--root--' ) && $use_root_padding ) {
$root_variable_duplicates[] = substr( $css_property, strlen( '--wp--style--root--' ) );
* 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 );
isset( static::PROTECTED_PROPERTIES[ $path_string ] ) &&
_wp_array_get( $settings, static::PROTECTED_PROPERTIES[ $path_string ], null ) === null
// 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 ) ) {
// Calculates fluid typography rules where available.
if ( 'font-size' === $css_property ) {
* wp_get_typography_font_size_value() will check
* if fluid typography has been activated and also
* whether the incoming value can be converted to a fluid value.
* Values that already have a clamp() function will not pass the test,
* and therefore the original $value will be returned.
* Pass the current theme_json settings to override any global settings.
$value = wp_get_typography_font_size_value( array( 'size' => $value ), $settings );
if ( 'aspect-ratio' === $css_property ) {
// For aspect ratio to work, other dimensions rules must be unset.
// This ensures that a fixed height does not override the aspect ratio.
// If a variable value is added to the root, the corresponding property should be removed.
foreach ( $root_variable_duplicates as $duplicate ) {
$discard = array_search( $duplicate, array_column( $declarations, 'name' ), true );
if ( is_numeric( $discard ) ) {
array_splice( $declarations, $discard, 1 );
* Returns the style property for the given path.
* It also converts references to a path to the value
* stored at that location, e.g.
* { "ref": "style.color.background" } => "#fff".
* @since 5.9.0 Added support for values of array type, which are returned as is.
* @since 6.1.0 Added the `$theme_json` parameter.
* @since 6.3.0 It no longer converts the internal format "var:preset|color|secondary"
* 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.
* @param array $styles Styles subtree.
* @param array $path Which property to process.
* @param array $theme_json Theme JSON array.
* @return string|array Style property value.
protected static function get_property_value( $styles, $path, $theme_json = null ) {
$value = _wp_array_get( $styles, $path, '' );
if ( '' === $value || null === $value ) {
// No need to process the value further.
* 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.
* For example: { "ref": "style.color.background" } => "#fff".
if ( is_array( $value ) && isset( $value['ref'] ) ) {
$value_path = explode( '.', $value['ref'] );
$ref_value = _wp_array_get( $theme_json, $value_path );
// Only use the ref value if we find anything.
if ( ! empty( $ref_value ) && is_string( $ref_value ) ) {
if ( is_array( $ref_value ) && isset( $ref_value['ref'] ) ) {
$path_string = json_encode( $path );
$ref_value_string = json_encode( $ref_value );
/* translators: 1: theme.json, 2: Value name, 3: Value path, 4: Another value name. */
__( 'Your %1$s file uses a dynamic value (%2$s) for the path at %3$s. However, the value at %3$s is also a dynamic value (pointing to %4$s) and pointing to another dynamic value is not supported. Please update %3$s to point directly to %4$s.' ),
if ( is_array( $value ) ) {
* Builds metadata for the setting nodes, which returns in the form of:
* 'path' => ['path', 'to', 'some', 'node' ],
* 'selector' => 'CSS selector for some node'
* 'path' => [ 'path', 'to', 'other', 'node' ],
* 'selector' => 'CSS selector for other node'
* @param array $theme_json The tree to extract setting nodes from.
* @param array $selectors List of selectors per block.
* @return array An array of setting nodes metadata.
protected static function get_setting_nodes( $theme_json, $selectors = array() ) {
if ( ! isset( $theme_json['settings'] ) ) {
'path' => array( 'settings' ),
'selector' => static::ROOT_CSS_PROPERTIES_SELECTOR,
// Calculate paths for blocks.
if ( ! isset( $theme_json['settings']['blocks'] ) ) {