WordPress.org

Make WordPress Core

Ticket #43316: 43316.18.diff

File 43316.18.diff, 51.9 KB (added by adamsilverstein, 3 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 82d856a063..0bb1bce426 100644
    a b function create_initial_rest_routes() { 
    189189
    190190                $controller->register_routes();
    191191
     192                if ( 'attachment' !== $post_type->name ) {
     193                        $controller = new WP_REST_Autosaves_Controller( $post_type->name );
     194                        $controller->register_routes();
     195                }
     196
    192197                if ( post_type_supports( $post_type->name, 'revisions' ) ) {
    193198                        $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
    194199                        $revisions_controller->register_routes();
  • 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..fe15b3f3f3
    - +  
     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                if ( ! defined( 'DOING_AUTOSAVE' ) ) {
     177                        define( 'DOING_AUTOSAVE', true );
     178                }
     179
     180                $post = get_post( $request->get_param( 'id' ) );
     181
     182                if ( is_wp_error( $post ) ) {
     183                        return $post;
     184                }
     185
     186                $prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
     187                $prepared_post->ID = $post->ID;
     188                $user_id           = get_current_user_id();
     189
     190                if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
     191                        // Draft posts for the same author: autosaving updates the post and does not create a revision.
     192                        // Convert the post object to an array and add slashes, wp_update_post expects escaped array.
     193                        $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
     194                } else {
     195                        // Non-draft posts: create or update the post autosave.
     196                        $autosave_id = $this->create_post_autosave( (array) $prepared_post );
     197                }
     198
     199                if ( is_wp_error( $autosave_id ) ) {
     200                        return $autosave_id;
     201                }
     202
     203                $autosave = get_post( $autosave_id );
     204                $request->set_param( 'context', 'edit' );
     205
     206                $response = $this->prepare_item_for_response( $autosave, $request );
     207                $response = rest_ensure_response( $response );
     208
     209                return $response;
     210        }
     211
     212        /**
     213         * Get the autosave, if the ID is valid.
     214         *
     215         * @since 5.0.0
     216         *
     217         * @param WP_REST_Request $request Full data about the request.
     218         * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
     219         */
     220        public function get_item( $request ) {
     221                $parent_id = (int) $request->get_param( 'parent' );
     222
     223                if ( $parent_id <= 0 ) {
     224                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.', 'gutenberg' ), array( 'status' => 404 ) );
     225                }
     226
     227                $autosave = wp_get_post_autosave( $parent_id );
     228
     229                if ( ! $autosave ) {
     230                        return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.', 'gutenberg' ), array( 'status' => 404 ) );
     231                }
     232
     233                $response = $this->prepare_item_for_response( $autosave, $request );
     234                return $response;
     235        }
     236
     237        /**
     238         * Gets a collection of autosaves using wp_get_post_autosave.
     239         *
     240         * Contains the user's autosave, for empty if it doesn't exist.
     241         *
     242         * @since 5.0.0
     243         *
     244         * @param WP_REST_Request $request Full data about the request.
     245         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     246         */
     247        public function get_items( $request ) {
     248                $parent = $this->get_parent( $request->get_param( 'parent' ) );
     249                if ( is_wp_error( $parent ) ) {
     250                        return $parent;
     251                }
     252
     253                $response  = array();
     254                $parent_id = $parent->ID;
     255                $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
     256
     257                foreach ( $revisions as $revision ) {
     258                        if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
     259                                $data       = $this->prepare_item_for_response( $revision, $request );
     260                                $response[] = $this->prepare_response_for_collection( $data );
     261                        }
     262                }
     263
     264                return rest_ensure_response( $response );
     265        }
     266
     267
     268        /**
     269         * Retrieves the autosave's schema, conforming to JSON Schema.
     270         *
     271         * @since 5.0.0
     272         *
     273         * @return array Item schema data.
     274         */
     275        public function get_item_schema() {
     276                $schema = $this->revisions_controller->get_item_schema();
     277
     278                $schema['properties']['preview_link'] = array(
     279                        'description' => __( 'Preview link for the post.', 'gutenberg' ),
     280                        'type'        => 'string',
     281                        'format'      => 'uri',
     282                        'context'     => array( 'edit' ),
     283                        'readonly'    => true,
     284                );
     285
     286                return $schema;
     287        }
     288
     289        /**
     290         * Creates autosave for the specified post.
     291         *
     292         * From wp-admin/post.php.
     293         *
     294         * @since 5.0.0
     295         *
     296         * @param mixed $post_data Associative array containing the post data.
     297         * @return mixed The autosave revision ID or WP_Error.
     298         */
     299        public function create_post_autosave( $post_data ) {
     300
     301                $post_id = (int) $post_data['ID'];
     302                $post    = get_post( $post_id );
     303
     304                if ( is_wp_error( $post ) ) {
     305                        return $post;
     306                }
     307
     308                $user_id = get_current_user_id();
     309
     310                // Store one autosave per author. If there is already an autosave, overwrite it.
     311                $old_autosave = wp_get_post_autosave( $post_id, $user_id );
     312
     313                if ( $old_autosave ) {
     314                        $new_autosave                = _wp_post_revision_data( $post_data, true );
     315                        $new_autosave['ID']          = $old_autosave->ID;
     316                        $new_autosave['post_author'] = $user_id;
     317
     318                        // If the new autosave has the same content as the post, delete the autosave.
     319                        $autosave_is_different = false;
     320
     321                        foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
     322                                if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) {
     323                                        $autosave_is_different = true;
     324                                        break;
     325                                }
     326                        }
     327
     328                        if ( ! $autosave_is_different ) {
     329                                wp_delete_post_revision( $old_autosave->ID );
     330                                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 ) );
     331                        }
     332
     333                        /**
     334                         * This filter is documented in wp-admin/post.php.
     335                         */
     336                        do_action( 'wp_creating_autosave', $new_autosave );
     337
     338                        // wp_update_post expects escaped array.
     339                        return wp_update_post( wp_slash( $new_autosave ) );
     340                }
     341
     342                // Create the new autosave as a special post revision.
     343                return _wp_put_post_revision( $post_data, true );
     344        }
     345
     346        /**
     347         * Prepares the revision for the REST response.
     348         *
     349         * @since 5.0.0
     350         *
     351         * @param WP_Post         $post    Post revision object.
     352         * @param WP_REST_Request $request Request object.
     353         *
     354         * @return WP_REST_Response Response object.
     355         */
     356        public function prepare_item_for_response( $post, $request ) {
     357
     358                $response = $this->revisions_controller->prepare_item_for_response( $post, $request );
     359
     360                $schema = $this->get_item_schema();
     361
     362                if ( ! empty( $schema['properties']['preview_link'] ) ) {
     363                        $parent_id          = wp_is_post_autosave( $post );
     364                        $preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
     365                        $preview_query_args = array();
     366
     367                        if ( false !== $parent_id ) {
     368                                $preview_query_args['preview_id']    = $parent_id;
     369                                $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
     370                        }
     371
     372                        $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
     373                }
     374
     375                $context        = ! empty( $request['context'] ) ? $request['context'] : 'view';
     376                $response->data = $this->filter_response_by_context( $response->data, $context );
     377
     378                /**
     379                 * Filters a revision returned from the API.
     380                 *
     381                 * Allows modification of the revision right before it is returned.
     382                 *
     383                 * @since 5.0.0
     384                 *
     385                 * @param WP_REST_Response $response The response object.
     386                 * @param WP_Post          $post     The original revision object.
     387                 * @param WP_REST_Request  $request  Request used to generate the response.
     388                 */
     389                return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
     390        }
     391}
  • src/wp-settings.php

    diff --git a/src/wp-settings.php b/src/wp-settings.php
    index bacf4cfddd..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' );
  • 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..4b02fe2ec4
    - +  
     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( 12, 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-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 c23dcb5020..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 { 
    153157                $post_revisions = array_values( wp_get_post_revisions( $post_id ) );
    154158                $post_revision_id = $post_revisions[ count( $post_revisions ) - 1 ]->ID;
    155159
     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
    156170                $page_id = $this->factory->post->create( array(
    157171                        'post_type'      => 'page',
    158172                        'post_name'      => 'restapi-client-fixture-page',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    170184                $page_revisions = array_values( wp_get_post_revisions( $page_id ) );
    171185                $page_revision_id = $page_revisions[ count( $page_revisions ) - 1 ]->ID;
    172186
     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
    173196                $tag_id = $this->factory->tag->create( array(
    174197                        'name'        => 'REST API Client Fixture: Tag',
    175198                        'slug'        => 'restapi-client-fixture-tag',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    255278                                'route' => '/wp/v2/posts/' . $post_id . '/revisions/' . $post_revision_id,
    256279                                'name'  => 'revision',
    257280                        ),
     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                        ),
    258289                        array(
    259290                                'route' => '/wp/v2/pages',
    260291                                'name'  => 'PagesCollection',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    271302                                'route' => '/wp/v2/pages/'. $page_id . '/revisions/' . $page_revision_id,
    272303                                'name'  => 'pageRevision',
    273304                        ),
     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                        ),
    274313                        array(
    275314                                'route' => '/wp/v2/media',
    276315                                'name'  => 'MediaCollection',
  • 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 25b13a29d8..26b2640db1 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                    }
     882                },
     883                {
     884                    "methods": [
     885                        "POST"
     886                    ],
     887                    "args": {
     888                        "parent": {
     889                            "required": false,
     890                            "description": "The ID for the parent of the object.",
     891                            "type": "integer"
     892                        },
     893                        "author": {
     894                            "required": false,
     895                            "description": "The ID for the author of the object.",
     896                            "type": "integer"
     897                        },
     898                        "date": {
     899                            "required": false,
     900                            "description": "The date the object was published, in the site's timezone.",
     901                            "type": "string"
     902                        },
     903                        "date_gmt": {
     904                            "required": false,
     905                            "description": "The date the object was published, as GMT.",
     906                            "type": "string"
     907                        },
     908                        "id": {
     909                            "required": false,
     910                            "description": "Unique identifier for the object.",
     911                            "type": "integer"
     912                        },
     913                        "modified": {
     914                            "required": false,
     915                            "description": "The date the object was last modified, in the site's timezone.",
     916                            "type": "string"
     917                        },
     918                        "modified_gmt": {
     919                            "required": false,
     920                            "description": "The date the object was last modified, as GMT.",
     921                            "type": "string"
     922                        },
     923                        "slug": {
     924                            "required": false,
     925                            "description": "An alphanumeric identifier for the object unique to its type.",
     926                            "type": "string"
     927                        },
     928                        "title": {
     929                            "required": false,
     930                            "description": "The title for the object.",
     931                            "type": "object"
     932                        },
     933                        "content": {
     934                            "required": false,
     935                            "description": "The content for the object.",
     936                            "type": "object"
     937                        },
     938                        "excerpt": {
     939                            "required": false,
     940                            "description": "The excerpt for the object.",
     941                            "type": "object"
     942                        }
     943                    }
     944                }
     945            ]
     946        },
     947        "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     948            "namespace": "wp/v2",
     949            "methods": [
     950                "GET"
     951            ],
     952            "endpoints": [
     953                {
     954                    "methods": [
     955                        "GET"
     956                    ],
     957                    "args": {
     958                        "parent": {
     959                            "required": false,
     960                            "description": "The ID for the parent of the object.",
     961                            "type": "integer"
     962                        },
     963                        "id": {
     964                            "required": false,
     965                            "description": "The ID for the object.",
     966                            "type": "integer"
     967                        },
     968                        "context": {
     969                            "required": false,
     970                            "default": "view",
     971                            "enum": [
     972                                "view",
     973                                "embed",
     974                                "edit"
     975                            ],
     976                            "description": "Scope under which the request is made; determines fields present in response.",
     977                            "type": "string"
     978                        }
     979                    }
     980                }
     981            ]
     982        },
    853983        "/wp/v2/pages": {
    854984            "namespace": "wp/v2",
    855985            "methods": [
    mockedApiResponse.Schema = { 
    14561586                }
    14571587            ]
    14581588        },
     1589        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves": {
     1590            "namespace": "wp/v2",
     1591            "methods": [
     1592                "GET",
     1593                "POST"
     1594            ],
     1595            "endpoints": [
     1596                {
     1597                    "methods": [
     1598                        "GET"
     1599                    ],
     1600                    "args": {
     1601                        "parent": {
     1602                            "required": false,
     1603                            "description": "The ID for the parent of the object.",
     1604                            "type": "integer"
     1605                        },
     1606                        "context": {
     1607                            "required": false,
     1608                            "default": "view",
     1609                            "enum": [
     1610                                "view",
     1611                                "embed",
     1612                                "edit"
     1613                            ],
     1614                            "description": "Scope under which the request is made; determines fields present in response.",
     1615                            "type": "string"
     1616                        }
     1617                    }
     1618                },
     1619                {
     1620                    "methods": [
     1621                        "POST"
     1622                    ],
     1623                    "args": {
     1624                        "parent": {
     1625                            "required": false,
     1626                            "description": "The ID for the parent of the object.",
     1627                            "type": "integer"
     1628                        },
     1629                        "author": {
     1630                            "required": false,
     1631                            "description": "The ID for the author of the object.",
     1632                            "type": "integer"
     1633                        },
     1634                        "date": {
     1635                            "required": false,
     1636                            "description": "The date the object was published, in the site's timezone.",
     1637                            "type": "string"
     1638                        },
     1639                        "date_gmt": {
     1640                            "required": false,
     1641                            "description": "The date the object was published, as GMT.",
     1642                            "type": "string"
     1643                        },
     1644                        "id": {
     1645                            "required": false,
     1646                            "description": "Unique identifier for the object.",
     1647                            "type": "integer"
     1648                        },
     1649                        "modified": {
     1650                            "required": false,
     1651                            "description": "The date the object was last modified, in the site's timezone.",
     1652                            "type": "string"
     1653                        },
     1654                        "modified_gmt": {
     1655                            "required": false,
     1656                            "description": "The date the object was last modified, as GMT.",
     1657                            "type": "string"
     1658                        },
     1659                        "slug": {
     1660                            "required": false,
     1661                            "description": "An alphanumeric identifier for the object unique to its type.",
     1662                            "type": "string"
     1663                        },
     1664                        "title": {
     1665                            "required": false,
     1666                            "description": "The title for the object.",
     1667                            "type": "object"
     1668                        },
     1669                        "content": {
     1670                            "required": false,
     1671                            "description": "The content for the object.",
     1672                            "type": "object"
     1673                        },
     1674                        "excerpt": {
     1675                            "required": false,
     1676                            "description": "The excerpt for the object.",
     1677                            "type": "object"
     1678                        }
     1679                    }
     1680                }
     1681            ]
     1682        },
     1683        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     1684            "namespace": "wp/v2",
     1685            "methods": [
     1686                "GET"
     1687            ],
     1688            "endpoints": [
     1689                {
     1690                    "methods": [
     1691                        "GET"
     1692                    ],
     1693                    "args": {
     1694                        "parent": {
     1695                            "required": false,
     1696                            "description": "The ID for the parent of the object.",
     1697                            "type": "integer"
     1698                        },
     1699                        "id": {
     1700                            "required": false,
     1701                            "description": "The ID for the object.",
     1702                            "type": "integer"
     1703                        },
     1704                        "context": {
     1705                            "required": false,
     1706                            "default": "view",
     1707                            "enum": [
     1708                                "view",
     1709                                "embed",
     1710                                "edit"
     1711                            ],
     1712                            "description": "Scope under which the request is made; determines fields present in response.",
     1713                            "type": "string"
     1714                        }
     1715                    }
     1716                }
     1717            ]
     1718        },
    14591719        "/wp/v2/media": {
    14601720            "namespace": "wp/v2",
    14611721            "methods": [
    mockedApiResponse.postRevisions = [ 
    38144074        "guid": {
    38154075            "rendered": "http://example.org/?p=5"
    38164076        },
     4077        "title": {
     4078            "rendered": ""
     4079        },
     4080        "content": {
     4081            "rendered": "<p>Autosave post content.</p>\n"
     4082        },
     4083        "excerpt": {
     4084            "rendered": ""
     4085        },
     4086        "_links": {
     4087            "parent": [
     4088                {
     4089                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/3"
     4090                }
     4091            ]
     4092        }
     4093    },
     4094    {
     4095        "author": 2,
     4096        "date": "2017-02-14T00:00:00",
     4097        "date_gmt": "2017-02-14T00:00:00",
     4098        "id": 4,
     4099        "modified": "2017-02-14T00:00:00",
     4100        "modified_gmt": "2017-02-14T00:00:00",
     4101        "parent": 3,
     4102        "slug": "3-revision-v1",
     4103        "guid": {
     4104            "rendered": "http://example.org/?p=4"
     4105        },
    38174106        "title": {
    38184107            "rendered": "REST API Client Fixture: Post"
    38194108        },
    mockedApiResponse.revision = { 
    38564145    }
    38574146};
    38584147
     4148mockedApiResponse.postAutosaves = [
     4149    {
     4150        "author": 2,
     4151        "date": "2017-02-14T00:00:00",
     4152        "date_gmt": "2017-02-14T00:00:00",
     4153        "id": 5,
     4154        "modified": "2017-02-14T00:00:00",
     4155        "modified_gmt": "2017-02-14T00:00:00",
     4156        "parent": 3,
     4157        "slug": "3-autosave-v1",
     4158        "guid": {
     4159            "rendered": "http://example.org/?p=5"
     4160        },
     4161        "title": {
     4162            "rendered": ""
     4163        },
     4164        "content": {
     4165            "rendered": "<p>Autosave post content.</p>\n"
     4166        },
     4167        "excerpt": {
     4168            "rendered": ""
     4169        },
     4170        "_links": {
     4171            "parent": [
     4172                {
     4173                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/3"
     4174                }
     4175            ]
     4176        }
     4177    }
     4178];
     4179
     4180mockedApiResponse.autosave = {
     4181    "author": 2,
     4182    "date": "2017-02-14T00:00:00",
     4183    "date_gmt": "2017-02-14T00:00:00",
     4184    "id": 5,
     4185    "modified": "2017-02-14T00:00:00",
     4186    "modified_gmt": "2017-02-14T00:00:00",
     4187    "parent": 3,
     4188    "slug": "3-autosave-v1",
     4189    "guid": {
     4190        "rendered": "http://example.org/?p=5"
     4191    },
     4192    "title": {
     4193        "rendered": ""
     4194    },
     4195    "content": {
     4196        "rendered": "<p>Autosave post content.</p>\n"
     4197    },
     4198    "excerpt": {
     4199        "rendered": ""
     4200    }
     4201};
     4202
    38594203mockedApiResponse.PagesCollection = [
    38604204    {
    38614205        "id": 6,
    mockedApiResponse.PageModel = { 
    39784322};
    39794323
    39804324mockedApiResponse.pageRevisions = [
     4325    {
     4326        "author": 2,
     4327        "date": "2017-02-14T00:00:00",
     4328        "date_gmt": "2017-02-14T00:00:00",
     4329        "id": 7,
     4330        "modified": "2017-02-14T00:00:00",
     4331        "modified_gmt": "2017-02-14T00:00:00",
     4332        "parent": 6,
     4333        "slug": "6-revision-v1",
     4334        "guid": {
     4335            "rendered": "http://example.org/?p=7"
     4336        },
     4337        "title": {
     4338            "rendered": ""
     4339        },
     4340        "content": {
     4341            "rendered": "<p>Autosave page content.</p>\n"
     4342        },
     4343        "excerpt": {
     4344            "rendered": ""
     4345        },
     4346        "_links": {
     4347            "parent": [
     4348                {
     4349                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/5"
     4350                }
     4351            ]
     4352        }
     4353    },
    39814354    {
    39824355        "author": 2,
    39834356        "date": "2017-02-14T00:00:00",
    mockedApiResponse.pageRevision = { 
    40324405    }
    40334406};
    40344407
     4408mockedApiResponse.pageAutosaves = [
     4409    {
     4410        "author": 2,
     4411        "date": "2017-02-14T00:00:00",
     4412        "date_gmt": "2017-02-14T00:00:00",
     4413        "id": 8,
     4414        "modified": "2017-02-14T00:00:00",
     4415        "modified_gmt": "2017-02-14T00:00:00",
     4416        "parent": 6,
     4417        "slug": "6-autosave-v1",
     4418        "guid": {
     4419            "rendered": "http://example.org/?p=8"
     4420        },
     4421        "title": {
     4422            "rendered": ""
     4423        },
     4424        "content": {
     4425            "rendered": "<p>Autosave page content.</p>\n"
     4426        },
     4427        "excerpt": {
     4428            "rendered": ""
     4429        },
     4430        "_links": {
     4431            "parent": [
     4432                {
     4433                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
     4434                }
     4435            ]
     4436        }
     4437    }
     4438];
     4439
     4440mockedApiResponse.pageAutosave = {
     4441    "author": 2,
     4442    "date": "2017-02-14T00:00:00",
     4443    "date_gmt": "2017-02-14T00:00:00",
     4444    "id": 8,
     4445    "modified": "2017-02-14T00:00:00",
     4446    "modified_gmt": "2017-02-14T00:00:00",
     4447    "parent": 6,
     4448    "slug": "6-autosave-v1",
     4449    "guid": {
     4450        "rendered": "http://example.org/?p=8"
     4451    },
     4452    "title": {
     4453        "rendered": ""
     4454    },
     4455    "content": {
     4456        "rendered": "<p>Autosave page content.</p>\n"
     4457    },
     4458    "excerpt": {
     4459        "rendered": ""
     4460    }
     4461};
     4462
    40354463mockedApiResponse.MediaCollection = [
    40364464    {
    40374465        "id": 8,