Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add block types REST API. #21065

Merged
merged 37 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4c0c2dc
Add block types REST API.
spacedmonkey Mar 22, 2020
32b995d
Remove space
spacedmonkey May 7, 2020
4ec1eea
Change comment.
spacedmonkey May 7, 2020
81ae460
Change comment.
spacedmonkey May 7, 2020
1be9aaa
Change title.
spacedmonkey May 7, 2020
371ce8d
Change comment.
spacedmonkey May 8, 2020
9afefda
Remove array_shift.
spacedmonkey May 8, 2020
348ff88
Change comment to 5.5.0
spacedmonkey May 8, 2020
df16a9f
Fixes.
spacedmonkey May 8, 2020
98b5e99
Formatting.
spacedmonkey May 8, 2020
a86db36
Merge branch 'master' into feautre/block-api
spacedmonkey May 9, 2020
c83d05b
More tweaks
spacedmonkey May 9, 2020
730acc5
Fix lints
spacedmonkey May 9, 2020
3905157
Tweak fields.
spacedmonkey May 13, 2020
8f32a72
Feedback.
spacedmonkey May 14, 2020
3479ffc
Add block category.
spacedmonkey May 20, 2020
acf6cbe
Feedback
spacedmonkey May 21, 2020
bfc2e07
Add back composer.lock.
spacedmonkey May 21, 2020
0e89f71
Merge branch 'master' into feautre/block-api
spacedmonkey May 21, 2020
ce8ea79
Tweak descriptions.
spacedmonkey May 21, 2020
e75b647
Add supports.
spacedmonkey May 21, 2020
6c2b6a6
Add unit tests.
spacedmonkey May 21, 2020
9eea8a3
More tweaks
spacedmonkey May 21, 2020
f11b18d
Fix tests
spacedmonkey May 22, 2020
a45c48c
Tweak test.
spacedmonkey May 22, 2020
dbcca03
Reuse fake block
spacedmonkey May 22, 2020
0ea4d71
Add in new fields and improve tests.
spacedmonkey May 22, 2020
50efa40
Fix lint
spacedmonkey May 22, 2020
f9878fd
Reuse wp_error.
spacedmonkey May 22, 2020
b9675fd
Add more tests
spacedmonkey May 22, 2020
62f604e
Improve asserts.
spacedmonkey May 24, 2020
6db18c8
Refactor permision check
spacedmonkey May 25, 2020
fcb6a1d
Add more tests.
spacedmonkey May 25, 2020
296efe6
Remove unused method.
spacedmonkey May 25, 2020
b98d04c
Fix tests.
spacedmonkey May 25, 2020
bfd9650
Use correct name.
spacedmonkey May 25, 2020
2744ebf
Improve tests.
spacedmonkey May 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
326 changes: 326 additions & 0 deletions lib/class-wp-rest-block-types-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
<?php
/**
* REST API: WP_REST_Block_Types_Controller class
*
* @since 5.4.0
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
* @subpackage REST_API
* @package WordPress
*/

