Skip to content
Merged
4 changes: 4 additions & 0 deletions .github/changelog/1518-from-description
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Support multiple layers of nested Outbox activities when searching for the Object ID.
115 changes: 91 additions & 24 deletions includes/activity/class-activity.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

namespace Activitypub\Activity;

use Activitypub\Link;
use Activitypub\Activity\Extended_Object\Event;
use Activitypub\Activity\Extended_Object\Place;

/**
* \Activitypub\Activity\Activity implements the common
Expand All @@ -23,6 +24,43 @@ class Activity extends Base_Object {
'https://www.w3.org/ns/activitystreams',
);

/**
* The default types for Activities.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
*
* @var array
*/
const TYPES = array(
'Accept',
'Add',
'Announce',
'Arrive',
'Block',
'Create',
'Delete',
'Dislike',
'Follow',
'Flag',
'Ignore',
'Invite',
'Join',
'Leave',
'Like',
'Listen',
'Move',
'Offer',
'Read',
'Reject',
'Remove',
'TentativeAccept',
'TentativeReject',
'Travel',
'Undo',
'Update',
'View',
);

/**
* The type of the object.
*
Expand Down Expand Up @@ -124,58 +162,87 @@ class Activity extends Base_Object {
*
* @see https://www.w3.org/TR/activitypub/#object-without-create
*
* @param array|string|Base_Object|Link|null $data Activity object.
* @param array|string|Base_Object|Activity|Actor|null $data Activity object.
*/
public function set_object( $data ) {
// Convert array to object.
$object = $data;

// Convert array to appropriate object type.
if ( is_array( $data ) ) {
$data = Generic_Object::init_from_array( $data );
$type = $data['type'] ?? null;

if ( in_array( $type, self::TYPES, true ) ) {
$object = self::init_from_array( $data );
} elseif ( in_array( $type, Actor::TYPES, true ) ) {
$object = Actor::init_from_array( $data );
} elseif ( in_array( $type, Base_Object::TYPES, true ) ) {
switch ( $type ) {
case 'Event':
$object = Event::init_from_array( $data );
break;
case 'Place':
$object = Place::init_from_array( $data );
break;
default:
$object = Base_Object::init_from_array( $data );
break;
}
} else {
$object = Generic_Object::init_from_array( $data );
}
}

// Set object.
$this->set( 'object', $data );
$this->set( 'object', $object );
$this->pre_fill_activity_from_object();
}

