: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if ( isset( self::$global_styles_presets ) ) {
return self::$global_styles_presets;
// Get the per block settings from the theme.json.
$tree = wp_get_global_settings();
$presets_by_origin = isset( $tree['color']['duotone'] ) ? $tree['color']['duotone'] : array();
self::$global_styles_presets = array();
foreach ( $presets_by_origin as $presets ) {
foreach ( $presets as $preset ) {
$filter_id = self::get_filter_id( _wp_to_kebab_case( $preset['slug'] ) );
self::$global_styles_presets[ $filter_id ] = $preset;
return self::$global_styles_presets;
* Scrape all block names from global styles and store in self::$global_styles_block_names.
* Used in conjunction with self::render_duotone_support to output the
* duotone filters defined in the theme.json global styles.
* @return string[] An array of global style block slugs, keyed on the block name.
private static function get_all_global_style_block_names() {
if ( isset( self::$global_styles_block_names ) ) {
return self::$global_styles_block_names;
// Get the per block settings from the theme.json.
$tree = WP_Theme_JSON_Resolver::get_merged_data();
$block_nodes = $tree->get_styles_block_nodes();
$theme_json = $tree->get_raw_data();
self::$global_styles_block_names = array();
foreach ( $block_nodes as $block_node ) {
// This block definition doesn't include any duotone settings. Skip it.
if ( empty( $block_node['duotone'] ) ) {
// Value looks like this: 'var(--wp--preset--duotone--blue-orange)' or 'var:preset|duotone|blue-orange'.
$duotone_attr_path = array_merge( $block_node['path'], array( 'filter', 'duotone' ) );
$duotone_attr = _wp_array_get( $theme_json, $duotone_attr_path, array() );
if ( empty( $duotone_attr ) ) {
// If it has a duotone filter preset, save the block name and the preset slug.
$slug = self::get_slug_from_attribute( $duotone_attr );
if ( $slug && $slug !== $duotone_attr ) {
self::$global_styles_block_names[ $block_node['name'] ] = $slug;
return self::$global_styles_block_names;
* Render out the duotone CSS styles and SVG.
* The hooks self::set_global_style_block_names and self::set_global_styles_presets
* must be called before this function.
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @param WP_Block $wp_block The block instance.
* @return string Filtered block content.
public static function render_duotone_support( $block_content, $block, $wp_block ) {
if ( ! $block['blockName'] ) {
$duotone_selector = self::get_selector( $wp_block->block_type );
if ( ! $duotone_selector ) {
$global_styles_block_names = self::get_all_global_style_block_names();
// The block should have a duotone attribute or have duotone defined in its theme.json to be processed.
$has_duotone_attribute = isset( $block['attrs']['style']['color']['duotone'] );
$has_global_styles_duotone = array_key_exists( $block['blockName'], $global_styles_block_names );
if ( ! $has_duotone_attribute && ! $has_global_styles_duotone ) {
// Generate the pieces needed for rendering a duotone to the page.
if ( $has_duotone_attribute ) {
* Possible values for duotone attribute:
* 1. Array of colors - e.g. array('#000000', '#ffffff').
* 2. Variable for an existing Duotone preset - e.g. 'var:preset|duotone|blue-orange' or 'var(--wp--preset--duotone--blue-orange)''
* 3. A CSS string - e.g. 'unset' to remove globally applied duotone.
$duotone_attr = $block['attrs']['style']['color']['duotone'];
$is_preset = is_string( $duotone_attr ) && self::is_preset( $duotone_attr );
$is_css = is_string( $duotone_attr ) && ! $is_preset;
$is_custom = is_array( $duotone_attr );
$slug = self::get_slug_from_attribute( $duotone_attr ); // e.g. 'blue-orange'.
$filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-blue-orange'.
$filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--blue-orange)'.
// CSS custom property, SVG filter, and block CSS.
self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value );
$slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) ); // e.g. 'unset-1'.
$filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-unset-1'.
$filter_value = $duotone_attr; // e.g. 'unset'.
self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value );
} elseif ( $is_custom ) {
$slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) ); // e.g. '000000-ffffff-2'.
$filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-000000-ffffff-2'.
$filter_value = self::get_filter_url( $filter_id ); // e.g. 'url(#wp-duotone-filter-000000-ffffff-2)'.
'colors' => $duotone_attr,
// SVG filter and block CSS.
self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data );
} elseif ( $has_global_styles_duotone ) {
$slug = $global_styles_block_names[ $block['blockName'] ]; // e.g. 'blue-orange'.
$filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-blue-orange'.
$filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--blue-orange)'.
// CSS custom property, SVG filter, and block CSS.
self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value );
// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
$tags = new WP_HTML_Tag_Processor( $block_content );
if ( $tags->next_tag() ) {
$tags->add_class( $filter_id );
return $tags->get_updated_html();
* Fixes the issue with our generated class name not being added to the block's outer container
* in classic themes due to gutenberg_restore_image_outer_container from layout block supports.
* @param string $block_content Rendered block content.
* @return string Filtered block content.
public static function restore_image_outer_container( $block_content ) {
if ( wp_theme_has_theme_json() ) {
$tags = new WP_HTML_Tag_Processor( $block_content );
'class_name' => 'wp-block-image',
if ( ! $tags->next_tag( $wrapper_query ) ) {
$tags->set_bookmark( 'wrapper-div' );
$inner_classnames = explode( ' ', $tags->get_attribute( 'class' ) );
foreach ( $inner_classnames as $classname ) {
if ( 0 === strpos( $classname, 'wp-duotone' ) ) {
$tags->remove_class( $classname );
$tags->seek( 'wrapper-div' );
$tags->add_class( $classname );
return $tags->get_updated_html();
* Appends the used block duotone filter declarations to the inline block supports CSS.
* Uses the declarations saved in earlier calls to self::enqueue_block_css.
public static function output_block_styles() {
if ( ! empty( self::$block_css_declarations ) ) {
wp_style_engine_get_stylesheet_from_css_rules(
self::$block_css_declarations,
'context' => 'block-supports',
* Appends the used global style duotone filter presets (CSS custom
* properties) to the inline global styles CSS.
* Uses the declarations saved in earlier calls to self::enqueue_global_styles_preset.
public static function output_global_styles() {
if ( ! empty( self::$used_global_styles_presets ) ) {
wp_add_inline_style( 'global-styles', self::get_global_styles_presets( self::$used_global_styles_presets ) );
* Outputs all necessary SVG for duotone filters, CSS for classic themes.
* Uses the declarations saved in earlier calls to self::enqueue_global_styles_preset
* and self::enqueue_custom_filter.
public static function output_footer_assets() {
if ( ! empty( self::$used_svg_filter_data ) ) {
echo self::get_svg_definitions( self::$used_svg_filter_data );
// In block themes, the CSS is added in the head via wp_add_inline_style in the wp_enqueue_scripts action.
if ( ! wp_is_block_theme() ) {
$style_tag_id = 'core-block-supports-duotone';
wp_register_style( $style_tag_id, false );
if ( ! empty( self::$used_global_styles_presets ) ) {
wp_add_inline_style( $style_tag_id, self::get_global_styles_presets( self::$used_global_styles_presets ) );
if ( ! empty( self::$block_css_declarations ) ) {
wp_add_inline_style( $style_tag_id, wp_style_engine_get_stylesheet_from_css_rules( self::$block_css_declarations ) );
wp_enqueue_style( $style_tag_id );
* Adds the duotone SVGs and CSS custom properties to the editor settings.
* This allows the properties to be pulled in by the EditorStyles component
* in JS and rendered in the post editor.
* @param array $settings The block editor settings from the `block_editor_settings_all` filter.
* @return array The editor settings with duotone SVGs and CSS custom properties.
public static function add_editor_settings( $settings ) {
$global_styles_presets = self::get_all_global_styles_presets();
if ( ! empty( $global_styles_presets ) ) {
if ( ! isset( $settings['styles'] ) ) {
$settings['styles'] = array();
$settings['styles'][] = array(
// For the editor we can add all of the presets by default.
'assets' => self::get_svg_definitions( $global_styles_presets ),
// The 'svgs' type is new in 6.3 and requires the corresponding JS changes in the EditorStyles component to work.
'__unstableType' => 'svgs',
// These styles not generated by global styles, so this must be false or they will be stripped out in wp_get_block_editor_settings.
'isGlobalStyles' => false,
$settings['styles'][] = array(
// For the editor we can add all of the presets by default.
'css' => self::get_global_styles_presets( $global_styles_presets ),
// This must be set and must be something other than 'theme' or they will be stripped out in the post editor <Editor> component.
'__unstableType' => 'presets',
// These styles are no longer generated by global styles, so this must be false or they will be stripped out in wp_get_block_editor_settings.
'isGlobalStyles' => false,
* Migrates the experimental duotone support flag to the stabilized location.
* This moves `supports.color.__experimentalDuotone` to `supports.filter.duotone`.
* @param array $settings Current block type settings.
* @param array $metadata Block metadata as read in via block.json.
* @return array Filtered block type settings.
public static function migrate_experimental_duotone_support_flag( $settings, $metadata ) {
$duotone_support = isset( $metadata['supports']['color']['__experimentalDuotone'] )
? $metadata['supports']['color']['__experimentalDuotone']
if ( ! isset( $settings['supports']['filter']['duotone'] ) && null !== $duotone_support ) {
_wp_array_set( $settings, array( 'supports', 'filter', 'duotone' ), (bool) $duotone_support );
* Gets the CSS filter property value from a preset.
* Exported for the deprecated function wp_get_duotone_filter_id().
* @param array $preset The duotone preset.
* @return string The CSS filter property value.
public static function get_filter_css_property_value_from_preset( $preset ) {
_deprecated_function( __FUNCTION__, '6.3.0' );
if ( isset( $preset['colors'] ) && is_string( $preset['colors'] ) ) {
return $preset['colors'];
$filter_id = self::get_filter_id_from_preset( $preset );
return 'url(#' . $filter_id . ')';