Skip to content

Commit

Permalink
Add capabilities for reusable blocks
Browse files Browse the repository at this point in the history
Adds capabilities for creating, reading, updating and deleting reusable
blocks. The capabilities are mapped like so:

|        | Editors^ | Authors | Contributors | Subscribers* |
| ------ | -------- | ------- | ------------ | ------------ |
| Create | Yes      | Yes     | No           | No           |
| Read   | Yes      | Yes     | Yes          | No           |
| Update | Yes      | Own     | Own          | No           |
| Delete | Yes      | Own     | Own          | No           |

^ Includes administrators.
* Includes visitors that are not logged in.
  • Loading branch information
noisysocks committed Jan 30, 2018
1 parent 477e27e commit f4a8d92
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 34 deletions.
73 changes: 40 additions & 33 deletions lib/class-wp-rest-blocks-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,39 @@
* @see WP_REST_Controller
*/
class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller {
/**
* Checks if a block can be read.
*
* @since 2.1.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 );
}

/**
* 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.
Expand All @@ -25,33 +58,22 @@ class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller {
* @param WP_REST_Request $request Request object.
* @return stdClass|WP_Error Post object or WP_Error.
*/
protected function prepare_item_for_database( $request ) {
$prepared_post = new stdClass;

if ( isset( $request['id'] ) ) {
$existing_post = $this->get_post( $request['id'] );
if ( is_wp_error( $existing_post ) ) {
return $existing_post;
}

$prepared_post->ID = $existing_post->ID;
}
public function prepare_item_for_database( $request ) {
$prepared_post = parent::prepare_item_for_database( $request );

$prepared_post->post_title = $request['title'];
$prepared_post->post_content = $request['content'];
$prepared_post->post_type = $this->post_type;
$prepared_post->post_status = 'publish';
// Force blocks to always be published.
$prepared_post->post_status = 'publish';

return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
return $prepared_post;
}

/**
* Given a post from the database, build the array that is returned from an
* 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.
* @param WP_Post $post Post object that backs the block.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
Expand All @@ -67,21 +89,6 @@ public function prepare_item_for_response( $post, $request ) {
return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
}

/**
* 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 );
}

/**
* Builds the block's schema, conforming to JSON Schema.
*
Expand Down
39 changes: 38 additions & 1 deletion lib/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,11 +401,48 @@ function gutenberg_register_post_types() {
'singular_name' => 'Block',
),
'public' => false,
'capability_type' => 'post',
'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,
) );

foreach ( array( 'administrator', 'editor' ) as $role_name ) {
$editor = get_role( $role_name );
$editor->add_cap( 'edit_blocks' );
$editor->add_cap( 'edit_others_blocks' );
$editor->add_cap( 'publish_blocks' );
$editor->add_cap( 'read_private_blocks' );
$editor->add_cap( 'read_blocks' );
$editor->add_cap( 'delete_blocks' );
$editor->add_cap( 'delete_private_blocks' );
$editor->add_cap( 'delete_published_blocks' );
$editor->add_cap( 'delete_others_blocks' );
$editor->add_cap( 'edit_private_blocks' );
$editor->add_cap( 'edit_published_blocks' );
$editor->add_cap( 'create_blocks' );
}

$author = get_role( 'author' );
$author->add_cap( 'edit_blocks' );
$author->add_cap( 'publish_blocks' );
$author->add_cap( 'read_blocks' );
$author->add_cap( 'delete_blocks' );
$author->add_cap( 'delete_published_blocks' );
$author->add_cap( 'edit_published_blocks' );
$author->add_cap( 'create_blocks' );

$contributor = get_role( 'contributor' );
$contributor->add_cap( 'edit_blocks' );
$contributor->add_cap( 'read_blocks' );
$contributor->add_cap( 'delete_blocks' );
$contributor->add_cap( 'delete_published_blocks' );
$contributor->add_cap( 'edit_published_blocks' );
}
add_action( 'init', 'gutenberg_register_post_types' );

Expand Down
122 changes: 122 additions & 0 deletions phpunit/class-rest-blocks-controller-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,128 @@ public function test_get_item_schema() {
$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', 200 ),

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' => '<!-- wp:core/paragraph --><p>Test</p><!-- /wp:core/paragraph -->',
)
);

$response = $this->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 = $this->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 = $this->server->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );

$request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . $post_id );

$response = $this->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 = $this->server->dispatch( $request );
$this->assertEquals( $expected_status, $response->get_status() );

$request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . self::$post_id );

$response = $this->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().' );
}
Expand Down

0 comments on commit f4a8d92

Please sign in to comment.