Skip to content

Revisions: render the author for users who cannot edit users - #82306

Closed
davidsword wants to merge 2 commits into
WordPress:trunkfrom
davidsword:fix/revision-author-name-for-non-admins
Closed

davidsword wants to merge 2 commits into
WordPress:trunkfrom
davidsword:fix/revision-author-name-for-non-admins

Conversation

@davidsword

@davidsword davidsword commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor

What?

Closes #82305

Renders a revision's author for users who cannot edit_users, by supplying the author's display name and avatars with the revision instead of resolving them through a separate user request.

Why?

AuthorView resolves a revision's author with getEntityRecord( 'root', 'user', authorId ). The root/user entity is fixed to context: 'edit', and WP_REST_Users_Controller::get_item_permissions_check() refuses that context unless the caller can edit_user the target, which maps to the administrator-only edit_users capability.

Every other role therefore gets a 403 rest_forbidden_context and the row renders as an empty name next to the fallback person icon. Viewers still see their own row, because the controller short-circuits when the caller is the target. Captured as an editor-role user on one page load:

200  /wp/v2/users/2?context=view&_fields=id,name   <- post author panel, fine
403  /wp/v2/users/2?context=edit                   <- revisions sidebar, blocked
200  /wp/v2/users/3?context=edit                   <- self, allowed

The obvious fix is to pass { context: 'view' } at the call site, and it does work for the common case. I did not take that route, because a second gate in the same permission check (count_user_posts()) still refuses a user who has never published, even in view context. Any approach that resolves the author through /wp/v2/users/<id> hits that gate, _embed included, since context=embed only skips the first one. That leaves a blank name for a revision authored by someone with no published posts.

How?

Supplies the author with the revision, mirroring WP_REST_Comments_Controller, which has exposed author_name and author_avatar_urls directly for exactly this reason since 4.7. The server decides what to expose, the client never asks about a user, and the permission question does not arise.

  • Gutenberg_REST_Revisions_Controller_7_2 (new, in lib/compat/wordpress-7.2/) extends Gutenberg_REST_Revisions_Controller and exposes author_name and author_avatar_urls alongside the existing numeric author, plus a get_item_schema() override declaring them. It calls the parent's prepare_item_for_response() and adds the two fields to the response rather than duplicating the method, so the 7.0 nested _fields handling stays in one place. The avatar block follows the comments controller, including the show_avatars guard and the rest_get_avatar_sizes() loop.
  • lib/compat/wordpress-7.2/rest-api.php points revisions_rest_controller_class at the new class at priority 11, superseding the 7.0 override while leaving a controller a site set itself alone.
  • buildRevisionsPageQuery requests the two new fields.
  • AuthorView prefers them and skips the fetch when they are present, falling back to the existing fetch and _embedded paths otherwise, so it behaves unchanged wherever the fields are absent.

This also removes one REST request per distinct revision author. In the repro below, the revisions sidebar goes from 3 user requests to 0.

Two incidental fixes:

  • Drops a comment claiming the fetch is free because getElements already populated the store. Records are keyed by the query's context: getElements uses getEntityRecords with context: 'view' and writes the "view" bucket, while AuthorView reads the "default" bucket. They never share an entry, so the request always happened. getElements also does not run in the revisions inspector at all.
  • Adds author_name and author_avatar_urls to BasePost.

Testing Instructions

  1. Fresh WordPress with this branch active.
  2. Create an administrator (alice) and an editor (bob).
  3. Publish a post as alice, then edit and save it twice more as alice.
  4. Edit and save the same post once as bob.
  5. As bob, open the post, enter the revisions view, and open the settings sidebar.
  6. Every revision should show its author's name and avatar. On trunk, alice's two revisions show an empty name and a generic person icon.
  7. Repeat as alice to confirm no regression for administrators.
  8. Optionally add a user with no published posts, have them save a revision, and confirm their name renders too. That case is not fixed by switching the call site to context: 'view'.

Verified against WordPress 7.2-alpha with an editor-role viewer, four revisions by three authors, one of whom has no published posts:

Author names rendered /wp/v2/users requests
Before ["", "Bob Editor", "", ""] 403 alice, 403 carol, 200 self
After ["Carol NoPosts", "Bob Editor", "Alice Admin", "Alice Admin"] none

Also checked: view/edit/embed contexts, _fields still honoured when the new fields are not requested, title.raw sub-field filtering unaffected, show_avatars = 0 omits author_avatar_urls from both the response and the schema, and HEAD still returns an empty body.

Testing Instructions for Keyboard

No UI or interaction changes. The revision list is reachable and operable exactly as before; only the rendered author text and avatar differ.

Screenshots or screencast

Before
before

After
after

The revisions inspector renders each revision's author with AuthorView,
which resolves the user through getEntityRecord( 'root', 'user', id ).
The root/user entity is fixed to context=edit, and
WP_REST_Users_Controller::get_item_permissions_check() refuses that
context unless the caller can edit_user the target. Any user without
edit_users therefore receives a 403, and the row renders with an empty
name and the fallback avatar icon. Viewers still see their own row,
because the controller short-circuits when the caller is the target.