/**
* Core class used to access block types via the REST API.
*
* @see WP_REST_Controller
*/
class WP_REST_Block_Types_Controller extends WP_REST_Controller {

/**
* Constructor.
*/
public function __construct() {
$this->namespace = '__experimental';
$this->rest_base = 'block-types';
}

/**
* Registers the routes for the objects of the controller.
*
* @see register_rest_route()
*/
public function register_routes() {

register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);

spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<namespace>[a-zA-Z0-9_-]+)/(?P<name>[a-zA-Z0-9_-]+)',
array(
'args' => array(
'name' => array(
'description' => __( 'Block name', 'gutenberg' ),
gziolo marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'string',
),
'namespace' => array(
'description' => __( 'Block namsspace', 'gutenberg' ),
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'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' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}

/**
* Checks whether a given request has permission to read post block types.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
aduth marked this conversation as resolved.
Show resolved Hide resolved
if ( ! current_user_can( 'edit_posts' ) ) {
aduth marked this conversation as resolved.
Show resolved Hide resolved
return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage block types.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
}

return true;
}

/**
* Retrieves all post block types, depending on user context.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
$data = array();
$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();

// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
$namespace = '';
if ( isset( $registered['namespace'] ) && ! empty( $request['namespace'] ) ) {
$namespace = $request['namespace'];
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
}

foreach ( $block_types as $slug => $obj ) {
$ret = $this->check_read_permission();
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved

if ( ! $ret ) {
continue;
}

$block_type = $this->prepare_item_for_response( $obj, $request );

if ( $namespace ) {
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
$pieces = explode( '/', $obj->name );
$block_namespace = array_shift( $pieces );
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
if ( $namespace !== $block_namespace ) {
continue;
}
}

$data[ $obj->name ] = $this->prepare_response_for_collection( $block_type );
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
}

return rest_ensure_response( $data );
}

/**
* Checks if a given request has access to read a block type.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
$block_name = $request['namespace'] . '/' . $request['name'];
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );

if ( empty( $block_type ) ) {
return new WP_Error( 'rest_block_type_invalid', __( 'Invalid block type.', 'gutenberg' ), array( 'status' => 404 ) );
}
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved

$check = $this->check_read_permission();

if ( ! $check ) {
return new WP_Error( 'rest_cannot_read_block_type', __( 'Cannot view block type.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
}

return true;
}

/**
* Checks whether a given block type should be visible.
*
* @return WP_Error|bool True if the block type is visible, otherwise false.
*/
protected function check_read_permission() {
if ( ! current_user_can( 'edit_posts' ) ) {
return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage block types.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
}

return true;
}

/**
* Retrieves a specific block type.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
$block_name = $request['namespace'] . '/' . $request['name'];
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );

if ( empty( $block_type ) ) {
return new WP_Error( 'rest_block_type_invalid', __( 'Invalid block type.', 'gutenberg' ), array( 'status' => 404 ) );
}

$data = $this->prepare_item_for_response( $block_type, $request );

return rest_ensure_response( $data );
TimothyBJacobs marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Prepares a block type object for serialization.
*
* @param stdClass $block_type block type data.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we able to assume it's of type WP_Block_Type ?

Suggested change
* @param stdClass $block_type block type data.
* @param WP_Block_Type $block_type block type data.

https://developer.wordpress.org/reference/classes/wp_block_type/

* @param WP_REST_Request $request Full details about the request.
*
* @return WP_REST_Response block type data.
*/
public function prepare_item_for_response( $block_type, $request ) {

$fields = $this->get_fields_for_response( $request );
$data = array();
if ( in_array( 'name', $fields, true ) ) {
$data['name'] = $block_type->name;
}

if ( in_array( 'attributes', $fields, true ) ) {
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
$data['attributes'] = $block_type->get_attributes();
}

if ( in_array( 'is_dynamic', $fields, true ) ) {
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
$data['is_dynamic'] = $block_type->is_dynamic();
}

$extra_fields = array( 'editor_script', 'script', 'editor_style', 'style' );
foreach ( $extra_fields as $extra_field ) {
if ( in_array( $extra_field, $fields, true ) ) {
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
$data[ $extra_field ] = (string) $block_type->$extra_field;
}
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );

$response = rest_ensure_response( $data );

$response->add_links(
array(
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
'https://api.w.org/items' => array(
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $block_type->name ) ),
),
)
);

/**
* Filters a block type returned from the REST API.
*
* Allows modification of the block type data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $block_type The original block type object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_block_type', $response, $block_type, $request );
}

/**
* Retrieves the block type' schema, conforming to JSON Schema.
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}

$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'block_type',
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'The title for the block type.', 'gutenberg' ),
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'attributes' => array(
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'description' => __( 'Block attributes.', 'gutenberg' ),
'type' => 'object',
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'is_dynamic' => array(
'description' => __( 'Is the block dynamically rendered.', 'gutenberg' ),
'type' => 'boolean',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'editor_script' => array(
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'description' => __( 'URL of editor script js file.', 'gutenberg' ),
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'script' => array(
'description' => __( 'URL of script js file.', 'gutenberg' ),
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'editor_style' => array(
'description' => __( 'URL of editor style css file.', 'gutenberg' ),
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'style' => array(
'description' => __( 'URL of style css file.', 'gutenberg' ),
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
),
);

$this->schema = $schema;

return $this->add_additional_fields_schema( $this->schema );
}

/**
* Retrieves the query params for collections.
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$new_params = array();
$new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
$new_params['namespace'] = array(
'description' => __( 'Block namespace.', 'gutenberg' ),
'type' => 'string',
);
return $new_params;
}

}
3 changes: 3 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( ! class_exists( 'WP_REST_Block_Directory_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-block-directory-controller.php';
}
if ( ! class_exists( 'WP_REST_Block_Types_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-block-types-controller.php';
}
if ( ! class_exists( 'WP_REST_Menus_Controller' ) ) {
require_once dirname( __FILE__ ) . '/class-wp-rest-menus-controller.php';
}
Expand Down
8 changes: 8 additions & 0 deletions lib/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ function gutenberg_register_rest_block_directory() {
}
add_filter( 'rest_api_init', 'gutenberg_register_rest_block_directory' );

/**
* Registers the Block types REST API routes.
*/
function gutenberg_register_block_type() {
$block_types = new WP_REST_Block_Types_Controller();
$block_types->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_block_type' );
/**
* Registers the menu locations area REST API routes.
*/
Expand Down