Skip to content

Comments: Enforce registered comment type capabilities in map_meta_cap() (Trac #35214)#55

Open
adamsilverstein wants to merge 4 commits into
feature/comment-type-capsfrom
feature/comment-type-cap-enforcement
Open

Comments: Enforce registered comment type capabilities in map_meta_cap() (Trac #35214)#55
adamsilverstein wants to merge 4 commits into
feature/comment-type-capsfrom
feature/comment-type-cap-enforcement

Conversation

@adamsilverstein

Copy link
Copy Markdown
Owner

Summary

Follow-up to PR #52 (the advisory cap object), implementing Stages 1-2 of the enforcement plan: wiring WP_Comment_Type::$cap into map_meta_cap(). Per discussion this supports both models.

Before this, the cap object was advisory: map_meta_cap() only knew edit_comment (derived from the parent post) and all moderation was a bare moderate_comments primitive check at each call site.

What changed (map_meta_cap() only - no call-site reroutes yet)

  • edit_comment generalized. A comment whose type uses the default comment capability model (its mapped edit_comment is the generic meta cap) still derives edit permission from the parent post - byte-for-byte unchanged, orphaned-comment fallback included. A type that opts into its own caps maps to edit_comments (own) / edit_others_comments (others) by user_id, mirroring edit_post.
  • delete_comment added. Default model follows the parent post (matching how core's delete flows gate on edit_comment today); independent model maps to delete_comments.
  • moderate_comment added. Default model requires the global moderate_comments primitive; independent model maps to the type's moderate_comments (e.g. moderate_reviews).

Design: supports both, maps don't grant

  • Default capability_type => 'comment' is unchanged. Built-in comment/pingback/trackback/note all resolve exactly as today, so no role (admin included) is affected.
  • Independent types are gated by their own primitives with no silent fallback. Sites grant the primitives to roles, exactly like custom post types. The default model is detected by the type's mapped meta cap still being the generic string (e.g. cap->edit_comment === 'edit_comment'), which also avoids any self-recursion.

Deferred

  • read_comment - the Comments: Add a capabilities object to comment types #52 cap object has no read primitive, and approved comments are publicly readable, so gating reads needs its own design.
  • Call-site rerouting (Stage 3) - wp_set_comment_status(), admin comment.php + list-table, REST permission callbacks, AJAX handlers - lands as small per-surface PRs so each gets a focused security review. This PR is intentionally map_meta_cap()-only.

Testing

New tests/phpunit/tests/comment/commentCapabilities.php:

  • Default model parity: edit/delete follow the parent post (incl. orphaned fallback), moderation requires moderate_comments.
  • Independent review type: a moderate_reviews user can moderate reviews but not default comments, and an administrator (has moderate_comments, not moderate_reviews) is the exact inverse - proving independence and the no-auto-grant rule.
  • Own-vs-others edit split and independent delete.

Registered the two new meta caps in the core meta-cap coverage list (tests/phpunit/tests/user/capabilities.php). Full --group comment --group capabilities suite passes (797 tests). PHPCS + PHPStan clean.

Stacking

Based on feature/comment-type-caps (#52). Retarget to trunk once the registration (#12311) and cap-object PRs land.

See #35214.

Give `WP_Comment_Type` a `cap` object built from new `capability_type` and
`capabilities` registration arguments, modeled on `WP_Post_Type` and
`get_post_type_capabilities()`. A new `get_comment_type_capabilities()` helper
builds the capability strings from the `capability_type` base (default
'comment'), so a registered type can describe its own read, edit, delete, and
moderate capabilities.

This is advisory metadata only: `map_meta_cap()` is intentionally not changed,
so there is no behavior change. The built-in `comment` type resolves to the
existing `edit_comment` and `moderate_comments` capabilities, preserving current
behavior. Enforcing per-type capabilities through `map_meta_cap()` is left to a
follow-up so the capability model can be agreed on first.

See #35214.
Cover the new `capability_type`/`capabilities` arguments and the
`get_comment_type_capabilities()` helper: default and custom capability types,
array capability types with explicit plurals, the `capabilities` override, that
the input `capabilities` array is not retained as a property, and that the
built-in `comment` type stays backward compatible with the existing core
capabilities.

See #35214.
…ap()`.

The capabilities object added for comment types was advisory only;
`map_meta_cap()` knew a single comment cap (`edit_comment`, derived from
the parent post) and all moderation ran through a bare `moderate_comments`
primitive check.

Wire the cap object into `map_meta_cap()` so a registered type can enforce
its own permissions, supporting both models:

- Types using the default `comment` capability model are unchanged. Their
  mapped `edit_comment` is the generic meta cap, so editing and deletion
  still derive from the comment's parent post and moderation still requires
  the global `moderate_comments` primitive.
- A type that opts into its own `capability_type`/`capabilities` is gated by
  its own primitives (e.g. `moderate_reviews`, `edit_others_reviews`),
  mirroring how registered post types map `edit_post`. Caps are mapped, not
  granted: sites assign the primitives to roles, so nothing is silently
  escalated or locked out.

Generalize the `edit_comment` case and add `delete_comment` and
`moderate_comment` meta caps. `read_comment` is deferred: the cap object has
no read primitive and approved comments are publicly readable, so gating
reads needs its own design.

See #35214.
Cover both capability models in `map_meta_cap()`: the default `comment`
model still derives edit/delete from the parent post (including the orphaned
comment fallback) and moderation from `moderate_comments`, while a type with
an independent `capability_type` is gated by its own primitives with no
fallback to the default caps. Register `delete_comment` and `moderate_comment`
in the meta-capability coverage list.

See #35214.
@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.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89237122-8581-4062-b4d8-8d332b563833

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/comment-type-cap-enforcement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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