From 0904ce2552941956c49ed8304c2f4710af8a2b1c Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 24 Jun 2026 14:48:12 -0700 Subject: [PATCH 01/16] Comments: Introduce a register_comment_type() API. 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. --- src/wp-includes/class-wp-comment-type.php | 238 ++++++++++++++++++ src/wp-includes/comment-template.php | 16 +- src/wp-includes/comment.php | 293 ++++++++++++++++++++++ src/wp-includes/default-filters.php | 4 + src/wp-settings.php | 4 +- 5 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 src/wp-includes/class-wp-comment-type.php diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php new file mode 100644 index 0000000000000..374108568e9a3 --- /dev/null +++ b/src/wp-includes/class-wp-comment-type.php @@ -0,0 +1,238 @@ +name = $comment_type; + + $this->set_props( $args ); + } + + /** + * Sets comment type properties. + * + * See the register_comment_type() function for accepted arguments for `$args`. + * + * @since 7.1.0 + * + * @param array|string $args Array or string of arguments for registering a comment type. + */ + public function set_props( $args ) { + $args = wp_parse_args( $args ); + + /** + * Filters the arguments for registering a comment type. + * + * @since 7.1.0 + * + * @param array $args Array of arguments for registering a comment type. + * See the register_comment_type() function for accepted arguments. + * @param string $comment_type Comment type key. + */ + $args = apply_filters( 'register_comment_type_args', $args, $this->name ); + + $comment_type = $this->name; + + /** + * Filters the arguments for registering a specific comment type. + * + * The dynamic portion of the filter name, `$comment_type`, refers to the comment type key. + * + * Possible hook names include: + * + * - `register_comment_comment_type_args` + * - `register_pingback_comment_type_args` + * + * @since 7.1.0 + * + * @param array $args Array of arguments for registering a comment type. + * See the register_comment_type() function for accepted arguments. + * @param string $comment_type Comment type key. + */ + $args = apply_filters( "register_{$comment_type}_comment_type_args", $args, $this->name ); + + $defaults = array( + 'label' => false, + 'labels' => array(), + 'description' => '', + 'public' => true, + 'internal' => false, + 'show_ui' => null, + '_builtin' => false, + ); + + $args = array_merge( $defaults, $args ); + + // If not set, default to the setting for 'public'. + if ( null === $args['show_ui'] ) { + $args['show_ui'] = $args['public']; + } + + $args['name'] = $this->name; + + foreach ( $args as $property_name => $property_value ) { + $this->$property_name = $property_value; + } + + $this->labels = get_comment_type_labels( $this ); + $this->label = $this->labels->name; + } + + /** + * Returns the default labels for comment types. + * + * @since 7.1.0 + * + * @return (string|null)[][] The default labels for comment types. + */ + public static function get_default_labels() { + if ( ! empty( self::$default_labels ) ) { + return self::$default_labels; + } + + self::$default_labels = array( + 'name' => array( _x( 'Comments', 'comment type general name' ), null ), + 'singular_name' => array( _x( 'Comment', 'comment type singular name' ), null ), + ); + + return self::$default_labels; + } + + /** + * Resets the cache for the default labels. + * + * @since 7.1.0 + */ + public static function reset_default_labels() { + self::$default_labels = array(); + } +} diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php index 43bd68ff972a4..38991f60929ad 100644 --- a/src/wp-includes/comment-template.php +++ b/src/wp-includes/comment-template.php @@ -1185,6 +1185,9 @@ function get_comment_type( $comment_id = 0 ) { * @param string|false $pingback_text Optional. String to display for pingback type. Default false. */ function comment_type( $comment_text = false, $trackback_text = false, $pingback_text = false ) { + // Whether the caller supplied custom text for the default comment label. + $comment_text_overridden = ( false !== $comment_text ); + if ( false === $comment_text ) { $comment_text = _x( 'Comment', 'noun' ); } @@ -1203,7 +1206,18 @@ function comment_type( $comment_text = false, $trackback_text = false, $pingback echo $pingback_text; break; default: - echo $comment_text; + /* + * For a registered, non-built-in comment type, fall back to its singular label + * when the caller did not supply custom text. Built-in types and explicit + * overrides keep their existing output. + */ + $comment_type_object = $comment_text_overridden ? null : get_comment_type_object( $type ); + + if ( $comment_type_object && ! $comment_type_object->_builtin && isset( $comment_type_object->labels->singular_name ) ) { + echo $comment_type_object->labels->singular_name; + } else { + echo $comment_text; + } } } diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b93908adc0519..2ebc55a1804b0 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -273,6 +273,299 @@ function get_comments( $args = '' ) { return $query->query( $args ); } +/** + * Creates the initial comment types when 'init' action is fired. + * + * See register_comment_type() for accepted arguments. + * + * @since 7.1.0 + */ +function create_initial_comment_types() { + WP_Comment_Type::reset_default_labels(); + + register_comment_type( + 'comment', + array( + 'label' => __( 'Comments' ), + 'labels' => array( + 'singular_name' => _x( 'Comment', 'noun' ), + ), + 'public' => true, + '_builtin' => true, + ) + ); + + register_comment_type( + 'pingback', + array( + 'label' => __( 'Pingbacks' ), + 'labels' => array( + 'singular_name' => __( 'Pingback' ), + ), + 'public' => true, + '_builtin' => true, + ) + ); + + register_comment_type( + 'trackback', + array( + 'label' => __( 'Trackbacks' ), + 'labels' => array( + 'singular_name' => __( 'Trackback' ), + ), + 'public' => true, + '_builtin' => true, + ) + ); + + register_comment_type( + 'note', + array( + 'label' => _x( 'Notes', 'comment type general name' ), + 'labels' => array( + 'singular_name' => _x( 'Note', 'comment type singular name' ), + ), + 'public' => false, + 'internal' => true, + '_builtin' => true, + ) + ); +} + +/** + * Registers a comment type. + * + * Note: Comment type registrations should not be hooked before the {@see 'init'} action. + * This is because comment type slugs need to be reserved as part of the upgrade routine + * and global variables need to be available for the comment type to register itself. + * + * Comment types are stored verbatim in the `comment_type` column of the comments table. + * Registration provides labels and metadata for a type; it does not constrain which values + * may be stored. + * + * @since 7.1.0 + * + * @global WP_Comment_Type[] $wp_comment_types List of comment types. + * + * @param string $comment_type Comment type key. Must not exceed 20 characters and may only + * contain lowercase alphanumeric characters, dashes, and underscores. + * See sanitize_key(). + * @param array|string $args { + * Optional. Array or string of arguments for registering a comment type. Default empty array. + * + * @type string $label Name of the comment type shown in the menu. Usually plural. + * Default is value of $labels['name']. + * @type string[] $labels An array of labels for this comment type. If not set, comment + * labels are inherited. See get_comment_type_labels() for a full + * list of supported labels. + * @type string $description A short descriptive summary of what the comment type is. + * Default empty. + * @type bool $public Whether the comment type is intended for use publicly either via + * the admin interface or by front-end users. Default true. + * @type bool $internal Whether the comment type is for internal use only and should be + * excluded from default public-facing contexts. Default false. + * @type bool $show_ui Whether to generate and allow a UI for managing this comment type + * in the admin. Default is value of $public. + * } + * @return WP_Comment_Type|WP_Error The registered comment type object on success, + * WP_Error object on failure. + */ +function register_comment_type( $comment_type, $args = array() ) { + global $wp_comment_types; + + if ( ! is_array( $wp_comment_types ) ) { + $wp_comment_types = array(); + } + + // Sanitize comment type name. + $comment_type = sanitize_key( $comment_type ); + + if ( empty( $comment_type ) || strlen( $comment_type ) > 20 ) { + _doing_it_wrong( __FUNCTION__, __( 'Comment type names must be between 1 and 20 characters in length.' ), '7.1.0' ); + return new WP_Error( 'comment_type_length_invalid', __( 'Comment type names must be between 1 and 20 characters in length.' ) ); + } + + $comment_type_object = new WP_Comment_Type( $comment_type, $args ); + + $wp_comment_types[ $comment_type ] = $comment_type_object; + + /** + * Fires after a comment type is registered. + * + * @since 7.1.0 + * + * @param string $comment_type Comment type key. + * @param WP_Comment_Type $comment_type_object Comment type object. + */ + do_action( 'registered_comment_type', $comment_type, $comment_type_object ); + + /** + * Fires after a specific comment type is registered. + * + * The dynamic portion of the filter name, `$comment_type`, refers to the comment type key. + * + * Possible hook names include: + * + * - `registered_comment_type_comment` + * - `registered_comment_type_pingback` + * + * @since 7.1.0 + * + * @param string $comment_type Comment type key. + * @param WP_Comment_Type $comment_type_object Comment type object. + */ + do_action( "registered_comment_type_{$comment_type}", $comment_type, $comment_type_object ); + + return $comment_type_object; +} + +/** + * Unregisters a comment type. + * + * Cannot be used to unregister built-in comment types. + * + * @since 7.1.0 + * + * @global WP_Comment_Type[] $wp_comment_types List of comment types. + * + * @param string $comment_type Comment type key. + * @return true|WP_Error True on success, WP_Error on failure or if the comment type doesn't exist. + */ +function unregister_comment_type( $comment_type ) { + global $wp_comment_types; + + if ( ! comment_type_exists( $comment_type ) ) { + return new WP_Error( 'invalid_comment_type', __( 'Invalid comment type.' ) ); + } + + $comment_type_object = get_comment_type_object( $comment_type ); + + // Do not allow unregistering built-in comment types. + if ( $comment_type_object->_builtin ) { + return new WP_Error( 'invalid_comment_type', __( 'Unregistering a built-in comment type is not allowed.' ) ); + } + + unset( $wp_comment_types[ $comment_type ] ); + + /** + * Fires after a comment type is unregistered. + * + * @since 7.1.0 + * + * @param string $comment_type Comment type key. + */ + do_action( 'unregistered_comment_type', $comment_type ); + + return true; +} + +/** + * Retrieves a comment type object by name. + * + * @since 7.1.0 + * + * @global WP_Comment_Type[] $wp_comment_types List of comment types. + * + * @param string $comment_type The name of a registered comment type. + * @return WP_Comment_Type|null WP_Comment_Type object if it exists, null otherwise. + */ +function get_comment_type_object( $comment_type ) { + global $wp_comment_types; + + if ( ! is_scalar( $comment_type ) || empty( $wp_comment_types[ $comment_type ] ) ) { + return null; + } + + return $wp_comment_types[ $comment_type ]; +} + +/** + * Retrieves a list of registered comment type names or objects. + * + * @since 7.1.0 + * + * @global WP_Comment_Type[] $wp_comment_types List of comment types. + * + * @param array|string $args Optional. An array of key => value arguments to match against + * the comment type objects. Default empty array. + * @param string $output Optional. The type of output to return. Either comment type 'names' + * or 'objects'. Default 'names'. + * @param string $operator Optional. The logical operation to perform. 'or' means only one + * element from the array needs to match; 'and' means all elements + * must match; 'not' means no elements may match. Default 'and'. + * @return string[]|WP_Comment_Type[] An array of comment type names or objects. + */ +function get_comment_types( $args = array(), $output = 'names', $operator = 'and' ) { + global $wp_comment_types; + + $field = ( 'names' === $output ) ? 'name' : false; + + return wp_filter_object_list( $wp_comment_types, $args, $operator, $field ); +} + +/** + * Determines whether a comment type is registered. + * + * @since 7.1.0 + * + * @param string $comment_type Comment type name. + * @return bool Whether the comment type is registered. + */ +function comment_type_exists( $comment_type ) { + return (bool) get_comment_type_object( $comment_type ); +} + +/** + * Builds an object with all comment type labels out of a comment type object. + * + * @since 7.1.0 + * + * @param WP_Comment_Type $comment_type_object Comment type object. + * @return object { + * Comment type labels object. + * + * @type string $name General name for the comment type, usually plural. The same and + * overridden by `$comment_type_object->label`. Default 'Comments'. + * @type string $singular_name Name for one object of this comment type. Default 'Comment'. + * @type string $menu_name Label for the menu name. Default is the same as `name`. + * } + */ +function get_comment_type_labels( $comment_type_object ) { + $nohier_vs_hier_defaults = WP_Comment_Type::get_default_labels(); + + $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name']; + + $labels = _get_custom_object_labels( $comment_type_object, $nohier_vs_hier_defaults ); + + $comment_type = $comment_type_object->name; + + $default_labels = clone $labels; + + /** + * Filters the labels of a specific comment type. + * + * The dynamic portion of the hook name, `$comment_type`, refers to the comment type slug. + * + * Possible hook names include: + * + * - `comment_type_labels_comment` + * - `comment_type_labels_pingback` + * + * @since 7.1.0 + * + * @see get_comment_type_labels() for the full list of comment type labels. + * + * @param object $labels Object with labels for the comment type as member variables. + */ + $labels = apply_filters( "comment_type_labels_{$comment_type}", $labels ); + + // Ensure that the filtered labels contain all required default values. + $labels = (object) array_merge( (array) $default_labels, (array) $labels ); + + return $labels; +} + /** * Retrieves all of the WordPress supported comment statuses. * diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 5581828a10b61..966c69cca5cf9 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -532,6 +532,10 @@ add_action( 'split_shared_term', '_wp_check_split_nav_menu_terms', 10, 4 ); add_action( 'wp_split_shared_term_batch', '_wp_batch_split_terms' ); +// Comment types. +add_action( 'init', 'create_initial_comment_types', 0 ); // Highest priority. +add_action( 'change_locale', 'create_initial_comment_types' ); + // Comment type updates. add_action( 'admin_init', '_wp_check_for_scheduled_update_comment_type' ); add_action( 'wp_update_comment_type_batch', '_wp_batch_update_comment_type' ); diff --git a/src/wp-settings.php b/src/wp-settings.php index ef5c7784ee561..9ec2c3607271d 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -232,6 +232,7 @@ require ABSPATH . WPINC . '/comment.php'; require ABSPATH . WPINC . '/class-wp-comment.php'; require ABSPATH . WPINC . '/class-wp-comment-query.php'; +require ABSPATH . WPINC . '/class-wp-comment-type.php'; require ABSPATH . WPINC . '/class-walker-comment.php'; require ABSPATH . WPINC . '/comment-template.php'; require ABSPATH . WPINC . '/rewrite.php'; @@ -554,10 +555,11 @@ // Create common globals. require ABSPATH . WPINC . '/vars.php'; -// Make taxonomies and posts available to plugins and themes. +// Make taxonomies, posts, and comment types available to plugins and themes. // @plugin authors: warning: these get registered again on the init hook. create_initial_taxonomies(); create_initial_post_types(); +create_initial_comment_types(); wp_start_scraping_edited_file_errors(); From ebb51c5e7fa89a3aa9c48cf8e48f005b5a5f3645 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 24 Jun 2026 14:48:18 -0700 Subject: [PATCH 02/16] Comments: Add tests for the comment type registration API. 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. --- tests/phpunit/tests/comment/commentType.php | 117 ++++++++ tests/phpunit/tests/comment/types.php | 281 ++++++++++++++++++ tests/phpunit/tests/comment/wpCommentType.php | 120 ++++++++ 3 files changed, 518 insertions(+) create mode 100644 tests/phpunit/tests/comment/commentType.php create mode 100644 tests/phpunit/tests/comment/types.php create mode 100644 tests/phpunit/tests/comment/wpCommentType.php diff --git a/tests/phpunit/tests/comment/commentType.php b/tests/phpunit/tests/comment/commentType.php new file mode 100644 index 0000000000000..da7d316afbe0a --- /dev/null +++ b/tests/phpunit/tests/comment/commentType.php @@ -0,0 +1,117 @@ +post->create(); + } + + public function tear_down() { + global $wp_comment_types; + + foreach ( array_keys( $wp_comment_types ) as $comment_type ) { + if ( ! $wp_comment_types[ $comment_type ]->_builtin ) { + unset( $wp_comment_types[ $comment_type ] ); + } + } + + parent::tear_down(); + } + + /** + * Returns the output of comment_type() for a comment of the given type. + * + * @param string $type Comment type stored on the comment. + * @param mixed ...$args Optional arguments passed through to comment_type(). + * @return string Captured output. + */ + private function get_comment_type_output( $type, ...$args ) { + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_type' => $type, + ) + ); + + $GLOBALS['comment'] = get_comment( $comment_id ); + + ob_start(); + comment_type( ...$args ); + $output = ob_get_clean(); + + unset( $GLOBALS['comment'] ); + + return $output; + } + + /** + * @ticket 35214 + */ + public function test_built_in_types_output_is_unchanged() { + $this->assertSame( 'Comment', $this->get_comment_type_output( 'comment' ) ); + $this->assertSame( 'Trackback', $this->get_comment_type_output( 'trackback' ) ); + $this->assertSame( 'Pingback', $this->get_comment_type_output( 'pingback' ) ); + } + + /** + * @ticket 35214 + */ + public function test_custom_text_overrides_are_respected() { + $this->assertSame( 'C', $this->get_comment_type_output( 'comment', 'C', 'T', 'P' ) ); + $this->assertSame( 'T', $this->get_comment_type_output( 'trackback', 'C', 'T', 'P' ) ); + $this->assertSame( 'P', $this->get_comment_type_output( 'pingback', 'C', 'T', 'P' ) ); + } + + /** + * @ticket 35214 + */ + public function test_registered_custom_type_outputs_its_label() { + register_comment_type( + 'foo', + array( + 'labels' => array( + 'singular_name' => 'Foo', + ), + ) + ); + + $this->assertSame( 'Foo', $this->get_comment_type_output( 'foo' ) ); + } + + /** + * @ticket 35214 + */ + public function test_unregistered_custom_type_falls_back_to_default_label() { + $this->assertSame( _x( 'Comment', 'noun' ), $this->get_comment_type_output( 'bar' ) ); + } + + /** + * @ticket 35214 + */ + public function test_custom_text_override_wins_over_registered_label() { + register_comment_type( + 'foo', + array( + 'labels' => array( + 'singular_name' => 'Foo', + ), + ) + ); + + $this->assertSame( 'Custom', $this->get_comment_type_output( 'foo', 'Custom' ) ); + } +} diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php new file mode 100644 index 0000000000000..dd10a6e200d0c --- /dev/null +++ b/tests/phpunit/tests/comment/types.php @@ -0,0 +1,281 @@ +_builtin ) { + unset( $wp_comment_types[ $comment_type ] ); + } + } + + parent::tear_down(); + } + + /** + * @ticket 35214 + */ + public function test_register_comment_type() { + $this->assertNull( get_comment_type_object( 'foo' ) ); + + register_comment_type( 'foo' ); + + $cobj = get_comment_type_object( 'foo' ); + $this->assertInstanceOf( 'WP_Comment_Type', $cobj ); + $this->assertSame( 'foo', $cobj->name ); + + // Test some defaults. + $this->assertTrue( $cobj->public ); + $this->assertFalse( $cobj->internal ); + $this->assertFalse( $cobj->_builtin ); + } + + /** + * @ticket 35214 + */ + public function test_register_comment_type_return_value() { + $this->assertInstanceOf( 'WP_Comment_Type', register_comment_type( 'foo' ) ); + } + + /** + * @ticket 35214 + * + * @expectedIncorrectUsage register_comment_type + */ + public function test_register_comment_type_with_too_long_name() { + $this->assertInstanceOf( 'WP_Error', register_comment_type( 'comment_type_with_a_too_long_name' ) ); + } + + /** + * @ticket 35214 + * + * @expectedIncorrectUsage register_comment_type + */ + public function test_register_comment_type_with_empty_name() { + $this->assertInstanceOf( 'WP_Error', register_comment_type( '' ) ); + } + + /** + * @ticket 35214 + */ + public function test_register_comment_type_show_ui_should_default_to_value_of_public() { + register_comment_type( 'public_type', array( 'public' => true ) ); + $this->assertTrue( get_comment_type_object( 'public_type' )->show_ui ); + + register_comment_type( 'private_type', array( 'public' => false ) ); + $this->assertFalse( get_comment_type_object( 'private_type' )->show_ui ); + } + + /** + * @ticket 35214 + */ + public function test_built_in_comment_types_are_registered() { + $this->assertTrue( comment_type_exists( 'comment' ) ); + $this->assertTrue( comment_type_exists( 'pingback' ) ); + $this->assertTrue( comment_type_exists( 'trackback' ) ); + $this->assertTrue( comment_type_exists( 'note' ) ); + } + + /** + * @ticket 35214 + */ + public function test_built_in_note_type_is_internal_and_non_public() { + $note = get_comment_type_object( 'note' ); + + $this->assertTrue( $note->internal ); + $this->assertFalse( $note->public ); + } + + /** + * @ticket 35214 + */ + public function test_comment_type_exists() { + $this->assertFalse( comment_type_exists( 'foo' ) ); + + register_comment_type( 'foo' ); + + $this->assertTrue( comment_type_exists( 'foo' ) ); + } + + /** + * @ticket 35214 + */ + public function test_get_comment_types_names() { + register_comment_type( 'foo' ); + + $types = get_comment_types(); + + $this->assertContains( 'comment', $types ); + $this->assertContains( 'foo', $types ); + } + + /** + * @ticket 35214 + */ + public function test_get_comment_types_objects() { + register_comment_type( 'foo' ); + + $types = get_comment_types( array(), 'objects' ); + + $this->assertInstanceOf( 'WP_Comment_Type', $types['foo'] ); + } + + /** + * @ticket 35214 + */ + public function test_get_comment_types_filtered_by_property() { + register_comment_type( 'foo', array( 'public' => false ) ); + + $public = get_comment_types( array( 'public' => true ) ); + + $this->assertContains( 'comment', $public ); + $this->assertNotContains( 'foo', $public ); + $this->assertNotContains( 'note', $public ); + } + + /** + * @ticket 35214 + * + * @covers ::unregister_comment_type + */ + public function test_unregister_comment_type() { + register_comment_type( 'foo' ); + + $this->assertTrue( unregister_comment_type( 'foo' ) ); + $this->assertNull( get_comment_type_object( 'foo' ) ); + } + + /** + * @ticket 35214 + * + * @covers ::unregister_comment_type + */ + public function test_unregister_comment_type_unknown_returns_error() { + $this->assertWPError( unregister_comment_type( 'does_not_exist' ) ); + } + + /** + * @ticket 35214 + * + * @covers ::unregister_comment_type + */ + public function test_unregister_comment_type_twice_returns_error() { + register_comment_type( 'foo' ); + + $this->assertTrue( unregister_comment_type( 'foo' ) ); + $this->assertWPError( unregister_comment_type( 'foo' ) ); + } + + /** + * @ticket 35214 + * + * @covers ::unregister_comment_type + * + * @dataProvider data_built_in_comment_types + */ + public function test_unregister_built_in_comment_type_is_not_allowed( $comment_type ) { + $this->assertWPError( unregister_comment_type( $comment_type ) ); + $this->assertTrue( comment_type_exists( $comment_type ) ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_built_in_comment_types() { + return array( + array( 'comment' ), + array( 'pingback' ), + array( 'trackback' ), + array( 'note' ), + ); + } + + /** + * @ticket 35214 + */ + public function test_registered_comment_type_actions_fire() { + $action = new MockAction(); + $action_for_foo = new MockAction(); + + add_action( 'registered_comment_type', array( $action, 'action' ) ); + add_action( 'registered_comment_type_foo', array( $action_for_foo, 'action' ) ); + + register_comment_type( 'foo' ); + + $this->assertSame( 1, $action->get_call_count() ); + $this->assertSame( 1, $action_for_foo->get_call_count() ); + } + + /** + * @ticket 35214 + */ + public function test_unregistered_comment_type_action_fires() { + register_comment_type( 'foo' ); + + $action = new MockAction(); + add_action( 'unregistered_comment_type', array( $action, 'action' ) ); + + unregister_comment_type( 'foo' ); + + $this->assertSame( 1, $action->get_call_count() ); + } + + /** + * @ticket 35214 + */ + public function test_labels_are_built_from_args() { + register_comment_type( + 'foo', + array( + 'label' => 'Foos', + 'labels' => array( + 'singular_name' => 'Foo', + ), + ) + ); + + $cobj = get_comment_type_object( 'foo' ); + + $this->assertSame( 'Foos', $cobj->label ); + $this->assertSame( 'Foos', $cobj->labels->name ); + $this->assertSame( 'Foo', $cobj->labels->singular_name ); + } + + /** + * @ticket 35214 + */ + public function test_comment_type_labels_filter() { + add_filter( + 'comment_type_labels_foo', + static function ( $labels ) { + $labels->singular_name = 'Filtered Foo'; + return $labels; + } + ); + + register_comment_type( 'foo' ); + + $this->assertSame( 'Filtered Foo', get_comment_type_object( 'foo' )->labels->singular_name ); + } +} diff --git a/tests/phpunit/tests/comment/wpCommentType.php b/tests/phpunit/tests/comment/wpCommentType.php new file mode 100644 index 0000000000000..bf6d3c7df28dd --- /dev/null +++ b/tests/phpunit/tests/comment/wpCommentType.php @@ -0,0 +1,120 @@ +assertSame( 'foo', $comment_type->name ); + $this->assertTrue( $comment_type->public ); + $this->assertFalse( $comment_type->internal ); + $this->assertFalse( $comment_type->_builtin ); + $this->assertTrue( $comment_type->show_ui ); + $this->assertFalse( $comment_type->hierarchical ); + } + + /** + * @ticket 35214 + * + * @covers ::set_props + */ + public function test_set_props_overrides_defaults() { + $comment_type = new WP_Comment_Type( + 'foo', + array( + 'public' => false, + 'internal' => true, + 'description' => 'A test comment type.', + ) + ); + + $this->assertFalse( $comment_type->public ); + $this->assertTrue( $comment_type->internal ); + $this->assertSame( 'A test comment type.', $comment_type->description ); + // show_ui follows public when not explicitly set. + $this->assertFalse( $comment_type->show_ui ); + } + + /** + * @ticket 35214 + * + * @covers ::set_props + */ + public function test_register_comment_type_args_filter() { + $filter = static function ( $args ) { + $args['public'] = false; + return $args; + }; + + add_filter( 'register_comment_type_args', $filter ); + $comment_type = new WP_Comment_Type( 'foo' ); + remove_filter( 'register_comment_type_args', $filter ); + + $this->assertFalse( $comment_type->public ); + } + + /** + * @ticket 35214 + * + * @covers ::set_props + */ + public function test_register_specific_comment_type_args_filter() { + $filter = static function ( $args ) { + $args['description'] = 'Filtered description.'; + return $args; + }; + + add_filter( 'register_foo_comment_type_args', $filter ); + $comment_type = new WP_Comment_Type( 'foo' ); + $other_type = new WP_Comment_Type( 'bar' ); + remove_filter( 'register_foo_comment_type_args', $filter ); + + $this->assertSame( 'Filtered description.', $comment_type->description ); + $this->assertSame( '', $other_type->description ); + } + + /** + * @ticket 35214 + * + * @covers ::get_default_labels + * @covers ::reset_default_labels + */ + public function test_get_default_labels_returns_expected_defaults() { + WP_Comment_Type::reset_default_labels(); + + $labels = WP_Comment_Type::get_default_labels(); + + $this->assertSame( 'Comments', $labels['name'][0] ); + $this->assertSame( 'Comment', $labels['singular_name'][0] ); + } + + /** + * @ticket 35214 + * + * @covers ::get_default_labels + * @covers ::reset_default_labels + */ + public function test_reset_default_labels_clears_cache() { + // Prime the cache, then mutate the returned (by-value) array. + WP_Comment_Type::get_default_labels(); + + WP_Comment_Type::reset_default_labels(); + + // A fresh call rebuilds the defaults from translation functions. + $labels = WP_Comment_Type::get_default_labels(); + $this->assertSame( 'Comments', $labels['name'][0] ); + } +} From 506e9c65351739e8a389c7797cab889a9c60c97a Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 24 Jun 2026 15:02:40 -0700 Subject: [PATCH 03/16] Comments: Fix default labels for comment types registered without a label. 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. --- src/wp-includes/class-wp-comment-type.php | 7 ++++++- tests/phpunit/tests/comment/types.php | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index 374108568e9a3..6f88c5e7b499d 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -180,8 +180,13 @@ public function set_props( $args ) { */ $args = apply_filters( "register_{$comment_type}_comment_type_args", $args, $this->name ); + /* + * Note: 'label' is intentionally omitted from the defaults. Leaving the property + * unset (null) lets get_comment_type_labels() fall back to the default labels, the + * same way WP_Post_Type and WP_Taxonomy behave. A 'label' default of false would be + * treated as a provided value and overwrite the default name with false. + */ $defaults = array( - 'label' => false, 'labels' => array(), 'description' => '', 'public' => true, diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index dd10a6e200d0c..54f8e2aa9ad33 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -49,6 +49,19 @@ public function test_register_comment_type() { $this->assertFalse( $cobj->_builtin ); } + /** + * @ticket 35214 + */ + public function test_register_comment_type_without_labels_uses_default_labels() { + register_comment_type( 'foo' ); + + $cobj = get_comment_type_object( 'foo' ); + + $this->assertSame( 'Comments', $cobj->label ); + $this->assertSame( 'Comments', $cobj->labels->name ); + $this->assertSame( 'Comment', $cobj->labels->singular_name ); + } + /** * @ticket 35214 */ From 81eceff2165adda1c67ef90855fa984fcfe2b87f Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 24 Jun 2026 22:18:28 -0700 Subject: [PATCH 04/16] Comments: Escape the comment type label in comment_type(). 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. --- src/wp-includes/comment-template.php | 2 +- src/wp-includes/comment.php | 3 +++ tests/phpunit/tests/comment/commentType.php | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php index 38991f60929ad..5dc0c76ac3b21 100644 --- a/src/wp-includes/comment-template.php +++ b/src/wp-includes/comment-template.php @@ -1214,7 +1214,7 @@ function comment_type( $comment_text = false, $trackback_text = false, $pingback $comment_type_object = $comment_text_overridden ? null : get_comment_type_object( $type ); if ( $comment_type_object && ! $comment_type_object->_builtin && isset( $comment_type_object->labels->singular_name ) ) { - echo $comment_type_object->labels->singular_name; + echo esc_html( $comment_type_object->labels->singular_name ); } else { echo $comment_text; } diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 2ebc55a1804b0..e8b4f28fe8ed9 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -556,6 +556,9 @@ function get_comment_type_labels( $comment_type_object ) { * * @see get_comment_type_labels() for the full list of comment type labels. * + * Labels are stored unescaped, mirroring the post type and taxonomy label + * contract; callers must escape them on output (for example with esc_html()). + * * @param object $labels Object with labels for the comment type as member variables. */ $labels = apply_filters( "comment_type_labels_{$comment_type}", $labels ); diff --git a/tests/phpunit/tests/comment/commentType.php b/tests/phpunit/tests/comment/commentType.php index da7d316afbe0a..0f0010001204b 100644 --- a/tests/phpunit/tests/comment/commentType.php +++ b/tests/phpunit/tests/comment/commentType.php @@ -114,4 +114,25 @@ public function test_custom_text_override_wins_over_registered_label() { $this->assertSame( 'Custom', $this->get_comment_type_output( 'foo', 'Custom' ) ); } + + /** + * The registered label is escaped on output to guard against HTML/script injection. + * + * @ticket 35214 + */ + public function test_registered_label_is_escaped_on_output() { + register_comment_type( + 'foo', + array( + 'labels' => array( + 'singular_name' => 'Foo', + ), + ) + ); + + $this->assertSame( + esc_html( 'Foo' ), + $this->get_comment_type_output( 'foo' ) + ); + } } From 3e1551be7e88d1d2a3d7adfcd789b3a76d5341cd Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sat, 11 Jul 2026 09:51:58 -0700 Subject: [PATCH 05/16] Tests: Reset comment types between tests in the shared test framework. 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. --- tests/phpunit/includes/abstract-testcase.php | 23 ++++++++++++++++---- tests/phpunit/includes/utils.php | 11 ++++++++++ tests/phpunit/tests/comment/commentType.php | 12 ---------- tests/phpunit/tests/comment/types.php | 22 ------------------- 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index b8e8598362ec5..3cab3b3c042c5 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -123,15 +123,16 @@ public function set_up() { $this->clean_up_global_scope(); /* - * When running core tests, ensure that post types and taxonomies - * are reset for each test. We skip this step for non-core tests, - * given the large number of plugins that register post types and - * taxonomies at 'init'. + * When running core tests, ensure that post types, taxonomies, + * and comment types are reset for each test. We skip this step + * for non-core tests, given the large number of plugins that + * register post types and taxonomies at 'init'. */ if ( defined( 'WP_RUN_CORE_TESTS' ) && WP_RUN_CORE_TESTS ) { $this->reset_post_types(); $this->reset_taxonomies(); $this->reset_post_statuses(); + $this->reset_comment_types(); $this->reset__SERVER(); if ( $wp_rewrite->permalink_structure ) { @@ -350,6 +351,20 @@ protected function reset_taxonomies() { create_initial_taxonomies(); } + /** + * Unregisters existing comment types and registers defaults. + * + * Run before each test in order to clean up the global scope, in case + * a test forgets to unregister a comment type on its own, or fails before + * it has a chance to do so. + */ + protected function reset_comment_types() { + foreach ( get_comment_types( array( '_builtin' => false ) ) as $comment_type ) { + _unregister_comment_type( $comment_type ); + } + create_initial_comment_types(); + } + /** * Unregisters non-built-in post statuses. */ diff --git a/tests/phpunit/includes/utils.php b/tests/phpunit/includes/utils.php index a7c4466338b5d..39ab3d7c822e6 100644 --- a/tests/phpunit/includes/utils.php +++ b/tests/phpunit/includes/utils.php @@ -518,6 +518,17 @@ function _unregister_taxonomy( $taxonomy_name ) { unregister_taxonomy( $taxonomy_name ); } +/** + * Unregisters a comment type. + * + * @since 7.1.0 + * + * @param string $comment_type_name Comment type name. + */ +function _unregister_comment_type( $comment_type_name ) { + unregister_comment_type( $comment_type_name ); +} + /** * Unregister a post status. * diff --git a/tests/phpunit/tests/comment/commentType.php b/tests/phpunit/tests/comment/commentType.php index 0f0010001204b..640d4848d5969 100644 --- a/tests/phpunit/tests/comment/commentType.php +++ b/tests/phpunit/tests/comment/commentType.php @@ -20,18 +20,6 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { self::$post_id = $factory->post->create(); } - public function tear_down() { - global $wp_comment_types; - - foreach ( array_keys( $wp_comment_types ) as $comment_type ) { - if ( ! $wp_comment_types[ $comment_type ]->_builtin ) { - unset( $wp_comment_types[ $comment_type ] ); - } - } - - parent::tear_down(); - } - /** * Returns the output of comment_type() for a comment of the given type. * diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index 54f8e2aa9ad33..56809ae1a1874 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -9,28 +9,6 @@ */ class Tests_Comment_Types extends WP_UnitTestCase { - /** - * Comment type slug used across tests. - * - * @var string - */ - public $comment_type = 'foo'; - - /** - * Ensures any comment type registered during a test is cleaned up. - */ - public function tear_down() { - global $wp_comment_types; - - foreach ( array_keys( $wp_comment_types ) as $comment_type ) { - if ( ! $wp_comment_types[ $comment_type ]->_builtin ) { - unset( $wp_comment_types[ $comment_type ] ); - } - } - - parent::tear_down(); - } - /** * @ticket 35214 */ From 2c7c4953d822610cd45fefc339b76cd824df9b48 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sat, 11 Jul 2026 09:52:59 -0700 Subject: [PATCH 06/16] Comments: Remove the unused 'show_ui' argument from comment type registration. 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. --- src/wp-includes/class-wp-comment-type.php | 16 ---------------- src/wp-includes/comment.php | 2 -- tests/phpunit/tests/comment/types.php | 11 ----------- tests/phpunit/tests/comment/wpCommentType.php | 3 --- 4 files changed, 32 deletions(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index 6f88c5e7b499d..9e3fd3fdb7885 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -84,16 +84,6 @@ final class WP_Comment_Type { */ public $internal = false; - /** - * Whether to generate and allow a UI for managing this comment type in the admin. - * - * Default is the value of $public. - * - * @since 7.1.0 - * @var bool - */ - public $show_ui; - /** * Whether this comment type is a native or "built-in" comment type. * @@ -191,17 +181,11 @@ public function set_props( $args ) { 'description' => '', 'public' => true, 'internal' => false, - 'show_ui' => null, '_builtin' => false, ); $args = array_merge( $defaults, $args ); - // If not set, default to the setting for 'public'. - if ( null === $args['show_ui'] ) { - $args['show_ui'] = $args['public']; - } - $args['name'] = $this->name; foreach ( $args as $property_name => $property_value ) { diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index e8b4f28fe8ed9..8e14dffe1b0c1 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -365,8 +365,6 @@ function create_initial_comment_types() { * the admin interface or by front-end users. Default true. * @type bool $internal Whether the comment type is for internal use only and should be * excluded from default public-facing contexts. Default false. - * @type bool $show_ui Whether to generate and allow a UI for managing this comment type - * in the admin. Default is value of $public. * } * @return WP_Comment_Type|WP_Error The registered comment type object on success, * WP_Error object on failure. diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index 56809ae1a1874..80ad037244dfe 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -65,17 +65,6 @@ public function test_register_comment_type_with_empty_name() { $this->assertInstanceOf( 'WP_Error', register_comment_type( '' ) ); } - /** - * @ticket 35214 - */ - public function test_register_comment_type_show_ui_should_default_to_value_of_public() { - register_comment_type( 'public_type', array( 'public' => true ) ); - $this->assertTrue( get_comment_type_object( 'public_type' )->show_ui ); - - register_comment_type( 'private_type', array( 'public' => false ) ); - $this->assertFalse( get_comment_type_object( 'private_type' )->show_ui ); - } - /** * @ticket 35214 */ diff --git a/tests/phpunit/tests/comment/wpCommentType.php b/tests/phpunit/tests/comment/wpCommentType.php index bf6d3c7df28dd..f0e9a5a6b2a7d 100644 --- a/tests/phpunit/tests/comment/wpCommentType.php +++ b/tests/phpunit/tests/comment/wpCommentType.php @@ -22,7 +22,6 @@ public function test_instance_defaults() { $this->assertTrue( $comment_type->public ); $this->assertFalse( $comment_type->internal ); $this->assertFalse( $comment_type->_builtin ); - $this->assertTrue( $comment_type->show_ui ); $this->assertFalse( $comment_type->hierarchical ); } @@ -44,8 +43,6 @@ public function test_set_props_overrides_defaults() { $this->assertFalse( $comment_type->public ); $this->assertTrue( $comment_type->internal ); $this->assertSame( 'A test comment type.', $comment_type->description ); - // show_ui follows public when not explicitly set. - $this->assertFalse( $comment_type->show_ui ); } /** From bb81be9129f5ea24ed6956b8a3a8ec040e98709f Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sat, 11 Jul 2026 09:53:54 -0700 Subject: [PATCH 07/16] Comments: Reject re-registration of built-in comment types. 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. --- src/wp-includes/comment.php | 25 +++++++++++++++ tests/phpunit/tests/comment/types.php | 45 +++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 8e14dffe1b0c1..4bb7f79c67a47 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -344,6 +344,8 @@ function create_initial_comment_types() { * Registration provides labels and metadata for a type; it does not constrain which values * may be stored. * + * Cannot be used to re-register built-in comment types. + * * @since 7.1.0 * * @global WP_Comment_Type[] $wp_comment_types List of comment types. @@ -376,6 +378,8 @@ function register_comment_type( $comment_type, $args = array() ) { $wp_comment_types = array(); } + $args = wp_parse_args( $args ); + // Sanitize comment type name. $comment_type = sanitize_key( $comment_type ); @@ -384,6 +388,27 @@ function register_comment_type( $comment_type, $args = array() ) { return new WP_Error( 'comment_type_length_invalid', __( 'Comment type names must be between 1 and 20 characters in length.' ) ); } + /* + * Re-registering a built-in comment type could strip flags that core relies on + * for rendering and query behavior, so it is not allowed. Core's own repeated + * registrations (on 'init' and 'change_locale') pass '_builtin' and are exempt. + */ + if ( isset( $wp_comment_types[ $comment_type ] ) + && $wp_comment_types[ $comment_type ]->_builtin + && empty( $args['_builtin'] ) + ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: %s: Comment type key. */ + __( 'The "%s" comment type is a built-in type and cannot be re-registered.' ), + $comment_type + ), + '7.1.0' + ); + return new WP_Error( 'comment_type_builtin', __( 'Built-in comment types cannot be re-registered.' ) ); + } + $comment_type_object = new WP_Comment_Type( $comment_type, $args ); $wp_comment_types[ $comment_type ] = $comment_type_object; diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index 80ad037244dfe..fb6d33fd0beb7 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -191,6 +191,51 @@ public function data_built_in_comment_types() { ); } + /** + * @ticket 35214 + * + * @covers ::register_comment_type + * + * @expectedIncorrectUsage register_comment_type + * + * @dataProvider data_built_in_comment_types + */ + public function test_register_built_in_comment_type_is_rejected( $comment_type ) { + $original_label = get_comment_type_object( $comment_type )->label; + + $result = register_comment_type( $comment_type, array( 'label' => 'Hijacked' ) ); + + $this->assertWPError( $result ); + $this->assertSame( 'comment_type_builtin', $result->get_error_code() ); + $this->assertSame( $original_label, get_comment_type_object( $comment_type )->label ); + } + + /** + * @ticket 35214 + * + * @covers ::register_comment_type + */ + public function test_register_comment_type_twice_overwrites_previous_registration() { + register_comment_type( 'foo', array( 'label' => 'First' ) ); + register_comment_type( 'foo', array( 'label' => 'Second' ) ); + + $this->assertSame( 'Second', get_comment_type_object( 'foo' )->label ); + } + + /** + * @ticket 35214 + * + * @covers ::register_comment_type + * @covers ::unregister_comment_type + */ + public function test_register_after_unregister_succeeds() { + register_comment_type( 'foo' ); + unregister_comment_type( 'foo' ); + + $this->assertInstanceOf( 'WP_Comment_Type', register_comment_type( 'foo' ) ); + $this->assertTrue( comment_type_exists( 'foo' ) ); + } + /** * @ticket 35214 */ From 9efe27b09c78bb890265379037bc36d9ee5b6285 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sat, 11 Jul 2026 09:55:23 -0700 Subject: [PATCH 08/16] Docs: Correct and clarify comment type registration documentation. - 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. --- src/wp-includes/class-wp-comment-type.php | 16 ++++++--- src/wp-includes/comment-template.php | 2 ++ src/wp-includes/comment.php | 41 +++++++++++++---------- src/wp-includes/post.php | 4 +-- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index 9e3fd3fdb7885..de6eff84b7549 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -25,7 +25,7 @@ final class WP_Comment_Type { public $name; /** - * Name of the comment type shown in the menu. Usually plural. + * Name of the comment type. Usually plural. * * @since 7.1.0 * @var string @@ -35,7 +35,7 @@ final class WP_Comment_Type { /** * Labels object for this comment type. * - * If not set, comment labels are inherited. + * If not set, the default comment labels are used. * * @see get_comment_type_labels() * @@ -63,6 +63,11 @@ final class WP_Comment_Type { /** * Whether a comment type is intended for use publicly either via the admin interface or by front-end users. * + * Core does not currently act on this property, but it is the intended default + * for future visibility-related arguments. It defaults to true so that + * registering a type in order to provide labels never hides comments that are + * already publicly visible. + * * Default true. * * @since 7.1.0 @@ -73,9 +78,10 @@ final class WP_Comment_Type { /** * Whether the comment type is for internal use only. * - * Internal comment types (such as `note`) are excluded from default comment listings, counts, - * and other public-facing contexts. This is advisory metadata; the query layer is not affected - * by this property in this release. + * Analogous to the `internal` argument of register_post_status(). Core does not + * currently consult this property: the exclusion of the built-in `note` type + * from default comment queries is hard-coded. The property is intended to drive + * that exclusion for registered types in the future. * * Default false. * diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php index 5dc0c76ac3b21..ce40ee014ac34 100644 --- a/src/wp-includes/comment-template.php +++ b/src/wp-includes/comment-template.php @@ -1179,6 +1179,8 @@ function get_comment_type( $comment_id = 0 ) { * Displays the comment type of the current comment. * * @since 0.71 + * @since 7.1.0 The default output for a registered non-built-in comment type + * falls back to the type's singular name label. * * @param string|false $comment_text Optional. String to display for comment type. Default false. * @param string|false $trackback_text Optional. String to display for trackback type. Default false. diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 4bb7f79c67a47..1d4b788528d6a 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -283,6 +283,11 @@ function get_comments( $args = '' ) { function create_initial_comment_types() { WP_Comment_Type::reset_default_labels(); + /* + * The 'comment', 'pingback', and 'trackback' labels deliberately reuse existing + * core translation strings, while 'note' introduces new strings with explicit + * contexts. Do not normalize one style to the other. + */ register_comment_type( 'comment', array( @@ -337,8 +342,8 @@ function create_initial_comment_types() { * Registers a comment type. * * Note: Comment type registrations should not be hooked before the {@see 'init'} action. - * This is because comment type slugs need to be reserved as part of the upgrade routine - * and global variables need to be available for the comment type to register itself. + * Registering a comment type earlier can result in its labels being generated before + * the current locale's translations are loaded. * * Comment types are stored verbatim in the `comment_type` column of the comments table. * Registration provides labels and metadata for a type; it does not constrain which values @@ -351,22 +356,24 @@ function create_initial_comment_types() { * @global WP_Comment_Type[] $wp_comment_types List of comment types. * * @param string $comment_type Comment type key. Must not exceed 20 characters and may only - * contain lowercase alphanumeric characters, dashes, and underscores. - * See sanitize_key(). + * contain lowercase alphanumeric characters, dashes, and underscores. + * See sanitize_key(). * @param array|string $args { * Optional. Array or string of arguments for registering a comment type. Default empty array. * - * @type string $label Name of the comment type shown in the menu. Usually plural. - * Default is value of $labels['name']. - * @type string[] $labels An array of labels for this comment type. If not set, comment - * labels are inherited. See get_comment_type_labels() for a full - * list of supported labels. + * @type string $label Name of the comment type. Usually plural. + * Default is the value of $labels['name']. + * @type string[] $labels An array of labels for this comment type. If not set, the + * default comment labels are used. See get_comment_type_labels() + * for a full list of supported labels. * @type string $description A short descriptive summary of what the comment type is. * Default empty. * @type bool $public Whether the comment type is intended for use publicly either via - * the admin interface or by front-end users. Default true. - * @type bool $internal Whether the comment type is for internal use only and should be - * excluded from default public-facing contexts. Default false. + * the admin interface or by front-end users. Core does not + * currently act on this argument. Default true. + * @type bool $internal Whether the comment type is for internal use only. Core does not + * currently consult this flag; it is intended to drive default + * query exclusions in the future. Default false. * } * @return WP_Comment_Type|WP_Error The registered comment type object on success, * WP_Error object on failure. @@ -510,7 +517,7 @@ function get_comment_type_object( $comment_type ) { * * @global WP_Comment_Type[] $wp_comment_types List of comment types. * - * @param array|string $args Optional. An array of key => value arguments to match against + * @param array $args Optional. An array of key => value arguments to match against * the comment type objects. Default empty array. * @param string $output Optional. The type of output to return. Either comment type 'names' * or 'objects'. Default 'names'. @@ -548,7 +555,7 @@ function comment_type_exists( $comment_type ) { * @return object { * Comment type labels object. * - * @type string $name General name for the comment type, usually plural. The same and + * @type string $name General name for the comment type, usually plural. The same as and * overridden by `$comment_type_object->label`. Default 'Comments'. * @type string $singular_name Name for one object of this comment type. Default 'Comment'. * @type string $menu_name Label for the menu name. Default is the same as `name`. @@ -575,13 +582,13 @@ function get_comment_type_labels( $comment_type_object ) { * - `comment_type_labels_comment` * - `comment_type_labels_pingback` * + * Labels are stored unescaped, mirroring the post type and taxonomy label + * contract; callers must escape them on output (for example with esc_html()). + * * @since 7.1.0 * * @see get_comment_type_labels() for the full list of comment type labels. * - * Labels are stored unescaped, mirroring the post type and taxonomy label - * contract; callers must escape them on output (for example with esc_html()). - * * @param object $labels Object with labels for the comment type as member variables. */ $labels = apply_filters( "comment_type_labels_{$comment_type}", $labels ); diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 005ccadd62e34..9aee6d74a9b5c 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -2182,8 +2182,8 @@ function get_post_type_labels( $post_type_object ) { } /** - * Builds an object with custom-something object (post type, taxonomy) labels - * out of a custom-something object + * Builds an object with custom-something object (post type, taxonomy, comment type) + * labels out of a custom-something object * * @since 3.0.0 * @access private From 6a1ea10ad1d0eab0fdd070d4fb4bad3e3f6872ff Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sat, 11 Jul 2026 09:58:24 -0700 Subject: [PATCH 09/16] Comments: Keep post-type-only labels out of comment type labels objects. _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. --- src/wp-includes/comment.php | 12 + tests/phpunit/tests/comment/commentType.php | 19 ++ tests/phpunit/tests/comment/types.php | 223 +++++++++++++++++- tests/phpunit/tests/comment/wpCommentType.php | 13 +- 4 files changed, 262 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 1d4b788528d6a..b01a5c3c4bd8e 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -566,8 +566,20 @@ function get_comment_type_labels( $comment_type_object ) { $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name']; + $provided_labels = (array) $comment_type_object->labels; + $labels = _get_custom_object_labels( $comment_type_object, $nohier_vs_hier_defaults ); + /* + * _get_custom_object_labels() derives labels that only apply to post types. + * Remove them unless they were explicitly provided at registration. + */ + foreach ( array( 'name_admin_bar', 'all_items', 'archives' ) as $post_type_only_label ) { + if ( ! array_key_exists( $post_type_only_label, $provided_labels ) ) { + unset( $labels->$post_type_only_label ); + } + } + $comment_type = $comment_type_object->name; $default_labels = clone $labels; diff --git a/tests/phpunit/tests/comment/commentType.php b/tests/phpunit/tests/comment/commentType.php index 640d4848d5969..7d52cc9a20bdb 100644 --- a/tests/phpunit/tests/comment/commentType.php +++ b/tests/phpunit/tests/comment/commentType.php @@ -87,6 +87,25 @@ public function test_unregistered_custom_type_falls_back_to_default_label() { $this->assertSame( _x( 'Comment', 'noun' ), $this->get_comment_type_output( 'bar' ) ); } + /** + * A comment stored with the legacy empty string type is treated as 'comment'. + * + * @ticket 35214 + */ + public function test_legacy_empty_type_outputs_comment() { + $this->assertSame( 'Comment', $this->get_comment_type_output( '' ) ); + } + + /** + * The label fallback must not apply to built-in types: 'note' has 'Note' labels + * but comment_type() output stays 'Comment'. + * + * @ticket 35214 + */ + public function test_built_in_note_type_outputs_default_comment_text() { + $this->assertSame( 'Comment', $this->get_comment_type_output( 'note' ) ); + } + /** * @ticket 35214 */ diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index fb6d33fd0beb7..bd824984fa7bb 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -4,13 +4,14 @@ * Tests for the comment type registration API. * * @group comment - * - * @covers ::register_comment_type */ class Tests_Comment_Types extends WP_UnitTestCase { /** * @ticket 35214 + * + * @covers ::register_comment_type + * @covers ::get_comment_type_object */ public function test_register_comment_type() { $this->assertNull( get_comment_type_object( 'foo' ) ); @@ -29,6 +30,9 @@ public function test_register_comment_type() { /** * @ticket 35214 + * + * @covers ::register_comment_type + * @covers ::get_comment_type_labels */ public function test_register_comment_type_without_labels_uses_default_labels() { register_comment_type( 'foo' ); @@ -42,6 +46,8 @@ public function test_register_comment_type_without_labels_uses_default_labels() /** * @ticket 35214 + * + * @covers ::register_comment_type */ public function test_register_comment_type_return_value() { $this->assertInstanceOf( 'WP_Comment_Type', register_comment_type( 'foo' ) ); @@ -50,6 +56,8 @@ public function test_register_comment_type_return_value() { /** * @ticket 35214 * + * @covers ::register_comment_type + * * @expectedIncorrectUsage register_comment_type */ public function test_register_comment_type_with_too_long_name() { @@ -59,6 +67,8 @@ public function test_register_comment_type_with_too_long_name() { /** * @ticket 35214 * + * @covers ::register_comment_type + * * @expectedIncorrectUsage register_comment_type */ public function test_register_comment_type_with_empty_name() { @@ -67,6 +77,8 @@ public function test_register_comment_type_with_empty_name() { /** * @ticket 35214 + * + * @covers ::create_initial_comment_types */ public function test_built_in_comment_types_are_registered() { $this->assertTrue( comment_type_exists( 'comment' ) ); @@ -77,6 +89,8 @@ public function test_built_in_comment_types_are_registered() { /** * @ticket 35214 + * + * @covers ::create_initial_comment_types */ public function test_built_in_note_type_is_internal_and_non_public() { $note = get_comment_type_object( 'note' ); @@ -87,6 +101,8 @@ public function test_built_in_note_type_is_internal_and_non_public() { /** * @ticket 35214 + * + * @covers ::comment_type_exists */ public function test_comment_type_exists() { $this->assertFalse( comment_type_exists( 'foo' ) ); @@ -98,6 +114,8 @@ public function test_comment_type_exists() { /** * @ticket 35214 + * + * @covers ::get_comment_types */ public function test_get_comment_types_names() { register_comment_type( 'foo' ); @@ -110,6 +128,8 @@ public function test_get_comment_types_names() { /** * @ticket 35214 + * + * @covers ::get_comment_types */ public function test_get_comment_types_objects() { register_comment_type( 'foo' ); @@ -121,6 +141,8 @@ public function test_get_comment_types_objects() { /** * @ticket 35214 + * + * @covers ::get_comment_types */ public function test_get_comment_types_filtered_by_property() { register_comment_type( 'foo', array( 'public' => false ) ); @@ -238,6 +260,8 @@ public function test_register_after_unregister_succeeds() { /** * @ticket 35214 + * + * @covers ::register_comment_type */ public function test_registered_comment_type_actions_fire() { $action = new MockAction(); @@ -254,6 +278,8 @@ public function test_registered_comment_type_actions_fire() { /** * @ticket 35214 + * + * @covers ::unregister_comment_type */ public function test_unregistered_comment_type_action_fires() { register_comment_type( 'foo' ); @@ -268,6 +294,106 @@ public function test_unregistered_comment_type_action_fires() { /** * @ticket 35214 + * + * @covers ::register_comment_type + */ + public function test_register_comment_type_with_20_character_name_succeeds() { + $comment_type = str_repeat( 'a', 20 ); + + $this->assertInstanceOf( 'WP_Comment_Type', register_comment_type( $comment_type ) ); + $this->assertTrue( comment_type_exists( $comment_type ) ); + } + + /** + * @ticket 35214 + * + * @covers ::register_comment_type + */ + public function test_register_comment_type_name_is_sanitized() { + $comment_type_object = register_comment_type( 'Foo Bar!' ); + + $this->assertSame( 'foobar', $comment_type_object->name ); + $this->assertFalse( comment_type_exists( 'Foo Bar!' ) ); + $this->assertTrue( comment_type_exists( 'foobar' ) ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_types + */ + public function test_get_comment_types_with_or_operator() { + register_comment_type( 'foo', array( 'public' => false ) ); + + $types = get_comment_types( + array( + 'public' => true, + 'internal' => true, + ), + 'names', + 'or' + ); + + // 'comment' matches on public, 'note' matches on internal. + $this->assertContains( 'comment', $types ); + $this->assertContains( 'note', $types ); + $this->assertNotContains( 'foo', $types ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_types + */ + public function test_get_comment_types_with_not_operator() { + register_comment_type( 'foo', array( 'internal' => true ) ); + + $types = get_comment_types( array( 'internal' => true ), 'names', 'not' ); + + $this->assertContains( 'comment', $types ); + $this->assertNotContains( 'note', $types ); + $this->assertNotContains( 'foo', $types ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_types + */ + public function test_get_comment_types_names_output_is_keyed_by_type_name() { + register_comment_type( 'foo' ); + + $types = get_comment_types(); + + $this->assertSame( 'foo', $types['foo'] ); + $this->assertSame( 'comment', $types['comment'] ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_type_object + */ + public function test_get_comment_type_object_with_non_scalar_returns_null() { + $this->assertNull( get_comment_type_object( array() ) ); + } + + /** + * @ticket 35214 + * + * @covers ::create_initial_comment_types + */ + public function test_create_initial_comment_types_is_idempotent() { + create_initial_comment_types(); + create_initial_comment_types(); + + $this->assertCount( 4, get_comment_types( array( '_builtin' => true ) ) ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_type_labels */ public function test_labels_are_built_from_args() { register_comment_type( @@ -289,6 +415,8 @@ public function test_labels_are_built_from_args() { /** * @ticket 35214 + * + * @covers ::get_comment_type_labels */ public function test_comment_type_labels_filter() { add_filter( @@ -303,4 +431,95 @@ static function ( $labels ) { $this->assertSame( 'Filtered Foo', get_comment_type_object( 'foo' )->labels->singular_name ); } + + /** + * @ticket 35214 + * + * @covers ::get_comment_type_labels + */ + public function test_label_only_registration_populates_label_fallback_chain() { + register_comment_type( 'foo', array( 'label' => 'Foos' ) ); + + $labels = get_comment_type_object( 'foo' )->labels; + + $this->assertSame( 'Foos', $labels->name ); + $this->assertSame( 'Foos', $labels->singular_name ); + $this->assertSame( 'Foos', $labels->menu_name ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_type_labels + */ + public function test_labels_do_not_include_post_type_only_labels() { + register_comment_type( 'foo', array( 'label' => 'Foos' ) ); + + $labels = get_comment_type_object( 'foo' )->labels; + + $this->assertObjectNotHasProperty( 'name_admin_bar', $labels ); + $this->assertObjectNotHasProperty( 'all_items', $labels ); + $this->assertObjectNotHasProperty( 'archives', $labels ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_type_labels + */ + public function test_labels_do_not_spawn_post_type_only_labels_from_menu_name() { + register_comment_type( + 'foo', + array( + 'label' => 'Foos', + 'labels' => array( + 'menu_name' => 'Foo Menu', + ), + ) + ); + + $labels = get_comment_type_object( 'foo' )->labels; + + $this->assertSame( 'Foo Menu', $labels->menu_name ); + $this->assertObjectNotHasProperty( 'all_items', $labels ); + $this->assertObjectNotHasProperty( 'archives', $labels ); + } + + /** + * @ticket 35214 + * + * @covers ::get_comment_type_labels + */ + public function test_comment_type_labels_filter_missing_name_is_backfilled() { + add_filter( + 'comment_type_labels_foo', + static function ( $labels ) { + unset( $labels->name ); + return $labels; + } + ); + + register_comment_type( 'foo', array( 'label' => 'Foos' ) ); + + $this->assertSame( 'Foos', get_comment_type_object( 'foo' )->labels->name ); + } + + /** + * @ticket 35214 + * + * @covers ::register_comment_type + */ + public function test_registered_comment_type_action_receives_type_and_object() { + $action = new MockAction(); + + add_action( 'registered_comment_type', array( $action, 'action' ), 10, 2 ); + + register_comment_type( 'foo' ); + + $args = $action->get_args(); + + $this->assertSame( 'foo', $args[0][0] ); + $this->assertInstanceOf( 'WP_Comment_Type', $args[0][1] ); + $this->assertSame( 'foo', $args[0][1]->name ); + } } diff --git a/tests/phpunit/tests/comment/wpCommentType.php b/tests/phpunit/tests/comment/wpCommentType.php index f0e9a5a6b2a7d..dc316aac4a735 100644 --- a/tests/phpunit/tests/comment/wpCommentType.php +++ b/tests/phpunit/tests/comment/wpCommentType.php @@ -105,13 +105,20 @@ public function test_get_default_labels_returns_expected_defaults() { * @covers ::reset_default_labels */ public function test_reset_default_labels_clears_cache() { - // Prime the cache, then mutate the returned (by-value) array. - WP_Comment_Type::get_default_labels(); + // Poison the static cache so a stale value is observable. + $property = new ReflectionProperty( WP_Comment_Type::class, 'default_labels' ); + if ( PHP_VERSION_ID < 80100 ) { + $property->setAccessible( true ); + } + $property->setValue( null, array( 'name' => array( 'Poisoned', null ) ) ); + + $labels = WP_Comment_Type::get_default_labels(); + $this->assertSame( 'Poisoned', $labels['name'][0], 'The poisoned cache should be served as-is.' ); WP_Comment_Type::reset_default_labels(); // A fresh call rebuilds the defaults from translation functions. $labels = WP_Comment_Type::get_default_labels(); - $this->assertSame( 'Comments', $labels['name'][0] ); + $this->assertSame( 'Comments', $labels['name'][0], 'Resetting should rebuild the default labels.' ); } } From 413d67733c9f0aa6bec57caa1f03d867b5d9d4c6 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 12 Aug 2026 13:49:48 -0700 Subject: [PATCH 10/16] Build/Test Tools: Reset the whole comment type registry between tests. 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. --- tests/phpunit/includes/abstract-testcase.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index c3bf0b7f22c29..9e7b1351412a6 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -357,11 +357,15 @@ protected function reset_taxonomies() { * Run before each test in order to clean up the global scope, in case * a test forgets to unregister a comment type on its own, or fails before * it has a chance to do so. + * + * The registry is emptied outright rather than unregistered type by type, since a + * test can register a custom type with '_builtin' set and that type would otherwise + * survive into the next test. Registering a comment type creates no hooks, rewrite + * rules, or meta boxes, so dropping the registry is complete cleanup. */ protected function reset_comment_types() { - foreach ( get_comment_types( array( '_builtin' => false ) ) as $comment_type ) { - _unregister_comment_type( $comment_type ); - } + $GLOBALS['wp_comment_types'] = array(); + create_initial_comment_types(); } From 11a49750c0dee0e1f2d05cb36398fc0aabe55411 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 12 Aug 2026 13:50:40 -0700 Subject: [PATCH 11/16] Comments: Ignore a hierarchical argument when registering a comment type. 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. --- src/wp-includes/class-wp-comment-type.php | 12 ++++++++++-- tests/phpunit/tests/comment/types.php | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index de6eff84b7549..fd702ecf4ebe3 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -103,8 +103,9 @@ final class WP_Comment_Type { /** * Whether the comment type is hierarchical. * - * Comment types are never hierarchical. This property exists so the shared - * label helper {@see _get_custom_object_labels()} can resolve default labels. + * Comment types are never hierarchical. This property exists so the shared label + * helper {@see _get_custom_object_labels()} can resolve default labels, and + * set_props() forces it to false so a provided value cannot resolve them to null. * * @since 7.1.0 * @var bool @@ -194,6 +195,13 @@ public function set_props( $args ) { $args['name'] = $this->name; + /* + * Comment types are never hierarchical. The property exists only so the shared + * label helper can pick a slot, and the hierarchical slot is deliberately null, + * so honoring a provided value would resolve every default label to null. + */ + $args['hierarchical'] = false; + foreach ( $args as $property_name => $property_value ) { $this->$property_name = $property_value; } diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index bd824984fa7bb..4cc56a8d124d5 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -522,4 +522,24 @@ public function test_registered_comment_type_action_receives_type_and_object() { $this->assertInstanceOf( 'WP_Comment_Type', $args[0][1] ); $this->assertSame( 'foo', $args[0][1]->name ); } + + /** + * Comment types are never hierarchical. The default labels reserve the hierarchical + * slot as null, so honoring a provided value would resolve every label to null. + * + * @ticket 35214 + * + * @covers WP_Comment_Type::set_props + */ + public function test_register_comment_type_ignores_hierarchical_argument() { + register_comment_type( 'foo', array( 'hierarchical' => true ) ); + + $cobj = get_comment_type_object( 'foo' ); + + $this->assertFalse( $cobj->hierarchical, 'A comment type should never be hierarchical.' ); + $this->assertSame( 'Comments', $cobj->label, 'The default label should survive the argument.' ); + $this->assertSame( 'Comments', $cobj->labels->name ); + $this->assertSame( 'Comment', $cobj->labels->singular_name ); + } + } From e9e894c80d8baa37c7e6d4850c092555c43f60ed Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 12 Aug 2026 13:51:29 -0700 Subject: [PATCH 12/16] Comments: Reserve the query token names and spell out the argument contract. 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. --- src/wp-includes/class-wp-comment-type.php | 7 +- src/wp-includes/comment.php | 47 ++++++- tests/phpunit/tests/comment/types.php | 154 ++++++++++++++++++++++ 3 files changed, 199 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index fd702ecf4ebe3..64566d5ef35d1 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -78,10 +78,9 @@ final class WP_Comment_Type { /** * Whether the comment type is for internal use only. * - * Analogous to the `internal` argument of register_post_status(). Core does not - * currently consult this property: the exclusion of the built-in `note` type - * from default comment queries is hard-coded. The property is intended to drive - * that exclusion for registered types in the future. + * Analogous to the `internal` argument of register_post_status(). Internal types are + * excluded from comment queries and counts by default, through the + * {@see 'default_excluded_comment_types'} filter. * * Default false. * diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 9e0c6eebe6f1c..d8bbc0158922d 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -404,9 +404,25 @@ function create_initial_comment_types() { * * Comment types are stored verbatim in the `comment_type` column of the comments table. * Registration provides labels and metadata for a type; it does not constrain which values - * may be stored. + * may be stored. Comment types that are stored but never registered keep behaving exactly + * as they did before this API existed. * - * Cannot be used to re-register built-in comment types. + * Each argument drives exactly one layer of behavior, so the flags stay independently + * meaningful as more of them are added: + * + * - `public` states display-surface intent: whether the type is meant to be seen by site + * visitors. It does not affect what queries return. + * - `internal` marks the type as excluded from comment queries and counts by default. + * + * An argument never implies another argument. The one cascade planned for the future is + * `show_in_rest`, which will default from `public`. + * + * Registrations live in a per-process global. Like post types, they are not scoped to a + * site on multisite: a type registered by one site's plugins is visible after + * switch_to_blog() for the rest of the request. + * + * Cannot be used to re-register built-in comment types. The names WP_Comment_Query reads + * as query tokens ('all', 'comments', 'pings') cannot be registered either. * * @since 7.1.0 * @@ -428,9 +444,12 @@ function create_initial_comment_types() { * @type bool $public Whether the comment type is intended for use publicly either via * the admin interface or by front-end users. Core does not * currently act on this argument. Default true. - * @type bool $internal Whether the comment type is for internal use only. Core does not - * currently consult this flag; it is intended to drive default - * query exclusions in the future. Default false. + * @type bool $internal Whether the comment type is for internal use only. Internal types + * are excluded from comment queries and counts by default, through + * the {@see 'default_excluded_comment_types'} filter. Default false. + * @type bool $_builtin For internal core use only. Marks the type as native to + * WordPress, which blocks it from being re-registered or + * unregistered. Default false. * } * @return WP_Comment_Type|WP_Error The registered comment type object on success, * WP_Error object on failure. @@ -473,6 +492,24 @@ function register_comment_type( $comment_type, $args = array() ) { return new WP_Error( 'comment_type_builtin', __( 'Built-in comment types cannot be re-registered.' ) ); } + /* + * WP_Comment_Query reads these names as query tokens rather than as literal + * comment_type values, so a type registered under one of them could never be + * queried for on its own. + */ + if ( in_array( $comment_type, array( 'all', 'comments', 'pings' ), true ) ) { + _doing_it_wrong( + __FUNCTION__, + sprintf( + /* translators: %s: Comment type key. */ + __( 'The "%s" comment type name is reserved for use by WP_Comment_Query.' ), + $comment_type + ), + '7.1.0' + ); + return new WP_Error( 'comment_type_reserved', __( 'This comment type name is reserved.' ) ); + } + $comment_type_object = new WP_Comment_Type( $comment_type, $args ); $wp_comment_types[ $comment_type ] = $comment_type_object; diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index 4cc56a8d124d5..79774eb440b18 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -542,4 +542,158 @@ public function test_register_comment_type_ignores_hierarchical_argument() { $this->assertSame( 'Comment', $cobj->labels->singular_name ); } + /** + * The names WP_Comment_Query reads as query tokens cannot be registered, since a type + * stored under one of them could never be queried for on its own. + * + * @ticket 35214 + * + * @covers ::register_comment_type + * + * @dataProvider data_reserved_comment_type_names + * + * @expectedIncorrectUsage register_comment_type + * + * @param string $comment_type Reserved comment type name. + */ + public function test_register_reserved_comment_type_is_rejected( string $comment_type ) { + $result = register_comment_type( $comment_type ); + + $this->assertInstanceOf( 'WP_Error', $result ); + $this->assertSame( 'comment_type_reserved', $result->get_error_code() ); + $this->assertNull( get_comment_type_object( $comment_type ) ); + } + + /** + * Data provider for test_register_reserved_comment_type_is_rejected(). + * + * @return array + */ + public function data_reserved_comment_type_names(): array { + return array( + 'all type token' => array( 'all' ), + 'comments alias' => array( 'comments' ), + 'pings bucket key' => array( 'pings' ), + ); + } + + /** + * The built-in guard reads the raw arguments, so passing '_builtin' bypasses it. This + * matches register_post_type(), where '_builtin' is an accepted internal-use argument + * with no guard at all. Pinned so the follow-ups that give '_builtin' more meaning + * cannot change it by accident. + * + * @ticket 35214 + * + * @covers ::register_comment_type + */ + public function test_register_comment_type_builtin_argument_bypasses_the_built_in_guard() { + $result = register_comment_type( + 'pingback', + array( + '_builtin' => true, + 'label' => 'Hijacked', + ) + ); + + $this->assertInstanceOf( 'WP_Comment_Type', $result, 'Passing _builtin bypasses the guard.' ); + $this->assertSame( 'Hijacked', get_comment_type_object( 'pingback' )->label ); + } + + /** + * Passing '_builtin' on a custom type makes it behave like a built-in for everyone + * else: it can no longer be re-registered or unregistered. + * + * @ticket 35214 + * + * @covers ::register_comment_type + * @covers ::unregister_comment_type + * + * @expectedIncorrectUsage register_comment_type + */ + public function test_register_comment_type_builtin_argument_locks_a_custom_type() { + register_comment_type( 'foo', array( '_builtin' => true ) ); + + $this->assertInstanceOf( 'WP_Error', register_comment_type( 'foo' ) ); + $this->assertInstanceOf( 'WP_Error', unregister_comment_type( 'foo' ) ); + } + + /** + * Built-in labels are rebuilt on a locale change rather than served from the static + * default-labels cache. + * + * @ticket 35214 + * + * @covers ::create_initial_comment_types + */ + public function test_built_in_labels_are_rebuilt_on_locale_change() { + $original = get_comment_type_object( 'comment' )->label; + + add_filter( + 'gettext', + static function ( $translation, $text ) { + return 'Comments' === $text ? 'Kommentare' : $translation; + }, + 10, + 2 + ); + + do_action( 'change_locale', 'de_DE' ); + + $this->assertSame( + 'Kommentare', + get_comment_type_object( 'comment' )->label, + 'A locale change should rebuild the built-in labels.' + ); + $this->assertNotSame( $original, get_comment_type_object( 'comment' )->label ); + } + + /** + * Before wp-settings.php registers the built-ins, the registry global does not exist. + * Both accessors have to cope with that rather than warn. + * + * @ticket 35214 + * + * @covers ::get_comment_types + * @covers ::get_comment_type_object + */ + public function test_accessors_handle_an_unset_registry() { + $registry = $GLOBALS['wp_comment_types']; + unset( $GLOBALS['wp_comment_types'] ); + + try { + $this->assertSame( array(), get_comment_types() ); + $this->assertNull( get_comment_type_object( 'comment' ) ); + } finally { + $GLOBALS['wp_comment_types'] = $registry; + } + } + + /** + * The registry is a per-process global, so it is not scoped to a site. This matches + * post types, and is worth pinning because comment counts and query exclusions are + * per-site data. + * + * @ticket 35214 + * + * @group ms-required + * + * @covers ::register_comment_type + */ + public function test_registry_is_not_scoped_to_a_site() { + register_comment_type( 'foo' ); + + $blog_id = self::factory()->blog->create(); + + switch_to_blog( $blog_id ); + + $registered_after_switch = comment_type_exists( 'foo' ); + + restore_current_blog(); + + $this->assertTrue( + $registered_after_switch, + 'A registered comment type should still be registered after switch_to_blog().' + ); + } } From 1504bdf4961c141ee640103e1b8ec04292f9a969 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 23 Aug 2026 08:11:26 -0700 Subject: [PATCH 13/16] Comments: Stop get_comment_type_labels() from modifying the passed object. _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. --- src/wp-includes/comment.php | 8 +++++++- tests/phpunit/tests/comment/types.php | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index d8bbc0158922d..a8aa0d8f4b232 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -662,7 +662,13 @@ function get_comment_type_labels( $comment_type_object ) { $provided_labels = (array) $comment_type_object->labels; - $labels = _get_custom_object_labels( $comment_type_object, $nohier_vs_hier_defaults ); + /* + * _get_custom_object_labels() writes every label it derives back onto the object + * it is given, including the post-type-only labels removed below. Hand it a copy + * so calling this function on a registered comment type leaves the registered + * object untouched. + */ + $labels = _get_custom_object_labels( clone $comment_type_object, $nohier_vs_hier_defaults ); /* * _get_custom_object_labels() derives labels that only apply to post types. diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php index 79774eb440b18..58fdb791127b0 100644 --- a/tests/phpunit/tests/comment/types.php +++ b/tests/phpunit/tests/comment/types.php @@ -462,6 +462,25 @@ public function test_labels_do_not_include_post_type_only_labels() { $this->assertObjectNotHasProperty( 'archives', $labels ); } + /** + * @ticket 35214 + * + * @covers ::get_comment_type_labels + */ + public function test_get_comment_type_labels_does_not_modify_a_registered_type() { + register_comment_type( 'foo', array( 'label' => 'Foos' ) ); + + $comment_type_object = get_comment_type_object( 'foo' ); + $registered_labels = get_object_vars( $comment_type_object->labels ); + + $first = get_comment_type_labels( $comment_type_object ); + $second = get_comment_type_labels( $comment_type_object ); + + $this->assertSame( $registered_labels, get_object_vars( $comment_type_object->labels ), 'The registered labels should not change.' ); + $this->assertEquals( $first, $second, 'Repeated calls should return the same labels.' ); + $this->assertObjectNotHasProperty( 'archives', $second, 'Post-type-only labels should not appear on repeated calls.' ); + } + /** * @ticket 35214 * From 8a21e22effa7c6734c262fd05d03efa871273bc8 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 23 Aug 2026 08:11:55 -0700 Subject: [PATCH 14/16] Docs: Describe the internal comment type flag as intent, not behavior. 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. --- src/wp-includes/class-wp-comment-type.php | 6 +++--- src/wp-includes/comment.php | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index 64566d5ef35d1..90ca3baee9423 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -78,9 +78,9 @@ final class WP_Comment_Type { /** * Whether the comment type is for internal use only. * - * Analogous to the `internal` argument of register_post_status(). Internal types are - * excluded from comment queries and counts by default, through the - * {@see 'default_excluded_comment_types'} filter. + * Analogous to the `internal` argument of register_post_status(). Internal types + * are meant to be excluded from comment queries and counts by default. Core does + * not currently act on this flag. * * Default false. * diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index a8aa0d8f4b232..c67de15709469 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -412,7 +412,8 @@ function create_initial_comment_types() { * * - `public` states display-surface intent: whether the type is meant to be seen by site * visitors. It does not affect what queries return. - * - `internal` marks the type as excluded from comment queries and counts by default. + * - `internal` states query-surface intent: whether the type should be excluded from + * comment queries and counts by default. * * An argument never implies another argument. The one cascade planned for the future is * `show_in_rest`, which will default from `public`. @@ -444,9 +445,10 @@ function create_initial_comment_types() { * @type bool $public Whether the comment type is intended for use publicly either via * the admin interface or by front-end users. Core does not * currently act on this argument. Default true. - * @type bool $internal Whether the comment type is for internal use only. Internal types - * are excluded from comment queries and counts by default, through - * the {@see 'default_excluded_comment_types'} filter. Default false. + * @type bool $internal Whether the comment type is for internal use only. Internal + * types are meant to be excluded from comment queries and counts + * by default. Core does not currently act on this argument. + * Default false. * @type bool $_builtin For internal core use only. Marks the type as native to * WordPress, which blocks it from being re-registered or * unregistered. Default false. From 16b2c4f2728f2611d2395f2b1e6ec3cf91b34846 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 23 Aug 2026 08:12:21 -0700 Subject: [PATCH 15/16] Build/Test Tools: Drop the unused _unregister_comment_type() helper. 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. --- tests/phpunit/includes/utils.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/tests/phpunit/includes/utils.php b/tests/phpunit/includes/utils.php index 0beda29f9bd67..02254903fa745 100644 --- a/tests/phpunit/includes/utils.php +++ b/tests/phpunit/includes/utils.php @@ -582,17 +582,6 @@ function _unregister_taxonomy( $taxonomy_name ) { unregister_taxonomy( $taxonomy_name ); } -/** - * Unregisters a comment type. - * - * @since 7.1.0 - * - * @param string $comment_type_name Comment type name. - */ -function _unregister_comment_type( $comment_type_name ) { - unregister_comment_type( $comment_type_name ); -} - /** * Unregister a post status. * From 1473c5bec23d9854bea952ed1badeead00abce3c Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Sun, 23 Aug 2026 08:13:05 -0700 Subject: [PATCH 16/16] Comments: Stamp the comment type API for 7.2.0. 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. --- src/wp-includes/class-wp-comment-type.php | 34 +++++++++++------------ src/wp-includes/comment-template.php | 2 +- src/wp-includes/comment.php | 28 +++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/wp-includes/class-wp-comment-type.php b/src/wp-includes/class-wp-comment-type.php index 90ca3baee9423..bb7b22ebdf2fc 100644 --- a/src/wp-includes/class-wp-comment-type.php +++ b/src/wp-includes/class-wp-comment-type.php @@ -4,13 +4,13 @@ * * @package WordPress * @subpackage Comments - * @since 7.1.0 + * @since 7.2.0 */ /** * Core class used for interacting with comment types. * - * @since 7.1.0 + * @since 7.2.0 * * @see register_comment_type() */ @@ -19,7 +19,7 @@ final class WP_Comment_Type { /** * Comment type key. * - * @since 7.1.0 + * @since 7.2.0 * @var string */ public $name; @@ -27,7 +27,7 @@ final class WP_Comment_Type { /** * Name of the comment type. Usually plural. * - * @since 7.1.0 + * @since 7.2.0 * @var string */ public $label; @@ -39,7 +39,7 @@ final class WP_Comment_Type { * * @see get_comment_type_labels() * - * @since 7.1.0 + * @since 7.2.0 * @var stdClass */ public $labels; @@ -47,7 +47,7 @@ final class WP_Comment_Type { /** * Default labels. * - * @since 7.1.0 + * @since 7.2.0 * @var (string|null)[][] $default_labels */ protected static $default_labels = array(); @@ -55,7 +55,7 @@ final class WP_Comment_Type { /** * A short descriptive summary of what the comment type is for. * - * @since 7.1.0 + * @since 7.2.0 * @var string */ public $description = ''; @@ -70,7 +70,7 @@ final class WP_Comment_Type { * * Default true. * - * @since 7.1.0 + * @since 7.2.0 * @var bool */ public $public = true; @@ -84,7 +84,7 @@ final class WP_Comment_Type { * * Default false. * - * @since 7.1.0 + * @since 7.2.0 * @var bool */ public $internal = false; @@ -94,7 +94,7 @@ final class WP_Comment_Type { * * Default false. * - * @since 7.1.0 + * @since 7.2.0 * @var bool */ public $_builtin = false; @@ -106,7 +106,7 @@ final class WP_Comment_Type { * helper {@see _get_custom_object_labels()} can resolve default labels, and * set_props() forces it to false so a provided value cannot resolve them to null. * - * @since 7.1.0 + * @since 7.2.0 * @var bool */ public $hierarchical = false; @@ -119,7 +119,7 @@ final class WP_Comment_Type { * Will populate object properties from the provided arguments and assign other * default properties based on that information. * - * @since 7.1.0 + * @since 7.2.0 * * @see register_comment_type() * @@ -138,7 +138,7 @@ public function __construct( $comment_type, $args = array() ) { * * See the register_comment_type() function for accepted arguments for `$args`. * - * @since 7.1.0 + * @since 7.2.0 * * @param array|string $args Array or string of arguments for registering a comment type. */ @@ -148,7 +148,7 @@ public function set_props( $args ) { /** * Filters the arguments for registering a comment type. * - * @since 7.1.0 + * @since 7.2.0 * * @param array $args Array of arguments for registering a comment type. * See the register_comment_type() function for accepted arguments. @@ -168,7 +168,7 @@ public function set_props( $args ) { * - `register_comment_comment_type_args` * - `register_pingback_comment_type_args` * - * @since 7.1.0 + * @since 7.2.0 * * @param array $args Array of arguments for registering a comment type. * See the register_comment_type() function for accepted arguments. @@ -212,7 +212,7 @@ public function set_props( $args ) { /** * Returns the default labels for comment types. * - * @since 7.1.0 + * @since 7.2.0 * * @return (string|null)[][] The default labels for comment types. */ @@ -232,7 +232,7 @@ public static function get_default_labels() { /** * Resets the cache for the default labels. * - * @since 7.1.0 + * @since 7.2.0 */ public static function reset_default_labels() { self::$default_labels = array(); diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php index ce40ee014ac34..77702035862dc 100644 --- a/src/wp-includes/comment-template.php +++ b/src/wp-includes/comment-template.php @@ -1179,7 +1179,7 @@ function get_comment_type( $comment_id = 0 ) { * Displays the comment type of the current comment. * * @since 0.71 - * @since 7.1.0 The default output for a registered non-built-in comment type + * @since 7.2.0 The default output for a registered non-built-in comment type * falls back to the type's singular name label. * * @param string|false $comment_text Optional. String to display for comment type. Default false. diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index c67de15709469..10998c7c5d396 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -335,7 +335,7 @@ function get_comments( $args = '' ) { * * See register_comment_type() for accepted arguments. * - * @since 7.1.0 + * @since 7.2.0 */ function create_initial_comment_types() { WP_Comment_Type::reset_default_labels(); @@ -425,7 +425,7 @@ function create_initial_comment_types() { * Cannot be used to re-register built-in comment types. The names WP_Comment_Query reads * as query tokens ('all', 'comments', 'pings') cannot be registered either. * - * @since 7.1.0 + * @since 7.2.0 * * @global WP_Comment_Type[] $wp_comment_types List of comment types. * @@ -469,7 +469,7 @@ function register_comment_type( $comment_type, $args = array() ) { $comment_type = sanitize_key( $comment_type ); if ( empty( $comment_type ) || strlen( $comment_type ) > 20 ) { - _doing_it_wrong( __FUNCTION__, __( 'Comment type names must be between 1 and 20 characters in length.' ), '7.1.0' ); + _doing_it_wrong( __FUNCTION__, __( 'Comment type names must be between 1 and 20 characters in length.' ), '7.2.0' ); return new WP_Error( 'comment_type_length_invalid', __( 'Comment type names must be between 1 and 20 characters in length.' ) ); } @@ -489,7 +489,7 @@ function register_comment_type( $comment_type, $args = array() ) { __( 'The "%s" comment type is a built-in type and cannot be re-registered.' ), $comment_type ), - '7.1.0' + '7.2.0' ); return new WP_Error( 'comment_type_builtin', __( 'Built-in comment types cannot be re-registered.' ) ); } @@ -507,7 +507,7 @@ function register_comment_type( $comment_type, $args = array() ) { __( 'The "%s" comment type name is reserved for use by WP_Comment_Query.' ), $comment_type ), - '7.1.0' + '7.2.0' ); return new WP_Error( 'comment_type_reserved', __( 'This comment type name is reserved.' ) ); } @@ -519,7 +519,7 @@ function register_comment_type( $comment_type, $args = array() ) { /** * Fires after a comment type is registered. * - * @since 7.1.0 + * @since 7.2.0 * * @param string $comment_type Comment type key. * @param WP_Comment_Type $comment_type_object Comment type object. @@ -536,7 +536,7 @@ function register_comment_type( $comment_type, $args = array() ) { * - `registered_comment_type_comment` * - `registered_comment_type_pingback` * - * @since 7.1.0 + * @since 7.2.0 * * @param string $comment_type Comment type key. * @param WP_Comment_Type $comment_type_object Comment type object. @@ -551,7 +551,7 @@ function register_comment_type( $comment_type, $args = array() ) { * * Cannot be used to unregister built-in comment types. * - * @since 7.1.0 + * @since 7.2.0 * * @global WP_Comment_Type[] $wp_comment_types List of comment types. * @@ -577,7 +577,7 @@ function unregister_comment_type( $comment_type ) { /** * Fires after a comment type is unregistered. * - * @since 7.1.0 + * @since 7.2.0 * * @param string $comment_type Comment type key. */ @@ -589,7 +589,7 @@ function unregister_comment_type( $comment_type ) { /** * Retrieves a comment type object by name. * - * @since 7.1.0 + * @since 7.2.0 * * @global WP_Comment_Type[] $wp_comment_types List of comment types. * @@ -609,7 +609,7 @@ function get_comment_type_object( $comment_type ) { /** * Retrieves a list of registered comment type names or objects. * - * @since 7.1.0 + * @since 7.2.0 * * @global WP_Comment_Type[] $wp_comment_types List of comment types. * @@ -633,7 +633,7 @@ function get_comment_types( $args = array(), $output = 'names', $operator = 'and /** * Determines whether a comment type is registered. * - * @since 7.1.0 + * @since 7.2.0 * * @param string $comment_type Comment type name. * @return bool Whether the comment type is registered. @@ -645,7 +645,7 @@ function comment_type_exists( $comment_type ) { /** * Builds an object with all comment type labels out of a comment type object. * - * @since 7.1.0 + * @since 7.2.0 * * @param WP_Comment_Type $comment_type_object Comment type object. * @return object { @@ -699,7 +699,7 @@ function get_comment_type_labels( $comment_type_object ) { * Labels are stored unescaped, mirroring the post type and taxonomy label * contract; callers must escape them on output (for example with esc_html()). * - * @since 7.1.0 + * @since 7.2.0 * * @see get_comment_type_labels() for the full list of comment type labels. *