diff --git a/gutenberg.php b/gutenberg.php index 307d349f0cf283..d420337390d9ea 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -264,8 +264,15 @@ 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'] ); + + // 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, diff --git a/lib/class-wp-rest-blocks-controller.php b/lib/class-wp-rest-blocks-controller.php index 75f69afe917598..9689820c7494be 100644 --- a/lib/class-wp-rest-blocks-controller.php +++ b/lib/class-wp-rest-blocks-controller.php @@ -33,91 +33,4 @@ public function check_read_permission( $post ) { 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/register.php b/lib/register.php index 1fc63f8c22d6de..8a5c02777cdc66 100644 --- a/lib/register.php +++ b/lib/register.php @@ -457,7 +457,10 @@ function gutenberg_register_post_types() { 'create_posts' => 'create_blocks', ), 'map_meta_cap' => true, - 'supports' => false, + '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 d1cb3834693283..a895d9eae615f8 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 }`, + 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 2ff4081a650a79..dcfe687f744129 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 e3ca3b8b07788e..a0d75bd427c1e7 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 @@ -20,12 +20,15 @@ import { download } from './file'; */ 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 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( { __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/packages/list-reusable-blocks/src/utils/import.js b/packages/list-reusable-blocks/src/utils/import.js index 6bf02048952840..e885cbb6b2b24c 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', } ); diff --git a/phpunit/class-rest-blocks-controller-test.php b/phpunit/class-rest-blocks-controller-test.php index 7b92f200a2afce..a5c58c6f49805a 100644 --- a/phpunit/class-rest-blocks-controller-test.php +++ b/phpunit/class-rest-blocks-controller-test.php @@ -8,7 +8,7 @@ /** * Tests for WP_REST_Blocks_Controller. */ -class REST_Blocks_Controller_Test extends WP_Test_REST_Controller_Testcase { +class REST_Blocks_Controller_Test extends WP_UnitTestCase { /** * Our fake block's post ID. @@ -55,161 +55,6 @@ public static function wpTearDownAfterClass() { 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(). */ @@ -331,11 +176,4 @@ public function test_capabilities( $action, $role, $expected_status ) { 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().' ); - } }