Skip to content

Comments: Generalize ping handling with an is_ping comment type flag (Trac #35214) - #54

Open
adamsilverstein wants to merge 21 commits into
feature/comment-type-render-callbackfrom
feature/comment-type-ping-grouping
Open

adamsilverstein wants to merge 21 commits into
feature/comment-type-render-callbackfrom
feature/comment-type-ping-grouping

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 #56
#53 render_callback display callback WordPress#12311
👉 #54 is_ping ping grouping (this PR) #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.

Handing the write-up to Claude, which reports:

Adds an is_ping flag to registered comment types, so a plugin type like webmention can be grouped, queried and displayed as a ping, the same way pingbacks and trackbacks are today.

Why

Ping handling is hard-coded to the pingback and trackback type names in separate_comments(), Walker_Comment::start_el() and the 'pings' token in WP_Comment_Query. A registered type has no way to opt in, so a webmention ends up in the regular comment list and is left out of a 'pings' query. This is part of the generalization work on https://core.trac.wordpress.org/ticket/35214.

How

  • WP_Comment_Type::$is_ping - new flag, default false. The built-in pingback and trackback types register with 'is_ping' => true.
  • separate_comments() puts every ping type in the pings bucket.
  • WP_Comment_Query expands 'pings' (in type, type__in or type__not_in) to every registered ping type. The resolved set is part of the cache key for both get_comments() and fill_descendants(), because registering a ping type does not bump the comment last_changed value. If the registry is empty, it falls back to the built-in pair.
  • Walker_Comment::start_el() uses the compact ping markup for any ping type when short_ping is set. A render_callback still takes precedence.
  • Walker_Comment::ping() labels a registered ping type with its own singular_name (eg. "Webmention:"). The built-ins, and any type without its own singular name, keep the literal "Pingback:", so existing markup is unchanged.

Note: the flag drives grouping, querying and display only. check_comment(), get_default_comment_status() and the notification emails still key on the pingback and trackback names, so a custom ping type gets none of their handling, and none of their bypasses either.

Testing

  • vendor/bin/phpunit --group comment - new coverage is in tests/phpunit/tests/comment/query.php, separateComments.php, walker.php, types.php and wpCommentType.php.
  • Manual test on a classic theme (eg. Twenty Twenty-One):
    1. In a mu-plugin, add add_action( 'init', function () { register_comment_type( 'webmention', array( 'is_ping' => true, 'labels' => array( 'singular_name' => 'Webmention' ) ) ); } );
    2. Add an approved comment with wp comment create --comment_post_ID=<id> --comment_type=webmention --comment_author_url=https://example.com --comment_approved=1
    3. View the post. The webmention should render as a compact "Webmention: example.com" line next to any pingbacks, not as a full comment.
    4. wp eval 'print_r( get_comments( array( "type" => "pings", "fields" => "ids" ) ) );' should include the webmention.

Out of scope / follow-ups

  • Ping handling in check_comment(), get_default_comment_status(), wp_notify_postauthor() / wp_notify_moderator() and the dashboard labels still keys on the two built-in names. Those are moderation and notification behavior, and each change carries its own back-compat risk.
  • A generic ping class from get_comment_class(), so themes have one selector for custom ping types.
  • Exposing is_ping from /wp/v2/comment-types.

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

AI Use

I told Claude what I wanted and it wrote the code and this summary. I will review and test.

@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

  • 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: 5b71c53f-0f29-49e3-88db-1ce9894d3062

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-ping-grouping
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Pingbacks and trackbacks were singled out by hard-coded `comment_type`
string comparisons in `separate_comments()` and `Walker_Comment`, so the
"this is a ping, not a human comment" distinction could not be expressed
by a registered comment type.

Add an `is_ping` property to `WP_Comment_Type` (default false) and mark
the built-in `pingback` and `trackback` types with it. `separate_comments()`
now groups any registered ping type into the `pings` bucket, and
`Walker_Comment::start_el()` renders any ping type with the compact ping
markup. Built-in behaviour is unchanged.

See #35214.
Cover the new flag end to end: the `WP_Comment_Type` default and storage,
the built-in pingback/trackback types being marked as pings (and
comment/note not), `separate_comments()` grouping a registered ping type
into the `pings` bucket while leaving non-ping types out, and
`Walker_Comment` rendering a registered ping type with the compact ping
markup.

See #35214.
Add the complementary cases for the is_ping && short_ping guard: the
built-in pingback still renders as a compact ping (regression guard for
the move from hard-coded type strings to the is_ping flag), a ping type
renders its full markup when short_ping is off, and a non-ping type is
never rendered as a ping even with short_ping enabled.

See #35214.
@adamsilverstein
adamsilverstein force-pushed the feature/comment-type-ping-grouping branch from 6c4b5b3 to 3a5a401 Compare June 25, 2026 06:33
…ent-type-ping-grouping

# Conflicts:
#	src/wp-includes/comment.php
- The is_ping docs now state the 'short_ping' condition, that a
  registered 'render_callback' takes precedence over the compact ping
  markup, and the classic-theme-only scope.
- Add the missing @SInCE 7.1.0 changelog entries to
  separate_comments() and extend Walker_Comment::start_el()'s.
- Update the wp_list_comments() 'type' argument doc: 'pings' now means
  every registered comment type with 'is_ping'.
- Tests: pin the render_callback-beats-short_ping precedence, the
  unregistered-type bucket (no 'pings' membership, matching the old
  hard-coded behavior), and the legacy empty-string 'comment' bucket.
  Drop cleanup-only unregister calls now handled by the test framework.
…ent-type-ping-grouping

# Conflicts:
#	src/wp-includes/comment.php
…ct markup.

`Walker_Comment::ping()` prints a literal "Pingback:". That has been imprecise
since 3.6, when trackbacks started using the same markup, but it was at least
never wrong by more than one word. Now that any type can opt into ping grouping,
a webmention rendering as "Pingback: example.com" is plainly wrong output for
exactly the audience the flag is for.

Use the type's `singular_name` for registered, non-built-in types, matching what
`comment_type()` already does on this branch, and leave the string alone for the
built-ins so their markup is byte-identical.

The test that asserted the literal "Pingback:" for a webmention was pinning the
bug, so replace it: one test for the compact shape (label, author link, no
comment body), one for the label itself. Also add the end-to-end case the suite
was missing - `wp_list_comments()` with `type => 'pings'` listing a registered
ping type - which is where a theme actually meets the grouping change.
`separate_comments()` and `wp_list_comments()` group pings by the `is_ping` flag,
while `WP_Comment_Query` kept expanding the 'pings' token to 'pingback' and
'trackback'. A theme reading `$wp_query->comments_by_type['pings']` and a plugin
calling `get_comments( array( 'type' => 'pings' ) )` would disagree about which
comments are pings, so land the query side in the same release rather than ship
that window.

The resolved set goes into the comment query cache key. Which types are pings
depends on what is registered, which changes when a plugin is activated, and the
comment `last_changed` salt only moves when a comment does - so without this a
'pings' query would keep serving results cached before the type existed.

Registration happens on 'init', so the built-in pair remains the fallback for
callers that query before then.
The flag's docs covered grouping and rendering but not the two things a plugin
author most needs to know before using it: that the compact ping markup now
labels the comment with the type's `singular_name`, so a ping type ought to
register one, and that the flag stops at display and grouping. Validation,
default comment status, and notification emails still key on the 'pingback' and
'trackback' type names, so a custom ping type gains none of their handling - and,
just as importantly, none of their bypasses.
…ent-type-ping-grouping

# Conflicts:
#	src/wp-includes/class-walker-comment.php
get_comments() folds the registered ping types into its cache key so
a plugin registering a ping type invalidates 'pings' results, but
fill_descendants() built its per-parent child ID keys from the raw
query vars alone. The cached child lists are filtered by the resolved
ping set, so after registering a new ping type a threaded 'pings'
query returned the updated top-level comments while still hiding the
new type's children until something bumped the comment last_changed
salt - indefinitely, under a persistent object cache.

Extract the key construction into a shared get_cache_key_args()
helper used by both paths so they cannot drift apart again. As a side
effect the descendant keys now also drop 'fields' and the two
update_*_cache flags, which never affected which children exist.

See #35214.
Every registered comment type gets the default 'Comment' singular
name from set_props(), so the singular-name guard in
Walker_Comment::ping() could never fail and a ping type registered
without labels rendered as 'Comment:' - a worse mislabel than the
'Pingback:' it replaced. Fall back to 'Pingback:' whenever the type
inherited the default singular name, and pin it with a test (the
existing suite only registered ping types that supplied a label).

Also give the '%s:' string a translator context, matching the
existing '%s:' prefixes in post-template.php and general-template.php
so it does not merge with unrelated uses in the POT.

See #35214.
The branch was written while trunk was 7.1-alpha; trunk is now
7.2-alpha, so the six @SInCE tags this branch adds pointed at a
release that will never contain them.

Beyond the stamps, resolve the smaller review findings around the
'pings' expansion: reset the memoized ping type set in parse_query()
so reusing one WP_Comment_Query instance cannot build SQL and cache
keys from a set resolved before a registration change; correct the
empty-registry fallback rationale (comment types are registered
directly in wp-settings.php, not on 'init', so only a partial
bootstrap hits it); update the two get_page_of_comment() docs that
still described 'pings' as trackbacks and pingbacks; and repair the
test docblocks displaced when the ping tests were inserted between
test_comment_type_exists() and its annotations.

See #35214.
…ent-type-ping-grouping

# Conflicts:
#	tests/phpunit/tests/comment/separateComments.php
@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 docblocks and inline comments added for the `is_ping` flag narrated
design history and repeated the same rationale in several places. Trim
them to what a caller needs: which surfaces the flag drives, that
`render_callback` still takes precedence, and that validation and
notifications still key on the 'pingback' and 'trackback' names.

No functional change.

See #35214.
PHPStan level 10 flagged the new ping lines: get_comment_types()
returns names or objects, and a registered label can be any value, so
esc_html() could receive a non-string. Declare `: array` returns on the
two new protected WP_Comment_Query helpers, narrow the resolved ping
names, and only use a ping type's singular name when it is a string.

Strings are the only labels that render today, so output is unchanged.

See #35214.
The 'pings' token expands to the registered ping types for both the IN
and NOT IN clauses, but only the IN side was covered. Add a
`type__not_in` test and declare `: void` on the new ping tests to match
the rest of the comment type suite.

See #35214.
…ent-type-ping-grouping

# Conflicts:
#	src/wp-includes/class-walker-comment.php
#	src/wp-includes/comment.php
#	tests/phpunit/tests/comment/types.php
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