: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'l' => (float) $match[4],
'a' => '' === $match[5] ? 1 : (float) $match[5] / ( $match[6] ? 100 : 1 ),
return self::colord_hsla_to_rgba( $hsla );
* Tries to convert an incoming string into RGBA values.
* Direct port of colord's parse function simplified for our use case. This
* version only supports string parsing and only returns RGBA values.
* @link https://github.com/omgovich/colord/blob/3f859e03b0ca622eb15480f611371a0f15c9427f/src/parse.ts#L37 Sourced from colord.
* @param string $input The string to parse.
* @return array|null An array of RGBA values or null if the string is invalid.
private static function colord_parse( $input ) {
$result = self::colord_parse_hex( $input );
$result = self::colord_parse_rgba_string( $input );
$result = self::colord_parse_hsla_string( $input );
* Takes the inline CSS duotone variable from a block and return the slug.
* Handles styles slugs like:
* var:preset|duotone|blue-orange
* var(--wp--preset--duotone--blue-orange)
* @param string $duotone_attr The duotone attribute from a block.
* @return string The slug of the duotone preset or an empty string if no slug is found.
private static function get_slug_from_attribute( $duotone_attr ) {
// Uses Branch Reset Groups `(?|…)` to return one capture group.
preg_match( '/(?|var:preset\|duotone\|(\S+)|var\(--wp--preset--duotone--(\S+)\))/', $duotone_attr, $matches );
return ! empty( $matches[1] ) ? $matches[1] : '';
* Checks if we have a valid duotone preset.
* Valid presets are defined in the $global_styles_presets array.
* @param string $duotone_attr The duotone attribute from a block.
* @return bool True if the duotone preset present and valid.
private static function is_preset( $duotone_attr ) {
$slug = self::get_slug_from_attribute( $duotone_attr );
$filter_id = self::get_filter_id( $slug );
return array_key_exists( $filter_id, self::get_all_global_styles_presets() );
* Gets the CSS variable name for a duotone preset.
* --wp--preset--duotone--blue-orange
* @param string $slug The slug of the duotone preset.
* @return string The CSS variable name.
private static function get_css_custom_property_name( $slug ) {
return "--wp--preset--duotone--$slug";
* Get the ID of the duotone filter.
* @param string $slug The slug of the duotone preset.
* @return string The ID of the duotone filter.
private static function get_filter_id( $slug ) {
return "wp-duotone-$slug";
* Get the CSS variable for a duotone preset.
* var(--wp--preset--duotone--blue-orange)
* @param string $slug The slug of the duotone preset.
* @return string The CSS variable.
private static function get_css_var( $slug ) {
$name = self::get_css_custom_property_name( $slug );
* Get the URL for a duotone filter.
* url(#wp-duotone-blue-orange)
* @param string $filter_id The ID of the filter.
* @return string The URL for the duotone filter.
private static function get_filter_url( $filter_id ) {
return "url(#$filter_id)";
* Gets the SVG for the duotone filter definition.
* Whitespace is removed when SCRIPT_DEBUG is not enabled.
* @param string $filter_id The ID of the filter.
* @param array $colors An array of color strings.
* @return string An SVG with a duotone filter definition.
private static function get_filter_svg( $filter_id, $colors ) {
foreach ( $colors as $color_str ) {
$color = self::colord_parse( $color_str );
$error_message = sprintf(
/* translators: 1: Duotone colors, 2: theme.json, 3: settings.color.duotone */
__( '"%1$s" in %2$s %3$s is not a hex or rgb string.' ),
_doing_it_wrong( __METHOD__, $error_message, '6.3.0' );
$duotone_values['r'][] = $color['r'] / 255;
$duotone_values['g'][] = $color['g'] / 255;
$duotone_values['b'][] = $color['b'] / 255;
$duotone_values['a'][] = $color['a'];
xmlns="http://www.w3.org/2000/svg"
style="visibility: hidden; position: absolute; left: -9999px; overflow: hidden;"
<filter id="<?php echo esc_attr( $filter_id ); ?>">
color-interpolation-filters="sRGB"
<feComponentTransfer color-interpolation-filters="sRGB" >
<feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
<feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
<feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />
<feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" />
<feComposite in2="SourceGraphic" operator="in" />
// Clean up the whitespace.
$svg = preg_replace( "/[\r\n\t ]+/", ' ', $svg );
$svg = str_replace( '> <', '><', $svg );
* Returns the prefixed id for the duotone filter for use as a CSS id.
* Exported for the deprecated function wp_get_duotone_filter_id().
* @param array $preset Duotone preset value as seen in theme.json.
* @return string Duotone filter CSS id.
public static function get_filter_id_from_preset( $preset ) {
_deprecated_function( __FUNCTION__, '6.3.0' );
if ( isset( $preset['slug'] ) ) {
$filter_id = self::get_filter_id( $preset['slug'] );
* Gets the SVG for the duotone filter definition from a preset.
* Exported for the deprecated function wp_get_duotone_filter_property().
* @param array $preset The duotone preset.
* @return string The SVG for the filter definition.
public static function get_filter_svg_from_preset( $preset ) {
_deprecated_function( __FUNCTION__, '6.3.0' );
$filter_id = self::get_filter_id_from_preset( $preset );
return self::get_filter_svg( $filter_id, $preset['colors'] );
* Get the SVGs for the duotone filters.
* <svg><defs><filter id="wp-duotone-blue-orange">…</filter></defs></svg><svg>…</svg>
* @param array $sources The duotone presets.
* @return string The SVGs for the duotone filters.
private static function get_svg_definitions( $sources ) {
foreach ( $sources as $filter_id => $filter_data ) {
$colors = $filter_data['colors'];
$svgs .= self::get_filter_svg( $filter_id, $colors );
* Get the CSS for global styles.
* body{--wp--preset--duotone--blue-orange:url('#wp-duotone-blue-orange');}
* @since 6.6.0 Replaced body selector with `WP_Theme_JSON::ROOT_CSS_PROPERTIES_SELECTOR`.
* @param array $sources The duotone presets.
* @return string The CSS for global styles.
private static function get_global_styles_presets( $sources ) {
$css = WP_Theme_JSON::ROOT_CSS_PROPERTIES_SELECTOR . '{';
foreach ( $sources as $filter_id => $filter_data ) {
$slug = $filter_data['slug'];
$colors = $filter_data['colors'];
$css_property_name = self::get_css_custom_property_name( $slug );
$declaration_value = is_string( $colors ) ? $colors : self::get_filter_url( $filter_id );
$css .= "$css_property_name:$declaration_value;";
* Enqueue a block CSS declaration for the page.
* This does not include any SVGs.
* @param string $filter_id The filter ID. e.g. 'wp-duotone-000000-ffffff-2'.
* @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'.
* @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-000000-ffffff-2)' or 'unset'.
private static function enqueue_block_css( $filter_id, $duotone_selector, $filter_value ) {
// Build the CSS selectors to which the filter will be applied.
$selectors = explode( ',', $duotone_selector );
$selectors_scoped = array();
foreach ( $selectors as $selector_part ) {
* Assuming the selector part is a subclass selector (not a tag name)
* so we can prepend the filter id class. If we want to support elements
* such as `img` or namespaces, we'll need to add a case for that here.
$selectors_scoped[] = '.' . $filter_id . trim( $selector_part );
$selector = implode( ', ', $selectors_scoped );
self::$block_css_declarations[] = array(
'filter' => $filter_value,
* Enqueue custom filter assets for the page.
* Includes an SVG filter and block CSS declaration.
* @param string $filter_id The filter ID. e.g. 'wp-duotone-000000-ffffff-2'.
* @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'.
* @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-000000-ffffff-2)' or 'unset'.
* @param array $filter_data Duotone filter data with 'slug' and 'colors' keys.
private static function enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data ) {
self::$used_svg_filter_data[ $filter_id ] = $filter_data;
self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value );
* Enqueue preset assets for the page.
* Includes a CSS custom property, SVG filter, and block CSS declaration.
* @param string $filter_id The filter ID. e.g. 'wp-duotone-blue-orange'.
* @param string $duotone_selector The block's duotone selector. e.g. '.wp-block-image img'.
* @param string $filter_value The filter CSS value. e.g. 'url(#wp-duotone-blue-orange)' or 'unset'.
private static function enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ) {
$global_styles_presets = self::get_all_global_styles_presets();
if ( ! array_key_exists( $filter_id, $global_styles_presets ) ) {
$error_message = sprintf(
/* translators: 1: Duotone filter ID, 2: theme.json */
__( 'The duotone id "%1$s" is not registered in %2$s settings' ),
_doing_it_wrong( __METHOD__, $error_message, '6.3.0' );
self::$used_global_styles_presets[ $filter_id ] = $global_styles_presets[ $filter_id ];
self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $global_styles_presets[ $filter_id ] );
* Registers the style and colors block attributes for block types that support it.
* Block support is added with `supports.filter.duotone` in block.json.
* @param WP_Block_Type $block_type Block Type.
public static function register_duotone_support( $block_type ) {
* Previous `color.__experimentalDuotone` support flag is migrated
* to `filter.duotone` via `block_type_metadata_settings` filter.
if ( block_has_support( $block_type, array( 'filter', 'duotone' ), null ) ) {
if ( ! $block_type->attributes ) {
$block_type->attributes = array();
if ( ! array_key_exists( 'style', $block_type->attributes ) ) {
$block_type->attributes['style'] = array(
* Get the CSS selector for a block type.
* This handles selectors defined in `color.__experimentalDuotone` support
* if `filter.duotone` support is not defined.
* @param WP_Block_Type $block_type Block type to check for support.
* @return string|null The CSS selector or null if there is no support.
private static function get_selector( $block_type ) {
if ( ! ( $block_type instanceof WP_Block_Type ) ) {
* Backward compatibility with `supports.color.__experimentalDuotone`
* is provided via the `block_type_metadata_settings` filter. If
* `supports.filter.duotone` has not been set and the experimental
* property has been, the experimental property value is copied into
* `supports.filter.duotone`.
$duotone_support = block_has_support( $block_type, array( 'filter', 'duotone' ) );
if ( ! $duotone_support ) {
* If the experimental duotone support was set, that value is to be
* treated as a selector and requires scoping.
$experimental_duotone = isset( $block_type->supports['color']['__experimentalDuotone'] )
? $block_type->supports['color']['__experimentalDuotone']
if ( $experimental_duotone ) {
$root_selector = wp_get_block_css_selector( $block_type );
return is_string( $experimental_duotone )
? WP_Theme_JSON::scope_selector( $root_selector, $experimental_duotone )
// Regular filter.duotone support uses filter.duotone selectors with fallbacks.
return wp_get_block_css_selector( $block_type, array( 'filter', 'duotone' ), true );
* Scrape all possible duotone presets from global and theme styles and
* store them in self::$global_styles_presets.
* Used in conjunction with self::render_duotone_support for blocks that
* use duotone preset filters.
* @return array An array of global styles presets, keyed on the filter ID.
private static function get_all_global_styles_presets() {