wp/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
--- a/wp/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php	Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php	Fri Sep 05 18:40:08 2025 +0200
@@ -65,11 +65,16 @@
 			$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
 		}
 
-		$this->parent_controller    = $parent_controller;
-		$this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
+		$this->parent_controller = $parent_controller;
+
+		$revisions_controller = $post_type_object->get_revisions_rest_controller();
+		if ( ! $revisions_controller ) {
+			$revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
+		}
+		$this->revisions_controller = $revisions_controller;
 		$this->rest_base            = 'autosaves';
+		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
 		$this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
-		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
 	}
 
 	/**
@@ -131,7 +136,6 @@
 				'schema' => array( $this, 'get_public_item_schema' ),
 			)
 		);
-
 	}
 
 	/**
@@ -206,11 +210,11 @@
 	 */
 	public function create_item( $request ) {
 
-		if ( ! defined( 'DOING_AUTOSAVE' ) ) {
+		if ( ! defined( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) {
 			define( 'DOING_AUTOSAVE', true );
 		}
 
-		$post = get_post( $request['id'] );
+		$post = $this->get_parent( $request['id'] );
 
 		if ( is_wp_error( $post ) ) {
 			return $post;
@@ -220,13 +224,23 @@
 		$prepared_post->ID = $post->ID;
 		$user_id           = get_current_user_id();
 
-		if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
-			// Draft posts for the same author: autosaving updates the post and does not create a revision.
-			// Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
+		// We need to check post lock to ensure the original author didn't leave their browser tab open.
+		if ( ! function_exists( 'wp_check_post_lock' ) ) {
+			require_once ABSPATH . 'wp-admin/includes/post.php';
+		}
+
+		$post_lock = wp_check_post_lock( $post->ID );
+		$is_draft  = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;
+
+		if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock ) {
+			/*
+			 * Draft posts for the same author: autosaving updates the post and does not create a revision.
+			 * Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
+			 */
 			$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
 		} else {
-			// Non-draft posts: create or update the post autosave.
-			$autosave_id = $this->create_post_autosave( (array) $prepared_post );
+			// Non-draft posts: create or update the post autosave. Pass the meta data.
+			$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
 		}
 
 		if ( is_wp_error( $autosave_id ) ) {
@@ -296,7 +310,7 @@
 		$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
 
 		foreach ( $revisions as $revision ) {
-			if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
+			if ( str_contains( $revision->post_name, "{$parent_id}-autosave" ) ) {
 				$data       = $this->prepare_item_for_response( $revision, $request );
 				$response[] = $this->prepare_response_for_collection( $data );
 			}
@@ -339,11 +353,13 @@
 	 * From wp-admin/post.php.
 	 *
 	 * @since 5.0.0
+	 * @since 6.4.0 The `$meta` parameter was added.
 	 *
 	 * @param array $post_data Associative array containing the post data.
+	 * @param array $meta      Associative array containing the post meta data.
 	 * @return mixed The autosave revision ID or WP_Error.
 	 */
-	public function create_post_autosave( $post_data ) {
+	public function create_post_autosave( $post_data, array $meta = array() ) {
 
 		$post_id = (int) $post_data['ID'];
 		$post    = get_post( $post_id );
@@ -352,44 +368,70 @@
 			return $post;
 		}
 
+		// Only create an autosave when it is different from the saved post.
+		$autosave_is_different = false;
+		$new_autosave          = _wp_post_revision_data( $post_data, true );
+
+		foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
+			if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
+				$autosave_is_different = true;
+				break;
+			}
+		}
+
+		// Check if meta values have changed.
+		if ( ! empty( $meta ) ) {
+			$revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
+			foreach ( $revisioned_meta_keys as $meta_key ) {
+				// get_metadata_raw is used to avoid retrieving the default value.
+				$old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
+				$new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';
+
+				if ( $new_meta !== $old_meta ) {
+					$autosave_is_different = true;
+					break;
+				}
+			}
+		}
+
 		$user_id = get_current_user_id();
 
 		// Store one autosave per author. If there is already an autosave, overwrite it.
 		$old_autosave = wp_get_post_autosave( $post_id, $user_id );
 
+		if ( ! $autosave_is_different && $old_autosave ) {
+			// Nothing to save, return the existing autosave.
+			return $old_autosave->ID;
+		}
+
 		if ( $old_autosave ) {
-			$new_autosave                = _wp_post_revision_data( $post_data, true );
 			$new_autosave['ID']          = $old_autosave->ID;
 			$new_autosave['post_author'] = $user_id;
 
-			// If the new autosave has the same content as the post, delete the autosave.
-			$autosave_is_different = false;
-
-			foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
-				if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
-					$autosave_is_different = true;
-					break;
-				}
-			}
-
-			if ( ! $autosave_is_different ) {
-				wp_delete_post_revision( $old_autosave->ID );
-				return new WP_Error(
-					'rest_autosave_no_changes',
-					__( 'There is nothing to save. The autosave and the post content are the same.' ),
-					array( 'status' => 400 )
-				);
-			}
-
 			/** This filter is documented in wp-admin/post.php */
 			do_action( 'wp_creating_autosave', $new_autosave );
 
 			// wp_update_post() expects escaped array.
-			return wp_update_post( wp_slash( $new_autosave ) );
+			$revision_id = wp_update_post( wp_slash( $new_autosave ) );
+		} else {
+			// Create the new autosave as a special post revision.
+			$revision_id = _wp_put_post_revision( $post_data, true );
+		}
+
+		if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
+			return $revision_id;
 		}
 
-		// Create the new autosave as a special post revision.
-		return _wp_put_post_revision( $post_data, true );
+		// 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, wp_slash( $meta[ $meta_key ] ) );
+				}
+			}
+		}
+
+		return $revision_id;
 	}
 
 	/**
@@ -404,10 +446,10 @@
 	 */
 	public function prepare_item_for_response( $item, $request ) {
 		// Restores the more descriptive, specific name for use within this method.
-		$post     = $item;
+		$post = $item;
+
 		$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
-
-		$fields = $this->get_fields_for_response( $request );
+		$fields   = $this->get_fields_for_response( $request );
 
 		if ( in_array( 'preview_link', $fields, true ) ) {
 			$parent_id          = wp_is_post_autosave( $post );