diff --git a/lib/block-supports/duotone.php b/lib/block-supports/duotone.php index a9b2679bbee70a..153a96cf128a89 100644 --- a/lib/block-supports/duotone.php +++ b/lib/block-supports/duotone.php @@ -22,7 +22,9 @@ add_filter( 'render_block', array( 'WP_Duotone_Gutenberg', 'render_duotone_support' ), 10, 2 ); // Enqueue styles. +// Block styles (core-block-supports-inline-css) before the style engine (gutenberg_enqueue_stored_styles). // Global styles (global-styles-inline-css) after the other global styles (gutenberg_enqueue_global_styles). +add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_block_styles' ), 9 ); add_action( 'wp_enqueue_scripts', array( 'WP_Duotone_Gutenberg', 'output_global_styles' ), 11 ); // Add SVG filters to the footer. Also, for classic themes, output block styles (core-block-supports-inline-css). diff --git a/lib/class-wp-duotone-gutenberg.php b/lib/class-wp-duotone-gutenberg.php index 5b7927a7afa216..970840b683d270 100644 --- a/lib/class-wp-duotone-gutenberg.php +++ b/lib/class-wp-duotone-gutenberg.php @@ -74,17 +74,17 @@ class WP_Duotone_Gutenberg { /** * An array of Duotone SVG and CSS output needed for the frontend duotone rendering based on what is - * being output on the page. Organized by a slug of the preset/color group and the information needed + * being output on the page. Organized by an id of the preset/color group and the information needed * to generate the SVG and CSS at render. * * Example: * [ - * 'blue-orange' => [ + * 'wp-duotone-blue-orange' => [ * 'slug' => 'blue-orange', * 'colors' => [ '#0000ff', '#ffcc00' ], * ], * 'wp-duotone-000000-ffffff-2' => [ - * 'slug' => 'wp-duotone-000000-ffffff-2', + * 'slug' => '000000-ffffff-2', * 'colors' => [ '#000000', '#ffffff' ], * ], * ] @@ -92,7 +92,42 @@ class WP_Duotone_Gutenberg { * @since 6.3.0 * @var array */ - private static $output = array(); + private static $used_svg_filter_data = array(); + + /** + * All of the duotone filter data from presets for CSS custom properties on + * the page. + * + * Example: + * [ + * 'wp-duotone-blue-orange' => [ + * 'slug' => 'blue-orange', + * 'colors' => [ '#0000ff', '#ffcc00' ], + * ], + * … + * ] + * + * @var array + */ + private static $used_global_styles_presets = array(); + + /** + * All of the block CSS declarations for styles on the page. + * + * Example: + * [ + * [ + * 'selector' => '.wp-duotone-000000-ffffff-2.wp-block-image img', + * 'declarations' => [ + * 'filter' => 'url(#wp-duotone-000000-ffffff-2)', + * ], + * ], + * … + * ] + * + * @var array + */ + private static $block_css_declarations = array(); /** * Prefix used for generating and referencing duotone CSS custom properties. @@ -430,10 +465,9 @@ public static function set_global_styles_presets() { foreach ( $presets_by_origin as $presets ) { foreach ( $presets as $preset ) { - self::$global_styles_presets[ _wp_to_kebab_case( $preset['slug'] ) ] = array( - 'slug' => $preset['slug'], - 'colors' => $preset['colors'], - ); + $filter_id = self::get_filter_id( _wp_to_kebab_case( $preset['slug'] ) ); + + self::$global_styles_presets[ $filter_id ] = $preset; } } } @@ -491,9 +525,10 @@ private static function gutenberg_get_slug_from_attr( $duotone_attr ) { * @return bool True if the duotone preset present and valid. */ private static function is_preset( $duotone_attr ) { - $slug = self::gutenberg_get_slug_from_attr( $duotone_attr ); + $slug = self::gutenberg_get_slug_from_attr( $duotone_attr ); + $filter_id = self::get_filter_id( $slug ); - return array_key_exists( $slug, self::$global_styles_presets ); + return array_key_exists( $filter_id, self::$global_styles_presets ); } /** @@ -516,6 +551,16 @@ private static function get_filter_id( $slug ) { return self::FILTER_ID_PREFIX . $slug; } + /** + * Get the URL for a duotone filter. + * + * @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. * @@ -600,34 +645,17 @@ private static function get_css_var( $slug ) { return 'var(' . self::get_css_custom_property_name( $slug ) . ')'; } - /** - * Get the CSS declaration for a duotone preset. - * Example: --wp--preset--duotone--blue-orange: url('#wp-duotone-blue-orange'); - * - * @param array $filter_data The duotone data for presets and custom filters. - * @return string The CSS declaration. - */ - private static function get_css_custom_property_declaration( $filter_data ) { - $declaration_value = self::get_filter_css_property_value_from_preset( $filter_data ); - $duotone_preset_css_property_name = self::get_css_custom_property_name( $filter_data['slug'] ); - return $duotone_preset_css_property_name . ': ' . $declaration_value . ';'; - } - /** * Outputs all necessary SVG for duotone filters, CSS for classic themes. */ public static function output_footer_assets() { - foreach ( self::$output as $filter_data ) { - - // SVG will be output on the page later. - $filter_svg = self::get_filter_svg_from_preset( $filter_data ); - - echo $filter_svg; + if ( ! empty( self::$used_svg_filter_data ) ) { + echo self::get_svg_definitions( self::$used_svg_filter_data ); + } - // This is for classic themes - 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() ) { - wp_add_inline_style( 'core-block-supports', 'body{' . self::get_css_custom_property_declaration( $filter_data ) . '}' ); - } + // This is for classic themes - 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() && ! empty( self::$used_global_styles_presets ) ) { + wp_add_inline_style( 'core-block-supports', self::get_global_styles_presets( self::$used_global_styles_presets ) ); } } @@ -640,32 +668,29 @@ public static function output_footer_assets() { * @return array The editor settings with duotone SVGs and CSS custom properties. */ public static function add_editor_settings( $settings ) { - $duotone_svgs = ''; - $duotone_css = 'body{'; - foreach ( self::$global_styles_presets as $filter_data ) { - $duotone_svgs .= self::get_filter_svg_from_preset( $filter_data ); - $duotone_css .= self::get_css_custom_property_declaration( $filter_data ); - } - $duotone_css .= '}'; - - if ( ! isset( $settings['styles'] ) ) { - $settings['styles'] = array(); - } + if ( ! empty( self::$global_styles_presets ) ) { + if ( ! isset( $settings['styles'] ) ) { + $settings['styles'] = array(); + } - $settings['styles'][] = array( - 'assets' => $duotone_svgs, - // The 'svgs' type is new in 6.3 and requires the corresponding JS changes in the EditorStyles component to work. - '__unstableType' => 'svgs', - 'isGlobalStyles' => false, - ); + $settings['styles'][] = array( + // For the editor we can add all of the presets by default. + 'assets' => self::get_svg_definitions( self::$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 gutenberg_get_block_editor_settings. + 'isGlobalStyles' => false, + ); - $settings['styles'][] = array( - 'css' => $duotone_css, - // This must be set and must be something other than 'theme' or they will be stripped out in the post 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 gutenberg_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( self::$global_styles_presets ), + // This must be set and must be something other than 'theme' or they will be stripped out in the post 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 gutenberg_get_block_editor_settings. + 'isGlobalStyles' => false, + ); + } return $settings; } @@ -674,24 +699,63 @@ public static function add_editor_settings( $settings ) { * Appends the used global style duotone filter CSS Vars to the inline global styles CSS */ public static function output_global_styles() { - - if ( empty( self::$output ) ) { - return; + if ( ! empty( self::$used_global_styles_presets ) ) { + wp_add_inline_style( 'global-styles', self::get_global_styles_presets( self::$used_global_styles_presets ) ); } + } - $duotone_css_vars = ''; - - foreach ( self::$output as $filter_data ) { - if ( ! array_key_exists( $filter_data['slug'], self::$global_styles_presets ) ) { - continue; - } + /** + * Appends the used block duotone filter declarations to the inline block supports CSS. + */ + public static function output_block_styles() { + if ( ! empty( self::$block_css_declarations ) ) { + gutenberg_style_engine_get_stylesheet_from_css_rules( + self::$block_css_declarations, + array( + 'context' => 'block-supports', + ) + ); + } + } - $duotone_css_vars .= self::get_css_custom_property_declaration( $filter_data ); + /** + * Get the SVGs for the duotone filters. + * + * Example output: + * + * + * @param array $sources The duotone presets. + * @return string The SVGs for the duotone filters. + */ + private static function get_svg_definitions( $sources ) { + $svgs = ''; + foreach ( $sources as $filter_id => $filter_data ) { + $colors = $filter_data['colors']; + $svgs .= self::get_filter_svg( $filter_id, $colors ); } + return $svgs; + } - if ( ! empty( $duotone_css_vars ) ) { - wp_add_inline_style( 'global-styles', 'body{' . $duotone_css_vars . '}' ); + /** + * Get the CSS for global styles. + * + * Example output: + * body{--wp--preset--duotone--blue-orange:url('#wp-duotone-blue-orange');} + * + * @param array $sources The duotone presets. + * @return string The CSS for global styles. + */ + private static function get_global_styles_presets( $sources ) { + $css = 'body{'; + 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 . ';'; } + $css .= '}'; + return $css; } /** @@ -730,6 +794,60 @@ private static function get_selector( $block_name ) { } } + /** + * Enqueue a block CSS declaration for the page. + * + * @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( + 'selector' => $selector, + '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 ) { + self::$used_global_styles_presets[ $filter_id ] = self::$global_styles_presets[ $filter_id ]; + self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, self::$global_styles_presets[ $filter_id ] ); + } + /** * Registers the style and colors block attributes for block types that support it. * @@ -793,86 +911,41 @@ public static function render_duotone_support( $block_content, $block ) { if ( $is_preset ) { - // Extract the slug from the preset variable string. - $slug = self::gutenberg_get_slug_from_attr( $duotone_attr ); - - // Utilize existing preset CSS custom property. - $declaration_value = self::get_css_var( $slug ); + $slug = self::gutenberg_get_slug_from_attr( $duotone_attr ); // e.g. 'green-blue'. + $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-green-blue'. + $filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--green-blue)'. - self::$output[ $slug ] = self::$global_styles_presets[ $slug ]; + // CSS custom property, SVG filter, and block CSS. + self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ); } elseif ( $is_css ) { - // Build a unique slug for the filter based on the CSS value. - $slug = wp_unique_id( sanitize_key( $duotone_attr . '-' ) ); + $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'. - // Pass through the CSS value. - $declaration_value = $duotone_attr; + // Just block CSS. + self::enqueue_block_css( $filter_id, $duotone_selector, $filter_value ); } elseif ( $is_custom ) { - // Build a unique slug for the filter based on the array of colors. - $slug = wp_unique_id( sanitize_key( implode( '-', $duotone_attr ) . '-' ) ); - - $filter_data = array( + $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)'. + $filter_data = array( 'slug' => $slug, 'colors' => $duotone_attr, ); - // Build a customized CSS filter property for unique slug. - $declaration_value = self::get_filter_css_property_value_from_preset( $filter_data ); - self::$output[ $slug ] = $filter_data; + // SVG filter and block CSS. + self::enqueue_custom_filter( $filter_id, $duotone_selector, $filter_value, $filter_data ); } } elseif ( $has_global_styles_duotone ) { - $slug = self::$global_styles_block_names[ $block['blockName'] ]; - - // Utilize existing preset CSS custom property. - $declaration_value = self::get_css_var( $slug ); + $slug = self::$global_styles_block_names[ $block['blockName'] ]; // e.g. 'green-blue'. + $filter_id = self::get_filter_id( $slug ); // e.g. 'wp-duotone-filter-green-blue'. + $filter_value = self::get_css_var( $slug ); // e.g. 'var(--wp--preset--duotone--green-blue)'. - self::$output[ $slug ] = self::$global_styles_presets[ $slug ]; + // CSS custom property, SVG filter, and block CSS. + self::enqueue_global_styles_preset( $filter_id, $duotone_selector, $filter_value ); } - // - Applied as a class attribute to the block wrapper. - // - Used as a selector to apply the filter to the block. - $filter_id = self::get_filter_id_from_preset( array( 'slug' => $slug ) ); - - // 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 ); - - // We only want to add the selector if we have it in the output already, essentially skipping 'unset'. - if ( array_key_exists( $slug, self::$output ) ) { - self::$output[ $slug ]['selector'] = $selector; - } - - // Pass styles to the block-supports stylesheet via the style engine. - // This ensures that Duotone styles are included in a single stylesheet, - // avoiding multiple style tags or multiple stylesheets being output to - // the site frontend. - gutenberg_style_engine_get_stylesheet_from_css_rules( - array( - array( - 'selector' => $selector, - 'declarations' => array( - // !important is needed because these styles - // render before global styles, - // and they should be overriding the duotone - // filters set by global styles. - 'filter' => $declaration_value . ' !important', - ), - ), - ), - array( - 'context' => 'block-supports', - ) - ); - // 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() ) { @@ -904,10 +977,17 @@ public static function migrate_experimental_duotone_support_flag( $settings, $me /** * Returns the prefixed id for the duotone filter for use as a CSS id. * + * Exported for the deprecated function gutenberg_get_duotone_filter_id(). + * + * @since 6.3.0 + * @deprecated 6.3.0 + * * @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' ); + $filter_id = ''; if ( isset( $preset['slug'] ) ) { $filter_id = self::get_filter_id( $preset['slug'] ); @@ -918,10 +998,17 @@ public static function get_filter_id_from_preset( $preset ) { /** * Gets the SVG for the duotone filter definition from a preset. * + * Exported for the deprecated function gutenberg_get_duotone_filter_property(). + * + * @since 6.3.0 + * @deprecated 6.3.0 + * * @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'] ); } @@ -929,16 +1016,22 @@ public static function get_filter_svg_from_preset( $preset ) { /** * Gets the CSS filter property value from a preset. * + * Exported for the deprecated function gutenberg_get_duotone_filter_id(). + * + * @since 6.3.0 + * @deprecated 6.3.0 + * * @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 . ')'; } }