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/2502-from-description
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Improved handling of unusual activity data to avoid errors when activities contain unexpected formats.
4 changes: 2 additions & 2 deletions includes/collection/class-inbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ public static function add( $activity, $recipients ) {
/**
* Get the title of an activity recursively.
*
* @param Activity|Base_Object $activity_object The activity object.
* @param Activity|Base_Object|array $activity_object The activity object.
*
* @return string The title.
*/
private static function get_object_title( $activity_object ) {
if ( ! $activity_object ) {
if ( ! $activity_object || is_array( $activity_object ) ) {
return '';
}

Expand Down
189 changes: 189 additions & 0 deletions tests/phpunit/tests/includes/collection/class-test-inbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,195 @@ public function test_deduplicate_only_matches_specific_guid() {
$this->assertEquals( 2, $count_second, 'Should still have two posts with second-activity GUID (not deduplicated)' );
}

/**
* Test adding activity when object is an array.
*
* This test verifies that the inbox can handle activities where get_object()
* returns an array instead of an object, which can happen with certain
* ActivityPub implementations.
*
* @covers ::add
*/
public function test_add_activity_with_array_object() {
// Create an activity with an array as the object.
$activity = new Activity();
$activity->set_id( 'https://remote.example.com/activities/array-object' );
$activity->set_type( 'Create' );
$activity->set_actor( 'https://remote.example.com/users/testuser' );

// Set object as an array (this can happen with certain implementations).
$activity->set_object(
array(
'id' => 'https://remote.example.com/objects/array-test',
'type' => 'Note',
'content' => 'Test content',
)
);

$user_id = 1;

// Add activity to inbox - should not throw an error.
$inbox_id = Inbox::add( $activity, $user_id );

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

// Verify the post was created with empty title (since array doesn't have get_name()).
$post = \get_post( $inbox_id );
$this->assertInstanceOf( 'WP_Post', $post );
$this->assertEquals( Inbox::POST_TYPE, $post->post_type );

// Post title should be "[Create] " (activity type with no object title).
$this->assertStringStartsWith( '[Create]', $post->post_title );
}

/**
* Test adding Flag activity with array of URLs as object.
*
* This test verifies that the inbox can handle Flag activities (used for
* reporting content) where the object is an array of URLs, which is a
* real-world scenario from Mastodon moderation reports.
*
* @covers ::add
*/
public function test_add_flag_activity_with_url_array() {
// Create a Flag activity similar to moderation reports.
$activity = new Activity();
$activity->set_id( 'https://remote.example.com/activities/flag-report' );
$activity->set_type( 'Flag' );
$activity->set_actor( 'https://remote.example.com/users/reporter' );
$activity->set_content( '' ); // Flag activities often have empty content.

// Set object as an array of URLs (actor being reported + content being reported).
$activity->set_object(
array(
'https://example.org/users/reported-user',
'https://example.org/posts/12345',
)
);

$user_id = 1;

// Add activity to inbox - should not throw an error despite array object.
$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 );

// Post title should be "[Flag] " (activity type with no object title from array).
$this->assertStringStartsWith( '[Flag]', $post->post_title );
}

/**
* Test adding activity when object is null.
*
* @covers ::add
*/
public function test_add_activity_with_null_object() {
$activity = new Activity();
$activity->set_id( 'https://remote.example.com/activities/null-object' );
$activity->set_type( 'Delete' );
$activity->set_actor( 'https://remote.example.com/users/testuser' );
// Don't set any object - it will be null.

$inbox_id = Inbox::add( $activity, 1 );

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

// Post title should be "[Delete] " with no object title.
$post = \get_post( $inbox_id );
$this->assertStringStartsWith( '[Delete]', $post->post_title );
}

/**
* Test adding activity when object is a string URL.
*
* @covers ::add
*/
public function test_add_activity_with_string_object() {
// Create a post to reference.
$post_id = self::factory()->post->create(
array(
'post_title' => 'Referenced Post',
'post_status' => 'publish',
)
);
$post_url = \get_permalink( $post_id );

$activity = new Activity();
$activity->set_id( 'https://remote.example.com/activities/string-object' );
$activity->set_type( 'Like' );
$activity->set_actor( 'https://remote.example.com/users/testuser' );
$activity->set_object( $post_url ); // String URL.

$inbox_id = Inbox::add( $activity, 1 );

$this->assertIsInt( $inbox_id );

// Post title should include the referenced post title.
$post = \get_post( $inbox_id );
$this->assertStringContainsString( 'Referenced Post', $post->post_title );
}

/**
* Test adding activity with object that has name property.
*
* @covers ::add
*/
public function test_add_activity_with_object_name() {
$activity = new Activity();
$activity->set_id( 'https://remote.example.com/activities/object-name' );
$activity->set_type( 'Create' );
$activity->set_actor( 'https://remote.example.com/users/testuser' );

$object = new Base_Object();
$object->set_id( 'https://remote.example.com/objects/named' );
$object->set_type( 'Note' );
$object->set_name( 'My Note Title' );
$object->set_content( 'This is the content' );
$activity->set_object( $object );

$inbox_id = Inbox::add( $activity, 1 );

$this->assertIsInt( $inbox_id );

// Post title should include the object name.
$post = \get_post( $inbox_id );
$this->assertStringContainsString( 'My Note Title', $post->post_title );
}

/**
* Test adding activity with object that has content but no name.
*
* @covers ::add
*/
public function test_add_activity_with_object_content_no_name() {
$activity = new Activity();
$activity->set_id( 'https://remote.example.com/activities/object-content' );
$activity->set_type( 'Create' );
$activity->set_actor( 'https://remote.example.com/users/testuser' );

$object = new Base_Object();
$object->set_id( 'https://remote.example.com/objects/content-only' );
$object->set_type( 'Note' );
$object->set_content( 'This is the content without a name' );
$activity->set_object( $object );

$inbox_id = Inbox::add( $activity, 1 );

$this->assertIsInt( $inbox_id );

// Post title should include part of the content (since no name).
$post = \get_post( $inbox_id );
$this->assertStringContainsString( 'This is the content', $post->post_title );
}

/**
* Test adding Like activity with trailing slash in object URL.
*
Expand Down
Loading