Skip to content

Comments: Make admin comment dimming comment-type aware (Trac #35214, Stage 3) - #57

Open
adamsilverstein wants to merge 10 commits into
feature/comment-type-moderation-restfrom
feature/comment-type-moderation-admin
Open

adamsilverstein wants to merge 10 commits into
feature/comment-type-moderation-restfrom
feature/comment-type-moderation-admin

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Jun 25, 2026 •

Copy link
Copy Markdown
Owner

Stack: Custom Comment Types (#35214)

PR Change Builds on
#12311 register_comment_type() registration API trunk
#51 REST /wp/v2/comment-types endpoint WordPress#12311
#52 Capabilities object (advisory) WordPress#12311
#55 Enforce type capabilities in map_meta_cap() #52
#56 Type-aware REST moderation #55
👉 #57 Type-aware admin comment dimming (this PR) #56
#53 render_callback display callback WordPress#12311
#54 is_ping ping grouping #53
#12310 default_excluded_comment_types filter trunk
#58 wp_update_comment_counts() recount helper WordPress#12310

WordPress#12311 and WordPress#12310 are on WordPress/wordpress-develop; the rest are fork PRs targeting their parent branch and will retarget to trunk as their base lands.

Claude re-checked the audit here, summary below:

What

wp_ajax_dim_comment() (approve/unapprove from the comments list table) now falls back to the per-comment moderate_comment meta capability instead of the global moderate_comments primitive. A moderator of a custom comment type can then approve and unapprove that type's comments from the list table, and a global moderator no longer gets a pass on a type with its own capability model.

Why

Follow up to #56, same idea on the admin side. Before this change a moderate_reviews user couldn't dim review comments, while any moderate_comments user could dim every type. See https://core.trac.wordpress.org/ticket/35214

For the default capability model moderate_comment resolves to moderate_comments, so comment, pingback, trackback, note, reaction and unregistered types behave exactly as before.

How

  • src/wp-admin/includes/ajax-actions.php - wp_ajax_dim_comment() checks edit_comment first, as before, then current_user_can( 'moderate_comment', $comment->comment_ID ).

This looks like the last admin-side per-comment moderation gate on the bare primitive. wp-admin/comment.php, edit-comments.php, the other AJAX handlers, list table row actions and XML-RPC wp.editComment already use edit_comment, which #55 made type-aware. The remaining moderate_comments checks are either collection-level (list table bulk actions, Empty Spam/Trash, XML-RPC wp.getComments status filter) or creation-time checks on the commenter (flood bypass, moderator auto-approve), so there's no single comment to check against.

Testing

tests/phpunit/tests/ajax/wpAjaxDimComment.php registers a review type with capability_type => review and covers:

  • A moderate_reviews user can dim (approve) and un-dim (unapprove) a review comment.
  • A global moderate_comments user without the type's caps gets -1 on a review comment.
  • A review moderator gets -1 on a default comment.
  • A global moderator can still dim unregistered (webmention), note and reaction comments.

Run:

vendor/bin/phpunit --group ajax --filter Tests_Ajax_wpAjaxDimComment
vendor/bin/phpunit --group comment
vendor/bin/phpunit --group capabilities

To try it by hand, register a review type with capability_type => review, give a subscriber moderate_reviews, edit_others_reviews and edit_posts so they can reach the comments screen, then approve/unapprove a review comment from the list.

Out of scope / follow-ups

  • Creation-time checks (wp_check_comment_flood() bypass and wp_allow_comment() auto-approve) still look at moderate_comments for the commenting user. Worth a look once comment creation knows about types.

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

AI Use

Claude Code wrote the change, tests, and this description. I will review and test.

Summary by CodeRabbit

  • Bug Fixes
    • Comment moderation now checks permissions for the specific comment type. Moderators with the appropriate type-specific capability can approve or unapprove those comments, while permissions for standard comments and other types remain separate.

`wp_ajax_dim_comment()` fell back to the global `moderate_comments`
primitive when a user could not `edit_comment` the target comment, so a
moderator of a custom comment type could not approve or unapprove comments
of that type from the admin list table.

Route the fallback through the per-comment `moderate_comment` meta
capability. For comment types using the default capability model this
resolves to `moderate_comments` (behavior unchanged); a type with its own
capabilities is gated by its own moderation primitive.

This is the last per-comment moderation gate in the admin AJAX path. The
remaining `moderate_comments` checks in the list table (bulk action
availability, the Empty Spam/Trash button) and in XML-RPC `wp.getComments`
are collection-level, not per-comment, and intentionally stay global.

See #35214.
Drive `wp_ajax_dim_comment()` through the Ajax test harness: a moderator of
an independent `review` type can dim its comments, while a global
`moderate_comments` moderator without the type's capabilities is denied. The
existing administrator and subscriber tests continue to pass, confirming the
default model is unchanged.

See #35214.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The dim-comment AJAX action now checks edit_comment or moderate_comment for the target comment. Tests cover approval and unapproval permissions for registered, unregistered, and default comment types.

Changes: Comment moderation

Layer / File(s) Summary
Dim-comment authorization and coverage
src/wp-admin/includes/ajax-actions.php, tests/phpunit/tests/ajax/wpAjaxDimComment.php
The AJAX action uses the comment-specific moderate_comment capability instead of the general moderate_comments capability as its alternative authorization check. The tests cover type-specific moderation, global moderation, and default comment behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Feature

Suggested reviewers: westonruter

Merge Risk: Minimal · up to ddd60

The authorization change appears ready to merge after the reported test commands are run; their results were not supplied.

Security architecture risk: Low · up to ddd60

The change preserves comment-specific permission checks and nonce protection. However, newly authorized type-only moderators also receive broader moderation-count metadata through the existing response handler. The exposure is limited to aggregate counts, but its intended visibility should be clarified.

Retained concerns

  • Low · security · observed: Newly authorized type-only moderators inherit unscoped moderation-count visibility. The response exposes site-wide pending counts and can return totals for an unrelated post selected through the submitted URL, without checking authority over that post. The same moderation-only principal was denied this path at the PR base. This broadens metadata access beyond the target-type operation; whether that sharing is intentional remains unresolved.
Security review details

Security Blast Radius

  • observed — The moderation primitive is type-wide, not limited to comments authored by the caller or posts the caller can edit. The mutation targets the authorized comment, but response metadata includes broader site-level counts and potentially unrelated-post totals.

Security Findings and Attack Paths

  • observed — A subscriber granted only moderate_reviews can now moderate an eligible review comment with a valid nonce and receive the helper’s global pending count. Submitted pagination fields and an unrelated p parameter can also select another post’s aggregate totals. The inspected response returns aggregate metadata, not unrelated comment bodies; the confidentiality policy for these counts is unspecified.

Trust Boundaries and Controls

  • observed — Capability selection uses the stored comment type rather than a submitted type. Missing comment arguments or nonexistent comments map to do_not_allow. The action retains its comment-bound nonce before writes; its earlier matching-status response performs no mutation.

Resilience and Maintainability Implications

  • inferred — Reusing the existing status writer preserves the prior failure and recovery behavior rather than introducing a new multi-step persistence protocol. Concurrency and interrupted-write guarantees were not demonstrated dynamically, and this PR does not add atomicity or retry protections.

Hardening Proposals

  • proposed — Define whether aggregate moderation metadata is shared with type-only moderators. If it should follow delegated authority, restrict response counts to authorized types and posts instead of accepting an unrelated post selector; otherwise make the broader metadata contract explicit.
Pre-merge checks: Passed 5

Check name Status Explanation
Title check Passed The title names the main change: admin comment dimming becomes comment-type aware. The raw summary confirms the change to the wp_ajax_dim_comment() capability check and the added type-aware tests. The…
Description check Passed The description follows the repository template. It explains what changes, why, and how. It lists the testing approach and run commands, and it links the Trac ticket. It also includes the AI use discl…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: admin comment dimming becomes comment-type aware. The raw summary confirms the change to the wp_ajax_dim_comment() capability check and the added type-aware tests. The title is specific, concise, and includes the Trac reference and stage marker, which are acceptable.

Full details: Description check

Explanation

The description follows the repository template. It explains what changes, why, and how. It lists the testing approach and run commands, and it links the Trac ticket. It also includes the AI use disclosure, which says Claude Code wrote the change. The template's example disclosure asks for tool, model, and extent details, and the description gives only a short statement. The stack table and the copied audit text add material that does not belong in a template-based description. The description does not report test results. The Trac ticket link is present, but the template's placeholder comment was replaced with a URL, so the ticket requirement is met. The main gaps are the thin AI disclosure and the missing test results.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 5 functions across 2 files.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks the comment gate,
For each type, it tests the state.
Reviewers approve, then set it right,
The proper checks now guide the byte.
Soft paws hop off into the night.

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b225fedf-1c25-4f76-b6fe-aa42271036c5
Commits

Reviewing files that changed from the base of the PR and between b4f9139 and ddd60bc.

Files selected for processing: 2

  • src/wp-admin/includes/ajax-actions.php
  • tests/phpunit/tests/ajax/wpAjaxDimComment.php

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

- Add the missing @SInCE 7.1.0 changelog entry to wp_ajax_dim_comment()
  and an inline rationale at the rerouted capability check, making the
  pattern self-documenting at every rerouted site.
- Tests: the un-dim direction (dimming an approved comment unapproves
  it) for a type moderator, the inverse denial (a type moderator cannot
  dim a default-model comment), and the unregistered-type back-compat
  pin (a global moderator retains control of never-registered types via
  AJAX). Drop the cleanup-only unregister call now handled by the test
  framework.
@github-actions

github-actions Bot commented Nov 8, 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.

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.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

The `@since` entry named 7.1.0, but the change ships in 7.2.0, and the
inline comment restated what the meta capability mapping already
documents. No behavior change.

See #35214.
Trunk added the `reaction` type next to `note`. Neither registers its own
capabilities, so `moderate_comment` resolves to `moderate_comments` and
a global moderator can still dim them, as before. Pins that, and adds
native types to the new helpers and tests so they pass PHPStan level 10.

See #35214.
Trunk is in the 7.2 beta, so these additions can no longer ship in 7.2.0.
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