From 380d0445e84861ac0e634701d746b82634790f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=83=C2=B3=C3=85=E2=80=9Akowski?= Date: Fri, 12 Mar 2021 13:33:21 +0000 Subject: [PATCH] Editor: Make block type aware of variations Currently block variations are only defined on the client. In some cases, creating block variations on the server can be very useful, especially when needed data is not exposed in the REST APIs. Related to https://github.com/WordPress/gutenberg/pull/29095. Props: gwwar, timothyblynjacobs. Fixes: #52688. git-svn-id: https://develop.svn.wordpress.org/trunk@50527 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-admin/includes/post.php | 1 + src/wp-includes/class-wp-block-type.php | 7 + src/wp-includes/post.php | 13 +- .../class-wp-rest-block-types-controller.php | 180 ++++++++++++------ src/wp-includes/taxonomy.php | 12 ++ tests/phpunit/tests/admin/includesPost.php | 1 + .../rest-api/rest-block-type-controller.php | 65 ++++++- 7 files changed, 219 insertions(+), 60 deletions(-) diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index f79b68c299de0..1f94655646548 100644 --- a/src/wp-admin/includes/post.php +++ b/src/wp-admin/includes/post.php @@ -2257,6 +2257,7 @@ function get_block_editor_server_block_settings() { 'parent' => 'parent', 'keywords' => 'keywords', 'example' => 'example', + 'variations' => 'variations', ); foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { diff --git a/src/wp-includes/class-wp-block-type.php b/src/wp-includes/class-wp-block-type.php index f4a22d638bd75..30ba9ff407a93 100644 --- a/src/wp-includes/class-wp-block-type.php +++ b/src/wp-includes/class-wp-block-type.php @@ -99,6 +99,13 @@ class WP_Block_Type { */ public $styles = array(); + /** + * Block variations. + * @since 5.8.0 + * @var array + */ + public $variations = array(); + /** * Supported features. * diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 36b3a1aa7933f..88ee84d584c1f 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1703,6 +1703,9 @@ function _post_type_meta_capabilities( $capabilities = null ) { * - `item_scheduled` - Label used when an item is scheduled for publishing. Default is 'Post scheduled.' / * 'Page scheduled.' * - `item_updated` - Label used when an item is updated. Default is 'Post updated.' / 'Page updated.' + * - `item_link` - Title for a navigation link block variation. Default is 'Post Link' / 'Page Link'. + * - `item_link_description` - Description for a navigation link block variation. Default is 'A link to a post.' / + * 'A link to a page.' * * Above, the first default value is for non-hierarchical post types (like posts) * and the second one is for hierarchical post types (like pages). @@ -1726,7 +1729,7 @@ function _post_type_meta_capabilities( $capabilities = null ) { * @return object Object with all the labels as member variables. */ function get_post_type_labels( $post_type_object ) { - $nohier_vs_hier_defaults = array( + $nohier_vs_hier_defaults = array( 'name' => array( _x( 'Posts', 'post type general name' ), _x( 'Pages', 'post type general name' ) ), 'singular_name' => array( _x( 'Post', 'post type singular name' ), _x( 'Page', 'post type singular name' ) ), 'add_new' => array( _x( 'Add New', 'post' ), _x( 'Add New', 'page' ) ), @@ -1757,6 +1760,14 @@ function get_post_type_labels( $post_type_object ) { 'item_reverted_to_draft' => array( __( 'Post reverted to draft.' ), __( 'Page reverted to draft.' ) ), 'item_scheduled' => array( __( 'Post scheduled.' ), __( 'Page scheduled.' ) ), 'item_updated' => array( __( 'Post updated.' ), __( 'Page updated.' ) ), + 'item_link' => array( + _x( 'Post Link', 'navigation link block title' ), + _x( 'Page Link', 'navigation link block title' ), + ), + 'item_link_description' => array( + _x( 'A link to a post.', 'navigation link block description' ), + _x( 'A link to a page.', 'navigation link block description' ), + ), ); $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name']; diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php index 64a1ce653acdb..f2f277e5eb94f 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-types-controller.php @@ -274,6 +274,7 @@ public function prepare_item_for_response( $block_type, $request ) { 'script', 'editor_style', 'style', + 'variations', ); foreach ( $extra_fields as $extra_field ) { if ( rest_is_field_included( $extra_field, $fields ) ) { @@ -361,6 +362,71 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } + //rest_validate_value_from_schema doesn't understand $refs, pull out reused definitions for readability. + $inner_blocks_definition = array( + 'description' => __( 'The list of inner blocks used in the example.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'description' => __( 'The name of the inner block.' ), + 'type' => 'string', + ), + 'attributes' => array( + 'description' => __( 'The attributes of the inner block.' ), + 'type' => 'object', + ), + 'innerBlocks' => array( + 'description' => __( "A list of the inner block's own inner blocks. This is a recursive definition following the parent innerBlocks schema." ), + 'type' => 'array', + ), + ), + ), + ); + + $example_definition = array( + 'description' => __( 'Block example.' ), + 'type' => array( 'object', 'null' ), + 'default' => null, + 'properties' => array( + 'attributes' => array( + 'description' => __( 'The attributes used in the example.' ), + 'type' => 'object', + ), + 'innerBlocks' => $inner_blocks_definition, + ), + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + + $keywords_definition = array( + 'description' => __( 'Block keywords.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + 'default' => array(), + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + + $icon_definition = array( + 'description' => __( 'Icon of block type.' ), + 'type' => array( 'string', 'null' ), + 'default' => null, + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + + $category_definition = array( + 'description' => __( 'Block category.' ), + 'type' => array( 'string', 'null' ), + 'default' => null, + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ); + $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'block-type', @@ -394,13 +460,7 @@ public function get_item_schema() { 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'icon' => array( - 'description' => __( 'Icon of block type.' ), - 'type' => array( 'string', 'null' ), - 'default' => null, - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), + 'icon' => $icon_definition, 'attributes' => array( 'description' => __( 'Block attributes.' ), 'type' => array( 'object', 'null' ), @@ -441,13 +501,7 @@ public function get_item_schema() { 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'category' => array( - 'description' => __( 'Block category.' ), - 'type' => array( 'string', 'null' ), - 'default' => null, - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), + 'category' => $category_definition, 'is_dynamic' => array( 'description' => __( 'Is the block dynamically rendered.' ), 'type' => 'boolean', @@ -512,6 +566,58 @@ public function get_item_schema() { 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), + 'variations' => array( + 'description' => __( 'Block variations.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'description' => __( 'The unique and machine-readable name.' ), + 'type' => 'string', + 'required' => true, + ), + 'title' => array( + 'description' => __( 'A human-readable variation title.' ), + 'type' => 'string', + 'required' => true, + ), + 'description' => array( + 'description' => __( 'A detailed variation description.' ), + 'type' => 'string', + 'required' => false, + ), + 'category' => $category_definition, + 'icon' => $icon_definition, + 'isDefault' => array( + 'description' => __( 'Indicates whether the current variation is the default one.' ), + 'type' => 'boolean', + 'required' => false, + 'default' => false, + ), + 'attributes' => array( + 'description' => __( 'The initial values for attributes.' ), + 'type' => 'object', + ), + 'innerBlocks' => $inner_blocks_definition, + 'example' => $example_definition, + 'scope' => array( + 'description' => __( 'The list of scopes where the variation is applicable. When not provided, it assumes all available scopes.' ), + 'type' => array( 'array', 'null' ), + 'default' => null, + 'items' => array( + 'type' => 'string', + 'enum' => array( 'block', 'inserter', 'transform' ), + ), + 'readonly' => true, + ), + 'keywords' => $keywords_definition, + ), + ), + 'readonly' => true, + 'context' => array( 'embed', 'view', 'edit' ), + 'default' => null, + ), 'textdomain' => array( 'description' => __( 'Public text domain.' ), 'type' => array( 'string', 'null' ), @@ -529,50 +635,8 @@ public function get_item_schema() { 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'keywords' => array( - 'description' => __( 'Block keywords.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'string', - ), - 'default' => array(), - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), - 'example' => array( - 'description' => __( 'Block example.' ), - 'type' => array( 'object', 'null' ), - 'default' => null, - 'properties' => array( - 'attributes' => array( - 'description' => __( 'The attributes used in the example.' ), - 'type' => 'object', - ), - 'innerBlocks' => array( - 'description' => __( 'The list of inner blocks used in the example.' ), - 'type' => 'array', - 'items' => array( - 'type' => 'object', - 'properties' => array( - 'name' => array( - 'description' => __( 'The name of the inner block.' ), - 'type' => 'string', - ), - 'attributes' => array( - 'description' => __( 'The attributes of the inner block.' ), - 'type' => 'object', - ), - 'innerBlocks' => array( - 'description' => __( "A list of the inner block's own inner blocks. This is a recursive definition following the parent innerBlocks schema." ), - 'type' => 'array', - ), - ), - ), - ), - ), - 'context' => array( 'embed', 'view', 'edit' ), - 'readonly' => true, - ), + 'keywords' => $keywords_definition, + 'example' => $example_definition, ), ); diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index ae144a00b7044..13298022c10c4 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -576,6 +576,10 @@ function unregister_taxonomy( $taxonomy ) { * @type string $items_list Label for the table hidden heading. * @type string $most_used Title for the Most Used tab. Default 'Most Used'. * @type string $back_to_items Label displayed after a term has been updated. + * @type string $item_link Used in the block editor. Title for a navigation link block variation. + * Default 'Tag Link'/'Category Link'. + * @type string $item_link_description Used in the block editor. Description for a navigation link block + * variation. Default 'A link to a tag.'/'A link to a category'. * } */ function get_taxonomy_labels( $tax ) { @@ -613,6 +617,14 @@ function get_taxonomy_labels( $tax ) { /* translators: Tab heading when selecting from the most used terms. */ 'most_used' => array( _x( 'Most Used', 'tags' ), _x( 'Most Used', 'categories' ) ), 'back_to_items' => array( __( '← Go to Tags' ), __( '← Go to Categories' ) ), + 'item_link' => array( + _x( 'Tag Link', 'navigation link block title' ), + _x( 'Category Link', 'navigation link block description' ), + ), + 'item_link_description' => array( + _x( 'A link to a tag.', 'navigation link block description' ), + _x( 'A link to a category.', 'navigation link block description' ), + ), ); $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name']; diff --git a/tests/phpunit/tests/admin/includesPost.php b/tests/phpunit/tests/admin/includesPost.php index 4dffffd0d0ac3..915f1fbd04bdb 100644 --- a/tests/phpunit/tests/admin/includesPost.php +++ b/tests/phpunit/tests/admin/includesPost.php @@ -847,6 +847,7 @@ function test_get_block_editor_server_block_settings() { 'category' => 'common', 'styles' => array(), 'keywords' => array(), + 'variations' => array(), ), $blocks[ $name ] ); diff --git a/tests/phpunit/tests/rest-api/rest-block-type-controller.php b/tests/phpunit/tests/rest-api/rest-block-type-controller.php index 7bd1712f948c7..3958c9bd1978b 100644 --- a/tests/phpunit/tests/rest-api/rest-block-type-controller.php +++ b/tests/phpunit/tests/rest-api/rest-block-type-controller.php @@ -224,6 +224,7 @@ public function test_get_item_invalid() { 'styles' => 'invalid_styles', 'render_callback' => 'invalid_callback', 'textdomain' => true, + 'variations' => 'invalid_variations', ); register_block_type( $block_type, $settings ); wp_set_current_user( self::$admin_id ); @@ -249,6 +250,7 @@ public function test_get_item_invalid() { $this->assertNull( $data['category'] ); $this->assertNull( $data['textdomain'] ); $this->assertFalse( $data['is_dynamic'] ); + $this->assertSameSets( array( array() ), $data['variations'] ); } /** @@ -275,6 +277,7 @@ public function test_get_item_defaults() { 'render_callback' => false, 'textdomain' => false, 'example' => false, + 'variations' => false, ); register_block_type( $block_type, $settings ); wp_set_current_user( self::$admin_id ); @@ -301,6 +304,65 @@ public function test_get_item_defaults() { $this->assertNull( $data['example'] ); $this->assertNull( $data['textdomain'] ); $this->assertFalse( $data['is_dynamic'] ); + $this->assertSameSets( array(), $data['variations'] ); + } + + public function test_get_variation() { + $block_type = 'fake/variations'; + $settings = array( + 'title' => 'variations block test', + 'description' => 'a variations block test', + 'attributes' => array( 'kind' => array( 'type' => 'string' ) ), + 'variations' => array( + array( + 'name' => 'variation', + 'title' => 'variation title', + 'description' => 'variation description', + 'category' => 'media', + 'icon' => 'checkmark', + 'attributes' => array( 'kind' => 'foo' ), + 'isDefault' => true, + 'example' => array( 'attributes' => array( 'kind' => 'example' ) ), + 'scope' => array( 'inserter', 'block' ), + 'keywords' => array( 'dogs', 'cats', 'mice' ), + 'innerBlocks' => array( + array( + 'name' => 'fake/bar', + 'attributes' => array( 'label' => 'hi' ), + ), + ), + ), + ), + ); + register_block_type( $block_type, $settings ); + wp_set_current_user( self::$admin_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/block-types/' . $block_type ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( $block_type, $data['name'] ); + $this->assertArrayHasKey( 'variations', $data ); + $this->assertSame( 1, count( $data['variations'] ) ); + $variation = $data['variations'][0]; + $this->assertSame( 'variation title', $variation['title'] ); + $this->assertSame( 'variation description', $variation['description'] ); + $this->assertSame( 'media', $variation['category'] ); + $this->assertSame( 'checkmark', $variation['icon'] ); + $this->assertSameSets( array( 'inserter', 'block' ), $variation['scope'] ); + $this->assertSameSets( array( 'dogs', 'cats', 'mice' ), $variation['keywords'] ); + $this->assertSameSets( array( 'attributes' => array( 'kind' => 'example' ) ), $variation['example'] ); + $this->assertSameSets( + array( + array( + 'name' => 'fake/bar', + 'attributes' => array( 'label' => 'hi' ), + ), + ), + $variation['innerBlocks'] + ); + $this->assertSameSets( + array( 'kind' => 'foo' ), + $variation['attributes'] + ); } /** @@ -312,7 +374,7 @@ public function test_get_item_schema() { $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertCount( 20, $properties ); + $this->assertCount( 21, $properties ); $this->assertArrayHasKey( 'api_version', $properties ); $this->assertArrayHasKey( 'title', $properties ); $this->assertArrayHasKey( 'icon', $properties ); @@ -333,6 +395,7 @@ public function test_get_item_schema() { $this->assertArrayHasKey( 'example', $properties ); $this->assertArrayHasKey( 'uses_context', $properties ); $this->assertArrayHasKey( 'provides_context', $properties ); + $this->assertArrayHasKey( 'variations', $properties ); } /**