From e6818486ac2c22b769338dc5243e833beff4e81e Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 18 Oct 2018 12:49:10 -0400 Subject: [PATCH 1/7] REST API: Use posts endpoint for reusable blocks --- lib/class-wp-rest-blocks-controller.php | 123 ------- lib/load.php | 3 - lib/register.php | 26 +- .../src/store/effects/reusable-blocks.js | 31 +- .../src/store/effects/test/reusable-blocks.js | 18 +- .../list-reusable-blocks/src/utils/export.js | 13 +- phpunit/class-rest-blocks-controller-test.php | 341 ------------------ 7 files changed, 51 insertions(+), 504 deletions(-) delete mode 100644 lib/class-wp-rest-blocks-controller.php delete mode 100644 phpunit/class-rest-blocks-controller-test.php diff --git a/lib/class-wp-rest-blocks-controller.php b/lib/class-wp-rest-blocks-controller.php deleted file mode 100644 index 75f69afe91759..0000000000000 --- a/lib/class-wp-rest-blocks-controller.php +++ /dev/null @@ -1,123 +0,0 @@ -post_type ); - if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) { - return false; - } - - return parent::check_read_permission( $post ); - } - - /** - * Handle a DELETE request. - * - * @since 1.10.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 delete_item( $request ) { - // Always hard-delete a block. - $request->set_param( 'force', true ); - - return parent::delete_item( $request ); - } - - /** - * Given an update or create request, build the post object that is saved to - * the database. - * - * @since 1.10.0 - * - * @param WP_REST_Request $request Request object. - * @return stdClass|WP_Error Post object or WP_Error. - */ - public function prepare_item_for_database( $request ) { - $prepared_post = parent::prepare_item_for_database( $request ); - - // Force blocks to always be published. - $prepared_post->post_status = 'publish'; - - return $prepared_post; - } - - /** - * Given a block from the database, build the array that is returned from an - * API response. - * - * @since 1.10.0 - * - * @param WP_Post $post Post object that backs the block. - * @param WP_REST_Request $request Request object. - * @return WP_REST_Response Response object. - */ - public function prepare_item_for_response( $post, $request ) { - $data = array( - 'id' => $post->ID, - 'title' => $post->post_title, - 'content' => $post->post_content, - ); - - $response = rest_ensure_response( $data ); - - return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); - } - - /** - * Builds the block's schema, conforming to JSON Schema. - * - * @since 1.10.0 - * - * @return array Item schema data. - */ - public function get_item_schema() { - return array( - '$schema' => 'http://json-schema.org/schema#', - 'title' => $this->post_type, - 'type' => 'object', - 'properties' => array( - 'id' => array( - 'description' => __( 'Unique identifier for the block.', 'gutenberg' ), - 'type' => 'integer', - 'readonly' => true, - ), - 'title' => array( - 'description' => __( 'The block’s title.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - ), - 'content' => array( - 'description' => __( 'The block’s HTML content.', 'gutenberg' ), - 'type' => 'string', - 'required' => true, - ), - ), - ); - } -} diff --git a/lib/load.php b/lib/load.php index 3798ad4c37cae..7a41dc919615b 100644 --- a/lib/load.php +++ b/lib/load.php @@ -12,9 +12,6 @@ // These files only need to be loaded if within a rest server instance // which this class will exist if that is the case. if ( class_exists( 'WP_REST_Controller' ) ) { - if ( ! class_exists( 'WP_REST_Blocks_Controller' ) ) { - require dirname( __FILE__ ) . '/class-wp-rest-blocks-controller.php'; - } if ( ! class_exists( 'WP_REST_Autosaves_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php'; } diff --git a/lib/register.php b/lib/register.php index 1fc63f8c22d6d..e740f7b313f02 100644 --- a/lib/register.php +++ b/lib/register.php @@ -439,25 +439,27 @@ function gutenberg_register_post_types() { register_post_type( 'wp_block', array( - 'labels' => array( + 'labels' => array( 'name' => __( 'Blocks', 'gutenberg' ), 'singular_name' => __( 'Block', 'gutenberg' ), 'search_items' => __( 'Search Blocks', 'gutenberg' ), ), - 'public' => false, - '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( + 'public' => false, + 'show_ui' => true, + 'show_in_menu' => false, + 'rewrite' => false, + 'show_in_rest' => true, + 'rest_base' => 'blocks', + 'capability_type' => 'block', + 'capabilities' => array( 'read' => 'read_blocks', 'create_posts' => 'create_blocks', ), - 'map_meta_cap' => true, - 'supports' => false, + 'map_meta_cap' => true, + 'supports' => array( + 'title', + 'editor', + ), ) ); diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index d1cb383469328..999bdddec0bcd 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -36,6 +36,7 @@ import { getBlocks, getBlocksByClientId, } from '../selectors'; +import { getPostRawValue } from '../reducer'; /** * Module Constants @@ -61,26 +62,25 @@ export const fetchReusableBlocks = async ( action, store ) => { let result; if ( id ) { - result = apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }` } ); + result = apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }?context=edit` } ); } else { - result = apiFetch( { path: `/wp/v2/${ postType.rest_base }?per_page=-1` } ); + result = apiFetch( { path: `/wp/v2/${ postType.rest_base }?per_page=-1&context=edit` } ); } try { const reusableBlockOrBlocks = await result; dispatch( receiveReusableBlocksAction( map( castArray( reusableBlockOrBlocks ), - ( reusableBlock ) => { - const parsedBlocks = parse( reusableBlock.content ); - if ( parsedBlocks.length === 1 ) { - return { - reusableBlock, - parsedBlock: parsedBlocks[ 0 ], - }; - } + ( post ) => { + const parsedBlocks = parse( post.content.raw ); return { - reusableBlock, - parsedBlock: createBlock( 'core/template', {}, parsedBlocks ), + reusableBlock: { + id: post.id, + title: getPostRawValue( post.title ), + }, + parsedBlock: parsedBlocks.length === 1 ? + parsedBlocks[ 0 ] : + createBlock( 'core/template', {}, parsedBlocks ), }; } ) ) ); @@ -119,7 +119,7 @@ export const saveReusableBlocks = async ( action, store ) => { const reusableBlock = getBlock( state, clientId ); const content = serialize( reusableBlock.name === 'core/template' ? reusableBlock.innerBlocks : reusableBlock ); - const data = isTemporary ? { title, content } : { id, title, content }; + const data = isTemporary ? { title, content, status: 'publish' } : { id, title, content, status: 'publish' }; const path = isTemporary ? `/wp/v2/${ postType.rest_base }` : `/wp/v2/${ postType.rest_base }/${ id }`; const method = isTemporary ? 'POST' : 'PUT'; @@ -184,7 +184,10 @@ export const deleteReusableBlocks = async ( action, store ) => { ] ) ); try { - await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }`, method: 'DELETE' } ); + await apiFetch( { + path: `/wp/v2/${ postType.rest_base }/${ id }?force=true`, + method: 'DELETE', + } ); dispatch( { type: 'DELETE_REUSABLE_BLOCK_SUCCESS', id, diff --git a/packages/editor/src/store/effects/test/reusable-blocks.js b/packages/editor/src/store/effects/test/reusable-blocks.js index 2ff4081a650a7..dcfe687f74412 100644 --- a/packages/editor/src/store/effects/test/reusable-blocks.js +++ b/packages/editor/src/store/effects/test/reusable-blocks.js @@ -70,8 +70,12 @@ describe( 'reusable blocks effects', () => { const blockPromise = Promise.resolve( [ { id: 123, - title: 'My cool block', - content: '', + title: { + raw: 'My cool block', + }, + content: { + raw: '', + }, }, ] ); const postTypePromise = Promise.resolve( { @@ -97,7 +101,6 @@ describe( 'reusable blocks effects', () => { reusableBlock: { id: 123, title: 'My cool block', - content: '', }, parsedBlock: expect.objectContaining( { name: 'core/test-block', @@ -115,8 +118,12 @@ describe( 'reusable blocks effects', () => { it( 'should fetch a single reusable block', async () => { const blockPromise = Promise.resolve( { id: 123, - title: 'My cool block', - content: '', + title: { + raw: 'My cool block', + }, + content: { + raw: '', + }, } ); const postTypePromise = Promise.resolve( { slug: 'wp_block', rest_base: 'blocks', @@ -141,7 +148,6 @@ describe( 'reusable blocks effects', () => { reusableBlock: { id: 123, title: 'My cool block', - content: '', }, parsedBlock: expect.objectContaining( { name: 'core/test-block', diff --git a/packages/list-reusable-blocks/src/utils/export.js b/packages/list-reusable-blocks/src/utils/export.js index e3ca3b8b07788..379ed268b0419 100644 --- a/packages/list-reusable-blocks/src/utils/export.js +++ b/packages/list-reusable-blocks/src/utils/export.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { pick, kebabCase } from 'lodash'; +import { kebabCase } from 'lodash'; /** * WordPress dependencies @@ -19,13 +19,16 @@ import { download } from './file'; * @param {number} id */ async function exportReusableBlock( id ) { - const postType = await apiFetch( { path: `/wp/v2/types/wp_block` } ); - const reusableBlock = await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }` } ); + const postType = await apiFetch( { path: `/wp/v2/types/wp_block?context=edit` } ); + const post = await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }` } ); + const title = post.title.raw; + const content = post.content.raw; const fileContent = JSON.stringify( { __file: 'wp_block', - ...pick( reusableBlock, [ 'title', 'content' ] ), + title, + content, }, null, 2 ); - const fileName = kebabCase( reusableBlock.title ) + '.json'; + const fileName = kebabCase( title ) + '.json'; download( fileName, fileContent, 'application/json' ); } diff --git a/phpunit/class-rest-blocks-controller-test.php b/phpunit/class-rest-blocks-controller-test.php deleted file mode 100644 index 7b92f200a2afc..0000000000000 --- a/phpunit/class-rest-blocks-controller-test.php +++ /dev/null @@ -1,341 +0,0 @@ - 'wp_block', - 'post_status' => 'publish', - 'post_title' => 'My cool block', - 'post_content' => '

Hello!

', - ) - ); - - self::$user_id = $factory->user->create( - array( - 'role' => 'editor', - ) - ); - } - - /** - * Delete our fake data after our tests run. - */ - public static function wpTearDownAfterClass() { - wp_delete_post( self::$post_id ); - - self::delete_user( self::$user_id ); - } - - /** - * Check that our routes get set up properly. - */ - public function test_register_routes() { - $routes = rest_get_server()->get_routes(); - - $this->assertArrayHasKey( '/wp/v2/blocks', $routes ); - $this->assertCount( 2, $routes['/wp/v2/blocks'] ); - $this->assertArrayHasKey( '/wp/v2/blocks/(?P[\d]+)', $routes ); - $this->assertCount( 3, $routes['/wp/v2/blocks/(?P[\d]+)'] ); - } - - /** - * Check that we can GET a collection of blocks. - */ - public function test_get_items() { - wp_set_current_user( self::$user_id ); - - $request = new WP_REST_Request( 'GET', '/wp/v2/blocks' ); - $response = rest_get_server()->dispatch( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( - array( - array( - 'id' => self::$post_id, - 'title' => 'My cool block', - 'content' => '

Hello!

', - ), - ), - $response->get_data() - ); - } - - /** - * Check that we can GET a single block. - */ - public function test_get_item() { - wp_set_current_user( self::$user_id ); - - $request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . self::$post_id ); - $response = rest_get_server()->dispatch( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( - array( - 'id' => self::$post_id, - 'title' => 'My cool block', - 'content' => '

Hello!

', - ), - $response->get_data() - ); - } - - /** - * Check that we can POST to create a new block. - */ - public function test_create_item() { - wp_set_current_user( self::$user_id ); - - $request = new WP_REST_Request( 'POST', '/wp/v2/blocks/' . self::$post_id ); - $request->set_body_params( - array( - 'title' => 'New cool block', - 'content' => '

Wow!

', - ) - ); - - $response = rest_get_server()->dispatch( $request ); - - $this->assertEquals( 200, $response->get_status() ); - - $data = $response->get_data(); - - $this->assertArrayHasKey( 'id', $data ); - $this->assertArrayHasKey( 'title', $data ); - $this->assertArrayHasKey( 'content', $data ); - - $this->assertEquals( self::$post_id, $data['id'] ); - $this->assertEquals( 'New cool block', $data['title'] ); - $this->assertEquals( '

Wow!

', $data['content'] ); - } - - /** - * Check that we can PUT to update a block. - */ - public function test_update_item() { - wp_set_current_user( self::$user_id ); - - $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . self::$post_id ); - $request->set_body_params( - array( - 'title' => 'Updated cool block', - 'content' => '

Nice!

', - ) - ); - - $response = rest_get_server()->dispatch( $request ); - - $this->assertEquals( 200, $response->get_status() ); - - $data = $response->get_data(); - - $this->assertArrayHasKey( 'id', $data ); - $this->assertArrayHasKey( 'title', $data ); - $this->assertArrayHasKey( 'content', $data ); - - $this->assertEquals( self::$post_id, $data['id'] ); - $this->assertEquals( 'Updated cool block', $data['title'] ); - $this->assertEquals( '

Nice!

', $data['content'] ); - } - - /** - * Check that we can DELETE a block. - */ - public function test_delete_item() { - wp_set_current_user( self::$user_id ); - - $request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . self::$post_id ); - - $response = rest_get_server()->dispatch( $request ); - - $this->assertEquals( 200, $response->get_status() ); - - $data = $response->get_data(); - - $this->assertArrayHasKey( 'deleted', $data ); - $this->assertArrayHasKey( 'previous', $data ); - - $this->assertTrue( $data['deleted'] ); - - $this->assertArrayHasKey( 'id', $data['previous'] ); - $this->assertArrayHasKey( 'title', $data['previous'] ); - $this->assertArrayHasKey( 'content', $data['previous'] ); - - $this->assertEquals( self::$post_id, $data['previous']['id'] ); - $this->assertEquals( 'My cool block', $data['previous']['title'] ); - $this->assertEquals( '

Hello!

', $data['previous']['content'] ); - } - - /** - * Check that we have defined a JSON schema. - */ - public function test_get_item_schema() { - $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/blocks' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $properties = $data['schema']['properties']; - - $this->assertEquals( 3, count( $properties ) ); - $this->assertArrayHasKey( 'id', $properties ); - $this->assertArrayHasKey( 'title', $properties ); - $this->assertArrayHasKey( 'content', $properties ); - } - - /** - * Test cases for test_capabilities(). - */ - 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. - * - * @dataProvider data_capabilities - */ - 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' => '

