WordPress.org

Make WordPress Core

Ticket #43316: 43316.13.diff

File 43316.13.diff, 13.8 KB (added by rmccue, 4 years ago)

Build autosaves into existing revision endpoint

  • src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php

    diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php
    index 471c4c4b4f..d1fe9a1a7d 100644
     
    1515 * @see WP_REST_Controller
    1616 */
    1717class WP_REST_Revisions_Controller extends WP_REST_Controller {
     18        /**
     19         * Post type.
     20         *
     21         * @since 5.0.0
     22         * @var string
     23         */
     24        protected $post_type = 'revision';
    1825
    1926        /**
    2027         * Parent post type.
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    7986                                        'permission_callback' => array( $this, 'get_items_permissions_check' ),
    8087                                        'args'                => $this->get_collection_params(),
    8188                                ),
     89                                array(
     90                                        'methods'             => WP_REST_Server::CREATABLE,
     91                                        'callback'            => array( $this, 'create_item' ),
     92                                        'permission_callback' => array( $this, 'create_item_permissions_check' ),
     93                                        'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
     94                                ),
    8295                                'schema' => array( $this, 'get_public_item_schema' ),
    8396                        )
    8497                );
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    103116                                                'context' => $this->get_context_param( array( 'default' => 'view' ) ),
    104117                                        ),
    105118                                ),
     119                                array(
     120                                        'methods'             => WP_REST_Server::EDITABLE,
     121                                        'callback'            => array( $this, 'update_item' ),
     122                                        'permission_callback' => array( $this, 'update_item_permissions_check' ),
     123                                        'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
     124                                ),
    106125                                array(
    107126                                        'methods'             => WP_REST_Server::DELETABLE,
    108127                                        'callback'            => array( $this, 'delete_item' ),
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    205224
    206225                $response = array();
    207226                foreach ( $revisions as $revision ) {
     227                        if ( wp_is_post_autosave( $revision->ID ) ) {
     228                                if ( ! in_array( 'autosave', $request['revision_type'] ) ) {
     229                                        continue;
     230                                }
     231                        } elseif ( ! in_array( 'revision', $request['revision_type'] ) ) {
     232                                continue;
     233                        }
     234
    208235                        $data       = $this->prepare_item_for_response( $revision, $request );
    209236                        $response[] = $this->prepare_response_for_collection( $data );
    210237                }
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    246273                return rest_ensure_response( $response );
    247274        }
    248275
     276        /**
     277         * Checks if a given request has access to create an autosave revision.
     278         *
     279         * Autosave revisions inherit permissions from the parent post,
     280         * check if the current user has permission to edit the post.
     281         *
     282         * @since 5.0.0
     283         *
     284         * @param WP_REST_Request $request Full details about the request.
     285         * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
     286         */
     287        public function create_item_permissions_check( $request ) {
     288                if ( empty( $request->get_param( 'parent' ) ) ) {
     289                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) );
     290                }
     291
     292                // Set request ID for the permission check.
     293                $dummy_request = clone $request;
     294                $dummy_request['id'] = $request['parent'];
     295
     296                return $this->parent_controller->update_item_permissions_check( $dummy_request );
     297        }
     298
     299        /**
     300         * Creates, updates or deletes an autosave revision.
     301         *
     302         * @since 5.0.0
     303         *
     304         * @param WP_REST_Request $request Full details about the request.
     305         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     306         */
     307        public function create_item( $request ) {
     308                if ( ! defined( 'DOING_AUTOSAVE' ) ) {
     309                        define( 'DOING_AUTOSAVE', true );
     310                }
     311
     312                $post = get_post( $request->get_param( 'parent' ) );
     313
     314                if ( is_wp_error( $post ) ) {
     315                        return $post;
     316                }
     317
     318                $user_id      = get_current_user_id();
     319                $old_autosave = wp_get_post_autosave( $post->ID, $user_id );
     320                if ( $old_autosave ) {
     321                        return new WP_Error(
     322                                'rest_revision_existing_autosave',
     323                                __( 'An autosave already exists for this user.' ),
     324                                array(
     325                                        'status' => WP_Http::CONFLICT,
     326                                        'id'     => $old_autosave->ID,
     327                                )
     328                        );
     329                }
     330
     331                $prepared_post     = $this->prepare_item_for_database( $request );
     332                $prepared_post->ID = $post->ID;
     333
     334                if ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) {
     335                        return new WP_Error(
     336                                'rest_cannot_create_for_draft',
     337                                __( 'Draft posts may not have autosaves.' ),
     338                                array(
     339                                        'status' => WP_Http::BAD_REQUEST,
     340                                )
     341                        );
     342                }
     343
     344                // Non-draft posts: create or update the post autosave.
     345                $autosave_id = _wp_put_post_revision( (array) $prepared_post, true );
     346                if ( is_wp_error( $autosave_id ) ) {
     347                        return $autosave_id;
     348                }
     349
     350                $autosave      = get_post( $autosave_id );
     351                $fields_update = $this->update_additional_fields_for_object( $autosave, $request );
     352
     353                if ( is_wp_error( $fields_update ) ) {
     354                        return $fields_update;
     355                }
     356
     357                $request->set_param( 'context', 'edit' );
     358
     359                $response = $this->prepare_item_for_response( $autosave, $request );
     360                $response = rest_ensure_response( $response );
     361
     362                return $response;
     363        }
     364
     365        /**
     366         * Checks if a given request has access to create an autosave revision.
     367         *
     368         * Autosave revisions inherit permissions from the parent post,
     369         * check if the current user has permission to edit the post.
     370         *
     371         * @since 5.0.0
     372         *
     373         * @param WP_REST_Request $request Full details about the request.
     374         * @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
     375         */
     376        public function update_item_permissions_check( $request ) {
     377                if ( empty( $request->get_param( 'parent' ) ) ) {
     378                        return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) );
     379                }
     380
     381                $autosave = $this->get_revision( $request['id'] );
     382                if ( is_wp_error( $revision ) ) {
     383                        return $revision;
     384                }
     385
     386                if ( ! wp_is_post_autosave( $autosave ) ) {
     387                        return new WP_Error(
     388                                'rest_revision_cannot_update',
     389                                __( 'Only autosave revisions may be updated.' ),
     390                                array( 'status' => WP_Http::BAD_REQUEST )
     391                        );
     392                }
     393
     394                // Set request ID for the permission check.
     395                $dummy_request = clone $request;
     396                $dummy_request['id'] = $request['parent'];
     397
     398                return $this->parent_controller->update_item_permissions_check( $dummy_request );
     399        }
     400
     401        /**
     402         * Updates a single revision.
     403         *
     404         * Only allows updating autosave revisions.
     405         *
     406         * @since 4.7.0
     407         *
     408         * @param WP_REST_Request $request Full details about the request.
     409         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     410         */
     411        public function update_item( $request ) {
     412                $valid_check = $this->get_parent( $request['parent'] );
     413                if ( is_wp_error( $valid_check ) ) {
     414                        return $valid_check;
     415                }
     416
     417                $post = $this->prepare_item_for_database( $request );
     418
     419                if ( is_wp_error( $post ) ) {
     420                        return $post;
     421                }
     422
     423                // convert the post object to an array, otherwise wp_update_post will expect non-escaped input.
     424                $post_id = wp_update_post( wp_slash( (array) $post ), true );
     425
     426                if ( is_wp_error( $post_id ) ) {
     427                        if ( 'db_update_error' === $post_id->get_error_code() ) {
     428                                $post_id->add_data( array( 'status' => 500 ) );
     429                        } else {
     430                                $post_id->add_data( array( 'status' => 400 ) );
     431                        }
     432                        return $post_id;
     433                }
     434
     435                $post = get_post( $post_id );
     436
     437                /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
     438                do_action( "rest_insert_{$this->post_type}", $post, $request, false );
     439
     440                $schema = $this->get_item_schema();
     441
     442                $post          = get_post( $post_id );
     443                $fields_update = $this->update_additional_fields_for_object( $post, $request );
     444
     445                if ( is_wp_error( $fields_update ) ) {
     446                        return $fields_update;
     447                }
     448
     449                $request->set_param( 'context', 'edit' );
     450
     451                $response = $this->prepare_item_for_response( $post, $request );
     452
     453                return rest_ensure_response( $response );
     454        }
     455
    249456        /**
    250457         * Checks if a given request has access to delete a revision.
    251458         *
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    326533                return $response;
    327534        }
    328535
     536        /**
     537         * Prepares a single post for create or update.
     538         *
     539         * @since 4.7.0
     540         *
     541         * @param WP_REST_Request $request Request object.
     542         * @return stdClass|WP_Error Post object or WP_Error.
     543         */
     544        protected function prepare_item_for_database( $request ) {
     545                $prepared_post = new stdClass;
     546
     547                // Post ID.
     548                if ( isset( $request['id'] ) ) {
     549                        $existing_post = $this->get_revision( $request['id'] );
     550                        if ( is_wp_error( $existing_post ) ) {
     551                                return $existing_post;
     552                        }
     553
     554                        $prepared_post->ID = $existing_post->ID;
     555                }
     556
     557                $schema = $this->get_item_schema();
     558
     559                // Post title.
     560                if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
     561                        if ( is_string( $request['title'] ) ) {
     562                                $prepared_post->post_title = $request['title'];
     563                        } elseif ( ! empty( $request['title']['raw'] ) ) {
     564                                $prepared_post->post_title = $request['title']['raw'];
     565                        }
     566                }
     567
     568                // Post content.
     569                if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
     570                        if ( is_string( $request['content'] ) ) {
     571                                $prepared_post->post_content = $request['content'];
     572                        } elseif ( isset( $request['content']['raw'] ) ) {
     573                                $prepared_post->post_content = $request['content']['raw'];
     574                        }
     575                }
     576
     577                // Post excerpt.
     578                if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
     579                        if ( is_string( $request['excerpt'] ) ) {
     580                                $prepared_post->post_excerpt = $request['excerpt'];
     581                        } elseif ( isset( $request['excerpt']['raw'] ) ) {
     582                                $prepared_post->post_excerpt = $request['excerpt']['raw'];
     583                        }
     584                }
     585
     586                /**
     587                 * Filters a post before it is inserted via the REST API.
     588                 *
     589                 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug.
     590                 *
     591                 * @since 4.7.0
     592                 *
     593                 * @param stdClass        $prepared_post An object representing a single post prepared
     594                 *                                       for inserting or updating the database.
     595                 * @param WP_REST_Request $request       Request object.
     596                 */
     597                return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
     598
     599        }
     600
    329601        /**
    330602         * Prepares the revision for the REST response.
    331603         *
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    372644                        $data['parent'] = (int) $post->post_parent;
    373645                }
    374646
     647                if ( ! empty( $schema['properties']['revision_type'] ) ) {
     648                        $data['revision_type'] = wp_is_post_autosave( $post->ID ) ? 'autosave' : 'revision';
     649                }
     650
    375651                if ( ! empty( $schema['properties']['slug'] ) ) {
    376652                        $data['slug'] = $post->post_name;
    377653                }
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    510786                                        'type'        => 'integer',
    511787                                        'context'     => array( 'view', 'edit', 'embed' ),
    512788                                ),
     789                                'revision_type' => array(
     790                                        'description' => __( 'Type of revision.' ),
     791                                        'type'        => 'string',
     792                                        'enum'        => array(
     793                                                'autosave',
     794                                                'revision',
     795                                        ),
     796                                        'default'     => 'autosave',
     797                                        'context'     => array( 'view', 'edit' ),
     798                                        'arg_options' => array(
     799                                                'sanitize_callback' => array( $this, 'sanitize_revision_type' ),
     800                                        ),
     801                                ),
    513802                                'slug'         => array(
    514803                                        'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
    515804                                        'type'        => 'string',
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    549838        public function get_collection_params() {
    550839                return array(
    551840                        'context' => $this->get_context_param( array( 'default' => 'view' ) ),
     841                        'revision_type' => array(
     842                                'type'              => 'array',
     843                                'items'             => array(
     844                                        'enum' => array(
     845                                                'autosave',
     846                                                'revision',
     847                                        ),
     848                                        'type' => 'string',
     849                                ),
     850                                'default'           => array(
     851                                        'autosave',
     852                                        'revision',
     853                                ),
     854                                'sanitize_callback' => 'wp_parse_slug_list',
     855                        ),
    552856                );
    553857        }
    554858
     859        /**
     860         * Sanitizes and validates the revision type on creation.
     861         *
     862         * @since 5.0.0
     863         *
     864         * @param string $type Revision type.
     865         */
     866        public function sanitize_revision_type( $type ) {
     867                if ( $type !== 'autosave' ) {
     868                        return new WP_Error(
     869                                'rest_revision_invalid_type',
     870                                sprintf(
     871                                        /* translators: %s: revision type */
     872                                        __( 'Cannot create revisions of type %s' ),
     873                                        $type
     874                                ),
     875                                array(
     876                                        'status' => WP_Http::BAD_REQUEST
     877                                )
     878                        );
     879                }
     880
     881                return $type;
     882        }
     883
    555884        /**
    556885         * Checks the post excerpt and prepare it for single post output.
    557886         *
  • tests/phpunit/tests/rest-api/rest-schema-setup.php

    diff --git tests/phpunit/tests/rest-api/rest-schema-setup.php tests/phpunit/tests/rest-api/rest-schema-setup.php
    index e5b3460122..fc0f0757e0 100644
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    159159                $post_revisions   = array_values( wp_get_post_revisions( $post_id ) );
    160160                $post_revision_id = $post_revisions[ count( $post_revisions ) - 1 ]->ID;
    161161
     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
    162171                $page_id = $this->factory->post->create(
    163172                        array(
    164173                                'post_type'     => 'page',
    class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase { 
    180189                $page_revisions   = array_values( wp_get_post_revisions( $page_id ) );
    181190                $page_revision_id = $page_revisions[ count( $page_revisions ) - 1 ]->ID;
    182191
     192                // Create an autosave.
     193                wp_create_post_autosave(
     194                        array(
     195                                'post_ID'      => $page_id,
     196                                'post_content' => 'Autosave page content.',
     197                                'post_type'    => 'page',
     198                        )
     199                );
     200
    183201                $tag_id = $this->factory->tag->create(
    184202                        array(
    185203                                'name'        => 'REST API Client Fixture: Tag',