From 9e28f88ba521d30374c61de37c6d8f4dfa0af71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 8 Apr 2021 09:34:13 +0200 Subject: [PATCH 1/3] Add util to transfrom from v0 to v1 --- lib/class-wp-theme-json-schema-v0.php | 339 ++++++++++++++++++ lib/load.php | 1 + .../class-wp-theme-json-schema-v0-test.php | 155 ++++++++ 3 files changed, 495 insertions(+) create mode 100644 lib/class-wp-theme-json-schema-v0.php create mode 100644 phpunit/class-wp-theme-json-schema-v0-test.php diff --git a/lib/class-wp-theme-json-schema-v0.php b/lib/class-wp-theme-json-schema-v0.php new file mode 100644 index 00000000000000..680ed73f91eb85 --- /dev/null +++ b/lib/class-wp-theme-json-schema-v0.php @@ -0,0 +1,339 @@ + null, + 'templateParts' => null, + 'styles' => array( + 'border' => array( + 'radius' => null, + 'color' => null, + 'style' => null, + 'width' => null, + ), + 'color' => array( + 'background' => null, + 'gradient' => null, + 'link' => null, + 'text' => null, + ), + 'spacing' => array( + 'padding' => array( + 'top' => null, + 'right' => null, + 'bottom' => null, + 'left' => null, + ), + ), + 'typography' => array( + 'fontFamily' => null, + 'fontSize' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'lineHeight' => null, + 'textDecoration' => null, + 'textTransform' => null, + ), + ), + 'settings' => array( + 'border' => array( + 'customRadius' => null, + 'customColor' => null, + 'customStyle' => null, + 'customWidth' => null, + ), + 'color' => array( + 'custom' => null, + 'customGradient' => null, + 'gradients' => null, + 'link' => null, + 'palette' => null, + ), + 'spacing' => array( + 'customPadding' => null, + 'units' => null, + ), + 'typography' => array( + 'customFontSize' => null, + 'customLineHeight' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'customFontStyle' => null, + 'customFontWeight' => null, + 'customTextDecorations' => null, + 'customTextTransforms' => null, + ), + 'custom' => null, + 'layout' => null, + ), + ); + + /** + * Utility to convert v0 schemas into v1. + * + * @param array $old A theme.json in v0 format. + * + * @return array The new theme.json in the v1 format. + */ + public static function to_v1( $old ) { + // Copy everything. + $new = $old; + + $blocks_to_consolidate = array( + 'core/heading/h1' => 'core/heading', + 'core/heading/h2' => 'core/heading', + 'core/heading/h3' => 'core/heading', + 'core/heading/h4' => 'core/heading', + 'core/heading/h5' => 'core/heading', + 'core/heading/h6' => 'core/heading', + 'core/post-title/h1' => 'core/post-title', + 'core/post-title/h2' => 'core/post-title', + 'core/post-title/h3' => 'core/post-title', + 'core/post-title/h4' => 'core/post-title', + 'core/post-title/h5' => 'core/post-title', + 'core/post-title/h6' => 'core/post-title', + 'core/query-title/h1' => 'core/query-title', + 'core/query-title/h2' => 'core/query-title', + 'core/query-title/h3' => 'core/query-title', + 'core/query-title/h4' => 'core/query-title', + 'core/query-title/h5' => 'core/query-title', + 'core/query-title/h6' => 'core/query-title', + ); + + // Overwrite the things that change. + if ( isset( $old['settings'] ) ) { + $new['settings'] = self::process_settings( $old['settings'], $blocks_to_consolidate ); + } + if ( isset( $old['styles'] ) ) { + $new['styles'] = self::process_styles( $old['styles'], $blocks_to_consolidate ); + } + + $new['version'] = 1; + + return $new; + } + + /** + * Processes the settings subtree. + * + * @param array $settings Array to process. + * @param array $blocks_to_consolidate A list of blocks that are transformed. + * + * @return array The settings in the new format. + */ + private static function process_settings( $settings, $blocks_to_consolidate ) { + $new = array(); + + $paths_to_override = array( + array( 'color', 'palette' ), + array( 'color', 'gradients' ), + array( 'spacing', 'units' ), + array( 'typography', 'fontSizes' ), + array( 'typography', 'fontFamilies' ), + array( 'custom' ), + ); + + // 'defaults' settings become top-level. + if ( isset( $settings[ self::ALL_BLOCKS_NAME ] ) ) { + $new = $settings[ self::ALL_BLOCKS_NAME ]; + unset( $settings[ self::ALL_BLOCKS_NAME ] ); + } + + // 'root' settings override 'defaults'. + if ( isset( $settings[ self::ROOT_BLOCK_NAME ] ) ) { + $new = array_replace_recursive( $new, $settings[ self::ROOT_BLOCK_NAME ] ); + + // The array_replace_recursive algorithm merges at the leaf level. + // This means that when a leaf value is an array, + // the incoming array won't replace the existing, + // but the numeric indexes are used for replacement. + // + // These cases we hold into $paths_to_override + // and need to replace them with the new data. + foreach ( $paths_to_override as $path ) { + $root_value = _wp_array_get( + $settings, + array_merge( array( self::ROOT_BLOCK_NAME ), $path ), + null + ); + if ( null !== $root_value ) { + gutenberg_experimental_set( $new, $path, $root_value ); + } + } + + unset( $settings[ self::ROOT_BLOCK_NAME ] ); + } + + if ( empty( $settings ) ) { + return $new; + } + + /* + * At this point, it only contains block's data. + * However, some block data we need to consolidate + * into a different section, as it's the case for: + * + * - core/heading/h1, core/heading/h2, ... => core/heading + * - core/post-title/h1, core/post-title/h2, ... => core/post-title + * - core/query-title/h1, core/query-title/h2, ... => core/query-title + * + */ + $new['blocks'] = $settings; + foreach ( $blocks_to_consolidate as $old_name => $new_name ) { + // Unset the $old_name. + unset( $new[ $old_name ] ); + + // Consolidate the $new value. + $block_settings = _wp_array_get( $settings, array( $old_name ), null ); + if ( null !== $block_settings ) { + $new_path = array( 'blocks', $new_name ); + $new_settings = array(); + gutenberg_experimental_set( $new_settings, $new_path, $block_settings ); + + $new = array_replace_recursive( $new, $new_settings ); + foreach ( $paths_to_override as $path ) { + $block_value = _wp_array_get( $block_settings, $path, null ); + if ( null !== $block_value ) { + gutenberg_experimental_set( $new, array_merge( $new_path, $path ), $block_value ); + } + } + } + } + + return $new; + } + + /** + * Processes the styles subtree. + * + * @param array $styles Array to process. + * @param array $blocks_to_consolidate A list of blocks that are transformed. + * + * @return array The styles in the new format. + */ + private static function process_styles( $styles, $blocks_to_consolidate ) { + $new = array(); + + // Styles within root become top-level. + if ( isset( $styles[ self::ROOT_BLOCK_NAME ] ) ) { + $new = $styles[ self::ROOT_BLOCK_NAME ]; + unset( $styles[ self::ROOT_BLOCK_NAME ] ); + + // Transform root.styles.color.link into elements.link.color.text. + if ( isset( $new['color']['link'] ) ) { + $new['elements']['link']['color']['text'] = $new['color']['link']; + unset( $new['color']['link'] ); + } + } + + if ( empty( $styles ) ) { + return $new; + } + + /* + * At this point, it only contains block's data. + * However, we still need to consolidate a few things: + * + * - link element => tranform from link color property + * - heading elements => consolidate multiple blocks (core/heading/h1, core/heading/h2) + * into a single one with elements (core/heading with elements h1, h2, etc). + */ + $new['blocks'] = $styles; + + // link elements. + foreach ( $new['blocks'] as $block_name => $metadata ) { + if ( isset( $metadata['color']['link'] ) ) { + $new['blocks'][ $block_name ]['elements']['link']['color']['text'] = $metadata['color']['link']; + unset( $new['blocks'][ $block_name ]['color']['link'] ); + } + } + + // heading elements. + foreach ( $blocks_to_consolidate as $old_name => $new_name ) { + if ( ! isset( $new['blocks'][ $old_name ] ) ) { + continue; + } + + if ( ! isset( $new['blocks'][ $new_name ] ) ) { + $new['blocks'][ $new_name ] = array(); + } + + /* + * First, port the existing link color element to the block, + * overriding the previous value. + */ + if ( isset( $new['blocks'][ $old_name ]['elements'] ) ) { + $new['blocks'][ $new_name ]['elements']['link'] = $new['blocks'][ $old_name ]['elements']['link']; + unset( $new['blocks'][ $old_name ]['elements'] ); + } + + /* + * Then, port any style categories left to the element + * resulting of exploding the previous block selector: + * + * - core/heading/h1 => h1 element + * - core/heading/h2 => h2 element + * - and so on + */ + if ( isset( $new['blocks'][ $old_name ] ) ) { + $element_name = explode( '/', $old_name )[2]; + $new['blocks'][ $new_name ]['elements'][ $element_name ] = $new['blocks'][ $old_name ]; + unset( $new['blocks'][ $old_name ] ); + } + } + + return $new; + } +} diff --git a/lib/load.php b/lib/load.php index 20fb03eb37c50e..e0e756324c563d 100644 --- a/lib/load.php +++ b/lib/load.php @@ -89,6 +89,7 @@ function gutenberg_is_experiment_enabled( $name ) { // These are used by some FSE features // as well as global styles. +require __DIR__ . '/class-wp-theme-json-schema-v0.php'; require __DIR__ . '/class-wp-theme-json.php'; require __DIR__ . '/class-wp-theme-json-resolver.php'; diff --git a/phpunit/class-wp-theme-json-schema-v0-test.php b/phpunit/class-wp-theme-json-schema-v0-test.php new file mode 100644 index 00000000000000..4b102f8fef7ea0 --- /dev/null +++ b/phpunit/class-wp-theme-json-schema-v0-test.php @@ -0,0 +1,155 @@ + array( + 'defaults' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Black', + 'slug' => 'black', + 'color' => '#00000', + ), + array( + 'name' => 'White', + 'slug' => 'white', + 'color' => '#ffffff', + ), + array( + 'name' => 'Pale Pink', + 'slug' => 'pale-pink', + 'color' => '#f78da7', + ), + array( + 'name' => 'Vivid Red', + 'slug' => 'vivid-red', + 'color' => '#cf2e2', + ), + ), + 'custom' => false, + 'link' => false, + ), + ), + 'root' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Pale Pink', + 'slug' => 'pale-pink', + 'color' => '#f78da7', + ), + array( + 'name' => 'Vivid Red', + 'slug' => 'vivid-red', + 'color' => '#cf2e2e', + ), + ), + 'link' => true, + ), + 'border' => array( + 'customRadius' => false, + ), + ), + 'core/paragraph' => array( + 'typography' => array( + 'dropCap' => false, + ), + ), + ), + 'styles' => array( + 'root' => array( + 'color' => array( + 'background' => 'purple', + 'link' => 'red', + ), + ), + 'core/group' => array( + 'color' => array( + 'background' => 'red', + 'link' => 'yellow', + ), + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + ), + ), + ), + ), + ); + $theme_json_v1 = WP_Theme_JSON_Schema_V0::to_v1( $theme_json_v0 ); + + $expected = array( + 'version' => 1, + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'name' => 'Pale Pink', + 'slug' => 'pale-pink', + 'color' => '#f78da7', + ), + array( + 'name' => 'Vivid Red', + 'slug' => 'vivid-red', + 'color' => '#cf2e2e', + ), + ), + 'custom' => false, + 'link' => true, + ), + 'border' => array( + 'customRadius' => false, + ), + 'blocks' => array( + 'core/paragraph' => array( + 'typography' => array( + 'dropCap' => false, + ), + ), + ), + ), + 'styles' => array( + 'color' => array( + 'background' => 'purple', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => 'red', + ), + 'spacing' => array( + 'padding' => array( + 'top' => '10px', + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'yellow', + ), + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'red', + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $theme_json_v1 ); + } +} From 78fc146e9d25861b09fff1b72e80fcc95e3bbe0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 8 Apr 2021 19:16:03 +0200 Subject: [PATCH 2/3] Introduce an interface so all schemas implement the same --- lib/class-wp-theme-json-schema-v0.php | 17 +++++++++-------- lib/interface-wp-theme-json-schema.php | 18 ++++++++++++++++++ lib/load.php | 1 + phpunit/class-wp-theme-json-schema-v0-test.php | 7 ++++--- 4 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 lib/interface-wp-theme-json-schema.php diff --git a/lib/class-wp-theme-json-schema-v0.php b/lib/class-wp-theme-json-schema-v0.php index 680ed73f91eb85..73e02cdf2dd73f 100644 --- a/lib/class-wp-theme-json-schema-v0.php +++ b/lib/class-wp-theme-json-schema-v0.php @@ -1,15 +1,16 @@ array( 'defaults' => array( @@ -85,7 +85,8 @@ function test_schema_to_v1() { ), ), ); - $theme_json_v1 = WP_Theme_JSON_Schema_V0::to_v1( $theme_json_v0 ); + + $actual = WP_Theme_JSON_Schema_V0::parse( $theme_json_v0 ); $expected = array( 'version' => 1, @@ -150,6 +151,6 @@ function test_schema_to_v1() { ), ); - $this->assertEqualSetsWithIndex( $expected, $theme_json_v1 ); + $this->assertEqualSetsWithIndex( $expected, $actual ); } } From 19c2820afdb79894a3ca2f556cf44bc06064ca3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 8 Apr 2021 19:30:43 +0200 Subject: [PATCH 3/3] Make linter happy --- lib/interface-wp-theme-json-schema.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/interface-wp-theme-json-schema.php b/lib/interface-wp-theme-json-schema.php index a935cfe9284365..962a806df0c6a5 100644 --- a/lib/interface-wp-theme-json-schema.php +++ b/lib/interface-wp-theme-json-schema.php @@ -5,6 +5,11 @@ * @package gutenberg */ +/** + * Schema interface for theme.json structures. + * + * @package gutenberg + */ interface WP_Theme_JSON_Schema { /** * Parses an array that follows an old theme.json schema