Skip to content

Commit

Permalink
Reusable blocks: Use view context, always include title.raw and conte…
Browse files Browse the repository at this point in the history
…nt.raw (WordPress#12084)

* Reusable blocks: Use view context

Existing reusable blocks on a post would not render if the user was an
author or a contributor. This happened because requests to fetch a
single block or post are blocked when ?context=edit is passed and the
current user is not an editor.

The fix is to stop passing `?context=edit` to block requests, but this
causes another problem which is that Gutenberg needs access to both
`title.raw` and `content.raw` in the API response.

We therefore both stop passing `?context=edit` to block requests *and*
modify the blocks API so that `title.raw` and `content.raw` are always
provided.

Lastly, we modify the blocks API to always omit `content.rendered`, as
it doesn't make sense for a reusable block to have rendered content on
its own, since rendering a block requires it to be inside a post or a
page.

* Delete fake user after each test case, not after all test cases

* Typo: /** → /*

Co-Authored-By: noisysocks <[email protected]>

* Create all fake data before any test runs

* Omit `title.rendered` from the blocks REST API

The rendered title isn't used, so we may as well remove it so that
`content` and `title` are aligned.

* Fix fetching when using reusable blocks as a contributor

* Fix reusable block effect tests

* Fix reusable block E2E tests
  • Loading branch information
noisysocks authored and gziolo committed Nov 20, 2018
1 parent 2945a62 commit b462725
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 33 deletions.
53 changes: 53 additions & 0 deletions lib/class-wp-rest-blocks-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,57 @@ public function check_read_permission( $post ) {

return parent::check_read_permission( $post );
}

/**
* Filters a response based on the context defined in the schema.
*
* @since 4.4.0
*
* @param array $data Response data to fiter.
* @param string $context Context defined in the schema.
* @return array Filtered response.
*/
public function filter_response_by_context( $data, $context ) {
$data = parent::filter_response_by_context( $data, $context );

/*
* Remove `title.rendered` and `content.rendered` from the response. It
* doesn't make sense for a reusable block to have rendered content on its
* own, since rendering a block requires it to be inside a post or a page.
*/
unset( $data['title']['rendered'] );
unset( $data['content']['rendered'] );

return $data;
}

/**
* Retrieves the block's schema, conforming to JSON Schema.
*
* @since 4.4.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
$schema = parent::get_item_schema();

/*
* Allow all contexts to access `title.raw` and `content.raw`. Clients always
* need the raw markup of a reusable block to do anything useful, e.g. parse
* it or display it in an editor.
*/
$schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' );
$schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' );

/*
* Remove `title.rendered` and `content.rendered` from the schema. It doesn’t
* make sense for a reusable block to have rendered content on its own, since
* rendering a block requires it to be inside a post or a page.
*/
unset( $schema['properties']['title']['properties']['rendered'] );
unset( $schema['properties']['content']['properties']['rendered'] );

return $schema;
}

}
2 changes: 1 addition & 1 deletion packages/block-library/src/block/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const settings = {

category: 'reusable',

description: __( 'Create content, and save it to reuse across your site. Update the block, and the changes apply everywhere it’s used.' ),
description: __( 'Create content, and save it for you and other contributors to reuse across your site. Update the block, and the changes apply everywhere it’s used.' ),

attributes: {
ref: {
Expand Down
11 changes: 5 additions & 6 deletions packages/editor/src/store/effects/reusable-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { dispatch as dataDispatch } from '@wordpress/data';
/**
* Internal dependencies
*/
import { resolveSelector } from './utils';
import {
__experimentalReceiveReusableBlocks as receiveReusableBlocksAction,
removeBlocks,
Expand Down Expand Up @@ -57,16 +56,16 @@ export const fetchReusableBlocks = async ( action, store ) => {

// TODO: these are potentially undefined, this fix is in place
// until there is a filter to not use reusable blocks if undefined
const postType = await resolveSelector( 'core', 'getPostType', 'wp_block' );
const postType = await apiFetch( { path: '/wp/v2/types/wp_block' } );
if ( ! postType ) {
return;
}

let result;
if ( id ) {
result = apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }?context=edit` } );
result = apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }` } );
} else {
result = apiFetch( { path: `/wp/v2/${ postType.rest_base }?per_page=-1&context=edit` } );
result = apiFetch( { path: `/wp/v2/${ postType.rest_base }?per_page=-1` } );
}

try {
Expand Down Expand Up @@ -109,7 +108,7 @@ export const fetchReusableBlocks = async ( action, store ) => {
export const saveReusableBlocks = async ( action, store ) => {
// TODO: these are potentially undefined, this fix is in place
// until there is a filter to not use reusable blocks if undefined
const postType = await resolveSelector( 'core', 'getPostType', 'wp_block' );
const postType = await apiFetch( { path: '/wp/v2/types/wp_block' } );
if ( ! postType ) {
return;
}
Expand Down Expand Up @@ -153,7 +152,7 @@ export const saveReusableBlocks = async ( action, store ) => {
export const deleteReusableBlocks = async ( action, store ) => {
// TODO: these are potentially undefined, this fix is in place
// until there is a filter to not use reusable blocks if undefined
const postType = await resolveSelector( 'core', 'getPostType', 'wp_block' );
const postType = await apiFetch( { path: '/wp/v2/types/wp_block' } );
if ( ! postType ) {
return;
}
Expand Down
14 changes: 7 additions & 7 deletions packages/editor/src/store/effects/test/reusable-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down Expand Up @@ -130,7 +130,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down Expand Up @@ -172,7 +172,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down Expand Up @@ -202,7 +202,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down Expand Up @@ -236,7 +236,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down Expand Up @@ -284,7 +284,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down Expand Up @@ -330,7 +330,7 @@ describe( 'reusable blocks effects', () => {
} );

apiFetch.mockImplementation( ( options ) => {
if ( options.path === '/wp/v2/types/wp_block?context=edit' ) {
if ( options.path === '/wp/v2/types/wp_block' ) {
return postTypePromise;
}

Expand Down
59 changes: 42 additions & 17 deletions phpunit/class-rest-blocks-controller-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class REST_Blocks_Controller_Test extends WP_UnitTestCase {
protected static $post_id;

/**
* Our fake user's ID.
* Our fake user IDs, keyed by their role.
*
* @var int
* @var array
*/
protected static $user_id;
protected static $user_ids;

/**
* Create fake data before our tests run.
Expand All @@ -35,14 +35,14 @@ public static function wpSetUpBeforeClass( $factory ) {
'post_type' => 'wp_block',
'post_status' => 'publish',
'post_title' => 'My cool block',
'post_content' => '<!-- wp:core/paragraph --><p>Hello!</p><!-- /wp:core/paragraph -->',
'post_content' => '<!-- wp:paragraph --><p>Hello!</p><!-- /wp:paragraph -->',
)
);

self::$user_id = $factory->user->create(
array(
'role' => 'editor',
)
self::$user_ids = array(
'editor' => $factory->user->create( array( 'role' => 'editor' ) ),
'author' => $factory->user->create( array( 'role' => 'author' ) ),
'contributor' => $factory->user->create( array( 'role' => 'contributor' ) ),
);
}

Expand All @@ -52,7 +52,9 @@ public static function wpSetUpBeforeClass( $factory ) {
public static function wpTearDownAfterClass() {
wp_delete_post( self::$post_id );

self::delete_user( self::$user_id );
foreach ( self::$user_ids as $user_id ) {
self::delete_user( $user_id );
}
}

/**
Expand Down Expand Up @@ -89,7 +91,7 @@ public function data_capabilities() {
*/
public function test_capabilities( $action, $role, $expected_status ) {
if ( $role ) {
$user_id = $this->factory->user->create( array( 'role' => $role ) );
$user_id = self::$user_ids[ $role ];
wp_set_current_user( $user_id );
} else {
wp_set_current_user( 0 );
Expand All @@ -101,7 +103,7 @@ public function test_capabilities( $action, $role, $expected_status ) {
$request->set_body_params(
array(
'title' => 'Test',
'content' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
'content' => '<!-- wp:paragraph --><p>Test</p><!-- /wp:paragraph -->',
)
);

Expand All @@ -124,7 +126,7 @@ public function test_capabilities( $action, $role, $expected_status ) {
'post_type' => 'wp_block',
'post_status' => 'publish',
'post_title' => 'My cool block',
'post_content' => '<!-- wp:core/paragraph --><p>Hello!</p><!-- /wp:core/paragraph -->',
'post_content' => '<!-- wp:paragraph --><p>Hello!</p><!-- /wp:paragraph -->',
'post_author' => $user_id,
)
);
Expand All @@ -133,7 +135,7 @@ public function test_capabilities( $action, $role, $expected_status ) {
$request->set_body_params(
array(
'title' => 'Test',
'content' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
'content' => '<!-- wp:paragraph --><p>Test</p><!-- /wp:paragraph -->',
)
);

Expand All @@ -154,7 +156,7 @@ public function test_capabilities( $action, $role, $expected_status ) {
$request->set_body_params(
array(
'title' => 'Test',
'content' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
'content' => '<!-- wp:paragraph --><p>Test</p><!-- /wp:paragraph -->',
)
);

Expand All @@ -171,9 +173,32 @@ public function test_capabilities( $action, $role, $expected_status ) {
default:
$this->fail( "'$action' is not a valid action." );
}
}

if ( isset( $user_id ) ) {
self::delete_user( $user_id );
}
/**
* Check that the raw title and content of a block can be accessed when there
* is no set schema, and that the rendered content of a block is not included
* in the response.
*/
public function test_content() {
wp_set_current_user( self::$user_ids['author'] );

$request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . self::$post_id );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();

$this->assertEquals(
array(
'raw' => 'My cool block',
),
$data['title']
);
$this->assertEquals(
array(
'raw' => '<!-- wp:paragraph --><p>Hello!</p><!-- /wp:paragraph -->',
'protected' => false,
),
$data['content']
);
}
}
9 changes: 7 additions & 2 deletions test/e2e/specs/reusable-blocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,13 @@ describe( 'Reusable Blocks', () => {

// Delete the block and accept the confirmation dialog
await page.click( 'button[aria-label="More options"]' );
const convertButton = await page.waitForXPath( '//button[text()="Remove from Reusable Blocks"]' );
await Promise.all( [ waitForAndAcceptDialog(), convertButton.click() ] );
const deleteButton = await page.waitForXPath( '//button[text()="Remove from Reusable Blocks"]' );
await Promise.all( [ waitForAndAcceptDialog(), deleteButton.click() ] );

// Wait for deletion to finish
await page.waitForXPath(
'//*[contains(@class, "components-notice") and contains(@class, "is-success")]/*[text()="Block deleted."]'
);

// Check that we have an empty post again
expect( await getEditedPostContent() ).toBe( '' );
Expand Down

0 comments on commit b462725

Please sign in to comment.