wp/wp-includes/revision.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    15  * @access private
    15  * @access private
    16  *
    16  *
    17  * @param array|WP_Post $post       Optional. A post array or a WP_Post object being processed
    17  * @param array|WP_Post $post       Optional. A post array or a WP_Post object being processed
    18  *                                  for insertion as a post revision. Default empty array.
    18  *                                  for insertion as a post revision. Default empty array.
    19  * @param bool          $deprecated Not used.
    19  * @param bool          $deprecated Not used.
    20  * @return array Array of fields that can be versioned.
    20  * @return string[] Array of fields that can be versioned.
    21  */
    21  */
    22 function _wp_post_revision_fields( $post = array(), $deprecated = false ) {
    22 function _wp_post_revision_fields( $post = array(), $deprecated = false ) {
    23 	static $fields = null;
    23 	static $fields = null;
    24 
    24 
    25 	if ( ! is_array( $post ) ) {
    25 	if ( ! is_array( $post ) ) {
    45 	 * and 'post_author'.
    45 	 * and 'post_author'.
    46 	 *
    46 	 *
    47 	 * @since 2.6.0
    47 	 * @since 2.6.0
    48 	 * @since 4.5.0 The `$post` parameter was added.
    48 	 * @since 4.5.0 The `$post` parameter was added.
    49 	 *
    49 	 *
    50 	 * @param array $fields List of fields to revision. Contains 'post_title',
    50 	 * @param string[] $fields List of fields to revision. Contains 'post_title',
    51 	 *                      'post_content', and 'post_excerpt' by default.
    51 	 *                         'post_content', and 'post_excerpt' by default.
    52 	 * @param array $post   A post array being processed for insertion as a post revision.
    52 	 * @param array    $post   A post array being processed for insertion as a post revision.
    53 	 */
    53 	 */
    54 	$fields = apply_filters( '_wp_post_revision_fields', $fields, $post );
    54 	$fields = apply_filters( '_wp_post_revision_fields', $fields, $post );
    55 
    55 
    56 	// WP uses these internally either in versioning or elsewhere - they cannot be versioned.
    56 	// WP uses these internally either in versioning or elsewhere - they cannot be versioned.
    57 	foreach ( array( 'ID', 'post_name', 'post_parent', 'post_date', 'post_date_gmt', 'post_status', 'post_type', 'comment_count', 'post_author' ) as $protect ) {
    57 	foreach ( array( 'ID', 'post_name', 'post_parent', 'post_date', 'post_date_gmt', 'post_status', 'post_type', 'comment_count', 'post_author' ) as $protect ) {
    94 
    94 
    95 	return $revision_data;
    95 	return $revision_data;
    96 }
    96 }
    97 
    97 
    98 /**
    98 /**
       
    99  * Saves revisions for a post after all changes have been made.
       
   100  *
       
   101  * @since 6.4.0
       
   102  *
       
   103  * @param int     $post_id The post id that was inserted.
       
   104  * @param WP_Post $post    The post object that was inserted.
       
   105  * @param bool    $update  Whether this insert is updating an existing post.
       
   106  */
       
   107 function wp_save_post_revision_on_insert( $post_id, $post, $update ) {
       
   108 	if ( ! $update ) {
       
   109 		return;
       
   110 	}
       
   111 
       
   112 	if ( ! has_action( 'post_updated', 'wp_save_post_revision' ) ) {
       
   113 		return;
       
   114 	}
       
   115 
       
   116 	wp_save_post_revision( $post_id );
       
   117 }
       
   118 
       
   119 /**
    99  * Creates a revision for the current version of a post.
   120  * Creates a revision for the current version of a post.
   100  *
   121  *
   101  * Typically used immediately after a post update, as every update is a revision,
   122  * Typically used immediately after a post update, as every update is a revision,
   102  * and the most recent revision always matches the current post.
   123  * and the most recent revision always matches the current post.
   103  *
   124  *
   109 function wp_save_post_revision( $post_id ) {
   130 function wp_save_post_revision( $post_id ) {
   110 	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
   131 	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
   111 		return;
   132 		return;
   112 	}
   133 	}
   113 
   134 
       
   135 	// Prevent saving post revisions if revisions should be saved on wp_after_insert_post.
       
   136 	if ( doing_action( 'post_updated' ) && has_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert' ) ) {
       
   137 		return;
       
   138 	}
       
   139 
   114 	$post = get_post( $post_id );
   140 	$post = get_post( $post_id );
       
   141 
   115 	if ( ! $post ) {
   142 	if ( ! $post ) {
   116 		return;
   143 		return;
   117 	}
   144 	}
   118 
   145 
   119 	if ( ! post_type_supports( $post->post_type, 'revisions' ) ) {
   146 	if ( ! post_type_supports( $post->post_type, 'revisions' ) ) {
   133 	 * they are different, unless a plugin tells us to always save regardless.
   160 	 * they are different, unless a plugin tells us to always save regardless.
   134 	 * If no previous revisions, save one.
   161 	 * If no previous revisions, save one.
   135 	 */
   162 	 */
   136 	$revisions = wp_get_post_revisions( $post_id );
   163 	$revisions = wp_get_post_revisions( $post_id );
   137 	if ( $revisions ) {
   164 	if ( $revisions ) {
   138 		// Grab the last revision, but not an autosave.
   165 		// Grab the latest revision, but not an autosave.
   139 		foreach ( $revisions as $revision ) {
   166 		foreach ( $revisions as $revision ) {
   140 			if ( false !== strpos( $revision->post_name, "{$revision->post_parent}-revision" ) ) {
   167 			if ( str_contains( $revision->post_name, "{$revision->post_parent}-revision" ) ) {
   141 				$last_revision = $revision;
   168 				$latest_revision = $revision;
   142 				break;
   169 				break;
   143 			}
   170 			}
   144 		}
   171 		}
   145 
   172 
   146 		/**
   173 		/**
   147 		 * Filters whether the post has changed since the last revision.
   174 		 * Filters whether the post has changed since the latest revision.
   148 		 *
   175 		 *
   149 		 * By default a revision is saved only if one of the revisioned fields has changed.
   176 		 * By default a revision is saved only if one of the revisioned fields has changed.
   150 		 * This filter can override that so a revision is saved even if nothing has changed.
   177 		 * This filter can override that so a revision is saved even if nothing has changed.
   151 		 *
   178 		 *
   152 		 * @since 3.6.0
   179 		 * @since 3.6.0
   153 		 *
   180 		 *
   154 		 * @param bool    $check_for_changes Whether to check for changes before saving a new revision.
   181 		 * @param bool    $check_for_changes Whether to check for changes before saving a new revision.
   155 		 *                                   Default true.
   182 		 *                                   Default true.
   156 		 * @param WP_Post $last_revision     The last revision post object.
   183 		 * @param WP_Post $latest_revision   The latest revision post object.
   157 		 * @param WP_Post $post              The post object.
   184 		 * @param WP_Post $post              The post object.
   158 		 */
   185 		 */
   159 		if ( isset( $last_revision ) && apply_filters( 'wp_save_post_revision_check_for_changes', true, $last_revision, $post ) ) {
   186 		if ( isset( $latest_revision ) && apply_filters( 'wp_save_post_revision_check_for_changes', true, $latest_revision, $post ) ) {
   160 			$post_has_changed = false;
   187 			$post_has_changed = false;
   161 
   188 
   162 			foreach ( array_keys( _wp_post_revision_fields( $post ) ) as $field ) {
   189 			foreach ( array_keys( _wp_post_revision_fields( $post ) ) as $field ) {
   163 				if ( normalize_whitespace( $post->$field ) !== normalize_whitespace( $last_revision->$field ) ) {
   190 				if ( normalize_whitespace( $post->$field ) !== normalize_whitespace( $latest_revision->$field ) ) {
   164 					$post_has_changed = true;
   191 					$post_has_changed = true;
   165 					break;
   192 					break;
   166 				}
   193 				}
   167 			}
   194 			}
   168 
   195 
   173 			 * This filter allows for additional checks to determine if there were changes.
   200 			 * This filter allows for additional checks to determine if there were changes.
   174 			 *
   201 			 *
   175 			 * @since 4.1.0
   202 			 * @since 4.1.0
   176 			 *
   203 			 *
   177 			 * @param bool    $post_has_changed Whether the post has changed.
   204 			 * @param bool    $post_has_changed Whether the post has changed.
   178 			 * @param WP_Post $last_revision    The last revision post object.
   205 			 * @param WP_Post $latest_revision  The latest revision post object.
   179 			 * @param WP_Post $post             The post object.
   206 			 * @param WP_Post $post             The post object.
   180 			 */
   207 			 */
   181 			$post_has_changed = (bool) apply_filters( 'wp_save_post_revision_post_has_changed', $post_has_changed, $last_revision, $post );
   208 			$post_has_changed = (bool) apply_filters( 'wp_save_post_revision_post_has_changed', $post_has_changed, $latest_revision, $post );
   182 
   209 
   183 			// Don't save revision if post unchanged.
   210 			// Don't save revision if post unchanged.
   184 			if ( ! $post_has_changed ) {
   211 			if ( ! $post_has_changed ) {
   185 				return;
   212 				return;
   186 			}
   213 			}
   187 		}
   214 		}
   188 	}
   215 	}
   189 
   216 
   190 	$return = _wp_put_post_revision( $post );
   217 	$return = _wp_put_post_revision( $post );
   191 
   218 
   192 	// If a limit for the number of revisions to keep has been set,
   219 	/*
   193 	// delete the oldest ones.
   220 	 * If a limit for the number of revisions to keep has been set,
       
   221 	 * delete the oldest ones.
       
   222 	 */
   194 	$revisions_to_keep = wp_revisions_to_keep( $post );
   223 	$revisions_to_keep = wp_revisions_to_keep( $post );
   195 
   224 
   196 	if ( $revisions_to_keep < 0 ) {
   225 	if ( $revisions_to_keep < 0 ) {
   197 		return $return;
   226 		return $return;
   198 	}
   227 	}
   199 
   228 
   200 	$revisions = wp_get_post_revisions( $post_id, array( 'order' => 'ASC' ) );
   229 	$revisions = wp_get_post_revisions( $post_id, array( 'order' => 'ASC' ) );
   201 
   230 
       
   231 	/**
       
   232 	 * Filters the revisions to be considered for deletion.
       
   233 	 *
       
   234 	 * @since 6.2.0
       
   235 	 *
       
   236 	 * @param WP_Post[] $revisions Array of revisions, or an empty array if none.
       
   237 	 * @param int       $post_id   The ID of the post to save as a revision.
       
   238 	 */
       
   239 	$revisions = apply_filters(
       
   240 		'wp_save_post_revision_revisions_before_deletion',
       
   241 		$revisions,
       
   242 		$post_id
       
   243 	);
       
   244 
   202 	$delete = count( $revisions ) - $revisions_to_keep;
   245 	$delete = count( $revisions ) - $revisions_to_keep;
   203 
   246 
   204 	if ( $delete < 1 ) {
   247 	if ( $delete < 1 ) {
   205 		return $return;
   248 		return $return;
   206 	}
   249 	}
   207 
   250 
   208 	$revisions = array_slice( $revisions, 0, $delete );
   251 	$revisions = array_slice( $revisions, 0, $delete );
   209 
   252 
   210 	for ( $i = 0; isset( $revisions[ $i ] ); $i++ ) {
   253 	for ( $i = 0; isset( $revisions[ $i ] ); $i++ ) {
   211 		if ( false !== strpos( $revisions[ $i ]->post_name, 'autosave' ) ) {
   254 		if ( str_contains( $revisions[ $i ]->post_name, 'autosave' ) ) {
   212 			continue;
   255 			continue;
   213 		}
   256 		}
   214 
   257 
   215 		wp_delete_post_revision( $revisions[ $i ]->ID );
   258 		wp_delete_post_revision( $revisions[ $i ]->ID );
   216 	}
   259 	}
   217 
   260 
   218 	return $return;
   261 	return $return;
   219 }
   262 }
   220 
   263 
   221 /**
   264 /**
   222  * Retrieve the autosaved data of the specified post.
   265  * Retrieves the autosaved data of the specified post.
   223  *
   266  *
   224  * Returns a post object with the information that was autosaved for the specified post.
   267  * Returns a post object with the information that was autosaved for the specified post.
   225  * If the optional $user_id is passed, returns the autosave for that user, otherwise
   268  * If the optional $user_id is passed, returns the autosave for that user, otherwise
   226  * returns the latest autosave.
   269  * returns the latest autosave.
   227  *
   270  *
   228  * @since 2.6.0
   271  * @since 2.6.0
   229  *
   272  *
   230  * @global wpdb $wpdb WordPress database abstraction object.
   273  * @global wpdb $wpdb WordPress database abstraction object.
   231  *
   274  *
   232  * @param int $post_id The post ID.
   275  * @param int $post_id The post ID.
   233  * @param int $user_id Optional The post author ID.
   276  * @param int $user_id Optional. The post author ID. Default 0.
   234  * @return WP_Post|false The autosaved data or false on failure or when no autosave exists.
   277  * @return WP_Post|false The autosaved data or false on failure or when no autosave exists.
   235  */
   278  */
   236 function wp_get_post_autosave( $post_id, $user_id = 0 ) {
   279 function wp_get_post_autosave( $post_id, $user_id = 0 ) {
   237 	global $wpdb;
   280 	global $wpdb;
   238 
   281 
   273  * @param int|WP_Post $post Post ID or post object.
   316  * @param int|WP_Post $post Post ID or post object.
   274  * @return int|false ID of revision's parent on success, false if not a revision.
   317  * @return int|false ID of revision's parent on success, false if not a revision.
   275  */
   318  */
   276 function wp_is_post_revision( $post ) {
   319 function wp_is_post_revision( $post ) {
   277 	$post = wp_get_post_revision( $post );
   320 	$post = wp_get_post_revision( $post );
       
   321 
   278 	if ( ! $post ) {
   322 	if ( ! $post ) {
   279 		return false;
   323 		return false;
   280 	}
   324 	}
   281 
   325 
   282 	return (int) $post->post_parent;
   326 	return (int) $post->post_parent;
   290  * @param int|WP_Post $post Post ID or post object.
   334  * @param int|WP_Post $post Post ID or post object.
   291  * @return int|false ID of autosave's parent on success, false if not a revision.
   335  * @return int|false ID of autosave's parent on success, false if not a revision.
   292  */
   336  */
   293 function wp_is_post_autosave( $post ) {
   337 function wp_is_post_autosave( $post ) {
   294 	$post = wp_get_post_revision( $post );
   338 	$post = wp_get_post_revision( $post );
       
   339 
   295 	if ( ! $post ) {
   340 	if ( ! $post ) {
   296 		return false;
   341 		return false;
   297 	}
   342 	}
   298 
   343 
   299 	if ( false !== strpos( $post->post_name, "{$post->post_parent}-autosave" ) ) {
   344 	if ( str_contains( $post->post_name, "{$post->post_parent}-autosave" ) ) {
   300 		return (int) $post->post_parent;
   345 		return (int) $post->post_parent;
   301 	}
   346 	}
   302 
   347 
   303 	return false;
   348 	return false;
   304 }
   349 }
   308  *
   353  *
   309  * @since 2.6.0
   354  * @since 2.6.0
   310  * @access private
   355  * @access private
   311  *
   356  *
   312  * @param int|WP_Post|array|null $post     Post ID, post object OR post array.
   357  * @param int|WP_Post|array|null $post     Post ID, post object OR post array.
   313  * @param bool                   $autosave Optional. Is the revision an autosave?
   358  * @param bool                   $autosave Optional. Whether the revision is an autosave or not.
       
   359  *                                         Default false.
   314  * @return int|WP_Error WP_Error or 0 if error, new revision ID if success.
   360  * @return int|WP_Error WP_Error or 0 if error, new revision ID if success.
   315  */
   361  */
   316 function _wp_put_post_revision( $post = null, $autosave = false ) {
   362 function _wp_put_post_revision( $post = null, $autosave = false ) {
   317 	if ( is_object( $post ) ) {
   363 	if ( is_object( $post ) ) {
   318 		$post = get_object_vars( $post );
   364 		$post = get_object_vars( $post );
   339 	if ( $revision_id ) {
   385 	if ( $revision_id ) {
   340 		/**
   386 		/**
   341 		 * Fires once a revision has been saved.
   387 		 * Fires once a revision has been saved.
   342 		 *
   388 		 *
   343 		 * @since 2.6.0
   389 		 * @since 2.6.0
       
   390 		 * @since 6.4.0 The post_id parameter was added.
   344 		 *
   391 		 *
   345 		 * @param int $revision_id Post revision ID.
   392 		 * @param int $revision_id Post revision ID.
       
   393 		 * @param int $post_id     Post ID.
   346 		 */
   394 		 */
   347 		do_action( '_wp_put_post_revision', $revision_id );
   395 		do_action( '_wp_put_post_revision', $revision_id, $post['post_parent'] );
   348 	}
   396 	}
   349 
   397 
   350 	return $revision_id;
   398 	return $revision_id;
   351 }
   399 }
   352 
   400 
       
   401 
       
   402 /**
       
   403  * Save the revisioned meta fields.
       
   404  *
       
   405  * @since 6.4.0
       
   406  *
       
   407  * @param int $revision_id The ID of the revision to save the meta to.
       
   408  * @param int $post_id     The ID of the post the revision is associated with.
       
   409  */
       
   410 function wp_save_revisioned_meta_fields( $revision_id, $post_id ) {
       
   411 	$post_type = get_post_type( $post_id );
       
   412 	if ( ! $post_type ) {
       
   413 		return;
       
   414 	}
       
   415 
       
   416 	foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
       
   417 		if ( metadata_exists( 'post', $post_id, $meta_key ) ) {
       
   418 			_wp_copy_post_meta( $post_id, $revision_id, $meta_key );
       
   419 		}
       
   420 	}
       
   421 }
       
   422 
   353 /**
   423 /**
   354  * Gets a post revision.
   424  * Gets a post revision.
   355  *
   425  *
   356  * @since 2.6.0
   426  * @since 2.6.0
   357  *
   427  *
   358  * @param int|WP_Post $post   The post ID or object.
   428  * @param int|WP_Post $post   Post ID or post object.
   359  * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
   429  * @param string      $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
   360  *                            correspond to a WP_Post object, an associative array, or a numeric array,
   430  *                            correspond to a WP_Post object, an associative array, or a numeric array,
   361  *                            respectively. Default OBJECT.
   431  *                            respectively. Default OBJECT.
   362  * @param string      $filter Optional sanitation filter. See sanitize_post().
   432  * @param string      $filter Optional sanitization filter. See sanitize_post(). Default 'raw'.
   363  * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
   433  * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
   364  */
   434  */
   365 function wp_get_post_revision( &$post, $output = OBJECT, $filter = 'raw' ) {
   435 function wp_get_post_revision( &$post, $output = OBJECT, $filter = 'raw' ) {
   366 	$revision = get_post( $post, OBJECT, $filter );
   436 	$revision = get_post( $post, OBJECT, $filter );
       
   437 
   367 	if ( ! $revision ) {
   438 	if ( ! $revision ) {
   368 		return $revision;
   439 		return $revision;
   369 	}
   440 	}
       
   441 
   370 	if ( 'revision' !== $revision->post_type ) {
   442 	if ( 'revision' !== $revision->post_type ) {
   371 		return null;
   443 		return null;
   372 	}
   444 	}
   373 
   445 
   374 	if ( OBJECT === $output ) {
   446 	if ( OBJECT === $output ) {
   389  *
   461  *
   390  * Can restore a past revision using all fields of the post revision, or only selected fields.
   462  * Can restore a past revision using all fields of the post revision, or only selected fields.
   391  *
   463  *
   392  * @since 2.6.0
   464  * @since 2.6.0
   393  *
   465  *
   394  * @param int|WP_Post $revision_id Revision ID or revision object.
   466  * @param int|WP_Post $revision Revision ID or revision object.
   395  * @param array       $fields      Optional. What fields to restore from. Defaults to all.
   467  * @param array       $fields   Optional. What fields to restore from. Defaults to all.
   396  * @return int|false|null Null if error, false if no fields to restore, (int) post ID if success.
   468  * @return int|false|null Null if error, false if no fields to restore, (int) post ID if success.
   397  */
   469  */
   398 function wp_restore_post_revision( $revision_id, $fields = null ) {
   470 function wp_restore_post_revision( $revision, $fields = null ) {
   399 	$revision = wp_get_post_revision( $revision_id, ARRAY_A );
   471 	$revision = wp_get_post_revision( $revision, ARRAY_A );
       
   472 
   400 	if ( ! $revision ) {
   473 	if ( ! $revision ) {
   401 		return $revision;
   474 		return $revision;
   402 	}
   475 	}
   403 
   476 
   404 	if ( ! is_array( $fields ) ) {
   477 	if ( ! is_array( $fields ) ) {
   417 	$update['ID'] = $revision['post_parent'];
   490 	$update['ID'] = $revision['post_parent'];
   418 
   491 
   419 	$update = wp_slash( $update ); // Since data is from DB.
   492 	$update = wp_slash( $update ); // Since data is from DB.
   420 
   493 
   421 	$post_id = wp_update_post( $update );
   494 	$post_id = wp_update_post( $update );
       
   495 
   422 	if ( ! $post_id || is_wp_error( $post_id ) ) {
   496 	if ( ! $post_id || is_wp_error( $post_id ) ) {
   423 		return $post_id;
   497 		return $post_id;
   424 	}
   498 	}
   425 
   499 
   426 	// Update last edit user.
   500 	// Update last edit user.
   438 
   512 
   439 	return $post_id;
   513 	return $post_id;
   440 }
   514 }
   441 
   515 
   442 /**
   516 /**
       
   517  * Restore the revisioned meta values for a post.
       
   518  *
       
   519  * @since 6.4.0
       
   520  *
       
   521  * @param int $post_id     The ID of the post to restore the meta to.
       
   522  * @param int $revision_id The ID of the revision to restore the meta from.
       
   523  */
       
   524 function wp_restore_post_revision_meta( $post_id, $revision_id ) {
       
   525 	$post_type = get_post_type( $post_id );
       
   526 	if ( ! $post_type ) {
       
   527 		return;
       
   528 	}
       
   529 
       
   530 	// Restore revisioned meta fields.
       
   531 	foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
       
   532 
       
   533 		// Clear any existing meta.
       
   534 		delete_post_meta( $post_id, $meta_key );
       
   535 
       
   536 		_wp_copy_post_meta( $revision_id, $post_id, $meta_key );
       
   537 	}
       
   538 }
       
   539 
       
   540 /**
       
   541  * Copy post meta for the given key from one post to another.
       
   542  *
       
   543  * @since 6.4.0
       
   544  *
       
   545  * @param int    $source_post_id Post ID to copy meta value(s) from.
       
   546  * @param int    $target_post_id Post ID to copy meta value(s) to.
       
   547  * @param string $meta_key       Meta key to copy.
       
   548  */
       
   549 function _wp_copy_post_meta( $source_post_id, $target_post_id, $meta_key ) {
       
   550 
       
   551 	foreach ( get_post_meta( $source_post_id, $meta_key ) as $meta_value ) {
       
   552 		/**
       
   553 		 * We use add_metadata() function vs add_post_meta() here
       
   554 		 * to allow for a revision post target OR regular post.
       
   555 		 */
       
   556 		add_metadata( 'post', $target_post_id, $meta_key, wp_slash( $meta_value ) );
       
   557 	}
       
   558 }
       
   559 
       
   560 /**
       
   561  * Determine which post meta fields should be revisioned.
       
   562  *
       
   563  * @since 6.4.0
       
   564  *
       
   565  * @param string $post_type The post type being revisioned.
       
   566  * @return array An array of meta keys to be revisioned.
       
   567  */
       
   568 function wp_post_revision_meta_keys( $post_type ) {
       
   569 	$registered_meta = array_merge(
       
   570 		get_registered_meta_keys( 'post' ),
       
   571 		get_registered_meta_keys( 'post', $post_type )
       
   572 	);
       
   573 
       
   574 	$wp_revisioned_meta_keys = array();
       
   575 
       
   576 	foreach ( $registered_meta as $name => $args ) {
       
   577 		if ( $args['revisions_enabled'] ) {
       
   578 			$wp_revisioned_meta_keys[ $name ] = true;
       
   579 		}
       
   580 	}
       
   581 
       
   582 	$wp_revisioned_meta_keys = array_keys( $wp_revisioned_meta_keys );
       
   583 
       
   584 	/**
       
   585 	 * Filter the list of post meta keys to be revisioned.
       
   586 	 *
       
   587 	 * @since 6.4.0
       
   588 	 *
       
   589 	 * @param array  $keys      An array of meta fields to be revisioned.
       
   590 	 * @param string $post_type The post type being revisioned.
       
   591 	 */
       
   592 	return apply_filters( 'wp_post_revision_meta_keys', $wp_revisioned_meta_keys, $post_type );
       
   593 }
       
   594 
       
   595 /**
       
   596  * Check whether revisioned post meta fields have changed.
       
   597  *
       
   598  * @since 6.4.0
       
   599  *
       
   600  * @param bool    $post_has_changed Whether the post has changed.
       
   601  * @param WP_Post $last_revision    The last revision post object.
       
   602  * @param WP_Post $post             The post object.
       
   603  * @return bool Whether the post has changed.
       
   604  */
       
   605 function wp_check_revisioned_meta_fields_have_changed( $post_has_changed, WP_Post $last_revision, WP_Post $post ) {
       
   606 	foreach ( wp_post_revision_meta_keys( $post->post_type ) as $meta_key ) {
       
   607 		if ( get_post_meta( $post->ID, $meta_key ) !== get_post_meta( $last_revision->ID, $meta_key ) ) {
       
   608 			$post_has_changed = true;
       
   609 			break;
       
   610 		}
       
   611 	}
       
   612 	return $post_has_changed;
       
   613 }
       
   614 
       
   615 /**
   443  * Deletes a revision.
   616  * Deletes a revision.
   444  *
   617  *
   445  * Deletes the row from the posts table corresponding to the specified revision.
   618  * Deletes the row from the posts table corresponding to the specified revision.
   446  *
   619  *
   447  * @since 2.6.0
   620  * @since 2.6.0
   448  *
   621  *
   449  * @param int|WP_Post $revision_id Revision ID or revision object.
   622  * @param int|WP_Post $revision Revision ID or revision object.
   450  * @return WP_Post|false|null Null or false if error, deleted post object if success.
   623  * @return WP_Post|false|null Null or false if error, deleted post object if success.
   451  */
   624  */
   452 function wp_delete_post_revision( $revision_id ) {
   625 function wp_delete_post_revision( $revision ) {
   453 	$revision = wp_get_post_revision( $revision_id );
   626 	$revision = wp_get_post_revision( $revision );
       
   627 
   454 	if ( ! $revision ) {
   628 	if ( ! $revision ) {
   455 		return $revision;
   629 		return $revision;
   456 	}
   630 	}
   457 
   631 
   458 	$delete = wp_delete_post( $revision->ID );
   632 	$delete = wp_delete_post( $revision->ID );
       
   633 
   459 	if ( $delete ) {
   634 	if ( $delete ) {
   460 		/**
   635 		/**
   461 		 * Fires once a post revision has been deleted.
   636 		 * Fires once a post revision has been deleted.
   462 		 *
   637 		 *
   463 		 * @since 2.6.0
   638 		 * @since 2.6.0
   476  *
   651  *
   477  * @since 2.6.0
   652  * @since 2.6.0
   478  *
   653  *
   479  * @see get_children()
   654  * @see get_children()
   480  *
   655  *
   481  * @param int|WP_Post $post_id Optional. Post ID or WP_Post object. Default is global `$post`.
   656  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
   482  * @param array|null  $args    Optional. Arguments for retrieving post revisions. Default null.
   657  * @param array|null  $args Optional. Arguments for retrieving post revisions. Default null.
   483  * @return array An array of revisions, or an empty array if none.
   658  * @return WP_Post[]|int[] Array of revision objects or IDs, or an empty array if none.
   484  */
   659  */
   485 function wp_get_post_revisions( $post_id = 0, $args = null ) {
   660 function wp_get_post_revisions( $post = 0, $args = null ) {
   486 	$post = get_post( $post_id );
   661 	$post = get_post( $post );
       
   662 
   487 	if ( ! $post || empty( $post->ID ) ) {
   663 	if ( ! $post || empty( $post->ID ) ) {
   488 		return array();
   664 		return array();
   489 	}
   665 	}
   490 
   666 
   491 	$defaults = array(
   667 	$defaults = array(
   507 			'post_status' => 'inherit',
   683 			'post_status' => 'inherit',
   508 		)
   684 		)
   509 	);
   685 	);
   510 
   686 
   511 	$revisions = get_children( $args );
   687 	$revisions = get_children( $args );
       
   688 
   512 	if ( ! $revisions ) {
   689 	if ( ! $revisions ) {
   513 		return array();
   690 		return array();
   514 	}
   691 	}
   515 
   692 
   516 	return $revisions;
   693 	return $revisions;
   517 }
   694 }
   518 
   695 
   519 /**
   696 /**
       
   697  * Returns the latest revision ID and count of revisions for a post.
       
   698  *
       
   699  * @since 6.1.0
       
   700  *
       
   701  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
       
   702  * @return array|WP_Error {
       
   703  *     Returns associative array with latest revision ID and total count,
       
   704  *     or a WP_Error if the post does not exist or revisions are not enabled.
       
   705  *
       
   706  *     @type int $latest_id The latest revision post ID or 0 if no revisions exist.
       
   707  *     @type int $count     The total count of revisions for the given post.
       
   708  * }
       
   709  */
       
   710 function wp_get_latest_revision_id_and_total_count( $post = 0 ) {
       
   711 	$post = get_post( $post );
       
   712 
       
   713 	if ( ! $post ) {
       
   714 		return new WP_Error( 'invalid_post', __( 'Invalid post.' ) );
       
   715 	}
       
   716 
       
   717 	if ( ! wp_revisions_enabled( $post ) ) {
       
   718 		return new WP_Error( 'revisions_not_enabled', __( 'Revisions not enabled.' ) );
       
   719 	}
       
   720 
       
   721 	$args = array(
       
   722 		'post_parent'         => $post->ID,
       
   723 		'fields'              => 'ids',
       
   724 		'post_type'           => 'revision',
       
   725 		'post_status'         => 'inherit',
       
   726 		'order'               => 'DESC',
       
   727 		'orderby'             => 'date ID',
       
   728 		'posts_per_page'      => 1,
       
   729 		'ignore_sticky_posts' => true,
       
   730 	);
       
   731 
       
   732 	$revision_query = new WP_Query();
       
   733 	$revisions      = $revision_query->query( $args );
       
   734 
       
   735 	if ( ! $revisions ) {
       
   736 		return array(
       
   737 			'latest_id' => 0,
       
   738 			'count'     => 0,
       
   739 		);
       
   740 	}
       
   741 
       
   742 	return array(
       
   743 		'latest_id' => $revisions[0],
       
   744 		'count'     => $revision_query->found_posts,
       
   745 	);
       
   746 }
       
   747 
       
   748 /**
   520  * Returns the url for viewing and potentially restoring revisions of a given post.
   749  * Returns the url for viewing and potentially restoring revisions of a given post.
   521  *
   750  *
   522  * @since 5.9.0
   751  * @since 5.9.0
   523  *
   752  *
   524  * @param int|WP_Post $post_id Optional. Post ID or WP_Post object. Default is global `$post`.
   753  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`.
   525  * @return null|string The URL for editing revisions on the given post, otherwise null.
   754  * @return string|null The URL for editing revisions on the given post, otherwise null.
   526  */
   755  */
   527 function wp_get_post_revisions_url( $post_id = 0 ) {
   756 function wp_get_post_revisions_url( $post = 0 ) {
   528 	$post = get_post( $post_id );
   757 	$post = get_post( $post );
   529 
   758 
   530 	if ( ! $post instanceof WP_Post ) {
   759 	if ( ! $post instanceof WP_Post ) {
   531 		return null;
   760 		return null;
   532 	}
   761 	}
   533 
   762 
   538 
   767 
   539 	if ( ! wp_revisions_enabled( $post ) ) {
   768 	if ( ! wp_revisions_enabled( $post ) ) {
   540 		return null;
   769 		return null;
   541 	}
   770 	}
   542 
   771 
   543 	$revisions = wp_get_post_revisions( $post->ID, array( 'posts_per_page' => 1 ) );
   772 	$revisions = wp_get_latest_revision_id_and_total_count( $post->ID );
   544 
   773 
   545 	if ( 0 === count( $revisions ) ) {
   774 	if ( is_wp_error( $revisions ) || 0 === $revisions['count'] ) {
   546 		return null;
   775 		return null;
   547 	}
   776 	}
   548 
   777 
   549 	$revision = reset( $revisions );
   778 	return get_edit_post_link( $revisions['latest_id'] );
   550 	return get_edit_post_link( $revision );
   779 }
   551 }
   780 
   552 
   781 /**
   553 /**
   782  * Determines whether revisions are enabled for a given post.
   554  * Determine if revisions are enabled for a given post.
       
   555  *
   783  *
   556  * @since 3.6.0
   784  * @since 3.6.0
   557  *
   785  *
   558  * @param WP_Post $post The post object.
   786  * @param WP_Post $post The post object.
   559  * @return bool True if number of revisions to keep isn't zero, false otherwise.
   787  * @return bool True if number of revisions to keep isn't zero, false otherwise.
   561 function wp_revisions_enabled( $post ) {
   789 function wp_revisions_enabled( $post ) {
   562 	return wp_revisions_to_keep( $post ) !== 0;
   790 	return wp_revisions_to_keep( $post ) !== 0;
   563 }
   791 }
   564 
   792 
   565 /**
   793 /**
   566  * Determine how many revisions to retain for a given post.
   794  * Determines how many revisions to retain for a given post.
   567  *
   795  *
   568  * By default, an infinite number of revisions are kept.
   796  * By default, an infinite number of revisions are kept.
   569  *
   797  *
   570  * The constant WP_POST_REVISIONS can be set in wp-config to specify the limit
   798  * The constant WP_POST_REVISIONS can be set in wp-config to specify the limit
   571  * of revisions to keep.
   799  * of revisions to keep.
   647 		$post->post_excerpt = $preview->post_excerpt;
   875 		$post->post_excerpt = $preview->post_excerpt;
   648 	}
   876 	}
   649 
   877 
   650 	add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 );
   878 	add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 );
   651 	add_filter( 'get_post_metadata', '_wp_preview_post_thumbnail_filter', 10, 3 );
   879 	add_filter( 'get_post_metadata', '_wp_preview_post_thumbnail_filter', 10, 3 );
       
   880 	add_filter( 'get_post_metadata', '_wp_preview_meta_filter', 10, 4 );
   652 
   881 
   653 	return $post;
   882 	return $post;
   654 }
   883 }
   655 
   884 
   656 /**
   885 /**
   682  * @param string $taxonomy
   911  * @param string $taxonomy
   683  * @return array
   912  * @return array
   684  */
   913  */
   685 function _wp_preview_terms_filter( $terms, $post_id, $taxonomy ) {
   914 function _wp_preview_terms_filter( $terms, $post_id, $taxonomy ) {
   686 	$post = get_post();
   915 	$post = get_post();
       
   916 
   687 	if ( ! $post ) {
   917 	if ( ! $post ) {
   688 		return $terms;
   918 		return $terms;
   689 	}
   919 	}
   690 
   920 
   691 	if ( empty( $_REQUEST['post_format'] ) || $post->ID != $post_id
   921 	if ( empty( $_REQUEST['post_format'] ) || $post->ID !== $post_id
   692 		|| 'post_format' !== $taxonomy || 'revision' === $post->post_type
   922 		|| 'post_format' !== $taxonomy || 'revision' === $post->post_type
   693 	) {
   923 	) {
   694 		return $terms;
   924 		return $terms;
   695 	}
   925 	}
   696 
   926 
   697 	if ( 'standard' === $_REQUEST['post_format'] ) {
   927 	if ( 'standard' === $_REQUEST['post_format'] ) {
   698 		$terms = array();
   928 		$terms = array();
   699 	} else {
   929 	} else {
   700 		$term = get_term_by( 'slug', 'post-format-' . sanitize_key( $_REQUEST['post_format'] ), 'post_format' );
   930 		$term = get_term_by( 'slug', 'post-format-' . sanitize_key( $_REQUEST['post_format'] ), 'post_format' );
       
   931 
   701 		if ( $term ) {
   932 		if ( $term ) {
   702 			$terms = array( $term ); // Can only have one post format.
   933 			$terms = array( $term ); // Can only have one post format.
   703 		}
   934 		}
   704 	}
   935 	}
   705 
   936 
   717  * @param string            $meta_key Meta key.
   948  * @param string            $meta_key Meta key.
   718  * @return null|array The default return value or the post thumbnail meta array.
   949  * @return null|array The default return value or the post thumbnail meta array.
   719  */
   950  */
   720 function _wp_preview_post_thumbnail_filter( $value, $post_id, $meta_key ) {
   951 function _wp_preview_post_thumbnail_filter( $value, $post_id, $meta_key ) {
   721 	$post = get_post();
   952 	$post = get_post();
       
   953 
   722 	if ( ! $post ) {
   954 	if ( ! $post ) {
   723 		return $value;
   955 		return $value;
   724 	}
   956 	}
   725 
   957 
   726 	if ( empty( $_REQUEST['_thumbnail_id'] ) ||
   958 	if ( empty( $_REQUEST['_thumbnail_id'] ) || empty( $_REQUEST['preview_id'] )
   727 		empty( $_REQUEST['preview_id'] ) ||
   959 		|| $post->ID !== $post_id || $post_id !== (int) $_REQUEST['preview_id']
   728 		$post->ID != $post_id ||
   960 		|| '_thumbnail_id' !== $meta_key || 'revision' === $post->post_type
   729 		'_thumbnail_id' !== $meta_key ||
   961 	) {
   730 		'revision' === $post->post_type ||
       
   731 		$post_id != $_REQUEST['preview_id'] ) {
       
   732 
       
   733 		return $value;
   962 		return $value;
   734 	}
   963 	}
   735 
   964 
   736 	$thumbnail_id = (int) $_REQUEST['_thumbnail_id'];
   965 	$thumbnail_id = (int) $_REQUEST['_thumbnail_id'];
       
   966 
   737 	if ( $thumbnail_id <= 0 ) {
   967 	if ( $thumbnail_id <= 0 ) {
   738 		return '';
   968 		return '';
   739 	}
   969 	}
   740 
   970 
   741 	return (string) $thumbnail_id;
   971 	return (string) $thumbnail_id;
   763 
   993 
   764 	return 0;
   994 	return 0;
   765 }
   995 }
   766 
   996 
   767 /**
   997 /**
   768  * Upgrade the revisions author, add the current post as a revision and set the revisions version to 1
   998  * Upgrades the revisions author, adds the current post as a revision and sets the revisions version to 1.
   769  *
   999  *
   770  * @since 3.6.0
  1000  * @since 3.6.0
   771  * @access private
  1001  * @access private
   772  *
  1002  *
   773  * @global wpdb $wpdb WordPress database abstraction object.
  1003  * @global wpdb $wpdb WordPress database abstraction object.
   774  *
  1004  *
   775  * @param WP_Post $post      Post object
  1005  * @param WP_Post $post      Post object.
   776  * @param array   $revisions Current revisions of the post
  1006  * @param array   $revisions Current revisions of the post.
   777  * @return bool true if the revisions were upgraded, false if problems
  1007  * @return bool true if the revisions were upgraded, false if problems.
   778  */
  1008  */
   779 function _wp_upgrade_revisions_of_post( $post, $revisions ) {
  1009 function _wp_upgrade_revisions_of_post( $post, $revisions ) {
   780 	global $wpdb;
  1010 	global $wpdb;
   781 
  1011 
   782 	// Add post option exclusively.
  1012 	// Add post option exclusively.
   783 	$lock   = "revision-upgrade-{$post->ID}";
  1013 	$lock   = "revision-upgrade-{$post->ID}";
   784 	$now    = time();
  1014 	$now    = time();
   785 	$result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'no') /* LOCK */", $lock, $now ) );
  1015 	$result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'off') /* LOCK */", $lock, $now ) );
       
  1016 
   786 	if ( ! $result ) {
  1017 	if ( ! $result ) {
   787 		// If we couldn't get a lock, see how old the previous lock is.
  1018 		// If we couldn't get a lock, see how old the previous lock is.
   788 		$locked = get_option( $lock );
  1019 		$locked = get_option( $lock );
       
  1020 
   789 		if ( ! $locked ) {
  1021 		if ( ! $locked ) {
   790 			// Can't write to the lock, and can't read the lock.
  1022 			/*
   791 			// Something broken has happened.
  1023 			 * Can't write to the lock, and can't read the lock.
       
  1024 			 * Something broken has happened.
       
  1025 			 */
   792 			return false;
  1026 			return false;
   793 		}
  1027 		}
   794 
  1028 
   795 		if ( $locked > $now - 3600 ) {
  1029 		if ( $locked > $now - HOUR_IN_SECONDS ) {
   796 			// Lock is not too old: some other process may be upgrading this post. Bail.
  1030 			// Lock is not too old: some other process may be upgrading this post. Bail.
   797 			return false;
  1031 			return false;
   798 		}
  1032 		}
   799 
  1033 
   800 		// Lock is too old - update it (below) and continue.
  1034 		// Lock is too old - update it (below) and continue.
   815 		// Something terrible happened.
  1049 		// Something terrible happened.
   816 		if ( false === $this_revision_version ) {
  1050 		if ( false === $this_revision_version ) {
   817 			continue;
  1051 			continue;
   818 		}
  1052 		}
   819 
  1053 
   820 		// 1 is the latest revision version, so we're already up to date.
  1054 		/*
   821 		// No need to add a copy of the post as latest revision.
  1055 		 * 1 is the latest revision version, so we're already up to date.
       
  1056 		 * No need to add a copy of the post as latest revision.
       
  1057 		 */
   822 		if ( 0 < $this_revision_version ) {
  1058 		if ( 0 < $this_revision_version ) {
   823 			$add_last = false;
  1059 			$add_last = false;
   824 			continue;
  1060 			continue;
   825 		}
  1061 		}
   826 
  1062 
   858 		wp_save_post_revision( $post->ID );
  1094 		wp_save_post_revision( $post->ID );
   859 	}
  1095 	}
   860 
  1096 
   861 	return true;
  1097 	return true;
   862 }
  1098 }
       
  1099 
       
  1100 /**
       
  1101  * Filters preview post meta retrieval to get values from the autosave.
       
  1102  *
       
  1103  * Filters revisioned meta keys only.
       
  1104  *
       
  1105  * @since 6.4.0
       
  1106  *
       
  1107  * @param mixed  $value     Meta value to filter.
       
  1108  * @param int    $object_id Object ID.
       
  1109  * @param string $meta_key  Meta key to filter a value for.
       
  1110  * @param bool   $single    Whether to return a single value. Default false.
       
  1111  * @return mixed Original meta value if the meta key isn't revisioned, the object doesn't exist,
       
  1112  *               the post type is a revision or the post ID doesn't match the object ID.
       
  1113  *               Otherwise, the revisioned meta value is returned for the preview.
       
  1114  */
       
  1115 function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) {
       
  1116 
       
  1117 	$post = get_post();
       
  1118 	if (
       
  1119 		empty( $post ) ||
       
  1120 		$post->ID !== $object_id ||
       
  1121 		! in_array( $meta_key, wp_post_revision_meta_keys( $post->post_type ), true ) ||
       
  1122 		'revision' === $post->post_type
       
  1123 	) {
       
  1124 		return $value;
       
  1125 	}
       
  1126 
       
  1127 	$preview = wp_get_post_autosave( $post->ID );
       
  1128 	if ( false === $preview ) {
       
  1129 		return $value;
       
  1130 	}
       
  1131 
       
  1132 	return get_post_meta( $preview->ID, $meta_key, $single );
       
  1133 }