From cd1150a0e51a6f39210fecf460edf663ff3db6c0 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 18 May 2026 10:00:17 +0200 Subject: [PATCH 1/2] Merge AP comment-type exclusion instead of bailing on other filters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous `comment_query` filter bailed as soon as any of `type__in`, `type` or `type__not_in` was set on the query — even when the caller had nothing to do with our comment types. That let ActivityPub Likes, Reposts, and Quotes leak into the front-end comment list whenever another plugin (e.g. GatherPress, which sets `type__in` for its own RSVP filtering) touched those query vars. The same flaw applied to themes setting `type__not_in` for their own exclusions: the AP filter abdicated and AP comment types showed up anyway. Now we only bail when the caller is explicitly requesting an AP comment type. Otherwise we merge our slugs into `type__not_in` on top of whatever the caller already had, so AP's exclusion composes with everyone else's filters. WP_Comment_Query already excludes the 'note' comment type by default since WP 6.9, so the merged result respects that too. Fixes #3306, fixes #2849. --- .../fix-comment-query-type-coexistence | 4 ++ includes/class-comment.php | 31 ++++++--- .../tests/includes/class-test-comment.php | 67 +++++++++++++++++++ 3 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 .github/changelog/fix-comment-query-type-coexistence diff --git a/.github/changelog/fix-comment-query-type-coexistence b/.github/changelog/fix-comment-query-type-coexistence new file mode 100644 index 0000000000..8c096f88b1 --- /dev/null +++ b/.github/changelog/fix-comment-query-type-coexistence @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Stop ActivityPub Likes, Reposts, and Quotes from leaking into the front-end comment list on sites that also use other comment-filtering plugins. diff --git a/includes/class-comment.php b/includes/class-comment.php index 9500377f9d..1c772c682a 100644 --- a/includes/class-comment.php +++ b/includes/class-comment.php @@ -784,18 +784,31 @@ public static function comment_query( $query ) { return; } - // Do not exclude likes and reposts if the query is for specific types. - if ( ! empty( $query->query_vars['type__in'] ) || ! empty( $query->query_vars['type'] ) ) { - return; - } + $ap_types = self::get_comment_type_slugs(); - // Do not exclude likes and reposts if the query is already excluding other comment types. - if ( ! empty( $query->query_vars['type__not_in'] ) ) { - return; + /* + * If the caller is explicitly asking for one of the ActivityPub + * comment types (likes, reposts, …), respect that. Otherwise we + * still merge our slugs into `type__not_in`, so the AP exclusion + * composes with whatever other plugins are filtering on. The + * previous version bailed out as soon as any of `type__in`, `type` + * or `type__not_in` was set — which let AP comments leak through + * on themes that use plugins like GatherPress (which sets + * `type__in` for its own RSVP filtering). + */ + foreach ( array( 'type__in', 'type' ) as $key ) { + if ( empty( $query->query_vars[ $key ] ) ) { + continue; + } + + $requested = (array) $query->query_vars[ $key ]; + if ( \array_intersect( $requested, $ap_types ) ) { + return; + } } - // Exclude likes and reposts by the ActivityPub plugin. - $query->query_vars['type__not_in'] = self::get_comment_type_slugs(); + $existing = (array) ( $query->query_vars['type__not_in'] ?? array() ); + $query->query_vars['type__not_in'] = \array_values( \array_unique( \array_merge( $existing, $ap_types ) ) ); } /** diff --git a/tests/phpunit/tests/includes/class-test-comment.php b/tests/phpunit/tests/includes/class-test-comment.php index 639d4b2b44..1ff5913374 100644 --- a/tests/phpunit/tests/includes/class-test-comment.php +++ b/tests/phpunit/tests/includes/class-test-comment.php @@ -907,6 +907,73 @@ public function test_post_comments_filtered_by_type__not_in() { $this->assertNotContains( (string) $comment_id, $comment_ids, 'AP post comment should be hidden even when querying specific post' ); } + /** + * Test that AP exclusion composes with a caller's existing `type__in`. + * + * Regression for #3306: GatherPress sets `type__in` for its own RSVP + * filtering, which previously caused `comment_query` to bail out, and + * AP comment types leaked into the front-end comment list. The new + * behavior merges AP slugs into `type__not_in` whenever `type__in` + * does not request an AP type. + * + * @covers ::comment_query + */ + public function test_comment_query_merges_ap_exclusion_with_existing_type_in() { + $post_id = self::factory()->post->create(); + $this->go_to( \get_permalink( $post_id ) ); + + $this->assertTrue( \is_singular(), 'Sanity: test setup must reach the singular branch.' ); + + $query = new \WP_Comment_Query(); + $query->query_vars = array( 'type__in' => array( 'gatherpress_rsvp' ) ); + + \Activitypub\Comment::comment_query( $query ); + + $this->assertSame( array( 'gatherpress_rsvp' ), $query->query_vars['type__in'] ); + $this->assertArrayHasKey( 'type__not_in', $query->query_vars ); + $this->assertContains( 'like', $query->query_vars['type__not_in'] ); + $this->assertContains( 'repost', $query->query_vars['type__not_in'] ); + } + + /** + * Test that AP exclusion composes with a caller's existing `type__not_in`. + * + * @covers ::comment_query + */ + public function test_comment_query_merges_ap_exclusion_with_existing_type_not_in() { + $post_id = self::factory()->post->create(); + $this->go_to( \get_permalink( $post_id ) ); + + $query = new \WP_Comment_Query(); + $query->query_vars = array( 'type__not_in' => array( 'pingback' ) ); + + \Activitypub\Comment::comment_query( $query ); + + $this->assertContains( 'pingback', $query->query_vars['type__not_in'] ); + $this->assertContains( 'like', $query->query_vars['type__not_in'] ); + $this->assertContains( 'repost', $query->query_vars['type__not_in'] ); + } + + /** + * Test that an explicit request for an AP comment type is respected. + * + * @covers ::comment_query + */ + public function test_comment_query_respects_explicit_ap_type_request() { + $post_id = self::factory()->post->create(); + $this->go_to( \get_permalink( $post_id ) ); + + $query = new \WP_Comment_Query(); + $query->query_vars = array( 'type__in' => array( 'like' ) ); + + \Activitypub\Comment::comment_query( $query ); + + $this->assertTrue( + empty( $query->query_vars['type__not_in'] ), + 'Caller is explicitly asking for AP likes; we must not add likes to type__not_in.' + ); + } + /** * Test auto-approving comments on ap_post when option is enabled. * From e4a42f6de9316398d8703f737ba41aca57a9e6c7 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 18 May 2026 11:40:05 +0200 Subject: [PATCH 2/2] Honor the 'all' sentinel in comment_query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WordPress treats `type => 'all'` and `type__in => array( 'all' )` as "include everything, even types we'd normally hide" — `WP_Comment_Query` itself uses that flag to skip its built-in `note` exclusion. Our front-end filter needs the same escape hatch, otherwise a caller asking for the full set still can't see ActivityPub Likes, Reposts, or Quotes. --- includes/class-comment.php | 18 +++++---- .../tests/includes/class-test-comment.php | 37 +++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/includes/class-comment.php b/includes/class-comment.php index 1c772c682a..a04fbed3ff 100644 --- a/includes/class-comment.php +++ b/includes/class-comment.php @@ -788,13 +788,15 @@ public static function comment_query( $query ) { /* * If the caller is explicitly asking for one of the ActivityPub - * comment types (likes, reposts, …), respect that. Otherwise we - * still merge our slugs into `type__not_in`, so the AP exclusion - * composes with whatever other plugins are filtering on. The - * previous version bailed out as soon as any of `type__in`, `type` - * or `type__not_in` was set — which let AP comments leak through - * on themes that use plugins like GatherPress (which sets - * `type__in` for its own RSVP filtering). + * comment types (likes, reposts, …) — or for `'all'`, which WP + * treats as a sentinel meaning "include everything, even types we + * would normally exclude" — respect that. Otherwise we still merge + * our slugs into `type__not_in`, so the AP exclusion composes with + * whatever other plugins are filtering on. The previous version + * bailed out as soon as any of `type__in`, `type` or `type__not_in` + * was set — which let AP comments leak through on themes that use + * plugins like GatherPress (which sets `type__in` for its own RSVP + * filtering). */ foreach ( array( 'type__in', 'type' ) as $key ) { if ( empty( $query->query_vars[ $key ] ) ) { @@ -802,7 +804,7 @@ public static function comment_query( $query ) { } $requested = (array) $query->query_vars[ $key ]; - if ( \array_intersect( $requested, $ap_types ) ) { + if ( \in_array( 'all', $requested, true ) || \array_intersect( $requested, $ap_types ) ) { return; } } diff --git a/tests/phpunit/tests/includes/class-test-comment.php b/tests/phpunit/tests/includes/class-test-comment.php index 1ff5913374..d33fb8d3d2 100644 --- a/tests/phpunit/tests/includes/class-test-comment.php +++ b/tests/phpunit/tests/includes/class-test-comment.php @@ -974,6 +974,43 @@ public function test_comment_query_respects_explicit_ap_type_request() { ); } + /** + * Test that the WordPress `'all'` sentinel disables AP exclusion. + * + * WP_Comment_Query treats `type => 'all'` and `type__in => array('all')` + * as "include everything, even types we'd normally hide" (e.g. the + * built-in `note` exclusion is also disabled in that case). Our filter + * has to honor the same sentinel, otherwise a caller asking for the + * full set still can't see ActivityPub Likes / Reposts / Quotes. + * + * @covers ::comment_query + */ + public function test_comment_query_respects_all_sentinel() { + $post_id = self::factory()->post->create(); + $this->go_to( \get_permalink( $post_id ) ); + + $query = new \WP_Comment_Query(); + $query->query_vars = array( 'type' => 'all' ); + + \Activitypub\Comment::comment_query( $query ); + + $this->assertTrue( + empty( $query->query_vars['type__not_in'] ), + "Caller passed `type => 'all'`; AP slugs must not be appended to type__not_in." + ); + + // Same again via `type__in`. + $query = new \WP_Comment_Query(); + $query->query_vars = array( 'type__in' => array( 'all' ) ); + + \Activitypub\Comment::comment_query( $query ); + + $this->assertTrue( + empty( $query->query_vars['type__not_in'] ), + "Caller passed `type__in => array('all')`; AP slugs must not be appended to type__not_in." + ); + } + /** * Test auto-approving comments on ap_post when option is enabled. *