REST API: Make the templates controller follow core REST patterns.

The templates controller now respects the `_fields` parameter and filters the response accordingly. The schema has been updated to include all the fields returned. The `content.block_version` field has been added. The controller now returns WP_Error objects for improved error handling.

Add new unit tests.

Props TimothyBlynJacobs, hellofromtonya, zieladam.
Fixes #54422.

Built from https://develop.svn.wordpress.org/trunk@52186


git-svn-id: http://core.svn.wordpress.org/trunk@51778 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
spacedmonkey 2021-11-16 18:06:00 +00:00
parent 4a18ff86c9
commit 33499dc6d8
2 changed files with 188 additions and 55 deletions

View File

@ -70,15 +70,18 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'The id of a template' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'id' => array(
'description' => __( 'The id of a template' ),
'type' => 'string',
),
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
@ -231,7 +234,12 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
wp_delete_post( $template->wp_id, true );
return $this->prepare_item_for_response( get_block_file_template( $request['id'], $this->post_type ), $request );
$request->set_param( 'context', 'edit' );
$template = get_block_template( $request['id'], $this->post_type );
$response = $this->prepare_item_for_response( $template, $request );
return rest_ensure_response( $response );
}
$changes = $this->prepare_item_for_database( $request );
@ -241,7 +249,13 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
} else {
$result = wp_insert_post( wp_slash( (array) $changes ), true );
}
if ( is_wp_error( $result ) ) {
if ( 'db_update_error' === $result->get_error_code() ) {
$result->add_data( array( 'status' => 500 ) );
} else {
$result->add_data( array( 'status' => 400 ) );
}
return $result;
}
@ -251,10 +265,11 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
return $fields_update;
}
return $this->prepare_item_for_response(
get_block_template( $request['id'], $this->post_type ),
$request
);
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $template, $request );
return rest_ensure_response( $response );
}
/**
@ -278,15 +293,21 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
$changes = $this->prepare_item_for_database( $request );
$changes->post_name = $request['slug'];
$result = wp_insert_post( wp_slash( (array) $changes ), true );
if ( is_wp_error( $result ) ) {
return $result;
$prepared_post = $this->prepare_item_for_database( $request );
$prepared_post->post_name = $request['slug'];
$post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true );
if ( is_wp_error( $post_id ) ) {
if ( 'db_insert_error' === $post_id->get_error_code() ) {
$post_id->add_data( array( 'status' => 500 ) );
} else {
$post_id->add_data( array( 'status' => 400 ) );
}
return $post_id;
}
$posts = get_block_templates( array( 'wp_id' => $result ), $this->post_type );
$posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type );
if ( ! count( $posts ) ) {
return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ) );
return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) );
}
$id = $posts[0]->id;
$template = get_block_template( $id, $this->post_type );
@ -295,10 +316,13 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
return $fields_update;
}
return $this->prepare_item_for_response(
get_block_template( $id, $this->post_type ),
$request
);
$response = $this->prepare_item_for_response( $template, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) );
return $response;
}
/**
@ -333,10 +357,12 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
$id = $template->wp_id;
$force = (bool) $request['force'];
$request->set_param( 'context', 'edit' );
// If we're forcing, then delete permanently.
if ( $force ) {
$previous = $this->prepare_item_for_response( $template, $request );
wp_delete_post( $id, true );
$result = wp_delete_post( $id, true );
$response = new WP_REST_Response();
$response->set_data(
array(
@ -344,22 +370,32 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
'previous' => $previous->get_data(),
)
);
} else {
// Otherwise, only trash if we haven't already.
if ( 'trash' === $template->status ) {
return new WP_Error(
'rest_template_already_trashed',
__( 'The template has already been deleted.' ),
array( 'status' => 410 )
);
}
return $response;
// (Note that internally this falls through to `wp_delete_post()`
// if the Trash is disabled.)
$result = wp_trash_post( $id );
$template->status = 'trash';
$response = $this->prepare_item_for_response( $template, $request );
}
// Otherwise, only trash if we haven't already.
if ( 'trash' === $template->status ) {
if ( ! $result ) {
return new WP_Error(
'rest_template_already_trashed',
__( 'The template has already been deleted.' ),
array( 'status' => 410 )
'rest_cannot_delete',
__( 'The template cannot be deleted.' ),
array( 'status' => 500 )
);
}
wp_trash_post( $id );
$template->status = 'trash';
return $this->prepare_item_for_response( $template, $request );
return $response;
}
/**
@ -392,12 +428,20 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
$changes->post_status = 'publish';
}
if ( isset( $request['content'] ) ) {
$changes->post_content = $request['content'];
if ( is_string( $request['content'] ) ) {
$changes->post_content = $request['content'];
} elseif ( isset( $request['content']['raw'] ) ) {
$changes->post_content = $request['content']['raw'];
}
} elseif ( null !== $template && 'custom' !== $template->source ) {
$changes->post_content = $template->content;
}
if ( isset( $request['title'] ) ) {
$changes->post_title = $request['title'];
if ( is_string( $request['title'] ) ) {
$changes->post_title = $request['title'];
} elseif ( ! empty( $request['title']['raw'] ) ) {
$changes->post_title = $request['title']['raw'];
}
} elseif ( null !== $template && 'custom' !== $template->source ) {
$changes->post_title = $template->title;
}
@ -433,31 +477,88 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
// Restores the more descriptive, specific name for use within this method.
$template = $item;
$result = array(
'id' => $template->id,
'theme' => $template->theme,
'content' => array( 'raw' => $template->content ),
'slug' => $template->slug,
'source' => $template->source,
'type' => $template->type,
'description' => $template->description,
'title' => array(
'raw' => $template->title,
'rendered' => $template->title,
),
'status' => $template->status,
'wp_id' => $template->wp_id,
'has_theme_file' => $template->has_theme_file,
);
if ( 'wp_template_part' === $template->type ) {
$result['area'] = $template->area;
$fields = $this->get_fields_for_response( $request );
// Base fields for every template.
$data = array();
if ( rest_is_field_included( 'id', $fields ) ) {
$data['id'] = $template->id;
}
$result = $this->add_additional_fields_to_object( $result, $request );
if ( rest_is_field_included( 'theme', $fields ) ) {
$data['theme'] = $template->theme;
}
$response = rest_ensure_response( $result );
$links = $this->prepare_links( $template->id );
if ( rest_is_field_included( 'content', $fields ) ) {
$data['content'] = array();
}
if ( rest_is_field_included( 'content.raw', $fields ) ) {
$data['content']['raw'] = $template->content;
}
if ( rest_is_field_included( 'content.block_version', $fields ) ) {
$data['content']['block_version'] = block_version( $template->content );
}
if ( rest_is_field_included( 'slug', $fields ) ) {
$data['slug'] = $template->slug;
}
if ( rest_is_field_included( 'source', $fields ) ) {
$data['source'] = $template->source;
}
if ( rest_is_field_included( 'type', $fields ) ) {
$data['type'] = $template->type;
}
if ( rest_is_field_included( 'description', $fields ) ) {
$data['description'] = $template->description;
}
if ( rest_is_field_included( 'title', $fields ) ) {
$data['title'] = array();
}
if ( rest_is_field_included( 'title.raw', $fields ) ) {
$data['title']['raw'] = $template->title;
}
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
if ( $template->wp_id ) {
/** This filter is documented in wp-includes/post-template.php */
$data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id );
} else {
$data['title']['rendered'] = $template->title;
}
}
if ( rest_is_field_included( 'status', $fields ) ) {
$data['status'] = $template->status;
}
if ( rest_is_field_included( 'wp_id', $fields ) ) {
$data['wp_id'] = (int) $template->wp_id;
}
if ( rest_is_field_included( 'has_theme_file', $fields ) ) {
$data['has_theme_file'] = (bool) $template->has_theme_file;
}
if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
$data['area'] = $template->area;
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$links = $this->prepare_links( $template->id );
$response->add_links( $links );
if ( ! empty( $links['self']['href'] ) ) {
$actions = $this->get_available_actions();
@ -530,7 +631,7 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param(),
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
'wp_id' => array(
'description' => __( 'Limit to the specified post id.' ),
'type' => 'integer',
@ -583,6 +684,11 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
),
'type' => array(
'description' => __( 'Type of template.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
),
'source' => array(
'description' => __( 'Source of template' ),
'type' => 'string',
@ -594,12 +700,38 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
'type' => array( 'object', 'string' ),
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'properties' => array(
'raw' => array(
'description' => __( 'Content for the template, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'block_version' => array(
'description' => __( 'Version of the content block format used by the template.' ),
'type' => 'integer',
'context' => array( 'edit' ),
'readonly' => true,
),
),
),
'title' => array(
'description' => __( 'Title of template.' ),
'type' => array( 'object', 'string' ),
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'properties' => array(
'raw' => array(
'description' => __( 'Title for the template, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
),
'rendered' => array(
'description' => __( 'HTML title for the template, transformed for display.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
),
'description' => array(
'description' => __( 'Description of template.' ),
@ -610,6 +742,7 @@ class WP_REST_Templates_Controller extends WP_REST_Controller {
'status' => array(
'description' => __( 'Status of template.' ),
'type' => 'string',
'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ),
'default' => 'publish',
'context' => array( 'embed', 'view', 'edit' ),
),

View File

@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '5.9-alpha-52185';
$wp_version = '5.9-alpha-52186';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.