Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add revisioning of post meta, including ‘footnotes’ by default #4859

Closed
wants to merge 117 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
b8bf5ef
Add revisioning of post meta, including ‘footnotes’ by default
adamsilverstein Jul 17, 2023
6667610
correct typo in `src/wp-includes/default-filters.php`
adamsilverstein Jul 17, 2023
15e9e4e
add tests for meta revisioning
adamsilverstein Jul 17, 2023
b892323
Try: revisioning in the rest endpoint directly, after meta is saved
adamsilverstein Jul 17, 2023
6235af5
Add _wp_save_post_revision_meta helper that copies revisioned meta to…
adamsilverstein Jul 18, 2023
3a87749
remove direct REST API integration, move save to wp_after_insert_post
adamsilverstein Jul 18, 2023
05d0f86
Clean up revisions save
adamsilverstein Jul 18, 2023
19e1f8a
phpcbf
adamsilverstein Jul 18, 2023
0b86da9
Ensure `$revisions[0]->post_id ` is set before using
adamsilverstein Jul 18, 2023
52dd943
Test work
adamsilverstein Jul 18, 2023
ef14c11
Expected revision count is 6
adamsilverstein Jul 19, 2023
31a6d61
Merge branch 'trunk' into ticket/20564
adamsilverstein Jul 21, 2023
da8abe6
Add REST API tests for revisioning of post meta.
adamsilverstein Jul 25, 2023
d2f0db1
Merge branch 'trunk' into ticket/20564
adamsilverstein Jul 25, 2023
6c9bece
Remove footnote specific revisioning of meta
adamsilverstein Jul 25, 2023
2e74964
phpcbf
adamsilverstein Jul 25, 2023
268385f
Fix test setup
adamsilverstein Jul 25, 2023
4fd4302
Save revisioned meta in `_wp_put_post_revision`
adamsilverstein Jul 25, 2023
3722d1e
Enable setting revisioning in register_meta
adamsilverstein Jul 25, 2023
b26a695
phpcs
adamsilverstein Jul 26, 2023
5d6e5b9
remove unused $revisioned_keys
adamsilverstein Jul 26, 2023
b4fa33f
Add testing for enabling revisions in `register_meta`
adamsilverstein Jul 26, 2023
1603c55
phpcbf
adamsilverstein Jul 26, 2023
d244a39
Use a separate global for revisioned keys `$wp_revisioned_meta_keys`
adamsilverstein Jul 26, 2023
d86ec77
Merge branch 'trunk' into ticket/20564
adamsilverstein Jul 26, 2023
a80620a
Meta test cleanup to match new defaults
adamsilverstein Jul 26, 2023
885640d
Adjust expected for `test_should_register_persisted_preferences_meta`
adamsilverstein Jul 26, 2023
ea4f181
Refine REST tests for meta revisioning
adamsilverstein Jul 26, 2023
e63334b
Avoid duplicate keys in `wp_revisioned_meta_keys`
adamsilverstein Jul 26, 2023
f4234f8
remove unused `original_post_id`
adamsilverstein Jul 26, 2023
1f1e5a0
REST posts endpoint: update revision meta when saving meta
adamsilverstein Jul 26, 2023
6c5061b
test work
adamsilverstein Jul 26, 2023
5bd7e7b
Hook `wp_save_revisioned_meta_fields` on `_wp_put_post_revision` so i…
adamsilverstein Jul 27, 2023
65bfe81
Test cleanup
adamsilverstein Jul 27, 2023
7254e3a
Improve footnote display, show number and content
adamsilverstein Jul 27, 2023
8c3fba2
Save the revisioned meta directly in the REST endpoint
adamsilverstein Jul 27, 2023
26a9d0b
spacing
adamsilverstein Jul 27, 2023
210573b
Merge branch 'trunk' into ticket/20564
adamsilverstein Jul 27, 2023
485f20b
add meta to revisions endpoint
adamsilverstein Jul 31, 2023
167261e
test revisions endpoint returns meta
adamsilverstein Jul 31, 2023
5b5dc0d
new revision meta fields class
adamsilverstein Jul 31, 2023
f8d0d30
INCLUDE class-wp-rest-revision-meta-fields
adamsilverstein Aug 1, 2023
ea9b8ce
Revision existing meta even when update/insert lacks meta
adamsilverstein Aug 1, 2023
15e8bbd
Test that the revisions endpoint includes meta
adamsilverstein Aug 1, 2023
1e54aa3
Merge branch 'trunk' into ticket/20564
adamsilverstein Aug 1, 2023
a3fa561
phpcbf
adamsilverstein Aug 1, 2023
877420e
Merge branch 'trunk' into ticket/20564
adamsilverstein Aug 17, 2023
f57b79b
Update REST tests to reflect changed schema
adamsilverstein Aug 17, 2023
cad9272
phpcbf
adamsilverstein Aug 18, 2023
57c872a
Improve tests
adamsilverstein Aug 18, 2023
ca01058
Finetune tests based on git action failures
adamsilverstein Aug 18, 2023
92590aa
test
adamsilverstein Aug 18, 2023
6fd9a9e
restore footnotes.php from trunk
adamsilverstein Aug 24, 2023
f83d353
Merge branch 'trunk' into ticket/20564
adamsilverstein Aug 24, 2023
7f70990
Update wp-api-generated fixtures
adamsilverstein Aug 24, 2023
432a810
fix generated
adamsilverstein Aug 24, 2023
d80b7b4
Try: move hooking of revisions
adamsilverstein Aug 24, 2023
7160abb
fire after hooks in revisions controller test
adamsilverstein Aug 24, 2023
96301da
Try only adding revision on update.
TimothyBJacobs Aug 24, 2023
e2d0cc7
Use actions properly
TimothyBJacobs Aug 24, 2023
5f07145
Revert "Try only adding revision on update."
TimothyBJacobs Aug 24, 2023
afc1109
Don't rewrite everything
TimothyBJacobs Aug 24, 2023
ac279e3
Correct logic
TimothyBJacobs Aug 24, 2023
4205d65
Check if the legacy hook is being used
TimothyBJacobs Aug 24, 2023
e7b4495
Separate single and multi meta tests. Use native meta fields handler.
TimothyBJacobs Aug 24, 2023
68f6410
Regenerate fixtures
TimothyBJacobs Aug 24, 2023
ee35085
Use post meta fields directly
TimothyBJacobs Aug 24, 2023
61bfafc
Respect the object subtype.
TimothyBJacobs Aug 24, 2023
c27258a
Always pass a post array
TimothyBJacobs Aug 24, 2023
20874e3
Remove manually revisioning meta. This happens automatically now.
TimothyBJacobs Aug 24, 2023
68a419b
Correct has meta key assertion
TimothyBJacobs Aug 24, 2023
3ee633f
Add clarifying comment and some more fail safes
TimothyBJacobs Aug 24, 2023
1e17f5b
Merge branch 'trunk' into ticket/20564
adamsilverstein Aug 27, 2023
982c553
Merge branch 'trunk' into ticket/20564
adamsilverstein Aug 28, 2023
d04574a
Test cleanup, add page and cpt tests
adamsilverstein Aug 29, 2023
3a8f6fc
omyac
adamsilverstein Aug 29, 2023
192e7c0
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 8, 2023
225a91b
Clean up default filters after trunk merge
adamsilverstein Sep 8, 2023
c2482fd
Avoid duplicate revisions since hook change
adamsilverstein Sep 9, 2023
2331d36
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 11, 2023
5c48c7a
Restore hooks on wp_insert_post for revision - issue was plugin related
adamsilverstein Sep 11, 2023
e211ca1
Ensure meta data is revisioned with autosaves
adamsilverstein Sep 12, 2023
7247e93
Cleanup, only check meta when passed
adamsilverstein Sep 12, 2023
891a0e4
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 19, 2023
c35cc5f
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 22, 2023
826bebb
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 25, 2023
750dcdc
Hook wp_autosave_post_revisioned_meta_fields on wp_creating_autosave
adamsilverstein Sep 25, 2023
0b88e50
Improve error checking
adamsilverstein Sep 25, 2023
2c02dbc
Update src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-con…
adamsilverstein Sep 25, 2023
b5ba2d3
Update src/wp-includes/revision.php
adamsilverstein Sep 25, 2023
95508d9
Hook revision restore on action
adamsilverstein Sep 25, 2023
b0b880b
Test revisioning of meta with a default value
adamsilverstein Sep 25, 2023
f04a319
phpcs
adamsilverstein Sep 25, 2023
e231886
phpcbf
adamsilverstein Sep 25, 2023
e1950b0
Autosaves controller - get_metadata_raw is used to avoid retrieving t…
adamsilverstein Sep 25, 2023
6f6e3ce
Update src/wp-includes/revision.php
adamsilverstein Sep 25, 2023
b77bf22
Check post_type available in wp_save_revisioned_meta_fields
adamsilverstein Sep 25, 2023
b6e221a
Ensure post_type available in wp_restore_post_revision_meta
adamsilverstein Sep 25, 2023
40febf2
typo - missing closing parenthesis
adamsilverstein Sep 25, 2023
69c6107
phpcbf
adamsilverstein Sep 25, 2023
f9b760b
If the object_type is set, require a type that supports revisions wh…
adamsilverstein Sep 25, 2023
8360faa
Test enabling revisions for meta when object type doesn’t support rev…
adamsilverstein Sep 25, 2023
a1971f7
phpcbf
adamsilverstein Sep 25, 2023
99b2aa8
Update src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-con…
adamsilverstein Sep 26, 2023
b94e1a6
Update src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-con…
adamsilverstein Sep 26, 2023
e09f011
Update src/wp-admin/includes/post.php
adamsilverstein Sep 26, 2023
01a4788
Update src/wp-admin/includes/post.php
adamsilverstein Sep 26, 2023
8e7b72a
spacing
adamsilverstein Sep 26, 2023
d6c6ca2
Guard against _wp_put_post_revision returning 0
adamsilverstein Sep 26, 2023
73f6a23
Update src/wp-includes/meta.php
adamsilverstein Sep 26, 2023
3db644c
tabs vs spaces
adamsilverstein Sep 26, 2023
95c9e9d
Update src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-con…
adamsilverstein Sep 26, 2023
9bab9a9
Improve error message
adamsilverstein Sep 26, 2023
dc0b907
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 26, 2023
ab2d500
Merge branch 'trunk' into ticket/20564
adamsilverstein Sep 26, 2023
8bf1727
phpcbf
adamsilverstein Sep 26, 2023
c7904ce
phpcbf pt 2
adamsilverstein Sep 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions src/wp-admin/includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -1970,19 +1970,84 @@ function wp_create_post_autosave( $post_data ) {
* Fires before an autosave is stored.
*
* @since 4.1.0
* @since 6.4.0 The `$is_update` parameter was added to indicate if the autosave is being updated or was newly created.
*
* @param array $new_autosave Post array - the autosave that is about to be saved.
* @param bool $is_update Whether this is an existing autosave.
*/
do_action( 'wp_creating_autosave', $new_autosave );

do_action( 'wp_creating_autosave', $new_autosave, true );
return wp_update_post( $new_autosave );
}

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

