wp/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    63 
    63 
    64 		if ( ! $parent_controller ) {
    64 		if ( ! $parent_controller ) {
    65 			$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
    65 			$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
    66 		}
    66 		}
    67 
    67 
    68 		$this->parent_controller    = $parent_controller;
    68 		$this->parent_controller = $parent_controller;
    69 		$this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
    69 
       
    70 		$revisions_controller = $post_type_object->get_revisions_rest_controller();
       
    71 		if ( ! $revisions_controller ) {
       
    72 			$revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
       
    73 		}
       
    74 		$this->revisions_controller = $revisions_controller;
    70 		$this->rest_base            = 'autosaves';
    75 		$this->rest_base            = 'autosaves';
       
    76 		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
    71 		$this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
    77 		$this->namespace            = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
    72 		$this->parent_base          = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
       
    73 	}
    78 	}
    74 
    79 
    75 	/**
    80 	/**
    76 	 * Registers the routes for autosaves.
    81 	 * Registers the routes for autosaves.
    77 	 *
    82 	 *
   129 					),
   134 					),
   130 				),
   135 				),
   131 				'schema' => array( $this, 'get_public_item_schema' ),
   136 				'schema' => array( $this, 'get_public_item_schema' ),
   132 			)
   137 			)
   133 		);
   138 		);
   134 
       
   135 	}
   139 	}
   136 
   140 
   137 	/**
   141 	/**
   138 	 * Get the parent post.
   142 	 * Get the parent post.
   139 	 *
   143 	 *
   204 	 * @param WP_REST_Request $request Full details about the request.
   208 	 * @param WP_REST_Request $request Full details about the request.
   205 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
   209 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
   206 	 */
   210 	 */
   207 	public function create_item( $request ) {
   211 	public function create_item( $request ) {
   208 
   212 
   209 		if ( ! defined( 'DOING_AUTOSAVE' ) ) {
   213 		if ( ! defined( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) {
   210 			define( 'DOING_AUTOSAVE', true );
   214 			define( 'DOING_AUTOSAVE', true );
   211 		}
   215 		}
   212 
   216 
   213 		$post = get_post( $request['id'] );
   217 		$post = $this->get_parent( $request['id'] );
   214 
   218 
   215 		if ( is_wp_error( $post ) ) {
   219 		if ( is_wp_error( $post ) ) {
   216 			return $post;
   220 			return $post;
   217 		}
   221 		}
   218 
   222 
   219 		$prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
   223 		$prepared_post     = $this->parent_controller->prepare_item_for_database( $request );
   220 		$prepared_post->ID = $post->ID;
   224 		$prepared_post->ID = $post->ID;
   221 		$user_id           = get_current_user_id();
   225 		$user_id           = get_current_user_id();
   222 
   226 
   223 		if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
   227 		// We need to check post lock to ensure the original author didn't leave their browser tab open.
   224 			// Draft posts for the same author: autosaving updates the post and does not create a revision.
   228 		if ( ! function_exists( 'wp_check_post_lock' ) ) {
   225 			// Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
   229 			require_once ABSPATH . 'wp-admin/includes/post.php';
       
   230 		}
       
   231 
       
   232 		$post_lock = wp_check_post_lock( $post->ID );
       
   233 		$is_draft  = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;
       
   234 
       
   235 		if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock ) {
       
   236 			/*
       
   237 			 * Draft posts for the same author: autosaving updates the post and does not create a revision.
       
   238 			 * Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
       
   239 			 */
   226 			$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
   240 			$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
   227 		} else {
   241 		} else {
   228 			// Non-draft posts: create or update the post autosave.
   242 			// Non-draft posts: create or update the post autosave. Pass the meta data.
   229 			$autosave_id = $this->create_post_autosave( (array) $prepared_post );
   243 			$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
   230 		}
   244 		}
   231 
   245 
   232 		if ( is_wp_error( $autosave_id ) ) {
   246 		if ( is_wp_error( $autosave_id ) ) {
   233 			return $autosave_id;
   247 			return $autosave_id;
   234 		}
   248 		}
   294 		$response  = array();
   308 		$response  = array();
   295 		$parent_id = $parent->ID;
   309 		$parent_id = $parent->ID;
   296 		$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
   310 		$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
   297 
   311 
   298 		foreach ( $revisions as $revision ) {
   312 		foreach ( $revisions as $revision ) {
   299 			if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
   313 			if ( str_contains( $revision->post_name, "{$parent_id}-autosave" ) ) {
   300 				$data       = $this->prepare_item_for_response( $revision, $request );
   314 				$data       = $this->prepare_item_for_response( $revision, $request );
   301 				$response[] = $this->prepare_response_for_collection( $data );
   315 				$response[] = $this->prepare_response_for_collection( $data );
   302 			}
   316 			}
   303 		}
   317 		}
   304 
   318 
   337 	 * Creates autosave for the specified post.
   351 	 * Creates autosave for the specified post.
   338 	 *
   352 	 *
   339 	 * From wp-admin/post.php.
   353 	 * From wp-admin/post.php.
   340 	 *
   354 	 *
   341 	 * @since 5.0.0
   355 	 * @since 5.0.0
       
   356 	 * @since 6.4.0 The `$meta` parameter was added.
   342 	 *
   357 	 *
   343 	 * @param array $post_data Associative array containing the post data.
   358 	 * @param array $post_data Associative array containing the post data.
       
   359 	 * @param array $meta      Associative array containing the post meta data.
   344 	 * @return mixed The autosave revision ID or WP_Error.
   360 	 * @return mixed The autosave revision ID or WP_Error.
   345 	 */
   361 	 */
   346 	public function create_post_autosave( $post_data ) {
   362 	public function create_post_autosave( $post_data, array $meta = array() ) {
   347 
   363 
   348 		$post_id = (int) $post_data['ID'];
   364 		$post_id = (int) $post_data['ID'];
   349 		$post    = get_post( $post_id );
   365 		$post    = get_post( $post_id );
   350 
   366 
   351 		if ( is_wp_error( $post ) ) {
   367 		if ( is_wp_error( $post ) ) {
   352 			return $post;
   368 			return $post;
   353 		}
   369 		}
   354 
   370 
   355 		$user_id = get_current_user_id();
   371 		// Only create an autosave when it is different from the saved post.
   356 
   372 		$autosave_is_different = false;
   357 		// Store one autosave per author. If there is already an autosave, overwrite it.
   373 		$new_autosave          = _wp_post_revision_data( $post_data, true );
   358 		$old_autosave = wp_get_post_autosave( $post_id, $user_id );
   374 
   359 
   375 		foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
   360 		if ( $old_autosave ) {
   376 			if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
   361 			$new_autosave                = _wp_post_revision_data( $post_data, true );
   377 				$autosave_is_different = true;
   362 			$new_autosave['ID']          = $old_autosave->ID;
   378 				break;
   363 			$new_autosave['post_author'] = $user_id;
   379 			}
   364 
   380 		}
   365 			// If the new autosave has the same content as the post, delete the autosave.
   381 
   366 			$autosave_is_different = false;
   382 		// Check if meta values have changed.
   367 
   383 		if ( ! empty( $meta ) ) {
   368 			foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
   384 			$revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
   369 				if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
   385 			foreach ( $revisioned_meta_keys as $meta_key ) {
       
   386 				// get_metadata_raw is used to avoid retrieving the default value.
       
   387 				$old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
       
   388 				$new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';
       
   389 
       
   390 				if ( $new_meta !== $old_meta ) {
   370 					$autosave_is_different = true;
   391 					$autosave_is_different = true;
   371 					break;
   392 					break;
   372 				}
   393 				}
   373 			}
   394 			}
   374 
   395 		}
   375 			if ( ! $autosave_is_different ) {
   396 
   376 				wp_delete_post_revision( $old_autosave->ID );
   397 		$user_id = get_current_user_id();
   377 				return new WP_Error(
   398 
   378 					'rest_autosave_no_changes',
   399 		// Store one autosave per author. If there is already an autosave, overwrite it.
   379 					__( 'There is nothing to save. The autosave and the post content are the same.' ),
   400 		$old_autosave = wp_get_post_autosave( $post_id, $user_id );
   380 					array( 'status' => 400 )
   401 
   381 				);
   402 		if ( ! $autosave_is_different && $old_autosave ) {
   382 			}
   403 			// Nothing to save, return the existing autosave.
       
   404 			return $old_autosave->ID;
       
   405 		}
       
   406 
       
   407 		if ( $old_autosave ) {
       
   408 			$new_autosave['ID']          = $old_autosave->ID;
       
   409 			$new_autosave['post_author'] = $user_id;
   383 
   410 
   384 			/** This filter is documented in wp-admin/post.php */
   411 			/** This filter is documented in wp-admin/post.php */
   385 			do_action( 'wp_creating_autosave', $new_autosave );
   412 			do_action( 'wp_creating_autosave', $new_autosave );
   386 
   413 
   387 			// wp_update_post() expects escaped array.
   414 			// wp_update_post() expects escaped array.
   388 			return wp_update_post( wp_slash( $new_autosave ) );
   415 			$revision_id = wp_update_post( wp_slash( $new_autosave ) );
   389 		}
   416 		} else {
   390 
   417 			// Create the new autosave as a special post revision.
   391 		// Create the new autosave as a special post revision.
   418 			$revision_id = _wp_put_post_revision( $post_data, true );
   392 		return _wp_put_post_revision( $post_data, true );
   419 		}
       
   420 
       
   421 		if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
       
   422 			return $revision_id;
       
   423 		}
       
   424 
       
   425 		// Attached any passed meta values that have revisions enabled.
       
   426 		if ( ! empty( $meta ) ) {
       
   427 			foreach ( $revisioned_meta_keys as $meta_key ) {
       
   428 				if ( isset( $meta[ $meta_key ] ) ) {
       
   429 					update_metadata( 'post', $revision_id, $meta_key, wp_slash( $meta[ $meta_key ] ) );
       
   430 				}
       
   431 			}
       
   432 		}
       
   433 
       
   434 		return $revision_id;
   393 	}
   435 	}
   394 
   436 
   395 	/**
   437 	/**
   396 	 * Prepares the revision for the REST response.
   438 	 * Prepares the revision for the REST response.
   397 	 *
   439 	 *
   402 	 * @param WP_REST_Request $request Request object.
   444 	 * @param WP_REST_Request $request Request object.
   403 	 * @return WP_REST_Response Response object.
   445 	 * @return WP_REST_Response Response object.
   404 	 */
   446 	 */
   405 	public function prepare_item_for_response( $item, $request ) {
   447 	public function prepare_item_for_response( $item, $request ) {
   406 		// Restores the more descriptive, specific name for use within this method.
   448 		// Restores the more descriptive, specific name for use within this method.
   407 		$post     = $item;
   449 		$post = $item;
       
   450 
   408 		$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
   451 		$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
   409 
   452 		$fields   = $this->get_fields_for_response( $request );
   410 		$fields = $this->get_fields_for_response( $request );
       
   411 
   453 
   412 		if ( in_array( 'preview_link', $fields, true ) ) {
   454 		if ( in_array( 'preview_link', $fields, true ) ) {
   413 			$parent_id          = wp_is_post_autosave( $post );
   455 			$parent_id          = wp_is_post_autosave( $post );
   414 			$preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
   456 			$preview_post_id    = false === $parent_id ? $post->ID : $parent_id;
   415 			$preview_query_args = array();
   457 			$preview_query_args = array();