Skip to content

Editor: Add Heartbeat post locking for Site Editor templates#11805

Open
itzmekhokan wants to merge 6 commits into
WordPress:trunkfrom
itzmekhokan:feature/65126-site-editor-post-lock
Open

Editor: Add Heartbeat post locking for Site Editor templates#11805
itzmekhokan wants to merge 6 commits into
WordPress:trunkfrom
itzmekhokan:feature/65126-site-editor-post-lock

Conversation

@itzmekhokan
Copy link
Copy Markdown

Implements post locking for Site Editor template entities (wp_template, wp_template_part) using the same Heartbeat flow as the classic editor: wp-refresh-post-lock on send, existing wp_refresh_post_lock handling on the server, and the shared #post-lock-dialog markup from _admin_notice_post_locked().

Trac ticket: https://core.trac.wordpress.org/ticket/65126

Approach

  • REST: On rest_prepare_wp_template / rest_prepare_wp_template_part with context=edit, acquire a lock via wp_set_post_lock() when allowed.
  • Opt-out: New filter wp_apply_site_editor_post_lock (documented in wp-admin/includes/post.php) so real-time collaboration can disable locking consistently.
  • Site Editor screen: Resolve the edited template from URL params; handle get-post-lock + nonce for take-over; enqueue Heartbeat + site-editor-post-lock and localize lock/takeover data.
  • JS: site-editor-post-lock.js tracks the active template via core/edit-site, sends the same Heartbeat payload as wp-admin/js/post.js, and shows/refreshes the lock dialog on tick.

…n hook for templates.

Adds wp_set_site_editor_post_lock_on_rest_prepare(), a small helper hooked to
rest_prepare_wp_template and rest_prepare_wp_template_part that calls
wp_set_post_lock() when a template is fetched with context=edit. The classic
editor acquires its lock during edit-screen render in edit-form-advanced.php;
Site Editor templates load over the REST API, so this fills the equivalent
acquisition point for wp_template and wp_template_part.

A new wp_apply_site_editor_post_lock filter lets real-time collaboration
plugins (or future core RTC) opt the entire lock pipeline out with a single
return false.

No new Heartbeat handler is introduced; the existing wp_refresh_post_lock()
in wp-admin/includes/misc.php is already post-type-agnostic and will be
driven from the Site Editor in a follow-up commit.

See #65126.
Resolves the current wp_template or wp_template_part post when site-editor.php
loads with either the legacy ?postId= argument or the canonical
?p=/{post_type}/{post_id} form, and adds a get-post-lock=1 handler that
mirrors the classic wp-admin/post.php take-over flow:

  - Verifies the lock-post_{id} nonce via check_admin_referer().
  - Respects wp_apply_site_editor_post_lock so RTC plugins keep their opt-out.
  - Calls wp_set_post_lock() and redirects back to the editor without the
    take-over query args.

Theme-supplied templates that have no numeric post ID yet are intentionally
skipped; locking only starts once a real WP_Post exists in the database.

See #65126.
Hooks _admin_notice_post_locked() to admin_footer when site-editor.php has
resolved a wp_template or wp_template_part post and the
wp_apply_site_editor_post_lock filter has not opted out. The existing
notification-dialog markup from wp-admin/includes/post.php is reused as-is;
the JS bridge introduced in a follow-up commit reveals it on a Heartbeat
lock_error response and points the "Take over" button at the get-post-lock
handler added in the previous commit.

$GLOBALS['post'] is swapped to the resolved template post for the duration
of the callback so _admin_notice_post_locked() resolves the correct
WP_Post, mirroring how wp-admin/edit-form-advanced.php prepares the global
before registering the same hook.

See #65126.
Registers the new site-editor-post-lock script handle in wp_default_scripts()
next to its closest neighbour, post. The script depends on jquery, heartbeat,
wp-data, and wp-i18n.

On site-editor.php:

  - Acquires the lock on initial render when no other user holds it, using
    the wp_check_post_lock() then wp_set_post_lock() pattern from
    wp-admin/post.php:191-192.
  - Enqueues heartbeat and the new bridge script only when a template post
    has been resolved and the wp_apply_site_editor_post_lock filter is
    truthy.
  - Localizes wpSiteEditorPostLockL10n with the post ID, post type, current
    lock string, and a pre-built take-over URL that targets the canonical
    ?p=/{post_type}/{post_id} form so the round-trip resolution in the
    take-over handler is unambiguous.

The matching JS bridge that consumes this data is added in the next commit.

See #65126.
The bridge is a thin classic-jQuery layer that connects the Site Editor to
the existing wp-refresh-post-lock Heartbeat workflow:

  - Tracks the currently edited template entity via the core/edit-site data
    store so client-side navigation between templates is reflected in the
    Heartbeat payload.
  - On heartbeat-send, populates data['wp-refresh-post-lock'] with the
    active post ID and the most recent lock string. This is the same payload
    shape wp-admin/js/post.js sends from the classic editor, so the server
    handler wp_refresh_post_lock() needs no changes.
  - On heartbeat-tick, reveals the server-rendered #post-lock-dialog on
    lock_error and updates the cached lock token on new_lock.
  - Rewrites the dialog's "Take over" anchor to the canonical
    ?p=/{post_type}/{post_id} take-over URL so the server resolver in
    site-editor.php picks it up unambiguously.

Bails out immediately when wpSiteEditorPostLockL10n.enabled is false, which
is how the wp_apply_site_editor_post_lock filter propagates RTC opt-outs to
the client side.

See #65126.
Adds Tests_Admin_WpSetSiteEditorPostLockOnRestPrepare covering:

  - Both rest_prepare_wp_template and rest_prepare_wp_template_part have the
    new callback registered at priority 10.
  - Edit-context responses acquire the lock for wp_template and
    wp_template_part.
  - View-context responses do not acquire any lock.
  - wp_apply_site_editor_post_lock returning false skips acquisition.
  - The filter receives the resolved WP_Post as its second argument.
  - Edit-context responses refresh the lock for the current user when
    another user had previously held it.

The function is invoked directly rather than going through a full REST
round-trip so the assertions are focused on the filter behaviour itself
(matching the style of the rest of tests/phpunit/tests/admin/).

See #65126.
@github-actions
Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props khokansardar.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions
Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant