Make WordPress Core

Ticket #43316: 43316.diff

File 43316.diff, 16.9 KB (added by adamsilverstein, 7 years ago)
  • src/wp-includes/js/wp-api.js

    diff --git src/wp-includes/js/wp-api.js src/wp-includes/js/wp-api.js
    index d796ddc475..35fcb5c5d1 100644
     
    287287         * @param {Object}             loadingObjects An object containing the models and collections we are building.
    288288         */
    289289        wp.api.utils.addMixinsAndHelpers = function( model, modelClassName, loadingObjects ) {
    290 
    291290                var hasDate = false,
    292291
     292                        /**
     293                         * Specify the models that save autosaves.
     294                         */
     295                        canSaveAutosaves = wpApiSettings.canSaveAutosaves ||
     296                                [ 'Post', 'Page' ],
     297
     298                        /**
     299                         * Specify the models that retrieve autosaves.
     300                         */
     301                        canRetrieveAutosaves = wpApiSettings.canRetrieveAutosaves ||
     302                                [ 'PostRevision', 'PageRevision' ],
     303
    293304                        /**
    294305                         * Array of parseable dates.
    295306                         *
     
    741752                                getFeaturedMedia: function() {
    742753                                        return buildModelGetter( this, this.get( 'featured_media' ), 'Media', 'wp:featuredmedia', 'source_url' );
    743754                                }
     755                        },
     756
     757                        /**
     758                         * Add a helper to enable saving autosaves.
     759                         */
     760                        autosaveMixin = {
     761                                autosave: function() {
     762                                        return this.save( { is_autosave: true } );
     763                                }
     764                        },
     765
     766                        /**
     767                         * Add a helper to enable retrieving autosaves.
     768                         */
     769                        getAutosaveMixin = {
     770                                getAutosave: function() {
     771                                        return this.fetch( { data: { is_autosave: true } } );
     772                                }
    744773                        };
    745774
     775                // Add Autosaving for specific models
     776                if ( _.indexOf( canSaveAutosaves, modelClassName ) >= 0 ) {
     777                        model = model.extend( autosaveMixin );
     778                }
     779
     780                // Add Autosaving for specific models
     781                if ( _.indexOf( canRetrieveAutosaves, modelClassName ) >= 0 ) {
     782                        model = model.extend( getAutosaveMixin );
     783                }
     784
    746785                // Exit if we don't have valid model defaults.
    747786                if ( _.isUndefined( model.prototype.args ) ) {
    748787                        return model;
  • src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

    diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
    index 0661152de6..05f27713b5 100644
    class WP_REST_Posts_Controller extends WP_REST_Controller { 
    657657         * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    658658         */
    659659        public function update_item( $request ) {
    660                 $valid_check = $this->get_post( $request['id'] );
    661                 if ( is_wp_error( $valid_check ) ) {
    662                         return $valid_check;
     660                $existing_post = $this->get_post( $request['id'] );
     661                if ( is_wp_error( $existing_post ) ) {
     662                        return $existing_post;
    663663                }
    664664
    665                 $post = $this->prepare_item_for_database( $request );
     665                $new_post = $this->prepare_item_for_database( $request );
    666666
    667                 if ( is_wp_error( $post ) ) {
    668                         return $post;
     667                if ( is_wp_error( $new_post ) ) {
     668                        return $new_post;
    669669                }
    670670
    671                 // convert the post object to an array, otherwise wp_update_post will expect non-escaped input.
    672                 $post_id = wp_update_post( wp_slash( (array) $post ), true );
     671                // Keep the ID for later.
     672                $post_id = $new_post->ID;
     673
     674                // The following functions expect array.
     675                $post_data = get_object_vars( $new_post );
     676
     677                // Autosave
     678                if ( ! empty( $request['is_autosave'] ) ) {
     679                        if ( ! defined( 'DOING_AUTOSAVE' ) ) {
     680                                define( 'DOING_AUTOSAVE', true );
     681                        }
     682
     683                        $post_author = get_current_user_id();
     684                        $autosave_id = 0;
     685
     686                        // Also needs to check post lock.
     687                        if ( $post_author == $existing_post->post_author && ( 'auto-draft' === $existing_post->post_status || 'draft' === $existing_post->post_status ) ) {
     688                                // Drafts and auto-drafts are just overwritten by autosave for the same user.
     689                                // Expects escaped input when array.
     690                                $post_id = wp_update_post( wp_slash( $post_data ), true );
     691                        } else {
     692                                // Store one autosave per author. If there is already an autosave, update it.
     693                                if ( $old_autosave = wp_get_post_autosave( $post_data['ID'], $post_author ) ) {
     694                                        $new_autosave                = _wp_post_revision_data( $post_data, true );
     695                                        $new_autosave['ID']          = $old_autosave->ID;
     696                                        $new_autosave['post_author'] = $post_author;
     697
     698                                        // If the new autosave has the same content as the post, delete the autosave.
     699                                        $autosave_is_different = false;
     700
     701                                        foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $existing_post ) ) ) as $field ) {
     702                                                if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $existing_post->$field ) ) {
     703                                                        $autosave_is_different = true;
     704                                                        break;
     705                                                }
     706                                        }
     707
     708                                        if ( ! $autosave_is_different ) {
     709                                                wp_delete_post_revision( $old_autosave->ID );
     710                                        } else {
     711                                                /**
     712                                                 * Fires before an autosave is stored via the REST API.
     713                                                 *
     714                                                 * @since 5.0.0
     715                                                 *
     716                                                 * @param array           $new_autosave Post array - the autosave that is about to be saved.
     717                                                 * @param WP_REST_Request $request      Request object.     
     718                                                 */
     719                                                do_action( 'rest_creating_autosave', $new_autosave, $request );
     720
     721                                                $autosave_id = wp_update_post( $new_autosave );
     722                                        }
     723                                } else {
     724                                        // Need to merge the autosave data with some of the existing post data.
     725                                        foreach ( array_keys( _wp_post_revision_fields( $existing_post ) ) as $field ) {
     726                                                if ( empty( $post_data[ $field ] ) ) {
     727                                                        $post_data[ $field ] = $existing_post->$field;
     728                                                }
     729                                        }
     730
     731                                        /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
     732                                        do_action( 'rest_creating_autosave', $post_data, $request );
     733
     734                                        $autosave_id = _wp_put_post_revision( $post_data, true );
     735                                }
     736
     737                                if ( is_wp_error( $autosave_id ) ) {
     738                                        // Pass on the error.
     739                                        $post_id = $autosave_id;
     740                                }
     741                        }
     742                } else {
     743                        // Expects escaped input when array.
     744                        $post_id = wp_update_post( wp_slash( $post_data ), true );
     745                }
    673746
    674747                if ( is_wp_error( $post_id ) ) {
    675748                        if ( 'db_update_error' === $post_id->get_error_code() ) {
  • 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..8caaa43bfb 100644
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    187187                return $revision;
    188188        }
    189189
     190        /**
     191         * Get autosave revision if it exists.
     192         *
     193         * @since 5.0.0
     194         *
     195         * @param  WP_REST_Request $request Full data about the request.
     196         * @return WP_Post|WP_Error Revision post object if post autosave exists, WP_Error otherwise.
     197         */
     198        protected function get_autosave( $request ) {
     199                $parent = $this->get_parent( $request['parent'] );
     200                if ( is_wp_error( $parent ) ) {
     201                        return $parent;
     202                }
     203
     204                $user_id = 0;
     205
     206                if ( ! empty( $request['author'] ) ) {
     207                        $post_author = (int) $request['author'];
     208                        $user_obj = get_userdata( $post_author );
     209
     210                        if ( ! $user_obj ) {
     211                                return new WP_Error( 'rest_invalid_author', __( 'Invalid author ID.' ), array( 'status' => 400 ) );
     212                        } else {
     213                                $user_id = $post_author;
     214                        }
     215                }
     216
     217                $revision = wp_get_post_autosave( $parent->ID, $user_id );
     218                if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) {
     219                        return new WP_Error( 'rest_post_no_autosave', __( 'Autosave does not exist.' ), array( 'status' => 404 ) );
     220                }
     221
     222                return $revision;
     223        }
     224
    190225        /**
    191226         * Gets a collection of revisions.
    192227         *
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    201236                        return $parent;
    202237                }
    203238
    204                 $revisions = wp_get_post_revisions( $request['parent'] );
     239                $revisions = wp_get_post_revisions( $parent->ID );
     240
     241                if ( ! empty( $request['is_autosave'] ) ) {
     242                        foreach ( $revisions as $revision_id => $revision ) {
     243                                if ( ! wp_is_post_autosave( $revision ) ) {
     244                                        unset( $revisions[ $revision_id ] );
     245                                }
     246                        }
     247                }
    205248
    206249                $response = array();
    207250                foreach ( $revisions as $revision ) {
    class WP_REST_Revisions_Controller extends WP_REST_Controller { 
    237280                        return $parent;
    238281                }
    239282
    240                 $revision = $this->get_revision( $request['id'] );
     283                if ( ! empty( $request['is_autosave'] ) ) {
     284                        $revision = $this->get_autosave( $request );
     285                } else {
     286                        $revision = $this->get_revision( $request['id'] );
     287                }
     288
    241289                if ( is_wp_error( $revision ) ) {
    242290                        return $revision;
    243291                }
  • tests/phpunit/tests/rest-api/rest-posts-controller.php

    diff --git tests/phpunit/tests/rest-api/rest-posts-controller.php tests/phpunit/tests/rest-api/rest-posts-controller.php
    index bc820a8796..30f4378e5a 100644
    class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te 
    23632363                $this->assertEquals( $params['excerpt'], $post->post_excerpt );
    23642364        }
    23652365
     2366        public function test_rest_autosave_published_post() {
     2367                wp_set_current_user( self::$editor_id );
     2368
     2369                $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
     2370                $request->add_header( 'content-type', 'application/json' );
     2371
     2372                $autosave_data = $this->set_post_data(
     2373                        array(
     2374                                'id' => self::$post_id,
     2375                                'is_autosave' => true,
     2376                                'content' => 'Updated post content',
     2377                        )
     2378                );
     2379
     2380                $request->set_body( wp_json_encode( $autosave_data ) );
     2381                $response = $this->server->dispatch( $request );
     2382
     2383                $this->check_update_post_response( $response );
     2384                $new_data = $response->get_data();
     2385
     2386                // The published post shouldn't change.
     2387                $current_post = get_post( self::$post_id );
     2388                $this->assertEquals( $current_post->ID, $new_data['id'] );
     2389                $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] );
     2390                $this->assertEquals( $current_post->post_content, $new_data['content']['raw'] );
     2391                $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] );
     2392
     2393                $autosave_post = wp_get_post_autosave( self::$post_id );
     2394                $this->assertEquals( $autosave_data['title'], $autosave_post->post_title );
     2395                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     2396                $this->assertEquals( $autosave_data['excerpt'], $autosave_post->post_excerpt );
     2397        }
     2398
     2399        public function test_rest_autosave_draft_post_same_author() {
     2400                wp_set_current_user( self::$editor_id );
     2401
     2402                $post_data = array(
     2403                        'post_content' => 'Test post content',
     2404                        'post_title'   => 'Test post title',
     2405                        'post_excerpt' => 'Test post excerpt',
     2406                );
     2407                $post_id = wp_insert_post( $post_data );
     2408
     2409                $autosave_data = array(
     2410                        'id' => $post_id,
     2411                        'is_autosave' => true,
     2412                        'content' => 'Updated post content',
     2413                );
     2414
     2415                $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
     2416                $request->add_header( 'content-type', 'application/json' );
     2417                $request->set_body( wp_json_encode( $autosave_data ) );
     2418                $response = $this->server->dispatch( $request );
     2419
     2420                $this->check_update_post_response( $response );
     2421                $new_data = $response->get_data();
     2422
     2423                // The draft post should be updated.
     2424                $this->assertEquals( $post_id, $new_data['id'] );
     2425                $this->assertEquals( $autosave_data['content'], $new_data['content']['raw'] );
     2426
     2427                $post = get_post( $post_id );
     2428                $this->assertEquals( $post_data['post_title'], $post->post_title );
     2429                $this->assertEquals( $autosave_data['content'], $post->post_content );
     2430                $this->assertEquals( $post_data['post_excerpt'], $post->post_excerpt );
     2431
     2432                wp_delete_post( $post_id );
     2433        }
     2434
     2435        public function test_rest_autosave_draft_post_different_author() {
     2436                wp_set_current_user( self::$editor_id );
     2437
     2438                $post_data = array(
     2439                        'post_content' => 'Test post content',
     2440                        'post_title'   => 'Test post title',
     2441                        'post_excerpt' => 'Test post excerpt',
     2442                        'post_author'  => ++self::$editor_id,
     2443                );
     2444                $post_id = wp_insert_post( $post_data );
     2445
     2446                $autosave_data = array(
     2447                        'id' => $post_id,
     2448                        'is_autosave' => true,
     2449                        'content' => 'Updated post content',
     2450                );
     2451
     2452                $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
     2453                $request->add_header( 'content-type', 'application/json' );
     2454                $request->set_body( wp_json_encode( $autosave_data ) );
     2455                $response = $this->server->dispatch( $request );
     2456
     2457                $this->check_update_post_response( $response );
     2458                $new_data = $response->get_data();
     2459
     2460
     2461                // The draft post shouldn't change.
     2462                $current_post = get_post( $post_id );
     2463                $this->assertEquals( $current_post->ID, $new_data['id'] );
     2464                $this->assertEquals( $current_post->post_title, $new_data['title']['raw'] );
     2465                $this->assertEquals( $current_post->post_content, $new_data['content']['raw'] );
     2466                $this->assertEquals( $current_post->post_excerpt, $new_data['excerpt']['raw'] );
     2467
     2468                $autosave_post = wp_get_post_autosave( $post_id );
     2469                // No changes
     2470                $this->assertEquals( $current_post->post_title, $autosave_post->post_title );
     2471                $this->assertEquals( $current_post->post_excerpt, $autosave_post->post_excerpt );
     2472
     2473                // Has changes
     2474                $this->assertEquals( $autosave_data['content'], $autosave_post->post_content );
     2475
     2476                wp_delete_post( $post_id );
     2477        }
     2478
    23662479        public function test_rest_update_post_raw() {
    23672480                wp_set_current_user( self::$editor_id );
    23682481
  • tests/phpunit/tests/rest-api/rest-revisions-controller.php

    diff --git tests/phpunit/tests/rest-api/rest-revisions-controller.php tests/phpunit/tests/rest-api/rest-revisions-controller.php
    index c30ab17c85..11576889d4 100644
    class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase 
    4444                                'ID'           => self::$post_id,
    4545                        )
    4646                );
     47
     48                // Autosave, different user
     49                wp_set_current_user( self::$contributor_id );
     50                _wp_put_post_revision(
     51                        array(
     52                                'post_content' => 'This content is better autosaved.',
     53                                'ID'           => self::$post_id,
     54                        ),
     55                        true
     56                );
    4757                wp_set_current_user( 0 );
    4858        }
    4959
    class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase 
    6474                $this->revision_id1 = $this->revision_1->ID;
    6575                $this->revision_2   = array_pop( $revisions );
    6676                $this->revision_id2 = $this->revision_2->ID;
     77                $this->revision_3   = array_pop( $revisions );
     78                $this->revision_id3 = $this->revision_3->ID;
    6779        }
    6880
    6981        public function test_register_routes() {
    class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase 
    95107                $response = rest_get_server()->dispatch( $request );
    96108                $data     = $response->get_data();
    97109                $this->assertEquals( 200, $response->get_status() );
    98                 $this->assertCount( 2, $data );
     110                $this->assertCount( 3, $data );
    99111
    100112                // Reverse chron
    101                 $this->assertEquals( $this->revision_id2, $data[0]['id'] );
    102                 $this->check_get_revision_response( $data[0], $this->revision_2 );
     113                $this->assertEquals( $this->revision_id3, $data[0]['id'] );
     114                $this->check_get_revision_response( $data[0], $this->revision_3 );
     115
     116                $this->assertEquals( $this->revision_id2, $data[1]['id'] );
     117                $this->check_get_revision_response( $data[1], $this->revision_2 );
    103118
    104                 $this->assertEquals( $this->revision_id1, $data[1]['id'] );
    105                 $this->check_get_revision_response( $data[1], $this->revision_1 );
     119                $this->assertEquals( $this->revision_id1, $data[2]['id'] );
     120                $this->check_get_revision_response( $data[2], $this->revision_1 );
    106121        }
    107122
    108123        public function test_get_items_no_permission() {
    class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase 
    155170                $this->assertSame( self::$editor_id, $data['author'] );
    156171        }
    157172
     173        public function test_get_autosave_item() {
     174                wp_set_current_user( self::$editor_id );
     175                $request  = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id3 );
     176                $request->set_param( 'is_autosave', true );
     177                $response = rest_get_server()->dispatch( $request );
     178
     179                $this->assertEquals( 200, $response->get_status() );
     180                $data     = $response->get_data();
     181
     182                $this->assertNotEquals( self::$editor_id, $data['author'] );
     183                $this->assertEquals( $this->revision_id3, $data['id'] );
     184                $this->check_get_revision_response( $response, $this->revision_3 );
     185        }
     186
    158187        public function test_get_item_embed_context() {
    159188                wp_set_current_user( self::$editor_id );
    160189                $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . self::$post_id . '/revisions/' . $this->revision_id1 );
    class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase 
    330359                $this->assertEquals( mysql_to_rfc3339( $revision->post_date ), $response['date'] );
    331360                $this->assertEquals( mysql_to_rfc3339( $revision->post_date_gmt ), $response['date_gmt'] );
    332361
    333                 $rendered_excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $revision->post_excerpt, $revision ) );
    334                 $this->assertEquals( $rendered_excerpt, $response['excerpt']['rendered'] );
     362                if ( ! empty( $revision->post_excerpt ) ) {
     363                        $rendered_excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $revision->post_excerpt, $revision ) );
     364                        $this->assertEquals( $rendered_excerpt, $response['excerpt']['rendered'] );
     365                }
    335366
    336367                $rendered_guid = apply_filters( 'get_the_guid', $revision->guid, $revision->ID );
    337368                $this->assertEquals( $rendered_guid, $response['guid']['rendered'] );