Array of comments keyed by comment type.
@@ -1405,7 +1414,9 @@ function separate_comments( &$comments ) {
$comments_by_type[ $type ][] = &$comments[ $i ];
- if ( 'trackback' === $type || 'pingback' === $type ) {
+ $comment_type_object = get_comment_type_object( $type );
+
+ if ( $comment_type_object && $comment_type_object->is_ping ) {
$comments_by_type['pings'][] = &$comments[ $i ];
}
}
@@ -1485,7 +1496,8 @@ function get_comment_pages_count( $comments = null, $per_page = null, $threaded
*
* @type string $type Limit paginated comments to those matching a given type.
* Accepts 'comment', 'trackback', 'pingback', 'pings'
- * (trackbacks and pingbacks), or 'all'. Default 'all'.
+ * (the comments of every registered comment type with
+ * `is_ping`), or 'all'. Default 'all'.
* @type int $per_page Per-page count to use when calculating pagination.
* Defaults to the value of the 'comments_per_page' option.
* @type int|string $max_depth If greater than 1, comment page will be determined
@@ -1585,7 +1597,8 @@ function get_page_of_comment( $comment_id, $args = array() ) {
*
* @type string $type Limit paginated comments to those matching a given type.
* Accepts 'comment', 'trackback', 'pingback', 'pings'
- * (trackbacks and pingbacks), or 'all'. Default 'all'.
+ * (the comments of every registered comment type with
+ * `is_ping`), or 'all'. Default 'all'.
* @type int $post_id ID of the post.
* @type string $fields Comment fields to return.
* @type bool $count Whether to return a comment count (true) or array
diff --git a/tests/phpunit/tests/comment/query.php b/tests/phpunit/tests/comment/query.php
index b19ff930b9d0b..6d4c9f2ca1151 100644
--- a/tests/phpunit/tests/comment/query.php
+++ b/tests/phpunit/tests/comment/query.php
@@ -342,6 +342,186 @@ public function test_query_type_pings() {
$this->assertSameSets( array( $c2, $c3 ), $found );
}
+ /**
+ * A 'pings' query returns every registered ping type, matching what
+ * separate_comments() groups under the same name.
+ *
+ * @ticket 35214
+ *
+ * @covers WP_Comment_Query::query
+ */
+ public function test_query_type_pings_includes_registered_ping_types(): void {
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $pingback = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'pingback',
+ )
+ );
+ $mention = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'webmention',
+ )
+ );
+ self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'review',
+ )
+ );
+
+ $q = new WP_Comment_Query();
+ $found = $q->query(
+ array(
+ 'type' => 'pings',
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSameSets( array( $pingback, $mention ), $found );
+ }
+
+ /**
+ * Excluding 'pings' excludes every registered ping type.
+ *
+ * @ticket 35214
+ *
+ * @covers WP_Comment_Query::query
+ */
+ public function test_query_type__not_in_pings_excludes_registered_ping_types(): void {
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $comment = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ )
+ );
+ $review = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'review',
+ )
+ );
+
+ foreach ( array( 'pingback', 'trackback', 'webmention' ) as $ping_type ) {
+ self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => $ping_type,
+ )
+ );
+ }
+
+ $q = new WP_Comment_Query();
+ $found = $q->query(
+ array(
+ 'type__not_in' => 'pings',
+ 'fields' => 'ids',
+ )
+ );
+
+ $this->assertSameSets( array( $comment, $review ), $found );
+ }
+
+ /**
+ * The registered ping types are part of the 'pings' cache key. Otherwise a plugin
+ * registering a ping type would keep serving results cached before it existed, since
+ * the comment last_changed salt only moves when a comment does.
+ *
+ * @ticket 35214
+ *
+ * @covers WP_Comment_Query::get_comments
+ */
+ public function test_query_type_pings_is_not_served_from_a_stale_cache(): void {
+ $pingback = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'pingback',
+ )
+ );
+ $mention = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'webmention',
+ )
+ );
+
+ $args = array(
+ 'type' => 'pings',
+ 'fields' => 'ids',
+ );
+
+ $before = ( new WP_Comment_Query() )->query( $args );
+
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $after = ( new WP_Comment_Query() )->query( $args );
+
+ $this->assertSameSets( array( $pingback ), $before, 'Before registration the type is not a ping.' );
+ $this->assertSameSets( array( $pingback, $mention ), $after, 'After registration it is.' );
+ }
+
+ /**
+ * The resolved ping set is part of the per-parent descendant cache keys too.
+ * Otherwise a threaded 'pings' query run before a ping type existed would keep
+ * serving that parent's children from the stale child ID cache even though the
+ * top-level results update.
+ *
+ * @ticket 35214
+ *
+ * @covers WP_Comment_Query::fill_descendants
+ */
+ public function test_threaded_pings_descendants_are_not_served_from_a_stale_cache(): void {
+ $pingback = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'pingback',
+ )
+ );
+ $child = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => self::$post_id,
+ 'comment_approved' => '1',
+ 'comment_type' => 'webmention',
+ 'comment_parent' => $pingback,
+ )
+ );
+
+ $args = array(
+ 'type' => 'pings',
+ 'hierarchical' => 'threaded',
+ 'post_id' => self::$post_id,
+ );
+
+ $before = ( new WP_Comment_Query() )->query( $args );
+
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $after = ( new WP_Comment_Query() )->query( $args );
+
+ $this->assertSame(
+ array(),
+ $before[ $pingback ]->get_children(),
+ 'Before registration the child type is not a ping, so the parent has no children.'
+ );
+ $this->assertSame(
+ array( $child ),
+ array_values( array_map( 'intval', wp_list_pluck( $after[ $pingback ]->get_children(), 'comment_ID' ) ) ),
+ 'After registration the child should not be hidden by a stale descendant cache.'
+ );
+ }
+
/**
* Comments and custom
*
diff --git a/tests/phpunit/tests/comment/separateComments.php b/tests/phpunit/tests/comment/separateComments.php
index b1fa2d276ae8d..c5f61569b0446 100644
--- a/tests/phpunit/tests/comment/separateComments.php
+++ b/tests/phpunit/tests/comment/separateComments.php
@@ -105,4 +105,36 @@ public function test_groups_hold_references_to_the_passed_array() {
$this->assertSame( $replacement, $comments[0], 'Replacing an element in a group should replace it in the passed array.' );
$this->assertSame( $replacement, $grouped['pings'][0], 'The pings group should reference the same element.' );
}
+
+ /**
+ * A registered comment type marked as a ping is grouped into 'pings'.
+ *
+ * @ticket 35214
+ */
+ public function test_registered_ping_type_is_grouped_into_pings_bucket(): void {
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $comments = array( $this->create_comment( 'webmention' ) );
+
+ $separated = separate_comments( $comments );
+
+ $this->assertCount( 1, $separated['webmention'] );
+ $this->assertCount( 1, $separated['pings'] );
+ }
+
+ /**
+ * A registered comment type that is not a ping stays out of the 'pings' bucket.
+ *
+ * @ticket 35214
+ */
+ public function test_non_ping_type_is_not_grouped_into_pings_bucket(): void {
+ register_comment_type( 'review' );
+
+ $comments = array( $this->create_comment( 'review' ) );
+
+ $separated = separate_comments( $comments );
+
+ $this->assertCount( 1, $separated['review'] );
+ $this->assertCount( 0, $separated['pings'] );
+ }
}
diff --git a/tests/phpunit/tests/comment/types.php b/tests/phpunit/tests/comment/types.php
index e1702e5e5c720..7198eeb24cb57 100644
--- a/tests/phpunit/tests/comment/types.php
+++ b/tests/phpunit/tests/comment/types.php
@@ -113,6 +113,27 @@ public function test_built_in_note_type_is_internal_and_non_public(): void {
$this->assertFalse( $note->public );
}
+ /**
+ * @ticket 35214
+ *
+ * @covers ::create_initial_comment_types
+ */
+ public function test_built_in_ping_types_are_marked_as_pings(): void {
+ $this->assertTrue( get_comment_type_object( 'pingback' )->is_ping );
+ $this->assertTrue( get_comment_type_object( 'trackback' )->is_ping );
+ }
+
+ /**
+ * @ticket 35214
+ *
+ * @covers ::create_initial_comment_types
+ */
+ public function test_built_in_non_ping_types_are_not_marked_as_pings(): void {
+ $this->assertFalse( get_comment_type_object( 'comment' )->is_ping );
+ $this->assertFalse( get_comment_type_object( 'note' )->is_ping );
+ $this->assertFalse( get_comment_type_object( 'reaction' )->is_ping );
+ }
+
/**
* @ticket 35214
*
diff --git a/tests/phpunit/tests/comment/walker.php b/tests/phpunit/tests/comment/walker.php
index e68472ca62663..4e33314afbef0 100644
--- a/tests/phpunit/tests/comment/walker.php
+++ b/tests/phpunit/tests/comment/walker.php
@@ -214,6 +214,246 @@ public function test_explicit_callback_takes_precedence_over_render_callback():
$this->assertStringNotContainsString( 'FROM_TYPE_CALLBACK', $output );
}
+ /**
+ * A registered ping type renders with the compact ping markup when short_ping is on:
+ * a label, the author link, and no comment body.
+ *
+ * @ticket 35214
+ */
+ public function test_registered_ping_type_renders_as_ping(): void {
+ register_comment_type(
+ 'webmention',
+ array(
+ 'is_ping' => true,
+ 'labels' => array( 'singular_name' => 'Webmention' ),
+ )
+ );
+
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'webmention',
+ 'comment_content' => 'A webmention body',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => true )
+ );
+
+ $this->assertStringContainsString( '', $output, 'The compact ping markup should be used.' );
+ $this->assertStringContainsString( 'class="url"', $output, 'The author link should be rendered.' );
+ $this->assertStringNotContainsString( 'A webmention body', $output, 'The comment body should be omitted.' );
+ }
+
+ /**
+ * The user-visible end of the grouping change: a theme listing 'pings' gets the
+ * registered ping type alongside the built-ins, and nothing else.
+ *
+ * @ticket 35214
+ */
+ public function test_wp_list_comments_type_pings_lists_registered_ping_types(): void {
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $comments = array();
+
+ foreach ( array( 'comment', 'pingback', 'webmention' ) as $comment_type ) {
+ $comments[] = get_comment(
+ self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => $comment_type,
+ 'comment_author' => "Author of a $comment_type",
+ 'comment_approved' => '1',
+ )
+ )
+ );
+ }
+
+ $output = $this->render_comments( $comments, array( 'type' => 'pings' ) );
+
+ $this->assertStringContainsString( 'Author of a webmention', $output );
+ $this->assertStringContainsString( 'Author of a pingback', $output );
+ $this->assertStringNotContainsString( 'Author of a comment', $output );
+ }
+
+ /**
+ * The compact ping markup labels a registered type with its own singular name. The
+ * built-in "Pingback:" would be plainly wrong for anything else.
+ *
+ * @ticket 35214
+ */
+ public function test_registered_ping_type_renders_with_its_own_label(): void {
+ register_comment_type(
+ 'webmention',
+ array(
+ 'is_ping' => true,
+ 'labels' => array( 'singular_name' => 'Webmention' ),
+ )
+ );
+
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'webmention',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => true )
+ );
+
+ $this->assertStringContainsString( 'Webmention:', $output );
+ $this->assertStringNotContainsString( 'Pingback:', $output );
+ }
+
+ /**
+ * A ping type registered without labels inherits the default 'Comment' singular name,
+ * which is even more wrong for a ping than 'Pingback:'. The compact markup falls back
+ * to 'Pingback:' rather than labeling a ping 'Comment:'.
+ *
+ * @ticket 35214
+ */
+ public function test_ping_type_without_labels_falls_back_to_pingback_label(): void {
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'webmention',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => true )
+ );
+
+ $this->assertStringContainsString( 'Pingback:', $output );
+ $this->assertStringNotContainsString( 'Comment:', $output );
+ }
+
+ /**
+ * The built-in pingback type still renders with the compact ping markup.
+ *
+ * Guards the refactor from hard-coded `pingback`/`trackback` string checks to
+ * the `is_ping` flag: built-in ping rendering must remain unchanged.
+ *
+ * @ticket 35214
+ */
+ public function test_built_in_pingback_still_renders_as_ping(): void {
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'pingback',
+ 'comment_content' => 'A pingback body',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => true )
+ );
+
+ $this->assertStringContainsString( 'Pingback:', $output );
+ $this->assertStringNotContainsString( 'A pingback body', $output );
+ }
+
+ /**
+ * A ping type renders its full markup (not the compact ping) when short_ping is off.
+ *
+ * @ticket 35214
+ */
+ public function test_ping_type_renders_full_markup_when_short_ping_disabled(): void {
+ register_comment_type( 'webmention', array( 'is_ping' => true ) );
+
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'webmention',
+ 'comment_content' => 'A webmention body',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => false )
+ );
+
+ // With short_ping off the full markup is rendered, including the comment body.
+ $this->assertStringContainsString( 'A webmention body', $output );
+ }
+
+ /**
+ * A non-ping type is never rendered as a ping, even with short_ping enabled.
+ *
+ * @ticket 35214
+ */
+ public function test_non_ping_type_is_not_rendered_as_ping_with_short_ping(): void {
+ register_comment_type( 'review' );
+
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'review',
+ 'comment_content' => 'A review body',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => true )
+ );
+
+ $this->assertStringContainsString( 'A review body', $output );
+ $this->assertStringNotContainsString( 'Pingback:', $output );
+ }
+
+ /**
+ * A render_callback wins over the compact ping markup for ping types.
+ *
+ * This pins the precedence chain: explicit wp_list_comments() 'callback',
+ * then 'render_callback', then is_ping short-ping markup, then default markup.
+ *
+ * @ticket 35214
+ */
+ public function test_render_callback_takes_precedence_over_short_ping_markup(): void {
+ register_comment_type(
+ 'webmention',
+ array(
+ 'is_ping' => true,
+ 'render_callback' => static function ( $comment ) {
+ echo '
' . esc_html( $comment->comment_content );
+ },
+ )
+ );
+
+ $comment_id = self::factory()->comment->create(
+ array(
+ 'comment_post_ID' => $this->post_id,
+ 'comment_type' => 'webmention',
+ 'comment_content' => 'A webmention body',
+ 'comment_approved' => '1',
+ )
+ );
+
+ $output = $this->render_comments(
+ array( get_comment( $comment_id ) ),
+ array( 'short_ping' => true )
+ );
+
+ $this->assertStringContainsString( 'A webmention body', $output );
+ $this->assertStringNotContainsString( 'Pingback:', $output );
+ }
+
/**
* Built-in comment types without a render_callback render normally.
*
diff --git a/tests/phpunit/tests/comment/wpCommentType.php b/tests/phpunit/tests/comment/wpCommentType.php
index 4fc4947c49bb8..98b0c1a2f6b07 100644
--- a/tests/phpunit/tests/comment/wpCommentType.php
+++ b/tests/phpunit/tests/comment/wpCommentType.php
@@ -24,6 +24,18 @@ public function test_instance_defaults(): void {
$this->assertFalse( $comment_type->_builtin );
$this->assertFalse( $comment_type->hierarchical );
$this->assertNull( $comment_type->render_callback );
+ $this->assertFalse( $comment_type->is_ping );
+ }
+
+ /**
+ * @ticket 35214
+ *
+ * @covers ::set_props
+ */
+ public function test_is_ping_is_stored(): void {
+ $comment_type = new WP_Comment_Type( 'foo', array( 'is_ping' => true ) );
+
+ $this->assertTrue( $comment_type->is_ping );
}
/**