Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/changelog/fix-comment-query-type-coexistence
Original file line number Diff line number Diff line change
@@ -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.
33 changes: 24 additions & 9 deletions includes/class-comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) );
}

/**
Expand Down
104 changes: 104 additions & 0 deletions tests/phpunit/tests/includes/class-test-comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down