REST API: Refactor global styles endpoints in REST API to register with post type.

Updated the global styles endpoints in the REST API to extend from existing posts and revisions controllers. This reduces duplicated code and inconsistencies. The revisions controller is now a subclass of the WP_REST_Revisions_Controller. Related redundant methods were removed and schema generation and collection parameters were adjusted to suit the global styles context. Updated permission checks, constructor, and collection parameters accordingly. This change allows for easy override of these classes using the `register_post_type_args` filter. 

Props ramonopoly, spacedmonkey, mukesh27.
Fixes #60131.
Built from https://develop.svn.wordpress.org/trunk@57624


git-svn-id: http://core.svn.wordpress.org/trunk@57125 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
spacedmonkey 2024-02-13 14:09:08 +00:00
parent 9b3acbae25
commit de3ee0fde6
7 changed files with 78 additions and 350 deletions

View File

@ -913,6 +913,7 @@ final class WP_Post_Type {
* Will only instantiate the controller class once per request.
*
* @since 6.4.0
* @since 6.5.0 Prevents autosave class instantiation for wp_global_styles post types.
*
* @return WP_REST_Controller|null The controller instance, or null if the post type
* is set not to show in rest.
@ -922,7 +923,7 @@ final class WP_Post_Type {
return null;
}
if ( 'attachment' === $this->name ) {
if ( in_array( $this->name, array( 'attachment', 'wp_global_styles' ), true ) ) {
return null;
}

View File

@ -473,15 +473,19 @@ function create_initial_post_types() {
register_post_type(
'wp_global_styles',
array(
'label' => _x( 'Global Styles', 'post type general name' ),
'description' => __( 'Global styles to include in themes.' ),
'public' => false,
'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
'_edit_link' => '/site-editor.php?canvas=edit', /* internal use only. don't use this when registering your own post type. */
'show_ui' => false,
'show_in_rest' => false,
'rewrite' => false,
'capabilities' => array(
'label' => _x( 'Global Styles', 'post type general name' ),
'description' => __( 'Global styles to include in themes.' ),
'public' => false,
'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
'_edit_link' => '/site-editor.php?canvas=edit', /* internal use only. don't use this when registering your own post type. */
'show_ui' => false,
'show_in_rest' => true,
'rewrite' => false,
'rest_base' => 'global-styles',
'rest_controller_class' => 'WP_REST_Global_Styles_Controller',
'revisions_rest_controller_class' => 'WP_REST_Global_Styles_Revisions_Controller',
'late_route_registration' => true,
'capabilities' => array(
'read' => 'edit_theme_options',
'create_posts' => 'edit_theme_options',
'edit_posts' => 'edit_theme_options',
@ -490,8 +494,8 @@ function create_initial_post_types() {
'edit_others_posts' => 'edit_theme_options',
'delete_others_posts' => 'edit_theme_options',
),
'map_meta_cap' => true,
'supports' => array(
'map_meta_cap' => true,
'supports' => array(
'title',
'editor',
'revisions',

View File

@ -323,14 +323,6 @@ function create_initial_rest_routes() {
$controller = new WP_REST_Block_Types_Controller();
$controller->register_routes();
// Global Styles revisions.
$controller = new WP_REST_Global_Styles_Revisions_Controller();
$controller->register_routes();
// Global Styles.
$controller = new WP_REST_Global_Styles_Controller();
$controller->register_routes();
// Settings.
$controller = new WP_REST_Settings_Controller();
$controller->register_routes();

View File

@ -10,25 +10,14 @@
/**
* Base Global Styles REST API Controller.
*/
class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
/**
* Post type.
* Whether the controller supports batching.
*
* @since 5.9.0
* @var string
* @since 6.5.0
* @var array
*/
protected $post_type;
/**
* Constructor.
* @since 5.9.0
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'global-styles';
$this->post_type = 'wp_global_styles';
}
protected $allow_batch = array( 'v1' => false );
/**
* Registers the controllers routes.
@ -194,28 +183,10 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
* @param WP_Post $post Post object.
* @return bool Whether the post can be read.
*/
protected function check_read_permission( $post ) {
public function check_read_permission( $post ) {
return current_user_can( 'read_post', $post->ID );
}
/**
* Returns the given global styles config.
*
* @since 5.9.0
*
* @param WP_REST_Request $request The request instance.
*
* @return WP_REST_Response|WP_Error
*/
public function get_item( $request ) {
$post = $this->get_post( $request['id'] );
if ( is_wp_error( $post ) ) {
return $post;
}
return $this->prepare_item_for_response( $post, $request );
}
/**
* Checks if a given request has access to write a single global styles config.
*
@ -241,55 +212,6 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
return true;
}
/**
* Checks if a global style can be edited.
*
* @since 5.9.0
*
* @param WP_Post $post Post object.
* @return bool Whether the post can be edited.
*/
protected function check_update_permission( $post ) {
return current_user_can( 'edit_post', $post->ID );
}
/**
* Updates a single global style config.
*
* @since 5.9.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 update_item( $request ) {
$post_before = $this->get_post( $request['id'] );
if ( is_wp_error( $post_before ) ) {
return $post_before;
}
$changes = $this->prepare_item_for_database( $request );
if ( is_wp_error( $changes ) ) {
return $changes;
}
$result = wp_update_post( wp_slash( (array) $changes ), true, false );
if ( is_wp_error( $result ) ) {
return $result;
}
$post = get_post( $request['id'] );
$fields_update = $this->update_additional_fields_for_object( $post, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
wp_after_insert_post( $post, true, $post_before );
$response = $this->prepare_item_for_response( $post, $request );
return rest_ensure_response( $response );
}
/**
* Prepares a single global styles config for update.
*
@ -407,7 +329,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
$links = $this->prepare_links( $post->ID );
$response->add_links( $links );
if ( ! empty( $links['self']['href'] ) ) {
$actions = $this->get_available_actions();
$actions = $this->get_available_actions( $post, $request );
$self = $links['self']['href'];
foreach ( $actions as $rel ) {
$response->add_link( $rel, $self );
@ -431,9 +353,12 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
$links = array(
'self' => array(
'self' => array(
'href' => rest_url( trailingslashit( $base ) . $id ),
),
'about' => array(
'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
),
);
if ( post_type_supports( $this->post_type, 'revisions' ) ) {
@ -454,13 +379,16 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
*
* @since 5.9.0
* @since 6.2.0 Added 'edit-css' action.
* @since 6.5.0 Added $post and $request parameters.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return array List of link relations.
*/
protected function get_available_actions() {
protected function get_available_actions( $post, $request ) {
$rels = array();
$post_type = get_post_type_object( $this->post_type );
$post_type = get_post_type_object( $post->post_type );
if ( current_user_can( $post_type->cap->publish_posts ) ) {
$rels[] = 'https://api.w.org/action-publish';
}
@ -472,21 +400,6 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
return $rels;
}
/**
* Overwrites the default protected title format.
*
* By default, WordPress will show password protected posts with a title of
* "Protected: %s", as the REST API communicates the protected status of a post
* in a machine readable format, we remove the "Protected: " prefix.
*
* @since 5.9.0
*
* @return string Protected title format.
*/
public function protected_title_format() {
return '%s';
}
/**
* Retrieves the query params for the global styles collection.
*

View File

@ -14,14 +14,14 @@
*
* @see WP_REST_Controller
*/
class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Controller {
/**
* Parent post type.
* Parent controller.
*
* @since 6.3.0
* @var string
* @since 6.5.0
* @var WP_REST_Controller
*/
protected $parent_post_type;
private $parent_controller;
/**
* The base of the parent controller's route.
@ -35,12 +35,23 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
* Constructor.
*
* @since 6.3.0
* @since 6.5.0 Extends class from WP_REST_Revisions_Controller.
*
* @param string $parent_post_type Post type of the parent.
*/
public function __construct() {
$this->parent_post_type = 'wp_global_styles';
$this->rest_base = 'revisions';
$this->parent_base = 'global-styles';
$this->namespace = 'wp/v2';
public function __construct( $parent_post_type ) {
parent::__construct( $parent_post_type );
$post_type_object = get_post_type_object( $parent_post_type );
$parent_controller = $post_type_object->get_rest_controller();
if ( ! $parent_controller ) {
$parent_controller = new WP_REST_Global_Styles_Controller( $parent_post_type );
}
$this->parent_controller = $parent_controller;
$this->rest_base = 'revisions';
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
}
/**
@ -63,7 +74,7 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
@ -97,29 +108,6 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
);
}
/**
* Retrieves the query params for collections.
*
* Inherits from WP_REST_Controller::get_collection_params(),
* also reflects changes to return value WP_REST_Revisions_Controller::get_collection_params().
*
* @since 6.3.0
*
* @return array Collection parameters.
*/
public function get_collection_params() {
$collection_params = parent::get_collection_params();
$collection_params['context']['default'] = 'view';
$collection_params['offset'] = array(
'description' => __( 'Offset the result set by a specific number of items.' ),
'type' => 'integer',
);
unset( $collection_params['search'] );
unset( $collection_params['per_page']['default'] );
return $collection_params;
}
/**
* Returns decoded JSON from post content string,
* or a 404 if not found.
@ -268,80 +256,6 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
return $response;
}
/**
* Retrieves one global styles revision from the collection.
*
* @since 6.5.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 get_item( $request ) {
$parent = $this->get_parent( $request['parent'] );
if ( is_wp_error( $parent ) ) {
return $parent;
}
$revision = $this->get_revision( $request['id'] );
if ( is_wp_error( $revision ) ) {
return $revision;
}
$response = $this->prepare_item_for_response( $revision, $request );
return rest_ensure_response( $response );
}
/**
* Gets the global styles revision, if the ID is valid.
*
* @since 6.5.0
*
* @param int $id Supplied ID.
* @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
*/
protected function get_revision( $id ) {
$error = new WP_Error(
'rest_post_invalid_id',
__( 'Invalid global styles revision ID.' ),
array( 'status' => 404 )
);
if ( (int) $id <= 0 ) {
return $error;
}
$revision = get_post( (int) $id );
if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) {
return $error;
}
return $revision;
}
/**
* Checks the post_date_gmt or modified_gmt and prepare any post or
* modified date for single post output.
*
* Duplicate of WP_REST_Revisions_Controller::prepare_date_response.
*
* @since 6.3.0
*
* @param string $date_gmt GMT publication time.
* @param string|null $date Optional. Local publication time. Default null.
* @return string|null ISO8601/RFC3339 formatted datetime, otherwise null.
*/
protected function prepare_date_response( $date_gmt, $date = null ) {
if ( '0000-00-00 00:00:00' === $date_gmt ) {
return null;
}
if ( isset( $date ) ) {
return mysql_to_rfc3339( $date );
}
return mysql_to_rfc3339( $date_gmt );
}
/**
* Prepares the revision for the REST response.
*
@ -411,6 +325,7 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
* Retrieves the revision's schema, conforming to JSON Schema.
*
* @since 6.3.0
* @since 6.5.0 Merged parent and parent controller schema data.
*
* @return array Item schema data.
*/
@ -419,70 +334,15 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => "{$this->parent_post_type}-revision",
'type' => 'object',
// Base properties for every revision.
'properties' => array(
$schema = parent::get_item_schema();
$parent_schema = $this->parent_controller->get_item_schema();
$schema['properties'] = array_merge( $schema['properties'], $parent_schema['properties'] );
/*
* Adds settings and styles from the WP_REST_Revisions_Controller item fields.
* Leaves out GUID as global styles shouldn't be accessible via URL.
*/
'author' => array(
'description' => __( 'The ID for the author of the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'date' => array(
'description' => __( "The date the revision was published, in the site's timezone." ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit', 'embed' ),
),
'date_gmt' => array(
'description' => __( 'The date the revision was published, as GMT.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'id' => array(
'description' => __( 'Unique identifier for the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'modified' => array(
'description' => __( "The date the revision was last modified, in the site's timezone." ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'modified_gmt' => array(
'description' => __( 'The date the revision was last modified, as GMT.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'parent' => array(
'description' => __( 'The ID for the parent of the revision.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
// Adds settings and styles from the WP_REST_Global_Styles_Controller parent schema.
'styles' => array(
'description' => __( 'Global styles.' ),
'type' => array( 'object' ),
'context' => array( 'view', 'edit' ),
),
'settings' => array(
'description' => __( 'Global settings.' ),
'type' => array( 'object' ),
'context' => array( 'view', 'edit' ),
),
),
);
unset( $schema['properties']['guid'] );
unset( $schema['properties']['slug'] );
unset( $schema['properties']['meta'] );
unset( $schema['properties']['content'] );
unset( $schema['properties']['title'] );
$this->schema = $schema;
@ -490,62 +350,20 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
}
/**
* Checks if a given request has access to read a single global style.
* Retrieves the query params for collections.
* Removes params that are not supported by global styles revisions.
*
* @since 6.3.0
* @since 6.5.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
* @return array Collection parameters.
*/
public function get_item_permissions_check( $request ) {
$post = $this->get_parent( $request['parent'] );
if ( is_wp_error( $post ) ) {
return $post;
}
/*
* The same check as WP_REST_Global_Styles_Controller::get_item_permissions_check.
*/
if ( ! current_user_can( 'read_post', $post->ID ) ) {
return new WP_Error(
'rest_cannot_view',
__( 'Sorry, you are not allowed to view revisions for this global style.' ),
array( 'status' => rest_authorization_required_code() )
);
}
return true;
}
/**
* Gets the parent post, if the ID is valid.
*
* Duplicate of WP_REST_Revisions_Controller::get_parent.
*
* @since 6.3.0
*
* @param int $parent_post_id Supplied ID.
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
*/
protected function get_parent( $parent_post_id ) {
$error = new WP_Error(
'rest_post_invalid_parent',
__( 'Invalid post parent ID.' ),
array( 'status' => 404 )
);
if ( (int) $parent_post_id <= 0 ) {
return $error;
}
$parent_post = get_post( (int) $parent_post_id );
if ( empty( $parent_post ) || empty( $parent_post->ID )
|| $this->parent_post_type !== $parent_post->post_type
) {
return $error;
}
return $parent_post;
public function get_collection_params() {
$query_params = parent::get_collection_params();
unset( $query_params['exclude'] );
unset( $query_params['include'] );
unset( $query_params['search'] );
unset( $query_params['order'] );
unset( $query_params['orderby'] );
return $query_params;
}
}

View File

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

View File

@ -276,10 +276,10 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-posts-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-global-styles-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-template-revisions-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php';
require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-template-autosaves-controller.php';