Rather than widen who may read a user, supply the author alongside the
revision. Gutenberg_REST_Revisions_Controller now exposes author_name
and author_avatar_urls next to the numeric author, mirroring
WP_REST_Comments_Controller, and AuthorView prefers them and skips the
fetch when they are present. That also removes one request per distinct
revision author.

Drops a comment in AuthorView claiming the record is already in the
store from the field's getElements function. getElements populates the
'view' context bucket through getEntityRecords, while AuthorView reads
the default bucket through getEntityRecord, so the two never share a
cache entry and the fetch always happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026 •

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.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: davidsword <davidsword@git.wordpress.org>
Co-authored-by: ntsekouras <ntsekouras@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>

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

@github-actions github-actions Bot added [Package] Editor /packages/editor [Package] Fields /packages/fields labels Sep 1, 2026
@ramonjd

ramonjd commented Sep 2, 2026

Copy link
Copy Markdown
Member

Thanks for the issue + PR!!

On file placement: the change extends Gutenberg_REST_Revisions_Controller, which lives in lib/compat/wordpress-7.0/. These are new fields that have not shipped in any release, so they may belong in a version-suffixed subclass under a newer compat directory instead, following Gutenberg_REST_Attachments_Controller_7_1. Happy to move it, just point me at the right target.

That's the usual method. It can be a pain, but it keeps compat features separate. When Gutenberg drops support for older versions of WordPress, the older folders can be mostly deleted with the current compat files still in effect.

The next folder would be /7.2

In terms of reviewing I'd lean on @ntsekouras or @ellatrix as they've recently been looking at revisions.

The author fields were added to Gutenberg_REST_Revisions_Controller in
lib/compat/wordpress-7.0/. They have not shipped in any release, so they
belong in a version-suffixed subclass under the compat directory for the
release they target, following Gutenberg_REST_Attachments_Controller_7_1.

Restores the 7.0 controller and adds Gutenberg_REST_Revisions_Controller_7_2
in lib/compat/wordpress-7.2/, which extends it. Rather than duplicating the
whole of prepare_item_for_response(), the subclass calls the parent and adds
author_name and author_avatar_urls to the response, so the 7.0 nested _fields
handling stays in one place.

lib/compat/wordpress-7.2/rest-api.php points revisions_rest_controller_class
at the new class at priority 11, superseding the 7.0 override while leaving a
controller a site set itself alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@davidsword

Copy link
Copy Markdown
Contributor Author

Thanks @ramonjd! Moved it to Gutenberg_REST_Revisions_Controller_7_2 in lib/compat/wordpress-7.2/, with the 7.0 controller restored, and verified the fix against WordPress 7.2-alpha as an editor-role user.

@t-hamano t-hamano added [Type] Bug An existing feature does not function as intended [Feature] History History, undo, redo, revisions, autosave. labels Sep 3, 2026

@ntsekouras ntsekouras left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @davidsword! I've left some comments, but the fix works well for me as an editor. Can you rebase?

*/
export interface BasePost extends CommonPost {
author_name?: string;
author_avatar_urls?: Record< number, string >;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can match the type as above:

Partial< User< 'view' >[ 'avatar_urls' ] >

* @see Gutenberg_REST_Revisions_Controller
* @see WP_REST_Comments_Controller
*/
class Gutenberg_REST_Revisions_Controller_7_2 extends Gutenberg_REST_Revisions_Controller {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why we need a subclass here.. Wouldn't register_rest_field on {post_type}-revision do the same with less code?

// WP_REST_Users_Controller only allows that context for users who can
// `edit_user` the target, so everyone else receives a 403 and no name.
const authorId = item?.author;
const inlineAuthorName = item?.author_name;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the changes aren't big here, I'm wondering if it's better to have a new revisionAuthor field. Wdyt @oandregal?

@ntsekouras

Copy link
Copy Markdown
Contributor

👋 - While reviewing this PR I noticed that the author field would remain blank for users that cannot edit users (e.g. editor roles).

When I started fixing the above as a separate complementary PR, I remembered about who:authors, which bypass the restriction of getEntityRecord fetching authors without published posts to anyone who can't list users.

I opened a PR for that here: #83498. Can you test that? You'll get the props if that supersedes this one.

@youknowriad

Copy link
Copy Markdown
Contributor

Thank you for the thorough investigation and fix, @davidsword! The author rendering for users without edit_users was fixed in #83498 with a lighter client-side approach (who: 'authors'), which closed #82305, and you're credited there. Closing this as superseded. Feel free to reopen if you think the REST-side fields are still needed.

@youknowriad youknowriad closed this Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] History History, undo, redo, revisions, autosave. [Package] Editor /packages/editor [Package] Fields /packages/fields [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Revisions: author name is blank for users who cannot edit users

5 participants