WordPress.org

Make WordPress Core

Ticket #20564: 20564.14.diff

File 20564.14.diff, 12.9 KB (added by adamsilverstein, 6 years ago)

only save changed revisions

  • src/wp-admin/includes/post.php

     
    15231523                $new_autosave['ID'] = $old_autosave->ID;
    15241524                $new_autosave['post_author'] = $post_author;
    15251525
     1526                // Auto-save revisioned meta fields.
     1527                foreach ( _wp_post_revision_meta_keys() as $meta_key ) {
     1528                        if ( isset( $_POST[ $meta_key ] )
     1529                                && get_post_meta( $new_autosave['ID'], $meta_key, true ) != wp_unslash( $_POST[ $meta_key ] ) )
     1530                        {
     1531                                /*
     1532                                 * Use the underlying delete_metadata() and add_metadata() functions
     1533                                 * vs delete_post_meta() and add_post_meta() to make sure we're working
     1534                                 * with the actual revision meta.
     1535                                 */
     1536                                delete_metadata( 'post', $new_autosave['ID'], $meta_key );
     1537                                if ( ! empty( $_POST[ $meta_key ] ) ) {
     1538                                        error_log('wp_create_post_autosave saving meta data for ' . $meta_key );
     1539                                        //add_metadata( 'post', $new_autosave['ID'], $meta_key, $_POST[ $meta_key ] );
     1540                                }
     1541                        }
     1542                }
     1543
    15261544                // If the new autosave has the same content as the post, delete the autosave.
    15271545                $post = get_post( $post_id );
    15281546                $autosave_is_different = false;
  • src/wp-includes/revision.php

     
    7070}
    7171
    7272/**
     73 * Determine which post meta fields should be revisioned.
     74 *
     75 * @access private
     76 * @since 4.0.0
     77 *
     78 * @return array An array of meta keys to be revisioned.
     79 */
     80function _wp_post_revision_meta_keys() {
     81        /**
     82         * Filter the list of post meta keys to be revisioned.
     83         *
     84         * @since 4.0.0
     85         *
     86         * @param array $keys An array of default meta fields to be revisioned.
     87         */
     88        return apply_filters( 'wp_post_revision_meta_keys', array() );
     89}
     90
     91/**
    7392 * Saves an already existing post as a post revision.
    7493 *
    7594 * Typically used immediately after post updates.
     
    127146                if ( isset( $last_revision ) && apply_filters( 'wp_save_post_revision_check_for_changes', $check_for_changes = true, $last_revision, $post ) ) {
    128147                        $post_has_changed = false;
    129148
     149                        // Check whether revisioned post fields have been changed.
    130150                        foreach ( array_keys( _wp_post_revision_fields() ) as $field ) {
    131151                                if ( normalize_whitespace( $post->$field ) != normalize_whitespace( $last_revision->$field ) ) {
    132152                                        $post_has_changed = true;
     
    133153                                        break;
    134154                                }
    135155                        }
    136                         //don't save revision if post unchanged
     156
     157                        // Check whether revisioned post meta fields have changed.
     158                        foreach ( _wp_post_revision_meta_keys() as $meta_key ) {
     159                                if ( get_post_meta( $post->ID, $meta_key ) != get_revision_post_meta( $last_revision->ID, $meta_key ) ) {
     160                                        $post_has_changed = true;
     161                                        break;
     162                                }
     163                        }
     164
     165                        // Don't save revision if the post is unchanged.
    137166                        if( ! $post_has_changed )
    138167                                return;
    139168                }
    140169        }
    141 
    142170        $return = _wp_put_post_revision( $post );
    143171
    144172        $revisions_to_keep = wp_revisions_to_keep( $post );
     
    229257        return false;
    230258}
    231259
     260
    232261/**
     262 * Retrieves a post_meta value for a specific revision.
     263 * If the meta key is being revisioned, but the meta value was not saved
     264 * in that revision, searches backward through the post's revisions to
     265 * find last saved meta value.
     266 *
     267 * @since  4.0.0
     268 *
     269 * @param  int $revision_id ID of the revision to find meta for.
     270 * @param  string $meta_key The key to retrieve.
     271 * @return mixed            Last revisioned value of meta field, false if not stored
     272 *                          for this revision, blank if revisioned but never saved.
     273 *
     274 */
     275function get_revision_post_meta( $revision_id, $meta_key ) {
     276        $metas_revisioned = wp_unslash( get_metadata( 'post', $revision_id, '_wp_post_revision_meta_keys' ) );
     277
     278        // If this meta was not revisioned for this revision return false
     279        if ( ! in_array( $meta_key, $metas_revisioned[0] ) ) {
     280                return false;
     281        }
     282        // If the current revision contains the meta value,no need to search for it
     283        if ( '' !== ( $meta_value = wp_unslash( get_metadata( 'post', $revision_id, $meta_key, true ) ) ) ) {
     284                return $meta_value;
     285        }
     286        // Otherwise, try to find the most recent revision that contains the meta value
     287        // First select all this post's revisions
     288        $revision = get_post( $revision_id );
     289        $all_revisions = wp_get_post_revisions( $revision->post_parent );
     290
     291        if ( empty( $all_revisions ) ) {
     292                return ''; /* no revisions, return blank */
     293        }
     294        $current_found = false;
     295        foreach( $all_revisions as $single_revision ) {
     296                // Keep going back until we find the current revision
     297                if ( ! $current_found ){
     298                        if ( $single_revision->ID !== $revision_id ) {
     299                                continue; /* Not yet, try the next one */
     300                        } else {
     301                                $current_found = true; /* Found, now examine the rest */
     302                        }
     303                }
     304                // Check if this meta was revisioned for this revision
     305                $metas_revisioned = wp_unslash( get_metadata( 'post', $single_revision->ID, '_wp_post_revision_meta_keys' ) );
     306                if ( in_array( $meta_key, $metas_revisioned[0] ) ) {
     307                        // If the meta value is not blank, return it (blank meta values are not revisioned)
     308                        if ( '' !== ( $meta_value = wp_unslash( get_metadata( 'post', $single_revision->ID, $meta_key, true ) ) ) ) {
     309                                return $meta_value;
     310                        }
     311                }
     312        }
     313        // Fallback, no revisioned meta was found so return blank
     314        return '';
     315}
     316/**
    233317 * Inserts post data into the posts table as a post revision.
    234318 *
    235319 * @since 2.6.0
     
    253337        if ( isset($post['post_type']) && 'revision' == $post['post_type'] )
    254338                return new WP_Error( 'post_type', __( 'Cannot create a revision of a revision' ) );
    255339
     340        $post_id = (int) $post['ID'];
    256341        $post = _wp_post_revision_fields( $post, $autosave );
    257342        $post = wp_slash($post); //since data is from db
    258343
     
    271356                do_action( '_wp_put_post_revision', $revision_id );
    272357        }
    273358
     359        // Save revisioned meta fields.
     360        foreach ( _wp_post_revision_meta_keys() as $meta_key ) {
     361                $meta_value = get_post_meta( $post_id, $meta_key, true );
     362                // Don't save unchanged meta values
     363                $previous_revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 2 ) );
     364                array_shift( $previous_revisions );
     365                $last_revision      = array_shift( $previous_revisions );
     366
     367                // Only save if the meta value has changed
     368                if ( null == $last_revision  || $meta_value == get_revision_post_meta( $last_revision->ID, $meta_key ) ) {
     369                        continue;
     370                }
     371                /*
     372                 * Use the underlying add_metadata() function vs add_post_meta()
     373                 * to ensure metadata is added to the revision post and not its parent.
     374                 */
     375                add_metadata( 'post', $revision_id, $meta_key, wp_slash( $meta_value ) );
     376        }
     377        // Save the list of revisioned meta so we know which meta keys were revisioned
     378        add_metadata( 'post', $revision_id, '_wp_post_revision_meta_keys', wp_slash( _wp_post_revision_meta_keys() ) );
     379
    274380        return $revision_id;
    275381}
    276382
     
    336442
    337443        $update['ID'] = $revision['post_parent'];
    338444
     445        // Restore revisioned meta fields.
     446        foreach ( _wp_post_revision_meta_keys() as $meta_key ) {
     447                $meta_value = get_post_meta( $revision['ID'], $meta_key, true );
     448                if ( empty( $meta_value ) ) {
     449                        $meta_value = '';
     450                }
     451                // Add slashes to data pulled from the database.
     452                update_post_meta( $update['ID'], $meta_key, wp_slash( $meta_value ) );
     453        }
     454
    339455        $update = wp_slash( $update ); //since data is from db
    340456
    341457        $post_id = wp_update_post( $update );
     
    503619        $post->post_excerpt = $preview->post_excerpt;
    504620
    505621        add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 );
     622        add_filter( 'get_post_metadata', '_wp_preview_meta_filter', 10, 4 );
    506623
    507624        return $post;
    508625}
     
    526643}
    527644
    528645/**
     646 * Filters post meta retrieval to get values from the actual autosave post,
     647 * and not its parent.
     648 *
     649 * Filters revisioned meta keys only.
     650 *
     651 * @access private
     652 * @since 4.0.0
     653 *
     654 * @param mixed  $value     Meta value to filter.
     655 * @param int    $object_id Object ID.
     656 * @param string $meta_key  Meta key to filter a value for.
     657 * @param bool   $single    Whether to return a single value. Default false.
     658 * @return mixed Original meta value if the meta key isn't revisioned, the object doesn't exist,
     659 *               the post type is a revisionm or the post ID doesn't match the object ID.
     660 *               Otherwise, the revisioned meta value is returned for the preview.
     661 */
     662function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) {
     663        $post = get_post();
     664        if ( empty( $post )
     665                || $post->ID != $object_id
     666                || ! in_array( $meta_key, _wp_post_revision_meta_keys() )
     667                || 'revision' == $post->post_type )
     668        {
     669                return $value;
     670        }
     671
     672        // Grab the autosave.
     673        $preview = wp_get_post_autosave( $post->ID );
     674        if ( ! is_object( $preview ) ) {
     675                return $value;
     676        }
     677
     678        return get_post_meta( $preview->ID, $meta_key, $single );
     679}
     680
     681/**
    529682 * Filters terms lookup to set the post format.
    530683 *
    531684 * @since 3.6.0
  • tests/phpunit/tests/post/revisions.php

     
    385385                $this->assertEquals( 100, $ok );
    386386                $this->assertEquals( 0, $reversed );
    387387        }
     388
     389        /**
     390         * Test the revisions system for storage of meta values
     391         * @ticket 20564
     392         */
     393        function test_revisions_stores_meta_values() {
     394                // Set up a new post
     395                $original_post_id = $post_id = $this->factory->post->create();
     396                // And update to store an initial revision
     397                wp_update_post( array( 'post_content'   => 'some initial content', 'ID' => $post_id ) );
     398
     399                // One revision so far
     400                $revisions = wp_get_post_revisions( $post_id );
     401                $this->assertCount( 1, $revisions );
     402                /**
     403                 * First set up a meta value
     404                 */
     405
     406                // Store a custom meta value, which is not revisioned by default
     407                update_post_meta( $post_id, 'meta_revision_test', 'original' );
     408
     409                // Update the post, storing a revision
     410                wp_update_post( array( 'post_content'   => 'some more content', 'ID' => $post_id ) );
     411
     412                $revisions = wp_get_post_revisions( $post_id );
     413                $this->assertCount( 2, $revisions );
     414
     415
     416                //  Next, store some updated meta values for the same key
     417                update_post_meta( $post_id, 'meta_revision_test', 'update1' );
     418
     419                // Save the post, changing content to force a revision
     420                wp_update_post( array( 'post_content'   => 'some updated content', 'ID' => $post_id ) );
     421
     422                $revisions = wp_get_post_revisions( $post_id );
     423                $this->assertCount( 3, $revisions );
     424
     425
     426                /**
     427                 * Now restore the original revision
     428                 */
     429
     430                // Restore the previous revision
     431                $revisions = (Array)wp_get_post_revisions( $post_id );
     432                // Go back two to load the previous revision
     433                array_shift( $revisions );
     434                $last_revision = array_shift( $revisions );
     435                // Restore!
     436                wp_restore_post_revision( $last_revision->ID );
     437
     438                wp_update_post( array( 'ID' => $post_id ) );
     439                $revisions = wp_get_post_revisions( $post_id );
     440                $this->assertCount( 4, $revisions );
     441
     442                /**
     443                 * Check the meta values to verify they are NOT revisioned - they are not revisioned by default
     444                 */
     445
     446                // Custom post meta should NOT be restored, orignal value should not be restored, value still 'update1'
     447                $this->assertEquals( 'update1', get_post_meta( $post_id, 'meta_revision_test', true ) );
     448
     449                update_post_meta( $post_id, 'meta_revision_test', 'update2' );
     450
     451
     452                /*
     453                 * Now test the revisioning of custom meta when enabled by the wp_post_revision_meta_keys filter
     454                 */
     455
     456                // Add the custom field to be revised via the wp_post_revision_meta_keys filter
     457                add_filter( 'wp_post_revision_meta_keys', function( $keys ) {
     458                        $keys[] = 'meta_revision_test';
     459                        return $keys;
     460                });
     461
     462                // Save the post, changing content to force a revision
     463                wp_update_post( array( 'post_content'   => 'more updated content', 'ID' => $post_id ) );
     464
     465                $revisions = wp_get_post_revisions( $post_id );
     466                $this->assertCount( 5, $revisions );
     467
     468                // Store custom meta values, which should now be revisioned
     469                update_post_meta( $post_id, 'meta_revision_test', 'update3' );
     470
     471                /**
     472                 * Save the post again, custom meta should now be revisioned
     473                 *
     474                 * Note that a revision is saved even though there is no change
     475                 * in post content, becaused the revisioned post_meta has changed
     476                 *
     477                 */
     478                wp_update_post( array( 'ID' => $post_id ) );
     479
     480                // This revision contains the existing post meta ('update3')
     481                $revisions = wp_get_post_revisions( $post_id );
     482                $this->assertCount( 6, $revisions );
     483
     484                // Verify that previous post meta is set
     485                $this->assertEquals( 'update3', get_post_meta( $post_id, 'meta_revision_test', true ) );
     486
     487                // Retore the previous revision
     488                $revisions = wp_get_post_revisions( $post_id );
     489
     490
     491                // Go back two to load the previous revision
     492                array_shift( $revisions );
     493                $last_revision = array_shift( $revisions );
     494                wp_restore_post_revision( $last_revision->ID );
     495
     496                // Verify that previous post meta is restored
     497                $this->assertEquals( 'update2', get_post_meta( $post_id, 'meta_revision_test', true ) );
     498
     499                // Cleanup!
     500                wp_delete_post( $original_post_id );
     501
     502        }
    388503}