/**
* Fills the Activity with the specified activity object.
*/
public function pre_fill_activity_from_object() {
$object = $this->get_object();

// Check if `$data` is a URL and use it to generate an ID then.
if ( is_string( $data ) && filter_var( $data, FILTER_VALIDATE_URL ) && ! $this->get_id() ) {
$this->set( 'id', $data . '#activity-' . strtolower( $this->get_type() ) . '-' . time() );
if ( is_string( $object ) && filter_var( $object, FILTER_VALIDATE_URL ) && ! $this->get_id() ) {
$this->set( 'id', $object . '#activity-' . strtolower( $this->get_type() ) . '-' . time() );

return;
}

// Check if `$data` is an object and copy some properties otherwise do nothing.
if ( ! is_object( $data ) ) {
if ( ! is_object( $object ) ) {
return;
}

foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
$value = $data->get( $i );
$value = $object->get( $i );
if ( $value && ! $this->get( $i ) ) {
$this->set( $i, $value );
}
}

if ( $data->get_published() && ! $this->get_published() ) {
$this->set( 'published', $data->get_published() );
if ( $object->get_published() && ! $this->get_published() ) {
$this->set( 'published', $object->get_published() );
}

if ( $data->get_updated() && ! $this->get_updated() ) {
$this->set( 'updated', $data->get_updated() );
if ( $object->get_updated() && ! $this->get_updated() ) {
$this->set( 'updated', $object->get_updated() );
}

if ( $data->get_attributed_to() && ! $this->get_actor() ) {
$this->set( 'actor', $data->get_attributed_to() );
if ( $object->get_attributed_to() && ! $this->get_actor() ) {
$this->set( 'actor', $object->get_attributed_to() );
}

if ( $data->get_in_reply_to() && ! $this->get_in_reply_to() ) {
$this->set( 'in_reply_to', $data->get_in_reply_to() );
if ( $object->get_in_reply_to() && ! $this->get_in_reply_to() ) {
$this->set( 'in_reply_to', $object->get_in_reply_to() );
}

if ( $data->get_id() && ! $this->get_id() ) {
$id = strtok( $data->get_id(), '#' );
if ( $data->get_updated() ) {
$updated = $data->get_updated();
} elseif ( $data->get_published() ) {
$updated = $data->get_published();
if ( $object->get_id() && ! $this->get_id() ) {
$id = strtok( $object->get_id(), '#' );
if ( $object->get_updated() ) {
$updated = $object->get_updated();
} elseif ( $object->get_published() ) {
$updated = $object->get_published();
} else {
$updated = time();
}
Expand Down
15 changes: 15 additions & 0 deletions includes/activity/class-actor.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ class Actor extends Base_Object {
),
);

/**
* The default types for Actors.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
*
* @var array
*/
const TYPES = array(
'Application',
'Group',
'Organization',
'Person',
'Service',
);

/**
* The type of the object.
*
Expand Down
22 changes: 22 additions & 0 deletions includes/activity/class-base-object.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ class Base_Object extends Generic_Object {
),
);

/**
* The default types for Objects.
*
* @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
*
* @var array
*/
const TYPES = array(
'Article',
'Audio',
'Document',
'Event',
'Image',
'Note',
'Page',
'Place',
'Profile',
'Relationship',
'Tombstone',
'Video',
);

/**
* The type of the object.
*
Expand Down
26 changes: 14 additions & 12 deletions includes/collection/class-outbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Activitypub\Dispatcher;
use Activitypub\Scheduler;
use Activitypub\Activity\Activity;
use Activitypub\Activity\Base_Object;

use function Activitypub\add_to_outbox;

Expand Down Expand Up @@ -296,28 +297,29 @@ public static function maybe_get_activity( $outbox_item ) {
/**
* Get the object ID of an activity.
*
* @param Activity $activity The activity object.
* @param Activity|Base_Object|string $data The activity object.
*
* @return string The object ID.
*/
private static function get_object_id( $activity ) {
// Most common.
if ( is_object( $activity->get_object() ) ) {
return $activity->get_object()->get_id();
private static function get_object_id( $data ) {
$object = $data->get_object();

if ( is_object( $object ) ) {
return self::get_object_id( $object );
}

// Rare.
if ( is_string( $activity->get_object() ) ) {
return $activity->get_object();
if ( is_string( $object ) ) {
return $object;
}

// Exceptional.
return $activity->get_actor() ?? $activity->get_id();
return $data->get_id() ?? $data->get_actor();
}

/**
* Get the title of an activity recursively.
*
* @param \Activitypub\Activity\Base_Object $activity_object The activity object.
* @param Base_Object $activity_object The activity object.
*
* @return string The title.
*/
private static function get_object_title( $activity_object ) {
Expand All @@ -333,7 +335,7 @@ private static function get_object_title( $activity_object ) {

$title = $activity_object->get_name() ?? $activity_object->get_content();

if ( ! $title && $activity_object->get_object() instanceof \Activitypub\Activity\Base_Object ) {
if ( ! $title && $activity_object->get_object() instanceof Base_Object ) {
$title = $activity_object->get_object()->get_name() ?? $activity_object->get_object()->get_content();
}

Expand Down
45 changes: 3 additions & 42 deletions includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use WP_Error;
use Activitypub\Activity\Activity;
use Activitypub\Activity\Actor;
use Activitypub\Activity\Base_Object;
use Activitypub\Collection\Actors;
use Activitypub\Collection\Outbox;
Expand Down Expand Up @@ -1535,38 +1536,7 @@ function is_activity( $data ) {
*
* @param array $types The activity types.
*/
$types = apply_filters(
'activitypub_activity_types',
array(
'Accept',
'Add',
'Announce',
'Arrive',
'Block',
'Create',
'Delete',
'Dislike',
'Follow',
'Flag',
'Ignore',
'Invite',
'Join',
'Leave',
'Like',
'Listen',
'Move',
'Offer',
'Read',
'Reject',
'Remove',
'TentativeAccept',
'TentativeReject',
'Travel',
'Undo',
'Update',
'View',
)
);
$types = apply_filters( 'activitypub_activity_types', Activity::TYPES );

if ( is_string( $data ) ) {
return in_array( $data, $types, true );
Expand Down Expand Up @@ -1598,16 +1568,7 @@ function is_actor( $data ) {
*
* @param array $types The actor types.
*/
$types = apply_filters(
'activitypub_actor_types',
array(
'Application',
'Group',
'Organization',
'Person',
'Service',
)
);
$types = apply_filters( 'activitypub_actor_types', Actor::TYPES );

if ( is_string( $data ) ) {
return in_array( $data, $types, true );
Expand Down
Loading
Loading