From 2b42ac5a12c4ddb31d3915175824dc3ce278484a Mon Sep 17 00:00:00 2001 From: TimothyBlynJacobs Date: Wed, 17 Jun 2020 03:20:02 +0000 Subject: [PATCH] REST API: Only register one block renderer route. Every block has a different set of attributes. These attributes are specified as a JSON Schema object. Previously, every block registered its own block renderer route using its attributes for the schema. This allowed for the attributes to be validated using the built in endpoint validation rules. It had the unfortunate side effect, however, of creating a large number of nearly identical REST API routes, one for each dynamic block. Each registered route has a performance impact. As the number of server side blocks goes up, this becomes more and more of an issue. Now, we register a single block renderer route and dynamically validate the attributes based on the selected block. Fixes #48079. Props gziolo, TimothyBlynJacobs. git-svn-id: https://develop.svn.wordpress.org/trunk@48069 602fd350-edb4-49c9-b593-d223f7449a82 --- ...lass-wp-rest-block-renderer-controller.php | 87 ++-- .../rest-block-renderer-controller.php | 39 +- .../tests/rest-api/rest-schema-setup.php | 12 +- tests/qunit/fixtures/wp-api-generated.js | 424 +----------------- 4 files changed, 85 insertions(+), 477 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php index 3bdcae5435..3388078d05 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-renderer-controller.php @@ -34,47 +34,52 @@ class WP_REST_Block_Renderer_Controller extends WP_REST_Controller { * @see register_rest_route() */ public function register_routes() { - $block_types = WP_Block_Type_Registry::get_instance()->get_all_registered(); - - foreach ( $block_types as $block_type ) { - if ( ! $block_type->is_dynamic() ) { - continue; - } - - register_rest_route( - $this->namespace, - '/' . $this->rest_base . '/(?P' . $block_type->name . ')', + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[a-z0-9-]+/[a-z0-9-]+)', + array( + 'args' => array( + 'name' => array( + 'description' => __( 'Unique registered name for the block.' ), + 'type' => 'string', + ), + ), array( - 'args' => array( - 'name' => array( - 'description' => __( 'Unique registered name for the block.' ), - 'type' => 'string', + 'methods' => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ), + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + 'attributes' => array( + 'description' => __( 'Attributes for the block' ), + 'type' => 'object', + 'default' => array(), + 'validate_callback' => static function ( $value, $request ) { + $block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] ); + + if ( ! $block ) { + // This will get rejected in ::get_item(). + return true; + } + + $schema = array( + 'type' => 'object', + 'properties' => $block->get_attributes(), + 'additionalProperties' => false, + ); + + return rest_validate_value_from_schema( $value, $schema ); + }, + ), + 'post_id' => array( + 'description' => __( 'ID of the post context.' ), + 'type' => 'integer', ), ), - array( - 'methods' => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ), - 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), - 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), - 'attributes' => array( - /* translators: %s: The name of the block. */ - 'description' => sprintf( __( 'Attributes for %s block' ), $block_type->name ), - 'type' => 'object', - 'additionalProperties' => false, - 'properties' => $block_type->get_attributes(), - 'default' => array(), - ), - 'post_id' => array( - 'description' => __( 'ID of the post context.' ), - 'type' => 'integer', - ), - ), - ), - 'schema' => array( $this, 'get_public_item_schema' ), - ) - ); - } + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); } /** @@ -136,9 +141,11 @@ class WP_REST_Block_Renderer_Controller extends WP_REST_Controller { // Set up postdata since this will be needed if post_id was set. setup_postdata( $post ); } - $registry = WP_Block_Type_Registry::get_instance(); - if ( null === $registry->get_registered( $request['name'] ) ) { + $registry = WP_Block_Type_Registry::get_instance(); + $registered = $registry->get_registered( $request['name'] ); + + if ( null === $registered || ! $registered->is_dynamic() ) { return new WP_Error( 'block_invalid', __( 'Invalid block.' ), diff --git a/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php b/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php index 88b1b69bde..439f59b92c 100644 --- a/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php +++ b/tests/phpunit/tests/rest-api/rest-block-renderer-controller.php @@ -46,6 +46,15 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca */ protected static $context_block_name = 'core/context-test-block'; + /** + * Non dynamic block name. + * + * @since 5.5.0 + * + * @var string + */ + protected static $non_dynamic_block_name = 'core/non-dynamic'; + /** * Test API user's ID. * @@ -117,6 +126,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca public function setUp() { $this->register_test_block(); $this->register_post_context_test_block(); + $this->register_non_dynamic_block(); parent::setUp(); } @@ -128,6 +138,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca public function tearDown() { WP_Block_Type_Registry::get_instance()->unregister( self::$block_name ); WP_Block_Type_Registry::get_instance()->unregister( self::$context_block_name ); + WP_Block_Type_Registry::get_instance()->unregister( self::$non_dynamic_block_name ); parent::tearDown(); } @@ -175,6 +186,15 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca ); } + /** + * Registers the non-dynamic block name. + * + * @since 5.5.0 + */ + protected function register_non_dynamic_block() { + register_block_type( self::$non_dynamic_block_name ); + } + /** * Test render callback. * @@ -210,9 +230,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca $this->assertContains( self::$block_name, $dynamic_block_names ); $routes = rest_get_server()->get_routes(); - foreach ( $dynamic_block_names as $dynamic_block_name ) { - $this->assertArrayHasKey( self::$rest_api_route . "(?P$dynamic_block_name)", $routes ); - } + $this->assertArrayHasKey( self::$rest_api_route . '(?P[a-z0-9-]+/[a-z0-9-]+)', $routes ); } /** @@ -261,7 +279,7 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca $request->set_param( 'context', 'edit' ); $response = rest_get_server()->dispatch( $request ); - $this->assertErrorResponse( 'rest_no_route', $response, 404 ); + $this->assertErrorResponse( 'block_invalid', $response, 404 ); } /** @@ -510,6 +528,19 @@ class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testca $this->assertErrorResponse( 'block_cannot_read', $response, 403 ); } + /** + * @ticket 48079 + */ + public function test_get_item_non_dynamic_block() { + wp_set_current_user( self::$user_id ); + $request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$non_dynamic_block_name ); + + $request->set_param( 'context', 'edit' ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'block_invalid', $response, 404 ); + } + /** * Get item schema. * diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index c60170d45a..3561314dbe 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -120,17 +120,7 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { '/wp/v2/comments', '/wp/v2/comments/(?P[\\d]+)', '/wp/v2/search', - '/wp/v2/block-renderer/(?Pcore/archives)', - '/wp/v2/block-renderer/(?Pcore/block)', - '/wp/v2/block-renderer/(?Pcore/calendar)', - '/wp/v2/block-renderer/(?Pcore/categories)', - '/wp/v2/block-renderer/(?Pcore/latest-comments)', - '/wp/v2/block-renderer/(?Pcore/latest-posts)', - '/wp/v2/block-renderer/(?Pcore/rss)', - '/wp/v2/block-renderer/(?Pcore/search)', - '/wp/v2/block-renderer/(?Pcore/shortcode)', - '/wp/v2/block-renderer/(?Pcore/social-link)', - '/wp/v2/block-renderer/(?Pcore/tag-cloud)', + '/wp/v2/block-renderer/(?P[a-z0-9-]+/[a-z0-9-]+)', '/wp/v2/settings', '/wp/v2/themes', ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 2da6be93b5..280144750d 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -4394,7 +4394,7 @@ mockedApiResponse.Schema = { "self": "http://example.org/index.php?rest_route=/wp/v2/search" } }, - "/wp/v2/block-renderer/(?Pcore/archives)": { + "/wp/v2/block-renderer/(?P[a-z0-9-]+/[a-z0-9-]+)": { "namespace": "wp/v2", "methods": [ "GET", @@ -4424,427 +4424,7 @@ mockedApiResponse.Schema = { "attributes": { "required": false, "default": [], - "description": "Attributes for core/archives block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/block)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/block block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/calendar)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/calendar block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/categories)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/categories block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/latest-comments)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/latest-comments block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/latest-posts)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/latest-posts block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/rss)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/rss block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/search)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/search block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/shortcode)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/shortcode block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/social-link)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/social-link block", - "type": "object" - }, - "post_id": { - "required": false, - "description": "ID of the post context.", - "type": "integer" - } - } - } - ] - }, - "/wp/v2/block-renderer/(?Pcore/tag-cloud)": { - "namespace": "wp/v2", - "methods": [ - "GET", - "POST" - ], - "endpoints": [ - { - "methods": [ - "GET", - "POST" - ], - "args": { - "name": { - "required": false, - "description": "Unique registered name for the block.", - "type": "string" - }, - "context": { - "required": false, - "default": "view", - "enum": [ - "edit" - ], - "description": "Scope under which the request is made; determines fields present in response.", - "type": "string" - }, - "attributes": { - "required": false, - "default": [], - "description": "Attributes for core/tag-cloud block", + "description": "Attributes for the block", "type": "object" }, "post_id": {