// Otherwise create the new autosave as a special post revision.
return _wp_put_post_revision( $post_data, true );
$revision = _wp_put_post_revision( $post_data, true );
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved

if ( ! is_wp_error( $revision ) && 0 !== $revision ) {

/** This action is documented in wp-admin/includes/post.php */
do_action( 'wp_creating_autosave', get_post( $revision, ARRAY_A ), false );
}

return $revision;
}

/**
* Autosave the revisioned meta fields.
*
* Iterates through the revisioned meta fields and checks each to see if they are set,
* and have a changed value. If so, the meta value is saved and attached to the autosave.
*
* @since 6.4.0
*
* @param array $new_autosave The new post data being autosaved.
*/
function wp_autosave_post_revisioned_meta_fields( $new_autosave ) {
spacedmonkey marked this conversation as resolved.
Show resolved Hide resolved
/*
* The post data arrives as either $_POST['data']['wp_autosave'] or the $_POST
* itself. This sets $posted_data to the correct variable.
*
* Ignoring sanitization to avoid altering meta. Ignoring the nonce check because
* this is hooked on inner core hooks where a valid nonce was already checked.
*
* @phpcs:disable WordPress.Security
*/
$posted_data = isset( $_POST['data']['wp_autosave'] ) ? $_POST['data']['wp_autosave'] : $_POST;
// phpcs:enable

$post_type = get_post_type( $new_autosave['post_parent'] );

/*
* Go thru the revisioned meta keys and save them as part of the autosave, if
* the meta key is part of the posted data, the meta value is not blank and
* the the meta value has changes from the last autosaved value.
*/
foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {

if (
isset( $posted_data[ $meta_key ] ) &&
get_post_meta( $new_autosave['ID'], $meta_key, true ) !== wp_unslash( $posted_data[ $meta_key ] )
) {
/*
* Use the underlying delete_metadata() and add_metadata() functions
* vs delete_post_meta() and add_post_meta() to make sure we're working
* with the actual revision meta.
*/
delete_metadata( 'post', $new_autosave['ID'], $meta_key );

/*
* One last check to ensure meta value not empty().
*/
if ( ! empty( $posted_data[ $meta_key ] ) ) {
/*
* Add the revisions meta data to the autosave.
*/
add_metadata( 'post', $new_autosave['ID'], $meta_key, $posted_data[ $meta_key ] );
}
}
}
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@
add_action( 'plugins_loaded', 'wp_maybe_load_embeds', 0 );
add_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
// Create a revision whenever a post is updated.
add_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 );
add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
add_action( 'publish_post', '_publish_post_hook', 5, 1 );
add_action( 'transition_post_status', '_transition_post_status', 5, 3 );
Expand Down Expand Up @@ -719,6 +720,18 @@
// CPT wp_block custom postmeta field.
add_action( 'init', 'wp_create_initial_post_meta' );

