diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index f4e49038697dca..6f476cfe502314 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -328,6 +328,7 @@ class WP_Theme_JSON_Gutenberg { 'patterns', 'settings', 'styles', + 'supportedBlockTypes', 'templateParts', 'title', 'version', diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 0ad947957215fc..11448a45d6fa57 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -225,7 +225,8 @@ protected static function has_same_registered_blocks( $origin ) { * @since 5.8.0 * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. - * @since 6.5.0 Theme data will now also include block style variations that were registered with a style object. + * @since 6.5.0 Theme data will now also include block style variations that + * were registered with a style object or included via a standalone file. * * @param array $deprecated Deprecated. Not used. * @param array $options { @@ -373,9 +374,53 @@ public static function get_theme_data( $deprecated = array(), $options = array() $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data ); if ( $options['with_block_style_variations'] ) { + // Absorb block style variations that were registered with a style object. $block_style_variations_data = WP_Theme_JSON_Gutenberg::get_from_block_styles_registry(); $with_block_style_variations = new WP_Theme_JSON_Gutenberg( $block_style_variations_data ); $with_theme_supports->merge( $with_block_style_variations ); + + // Resolve shared block style variations that were bundled in the + // theme via standalone theme.json files. + $shared_block_style_variations = static::get_style_variations( '/block-styles' ); + $variations_data = array(); + $registry = WP_Block_Styles_Registry::get_instance(); + + foreach ( $shared_block_style_variations as $variation ) { + if ( empty( $variation['supportedBlockTypes'] ) || empty( $variation['styles'] ) ) { + continue; + } + + $variation_slug = _wp_to_kebab_case( $variation['title'] ); + + // If it proves desirable, block style variations could include + // custom settings which can be included here. + foreach ( $variation['supportedBlockTypes'] as $block_type ) { + // Automatically register the block style variation if it + // hasn't been already. + $registered_styles = $registry->get_registered_styles_for_block( $block_type ); + if ( ! array_key_exists( $variation_slug, $registered_styles ) ) { + gutenberg_register_block_style( + $block_type, + array( + 'name' => $variation_slug, + 'label' => $variation['title'], + ) + ); + } + + $path = array( $block_type, 'variations', $variation_slug ); + _wp_array_set( $variations_data, $path, $variation['styles'] ); + } + } + + if ( ! empty( $variations_data ) ) { + $variations_theme_json_data = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( 'blocks' => $variations_data ), + ); + $with_shared_variations = new WP_Theme_JSON_Gutenberg( $variations_theme_json_data ); + $with_theme_supports->merge( $with_shared_variations ); + } } $with_theme_supports->merge( static::$theme ); @@ -743,14 +788,16 @@ private static function recursively_iterate_json( $dir ) { * Returns the style variations defined by the theme (parent and child). * * @since 6.2.0 Returns parent theme variations if theme is a child. + * @since 6.5.0 Added configurable directory to allow block style variations + * to reside in a different directory to theme style variations. * * @return array */ - public static function get_style_variations() { + public static function get_style_variations( $dir = 'styles' ) { $variation_files = array(); $variations = array(); - $base_directory = get_stylesheet_directory() . '/styles'; - $template_directory = get_template_directory() . '/styles'; + $base_directory = get_stylesheet_directory() . '/' . $dir; + $template_directory = get_template_directory() . '/' . $dir; if ( is_dir( $base_directory ) ) { $variation_files = static::recursively_iterate_json( $base_directory ); } diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index eda8caede7bfec..e820c292708da5 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -226,6 +226,104 @@ public function test_add_theme_supports_are_loaded_for_themes_without_theme_json $this->assertSame( $color_palette, $settings['color']['palette']['theme'] ); } + /** + * Tests that block style variations registered via either + * `gutenberg_register_block_style` with a style object, or a standalone + * block style variation file within `/block-styles`, are added to the + * theme data. + */ + public function test_add_registered_block_styles_to_theme_data() { + switch_theme( 'block-theme' ); + + $variation_styles_data = array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'lavender', + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => 'violet', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'fuchsia', + ), + ':hover' => array( + 'color' => array( + 'text' => 'deeppink', + ), + ), + ), + ), + ); + + register_block_style( + 'core/group', + array( + 'name' => 'my-variation', + 'style_data' => $variation_styles_data, + ) + ); + + $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_raw_data(); + $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); + $expected = array( + 'variations' => array( + 'my-variation' => $variation_styles_data, + // The following variations are registered automatically from + // their respective JSON files within the theme's `block-styles` + // directory. + 'block-style-variation-a' => array( + 'color' => array( + 'background' => 'indigo', + 'text' => 'plum', + ), + ), + 'block-style-variation-b' => array( + 'color' => array( + 'background' => 'midnightblue', + 'text' => 'lightblue', + ), + ), + ), + ); + + unregister_block_style( 'core/group', 'my-variation' ); + + $this->assertSameSetsWithIndex( $group_styles, $expected ); + } + + public function test_registered_block_styles_not_added_to_theme_data_when_option_is_false() { + switch_theme( 'block-theme' ); + + $variation_styles_data = array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'lavender', + ), + ); + + register_block_style( + 'core/group', + array( + 'name' => 'my-variation', + 'style_data' => $variation_styles_data, + ) + ); + + $options = array( 'with_block_style_variations' => false ); + $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( null, $options )->get_raw_data(); + $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); + + unregister_block_style( 'core/group', 'my-variation' ); + + $this->assertArrayNotHasKey( 'variations', $group_styles ); + } + /** * Recursively applies ksort to an array. */ @@ -503,54 +601,81 @@ 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. + * Tests that `get_style_variations` returns all the appropriate variations, + * including parent variations if the theme is a child, and that the child + * variation overwrites the parent variation of the same name. + * + * Note: This covers both theme style variations (`/styles`) and block style + * variations (`/block-styles`). * * @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' ); + * + * @dataProvider data_get_style_variations + * + * @param string $theme Name of the theme to use. + * @param string $dir The directory to retrieve variation json files from. + * @param array $expected_variations Collection of expected variations. + */ + public function test_get_style_variations( $theme, $dir, $expected_variations ) { + switch_theme( $theme ); 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', + $actual_variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations( $dir ); + + self::recursive_ksort( $actual_variations ); + self::recursive_ksort( $expected_variations ); + + $this->assertSame( $expected_variations, $actual_variations ); + } + + /** + * Data provider for test_get_style_variations + * + * @return array + */ + public function data_get_style_variations() { + return array( + 'theme_style_variations' => array( + 'theme' => 'block-theme-child', + 'dir' => 'styles', + 'expected_variations' => 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', + 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', + ), + ), ), ), ), @@ -559,13 +684,34 @@ public function test_get_style_variations_returns_all_variations() { ), ), ), - ); - self::recursive_ksort( $actual_settings ); - self::recursive_ksort( $expected_settings ); - - $this->assertSame( - $expected_settings, - $actual_settings + 'block_style_variations' => array( + 'theme' => 'block-theme-child-with-block-style-variations', + 'dir' => 'block-styles', + 'expected_variations' => array( + array( + 'supportedBlockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), + 'version' => 2, + 'title' => 'block-style-variation-a', + 'styles' => array( + 'color' => array( + 'background' => 'darkcyan', + 'text' => 'aliceblue', + ), + ), + ), + array( + 'supportedBlockTypes' => array( 'core/group', 'core/columns' ), + 'version' => 2, + 'title' => 'block-style-variation-b', + 'styles' => array( + 'color' => array( + 'background' => 'midnightblue', + 'text' => 'lightblue', + ), + ), + ), + ), + ), ); } } diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json new file mode 100644 index 00000000000000..1daaac0062b9c6 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/block-styles/block-style-variation-a.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "supportedBlockTypes": [ "core/group", "core/columns", "core/media-text" ], + "styles": { + "color": { + "background": "darkcyan", + "text": "aliceblue" + } + } +} diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css new file mode 100644 index 00000000000000..c1cc20aaf1f101 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css @@ -0,0 +1,8 @@ +/* +Theme Name: Block Theme Child With Block Style Variations Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Template: block-theme +Version: 1.0.0 +Text Domain: block-theme-child-with-block-style-variations +*/ diff --git a/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json new file mode 100644 index 00000000000000..0da29ef16fd679 --- /dev/null +++ b/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 2 +} diff --git a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json new file mode 100644 index 00000000000000..0ba8417049eb0a --- /dev/null +++ b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-a.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "supportedBlockTypes": [ "core/group", "core/columns" ], + "styles": { + "color": { + "background": "indigo", + "text": "plum" + } + } +} diff --git a/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json new file mode 100644 index 00000000000000..6133b3e9f8d591 --- /dev/null +++ b/phpunit/data/themedir1/block-theme/block-styles/block-style-variation-b.json @@ -0,0 +1,10 @@ +{ + "version": 2, + "supportedBlockTypes": [ "core/group", "core/columns" ], + "styles": { + "color": { + "background": "midnightblue", + "text": "lightblue" + } + } +} diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 6ae8d15df63d2d..6a3f9e2da91790 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -2238,6 +2238,13 @@ "type": "string", "description": "Description of the global styles variation." }, + "supportedBlockTypes": { + "type": "array", + "description": "List of block types that can use the block style variation this theme.json file represents.", + "items": { + "type": "string" + } + }, "settings": { "description": "Settings for the block editor and individual blocks. These include things like:\n- Which customization options should be available to the user. \n- The default colors, font sizes... available to the user. \n- CSS custom properties and class names used in styles.\n- And the default layout of the editor (widths and available alignments).", "type": "object",