<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[56714] trunk: Revisions: framework for storing post meta revisions.</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/56714">56714</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/56714","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>adamsilverstein</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2023-09-26 15:30:34 +0000 (Tue, 26 Sep 2023)</dd>
</dl>
<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>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 <a href="https://core.trac.wordpress.org/ticket/20299">#20299</a>) 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 <a href="https://core.trac.wordpress.org/ticket/20564">#20564</a>, <a href="https://core.trac.wordpress.org/ticket/20299">#20299</a>.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpadminincludespostphp">trunk/src/wp-admin/includes/post.php</a></li>
<li><a href="#trunksrcwpincludesdefaultfiltersphp">trunk/src/wp-includes/default-filters.php</a></li>
<li><a href="#trunksrcwpincludesmetaphp">trunk/src/wp-includes/meta.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestautosavescontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php</a></li>
<li><a href="#trunksrcwpincludesrestapiendpointsclasswprestrevisionscontrollerphp">trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php</a></li>
<li><a href="#trunksrcwpincludesrevisionphp">trunk/src/wp-includes/revision.php</a></li>
<li><a href="#trunktestsphpunittestsmetaregisterMetaphp">trunk/tests/phpunit/tests/meta/registerMeta.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestautosavescontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-autosaves-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestglobalstylesrevisionscontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestpostmetafieldsphp">trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php</a></li>
<li><a href="#trunktestsphpunittestsrestapirestrevisionscontrollerphp">trunk/tests/phpunit/tests/rest-api/rest-revisions-controller.php</a></li>
<li><a href="#trunktestsphpunittestsuserwpRegisterPersistedPreferencesMetaphp">trunk/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php</a></li>
<li><a href="#trunktestsqunitfixtureswpapigeneratedjs">trunk/tests/qunit/fixtures/wp-api-generated.js</a></li>
</ul>
<h3>Added Paths</h3>
<ul>
<li><a href="#trunktestsphpunittestspostmetaRevisionsphp">trunk/tests/phpunit/tests/post/metaRevisions.php</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpadminincludespostphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-admin/includes/post.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-admin/includes/post.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/src/wp-admin/includes/post.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1970,11 +1970,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires before an autosave is stored.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.1.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.4.0 The `$is_update` parameter was added to indicate if the autosave is being updated or was newly created.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param array $new_autosave Post array - the autosave that is about to be saved.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param bool $is_update Whether this is an existing autosave.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- do_action( 'wp_creating_autosave', $new_autosave );
-
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ do_action( 'wp_creating_autosave', $new_autosave, true );
</ins><span class="cx" style="display: block; padding: 0 10px"> return wp_update_post( $new_autosave );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1982,10 +1983,74 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $post_data = wp_unslash( $post_data );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Otherwise create the new autosave as a special post revision.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- return _wp_put_post_revision( $post_data, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $revision = _wp_put_post_revision( $post_data, true );
+
+ 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;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * 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 ) {
+ /*
+ * 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 ] );
+ }
+ }
+ }
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Saves a draft or manually autosaves for the purpose of showing a post preview.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 2.7.0
</span></span></pre></div>
<a id="trunksrcwpincludesdefaultfiltersphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/default-filters.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/default-filters.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/src/wp-includes/default-filters.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -411,6 +411,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'plugins_loaded', 'wp_maybe_load_embeds', 0 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'shutdown', 'wp_ob_end_flush_all', 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> // Create a revision whenever a post is updated.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+add_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 );
</ins><span class="cx" style="display: block; padding: 0 10px"> add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'publish_post', '_publish_post_hook', 5, 1 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'transition_post_status', '_transition_post_status', 5, 3 );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -719,6 +720,18 @@
</span><span class="cx" style="display: block; padding: 0 10px"> // CPT wp_block custom postmeta field.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'init', 'wp_create_initial_post_meta' );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+// 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 );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // Font management.
</span><span class="cx" style="display: block; padding: 0 10px"> add_action( 'wp_head', 'wp_print_font_faces', 50 );
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre></div>
<a id="trunksrcwpincludesmetaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/meta.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/meta.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/src/wp-includes/meta.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1367,6 +1367,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.9.8 The `$object_subtype` argument was added to the arguments array.
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.3.0 Valid meta types expanded to include "array" and "object".
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.5.0 The `$default` argument was added to the arguments array.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.4.0 The `$revisions_enabled` argument was added to the arguments array.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user',
</span><span class="cx" style="display: block; padding: 0 10px"> * or any other object type with an associated meta table.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1392,6 +1393,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * support for custom fields for registered meta to be accessible via REST.
</span><span class="cx" style="display: block; padding: 0 10px"> * When registering complex meta values this argument may optionally be an
</span><span class="cx" style="display: block; padding: 0 10px"> * array with 'schema' or 'prepare_callback' keys instead of a boolean.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @type bool $revisions_enabled Whether to enable revisions support for this meta_key. Can only be used when the
+ * object type is 'post'.
</ins><span class="cx" style="display: block; padding: 0 10px"> * }
</span><span class="cx" style="display: block; padding: 0 10px"> * @param string|array $deprecated Deprecated. Use `$args` instead.
</span><span class="cx" style="display: block; padding: 0 10px"> * @return bool True if the meta key was successfully registered in the global array, false if not.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1414,6 +1417,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => null,
</span><span class="cx" style="display: block; padding: 0 10px"> 'auth_callback' => null,
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // There used to be individual args for sanitize and auth callbacks.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1460,7 +1464,18 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : '';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 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' );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 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;
+ }
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> // If `auth_callback` is not provided, fall back to `is_protected_meta()`.
</span><span class="cx" style="display: block; padding: 0 10px"> if ( empty( $args['auth_callback'] ) ) {
</span><span class="cx" style="display: block; padding: 0 10px"> if ( is_protected_meta( $meta_key, $object_type ) ) {
</span></span></pre></div>
<a id="trunksrcwpincludesrestapiendpointsclasswprestautosavescontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -234,8 +234,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
</span><span class="cx" style="display: block; padding: 0 10px"> } else {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // Non-draft posts: create or update the post autosave.
- $autosave_id = $this->create_post_autosave( (array) $prepared_post );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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' ) );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( is_wp_error( $autosave_id ) ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -348,11 +348,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * From wp-admin/post.php.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 5.0.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.4.0 The `$meta` parameter was added.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param array $post_data Associative array containing the post data.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param array $meta Associative array containing the post meta data.
</ins><span class="cx" style="display: block; padding: 0 10px"> * @return mixed The autosave revision ID or WP_Error.
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- public function create_post_autosave( $post_data ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ public function create_post_autosave( $post_data, array $meta = array() ) {
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $post_id = (int) $post_data['ID'];
</span><span class="cx" style="display: block; padding: 0 10px"> $post = get_post( $post_id );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -372,6 +374,21 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // 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;
+ }
+ }
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $user_id = get_current_user_id();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // Store one autosave per author. If there is already an autosave, overwrite it.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -390,11 +407,26 @@
</span><span class="cx" style="display: block; padding: 0 10px"> do_action( 'wp_creating_autosave', $new_autosave );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> // wp_update_post() expects escaped array.
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- return wp_update_post( wp_slash( $new_autosave ) );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $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 );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- // Create the new autosave as a special post revision.
- return _wp_put_post_revision( $post_data, true );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
+ return $revision_id;
+ }
+
+ // Attached any passed meta values that have revisions enabled.
+ if ( ! empty( $meta ) ) {
+ foreach ( $revisioned_meta_keys as $meta_key ) {
+ if ( isset( $meta[ $meta_key ] ) ) {
+ update_metadata( 'post', $revision_id, $meta_key, $meta[ $meta_key ] );
+ }
+ }
+ }
+
+ return $revision_id;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span></span></pre></div>
<a id="trunksrcwpincludesrestapiendpointsclasswprestrevisionscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -25,6 +25,14 @@
</span><span class="cx" style="display: block; padding: 0 10px"> private $parent_post_type;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Instance of a revision meta fields object.
+ *
+ * @since 6.4.0
+ * @var WP_REST_Post_Meta_Fields
+ */
+ protected $meta;
+
+ /**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Parent controller.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 4.7.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -60,6 +68,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->rest_base = 'revisions';
</span><span class="cx" style="display: block; padding: 0 10px"> $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
</span><span class="cx" style="display: block; padding: 0 10px"> $this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->meta = new WP_REST_Post_Meta_Fields( $parent_post_type );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -619,6 +628,10 @@
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ if ( rest_is_field_included( 'meta', $fields ) ) {
+ $data['meta'] = $this->meta->get_value( $post->ID, $request );
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
</span><span class="cx" style="display: block; padding: 0 10px"> $data = $this->add_additional_fields_to_object( $data, $request );
</span><span class="cx" style="display: block; padding: 0 10px"> $data = $this->filter_response_by_context( $data, $context );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -752,6 +765,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $schema['properties']['guid'] = $parent_schema['properties']['guid'];
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $schema['properties']['meta'] = $this->meta->get_field_schema();
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->schema = $schema;
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return $this->add_additional_fields_schema( $this->schema );
</span></span></pre></div>
<a id="trunksrcwpincludesrevisionphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/revision.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/revision.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/src/wp-includes/revision.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -96,6 +96,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Saves revisions for a post after all changes have been made.
+ *
+ * @since 6.4.0
+ *
+ * @param int $post_id The post id that was inserted.
+ * @param WP_Post $post The post object that was inserted.
+ * @param bool $update Whether this insert is updating an existing post.
+ */
+function wp_save_post_revision_on_insert( $post_id, $post, $update ) {
+ if ( ! $update ) {
+ return;
+ }
+
+ if ( ! has_action( 'post_updated', 'wp_save_post_revision' ) ) {
+ return;
+ }
+
+ wp_save_post_revision( $post_id );
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Creates a revision for the current version of a post.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Typically used immediately after a post update, as every update is a revision,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -111,6 +132,11 @@
</span><span class="cx" style="display: block; padding: 0 10px"> return;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Prevent saving post revisions if revisions should be saved on wp_after_insert_post.
+ if ( doing_action( 'post_updated' ) && has_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert' ) ) {
+ return;
+ }
+
</ins><span class="cx" style="display: block; padding: 0 10px"> $post = get_post( $post_id );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> if ( ! $post ) {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -361,16 +387,40 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires once a revision has been saved.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 2.6.0
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @since 6.4.0 The post_id parameter was added.
</ins><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @param int $revision_id Post revision ID.
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * @param int $post_id Post ID.
</ins><span class="cx" style="display: block; padding: 0 10px"> */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- do_action( '_wp_put_post_revision', $revision_id );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ do_action( '_wp_put_post_revision', $revision_id, $post['post_parent'] );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return $revision_id;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Save the revisioned meta fields.
+ *
+ * @since 6.4.0
+ *
+ * @param int $revision_id The ID of the revision to save the meta to.
+ * @param int $post_id The ID of the post the revision is associated with.
+ */
+function wp_save_revisioned_meta_fields( $revision_id, $post_id ) {
+ $post_type = get_post_type( $post_id );
+ if ( ! $post_type ) {
+ return;
+ }
+
+ foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
+ if ( metadata_exists( 'post', $post_id, $meta_key ) ) {
+ _wp_copy_post_meta( $post_id, $revision_id, $meta_key );
+ }
+ }
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Gets a post revision.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * @since 2.6.0
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -450,6 +500,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> // Update last edit user.
</span><span class="cx" style="display: block; padding: 0 10px"> update_post_meta( $post_id, '_edit_last', get_current_user_id() );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ // Restore any revisioned meta fields.
+ wp_restore_post_revision_meta( $post_id, $revision['ID'] );
+
</ins><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px"> * Fires after a post revision has been restored.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -464,6 +517,105 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Restore the revisioned meta values for a post.
+ *
+ * @param int $post_id The ID of the post to restore the meta to.
+ * @param int $revision_id The ID of the revision to restore the meta from.
+ *
+ * @since 6.4.0
+ */
+function wp_restore_post_revision_meta( $post_id, $revision_id ) {
+ $post_type = get_post_type( $post_id );
+ if ( ! $post_type ) {
+ return;
+ }
+
+ // Restore revisioned meta fields.
+ foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
+
+ // Clear any existing meta.
+ delete_post_meta( $post_id, $meta_key );
+
+ _wp_copy_post_meta( $revision_id, $post_id, $meta_key );
+ }
+}
+
+/**
+ * Copy post meta for the given key from one post to another.
+ *
+ * @param int $source_post_id Post ID to copy meta value(s) from.
+ * @param int $target_post_id Post ID to copy meta value(s) to.
+ * @param string $meta_key Meta key to copy.
+ *
+ * @since 6.4.0
+ */
+function _wp_copy_post_meta( $source_post_id, $target_post_id, $meta_key ) {
+
+ foreach ( get_post_meta( $source_post_id, $meta_key ) as $meta_value ) {
+ /**
+ * We use add_metadata() function vs add_post_meta() here
+ * to allow for a revision post target OR regular post.
+ */
+ add_metadata( 'post', $target_post_id, $meta_key, wp_slash( $meta_value ) );
+ }
+}
+
+/**
+ * Determine which post meta fields should be revisioned.
+ *
+ * @since 6.4.0
+ *
+ * @param string $post_type The post type being revisioned.
+ *
+ * @return array An array of meta keys to be revisioned.
+ */
+function wp_post_revision_meta_keys( $post_type ) {
+ $registered_meta = array_merge(
+ get_registered_meta_keys( 'post' ),
+ get_registered_meta_keys( 'post', $post_type )
+ );
+
+ $wp_revisioned_meta_keys = array();
+
+ foreach ( $registered_meta as $name => $args ) {
+ if ( $args['revisions_enabled'] ) {
+ $wp_revisioned_meta_keys[ $name ] = true;
+ }
+ }
+
+ $wp_revisioned_meta_keys = array_keys( $wp_revisioned_meta_keys );
+
+ /**
+ * Filter the list of post meta keys to be revisioned.
+ *
+ * @since 6.4.0
+ *
+ * @param array $keys An array of meta fields to be revisioned.
+ * @param string $post_type The post type being revisioned.
+ */
+ return apply_filters( 'wp_post_revision_meta_keys', $wp_revisioned_meta_keys, $post_type );
+}
+
+/**
+ * Check whether revisioned post meta fields have changed.
+ *
+ * @param bool $post_has_changed Whether the post has changed.
+ * @param WP_Post $last_revision The last revision post object.
+ * @param WP_Post $post The post object.
+ *
+ * @since 6.4.0
+ */
+function wp_check_revisioned_meta_fields_have_changed( $post_has_changed, WP_Post $last_revision, WP_Post $post ) {
+ foreach ( wp_post_revision_meta_keys( $post->post_type ) as $meta_key ) {
+ if ( get_post_meta( $post->ID, $meta_key ) !== get_post_meta( $last_revision->ID, $meta_key ) ) {
+ $post_has_changed = true;
+ break;
+ }
+ }
+ return $post_has_changed;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px"> * Deletes a revision.
</span><span class="cx" style="display: block; padding: 0 10px"> *
</span><span class="cx" style="display: block; padding: 0 10px"> * Deletes the row from the posts table corresponding to the specified revision.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -728,6 +880,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 );
</span><span class="cx" style="display: block; padding: 0 10px"> add_filter( 'get_post_metadata', '_wp_preview_post_thumbnail_filter', 10, 3 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ add_filter( 'get_post_metadata', '_wp_preview_meta_filter', 10, 4 );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return $post;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -946,3 +1099,38 @@
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> return true;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+/**
+ * Filters preview post meta retrieval to get values from the autosave.
+ *
+ * Filters revisioned meta keys only.
+ *
+ * @since 6.4.0
+ *
+ * @param mixed $value Meta value to filter.
+ * @param int $object_id Object ID.
+ * @param string $meta_key Meta key to filter a value for.
+ * @param bool $single Whether to return a single value. Default false.
+ * @return mixed Original meta value if the meta key isn't revisioned, the object doesn't exist,
+ * the post type is a revision or the post ID doesn't match the object ID.
+ * Otherwise, the revisioned meta value is returned for the preview.
+ */
+function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) {
+
+ $post = get_post();
+ if (
+ empty( $post ) ||
+ $post->ID !== $object_id ||
+ ! in_array( $meta_key, wp_post_revision_meta_keys( $post->post_type ), true ) ||
+ 'revision' === $post->post_type
+ ) {
+ return $value;
+ }
+
+ $preview = wp_get_post_autosave( $post->ID );
+ if ( false === $preview ) {
+ return $value;
+ }
+
+ return get_post_meta( $preview->ID, $meta_key, $single );
+}
</ins></span></pre></div>
<a id="trunktestsphpunittestsmetaregisterMetaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/meta/registerMeta.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/meta/registerMeta.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/phpunit/tests/meta/registerMeta.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -97,6 +97,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => null,
</span><span class="cx" style="display: block; padding: 0 10px"> 'auth_callback' => '__return_true',
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -121,6 +122,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => null,
</span><span class="cx" style="display: block; padding: 0 10px"> 'auth_callback' => '__return_true',
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -175,6 +177,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => array( $this, '_new_sanitize_meta_cb' ),
</span><span class="cx" style="display: block; padding: 0 10px"> 'auth_callback' => '__return_true',
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -342,6 +345,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => null,
</span><span class="cx" style="display: block; padding: 0 10px"> 'auth_callback' => '__return_true',
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -395,6 +399,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'sanitize_callback' => null,
</span><span class="cx" style="display: block; padding: 0 10px"> 'auth_callback' => '__return_true',
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => false,
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1081,4 +1086,35 @@
</span><span class="cx" style="display: block; padding: 0 10px"> array( 'user', 'user' ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ /**
+ * Test that attempting to register meta with revisions_enabled set to true on a
+ * post type that does not have revisions enabled fails and throws a `doing_it_wrong` notice.
+ *
+ * @ticket 20564
+ */
+ public function test_register_meta_with_revisions_enabled_on_post_type_without_revisions() {
+ $this->setExpectedIncorrectUsage( 'register_meta' );
+
+ // Set up a custom post type with revisions disabled.
+ register_post_type(
+ 'test_post_type',
+ array(
+ 'supports' => array( 'title', 'editor' ),
+ )
+ );
+
+ $meta_key = 'registered_key1';
+ $args = array(
+ 'revisions_enabled' => true,
+ );
+
+ $register = register_meta(
+ 'test_post_type',
+ $meta_key,
+ $args
+ );
+
+ $this->assertFalse( $register );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestspostmetaRevisionsphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: trunk/tests/phpunit/tests/post/metaRevisions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/post/metaRevisions.php (rev 0)
+++ trunk/tests/phpunit/tests/post/metaRevisions.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,722 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/**
+ *
+ * Tests for post meta revisioning.
+ *
+ * @group post
+ * @group revision
+ * @group meta
+ * @group meta-revisions
+ */
+class MetaRevisionTests extends WP_UnitTestCase {
+
+ /**
+ * Callback function to add the revisioned keys.
+ *
+ * @param array $keys The array of revisioned keys.
+ *
+ * @return array
+ */
+ public function add_revisioned_keys( $keys ) {
+ $keys[] = 'meta_revision_test';
+ $keys[] = 'meta_multiples_test';
+ return $keys;
+ }
+
+ /**
+ * Test the revisions system for storage of meta values with slashes.
+ *
+ * @param string $passed The passed data for testing.
+ *
+ * @param string $expected The expected value after storing & retrieving.
+ *
+ * @group revision
+ * @group slashed
+ * @dataProvider slashed_data_provider
+ */
+ public function test_revisions_stores_meta_values_with_slashes( $passed, $expected ) {
+ // Set up a new post.
+ $post_id = $this->factory->post->create();
+
+ // And update to store an initial revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some initial content',
+ 'ID' => $post_id,
+ )
+ );
+ add_filter( 'wp_post_revision_meta_keys', array( $this, 'add_revisioned_keys' ) );
+
+ // Store a custom meta value, which is not revisioned by default.
+ update_post_meta( $post_id, 'meta_revision_test', wp_slash( $passed ) );
+ $this->assertEquals( $expected, get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ // Update the post, storing a revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some more content',
+ 'ID' => $post_id,
+ )
+ );
+
+ // Overwrite.
+ update_post_meta( $post_id, 'meta_revision_test', 'original' );
+ // Update the post, storing a revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some more content again',
+ 'ID' => $post_id,
+ )
+ );
+
+ // Restore the previous revision.
+ $revisions = (array) wp_get_post_revisions( $post_id );
+
+ // Go back to load the previous revision.
+ array_shift( $revisions );
+ $last_revision = array_shift( $revisions );
+
+ // Restore!
+ wp_restore_post_revision( $last_revision->ID );
+
+ $this->assertEquals( $expected, get_post_meta( $post_id, 'meta_revision_test', true ) );
+ }
+
+ /**
+ * Provide data for the slashed data tests.
+ */
+ public function slashed_data_provider() {
+ return array(
+ array(
+ 'some\text',
+ 'some\text',
+ ),
+ array(
+ 'test some\ \\extra \\\slashed \\\\text ',
+ 'test some\ \\extra \\\slashed \\\\text ',
+ ),
+ array(
+ "This \'is\' an example \n of a \"quoted\" string",
+ "This \'is\' an example \n of a \"quoted\" string",
+ ),
+ array(
+ 'some unslashed text just to test! % & * ( ) #',
+ 'some unslashed text just to test! % & * ( ) #',
+ ),
+ );
+ }
+
+ /**
+ * Test the revisions system for storage of meta values.
+ *
+ * @group revision
+ */
+ public function test_revisions_stores_meta_values() {
+ /*
+ * Set Up.
+ */
+
+ // Set up a new post.
+ $post_id = $this->factory->post->create();
+ $original_post_id = $post_id;
+
+ // And update to store an initial revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some initial content',
+ 'ID' => $post_id,
+ )
+ );
+
+ // One revision so far.
+ $revisions = wp_get_post_revisions( $post_id );
+ $this->assertCount( 1, $revisions );
+
+ /*
+ * First set up a meta value.
+ */
+
+ // Store a custom meta value, which is not revisioned by default.
+ update_post_meta( $post_id, 'meta_revision_test', 'original' );
+
+ // Update the post, storing a revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some more content',
+ 'ID' => $post_id,
+ )
+ );
+
+ $revisions = wp_get_post_revisions( $post_id );
+ $this->assertCount( 2, $revisions );
+
+ // Next, store some updated meta values for the same key.
+ update_post_meta( $post_id, 'meta_revision_test', 'update1' );
+
+ // Save the post, changing content to force a revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some updated content',
+ 'ID' => $post_id,
+ )
+ );
+
+ $revisions = wp_get_post_revisions( $post_id );
+ $this->assertCount( 3, $revisions );
+
+ /*
+ * Now restore the original revision.
+ */
+
+ // Restore the previous revision.
+ $revisions = (array) wp_get_post_revisions( $post_id );
+
+ // Go back two to load the previous revision.
+ array_shift( $revisions );
+ $last_revision = array_shift( $revisions );
+
+ // Restore!
+ wp_restore_post_revision( $last_revision->ID );
+
+ wp_update_post( array( 'ID' => $post_id ) );
+ $revisions = wp_get_post_revisions( $post_id );
+ $this->assertCount( 4, $revisions );
+
+ /*
+ * Check the meta values to verify they are NOT revisioned - they are not revisioned by default.
+ */
+
+ // Custom post meta should NOT be restored, orignal value should not be restored, value still 'update1'.
+ $this->assertEquals( 'update1', get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ update_post_meta( $post_id, 'meta_revision_test', 'update2' );
+
+ /*
+ * Test the revisioning of custom meta when enabled by the wp_post_revision_meta_keys filter.
+ */
+
+ // Add the custom field to be revised via the wp_post_revision_meta_keys filter.
+ add_filter( 'wp_post_revision_meta_keys', array( $this, 'add_revisioned_keys' ) );
+
+ // Save the post, changing content to force a revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'more updated content',
+ 'ID' => $post_id,
+ )
+ );
+
+ $revisions = array_values( wp_get_post_revisions( $post_id ) );
+ $this->assertCount( 5, $revisions );
+ $this->assertEquals( 'update2', get_post_meta( $revisions[0]->ID, 'meta_revision_test', true ) );
+
+ // Store custom meta values, which should now be revisioned.
+ update_post_meta( $post_id, 'meta_revision_test', 'update3' );
+
+ /*
+ * Save the post again, custom meta should now be revisioned.
+ *
+ * Note that a revision is saved even though there is no change
+ * in post content, because the revisioned post_meta has changed.
+ */
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ )
+ );
+
+ // This revision contains the existing post meta ('update3').
+ $revisions = wp_get_post_revisions( $post_id );
+ $this->assertCount( 6, $revisions );
+
+ // Verify that previous post meta is set.
+ $this->assertEquals( 'update3', get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+
+ // Go back two to load the previous revision.
+ array_shift( $revisions );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+
+ /*
+ * Verify that previous post meta is restored.
+ */
+ $this->assertEquals( 'update2', get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ // Try storing a blank meta.
+ update_post_meta( $post_id, 'meta_revision_test', '' );
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ )
+ );
+
+ update_post_meta( $post_id, 'meta_revision_test', 'update 4' );
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ )
+ );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+ array_shift( $revisions );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+
+ /*
+ * Verify that previous blank post meta is restored.
+ */
+ $this->assertEquals( '', get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ /*
+ * Test not tracking a key - remove the key from the revisioned meta.
+ */
+ remove_all_filters( 'wp_post_revision_meta_keys' );
+
+ // Meta should no longer be revisioned.
+ update_post_meta( $post_id, 'meta_revision_test', 'update 5' );
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ 'post_content' => 'changed content',
+ )
+ );
+ update_post_meta( $post_id, 'meta_revision_test', 'update 6' );
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ 'post_content' => 'go updated content',
+ )
+ );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+ array_shift( $revisions );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+
+ /*
+ * Verify that previous post meta is NOT restored.
+ */
+ $this->assertEquals( 'update 6', get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ // Add the custom field to be revised via the wp_post_revision_meta_keys filter.
+ add_filter( 'wp_post_revision_meta_keys', array( $this, 'add_revisioned_keys' ) );
+
+ /*
+ * Test the revisioning of multiple meta keys.
+ */
+
+ // Add three values for meta.
+ update_post_meta( $post_id, 'meta_revision_test', 'update 7' );
+ add_post_meta( $post_id, 'meta_revision_test', 'update 7 number 2' );
+ add_post_meta( $post_id, 'meta_revision_test', 'update 7 number 3' );
+ wp_update_post( array( 'ID' => $post_id ) );
+
+ // Update all three values.
+ update_post_meta( $post_id, 'meta_revision_test', 'update 8', 'update 7' );
+ update_post_meta( $post_id, 'meta_revision_test', 'update 8 number 2', 'update 7 number 2' );
+ update_post_meta( $post_id, 'meta_revision_test', 'update 8 number 3', 'update 7 number 3' );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+
+ /*
+ * Verify that multiple metas stored correctly.
+ */
+ $this->assertEquals( array( 'update 7', 'update 7 number 2', 'update 7 number 3' ), get_post_meta( $post_id, 'meta_revision_test' ) );
+
+ /*
+ * Test the revisioning of a multidimensional array.
+ */
+ $test_array = array(
+ 'a' => array(
+ '1',
+ '2',
+ '3',
+ ),
+ 'b' => 'ok',
+ 'c' => array(
+ 'multi' => array(
+ 'a',
+ 'b',
+ 'c',
+ ),
+ 'not' => 'ok',
+ ),
+ );
+
+ // Clear any old value.
+ delete_post_meta( $post_id, 'meta_revision_test' );
+
+ // Set the test meta to the array.
+ update_post_meta( $post_id, 'meta_revision_test', $test_array );
+
+ // Update to save.
+ wp_update_post( array( 'ID' => $post_id ) );
+
+ // Set the test meta blank.
+ update_post_meta( $post_id, 'meta_revision_test', '' );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+
+ /*
+ * Verify multidimensional array stored correctly.
+ */
+ $stored_array = get_post_meta( $post_id, 'meta_revision_test' );
+ $this->assertEquals( $test_array, $stored_array[0] );
+ /*
+
+ * Test multiple revisions on the same key.
+ */
+
+ // Set the test meta to the array.
+ add_post_meta( $post_id, 'meta_multiples_test', 'test1' );
+ add_post_meta( $post_id, 'meta_multiples_test', 'test2' );
+ add_post_meta( $post_id, 'meta_multiples_test', 'test3' );
+
+ // Update to save.
+ wp_update_post( array( 'ID' => $post_id ) );
+
+ $stored_array = get_post_meta( $post_id, 'meta_multiples_test' );
+ $expect = array( 'test1', 'test2', 'test3' );
+
+ $this->assertEquals( $expect, $stored_array );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+
+ $stored_array = get_post_meta( $post_id, 'meta_multiples_test' );
+ $expect = array( 'test1', 'test2', 'test3' );
+
+ $this->assertEquals( $expect, $stored_array );
+
+ // Cleanup!
+ wp_delete_post( $original_post_id );
+ }
+
+ /**
+ * Verify that only existing meta is revisioned.
+ */
+ public function only_existing_meta_is_revisioned() {
+ add_filter( 'wp_post_revision_meta_keys', array( $this, 'add_revisioned_keys' ) );
+
+ // Set up a new post.
+ $post_id = $this->factory->post->create(
+ array(
+ 'post_content' => 'initial content',
+ )
+ );
+
+ // Revision v1.
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ 'post_content' => 'updated content v1',
+ )
+ );
+
+ $this->assertPostNotHasMetaKey( $post_id, 'foo' );
+ $this->assertPostNotHasMetaKey( $post_id, 'bar' );
+
+ $revisions = wp_get_post_revisions( $post_id );
+ $revision = array_shift( $revisions );
+ $this->assertEmpty( get_metadata( 'post', $revision->ID ) );
+
+ // Revision v2.
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ 'post_content' => 'updated content v2',
+ 'meta_input' => array(
+ 'foo' => 'foo v2',
+ ),
+ )
+ );
+
+ $this->assertPostHasMetaKey( $post_id, 'foo' );
+ $this->assertPostNotHasMetaKey( $post_id, 'bar' );
+ $this->assertPostNotHasMetaKey( $post_id, 'meta_revision_test' );
+
+ $revisions = wp_get_post_revisions( $post_id );
+ $revision = array_shift( $revisions );
+ $this->assertPostHasMetaKey( $revision->ID, 'foo' );
+ $this->assertPostNotHasMetaKey( $revision->ID, 'bar' );
+ $this->assertPostNotHasMetaKey( $revision->ID, 'meta_revision_test' );
+ }
+
+ /**
+ * Verify that blank strings are revisioned correctly.
+ */
+ public function blank_meta_is_revisioned() {
+
+ add_filter( 'wp_post_revision_meta_keys', array( $this, 'add_revisioned_keys' ) );
+
+ // Set up a new post.
+ $post_id = $this->factory->post->create(
+ array(
+ 'post_content' => 'initial content',
+ 'meta_input' => array(
+ 'foo' => 'foo',
+ ),
+ )
+ );
+
+ // Set the test meta to an empty string.
+ update_post_meta( $post_id, 'foo', '' );
+
+ // Update to save.
+ wp_update_post( array( 'ID' => $post_id ) );
+
+ $stored_array = get_post_meta( $post_id, 'meta_multiples_test' );
+ $expect = array( 'test1', 'test2', 'test3' );
+
+ $this->assertEquals( $expect, $stored_array );
+
+ // Restore the previous revision.
+ $revisions = wp_get_post_revisions( $post_id );
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+ $stored_data = get_post_meta( $post_id, 'foo' );
+ $this->assertEquals( '', $stored_data[0] );
+ }
+
+ /**
+ * Test revisioning of meta with a default value.
+ */
+ public function test_revisionining_of_meta_with_default_value() {
+
+ // Add a meta field to revision that includes a default value.
+ register_post_meta(
+ 'post',
+ 'meta_revision_test',
+ array(
+ 'single' => true,
+ 'default' => 'default value',
+ 'revisions_enabled' => true,
+ )
+ );
+
+ // Set up a new post.
+ $post_id = $this->factory->post->create(
+ array(
+ 'post_content' => 'initial content',
+ 'meta_input' => array(
+ 'meta_revision_test' => 'foo',
+ ),
+ )
+ );
+
+ // Set the test meta to an empty string.
+ update_post_meta( $post_id, 'meta_revision_test', '' );
+
+ // Update to save.
+ wp_update_post( array( 'ID' => $post_id ) );
+
+ // Check that the meta is blank.
+ $stored_data = get_post_meta( $post_id, 'meta_revision_test', true );
+ $this->assertEquals( '', $stored_data );
+
+ // Also verify that the latest revision has blank stored for the meta.
+ $revisions = wp_get_post_revisions( $post_id );
+ $last_revision = array_shift( $revisions );
+ $stored_data = get_post_meta( $last_revision->ID, 'meta_revision_test', true );
+ $this->assertEquals( '', $stored_data );
+
+ // Delete the meta.
+ delete_post_meta( $post_id, 'meta_revision_test' );
+
+ // Update to save.
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ 'post_content' => 'content update 1',
+ )
+ );
+
+ // Check that the default meta value is returned.
+ $this->assertEquals( 'default value', get_post_meta( $post_id, 'meta_revision_test', true ) );
+
+ // Also verify that the latest revision has the default value returned for the meta.
+ $revisions = wp_get_post_revisions( $post_id );
+ $last_revision = array_shift( $revisions );
+
+ // No ,eta data should be stored in the revision.
+ $this->assertEquals( array(), get_post_meta( $last_revision->ID ) );
+
+ // Set the test meta again.
+ update_post_meta( $post_id, 'meta_revision_test', 'test' );
+
+ // Update to save.
+ wp_update_post( array( 'ID' => $post_id ) );
+
+ // Now restore the previous revision.
+ wp_restore_post_revision( $last_revision->ID );
+
+ // Verify the default meta value is still returned.
+ $this->assertEquals( 'default value', get_post_meta( $post_id, 'meta_revision_test', true ) );
+ }
+
+ /**
+ * @dataProvider data_register_post_meta_supports_revisions
+ */
+ public function test_register_post_meta_supports_revisions( $post_type, $meta_key, $args, $expected_is_revisioned ) {
+ register_post_meta( $post_type, $meta_key, $args );
+
+ // Set up a new post.
+ $post_id = $this->factory->post->create(
+ array(
+ 'post_content' => 'initial content',
+ 'post_type' => $post_type,
+ 'meta_input' => array(
+ $meta_key => 'foo',
+ ),
+ )
+ );
+
+ // Update the post meta and post to save.
+ update_post_meta( $post_id, $meta_key, 'bar' );
+ wp_update_post(
+ array(
+ 'ID' => $post_id,
+ 'post_title' => 'updated title',
+ )
+ );
+
+ // Check the last revision for the post to see if the meta key was revisioned
+ $revisions = wp_get_post_revisions( $post_id );
+ $revision = array_shift( $revisions );
+ $revisioned_meta = get_post_meta( $revision->ID, $meta_key, true );
+ $this->assertEquals( $expected_is_revisioned, 'bar' === $revisioned_meta );
+
+ // Reset global so subsequent data tests do not get polluted.
+ $GLOBALS['wp_meta_keys'] = array();
+ }
+
+ public function data_register_post_meta_supports_revisions() {
+ return array(
+ array( 'post', 'registered_key1', array( 'single' => true ), false ),
+ array(
+ 'post',
+ 'registered_key1',
+ array(
+ 'single' => true,
+ 'revisions_enabled' => true,
+ ),
+ true,
+ ),
+ array( 'page', 'registered_key2', array( 'revisions_enabled' => false ), false ),
+ array( 'page', 'registered_key2', array( 'revisions_enabled' => true ), true ),
+ array( '', 'registered_key3', array( 'revisions_enabled' => false ), false ),
+ array( '', 'registered_key3', array( 'revisions_enabled' => true ), true ),
+ );
+ }
+
+ /**
+ * Assert the a post has a meta key.
+ *
+ * @param int $post_id The ID of the post to check.
+ * @param string $meta_key The meta key to check for.
+ */
+ protected function assertPostHasMetaKey( $post_id, $meta_key ) {
+ $this->assertArrayHasKey( $meta_key, get_metadata( 'post', $post_id ) );
+ }
+
+ /**
+ * Assert that post does not have a meta key.
+ *
+ * @param int $post_id The ID of the post to check.
+ * @param string $meta_key The meta key to check for.
+ */
+ protected function assertPostNotHasMetaKey( $post_id, $meta_key ) {
+ $this->assertArrayNotHasKey( $meta_key, get_metadata( 'post', $post_id ) );
+ }
+
+ /**
+ * Test post meta revisioning with a custom post type, as well as the "page" post type.
+ *
+ * @dataProvider page_post_type_data_provider
+ */
+ public function test_revisions_stores_meta_values_page_and_cpt( $passed, $expected, $post_type, $supports_revisions = false ) {
+
+ // If the post type doesn't exist, create it, potentially supporting revisions.
+ if ( ! post_type_exists( $post_type ) ) {
+ register_post_type(
+ $post_type,
+ array(
+ 'public' => true,
+ 'supports' => $supports_revisions ? array( 'revisions' ) : array(),
+ )
+ );
+ }
+
+ // Create a test post.
+ $page_id = $this->factory->post->create(
+ array(
+ 'post_type' => $post_type,
+ 'post_content' => 'some initial content',
+ )
+ );
+
+ // Add the revisioning filter.
+ add_filter( 'wp_post_revision_meta_keys', array( $this, 'add_revisioned_keys' ) );
+
+ // Test revisioning.
+ update_post_meta( $page_id, 'meta_revision_test', wp_slash( $passed ) );
+
+ // Update the post, storing a revision.
+ wp_update_post(
+ array(
+ 'post_content' => 'some more content',
+ 'ID' => $page_id,
+ )
+ );
+
+ // Retrieve the created revision.
+ $revisions = (array) wp_get_post_revisions( $page_id );
+
+ if ( $expected ) {
+ // Go back to load the previous revision.
+ $last_revision = array_shift( $revisions );
+ wp_restore_post_revision( $last_revision->ID );
+ $this->assertEquals( $expected, get_post_meta( $page_id, 'meta_revision_test', true ) );
+ } else {
+ $this->assertEmpty( $revisions );
+ }
+ }
+
+ /**
+ * Provide data for the page post type tests.
+ */
+ public function page_post_type_data_provider() {
+ return array(
+ array(
+ 'Test string',
+ 'Test string',
+ 'page',
+ ),
+ array(
+ 'Test string',
+ false,
+ 'custom_type',
+ ),
+ array(
+ 'Test string',
+ 'Test string',
+ 'custom_type',
+ true,
+ ),
+ );
+ }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: trunk/tests/phpunit/tests/post/metaRevisions.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="trunktestsphpunittestsrestapirestautosavescontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-autosaves-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-autosaves-controller.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/phpunit/tests/rest-api/rest-autosaves-controller.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -215,12 +215,13 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'author',
</span><span class="cx" style="display: block; padding: 0 10px"> 'date',
</span><span class="cx" style="display: block; padding: 0 10px"> 'date_gmt',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'id',
+ 'meta',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'modified',
</span><span class="cx" style="display: block; padding: 0 10px"> 'modified_gmt',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 'guid',
- 'id',
</del><span class="cx" style="display: block; padding: 0 10px"> 'parent',
</span><span class="cx" style="display: block; padding: 0 10px"> 'slug',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'guid',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'title',
</span><span class="cx" style="display: block; padding: 0 10px"> 'excerpt',
</span><span class="cx" style="display: block; padding: 0 10px"> 'content',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -288,7 +289,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $response = rest_get_server()->dispatch( $request );
</span><span class="cx" style="display: block; padding: 0 10px"> $data = $response->get_data();
</span><span class="cx" style="display: block; padding: 0 10px"> $properties = $data['schema']['properties'];
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $this->assertCount( 13, $properties );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertCount( 14, $properties );
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'author', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'content', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'date', $properties );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -302,6 +303,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'slug', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'title', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'preview_link', $properties );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertArrayHasKey( 'meta', $properties );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> public function test_create_item() {
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestglobalstylesrevisionscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/phpunit/tests/rest-api/rest-global-styles-revisions-controller.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -132,7 +132,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- wp_update_post( $new_styles_post, true, false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ wp_update_post( $new_styles_post, true );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $new_styles_post = array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'ID' => self::$global_styles_id,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -162,7 +162,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- wp_update_post( $new_styles_post, true, false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ wp_update_post( $new_styles_post, true );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $new_styles_post = array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'ID' => self::$global_styles_id,
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -192,7 +192,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- wp_update_post( $new_styles_post, true, false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ wp_update_post( $new_styles_post, true );
</ins><span class="cx" style="display: block; padding: 0 10px"> wp_set_current_user( 0 );
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -326,7 +326,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'post_content' => wp_json_encode( $config ),
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- wp_update_post( $updated_styles_post, true, false );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ wp_update_post( $updated_styles_post, true );
</ins><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $request = new WP_REST_Request( 'GET', '/wp/v2/global-styles/' . self::$global_styles_id . '/revisions' );
</span><span class="cx" style="display: block; padding: 0 10px"> $response = rest_get_server()->dispatch( $request );
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestpostmetafieldsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/phpunit/tests/rest-api/rest-post-meta-fields.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -17,7 +17,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'cpt',
</span><span class="cx" style="display: block; padding: 0 10px"> array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => true,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 'supports' => array( 'custom-fields' ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'supports' => array( 'custom-fields', 'revisions' ),
</ins><span class="cx" style="display: block; padding: 0 10px"> )
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -157,7 +157,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'cpt',
</span><span class="cx" style="display: block; padding: 0 10px"> array(
</span><span class="cx" style="display: block; padding: 0 10px"> 'show_in_rest' => true,
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- 'supports' => array( 'custom-fields' ),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'supports' => array( 'custom-fields', 'revisions' ),
</ins><span class="cx" style="display: block; padding: 0 10px"> )
</span><span class="cx" style="display: block; padding: 0 10px"> );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1376,8 +1376,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> * @dataProvider data_update_value_return_success_with_same_value
</span><span class="cx" style="display: block; padding: 0 10px"> */
</span><span class="cx" style="display: block; padding: 0 10px"> public function test_update_value_return_success_with_same_value( $meta_key, $meta_value ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- add_post_meta( self::$post_id, $meta_key, $meta_value );
-
</del><span class="cx" style="display: block; padding: 0 10px"> $this->grant_write_permission();
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $data = array(
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1392,6 +1390,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $response = rest_get_server()->dispatch( $request );
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertSame( 200, $response->get_status() );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+ // Verify the returned meta value is correct.
+ $data = $response->get_data();
+ $this->assertArrayHasKey( 'meta', $data );
+ $this->assertArrayHasKey( $meta_key, $data['meta'] );
+ $this->assertSame( $meta_value, $data['meta'][ $meta_key ] );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> public function data_update_value_return_success_with_same_value() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3112,4 +3116,359 @@
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> return $query;
</span><span class="cx" style="display: block; padding: 0 10px"> }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+
+ /**
+ * Test that single post meta is revisioned when saving to the posts REST API endpoint.
+ *
+ * @ticket 20564
+ */
+ public function test_revisioned_single_post_meta_with_posts_endpoint() {
+ $this->grant_write_permission();
+
+ register_post_meta(
+ 'post',
+ 'foo',
+ array(
+ 'single' => true,
+ 'show_in_rest' => true,
+ 'revisions_enabled' => true,
+ )
+ );
+
+ $post_id = self::$post_id;
+
+ // Update the post, saving the meta.
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 1',
+ 'meta' => array(
+ 'foo' => 'bar',
+ ),
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+ $revision_id = array_shift( $revisions )->ID;
+
+ // @todo Ensure the revisions endpoint returns the correct meta values
+ // Check that the revisions endpoint returns the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+ $data = $response->get_data();
+ $this->assertSame( 'bar', $response->get_data()['meta']['foo'] );
+
+ // Check that the post meta is set correctly.
+ $this->assertSame( 'bar', get_post_meta( $revision_id, 'foo', true ) );
+
+ // Create two more revisions with different meta values for the foo key.
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 2',
+ 'meta' => array(
+ 'foo' => 'baz',
+ ),
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+ $revision_id_2 = array_shift( $revisions )->ID;
+
+ // Check that the revision has the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id_2 ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( 'baz', $response->get_data()['meta']['foo'] );
+
+ // Check that the post meta is set correctly.
+ $this->assertSame( 'baz', get_post_meta( $revision_id_2, 'foo', true ) );
+
+ // One more revision!
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 3',
+ 'meta' => array(
+ 'foo' => 'qux',
+ ),
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+ $revision_id_3 = array_shift( $revisions )->ID;
+
+ // Check that the revision has the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id_3 ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+ $this->assertSame( 'qux', $response->get_data()['meta']['foo'] );
+
+ // Check that the post meta is set correctly.
+ $this->assertSame( 'qux', get_post_meta( $revision_id_3, 'foo', true ) );
+
+ // Restore Revision 3 and verify the post gets the correct meta value.
+ wp_restore_post_revision( $revision_id_3 );
+ $this->assertSame( 'qux', get_post_meta( $post_id, 'foo', true ) );
+
+ // Restore Revision 2 and verify the post gets the correct meta value.
+ wp_restore_post_revision( $revision_id_2 );
+ $this->assertSame( 'baz', get_post_meta( $post_id, 'foo', true ) );
+ }
+
+ /**
+ * Test that multi-post meta is revisioned when saving to the posts REST API endpoint.
+ *
+ * @ticket 20564
+ */
+ public function test_revisioned_multiple_post_meta_with_posts_endpoint() {
+ $this->grant_write_permission();
+
+ register_post_meta(
+ 'post',
+ 'foo',
+ array(
+ 'single' => false,
+ 'show_in_rest' => true,
+ 'revisions_enabled' => true,
+ )
+ );
+
+ $post_id = self::$post_id;
+
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 1',
+ 'meta' => array(
+ 'foo' => array(
+ 'bar',
+ 'bat',
+ 'baz',
+ ),
+ ),
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Log the current post meta.
+ $meta = get_post_meta( $post_id );
+
+ // Update the post.
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 1 update',
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+ $revision_id_1 = array_shift( $revisions )->ID;
+
+ // Check that the revision has the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id_1 ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ $this->assertSame(
+ array( 'bar', 'bat', 'baz' ),
+ $response->get_data()['meta']['foo']
+ );
+ $this->assertSame(
+ array( 'bar', 'bat', 'baz' ),
+ get_post_meta( $revision_id_1, 'foo' )
+ );
+
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 2',
+ 'meta' => array(
+ 'foo' => array(
+ 'car',
+ 'cat',
+ ),
+ ),
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+ $revision_id_2 = array_shift( $revisions )->ID;
+
+ // Check that the revision has the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id_2 ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ $this->assertSame(
+ array( 'car', 'cat' ),
+ $response->get_data()['meta']['foo']
+ );
+ $this->assertSame( array( 'car', 'cat' ), get_post_meta( $revision_id_2, 'foo' ) );
+
+ $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 3',
+ 'meta' => array(
+ 'foo' => null,
+ ),
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+ $revision_id_3 = array_shift( $revisions )->ID;
+
+ // Check that the revision has the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d/revisions/%d', $post_id, $revision_id_3 ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ $this->assertSame(
+ array(),
+ $response->get_data()['meta']['foo']
+ );
+ $this->assertSame( array(), get_post_meta( $revision_id_3, 'foo' ) );
+
+ // Restore Revision 3 and verify the post gets the correct meta value.
+ wp_restore_post_revision( $revision_id_3 );
+ $this->assertSame( array(), get_post_meta( $post_id, 'foo' ) );
+
+ // Restore Revision 2 and verify the post gets the correct meta value.
+ wp_restore_post_revision( $revision_id_2 );
+ $this->assertSame( array( 'car', 'cat' ), get_post_meta( $post_id, 'foo' ) );
+ }
+
+ /**
+ * Test post meta revisions with a custom post type and the page post type.
+ *
+ * @group revision
+ * @dataProvider test_revisioned_single_post_meta_with_posts_endpoint_page_and_cpt_data_provider
+ */
+ public function test_revisioned_single_post_meta_with_posts_endpoint_page_and_cpt( $passed, $expected, $post_type ) {
+
+ $this->grant_write_permission();
+
+ // Create the custom meta.
+ register_post_meta(
+ $post_type,
+ 'foo',
+ array(
+ 'show_in_rest' => true,
+ 'revisions_enabled' => true,
+ 'single' => true,
+ 'type' => 'string',
+ )
+ );
+
+ // Set up a new post.
+ $post_id = $this->factory->post->create(
+ array(
+ 'post_content' => 'initial content',
+ 'post_type' => $post_type,
+ 'meta_input' => array(
+ 'foo' => 'foo',
+ ),
+ )
+ );
+
+ $plural_mapping = array(
+ 'page' => 'pages',
+ 'cpt' => 'cpt',
+ );
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/%s', $plural_mapping[ $post_type ] ) );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/%s/%d', $plural_mapping[ $post_type ], $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 1',
+ 'meta' => array(
+ 'foo' => $passed,
+ ),
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Update the post.
+ $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/%s/%d', $plural_mapping[ $post_type ], $post_id ) );
+ $request->set_body_params(
+ array(
+ 'title' => 'Revision 1 update',
+ )
+ );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ // Get the last revision.
+ $revisions = wp_get_post_revisions( $post_id, array( 'posts_per_page' => 1 ) );
+
+ $revision_id_1 = array_shift( $revisions )->ID;
+
+ // Check that the revision has the correct meta value.
+ $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/%s/%d/revisions/%d', $plural_mapping[ $post_type ], $post_id, $revision_id_1 ) );
+ $response = rest_get_server()->dispatch( $request );
+ $this->assertSame( 200, $response->get_status() );
+
+ $this->assertSame(
+ $passed,
+ $response->get_data()['meta']['foo']
+ );
+
+ $this->assertSame(
+ array( $passed ),
+ get_post_meta( $revision_id_1, 'foo' )
+ );
+
+ unregister_post_meta( $post_type, 'foo' );
+ wp_delete_post( $post_id, true );
+ }
+
+ /**
+ * Provide data for the meta revision checks.
+ */
+ public function test_revisioned_single_post_meta_with_posts_endpoint_page_and_cpt_data_provider() {
+ return array(
+ array(
+ 'Test string',
+ 'Test string',
+ 'cpt',
+ ),
+ array(
+ 'Test string',
+ 'Test string',
+ 'page',
+ ),
+ array(
+ 'Test string',
+ false,
+ 'cpt',
+ ),
+
+ );
+ }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre></div>
<a id="trunktestsphpunittestsrestapirestrevisionscontrollerphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/rest-api/rest-revisions-controller.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/rest-api/rest-revisions-controller.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/phpunit/tests/rest-api/rest-revisions-controller.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -179,6 +179,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'modified_gmt',
</span><span class="cx" style="display: block; padding: 0 10px"> 'guid',
</span><span class="cx" style="display: block; padding: 0 10px"> 'id',
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'meta',
</ins><span class="cx" style="display: block; padding: 0 10px"> 'parent',
</span><span class="cx" style="display: block; padding: 0 10px"> 'slug',
</span><span class="cx" style="display: block; padding: 0 10px"> 'title',
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -335,7 +336,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $response = rest_get_server()->dispatch( $request );
</span><span class="cx" style="display: block; padding: 0 10px"> $data = $response->get_data();
</span><span class="cx" style="display: block; padding: 0 10px"> $properties = $data['schema']['properties'];
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">- $this->assertCount( 12, $properties );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertCount( 13, $properties );
</ins><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'author', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'content', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'date', $properties );
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -348,6 +349,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'parent', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'slug', $properties );
</span><span class="cx" style="display: block; padding: 0 10px"> $this->assertArrayHasKey( 'title', $properties );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ $this->assertArrayHasKey( 'meta', $properties );
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="cx" style="display: block; padding: 0 10px"> public function test_create_item() {
</span></span></pre></div>
<a id="trunktestsphpunittestsuserwpRegisterPersistedPreferencesMetaphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/phpunit/tests/user/wpRegisterPersistedPreferencesMeta.php 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -52,6 +52,7 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 'additionalProperties' => true,
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> ),
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ 'revisions_enabled' => false,
</ins><span class="cx" style="display: block; padding: 0 10px"> ),
</span><span class="cx" style="display: block; padding: 0 10px"> $wp_meta_keys['user'][''][ $meta_key ],
</span><span class="cx" style="display: block; padding: 0 10px"> 'The registered metadata did not have the expected structure'
</span></span></pre></div>
<a id="trunktestsqunitfixtureswpapigeneratedjs"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/qunit/fixtures/wp-api-generated.js</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/qunit/fixtures/wp-api-generated.js 2023-09-26 15:25:50 UTC (rev 56713)
+++ trunk/tests/qunit/fixtures/wp-api-generated.js 2023-09-26 15:30:34 UTC (rev 56714)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11782,6 +11782,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": ""
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "meta": {
+ "meta_key": ""
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "_links": {
</span><span class="cx" style="display: block; padding: 0 10px"> "parent": [
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11811,6 +11814,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": "<p>REST API Client Fixture: Post</p>\n"
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "meta": {
+ "meta_key": ""
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "_links": {
</span><span class="cx" style="display: block; padding: 0 10px"> "parent": [
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11841,6 +11847,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": "<p>REST API Client Fixture: Post</p>\n"
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ },
+ "meta": {
+ "meta_key": ""
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> };
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11866,6 +11875,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": ""
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "meta": {
+ "meta_key": ""
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "_links": {
</span><span class="cx" style="display: block; padding: 0 10px"> "parent": [
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -11896,6 +11908,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": ""
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ },
+ "meta": {
+ "meta_key": ""
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> };
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12042,6 +12057,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": ""
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "meta": {
+ "meta_key": ""
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "_links": {
</span><span class="cx" style="display: block; padding: 0 10px"> "parent": [
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12071,6 +12089,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": "<p>REST API Client Fixture: Page</p>\n"
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "meta": {
+ "meta_key": ""
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "_links": {
</span><span class="cx" style="display: block; padding: 0 10px"> "parent": [
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12101,6 +12122,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": "<p>REST API Client Fixture: Page</p>\n"
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ },
+ "meta": {
+ "meta_key": ""
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> };
</span><span class="cx" style="display: block; padding: 0 10px">
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12126,6 +12150,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": ""
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ "meta": {
+ "meta_key": ""
+ },
</ins><span class="cx" style="display: block; padding: 0 10px"> "_links": {
</span><span class="cx" style="display: block; padding: 0 10px"> "parent": [
</span><span class="cx" style="display: block; padding: 0 10px"> {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -12156,6 +12183,9 @@
</span><span class="cx" style="display: block; padding: 0 10px"> },
</span><span class="cx" style="display: block; padding: 0 10px"> "excerpt": {
</span><span class="cx" style="display: block; padding: 0 10px"> "rendered": ""
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ },
+ "meta": {
+ "meta_key": ""
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> };
</span><span class="cx" style="display: block; padding: 0 10px">
</span></span></pre>
</div>
</div>
</body>
</html>