Skip to content

Comments: Notes thread followers, and notifications when a thread is resolved or reopened - #13026

Open
adamsilverstein wants to merge 3 commits into
WordPress:trunkfrom
adamsilverstein:add/notes-thread-followers-events
Open

Comments: Notes thread followers, and notifications when a thread is resolved or reopened#13026
adamsilverstein wants to merge 3 commits into
WordPress:trunkfrom
adamsilverstein:add/notes-thread-followers-events

Conversation

@adamsilverstein

Copy link
Copy Markdown
Member

Backports the Notes thread followers and resolution notification layers from Gutenberg, building on the mention notifications that landed in 7.1 (#12548, Trac #65639).

Trac ticket: to be filed - see the open question at the bottom before it is.

Upstream status: the source Gutenberg PRs are open, WordPress/gutenberg#80281 (followers) and WordPress/gutenberg#81544 (events). This should not land before they do.

What

Two audiences that Notes currently has no way to reach.

Thread followers. Start a thread, reply to it, or get mentioned in it, and you are subscribed to it. Later replies email you, even when they do not name you. Today only the post author (a generic email for every note) and users named in one specific note (the mention email) hear anything at all, so the people already in a conversation miss its replies.

Resolution events. Resolving or reopening a thread posts a child note carrying _wp_note_status meta. That note is bookkeeping, not conversation - the resolve one has no content whatsoever - but the post author is currently emailed the generic "a new note was added" message for it, with an empty body. Followers and the post author now get copy about the event instead, and the generic emails are suppressed for that note.

How

Everything lands in wp-includes/comment.php next to the existing note notification functions, hooked from default-filters.php.

Priority on rest_insert_comment Function Role
9 wp_route_post_author_mention_notification() A mentioned post author gets the mention email; the generic one is suppressed for that note through notify_post_author, the filter it already consults.
10 wp_notify_note_mentions() Unchanged, from 7.1.
11 wp_notify_note_followers() Followers hear about a new reply, minus the users it mentions, its author, and the post author when they are already being emailed.
11 wp_notify_new_mentions_on_note_update() An edit that adds a mention notifies the newly mentioned user; existing followers are not re-notified.
12 wp_maintain_note_followers() Subscribes the author and mentioned users. Runs last, so "existing followers" means "before this note".

Resolution events run on rest_after_insert_comment (wp_notify_note_event()). WP_REST_Comments_Controller saves comment meta between the two REST comment actions, so on the later one _wp_note_status is stored and the event is recognized whatever the client sent. For the same reason wp_get_note_status_event() consults the request first and the stored meta second, which is what lets the earlier hooks recognize a system note before its meta exists.

Followers are stored as one _wp_note_followers comment-meta row per user on the thread's top-level note, so concurrent replies subscribe users independently instead of racing over a single array value. Bookkeeping is deliberately not gated on wp_notes_notify: that option governs whether email is sent, not who is participating, so turning notifications on later works for threads that already exist.

Every email carries a tokenized unfollow link - wp_hash() over thread and user, compared with hash_equals(), handled through admin-post.php including the nopriv variant so it works logged out, like any email unsubscribe. It never expires, so links in old email keep working.

One email per user per note insert

  1. Actor - never emailed about their own note or their own resolve.
  2. Mention - a mentioned user, post author included, gets the mention email and nothing else.
  3. Event - remaining followers and the post author get the event email.
  4. Follower, then the generic post-author email - unchanged for ordinary replies.

Security

Every recipient must pass user_can( $user_id, 'edit_comment', $note_id ) before any note content is composed for them. That is the bar WP_REST_Comments_Controller::check_read_permission() already applies to reading a note; a read_post check would leak internal notes to, say, subscribers on a public post. Emails pin Content-Type: text/plain explicitly so a filtered default cannot turn a body into HTML, compose in the recipient's locale, and build the editor link as the recipient.

Extensibility

wp_note_notification_sent fires once per addressed recipient with the user ID, the note, why they were notified (mention, post_author_mention, follower, resolved, reopen), and whether wp_mail() accepted it. Email is the only channel here; this is the seam another channel - or a future notifications API - can consume without reimplementing recipient resolution. Recipient, subject, and body filters are provided per notification type.

Testing instructions

npm run test:php -- --group notes

tests/phpunit/tests/comment/wpNotifyNoteFollowers.php and wpNotifyNoteEvent.php cover subscription bookkeeping, the dedupe rules, the visibility gate, the unfollow token, the meta-timing behavior, and the suppression of both generic emails. The event tests dispatch real REST requests so the controller's own hook order and meta save are part of what is being tested.

Open question

wp_new_comment_via_rest_notify_postauthor() gains optional $request and $creating parameters here so it can recognize a thread-event note and step aside. The alternative is leaving it untouched and suppressing it through the notify_post_author filter, which is what the Gutenberg plugin does, since it has to work on older versions. The direct check reads better in core, but happy to switch if the added parameters are unwelcome on an existing function.

…them of replies.

Notes reach only the post author, who is emailed about every note, and
users named in one specific note. Reply to a thread and the people
already in it hear nothing unless the reply mentions them by name.

Subscribes the users who start, reply to, or are mentioned in a thread
as followers of its top-level note, stored one meta row per user so
concurrent replies cannot lose an update, and emails them about later
replies. Every email carries a tokenized unfollow link that works
logged out. Bookkeeping is not gated on the notification option, so
enabling notifications later works for threads that already exist.
…eopened.

Resolving or reopening a thread posts a child note carrying the new
status as comment meta. That note is bookkeeping rather than
conversation, and the resolve one has no content at all, so the post
author was emailed a generic new-note message with an empty body and
followers heard nothing about the outcome.

Announces the event to the thread's followers and the post author, and
leaves the generic email alone for that note. The controller saves
comment meta between rest_insert_comment and rest_after_insert_comment,
so the notifier runs on the later action where the status is stored,
while the earlier handlers recognize the note from the request.
@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.

@adamsilverstein
adamsilverstein marked this pull request as ready for review August 13, 2026 19:17
@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 adamsilverstein.

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

Trac Ticket Missing

This pull request is missing a link to a Trac ticket. For a contribution to be considered, there must be a corresponding ticket in Trac.

To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description. More information about contributing to WordPress on GitHub can be found in the Core Handbook.

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