Skip to content

Commit 4f56c3d

Browse files
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. git-svn-id: https://develop.svn.wordpress.org/trunk@56714 602fd350-edb4-49c9-b593-d223f7449a82
1 parent f84d5c2 commit 4f56c3d

14 files changed

+1502
-22
lines changed

src/wp-admin/includes/post.php

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,19 +1970,84 @@ function wp_create_post_autosave( $post_data ) {
19701970
* Fires before an autosave is stored.
19711971
*
19721972
* @since 4.1.0
1973+
* @since 6.4.0 The `$is_update` parameter was added to indicate if the autosave is being updated or was newly created.
19731974
*
19741975
* @param array $new_autosave Post array - the autosave that is about to be saved.
1976+
* @param bool $is_update Whether this is an existing autosave.
19751977
*/
1976-
do_action( 'wp_creating_autosave', $new_autosave );
1977-
1978+
do_action( 'wp_creating_autosave', $new_autosave, true );
19781979
return wp_update_post( $new_autosave );
19791980
}
19801981

19811982
// _wp_put_post_revision() expects unescaped.
19821983
$post_data = wp_unslash( $post_data );
19831984

19841985
// Otherwise create the new autosave as a special post revision.
1985-
return _wp_put_post_revision( $post_data, true );
1986+
$revision = _wp_put_post_revision( $post_data, true );
1987+
1988+
if ( ! is_wp_error( $revision ) && 0 !== $revision ) {
1989+
1990+
/** This action is documented in wp-admin/includes/post.php */
1991+
do_action( 'wp_creating_autosave', get_post( $revision, ARRAY_A ), false );
1992+
}
1993+
1994+
return $revision;
1995+
}
1996+
1997+
/**
1998+
* Autosave the revisioned meta fields.
1999+
*
2000+
* Iterates through the revisioned meta fields and checks each to see if they are set,
2001+
* and have a changed value. If so, the meta value is saved and attached to the autosave.
2002+
*
2003+
* @since 6.4.0
2004+
*
2005+
* @param array $new_autosave The new post data being autosaved.
2006+
*/
2007+
function wp_autosave_post_revisioned_meta_fields( $new_autosave ) {
2008+
/*
2009+
* The post data arrives as either $_POST['data']['wp_autosave'] or the $_POST
2010+
* itself. This sets $posted_data to the correct variable.
2011+
*
2012+
* Ignoring sanitization to avoid altering meta. Ignoring the nonce check because
2013+
* this is hooked on inner core hooks where a valid nonce was already checked.
2014+
*
2015+
* @phpcs:disable WordPress.Security
2016+
*/
2017+
$posted_data = isset( $_POST['data']['wp_autosave'] ) ? $_POST['data']['wp_autosave'] : $_POST;
2018+
// phpcs:enable
2019+
2020+
$post_type = get_post_type( $new_autosave['post_parent'] );
2021+
2022+
/*
2023+
* Go thru the revisioned meta keys and save them as part of the autosave, if
2024+
* the meta key is part of the posted data, the meta value is not blank and
2025+
* the the meta value has changes from the last autosaved value.
2026+
*/
2027+
foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
2028+
2029+
if (
2030+
isset( $posted_data[ $meta_key ] ) &&
2031+
get_post_meta( $new_autosave['ID'], $meta_key, true ) !== wp_unslash( $posted_data[ $meta_key ] )
2032+
) {
2033+
/*
2034+
* Use the underlying delete_metadata() and add_metadata() functions
2035+
* vs delete_post_meta() and add_post_meta() to make sure we're working
2036+
* with the actual revision meta.
2037+
*/
2038+
delete_metadata( 'post', $new_autosave['ID'], $meta_key );
2039+
2040+
/*
2041+
* One last check to ensure meta value not empty().
2042+
*/
2043+
if ( ! empty( $posted_data[ $meta_key ] ) ) {
2044+
/*
2045+
* Add the revisions meta data to the autosave.
2046+
*/
2047+
add_metadata( 'post', $new_autosave['ID'], $meta_key, $posted_data[ $meta_key ] );
2048+
}
2049+
}
2050+
}
19862051
}
19872052

19882053
/**

src/wp-includes/default-filters.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@
411411
add_action( 'plugins_loaded', 'wp_maybe_load_embeds', 0 );
412412
add_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
413413
// Create a revision whenever a post is updated.
414+
add_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 );
414415
add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
415416
add_action( 'publish_post', '_publish_post_hook', 5, 1 );
416417
add_action( 'transition_post_status', '_transition_post_status', 5, 3 );
@@ -719,6 +720,18 @@
719720
// CPT wp_block custom postmeta field.
720721
add_action( 'init', 'wp_create_initial_post_meta' );
721722

723+
// Include revisioned meta when considering whether a post revision has changed.
724+
add_filter( 'wp_save_post_revision_post_has_changed', 'wp_check_revisioned_meta_fields_have_changed', 10, 3 );
725+
726+
// Save revisioned post meta immediately after a revision is saved
727+
add_action( '_wp_put_post_revision', 'wp_save_revisioned_meta_fields', 10, 2 );
728+
729+
// Include revisioned meta when creating or updating an autosave revision.
730+
add_action( 'wp_creating_autosave', 'wp_autosave_post_revisioned_meta_fields' );
731+
732+
// When restoring revisions, also restore revisioned meta.
733+
add_action( 'wp_restore_post_revision', 'wp_restore_post_revision_meta', 10, 2 );
734+
722735
// Font management.
723736
add_action( 'wp_head', 'wp_print_font_faces', 50 );
724737

src/wp-includes/meta.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,7 @@ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype =
13671367
* @since 4.9.8 The `$object_subtype` argument was added to the arguments array.
13681368
* @since 5.3.0 Valid meta types expanded to include "array" and "object".
13691369
* @since 5.5.0 The `$default` argument was added to the arguments array.
1370+
* @since 6.4.0 The `$revisions_enabled` argument was added to the arguments array.
13701371
*
13711372
* @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
13721373
* or any other object type with an associated meta table.
@@ -1392,6 +1393,8 @@ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype =
13921393
* support for custom fields for registered meta to be accessible via REST.
13931394
* When registering complex meta values this argument may optionally be an
13941395
* array with 'schema' or 'prepare_callback' keys instead of a boolean.
1396+
* @type bool $revisions_enabled Whether to enable revisions support for this meta_key. Can only be used when the
1397+
* object type is 'post'.
13951398
* }
13961399
* @param string|array $deprecated Deprecated. Use `$args` instead.
13971400
* @return bool True if the meta key was successfully registered in the global array, false if not.
@@ -1414,6 +1417,7 @@ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) {
14141417
'sanitize_callback' => null,
14151418
'auth_callback' => null,
14161419
'show_in_rest' => false,
1420+
'revisions_enabled' => false,
14171421
);
14181422

14191423
// There used to be individual args for sanitize and auth callbacks.
@@ -1460,6 +1464,17 @@ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) {
14601464
}
14611465

14621466
$object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : '';
1467+
if ( $args['revisions_enabled'] ) {
1468+
if ( 'post' !== $object_type ) {
1469+
_doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object type supports revisions.' ), '6.4.0' );
1470+
1471+
return false;
1472+
} elseif ( ! empty( $object_subtype ) && ! post_type_supports( $object_subtype, 'revisions' ) ) {
1473+
_doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object subtype supports revisions.' ), '6.4.0' );
1474+
1475+
return false;
1476+
}
1477+
}
14631478

14641479
// If `auth_callback` is not provided, fall back to `is_protected_meta()`.
14651480
if ( empty( $args['auth_callback'] ) ) {

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

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,8 @@ public function create_item( $request ) {
234234
*/
235235
$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
236236
} else {
237-
// Non-draft posts: create or update the post autosave.
238-
$autosave_id = $this->create_post_autosave( (array) $prepared_post );
237+
// Non-draft posts: create or update the post autosave. Pass the meta data.
238+
$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
239239
}
240240

241241
if ( is_wp_error( $autosave_id ) ) {
@@ -348,11 +348,13 @@ public function get_item_schema() {
348348
* From wp-admin/post.php.
349349
*
350350
* @since 5.0.0
351+
* @since 6.4.0 The `$meta` parameter was added.
351352
*
352353
* @param array $post_data Associative array containing the post data.
354+
* @param array $meta Associative array containing the post meta data.
353355
* @return mixed The autosave revision ID or WP_Error.
354356
*/
355-
public function create_post_autosave( $post_data ) {
357+
public function create_post_autosave( $post_data, array $meta = array() ) {
356358

357359
$post_id = (int) $post_data['ID'];
358360
$post = get_post( $post_id );
@@ -372,6 +374,21 @@ public function create_post_autosave( $post_data ) {
372374
}
373375
}
374376

377+
// Check if meta values have changed.
378+
if ( ! empty( $meta ) ) {
379+
$revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
380+
foreach ( $revisioned_meta_keys as $meta_key ) {
381+
// get_metadata_raw is used to avoid retrieving the default value.
382+
$old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
383+
$new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';
384+
385+
if ( $new_meta !== $old_meta ) {
386+
$autosave_is_different = true;
387+
break;
388+
}
389+
}
390+
}
391+
375392
$user_id = get_current_user_id();
376393

377394
// Store one autosave per author. If there is already an autosave, overwrite it.
@@ -390,11 +407,26 @@ public function create_post_autosave( $post_data ) {
390407
do_action( 'wp_creating_autosave', $new_autosave );
391408

392409
// wp_update_post() expects escaped array.
393-
return wp_update_post( wp_slash( $new_autosave ) );
410+
$revision_id = wp_update_post( wp_slash( $new_autosave ) );
411+
} else {
412+
// Create the new autosave as a special post revision.
413+
$revision_id = _wp_put_post_revision( $post_data, true );
414+
}
415+
416+
if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
417+
return $revision_id;
418+
}
419+
420+
// Attached any passed meta values that have revisions enabled.
421+
if ( ! empty( $meta ) ) {
422+
foreach ( $revisioned_meta_keys as $meta_key ) {
423+
if ( isset( $meta[ $meta_key ] ) ) {
424+
update_metadata( 'post', $revision_id, $meta_key, $meta[ $meta_key ] );
425+
}
426+
}
394427
}
395428

396-
// Create the new autosave as a special post revision.
397-
return _wp_put_post_revision( $post_data, true );
429+
return $revision_id;
398430
}
399431

400432
/**

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
2424
*/
2525
private $parent_post_type;
2626

27+
/**
28+
* Instance of a revision meta fields object.
29+
*
30+
* @since 6.4.0
31+
* @var WP_REST_Post_Meta_Fields
32+
*/
33+
protected $meta;
34+
2735
/**
2836
* Parent controller.
2937
*
@@ -60,6 +68,7 @@ public function __construct( $parent_post_type ) {
6068
$this->rest_base = 'revisions';
6169
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
6270
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
71+
$this->meta = new WP_REST_Post_Meta_Fields( $parent_post_type );
6372
}
6473

6574
/**
@@ -619,6 +628,10 @@ public function prepare_item_for_response( $item, $request ) {
619628
);
620629
}
621630

631+
if ( rest_is_field_included( 'meta', $fields ) ) {
632+
$data['meta'] = $this->meta->get_value( $post->ID, $request );
633+
}
634+
622635
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
623636
$data = $this->add_additional_fields_to_object( $data, $request );
624637
$data = $this->filter_response_by_context( $data, $context );
@@ -752,6 +765,8 @@ public function get_item_schema() {
752765
$schema['properties']['guid'] = $parent_schema['properties']['guid'];
753766
}
754767

768+
$schema['properties']['meta'] = $this->meta->get_field_schema();
769+
755770
$this->schema = $schema;
756771

757772
return $this->add_additional_fields_schema( $this->schema );

0 commit comments

Comments
 (0)