WordPress.org

Make WordPress Core

Ticket #43316: 43316.23.diff

File 43316.23.diff, 62.3 KB (added by danielbachhuber, 3 years ago)
  • phpunit.xml.dist

    diff --git a/phpunit.xml.dist b/phpunit.xml.dist
    index f81a85e516..1d894aaa8f 100644
    a b  
    66        >
    77    <testsuites>
    88        <!-- Default test suite to run all tests -->
    9         <testsuite>
     9        <testsuite name="default">
    1010            <directory suffix=".php">tests/phpunit/tests</directory>
    1111            <exclude>tests/phpunit/tests/actions/closures.php</exclude>
    1212            <exclude>tests/phpunit/tests/image/editor.php</exclude>
    1313            <exclude>tests/phpunit/tests/image/editorGd.php</exclude>
    1414            <exclude>tests/phpunit/tests/image/editorImagick.php</exclude>
    1515            <exclude>tests/phpunit/tests/oembed/headers.php</exclude>
     16            <exclude>tests/phpunit/tests/rest-api/rest-autosaves-controller.php</exclude>
    1617            <file phpVersion="5.3.0">tests/phpunit/tests/actions/closures.php</file>
    1718            <file phpVersion="5.3.0">tests/phpunit/tests/image/editor.php</file>
    1819            <file phpVersion="5.3.0">tests/phpunit/tests/image/editorGd.php</file>
    1920            <file phpVersion="5.3.0">tests/phpunit/tests/image/editorImagick.php</file>
    2021            <file phpVersion="5.3.0">tests/phpunit/tests/oembed/headers.php</file>
    2122        </testsuite>
     23        <!-- Sets the DOING_AUTOSAVE constant, so needs to be run last -->
     24        <testsuite name="restapi-autosave">
     25            <file>tests/phpunit/tests/rest-api/rest-autosaves-controller.php</file>
     26        </testsuite>
    2227    </testsuites>
    2328    <groups>
    2429        <exclude>
  • src/wp-includes/rest-api.php

    diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
    index 72ee1b696e..ebc3d9a053 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..79529213c3
    - +  
     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.' ),
     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.' ),
     113                                                'type'        => 'integer',
     114                                        ),
     115                                        'id'     => array(
     116                                                'description' => __( 'The ID for the object.' ),
     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.' ), 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.' ), 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.' ), 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.' ),
     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.' ), 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                $fields = $this->get_fields_for_response( $request );
     361
     362                if ( in_array( 'preview_link', $fields, true ) ) {
     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->add_additional_fields_to_object( $response->data, $request );
     377                $response->data = $this->filter_response_by_context( $response->data, $context );
     378
     379                /**
     380                 * Filters a revision returned from the API.
     381                 *
     382                 * Allows modification of the revision right before it is returned.
     383                 *
     384                 * @since 5.0.0
     385                 *
     386                 * @param WP_REST_Response $response The response object.
     387                 * @param WP_Post          $post     The original revision object.
     388                 * @param WP_REST_Request  $request  Request used to generate the response.
     389                 */
     390                return apply_filters( 'rest_prepare_autosave', $response, $post, $request );
     391        }
     392}
  • src/wp-settings.php

    diff --git a/src/wp-settings.php b/src/wp-settings.php
    index c0f0816130..f711b9167b 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' );
  • tests/phpunit/multisite.xml

    diff --git a/tests/phpunit/multisite.xml b/tests/phpunit/multisite.xml
    index 0d60efbe1c..e3ca5bd5d8 100644
    a b  
    99    </php>
    1010    <testsuites>
    1111        <!-- Default test suite to run all tests -->
    12         <testsuite>
     12        <testsuite name="default">
    1313            <directory suffix=".php">tests</directory>
    1414            <exclude>tests/phpunit/tests/actions/closures.php</exclude>
    1515            <exclude>tests/phpunit/tests/image/editor.php</exclude>
    1616            <exclude>tests/phpunit/tests/image/editorGd.php</exclude>
    1717            <exclude>tests/phpunit/tests/image/editorImagick.php</exclude>
     18            <exclude>tests/phpunit/tests/rest-api/rest-autosaves-controller.php</exclude>
    1819            <file phpVersion="5.3.0">tests/phpunit/tests/actions/closures.php</file>
    1920            <file phpVersion="5.3.0">tests/phpunit/tests/image/editor.php</file>
    2021            <file phpVersion="5.3.0">tests/phpunit/tests/image/editorGd.php</file>
    2122            <file phpVersion="5.3.0">tests/phpunit/tests/image/editorImagick.php</file>
    2223        </testsuite>
     24        <!-- Sets the DOING_AUTOSAVE constant, so needs to be run last -->
     25        <testsuite name="restapi-autosave">
     26            <file>tests/phpunit/tests/rest-api/rest-autosaves-controller.php</file>
     27        </testsuite>
    2328    </testsuites>
    2429    <groups>
    2530        <exclude>
  • 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..ede81c5f25
    - +  
     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
     107                // Collection.
     108                $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     109                $response = rest_get_server()->dispatch( $request );
     110                $data     = $response->get_data();
     111                $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
     112                $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
     113
     114                // Single.
     115                $request  = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     116                $response = rest_get_server()->dispatch( $request );
     117                $data     = $response->get_data();
     118                $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
     119                $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
     120        }
     121
     122        public function test_get_items() {
     123                wp_set_current_user( self::$editor_id );
     124                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     125                $response = rest_get_server()->dispatch( $request );
     126                $data     = $response->get_data();
     127                $this->assertEquals( 200, $response->get_status() );
     128                $this->assertCount( 1, $data );
     129
     130                $this->assertEquals( self::$autosave_post_id, $data[0]['id'] );
     131
     132                $this->check_get_autosave_response( $data[0], $this->post_autosave );
     133        }
     134
     135        public function test_get_items_no_permission() {
     136                wp_set_current_user( 0 );
     137                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     138                $response = rest_get_server()->dispatch( $request );
     139                $this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
     140                wp_set_current_user( self::$contributor_id );
     141                $response = rest_get_server()->dispatch( $request );
     142                $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
     143        }
     144
     145        public function test_get_items_missing_parent() {
     146                wp_set_current_user( self::$editor_id );
     147                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' );
     148                $response = rest_get_server()->dispatch( $request );
     149                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     150        }
     151
     152        public function test_get_items_invalid_parent_post_type() {
     153                wp_set_current_user( self::$editor_id );
     154                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
     155                $response = rest_get_server()->dispatch( $request );
     156                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     157        }
     158
     159        public function test_get_item() {
     160                wp_set_current_user( self::$editor_id );
     161                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     162                $response = rest_get_server()->dispatch( $request );
     163                $this->assertEquals( 200, $response->get_status() );
     164                $data = $response->get_data();
     165
     166                $this->check_get_autosave_response( $response, $this->post_autosave );
     167                $fields = array(
     168                        'author',
     169                        'date',
     170                        'date_gmt',
     171                        'modified',
     172                        'modified_gmt',
     173                        'guid',
     174                        'id',
     175                        'parent',
     176                        'slug',
     177                        'title',
     178                        'excerpt',
     179                        'content',
     180                );
     181                $this->assertEqualSets( $fields, array_keys( $data ) );
     182                $this->assertSame( self::$editor_id, $data['author'] );
     183        }
     184
     185        public function test_get_item_embed_context() {
     186                wp_set_current_user( self::$editor_id );
     187                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     188                $request->set_param( 'context', 'embed' );
     189                $response = rest_get_server()->dispatch( $request );
     190                $fields   = array(
     191                        'author',
     192                        'date',
     193                        'id',
     194                        'parent',
     195                        'slug',
     196                        'title',
     197                        'excerpt',
     198                );
     199                $data     = $response->get_data();
     200                $this->assertEqualSets( $fields, array_keys( $data ) );
     201        }
     202
     203        public function test_get_item_no_permission() {
     204                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     205                wp_set_current_user( self::$contributor_id );
     206                $response = rest_get_server()->dispatch( $request );
     207                $this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
     208        }
     209
     210        public function test_get_item_missing_parent() {
     211                wp_set_current_user( self::$editor_id );
     212                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id );
     213                $response = rest_get_server()->dispatch( $request );
     214                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     215
     216        }
     217
     218        public function test_get_item_invalid_parent_post_type() {
     219                wp_set_current_user( self::$editor_id );
     220                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' );
     221                $response = rest_get_server()->dispatch( $request );
     222                $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
     223        }
     224
     225        public function test_delete_item() {
     226                // Doesn't exist.
     227        }
     228
     229        public function test_prepare_item() {
     230                wp_set_current_user( self::$editor_id );
     231                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     232                $response = rest_get_server()->dispatch( $request );
     233                $this->assertEquals( 200, $response->get_status() );
     234                $this->check_get_autosave_response( $response, $this->post_autosave );
     235        }
     236
     237        public function test_get_item_schema() {
     238                $request    = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     239                $response   = rest_get_server()->dispatch( $request );
     240                $data       = $response->get_data();
     241                $properties = $data['schema']['properties'];
     242                $this->assertEquals( 13, count( $properties ) );
     243                $this->assertArrayHasKey( 'author', $properties );
     244                $this->assertArrayHasKey( 'content', $properties );
     245                $this->assertArrayHasKey( 'date', $properties );
     246                $this->assertArrayHasKey( 'date_gmt', $properties );
     247                $this->assertArrayHasKey( 'excerpt', $properties );
     248                $this->assertArrayHasKey( 'guid', $properties );
     249                $this->assertArrayHasKey( 'id', $properties );
     250                $this->assertArrayHasKey( 'modified', $properties );
     251                $this->assertArrayHasKey( 'modified_gmt', $properties );
     252                $this->assertArrayHasKey( 'parent', $properties );
     253                $this->assertArrayHasKey( 'slug', $properties );
     254                $this->assertArrayHasKey( 'title', $properties );
     255                $this->assertArrayHasKey( 'preview_link', $properties );
     256        }
     257
     258        public function test_create_item() {
     259                wp_set_current_user( self::$editor_id );
     260
     261                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     262                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     263
     264                $params = $this->set_post_data(
     265                        array(
     266                                'id' => self::$post_id,
     267                        )
     268                );
     269                $request->set_body_params( $params );
     270                $response = rest_get_server()->dispatch( $request );
     271
     272                $this->check_create_autosave_response( $response );
     273        }
     274
     275        public function test_update_item() {
     276                wp_set_current_user( self::$editor_id );
     277                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     278                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     279
     280                $params = $this->set_post_data(
     281                        array(
     282                                'id'     => self::$post_id,
     283                                'author' => self::$contributor_id,
     284                        )
     285                );
     286
     287                $request->set_body_params( $params );
     288                $response = rest_get_server()->dispatch( $request );
     289
     290                $this->check_create_autosave_response( $response );
     291        }
     292
     293        public function test_update_item_nopriv() {
     294                wp_set_current_user( self::$contributor_id );
     295
     296                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     297                $request->add_header( 'content-type', 'application/x-www-form-urlencoded' );
     298
     299                $params = $this->set_post_data(
     300                        array(
     301                                'id'     => self::$post_id,
     302                                'author' => self::$editor_id,
     303                        )
     304                );
     305
     306                $request->set_body_params( $params );
     307                $response = rest_get_server()->dispatch( $request );
     308
     309                $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
     310        }
     311
     312        public function test_rest_autosave_published_post() {
     313                wp_set_current_user( self::$editor_id );
     314
     315                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     316                $request->add_header( 'content-type', 'application/json' );
     317
     318                $current_post = get_post( self::$post_id );
     319
     320                $autosave_data = $this->set_post_data(
     321                        array(
     322                                'id'      => self::$post_id,
     323                                'content' => 'Updated post \ content',
     324                                'excerpt' => $current_post->post_excerpt,
     325                                'title'   => $current_post->post_title,
     326                        )
     327                );
     328
     329                $request->set_body( wp_json_encode( $autosave_data ) );
     330                $response = rest_get_server()->dispatch( $request );
     331                $new_data = $response->get_data();
     332
     333                $this->assertEquals( $current_post->ID, $new_data['parent'] );
     334                $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] );
     335                $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] );
     336
     337                // Updated post_content.
     338                $this->assertNotEquals( $current_post->post_content, $new_data['content']['raw'] );
     339
     340                $autosave_post = wp_get_post_autosave( self::$post_id );
     341                $this->assertEquals( $autosave_data['title'], $autosave_post->post_title );
     342                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     343                $this->assertEquals( $autosave_data['excerpt'], $autosave_post->post_excerpt );
     344        }
     345
     346        public function test_rest_autosave_draft_post_same_author() {
     347                wp_set_current_user( self::$editor_id );
     348
     349                $post_data = array(
     350                        'post_content' => 'Test post content',
     351                        'post_title'   => 'Test post title',
     352                        'post_excerpt' => 'Test post excerpt',
     353                );
     354                $post_id   = wp_insert_post( $post_data );
     355
     356                $autosave_data = array(
     357                        'id'      => $post_id,
     358                        'content' => 'Updated post \ content',
     359                        'title'   => 'Updated post title',
     360                );
     361
     362                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     363                $request->add_header( 'content-type', 'application/json' );
     364                $request->set_body( wp_json_encode( $autosave_data ) );
     365
     366                $response = rest_get_server()->dispatch( $request );
     367                $new_data = $response->get_data();
     368                $post     = get_post( $post_id );
     369
     370                $this->assertEquals( $post_id, $new_data['id'] );
     371                // The draft post should be updated.
     372                $this->assertEquals( $autosave_data['content'], $new_data['content']['raw'] );
     373                $this->assertEquals( $autosave_data['title'], $new_data['title']['raw'] );
     374                $this->assertEquals( $autosave_data['content'], $post->post_content );
     375                $this->assertEquals( $autosave_data['title'], $post->post_title );
     376
     377                // Not updated.
     378                $this->assertEquals( $post_data['post_excerpt'], $post->post_excerpt );
     379
     380                wp_delete_post( $post_id );
     381        }
     382
     383        public function test_rest_autosave_draft_post_different_author() {
     384                wp_set_current_user( self::$editor_id );
     385
     386                $post_data = array(
     387                        'post_content' => 'Test post content',
     388                        'post_title'   => 'Test post title',
     389                        'post_excerpt' => 'Test post excerpt',
     390                        'post_author'  => self::$editor_id + 1,
     391                );
     392                $post_id   = wp_insert_post( $post_data );
     393
     394                $autosave_data = array(
     395                        'id'      => $post_id,
     396                        'content' => 'Updated post content',
     397                        'excerpt' => $post_data['post_excerpt'],
     398                        'title'   => $post_data['post_title'],
     399                );
     400
     401                $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     402                $request->add_header( 'content-type', 'application/json' );
     403                $request->set_body( wp_json_encode( $autosave_data ) );
     404
     405                $response     = rest_get_server()->dispatch( $request );
     406                $new_data     = $response->get_data();
     407                $current_post = get_post( $post_id );
     408
     409                $this->assertEquals( $current_post->ID, $new_data['parent'] );
     410
     411                // The draft post shouldn't change.
     412                $this->assertEquals( $current_post->post_title, $post_data['post_title'] );
     413                $this->assertEquals( $current_post->post_content, $post_data['post_content'] );
     414                $this->assertEquals( $current_post->post_excerpt, $post_data['post_excerpt'] );
     415
     416                $autosave_post = wp_get_post_autosave( $post_id );
     417
     418                // No changes.
     419                $this->assertEquals( $current_post->post_title, $autosave_post->post_title );
     420                $this->assertEquals( $current_post->post_excerpt, $autosave_post->post_excerpt );
     421
     422                // Has changes.
     423                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     424
     425                wp_delete_post( $post_id );
     426        }
     427
     428        public function test_get_additional_field_registration() {
     429                $schema = array(
     430                        'type'        => 'integer',
     431                        'description' => 'Some integer of mine',
     432                        'enum'        => array( 1, 2, 3, 4 ),
     433                        'context'     => array( 'view', 'edit' ),
     434                );
     435
     436                register_rest_field(
     437                        'post-revision',
     438                        'my_custom_int',
     439                        array(
     440                                'schema'          => $schema,
     441                                'get_callback'    => array( $this, 'additional_field_get_callback' ),
     442                                'update_callback' => array( $this, 'additional_field_update_callback' ),
     443                        )
     444                );
     445
     446                $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' );
     447
     448                $response = rest_get_server()->dispatch( $request );
     449                $data     = $response->get_data();
     450
     451                $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
     452                $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
     453
     454                wp_set_current_user( 1 );
     455
     456                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     457
     458                $response = rest_get_server()->dispatch( $request );
     459                $this->assertArrayHasKey( 'my_custom_int', $response->data );
     460
     461                global $wp_rest_additional_fields;
     462                $wp_rest_additional_fields = array();
     463        }
     464
     465        public function additional_field_get_callback( $object ) {
     466                return get_post_meta( $object['id'], 'my_custom_int', true );
     467        }
     468
     469        public function additional_field_update_callback( $value, $post ) {
     470                update_post_meta( $post->ID, 'my_custom_int', $value );
     471        }
     472
     473        protected function check_get_autosave_response( $response, $autosave ) {
     474                if ( $response instanceof WP_REST_Response ) {
     475                        $links    = $response->get_links();
     476                        $response = $response->get_data();
     477                } else {
     478                        $this->assertArrayHasKey( '_links', $response );
     479                        $links = $response['_links'];
     480                }
     481
     482                $this->assertEquals( $autosave->post_author, $response['author'] );
     483
     484                $rendered_content = apply_filters( 'the_content', $autosave->post_content );
     485                $this->assertEquals( $rendered_content, $response['content']['rendered'] );
     486
     487                $this->assertEquals( mysql_to_rfc3339( $autosave->post_date ), $response['date'] ); //@codingStandardsIgnoreLine
     488                $this->assertEquals( mysql_to_rfc3339( $autosave->post_date_gmt ), $response['date_gmt'] ); //@codingStandardsIgnoreLine
     489
     490                $rendered_guid = apply_filters( 'get_the_guid', $autosave->guid, $autosave->ID );
     491                $this->assertEquals( $rendered_guid, $response['guid']['rendered'] );
     492
     493                $this->assertEquals( $autosave->ID, $response['id'] );
     494                $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified ), $response['modified'] ); //@codingStandardsIgnoreLine
     495                $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified_gmt ), $response['modified_gmt'] ); //@codingStandardsIgnoreLine
     496                $this->assertEquals( $autosave->post_name, $response['slug'] );
     497
     498                $rendered_title = get_the_title( $autosave->ID );
     499                $this->assertEquals( $rendered_title, $response['title']['rendered'] );
     500
     501                $parent            = get_post( $autosave->post_parent );
     502                $parent_controller = new WP_REST_Posts_Controller( $parent->post_type );
     503                $parent_object     = get_post_type_object( $parent->post_type );
     504                $parent_base       = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name;
     505                $this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $autosave->post_parent ), $links['parent'][0]['href'] );
     506        }
     507
     508        public function test_get_item_sets_up_postdata() {
     509                wp_set_current_user( self::$editor_id );
     510                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id );
     511                rest_get_server()->dispatch( $request );
     512
     513                $post           = get_post();
     514                $parent_post_id = wp_is_post_revision( $post->ID );
     515
     516                $this->assertEquals( $post->ID, self::$autosave_post_id );
     517                $this->assertEquals( $parent_post_id, self::$post_id );
     518        }
     519
     520}
  • 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 1b868316f0..70330b7f03 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 { 
    155159                $post_revisions = array_values( wp_get_post_revisions( $post_id ) );
    156160                $post_revision_id = $post_revisions[ count( $post_revisions ) - 1 ]->ID;
    157161
     162                // Create an autosave.
     163                wp_create_post_autosave(
     164                        array(
     165                                'post_ID'      => $post_id,
     166                                'post_content' => 'Autosave post content.',
     167                                'post_type'    => 'post',
     168                        )
     169                );
     170
     171
    158172                $page_id = $this->factory->post->create( array(
    159173                        'post_type'      => 'page',
    160174                        'post_name'      => 'restapi-client-fixture-page',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    172186                $page_revisions = array_values( wp_get_post_revisions( $page_id ) );
    173187                $page_revision_id = $page_revisions[ count( $page_revisions ) - 1 ]->ID;
    174188
     189                // Create an autosave.
     190                wp_create_post_autosave(
     191                        array(
     192                                'post_ID'      => $page_id,
     193                                'post_content' => 'Autosave page content.',
     194                                'post_type'    => 'page',
     195                        )
     196                );
     197
    175198                $tag_id = $this->factory->tag->create( array(
    176199                        'name'        => 'REST API Client Fixture: Tag',
    177200                        'slug'        => 'restapi-client-fixture-tag',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    257280                                'route' => '/wp/v2/posts/' . $post_id . '/revisions/' . $post_revision_id,
    258281                                'name'  => 'revision',
    259282                        ),
     283                        array(
     284                                'route' => '/wp/v2/posts/' . $post_id . '/autosaves',
     285                                'name'  => 'postAutosaves',
     286                        ),
     287                        array(
     288                                'route' => '/wp/v2/posts/' . $post_id . '/autosaves/' . $post_revision_id,
     289                                'name'  => 'autosave',
     290                        ),
    260291                        array(
    261292                                'route' => '/wp/v2/pages',
    262293                                'name'  => 'PagesCollection',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    273304                                'route' => '/wp/v2/pages/'. $page_id . '/revisions/' . $page_revision_id,
    274305                                'name'  => 'pageRevision',
    275306                        ),
     307                        array(
     308                                'route' => '/wp/v2/pages/' . $page_id . '/autosaves',
     309                                'name'  => 'pageAutosaves',
     310                        ),
     311                        array(
     312                                'route' => '/wp/v2/pages/' . $page_id . '/autosaves/' . $page_revision_id,
     313                                'name'  => 'pageAutosave',
     314                        ),
    276315                        array(
    277316                                'route' => '/wp/v2/media',
    278317                                '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 5f3875ca04..42cd329df9 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 = [ 
    38424230            ],
    38434231            "version-history": [
    38444232                {
    3845                     "count": 1,
     4233                    "count": 2,
    38464234                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4/revisions"
    38474235                }
    38484236            ],
    mockedApiResponse.postRevisions = [ 
    39334321        "guid": {
    39344322            "rendered": "http://example.org/?p=5"
    39354323        },
     4324        "title": {
     4325            "rendered": ""
     4326        },
     4327        "content": {
     4328            "rendered": "<p>Autosave post content.</p>\n"
     4329        },
     4330        "excerpt": {
     4331            "rendered": ""
     4332        },
     4333        "_links": {
     4334            "parent": [
     4335                {
     4336                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4"
     4337                }
     4338            ]
     4339        }
     4340    },
     4341    {
     4342        "author": 389,
     4343        "date": "2017-02-14T00:00:00",
     4344        "date_gmt": "2017-02-14T00:00:00",
     4345        "id": 36707,
     4346        "modified": "2017-02-14T00:00:00",
     4347        "modified_gmt": "2017-02-14T00:00:00",
     4348        "parent": 36706,
     4349        "slug": "36706-revision-v1",
     4350        "guid": {
     4351            "rendered": "http://example.org/?p=36707"
     4352        },
    39364353        "title": {
    39374354            "rendered": "REST API Client Fixture: Post"
    39384355        },
    mockedApiResponse.postRevisions = [ 
    39454362        "_links": {
    39464363            "parent": [
    39474364                {
    3948                     "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4"
     4365                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/36706"
    39494366                }
    39504367            ]
    39514368        }
    mockedApiResponse.revision = { 
    39754392    }
    39764393};
    39774394
     4395mockedApiResponse.postAutosaves = [
     4396    {
     4397        "author": 389,
     4398        "date": "2017-02-14T00:00:00",
     4399        "date_gmt": "2017-02-14T00:00:00",
     4400        "id": 36708,
     4401        "modified": "2017-02-14T00:00:00",
     4402        "modified_gmt": "2017-02-14T00:00:00",
     4403        "parent": 36706,
     4404        "slug": "36706-autosave-v1",
     4405        "guid": {
     4406            "rendered": "http://example.org/?p=36708"
     4407        },
     4408        "title": {
     4409            "rendered": ""
     4410        },
     4411        "content": {
     4412            "rendered": "<p>Autosave post content.</p>\n"
     4413        },
     4414        "excerpt": {
     4415            "rendered": ""
     4416        },
     4417        "_links": {
     4418            "parent": [
     4419                {
     4420                    "href": "http://example.org/index.php?rest_route=/wp/v2/posts/36706"
     4421                }
     4422            ]
     4423        }
     4424    }
     4425];
     4426
     4427mockedApiResponse.autosave = {
     4428    "author": 389,
     4429    "date": "2017-02-14T00:00:00",
     4430    "date_gmt": "2017-02-14T00:00:00",
     4431    "id": 36708,
     4432    "modified": "2017-02-14T00:00:00",
     4433    "modified_gmt": "2017-02-14T00:00:00",
     4434    "parent": 36706,
     4435    "slug": "36706-autosave-v1",
     4436    "guid": {
     4437        "rendered": "http://example.org/?p=36708"
     4438    },
     4439    "title": {
     4440        "rendered": ""
     4441    },
     4442    "content": {
     4443        "rendered": "<p>Autosave post content.</p>\n"
     4444    },
     4445    "excerpt": {
     4446        "rendered": ""
     4447    }
     4448};
     4449
    39784450mockedApiResponse.PagesCollection = [
    39794451    {
    39804452        "id": 6,
    mockedApiResponse.PagesCollection = [ 
    40344506            ],
    40354507            "version-history": [
    40364508                {
    4037                     "count": 1,
     4509                    "count": 2,
    40384510                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6/revisions"
    40394511                }
    40404512            ],
    mockedApiResponse.pageRevisions = [ 
    41094581        "guid": {
    41104582            "rendered": "http://example.org/?p=7"
    41114583        },
     4584        "title": {
     4585            "rendered": ""
     4586        },
     4587        "content": {
     4588            "rendered": "<p>Autosave page content.</p>\n"
     4589        },
     4590        "excerpt": {
     4591            "rendered": ""
     4592        },
     4593        "_links": {
     4594            "parent": [
     4595                {
     4596                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
     4597                }
     4598            ]
     4599        }
     4600    },
     4601    {
     4602        "author": 389,
     4603        "date": "2017-02-14T00:00:00",
     4604        "date_gmt": "2017-02-14T00:00:00",
     4605        "id": 36710,
     4606        "modified": "2017-02-14T00:00:00",
     4607        "modified_gmt": "2017-02-14T00:00:00",
     4608        "parent": 36709,
     4609        "slug": "36709-revision-v1",
     4610        "guid": {
     4611            "rendered": "http://example.org/?p=36710"
     4612        },
    41124613        "title": {
    41134614            "rendered": "REST API Client Fixture: Page"
    41144615        },
    mockedApiResponse.pageRevisions = [ 
    41214622        "_links": {
    41224623            "parent": [
    41234624                {
    4124                     "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6"
     4625                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/36709"
    41254626                }
    41264627            ]
    41274628        }
    mockedApiResponse.pageRevision = { 
    41514652    }
    41524653};
    41534654
     4655mockedApiResponse.pageAutosaves = [
     4656    {
     4657        "author": 389,
     4658        "date": "2017-02-14T00:00:00",
     4659        "date_gmt": "2017-02-14T00:00:00",
     4660        "id": 36711,
     4661        "modified": "2017-02-14T00:00:00",
     4662        "modified_gmt": "2017-02-14T00:00:00",
     4663        "parent": 36709,
     4664        "slug": "36709-autosave-v1",
     4665        "guid": {
     4666            "rendered": "http://example.org/?p=36711"
     4667        },
     4668        "title": {
     4669            "rendered": ""
     4670        },
     4671        "content": {
     4672            "rendered": "<p>Autosave page content.</p>\n"
     4673        },
     4674        "excerpt": {
     4675            "rendered": ""
     4676        },
     4677        "_links": {
     4678            "parent": [
     4679                {
     4680                    "href": "http://example.org/index.php?rest_route=/wp/v2/pages/36709"
     4681                }
     4682            ]
     4683        }
     4684    }
     4685];
     4686
     4687mockedApiResponse.pageAutosave = {
     4688    "author": 389,
     4689    "date": "2017-02-14T00:00:00",
     4690    "date_gmt": "2017-02-14T00:00:00",
     4691    "id": 36711,
     4692    "modified": "2017-02-14T00:00:00",
     4693    "modified_gmt": "2017-02-14T00:00:00",
     4694    "parent": 36709,
     4695    "slug": "36709-autosave-v1",
     4696    "guid": {
     4697        "rendered": "http://example.org/?p=36711"
     4698    },
     4699    "title": {
     4700        "rendered": ""
     4701    },
     4702    "content": {
     4703        "rendered": "<p>Autosave page content.</p>\n"
     4704    },
     4705    "excerpt": {
     4706        "rendered": ""
     4707    }
     4708};
     4709
    41544710mockedApiResponse.MediaCollection = [
    41554711    {
    41564712        "id": 8,