| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Post revision functions. 4 * 5 * @package WordPress 6 * @subpackage Post_Revisions 7 */ 8 9 /** 10 * Determines which fields of posts are to be saved in revisions. 11 * 12 * @since 2.6.0 13 * @since 4.5.0 A `WP_Post` object can now be passed to the `$post` parameter. 14 * @since 4.5.0 The optional `$autosave` parameter was deprecated and renamed to `$deprecated`. 15 * @access private 16 * 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. 19 * @param bool $deprecated Not used. 20 * @return string[] Array of fields that can be versioned. 21 * 22 * @phpstan-param false $deprecated 23 */ 24 function _wp_post_revision_fields( $post = array(), $deprecated = false ) { 25 static $fields = null; 26 27 if ( ! is_array( $post ) ) { 28 $post = get_post( $post, ARRAY_A ); 29 } 30 31 if ( is_null( $fields ) ) { 32 // Allow these to be versioned. 33 $fields = array( 34 'post_title' => __( 'Title' ), 35 'post_content' => __( 'Content' ), 36 'post_excerpt' => __( 'Excerpt' ), 37 ); 38 } 39 40 /** 41 * Filters the list of fields saved in post revisions. 42 * 43 * Included by default: 'post_title', 'post_content' and 'post_excerpt'. 44 * 45 * Disallowed fields: 'ID', 'post_name', 'post_parent', 'post_date', 46 * 'post_date_gmt', 'post_status', 'post_type', 'comment_count', 47 * and 'post_author'. 48 * 49 * @since 2.6.0 50 * @since 4.5.0 The `$post` parameter was added. 51 * 52 * @param string[] $fields List of fields to revision. Contains 'post_title', 53 * 'post_content', and 'post_excerpt' by default. 54 * @param array $post A post array being processed for insertion as a post revision. 55 */ 56 $fields = apply_filters( '_wp_post_revision_fields', $fields, $post ); 57 58 // WP uses these internally either in versioning or elsewhere - they cannot be versioned. 59 foreach ( array( 'ID', 'post_name', 'post_parent', 'post_date', 'post_date_gmt', 'post_status', 'post_type', 'comment_count', 'post_author' ) as $protect ) { 60 unset( $fields[ $protect ] ); 61 } 62 63 return $fields; 64 } 65 66 /** 67 * Returns a post array ready to be inserted into the posts table as a post revision. 68 * 69 * @since 4.5.0 70 * @access private 71 * 72 * @param array|WP_Post $post Optional. A post array or a WP_Post object to be processed 73 * for insertion as a post revision. Default empty array. 74 * @param bool $autosave Optional. Is the revision an autosave? Default false. 75 * @return array Post array ready to be inserted as a post revision. 76 */ 77 function _wp_post_revision_data( $post = array(), $autosave = false ) { 78 if ( ! is_array( $post ) ) { 79 $post = get_post( $post, ARRAY_A ); 80 } 81 82 $fields = _wp_post_revision_fields( $post ); 83 84 $revision_data = array(); 85 86 foreach ( array_intersect( array_keys( $post ), array_keys( $fields ) ) as $field ) { 87 $revision_data[ $field ] = $post[ $field ]; 88 } 89 90 $revision_data['post_parent'] = $post['ID']; 91 $revision_data['post_status'] = 'inherit'; 92 $revision_data['post_type'] = 'revision'; 93 $revision_data['post_name'] = $autosave ? "$post[ID]-autosave-v1" : "$post[ID]-revision-v1"; // "1" is the revisioning system version. 94 $revision_data['post_date'] = $post['post_modified'] ?? ''; 95 $revision_data['post_date_gmt'] = $post['post_modified_gmt'] ?? ''; 96 97 return $revision_data; 98 } 99 100 /** 101 * Saves revisions for a post after all changes have been made. 102 * 103 * @since 6.4.0 104 * 105 * @param int $post_id The post id that was inserted. 106 * @param WP_Post $post The post object that was inserted. 107 * @param bool $update Whether this insert is updating an existing post. 108 */ 109 function wp_save_post_revision_on_insert( $post_id, $post, $update ) { 110 if ( ! $update ) { 111 return; 112 } 113 114 if ( ! has_action( 'post_updated', 'wp_save_post_revision' ) ) { 115 return; 116 } 117 118 wp_save_post_revision( $post_id ); 119 } 120 121 /** 122 * Creates a revision for the current version of a post. 123 * 124 * Typically used immediately after a post update, as every update is a revision, 125 * and the most recent revision always matches the current post. 126 * 127 * @since 2.6.0 128 * 129 * @param int $post_id The ID of the post to save as a revision. 130 * @return int|WP_Error|null Null or 0 if error, new revision ID, if success. 131 */ 132 function wp_save_post_revision( $post_id ) { 133 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 134 return null; 135 } 136 137 // Prevent saving post revisions if revisions should be saved on wp_after_insert_post. 138 if ( doing_action( 'post_updated' ) && has_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert' ) ) { 139 return null; 140 } 141 142 $post = get_post( $post_id ); 143 144 if ( ! $post ) { 145 return null; 146 } 147 148 if ( ! post_type_supports( $post->post_type, 'revisions' ) ) { 149 return null; 150 } 151 152 if ( 'auto-draft' === $post->post_status ) { 153 return null; 154 } 155 156 if ( ! wp_revisions_enabled( $post ) ) { 157 return null; 158 } 159 160 /* 161 * Compare the proposed update with the last stored revision verifying that 162 * they are different, unless a plugin tells us to always save regardless. 163 * If no previous revisions, save one. 164 */ 165 $revisions = wp_get_post_revisions( $post_id ); 166 if ( $revisions ) { 167 // Grab the latest revision, but not an autosave. 168 foreach ( $revisions as $revision ) { 169 if ( str_contains( $revision->post_name, "{$revision->post_parent}-revision" ) ) { 170 $latest_revision = $revision; 171 break; 172 } 173 } 174 175 /** 176 * Filters whether the post has changed since the latest revision. 177 * 178 * By default a revision is saved only if one of the revisioned fields has changed. 179 * This filter can override that so a revision is saved even if nothing has changed. 180 * 181 * @since 3.6.0 182 * 183 * @param bool $check_for_changes Whether to check for changes before saving a new revision. 184 * Default true. 185 * @param WP_Post $latest_revision The latest revision post object. 186 * @param WP_Post $post The post object. 187 */ 188 if ( isset( $latest_revision ) && apply_filters( 'wp_save_post_revision_check_for_changes', true, $latest_revision, $post ) ) { 189 $post_has_changed = false; 190 191 foreach ( array_keys( _wp_post_revision_fields( $post ) ) as $field ) { 192 if ( normalize_whitespace( maybe_serialize( $post->$field ) ) !== normalize_whitespace( maybe_serialize( $latest_revision->$field ) ) ) { 193 $post_has_changed = true; 194 break; 195 } 196 } 197 198 /** 199 * Filters whether a post has changed. 200 * 201 * By default a revision is saved only if one of the revisioned fields has changed. 202 * This filter allows for additional checks to determine if there were changes. 203 * 204 * @since 4.1.0 205 * 206 * @param bool $post_has_changed Whether the post has changed. 207 * @param WP_Post $latest_revision The latest revision post object. 208 * @param WP_Post $post The post object. 209 */ 210 $post_has_changed = (bool) apply_filters( 'wp_save_post_revision_post_has_changed', $post_has_changed, $latest_revision, $post ); 211 212 // Don't save revision if post unchanged. 213 if ( ! $post_has_changed ) { 214 return null; 215 } 216 } 217 } 218 219 $return = _wp_put_post_revision( $post ); 220 221 /* 222 * If a limit for the number of revisions to keep has been set, 223 * delete the oldest ones. 224 */ 225 $revisions_to_keep = wp_revisions_to_keep( $post ); 226 227 if ( $revisions_to_keep < 0 ) { 228 return $return; 229 } 230 231 $revisions = wp_get_post_revisions( $post_id, array( 'order' => 'ASC' ) ); 232 233 /** 234 * Filters the revisions to be considered for deletion. 235 * 236 * @since 6.2.0 237 * 238 * @param WP_Post[] $revisions Array of revisions, or an empty array if none. 239 * @param int $post_id The ID of the post to save as a revision. 240 */ 241 $revisions = apply_filters( 242 'wp_save_post_revision_revisions_before_deletion', 243 $revisions, 244 $post_id 245 ); 246 247 $delete = count( $revisions ) - $revisions_to_keep; 248 249 if ( $delete < 1 ) { 250 return $return; 251 } 252 253 $revisions = array_slice( $revisions, 0, $delete ); 254 255 for ( $i = 0; isset( $revisions[ $i ] ); $i++ ) { 256 if ( str_contains( $revisions[ $i ]->post_name, 'autosave' ) ) { 257 continue; 258 } 259 260 wp_delete_post_revision( $revisions[ $i ]->ID ); 261 } 262 263 return $return; 264 } 265 266 /** 267 * Retrieves the autosaved data of the specified post. 268 * 269 * Returns a post object with the information that was autosaved for the specified post. 270 * If the optional $user_id is passed, returns the autosave for that user, otherwise 271 * returns the latest autosave. 272 * 273 * @since 2.6.0 274 * 275 * @param int $post_id The post ID. 276 * @param int $user_id Optional. The post author ID. Default 0. 277 * @return WP_Post|false The autosaved data or false on failure or when no autosave exists. 278 */ 279 function wp_get_post_autosave( $post_id, $user_id = 0 ) { 280 $args = array( 281 'post_type' => 'revision', 282 'post_status' => 'inherit', 283 'post_parent' => $post_id, 284 'name' => $post_id . '-autosave-v1', 285 'posts_per_page' => 1, 286 'orderby' => 'date', 287 'order' => 'DESC', 288 'fields' => 'ids', 289 'no_found_rows' => true, 290 ); 291 292 if ( 0 !== $user_id ) { 293 $args['author'] = $user_id; 294 } 295 296 $query = new WP_Query( $args ); 297 298 if ( ! $query->have_posts() ) { 299 return false; 300 } 301 302 return get_post( $query->posts[0] ); 303 } 304 305 /** 306 * Determines if the specified post is a revision. 307 * 308 * @since 2.6.0 309 * 310 * @param int|WP_Post $post Post ID or post object. 311 * @return int|false ID of revision's parent on success, false if not a revision. 312 * 313 * @phpstan-return ( $post is int<min, -1> ? false : false|int<0, max> ) 314 */ 315 function wp_is_post_revision( $post ) { 316 $post = wp_get_post_revision( $post ); 317 318 if ( ! $post ) { 319 return false; 320 } 321 322 return (int) $post->post_parent; 323 } 324 325 /** 326 * Determines if the specified post is an autosave. 327 * 328 * @since 2.6.0 329 * 330 * @param int|WP_Post $post Post ID or post object. 331 * @return int|false ID of autosave's parent on success, false if not a revision. 332 */ 333 function wp_is_post_autosave( $post ) { 334 $post = wp_get_post_revision( $post ); 335 336 if ( ! $post ) { 337 return false; 338 } 339 340 if ( str_contains( $post->post_name, "{$post->post_parent}-autosave" ) ) { 341 return (int) $post->post_parent; 342 } 343 344 return false; 345 } 346 347 /** 348 * Inserts post data into the posts table as a post revision. 349 * 350 * @since 2.6.0 351 * @access private 352 * 353 * @param int|WP_Post|array|null $post Post ID, post object OR post array. 354 * @param bool $autosave Optional. Whether the revision is an autosave or not. 355 * Default false. 356 * @return int|WP_Error WP_Error or 0 if error, new revision ID if success. 357 */ 358 function _wp_put_post_revision( $post = null, $autosave = false ) { 359 if ( is_object( $post ) ) { 360 $post = get_object_vars( $post ); 361 } elseif ( ! is_array( $post ) ) { 362 $post = get_post( $post, ARRAY_A ); 363 } 364 365 if ( ! $post || empty( $post['ID'] ) ) { 366 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) ); 367 } 368 369 if ( isset( $post['post_type'] ) && 'revision' === $post['post_type'] ) { 370 return new WP_Error( 'post_type', __( 'Cannot create a revision of a revision' ) ); 371 } 372 373 $post = _wp_post_revision_data( $post, $autosave ); 374 $post = wp_slash( $post ); // Since data is from DB. 375 376 $revision_id = wp_insert_post( $post, true ); 377 if ( is_wp_error( $revision_id ) ) { 378 return $revision_id; 379 } 380 381 if ( $revision_id ) { 382 /** 383 * Fires once a revision has been saved. 384 * 385 * @since 2.6.0 386 * @since 6.4.0 The `$post_id` parameter was added. 387 * 388 * @param int $revision_id Post revision ID. 389 * @param int $post_id Post ID. 390 */ 391 do_action( '_wp_put_post_revision', $revision_id, $post['post_parent'] ); 392 } 393 394 return $revision_id; 395 } 396 397 398 /** 399 * Save the revisioned meta fields. 400 * 401 * @since 6.4.0 402 * 403 * @param int $revision_id The ID of the revision to save the meta to. 404 * @param int $post_id The ID of the post the revision is associated with. 405 */ 406 function wp_save_revisioned_meta_fields( $revision_id, $post_id ) { 407 $post_type = get_post_type( $post_id ); 408 if ( ! $post_type ) { 409 return; 410 } 411 412 foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) { 413 if ( metadata_exists( 'post', $post_id, $meta_key ) ) { 414 _wp_copy_post_meta( $post_id, $revision_id, $meta_key ); 415 } 416 } 417 } 418 419 /** 420 * Gets a post revision. 421 * 422 * @since 2.6.0 423 * 424 * @param int|WP_Post $post Post ID or post object. 425 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which 426 * correspond to a WP_Post object, an associative array, or a numeric array, 427 * respectively. Default OBJECT. 428 * @param string $filter Optional sanitization filter. See sanitize_post(). Default 'raw'. 429 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure. 430 * 431 * @phpstan-param int|WP_Post $post 432 * @phpstan-param 'OBJECT'|'ARRAY_A'|'ARRAY_N' $output 433 * @phpstan-param 'raw'|'edit'|'db'|'display' $filter 434 * @phpstan-return ( 435 * $output is 'ARRAY_A' ? non-empty-array<string, mixed>|null : ( 436 * $output is 'ARRAY_N' ? non-empty-array<int, mixed>|null : ( 437 * WP_Post|null 438 * ) 439 * ) 440 * ) 441 */ 442 function wp_get_post_revision( &$post, $output = OBJECT, $filter = 'raw' ) { 443 $revision = get_post( $post, OBJECT, $filter ); 444 445 if ( ! $revision ) { 446 return $revision; 447 } 448 449 if ( 'revision' !== $revision->post_type ) { 450 return null; 451 } 452 453 if ( OBJECT === $output ) { 454 return $revision; 455 } elseif ( ARRAY_A === $output ) { 456 /** @var non-empty-array<string, mixed> $_revision */ 457 $_revision = get_object_vars( $revision ); 458 return $_revision; 459 } elseif ( ARRAY_N === $output ) { 460 /** @var non-empty-array<string, mixed> $vars */ 461 $vars = get_object_vars( $revision ); 462 $_revision = array_values( $vars ); 463 return $_revision; 464 } 465 466 return $revision; 467 } 468 469 /** 470 * Restores a post to the specified revision. 471 * 472 * Can restore a past revision using all fields of the post revision, or only selected fields. 473 * 474 * @since 2.6.0 475 * 476 * @param int|WP_Post $revision Revision ID or revision object. 477 * @param array $fields Optional. What fields to restore from. Defaults to all. 478 * @return int|false|null Null if error, false if no fields to restore, (int) post ID if success. 479 */ 480 function wp_restore_post_revision( $revision, $fields = null ) { 481 $revision = wp_get_post_revision( $revision, ARRAY_A ); 482 483 if ( ! $revision ) { 484 return $revision; 485 } 486 487 if ( ! is_array( $fields ) ) { 488 $fields = array_keys( _wp_post_revision_fields( $revision ) ); 489 } 490 491 $update = array(); 492 foreach ( array_intersect( array_keys( $revision ), $fields ) as $field ) { 493 $update[ $field ] = $revision[ $field ]; 494 } 495 496 if ( ! $update ) { 497 return false; 498 } 499 500 $update['ID'] = $revision['post_parent']; 501 502 $update = wp_slash( $update ); // Since data is from DB. 503 504 $post_id = wp_update_post( $update ); 505 506 if ( ! $post_id || is_wp_error( $post_id ) ) { 507 return $post_id; 508 } 509 510 // Update last edit user. 511 update_post_meta( $post_id, '_edit_last', get_current_user_id() ); 512 513 /** 514 * Fires after a post revision has been restored. 515 * 516 * @since 2.6.0 517 * 518 * @param int $post_id Post ID. 519 * @param int $revision_id Post revision ID. 520 */ 521 do_action( 'wp_restore_post_revision', $post_id, $revision['ID'] ); 522 523 return $post_id; 524 } 525 526 /** 527 * Restore the revisioned meta values for a post. 528 * 529 * @since 6.4.0 530 * 531 * @param int $post_id The ID of the post to restore the meta to. 532 * @param int $revision_id The ID of the revision to restore the meta from. 533 */ 534 function wp_restore_post_revision_meta( $post_id, $revision_id ) { 535 $post_type = get_post_type( $post_id ); 536 if ( ! $post_type ) { 537 return; 538 } 539 540 // Restore revisioned meta fields. 541 foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) { 542 543 // Clear any existing meta. 544 delete_post_meta( $post_id, $meta_key ); 545 546 _wp_copy_post_meta( $revision_id, $post_id, $meta_key ); 547 } 548 } 549 550 /** 551 * Copy post meta for the given key from one post to another. 552 * 553 * @since 6.4.0 554 * 555 * @param int $source_post_id Post ID to copy meta value(s) from. 556 * @param int $target_post_id Post ID to copy meta value(s) to. 557 * @param string $meta_key Meta key to copy. 558 */ 559 function _wp_copy_post_meta( $source_post_id, $target_post_id, $meta_key ) { 560 561 foreach ( get_post_meta( $source_post_id, $meta_key ) as $meta_value ) { 562 /** 563 * We use add_metadata() function vs add_post_meta() here 564 * to allow for a revision post target OR regular post. 565 */ 566 add_metadata( 'post', $target_post_id, $meta_key, wp_slash( $meta_value ) ); 567 } 568 } 569 570 /** 571 * Determine which post meta fields should be revisioned. 572 * 573 * @since 6.4.0 574 * 575 * @param string $post_type The post type being revisioned. 576 * @return array An array of meta keys to be revisioned. 577 */ 578 function wp_post_revision_meta_keys( $post_type ) { 579 $registered_meta = array_merge( 580 get_registered_meta_keys( 'post' ), 581 get_registered_meta_keys( 'post', $post_type ) 582 ); 583 584 $wp_revisioned_meta_keys = array(); 585 586 foreach ( $registered_meta as $name => $args ) { 587 if ( $args['revisions_enabled'] ) { 588 $wp_revisioned_meta_keys[ $name ] = true; 589 } 590 } 591 592 $wp_revisioned_meta_keys = array_keys( $wp_revisioned_meta_keys ); 593 594 /** 595 * Filter the list of post meta keys to be revisioned. 596 * 597 * @since 6.4.0 598 * 599 * @param array $keys An array of meta fields to be revisioned. 600 * @param string $post_type The post type being revisioned. 601 */ 602 return apply_filters( 'wp_post_revision_meta_keys', $wp_revisioned_meta_keys, $post_type ); 603 } 604 605 /** 606 * Check whether revisioned post meta fields have changed. 607 * 608 * @since 6.4.0 609 * 610 * @param bool $post_has_changed Whether the post has changed. 611 * @param WP_Post $last_revision The last revision post object. 612 * @param WP_Post $post The post object. 613 * @return bool Whether the post has changed. 614 */ 615 function wp_check_revisioned_meta_fields_have_changed( $post_has_changed, WP_Post $last_revision, WP_Post $post ) { 616 foreach ( wp_post_revision_meta_keys( $post->post_type ) as $meta_key ) { 617 if ( get_post_meta( $post->ID, $meta_key ) !== get_post_meta( $last_revision->ID, $meta_key ) ) { 618 $post_has_changed = true; 619 break; 620 } 621 } 622 return $post_has_changed; 623 } 624 625 /** 626 * Deletes a revision. 627 * 628 * Deletes the row from the posts table corresponding to the specified revision. 629 * 630 * @since 2.6.0 631 * 632 * @param int|WP_Post $revision Revision ID or revision object. 633 * @return WP_Post|false|null Null or false if error, deleted post object if success. 634 */ 635 function wp_delete_post_revision( $revision ) { 636 $revision = wp_get_post_revision( $revision ); 637 638 if ( ! $revision ) { 639 return $revision; 640 } 641 642 $delete = wp_delete_post( $revision->ID ); 643 644 if ( $delete ) { 645 /** 646 * Fires once a post revision has been deleted. 647 * 648 * @since 2.6.0 649 * 650 * @param int $revision_id Post revision ID. 651 * @param WP_Post $revision Post revision object. 652 */ 653 do_action( 'wp_delete_post_revision', $revision->ID, $revision ); 654 } 655 656 return $delete; 657 } 658 659 /** 660 * Returns all revisions of specified post. 661 * 662 * @since 2.6.0 663 * 664 * @see get_children() 665 * 666 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`. 667 * @param array|null $args Optional. Arguments for retrieving post revisions. Default null. 668 * @return WP_Post[]|int[] Array of revision objects or IDs, or an empty array if none. 669 */ 670 function wp_get_post_revisions( $post = 0, $args = null ) { 671 $post = get_post( $post ); 672 673 if ( ! $post || empty( $post->ID ) ) { 674 return array(); 675 } 676 677 $defaults = array( 678 'order' => 'DESC', 679 'orderby' => 'date ID', 680 'check_enabled' => true, 681 ); 682 $args = wp_parse_args( $args, $defaults ); 683 684 if ( $args['check_enabled'] && ! wp_revisions_enabled( $post ) ) { 685 return array(); 686 } 687 688 $args = array_merge( 689 $args, 690 array( 691 'post_parent' => $post->ID, 692 'post_type' => 'revision', 693 'post_status' => 'inherit', 694 ) 695 ); 696 697 $revisions = get_children( $args ); 698 699 if ( ! $revisions ) { 700 return array(); 701 } 702 703 return $revisions; 704 } 705 706 /** 707 * Returns the latest revision ID and count of revisions for a post. 708 * 709 * @since 6.1.0 710 * 711 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post. 712 * @return array|WP_Error { 713 * Returns associative array with latest revision ID and total count, 714 * or a WP_Error if the post does not exist or revisions are not enabled. 715 * 716 * @type int $latest_id The latest revision post ID or 0 if no revisions exist. 717 * @type int $count The total count of revisions for the given post. 718 * } 719 */ 720 function wp_get_latest_revision_id_and_total_count( $post = 0 ) { 721 $post = get_post( $post ); 722 723 if ( ! $post ) { 724 return new WP_Error( 'invalid_post', __( 'Invalid post.' ) ); 725 } 726 727 if ( ! wp_revisions_enabled( $post ) ) { 728 return new WP_Error( 'revisions_not_enabled', __( 'Revisions not enabled.' ) ); 729 } 730 731 $args = array( 732 'post_parent' => $post->ID, 733 'fields' => 'ids', 734 'post_type' => 'revision', 735 'post_status' => 'inherit', 736 'order' => 'DESC', 737 'orderby' => 'date ID', 738 'posts_per_page' => 1, 739 'ignore_sticky_posts' => true, 740 ); 741 742 $revision_query = new WP_Query(); 743 $revisions = $revision_query->query( $args ); 744 745 if ( ! $revisions ) { 746 return array( 747 'latest_id' => 0, 748 'count' => 0, 749 ); 750 } 751 752 return array( 753 'latest_id' => $revisions[0], 754 'count' => $revision_query->found_posts, 755 ); 756 } 757 758 /** 759 * Returns the url for viewing and potentially restoring revisions of a given post. 760 * 761 * @since 5.9.0 762 * 763 * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global `$post`. 764 * @return string|null The URL for editing revisions on the given post, otherwise null. 765 */ 766 function wp_get_post_revisions_url( $post = 0 ) { 767 $post = get_post( $post ); 768 769 if ( ! $post instanceof WP_Post ) { 770 return null; 771 } 772 773 // If the post is a revision, return early. 774 if ( 'revision' === $post->post_type ) { 775 return get_edit_post_link( $post ); 776 } 777 778 if ( ! wp_revisions_enabled( $post ) ) { 779 return null; 780 } 781 782 $revisions = wp_get_latest_revision_id_and_total_count( $post->ID ); 783 784 if ( is_wp_error( $revisions ) || 0 === $revisions['count'] ) { 785 return null; 786 } 787 788 return get_edit_post_link( $revisions['latest_id'] ); 789 } 790 791 /** 792 * Determines whether revisions are enabled for a given post. 793 * 794 * @since 3.6.0 795 * 796 * @param WP_Post $post The post object. 797 * @return bool True if number of revisions to keep isn't zero, false otherwise. 798 */ 799 function wp_revisions_enabled( $post ) { 800 return wp_revisions_to_keep( $post ) !== 0; 801 } 802 803 /** 804 * Determines how many revisions to retain for a given post. 805 * 806 * By default, an infinite number of revisions are kept. 807 * 808 * The constant WP_POST_REVISIONS can be set in wp-config to specify the limit 809 * of revisions to keep. 810 * 811 * @since 3.6.0 812 * 813 * @param WP_Post $post The post object. 814 * @return int The number of revisions to keep. 815 */ 816 function wp_revisions_to_keep( $post ) { 817 $num = WP_POST_REVISIONS; 818 819 if ( true === $num ) { 820 $num = -1; 821 } else { 822 $num = (int) $num; 823 } 824 825 if ( ! post_type_supports( $post->post_type, 'revisions' ) ) { 826 $num = 0; 827 } 828 829 /** 830 * Filters the number of revisions to save for the given post. 831 * 832 * Overrides the value of WP_POST_REVISIONS. 833 * 834 * @since 3.6.0 835 * 836 * @param int $num Number of revisions to store. 837 * @param WP_Post $post Post object. 838 */ 839 $num = apply_filters( 'wp_revisions_to_keep', $num, $post ); 840 841 /** 842 * Filters the number of revisions to save for the given post by its post type. 843 * 844 * Overrides both the value of WP_POST_REVISIONS and the {@see 'wp_revisions_to_keep'} filter. 845 * 846 * The dynamic portion of the hook name, `$post->post_type`, refers to 847 * the post type slug. 848 * 849 * Possible hook names include: 850 * 851 * - `wp_post_revisions_to_keep` 852 * - `wp_page_revisions_to_keep` 853 * 854 * @since 5.8.0 855 * 856 * @param int $num Number of revisions to store. 857 * @param WP_Post $post Post object. 858 */ 859 $num = apply_filters( "wp_{$post->post_type}_revisions_to_keep", $num, $post ); 860 861 return (int) $num; 862 } 863 864 /** 865 * Sets up the post object for preview based on the post autosave. 866 * 867 * @since 2.7.0 868 * @access private 869 * 870 * @param WP_Post $post 871 * @return WP_Post|false 872 */ 873 function _set_preview( $post ) { 874 if ( ! is_object( $post ) ) { 875 return $post; 876 } 877 878 $preview = wp_get_post_autosave( $post->ID ); 879 880 if ( is_object( $preview ) ) { 881 $preview = sanitize_post( $preview ); 882 883 $post->post_content = $preview->post_content; 884 $post->post_title = $preview->post_title; 885 $post->post_excerpt = $preview->post_excerpt; 886 } 887 888 add_filter( 'get_the_terms', '_wp_preview_terms_filter', 10, 3 ); 889 add_filter( 'get_post_metadata', '_wp_preview_post_thumbnail_filter', 10, 3 ); 890 add_filter( 'get_post_metadata', '_wp_preview_meta_filter', 10, 4 ); 891 892 return $post; 893 } 894 895 /** 896 * Filters the latest content for preview from the post autosave. 897 * 898 * @since 2.7.0 899 * @access private 900 */ 901 function _show_post_preview() { 902 if ( isset( $_GET['preview_id'] ) && isset( $_GET['preview_nonce'] ) ) { 903 $id = (int) $_GET['preview_id']; 904 905 if ( false === wp_verify_nonce( $_GET['preview_nonce'], 'post_preview_' . $id ) ) { 906 wp_die( __( 'Sorry, you are not allowed to preview drafts.' ), 403 ); 907 } 908 909 add_filter( 'the_preview', '_set_preview' ); 910 } 911 } 912 913 /** 914 * Filters terms lookup to set the post format. 915 * 916 * @since 3.6.0 917 * @access private 918 * 919 * @param array $terms 920 * @param int $post_id 921 * @param string $taxonomy 922 * @return array 923 */ 924 function _wp_preview_terms_filter( $terms, $post_id, $taxonomy ) { 925 $post = get_post(); 926 927 if ( ! $post ) { 928 return $terms; 929 } 930 931 if ( empty( $_REQUEST['post_format'] ) || $post->ID !== $post_id 932 || 'post_format' !== $taxonomy || 'revision' === $post->post_type 933 ) { 934 return $terms; 935 } 936 937 if ( 'standard' === $_REQUEST['post_format'] ) { 938 $terms = array(); 939 } else { 940 $term = get_term_by( 'slug', 'post-format-' . sanitize_key( $_REQUEST['post_format'] ), 'post_format' ); 941 942 if ( $term ) { 943 $terms = array( $term ); // Can only have one post format. 944 } 945 } 946 947 return $terms; 948 } 949 950 /** 951 * Filters post thumbnail lookup to set the post thumbnail. 952 * 953 * @since 4.6.0 954 * @access private 955 * 956 * @param null|array|string $value The value to return - a single metadata value, or an array of values. 957 * @param int $post_id Post ID. 958 * @param string $meta_key Meta key. 959 * @return null|array The default return value or the post thumbnail meta array. 960 */ 961 function _wp_preview_post_thumbnail_filter( $value, $post_id, $meta_key ) { 962 $post = get_post(); 963 964 if ( ! $post ) { 965 return $value; 966 } 967 968 if ( empty( $_REQUEST['_thumbnail_id'] ) || empty( $_REQUEST['preview_id'] ) 969 || $post->ID !== $post_id || $post_id !== (int) $_REQUEST['preview_id'] 970 || '_thumbnail_id' !== $meta_key || 'revision' === $post->post_type 971 ) { 972 return $value; 973 } 974 975 $thumbnail_id = (int) $_REQUEST['_thumbnail_id']; 976 977 if ( $thumbnail_id <= 0 ) { 978 return ''; 979 } 980 981 return (string) $thumbnail_id; 982 } 983 984 /** 985 * Gets the post revision version. 986 * 987 * @since 3.6.0 988 * @access private 989 * 990 * @param WP_Post $revision 991 * @return int|false 992 */ 993 function _wp_get_post_revision_version( $revision ) { 994 if ( is_object( $revision ) ) { 995 $revision = get_object_vars( $revision ); 996 } elseif ( ! is_array( $revision ) ) { 997 return false; 998 } 999 1000 if ( preg_match( '/^\d+-(?:autosave|revision)-v(\d+)$/', $revision['post_name'], $matches ) ) { 1001 return (int) $matches[1]; 1002 } 1003 1004 return 0; 1005 } 1006 1007 /** 1008 * Upgrades the revisions author, adds the current post as a revision and sets the revisions version to 1. 1009 * 1010 * @since 3.6.0 1011 * @access private 1012 * 1013 * @global wpdb $wpdb WordPress database abstraction object. 1014 * 1015 * @param WP_Post $post Post object. 1016 * @param array $revisions Current revisions of the post. 1017 * @return bool true if the revisions were upgraded, false if problems. 1018 */ 1019 function _wp_upgrade_revisions_of_post( $post, $revisions ) { 1020 global $wpdb; 1021 1022 // Add post option exclusively. 1023 $lock = "revision-upgrade-{$post->ID}"; 1024 $now = time(); 1025 $result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'off') /* LOCK */", $lock, $now ) ); 1026 1027 if ( ! $result ) { 1028 // If we couldn't get a lock, see how old the previous lock is. 1029 $locked = get_option( $lock ); 1030 1031 if ( ! $locked ) { 1032 /* 1033 * Can't write to the lock, and can't read the lock. 1034 * Something broken has happened. 1035 */ 1036 return false; 1037 } 1038 1039 if ( $locked > $now - HOUR_IN_SECONDS ) { 1040 // Lock is not too old: some other process may be upgrading this post. Bail. 1041 return false; 1042 } 1043 1044 // Lock is too old - update it (below) and continue. 1045 } 1046 1047 // If we could get a lock, re-"add" the option to fire all the correct filters. 1048 update_option( $lock, $now ); 1049 1050 reset( $revisions ); 1051 $add_last = true; 1052 1053 do { 1054 $this_revision = current( $revisions ); 1055 $prev_revision = next( $revisions ); 1056 1057 $this_revision_version = _wp_get_post_revision_version( $this_revision ); 1058 1059 // Something terrible happened. 1060 if ( false === $this_revision_version ) { 1061 continue; 1062 } 1063 1064 /* 1065 * 1 is the latest revision version, so we're already up to date. 1066 * No need to add a copy of the post as latest revision. 1067 */ 1068 if ( 0 < $this_revision_version ) { 1069 $add_last = false; 1070 continue; 1071 } 1072 1073 // Always update the revision version. 1074 $update = array( 1075 'post_name' => preg_replace( '/^(\d+-(?:autosave|revision))[\d-]*$/', '$1-v1', $this_revision->post_name ), 1076 ); 1077 1078 /* 1079 * If this revision is the oldest revision of the post, i.e. no $prev_revision, 1080 * the correct post_author is probably $post->post_author, but that's only a good guess. 1081 * Update the revision version only and Leave the author as-is. 1082 */ 1083 if ( $prev_revision ) { 1084 $prev_revision_version = _wp_get_post_revision_version( $prev_revision ); 1085 1086 // If the previous revision is already up to date, it no longer has the information we need :( 1087 if ( $prev_revision_version < 1 ) { 1088 $update['post_author'] = $prev_revision->post_author; 1089 } 1090 } 1091 1092 // Upgrade this revision. 1093 $result = $wpdb->update( $wpdb->posts, $update, array( 'ID' => $this_revision->ID ) ); 1094 1095 if ( $result ) { 1096 wp_cache_delete( $this_revision->ID, 'posts' ); 1097 } 1098 } while ( $prev_revision ); 1099 1100 delete_option( $lock ); 1101 1102 // Add a copy of the post as latest revision. 1103 if ( $add_last ) { 1104 wp_save_post_revision( $post->ID ); 1105 } 1106 1107 return true; 1108 } 1109 1110 /** 1111 * Filters preview post meta retrieval to get values from the autosave. 1112 * 1113 * Filters revisioned meta keys only. 1114 * 1115 * @since 6.4.0 1116 * 1117 * @param mixed $value Meta value to filter. 1118 * @param int $object_id Object ID. 1119 * @param string $meta_key Meta key to filter a value for. 1120 * @param bool $single Whether to return a single value. 1121 * @return mixed Original meta value if the meta key isn't revisioned, the object doesn't exist, 1122 * the post type is a revision or the post ID doesn't match the object ID. 1123 * Otherwise, the revisioned meta value is returned for the preview. 1124 */ 1125 function _wp_preview_meta_filter( $value, $object_id, $meta_key, $single ) { 1126 $post = get_post(); 1127 1128 if ( empty( $post ) 1129 || $post->ID !== $object_id 1130 || ! in_array( $meta_key, wp_post_revision_meta_keys( $post->post_type ), true ) 1131 || 'revision' === $post->post_type 1132 ) { 1133 return $value; 1134 } 1135 1136 $preview = wp_get_post_autosave( $post->ID ); 1137 1138 if ( false === $preview ) { 1139 return $value; 1140 } 1141 1142 return get_post_meta( $preview->ID, $meta_key, $single ); 1143 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Oct 13 08:20:33 2026 | Cross-referenced by PHPXref |