Make WordPress Core

Ticket #43316: autosave-controller.diff

File autosave-controller.diff, 10.6 KB (added by adamsilverstein, 7 years ago)
  • new file src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php

    diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php
    new file mode 100644
    index 0000000000..d964e29f20
    - +  
     1<?php
     2/**
     3 * REST API: WP_REST_Autosaves_Controller class.
     4 *
     5 * @package WordPress
     6 * @subpackage REST_API
     7 * @since 5.0.0
     8 */
     9
     10/**
     11 * Core class used to access autosaves via the REST API.
     12 *
     13 * @since 5.0.0
     14 *
     15 * @see WP_REST_Controller
     16 */
     17class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
     18
     19        /**
     20         * Parent post type.
     21         *
     22         * @since 5.0.0
     23         * @var string
     24         */
     25        private $parent_post_type;
     26
     27        /**
     28         * Parent controller.
     29         *
     30         * @since 5.0.0
     31         * @var WP_REST_Controller
     32         */
     33        private $parent_controller;
     34
     35        /**
     36         * Parent controller.
     37         *
     38         * @since 5.0.0
     39         * @var WP_REST_Controller
     40         */
     41        private $revision_controller;
     42
     43        /**
     44         * The base of the parent controller's route.
     45         *
     46         * @since 5.0.0
     47         * @var string
     48         */
     49        private $parent_base;
     50
     51        /**
     52         * Constructor.
     53         *
     54         * @since 5.0.0
     55         *
     56         * @param string $parent_post_type Post type of the parent.
     57         */
     58        public function __construct( $parent_post_type ) {
     59                $this->parent_post_type    = $parent_post_type;
     60                $this->parent_controller   = new WP_REST_Posts_Controller( $parent_post_type );
     61                $this->revision_controller = new WP_REST_Revisions_Controller( $parent_post_type );
     62                $this->rest_namespace      = 'wp/v2';
     63                $this->rest_base           = 'autosaves';
     64                $post_type_object          = get_post_type_object( $parent_post_type );
     65                $this->parent_base         = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
     66        }
     67
     68        /**
     69         * Registers routes for autosaves based on post types supporting autosaves.
     70         *
     71         * @since 5.0.0
     72         *
     73         * @see register_rest_route()
     74         */
     75        public function register_routes() {
     76                register_rest_route(
     77                        $this->rest_namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array(
     78                                'args'   => array(
     79                                        'parent' => array(
     80                                                'description' => __( 'The ID for the parent of the object.', 'gutenberg' ),
     81                                                'type'        => 'integer',
     82                                        ),
     83                                ),
     84                                array(
     85                                        'methods'             => WP_REST_Server::READABLE,
     86                                        'callback'            => array( $this, 'get_items' ),
     87                                        'permission_callback' => array( $this->revision_controller, 'get_items_permissions_check' ),
     88                                        'args'                => $this->get_collection_params(),
     89                                ),
     90                                array(
     91                                        'methods'             => WP_REST_Server::CREATABLE,
     92                                        'callback'            => array( $this, 'create_item' ),
     93                                        'permission_callback' => array( $this->parent_controller, 'create_item_permissions_check' ),
     94                                        'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
     95                                ),
     96                                'schema' => array( $this, 'get_public_item_schema' ),
     97                        )
     98                );
     99
     100                register_rest_route(
     101                        $this->rest_namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array(
     102                                'args'   => array(
     103                                        'parent' => array(
     104                                                'description' => __( 'The ID for the parent of the object.', 'gutenberg' ),
     105                                                'type'        => 'integer',
     106                                        ),
     107                                        'id'     => array(
     108                                                'description' => __( 'Unique identifier for the object.', 'gutenberg' ),
     109                                                'type'        => 'integer',
     110                                        ),
     111                                ),
     112                                array(
     113                                        'methods'             => WP_REST_Server::READABLE,
     114                                        'callback'            => array( $this, 'get_item' ),
     115                                        'permission_callback' => array( $this->revision_controller, 'get_item_permissions_check' ),
     116                                        'args'                => array(
     117                                                'context' => $this->get_context_param( array( 'default' => 'view' ) ),
     118                                        ),
     119                                ),
     120                                array(
     121                                        'methods'             => WP_REST_Server::DELETABLE,
     122                                        'callback'            => array( $this, 'delete_item' ),
     123                                        'permission_callback' => array( $this->revision_controller, 'delete_item_permissions_check' ),
     124                                        'args'                => array(
     125                                                'force' => array(
     126                                                        'type'        => 'boolean',
     127                                                        'default'     => false,
     128                                                        'description' => __( 'Required to be true, as autosaves do not support trashing.', 'gutenberg' ),
     129                                                ),
     130                                        ),
     131                                ),
     132                                array(
     133                                        'methods'             => WP_REST_Server::CREATABLE,
     134                                        'callback'            => array( $this, 'create_item' ),
     135                                        'permission_callback' => array( $this->parent_controller, 'create_item_permissions_check' ),
     136                                        'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
     137                                ),
     138                                'schema' => array( $this, 'get_public_item_schema' ),
     139                        )
     140                );
     141
     142        }
     143
     144        /**
     145         * Creates a single autosave.
     146         *
     147         * @since 5.0.0
     148         *
     149         * @param WP_REST_Request $request Full details about the request.
     150         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     151         */
     152        public function create_item( $request ) {
     153
     154                // Map new fields onto the existing post data.
     155                $parent            = $this->revision_controller->get_parent( $request['parent'] );
     156                $prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
     157                $prepared_post->ID = $parent->ID;
     158
     159                // If the parent post a draft, autosaving updates it and does not create a revision.
     160                if ( 'draft' === $parent->post_status ) {
     161
     162                        // Disable revisions.
     163                        remove_action( 'post_updated', 'wp_save_post_revision' );
     164
     165                        $autosave_id = wp_update_post( (array) $prepared_post, true );
     166
     167                        // Re-enable revisions.
     168                        add_action( 'post_updated', 'wp_save_post_revision' );
     169                        if ( ! is_wp_error( $autosave_id ) ) {
     170                                $post = get_post( $autosave_id );
     171                        }
     172                } else {
     173
     174                        // Non-draft posts - update the post, creating an autosave.
     175                        $autosave_id = $this->create_post_autosave( (array) $prepared_post );
     176                        $post        = get_post( $autosave_id );
     177                }
     178                $request->set_param( 'context', 'edit' );
     179
     180                $response = $this->prepare_item_for_response( $post, $request );
     181                $response = rest_ensure_response( $response );
     182
     183                $response->set_status( 201 );
     184                $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->rest_namespace, $this->rest_base, $autosave_id ) ) );
     185
     186                return $response;
     187        }
     188
     189        /**
     190         * Get the autosave, if the ID is valid.
     191         *
     192         * @since 5.0.0
     193         *
     194         * @param int $id Supplied ID.
     195         * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
     196         */
     197        public function get_item( $id ) {
     198                $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid autosave ID.', 'gutenberg' ), array( 'status' => 404 ) );
     199                if ( (int) $id <= 0 ) {
     200                        return $error;
     201                }
     202
     203                $autosave = get_post( (int) $id );
     204                if ( empty( $autosave ) || empty( $autosave->ID ) || 'autosave' !== $autosave->post_type ) {
     205                        return $error;
     206                }
     207
     208                return $autosave;
     209        }
     210
     211        /**
     212         * Gets a collection of autosaves using wp_get_post_autosave.
     213         *
     214         * Contains the user's autosave, for empty if it doesn't exist.
     215         *
     216         * @since 5.0.0
     217         *
     218         * @param WP_REST_Request $request Full data about the request.
     219         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
     220         */
     221        public function get_items( $request ) {
     222                $parent = $this->revision_controller->get_parent( $request['parent'] );
     223                if ( is_wp_error( $parent ) ) {
     224                        return $parent;
     225                }
     226
     227                $autosave = wp_get_post_autosave( $request['parent'] );
     228
     229                if ( ! $autosave ) {
     230                        return array();
     231                }
     232
     233                $response   = array();
     234                $data       = $this->prepare_item_for_response( $autosave, $request );
     235                $response[] = $this->prepare_response_for_collection( $data );
     236
     237                return rest_ensure_response( $response );
     238        }
     239
     240
     241        /**
     242         * Retrieves the autosave's schema, conforming to JSON Schema.
     243         *
     244         * @since 5.0.0
     245         *
     246         * @return array Item schema data.
     247         */
     248        public function get_item_schema() {
     249                return $this->revision_controller->get_item_schema();
     250        }
     251
     252        /**
     253         * Creates autosave data for the specified post from $_POST data.
     254         *
     255         * From core post.php.
     256         *
     257         * @since 2.6.0
     258         *
     259         * @param mixed $post_data Associative array containing the post data or int post ID.
     260         * @return mixed The autosave revision ID. WP_Error or 0 on error.
     261         */
     262        public function create_post_autosave( $post_data ) {
     263
     264                $post_id     = (int) $post_data['ID'];
     265                $post_author = get_current_user_id();
     266
     267                // Store one autosave per author. If there is already an autosave, overwrite it.
     268                $old_autosave = wp_get_post_autosave( $post_id, $post_author );
     269                if ( $old_autosave ) {
     270                        $new_autosave                = _wp_post_revision_data( $post_data, true );
     271                        $new_autosave['ID']          = $old_autosave->ID;
     272                        $new_autosave['post_author'] = $post_author;
     273
     274                        // If the new autosave has the same content as the post, delete the autosave.
     275                        $post                  = get_post( $post_id );
     276                        $autosave_is_different = false;
     277                        foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
     278                                if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) {
     279                                        $autosave_is_different = true;
     280                                        break;
     281                                }
     282                        }
     283
     284                        if ( ! $autosave_is_different ) {
     285                                wp_delete_post_revision( $old_autosave->ID );
     286                                return 0;
     287                        }
     288
     289                        /**
     290                         * Fires before an autosave is stored.
     291                         *
     292                         * @since 4.1.0
     293                         *
     294                         * @param array $new_autosave Post array - the autosave that is about to be saved.
     295                         */
     296                        do_action( 'wp_creating_autosave', $new_autosave );
     297
     298                        return wp_update_post( $new_autosave );
     299                }
     300
     301                // _wp_put_post_revision() expects unescaped.
     302                $post_data = wp_unslash( $post_data );
     303
     304                // Otherwise create the new autosave as a special post revision.
     305                return _wp_put_post_revision( $post_data, true );
     306        }
     307}
  • src/wp-settings.php

    diff --git src/wp-settings.php src/wp-settings.php
    index 6edd3c98ca..b2c4189c64 100644
    require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-contro 
    230230require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' );
    231231require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' );
    232232require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' );
     233require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-autosaves-controller.php' );
    233234require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' );
    234235require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' );
    235236require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );