From 30d4f2e9028a3c79960a924917144e656ad5cc7c Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Mon, 12 Dec 2022 12:36:04 +0200 Subject: [PATCH] [Patterns]: Add new pattern categories (#46144) * [Patterns]: Add new pattern categories * remove some categories * update php code * update php * add php test and update/remove test that are in core * map more categories * migrate only `columns` to `text` and show `text` at the bottom of categories list * address feedback --- ...rg-rest-block-patterns-controller-6-1.php} | 99 +------------- lib/compat/wordpress-6.1/rest-api.php | 9 -- lib/compat/wordpress-6.2/block-patterns.php | 102 ++++++++++++--- ...erg-rest-block-patterns-controller-6-2.php | 107 +++++++++++++++ lib/compat/wordpress-6.2/rest-api.php | 9 ++ lib/load.php | 3 +- .../components/inserter/block-patterns-tab.js | 11 +- ...rg-rest-block-patterns-controller-test.php | 123 +++++++++++++----- 8 files changed, 305 insertions(+), 158 deletions(-) rename lib/compat/wordpress-6.1/{class-gutenberg-rest-block-patterns-controller.php => class-gutenberg-rest-block-patterns-controller-6-1.php} (60%) create mode 100644 lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php similarity index 60% rename from lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php rename to lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php index c053a678083da..b40f3aa2497f1 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php @@ -1,6 +1,6 @@ namespace = 'wp/v2'; - $this->rest_base = 'block-patterns/patterns'; - } - - /** - * Registers the routes for the objects of the controller. - * - * @since 6.0.0 - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ), - true - ); - } - - /** - * Checks whether a given request has permission to read block patterns. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - if ( current_user_can( 'edit_posts' ) ) { - return true; - } - - foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { - if ( current_user_can( $post_type->cap->edit_posts ) ) { - return true; - } - } - - return new WP_Error( - 'rest_cannot_view', - __( 'Sorry, you are not allowed to view the registered block patterns.', 'gutenberg' ), - array( 'status' => rest_authorization_required_code() ) - ); - } - - /** - * Retrieves all block patterns. - * - * @since 6.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. - */ - public function get_items( $request ) { - if ( ! $this->remote_patterns_loaded ) { - // Load block patterns from w.org. - _load_remote_block_patterns(); // Patterns with the `core` keyword. - _load_remote_featured_patterns(); // Patterns in the `featured` category. - _register_remote_theme_patterns(); // Patterns requested by current theme. - - $this->remote_patterns_loaded = true; - } - - $response = array(); - $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); - foreach ( $patterns as $pattern ) { - $prepared_pattern = $this->prepare_item_for_response( $pattern, $request ); - $response[] = $this->prepare_response_for_collection( $prepared_pattern ); - } - return rest_ensure_response( $response ); - } - +class Gutenberg_REST_Block_Patterns_Controller_6_1 extends WP_REST_Block_Patterns_Controller { /** * Prepare a raw block pattern before it gets output in a REST API response. * * @since 6.0.0 + * @since 6.1.0 Added `postTypes` property. * * @param array $item Raw pattern as registered, before any changes. * @param WP_REST_Request $request Request object. @@ -147,6 +55,7 @@ public function prepare_item_for_response( $item, $request ) { * Retrieves the block pattern schema, conforming to JSON Schema. * * @since 6.0.0 + * @since 6.1.0 Added `post_types` property. * * @return array Item schema data. */ diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index f3391389d9e38..5dd06d3aad855 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -35,15 +35,6 @@ function gutenberg_update_post_types_rest_response( $response, $post_type ) { } add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 ); -/** - * Registers the block patterns REST API routes. - */ -function gutenberg_register_gutenberg_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); - $block_patterns->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_gutenberg_rest_block_patterns', 100 ); - /** * Exposes the site logo URL through the WordPress REST API. * diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php index 8f50c9ff33c5d..1e529faf96321 100644 --- a/lib/compat/wordpress-6.2/block-patterns.php +++ b/lib/compat/wordpress-6.2/block-patterns.php @@ -8,7 +8,7 @@ /** * Registers the block pattern categories. */ -function gutenberg_register_core_block_patterns_and_categories() { +function gutenberg_register_core_block_patterns_categories() { register_block_pattern_category( 'banner', array( @@ -30,49 +30,115 @@ function gutenberg_register_core_block_patterns_and_categories() { ) ); register_block_pattern_category( - 'footer', + 'text', array( - 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ), - 'description' => __( 'A variety of footer designs displaying information and site navigation.', 'gutenberg' ), + 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Patterns containing mostly text.', 'gutenberg' ), ) ); register_block_pattern_category( - 'gallery', + 'query', array( - 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ), - 'description' => __( 'Patterns containing mostly images or other media.', 'gutenberg' ), + 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ), ) ); register_block_pattern_category( - 'header', + 'featured', array( - 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ), - 'description' => __( 'A variety of header designs displaying your site title and navigation.', 'gutenberg' ), + 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A set of high quality curated patterns.', 'gutenberg' ), ) ); + + // Register new core block pattern categories. register_block_pattern_category( - 'text', + 'call-to-action', array( - 'label' => _x( 'Text', 'Block pattern category', 'gutenberg' ), - 'description' => __( 'Patterns containing mostly text.', 'gutenberg' ), + 'label' => _x( 'Call to Action', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Sections whose purpose is to trigger a specific action.', 'gutenberg' ), ) ); register_block_pattern_category( - 'query', + 'team', + array( + 'label' => _x( 'Team', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A variety of designs to display your team members.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'testimonials', + array( + 'label' => _x( 'Testimonials', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Share reviews and feedback about your brand/business.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'services', + array( + 'label' => _x( 'Services', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Briefly describe what your business does and how you can help.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'contact', + array( + 'label' => _x( 'Contact', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Display your contact information.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'about', + array( + 'label' => _x( 'About', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Introduce yourself.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'portfolio', + array( + 'label' => _x( 'Portfolio', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Showcase your latest work.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'gallery', + array( + 'label' => _x( 'Gallery', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Different layouts for displaying images.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'media', + array( + 'label' => _x( 'Media', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'Different layouts containing video or audio.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'posts', array( 'label' => _x( 'Posts', 'Block pattern category', 'gutenberg' ), 'description' => __( 'Display your latest posts in lists, grids or other layouts.', 'gutenberg' ), ) ); + // Site building pattern categories. register_block_pattern_category( - 'featured', + 'footer', array( - 'label' => _x( 'Featured', 'Block pattern category', 'gutenberg' ), - 'description' => __( 'A set of high quality curated patterns.', 'gutenberg' ), + 'label' => _x( 'Footers', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A variety of footer designs displaying information and site navigation.', 'gutenberg' ), + ) + ); + register_block_pattern_category( + 'header', + array( + 'label' => _x( 'Headers', 'Block pattern category', 'gutenberg' ), + 'description' => __( 'A variety of header designs displaying your site title and navigation.', 'gutenberg' ), ) ); } -add_action( 'init', 'gutenberg_register_core_block_patterns_and_categories' ); +add_action( 'init', 'gutenberg_register_core_block_patterns_categories' ); /** * Registers Gutenberg-bundled patterns, with a focus on headers and footers diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php new file mode 100644 index 0000000000000..d34160edb2af0 --- /dev/null +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php @@ -0,0 +1,107 @@ + 'call-to-action', + 'columns' => 'text', + 'query' => 'posts', + ); + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.0.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ), + true + ); + } + /** + * Retrieves all block patterns. + * + * @since 6.0.0 + * @since 6.2.0 Added migration for old core pattern categories to the new ones. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + if ( ! $this->remote_patterns_loaded ) { + // Load block patterns from w.org. + _load_remote_block_patterns(); // Patterns with the `core` keyword. + _load_remote_featured_patterns(); // Patterns in the `featured` category. + _register_remote_theme_patterns(); // Patterns requested by current theme. + + $this->remote_patterns_loaded = true; + } + + $response = array(); + $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); + foreach ( $patterns as $pattern ) { + $migrated_pattern = $this->migrate_pattern_categories( $pattern ); + $prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request ); + $response[] = $this->prepare_response_for_collection( $prepared_pattern ); + } + return rest_ensure_response( $response ); + } + + /** + * Migrates old core pattern categories to new ones. + * + * Core pattern categories are being revamped and we need to handle the migration + * to the new ones and ensure backwards compatibility. + * + * @since 6.2.0 + * + * @param array $pattern Raw pattern as registered, before applying any changes. + * @return array Migrated pattern. + */ + protected function migrate_pattern_categories( $pattern ) { + if ( isset( $pattern['categories'] ) && is_array( $pattern['categories'] ) ) { + foreach ( $pattern['categories'] as $i => $category ) { + if ( array_key_exists( $category, static::$categories_migration ) ) { + $pattern['categories'][ $i ] = static::$categories_migration[ $category ]; + } + } + } + return $pattern; + } +} diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php index 023a79e3db95f..4541ce0ae158c 100644 --- a/lib/compat/wordpress-6.2/rest-api.php +++ b/lib/compat/wordpress-6.2/rest-api.php @@ -23,6 +23,15 @@ function gutenberg_register_rest_pattern_directory() { } add_action( 'rest_api_init', 'gutenberg_register_rest_pattern_directory' ); +/** + * Registers the block patterns REST API routes. + */ +function gutenberg_register_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_2(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); + /** * Add extra collection params to pattern directory requests. * diff --git a/lib/load.php b/lib/load.php index 3c9f458fcd388..d2ec3611e6cb4 100644 --- a/lib/load.php +++ b/lib/load.php @@ -36,11 +36,12 @@ function gutenberg_is_experiment_enabled( $name ) { } // WordPress 6.1 compat. - require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php'; + require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller-6-1.php'; require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/rest-api.php'; // WordPress 6.2 compat. + require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php'; diff --git a/packages/block-editor/src/components/inserter/block-patterns-tab.js b/packages/block-editor/src/components/inserter/block-patterns-tab.js index a1e9c83989465..93b4ca40978e1 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-tab.js +++ b/packages/block-editor/src/components/inserter/block-patterns-tab.js @@ -53,10 +53,17 @@ function usePatternsCategories() { ) ) .sort( ( { name: currentName }, { name: nextName } ) => { - if ( ! [ currentName, nextName ].includes( 'featured' ) ) { + if ( + ! [ currentName, nextName ].some( ( categoryName ) => + [ 'featured', 'text' ].includes( categoryName ) + ) + ) { return 0; } - return currentName === 'featured' ? -1 : 1; + // Move `featured` category to the top and `text` to the bottom. + return currentName === 'featured' || nextName === 'text' + ? -1 + : 1; } ); if ( diff --git a/phpunit/class-gutenberg-rest-block-patterns-controller-test.php b/phpunit/class-gutenberg-rest-block-patterns-controller-test.php index ad6f42b033e84..9f9cf60f0572a 100644 --- a/phpunit/class-gutenberg-rest-block-patterns-controller-test.php +++ b/phpunit/class-gutenberg-rest-block-patterns-controller-test.php @@ -10,27 +10,54 @@ * Unit tests for REST API for Block Patterns. * * @group restapi - * @covers Gutenberg_REST_Block_Patterns_Controller + * @covers Gutenberg_REST_Block_Patterns_Controller_6_2 */ -class Gutenberg_REST_Block_Patterns_Controller_Test extends WP_Test_REST_Controller_Testcase { +class Gutenberg_REST_Block_Patterns_Controller_6_2_Test extends WP_Test_REST_Controller_Testcase { + /** + * Admin user ID. + * + * @since 6.0.0 + * + * @var int + */ protected static $admin_id; + + /** + * Original instance of WP_Block_Patterns_Registry. + * + * @since 6.0.0 + * + * @var WP_Block_Patterns_Registry + */ protected static $orig_registry; - public function set_up() { - parent::set_up(); - switch_theme( 'emptytheme' ); - } + /** + * Instance of the reflected `instance` property. + * + * @since 6.0.0 + * + * @var ReflectionProperty + */ + private static $registry_instance_property; + + /** + * The REST API route. + * + * @since 6.0.0 + * + * @var string + */ + const REQUEST_ROUTE = '/wp/v2/block-patterns/patterns'; public static function wpSetUpBeforeClass( $factory ) { - // Create a test user. self::$admin_id = $factory->user->create( array( 'role' => 'administrator' ) ); // Setup an empty testing instance of `WP_Block_Patterns_Registry` and save the original. - $reflection = new ReflectionClass( 'WP_Block_Patterns_Registry' ); - $reflection->getProperty( 'instance' )->setAccessible( true ); - self::$orig_registry = $reflection->getStaticPropertyValue( 'instance' ); - $test_registry = new WP_Block_Patterns_Registry(); - $reflection->setStaticPropertyValue( 'instance', $test_registry ); + self::$orig_registry = WP_Block_Patterns_Registry::get_instance(); + self::$registry_instance_property = new ReflectionProperty( 'WP_Block_Patterns_Registry', 'instance' ); + self::$registry_instance_property->setAccessible( true ); + $test_registry = new WP_Block_Pattern_Categories_Registry(); + self::$registry_instance_property->setValue( $test_registry ); // Register some patterns in the test registry. $test_registry->register( @@ -51,47 +78,77 @@ public static function wpSetUpBeforeClass( $factory ) { 'content' => '

Two

', ) ); + + $test_registry->register( + 'test/three', + array( + 'title' => 'Pattern Three', + 'categories' => array( 'test', 'buttons', 'query' ), + 'content' => '

Three

', + ) + ); } public static function wpTearDownAfterClass() { - // Delete the test user. self::delete_user( self::$admin_id ); // Restore the original registry instance. - $reflection = new ReflectionClass( 'WP_Block_Patterns_Registry' ); - $reflection->setStaticPropertyValue( 'instance', self::$orig_registry ); + self::$registry_instance_property->setValue( self::$orig_registry ); + self::$registry_instance_property->setAccessible( false ); + self::$registry_instance_property = null; + self::$orig_registry = null; } - public function test_register_routes() { - $routes = rest_get_server()->get_routes(); - $this->assertArrayHasKey( - '/wp/v2/block-patterns/patterns', - $routes, - 'The patterns route does not exist' - ); + public function set_up() { + parent::set_up(); + switch_theme( 'emptytheme' ); } - public function test_get_items() { + public function test_get_items_migrate_pattern_categories() { wp_set_current_user( self::$admin_id ); - $expected_names = array( 'test/one', 'test/two' ); - $expected_fields = array( 'name', 'content' ); - - $request = new WP_REST_Request( 'GET', '/wp/v2/block-patterns/patterns' ); - $request['_fields'] = 'name,content'; + $request = new WP_REST_Request( 'GET', static::REQUEST_ROUTE ); + $request['_fields'] = 'name,categories'; $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); - $this->assertCount( count( $expected_names ), $data ); - foreach ( $data as $idx => $item ) { - $this->assertEquals( $expected_names[ $idx ], $item['name'] ); - $this->assertEquals( $expected_fields, array_keys( $item ) ); - } + $this->assertIsArray( $data, 'WP_REST_Block_Patterns_Controller::get_items() should return an array' ); + $this->assertGreaterThanOrEqual( 3, count( $data ), 'WP_REST_Block_Patterns_Controller::get_items() should return at least 3 items' ); + $this->assertSame( + array( + 'name' => 'test/one', + 'categories' => array( 'test' ), + ), + $data[0], + 'WP_REST_Block_Patterns_Controller::get_items() should return test/one' + ); + $this->assertSame( + array( + 'name' => 'test/two', + 'categories' => array( 'test' ), + ), + $data[1], + 'WP_REST_Block_Patterns_Controller::get_items() should return test/two' + ); + $this->assertSame( + array( + 'name' => 'test/three', + 'categories' => array( 'test', 'call-to-action', 'posts' ), + ), + $data[2], + 'WP_REST_Block_Patterns_Controller::get_items() should return test/three' + ); } /** * Abstract methods that we must implement. */ + public function test_register_routes() { + $this->markTestIncomplete(); + } + public function test_get_items() { + $this->markTestIncomplete(); + } public function test_context_param() { $this->markTestIncomplete(); }