Make WordPress Core

Changeset 62311


Ignore:
Timestamp:
05/06/2026 09:55:18 PM (4 days ago)
Author:
dmsnell
Message:

RTC: Promote auto-draft posts on first autosave for collaborative sessions to avoid post loss.

When an RTC session in the editor saves edits on a draft post, they always save into an autosave revision. This prevents a sync but where storing to the post itself would lead to duplicate edits. This was set in wordpress/gutenberg commit 9df142b839320316b406ee1a02e23704d42f8719. However, there is an exception to this rule: the first session to save edits needs to promote the autosave revision into a real draft post, if a post doesn’t exist. The reason is that WordPress hides autosaves.

That is, multiple people could be editing a draft post and then once they close it, never be able to find it again. While the data is still in the database, this is a loss of the post from a practical standpoint.

This change introduces an exception where the first time a collaborative session is saving draft edits, the autodraft is promoted to a real draft post, making it visible in the post list.

AI Disclaimer: This bug was detected in a fuzzing system built by AI models and the fix was first proposed by an AI model.

Developed in: https://github.com/WordPress/gutenberg/pull/77865
Discussed in: https://core.trac.wordpress.org/ticket/65138
See also: https://github.com/WordPress/gutenberg/pull/77716

Follow-up to [61680].
Props danluu, dmsnell.
See #65138.

Location:
trunk
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php

    r62075 r62311  
    230230        }
    231231
    232         $post_lock = wp_check_post_lock( $post->ID );
    233         $is_draft  = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;
     232        $post_lock_is_active      = wp_check_post_lock( $post->ID );
     233        $is_auto_draft            = 'auto-draft' === $post->post_status;
     234        $is_draft                 = 'draft' === $post->post_status || $is_auto_draft;
     235        $is_collaboration_enabled = wp_is_collaboration_enabled();
    234236
    235237        /*
    236          * In the context of real-time collaboration, all peers are effectively
    237          * authors and we don't want to vary behavior based on whether they are the
    238          * original author. Always target an autosave revision.
     238         * When a post is still in draft form, updates from the author can directly update the post.
     239         * Other autosaves must be stored as per-user autosave revisions.
    239240         *
    240          * This avoids the following issue when real-time collaboration is enabled:
     241         * When RTC is active, however, regular draft autosaves must not update the parent post directly.
     242         * Since all peers are sharing a persisted editing state (a shared CRDT), it’s important that
     243         * they all store updates in a revision. If edits were applied to the post, then upon the next
     244         * editor reload, it would appear as though the post had been updated externally, and those same
     245         * changes would be re-applied to the CRDT, duplicating the edits.
    241246         *
    242          * - Autosaves from the original author (if they have the post lock) will
    243          *   target the saved post.
    244          *
    245          * - Autosaves from other users are applied to a post revision.
    246          *
    247          * - If any user reloads a post, they load changes from the author's autosave.
    248          *
    249          * - The saved post has now diverged from the persisted CRDT document. The
    250          *   content (and/or title or excerpt) are now "ahead" of the persisted CRDT
    251          *   document.
    252          *
    253          * - When the persisted CRDT document is loaded, a diff is computed against
    254          *   the saved post. This diff is then applied to the in-memory CRDT
    255          *   document, which can lead to duplicate inserts or deletions.
     247         * The one caveat for RTC is that the first peer to store an edit must promote an auto-draft
     248         * into a real draft post. If this doesn’t happen then the peers may continue to make edits
     249         * but the draft will be lost, as auto-drafts are not listed in post views.
    256250         */
    257         $is_collaboration_enabled = wp_is_collaboration_enabled();
    258 
    259         if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock && ! $is_collaboration_enabled ) {
    260             /*
    261              * Draft posts for the same author: autosaving updates the post and does not create a revision.
    262              * Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
    263              */
     251        $can_update_author_draft_post = (
     252            $is_draft &&
     253            (int) $post->post_author === $user_id &&
     254            ! $is_collaboration_enabled
     255        );
     256
     257        $can_promote_auto_draft_post = (
     258            $is_auto_draft &&
     259            $is_collaboration_enabled &&
     260            current_user_can( 'edit_post', $post->ID )
     261        );
     262
     263        $should_update_parent_draft_post = (
     264            $can_promote_auto_draft_post ||
     265            ( ! $post_lock_is_active && $can_update_author_draft_post )
     266        );
     267
     268        if ( $should_update_parent_draft_post ) {
    264269            $autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
    265270        } else {
    266             // Non-draft posts: create or update the post autosave. Pass the meta data.
    267271            $autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
    268272        }
Note: See TracChangeset for help on using the changeset viewer.