// Include revisioned meta when considering whether a post revision has changed.
add_filter( 'wp_save_post_revision_post_has_changed', 'wp_check_revisioned_meta_fields_have_changed', 10, 3 );

// Save revisioned post meta immediately after a revision is saved
add_action( '_wp_put_post_revision', 'wp_save_revisioned_meta_fields', 10, 2 );

// Include revisioned meta when creating or updating an autosave revision.
add_action( 'wp_creating_autosave', 'wp_autosave_post_revisioned_meta_fields' );

// When restoring revisions, also restore revisioned meta.
add_action( 'wp_restore_post_revision', 'wp_restore_post_revision_meta', 10, 2 );

// Font management.
add_action( 'wp_head', 'wp_print_font_faces', 50 );

Expand Down
15 changes: 15 additions & 0 deletions src/wp-includes/meta.php
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,7 @@ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype =
* @since 4.9.8 The `$object_subtype` argument was added to the arguments array.
* @since 5.3.0 Valid meta types expanded to include "array" and "object".
* @since 5.5.0 The `$default` argument was added to the arguments array.
* @since 6.4.0 The `$revisions_enabled` argument was added to the arguments array.
*
* @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
* or any other object type with an associated meta table.
Expand All @@ -1392,6 +1393,8 @@ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype =
* support for custom fields for registered meta to be accessible via REST.
* When registering complex meta values this argument may optionally be an
* array with 'schema' or 'prepare_callback' keys instead of a boolean.
* @type bool $revisions_enabled Whether to enable revisions support for this meta_key. Can only be used when the
* object type is 'post'.
* }
* @param string|array $deprecated Deprecated. Use `$args` instead.
* @return bool True if the meta key was successfully registered in the global array, false if not.
Expand All @@ -1414,6 +1417,7 @@ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) {
'sanitize_callback' => null,
'auth_callback' => null,
'show_in_rest' => false,
'revisions_enabled' => false,
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
);

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

$object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : '';
if ( $args['revisions_enabled'] ) {
if ( 'post' !== $object_type ) {
_doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object type supports revisions.' ), '6.4.0' );

return false;
} elseif ( ! empty( $object_subtype ) && ! post_type_supports( $object_subtype, 'revisions' ) ) {
_doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object subtype supports revisions.' ), '6.4.0' );

return false;
}
}

// If `auth_callback` is not provided, fall back to `is_protected_meta()`.
if ( empty( $args['auth_callback'] ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ public function create_item( $request ) {
*/
$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
} else {
// Non-draft posts: create or update the post autosave.
$autosave_id = $this->create_post_autosave( (array) $prepared_post );
// Non-draft posts: create or update the post autosave. Pass the meta data.
$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
}

if ( is_wp_error( $autosave_id ) ) {
Expand Down Expand Up @@ -348,11 +348,13 @@ public function get_item_schema() {
* From wp-admin/post.php.
*
* @since 5.0.0
* @since 6.4.0 The `$meta` parameter was added.
*
* @param array $post_data Associative array containing the post data.
* @param array $meta Associative array containing the post meta data.
* @return mixed The autosave revision ID or WP_Error.
*/
public function create_post_autosave( $post_data ) {
public function create_post_autosave( $post_data, array $meta = array() ) {

$post_id = (int) $post_data['ID'];
$post = get_post( $post_id );
Expand All @@ -372,6 +374,21 @@ public function create_post_autosave( $post_data ) {
}
}

// Check if meta values have changed.
if ( ! empty( $meta ) ) {
$revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
foreach ( $revisioned_meta_keys as $meta_key ) {
// get_metadata_raw is used to avoid retrieving the default value.
$old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
$new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';

if ( $new_meta !== $old_meta ) {
$autosave_is_different = true;
break;
}
}
}

$user_id = get_current_user_id();

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

// wp_update_post() expects escaped array.
return wp_update_post( wp_slash( $new_autosave ) );
$revision_id = wp_update_post( wp_slash( $new_autosave ) );
} else {
// Create the new autosave as a special post revision.
$revision_id = _wp_put_post_revision( $post_data, true );
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
}

if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
return $revision_id;
}

// Attached any passed meta values that have revisions enabled.
if ( ! empty( $meta ) && $revision_id ) {
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
foreach ( $revisioned_meta_keys as $meta_key ) {
if ( isset( $meta[ $meta_key ] ) ) {
update_metadata( 'post', $revision_id, $meta_key, $meta[ $meta_key ] );
}
}
}

// Create the new autosave as a special post revision.
return _wp_put_post_revision( $post_data, true );
return $revision_id;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ class WP_REST_Revisions_Controller extends WP_REST_Controller {
*/
private $parent_post_type;

/**
* Instance of a revision meta fields object.
*
* @since 6.4.0
* @var WP_REST_Post_Meta_Fields
*/
protected $meta;

/**
* Parent controller.
*
Expand Down Expand Up @@ -60,6 +68,7 @@ public function __construct( $parent_post_type ) {
$this->rest_base = 'revisions';
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
$this->meta = new WP_REST_Post_Meta_Fields( $parent_post_type );
}

/**
Expand Down Expand Up @@ -619,6 +628,10 @@ public function prepare_item_for_response( $item, $request ) {
);
}

if ( rest_is_field_included( 'meta', $fields ) ) {
$data['meta'] = $this->meta->get_value( $post->ID, $request );
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
Expand Down Expand Up @@ -752,6 +765,8 @@ public function get_item_schema() {
$schema['properties']['guid'] = $parent_schema['properties']['guid'];
}

$schema['properties']['meta'] = $this->meta->get_field_schema();

$this->schema = $schema;

return $this->add_additional_fields_schema( $this->schema );
Expand Down
Loading
Loading