Test

', - ) - ); - - $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' => '

Hello!

', - 'post_author' => $user_id, - ) - ); - - $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . $post_id ); - $request->set_body_params( - array( - 'title' => 'Test', - 'content' => '

Test

', - ) - ); - - $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' => '

Test

', - ) - ); - - $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 ); - } - } - - public function test_context_param() { - $this->markTestSkipped( 'Controller doesn\'t implement get_context_param().' ); - } - public function test_prepare_item() { - $this->markTestSkipped( 'Controller doesn\'t implement prepare_item().' ); - } -} From e3b127985e219837bab14ee0ab8a2b2e2c0aaf0b Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 19 Oct 2018 14:32:11 -0400 Subject: [PATCH 2/7] REST: Restore WP_REST_Blocks_Controller for permissions check --- lib/class-wp-rest-blocks-controller.php | 36 ++++ lib/load.php | 3 + lib/register.php | 23 +-- phpunit/class-rest-blocks-controller-test.php | 179 ++++++++++++++++++ 4 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 lib/class-wp-rest-blocks-controller.php create mode 100644 phpunit/class-rest-blocks-controller-test.php diff --git a/lib/class-wp-rest-blocks-controller.php b/lib/class-wp-rest-blocks-controller.php new file mode 100644 index 0000000000000..9689820c7494b --- /dev/null +++ b/lib/class-wp-rest-blocks-controller.php @@ -0,0 +1,36 @@ +post_type ); + if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) { + return false; + } + + return parent::check_read_permission( $post ); + } +} diff --git a/lib/load.php b/lib/load.php index 7a41dc919615b..3798ad4c37cae 100644 --- a/lib/load.php +++ b/lib/load.php @@ -12,6 +12,9 @@ // These files only need to be loaded if within a rest server instance // which this class will exist if that is the case. if ( class_exists( 'WP_REST_Controller' ) ) { + if ( ! class_exists( 'WP_REST_Blocks_Controller' ) ) { + require dirname( __FILE__ ) . '/class-wp-rest-blocks-controller.php'; + } if ( ! class_exists( 'WP_REST_Autosaves_Controller' ) ) { require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php'; } diff --git a/lib/register.php b/lib/register.php index e740f7b313f02..8a5c02777cdc6 100644 --- a/lib/register.php +++ b/lib/register.php @@ -439,24 +439,25 @@ function gutenberg_register_post_types() { register_post_type( 'wp_block', array( - 'labels' => array( + 'labels' => array( 'name' => __( 'Blocks', 'gutenberg' ), 'singular_name' => __( 'Block', 'gutenberg' ), 'search_items' => __( 'Search Blocks', 'gutenberg' ), ), - 'public' => false, - 'show_ui' => true, - 'show_in_menu' => false, - 'rewrite' => false, - 'show_in_rest' => true, - 'rest_base' => 'blocks', - 'capability_type' => 'block', - 'capabilities' => array( + 'public' => false, + '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( 'read' => 'read_blocks', 'create_posts' => 'create_blocks', ), - 'map_meta_cap' => true, - 'supports' => array( + 'map_meta_cap' => true, + 'supports' => array( 'title', 'editor', ), diff --git a/phpunit/class-rest-blocks-controller-test.php b/phpunit/class-rest-blocks-controller-test.php new file mode 100644 index 0000000000000..a5c58c6f49805 --- /dev/null +++ b/phpunit/class-rest-blocks-controller-test.php @@ -0,0 +1,179 @@ + 'wp_block', + 'post_status' => 'publish', + 'post_title' => 'My cool block', + 'post_content' => '

