From 5a53d35cda7aca8d1e5b146ac66f2746a916c622 Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Thu, 4 May 2023 15:29:04 +0200 Subject: [PATCH 01/11] Extract logic that converts internal props to css variables The logic that converts internal css variables to custom css variables is extracted to a function --- lib/class-wp-theme-json-gutenberg.php | 41 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index fbbf9a05d1c817..625fd36270d9ef 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1989,21 +1989,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null return $value; } - // Convert custom CSS properties. - $prefix = 'var:'; - $prefix_len = strlen( $prefix ); - $token_in = '|'; - $token_out = '--'; - if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { - $unwrapped_name = str_replace( - $token_in, - $token_out, - substr( $value, $prefix_len ) - ); - $value = "var(--wp--$unwrapped_name)"; - } - - return $value; + return self::convert_custom_properties( $value ); } /** @@ -3578,4 +3564,29 @@ protected function get_feature_declarations_for_node( $metadata, &$node ) { return $declarations; } + + /** + * This is used to convert the internal representation of variables to the CSS representation. + * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. + * + * @since 6.3.0 + * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. + * @return string The converted variable. + */ + private static function convert_custom_properties($value) { + $prefix = 'var:'; + $prefix_len = strlen( $prefix ); + $token_in = '|'; + $token_out = '--'; + if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { + $unwrapped_name = str_replace( + $token_in, + $token_out, + substr( $value, $prefix_len ) + ); + $value = "var(--wp--$unwrapped_name)"; + } + + return $value; + } } From de1f0772ee9ae537bfd139d98ae4bdfa3cd7f354 Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Thu, 4 May 2023 21:03:32 +0200 Subject: [PATCH 02/11] Convert the internal css variables to css custom variables --- lib/class-wp-theme-json-gutenberg.php | 97 ++++++++++++++++++++------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 625fd36270d9ef..cf56abc03cd611 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -795,7 +795,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n if ( empty( $result ) ) { unset( $output[ $subtree ] ); } else { - $output[ $subtree ] = $result; + $output[ $subtree ] = static::sanitize_variables( $result, $schema[ $subtree ] ); } } @@ -1989,7 +1989,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null return $value; } - return self::convert_custom_properties( $value ); + return self::convert_custom_properties( $value ); } /** @@ -3565,28 +3565,73 @@ protected function get_feature_declarations_for_node( $metadata, &$node ) { return $declarations; } - /** - * This is used to convert the internal representation of variables to the CSS representation. - * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. - * - * @since 6.3.0 - * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. - * @return string The converted variable. - */ - private static function convert_custom_properties($value) { - $prefix = 'var:'; - $prefix_len = strlen( $prefix ); - $token_in = '|'; - $token_out = '--'; - if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { - $unwrapped_name = str_replace( - $token_in, - $token_out, - substr( $value, $prefix_len ) - ); - $value = "var(--wp--$unwrapped_name)"; - } - - return $value; - } + /** + * This is used to convert the internal representation of variables to the CSS representation. + * For example, `var:preset|color|vivid-green-cyan` becomes `var(--wp--preset--color--vivid-green-cyan)`. + * + * @since 6.3.0 + * @param string $value The variable such as var:preset|color|vivid-green-cyan to convert. + * @return string The converted variable. + */ + private static function convert_custom_properties( $value ) { + $prefix = 'var:'; + $prefix_len = strlen( $prefix ); + $token_in = '|'; + $token_out = '--'; + if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { + $unwrapped_name = str_replace( + $token_in, + $token_out, + substr( $value, $prefix_len ) + ); + $value = "var(--wp--$unwrapped_name)"; + } + + return $value; + } + + /** + * Given a tree, converts the internal representation of variables to the CSS representation. + * It is recursive and modifies the input in-place. + * + * @since 6.3.0 + * @param array $tree Input to process. + * @param array $schema Schema to adhere to. + * @return array The modified $tree. + */ + private static function sanitize_variables( $tree, $schema ) { + $tree = array_intersect_key( $tree, $schema ); + $prefix = 'var:'; + + foreach ( $schema as $key => $data ) { + if ( ! isset( $tree[ $key ] ) ) { + continue; + } + $values = $tree[ $key ]; + if ( is_array( $values ) ) { + foreach ( $values as $name => $value ) { + // if value is an array, do recursion. + if ( is_array( $value ) ) { + $values[ $name ] = array_merge( $value, self::sanitize_variables( $value, $schema ) ); + continue; + } + if ( ! is_string( $value ) || 0 !== strpos( $value, $prefix ) ) { + continue; + } + + $values[ $name ] = self::convert_custom_properties( $value ); + } + } else { + if ( ! is_string( $values ) || 0 !== strpos( $values, $prefix ) ) { + continue; + } + + $values = self::convert_custom_properties( $values ); + } + + $tree[ $key ] = $values; + } + + return $tree; + } } From 02fb5c2338282978eaf1f0f6c87c041f42feaf11 Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Fri, 5 May 2023 14:11:17 +0200 Subject: [PATCH 03/11] Add test to assert internal css syntax is converted to CSS variable --- phpunit/class-wp-theme-json-test.php | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index d7a28e2d6e2389..7d63f62a2c1f0e 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2071,4 +2071,69 @@ public function data_process_blocks_custom_css() { ), ); } + + public function test_internal_syntax_is_converted_to_css_variables() { + $result = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'color' => array( + 'background' => 'var:preset|color|primary', + 'text' => 'var(--wp--preset--color--secondary)', + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'background' => 'var:preset|color|pri', + 'text' => 'var(--wp--preset--color--sec)', + ), + ), + ), + 'blocks' => array( + 'core/post-terms' => array( + 'typography' => array( 'fontSize' => 'var(--wp--preset--font-size--small)' ), + 'color' => array( 'background' => 'var:preset|color|secondary' ), + ), + 'core/navigation' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'background' => 'var:preset|color|p', + 'text' => 'var(--wp--preset--color--s)', + ), + ), + ), + ), + 'core/quote' => array( + 'typography' => array( 'fontSize' => 'var(--wp--preset--font-size--d)' ), + 'color' => array( 'background' => 'var:preset|color|d' ), + 'variations' => array( + 'plain' => array( + 'typography' => array( 'fontSize' => 'var(--wp--preset--font-size--s)' ), + 'color' => array( 'background' => 'var:preset|color|s' ), + ), + ), + ), + ), + ), + ) + ); + $styles = $result->get_raw_data()['styles']; + + $this->assertEquals( 'var(--wp--preset--color--primary)', $styles['color']['background'], 'Top level: Assert the originally correct values are still correct.' ); + $this->assertEquals( 'var(--wp--preset--color--secondary)', $styles['color']['text'], 'Top level: Assert the originally correct values are still correct.' ); + + $this->assertEquals( 'var(--wp--preset--color--pri)', $styles['elements']['link']['color']['background'], 'Element top level: Assert the originally correct values are still correct.' ); + $this->assertEquals( 'var(--wp--preset--color--sec)', $styles['elements']['link']['color']['text'], 'Element top level: Assert the originally correct values are still correct.' ); + + $this->assertEquals( 'var(--wp--preset--font-size--small)', $styles['blocks']['core/post-terms']['typography']['fontSize'], 'Top block level: Assert the originally correct values are still correct.' ); + $this->assertEquals( 'var(--wp--preset--color--secondary)', $styles['blocks']['core/post-terms']['color']['background'], 'Top block level: Assert the internal variables are convert to CSS custom variables.' ); + + $this->assertEquals( 'var(--wp--preset--color--p)', $styles['blocks']['core/navigation']['elements']['link']['color']['background'], 'Elements block level: Assert the originally correct values are still correct.' ); + $this->assertEquals( 'var(--wp--preset--color--s)', $styles['blocks']['core/navigation']['elements']['link']['color']['text'], 'Elements block level: Assert the originally correct values are still correct.' ); + + $this->assertEquals( 'var(--wp--preset--font-size--s)', $styles['blocks']['core/quote']['variations']['plain']['typography']['fontSize'], 'Style variations: Assert the originally correct values are still correct.' ); + $this->assertEquals( 'var(--wp--preset--color--s)', $styles['blocks']['core/quote']['variations']['plain']['color']['background'], 'Style variations: Assert the internal variables are convert to CSS custom variables.' ); + + } } From b4b0823df687de0dbc954827e1503d8053d64cfb Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Fri, 5 May 2023 18:59:43 +0200 Subject: [PATCH 04/11] Define gutenberg_get_global_styles and update usages As of the changes to WP_Theme_JSON_Resolver_Gutenberg, this function had to get defined to keep in sync with core --- .../get-global-styles-and-settings.php | 32 +++++++++++++++++++ .../block-editor-settings-mobile.php | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index bb489664e1eea9..870af77c8378ab 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -124,3 +124,35 @@ function wp_get_block_css_selector( $block_type, $target = 'root', $fallback = f function gutenberg_get_remote_theme_patterns() { return WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) )->get_patterns(); } + +/** + * Gets the styles resulting of merging core, theme, and user data. + * + * @since 6.3.0 + * + * @param array $path Path to the specific style to retrieve. Optional. + * If empty, will return all styles. + * @param array $context { + * Metadata to know where to retrieve the $path from. Optional. + * + * @type string $block_name Which block to retrieve the styles from. + * If empty, it'll return the styles for the global context. + * @type string $origin Which origin to take data from. + * Valid values are 'all' (core, theme, and user) or 'base' (core and theme). + * If empty or unknown, 'all' is used. + * } + * @return array The styles to retrieve. + */ +function gutenberg_get_global_styles( $path = array(), $context = array() ) { + if ( ! empty( $context['block_name'] ) ) { + $path = array_merge( array( 'blocks', $context['block_name'] ), $path ); + } + + $origin = 'custom'; + if ( isset( $context['origin'] ) && 'base' === $context['origin'] ) { + $origin = 'theme'; + } + $styles = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin )->get_raw_data()['styles']; + + return _wp_array_get( $styles, $path, $styles ); +} diff --git a/lib/experimental/block-editor-settings-mobile.php b/lib/experimental/block-editor-settings-mobile.php index b9d1147d60f96c..91e3694c199f83 100644 --- a/lib/experimental/block-editor-settings-mobile.php +++ b/lib/experimental/block-editor-settings-mobile.php @@ -23,7 +23,7 @@ function gutenberg_get_block_editor_settings_mobile( $settings ) { 'mobile' === $_GET['context'] ) { if ( wp_theme_has_theme_json() ) { - $settings['__experimentalStyles'] = wp_get_global_styles(); + $settings['__experimentalStyles'] = gutenberg_get_global_styles(); } // To tell mobile that the site uses quote v2 (inner blocks). From b035fb60fde27c5381424217e5c2f97ef63093e7 Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Sun, 7 May 2023 15:01:34 +0200 Subject: [PATCH 05/11] Make sure variation styles are includes when sanitizing --- lib/class-wp-theme-json-gutenberg.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index cf56abc03cd611..566258142ac897 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3612,6 +3612,10 @@ private static function sanitize_variables( $tree, $schema ) { foreach ( $values as $name => $value ) { // if value is an array, do recursion. if ( is_array( $value ) ) { + // make sure variations are included in the schema for recursion. + if ( isset( $value['variations'] ) ) { + $schema = $schema + array( 'variations' => null ); + } $values[ $name ] = array_merge( $value, self::sanitize_variables( $value, $schema ) ); continue; } From 95643747d181cddd94b4d4187d0990a36a35dfbc Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 8 May 2023 10:55:44 +0200 Subject: [PATCH 06/11] Add correct since annotation to gutenberg_get_global_styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- lib/compat/wordpress-6.3/get-global-styles-and-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 870af77c8378ab..69127aaac8b037 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -128,7 +128,7 @@ function gutenberg_get_remote_theme_patterns() { /** * Gets the styles resulting of merging core, theme, and user data. * - * @since 6.3.0 + * @since 5.9.0 * * @param array $path Path to the specific style to retrieve. Optional. * If empty, will return all styles. From a6b0e78f1b8d85897ccb52150607e1922190fb40 Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Mon, 8 May 2023 11:01:26 +0200 Subject: [PATCH 07/11] Add descriptive since annotation to gutenberg_get_global_styles --- lib/compat/wordpress-6.3/get-global-styles-and-settings.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php index 69127aaac8b037..609d9b3de38a16 100644 --- a/lib/compat/wordpress-6.3/get-global-styles-and-settings.php +++ b/lib/compat/wordpress-6.3/get-global-styles-and-settings.php @@ -129,6 +129,8 @@ function gutenberg_get_remote_theme_patterns() { * Gets the styles resulting of merging core, theme, and user data. * * @since 5.9.0 + * @since 6.3.0 the internal link format "var:preset|color|secondary" is resolved + * to "var(--wp--preset--font-size--small)" so consumers don't have to. * * @param array $path Path to the specific style to retrieve. Optional. * If empty, will return all styles. From a19f4700f2a11809c1a047dcdef529023120284c Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Mon, 8 May 2023 14:19:04 +0200 Subject: [PATCH 08/11] Remove usage of convert_custom_properties from get_property_value Since convert_custom_properties is applied in constructor the other methods don't need to apply it again --- lib/class-wp-theme-json-gutenberg.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 566258142ac897..f4141bacfd1b36 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1989,7 +1989,7 @@ protected static function get_property_value( $styles, $path, $theme_json = null return $value; } - return self::convert_custom_properties( $value ); + return $value; } /** From 1250ec9e72cb5abbb21d021165278371ba77935a Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Mon, 8 May 2023 17:26:43 +0200 Subject: [PATCH 09/11] Apply improvements suggested in PR review --- lib/class-wp-theme-json-gutenberg.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index f4141bacfd1b36..150b8ebb736606 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -3578,7 +3578,7 @@ private static function convert_custom_properties( $value ) { $prefix_len = strlen( $prefix ); $token_in = '|'; $token_out = '--'; - if ( 0 === strncmp( $value, $prefix, $prefix_len ) ) { + if ( 0 === strpos( $value, $prefix ) ) { $unwrapped_name = str_replace( $token_in, $token_out, @@ -3602,7 +3602,6 @@ private static function convert_custom_properties( $value ) { private static function sanitize_variables( $tree, $schema ) { $tree = array_intersect_key( $tree, $schema ); $prefix = 'var:'; - foreach ( $schema as $key => $data ) { if ( ! isset( $tree[ $key ] ) ) { continue; @@ -3625,11 +3624,9 @@ private static function sanitize_variables( $tree, $schema ) { $values[ $name ] = self::convert_custom_properties( $value ); } + } elseif ( ! is_string( $values ) || 0 !== strpos( $values, $prefix ) ) { + continue; } else { - if ( ! is_string( $values ) || 0 !== strpos( $values, $prefix ) ) { - continue; - } - $values = self::convert_custom_properties( $values ); } From 58334011617e9d1165128749944fc4e705a09f67 Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Tue, 9 May 2023 14:34:46 +0200 Subject: [PATCH 10/11] Simplify sanitize_variables and update usage Since the data is already checked against the schema sanitize_variables doesn't need to know about the schema anymore --- lib/class-wp-theme-json-gutenberg.php | 38 +++++---------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 150b8ebb736606..cfec00c293b034 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -795,7 +795,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n if ( empty( $result ) ) { unset( $output[ $subtree ] ); } else { - $output[ $subtree ] = static::sanitize_variables( $result, $schema[ $subtree ] ); + $output[ $subtree ] = static::sanitize_variables( $result ); } } @@ -3596,41 +3596,17 @@ private static function convert_custom_properties( $value ) { * * @since 6.3.0 * @param array $tree Input to process. - * @param array $schema Schema to adhere to. * @return array The modified $tree. */ - private static function sanitize_variables( $tree, $schema ) { - $tree = array_intersect_key( $tree, $schema ); + private static function sanitize_variables( $tree ) { $prefix = 'var:'; - foreach ( $schema as $key => $data ) { - if ( ! isset( $tree[ $key ] ) ) { - continue; - } - $values = $tree[ $key ]; - if ( is_array( $values ) ) { - foreach ( $values as $name => $value ) { - // if value is an array, do recursion. - if ( is_array( $value ) ) { - // make sure variations are included in the schema for recursion. - if ( isset( $value['variations'] ) ) { - $schema = $schema + array( 'variations' => null ); - } - $values[ $name ] = array_merge( $value, self::sanitize_variables( $value, $schema ) ); - continue; - } - if ( ! is_string( $value ) || 0 !== strpos( $value, $prefix ) ) { - continue; - } - $values[ $name ] = self::convert_custom_properties( $value ); - } - } elseif ( ! is_string( $values ) || 0 !== strpos( $values, $prefix ) ) { - continue; - } else { - $values = self::convert_custom_properties( $values ); + foreach ( $tree as $key => $data ) { + if ( is_string( $data ) && 0 === strpos( $data, $prefix ) ) { + $tree[ $key ] = self::convert_custom_properties( $data ); + } elseif ( is_array( $data ) ) { + $tree[ $key ] = self::sanitize_variables( $data ); } - - $tree[ $key ] = $values; } return $tree; From 92ed17b5b15c64c93aba0fa515e7fc8167e8ad7f Mon Sep 17 00:00:00 2001 From: Sam Najian Date: Tue, 9 May 2023 14:42:23 +0200 Subject: [PATCH 11/11] Rename sanitize_variables to resolve_custom_css_format --- lib/class-wp-theme-json-gutenberg.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index cfec00c293b034..6f72ef2c403ceb 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -795,7 +795,7 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n if ( empty( $result ) ) { unset( $output[ $subtree ] ); } else { - $output[ $subtree ] = static::sanitize_variables( $result ); + $output[ $subtree ] = static::resolve_custom_css_format( $result ); } } @@ -3598,14 +3598,14 @@ private static function convert_custom_properties( $value ) { * @param array $tree Input to process. * @return array The modified $tree. */ - private static function sanitize_variables( $tree ) { + private static function resolve_custom_css_format( $tree ) { $prefix = 'var:'; foreach ( $tree as $key => $data ) { if ( is_string( $data ) && 0 === strpos( $data, $prefix ) ) { $tree[ $key ] = self::convert_custom_properties( $data ); } elseif ( is_array( $data ) ) { - $tree[ $key ] = self::sanitize_variables( $data ); + $tree[ $key ] = self::resolve_custom_css_format( $data ); } }