REST API: Add endpoints for blocks.

`WP_REST_Block_Renderer_Controller` allows rendering of server-side rendered blocks, whilst `WP_REST_Blocks_Controller` allows retrieving of reusable blocks.

Merges [43805] and [43806] from the 5.0 branch to trunk.

Props desrosj, danielbachhuber, pento, Presskopp, swissspidy.
See #45065, #45098.


git-svn-id: https://develop.svn.wordpress.org/trunk@44150 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Jeremy Felt 2018-12-14 02:34:28 +00:00
parent aadbdd7ad0
commit 7702b535be
9 changed files with 1776 additions and 44 deletions

View File

@ -255,18 +255,21 @@ function create_initial_post_types() {
register_post_type(
'wp_block',
array(
'labels' => array(
'labels' => array(
'name' => __( 'Blocks' ),
'singular_name' => __( 'Block' ),
'search_items' => __( 'Search Blocks' ),
),
'public' => false,
'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
'show_ui' => true,
'show_in_menu' => false,
'rewrite' => false,
'capability_type' => 'block',
'capabilities' => array(
'public' => false,
'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
'show_ui' => true,
'show_in_menu' => false,
'rewrite' => false,
'show_in_rest' => true,
'rest_base' => 'blocks',
'rest_controller_class' => 'WP_REST_Blocks_Controller',
'capability_type' => 'block',
'capabilities' => array(
// You need to be able to edit posts, in order to read blocks in their raw form.
'read' => 'edit_posts',
// You need to be able to publish posts, in order to create blocks.
@ -276,8 +279,8 @@ function create_initial_post_types() {
'edit_others_posts' => 'edit_others_posts',
'delete_others_posts' => 'delete_others_posts',
),
'map_meta_cap' => true,
'supports' => array(
'map_meta_cap' => true,
'supports' => array(
'title',
'editor',
),

View File

@ -249,6 +249,10 @@ function create_initial_rest_routes() {
$controller = new WP_REST_Search_Controller( $search_handlers );
$controller->register_routes();
// Block Renderer.
$controller = new WP_REST_Block_Renderer_Controller;
$controller->register_routes();
// Settings.
$controller = new WP_REST_Settings_Controller;
$controller->register_routes();
@ -256,6 +260,7 @@ function create_initial_rest_routes() {
// Themes.
$controller = new WP_REST_Themes_Controller;
$controller->register_routes();
}
/**

View File

@ -0,0 +1,177 @@
<?php
/**
* Block Renderer REST API: WP_REST_Block_Renderer_Controller class
*
* @package WordPress
* @subpackage REST_API
* @since 5.0.0
*/
/**
* Controller which provides REST endpoint for rendering a block.
*
* @since 5.0.0
*
* @see WP_REST_Controller
*/
class WP_REST_Block_Renderer_Controller extends WP_REST_Controller {
/**
* Constructs the controller.
*
* @since 5.0.0
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'block-renderer';
}
/**
* Registers the necessary REST API routes, one for each dynamic block.
*
* @since 5.0.0
*/
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<name>' . $block_type->name . ')',
array(
'args' => array(
'name' => array(
'description' => __( 'Unique registered name for the block.' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'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 is the name of the block */
'description' => sprintf( __( 'Attributes for %s block' ), $block_type->name ),
'type' => 'object',
'additionalProperties' => false,
'properties' => $block_type->get_attributes(),
),
'post_id' => array(
'description' => __( 'ID of the post context.' ),
'type' => 'integer',
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
}
/**
* Checks if a given request has access to read blocks.
*
* @since 5.0.0
*
* @param WP_REST_Request $request Request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
global $post;
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
if ( 0 < $post_id ) {
$post = get_post( $post_id );
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
return new WP_Error(
'block_cannot_read',
__( 'Sorry, you are not allowed to read blocks of this post.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
} else {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error(
'block_cannot_read',
__( 'Sorry, you are not allowed to read blocks as this user.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
}
return true;
}
/**
* Returns block output from block's registered render_callback.
*
* @since 5.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_item( $request ) {
global $post;
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
if ( 0 < $post_id ) {
$post = get_post( $post_id );
// Set up postdata since this will be needed if post_id was set.
setup_postdata( $post );
}
$registry = WP_Block_Type_Registry::get_instance();
$block = $registry->get_registered( $request['name'] );
if ( null === $block ) {
return new WP_Error(
'block_invalid',
__( 'Invalid block.' ),
array(
'status' => 404,
)
);
}
$data = array(
'rendered' => $block->render( $request->get_param( 'attributes' ) ),
);
return rest_ensure_response( $data );
}
/**
* Retrieves block's output schema, conforming to JSON Schema.
*
* @since 5.0.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
return array(
'$schema' => 'http://json-schema.org/schema#',
'title' => 'rendered-block',
'type' => 'object',
'properties' => array(
'rendered' => array(
'description' => __( 'The rendered block.' ),
'type' => 'string',
'required' => true,
'context' => array( 'edit' ),
),
),
);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Reusable blocks REST API: WP_REST_Blocks_Controller class
*
* @package WordPress
* @subpackage REST_API
* @since 5.0.0
*/
/**
* Controller which provides a REST endpoint for the editor to read, create,
* edit and delete reusable blocks. Blocks are stored as posts with the wp_block
* post type.
*
* @since 5.0.0
*
* @see WP_REST_Posts_Controller
* @see WP_REST_Controller
*/
class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller {
/**
* Checks if a block can be read.
*
* @since 5.0.0
*
* @param object $post Post object that backs the block.
* @return bool Whether the block can be read.
*/
public function check_read_permission( $post ) {
// Ensure that the user is logged in and has the read_blocks capability.
$post_type = get_post_type_object( $post->post_type );
if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) {
return false;
}
return parent::check_read_permission( $post );
}
}

View File

@ -236,6 +236,8 @@ require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.p
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-search-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-blocks-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-renderer-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-themes-controller.php' );
require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' );

View File

@ -0,0 +1,529 @@
<?php
/**
* WP_REST_Block_Renderer_Controller tests.
*
* @package WordPress
* @subpackage REST_API
* @since 5.0.0
*/
/**
* Tests for WP_REST_Block_Renderer_Controller.
*
* @since 5.0.0
*
* @covers WP_REST_Block_Renderer_Controller
*
* @group restapi-blocks
* @group restapi
*/
class REST_Block_Renderer_Controller_Test extends WP_Test_REST_Controller_Testcase {
/**
* The REST API route for the block renderer.
*
* @since 5.0.0
*
* @var string
*/
protected static $rest_api_route = '/wp/v2/block-renderer/';
/**
* Test block's name.
*
* @since 5.0.0
*
* @var string
*/
protected static $block_name = 'core/test-block';
/**
* Test post context block's name.
*
* @since 5.0.0
*
* @var string
*/
protected static $context_block_name = 'core/context-test-block';
/**
* Test API user's ID.
*
* @since 5.0.0
*
* @var int
*/
protected static $user_id;
/**
* Test post ID.
*
* @since 5.0.0
*
* @var int
*/
protected static $post_id;
/**
* Author test user ID.
*
* @since 5.0.0
*
* @var int
*/
protected static $author_id;
/**
* Create test data before the tests run.
*
* @since 5.0.0
*
* @param WP_UnitTest_Factory $factory Helper that lets us create fake data.
*/
public static function wpSetUpBeforeClass( $factory ) {
self::$user_id = $factory->user->create(
array(
'role' => 'editor',
)
);
self::$author_id = $factory->user->create(
array(
'role' => 'author',
)
);
self::$post_id = $factory->post->create(
array(
'post_title' => 'Test Post',
)
);
}
/**
* Delete test data after our tests run.
*
* @since 5.0.0
*/
public static function wpTearDownAfterClass() {
self::delete_user( self::$user_id );
}
/**
* Set up each test method.
*
* @since 5.0.0
*/
public function setUp() {
$this->register_test_block();
$this->register_post_context_test_block();
parent::setUp();
}
/**
* Tear down each test method.
*
* @since 5.0.0
*/
public function tearDown() {
WP_Block_Type_Registry::get_instance()->unregister( self::$block_name );
WP_Block_Type_Registry::get_instance()->unregister( self::$context_block_name );
parent::tearDown();
}
/**
* Register test block.
*
* @since 5.0.0
*/
public function register_test_block() {
register_block_type(
self::$block_name,
array(
'attributes' => array(
'some_string' => array(
'type' => 'string',
'default' => 'some_default',
),
'some_int' => array(
'type' => 'integer',
),
'some_array' => array(
'type' => 'array',
'items' => array(
'type' => 'integer',
),
),
),
'render_callback' => array( $this, 'render_test_block' ),
)
);
}
/**
* Register test block with post_id as attribute for post context test.
*
* @since 5.0.0
*/
public function register_post_context_test_block() {
register_block_type(
self::$context_block_name,
array(
'attributes' => array(),
'render_callback' => array( $this, 'render_post_context_test_block' ),
)
);
}
/**
* Test render callback.
*
* @since 5.0.0
*
* @param array $attributes Props.
* @return string Rendered attributes, which is here just JSON.
*/
public function render_test_block( $attributes ) {
return wp_json_encode( $attributes );
}
/**
* Test render callback for testing post context.
*
* @since 5.0.0
*
* @return string
*/
public function render_post_context_test_block() {
return get_the_title();
}
/**
* Check that the route was registered properly.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::register_routes()
*/
public function test_register_routes() {
$dynamic_block_names = get_dynamic_block_names();
$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<name>$dynamic_block_name)", $routes );
}
}
/**
* Test getting item without permissions.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item()
*/
public function test_get_item_without_permissions() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'block_cannot_read', $response, rest_authorization_required_code() );
}
/**
* Test getting item without 'edit' context.
*
* @ticket 45098
*/
public function test_get_item_with_invalid_context() {
wp_set_current_user( self::$user_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
/**
* Test getting item with invalid block name.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item()
*/
public function test_get_item_invalid_block_name() {
wp_set_current_user( self::$user_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . 'core/123' );
$request->set_param( 'context', 'edit' );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
/**
* Check getting item with an invalid param provided.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item()
*/
public function test_get_item_invalid_attribute() {
wp_set_current_user( self::$user_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$request->set_param( 'context', 'edit' );
$request->set_param(
'attributes',
array(
'some_string' => array( 'no!' ),
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
}
/**
* Check getting item with an invalid param provided.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item()
*/
public function test_get_item_unrecognized_attribute() {
wp_set_current_user( self::$user_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$request->set_param( 'context', 'edit' );
$request->set_param(
'attributes',
array(
'unrecognized' => 'yes',
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 400, $response->get_status() );
}
/**
* Check getting item with default attributes provided.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item()
*/
public function test_get_item_default_attributes() {
wp_set_current_user( self::$user_id );
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( self::$block_name );
$defaults = array();
foreach ( $block_type->attributes as $key => $attribute ) {
$defaults[ $key ] = isset( $attribute['default'] ) ? $attribute['default'] : null;
}
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$request->set_param( 'context', 'edit' );
$request->set_param( 'attributes', array() );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $defaults, json_decode( $data['rendered'], true ) );
$this->assertEquals(
json_decode( $block_type->render( $defaults ) ),
json_decode( $data['rendered'] )
);
}
/**
* Check getting item with attributes provided.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item()
*/
public function test_get_item() {
wp_set_current_user( self::$user_id );
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( self::$block_name );
$attributes = array(
'some_int' => '123',
'some_string' => 'foo',
'some_array' => array( 1, '2', 3 ),
);
$expected_attributes = $attributes;
$expected_attributes['some_int'] = (int) $expected_attributes['some_int'];
$expected_attributes['some_array'] = array_map( 'intval', $expected_attributes['some_array'] );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$request->set_param( 'context', 'edit' );
$request->set_param( 'attributes', $attributes );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $expected_attributes, json_decode( $data['rendered'], true ) );
$this->assertEquals(
json_decode( $block_type->render( $attributes ), true ),
json_decode( $data['rendered'], true )
);
}
/**
* Check success response for getting item with layout attribute provided.
*
* @ticket 45098
*/
public function test_get_item_with_layout() {
wp_set_current_user( self::$user_id );
$attributes = array(
'layout' => 'foo',
);
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$block_name );
$request->set_param( 'context', 'edit' );
$request->set_param( 'attributes', $attributes );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
}
/**
* Test getting item with post context.
*
* @ticket 45098
*/
public function test_get_item_with_post_context() {
wp_set_current_user( self::$user_id );
$expected_title = 'Test Post';
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$context_block_name );
$request->set_param( 'context', 'edit' );
// Test without post ID.
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertTrue( empty( $data['rendered'] ) );
// Now test with post ID.
$request->set_param( 'post_id', self::$post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $expected_title, $data['rendered'] );
}
/**
* Test getting item with invalid post ID.
*
* @ticket 45098
*/
public function test_get_item_without_permissions_invalid_post() {
wp_set_current_user( self::$user_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$context_block_name );
$request->set_param( 'context', 'edit' );
// Test with invalid post ID.
$request->set_param( 'post_id', PHP_INT_MAX );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'block_cannot_read', $response, 403 );
}
/**
* Test getting item without permissions to edit context post.
*
* @ticket 45098
*/
public function test_get_item_without_permissions_cannot_edit_post() {
wp_set_current_user( self::$author_id );
$request = new WP_REST_Request( 'GET', self::$rest_api_route . self::$context_block_name );
$request->set_param( 'context', 'edit' );
// Test with private post ID.
$request->set_param( 'post_id', self::$post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertErrorResponse( 'block_cannot_read', $response, 403 );
}
/**
* Get item schema.
*
* @ticket 45098
*
* @covers WP_REST_Block_Renderer_Controller::get_item_schema()
*/
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', self::$rest_api_route . self::$block_name );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$this->assertEqualSets( array( 'GET' ), $data['endpoints'][0]['methods'] );
$this->assertEqualSets(
array( 'name', 'context', 'attributes', 'post_id' ),
array_keys( $data['endpoints'][0]['args'] )
);
$this->assertEquals( 'object', $data['endpoints'][0]['args']['attributes']['type'] );
$this->assertArrayHasKey( 'schema', $data );
$this->assertEquals( 'rendered-block', $data['schema']['title'] );
$this->assertEquals( 'object', $data['schema']['type'] );
$this->arrayHasKey( 'rendered', $data['schema']['properties'] );
$this->arrayHasKey( 'string', $data['schema']['properties']['rendered']['type'] );
$this->assertEquals( array( 'edit' ), $data['schema']['properties']['rendered']['context'] );
}
/**
* The update_item() method does not exist for block rendering.
*/
public function test_update_item() {
$this->markTestSkipped( 'Controller does not implement update_item().' );
}
/**
* The create_item() method does not exist for block rendering.
*/
public function test_create_item() {
$this->markTestSkipped( 'Controller does not implement create_item().' );
}
/**
* The delete_item() method does not exist for block rendering.
*/
public function test_delete_item() {
$this->markTestSkipped( 'Controller does not implement delete_item().' );
}
/**
* The get_items() method does not exist for block rendering.
*/
public function test_get_items() {
$this->markTestSkipped( 'Controller does not implement get_items().' );
}
/**
* The context_param() method does not exist for block rendering.
*/
public function test_context_param() {
$this->markTestSkipped( 'Controller does not implement context_param().' );
}
/**
* The prepare_item() method does not exist for block rendering.
*/
public function test_prepare_item() {
$this->markTestSkipped( 'Controller does not implement prepare_item().' );
}
}

View File

@ -0,0 +1,204 @@
<?php
/**
* WP_REST_Blocks_Controller tests
*
* @package WordPress
* @subpackage REST_API
* @since 5.0.0
*/
/**
* Tests for WP_REST_Blocks_Controller.
*
* @since 5.0.0
*
* @see WP_Test_REST_Controller_Testcase
*
* @group restapi-blocks
* @group restapi
*/
class REST_Blocks_Controller_Test extends WP_UnitTestCase {
/**
* Our fake block's post ID.
*
* @since 5.0.0
*
* @var int
*/
protected static $post_id;
/**
* Our fake user's ID.
*
* @since 5.0.0
*
* @var int
*/
protected static $user_id;
/**
* Create fake data before our tests run.
*
* @since 5.0.0
*
* @param WP_UnitTest_Factory $factory Helper that lets us create fake data.
*/
public static function wpSetUpBeforeClass( $factory ) {
self::$post_id = wp_insert_post(
array(
'post_type' => 'wp_block',
'post_status' => 'publish',
'post_title' => 'My cool block',
'post_content' => '<!-- wp:core/paragraph --><p>Hello!</p><!-- /wp:core/paragraph -->',
)
);
self::$user_id = $factory->user->create(
array(
'role' => 'editor',
)
);
}
/**
* Delete our fake data after our tests run.
*
* @since 5.0.0
*/
public static function wpTearDownAfterClass() {
wp_delete_post( self::$post_id );
self::delete_user( self::$user_id );
}
/**
* Test cases for test_capabilities().
*
* @since 5.0.0
*/
public function data_capabilities() {
return array(
array( 'create', 'editor', 201 ),
array( 'create', 'author', 201 ),
array( 'create', 'contributor', 403 ),
array( 'create', null, 401 ),
array( 'read', 'editor', 200 ),
array( 'read', 'author', 200 ),
array( 'read', 'contributor', 200 ),
array( 'read', null, 401 ),
array( 'update_delete_own', 'editor', 200 ),
array( 'update_delete_own', 'author', 200 ),
array( 'update_delete_own', 'contributor', 403 ),
array( 'update_delete_others', 'editor', 200 ),
array( 'update_delete_others', 'author', 403 ),
array( 'update_delete_others', 'contributor', 403 ),
array( 'update_delete_others', null, 401 ),
);
}
/**
* Exhaustively check that each role either can or cannot create, edit,
* update, and delete reusable blocks.
*
* @ticket 45098
*
* @dataProvider data_capabilities
*
* @param string $action Action to perform in the test.
* @param string $role User role to test.
* @param int $expected_status Expected HTTP response status.
*/
public function test_capabilities( $action, $role, $expected_status ) {
if ( $role ) {
$user_id = $this->factory->user->create( array( 'role' => $role ) );
wp_set_current_user( $user_id );
} else {
wp_set_current_user( 0 );
}
switch ( $action ) {
case 'create':
$request = new WP_REST_Request( 'POST', '/wp/v2/blocks' );
$request->set_body_params(
array(
'title' => 'Test',
'content' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );
break;
case 'read':
$request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . self::$post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );
break;
case 'update_delete_own':
$post_id = wp_insert_post(
array(
'post_type' => 'wp_block',
'post_status' => 'publish',
'post_title' => 'My cool block',
'post_content' => '<!-- wp:core/paragraph --><p>Hello!</p><!-- /wp:core/paragraph -->',
'post_author' => $user_id,
)
);
$request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . $post_id );
$request->set_body_params(
array(
'title' => 'Test',
'content' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . $post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );
wp_delete_post( $post_id );
break;
case 'update_delete_others':
$request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . self::$post_id );
$request->set_body_params(
array(
'title' => 'Test',
'content' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
)
);
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . self::$post_id );
$response = rest_get_server()->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );
break;
default:
$this->fail( "'$action' is not a valid action." );
}
if ( isset( $user_id ) ) {
self::delete_user( $user_id );
}
}
}

View File

@ -99,6 +99,10 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
'/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)',
'/wp/v2/media',
'/wp/v2/media/(?P<id>[\\d]+)',
'/wp/v2/blocks',
'/wp/v2/blocks/(?P<id>[\d]+)',
'/wp/v2/blocks/(?P<parent>[\d]+)/autosaves',
'/wp/v2/blocks/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)',
'/wp/v2/types',
'/wp/v2/types/(?P<type>[\\w-]+)',
'/wp/v2/statuses',
@ -115,6 +119,12 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
'/wp/v2/comments',
'/wp/v2/comments/(?P<id>[\\d]+)',
'/wp/v2/search',
'/wp/v2/block-renderer/(?P<name>core/block)',
'/wp/v2/block-renderer/(?P<name>core/latest-comments)',
'/wp/v2/block-renderer/(?P<name>core/archives)',
'/wp/v2/block-renderer/(?P<name>core/categories)',
'/wp/v2/block-renderer/(?P<name>core/latest-posts)',
'/wp/v2/block-renderer/(?P<name>core/shortcode)',
'/wp/v2/settings',
'/wp/v2/themes',
);

View File

@ -2271,6 +2271,508 @@ mockedApiResponse.Schema = {
}
]
},
"/wp/v2/blocks": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET"
],
"args": {
"context": {
"required": false,
"default": "view",
"enum": [
"view",
"embed",
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"page": {
"required": false,
"default": 1,
"description": "Current page of the collection.",
"type": "integer"
},
"per_page": {
"required": false,
"default": 10,
"description": "Maximum number of items to be returned in result set.",
"type": "integer"
},
"search": {
"required": false,
"description": "Limit results to those matching a string.",
"type": "string"
},
"after": {
"required": false,
"description": "Limit response to posts published after a given ISO8601 compliant date.",
"type": "string"
},
"before": {
"required": false,
"description": "Limit response to posts published before a given ISO8601 compliant date.",
"type": "string"
},
"exclude": {
"required": false,
"default": [],
"description": "Ensure result set excludes specific IDs.",
"type": "array",
"items": {
"type": "integer"
}
},
"include": {
"required": false,
"default": [],
"description": "Limit result set to specific IDs.",
"type": "array",
"items": {
"type": "integer"
}
},
"offset": {
"required": false,
"description": "Offset the result set by a specific number of items.",
"type": "integer"
},
"order": {
"required": false,
"default": "desc",
"enum": [
"asc",
"desc"
],
"description": "Order sort attribute ascending or descending.",
"type": "string"
},
"orderby": {
"required": false,
"default": "date",
"enum": [
"author",
"date",
"id",
"include",
"modified",
"parent",
"relevance",
"slug",
"include_slugs",
"title"
],
"description": "Sort collection by object attribute.",
"type": "string"
},
"slug": {
"required": false,
"description": "Limit result set to posts with one or more specific slugs.",
"type": "array",
"items": {
"type": "string"
}
},
"status": {
"required": false,
"default": "publish",
"description": "Limit result set to posts assigned one or more statuses.",
"type": "array",
"items": {
"enum": [
"publish",
"future",
"draft",
"pending",
"private",
"trash",
"auto-draft",
"inherit",
"request-pending",
"request-confirmed",
"request-failed",
"request-completed",
"any"
],
"type": "string"
}
}
}
},
{
"methods": [
"POST"
],
"args": {
"date": {
"required": false,
"description": "The date the object was published, in the site's timezone.",
"type": "string"
},
"date_gmt": {
"required": false,
"description": "The date the object was published, as GMT.",
"type": "string"
},
"slug": {
"required": false,
"description": "An alphanumeric identifier for the object unique to its type.",
"type": "string"
},
"status": {
"required": false,
"enum": [
"publish",
"future",
"draft",
"pending",
"private"
],
"description": "A named status for the object.",
"type": "string"
},
"password": {
"required": false,
"description": "A password to protect access to the content and excerpt.",
"type": "string"
},
"title": {
"required": false,
"description": "The title for the object.",
"type": "object"
},
"content": {
"required": false,
"description": "The content for the object.",
"type": "object"
},
"template": {
"required": false,
"description": "The theme file to use to display the object.",
"type": "string"
}
}
}
],
"_links": {
"self": "http://example.org/index.php?rest_route=/wp/v2/blocks"
}
},
"/wp/v2/blocks/(?P<id>[\\d]+)": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE"
],
"endpoints": [
{
"methods": [
"GET"
],
"args": {
"id": {
"required": false,
"description": "Unique identifier for the object.",
"type": "integer"
},
"context": {
"required": false,
"default": "view",
"enum": [
"view",
"embed",
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"password": {
"required": false,
"description": "The password for the post if it is password protected.",
"type": "string"
}
}
},
{
"methods": [
"POST",
"PUT",
"PATCH"
],
"args": {
"id": {
"required": false,
"description": "Unique identifier for the object.",
"type": "integer"
},
"date": {
"required": false,
"description": "The date the object was published, in the site's timezone.",
"type": "string"
},
"date_gmt": {
"required": false,
"description": "The date the object was published, as GMT.",
"type": "string"
},
"slug": {
"required": false,
"description": "An alphanumeric identifier for the object unique to its type.",
"type": "string"
},
"status": {
"required": false,
"enum": [
"publish",
"future",
"draft",
"pending",
"private"
],
"description": "A named status for the object.",
"type": "string"
},
"password": {
"required": false,
"description": "A password to protect access to the content and excerpt.",
"type": "string"
},
"title": {
"required": false,
"description": "The title for the object.",
"type": "object"
},
"content": {
"required": false,
"description": "The content for the object.",
"type": "object"
},
"template": {
"required": false,
"description": "The theme file to use to display the object.",
"type": "string"
}
}
},
{
"methods": [
"DELETE"
],
"args": {
"id": {
"required": false,
"description": "Unique identifier for the object.",
"type": "integer"
},
"force": {
"required": false,
"default": false,
"description": "Whether to bypass trash and force deletion.",
"type": "boolean"
}
}
}
]
},
"/wp/v2/blocks/(?P<parent>[\\d]+)/autosaves": {
"namespace": "wp/v2",
"methods": [
"GET",
"POST"
],
"endpoints": [
{
"methods": [
"GET"
],
"args": {
"parent": {
"required": false,
"description": "The ID for the parent of the object.",
"type": "integer"
},
"context": {
"required": false,
"default": "view",
"enum": [
"view",
"embed",
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
},
"page": {
"required": false,
"default": 1,
"description": "Current page of the collection.",
"type": "integer"
},
"per_page": {
"required": false,
"description": "Maximum number of items to be returned in result set.",
"type": "integer"
},
"search": {
"required": false,
"description": "Limit results to those matching a string.",
"type": "string"
},
"exclude": {
"required": false,
"default": [],
"description": "Ensure result set excludes specific IDs.",
"type": "array",
"items": {
"type": "integer"
}
},
"include": {
"required": false,
"default": [],
"description": "Limit result set to specific IDs.",
"type": "array",
"items": {
"type": "integer"
}
},
"offset": {
"required": false,
"description": "Offset the result set by a specific number of items.",
"type": "integer"
},
"order": {
"required": false,
"default": "desc",
"enum": [
"asc",
"desc"
],
"description": "Order sort attribute ascending or descending.",
"type": "string"
},
"orderby": {
"required": false,
"default": "date",
"enum": [
"date",
"id",
"include",
"relevance",
"slug",
"include_slugs",
"title"
],
"description": "Sort collection by object attribute.",
"type": "string"
}
}
},
{
"methods": [
"POST"
],
"args": {
"parent": {
"required": false,
"description": "The ID for the parent of the object.",
"type": "integer"
},
"author": {
"required": false,
"description": "The ID for the author of the object.",
"type": "integer"
},
"date": {
"required": false,
"description": "The date the object was published, in the site's timezone.",
"type": "string"
},
"date_gmt": {
"required": false,
"description": "The date the object was published, as GMT.",
"type": "string"
},
"id": {
"required": false,
"description": "Unique identifier for the object.",
"type": "integer"
},
"modified": {
"required": false,
"description": "The date the object was last modified, in the site's timezone.",
"type": "string"
},
"modified_gmt": {
"required": false,
"description": "The date the object was last modified, as GMT.",
"type": "string"
},
"slug": {
"required": false,
"description": "An alphanumeric identifier for the object unique to its type.",
"type": "string"
},
"title": {
"required": false,
"description": "The title for the object.",
"type": "object"
},
"content": {
"required": false,
"description": "The content for the object.",
"type": "object"
}
}
}
]
},
"/wp/v2/blocks/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"args": {
"parent": {
"required": false,
"description": "The ID for the parent of the object.",
"type": "integer"
},
"id": {
"required": false,
"description": "The ID for the object.",
"type": "integer"
},
"context": {
"required": false,
"default": "view",
"enum": [
"view",
"embed",
"edit"
],
"description": "Scope under which the request is made; determines fields present in response.",
"type": "string"
}
}
}
]
},
"/wp/v2/types": {
"namespace": "wp/v2",
"methods": [
@ -3865,6 +4367,240 @@ mockedApiResponse.Schema = {
"self": "http://example.org/index.php?rest_route=/wp/v2/search"
}
},
"/wp/v2/block-renderer/(?P<name>core/block)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"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,
"description": "Attributes for core/block block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/latest-comments)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"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,
"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/(?P<name>core/archives)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"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,
"description": "Attributes for core/archives block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/categories)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"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,
"description": "Attributes for core/categories block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/block-renderer/(?P<name>core/latest-posts)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"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,
"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/(?P<name>core/shortcode)": {
"namespace": "wp/v2",
"methods": [
"GET"
],
"endpoints": [
{
"methods": [
"GET"
],
"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,
"description": "Attributes for core/shortcode block",
"type": "object"
},
"post_id": {
"required": false,
"description": "ID of the post context.",
"type": "integer"
}
}
}
]
},
"/wp/v2/settings": {
"namespace": "wp/v2",
"methods": [
@ -4347,16 +5083,16 @@ mockedApiResponse.postRevisions = [
}
},
{
"author": 359,
"author": 375,
"date": "2017-02-14T00:00:00",
"date_gmt": "2017-02-14T00:00:00",
"id": 3153,
"id": 36744,
"modified": "2017-02-14T00:00:00",
"modified_gmt": "2017-02-14T00:00:00",
"parent": 3152,
"slug": "3152-revision-v1",
"parent": 36743,
"slug": "36743-revision-v1",
"guid": {
"rendered": "http://example.org/?p=3153"
"rendered": "http://example.org/?p=36744"
},
"title": {
"rendered": "REST API Client Fixture: Post"
@ -4370,7 +5106,7 @@ mockedApiResponse.postRevisions = [
"_links": {
"parent": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/posts/3152"
"href": "http://example.org/index.php?rest_route=/wp/v2/posts/36743"
}
]
}
@ -4402,16 +5138,16 @@ mockedApiResponse.revision = {
mockedApiResponse.postAutosaves = [
{
"author": 359,
"author": 375,
"date": "2017-02-14T00:00:00",
"date_gmt": "2017-02-14T00:00:00",
"id": 3154,
"id": 36745,
"modified": "2017-02-14T00:00:00",
"modified_gmt": "2017-02-14T00:00:00",
"parent": 3152,
"slug": "3152-autosave-v1",
"parent": 36743,
"slug": "36743-autosave-v1",
"guid": {
"rendered": "http://example.org/?p=3154"
"rendered": "http://example.org/?p=36745"
},
"title": {
"rendered": ""
@ -4425,7 +5161,7 @@ mockedApiResponse.postAutosaves = [
"_links": {
"parent": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/posts/3152"
"href": "http://example.org/index.php?rest_route=/wp/v2/posts/36743"
}
]
}
@ -4433,16 +5169,16 @@ mockedApiResponse.postAutosaves = [
];
mockedApiResponse.autosave = {
"author": 359,
"author": 375,
"date": "2017-02-14T00:00:00",
"date_gmt": "2017-02-14T00:00:00",
"id": 3154,
"id": 36745,
"modified": "2017-02-14T00:00:00",
"modified_gmt": "2017-02-14T00:00:00",
"parent": 3152,
"slug": "3152-autosave-v1",
"parent": 36743,
"slug": "36743-autosave-v1",
"guid": {
"rendered": "http://example.org/?p=3154"
"rendered": "http://example.org/?p=36745"
},
"title": {
"rendered": ""
@ -4607,16 +5343,16 @@ mockedApiResponse.pageRevisions = [
}
},
{
"author": 359,
"author": 375,
"date": "2017-02-14T00:00:00",
"date_gmt": "2017-02-14T00:00:00",
"id": 3156,
"id": 36747,
"modified": "2017-02-14T00:00:00",
"modified_gmt": "2017-02-14T00:00:00",
"parent": 3155,
"slug": "3155-revision-v1",
"parent": 36746,
"slug": "36746-revision-v1",
"guid": {
"rendered": "http://example.org/?p=3156"
"rendered": "http://example.org/?p=36747"
},
"title": {
"rendered": "REST API Client Fixture: Page"
@ -4630,7 +5366,7 @@ mockedApiResponse.pageRevisions = [
"_links": {
"parent": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/pages/3155"
"href": "http://example.org/index.php?rest_route=/wp/v2/pages/36746"
}
]
}
@ -4662,16 +5398,16 @@ mockedApiResponse.pageRevision = {
mockedApiResponse.pageAutosaves = [
{
"author": 359,
"author": 375,
"date": "2017-02-14T00:00:00",
"date_gmt": "2017-02-14T00:00:00",
"id": 3157,
"id": 36748,
"modified": "2017-02-14T00:00:00",
"modified_gmt": "2017-02-14T00:00:00",
"parent": 3155,
"slug": "3155-autosave-v1",
"parent": 36746,
"slug": "36746-autosave-v1",
"guid": {
"rendered": "http://example.org/?p=3157"
"rendered": "http://example.org/?p=36748"
},
"title": {
"rendered": ""
@ -4685,7 +5421,7 @@ mockedApiResponse.pageAutosaves = [
"_links": {
"parent": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/pages/3155"
"href": "http://example.org/index.php?rest_route=/wp/v2/pages/36746"
}
]
}
@ -4693,16 +5429,16 @@ mockedApiResponse.pageAutosaves = [
];
mockedApiResponse.pageAutosave = {
"author": 359,
"author": 375,
"date": "2017-02-14T00:00:00",
"date_gmt": "2017-02-14T00:00:00",
"id": 3157,
"id": 36748,
"modified": "2017-02-14T00:00:00",
"modified_gmt": "2017-02-14T00:00:00",
"parent": 3155,
"slug": "3155-autosave-v1",
"parent": 36746,
"slug": "36746-autosave-v1",
"guid": {
"rendered": "http://example.org/?p=3157"
"rendered": "http://example.org/?p=36748"
},
"title": {
"rendered": ""
@ -4898,6 +5634,33 @@ mockedApiResponse.TypesCollection = {
}
]
}
},
"wp_block": {
"description": "",
"hierarchical": false,
"name": "Blocks",
"slug": "wp_block",
"taxonomies": [],
"rest_base": "blocks",
"_links": {
"collection": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/types"
}
],
"wp:items": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/blocks"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
};