Hello!

', + ) + ); + + self::$user_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + } + + /** + * Delete our fake data after our tests run. + */ + public static function wpTearDownAfterClass() { + wp_delete_post( self::$post_id ); + + self::delete_user( self::$user_id ); + } + + /** + * Test cases for test_capabilities(). + */ + 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. + * + * @dataProvider data_capabilities + */ + 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' => '

Test

', + ) + ); + + $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' => '

Hello!

', + 'post_author' => $user_id, + ) + ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . $post_id ); + $request->set_body_params( + array( + 'title' => 'Test', + 'content' => '

Test

', + ) + ); + + $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' => '

Test

', + ) + ); + + $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 ); + } + } +} From c8ad73aa51b2fe22ed8056301477d7b828b99b98 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 19 Oct 2018 14:32:56 -0400 Subject: [PATCH 3/7] Reusable Blocks: Enable post listing edit --- gutenberg.php | 1 - 1 file changed, 1 deletion(-) diff --git a/gutenberg.php b/gutenberg.php index 307d349f0cf28..22e3ec6dfff24 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -264,7 +264,6 @@ function gutenberg_add_edit_link( $actions, $post ) { $title = _draft_or_post_title( $post->ID ); if ( 'wp_block' === $post->post_type ) { - unset( $actions['edit'] ); unset( $actions['inline hide-if-no-js'] ); $actions['export'] = sprintf( '', From 9c1da9e09db6bcf6b2993b43f6a99e73ba2bdaab Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 19 Oct 2018 14:34:06 -0400 Subject: [PATCH 4/7] Reusable Blocks: Trash on delete action --- packages/editor/src/store/effects/reusable-blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index 999bdddec0bcd..a895d9eae615f 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -185,7 +185,7 @@ export const deleteReusableBlocks = async ( action, store ) => { try { await apiFetch( { - path: `/wp/v2/${ postType.rest_base }/${ id }?force=true`, + path: `/wp/v2/${ postType.rest_base }/${ id }`, method: 'DELETE', } ); dispatch( { From 0235f5d03709effb0f1a136c69f2f2c20dd224b8 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 19 Oct 2018 14:39:26 -0400 Subject: [PATCH 5/7] List Reusable Blocks: Provide context to post request --- packages/list-reusable-blocks/src/utils/export.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/list-reusable-blocks/src/utils/export.js b/packages/list-reusable-blocks/src/utils/export.js index 379ed268b0419..a0d75bd427c1e 100644 --- a/packages/list-reusable-blocks/src/utils/export.js +++ b/packages/list-reusable-blocks/src/utils/export.js @@ -19,8 +19,8 @@ import { download } from './file'; * @param {number} id */ async function exportReusableBlock( id ) { - const postType = await apiFetch( { path: `/wp/v2/types/wp_block?context=edit` } ); - const post = await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }` } ); + const postType = await apiFetch( { path: `/wp/v2/types/wp_block` } ); + const post = await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }?context=edit` } ); const title = post.title.raw; const content = post.content.raw; const fileContent = JSON.stringify( { From ac75c14edcd96e01f3ff41f67fddac3f74b9ca45 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 19 Oct 2018 14:47:47 -0400 Subject: [PATCH 6/7] Reusable Blocks: Verify edit capability on export action --- gutenberg.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gutenberg.php b/gutenberg.php index 22e3ec6dfff24..d420337390d9e 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -265,6 +265,14 @@ function gutenberg_add_edit_link( $actions, $post ) { if ( 'wp_block' === $post->post_type ) { unset( $actions['inline hide-if-no-js'] ); + + // Export uses block raw content, which is only returned from the post + // REST endpoint via `context=edit`, requiring edit capability. + $post_type = get_post_type_object( $post->post_type ); + if ( ! current_user_can( $post_type->cap->edit_post, $post->ID ) ) { + return $actions; + } + $actions['export'] = sprintf( '', $post->ID, From 921f66912be7b630e4859e7edd1d7d227351084c Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 19 Oct 2018 15:00:17 -0400 Subject: [PATCH 7/7] List Reusable Blocks: Import as published --- packages/list-reusable-blocks/src/utils/import.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/list-reusable-blocks/src/utils/import.js b/packages/list-reusable-blocks/src/utils/import.js index 6bf0204895284..e885cbb6b2b24 100644 --- a/packages/list-reusable-blocks/src/utils/import.js +++ b/packages/list-reusable-blocks/src/utils/import.js @@ -42,6 +42,7 @@ async function importReusableBlock( file ) { data: { title: parsedContent.title, content: parsedContent.content, + status: 'publish', }, method: 'POST', } );