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..a04fbed3ff 100644 --- a/includes/class-comment.php +++ b/includes/class-comment.php @@ -784,18 +784,33 @@ 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, …) — 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 ] ) ) { + continue; + } + + $requested = (array) $query->query_vars[ $key ]; + if ( \in_array( 'all', $requested, true ) || \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..d33fb8d3d2 100644 --- a/tests/phpunit/tests/includes/class-test-comment.php +++ b/tests/phpunit/tests/includes/class-test-comment.php @@ -907,6 +907,110 @@ 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 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. *