Make WordPress Core

Ticket #43316: 43316.20.diff

File 43316.20.diff, 86.4 KB (added by adamsilverstein, 6 years ago)
  • src/wp-includes/rest-api.php

    diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
    index e1403d71bd..1025836de5 100644
    a b function create_initial_rest_routes() { 
    193193                        $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
    194194                        $revisions_controller->register_routes();
    195195                }
     196
     197                if ( 'attachment' !== $post_type->name ) {
     198                        $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name );
     199                        $autosaves_controller->register_routes();
     200                }
     201
    196202        }
    197203
    198204        // Post types.
    function create_initial_rest_routes() { 
    233239        // Settings.
    234240        $controller = new WP_REST_Settings_Controller;
    235241        $controller->register_routes();
    236 
    237         // Themes.
    238         $controller = new WP_REST_Themes_Controller;
    239         $controller->register_routes();
    240242}
    241243
    242244/**
  • new file src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php
    new file mode 100644
    index 0000000000..0f650821e3
    - +  
     1<?php
     2/**
     3 * REST API: WP_REST_Autosaves_Controller class.
     4 *
     5 * @package WordPress
     6 * @subpackage REST_API
     7 * @since 5.0.0
     8 */
     9
     10/**
     11 * Core class used to access autosaves via the REST API.
     12 *
     13 * @since 5.0.0
     14 *
     15 * @see WP_REST_Controller
     16 */
     17class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
     18
     19        /**
     20         * Parent post type.
     21         *
     22         * @since 5.0.0
     23         * @var string
     24         */
     25        private $parent_post_type;
     26
     27        /**
     28         * Parent post controller.
     29         *
     30         * @since 5.0.0
     31         * @var WP_REST_Controller
     32         */
     33        private $parent_controller;
     34
     35        /**
     36         * Revision controller.
     37         *
     38         * @since 5.0.0
     39         * @var WP_REST_Controller
     40         */
     41        private $revisions_controller;
     42
     43        /**
     44         * The base of the parent controller's route.
     45         *
     46         * @since 5.0.0
     47         * @var string
     48         */
     49        private $parent_base;
     50
     51        /**
     52         * Constructor.
     53         *
     54         * @since 5.0.0
     55         *
     56         * @param string $parent_post_type Post type of the parent.
     57         */
     58        public function __construct( $parent_post_type ) {
     59                $this->parent_post_type = $parent_post_type;
     60                $post_type_object       = get_post_type_object( $parent_post_type );
     61
     62                // Ensure that post type-specific controller logic is available.
     63                $parent_controller_class = ! empty( $post_type_object->rest_controller_class ) ? $post_type_object->rest_controller_class : 'WP_REST_Posts_Controller';
     64
     65                $this->parent_controller    = new $parent_controller_class( $post_type_object->name );
     66                $this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
     67                $this->rest_namespace       = 'wp/v2';
     68                $this->rest_base            = 'autosaves';
     69                $this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
     70        }
     71
     72        /**
     73         * Registers routes for autosaves.
     74         *
     75         * @since 5.0.0
     76         *
     77         * @see register_rest_route()
     78         */
     79        public function register_routes() {
     80                register_rest_route(
     81                        $this->rest_namespace,
     82                        '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base,
     83                        array(
     84                                'args'   => array(
     85                                        'parent' => array(
     86                                                'description' => __( 'The ID for the parent of the object.', 'gutenberg' ),
     87                                                'type'        => 'integer',
     88                                        ),
     89                                ),
     90                                array(
     91                                        'methods'             => WP_REST_Server::READABLE,
     92                                        'callback'            => array( $this, 'get_items' ),
     93                                        'permission_callback' => array( $this->revisions_controller, 'get_items_permissions_check' ),
     94                                        'args'                => $this->get_collection_params(),
     95                                ),
     96                                array(
     97                                        'methods'             => WP_REST_Server::CREATABLE,
     98                                        'callback'            => array( $this, 'create_item' ),
     99                                        'permission_callback' => array( $this, 'create_item_permissions_check' ),
     100                                        'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
     101                                ),
     102                                'schema' => array( $this, 'get_public_item_schema' ),
     103                        )
     104                );
     105
     106                register_rest_route(
     107                        $this->rest_namespace,
     108                        '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
     109                        array(
     110                                'args'   => array(
     111                                        'parent' => array(
     112                                                'description' => __( 'The ID for the parent of the object.', 'gutenberg' ),
     113                                                'type'        => 'integer',
     114                                        ),
     115                                        'id'     => array(
     116                                                'description' => __( 'The ID for the object.', 'gutenberg' ),
     117                                                'type'        => 'integer',
     118                                        ),
     119                                ),
     120                                array(
     121                                        'methods'             => WP_REST_Server::READABLE,
     122                                        'callback'            => array( $this, 'get_item' ),
     123                                        'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ),
     124                                        'args'                => array(
     125                                                'context' => $this->get_context_param( array( 'default' => 'view' ) ),
     126                                        ),
     127                                ),
     128                                'schema' => array( $this, 'get_public_item_schema' ),
     129                        )
     130                );
     131
     132        }
     133
     134        /**
     135         * Get the parent post.
     136         *
     137         * @since 5.0.0
     138         *
     139         * @param int $parent_id Supplied ID.
     140         * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
     141         */
     142        protected function get_parent( $parent_id ) {
     143                return $this->revisions_controller->get_parent( $parent_id );
     144        }
     145
     146        /**
     147         * Checks if a given request has access to create an autosave revision.
     148         *
     149         * Autosave revisions inherit permissions from the parent post,
     150         * check if the current user has permission to edit the post.
     151         *
     152         * @since 5.0.0
     153         *
     154         * @param WP_REST_Request $request Full details about the request.
     155         * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
     156         */
     157        public function create_item_permissions_check( $request ) {
     158                $id = $request->get_param( 'id' );
     159                if ( empty( $id ) ) {
     160                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.', 'gutenberg' ), array( 'status' => 404 ) );
     161                }
     162
     163                return $this->parent_controller->update_item_permissions_check( $request );
     164        }
     165
     166        /**
     167         * Creates, updates or deletes an autosave revision.
     168         *
     169         * @since 5.0.0
     170         *
     171         * @param WP_REST_Request $request Full details about the request.
     172         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     173         */
     174        public function create_item( $request ) {
     175
     176                $post = get_post( $request->get_param( 'id' ) );
     177
     178                if ( is_wp_error( $post ) ) {
     179                        return $post;
     180                }
     181
     182                $prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
     183                $prepared_post->ID = $post->ID;
     184                $user_id           = get_current_user_id();
     185
     186                if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
     187                        // Draft posts for the same author: autosaving updates the post and does not create a revision.
     188                        // Convert the post object to an array and add slashes, wp_update_post expects escaped array.
     189                        $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
     190                } else {
     191                        // Non-draft posts: create or update the post autosave.
     192                        $autosave_id = $this->create_post_autosave( (array) $prepared_post );
     193                }
     194
     195                if ( is_wp_error( $autosave_id ) ) {
     196                        return $autosave_id;
     197                }
     198
     199                $autosave = get_post( $autosave_id );
     200                $request->set_param( 'context', 'edit' );
     201
     202                $response = $this->prepare_item_for_response( $autosave, $request );
     203                $response = rest_ensure_response( $response );
     204
     205                return $response;
     206        }
     207
     208        /**
     209         * Get the autosave, if the ID is valid.
     210         *
     211         * @since 5.0.0
     212         *
     213         * @param WP_REST_Request $request Full data about the request.
     214         * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
     215         */
     216        public function get_item( $request ) {
     217                $parent_id = (int) $request->get_param( 'parent' );
     218
     219                if ( $parent_id <= 0 ) {
     220                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.', 'gutenberg' ), array( 'status' => 404 ) );
     221                }
     222
     223                $autosave = wp_get_post_autosave( $parent_id );
     224
     225                if ( ! $autosave ) {
     226                        return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.', 'gutenberg' ), array( 'status' => 404 ) );
     227                }
     228
     229                $response = $this->prepare_item_for_response( $autosave, $request );
     230                return $response;
     231        }
     232
     233        /**
     234         * Gets a collection of autosaves using wp_get_post_autosave.
     235         *
     236         * Contains the user's autosave, for empty if it doesn't exist.
     237         *
     238         * @since 5.0.0
     239         *
     240         * @param WP_REST_Request $request Full data about the request.
     241         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     242         */
     243        public function get_items( $request ) {
     244                $parent = $this->get_parent( $request->get_param( 'parent' ) );
     245                if ( is_wp_error( $parent ) ) {
     246                        return $parent;
     247                }
     248
     249                $response  = array();
     250                $parent_id = $parent->ID;
     251                $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
     252
     253                foreach ( $revisions as $revision ) {
     254                        if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
     255                                $data       = $this->prepare_item_for_response( $revision, $request );
     256                                $response[] = $this->prepare_response_for_collection( $data );
     257                        }
     258                }
     259
     260                return rest_ensure_response( $response );
     261        }
     262
     263
     264        /**
     265         * Retrieves the autosave's schema, conforming to JSON Schema.
     266         *
     267         * @since 5.0.0
     268         *
     269         * @return array Item schema data.
     270         */
     271        public function get_item_schema() {
     272                $schema = $this->revisions_controller->get_item_schema();
     273
     274                $schema['properties']['preview_link'] = array(
     275                        'description' => __( 'Preview link for the post.', 'gutenberg' ),
     276                        'type'        => 'string',
     277                        'format'      => 'uri',
     278                        'context'     => array( 'edit' ),
     279                        'readonly'    => true,
     280                );
     281
     282                return $schema;
     283        }
     284
     285        /**
     286         * Creates autosave for the specified post.
     287         *
     288         * From wp-admin/post.php.
     289         *
     290         * @since 5.0.0
     291         *
     292         * @param mixed $post_data Associative array containing the post data.
     293         * @return mixed The autosave revision ID or WP_Error.
     294         */
     295        public function create_post_autosave( $post_data ) {
     296
     297                $post_id = (int) $post_data['ID'];
     298                $post    = get_post( $post_id );
     299
     300                if ( is_wp_error( $post ) ) {
     301                        return $post;
     302                }
     303
     304                $user_id = get_current_user_id();
     305
     306                // Store one autosave per author. If there is already an autosave, overwrite it.
     307                $old_autosave = wp_get_post_autosave( $post_id, $user_id );
     308
     309                if ( $old_autosave ) {
     310                        $new_autosave                = _wp_post_revision_data( $post_data, true );
     311                        $new_autosave['ID']          = $old_autosave->ID;
     312                        $new_autosave['post_author'] = $user_id;
     313
     314                        // If the new autosave has the same content as the post, delete the autosave.
     315                        $autosave_is_different = false;
     316
     317                        foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
     318                                if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) {
     319                                        $autosave_is_different = true;
     320                                        break;
     321                                }
     322                        }
     323
     324                        if ( ! $autosave_is_different ) {
     325                                wp_delete_post_revision( $old_autosave->ID );
     326                                return new WP_Error( 'rest_autosave_no_changes', __( 'There is nothing to save. The autosave and the post content are the same.', 'gutenberg' ), array( 'status' => 400 ) );
     327                        }
     328
     329                        /**
     330                         * This filter is documented in wp-admin/post.php.
     331                         */
     332                        do_action( 'wp_creating_autosave', $new_autosave );
     333
     334                        // wp_update_post expects escaped array.
     335                        return wp_update_post( wp_slash( $new_autosave ) );
     336                }
     337
     338                // Create the new autosave as a special post revision.
     339                return _wp_put_post_revision( $post_data, true );
     340        }
     341
     342        /**
     343         * Prepares the revision for the REST response.
     344         *
     345         * @since 5.0.0
     346         *
     347         * @param WP_Post         $post    Post revision object.
     348         * @param WP_REST_Request $request Request object.
     349         *
     350         * @return WP_REST_Response Response object.
     351         */
     352        public function prepare_item_for_response( $post, $request ) {
     353
     354                $response = $this->revisions_controller->prepare_item_for_response( $post, $request );
     355
     356                $schema = $this->get_item_schema();
     357
     358                if ( ! empty( $schema['properties']['preview_link'] ) ) {
     359                        $parent_id          = wp_is_post_autosave( $post );
     360                        $preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
     361                        $preview_query_args = array();
     362
     363                        if ( false !== $parent_id ) {
     364                                $preview_query_args['preview_id']    = $parent_id;
     365                                $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
     366                        }
     367
     368                        $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
     369                }
     370
     371                $context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
     372                $response->data = $this->filter_response_by_context( $response->data, $context );
     373
     374                /**
     375                 * Filters a revision returned from the API.
     376                 *
     377                 * Allows modification of the revision right before it is returned.
     378                 *
     379                 * @since 5.0.0
     380                 *
     381                 * @param WP_REST_Response $response The response object.
     382                 * @param WP_Post          $post     The original revision object.
     383                 * @param WP_REST_Request  $request  Request used to generate the response.
     384                 */
     385                return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
     386        }
     387}
  • src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
    index 04bd1580f7..3bc6b59091 100644
    a b abstract class WP_REST_Controller { 
    385385
    386386                $additional_fields = $this->get_additional_fields();
    387387
    388                 $requested_fields = $this->get_fields_for_response( $request );
    389 
    390388                foreach ( $additional_fields as $field_name => $field_options ) {
    391389
    392390                        if ( ! $field_options['get_callback'] ) {
    393391                                continue;
    394392                        }
    395393
    396                         if ( ! in_array( $field_name, $requested_fields, true ) ) {
    397                                 continue;
    398                         }
    399 
    400394                        $object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
    401395                }
    402396
  • deleted file src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php
    deleted file mode 100644
    index 4aa7db4f6f..0000000000
    + -  
    1 <?php
    2 /**
    3  * REST API: WP_REST_Themes_Controller class
    4  *
    5  * @package WordPress
    6  * @subpackage REST_API
    7  * @since 5.0.0
    8  */
    9 
    10 /**
    11  * Core class used to manage themes via the REST API.
    12  *
    13  * @since 5.0.0
    14  *
    15  * @see WP_REST_Controller
    16  */
    17 class WP_REST_Themes_Controller extends WP_REST_Controller {
    18 
    19         /**
    20          * Constructor.
    21          *
    22          * @since 5.0.0
    23          */
    24         public function __construct() {
    25                 $this->namespace = 'wp/v2';
    26                 $this->rest_base = 'themes';
    27         }
    28 
    29         /**
    30          * Registers the routes for the objects of the controller.
    31          *
    32          * @since 5.0.0
    33          *
    34          * @see register_rest_route()
    35          */
    36         public function register_routes() {
    37                 register_rest_route(
    38                         $this->namespace,
    39                         '/' . $this->rest_base,
    40                         array(
    41                                 array(
    42                                         'methods'             => WP_REST_Server::READABLE,
    43                                         'callback'            => array( $this, 'get_items' ),
    44                                         'permission_callback' => array( $this, 'get_items_permissions_check' ),
    45                                         'args'                => $this->get_collection_params(),
    46                                 ),
    47                                 'schema' => array( $this, 'get_item_schema' ),
    48                         )
    49                 );
    50         }
    51 
    52         /**
    53          * Checks if a given request has access to read the theme.
    54          *
    55          * @since 5.0.0
    56          *
    57          * @param WP_REST_Request $request Full details about the request.
    58          * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
    59          */
    60         public function get_items_permissions_check( $request ) {
    61                 if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) {
    62                         return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to view themes.' ), array( 'status' => rest_authorization_required_code() ) );
    63                 }
    64 
    65                 return true;
    66         }
    67 
    68         /**
    69          * Retrieves a collection of themes.
    70          *
    71          * @since 5.0.0
    72          *
    73          * @param WP_REST_Request $request Full details about the request.
    74          * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    75          */
    76         public function get_items( $request ) {
    77                 // Retrieve the list of registered collection query parameters.
    78                 $registered = $this->get_collection_params();
    79                 $themes     = array();
    80 
    81                 if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) {
    82                         $active_theme = wp_get_theme();
    83                         $active_theme = $this->prepare_item_for_response( $active_theme, $request );
    84                         $themes[]     = $this->prepare_response_for_collection( $active_theme );
    85                 }
    86 
    87                 $response = rest_ensure_response( $themes );
    88 
    89                 $response->header( 'X-WP-Total', count( $themes ) );
    90                 $response->header( 'X-WP-TotalPages', count( $themes ) );
    91 
    92                 return $response;
    93         }
    94 
    95         /**
    96          * Prepares a single theme output for response.
    97          *
    98          * @since 5.0.0
    99          *
    100          * @param WP_Theme        $theme   Theme object.
    101          * @param WP_REST_Request $request Request object.
    102          * @return WP_REST_Response Response object.
    103          */
    104         public function prepare_item_for_response( $theme, $request ) {
    105                 $data   = array();
    106                 $fields = $this->get_fields_for_response( $request );
    107 
    108                 if ( in_array( 'theme_supports', $fields, true ) ) {
    109                         $formats                           = get_theme_support( 'post-formats' );
    110                         $formats                           = is_array( $formats ) ? array_values( $formats[0] ) : array();
    111                         $formats                           = array_merge( array( 'standard' ), $formats );
    112                         $data['theme_supports']['formats'] = $formats;
    113 
    114                         $data['theme_supports']['post-thumbnails'] = false;
    115                         $post_thumbnails                           = get_theme_support( 'post-thumbnails' );
    116 
    117                         if ( $post_thumbnails ) {
    118                                 // $post_thumbnails can contain a nested array of post types.
    119                                 // e.g. array( array( 'post', 'page' ) ).
    120                                 $data['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true;
    121                         }
    122                 }
    123 
    124                 $data = $this->add_additional_fields_to_object( $data, $request );
    125 
    126                 // Wrap the data in a response object.
    127                 $response = rest_ensure_response( $data );
    128 
    129                 /**
    130                  * Filters theme data returned from the REST API.
    131                  *
    132                  * @since 5.0.0
    133                  *
    134                  * @param WP_REST_Response $response The response object.
    135                  * @param WP_Theme         $theme    Theme object used to create response.
    136                  * @param WP_REST_Request  $request  Request object.
    137                  */
    138                 return apply_filters( 'rest_prepare_theme', $response, $theme, $request );
    139         }
    140 
    141         /**
    142          * Retrieves the theme's schema, conforming to JSON Schema.
    143          *
    144          * @since 5.0.0
    145          *
    146          * @return array Item schema data.
    147          */
    148         public function get_item_schema() {
    149                 $schema = array(
    150                         '$schema'    => 'http://json-schema.org/draft-04/schema#',
    151                         'title'      => 'theme',
    152                         'type'       => 'object',
    153                         'properties' => array(
    154                                 'theme_supports' => array(
    155                                         'description' => __( 'Features supported by this theme.' ),
    156                                         'type'        => 'array',
    157                                         'readonly'    => true,
    158                                         'properties'  => array(
    159                                                 'formats'         => array(
    160                                                         'description' => __( 'Post formats supported.' ),
    161                                                         'type'        => 'array',
    162                                                         'readonly'    => true,
    163                                                 ),
    164                                                 'post-thumbnails' => array(
    165                                                         'description' => __( 'Whether the theme supports post thumbnails.' ),
    166                                                         'type'        => array( 'array', 'bool' ),
    167                                                         'readonly'    => true,
    168                                                 ),
    169                                         ),
    170                                 ),
    171                         ),
    172                 );
    173 
    174                 return $this->add_additional_fields_schema( $schema );
    175         }
    176 
    177         /**
    178          * Retrieves the search params for the themes collection.
    179          *
    180          * @since 5.0.0
    181          *
    182          * @return array Collection parameters.
    183          */
    184         public function get_collection_params() {
    185                 $query_params = parent::get_collection_params();
    186 
    187                 $query_params['status'] = array(
    188                         'description'       => __( 'Limit result set to themes assigned one or more statuses.' ),
    189                         'type'              => 'array',
    190                         'items'             => array(
    191                                 'enum' => array( 'active' ),
    192                                 'type' => 'string',
    193                         ),
    194                         'required'          => true,
    195                         'sanitize_callback' => array( $this, 'sanitize_theme_status' ),
    196                 );
    197 
    198                 /**
    199                  * Filter collection parameters for the themes controller.
    200                  *
    201                  * @since 5.0.0
    202                  *
    203                  * @param array        $query_params JSON Schema-formatted collection parameters.
    204                  */
    205                 return apply_filters( 'rest_themes_collection_params', $query_params );
    206         }
    207 
    208         /**
    209          * Sanitizes and validates the list of theme status.
    210          *
    211          * @since 5.0.0
    212          *
    213          * @param  string|array    $statuses  One or more theme statuses.
    214          * @param  WP_REST_Request $request   Full details about the request.
    215          * @param  string          $parameter Additional parameter to pass to validation.
    216          * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
    217          */
    218         public function sanitize_theme_status( $statuses, $request, $parameter ) {
    219                 $statuses = wp_parse_slug_list( $statuses );
    220 
    221                 foreach ( $statuses as $status ) {
    222                         $result = rest_validate_request_arg( $status, $request, $parameter );
    223 
    224                         if ( is_wp_error( $result ) ) {
    225                                 return $result;
    226                         }
    227                 }
    228 
    229                 return $statuses;
    230         }
    231 }
  • src/wp-settings.php

    diff --git a/src/wp-settings.php b/src/wp-settings.php
    index 44c8a91a07..f4f2ba0d88 100644
    a b require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-contro 
    229229require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' );
    230230require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' );
    231231require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' );
     232require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php' );
    232233require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' );
    233234require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' );
    234235require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );
    235236require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' );
    236237require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' );
    237 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-themes-controller.php' );
    238238require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' );
    239239require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' );
    240240require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );
  • new file tests/phpunit/tests/rest-api/rest-autosaves-controller.php

    diff --git a/tests/phpunit/tests/rest-api/rest-autosaves-controller.php b/tests/phpunit/tests/rest-api/rest-autosaves-controller.php
    new file mode 100644
    index 0000000000..6805b61552
    - +  
     1<?php
     2/**
     3 * Unit tests covering WP_REST_Autosaves_Controller functionality.
     4 *
     5 * @package WordPress
     6 * @subpackage REST API
     7 */
     8
     9/**
     10 * @group restapi-autosave
     11 * @group restapi
     12 */
     13class WP_Test_REST_Autosaves_Controller extends WP_Test_REST_Post_Type_Controller_Testcase {
     14        protected static $post_id;
     15        protected static $page_id;
     16
     17        protected static $autosave_post_id;
     18        protected static $autosave_page_id;
     19
     20        protected static $editor_id;
     21        protected static $contributor_id;
     22
     23        protected function set_post_data( $args = array() ) {
     24                $defaults = array(
     25                        'title'   => 'Post Title',
     26                        'content' => 'Post content',
     27                        'excerpt' => 'Post excerpt',
     28                        'name'    => 'test',
     29                        'author'  => get_current_user_id(),
     30                );
     31
     32                return wp_parse_args( $args, $defaults );
     33        }
     34
     35        protected function check_create_autosave_response( $response ) {
     36                $this->assertNotInstanceOf( 'WP_Error', $response );
     37                $response = rest_ensure_response( $response );
     38                $data = $response->get_data();
     39
     40                $this->assertArrayHasKey( 'content', $data );
     41                $this->assertArrayHasKey( 'excerpt', $data );
     42                $this->assertArrayHasKey( 'title', $data );
     43        }
     44
     45        public static function wpSetUpBeforeClass( $factory ) {
     46                self::$post_id = $factory->post->create();
     47                self::$page_id = $factory->post->create( array( 'post_type' => 'page' ) );
     48
     49                self::$editor_id      = $factory->user->create(
     50                        array(
     51                                'role' => 'editor',
     52                        )
     53                );
     54                self::$contributor_id = $factory->user->create(
     55                        array(
     56                                'role' => 'contributor',
     57                        )
     58                );
     59
     60                wp_set_current_user( self::$editor_id );
     61
     62                // Create an autosave.
     63                self::$autosave_post_id = wp_create_post_autosave(
     64                        array(
     65                                'post_content' => 'This content is better.',
     66                                'post_ID'      => self::$post_id,
     67                                'post_type'    => 'post',
     68                        )
     69                );
     70
     71                self::$autosave_page_id = wp_create_post_autosave(
     72                        array(
     73                                'post_content' => 'This content is better.',
     74                                'post_ID'      => self::$page_id,
     75                                'post_type'    => 'post',
     76                        )
     77                );
     78
     79        }
     80
     81        public static function wpTearDownAfterClass() {
     82                // Also deletes revisions.
     83                wp_delete_post( self::$post_id, true );
     84                wp_delete_post( self::$page_id, true );
     85
     86                self::delete_user( self::$editor_id );
     87                self::delete_user( self::$contributor_id );
     88        }
     89
     90        public function setUp() {
     91                parent::setUp();
     92                wp_set_current_user( self::$editor_id );
     93
     94                $this->post_autosave = wp_get_post_autosave( self::$post_id );
     95        }
     96
     97        public function test_register_routes() {
     98                $routes = rest_get_server()->get_routes();
     99                $this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/autosaves', $routes );
     100                $this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)', $routes );
     101                $this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/autosaves', $routes );
     102                $this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/autosaves/(?P<id>[\d]+)', $routes );
     103        }
     104
     105        public function test_context_param() {
     106                // Collection
     107                $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     108                $response = rest_get_server()->dispatch( $request );
     109                $data     = $response->get_data();
     110                $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
     111                $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
     112                // Single
     113                $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     114                $response = rest_get_server()->dispatch( $request );
     115                $data     = $response->get_data();
     116                $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
     117                $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );   }
     118
     119        public function test_get_items() {
     120                wp_set_current_user( self::$editor_id );
     121                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     122                $response = rest_get_server()->dispatch( $request );
     123                $data     = $response->get_data();
     124                $this->assertEquals( 200, $response->get_status() );
     125                $this->assertCount( 1, $data );
     126
     127                $this->assertEquals( self::$autosave_post_id, $data[0]['id'] );
     128
     129                $this->check_get_autosave_response( $data[0], $this->post_autosave );
     130        }
     131
     132        public function test_get_items_no_permission() {
     133                wp_set_current_user( 0 );
     134                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     135                $response = rest_get_server()->dispatch( $request );
     136                $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
     137                wp_set_current_user( self::$contributor_id );
     138                $response = rest_get_server()->dispatch( $request );
     139                $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
     140        }
     141
     142        public function test_get_items_missing_parent() {
     143                wp_set_current_user( self::$editor_id );
     144                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' );
     145                $response = rest_get_server()->dispatch( $request );
     146                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     147        }
     148
     149        public function test_get_items_invalid_parent_post_type() {
     150                wp_set_current_user( self::$editor_id );
     151                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
     152                $response = rest_get_server()->dispatch( $request );
     153                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     154        }
     155
     156        public function test_get_item() {
     157                wp_set_current_user( self::$editor_id );
     158                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     159                $response = rest_get_server()->dispatch( $request );
     160                $this->assertEquals( 200, $response->get_status() );
     161                $data     = $response->get_data();
     162
     163                $this->check_get_autosave_response( $response, $this->post_autosave );
     164                $fields = array(
     165                        'author',
     166                        'date',
     167                        'date_gmt',
     168                        'modified',
     169                        'modified_gmt',
     170                        'guid',
     171                        'id',
     172                        'parent',
     173                        'slug',
     174                        'title',
     175                        'excerpt',
     176                        'content',
     177                );
     178                $this->assertEqualSets( $fields, array_keys( $data ) );
     179                $this->assertSame( self::$editor_id, $data['author'] );
     180        }
     181
     182        public function test_get_item_embed_context() {
     183                wp_set_current_user( self::$editor_id );
     184                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     185                $request->set_param( 'context', 'embed' );
     186                $response = rest_get_server()->dispatch( $request );
     187                $fields   = array(
     188                        'author',
     189                        'date',
     190                        'id',
     191                        'parent',
     192                        'slug',
     193                        'title',
     194                        'excerpt',
     195                );
     196                $data     = $response->get_data();
     197                $this->assertEqualSets( $fields, array_keys( $data ) );
     198        }
     199
     200        public function test_get_item_no_permission() {
     201                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     202                wp_set_current_user( self::$contributor_id );
     203                $response = rest_get_server()->dispatch( $request );
     204                $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
     205        }
     206
     207        public function test_get_item_missing_parent() {
     208                wp_set_current_user( self::$editor_id );
     209                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id );
     210                $response = rest_get_server()->dispatch( $request );
     211                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     212
     213        }
     214
     215        public function test_get_item_invalid_parent_post_type() {
     216                wp_set_current_user( self::$editor_id );
     217                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
     218                $response = rest_get_server()->dispatch( $request );
     219                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     220        }
     221
     222        public function test_delete_item() {
     223                // Doesn't exist.
     224        }
     225
     226        public function test_prepare_item() {
     227                wp_set_current_user( self::$editor_id );
     228                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     229                $response = rest_get_server()->dispatch( $request );
     230                $this->assertEquals( 200, $response->get_status() );
     231                $this->check_get_autosave_response( $response, $this->post_autosave );
     232        }
     233
     234        public function test_get_item_schema() {
     235                $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     236                $response   = rest_get_server()->dispatch( $request );
     237                $data       = $response->get_data();
     238                $properties = $data['schema']['properties'];
     239                $this->assertEquals( 13, count( $properties ) );
     240                $this->assertArrayHasKey( 'author', $properties );
     241                $this->assertArrayHasKey( 'content', $properties );
     242                $this->assertArrayHasKey( 'date', $properties );
     243                $this->assertArrayHasKey( 'date_gmt', $properties );
     244                $this->assertArrayHasKey( 'excerpt', $properties );
     245                $this->assertArrayHasKey( 'guid', $properties );
     246                $this->assertArrayHasKey( 'id', $properties );
     247                $this->assertArrayHasKey( 'modified', $properties );
     248                $this->assertArrayHasKey( 'modified_gmt', $properties );
     249                $this->assertArrayHasKey( 'parent', $properties );
     250                $this->assertArrayHasKey( 'slug', $properties );
     251                $this->assertArrayHasKey( 'title', $properties );
     252        }
     253
     254        public function test_create_item() {
     255                wp_set_current_user( self::$editor_id );
     256
     257                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     258                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     259
     260                $params = $this->set_post_data(
     261                        array(
     262                                'id' => self::$post_id,
     263                        )
     264                );
     265                $request->set_body_params( $params );
     266                $response = rest_get_server()->dispatch( $request );
     267
     268                $this->check_create_autosave_response( $response );
     269        }
     270
     271        public function test_update_item() {
     272                wp_set_current_user( self::$editor_id );
     273                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     274                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     275
     276                $params = $this->set_post_data(
     277                        array(
     278                                'id'     => self::$post_id,
     279                                'author' => self::$contributor_id,
     280                        )
     281                );
     282
     283                $request->set_body_params( $params );
     284                $response = rest_get_server()->dispatch( $request );
     285
     286                $this->check_create_autosave_response( $response );
     287        }
     288
     289        public function test_update_item_nopriv() {
     290                wp_set_current_user( self::$contributor_id );
     291
     292                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     293                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     294
     295                $params = $this->set_post_data(
     296                        array(
     297                                'id'     => self::$post_id,
     298                                'author' => self::$editor_id,
     299                        )
     300                );
     301
     302                $request->set_body_params( $params );
     303                $response = rest_get_server()->dispatch( $request );
     304
     305                $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
     306        }
     307
     308        public function test_rest_autosave_published_post() {
     309                wp_set_current_user( self::$editor_id );
     310
     311                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     312                $request->add_header( 'content-type', 'application/json' );
     313
     314                $current_post = get_post( self::$post_id );
     315
     316                $autosave_data = $this->set_post_data(
     317                        array(
     318                                'id'      => self::$post_id,
     319                                'content' => 'Updated post \ content',
     320                                'excerpt' => $current_post->post_excerpt,
     321                                'title'   => $current_post->post_title,
     322                        )
     323                );
     324
     325                $request->set_body( wp_json_encode( $autosave_data ) );
     326                $response = rest_get_server()->dispatch( $request );
     327                $new_data = $response->get_data();
     328
     329                $this->assertEquals( $current_post->ID, $new_data['parent'] );
     330                $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] );
     331                $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] );
     332                // Updated post_content
     333                $this->assertNotEquals( $current_post->post_content, $new_data['content']['raw'] );
     334
     335                $autosave_post = wp_get_post_autosave( self::$post_id );
     336                $this->assertEquals( $autosave_data['title'], $autosave_post->post_title );
     337                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     338                $this->assertEquals( $autosave_data['excerpt'], $autosave_post->post_excerpt );
     339        }
     340
     341        public function test_rest_autosave_draft_post_same_author() {
     342                wp_set_current_user( self::$editor_id );
     343
     344                $post_data = array(
     345                        'post_content' => 'Test post content',
     346                        'post_title'   => 'Test post title',
     347                        'post_excerpt' => 'Test post excerpt',
     348                );
     349                $post_id = wp_insert_post( $post_data );
     350
     351                $autosave_data = array(
     352                        'id' => $post_id,
     353                        'content' => 'Updated post \ content',
     354                        'title'   => 'Updated post title',
     355                );
     356
     357                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     358                $request->add_header( 'content-type', 'application/json' );
     359                $request->set_body( wp_json_encode( $autosave_data ) );
     360
     361                $response = rest_get_server()->dispatch( $request );
     362                $new_data = $response->get_data();
     363                $post = get_post( $post_id );
     364
     365                $this->assertEquals( $post_id, $new_data['id'] );
     366                // The draft post should be updated.
     367                $this->assertEquals( $autosave_data['content'], $new_data['content']['raw'] );
     368                $this->assertEquals( $autosave_data['title'], $new_data['title']['raw'] );
     369                $this->assertEquals( $autosave_data['content'], $post->post_content );
     370                $this->assertEquals( $autosave_data['title'], $post->post_title );
     371
     372                // Not updated.
     373                $this->assertEquals( $post_data['post_excerpt'], $post->post_excerpt );
     374
     375                wp_delete_post( $post_id );
     376        }
     377
     378        public function test_rest_autosave_draft_post_different_author() {
     379                wp_set_current_user( self::$editor_id );
     380
     381                $post_data = array(
     382                        'post_content' => 'Test post content',
     383                        'post_title'   => 'Test post title',
     384                        'post_excerpt' => 'Test post excerpt',
     385                        'post_author'  => self::$editor_id + 1,
     386                );
     387                $post_id = wp_insert_post( $post_data );
     388
     389                $autosave_data = array(
     390                        'id' => $post_id,
     391                        'content' => 'Updated post content',
     392                        'excerpt' => $post_data['post_excerpt'],
     393                        'title'   => $post_data['post_title'],
     394                );
     395
     396                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     397                $request->add_header( 'content-type', 'application/json' );
     398                $request->set_body( wp_json_encode( $autosave_data ) );
     399
     400                $response = rest_get_server()->dispatch( $request );
     401                $new_data = $response->get_data();
     402                $current_post = get_post( $post_id );
     403
     404                $this->assertEquals( $current_post->ID, $new_data['parent'] );
     405
     406                // The draft post shouldn't change.
     407                $this->assertEquals( $current_post->post_title, $post_data['post_title'] );
     408                $this->assertEquals( $current_post->post_content, $post_data['post_content'] );
     409                $this->assertEquals( $current_post->post_excerpt, $post_data['post_excerpt'] );
     410
     411                $autosave_post = wp_get_post_autosave( $post_id );
     412
     413                // No changes
     414                $this->assertEquals( $current_post->post_title, $autosave_post->post_title );
     415                $this->assertEquals( $current_post->post_excerpt, $autosave_post->post_excerpt );
     416
     417                // Has changes
     418                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     419
     420                wp_delete_post( $post_id );
     421        }
     422
     423        public function test_get_additional_field_registration() {
     424                $schema = array(
     425                        'type'        => 'integer',
     426                        'description' => 'Some integer of mine',
     427                        'enum'        => array( 1, 2, 3, 4 ),
     428                        'context'     => array( 'view', 'edit' ),
     429                );
     430
     431                register_rest_field(
     432                        'post-revision', 'my_custom_int', array(
     433                                'schema'          => $schema,
     434                                'get_callback'    => array( $this, 'additional_field_get_callback' ),
     435                                'update_callback' => array( $this, 'additional_field_update_callback' ),
     436                        )
     437                );
     438
     439                $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     440
     441                $response = rest_get_server()->dispatch( $request );
     442                $data     = $response->get_data();
     443
     444                $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
     445                $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
     446
     447                wp_set_current_user( 1 );
     448
     449                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     450
     451                $response = rest_get_server()->dispatch( $request );
     452                $this->assertArrayHasKey( 'my_custom_int', $response->data );
     453
     454                global $wp_rest_additional_fields;
     455                $wp_rest_additional_fields = array();
     456        }
     457
     458        public function additional_field_get_callback( $object ) {
     459                return get_post_meta( $object['id'], 'my_custom_int', true );
     460        }
     461
     462        public function additional_field_update_callback( $value, $post ) {
     463                update_post_meta( $post->ID, 'my_custom_int', $value );
     464        }
     465
     466        protected function check_get_autosave_response( $response, $autosave ) {
     467                if ( $response instanceof WP_REST_Response ) {
     468                        $links    = $response->get_links();
     469                        $response = $response->get_data();
     470                } else {
     471                        $this->assertArrayHasKey( '_links', $response );
     472                        $links = $response['_links'];
     473                }
     474
     475                $this->assertEquals( $autosave->post_author, $response['author'] );
     476
     477                $rendered_content = apply_filters( 'the_content', $autosave->post_content );
     478                $this->assertEquals( $rendered_content, $response['content']['rendered'] );
     479
     480                $this->assertEquals( mysql_to_rfc3339( $autosave->post_date ), $response['date'] );
     481                $this->assertEquals( mysql_to_rfc3339( $autosave->post_date_gmt ), $response['date_gmt'] );
     482
     483                $rendered_guid = apply_filters( 'get_the_guid', $autosave->guid, $autosave->ID );
     484                $this->assertEquals( $rendered_guid, $response['guid']['rendered'] );
     485
     486                $this->assertEquals( $autosave->ID, $response['id'] );
     487                $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified ), $response['modified'] );
     488                $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified_gmt ), $response['modified_gmt'] );
     489                $this->assertEquals( $autosave->post_name, $response['slug'] );
     490
     491                $rendered_title = get_the_title( $autosave->ID );
     492                $this->assertEquals( $rendered_title, $response['title']['rendered'] );
     493
     494                $parent            = get_post( $autosave->post_parent );
     495                $parent_controller = new WP_REST_Posts_Controller( $parent->post_type );
     496                $parent_object     = get_post_type_object( $parent->post_type );
     497                $parent_base       = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name;
     498                $this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $autosave->post_parent ), $links['parent'][0]['href'] );
     499        }
     500
     501        public function test_get_item_sets_up_postdata() {
     502                wp_set_current_user( self::$editor_id );
     503                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     504                rest_get_server()->dispatch( $request );
     505
     506                $post           = get_post();
     507                $parent_post_id = wp_is_post_revision( $post->ID );
     508
     509                $this->assertEquals( $post->ID, self::$autosave_post_id );
     510                $this->assertEquals( $parent_post_id, self::$post_id );
     511        }
     512
     513}
  • tests/phpunit/tests/rest-api/rest-controller.php

    diff --git a/tests/phpunit/tests/rest-api/rest-controller.php b/tests/phpunit/tests/rest-api/rest-controller.php
    index b9144685ad..88941ec1ee 100644
    a b class WP_Test_REST_Controller extends WP_Test_REST_TestCase { 
    213213                        'someinteger',
    214214                ), $fields );
    215215        }
    216 
    217         public function test_add_additional_fields_to_object_respects_fields_param() {
    218                 $controller = new WP_REST_Test_Controller();
    219                 $request    = new WP_REST_Request( 'GET', '/wp/v2/testroute' );
    220                 $schema     = $controller->get_item_schema();
    221                 $field      = 'somefield';
    222 
    223                 $listener = new MockAction();
    224                 $method = 'action';
    225 
    226                 register_rest_field(
    227                         $schema['title'],
    228                         $field,
    229                         array(
    230                                 'get_callback' => array( $listener, $method ),
    231                                 'schema'       => array(
    232                                         'type' => 'string',
    233                                 ),
    234                         )
    235                 );
    236 
    237                 $item = array();
    238 
    239                 $controller->prepare_item_for_response( $item, $request );
    240 
    241                 $first_call_count = $listener->get_call_count( $method );
    242 
    243                 $this->assertTrue( $first_call_count > 0 );
    244 
    245                 $request->set_param( '_fields', 'somestring' );
    246 
    247                 $controller->prepare_item_for_response( $item, $request );
    248 
    249                 $this->assertSame( $first_call_count, $listener->get_call_count( $method ) );
    250 
    251                 $request->set_param( '_fields', $field );
    252 
    253                 $controller->prepare_item_for_response( $item, $request );
    254 
    255                 $this->assertTrue( $listener->get_call_count( $method ) > $first_call_count );
    256         }
    257216}
  • tests/phpunit/tests/rest-api/rest-schema-setup.php

    diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php
    index e51fcc796d..e19a46d47d 100644
    a b class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    8989                        '/wp/v2/posts/(?P<id>[\\d]+)',
    9090                        '/wp/v2/posts/(?P<parent>[\\d]+)/revisions',
    9191                        '/wp/v2/posts/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)',
     92                        '/wp/v2/posts/(?P<parent>[\\d]+)/autosaves',
     93                        '/wp/v2/posts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)',
    9294                        '/wp/v2/pages',
    9395                        '/wp/v2/pages/(?P<id>[\\d]+)',
    9496                        '/wp/v2/pages/(?P<parent>[\\d]+)/revisions',
    9597                        '/wp/v2/pages/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)',
     98                        '/wp/v2/pages/(?P<parent>[\\d]+)/autosaves',
     99                        '/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)',
    96100                        '/wp/v2/media',
    97101                        '/wp/v2/media/(?P<id>[\\d]+)',
    98102                        '/wp/v2/types',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    111115                        '/wp/v2/comments',
    112116                        '/wp/v2/comments/(?P<id>[\\d]+)',
    113117                        '/wp/v2/settings',
    114                         '/wp/v2/themes',
    115118                );
    116119
    117120                $this->assertEquals( $expected_routes, $routes );
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    154157                $post_revisions = array_values( wp_get_post_revisions( $post_id ) );
    155158                $post_revision_id = $post_revisions[ count( $post_revisions ) - 1 ]->ID;
    156159
     160                // Create an autosave.
     161                wp_create_post_autosave(
     162                        array(
     163                                'post_ID'      => $post_id,
     164                                'post_content' => 'Autosave post content.',
     165                                'post_type'    => 'post',
     166                        )
     167                );
     168
     169
    157170                $page_id = $this->factory->post->create( array(
    158171                        'post_type'      => 'page',
    159172                        'post_name'      => 'restapi-client-fixture-page',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    171184                $page_revisions = array_values( wp_get_post_revisions( $page_id ) );
    172185                $page_revision_id = $page_revisions[ count( $page_revisions ) - 1 ]->ID;
    173186
     187                // Create an autosave.
     188                wp_create_post_autosave(
     189                        array(
     190                                'post_ID'      => $page_id,
     191                                'post_content' => 'Autosave page content.',
     192                                'post_type'    => 'page',
     193                        )
     194                );
     195
    174196                $tag_id = $this->factory->tag->create( array(
    175197                        'name'        => 'REST API Client Fixture: Tag',
    176198                        'slug'        => 'restapi-client-fixture-tag',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    256278                                'route' => '/wp/v2/posts/' . $post_id . '/revisions/' . $post_revision_id,
    257279                                'name'  => 'revision',
    258280                        ),
     281                        array(
     282                                'route' => '/wp/v2/posts/' . $post_id . '/autosaves',
     283                                'name'  => 'postAutosaves',
     284                        ),
     285                        array(
     286                                'route' => '/wp/v2/posts/' . $post_id . '/autosaves/' . $post_revision_id,
     287                                'name'  => 'autosave',
     288                        ),
    259289                        array(
    260290                                'route' => '/wp/v2/pages',
    261291                                'name'  => 'PagesCollection',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    272302                                'route' => '/wp/v2/pages/'. $page_id . '/revisions/' . $page_revision_id,
    273303                                'name'  => 'pageRevision',
    274304                        ),
     305                        array(
     306                                'route' => '/wp/v2/pages/' . $page_id . '/autosaves',
     307                                'name'  => 'pageAutosaves',
     308                        ),
     309                        array(
     310                                'route' => '/wp/v2/pages/' . $page_id . '/autosaves/' . $page_revision_id,
     311                                'name'  => 'pageAutosave',
     312                        ),
    275313                        array(
    276314                                'route' => '/wp/v2/media',
    277315                                'name'  => 'MediaCollection',
  • tests/phpunit/tests/rest-api/rest-test-controller.php

    diff --git a/tests/phpunit/tests/rest-api/rest-test-controller.php b/tests/phpunit/tests/rest-api/rest-test-controller.php
    index a8c3fefe0a..5b90f76e44 100644
    a b  
    1010 * @group restapi
    1111 */
    1212class WP_REST_Test_Controller extends WP_REST_Controller {
    13         /**
    14          * Prepares the item for the REST response.
    15          *
    16          * @param mixed           $item    WordPress representation of the item.
    17          * @param WP_REST_Request $request Request object.
    18          * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
    19          */
    20         public function prepare_item_for_response( $item, $request ) {
    21                 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
    22                 $item = $this->add_additional_fields_to_object( $item, $request );
    23                 $item = $this->filter_response_by_context( $item, $context );
    24                 $response = rest_ensure_response( $item );
    25                 return $response;
    26         }
    2713
    2814        /**
    29          * Get the item's schema, conforming to JSON Schema.
     15         * Get the Post type's schema, conforming to JSON Schema
    3016         *
    3117         * @return array
    3218         */
    class WP_REST_Test_Controller extends WP_REST_Controller { 
    8672                        ),
    8773                );
    8874
    89                 return $this->add_additional_fields_schema( $schema );
     75                return $schema;
    9076        }
    9177
    9278}
  • deleted file tests/phpunit/tests/rest-api/rest-themes-controller.php

    diff --git a/tests/phpunit/tests/rest-api/rest-themes-controller.php b/tests/phpunit/tests/rest-api/rest-themes-controller.php
    deleted file mode 100644
    index 590f91ec26..0000000000
    + -  
    1 <?php
    2 /**
    3  * Unit tests covering WP_REST_Themes_Controller functionality.
    4  *
    5  * @package WordPress
    6  * @subpackage REST API
    7  */
    8 
    9 /**
    10  * @group restapi-themes
    11  * @group restapi
    12  */
    13 class WP_Test_REST_Themes_Controller extends WP_Test_REST_Controller_Testcase {
    14         /**
    15          * Subscriber user ID.
    16          *
    17          * @since 5.0.0
    18          *
    19          * @var int $subscriber_id
    20          */
    21         protected static $subscriber_id;
    22 
    23         /**
    24          * Contributor user ID.
    25          *
    26          * @since 5.0.0
    27          *
    28          * @var int $contributor_id
    29          */
    30         protected static $contributor_id;
    31 
    32         /**
    33          * The current theme object.
    34          *
    35          * @since 5.0.0
    36          *
    37          * @var WP_Theme $current_theme
    38          */
    39         protected static $current_theme;
    40 
    41         /**
    42          * The REST API route for themes.
    43          *
    44          * @since 5.0.0
    45          *
    46          * @var string $themes_route
    47          */
    48         protected static $themes_route = '/wp/v2/themes';
    49 
    50         /**
    51          * Performs a REST API request for the active theme.
    52          *
    53          * @since 5.0.0
    54          *
    55          * @param string $method Optional. Request method. Default GET.
    56          * @return WP_REST_Response The request's response.
    57          */
    58         protected function perform_active_theme_request( $method = 'GET' ) {
    59                 $request = new WP_REST_Request( $method, self::$themes_route );
    60                 $request->set_param( 'status', 'active' );
    61 
    62                 return rest_get_server()->dispatch( $request );
    63         }
    64 
    65         /**
    66          * Check that common properties are included in a response.
    67          *
    68          * @since 5.0.0
    69          *
    70          * @param WP_REST_Response $response Current REST API response.
    71          */
    72         protected function check_get_theme_response( $response ) {
    73                 if ( $response instanceof WP_REST_Response ) {
    74                         $headers  = $response->get_headers();
    75                         $response = $response->get_data();
    76                 } else {
    77                         $headers = array();
    78                 }
    79 
    80                 $this->assertArrayHasKey( 'X-WP-Total', $headers );
    81                 $this->assertEquals( 1, $headers['X-WP-Total'] );
    82                 $this->assertArrayHasKey( 'X-WP-TotalPages', $headers );
    83                 $this->assertEquals( 1, $headers['X-WP-TotalPages'] );
    84         }
    85 
    86         /**
    87          * Set up class test fixtures.
    88          *
    89          * @since 5.0.0
    90          *
    91          * @param WP_UnitTest_Factory $factory WordPress unit test factory.
    92          */
    93         public static function wpSetUpBeforeClass( $factory ) {
    94                 self::$subscriber_id  = $factory->user->create(
    95                         array(
    96                                 'role' => 'subscriber',
    97                         )
    98                 );
    99                 self::$contributor_id = $factory->user->create(
    100                         array(
    101                                 'role' => 'contributor',
    102                         )
    103                 );
    104                 self::$current_theme  = wp_get_theme();
    105 
    106                 wp_set_current_user( self::$contributor_id );
    107         }
    108 
    109         /**
    110          * Clean up test fixtures.
    111          *
    112          * @since 5.0.0
    113          */
    114         public static function wpTearDownAfterClass() {
    115                 self::delete_user( self::$subscriber_id );
    116                 self::delete_user( self::$contributor_id );
    117         }
    118 
    119         /**
    120          * Set up each test method.
    121          *
    122          * @since 5.0.0
    123          */
    124         public function setUp() {
    125                 parent::setUp();
    126 
    127                 wp_set_current_user( self::$contributor_id );
    128         }
    129 
    130         /**
    131          * Theme routes should be registered correctly.
    132          *
    133          * @ticket 45016
    134          */
    135         public function test_register_routes() {
    136                 $routes = rest_get_server()->get_routes();
    137                 $this->assertArrayHasKey( self::$themes_route, $routes );
    138         }
    139 
    140         /**
    141          * Test retrieving a collection of themes.
    142          *
    143          * @ticket 45016
    144          */
    145         public function test_get_items() {
    146                 $response = self::perform_active_theme_request();
    147 
    148                 $this->assertEquals( 200, $response->get_status() );
    149                 $data = $response->get_data();
    150 
    151                 $this->check_get_theme_response( $response );
    152                 $fields = array(
    153                         'theme_supports',
    154                 );
    155                 $this->assertEqualSets( $fields, array_keys( $data[0] ) );
    156         }
    157 
    158         /**
    159          * An error should be returned when the user does not have the edit_posts capability.
    160          *
    161          * @ticket 45016
    162          */
    163         public function test_get_items_no_permission() {
    164                 wp_set_current_user( self::$subscriber_id );
    165                 $response = self::perform_active_theme_request();
    166                 $this->assertErrorResponse( 'rest_user_cannot_view', $response, 403 );
    167         }
    168 
    169         /**
    170          * Test an item is prepared for the response.
    171          *
    172          * @ticket 45016
    173          */
    174         public function test_prepare_item() {
    175                 $response = self::perform_active_theme_request();
    176                 $this->assertEquals( 200, $response->get_status() );
    177                 $this->check_get_theme_response( $response );
    178         }
    179 
    180         /**
    181          * Verify the theme schema.
    182          *
    183          * @ticket 45016
    184          */
    185         public function test_get_item_schema() {
    186                 $response   = self::perform_active_theme_request( 'OPTIONS' );
    187                 $data       = $response->get_data();
    188                 $properties = $data['schema']['properties'];
    189                 $this->assertEquals( 1, count( $properties ) );
    190                 $this->assertArrayHasKey( 'theme_supports', $properties );
    191 
    192                 $this->assertEquals( 2, count( $properties['theme_supports']['properties'] ) );
    193                 $this->assertArrayHasKey( 'formats', $properties['theme_supports']['properties'] );
    194                 $this->assertArrayHasKey( 'post-thumbnails', $properties['theme_supports']['properties'] );
    195         }
    196 
    197         /**
    198          * Should include relevant data in the 'theme_supports' key.
    199          *
    200          * @ticket 45016
    201          */
    202         public function test_theme_supports_formats() {
    203                 remove_theme_support( 'post-formats' );
    204                 $response = self::perform_active_theme_request();
    205                 $result   = $response->get_data();
    206                 $this->assertTrue( isset( $result[0]['theme_supports'] ) );
    207                 $this->assertTrue( isset( $result[0]['theme_supports']['formats'] ) );
    208                 $this->assertSame( array( 'standard' ), $result[0]['theme_supports']['formats'] );
    209         }
    210 
    211         /**
    212          * Test when a theme only supports some post formats.
    213          *
    214          * @ticket 45016
    215          */
    216         public function test_theme_supports_formats_non_default() {
    217                 add_theme_support( 'post-formats', array( 'aside', 'video' ) );
    218                 $response = self::perform_active_theme_request();
    219                 $result   = $response->get_data();
    220                 $this->assertTrue( isset( $result[0]['theme_supports'] ) );
    221                 $this->assertTrue( isset( $result[0]['theme_supports']['formats'] ) );
    222                 $this->assertSame( array( 'standard', 'aside', 'video' ), $result[0]['theme_supports']['formats'] );
    223         }
    224 
    225         /**
    226          * Test when a theme does not support post thumbnails.
    227          *
    228          * @ticket 45016
    229          */
    230         public function test_theme_supports_post_thumbnails_false() {
    231                 remove_theme_support( 'post-thumbnails' );
    232                 $response = self::perform_active_theme_request();
    233 
    234                 $result = $response->get_data();
    235                 $this->assertTrue( isset( $result[0]['theme_supports'] ) );
    236                 $this->assertTrue( isset( $result[0]['theme_supports']['post-thumbnails'] ) );
    237                 $this->assertFalse( $result[0]['theme_supports']['post-thumbnails'] );
    238         }
    239 
    240         /**
    241          * Test when a theme supports all post thumbnails.
    242          *
    243          * @ticket 45016
    244          */
    245         public function test_theme_supports_post_thumbnails_true() {
    246                 remove_theme_support( 'post-thumbnails' );
    247                 add_theme_support( 'post-thumbnails' );
    248                 $response = self::perform_active_theme_request();
    249                 $result   = $response->get_data();
    250                 $this->assertTrue( isset( $result[0]['theme_supports'] ) );
    251                 $this->assertTrue( $result[0]['theme_supports']['post-thumbnails'] );
    252         }
    253 
    254         /**
    255          * Test when a theme only supports post thumbnails for certain post types.
    256          *
    257          * @ticket 45016
    258          */
    259         public function test_theme_supports_post_thumbnails_array() {
    260                 remove_theme_support( 'post-thumbnails' );
    261                 add_theme_support( 'post-thumbnails', array( 'post' ) );
    262                 $response = self::perform_active_theme_request();
    263                 $result   = $response->get_data();
    264                 $this->assertTrue( isset( $result[0]['theme_supports'] ) );
    265                 $this->assertEquals( array( 'post' ), $result[0]['theme_supports']['post-thumbnails'] );
    266         }
    267 
    268         /**
    269          * It should be possible to register custom fields to the endpoint.
    270          *
    271          * @ticket 45016
    272          */
    273         public function test_get_additional_field_registration() {
    274                 $schema = array(
    275                         'type'        => 'integer',
    276                         'description' => 'Some integer of mine',
    277                         'enum'        => array( 1, 2, 3, 4 ),
    278                 );
    279 
    280                 register_rest_field(
    281                         'theme',
    282                         'my_custom_int',
    283                         array(
    284                                 'schema'       => $schema,
    285                                 'get_callback' => array( $this, 'additional_field_get_callback' ),
    286                         )
    287                 );
    288 
    289                 $response = self::perform_active_theme_request( 'OPTIONS' );
    290                 $data     = $response->get_data();
    291 
    292                 $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
    293                 $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
    294 
    295                 $response = self::perform_active_theme_request( 'GET' );
    296                 $data     = $response->get_data();
    297                 $this->assertArrayHasKey( 'my_custom_int', $data[0] );
    298                 $this->assertSame( 2, $data[0]['my_custom_int'] );
    299 
    300                 global $wp_rest_additional_fields;
    301                 $wp_rest_additional_fields = array();
    302         }
    303 
    304         /**
    305          * Return a value for the custom field.
    306          *
    307          * @since 5.0.0
    308          *
    309          * @param array $theme Theme data array.
    310          * @return int Additional field value.
    311          */
    312         public function additional_field_get_callback( $theme ) {
    313                 return 2;
    314         }
    315 
    316         /**
    317          * The create_item() method does not exist for themes.
    318          */
    319         public function test_create_item() {}
    320 
    321         /**
    322          * The update_item() method does not exist for themes.
    323          */
    324         public function test_update_item() {}
    325 
    326         /**
    327          * The get_item() method does not exist for themes.
    328          */
    329         public function test_get_item() {}
    330 
    331         /**
    332          * The delete_item() method does not exist for themes.
    333          */
    334         public function test_delete_item() {}
    335 
    336         /**
    337          * Context is not supported for themes.
    338          */
    339         public function test_context_param() {}
    340 }
  • tests/qunit/fixtures/wp-api-generated.js

    diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js
    index 6b9d530b46..524f31e5be 100644
    a b mockedApiResponse.Schema = { 
    850850                }
    851851            ]
    852852        },
     853        "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves": {
     854            "namespace": "wp/v2",
     855            "methods": [
     856                "GET",
     857                "POST"
     858            ],
     859            "endpoints": [
     860                {
     861                    "methods": [
     862                        "GET"
     863                    ],
     864                    "args": {
     865                        "parent": {
     866                            "required": false,
     867                            "description": "The ID for the parent of the object.",
     868                            "type": "integer"
     869                        },
     870                        "context": {
     871                            "required": false,
     872                            "default": "view",
     873                            "enum": [
     874                                "view",
     875                                "embed",
     876                                "edit"
     877                            ],
     878                            "description": "Scope under which the request is made; determines fields present in response.",
     879                            "type": "string"
     880                        },
     881                        "page": {
     882                            "required": false,
     883                            "default": 1,
     884                            "description": "Current page of the collection.",
     885                            "type": "integer"
     886                        },
     887                        "per_page": {
     888                            "required": false,
     889                            "description": "Maximum number of items to be returned in result set.",
     890                            "type": "integer"
     891                        },
     892                        "search": {
     893                            "required": false,
     894                            "description": "Limit results to those matching a string.",
     895                            "type": "string"
     896                        },
     897                        "exclude": {
     898                            "required": false,
     899                            "default": [],
     900                            "description": "Ensure result set excludes specific IDs.",
     901                            "type": "array",
     902                            "items": {
     903                                "type": "integer"
     904                            }
     905                        },
     906                        "include": {
     907                            "required": false,
     908                            "default": [],
     909                            "description": "Limit result set to specific IDs.",
     910                            "type": "array",
     911                            "items": {
     912                                "type": "integer"
     913                            }
     914                        },
     915                        "offset": {
     916                            "required": false,
     917                            "description": "Offset the result set by a specific number of items.",
     918                            "type": "integer"
     919                        },
     920                        "order": {
     921                            "required": false,
     922                            "default": "desc",
     923                            "enum": [
     924                                "asc",
     925                                "desc"
     926                            ],
     927                            "description": "Order sort attribute ascending or descending.",
     928                            "type": "string"
     929                        },
     930                        "orderby": {
     931                            "required": false,
     932                            "default": "date",
     933                            "enum": [
     934                                "date",
     935                                "id",
     936                                "include",
     937                                "relevance",
     938                                "slug",
     939                                "include_slugs",
     940                                "title"
     941                            ],
     942                            "description": "Sort collection by object attribute.",
     943                            "type": "string"
     944                        }
     945                    }
     946                },
     947                {
     948                    "methods": [
     949                        "POST"
     950                    ],
     951                    "args": {
     952                        "parent": {
     953                            "required": false,
     954                            "description": "The ID for the parent of the object.",
     955                            "type": "integer"
     956                        },
     957                        "author": {
     958                            "required": false,
     959                            "description": "The ID for the author of the object.",
     960                            "type": "integer"
     961                        },
     962                        "date": {
     963                            "required": false,
     964                            "description": "The date the object was published, in the site's timezone.",
     965                            "type": "string"
     966                        },
     967                        "date_gmt": {
     968                            "required": false,
     969                            "description": "The date the object was published, as GMT.",
     970                            "type": "string"
     971                        },
     972                        "id": {
     973                            "required": false,
     974                            "description": "Unique identifier for the object.",
     975                            "type": "integer"
     976                        },
     977                        "modified": {
     978                            "required": false,
     979                            "description": "The date the object was last modified, in the site's timezone.",
     980                            "type": "string"
     981                        },
     982                        "modified_gmt": {
     983                            "required": false,
     984                            "description": "The date the object was last modified, as GMT.",
     985                            "type": "string"
     986                        },
     987                        "slug": {
     988                            "required": false,
     989                            "description": "An alphanumeric identifier for the object unique to its type.",
     990                            "type": "string"
     991                        },
     992                        "title": {
     993                            "required": false,
     994                            "description": "The title for the object.",
     995                            "type": "object"
     996                        },
     997                        "content": {
     998                            "required": false,
     999                            "description": "The content for the object.",
     1000                            "type": "object"
     1001                        },
     1002                        "excerpt": {
     1003                            "required": false,
     1004                            "description": "The excerpt for the object.",
     1005                            "type": "object"
     1006                        }
     1007                    }
     1008                }
     1009            ]
     1010        },
     1011        "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     1012            "namespace": "wp/v2",
     1013            "methods": [
     1014                "GET"
     1015            ],
     1016            "endpoints": [
     1017                {
     1018                    "methods": [
     1019                        "GET"
     1020                    ],
     1021                    "args": {
     1022                        "parent": {
     1023                            "required": false,
     1024                            "description": "The ID for the parent of the object.",
     1025                            "type": "integer"
     1026                        },
     1027                        "id": {
     1028                            "required": false,
     1029                            "description": "The ID for the object.",
     1030                            "type": "integer"
     1031                        },
     1032                        "context": {
     1033                            "required": false,
     1034                            "default": "view",
     1035                            "enum": [
     1036                                "view",
     1037                                "embed",
     1038                                "edit"
     1039                            ],
     1040                            "description": "Scope under which the request is made; determines fields present in response.",
     1041                            "type": "string"
     1042                        }
     1043                    }
     1044                }
     1045            ]
     1046        },
    8531047        "/wp/v2/pages": {
    8541048            "namespace": "wp/v2",
    8551049            "methods": [
    mockedApiResponse.Schema = { 
    12941488                        "force": {
    12951489                            "required": false,
    12961490                            "default": false,
    1297                             "description": "Whether to bypass trash and force deletion.",
     1491                            "description": "Whether to bypass trash and force deletion.",
     1492                            "type": "boolean"
     1493                        }
     1494                    }
     1495                }
     1496            ]
     1497        },
     1498        "/wp/v2/pages/(?P<parent>[\\d]+)/revisions": {
     1499            "namespace": "wp/v2",
     1500            "methods": [
     1501                "GET"
     1502            ],
     1503            "endpoints": [
     1504                {
     1505                    "methods": [
     1506                        "GET"
     1507                    ],
     1508                    "args": {
     1509                        "parent": {
     1510                            "required": false,
     1511                            "description": "The ID for the parent of the object.",
     1512                            "type": "integer"
     1513                        },
     1514                        "context": {
     1515                            "required": false,
     1516                            "default": "view",
     1517                            "enum": [
     1518                                "view",
     1519                                "embed",
     1520                                "edit"
     1521                            ],
     1522                            "description": "Scope under which the request is made; determines fields present in response.",
     1523                            "type": "string"
     1524                        },
     1525                        "page": {
     1526                            "required": false,
     1527                            "default": 1,
     1528                            "description": "Current page of the collection.",
     1529                            "type": "integer"
     1530                        },
     1531                        "per_page": {
     1532                            "required": false,
     1533                            "description": "Maximum number of items to be returned in result set.",
     1534                            "type": "integer"
     1535                        },
     1536                        "search": {
     1537                            "required": false,
     1538                            "description": "Limit results to those matching a string.",
     1539                            "type": "string"
     1540                        },
     1541                        "exclude": {
     1542                            "required": false,
     1543                            "default": [],
     1544                            "description": "Ensure result set excludes specific IDs.",
     1545                            "type": "array",
     1546                            "items": {
     1547                                "type": "integer"
     1548                            }
     1549                        },
     1550                        "include": {
     1551                            "required": false,
     1552                            "default": [],
     1553                            "description": "Limit result set to specific IDs.",
     1554                            "type": "array",
     1555                            "items": {
     1556                                "type": "integer"
     1557                            }
     1558                        },
     1559                        "offset": {
     1560                            "required": false,
     1561                            "description": "Offset the result set by a specific number of items.",
     1562                            "type": "integer"
     1563                        },
     1564                        "order": {
     1565                            "required": false,
     1566                            "default": "desc",
     1567                            "enum": [
     1568                                "asc",
     1569                                "desc"
     1570                            ],
     1571                            "description": "Order sort attribute ascending or descending.",
     1572                            "type": "string"
     1573                        },
     1574                        "orderby": {
     1575                            "required": false,
     1576                            "default": "date",
     1577                            "enum": [
     1578                                "date",
     1579                                "id",
     1580                                "include",
     1581                                "relevance",
     1582                                "slug",
     1583                                "include_slugs",
     1584                                "title"
     1585                            ],
     1586                            "description": "Sort collection by object attribute.",
     1587                            "type": "string"
     1588                        }
     1589                    }
     1590                }
     1591            ]
     1592        },
     1593        "/wp/v2/pages/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)": {
     1594            "namespace": "wp/v2",
     1595            "methods": [
     1596                "GET",
     1597                "DELETE"
     1598            ],
     1599            "endpoints": [
     1600                {
     1601                    "methods": [
     1602                        "GET"
     1603                    ],
     1604                    "args": {
     1605                        "parent": {
     1606                            "required": false,
     1607                            "description": "The ID for the parent of the object.",
     1608                            "type": "integer"
     1609                        },
     1610                        "id": {
     1611                            "required": false,
     1612                            "description": "Unique identifier for the object.",
     1613                            "type": "integer"
     1614                        },
     1615                        "context": {
     1616                            "required": false,
     1617                            "default": "view",
     1618                            "enum": [
     1619                                "view",
     1620                                "embed",
     1621                                "edit"
     1622                            ],
     1623                            "description": "Scope under which the request is made; determines fields present in response.",
     1624                            "type": "string"
     1625                        }
     1626                    }
     1627                },
     1628                {
     1629                    "methods": [
     1630                        "DELETE"
     1631                    ],
     1632                    "args": {
     1633                        "parent": {
     1634                            "required": false,
     1635                            "description": "The ID for the parent of the object.",
     1636                            "type": "integer"
     1637                        },
     1638                        "id": {
     1639                            "required": false,
     1640                            "description": "Unique identifier for the object.",
     1641                            "type": "integer"
     1642                        },
     1643                        "force": {
     1644                            "required": false,
     1645                            "default": false,
     1646                            "description": "Required to be true, as revisions do not support trashing.",
    12981647                            "type": "boolean"
    12991648                        }
    13001649                    }
    13011650                }
    13021651            ]
    13031652        },
    1304         "/wp/v2/pages/(?P<parent>[\\d]+)/revisions": {
     1653        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves": {
    13051654            "namespace": "wp/v2",
    13061655            "methods": [
    1307                 "GET"
     1656                "GET",
     1657                "POST"
    13081658            ],
    13091659            "endpoints": [
    13101660                {
    mockedApiResponse.Schema = { 
    13931743                            "type": "string"
    13941744                        }
    13951745                    }
    1396                 }
    1397             ]
    1398         },
    1399         "/wp/v2/pages/(?P<parent>[\\d]+)/revisions/(?P<id>[\\d]+)": {
    1400             "namespace": "wp/v2",
    1401             "methods": [
    1402                 "GET",
    1403                 "DELETE"
    1404             ],
    1405             "endpoints": [
     1746                },
    14061747                {
    14071748                    "methods": [
    1408                         "GET"
     1749                        "POST"
    14091750                    ],
    14101751                    "args": {
    14111752                        "parent": {
    mockedApiResponse.Schema = { 
    14131754                            "description": "The ID for the parent of the object.",
    14141755                            "type": "integer"
    14151756                        },
     1757                        "author": {
     1758                            "required": false,
     1759                            "description": "The ID for the author of the object.",
     1760                            "type": "integer"
     1761                        },
     1762                        "date": {
     1763                            "required": false,
     1764                            "description": "The date the object was published, in the site's timezone.",
     1765                            "type": "string"
     1766                        },
     1767                        "date_gmt": {
     1768                            "required": false,
     1769                            "description": "The date the object was published, as GMT.",
     1770                            "type": "string"
     1771                        },
    14161772                        "id": {
    14171773                            "required": false,
    14181774                            "description": "Unique identifier for the object.",
    14191775                            "type": "integer"
    14201776                        },
    1421                         "context": {
     1777                        "modified": {
    14221778                            "required": false,
    1423                             "default": "view",
    1424                             "enum": [
    1425                                 "view",
    1426                                 "embed",
    1427                                 "edit"
    1428                             ],
    1429                             "description": "Scope under which the request is made; determines fields present in response.",
     1779                            "description": "The date the object was last modified, in the site's timezone.",
     1780                            "type": "string"
     1781                        },
     1782                        "modified_gmt": {
     1783                            "required": false,
     1784                            "description": "The date the object was last modified, as GMT.",
    14301785                            "type": "string"
     1786                        },
     1787                        "slug": {
     1788                            "required": false,
     1789                            "description": "An alphanumeric identifier for the object unique to its type.",
     1790                            "type": "string"
     1791                        },
     1792                        "title": {
     1793                            "required": false,
     1794                            "description": "The title for the object.",
     1795                            "type": "object"
     1796                        },
     1797                        "content": {
     1798                            "required": false,
     1799                            "description": "The content for the object.",
     1800                            "type": "object"
     1801                        },
     1802                        "excerpt": {
     1803                            "required": false,
     1804                            "description": "The excerpt for the object.",
     1805                            "type": "object"
    14311806                        }
    14321807                    }
    1433                 },
     1808                }
     1809            ]
     1810        },
     1811        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     1812            "namespace": "wp/v2",
     1813            "methods": [
     1814                "GET"
     1815            ],
     1816            "endpoints": [
    14341817                {
    14351818                    "methods": [
    1436                         "DELETE"
     1819                        "GET"
    14371820                    ],
    14381821                    "args": {
    14391822                        "parent": {
    mockedApiResponse.Schema = { 
    14431826                        },
    14441827                        "id": {
    14451828                            "required": false,
    1446                             "description": "Unique identifier for the object.",
     1829                            "description": "The ID for the object.",
    14471830                            "type": "integer"
    14481831                        },
    1449                         "force": {
     1832                        "context": {
    14501833                            "required": false,
    1451                             "default": false,
    1452                             "description": "Required to be true, as revisions do not support trashing.",
    1453                             "type": "boolean"
     1834                            "default": "view",
     1835                            "enum": [
     1836                                "view",
     1837                                "embed",
     1838                                "edit"
     1839                            ],
     1840                            "description": "Scope under which the request is made; determines fields present in response.",
     1841                            "type": "string"
    14541842                        }
    14551843                    }
    14561844                }
    mockedApiResponse.Schema = { 
    35173905            "_links": {
    35183906                "self": "http://example.org/index.php?rest_route=/wp/v2/settings"
    35193907            }
    3520         },
    3521         "/wp/v2/themes": {
    3522             "namespace": "wp/v2",
    3523             "methods": [
    3524                 "GET"
    3525             ],
    3526             "endpoints": [
    3527                 {
    3528                     "methods": [
    3529                         "GET"
    3530                     ],
    3531                     "args": {
    3532                         "context": {
    3533                             "required": false,
    3534                             "description": "Scope under which the request is made; determines fields present in response.",
    3535                             "type": "string"
    3536                         },
    3537                         "page": {
    3538                             "required": false,
    3539                             "default": 1,
    3540                             "description": "Current page of the collection.",
    3541                             "type": "integer"
    3542                         },
    3543                         "per_page": {
    3544                             "required": false,
    3545                             "default": 10,
    3546                             "description": "Maximum number of items to be returned in result set.",
    3547                             "type": "integer"
    3548                         },
    3549                         "search": {
    3550                             "required": false,
    3551                             "description": "Limit results to those matching a string.",
    3552                             "type": "string"
    3553                         },
    3554                         "status": {
    3555                             "required": true,
    3556                             "description": "Limit result set to themes assigned one or more statuses.",
    3557                             "type": "array",
    3558                             "items": {
    3559                                 "enum": [
    3560                                     "active"
    3561                                 ],
    3562                                 "type": "string"
    3563                             }
    3564                         }
    3565                     }
    3566                 }
    3567             ],
    3568             "_links": {
    3569                 "self": "http://example.org/index.php?rest_route=/wp/v2/themes"
    3570             }
    35713908        }
    35723909    }
    35733910};
    mockedApiResponse.PostsCollection = [ 
    37744111            ],
    37754112            "version-history": [
    37764113                {
    3777                     "count": 1,
     4114                    "count": 2,
    37784115                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4/revisions"
    37794116                }
    37804117            ],
    mockedApiResponse.postRevisions = [ 
    38654202        "guid": {
    38664203            "rendered": "http://example.org/?p=5"
    38674204        },
     4205        "title": {
     4206            "rendered": ""
     4207        },
     4208        "content": {
     4209            "rendered": "<p>Autosave post content.</p>\n"
     4210        },
     4211        "excerpt": {
     4212            "rendered": ""
     4213        },
     4214        "_links": {
     4215            "parent": [
     4216                {
     4217                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4"
     4218                }
     4219            ]
     4220        }
     4221    },
     4222    {
     4223        "author": 67,
     4224        "date": "2017-02-14T00:00:00",
     4225        "date_gmt": "2017-02-14T00:00:00",
     4226        "id": 456,
     4227        "modified": "2017-02-14T00:00:00",
     4228        "modified_gmt": "2017-02-14T00:00:00",
     4229        "parent": 455,
     4230        "slug": "455-revision-v1",
     4231        "guid": {
     4232            "rendered": "http://example.org/?p=456"
     4233        },
    38684234        "title": {
    38694235            "rendered": "REST API Client Fixture: Post"
    38704236        },
    mockedApiResponse.postRevisions = [ 
    38774243        "_links": {
    38784244            "parent": [
    38794245                {
    3880                     "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4"
     4246                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/455"
    38814247                }
    38824248            ]
    38834249        }
    mockedApiResponse.revision = { 
    39074273    }
    39084274};
    39094275
     4276mockedApiResponse.postAutosaves = [
     4277    {
     4278        "author": 67,
     4279        "date": "2017-02-14T00:00:00",
     4280        "date_gmt": "2017-02-14T00:00:00",
     4281        "id": 457,
     4282        "modified": "2017-02-14T00:00:00",
     4283        "modified_gmt": "2017-02-14T00:00:00",
     4284        "parent": 455,
     4285        "slug": "455-autosave-v1",
     4286        "guid": {
     4287            "rendered": "http://example.org/?p=457"
     4288        },
     4289        "title": {
     4290            "rendered": ""
     4291        },
     4292        "content": {
     4293            "rendered": "<p>Autosave post content.</p>\n"
     4294        },
     4295        "excerpt": {
     4296            "rendered": ""
     4297        },
     4298        "_links": {
     4299            "parent": [
     4300                {
     4301                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/455"
     4302                }
     4303            ]
     4304        }
     4305    }
     4306];
     4307
     4308mockedApiResponse.autosave = {
     4309    "author": 67,
     4310    "date": "2017-02-14T00:00:00",
     4311    "date_gmt": "2017-02-14T00:00:00",
     4312    "id": 457,
     4313    "modified": "2017-02-14T00:00:00",
     4314    "modified_gmt": "2017-02-14T00:00:00",
     4315    "parent": 455,
     4316    "slug": "455-autosave-v1",
     4317    "guid": {
     4318        "rendered": "http://example.org/?p=457"
     4319    },
     4320    "title": {
     4321        "rendered": ""
     4322    },
     4323    "content": {
     4324        "rendered": "<p>Autosave post content.</p>\n"
     4325    },
     4326    "excerpt": {
     4327        "rendered": ""
     4328    }
     4329};
     4330
    39104331mockedApiResponse.PagesCollection = [
    39114332    {
    39124333        "id": 6,
    mockedApiResponse.PagesCollection = [ 
    39664387            ],
    39674388            "version-history": [
    39684389                {
    3969                     "count": 1,
     4390                    "count": 2,
    39704391                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6/revisions"
    39714392                }
    39724393            ],
    mockedApiResponse.pageRevisions = [ 
    40414462        "guid": {
    40424463            "rendered": "http://example.org/?p=7"
    40434464        },
     4465        "title": {
     4466            "rendered": ""
     4467        },
     4468        "content": {
     4469            "rendered": "<p>Autosave page content.</p>\n"
     4470        },
     4471        "excerpt": {
     4472            "rendered": ""
     4473        },
     4474        "_links": {
     4475            "parent": [
     4476                {
     4477                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
     4478                }
     4479            ]
     4480        }
     4481    },
     4482    {
     4483        "author": 67,
     4484        "date": "2017-02-14T00:00:00",
     4485        "date_gmt": "2017-02-14T00:00:00",
     4486        "id": 459,
     4487        "modified": "2017-02-14T00:00:00",
     4488        "modified_gmt": "2017-02-14T00:00:00",
     4489        "parent": 458,
     4490        "slug": "458-revision-v1",
     4491        "guid": {
     4492            "rendered": "http://example.org/?p=459"
     4493        },
    40444494        "title": {
    40454495            "rendered": "REST API Client Fixture: Page"
    40464496        },
    mockedApiResponse.pageRevisions = [ 
    40534503        "_links": {
    40544504            "parent": [
    40554505                {
    4056                     "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
     4506                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/458"
    40574507                }
    40584508            ]
    40594509        }
    mockedApiResponse.pageRevision = { 
    40834533    }
    40844534};
    40854535
     4536mockedApiResponse.pageAutosaves = [
     4537    {
     4538        "author": 67,
     4539        "date": "2017-02-14T00:00:00",
     4540        "date_gmt": "2017-02-14T00:00:00",
     4541        "id": 460,
     4542        "modified": "2017-02-14T00:00:00",
     4543        "modified_gmt": "2017-02-14T00:00:00",
     4544        "parent": 458,
     4545        "slug": "458-autosave-v1",
     4546        "guid": {
     4547            "rendered": "http://example.org/?p=460"
     4548        },
     4549        "title": {
     4550            "rendered": ""
     4551        },
     4552        "content": {
     4553            "rendered": "<p>Autosave page content.</p>\n"
     4554        },
     4555        "excerpt": {
     4556            "rendered": ""
     4557        },
     4558        "_links": {
     4559            "parent": [
     4560                {
     4561                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/458"
     4562                }
     4563            ]
     4564        }
     4565    }
     4566];
     4567
     4568mockedApiResponse.pageAutosave = {
     4569    "author": 67,
     4570    "date": "2017-02-14T00:00:00",
     4571    "date_gmt": "2017-02-14T00:00:00",
     4572    "id": 460,
     4573    "modified": "2017-02-14T00:00:00",
     4574    "modified_gmt": "2017-02-14T00:00:00",
     4575    "parent": 458,
     4576    "slug": "458-autosave-v1",
     4577    "guid": {
     4578        "rendered": "http://example.org/?p=460"
     4579    },
     4580    "title": {
     4581        "rendered": ""
     4582    },
     4583    "content": {
     4584        "rendered": "<p>Autosave page content.</p>\n"
     4585    },
     4586    "excerpt": {
     4587        "rendered": ""
     4588    }
     4589};
     4590
    40864591mockedApiResponse.MediaCollection = [
    40874592    {
    40884593        "id": 8,