Make WordPress Core


Ignore:
Timestamp:
09/26/2023 03:30:34 PM (9 months ago)
Author:
adamsilverstein
Message:

Revisions: framework for storing post meta revisions.

Enable the storing of post meta in revisions including autosaves and previews:

Add a new argument revisions_enabled to the register_meta function which enables storing meta in revisions.

Add a new wp_post_revision_meta_keys filter which developers can use to control which meta is revisioned - it passes an array of the meta keys with revisions enabled as well as the post type.

Meta keys with revisions enabled are also stored for autosaves, and are restored when a revision or autosave is restored. In addition, meta values are now stored with the autosave revision used for previews. Changes to meta can now be previewed correctly without overwriting the published meta (see #20299) or passing data as a query variable, as the editor currently does to preview changes to the featured image.

Changes to meta with revisions enabled are considered when determining if a new revision should be created. A new revision is created if the meta value has changed since the last revision.

Revisions are now saved on the wp_after_insert_post hook instead of post_updated. The wp_after_insert_post action is fired after post meta has been saved by the REST API which enables attaching meta to the revision. To ensure backwards compatibility with existing action uses, wp_save_post_revision_on_insert function exits early if plugins have removed the previous do_action( 'post_updated', 'wp_save_post_revision' ) call.

Props: alexkingorg, johnbillion, markjaquith, WraithKenny, kovshenin, azaozz, tv-productions, p51labs, mattheu, mikeschroder, Mamaduka, ellatrix, timothyblynjacobs, jakemgold, bookwyrm, ryanduff, mintindeed, wonderboymusic, sanchothefat, westonruter, spacedmonkey, hellofromTonya, drewapicture, adamsilverstein, swisspiddy.
Fixes #20564, #20299.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/revision.php

    r56359 r56714  
    9797
    9898/**
     99 * Saves revisions for a post after all changes have been made.
     100 *
     101 * @since 6.4.0
     102 *
     103 * @param int     $post_id The post id that was inserted.
     104 * @param WP_Post $post    The post object that was inserted.
     105 * @param bool    $update  Whether this insert is updating an existing post.
     106 */
     107function wp_save_post_revision_on_insert( $post_id, $post, $update ) {
     108    if ( ! $update ) {
     109        return;
     110    }
     111
     112    if ( ! has_action( 'post_updated', 'wp_save_post_revision' ) ) {
     113        return;
     114    }
     115
     116    wp_save_post_revision( $post_id );
     117}
     118
     119/**
    99120 * Creates a revision for the current version of a post.
    100121 *
     
    109130function wp_save_post_revision( $post_id ) {
    110131    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
     132        return;
     133    }
     134
     135    // Prevent saving post revisions if revisions should be saved on wp_after_insert_post.
     136    if ( doing_action( 'post_updated' ) && has_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert' ) ) {
    111137        return;
    112138    }
     
    362388         *
    363389         * @since 2.6.0
     390         * @since 6.4.0 The post_id parameter was added.
    364391         *
    365392         * @param int $revision_id Post revision ID.
     393         * @param int $post_id     Post ID.
    366394         */
    367         do_action( '_wp_put_post_revision', $revision_id );
     395        do_action( '_wp_put_post_revision', $revision_id, $post['post_parent'] );
    368396    }
    369397
    370398    return $revision_id;
     399}
     400
     401
     402/**
     403 * Save the revisioned meta fields.
     404 *
     405 * @since 6.4.0
     406 *
     407 * @param int $revision_id The ID of the revision to save the meta to.
     408 * @param int $post_id     The ID of the post the revision is associated with.
     409 */
     410function wp_save_revisioned_meta_fields( $revision_id, $post_id ) {
     411    $post_type = get_post_type( $post_id );
     412    if ( ! $post_type ) {
     413        return;
     414    }
     415
     416    foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
     417        if ( metadata_exists( 'post', $post_id, $meta_key ) ) {
     418            _wp_copy_post_meta( $post_id, $revision_id, $meta_key );
     419        }
     420    }
    371421}
    372422
     
    451501    update_post_meta( $post_id, '_edit_last', get_current_user_id() );
    452502
     503    // Restore any revisioned meta fields.
     504    wp_restore_post_revision_meta( $post_id, $revision['ID'] );
     505
    453506    /**
    454507     * Fires after a post revision has been restored.
     
    462515
    463516    return $post_id;
     517}
     518
     519/**
     520 * Restore the revisioned meta values for a post.
     521 *
     522 * @param int $post_id     The ID of the post to restore the meta to.
     523 * @param int $revision_id The ID of the revision to restore the meta from.
     524 *
     525 * @since 6.4.0
     526 */
     527function wp_restore_post_revision_meta( $post_id, $revision_id ) {
     528    $post_type = get_post_type( $post_id );
     529    if ( ! $post_type ) {
     530        return;
     531    }
     532
     533    // Restore revisioned meta fields.
     534    foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
     535
     536        // Clear any existing meta.
     537        delete_post_meta( $post_id, $meta_key );
     538
     539        _wp_copy_post_meta( $revision_id, $post_id, $meta_key );
     540    }
     541}
     542
     543/**
     544 * Copy post meta for the given key from one post to another.
     545 *
     546 * @param int    $source_post_id Post ID to copy meta value(s) from.
     547 * @param int    $target_post_id Post ID to copy meta value(s) to.
     548 * @param string $meta_key       Meta key to copy.
     549 *
     550 * @since 6.4.0
     551 */
     552function _wp_copy_post_meta( $source_post_id, $target_post_id, $meta_key ) {
     553
     554    foreach ( get_post_meta( $source_post_id, $meta_key ) as $meta_value ) {
     555        /**
     556         * We use add_metadata() function vs add_post_meta() here
     557         * to allow for a revision post target OR regular post.
     558         */
     559        add_metadata( 'post', $target_post_id, $meta_key, wp_slash( $meta_value ) );
     560    }
     561}
     562
     563/**
     564 * Determine which post meta fields should be revisioned.
     565 *
     566 * @since 6.4.0
     567 *
     568 * @param string $post_type The post type being revisioned.
     569 *
     570 * @return array An array of meta keys to be revisioned.
     571 */
     572function wp_post_revision_meta_keys( $post_type ) {
     573    $registered_meta = array_merge(
     574        get_registered_meta_keys( 'post' ),
     575        get_registered_meta_keys( 'post', $post_type )
     576    );
     577
     578    $wp_revisioned_meta_keys = array();
     579
     580    foreach ( $registered_meta as $name => $args ) {
     581        if ( $args['revisions_enabled'] ) {
     582            $wp_revisioned_meta_keys[ $name ] = true;
     583        }
     584    }
     585
     586    $wp_revisioned_meta_keys = array_keys( $wp_revisioned_meta_keys );
     587
     588    /**
     589     * Filter the list of post meta keys to be revisioned.
     590     *
     591     * @since 6.4.0
     592     *
     593     * @param array $keys       An array of meta fields to be revisioned.
     594     * @param string $post_type The post type being revisioned.
     595     */
     596    return apply_filters( 'wp_post_revision_meta_keys', $wp_revisioned_meta_keys, $post_type );
     597}
     598
     599/**
     600 * Check whether revisioned post meta fields have changed.
     601 *
     602 * @param bool    $post_has_changed Whether the post has changed.
     603 * @param WP_Post $last_revision    The last revision post object.
     604 * @param WP_Post $post             The post object.
     605 *
     606 * @since 6.4.0
     607 */
     608function wp_check_revisioned_meta_fields_have_changed( $post_has_changed, WP_Post $last_revision, WP_Post $post ) {
     609    foreach ( wp_post_revision_meta_keys( $post->post_type ) as $meta_key ) {
     610        if ( get_post_meta( $post->ID, $meta_key ) !== get_post_meta( $last_revision->ID, $meta_key ) ) {
     611            $post_has_changed = true;
     612            break;
     613        }
     614    }
     615    return $post_has_changed;
    464616}
    465617
     
    729881    add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 );
    730882    add_filter( 'get_post_metadata', '_wp_preview_post_thumbnail_filter', 10, 3 );
     883    add_filter( 'get_post_metadata', '_wp_preview_meta_filter', 10, 4 );
    731884
    732885    return $post;
     
    9471100    return true;
    9481101}
     1102
     1103/**
     1104 * Filters preview post meta retrieval to get values from the autosave.
     1105 *
     1106 * Filters revisioned meta keys only.
     1107 *
     1108 * @since 6.4.0
     1109 *
     1110 * @param mixed  $value     Meta value to filter.
     1111 * @param int    $object_id Object ID.
     1112 * @param string $meta_key  Meta key to filter a value for.
     1113 * @param bool   $single    Whether to return a single value. Default false.
     1114 * @return mixed Original meta value if the meta key isn't revisioned, the object doesn't exist,
     1115 *               the post type is a revision or the post ID doesn't match the object ID.
     1116 *               Otherwise, the revisioned meta value is returned for the preview.
     1117 */
     1118function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) {
     1119
     1120    $post = get_post();
     1121    if (
     1122        empty( $post ) ||
     1123        $post->ID !== $object_id ||
     1124        ! in_array( $meta_key, wp_post_revision_meta_keys( $post->post_type ), true ) ||
     1125        'revision' === $post->post_type
     1126    ) {
     1127        return $value;
     1128    }
     1129
     1130    $preview = wp_get_post_autosave( $post->ID );
     1131    if ( false === $preview ) {
     1132        return $value;
     1133    }
     1134
     1135    return get_post_meta( $preview->ID, $meta_key, $single );
     1136}
Note: See TracChangeset for help on using the changeset viewer.