From 225f191f3273346b844c0c9e2203981bc586f600 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers Date: Thu, 13 Dec 2018 22:41:47 +0000 Subject: [PATCH] REST API: Introduce Autosaves controller and endpoint. - Adds `WP_REST_Autosaves_Controller` which extends `WP_REST_Revisions_Controller`. - Autosaves endpoint is registered for all post types except attachment because even post types without revisions enabled are expected to autosave. - Because setting the `DOING_AUTOSAVE` constant pollutes the test suite, autosaves tests are run last. We may want to improve upon this later. Also, use a truly impossibly high number in User Controller tests. The number `100`, (or `7777` in `trunk`), could be valid in certain test run configurations. The `REST_TESTS_IMPOSSIBLY_HIGH_NUMBER` constant is impossibly high for this very reason. Finally, Skip Autosaves controller test for multisite. There's a PHP 5.2 edge case where paths calculated differently, possibly caused by differing version of PHPUnit. Props adamsilverstein, aduth, azaozz, danielbachhuber, rmccue, danielbachhuber. Merges [43767], [43768], [43769] to trunk. See #45132, #45131. Fixes #45128, #43316. git-svn-id: https://develop.svn.wordpress.org/trunk@44126 602fd350-edb4-49c9-b593-d223f7449a82 --- phpunit.xml.dist | 7 +- src/wp-includes/rest-api.php | 5 + .../class-wp-rest-autosaves-controller.php | 392 ++++++++++++ src/wp-settings.php | 1 + tests/phpunit/multisite.xml | 6 +- .../rest-api/rest-autosaves-controller.php | 520 ++++++++++++++++ .../tests/rest-api/rest-schema-setup.php | 38 ++ .../tests/rest-api/rest-users-controller.php | 7 +- tests/qunit/fixtures/wp-api-generated.js | 564 +++++++++++++++++- 9 files changed, 1531 insertions(+), 9 deletions(-) create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php create mode 100644 tests/phpunit/tests/rest-api/rest-autosaves-controller.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f81a85e516..1d894aaa8f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,19 +6,24 @@ > - + tests/phpunit/tests tests/phpunit/tests/actions/closures.php tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editorGd.php tests/phpunit/tests/image/editorImagick.php tests/phpunit/tests/oembed/headers.php + tests/phpunit/tests/rest-api/rest-autosaves-controller.php tests/phpunit/tests/actions/closures.php tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editorGd.php tests/phpunit/tests/image/editorImagick.php tests/phpunit/tests/oembed/headers.php + + + tests/phpunit/tests/rest-api/rest-autosaves-controller.php + diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 5cd7ef8774..5ab3857cd2 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -193,6 +193,11 @@ function create_initial_rest_routes() { $revisions_controller = new WP_REST_Revisions_Controller( $post_type->name ); $revisions_controller->register_routes(); } + + if ( 'attachment' !== $post_type->name ) { + $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name ); + $autosaves_controller->register_routes(); + } } // Post types. 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 --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php @@ -0,0 +1,392 @@ +parent_post_type = $parent_post_type; + $post_type_object = get_post_type_object( $parent_post_type ); + + // Ensure that post type-specific controller logic is available. + $parent_controller_class = ! empty( $post_type_object->rest_controller_class ) ? $post_type_object->rest_controller_class : 'WP_REST_Posts_Controller'; + + $this->parent_controller = new $parent_controller_class( $post_type_object->name ); + $this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type ); + $this->rest_namespace = 'wp/v2'; + $this->rest_base = 'autosaves'; + $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + } + + /** + * Registers routes for autosaves. + * + * @since 5.0.0 + * + * @see register_rest_route() + */ + public function register_routes() { + register_rest_route( + $this->rest_namespace, + '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The ID for the parent of the object.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this->revisions_controller, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->rest_namespace, + '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The ID for the parent of the object.' ), + 'type' => 'integer', + ), + 'id' => array( + 'description' => __( 'The ID for the object.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + } + + /** + * Get the parent post. + * + * @since 5.0.0 + * + * @param int $parent_id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent( $parent_id ) { + return $this->revisions_controller->get_parent( $parent_id ); + } + + /** + * Checks if a given request has access to create an autosave revision. + * + * Autosave revisions inherit permissions from the parent post, + * check if the current user has permission to edit the post. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise. + */ + public function create_item_permissions_check( $request ) { + $id = $request->get_param( 'id' ); + if ( empty( $id ) ) { + return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) ); + } + + return $this->parent_controller->update_item_permissions_check( $request ); + } + + /** + * Creates, updates or deletes an autosave revision. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function create_item( $request ) { + + if ( ! defined( 'DOING_AUTOSAVE' ) ) { + define( 'DOING_AUTOSAVE', true ); + } + + $post = get_post( $request->get_param( 'id' ) ); + + if ( is_wp_error( $post ) ) { + return $post; + } + + $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); + $prepared_post->ID = $post->ID; + $user_id = get_current_user_id(); + + if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) { + // Draft posts for the same author: autosaving updates the post and does not create a revision. + // Convert the post object to an array and add slashes, wp_update_post expects escaped array. + $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true ); + } else { + // Non-draft posts: create or update the post autosave. + $autosave_id = $this->create_post_autosave( (array) $prepared_post ); + } + + if ( is_wp_error( $autosave_id ) ) { + return $autosave_id; + } + + $autosave = get_post( $autosave_id ); + $request->set_param( 'context', 'edit' ); + + $response = $this->prepare_item_for_response( $autosave, $request ); + $response = rest_ensure_response( $response ); + + return $response; + } + + /** + * Get the autosave, if the ID is valid. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. + */ + public function get_item( $request ) { + $parent_id = (int) $request->get_param( 'parent' ); + + if ( $parent_id <= 0 ) { + return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.' ), array( 'status' => 404 ) ); + } + + $autosave = wp_get_post_autosave( $parent_id ); + + if ( ! $autosave ) { + return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.' ), array( 'status' => 404 ) ); + } + + $response = $this->prepare_item_for_response( $autosave, $request ); + return $response; + } + + /** + * Gets a collection of autosaves using wp_get_post_autosave. + * + * Contains the user's autosave, for empty if it doesn't exist. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $parent = $this->get_parent( $request->get_param( 'parent' ) ); + if ( is_wp_error( $parent ) ) { + return $parent; + } + + $response = array(); + $parent_id = $parent->ID; + $revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) ); + + foreach ( $revisions as $revision ) { + if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) { + $data = $this->prepare_item_for_response( $revision, $request ); + $response[] = $this->prepare_response_for_collection( $data ); + } + } + + return rest_ensure_response( $response ); + } + + + /** + * Retrieves the autosave's schema, conforming to JSON Schema. + * + * @since 5.0.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + $schema = $this->revisions_controller->get_item_schema(); + + $schema['properties']['preview_link'] = array( + 'description' => __( 'Preview link for the post.' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => array( 'edit' ), + 'readonly' => true, + ); + + return $schema; + } + + /** + * Creates autosave for the specified post. + * + * From wp-admin/post.php. + * + * @since 5.0.0 + * + * @param mixed $post_data Associative array containing the post data. + * @return mixed The autosave revision ID or WP_Error. + */ + public function create_post_autosave( $post_data ) { + + $post_id = (int) $post_data['ID']; + $post = get_post( $post_id ); + + if ( is_wp_error( $post ) ) { + return $post; + } + + $user_id = get_current_user_id(); + + // Store one autosave per author. If there is already an autosave, overwrite it. + $old_autosave = wp_get_post_autosave( $post_id, $user_id ); + + if ( $old_autosave ) { + $new_autosave = _wp_post_revision_data( $post_data, true ); + $new_autosave['ID'] = $old_autosave->ID; + $new_autosave['post_author'] = $user_id; + + // If the new autosave has the same content as the post, delete the autosave. + $autosave_is_different = false; + + foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { + if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { + $autosave_is_different = true; + break; + } + } + + if ( ! $autosave_is_different ) { + wp_delete_post_revision( $old_autosave->ID ); + 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 ) ); + } + + /** + * This filter is documented in wp-admin/post.php. + */ + do_action( 'wp_creating_autosave', $new_autosave ); + + // wp_update_post expects escaped array. + return wp_update_post( wp_slash( $new_autosave ) ); + } + + // Create the new autosave as a special post revision. + return _wp_put_post_revision( $post_data, true ); + } + + /** + * Prepares the revision for the REST response. + * + * @since 5.0.0 + * + * @param WP_Post $post Post revision object. + * @param WP_REST_Request $request Request object. + * + * @return WP_REST_Response Response object. + */ + public function prepare_item_for_response( $post, $request ) { + + $response = $this->revisions_controller->prepare_item_for_response( $post, $request ); + + $fields = $this->get_fields_for_response( $request ); + + if ( in_array( 'preview_link', $fields, true ) ) { + $parent_id = wp_is_post_autosave( $post ); + $preview_post_id = false === $parent_id ? $post->ID : $parent_id; + $preview_query_args = array(); + + if ( false !== $parent_id ) { + $preview_query_args['preview_id'] = $parent_id; + $preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id ); + } + + $response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args ); + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $response->data = $this->add_additional_fields_to_object( $response->data, $request ); + $response->data = $this->filter_response_by_context( $response->data, $context ); + + /** + * Filters a revision returned from the API. + * + * Allows modification of the revision right before it is returned. + * + * @since 5.0.0 + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post The original revision object. + * @param WP_REST_Request $request Request used to generate the response. + */ + return apply_filters( 'rest_prepare_autosave', $response, $post, $request ); + } +} diff --git a/src/wp-settings.php b/src/wp-settings.php index 87ca2d9e94..9b1d9f5445 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -230,6 +230,7 @@ require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-contro require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' ); require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' ); require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' ); +require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php' ); require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' ); require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' ); require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' ); diff --git a/tests/phpunit/multisite.xml b/tests/phpunit/multisite.xml index 0d60efbe1c..d8aa698e36 100644 --- a/tests/phpunit/multisite.xml +++ b/tests/phpunit/multisite.xml @@ -9,12 +9,16 @@ - + tests tests/phpunit/tests/actions/closures.php tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editorGd.php tests/phpunit/tests/image/editorImagick.php + + + tests/rest-api/rest-autosaves-controller.php + tests/phpunit/tests/rest-api/rest-autosaves-controller.php tests/phpunit/tests/actions/closures.php tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editorGd.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 --- /dev/null +++ b/tests/phpunit/tests/rest-api/rest-autosaves-controller.php @@ -0,0 +1,520 @@ + 'Post Title', + 'content' => 'Post content', + 'excerpt' => 'Post excerpt', + 'name' => 'test', + 'author' => get_current_user_id(), + ); + + return wp_parse_args( $args, $defaults ); + } + + protected function check_create_autosave_response( $response ) { + $this->assertNotInstanceOf( 'WP_Error', $response ); + $response = rest_ensure_response( $response ); + $data = $response->get_data(); + + $this->assertArrayHasKey( 'content', $data ); + $this->assertArrayHasKey( 'excerpt', $data ); + $this->assertArrayHasKey( 'title', $data ); + } + + public static function wpSetUpBeforeClass( $factory ) { + self::$post_id = $factory->post->create(); + self::$page_id = $factory->post->create( array( 'post_type' => 'page' ) ); + + self::$editor_id = $factory->user->create( + array( + 'role' => 'editor', + ) + ); + self::$contributor_id = $factory->user->create( + array( + 'role' => 'contributor', + ) + ); + + wp_set_current_user( self::$editor_id ); + + // Create an autosave. + self::$autosave_post_id = wp_create_post_autosave( + array( + 'post_content' => 'This content is better.', + 'post_ID' => self::$post_id, + 'post_type' => 'post', + ) + ); + + self::$autosave_page_id = wp_create_post_autosave( + array( + 'post_content' => 'This content is better.', + 'post_ID' => self::$page_id, + 'post_type' => 'post', + ) + ); + + } + + public static function wpTearDownAfterClass() { + // Also deletes revisions. + wp_delete_post( self::$post_id, true ); + wp_delete_post( self::$page_id, true ); + + self::delete_user( self::$editor_id ); + self::delete_user( self::$contributor_id ); + } + + public function setUp() { + parent::setUp(); + wp_set_current_user( self::$editor_id ); + + $this->post_autosave = wp_get_post_autosave( self::$post_id ); + } + + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( '/wp/v2/posts/(?P[\d]+)/autosaves', $routes ); + $this->assertArrayHasKey( '/wp/v2/posts/(?P[\d]+)/autosaves/(?P[\d]+)', $routes ); + $this->assertArrayHasKey( '/wp/v2/pages/(?P[\d]+)/autosaves', $routes ); + $this->assertArrayHasKey( '/wp/v2/pages/(?P[\d]+)/autosaves/(?P[\d]+)', $routes ); + } + + public function test_context_param() { + + // Collection. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); + $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); + + // Single. + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); + $this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] ); + } + + public function test_get_items() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 1, $data ); + + $this->assertEquals( self::$autosave_post_id, $data[0]['id'] ); + + $this->check_get_autosave_response( $data[0], $this->post_autosave ); + } + + public function test_get_items_no_permission() { + wp_set_current_user( 0 ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 401 ); + wp_set_current_user( self::$contributor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); + } + + public function test_get_items_missing_parent() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + public function test_get_items_invalid_parent_post_type() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + public function test_get_item() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + + $this->check_get_autosave_response( $response, $this->post_autosave ); + $fields = array( + 'author', + 'date', + 'date_gmt', + 'modified', + 'modified_gmt', + 'guid', + 'id', + 'parent', + 'slug', + 'title', + 'excerpt', + 'content', + ); + $this->assertEqualSets( $fields, array_keys( $data ) ); + $this->assertSame( self::$editor_id, $data['author'] ); + } + + public function test_get_item_embed_context() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + $request->set_param( 'context', 'embed' ); + $response = rest_get_server()->dispatch( $request ); + $fields = array( + 'author', + 'date', + 'id', + 'parent', + 'slug', + 'title', + 'excerpt', + ); + $data = $response->get_data(); + $this->assertEqualSets( $fields, array_keys( $data ) ); + } + + public function test_get_item_no_permission() { + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + wp_set_current_user( self::$contributor_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_read', $response, 403 ); + } + + public function test_get_item_missing_parent() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/autosaves/' . self::$autosave_post_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + + } + + public function test_get_item_invalid_parent_post_type() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$page_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 ); + } + + public function test_delete_item() { + // Doesn't exist. + } + + public function test_prepare_item() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->check_get_autosave_response( $response, $this->post_autosave ); + } + + public function test_get_item_schema() { + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $properties = $data['schema']['properties']; + $this->assertEquals( 13, count( $properties ) ); + $this->assertArrayHasKey( 'author', $properties ); + $this->assertArrayHasKey( 'content', $properties ); + $this->assertArrayHasKey( 'date', $properties ); + $this->assertArrayHasKey( 'date_gmt', $properties ); + $this->assertArrayHasKey( 'excerpt', $properties ); + $this->assertArrayHasKey( 'guid', $properties ); + $this->assertArrayHasKey( 'id', $properties ); + $this->assertArrayHasKey( 'modified', $properties ); + $this->assertArrayHasKey( 'modified_gmt', $properties ); + $this->assertArrayHasKey( 'parent', $properties ); + $this->assertArrayHasKey( 'slug', $properties ); + $this->assertArrayHasKey( 'title', $properties ); + $this->assertArrayHasKey( 'preview_link', $properties ); + } + + public function test_create_item() { + wp_set_current_user( self::$editor_id ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); + + $params = $this->set_post_data( + array( + 'id' => self::$post_id, + ) + ); + $request->set_body_params( $params ); + $response = rest_get_server()->dispatch( $request ); + + $this->check_create_autosave_response( $response ); + } + + public function test_update_item() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); + + $params = $this->set_post_data( + array( + 'id' => self::$post_id, + 'author' => self::$contributor_id, + ) + ); + + $request->set_body_params( $params ); + $response = rest_get_server()->dispatch( $request ); + + $this->check_create_autosave_response( $response ); + } + + public function test_update_item_nopriv() { + wp_set_current_user( self::$contributor_id ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $request->add_header( 'content-type', 'application/x-www-form-urlencoded' ); + + $params = $this->set_post_data( + array( + 'id' => self::$post_id, + 'author' => self::$editor_id, + ) + ); + + $request->set_body_params( $params ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); + } + + public function test_rest_autosave_published_post() { + wp_set_current_user( self::$editor_id ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $request->add_header( 'content-type', 'application/json' ); + + $current_post = get_post( self::$post_id ); + + $autosave_data = $this->set_post_data( + array( + 'id' => self::$post_id, + 'content' => 'Updated post \ content', + 'excerpt' => $current_post->post_excerpt, + 'title' => $current_post->post_title, + ) + ); + + $request->set_body( wp_json_encode( $autosave_data ) ); + $response = rest_get_server()->dispatch( $request ); + $new_data = $response->get_data(); + + $this->assertEquals( $current_post->ID, $new_data['parent'] ); + $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] ); + $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] ); + + // Updated post_content. + $this->assertNotEquals( $current_post->post_content, $new_data['content']['raw'] ); + + $autosave_post = wp_get_post_autosave( self::$post_id ); + $this->assertEquals( $autosave_data['title'], $autosave_post->post_title ); + $this->assertEquals( $autosave_data['content'], $autosave_post->post_content ); + $this->assertEquals( $autosave_data['excerpt'], $autosave_post->post_excerpt ); + } + + public function test_rest_autosave_draft_post_same_author() { + wp_set_current_user( self::$editor_id ); + + $post_data = array( + 'post_content' => 'Test post content', + 'post_title' => 'Test post title', + 'post_excerpt' => 'Test post excerpt', + ); + $post_id = wp_insert_post( $post_data ); + + $autosave_data = array( + 'id' => $post_id, + 'content' => 'Updated post \ content', + 'title' => 'Updated post title', + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $request->add_header( 'content-type', 'application/json' ); + $request->set_body( wp_json_encode( $autosave_data ) ); + + $response = rest_get_server()->dispatch( $request ); + $new_data = $response->get_data(); + $post = get_post( $post_id ); + + $this->assertEquals( $post_id, $new_data['id'] ); + // The draft post should be updated. + $this->assertEquals( $autosave_data['content'], $new_data['content']['raw'] ); + $this->assertEquals( $autosave_data['title'], $new_data['title']['raw'] ); + $this->assertEquals( $autosave_data['content'], $post->post_content ); + $this->assertEquals( $autosave_data['title'], $post->post_title ); + + // Not updated. + $this->assertEquals( $post_data['post_excerpt'], $post->post_excerpt ); + + wp_delete_post( $post_id ); + } + + public function test_rest_autosave_draft_post_different_author() { + wp_set_current_user( self::$editor_id ); + + $post_data = array( + 'post_content' => 'Test post content', + 'post_title' => 'Test post title', + 'post_excerpt' => 'Test post excerpt', + 'post_author' => self::$editor_id + 1, + ); + $post_id = wp_insert_post( $post_data ); + + $autosave_data = array( + 'id' => $post_id, + 'content' => 'Updated post content', + 'excerpt' => $post_data['post_excerpt'], + 'title' => $post_data['post_title'], + ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + $request->add_header( 'content-type', 'application/json' ); + $request->set_body( wp_json_encode( $autosave_data ) ); + + $response = rest_get_server()->dispatch( $request ); + $new_data = $response->get_data(); + $current_post = get_post( $post_id ); + + $this->assertEquals( $current_post->ID, $new_data['parent'] ); + + // The draft post shouldn't change. + $this->assertEquals( $current_post->post_title, $post_data['post_title'] ); + $this->assertEquals( $current_post->post_content, $post_data['post_content'] ); + $this->assertEquals( $current_post->post_excerpt, $post_data['post_excerpt'] ); + + $autosave_post = wp_get_post_autosave( $post_id ); + + // No changes. + $this->assertEquals( $current_post->post_title, $autosave_post->post_title ); + $this->assertEquals( $current_post->post_excerpt, $autosave_post->post_excerpt ); + + // Has changes. + $this->assertEquals( $autosave_data['content'], $autosave_post->post_content ); + + wp_delete_post( $post_id ); + } + + public function test_get_additional_field_registration() { + $schema = array( + 'type' => 'integer', + 'description' => 'Some integer of mine', + 'enum' => array( 1, 2, 3, 4 ), + 'context' => array( 'view', 'edit' ), + ); + + register_rest_field( + 'post-revision', + 'my_custom_int', + array( + 'schema' => $schema, + 'get_callback' => array( $this, 'additional_field_get_callback' ), + 'update_callback' => array( $this, 'additional_field_update_callback' ), + ) + ); + + $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . self::$post_id . '/autosaves' ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] ); + $this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] ); + + wp_set_current_user( 1 ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertArrayHasKey( 'my_custom_int', $response->data ); + + global $wp_rest_additional_fields; + $wp_rest_additional_fields = array(); + } + + public function additional_field_get_callback( $object ) { + return get_post_meta( $object['id'], 'my_custom_int', true ); + } + + public function additional_field_update_callback( $value, $post ) { + update_post_meta( $post->ID, 'my_custom_int', $value ); + } + + protected function check_get_autosave_response( $response, $autosave ) { + if ( $response instanceof WP_REST_Response ) { + $links = $response->get_links(); + $response = $response->get_data(); + } else { + $this->assertArrayHasKey( '_links', $response ); + $links = $response['_links']; + } + + $this->assertEquals( $autosave->post_author, $response['author'] ); + + $rendered_content = apply_filters( 'the_content', $autosave->post_content ); + $this->assertEquals( $rendered_content, $response['content']['rendered'] ); + + $this->assertEquals( mysql_to_rfc3339( $autosave->post_date ), $response['date'] ); //@codingStandardsIgnoreLine + $this->assertEquals( mysql_to_rfc3339( $autosave->post_date_gmt ), $response['date_gmt'] ); //@codingStandardsIgnoreLine + + $rendered_guid = apply_filters( 'get_the_guid', $autosave->guid, $autosave->ID ); + $this->assertEquals( $rendered_guid, $response['guid']['rendered'] ); + + $this->assertEquals( $autosave->ID, $response['id'] ); + $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified ), $response['modified'] ); //@codingStandardsIgnoreLine + $this->assertEquals( mysql_to_rfc3339( $autosave->post_modified_gmt ), $response['modified_gmt'] ); //@codingStandardsIgnoreLine + $this->assertEquals( $autosave->post_name, $response['slug'] ); + + $rendered_title = get_the_title( $autosave->ID ); + $this->assertEquals( $rendered_title, $response['title']['rendered'] ); + + $parent = get_post( $autosave->post_parent ); + $parent_controller = new WP_REST_Posts_Controller( $parent->post_type ); + $parent_object = get_post_type_object( $parent->post_type ); + $parent_base = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name; + $this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $autosave->post_parent ), $links['parent'][0]['href'] ); + } + + public function test_get_item_sets_up_postdata() { + wp_set_current_user( self::$editor_id ); + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/autosaves/' . self::$autosave_post_id ); + rest_get_server()->dispatch( $request ); + + $post = get_post(); + $parent_post_id = wp_is_post_revision( $post->ID ); + + $this->assertEquals( $post->ID, self::$autosave_post_id ); + $this->assertEquals( $parent_post_id, self::$post_id ); + } + +} diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 0530d7397c..32a8849b9f 100644 --- a/tests/phpunit/tests/rest-api/rest-schema-setup.php +++ b/tests/phpunit/tests/rest-api/rest-schema-setup.php @@ -89,10 +89,14 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { '/wp/v2/posts/(?P[\\d]+)', '/wp/v2/posts/(?P[\\d]+)/revisions', '/wp/v2/posts/(?P[\\d]+)/revisions/(?P[\\d]+)', + '/wp/v2/posts/(?P[\\d]+)/autosaves', + '/wp/v2/posts/(?P[\\d]+)/autosaves/(?P[\\d]+)', '/wp/v2/pages', '/wp/v2/pages/(?P[\\d]+)', '/wp/v2/pages/(?P[\\d]+)/revisions', '/wp/v2/pages/(?P[\\d]+)/revisions/(?P[\\d]+)', + '/wp/v2/pages/(?P[\\d]+)/autosaves', + '/wp/v2/pages/(?P[\\d]+)/autosaves/(?P[\\d]+)', '/wp/v2/media', '/wp/v2/media/(?P[\\d]+)', '/wp/v2/types', @@ -161,6 +165,15 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { $post_revisions = array_values( wp_get_post_revisions( $post_id ) ); $post_revision_id = $post_revisions[ count( $post_revisions ) - 1 ]->ID; + // Create an autosave. + wp_create_post_autosave( + array( + 'post_ID' => $post_id, + 'post_content' => 'Autosave post content.', + 'post_type' => 'post', + ) + ); + $page_id = $this->factory->post->create( array( 'post_type' => 'page', @@ -182,6 +195,15 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { $page_revisions = array_values( wp_get_post_revisions( $page_id ) ); $page_revision_id = $page_revisions[ count( $page_revisions ) - 1 ]->ID; + // Create an autosave. + wp_create_post_autosave( + array( + 'post_ID' => $page_id, + 'post_content' => 'Autosave page content.', + 'post_type' => 'page', + ) + ); + $tag_id = $this->factory->tag->create( array( 'name' => 'REST API Client Fixture: Tag', @@ -275,6 +297,14 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 'route' => '/wp/v2/posts/' . $post_id . '/revisions/' . $post_revision_id, 'name' => 'revision', ), + array( + 'route' => '/wp/v2/posts/' . $post_id . '/autosaves', + 'name' => 'postAutosaves', + ), + array( + 'route' => '/wp/v2/posts/' . $post_id . '/autosaves/' . $post_revision_id, + 'name' => 'autosave', + ), array( 'route' => '/wp/v2/pages', 'name' => 'PagesCollection', @@ -291,6 +321,14 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 'route' => '/wp/v2/pages/' . $page_id . '/revisions/' . $page_revision_id, 'name' => 'pageRevision', ), + array( + 'route' => '/wp/v2/pages/' . $page_id . '/autosaves', + 'name' => 'pageAutosaves', + ), + array( + 'route' => '/wp/v2/pages/' . $page_id . '/autosaves/' . $page_revision_id, + 'name' => 'pageAutosave', + ), array( 'route' => '/wp/v2/media', 'name' => 'MediaCollection', diff --git a/tests/phpunit/tests/rest-api/rest-users-controller.php b/tests/phpunit/tests/rest-api/rest-users-controller.php index a1a3d9c6f3..76c7678c77 100644 --- a/tests/phpunit/tests/rest-api/rest-users-controller.php +++ b/tests/phpunit/tests/rest-api/rest-users-controller.php @@ -896,7 +896,8 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { public function test_get_user_invalid_id() { wp_set_current_user( self::$user ); - $request = new WP_REST_Request( 'GET', '/wp/v2/users/7777' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/users/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); $response = rest_get_server()->dispatch( $request ); $this->assertErrorResponse( 'rest_user_invalid_id', $response, 404 ); @@ -2265,7 +2266,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $this->allow_user_to_manage_multisite(); wp_set_current_user( self::$user ); - $request = new WP_REST_Request( 'DELETE', '/wp/v2/users/7777' ); + $request = new WP_REST_Request( 'DELETE', '/wp/v2/users/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); $request['force'] = true; $request->set_param( 'reassign', false ); $response = rest_get_server()->dispatch( $request ); @@ -2317,7 +2318,7 @@ class WP_Test_REST_Users_Controller extends WP_Test_REST_Controller_Testcase { $request = new WP_REST_Request( 'DELETE', sprintf( '/wp/v2/users/%d', $user_id ) ); $request['force'] = true; - $request->set_param( 'reassign', 7777 ); + $request->set_param( 'reassign', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ); $response = rest_get_server()->dispatch( $request ); // Not implemented in multisite. diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 1473c242d7..9842de3244 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -850,6 +850,200 @@ mockedApiResponse.Schema = { } ] }, + "/wp/v2/posts/(?P[\\d]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "required": false, + "description": "The ID for the parent of the object.", + "type": "integer" + }, + "context": { + "required": false, + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "required": false, + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "required": false, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "required": false, + "description": "Limit results to those matching a string.", + "type": "string" + }, + "exclude": { + "required": false, + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "required": false, + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "required": false, + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "required": false, + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "required": false, + "default": "date", + "enum": [ + "date", + "id", + "include", + "relevance", + "slug", + "include_slugs", + "title" + ], + "description": "Sort collection by object attribute.", + "type": "string" + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "parent": { + "required": false, + "description": "The ID for the parent of the object.", + "type": "integer" + }, + "author": { + "required": false, + "description": "The ID for the author of the object.", + "type": "integer" + }, + "date": { + "required": false, + "description": "The date the object was published, in the site's timezone.", + "type": "string" + }, + "date_gmt": { + "required": false, + "description": "The date the object was published, as GMT.", + "type": "string" + }, + "id": { + "required": false, + "description": "Unique identifier for the object.", + "type": "integer" + }, + "modified": { + "required": false, + "description": "The date the object was last modified, in the site's timezone.", + "type": "string" + }, + "modified_gmt": { + "required": false, + "description": "The date the object was last modified, as GMT.", + "type": "string" + }, + "slug": { + "required": false, + "description": "An alphanumeric identifier for the object unique to its type.", + "type": "string" + }, + "title": { + "required": false, + "description": "The title for the object.", + "type": "object" + }, + "content": { + "required": false, + "description": "The content for the object.", + "type": "object" + }, + "excerpt": { + "required": false, + "description": "The excerpt for the object.", + "type": "object" + } + } + } + ] + }, + "/wp/v2/posts/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "required": false, + "description": "The ID for the parent of the object.", + "type": "integer" + }, + "id": { + "required": false, + "description": "The ID for the object.", + "type": "integer" + }, + "context": { + "required": false, + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + } + } + } + ] + }, "/wp/v2/pages": { "namespace": "wp/v2", "methods": [ @@ -1456,6 +1650,200 @@ mockedApiResponse.Schema = { } ] }, + "/wp/v2/pages/(?P[\\d]+)/autosaves": { + "namespace": "wp/v2", + "methods": [ + "GET", + "POST" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "required": false, + "description": "The ID for the parent of the object.", + "type": "integer" + }, + "context": { + "required": false, + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + }, + "page": { + "required": false, + "default": 1, + "description": "Current page of the collection.", + "type": "integer" + }, + "per_page": { + "required": false, + "description": "Maximum number of items to be returned in result set.", + "type": "integer" + }, + "search": { + "required": false, + "description": "Limit results to those matching a string.", + "type": "string" + }, + "exclude": { + "required": false, + "default": [], + "description": "Ensure result set excludes specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "include": { + "required": false, + "default": [], + "description": "Limit result set to specific IDs.", + "type": "array", + "items": { + "type": "integer" + } + }, + "offset": { + "required": false, + "description": "Offset the result set by a specific number of items.", + "type": "integer" + }, + "order": { + "required": false, + "default": "desc", + "enum": [ + "asc", + "desc" + ], + "description": "Order sort attribute ascending or descending.", + "type": "string" + }, + "orderby": { + "required": false, + "default": "date", + "enum": [ + "date", + "id", + "include", + "relevance", + "slug", + "include_slugs", + "title" + ], + "description": "Sort collection by object attribute.", + "type": "string" + } + } + }, + { + "methods": [ + "POST" + ], + "args": { + "parent": { + "required": false, + "description": "The ID for the parent of the object.", + "type": "integer" + }, + "author": { + "required": false, + "description": "The ID for the author of the object.", + "type": "integer" + }, + "date": { + "required": false, + "description": "The date the object was published, in the site's timezone.", + "type": "string" + }, + "date_gmt": { + "required": false, + "description": "The date the object was published, as GMT.", + "type": "string" + }, + "id": { + "required": false, + "description": "Unique identifier for the object.", + "type": "integer" + }, + "modified": { + "required": false, + "description": "The date the object was last modified, in the site's timezone.", + "type": "string" + }, + "modified_gmt": { + "required": false, + "description": "The date the object was last modified, as GMT.", + "type": "string" + }, + "slug": { + "required": false, + "description": "An alphanumeric identifier for the object unique to its type.", + "type": "string" + }, + "title": { + "required": false, + "description": "The title for the object.", + "type": "object" + }, + "content": { + "required": false, + "description": "The content for the object.", + "type": "object" + }, + "excerpt": { + "required": false, + "description": "The excerpt for the object.", + "type": "object" + } + } + } + ] + }, + "/wp/v2/pages/(?P[\\d]+)/autosaves/(?P[\\d]+)": { + "namespace": "wp/v2", + "methods": [ + "GET" + ], + "endpoints": [ + { + "methods": [ + "GET" + ], + "args": { + "parent": { + "required": false, + "description": "The ID for the parent of the object.", + "type": "integer" + }, + "id": { + "required": false, + "description": "The ID for the object.", + "type": "integer" + }, + "context": { + "required": false, + "default": "view", + "enum": [ + "view", + "embed", + "edit" + ], + "description": "Scope under which the request is made; determines fields present in response.", + "type": "string" + } + } + } + ] + }, "/wp/v2/media": { "namespace": "wp/v2", "methods": [ @@ -3850,7 +4238,7 @@ mockedApiResponse.PostsCollection = [ ], "version-history": [ { - "count": 1, + "count": 2, "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4/revisions" } ], @@ -3941,6 +4329,35 @@ mockedApiResponse.postRevisions = [ "guid": { "rendered": "http://example.org/?p=5" }, + "title": { + "rendered": "" + }, + "content": { + "rendered": "

Autosave post content.

\n" + }, + "excerpt": { + "rendered": "" + }, + "_links": { + "parent": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4" + } + ] + } + }, + { + "author": 359, + "date": "2017-02-14T00:00:00", + "date_gmt": "2017-02-14T00:00:00", + "id": 36734, + "modified": "2017-02-14T00:00:00", + "modified_gmt": "2017-02-14T00:00:00", + "parent": 36733, + "slug": "36733-revision-v1", + "guid": { + "rendered": "http://example.org/?p=36734" + }, "title": { "rendered": "REST API Client Fixture: Post" }, @@ -3953,7 +4370,7 @@ mockedApiResponse.postRevisions = [ "_links": { "parent": [ { - "href": "http://example.org/index.php?rest_route=/wp/v2/posts/4" + "href": "http://example.org/index.php?rest_route=/wp/v2/posts/36733" } ] } @@ -3983,6 +4400,61 @@ mockedApiResponse.revision = { } }; +mockedApiResponse.postAutosaves = [ + { + "author": 359, + "date": "2017-02-14T00:00:00", + "date_gmt": "2017-02-14T00:00:00", + "id": 36735, + "modified": "2017-02-14T00:00:00", + "modified_gmt": "2017-02-14T00:00:00", + "parent": 36733, + "slug": "36733-autosave-v1", + "guid": { + "rendered": "http://example.org/?p=36735" + }, + "title": { + "rendered": "" + }, + "content": { + "rendered": "

Autosave post content.

\n" + }, + "excerpt": { + "rendered": "" + }, + "_links": { + "parent": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/posts/36733" + } + ] + } + } +]; + +mockedApiResponse.autosave = { + "author": 359, + "date": "2017-02-14T00:00:00", + "date_gmt": "2017-02-14T00:00:00", + "id": 36735, + "modified": "2017-02-14T00:00:00", + "modified_gmt": "2017-02-14T00:00:00", + "parent": 36733, + "slug": "36733-autosave-v1", + "guid": { + "rendered": "http://example.org/?p=36735" + }, + "title": { + "rendered": "" + }, + "content": { + "rendered": "

Autosave post content.

\n" + }, + "excerpt": { + "rendered": "" + } +}; + mockedApiResponse.PagesCollection = [ { "id": 6, @@ -4042,7 +4514,7 @@ mockedApiResponse.PagesCollection = [ ], "version-history": [ { - "count": 1, + "count": 2, "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6/revisions" } ], @@ -4117,6 +4589,35 @@ mockedApiResponse.pageRevisions = [ "guid": { "rendered": "http://example.org/?p=7" }, + "title": { + "rendered": "" + }, + "content": { + "rendered": "

Autosave page content.

\n" + }, + "excerpt": { + "rendered": "" + }, + "_links": { + "parent": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6" + } + ] + } + }, + { + "author": 359, + "date": "2017-02-14T00:00:00", + "date_gmt": "2017-02-14T00:00:00", + "id": 36737, + "modified": "2017-02-14T00:00:00", + "modified_gmt": "2017-02-14T00:00:00", + "parent": 36736, + "slug": "36736-revision-v1", + "guid": { + "rendered": "http://example.org/?p=36737" + }, "title": { "rendered": "REST API Client Fixture: Page" }, @@ -4129,7 +4630,7 @@ mockedApiResponse.pageRevisions = [ "_links": { "parent": [ { - "href": "http://example.org/index.php?rest_route=/wp/v2/pages/6" + "href": "http://example.org/index.php?rest_route=/wp/v2/pages/36736" } ] } @@ -4159,6 +4660,61 @@ mockedApiResponse.pageRevision = { } }; +mockedApiResponse.pageAutosaves = [ + { + "author": 359, + "date": "2017-02-14T00:00:00", + "date_gmt": "2017-02-14T00:00:00", + "id": 36738, + "modified": "2017-02-14T00:00:00", + "modified_gmt": "2017-02-14T00:00:00", + "parent": 36736, + "slug": "36736-autosave-v1", + "guid": { + "rendered": "http://example.org/?p=36738" + }, + "title": { + "rendered": "" + }, + "content": { + "rendered": "

Autosave page content.

\n" + }, + "excerpt": { + "rendered": "" + }, + "_links": { + "parent": [ + { + "href": "http://example.org/index.php?rest_route=/wp/v2/pages/36736" + } + ] + } + } +]; + +mockedApiResponse.pageAutosave = { + "author": 359, + "date": "2017-02-14T00:00:00", + "date_gmt": "2017-02-14T00:00:00", + "id": 36738, + "modified": "2017-02-14T00:00:00", + "modified_gmt": "2017-02-14T00:00:00", + "parent": 36736, + "slug": "36736-autosave-v1", + "guid": { + "rendered": "http://example.org/?p=36738" + }, + "title": { + "rendered": "" + }, + "content": { + "rendered": "

Autosave page content.

\n" + }, + "excerpt": { + "rendered": "" + } +}; + mockedApiResponse.MediaCollection = [ { "id": 8,