From b7cb7fcfc9bc3d6dc089741b1f5bac829ac20dd6 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 24 Jun 2026 14:30:02 -0700 Subject: [PATCH 1/8] Comments: Add filter for comment types excluded from queries by default WP_Comment_Query hard-codes the exclusion of the 'note' comment type (introduced in 6.9) with no extension point. Plugins that add their own "private" comment types must instead rewrite query SQL through comments_clauses in every context and re-verify it on each release. Introduce a default_excluded_comment_types filter so extenders can contribute additional comment types to the default-excluded set, generalizing the existing 'note' handling. The default value of array( 'note' ) preserves current behavior exactly. See #65537. --- src/wp-includes/class-wp-comment-query.php | 38 +++- tests/phpunit/tests/comment/query.php | 213 +++++++++++++++++++++ 2 files changed, 244 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index cfabfd7e6b964..9185ddf367be8 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -537,6 +537,7 @@ public function get_comments() { * * @since 4.4.0 * @since 6.9.0 Excludes the 'note' comment type, unless 'all' or the 'note' types are requested. + * @since 6.10.0 The default-excluded comment types are filterable via {@see 'default_excluded_comment_types'}. * * @global wpdb $wpdb WordPress database abstraction object. * @@ -771,13 +772,36 @@ protected function get_comment_ids() { 'NOT IN' => (array) $this->query_vars['type__not_in'], ); - // Exclude the 'note' comment type, unless 'all' types or the 'note' type explicitly are requested. - if ( - ! in_array( 'all', $raw_types['IN'], true ) && - ! in_array( 'note', $raw_types['IN'], true ) && - ! in_array( 'note', $raw_types['NOT IN'], true ) - ) { - $raw_types['NOT IN'][] = 'note'; + /** + * Filters the comment types that are excluded from query results by default. + * + * Comment types in this list are omitted from `WP_Comment_Query` results + * unless the query explicitly requests the 'all' type, or requests the + * specific type via the 'type', 'type__in', or 'type__not_in' query + * variables. + * + * This allows plugins to register "private" comment types that should not + * surface in standard comment listings, counts, or feeds, without having + * to filter every query individually. The 'note' comment type, used by the + * editor, is excluded by default. + * + * @since 6.10.0 + * + * @param string[] $excluded_types Comment types excluded from query results by default. + * Default array contains the 'note' type. + * @param WP_Comment_Query $query The WP_Comment_Query instance (passed by reference). + */ + $excluded_types = apply_filters_ref_array( 'default_excluded_comment_types', array( array( 'note' ), &$this ) ); + + // Exclude the default-excluded comment types, unless 'all' types or that type explicitly are requested. + foreach ( array_unique( (array) $excluded_types ) as $excluded_type ) { + if ( + ! in_array( 'all', $raw_types['IN'], true ) && + ! in_array( $excluded_type, $raw_types['IN'], true ) && + ! in_array( $excluded_type, $raw_types['NOT IN'], true ) + ) { + $raw_types['NOT IN'][] = $excluded_type; + } } $comment_types = array(); diff --git a/tests/phpunit/tests/comment/query.php b/tests/phpunit/tests/comment/query.php index dc870a78ae494..07cabe2c64eb4 100644 --- a/tests/phpunit/tests/comment/query.php +++ b/tests/phpunit/tests/comment/query.php @@ -5534,4 +5534,217 @@ public function test_get_comment_count_excludes_note_type() { $this->assertSame( 1, $counts['all'] ); $this->assertSame( 1, $counts['total_comments'] ); } + + /** + * Helper method to create the standard set of comments used by the + * `default_excluded_comment_types` filter tests. + * + * Creates one comment of each of the 'comment', 'note', and 'private' types. + * + * @since 6.10.0 + * + * @return array<'comment'|'note'|'private', int> Array of created comment IDs keyed by type. + */ + protected function create_excluded_type_test_comments(): array { + return array( + 'comment' => self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_approved' => '1', + ) + ), + 'note' => self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_approved' => '1', + 'comment_type' => 'note', + ) + ), + 'private' => self::factory()->comment->create( + array( + 'comment_post_ID' => self::$post_id, + 'comment_approved' => '1', + 'comment_type' => 'private', + ) + ), + ); + } + + /** + * Returns the comment types for a list of comment IDs. + * + * @param int[] $comment_ids Comment IDs. + * @return string[] Comment types. + */ + private function get_comment_types_for_ids( array $comment_ids ): array { + return array_map( + static function ( int $comment_id ): string { + return get_comment( $comment_id )->comment_type; + }, + $comment_ids + ); + } + + /** + * A custom comment type added through the filter is excluded by default, + * alongside the default-excluded 'note' type. + * + * @ticket 65537 + * @covers WP_Comment_Query::get_comment_ids + */ + public function test_default_excluded_comment_types_filter_excludes_custom_type() { + $this->create_excluded_type_test_comments(); + + add_filter( + 'default_excluded_comment_types', + static function ( array $types ): array { + $types[] = 'private'; + return $types; + } + ); + + $query = new WP_Comment_Query(); + $found = $query->query( array( 'fields' => 'ids' ) ); + + $this->assertSameSets( + array( 'comment' ), + $this->get_comment_types_for_ids( $found ), + 'The custom excluded type and the default note type should both be omitted.' + ); + } + + /** + * Removing 'note' from the filtered list makes notes appear in default queries, + * proving the default exclusion itself is filterable. + * + * @ticket 65537 + * @covers WP_Comment_Query::get_comment_ids + */ + public function test_default_excluded_comment_types_filter_can_remove_note() { + $this->create_excluded_type_test_comments(); + + add_filter( 'default_excluded_comment_types', '__return_empty_array' ); + + $query = new WP_Comment_Query(); + $found = $query->query( array( 'fields' => 'ids' ) ); + + $this->assertSameSets( + array( 'comment', 'note', 'private' ), + $this->get_comment_types_for_ids( $found ), + 'With an empty exclusion list, all comment types should be returned.' + ); + } + + /** + * A custom excluded type is still returned when explicitly requested. + * + * @ticket 65537 + * @covers WP_Comment_Query::get_comment_ids + * @dataProvider data_default_excluded_comment_types_explicit_request + * + * @param array $query_args Query arguments for WP_Comment_Query. + * @param string[] $expected_types Expected comment types. + */ + public function test_default_excluded_comment_types_filter_respects_explicit_request( array $query_args, array $expected_types ) { + $this->create_excluded_type_test_comments(); + + add_filter( + 'default_excluded_comment_types', + static function ( array $types ): array { + $types[] = 'private'; + return $types; + } + ); + + $query = new WP_Comment_Query(); + $found = $query->query( array_merge( $query_args, array( 'fields' => 'ids' ) ) ); + + $this->assertSameSets( $expected_types, $this->get_comment_types_for_ids( $found ) ); + } + + /** + * Data provider for explicit-request tests against a filtered excluded type. + * + * @since 6.10.0 + * + * @return array, expected_types: string[] }> + */ + public function data_default_excluded_comment_types_explicit_request(): array { + return array( + 'type all includes excluded types' => array( + 'query_args' => array( 'type' => 'all' ), + 'expected_types' => array( 'comment', 'note', 'private' ), + ), + 'explicit custom type' => array( + 'query_args' => array( 'type' => 'private' ), + 'expected_types' => array( 'private' ), + ), + 'custom type via type__in' => array( + 'query_args' => array( 'type__in' => array( 'private' ) ), + 'expected_types' => array( 'private' ), + ), + 'custom type with comment via type__in' => array( + 'query_args' => array( 'type__in' => array( 'private', 'comment' ) ), + 'expected_types' => array( 'private', 'comment' ), + ), + ); + } + + /** + * The filter receives the default 'note' type and the WP_Comment_Query instance. + * + * @ticket 65537 + * @covers WP_Comment_Query::get_comment_ids + */ + public function test_default_excluded_comment_types_filter_receives_default_and_instance() { + $filter_args = array(); + + add_filter( + 'default_excluded_comment_types', + static function ( $types, $query ) use ( &$filter_args ) { + $filter_args = array( $types, $query ); + return $types; + }, + 10, + 2 + ); + + $query = new WP_Comment_Query(); + $query->query( array( 'fields' => 'ids' ) ); + + $this->assertSame( array( 'note' ), $filter_args[0], 'The filter should receive the default note type.' ); + $this->assertInstanceOf( WP_Comment_Query::class, $filter_args[1], 'The filter should receive the query instance.' ); + } + + /** + * A custom excluded type is only added once to the query, even when a query + * already excludes it via type__not_in. + * + * @ticket 65537 + * @covers WP_Comment_Query::get_comment_ids + */ + public function test_default_excluded_comment_types_filter_not_duplicated_in_query() { + global $wpdb; + + $this->create_excluded_type_test_comments(); + + add_filter( + 'default_excluded_comment_types', + static function ( array $types ): array { + $types[] = 'private'; + return $types; + } + ); + + $query = new WP_Comment_Query(); + $query->query( + array( + 'type__not_in' => array( 'private' ), + 'fields' => 'ids', + ) + ); + + $private_count = substr_count( $wpdb->last_query, "'private'" ); + $this->assertSame( 1, $private_count, 'The private type should only appear once in the query.' ); + } } From 4b57b1a0cf8e1fce47a1b4089c09150e46f21772 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jun 2026 14:35:19 -0700 Subject: [PATCH 2/8] Apply suggestion from @adamsilverstein --- src/wp-includes/class-wp-comment-query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index 9185ddf367be8..c2dc946871972 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -537,7 +537,7 @@ public function get_comments() { * * @since 4.4.0 * @since 6.9.0 Excludes the 'note' comment type, unless 'all' or the 'note' types are requested. - * @since 6.10.0 The default-excluded comment types are filterable via {@see 'default_excluded_comment_types'}. + * @since 7.1.0 The default-excluded comment types are filterable via {@see 'default_excluded_comment_types'}. * * @global wpdb $wpdb WordPress database abstraction object. * From 9e9fb0b7a4e2edbbbd5331be10b8a15d334b4629 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jun 2026 14:35:42 -0700 Subject: [PATCH 3/8] Apply suggestion from @adamsilverstein --- src/wp-includes/class-wp-comment-query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index c2dc946871972..edc184f4404fb 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -785,7 +785,7 @@ protected function get_comment_ids() { * to filter every query individually. The 'note' comment type, used by the * editor, is excluded by default. * - * @since 6.10.0 + * @since 7.1.0 * * @param string[] $excluded_types Comment types excluded from query results by default. * Default array contains the 'note' type. From 6e67226feb3a1e783f1bc9a6c5c07f7830a8fc30 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jun 2026 14:37:13 -0700 Subject: [PATCH 4/8] Apply suggestion from @adamsilverstein --- tests/phpunit/tests/comment/query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/comment/query.php b/tests/phpunit/tests/comment/query.php index 07cabe2c64eb4..98be9d976e658 100644 --- a/tests/phpunit/tests/comment/query.php +++ b/tests/phpunit/tests/comment/query.php @@ -5541,7 +5541,7 @@ public function test_get_comment_count_excludes_note_type() { * * Creates one comment of each of the 'comment', 'note', and 'private' types. * - * @since 6.10.0 + * @since 7.1.0 * * @return array<'comment'|'note'|'private', int> Array of created comment IDs keyed by type. */ From 237d916912bfa2f6526a11a737600c30b376bf76 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 24 Jun 2026 14:38:04 -0700 Subject: [PATCH 5/8] Apply suggestion from @adamsilverstein --- tests/phpunit/tests/comment/query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/comment/query.php b/tests/phpunit/tests/comment/query.php index 98be9d976e658..e65ccab53b400 100644 --- a/tests/phpunit/tests/comment/query.php +++ b/tests/phpunit/tests/comment/query.php @@ -5665,7 +5665,7 @@ static function ( array $types ): array { /** * Data provider for explicit-request tests against a filtered excluded type. * - * @since 6.10.0 + * @since 7.1.0 * * @return array, expected_types: string[] }> */ From bb238e958a7027e6325f7a3a338bae316318c0f3 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Wed, 24 Jun 2026 22:21:55 -0700 Subject: [PATCH 6/8] Comments: Clarify the default_excluded_comment_types filter is not access control. The filter docblock described excluded types as "private", which could be read as a security boundary. The exclusion only governs default visibility; excluded types remain retrievable via explicit 'type' or 'all' requests, so document that callers must enforce capability checks wherever comment data is displayed or exposed. --- src/wp-includes/class-wp-comment-query.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index edc184f4404fb..f6a5563e79e84 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -780,10 +780,16 @@ protected function get_comment_ids() { * specific type via the 'type', 'type__in', or 'type__not_in' query * variables. * - * This allows plugins to register "private" comment types that should not - * surface in standard comment listings, counts, or feeds, without having - * to filter every query individually. The 'note' comment type, used by the - * editor, is excluded by default. + * This allows plugins to keep comment types out of standard comment + * listings, counts, or feeds by default, without having to filter every + * query individually. The 'note' comment type, used by the editor, is + * excluded by default. + * + * This exclusion is a default-visibility convenience, not an access-control + * mechanism: callers can still retrieve excluded types explicitly (for + * example with 'type' => 'all'), so do not rely on this filter to keep + * comment data private. Enforce capability checks wherever the data is + * displayed or exposed (for example over REST). * * @since 7.1.0 * From 1a4bf12ffa602403635ba581bc02b03e5acbdb84 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 25 Jun 2026 09:48:11 -0700 Subject: [PATCH 7/8] Comments: Feed default_excluded_comment_types into the comment counter. wp_update_comment_count_now() recalculated a post's stored comment_count with a hard-coded 'AND comment_type != note', bypassing the default_excluded_comment_types filter that hides those types from WP_Comment_Query. A type that opted out of listings still inflated comment_count and therefore get_comments_number(). Apply the same filtered exclusion set to the counter so the listings and the stored count stay consistent, removing the need for plugins to also re-implement the count via pre_wp_update_comment_count_now. See #35214, #65537. --- src/wp-includes/class-wp-comment-query.php | 11 +-- src/wp-includes/comment.php | 23 ++++++- .../tests/comment/wpUpdateCommentCountNow.php | 69 +++++++++++++++++++ 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index f6a5563e79e84..7018c10cb5e0a 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -783,7 +783,9 @@ protected function get_comment_ids() { * This allows plugins to keep comment types out of standard comment * listings, counts, or feeds by default, without having to filter every * query individually. The 'note' comment type, used by the editor, is - * excluded by default. + * excluded by default. The same set is applied when recalculating a + * post's stored comment count in wp_update_comment_count_now(), so an + * excluded type does not inflate get_comments_number(). * * This exclusion is a default-visibility convenience, not an access-control * mechanism: callers can still retrieve excluded types explicitly (for @@ -793,9 +795,10 @@ protected function get_comment_ids() { * * @since 7.1.0 * - * @param string[] $excluded_types Comment types excluded from query results by default. - * Default array contains the 'note' type. - * @param WP_Comment_Query $query The WP_Comment_Query instance (passed by reference). + * @param string[] $excluded_types Comment types excluded from query results by default. + * Default array contains the 'note' type. + * @param WP_Comment_Query|null $query The WP_Comment_Query instance (passed by reference), + * or null when recalculating a post's comment count. */ $excluded_types = apply_filters_ref_array( 'default_excluded_comment_types', array( array( 'note' ), &$this ) ); diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b93908adc0519..acf634e2bab02 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -2876,7 +2876,28 @@ function wp_update_comment_count_now( $post_id ) { $new = apply_filters( 'pre_wp_update_comment_count_now', null, $old, $post_id ); if ( is_null( $new ) ) { - $new = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type != 'note'", $post_id ) ); + /** This filter is documented in wp-includes/class-wp-comment-query.php */ + $excluded_types = apply_filters( 'default_excluded_comment_types', array( 'note' ), null ); + $excluded_types = array_unique( array_filter( array_map( 'strval', (array) $excluded_types ) ) ); + + if ( $excluded_types ) { + $new = (int) $wpdb->get_var( + $wpdb->prepare( + sprintf( + "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %%d AND comment_approved = '1' AND comment_type NOT IN (%s)", + implode( ', ', array_fill( 0, count( $excluded_types ), '%s' ) ) + ), + array_merge( array( $post_id ), $excluded_types ) + ) + ); + } else { + $new = (int) $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1'", + $post_id + ) + ); + } } else { $new = (int) $new; } diff --git a/tests/phpunit/tests/comment/wpUpdateCommentCountNow.php b/tests/phpunit/tests/comment/wpUpdateCommentCountNow.php index 9dbb1f244ccf8..5f48b89f95524 100644 --- a/tests/phpunit/tests/comment/wpUpdateCommentCountNow.php +++ b/tests/phpunit/tests/comment/wpUpdateCommentCountNow.php @@ -83,6 +83,75 @@ public function test_only_approved_regular_comments_are_counted() { $this->assertSame( '1', get_comments_number( $post_id ) ); } + /** + * A comment type excluded via the shared filter must not inflate the stored count. + * + * @ticket 65537 + */ + public function test_filtered_excluded_type_does_not_inflate_count() { + $post_id = self::factory()->post->create(); + + self::factory()->comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_approved' => 1, + ) + ); + self::factory()->comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_type' => 'review', + 'comment_approved' => 1, + ) + ); + + // Without exclusion, both approved comments are counted. + $this->assertTrue( wp_update_comment_count_now( $post_id ) ); + $this->assertSame( '2', get_comments_number( $post_id ) ); + + // Excluding 'review' through the same filter that hides it from queries drops it from the count. + $filter = static function ( $types ) { + $types[] = 'review'; + return $types; + }; + add_filter( 'default_excluded_comment_types', $filter ); + $this->assertTrue( wp_update_comment_count_now( $post_id ) ); + remove_filter( 'default_excluded_comment_types', $filter ); + + $this->assertSame( '1', get_comments_number( $post_id ) ); + } + + /** + * The count is driven by the filtered set, not a hard-coded 'note' literal. + * + * Clearing the excluded set causes 'note' comments to be counted, proving the + * exclusion comes from the filter rather than an in-query literal. + * + * @ticket 65537 + */ + public function test_emptying_filter_counts_otherwise_excluded_types() { + $post_id = self::factory()->post->create(); + + self::factory()->comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_type' => 'note', + 'comment_approved' => 1, + ) + ); + + // By default the 'note' type is excluded. + $this->assertTrue( wp_update_comment_count_now( $post_id ) ); + $this->assertSame( '0', get_comments_number( $post_id ) ); + + // A plugin that clears the excluded set causes notes to be counted. + add_filter( 'default_excluded_comment_types', '__return_empty_array' ); + $this->assertTrue( wp_update_comment_count_now( $post_id ) ); + remove_filter( 'default_excluded_comment_types', '__return_empty_array' ); + + $this->assertSame( '1', get_comments_number( $post_id ) ); + } + public function _return_100() { return 100; } From 2f75e55538cbf1ad3d85e9ccd32c24384c09da78 Mon Sep 17 00:00:00 2001 From: adamsilverstein Date: Thu, 25 Jun 2026 11:16:46 -0700 Subject: [PATCH 8/8] Comments: Honor default_excluded_comment_types in pending counts. get_pending_comments_num() recomputed the admin pending-comments count with a hard-coded 'AND comment_type != note', the same gap fixed in wp_update_comment_count_now(): a type that opts out of default listings via default_excluded_comment_types still inflated the pending bubble shown next to each post in the admin list tables. Derive the excluded set from the same filter so the pending count stays consistent with the listings and the stored comment count. See #35214, #65537. --- src/wp-admin/includes/comment.php | 18 ++- .../comment/GetPendingCommentsNum_Test.php | 118 ++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/tests/admin/includes/comment/GetPendingCommentsNum_Test.php diff --git a/src/wp-admin/includes/comment.php b/src/wp-admin/includes/comment.php index ae5ba9d223350..07ba7d05d06df 100644 --- a/src/wp-admin/includes/comment.php +++ b/src/wp-admin/includes/comment.php @@ -139,6 +139,8 @@ function get_comment_to_edit( $id ) { * * @since 2.3.0 * @since 6.9.0 Exclude the 'note' comment type from the count. + * @since 7.1.0 The excluded comment types are derived from the + * {@see 'default_excluded_comment_types'} filter. * * @global wpdb $wpdb WordPress database abstraction object. * @@ -158,7 +160,21 @@ function get_pending_comments_num( $post_id ) { $post_id_array = array_map( 'intval', $post_id_array ); $post_id_in = "'" . implode( "', '", $post_id_array ) . "'"; - $pending = $wpdb->get_results( "SELECT comment_post_ID, COUNT(comment_ID) as num_comments FROM $wpdb->comments WHERE comment_post_ID IN ( $post_id_in ) AND comment_approved = '0' AND comment_type != 'note' GROUP BY comment_post_ID", ARRAY_A ); + /** This filter is documented in wp-includes/class-wp-comment-query.php */ + $excluded_types = apply_filters( 'default_excluded_comment_types', array( 'note' ), null ); + $excluded_types = array_unique( array_filter( array_map( 'strval', (array) $excluded_types ) ) ); + + $type_not_in = ''; + if ( $excluded_types ) { + $type_not_in = $wpdb->prepare( + sprintf( ' AND comment_type NOT IN ( %s )', implode( ', ', array_fill( 0, count( $excluded_types ), '%s' ) ) ), + $excluded_types + ); + } + + // $post_id_in is built from integers and $type_not_in is prepared above. + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $pending = $wpdb->get_results( "SELECT comment_post_ID, COUNT(comment_ID) as num_comments FROM $wpdb->comments WHERE comment_post_ID IN ( $post_id_in ) AND comment_approved = '0'$type_not_in GROUP BY comment_post_ID", ARRAY_A ); if ( $single ) { if ( empty( $pending ) ) { diff --git a/tests/phpunit/tests/admin/includes/comment/GetPendingCommentsNum_Test.php b/tests/phpunit/tests/admin/includes/comment/GetPendingCommentsNum_Test.php new file mode 100644 index 0000000000000..e609f7ca46f12 --- /dev/null +++ b/tests/phpunit/tests/admin/includes/comment/GetPendingCommentsNum_Test.php @@ -0,0 +1,118 @@ +comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_type' => $comment_type, + 'comment_approved' => '0', + ) + ); + } + + /** + * @ticket 65537 + */ + public function test_counts_only_pending_comments() { + $post_id = self::factory()->post->create(); + $this->make_pending( $post_id ); + self::factory()->comment->create( + array( + 'comment_post_ID' => $post_id, + 'comment_approved' => '1', + ) + ); + + $this->assertSame( 1, get_pending_comments_num( $post_id ) ); + } + + /** + * @ticket 65537 + */ + public function test_excludes_note_type_by_default() { + $post_id = self::factory()->post->create(); + $this->make_pending( $post_id ); + $this->make_pending( $post_id, 'note' ); + + $this->assertSame( 1, get_pending_comments_num( $post_id ) ); + } + + /** + * A type added to the excluded set must drop out of the pending count. + * + * @ticket 65537 + */ + public function test_excludes_a_filtered_type() { + $post_id = self::factory()->post->create(); + $this->make_pending( $post_id ); + $this->make_pending( $post_id, 'review' ); + + // 'review' is counted by default. + $this->assertSame( 2, get_pending_comments_num( $post_id ) ); + + $filter = static function ( $types ) { + $types[] = 'review'; + return $types; + }; + add_filter( 'default_excluded_comment_types', $filter ); + $num = get_pending_comments_num( $post_id ); + remove_filter( 'default_excluded_comment_types', $filter ); + + $this->assertSame( 1, $num ); + } + + /** + * The exclusion is filter-driven, not a hard-coded 'note' literal. + * + * @ticket 65537 + */ + public function test_emptying_filter_counts_note_type() { + $post_id = self::factory()->post->create(); + $this->make_pending( $post_id, 'note' ); + + $this->assertSame( 0, get_pending_comments_num( $post_id ) ); + + add_filter( 'default_excluded_comment_types', '__return_empty_array' ); + $num = get_pending_comments_num( $post_id ); + remove_filter( 'default_excluded_comment_types', '__return_empty_array' ); + + $this->assertSame( 1, $num ); + } + + /** + * @ticket 65537 + */ + public function test_array_input_returns_counts_keyed_by_post() { + $post_a = self::factory()->post->create(); + $post_b = self::factory()->post->create(); + $this->make_pending( $post_a ); + $this->make_pending( $post_a, 'note' ); + $this->make_pending( $post_b ); + $this->make_pending( $post_b ); + + $counts = get_pending_comments_num( array( $post_a, $post_b ) ); + + $this->assertSame( + array( + $post_a => 1, + $post_b => 2, + ), + $counts + ); + } +}