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": {