From da16e421a96749800eea9000ce2f5f82097a2074 Mon Sep 17 00:00:00 2001 From: Jeff Ong Date: Wed, 18 Jan 2023 16:01:29 +0800 Subject: [PATCH] Make child themes inherit parent's style variations. (#46554) * Include parent theme dir in style variation getter. * Fix mismatched variable names. * Refactor to static utility function. * Check for naming collisions via basename. * Fix linting errors and warnings. * Rebase trunk. * Update version since. * Use _Gutenberg class. * Add back line break. * Fix linting errors. * Refactor to allow parent theme variations to show up when child has no variations. * Add unit test for resolver class. * Comment the test. * Fix lint. * Rename variations to account for child overwriting parent variation. --- ...class-wp-theme-json-resolver-gutenberg.php | 58 +++++++++++----- ...berg-rest-global-styles-controller-6-2.php | 26 ++++++++ phpunit/class-wp-theme-json-resolver-test.php | 66 +++++++++++++++++++ .../block-theme-child/styles/variation-a.json | 18 +++++ .../themedir1/block-theme-child/theme.json | 2 +- .../block-theme/styles/variation-a.json | 18 +++++ .../block-theme/styles/variation-b.json | 18 +++++ phpunit/data/themedir1/block-theme/theme.json | 2 +- 8 files changed, 190 insertions(+), 18 deletions(-) create mode 100644 phpunit/data/themedir1/block-theme-child/styles/variation-a.json create mode 100644 phpunit/data/themedir1/block-theme/styles/variation-a.json create mode 100644 phpunit/data/themedir1/block-theme/styles/variation-b.json diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 8733fd13bf2c77..bbc66c0bbdf4fc 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -670,32 +670,58 @@ public static function clean_cached_data() { } /** - * Returns the style variations defined by the theme. + * Returns an array of all nested json files within a given directory. * - * @since 6.0.0 + * @since 6.2.0 + * + * @param dir $dir The directory to recursively iterate and list files of. + * @return array The merged array. + */ + private static function recursively_iterate_json( $dir ) { + $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dir ) ); + $nested_json_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); + return $nested_json_files; + } + + /** + * Returns the style variations defined by the theme (parent and child). + * + * @since 6.2.0 Returns parent theme variations if theme is a child. * * @return array */ public static function get_style_variations() { - $variations = array(); - $base_directory = get_stylesheet_directory() . '/styles'; + $variation_files = array(); + $variations = array(); + $base_directory = get_stylesheet_directory() . '/styles'; + $template_directory = get_template_directory() . '/styles'; if ( is_dir( $base_directory ) ) { - $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) ); - $nested_html_files = iterator_to_array( new RegexIterator( $nested_files, '/^.+\.json$/i', RecursiveRegexIterator::GET_MATCH ) ); - ksort( $nested_html_files ); - foreach ( $nested_html_files as $path => $file ) { - $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); - if ( is_array( $decoded_file ) ) { - $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); - $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); - if ( empty( $variation['title'] ) ) { - $variation['title'] = basename( $path, '.json' ); + $variation_files = static::recursively_iterate_json( $base_directory ); + } + if ( is_dir( $template_directory ) && $template_directory !== $base_directory ) { + $variation_files_parent = static::recursively_iterate_json( $template_directory ); + // If the child and parent variation file basename are the same, only include the child theme's. + foreach ( $variation_files_parent as $parent_path => $parent ) { + foreach ( $variation_files as $child_path => $child ) { + if ( basename( $parent_path ) === basename( $child_path ) ) { + unset( $variation_files_parent[ $parent_path ] ); } - $variations[] = $variation; } } + $variation_files = array_merge( $variation_files, $variation_files_parent ); + } + ksort( $variation_files ); + foreach ( $variation_files as $path => $file ) { + $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); + if ( is_array( $decoded_file ) ) { + $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); + $variation = ( new WP_Theme_JSON_Gutenberg( $translated ) )->get_raw_data(); + if ( empty( $variation['title'] ) ) { + $variation['title'] = basename( $path, '.json' ); + } + $variations[] = $variation; + } } return $variations; } - } diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php index ad92f3cc64320b..591c41128f8393 100644 --- a/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php @@ -275,4 +275,30 @@ public function get_theme_item( $request ) { return $response; } + + /** + * Returns the given theme global styles variations. + * + * @since 6.0.0 + * @since 6.2.0 Returns parent theme variations, if they exist. + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response|WP_Error + */ + public function get_theme_items( $request ) { + if ( get_stylesheet() !== $request['stylesheet'] ) { + // This endpoint only supports the active or parent theme for now. + return new WP_Error( + sprintf( '%s', $request['template'] ), + __( 'Theme not found.', 'gutenberg' ), + array( 'status' => 404 ) + ); + } + + $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); + $response = rest_ensure_response( $variations ); + + return $response; + } } diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 7b46dc3cd0d4a2..4cc34bf97b32c7 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -503,4 +503,70 @@ public function data_get_merged_data_returns_origin() { ); } + + /** + * Test that get_style_variations returns all variations, including parent theme variations if the theme is a child, + * and that the child variation overwrites the parent variation of the same name. + * + * @covers WP_Theme_JSON_Resolver::get_style_variations + **/ + public function test_get_style_variations_returns_all_variations() { + // Switch to a child theme. + switch_theme( 'block-theme-child' ); + wp_set_current_user( self::$administrator_id ); + + $actual_settings = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); + $expected_settings = array( + array( + 'version' => 2, + 'title' => 'variation-a', + 'settings' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#010101', + ), + ), + ), + ), + ), + ), + ), + ), + array( + 'version' => 2, + 'title' => 'variation-b', + 'settings' => array( + 'blocks' => array( + 'core/post-title' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'light', + 'name' => 'Light', + 'color' => '#f1f1f1', + ), + ), + ), + ), + ), + ), + ), + ), + ); + self::recursive_ksort( $actual_settings ); + self::recursive_ksort( $expected_settings ); + + $this->assertSame( + $expected_settings, + $actual_settings + ); + } + } diff --git a/phpunit/data/themedir1/block-theme-child/styles/variation-a.json b/phpunit/data/themedir1/block-theme-child/styles/variation-a.json new file mode 100644 index 00000000000000..a9d5ade8946928 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child/styles/variation-a.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "settings": { + "blocks": { + "core/paragraph": { + "color": { + "palette": [ + { + "slug": "dark", + "name": "Dark", + "color": "#010101" + } + ] + } + } + } + } +} diff --git a/phpunit/data/themedir1/block-theme-child/theme.json b/phpunit/data/themedir1/block-theme-child/theme.json index 90fe35e758b455..ebfa68d9f74811 100644 --- a/phpunit/data/themedir1/block-theme-child/theme.json +++ b/phpunit/data/themedir1/block-theme-child/theme.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "settings": { "color": { "palette": [ diff --git a/phpunit/data/themedir1/block-theme/styles/variation-a.json b/phpunit/data/themedir1/block-theme/styles/variation-a.json new file mode 100644 index 00000000000000..42c20fc63b5925 --- /dev/null +++ b/phpunit/data/themedir1/block-theme/styles/variation-a.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "settings": { + "blocks": { + "core/paragraph": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f2f2f2" + } + ] + } + } + } + } +} diff --git a/phpunit/data/themedir1/block-theme/styles/variation-b.json b/phpunit/data/themedir1/block-theme/styles/variation-b.json new file mode 100644 index 00000000000000..340198ffe0b65f --- /dev/null +++ b/phpunit/data/themedir1/block-theme/styles/variation-b.json @@ -0,0 +1,18 @@ +{ + "version": 2, + "settings": { + "blocks": { + "core/post-title": { + "color": { + "palette": [ + { + "slug": "light", + "name": "Light", + "color": "#f1f1f1" + } + ] + } + } + } + } +} diff --git a/phpunit/data/themedir1/block-theme/theme.json b/phpunit/data/themedir1/block-theme/theme.json index fdedf31009473d..aa9f93a3c0aa1a 100644 --- a/phpunit/data/themedir1/block-theme/theme.json +++ b/phpunit/data/themedir1/block-theme/theme.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "settings": { "color": { "palette": [