: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Processes a setting node and returns the same node
* without the insecure settings.
* @param array $input Node to process.
protected static function remove_insecure_settings( $input ) {
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
foreach ( static::VALID_ORIGINS as $origin ) {
$path_with_origin = $preset_metadata['path'];
$path_with_origin[] = $origin;
$presets = _wp_array_get( $input, $path_with_origin, null );
if ( null === $presets ) {
$escaped_preset = array();
foreach ( $presets as $preset ) {
esc_attr( esc_html( $preset['name'] ) ) === $preset['name'] &&
sanitize_html_class( $preset['slug'] ) === $preset['slug']
if ( isset( $preset_metadata['value_key'], $preset[ $preset_metadata['value_key'] ] ) ) {
$value = $preset[ $preset_metadata['value_key'] ];
isset( $preset_metadata['value_func'] ) &&
is_callable( $preset_metadata['value_func'] )
$value = call_user_func( $preset_metadata['value_func'], $preset );
foreach ( $preset_metadata['properties'] as $property ) {
if ( ! static::is_safe_css_declaration( $property, $value ) ) {
$preset_is_valid = false;
if ( $preset_is_valid ) {
$escaped_preset[] = $preset;
if ( ! empty( $escaped_preset ) ) {
_wp_array_set( $output, $path_with_origin, $escaped_preset );
// Ensure indirect properties not included in any `PRESETS_METADATA` value are allowed.
static::remove_indirect_properties( $input, $output );
* Processes a style node and returns the same node
* without the insecure styles.
* @param array $input Node to process.
protected static function remove_insecure_styles( $input ) {
$declarations = static::compute_style_properties( $input );
foreach ( $declarations as $declaration ) {
if ( static::is_safe_css_declaration( $declaration['name'], $declaration['value'] ) ) {
$path = static::PROPERTIES_METADATA[ $declaration['name'] ];
* Check the value isn't an array before adding so as to not
* double up shorthand and longhand styles.
$value = _wp_array_get( $input, $path, array() );
if ( ! is_array( $value ) ) {
_wp_array_set( $output, $path, $value );
// Ensure indirect properties not handled by `compute_style_properties` are allowed.
static::remove_indirect_properties( $input, $output );
* Checks that a declaration provided by the user is safe.
* @param string $property_name Property name in a CSS declaration, i.e. the `color` in `color: red`.
* @param string $property_value Value in a CSS declaration, i.e. the `red` in `color: red`.
protected static function is_safe_css_declaration( $property_name, $property_value ) {
$style_to_validate = $property_name . ': ' . $property_value;
$filtered = esc_html( safecss_filter_attr( $style_to_validate ) );
return ! empty( trim( $filtered ) );
* Removes indirect properties from the given input node and
* sets in the given output node.
* @param array $input Node to process.
* @param array $output The processed node. Passed by reference.
private static function remove_indirect_properties( $input, &$output ) {
foreach ( static::INDIRECT_PROPERTIES_METADATA as $property => $paths ) {
foreach ( $paths as $path ) {
$value = _wp_array_get( $input, $path );
static::is_safe_css_declaration( $property, $value )
_wp_array_set( $output, $path, $value );
* @return array Raw data.
public function get_raw_data() {
return $this->theme_json;
* Transforms the given editor settings according the
* add_theme_support format to the theme.json format.
* @param array $settings Existing editor settings.
* @return array Config that adheres to the theme.json schema.
public static function get_from_editor_settings( $settings ) {
'version' => static::LATEST_SCHEMA,
// Deprecated theme supports.
if ( isset( $settings['disableCustomColors'] ) ) {
$theme_settings['settings']['color']['custom'] = ! $settings['disableCustomColors'];
if ( isset( $settings['disableCustomGradients'] ) ) {
$theme_settings['settings']['color']['customGradient'] = ! $settings['disableCustomGradients'];
if ( isset( $settings['disableCustomFontSizes'] ) ) {
$theme_settings['settings']['typography']['customFontSize'] = ! $settings['disableCustomFontSizes'];
if ( isset( $settings['enableCustomLineHeight'] ) ) {
$theme_settings['settings']['typography']['lineHeight'] = $settings['enableCustomLineHeight'];
if ( isset( $settings['enableCustomUnits'] ) ) {
$theme_settings['settings']['spacing']['units'] = ( true === $settings['enableCustomUnits'] ) ?
array( 'px', 'em', 'rem', 'vh', 'vw', '%' ) :
$settings['enableCustomUnits'];
if ( isset( $settings['colors'] ) ) {
$theme_settings['settings']['color']['palette'] = $settings['colors'];
if ( isset( $settings['gradients'] ) ) {
$theme_settings['settings']['color']['gradients'] = $settings['gradients'];
if ( isset( $settings['fontSizes'] ) ) {
$font_sizes = $settings['fontSizes'];
// Back-compatibility for presets without units.
foreach ( $font_sizes as $key => $font_size ) {
if ( is_numeric( $font_size['size'] ) ) {
$font_sizes[ $key ]['size'] = $font_size['size'] . 'px';
$theme_settings['settings']['typography']['fontSizes'] = $font_sizes;
if ( isset( $settings['enableCustomSpacing'] ) ) {
$theme_settings['settings']['spacing']['padding'] = $settings['enableCustomSpacing'];
if ( isset( $settings['spacingSizes'] ) ) {
$theme_settings['settings']['spacing']['spacingSizes'] = $settings['spacingSizes'];
* Returns the current theme's wanted patterns(slugs) to be
* registered from Pattern Directory.
public function get_patterns() {
if ( isset( $this->theme_json['patterns'] ) && is_array( $this->theme_json['patterns'] ) ) {
return $this->theme_json['patterns'];
* Returns a valid theme.json as provided by a theme.
* Unlike get_raw_data() this returns the presets flattened, as provided by a theme.
* This also uses appearanceTools instead of their opt-ins if all of them are true.
public function get_data() {
$output = $this->theme_json;
$nodes = static::get_setting_nodes( $output );
* Flatten the theme & custom origins into a single one.
* For example, the following:
foreach ( $nodes as $node ) {
foreach ( static::PRESETS_METADATA as $preset_metadata ) {
foreach ( $preset_metadata['path'] as $preset_metadata_path ) {
$path[] = $preset_metadata_path;
$preset = _wp_array_get( $output, $path, null );
if ( null === $preset ) {
if ( isset( $preset['theme'] ) ) {
foreach ( $preset['theme'] as $item ) {
if ( isset( $preset['custom'] ) ) {
foreach ( $preset['custom'] as $item ) {
$flattened_preset = array();
foreach ( $items as $slug => $value ) {
$flattened_preset[] = array_merge( array( 'slug' => (string) $slug ), $value );
_wp_array_set( $output, $path, $flattened_preset );
* If all of the static::APPEARANCE_TOOLS_OPT_INS are true,
* this code unsets them and sets 'appearanceTools' instead.
foreach ( $nodes as $node ) {
$all_opt_ins_are_set = true;
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) {
$full_path = $node['path'];
foreach ( $opt_in_path as $opt_in_path_item ) {
$full_path[] = $opt_in_path_item;
* Use "unset prop" as a marker instead of "null" because
* "null" can be a valid value for some props (e.g. blockGap).
$opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' );
if ( 'unset prop' === $opt_in_value ) {
$all_opt_ins_are_set = false;
if ( $all_opt_ins_are_set ) {
$node_path_with_appearance_tools = $node['path'];
$node_path_with_appearance_tools[] = 'appearanceTools';
_wp_array_set( $output, $node_path_with_appearance_tools, true );
foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) {
$full_path = $node['path'];
foreach ( $opt_in_path as $opt_in_path_item ) {
$full_path[] = $opt_in_path_item;
* Use "unset prop" as a marker instead of "null" because
* "null" can be a valid value for some props (e.g. blockGap).
$opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' );
if ( true !== $opt_in_value ) {
* The following could be improved to be path independent.
* At the moment it relies on a couple of assumptions:
* - all opt-ins having a path of size 2.
* - there's two sources of settings: the top-level and the block-level.
( 1 === count( $node['path'] ) ) &&
( 'settings' === $node['path'][0] )
unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] );
if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) {
unset( $output['settings'][ $opt_in_path[0] ] );
( 3 === count( $node['path'] ) ) &&
( 'settings' === $node['path'][0] ) &&
( 'blocks' === $node['path'][1] )
$block_name = $node['path'][2];
unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] );
if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) {
unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] );
wp_recursive_ksort( $output );
* Sets the spacingSizes array based on the spacingScale values from theme.json.
* @deprecated 6.6.0 No longer used as the spacingSizes are automatically
* generated in the constructor and merge methods instead
* of manually after instantiation.
public function set_spacing_sizes() {
_deprecated_function( __METHOD__, '6.6.0' );
$spacing_scale = isset( $this->theme_json['settings']['spacing']['spacingScale'] )
? $this->theme_json['settings']['spacing']['spacingScale']
if ( ! isset( $spacing_scale['steps'] )
|| ! is_numeric( $spacing_scale['steps'] )
|| ! isset( $spacing_scale['mediumStep'] )
|| ! isset( $spacing_scale['unit'] )
|| ! isset( $spacing_scale['operator'] )
|| ! isset( $spacing_scale['increment'] )
|| ! isset( $spacing_scale['steps'] )
|| ! is_numeric( $spacing_scale['increment'] )
|| ! is_numeric( $spacing_scale['mediumStep'] )
|| ( '+' !== $spacing_scale['operator'] && '*' !== $spacing_scale['operator'] ) ) {
if ( ! empty( $spacing_scale ) ) {
/* translators: 1: theme.json, 2: settings.spacing.spacingScale */
__( 'Some of the %1$s %2$s values are invalid' ),
'settings.spacing.spacingScale'
// If theme authors want to prevent the generation of the core spacing scale they can set their theme.json spacingScale.steps to 0.
if ( 0 === $spacing_scale['steps'] ) {
$spacing_sizes = static::compute_spacing_sizes( $spacing_scale );
// If there are 7 or fewer steps in the scale revert to numbers for labels instead of t-shirt sizes.
if ( $spacing_scale['steps'] <= 7 ) {
for ( $spacing_sizes_count = 0; $spacing_sizes_count < count( $spacing_sizes ); $spacing_sizes_count++ ) {
$spacing_sizes[ $spacing_sizes_count ]['name'] = (string) ( $spacing_sizes_count + 1 );
_wp_array_set( $this->theme_json, array( 'settings', 'spacing', 'spacingSizes', 'default' ), $spacing_sizes );
* Merges two sets of spacing size presets.
* @param array $base The base set of spacing sizes.
* @param array $incoming The set of spacing sizes to merge with the base. Duplicate slugs will override the base values.
* @return array The merged set of spacing sizes.
private static function merge_spacing_sizes( $base, $incoming ) {
// Preserve the order if there are no base (spacingScale) values.
foreach ( $base as $item ) {
$merged[ $item['slug'] ] = $item;
foreach ( $incoming as $item ) {
$merged[ $item['slug'] ] = $item;
ksort( $merged, SORT_NUMERIC );
return array_values( $merged );
* Generates a set of spacing sizes by starting with a medium size and
* applying an operator with an increment value to generate the rest of the
* sizes outward from the medium size. The medium slug is '50' with the rest
* of the slugs being 10 apart. The generated names use t-shirt sizing.
* $spacing_scale = array(
* $spacing_sizes = static::compute_spacing_sizes( $spacing_scale );
* // array( 'name' => 'Small', 'slug' => '40', 'size' => '14px' ),
* // array( 'name' => 'Medium', 'slug' => '50', 'size' => '16px' ),
* // array( 'name' => 'Large', 'slug' => '60', 'size' => '18px' ),
* // array( 'name' => 'X-Large', 'slug' => '70', 'size' => '20px' ),
* @param array $spacing_scale {
* The spacing scale values. All are required.
* @type int $steps The number of steps in the scale. (up to 10 steps are supported.)
* @type float $mediumStep The middle value that gets the slug '50'. (For even number of steps, this becomes the first middle value.)
* @type string $unit The CSS unit to use for the sizes.
* @type string $operator The mathematical operator to apply to generate the other sizes. Either '+' or '*'.