From 70c0f6f0e1ffdc7dad1dc790274f62e814bc5c67 Mon Sep 17 00:00:00 2001 From: Sergey Biryukov Date: Sat, 10 Sep 2022 12:38:12 +0000 Subject: [PATCH] Editor: Backport Elements API updates. This commit backports the original PRs from Gutenberg repository: * [https://github.com/WordPress/gutenberg/pull/40260 #40260 Add support for button elements to theme.json] * [https://github.com/WordPress/gutenberg/pull/40889 #40889 Theme Json: Don't output double selectors for elements inside blocks] * [https://github.com/WordPress/gutenberg/pull/41140 #41140 Global Styles: Add support for caption elements] * [https://github.com/WordPress/gutenberg/pull/41160 #41160 Global Styles: Load block CSS conditionally] * [https://github.com/WordPress/gutenberg/pull/41240 #41240 Global Styles: Button Element: update button element selector] * [https://github.com/WordPress/gutenberg/pull/41335 #41335 Duotone: Fix CSS Selectors rendered by theme.json duotone/filter settings for blocks on public pages] * [https://github.com/WordPress/gutenberg/pull/41446 #41446 Block styles: Account for style block nodes that have no name] * [https://github.com/WordPress/gutenberg/pull/41696 #41696 Global Styles: Allow references to values in other locations in the tree] * [https://github.com/WordPress/gutenberg/pull/41753 #41753 Elements: Add an API make it easier to get class names] * [https://github.com/WordPress/gutenberg/pull/41786 #41786 Support pseudo selectors on elements in theme json] * [https://github.com/WordPress/gutenberg/pull/41822 #41822 Elements: Button - Fix element selectors] * [https://github.com/WordPress/gutenberg/pull/41981 #41981 Global Styles: Add support for heading elements] * [https://github.com/WordPress/gutenberg/pull/42072 #42072 Fix link element hover bleeding into button element default styles] * [https://github.com/WordPress/gutenberg/pull/42096 #42096 Add visited to link element allowed pseudo selector list] * [https://github.com/WordPress/gutenberg/pull/42669 #42669 Link elements: Add a :where selector to the :not to lower specificity] * [https://github.com/WordPress/gutenberg/pull/42776 #42776 Theme JSON: Add a static $blocks_metadata data definition to the Gutenberg instance of WP_Theme_JSON] * [https://github.com/WordPress/gutenberg/pull/43088 #43088 Pseudo elements supports on button elements] * [https://github.com/WordPress/gutenberg/pull/43167 #43167 Theme_JSON: Use existing append_to_selector for pseudo elements] * [https://github.com/WordPress/gutenberg/pull/43988 #43988 Styles API: Fixed selectors for nested elements] Props onemaggie, bernhard-reiter, cbravobernal, mmaattiiaass, scruffian, andraganescu, dpcalhoun, get_dave, Mamaduka, SergeyBiryukov. See #56467. Built from https://develop.svn.wordpress.org/trunk@54118 git-svn-id: http://core.svn.wordpress.org/trunk@53677 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-theme-json.php | 416 +++++++++++++++++---- wp-includes/global-styles-and-settings.php | 42 +++ wp-includes/script-loader.php | 34 ++ wp-includes/version.php | 2 +- 4 files changed, 413 insertions(+), 81 deletions(-) diff --git a/wp-includes/class-wp-theme-json.php b/wp-includes/class-wp-theme-json.php index 2833e8be9c9..69a349f970c 100644 --- a/wp-includes/class-wp-theme-json.php +++ b/wp-includes/class-wp-theme-json.php @@ -343,15 +343,27 @@ class WP_Theme_JSON { ), ); + /** + * Defines which pseudo selectors are enabled for which elements. + * + * Note: this will affect both top-level and block-level elements. + * + * @since 6.1.0 + */ + const VALID_ELEMENT_PSEUDO_SELECTORS = array( + 'link' => array( ':hover', ':focus', ':active', ':visited' ), + 'button' => array( ':hover', ':focus', ':active', ':visited' ), + ); + /** * The valid elements that can be found under styles. * * @since 5.8.0 - * @since 6.1.0 Added `heading`, `button`, and `caption` to the elements. + * @since 6.1.0 Added `heading`, `button`. and `caption` elements. * @var string[] */ const ELEMENTS = array( - 'link' => 'a', + 'link' => 'a:where(:not(.wp-element-button))', // The `where` is needed to lower the specificity. 'heading' => 'h1, h2, h3, h4, h5, h6', 'h1' => 'h1', 'h2' => 'h2', @@ -365,6 +377,29 @@ class WP_Theme_JSON { 'caption' => '.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption', ); + const __EXPERIMENTAL_ELEMENT_CLASS_NAMES = array( + 'button' => 'wp-element-button', + 'caption' => 'wp-element-caption', + ); + + /** + * Returns a class name by an element name. + * + * @since 6.1.0 + * + * @param string $element The name of the element. + * @return string The name of the class. + */ + public static function get_element_class_name( $element ) { + $class_name = ''; + + if ( array_key_exists( $element, static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES ) ) { + $class_name = static::__EXPERIMENTAL_ELEMENT_CLASS_NAMES[ $element ]; + } + + return $class_name; + } + /** * Options that settings.appearanceTools enables. * @@ -488,16 +523,21 @@ protected static function do_opt_in_into_settings( &$context ) { * @return array The sanitized output. */ protected static function sanitize( $input, $valid_block_names, $valid_element_names ) { + $output = array(); if ( ! is_array( $input ) ) { return $output; } + // Preserve only the top most level keys. $output = array_intersect_key( $input, array_flip( static::VALID_TOP_LEVEL_KEYS ) ); - // Some styles are only meant to be available at the top-level (e.g.: blockGap), - // hence, the schema for blocks & elements should not have them. + /* + * Remove any rules that are annotated as "top" in VALID_STYLES constant. + * Some styles are only meant to be available at the top-level (e.g.: blockGap), + * hence, the schema for blocks & elements should not have them. + */ $styles_non_top_level = static::VALID_STYLES; foreach ( array_keys( $styles_non_top_level ) as $section ) { foreach ( array_keys( $styles_non_top_level[ $section ] ) as $prop ) { @@ -510,9 +550,24 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n // Build the schema based on valid block & element names. $schema = array(); $schema_styles_elements = array(); + + /* + * Set allowed element pseudo selectors based on per element allow list. + * Target data structure in schema: + * e.g. + * - top level elements: `$schema['styles']['elements']['link'][':hover']`. + * - block level elements: `$schema['styles']['blocks']['core/button']['elements']['link'][':hover']`. + */ foreach ( $valid_element_names as $element ) { $schema_styles_elements[ $element ] = $styles_non_top_level; + + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { + $schema_styles_elements[ $element ][ $pseudo_selector ] = $styles_non_top_level; + } + } } + $schema_styles_blocks = array(); $schema_settings_blocks = array(); foreach ( $valid_block_names as $block ) { @@ -520,6 +575,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks[ $block ] = $styles_non_top_level; $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; } + $schema['styles'] = static::VALID_STYLES; $schema['styles']['blocks'] = $schema_styles_blocks; $schema['styles']['elements'] = $schema_styles_elements; @@ -549,6 +605,30 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n return $output; } + /** + * Appends a sub-selector to an existing one. + * + * Given the compounded $selector "h1, h2, h3" + * and the $to_append selector ".some-class" the result will be + * "h1.some-class, h2.some-class, h3.some-class". + * + * @since 5.8.0 + * @since 6.1.0 Added append position. + * + * @param string $selector Original selector. + * @param string $to_append Selector to append. + * @param string $position A position sub-selector should be appended. Default 'right'. + * @return string + */ + protected static function append_to_selector( $selector, $to_append, $position = 'right' ) { + $new_selectors = array(); + $selectors = explode( ',', $selector ); + foreach ( $selectors as $sel ) { + $new_selectors[] = 'right' === $position ? $sel . $to_append : $to_append . $sel; + } + return implode( ',', $new_selectors ); + } + /** * Returns the metadata for each block. * @@ -611,7 +691,11 @@ protected static function get_blocks_metadata() { foreach ( static::ELEMENTS as $el_name => $el_selector ) { $element_selector = array(); foreach ( $block_selectors as $selector ) { - $element_selector[] = $selector . ' ' . $el_selector; + if ( $selector === $el_selector ) { + $element_selector = array( $el_selector ); + break; + } + $element_selector[] = static::append_to_selector( $el_selector, $selector . ' ', 'left' ); } static::$blocks_metadata[ $block_name ]['elements'][ $el_name ] = implode( ',', $element_selector ); } @@ -810,54 +894,7 @@ protected function get_block_classes( $style_nodes ) { if ( null === $metadata['selector'] ) { continue; } - - $node = _wp_array_get( $this->theme_json, $metadata['path'], array() ); - $selector = $metadata['selector']; - $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); - $declarations = static::compute_style_properties( $node, $settings ); - - // 1. Separate the ones who use the general selector - // and the ones who use the duotone selector. - $declarations_duotone = array(); - foreach ( $declarations as $index => $declaration ) { - if ( 'filter' === $declaration['name'] ) { - unset( $declarations[ $index ] ); - $declarations_duotone[] = $declaration; - } - } - - /* - * Reset default browser margin on the root body element. - * This is set on the root selector **before** generating the ruleset - * from the `theme.json`. This is to ensure that if the `theme.json` declares - * `margin` in its `spacing` declaration for the `body` element then these - * user-generated values take precedence in the CSS cascade. - * @link https://github.com/WordPress/gutenberg/issues/36147. - */ - if ( static::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= 'body { margin: 0; }'; - } - - // 2. Generate the rules that use the general selector. - $block_rules .= static::to_ruleset( $selector, $declarations ); - - // 3. Generate the rules that use the duotone selector. - if ( isset( $metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { - $selector_duotone = static::scope_selector( $metadata['selector'], $metadata['duotone'] ); - $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); - } - - if ( static::ROOT_BLOCK_SELECTOR === $selector ) { - $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; - $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; - $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; - - $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; - if ( $has_block_gap_support ) { - $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; - $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; - } - } + $block_rules .= static::get_styles_for_block( $metadata ); } return $block_rules; @@ -972,29 +1009,6 @@ static function ( $carry, $element ) { return $selector . '{' . $declaration_block . '}'; } - /** - * Function that appends a sub-selector to a existing one. - * - * Given the compounded $selector "h1, h2, h3" - * and the $to_append selector ".some-class" the result will be - * "h1.some-class, h2.some-class, h3.some-class". - * - * @since 5.8.0 - * - * @param string $selector Original selector. - * @param string $to_append Selector to append. - * @return string - */ - protected static function append_to_selector( $selector, $to_append ) { - $new_selectors = array(); - $selectors = explode( ',', $selector ); - foreach ( $selectors as $sel ) { - $new_selectors[] = $sel . $to_append; - } - - return implode( ',', $new_selectors ); - } - /** * Given a settings array, it returns the generated rulesets * for the preset classes. @@ -1312,13 +1326,15 @@ protected static function flatten_tree( $tree, $prefix = '', $token = '--' ) { * * @since 5.8.0 * @since 5.9.0 Added the `$settings` and `$properties` parameters. + * @since 6.1.0 Added the `$theme_json` parameter. * * @param array $styles Styles to process. * @param array $settings Theme settings. * @param array $properties Properties metadata. + * @param array $theme_json Theme JSON array. * @return array Returns the modified $declarations. */ - protected static function compute_style_properties( $styles, $settings = array(), $properties = null ) { + protected static function compute_style_properties( $styles, $settings = array(), $properties = null, $theme_json = null ) { if ( null === $properties ) { $properties = static::PROPERTIES_METADATA; } @@ -1329,7 +1345,7 @@ protected static function compute_style_properties( $styles, $settings = array() } foreach ( $properties as $css_property => $value_path ) { - $value = static::get_property_value( $styles, $value_path ); + $value = static::get_property_value( $styles, $value_path, $theme_json ); // Look up protected properties, keyed by value path. // Skip protected properties that are explicitly set to `null`. @@ -1365,20 +1381,58 @@ protected static function compute_style_properties( $styles, $settings = array() * "var:preset|color|secondary" to the form * "--wp--preset--color--secondary". * + * It also converts references to a path to the value + * stored at that location, e.g. + * { "ref": "style.color.background" } => "#fff". + * * @since 5.8.0 * @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. * * @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 ) { + protected static function get_property_value( $styles, $path, $theme_json = null ) { $value = _wp_array_get( $styles, $path, '' ); + /* + * 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 ) && array_key_exists( 'ref', $value ) ) { + $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 ) ) { + $value = $ref_value; + } + + if ( is_array( $ref_value ) && array_key_exists( 'ref', $ref_value ) ) { + $path_string = json_encode( $path ); + $ref_value_string = json_encode( $ref_value ); + _doing_it_wrong( + 'get_property_value', + sprintf( + /* 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.' ), + 'theme.json', + $ref_value_string, + $path_string, + $ref_value['ref'] + ), + '6.1.0' + ); + } + } + if ( '' === $value || is_array( $value ) ) { return $value; } + // Convert custom CSS properties. $prefix = 'var:'; $prefix_len = strlen( $prefix ); $token_in = '|'; @@ -1490,6 +1544,19 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { 'path' => array( 'styles', 'elements', $element ), 'selector' => static::ELEMENTS[ $element ], ); + + // Handle any pseudo selectors for the element. + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { + + if ( isset( $theme_json['styles']['elements'][ $element ][ $pseudo_selector ] ) ) { + $nodes[] = array( + 'path' => array( 'styles', 'elements', $element ), + 'selector' => static::append_to_selector( static::ELEMENTS[ $element ], $pseudo_selector ), + ); + } + } + } } } @@ -1498,6 +1565,51 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { return $nodes; } + $nodes = array_merge( $nodes, static::get_block_nodes( $theme_json ) ); + + /** + * Filters the list of style nodes with metadata. + * + * This allows for things like loading block CSS independently. + * + * @since 6.1.0 + * + * @param array $nodes Style nodes with metadata. + */ + return apply_filters( 'get_style_nodes', $nodes ); + } + + /** + * A public helper to get the block nodes from a theme.json file. + * + * @since 6.1.0 + * + * @return array The block nodes in theme.json. + */ + public function get_styles_block_nodes() { + return static::get_block_nodes( $this->theme_json ); + } + + /** + * An internal method to get the block nodes from a theme.json file. + * + * @since 6.1.0 + * + * @param array $theme_json The theme.json converted to an array. + * @return array The block nodes in theme.json. + */ + private static function get_block_nodes( $theme_json ) { + $selectors = static::get_blocks_metadata(); + $nodes = array(); + if ( ! isset( $theme_json['styles'] ) ) { + return $nodes; + } + + // Blocks. + if ( ! isset( $theme_json['styles']['blocks'] ) ) { + return $nodes; + } + foreach ( $theme_json['styles']['blocks'] as $name => $node ) { $selector = null; if ( isset( $selectors[ $name ]['selector'] ) ) { @@ -1510,6 +1622,7 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { } $nodes[] = array( + 'name' => $name, 'path' => array( 'styles', 'blocks', $name ), 'selector' => $selector, 'duotone' => $duotone_selector, @@ -1521,6 +1634,18 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), 'selector' => $selectors[ $name ]['elements'][ $element ], ); + + // Handle any pseudo selectors for the element. + if ( array_key_exists( $element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element ] as $pseudo_selector ) { + if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'][ $element ][ $pseudo_selector ] ) ) { + $nodes[] = array( + 'path' => array( 'styles', 'blocks', $name, 'elements', $element ), + 'selector' => static::append_to_selector( $selectors[ $name ]['elements'][ $element ], $pseudo_selector ), + ); + } + } + } } } } @@ -1528,6 +1653,116 @@ protected static function get_style_nodes( $theme_json, $selectors = array() ) { return $nodes; } + /** + * Gets the CSS rules for a particular block from theme.json. + * + * @since 6.1.0 + * + * @param array $block_metadata Meta data about the block to get styles for. + * @return array Styles for the block. + */ + public function get_styles_for_block( $block_metadata ) { + + $node = _wp_array_get( $this->theme_json, $block_metadata['path'], array() ); + + $selector = $block_metadata['selector']; + $settings = _wp_array_get( $this->theme_json, array( 'settings' ) ); + + /* + * Get a reference to element name from path. + * $block_metadata['path'] = array( 'styles','elements','link' ); + * Make sure that $block_metadata['path'] describes an element node, like [ 'styles', 'element', 'link' ]. + * Skip non-element paths like just ['styles']. + */ + $is_processing_element = in_array( 'elements', $block_metadata['path'], true ); + + $current_element = $is_processing_element ? $block_metadata['path'][ count( $block_metadata['path'] ) - 1 ] : null; + + $element_pseudo_allowed = array(); + + if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + $element_pseudo_allowed = static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ]; + } + + /* + * Check for allowed pseudo classes (e.g. ":hover") from the $selector ("a:hover"). + * This also resets the array keys. + */ + $pseudo_matches = array_values( + array_filter( + $element_pseudo_allowed, + function( $pseudo_selector ) use ( $selector ) { + return str_contains( $selector, $pseudo_selector ); + } + ) + ); + + $pseudo_selector = isset( $pseudo_matches[0] ) ? $pseudo_matches[0] : null; + + /* + * If the current selector is a pseudo selector that's defined in the allow list for the current + * element then compute the style properties for it. + * Otherwise just compute the styles for the default selector as normal. + */ + if ( $pseudo_selector && isset( $node[ $pseudo_selector ] ) && + array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) + && in_array( $pseudo_selector, static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ], true ) + ) { + $declarations = static::compute_style_properties( $node[ $pseudo_selector ], $settings, null, $this->theme_json ); + } else { + $declarations = static::compute_style_properties( $node, $settings, null, $this->theme_json ); + } + + $block_rules = ''; + + /* + * 1. Separate the declarations that use the general selector + * from the ones using the duotone selector. + */ + $declarations_duotone = array(); + foreach ( $declarations as $index => $declaration ) { + if ( 'filter' === $declaration['name'] ) { + unset( $declarations[ $index ] ); + $declarations_duotone[] = $declaration; + } + } + + /* + * Reset default browser margin on the root body element. + * This is set on the root selector **before** generating the ruleset + * from the `theme.json`. This is to ensure that if the `theme.json` declares + * `margin` in its `spacing` declaration for the `body` element then these + * user-generated values take precedence in the CSS cascade. + * @link https://github.com/WordPress/gutenberg/issues/36147. + */ + if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + $block_rules .= 'body { margin: 0; }'; + } + + // 2. Generate and append the rules that use the general selector. + $block_rules .= static::to_ruleset( $selector, $declarations ); + + // 3. Generate and append the rules that use the duotone selector. + if ( isset( $block_metadata['duotone'] ) && ! empty( $declarations_duotone ) ) { + $selector_duotone = static::scope_selector( $block_metadata['selector'], $block_metadata['duotone'] ); + $block_rules .= static::to_ruleset( $selector_duotone, $declarations_duotone ); + } + + if ( static::ROOT_BLOCK_SELECTOR === $selector ) { + $block_rules .= '.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }'; + $block_rules .= '.wp-site-blocks > .alignright { float: right; margin-left: 2em; }'; + $block_rules .= '.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }'; + + $has_block_gap_support = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'blockGap' ) ) !== null; + if ( $has_block_gap_support ) { + $block_rules .= '.wp-site-blocks > * { margin-block-start: 0; margin-block-end: 0; }'; + $block_rules .= '.wp-site-blocks > * + * { margin-block-start: var( --wp--style--block-gap ); }'; + } + } + + return $block_rules; + } + /** * For metadata values that can either be booleans or paths to booleans, gets the value. * @@ -1837,10 +2072,12 @@ public static function remove_insecure_properties( $theme_json ) { $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); - $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); + + $theme_json = static::sanitize( $theme_json, $valid_block_names, $valid_element_names ); $blocks_metadata = static::get_blocks_metadata(); $style_nodes = static::get_style_nodes( $theme_json, $blocks_metadata ); + foreach ( $style_nodes as $metadata ) { $input = _wp_array_get( $theme_json, $metadata['path'], array() ); if ( empty( $input ) ) { @@ -1848,6 +2085,25 @@ public static function remove_insecure_properties( $theme_json ) { } $output = static::remove_insecure_styles( $input ); + + /* + * Get a reference to element name from path. + * $metadata['path'] = array( 'styles', 'elements', 'link' ); + */ + $current_element = $metadata['path'][ count( $metadata['path'] ) - 1 ]; + + /* + * $output is stripped of pseudo selectors. Re-add and process them + * or insecure styles here. + */ + if ( array_key_exists( $current_element, static::VALID_ELEMENT_PSEUDO_SELECTORS ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $current_element ] as $pseudo_selector ) { + if ( isset( $input[ $pseudo_selector ] ) ) { + $output[ $pseudo_selector ] = static::remove_insecure_styles( $input[ $pseudo_selector ] ); + } + } + } + if ( ! empty( $output ) ) { _wp_array_set( $sanitized, $metadata['path'], $output ); } diff --git a/wp-includes/global-styles-and-settings.php b/wp-includes/global-styles-and-settings.php index efc2edcaaa3..2e642cc2b1e 100644 --- a/wp-includes/global-styles-and-settings.php +++ b/wp-includes/global-styles-and-settings.php @@ -192,3 +192,45 @@ function wp_get_global_styles_svg_filters() { return $svgs; } + +/** + * Adds global style rules to the inline style for each block. + * + * @since 6.1.0 + */ +function wp_add_global_styles_for_blocks() { + $tree = WP_Theme_JSON_Resolver::get_merged_data(); + $block_nodes = $tree->get_styles_block_nodes(); + foreach ( $block_nodes as $metadata ) { + $block_css = $tree->get_styles_for_block( $metadata ); + + if ( isset( $metadata['name'] ) ) { + $block_name = str_replace( 'core/', '', $metadata['name'] ); + /* + * These block styles are added on block_render. + * This hooks inline CSS to them so that they are loaded conditionally + * based on whether or not the block is used on the page. + */ + wp_add_inline_style( 'wp-block-' . $block_name, $block_css ); + } + + // The likes of block element styles from theme.json do not have $metadata['name'] set. + if ( ! isset( $metadata['name'] ) && ! empty( $metadata['path'] ) ) { + $result = array_values( + array_filter( + $metadata['path'], + function ( $item ) { + if ( strpos( $item, 'core/' ) !== false ) { + return true; + } + return false; + } + ) + ); + if ( isset( $result[0] ) ) { + $block_name = str_replace( 'core/', '', $result[0] ); + wp_add_inline_style( 'wp-block-' . $block_name, $block_css ); + } + } + } +} diff --git a/wp-includes/script-loader.php b/wp-includes/script-loader.php index 11858ec9bfe..354845fb9d6 100644 --- a/wp-includes/script-loader.php +++ b/wp-includes/script-loader.php @@ -2353,6 +2353,30 @@ function wp_common_block_scripts_and_styles() { do_action( 'enqueue_block_assets' ); } +/** + * Applies a filter to the list of style nodes that comes from WP_Theme_JSON::get_style_nodes(). + * + * This particular filter removes all of the blocks from the array. + * + * We want WP_Theme_JSON to be ignorant of the implementation details of how the CSS is being used. + * This filter allows us to modify the output of WP_Theme_JSON depending on whether or not we are + * loading separate assets, without making the class aware of that detail. + * + * @since 6.1.0 + * + * @param array $nodes The nodes to filter. + * @return array A filtered array of style nodes. + */ +function wp_filter_out_block_nodes( $nodes ) { + return array_filter( + $nodes, + function( $node ) { + return ! in_array( 'blocks', $node['path'], true ); + }, + ARRAY_FILTER_USE_BOTH + ); +} + /** * Enqueues the global styles defined via theme.json. * @@ -2377,6 +2401,16 @@ function wp_enqueue_global_styles() { return; } + /* + * If we are loading CSS for each block separately, then we can load the theme.json CSS conditionally. + * This removes the CSS from the global-styles stylesheet and adds it to the inline CSS for each block. + */ + if ( $separate_assets ) { + add_filter( 'get_style_nodes', 'wp_filter_out_block_nodes' ); + // Add each block as an inline css. + wp_add_global_styles_for_blocks(); + } + $stylesheet = wp_get_global_stylesheet(); if ( empty( $stylesheet ) ) { diff --git a/wp-includes/version.php b/wp-includes/version.php index 8488d0e8e33..b5463b419db 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.1-alpha-54117'; +$wp_version = '6.1-alpha-54118'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.