WordPress.org

Make WordPress Core

Ticket #43316: 43316.12.diff

File 43316.12.diff, 56.7 KB (added by adamsilverstein, 4 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 0cce9fa62f..51e5e6f1a3 100644
    a b function create_initial_rest_routes() { 
    192192                if ( post_type_supports( $post_type->name, 'revisions' ) ) {
    193193                        $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
    194194                        $revisions_controller->register_routes();
     195                        $controller = new WP_REST_Autosaves_Controller( $post_type->name );
     196                        $controller->register_routes();
    195197                }
    196198        }
    197199
  • 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..a9ac2e2bf4
    - +  
     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 controller.
     29         *
     30         * @since 5.0.0
     31         * @var WP_REST_Controller
     32         */
     33        private $parent_controller;
     34
     35        /**
     36         * Parent controller.
     37         *
     38         * @since 5.0.0
     39         * @var WP_REST_Controller
     40         */
     41        private $revision_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                $this->parent_controller   = new WP_REST_Posts_Controller( $parent_post_type );
     61                $this->revision_controller = new WP_REST_Revisions_Controller( $parent_post_type );
     62                $this->rest_namespace      = 'wp/v2';
     63                $this->rest_base           = 'autosaves';
     64                $post_type_object          = get_post_type_object( $parent_post_type );
     65                $this->parent_base         = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
     66        }
     67
     68        /**o
     69         * Registers routes for autosaves.
     70         *
     71         * @since 5.0.0
     72         *
     73         * @see register_rest_route()
     74         */
     75        public function register_routes() {
     76                register_rest_route(
     77                        $this->rest_namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array(
     78                                'args'   => array(
     79                                        'parent' => array(
     80                                                'description' => __( 'The ID for the parent of the object.' ),
     81                                                'type'        => 'integer',
     82                                        ),
     83                                ),
     84                                array(
     85                                        'methods'             => WP_REST_Server::READABLE,
     86                                        'callback'            => array( $this, 'get_items' ),
     87                                        'permission_callback' => array( $this->revision_controller, 'get_items_permissions_check' ),
     88                                        'args'                => $this->get_collection_params(),
     89                                ),
     90                                array(
     91                                        'methods'             => WP_REST_Server::CREATABLE,
     92                                        'callback'            => array( $this, 'create_item' ),
     93                                        'permission_callback' => array( $this, 'create_item_permissions_check' ),
     94                                        'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
     95                                ),
     96                                'schema' => array( $this, 'get_public_item_schema' ),
     97                        )
     98                );
     99
     100                register_rest_route(
     101                        $this->rest_namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array(
     102                                'args'   => array(
     103                                        'parent' => array(
     104                                                'description' => __( 'The ID for the parent of the object.' ),
     105                                                'type'        => 'integer',
     106                                        ),
     107                                        'id'     => array(
     108                                                'description' => __( 'The ID for the object.' ),
     109                                                'type'        => 'integer',
     110                                        ),
     111                                ),
     112                                array(
     113                                        'methods'             => WP_REST_Server::READABLE,
     114                                        'callback'            => array( $this, 'get_item' ),
     115                                        'permission_callback' => array( $this->revision_controller, 'get_item_permissions_check' ),
     116                                        'args'                => array(
     117                                                'context' => $this->get_context_param( array( 'default' => 'view' ) ),
     118                                        ),
     119                                ),
     120                                array(
     121                                        'methods'             => WP_REST_Server::DELETABLE,
     122                                        'callback'            => array( $this, 'delete_item' ),
     123                                        'permission_callback' => array( $this->revision_controller, 'delete_item_permissions_check' ),
     124                                        'args'                => array(
     125                                                'force' => array(
     126                                                        'type'        => 'boolean',
     127                                                        'default'     => false,
     128                                                        'description' => __( 'Required to be true, as autosaves do not support trashing.' ),
     129                                                ),
     130                                        ),
     131                                ),
     132                                'schema' => array( $this, 'get_public_item_schema' ),
     133                        )
     134                );
     135
     136        }
     137
     138        /**
     139         * Get the parent post.
     140         *
     141         * @since 5.0.0
     142         *
     143         * @param int $parent_id Supplied ID.
     144         * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
     145         */
     146        protected function get_parent( $parent_id ) {
     147                return $this->revision_controller->get_parent( $parent_id );
     148        }
     149
     150        /**
     151         * Checks if a given request has access to create an autosave revision.
     152         *
     153         * Autosave revisions inherit permissions from the parent post,
     154         * check if the current user has permission to edit the post.
     155         *
     156         * @since 5.0.0
     157         *
     158         * @param WP_REST_Request $request Full details about the request.
     159         * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
     160         */
     161        public function create_item_permissions_check( $request ) {
     162                if ( empty( $request->get_param( 'id' ) ) ) {
     163                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) );
     164                }
     165
     166                return $this->parent_controller->update_item_permissions_check( $request );
     167        }
     168
     169        /**
     170         * Create a revision when the autosave content is significantly different.
     171         *
     172         * If the autosave content is significantly different from the current post or the previous autosave,
     173         * create a revision from the old data.
     174         *
     175         * @since 5.0.0
     176         *
     177         * @param WP_Post $post          The current post or the previous autosave revision.
     178         * @param array   $autosave_data The autosave data.
     179         */
     180        protected function create_revision_for_autosave( $post, $autosave_data ) {
     181
     182                if ( ! wp_revisions_enabled( $post ) ) {
     183                        return;
     184                }
     185
     186                $post_data = get_object_vars( $post );
     187
     188                if ( $post_data['post_type'] === 'revision' ) {
     189                        // If the old post is a revision, need to merge it with the actual post.
     190                        $parent_post = $this->get_parent( $post_data['post_parent'] );
     191                        foreach ( array_keys( _wp_post_revision_fields( $parent_post ) ) as $field ) {
     192                                if ( isset( $post_data[ $field ] ) ) {
     193                                        $parent_post->$field = $post_data[ $field ];
     194                                }
     195                        }
     196                        $post_data = get_object_vars( $parent_post );
     197                }
     198
     199                $old_length = strlen( $post_data['post_content'] );
     200
     201                if ( $old_length > 1000 ) {
     202                        $difference = 250;
     203                } elseif ( $old_length > 500 ) {
     204                        $difference = (int) $old_length * 0.2;
     205                } elseif ( $old_length > 50 ) {
     206                        $difference = (int) $old_length * 0.3;
     207                } else {
     208                        $difference = (int) $old_length * 0.5;
     209                }
     210
     211                $size_diff = strlen( $autosave_data['post_content'] ) - $old_length;
     212                $create_revision = absint( $size_diff ) > $difference;
     213                $revision_id = 0;
     214
     215                /**
     216                 * Filter whether a revision is created when an autosave is made via the REST API.
     217                 *
     218                 * @since 5.0.0
     219                 *
     220                 * @param bool  $create_revision Create a revision?
     221                 * @param array $post_data       The current post data.
     222                 * @param int   $size_diff       The calculated autosave difference.
     223                 */
     224                if ( apply_filters( 'wp_create_revision_for_autosave', $create_revision, $post_data, $size_diff ) ) {
     225                        _wp_put_post_revision( $post_data );
     226                }
     227        }
     228
     229        /**
     230         * Creates, updates or deletes an autosave revision.
     231         *
     232         * @since 5.0.0
     233         *
     234         * @param WP_REST_Request $request Full details about the request.
     235         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     236         */
     237        public function create_item( $request ) {
     238
     239                if ( ! defined( 'DOING_AUTOSAVE' ) ) {
     240                        define( 'DOING_AUTOSAVE', true );
     241                }
     242
     243                $post = get_post( $request->get_param( 'id' ) );
     244
     245                if ( is_wp_error( $post ) ) {
     246                        return $post;
     247                }
     248
     249                $prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
     250                $prepared_post->ID = $post->ID;
     251                $user_id           = get_current_user_id();
     252
     253                if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
     254
     255                        // Optionally create a revision if the autosave data is significantly different.
     256                        // This protects the user from errors when editing, accidental deletes, etc.
     257                        $this->create_revision_for_autosave( $post, (array) $prepared_post );
     258
     259                        // Draft posts for the same author: autosaving updates the post and does not create a revision.
     260                        // Convert the post object to an array and add slashes, wp_update_post expects escaped array.
     261                        $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
     262                } else {
     263
     264                        // Non-draft posts: create or update the post autosave.
     265                        $autosave_id = $this->create_post_autosave( (array) $prepared_post );
     266                }
     267
     268                if ( is_wp_error( $autosave_id ) ) {
     269                        return $autosave_id;
     270                }
     271
     272                $autosave = get_post( $autosave_id );
     273                $request->set_param( 'context', 'edit' );
     274
     275                $response = $this->prepare_item_for_response( $autosave, $request );
     276                $response = rest_ensure_response( $response );
     277
     278                return $response;
     279        }
     280
     281        /**
     282         * Get the autosave, if the ID is valid.
     283         *
     284         * @since 5.0.0
     285         *
     286         * @param WP_REST_Request $request Full data about the request.
     287         * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
     288         */
     289        public function get_item( $request ) {
     290                $parent_id = (int) $request->get_param( 'parent' );
     291
     292                if ( $parent_id <= 0 ) {
     293                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.' ), array( 'status' => 404 ) );
     294                }
     295
     296                $autosave = wp_get_post_autosave( $parent_id );
     297
     298                if ( ! $autosave ) {
     299                        return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.' ), array( 'status' => 404 ) );
     300                }
     301
     302                $response = $this->prepare_item_for_response( $autosave, $request );
     303                return $response;
     304        }
     305
     306        /**
     307         * Gets a collection of autosaves using wp_get_post_autosave.
     308         *
     309         * @since 5.0.0
     310         *
     311         * @param WP_REST_Request $request Full data about the request.
     312         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     313         */
     314        public function get_items( $request ) {
     315                $parent = $this->get_parent( $request->get_param( 'parent' ) );
     316                if ( is_wp_error( $parent ) ) {
     317                        return $parent;
     318                }
     319
     320                $response  = array();
     321                $parent_id = $parent->ID;
     322                $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
     323
     324                foreach ( $revisions as $revision ) {
     325                        if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
     326                                $data       = $this->prepare_item_for_response( $revision, $request );
     327                                $response[] = $this->prepare_response_for_collection( $data );
     328                        }
     329                }
     330
     331                return rest_ensure_response( $response );
     332        }
     333
     334
     335        /**
     336         * Retrieves the autosave's schema, conforming to JSON Schema.
     337         *
     338         * @since 5.0.0
     339         *
     340         * @return array Item schema data.
     341         */
     342        public function get_item_schema() {
     343                return $this->revision_controller->get_item_schema();
     344        }
     345
     346        /**
     347         * Creates autosave for the specified post.
     348         *
     349         * From wp-admin/post.php.
     350         *
     351         * @since 5.0.0
     352         *
     353         * @param mixed $post_data Associative array containing the post data.
     354         * @return mixed The autosave revision ID or WP_Error.
     355         */
     356        public function create_post_autosave( $post_data ) {
     357
     358                $post_id = (int) $post_data['ID'];
     359                $post    = get_post( $post_id );
     360
     361                if ( is_wp_error( $post ) ) {
     362                        return $post;
     363                }
     364
     365                $user_id = get_current_user_id();
     366
     367                // Store one autosave per author. If there is already an autosave, overwrite it.
     368                $old_autosave = wp_get_post_autosave( $post_id, $user_id );
     369
     370                if ( $old_autosave ) {
     371                        $new_autosave                = _wp_post_revision_data( $post_data, true );
     372                        $new_autosave['ID']          = $old_autosave->ID;
     373                        $new_autosave['post_author'] = $user_id;
     374
     375                        // If the new autosave has the same content as the post, delete the autosave.
     376                        $autosave_is_different = false;
     377
     378                        foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
     379                                if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) {
     380                                        $autosave_is_different = true;
     381                                        break;
     382                                }
     383                        }
     384
     385                        if ( ! $autosave_is_different ) {
     386                                wp_delete_post_revision( $old_autosave->ID );
     387                                return new WP_Error( 'rest_autosave_no_changes', __( 'There is nothing to save. The autosave and the post content are the same.' ) );
     388                        }
     389
     390                        /**
     391                         * This filter is documented in wp-admin/post.php.
     392                         */
     393                        do_action( 'wp_creating_autosave', $new_autosave );
     394
     395                        // Optionally create a post revision if the new autosave data is significantly different.
     396                        $this->create_revision_for_autosave( $old_autosave, $new_autosave );
     397
     398                        // wp_update_post expects escaped array.
     399                        return wp_update_post( wp_slash( $new_autosave ) );
     400                }
     401
     402                // Create the new autosave as a special post revision.
     403                return _wp_put_post_revision( $post_data, true );
     404        }
     405
     406        /**
     407         * Prepares the revision for the REST response.
     408         *
     409         * @since 5.0.0
     410         *
     411         * @param WP_Post         $post    Post revision object.
     412         * @param WP_REST_Request $request Request object.
     413         *
     414         * @return WP_REST_Response Response object.
     415         */
     416        public function prepare_item_for_response( $post, $request ) {
     417
     418                $response = $this->revision_controller->prepare_item_for_response( $post, $request );
     419
     420                /**
     421                 * Filters a revision returned from the API.
     422                 *
     423                 * Allows modification of the revision right before it is returned.
     424                 *
     425                 * @since 5.0.0
     426                 *
     427                 * @param WP_REST_Response $response The response object.
     428                 * @param WP_Post          $post     The original revision object.
     429                 * @param WP_REST_Request  $request  Request used to generate the response.
     430                 */
     431                return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
     432        }
     433}
  • src/wp-settings.php

    diff --git a/src/wp-settings.php b/src/wp-settings.php
    index 6b1f32bbff..767b9ddf73 100644
    a b require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-contro 
    230230require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' );
    231231require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' );
    232232require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' );
     233require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php' );
    233234require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' );
    234235require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' );
    235236require( 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..0185091588
    - +  
     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                wp_set_current_user( self::$editor_id );
     224                $request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     225                $request->set_param( 'force', true );
     226                $response = rest_get_server()->dispatch( $request );
     227                $this->assertEquals( 200, $response->get_status() );
     228                $this->assertNull( get_post( self::$autosave_post_id ) );
     229        }
     230
     231        public function test_delete_item_no_trash() {
     232                wp_set_current_user( self::$editor_id );
     233
     234                $request  = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     235                $response = rest_get_server()->dispatch( $request );
     236                $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
     237
     238                $request->set_param( 'force', 'false' );
     239                $response = rest_get_server()->dispatch( $request );
     240                $this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
     241
     242                // Ensure the revision still exists
     243                $this->assertNotNull( get_post( self::$autosave_post_id ) );
     244        }
     245
     246        public function test_delete_item_no_permission() {
     247                wp_set_current_user( self::$contributor_id );
     248                $request  = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     249                $response = rest_get_server()->dispatch( $request );
     250                $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
     251        }
     252
     253        public function test_prepare_item() {
     254                wp_set_current_user( self::$editor_id );
     255                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     256                $response = rest_get_server()->dispatch( $request );
     257                $this->assertEquals( 200, $response->get_status() );
     258                $this->check_get_autosave_response( $response, $this->post_autosave );
     259        }
     260
     261        public function test_get_item_schema() {
     262                $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     263                $response   = rest_get_server()->dispatch( $request );
     264                $data       = $response->get_data();
     265                $properties = $data['schema']['properties'];
     266                $this->assertEquals( 12, count( $properties ) );
     267                $this->assertArrayHasKey( 'author', $properties );
     268                $this->assertArrayHasKey( 'content', $properties );
     269                $this->assertArrayHasKey( 'date', $properties );
     270                $this->assertArrayHasKey( 'date_gmt', $properties );
     271                $this->assertArrayHasKey( 'excerpt', $properties );
     272                $this->assertArrayHasKey( 'guid', $properties );
     273                $this->assertArrayHasKey( 'id', $properties );
     274                $this->assertArrayHasKey( 'modified', $properties );
     275                $this->assertArrayHasKey( 'modified_gmt', $properties );
     276                $this->assertArrayHasKey( 'parent', $properties );
     277                $this->assertArrayHasKey( 'slug', $properties );
     278                $this->assertArrayHasKey( 'title', $properties );
     279        }
     280
     281        public function test_create_item() {
     282                wp_set_current_user( self::$editor_id );
     283
     284                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     285                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     286
     287                $params = $this->set_post_data(
     288                        array(
     289                                'id' => self::$post_id,
     290                        )
     291                );
     292                $request->set_body_params( $params );
     293                $response = rest_get_server()->dispatch( $request );
     294
     295                $this->check_create_autosave_response( $response );
     296        }
     297
     298        public function test_update_item() {
     299                wp_set_current_user( self::$editor_id );
     300                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     301                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     302
     303                $params = $this->set_post_data(
     304                        array(
     305                                'id'     => self::$post_id,
     306                                'author' => self::$contributor_id,
     307                        )
     308                );
     309
     310                $request->set_body_params( $params );
     311                $response = rest_get_server()->dispatch( $request );
     312
     313                $this->check_create_autosave_response( $response );
     314        }
     315
     316        public function test_update_item_nopriv() {
     317                wp_set_current_user( self::$contributor_id );
     318
     319                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     320                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     321
     322                $params = $this->set_post_data(
     323                        array(
     324                                'id'     => self::$post_id,
     325                                'author' => self::$editor_id,
     326                        )
     327                );
     328
     329                $request->set_body_params( $params );
     330                $response = rest_get_server()->dispatch( $request );
     331
     332                $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
     333        }
     334
     335        public function test_rest_autosave_published_post() {
     336                wp_set_current_user( self::$editor_id );
     337
     338                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     339                $request->add_header( 'content-type', 'application/json' );
     340
     341                $current_post = get_post( self::$post_id );
     342
     343                $autosave_data = $this->set_post_data(
     344                        array(
     345                                'id'      => self::$post_id,
     346                                'content' => 'Updated post \ content',
     347                                'excerpt' => $current_post->post_excerpt,
     348                                'title'   => $current_post->post_title,
     349                        )
     350                );
     351
     352                $request->set_body( wp_json_encode( $autosave_data ) );
     353                $response = rest_get_server()->dispatch( $request );
     354                $new_data = $response->get_data();
     355
     356                $this->assertEquals( $current_post->ID, $new_data['parent'] );
     357                $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] );
     358                $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] );
     359                // Updated post_content
     360                $this->assertNotEquals( $current_post->post_content, $new_data['content']['raw'] );
     361
     362                $autosave_post = wp_get_post_autosave( self::$post_id );
     363                $this->assertEquals( $autosave_data['title'], $autosave_post->post_title );
     364                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     365                $this->assertEquals( $autosave_data['excerpt'], $autosave_post->post_excerpt );
     366        }
     367
     368        public function test_rest_autosave_draft_post_same_author() {
     369                wp_set_current_user( self::$editor_id );
     370
     371                $post_data = array(
     372                        'post_content' => 'Test post content',
     373                        'post_title'   => 'Test post title',
     374                        'post_excerpt' => 'Test post excerpt',
     375                );
     376                $post_id = wp_insert_post( $post_data );
     377
     378                $autosave_data = array(
     379                        'id' => $post_id,
     380                        'content' => 'Updated post \ content',
     381                        'title'   => 'Updated post title',
     382                );
     383
     384                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     385                $request->add_header( 'content-type', 'application/json' );
     386                $request->set_body( wp_json_encode( $autosave_data ) );
     387
     388                $response = rest_get_server()->dispatch( $request );
     389                $new_data = $response->get_data();
     390                $post = get_post( $post_id );
     391
     392                $this->assertEquals( $post_id, $new_data['id'] );
     393                // The draft post should be updated.
     394                $this->assertEquals( $autosave_data['content'], $new_data['content']['raw'] );
     395                $this->assertEquals( $autosave_data['title'], $new_data['title']['raw'] );
     396                $this->assertEquals( $autosave_data['content'], $post->post_content );
     397                $this->assertEquals( $autosave_data['title'], $post->post_title );
     398
     399                // Not updated.
     400                $this->assertEquals( $post_data['post_excerpt'], $post->post_excerpt );
     401
     402                wp_delete_post( $post_id );
     403        }
     404
     405        public function test_rest_autosave_draft_post_different_author() {
     406                wp_set_current_user( self::$editor_id );
     407
     408                $post_data = array(
     409                        'post_content' => 'Test post content',
     410                        'post_title'   => 'Test post title',
     411                        'post_excerpt' => 'Test post excerpt',
     412                        'post_author'  => self::$editor_id + 1,
     413                );
     414                $post_id = wp_insert_post( $post_data );
     415
     416                $autosave_data = array(
     417                        'id' => $post_id,
     418                        'content' => 'Updated post content',
     419                        'excerpt' => $post_data['post_excerpt'],
     420                        'title'   => $post_data['post_title'],
     421                );
     422
     423                $request  = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     424                $request->add_header( 'content-type', 'application/json' );
     425                $request->set_body( wp_json_encode( $autosave_data ) );
     426
     427                $response = rest_get_server()->dispatch( $request );
     428                $new_data = $response->get_data();
     429                $current_post = get_post( $post_id );
     430
     431                $this->assertEquals( $current_post->ID, $new_data['parent'] );
     432
     433                // The draft post shouldn't change.
     434                $this->assertEquals( $current_post->post_title, $post_data['post_title'] );
     435                $this->assertEquals( $current_post->post_content, $post_data['post_content'] );
     436                $this->assertEquals( $current_post->post_excerpt, $post_data['post_excerpt'] );
     437
     438                $autosave_post = wp_get_post_autosave( $post_id );
     439
     440                // No changes
     441                $this->assertEquals( $current_post->post_title, $autosave_post->post_title );
     442                $this->assertEquals( $current_post->post_excerpt, $autosave_post->post_excerpt );
     443
     444                // Has changes
     445                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     446
     447                wp_delete_post( $post_id );
     448        }
     449
     450        public function test_get_additional_field_registration() {
     451                $schema = array(
     452                        'type'        => 'integer',
     453                        'description' => 'Some integer of mine',
     454                        'enum'        => array( 1, 2, 3, 4 ),
     455                        'context'     => array( 'view', 'edit' ),
     456                );
     457
     458                register_rest_field(
     459                        'post-revision', 'my_custom_int', array(
     460                                'schema'          => $schema,
     461                                'get_callback'    => array( $this, 'additional_field_get_callback' ),
     462                                'update_callback' => array( $this, 'additional_field_update_callback' ),
     463                        )
     464                );
     465
     466                $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     467
     468                $response = rest_get_server()->dispatch( $request );
     469                $data     = $response->get_data();
     470
     471                $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
     472                $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
     473
     474                wp_set_current_user( 1 );
     475
     476                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     477
     478                $response = rest_get_server()->dispatch( $request );
     479                $this->assertArrayHasKey( 'my_custom_int', $response->data );
     480
     481                global $wp_rest_additional_fields;
     482                $wp_rest_additional_fields = array();
     483        }
     484
     485        public function additional_field_get_callback( $object ) {
     486                return get_post_meta( $object['id'], 'my_custom_int', true );
     487        }
     488
     489        public function additional_field_update_callback( $value, $post ) {
     490                update_post_meta( $post->ID, 'my_custom_int', $value );
     491        }
     492
     493        protected function check_get_autosave_response( $response, $autosave ) {
     494                if ( $response instanceof WP_REST_Response ) {
     495                        $links    = $response->get_links();
     496                        $response = $response->get_data();
     497                } else {
     498                        $this->assertArrayHasKey( '_links', $response );
     499                        $links = $response['_links'];
     500                }
     501
     502                $this->assertEquals( $autosave->post_author, $response['author'] );
     503
     504                $rendered_content = apply_filters( 'the_content', $autosave->post_content );
     505                $this->assertEquals( $rendered_content, $response['content']['rendered'] );
     506
     507                $this->assertEquals( mysql_to_rfc3339( $autosave->post_date ), $response['date'] );
     508                $this->assertEquals( mysql_to_rfc3339( $autosave->post_date_gmt ), $response['date_gmt'] );
     509
     510                $rendered_guid = apply_filters( 'get_the_guid', $autosave->guid, $autosave->ID );
     511                $this->assertEquals( $rendered_guid, $response['guid']['rendered'] );
     512
     513                $this->assertEquals( $autosave->ID, $response['id'] );
     514                $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified ), $response['modified'] );
     515                $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified_gmt ), $response['modified_gmt'] );
     516                $this->assertEquals( $autosave->post_name, $response['slug'] );
     517
     518                $rendered_title = get_the_title( $autosave->ID );
     519                $this->assertEquals( $rendered_title, $response['title']['rendered'] );
     520
     521                $parent            = get_post( $autosave->post_parent );
     522                $parent_controller = new WP_REST_Posts_Controller( $parent->post_type );
     523                $parent_object     = get_post_type_object( $parent->post_type );
     524                $parent_base       = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name;
     525                $this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $autosave->post_parent ), $links['parent'][0]['href'] );
     526        }
     527
     528        public function test_get_item_sets_up_postdata() {
     529                wp_set_current_user( self::$editor_id );
     530                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     531                rest_get_server()->dispatch( $request );
     532
     533                $post           = get_post();
     534                $parent_post_id = wp_is_post_revision( $post->ID );
     535
     536                $this->assertEquals( $post->ID, self::$autosave_post_id );
     537                $this->assertEquals( $parent_post_id, self::$post_id );
     538        }
     539
     540}
  • 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 e5b3460122..e3271318b6 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 { 
    159163                $post_revisions   = array_values( wp_get_post_revisions( $post_id ) );
    160164                $post_revision_id = $post_revisions[ count( $post_revisions ) - 1 ]->ID;
    161165
     166                // Create an autosave.
     167                wp_create_post_autosave(
     168                        array(
     169                                'post_ID'      => $post_id,
     170                                'post_content' => 'Autosave post content.',
     171                                'post_type'    => 'post',
     172                        )
     173                );
     174
    162175                $page_id = $this->factory->post->create(
    163176                        array(
    164177                                'post_type'     => 'page',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    180193                $page_revisions   = array_values( wp_get_post_revisions( $page_id ) );
    181194                $page_revision_id = $page_revisions[ count( $page_revisions ) - 1 ]->ID;
    182195
     196                // Create an autosave.
     197                wp_create_post_autosave(
     198                        array(
     199                                'post_ID'      => $page_id,
     200                                'post_content' => 'Autosave page content.',
     201                                'post_type'    => 'page',
     202                        )
     203                );
     204
    183205                $tag_id = $this->factory->tag->create(
    184206                        array(
    185207                                'name'        => 'REST API Client Fixture: Tag',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    271293                                'route' => '/wp/v2/posts/' . $post_id . '/revisions/' . $post_revision_id,
    272294                                'name'  => 'revision',
    273295                        ),
     296                        array(
     297                                'route' => '/wp/v2/posts/' . $post_id . '/autosaves',
     298                                'name'  => 'postAutosaves',
     299                        ),
     300                        array(
     301                                'route' => '/wp/v2/posts/' . $post_id . '/autosaves/' . $post_revision_id,
     302                                'name'  => 'autosave',
     303                        ),
    274304                        array(
    275305                                'route' => '/wp/v2/pages',
    276306                                'name'  => 'PagesCollection',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    287317                                'route' => '/wp/v2/pages/' . $page_id . '/revisions/' . $page_revision_id,
    288318                                'name'  => 'pageRevision',
    289319                        ),
     320                        array(
     321                                'route' => '/wp/v2/pages/' . $page_id . '/autosaves',
     322                                'name'  => 'pageAutosaves',
     323                        ),
     324                        array(
     325                                'route' => '/wp/v2/pages/' . $page_id . '/autosaves/' . $page_revision_id,
     326                                'name'  => 'pageAutosave',
     327                        ),
    290328                        array(
    291329                                'route' => '/wp/v2/media',
    292330                                '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 583843fbc1..50c706f583 100644
    a b mockedApiResponse.Schema = { 
    783783                }
    784784            ]
    785785        },
     786        "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves": {
     787            "namespace": "wp/v2",
     788            "methods": [
     789                "GET",
     790                "POST"
     791            ],
     792            "endpoints": [
     793                {
     794                    "methods": [
     795                        "GET"
     796                    ],
     797                    "args": {
     798                        "parent": {
     799                            "required": false,
     800                            "description": "The ID for the parent of the object.",
     801                            "type": "integer"
     802                        },
     803                        "context": {
     804                            "required": false,
     805                            "default": "view",
     806                            "enum": [
     807                                "view",
     808                                "embed",
     809                                "edit"
     810                            ],
     811                            "description": "Scope under which the request is made; determines fields present in response.",
     812                            "type": "string"
     813                        }
     814                    }
     815                },
     816                {
     817                    "methods": [
     818                        "POST"
     819                    ],
     820                    "args": {
     821                        "parent": {
     822                            "required": false,
     823                            "description": "The ID for the parent of the object.",
     824                            "type": "integer"
     825                        },
     826                        "author": {
     827                            "required": false,
     828                            "description": "The ID for the author of the object.",
     829                            "type": "integer"
     830                        },
     831                        "date": {
     832                            "required": false,
     833                            "description": "The date the object was published, in the site's timezone.",
     834                            "type": "string"
     835                        },
     836                        "date_gmt": {
     837                            "required": false,
     838                            "description": "The date the object was published, as GMT.",
     839                            "type": "string"
     840                        },
     841                        "id": {
     842                            "required": false,
     843                            "description": "Unique identifier for the object.",
     844                            "type": "integer"
     845                        },
     846                        "modified": {
     847                            "required": false,
     848                            "description": "The date the object was last modified, in the site's timezone.",
     849                            "type": "string"
     850                        },
     851                        "modified_gmt": {
     852                            "required": false,
     853                            "description": "The date the object was last modified, as GMT.",
     854                            "type": "string"
     855                        },
     856                        "slug": {
     857                            "required": false,
     858                            "description": "An alphanumeric identifier for the object unique to its type.",
     859                            "type": "string"
     860                        },
     861                        "title": {
     862                            "required": false,
     863                            "description": "The title for the object.",
     864                            "type": "object"
     865                        },
     866                        "content": {
     867                            "required": false,
     868                            "description": "The content for the object.",
     869                            "type": "object"
     870                        },
     871                        "excerpt": {
     872                            "required": false,
     873                            "description": "The excerpt for the object.",
     874                            "type": "object"
     875                        }
     876                    }
     877                }
     878            ]
     879        },
     880        "/wp/v2/posts/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     881            "namespace": "wp/v2",
     882            "methods": [
     883                "GET",
     884                "DELETE"
     885            ],
     886            "endpoints": [
     887                {
     888                    "methods": [
     889                        "GET"
     890                    ],
     891                    "args": {
     892                        "parent": {
     893                            "required": false,
     894                            "description": "The ID for the parent of the object.",
     895                            "type": "integer"
     896                        },
     897                        "id": {
     898                            "required": false,
     899                            "description": "The ID for the object.",
     900                            "type": "integer"
     901                        },
     902                        "context": {
     903                            "required": false,
     904                            "default": "view",
     905                            "enum": [
     906                                "view",
     907                                "embed",
     908                                "edit"
     909                            ],
     910                            "description": "Scope under which the request is made; determines fields present in response.",
     911                            "type": "string"
     912                        }
     913                    }
     914                },
     915                {
     916                    "methods": [
     917                        "DELETE"
     918                    ],
     919                    "args": {
     920                        "parent": {
     921                            "required": false,
     922                            "description": "The ID for the parent of the object.",
     923                            "type": "integer"
     924                        },
     925                        "id": {
     926                            "required": false,
     927                            "description": "The ID for the object.",
     928                            "type": "integer"
     929                        },
     930                        "force": {
     931                            "required": false,
     932                            "default": false,
     933                            "description": "Required to be true, as autosaves do not support trashing.",
     934                            "type": "boolean"
     935                        }
     936                    }
     937                }
     938            ]
     939        },
    786940        "/wp/v2/pages": {
    787941            "namespace": "wp/v2",
    788942            "methods": [
    mockedApiResponse.Schema = { 
    13211475                }
    13221476            ]
    13231477        },
     1478        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves": {
     1479            "namespace": "wp/v2",
     1480            "methods": [
     1481                "GET",
     1482                "POST"
     1483            ],
     1484            "endpoints": [
     1485                {
     1486                    "methods": [
     1487                        "GET"
     1488                    ],
     1489                    "args": {
     1490                        "parent": {
     1491                            "required": false,
     1492                            "description": "The ID for the parent of the object.",
     1493                            "type": "integer"
     1494                        },
     1495                        "context": {
     1496                            "required": false,
     1497                            "default": "view",
     1498                            "enum": [
     1499                                "view",
     1500                                "embed",
     1501                                "edit"
     1502                            ],
     1503                            "description": "Scope under which the request is made; determines fields present in response.",
     1504                            "type": "string"
     1505                        }
     1506                    }
     1507                },
     1508                {
     1509                    "methods": [
     1510                        "POST"
     1511                    ],
     1512                    "args": {
     1513                        "parent": {
     1514                            "required": false,
     1515                            "description": "The ID for the parent of the object.",
     1516                            "type": "integer"
     1517                        },
     1518                        "author": {
     1519                            "required": false,
     1520                            "description": "The ID for the author of the object.",
     1521                            "type": "integer"
     1522                        },
     1523                        "date": {
     1524                            "required": false,
     1525                            "description": "The date the object was published, in the site's timezone.",
     1526                            "type": "string"
     1527                        },
     1528                        "date_gmt": {
     1529                            "required": false,
     1530                            "description": "The date the object was published, as GMT.",
     1531                            "type": "string"
     1532                        },
     1533                        "id": {
     1534                            "required": false,
     1535                            "description": "Unique identifier for the object.",
     1536                            "type": "integer"
     1537                        },
     1538                        "modified": {
     1539                            "required": false,
     1540                            "description": "The date the object was last modified, in the site's timezone.",
     1541                            "type": "string"
     1542                        },
     1543                        "modified_gmt": {
     1544                            "required": false,
     1545                            "description": "The date the object was last modified, as GMT.",
     1546                            "type": "string"
     1547                        },
     1548                        "slug": {
     1549                            "required": false,
     1550                            "description": "An alphanumeric identifier for the object unique to its type.",
     1551                            "type": "string"
     1552                        },
     1553                        "title": {
     1554                            "required": false,
     1555                            "description": "The title for the object.",
     1556                            "type": "object"
     1557                        },
     1558                        "content": {
     1559                            "required": false,
     1560                            "description": "The content for the object.",
     1561                            "type": "object"
     1562                        },
     1563                        "excerpt": {
     1564                            "required": false,
     1565                            "description": "The excerpt for the object.",
     1566                            "type": "object"
     1567                        }
     1568                    }
     1569                }
     1570            ]
     1571        },
     1572        "/wp/v2/pages/(?P<parent>[\\d]+)/autosaves/(?P<id>[\\d]+)": {
     1573            "namespace": "wp/v2",
     1574            "methods": [
     1575                "GET",
     1576                "DELETE"
     1577            ],
     1578            "endpoints": [
     1579                {
     1580                    "methods": [
     1581                        "GET"
     1582                    ],
     1583                    "args": {
     1584                        "parent": {
     1585                            "required": false,
     1586                            "description": "The ID for the parent of the object.",
     1587                            "type": "integer"
     1588                        },
     1589                        "id": {
     1590                            "required": false,
     1591                            "description": "The ID for the object.",
     1592                            "type": "integer"
     1593                        },
     1594                        "context": {
     1595                            "required": false,
     1596                            "default": "view",
     1597                            "enum": [
     1598                                "view",
     1599                                "embed",
     1600                                "edit"
     1601                            ],
     1602                            "description": "Scope under which the request is made; determines fields present in response.",
     1603                            "type": "string"
     1604                        }
     1605                    }
     1606                },
     1607                {
     1608                    "methods": [
     1609                        "DELETE"
     1610                    ],
     1611                    "args": {
     1612                        "parent": {
     1613                            "required": false,
     1614                            "description": "The ID for the parent of the object.",
     1615                            "type": "integer"
     1616                        },
     1617                        "id": {
     1618                            "required": false,
     1619                            "description": "The ID for the object.",
     1620                            "type": "integer"
     1621                        },
     1622                        "force": {
     1623                            "required": false,
     1624                            "default": false,
     1625                            "description": "Required to be true, as autosaves do not support trashing.",
     1626                            "type": "boolean"
     1627                        }
     1628                    }
     1629                }
     1630            ]
     1631        },
    13241632        "/wp/v2/media": {
    13251633            "namespace": "wp/v2",
    13261634            "methods": [
    mockedApiResponse.PostModel = { 
    36573965};
    36583966
    36593967mockedApiResponse.postRevisions = [
     3968    {
     3969        "author": 2,
     3970        "date": "2017-02-14T00:00:00",
     3971        "date_gmt": "2017-02-14T00:00:00",
     3972        "id": 4,
     3973        "modified": "2017-02-14T00:00:00",
     3974        "modified_gmt": "2017-02-14T00:00:00",
     3975        "parent": 3,
     3976        "slug": "3-revision-v1",
     3977        "guid": {
     3978            "rendered": "http://example.org/?p=4"
     3979        },
     3980        "title": {
     3981            "rendered": ""
     3982        },
     3983        "content": {
     3984            "rendered": "<p>Autosave post content.</p>\n"
     3985        },
     3986        "excerpt": {
     3987            "rendered": ""
     3988        },
     3989        "_links": {
     3990            "parent": [
     3991                {
     3992                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/3"
     3993                }
     3994            ]
     3995        }
     3996    },
    36603997    {
    36613998        "author": 2,
    36623999        "date": "2017-02-14T00:00:00",
    mockedApiResponse.revision = { 
    37114048    }
    37124049};
    37134050
     4051mockedApiResponse.postAutosaves = [
     4052    {
     4053        "author": 2,
     4054        "date": "2017-02-14T00:00:00",
     4055        "date_gmt": "2017-02-14T00:00:00",
     4056        "id": 5,
     4057        "modified": "2017-02-14T00:00:00",
     4058        "modified_gmt": "2017-02-14T00:00:00",
     4059        "parent": 3,
     4060        "slug": "3-autosave-v1",
     4061        "guid": {
     4062            "rendered": "http://example.org/?p=5"
     4063        },
     4064        "title": {
     4065            "rendered": ""
     4066        },
     4067        "content": {
     4068            "rendered": "<p>Autosave post content.</p>\n"
     4069        },
     4070        "excerpt": {
     4071            "rendered": ""
     4072        },
     4073        "_links": {
     4074            "parent": [
     4075                {
     4076                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/3"
     4077                }
     4078            ]
     4079        }
     4080    }
     4081];
     4082
     4083mockedApiResponse.autosave = {
     4084    "author": 2,
     4085    "date": "2017-02-14T00:00:00",
     4086    "date_gmt": "2017-02-14T00:00:00",
     4087    "id": 5,
     4088    "modified": "2017-02-14T00:00:00",
     4089    "modified_gmt": "2017-02-14T00:00:00",
     4090    "parent": 3,
     4091    "slug": "3-autosave-v1",
     4092    "guid": {
     4093        "rendered": "http://example.org/?p=5"
     4094    },
     4095    "title": {
     4096        "rendered": ""
     4097    },
     4098    "content": {
     4099        "rendered": "<p>Autosave post content.</p>\n"
     4100    },
     4101    "excerpt": {
     4102        "rendered": ""
     4103    }
     4104};
     4105
    37144106mockedApiResponse.PagesCollection = [
    37154107    {
    37164108        "id": 5,
    mockedApiResponse.pageRevisions = [ 
    38384230        "guid": {
    38394231            "rendered": "http://example.org/?p=6"
    38404232        },
     4233        "title": {
     4234            "rendered": ""
     4235        },
     4236        "content": {
     4237            "rendered": "<p>Autosave page content.</p>\n"
     4238        },
     4239        "excerpt": {
     4240            "rendered": ""
     4241        },
     4242        "_links": {
     4243            "parent": [
     4244                {
     4245                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/5"
     4246                }
     4247            ]
     4248        }
     4249    },
     4250    {
     4251        "author": 2,
     4252        "date": "2017-02-14T00:00:00",
     4253        "date_gmt": "2017-02-14T00:00:00",
     4254        "id": 7,
     4255        "modified": "2017-02-14T00:00:00",
     4256        "modified_gmt": "2017-02-14T00:00:00",
     4257        "parent": 6,
     4258        "slug": "6-revision-v1",
     4259        "guid": {
     4260            "rendered": "http://example.org/?p=7"
     4261        },
    38414262        "title": {
    38424263            "rendered": "REST API Client Fixture: Page"
    38434264        },
    mockedApiResponse.pageRevisions = [ 
    38504271        "_links": {
    38514272            "parent": [
    38524273                {
    3853                     "href": "http://example.org/index.php?rest_route=/wp/v2/pages/5"
     4274                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
    38544275                }
    38554276            ]
    38564277        }
    mockedApiResponse.pageRevision = { 
    38804301    }
    38814302};
    38824303
     4304mockedApiResponse.pageAutosaves = [
     4305    {
     4306        "author": 2,
     4307        "date": "2017-02-14T00:00:00",
     4308        "date_gmt": "2017-02-14T00:00:00",
     4309        "id": 8,
     4310        "modified": "2017-02-14T00:00:00",
     4311        "modified_gmt": "2017-02-14T00:00:00",
     4312        "parent": 6,
     4313        "slug": "6-autosave-v1",
     4314        "guid": {
     4315            "rendered": "http://example.org/?p=8"
     4316        },
     4317        "title": {
     4318            "rendered": ""
     4319        },
     4320        "content": {
     4321            "rendered": "<p>Autosave page content.</p>\n"
     4322        },
     4323        "excerpt": {
     4324            "rendered": ""
     4325        },
     4326        "_links": {
     4327            "parent": [
     4328                {
     4329                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
     4330                }
     4331            ]
     4332        }
     4333    }
     4334];
     4335
     4336mockedApiResponse.pageAutosave = {
     4337    "author": 2,
     4338    "date": "2017-02-14T00:00:00",
     4339    "date_gmt": "2017-02-14T00:00:00",
     4340    "id": 8,
     4341    "modified": "2017-02-14T00:00:00",
     4342    "modified_gmt": "2017-02-14T00:00:00",
     4343    "parent": 6,
     4344    "slug": "6-autosave-v1",
     4345    "guid": {
     4346        "rendered": "http://example.org/?p=8"
     4347    },
     4348    "title": {
     4349        "rendered": ""
     4350    },
     4351    "content": {
     4352        "rendered": "<p>Autosave page content.</p>\n"
     4353    },
     4354    "excerpt": {
     4355        "rendered": ""
     4356    }
     4357};
     4358
    38834359mockedApiResponse.MediaCollection = [
    38844360    {
    38854361        "id": 7,