Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/2448-from-description
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Fixed compatibility with Pixelfed and similar platforms by treating activities without recipients as public, ensuring boosts and reposts work correctly.
10 changes: 10 additions & 0 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,12 @@ function get_activity_visibility( $activity ) {
return ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC;
}

// Activities with no recipients are treated as public.
$recipients = extract_recipients_from_activity( $activity );
if ( empty( $recipients ) ) {
return ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC;
}

return ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE;
}

Expand All @@ -607,6 +613,10 @@ function is_activity_public( $data ) {

$recipients = extract_recipients_from_activity( $data );

if ( empty( $recipients ) ) {
return true;
}

return ! empty( array_intersect( $recipients, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS ) );
}

Expand Down
8 changes: 4 additions & 4 deletions tests/phpunit/tests/includes/class-test-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ public function public_activity_provider() {
'monkey' => 'https://www.w3.org/ns/activitystreams#Public',
),
),
false,
true,
),
array(
array(
Expand Down Expand Up @@ -1334,13 +1334,13 @@ public function visibility_data_provider() {
'expected' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
'description' => 'Public visibility via as:Public identifier',
),
// Empty activity.
// Empty activity - no recipients means public.
array(
'activity' => array(
'type' => 'Create',
),
'expected' => ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE,
'description' => 'Empty activity defaults to private',
'expected' => ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC,
'description' => 'Empty activity (no recipients) is treated as public',
),
);
}
Expand Down
60 changes: 58 additions & 2 deletions tests/phpunit/tests/includes/collection/class-test-inbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ public function test_add_activity_with_post_meta() {
$remote_actor_meta = \get_post_meta( $inbox_id, '_activitypub_activity_remote_actor', true );
$this->assertEquals( 'https://remote.example.com/users/testuser', $remote_actor_meta );

// Test activitypub_content_visibility meta.
// Activities with no recipients are treated as public.
$visibility_meta = \get_post_meta( $inbox_id, 'activitypub_content_visibility', true );
$this->assertEquals( ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE, $visibility_meta );
$this->assertEquals( ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC, $visibility_meta );
}

/**
Expand Down Expand Up @@ -723,4 +723,60 @@ public function test_deduplicate_non_existent() {
$result = Inbox::deduplicate( 'https://remote.example.com/activities/non-existent' );
$this->assertFalse( $result );
}

/**
* Test adding Like activity with trailing slash in object URL.
*
* This test verifies that Like activities from Pixelfed and other platforms
* that include trailing slashes in object URLs are stored correctly in the inbox.
*
* @covers ::add
*/
public function test_add_like_activity_with_trailing_slash() {
// Create a post to be liked.
$post_id = self::factory()->post->create(
array(
'post_title' => 'Test Post for Like',
'post_content' => 'Test content',
'post_status' => 'publish',
)
);
$post_permalink = \get_permalink( $post_id );

// Create a Like activity with trailing slash in object URL (as Pixelfed sends).
$activity = new Activity();
$activity->set_id( 'https://pixelfed.social/users/pfefferle#likes/30434186' );
$activity->set_type( 'Like' );
$activity->set_actor( 'https://pixelfed.social/users/pfefferle' );
$activity->set_object( $post_permalink . '/' ); // Add trailing slash.

$user_id = 1;

// Add activity to inbox.
$inbox_id = Inbox::add( $activity, $user_id );

$this->assertIsInt( $inbox_id );
$this->assertGreaterThan( 0, $inbox_id );

// Verify the post was created.
$post = \get_post( $inbox_id );
$this->assertInstanceOf( 'WP_Post', $post );
$this->assertEquals( Inbox::POST_TYPE, $post->post_type );

// Test _activitypub_object_id meta - should preserve the trailing slash as-is.
$object_id_meta = \get_post_meta( $inbox_id, '_activitypub_object_id', true );
$this->assertEquals( $post_permalink . '/', $object_id_meta );

// Test _activitypub_activity_type meta.
$activity_type_meta = \get_post_meta( $inbox_id, '_activitypub_activity_type', true );
$this->assertEquals( 'Like', $activity_type_meta );

// Test _activitypub_user_id meta.
$user_id_meta = \get_post_meta( $inbox_id, '_activitypub_user_id', true );
$this->assertEquals( $user_id, $user_id_meta );

// Test _activitypub_activity_remote_actor meta.
$remote_actor_meta = \get_post_meta( $inbox_id, '_activitypub_activity_remote_actor', true );
$this->assertEquals( 'https://pixelfed.social/users/pfefferle', $remote_actor_meta );
}
}
44 changes: 44 additions & 0 deletions tests/phpunit/tests/includes/handler/class-test-like.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,50 @@ public function handle_like_provider() {
);
}

/**
* Test Like activity with trailing slash in object URL.
*
* This test verifies that Like activities from Pixelfed and other platforms
* that include trailing slashes in object URLs are processed correctly.
*
* @covers ::handle_like
* @covers \Activitypub\Collection\Interactions::add_reaction
*/
public function test_handle_like_with_trailing_slash() {
// Create activity with trailing slash in object URL (as Pixelfed sends).
$activity = array(
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => 'https://pixelfed.social/users/pfefferle#likes/30434186',
'type' => 'Like',
'actor' => $this->user_url,
'object' => $this->post_permalink . '/', // Add trailing slash.
);

// Get comment count before.
$comments_before = \get_comments(
array(
'type' => 'like',
'post_id' => $this->post_id,
)
);
$count_before = count( $comments_before );

// Process the like.
Like::handle_like( $activity, $this->user_id );

// Check that comment was created despite trailing slash.
$comments_after = \get_comments(
array(
'type' => 'like',
'post_id' => $this->post_id,
)
);
$count_after = count( $comments_after );

$this->assertEquals( $count_before + 1, $count_after, 'Like with trailing slash should create comment' );
$this->assertInstanceOf( 'WP_Comment', $comments_after[0], 'Should create WP_Comment object' );
}

/**
* Test duplicate like handling.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,9 @@ public function test_get_local_recipients_no_recipients() {
$method->setAccessible( true );

$result = $method->invoke( $this->inbox_controller, $activity );
$this->assertEmpty( $result, 'Should return empty array when no recipients' );
// Activities with no recipients are treated as public and delivered to all local actors.
$this->assertNotEmpty( $result, 'Should return all local actors when no recipients (treated as public)' );
$this->assertEquals( Actors::get_all_ids(), $result, 'Should match all local actor IDs' );
}

/**
Expand Down