Make WordPress Core

Ticket #43316: 43316.19.diff

File 43316.19.diff, 59.1 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 82d856a063..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.
  • 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..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-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..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 = { 
    14561650                }
    14571651            ]
    14581652        },
     1653        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves": {
     1654            "namespace": "wp/v2",
     1655            "methods": [
     1656                "GET",
     1657                "POST"
     1658            ],
     1659            "endpoints": [
     1660                {
     1661                    "methods": [
     1662                        "GET"
     1663                    ],
     1664                    "args": {
     1665                        "parent": {
     1666                            "required": false,
     1667                            "description": "The ID for the parent of the object.",
     1668                            "type": "integer"
     1669                        },
     1670                        "context": {
     1671                            "required": false,
     1672                            "default": "view",
     1673                            "enum": [
     1674                                "view",
     1675                                "embed",
     1676                                "edit"
     1677                            ],
     1678                            "description": "Scope under which the request is made; determines fields present in response.",
     1679                            "type": "string"
     1680                        },
     1681                        "page": {
     1682                            "required": false,
     1683                            "default": 1,
     1684                            "description": "Current page of the collection.",
     1685                            "type": "integer"
     1686                        },
     1687                        "per_page": {
     1688                            "required": false,
     1689                            "description": "Maximum number of items to be returned in result set.",
     1690                            "type": "integer"
     1691                        },
     1692                        "search": {
     1693                            "required": false,
     1694                            "description": "Limit results to those matching a string.",
     1695                            "type": "string"
     1696                        },
     1697                        "exclude": {
     1698                            "required": false,
     1699                            "default": [],
     1700                            "description": "Ensure result set excludes specific IDs.",
     1701                            "type": "array",
     1702                            "items": {
     1703                                "type": "integer"
     1704                            }
     1705                        },
     1706                        "include": {
     1707                            "required": false,
     1708                            "default": [],
     1709                            "description": "Limit result set to specific IDs.",
     1710                            "type": "array",
     1711                            "items": {
     1712                                "type": "integer"
     1713                            }
     1714                        },
     1715                        "offset": {
     1716                            "required": false,
     1717                            "description": "Offset the result set by a specific number of items.",
     1718                            "type": "integer"
     1719                        },
     1720                        "order": {
     1721                            "required": false,
     1722                            "default": "desc",
     1723                            "enum": [
     1724                                "asc",
     1725                                "desc"
     1726                            ],
     1727                            "description": "Order sort attribute ascending or descending.",
     1728                            "type": "string"
     1729                        },
     1730                        "orderby": {
     1731                            "required": false,
     1732                            "default": "date",
     1733                            "enum": [
     1734                                "date",
     1735                                "id",
     1736                                "include",
     1737                                "relevance",
     1738                                "slug",
     1739                                "include_slugs",
     1740                                "title"
     1741                            ],
     1742                            "description": "Sort collection by object attribute.",
     1743                            "type": "string"
     1744                        }
     1745                    }
     1746                },
     1747                {
     1748                    "methods": [
     1749                        "POST"
     1750                    ],
     1751                    "args": {
     1752                        "parent": {
     1753                            "required": false,
     1754                            "description": "The ID for the parent of the object.",
     1755                            "type": "integer"
     1756                        },
     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                        },
     1772                        "id": {
     1773                            "required": false,
     1774                            "description": "Unique identifier for the object.",
     1775                            "type": "integer"
     1776                        },
     1777                        "modified": {
     1778                            "required": false,
     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.",
     1785                            "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"
     1806                        }
     1807                    }
     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": [
     1817                {
     1818                    "methods": [
     1819                        "GET"
     1820                    ],
     1821                    "args": {
     1822                        "parent": {
     1823                            "required": false,
     1824                            "description": "The ID for the parent of the object.",
     1825                            "type": "integer"
     1826                        },
     1827                        "id": {
     1828                            "required": false,
     1829                            "description": "The ID for the object.",
     1830                            "type": "integer"
     1831                        },
     1832                        "context": {
     1833                            "required": false,
     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"
     1842                        }
     1843                    }
     1844                }
     1845            ]
     1846        },
    14591847        "/wp/v2/media": {
    14601848            "namespace": "wp/v2",
    14611849            "methods": [
    mockedApiResponse.PostsCollection = [ 
    37234111            ],
    37244112            "version-history": [
    37254113                {
    3726                     "count": 1,
     4114                    "count": 2,
    37274115                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4/revisions"
    37284116                }
    37294117            ],
    mockedApiResponse.postRevisions = [ 
    38144202        "guid": {
    38154203            "rendered": "http://example.org/?p=5"
    38164204        },
     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        },
    38174234        "title": {
    38184235            "rendered": "REST API Client Fixture: Post"
    38194236        },
    mockedApiResponse.postRevisions = [ 
    38264243        "_links": {
    38274244            "parent": [
    38284245                {
    3829                     "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"
    38304247                }
    38314248            ]
    38324249        }
    mockedApiResponse.revision = { 
    38564273    }
    38574274};
    38584275
     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
    38594331mockedApiResponse.PagesCollection = [
    38604332    {
    38614333        "id": 6,
    mockedApiResponse.PagesCollection = [ 
    39154387            ],
    39164388            "version-history": [
    39174389                {
    3918                     "count": 1,
     4390                    "count": 2,
    39194391                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6/revisions"
    39204392                }
    39214393            ],
    mockedApiResponse.pageRevisions = [ 
    39904462        "guid": {
    39914463            "rendered": "http://example.org/?p=7"
    39924464        },
     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        },
    39934494        "title": {
    39944495            "rendered": "REST API Client Fixture: Page"
    39954496        },
    mockedApiResponse.pageRevisions = [ 
    40024503        "_links": {
    40034504            "parent": [
    40044505                {
    4005                     "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"
    40064507                }
    40074508            ]
    40084509        }
    mockedApiResponse.pageRevision = { 
    40324533    }
    40334534};
    40344535
     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
    40354591mockedApiResponse.MediaCollection = [
    40364592    {
    40374593        "id": 8,