Comments: Introduce a register_comment_type() API - #12311
Comments: Introduce a register_comment_type() API#12311adamsilverstein wants to merge 17 commits into
Conversation
Add a comment type registration API modeled on the post type and taxonomy APIs, a first step toward custom comment types. Introduce the WP_Comment_Type class and register_comment_type(), unregister_comment_type(), get_comment_type_object(), get_comment_types(), and comment_type_exists(). Register the built-in comment, pingback, trackback, and note types via create_initial_comment_types(), hooked on init at priority 0 and on change_locale so labels re-translate. The note type is marked internal. Make the comment_type() template tag fall back to a registered type's singular label for non-built-in types when no custom text is supplied. Built-in output and explicit overrides are unchanged. Registration provides labels and metadata only; it does not constrain the values stored in the comment_type column or alter WP_Comment_Query behavior. See #35214.
Cover register_comment_type(), unregister_comment_type(), get_comment_type_object(), get_comment_types(), comment_type_exists(), the WP_Comment_Type class, and the registered/unregistered actions and label filters. Verify the built-in types are registered and cannot be unregistered, and that the comment_type() template tag renders registered type labels while preserving built-in output. See #35214.
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Hi there! 👋 Thank you for your contribution to WordPress! 💖 It looks like this is your first pull request to No one monitors this repository for new pull requests. Pull requests must be attached to a Trac ticket to be considered for inclusion in WordPress Core. To attach a pull request to a Trac ticket, please include the ticket's full URL in your pull request description. Pull requests are never merged on GitHub. The WordPress codebase continues to be managed through the SVN repository that this GitHub repository mirrors. Please feel free to open pull requests to work on any contribution you are making. More information about how GitHub pull requests can be used to contribute to WordPress can be found in the Core Handbook. Please include automated tests. Including tests in your pull request is one way to help your patch be considered faster. To learn about WordPress' test suites, visit the Automated Testing page in the handbook. If you have not had a chance, please review the Contribute with Code page in the WordPress Core Handbook. The Developer Hub also documents the various coding standards that are followed:
Thank you, |
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
…abel. Omit 'label' from the WP_Comment_Type::set_props() defaults so the property stays null when not provided. A false default was treated as a supplied value by _get_custom_object_labels(), which overwrote the default name with false, leaving labels->name and label as false for any type registered without an explicit label. This mirrors WP_Post_Type and WP_Taxonomy, which omit 'label' from their defaults for the same reason. Add a regression test covering default labels for a label-less comment type. See #35214.
The default branch of comment_type() echoed a registered type's
singular_name label without escaping. Labels are developer-supplied
(via register_comment_type() or the comment_type_labels_{$type}
filter) and reach the public comment list unescaped, so wrap the
output in esc_html() to match the post type and taxonomy label
contract. Document that labels are stored unescaped and add a
regression test asserting an HTML payload in the label is escaped.
Expose registered comment types through a read-only `/wp/v2/comment-types` controller, mirroring the post types controller (`/wp/v2/types`). This lets REST clients discover the registered types and their labels, which the block editor's inline-commenting work needs in order to add and query comments by type. Add a `show_in_rest` argument to `WP_Comment_Type` (defaulting to the value of `public`, as `show_ui` does) to gate which types the endpoint exposes. The built-in `comment`, `pingback`, and `trackback` types are public and therefore visible; the internal `note` type is not. Builds on the registration API in WordPress#12311. See #35214.
Add reset_comment_types() to WP_UnitTestCase_Base, mirroring reset_post_types()/reset_taxonomies(), plus an _unregister_comment_type() helper. This removes the need for hand-rolled tear_down() cleanup loops in individual test classes, which never ran when an assertion failed first, and heals any test that mutates the built-in types.
…stration. Nothing in the comment type API consumes 'show_ui' yet; the admin list table work that would read it is still design-gated. Removing an argument after release is impossible while adding one later is trivial, so defer it to the PR that introduces the admin UI.
register_comment_type() previously overwrote an existing registration silently, matching register_post_type(). For built-in comment types that would let a plugin strip flags core relies on (for example rendering and query behavior), so reject those with _doing_it_wrong() and a WP_Error instead. Core's own repeated registrations on 'init' and 'change_locale' pass '_builtin' and remain allowed, and unregister_comment_type() already guards built-ins the same way. Custom (non-built-in) types keep silent-overwrite semantics, now pinned by a test alongside register -> unregister -> register.
- Replace the inaccurate 'upgrade routine' rationale on
register_comment_type() with the real reason registrations should not
be hooked before 'init' (translations must be loaded).
- Rewrite the 'public' and 'internal' docs to describe what core
actually does with them today instead of promising behavior that is
not implemented; note why 'public' defaults to true.
- Move the misplaced escaping paragraph in the
comment_type_labels_{$comment_type} filter docblock above the tags so
the code reference parser keeps it.
- Add the missing @SInCE 7.1.0 changelog entry to comment_type() for
the new singular-label fallback.
- Type get_comment_types() $args as array: a string would fatal in
wp_filter_object_list() on PHP 8.
- Note the deliberate i18n string reuse in create_initial_comment_types()
and include comment types in the _get_custom_object_labels() summary.
- Fix label docblock nits and @PARAM alignment.
_get_custom_object_labels() unconditionally derives 'name_admin_bar' and spawns 'all_items'/'archives' when a 'menu_name' is provided, all of which are meaningless for comment types. Strip them from the labels object unless explicitly provided at registration. Also expand test coverage for the registration API: name sanitization and the 20-character boundary, get_comment_types() operators and output keying, the label fallback chain, filter backfill of required labels, action arguments, non-scalar lookups, idempotent built-in registration, template output for legacy empty and built-in 'note' types, and make the reset_default_labels() test able to fail by poisoning the static cache. Add per-method @Covers tags.
reset_comment_types() unregistered only the types reporting _builtin false, so a test that registers a custom type with '_builtin' set left it in the registry for every test that followed - which then hit the built-in re-registration guard for a name it had never registered. Registering a comment type creates no hooks, rewrite rules, or meta boxes, so emptying the registry and re-running create_initial_comment_types() is both complete cleanup and simpler. See #35214.
…ype. The hierarchical property exists only so the shared label helper has a slot to resolve against, and comment types deliberately leave that slot null. set_props() copies every provided argument onto the object, though, so a plugin carrying `'hierarchical' => true` over from register_post_type() flipped the property and resolved name, singular_name, menu_name, and label to null. Nothing errored - the type was silently unlabeled, and comment_type()'s isset() check hid it. Force the property false alongside the other derived values. See #35214.
…ntract. WP_Comment_Query reads 'all', 'comments', and 'pings' as query tokens rather than as literal comment_type values, so a type registered under one of those names could never be queried for on its own. Reject them at registration instead of letting a plugin discover it later. The rest is contract that was implied but never written down, and the follow-up PRs in this stack each add another argument, so it is worth fixing the shape now rather than accumulating a register_post_type()-style cascade: each argument drives exactly one layer of behavior and never implies another, `internal` is the one that drives default query and count exclusion, and `show_in_rest` defaulting from `public` is the single cascade planned. Also document that '_builtin' is for core's own use, that unregistered types behave exactly as they did before this API existed, and that the registry is per-process rather than per-site. Pin the edges with tests: '_builtin' passed by a plugin both bypasses the re-registration guard and locks a custom type, built-in labels rebuild on a locale change, the accessors cope with the registry global not existing yet, and a registration survives switch_to_blog(). See #35214.
…ject. _get_custom_object_labels() writes every label it derives back onto the object it receives, including the post-type-only labels this function strips from its return value. Calling get_comment_type_labels() on an already registered comment type therefore polluted the registered object's labels with name_admin_bar, all_items, and archives, and a second call returned them, breaking the documented label set. The registration path masked this because set_props() reassigns labels from the return value immediately. Pass a copy of the object into the helper so the registered object is never touched, and pin the invariant with a repeated-call test. See #35214.
The internal argument's docs stated that internal types are excluded
from comment queries and counts through the
default_excluded_comment_types filter, but that filter is proposed
separately (see #65537) and does not exist in this change. A plugin
author following the argument contract would expect an exclusion that
never happens, and the {@see} tag pointed hook docs at a nonexistent
hook.
Describe the flag the way public is already described: what it is
meant to drive, with an explicit note that core does not act on it
yet. The wording can be tightened back up when the exclusion filter
lands.
See #35214.
Unlike _unregister_post_type() and _unregister_taxonomy(), which tests call to tear down objects registered in wpSetUpBeforeClass(), nothing in the suite calls this helper: reset_comment_types() already drops the whole registry before every test, so per-type teardown has no use case yet. Remove it rather than shipping dead code; it can return if a real caller appears. See #35214.
The branch was written while trunk was 7.1-alpha; trunk is now 7.2-alpha, so the API's @SInCE tags and _doing_it_wrong() version strings would have credited the wrong release and shown plugin authors a wrong version at runtime. Pre-existing 7.1.0 stamps for features that did ship in 7.1 are untouched. See #35214.
Description
Trac #35214 is the long-running tracking ticket for custom comment types. Its repeated consensus (dshanske, jeremyfelt) has been "backend first, smaller steps." The data-storage groundwork already shipped in 5.5 via #49236 (comments now store
comment_type = 'comment'instead of''). This PR adds the next logical slice: a comment type registration API modeled on the post type and taxonomy APIs.What this adds
WP_Comment_Typeclass (src/wp-includes/class-wp-comment-type.php), a trimmed mirror ofWP_Taxonomy.register_comment_type(),unregister_comment_type(),get_comment_type_object(),get_comment_types(), andcomment_type_exists().create_initial_comment_types()registering the built-incomment,pingback,trackback, andnotetypes, hooked oninit(priority 0) andchange_locale. Thenotetype (added in 6.9 for editor Notes) is markedinternal.get_comment_type_labels(), reusing the shared_get_custom_object_labels()helper. Post-type-only labels the helper derives (name_admin_bar,all_items,archives) are stripped unless explicitly provided.comment_type()template tag now falls back to a registered, non-built-in type's singular label when the caller supplies no custom text. Built-in output and explicit overrides are byte-for-byte unchanged.register_comment_type( 'pingback', ... )from a plugin returns aWP_Errorwith_doing_it_wrong(), stricter thanregister_post_type(). A new API has no legacy re-registration usage to preserve, and silently overwriting a built-in could strip flags core relies on (this matters more once per-type rendering/query flags land in follow-ups). Core's own repeated registrations oninit/change_localepass_builtinand remain allowed; custom types keep silent-overwrite semantics (pinned by a test).Filters mirror the post type/taxonomy conventions:
register_comment_type_args,register_{$type}_comment_type_args,comment_type_labels_{$type}, and theregistered_comment_type/registered_comment_type_{$type}/unregistered_comment_typeactions.Scope / non-goals
This is deliberately a small, non-breaking step. Registration provides labels and metadata only; it does not constrain values stored in the
comment_typecolumn and makes no change toWP_Comment_Query.The
internalflag is advisory metadata in this PR. Generalizing the hard-codednoteexclusion inWP_Comment_Queryis handled separately by #12310 (default_excluded_comment_typesfilter); these two PRs are complementary and do not overlap. Admin list-table/dropdown integration and capabilities are left to follow-ups (the admin UI is design-feedback gated per discussion on #35214). Ashow_uiargument was originally included here and has been removed: nothing consumes it yet, and it can be reintroduced trivially with the admin UI follow-up, while removing an argument after release would be impossible.Testing
Test framework:
WP_UnitTestCase_Basenow resets comment types between tests (reset_comment_types(), alongside the existing post type/taxonomy resets), with an_unregister_comment_type()helper intests/phpunit/includes/utils.php.New tests under
tests/phpunit/tests/comment/:types.php— registration (including name sanitization, length boundaries, and the built-in re-registration guard), unregistration (including blocking built-ins), re-registration semantics,get_comment_types()filtering/operators, actions and their arguments, label fallbacks, and label filters.wpCommentType.php—WP_Comment_Typedefaults, arg filters, and default-label caching (the reset test poisons the static cache via reflection so it can actually fail).commentType.php—comment_type()output for built-in types (unchanged), the legacy''type,note(label fallback must not apply to built-ins), explicit overrides, escaping, and registered custom type labels.All
--group commenttests pass on single site and multisite;post/types.phpstill passes (shared label helper unaffected); PHPCS is clean on changed files.Review updates
'hierarchical' => trueno longer breaks the labels. The property exists only so the shared label helper has a slot to resolve against, and comment types leave that slot null on purpose.set_props()copied every provided argument onto the object, though, so a plugin carrying the argument over fromregister_post_type()resolvedname,singular_name,menu_name, andlabelto null with no error -comment_type()'sisset()check then hid it. The property is forced false alongside the other derived values.WP_Comment_Queryreadsall,comments, andpingsas tokens rather than as literalcomment_typevalues, so a type registered under one of them could never be queried for on its own.register_comment_type()now rejects them.register_post_type()-style cascade. Each argument drives exactly one layer of behavior and never implies another;internalis the one that drives default query and count exclusion (throughdefault_excluded_comment_typesin Comments: Add filter for comment types excluded from queries by default #12310);show_in_restdefaulting frompublicis the single cascade planned. Also documented:_builtinis for core's own use, unregistered types behave exactly as they did before this API existed, and the registry is per-process rather than per-site._builtinfrom a plugin both bypasses the re-registration guard and locks a custom type (same exposureregister_post_type()has), built-in labels rebuild on a locale change, the accessors cope with the registry global not existing yet, and a registration survivesswitch_to_blog().reset_comment_types()unregistered only the types reporting_builtinfalse, so a test registering a custom type with_builtinset left it in the registry for everything that followed. The registry is now emptied outright beforecreate_initial_comment_types()re-runs; registering a comment type creates no hooks, rewrite rules, or meta boxes, so that is complete cleanup.See #35214
AI Use
The later commits and this description's review-updates section were written with Claude, working from a review of the PR. I will review and test.