From 99396f6c868b4b332aa08eba7d77706cc8811b6f Mon Sep 17 00:00:00 2001 From: davidbaumwald Date: Tue, 13 Feb 2024 15:13:17 +0000 Subject: [PATCH] REST API: Revert the refactor of global styles endpoints in REST API in [57624]. [57624] introduced some E2E test failures which are the result of an incompatibility with the Gutenberg plugin. Props jorbin, spacedmonkey, swissspidy, hellofromTonya, youknowriad, costdev. See #60131. Built from https://develop.svn.wordpress.org/trunk@57628 git-svn-id: http://core.svn.wordpress.org/trunk@57129 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-includes/class-wp-post-type.php | 3 +- wp-includes/post.php | 26 +- wp-includes/rest-api.php | 8 + ...class-wp-rest-global-styles-controller.php | 119 ++++++-- ...est-global-styles-revisions-controller.php | 268 +++++++++++++++--- wp-includes/version.php | 2 +- wp-settings.php | 2 +- 7 files changed, 350 insertions(+), 78 deletions(-) diff --git a/wp-includes/class-wp-post-type.php b/wp-includes/class-wp-post-type.php index 9d22e2617b..7a2769ed88 100644 --- a/wp-includes/class-wp-post-type.php +++ b/wp-includes/class-wp-post-type.php @@ -913,7 +913,6 @@ 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. @@ -923,7 +922,7 @@ final class WP_Post_Type { return null; } - if ( in_array( $this->name, array( 'attachment', 'wp_global_styles' ), true ) ) { + if ( 'attachment' === $this->name ) { return null; } diff --git a/wp-includes/post.php b/wp-includes/post.php index c08e0ecb2e..5fa058363a 100644 --- a/wp-includes/post.php +++ b/wp-includes/post.php @@ -473,19 +473,15 @@ 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' => 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( + '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( 'read' => 'edit_theme_options', 'create_posts' => 'edit_theme_options', 'edit_posts' => 'edit_theme_options', @@ -494,8 +490,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', diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php index 6014e93dbc..b44b205afb 100644 --- a/wp-includes/rest-api.php +++ b/wp-includes/rest-api.php @@ -323,6 +323,14 @@ 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(); diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index 9a0b687615..9837f535e2 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -10,14 +10,25 @@ /** * Base Global Styles REST API Controller. */ -class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller { +class WP_REST_Global_Styles_Controller extends WP_REST_Controller { + /** - * Whether the controller supports batching. + * Post type. * - * @since 6.5.0 - * @var array + * @since 5.9.0 + * @var string */ - protected $allow_batch = array( 'v1' => false ); + 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'; + } /** * Registers the controllers routes. @@ -183,10 +194,28 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller { * @param WP_Post $post Post object. * @return bool Whether the post can be read. */ - public function check_read_permission( $post ) { + protected 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. * @@ -212,6 +241,55 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_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. * @@ -329,7 +407,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller { $links = $this->prepare_links( $post->ID ); $response->add_links( $links ); if ( ! empty( $links['self']['href'] ) ) { - $actions = $this->get_available_actions( $post, $request ); + $actions = $this->get_available_actions(); $self = $links['self']['href']; foreach ( $actions as $rel ) { $response->add_link( $rel, $self ); @@ -353,12 +431,9 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_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' ) ) { @@ -379,16 +454,13 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_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( $post, $request ) { + protected function get_available_actions() { $rels = array(); - $post_type = get_post_type_object( $post->post_type ); + $post_type = get_post_type_object( $this->post_type ); if ( current_user_can( $post_type->cap->publish_posts ) ) { $rels[] = 'https://api.w.org/action-publish'; } @@ -400,6 +472,21 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_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. * diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php index 849f5be5cd..db1be197e5 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php @@ -14,14 +14,14 @@ * * @see WP_REST_Controller */ -class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Controller { +class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller { /** - * Parent controller. + * Parent post type. * - * @since 6.5.0 - * @var WP_REST_Controller + * @since 6.3.0 + * @var string */ - private $parent_controller; + protected $parent_post_type; /** * The base of the parent controller's route. @@ -35,23 +35,12 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr * 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( $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'; + public function __construct() { + $this->parent_post_type = 'wp_global_styles'; + $this->rest_base = 'revisions'; + $this->parent_base = 'global-styles'; + $this->namespace = 'wp/v2'; } /** @@ -74,7 +63,7 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), @@ -108,6 +97,29 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr ); } + /** + * 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. @@ -256,6 +268,80 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr 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. * @@ -325,7 +411,6 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr * 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. */ @@ -334,15 +419,70 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr return $this->add_additional_fields_schema( $this->schema ); } - $schema = parent::get_item_schema(); - $parent_schema = $this->parent_controller->get_item_schema(); - $schema['properties'] = array_merge( $schema['properties'], $parent_schema['properties'] ); + $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( - unset( $schema['properties']['guid'] ); - unset( $schema['properties']['slug'] ); - unset( $schema['properties']['meta'] ); - unset( $schema['properties']['content'] ); - unset( $schema['properties']['title'] ); + /* + * 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' ), + ), + ), + ); $this->schema = $schema; @@ -350,20 +490,62 @@ class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Contr } /** - * Retrieves the query params for collections. - * Removes params that are not supported by global styles revisions. + * Checks if a given request has access to read a single global style. * - * @since 6.5.0 + * @since 6.3.0 * - * @return array Collection parameters. + * @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. */ - 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; + 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; } } diff --git a/wp-includes/version.php b/wp-includes/version.php index f1438c6ab7..8b47c13fd3 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.5-alpha-57627'; +$wp_version = '6.5-alpha-57628'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/wp-settings.php b/wp-settings.php index c3c080e5fe..2772568dee 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -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';