diff --git a/src/wp-admin/includes/post.php b/src/wp-admin/includes/post.php index f79b68c299..1f94655646 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 f4a22d638b..30ba9ff407 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 36b3a1aa79..88ee84d584 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 64a1ce653a..f2f277e5eb 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 ae144a00b7..13298022c1 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 4dffffd0d0..915f1fbd04 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 7bd1712f94..3958c9bd19 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 ); } /**