updated plugin ActivityPub version 8.3.0

This commit is contained in:
2026-06-03 21:28:46 +00:00
committed by Gitium
parent a4b78ec277
commit 6fe182458a
340 changed files with 43232 additions and 7568 deletions

View File

@ -7,10 +7,10 @@
namespace Activitypub\Handler;
use WP_REST_Request;
use Activitypub\Http;
use Activitypub\Collection\Followers;
use Activitypub\Collection\Interactions;
use Activitypub\Collection\Remote_Actors;
use Activitypub\Collection\Remote_Posts;
use Activitypub\Tombstone;
use function Activitypub\object_to_uri;
@ -22,19 +22,24 @@ class Delete {
* Initialize the class, registering WordPress hooks.
*/
public static function init() {
\add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ) );
\add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 2 );
\add_action( 'activitypub_delete_actor_interactions', array( self::class, 'delete_interactions' ) );
\add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ), 10, 2 );
\add_filter( 'activitypub_skip_inbox_storage', array( self::class, 'skip_inbox_storage' ), 10, 2 );
\add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 3 );
\add_action( 'activitypub_delete_remote_actor_interactions', array( self::class, 'delete_interactions' ) );
\add_action( 'activitypub_delete_remote_actor_posts', array( self::class, 'delete_posts' ) );
\add_filter( 'activitypub_get_outbox_activity', array( self::class, 'outbox_activity' ) );
\add_action( 'post_activitypub_add_to_outbox', array( self::class, 'maybe_bury' ), 10, 2 );
}
/**
* Handles "Delete" requests.
*
* @param array $activity The delete activity.
* @param array $activity The delete activity.
* @param int|int[] $user_ids The local user ID(s).
*/
public static function handle_delete( $activity ) {
$object_type = isset( $activity['object']['type'] ) ? $activity['object']['type'] : '';
public static function handle_delete( $activity, $user_ids ) {
$object_type = $activity['object']['type'] ?? '';
switch ( $object_type ) {
/*
@ -47,7 +52,7 @@ class Delete {
case 'Organization':
case 'Service':
case 'Application':
self::maybe_delete_follower( $activity );
self::delete_remote_actor( $activity, $user_ids );
break;
/*
@ -62,7 +67,7 @@ class Delete {
case 'Video':
case 'Event':
case 'Document':
self::maybe_delete_interaction( $activity );
self::delete_object( $activity, $user_ids );
break;
/*
@ -71,7 +76,7 @@ class Delete {
* @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
*/
case 'Tombstone':
self::maybe_delete_interaction( $activity );
self::delete_object( $activity, $user_ids );
break;
/*
@ -80,110 +85,252 @@ class Delete {
* @see https://www.w3.org/TR/activitystreams-core/#example-1
*/
default:
// Ignore non Minimal Activities.
if ( ! is_string( $activity['object'] ) ) {
return;
}
// Check if Object is an Actor.
if ( $activity['actor'] === $activity['object'] ) {
self::maybe_delete_follower( $activity );
} else { // Assume an interaction otherwise.
self::maybe_delete_interaction( $activity );
if ( object_to_uri( $activity['object'] ) === $activity['actor'] ) {
self::delete_remote_actor( $activity, $user_ids );
} else { // Assume an object otherwise.
self::delete_object( $activity, $user_ids );
}
// Maybe handle Delete Activity for other Object Types.
break;
}
}
/**
* Delete an Object.
*
* @param array $activity The Activity object.
* @param int|int[] $user_ids The user ID(s).
*/
public static function delete_object( $activity, $user_ids ) {
$result = self::maybe_delete_interaction( $activity );
if ( ! $result ) {
$result = self::maybe_delete_post( $activity );
}
$success = ( $result && ! \is_wp_error( $result ) );
/**
* Fires after an ActivityPub Delete activity has been handled.
*
* @param array $activity The ActivityPub activity data.
* @param int[] $user_ids The local user IDs.
* @param bool $success True on success, false otherwise.
* @param mixed|null $result The result of the delete operation.
*/
\do_action( 'activitypub_handled_delete', $activity, (array) $user_ids, $success, $result );
}
/**
* Delete an Actor.
*
* @param array $activity The Activity object.
* @param int|int[] $user_ids The user ID(s).
*/
public static function delete_remote_actor( $activity, $user_ids ) {
$result = self::maybe_delete_follower( $activity );
$success = ( $result && ! \is_wp_error( $result ) );
/**
* Fires after an ActivityPub Delete activity has been handled.
*
* @param array $activity The ActivityPub activity data.
* @param int[] $user_ids The local user IDs.
* @param bool $success True on success, false otherwise.
* @param mixed|null $result The result of the delete operation.
*/
\do_action( 'activitypub_handled_delete', $activity, (array) $user_ids, $success, $result );
return $result;
}
/**
* Delete a Follower if Actor-URL is a Tombstone.
*
* @param array $activity The delete activity.
*
* @return bool True on success, false otherwise.
*/
public static function maybe_delete_follower( $activity ) {
/* @var \Activitypub\Model\Follower $follower Follower object. */
$follower = Followers::get_follower_by_actor( $activity['actor'] );
$follower = Remote_Actors::get_by_uri( $activity['actor'] );
// Verify that Actor is deleted.
if ( $follower && Http::is_tombstone( $activity['actor'] ) ) {
$follower->delete();
self::maybe_delete_interactions( $activity );
if ( ! is_wp_error( $follower ) && Tombstone::exists( $activity['actor'] ) ) {
self::maybe_delete_interactions( $follower->ID );
self::maybe_delete_posts( $follower->ID );
$state = Remote_Actors::delete( $follower->ID );
}
return $state ?? false;
}
/**
* Delete Reactions if Actor-URL is a Tombstone.
* Schedule Deletion of Interactions of a Remote Actor.
*
* @param array $activity The delete activity.
* @param int $id The remote actor ID.
*/
public static function maybe_delete_interactions( $activity ) {
// Verify that Actor is deleted.
if ( Http::is_tombstone( $activity['actor'] ) ) {
\wp_schedule_single_event(
\time(),
'activitypub_delete_actor_interactions',
array( $activity['actor'] )
);
}
public static function maybe_delete_interactions( $id ) {
\wp_schedule_single_event(
\time(),
'activitypub_delete_remote_actor_interactions',
array( $id )
);
}
/**
* Delete comments from an Actor.
* Schedule Deletion of Reader Items of a Remote Actor.
*
* @param string $actor The URL of the actor whose comments to delete.
* @param int $id The remote actor ID.
*/
public static function delete_interactions( $actor ) {
$comments = Interactions::get_interactions_by_actor( $actor );
public static function maybe_delete_posts( $id ) {
\wp_schedule_single_event(
\time(),
'activitypub_delete_remote_actor_posts',
array( $id )
);
}
/**
* Delete Interactions from a Remote Actor.
*
* @param int $id The ID of the actor whose comments to delete.
*
* @return bool True on success, false otherwise.
*/
public static function delete_interactions( $id ) {
$comments = Interactions::get_by_remote_actor_id( $id );
foreach ( $comments as $comment ) {
wp_delete_comment( $comment, true );
\wp_delete_comment( $comment, true );
}
if ( $comments ) {
return true;
} else {
return false;
}
}
/**
* Delete Reader Items from an Actor.
*
* @param int $id The ID of the actor whose comments to delete.
*
* @return bool True on success, false otherwise.
*/
public static function delete_posts( $id ) {
$posts = Remote_Posts::get_by_remote_actor_id( $id );
foreach ( $posts as $post ) {
Remote_Posts::delete( $post->ID );
}
if ( $posts ) {
return true;
} else {
return false;
}
}
/**
* Delete a Reaction if URL is a Tombstone.
*
* Note: When comments are deleted, WordPress automatically deletes all associated
* comment meta including _activitypub_remote_actor_id. The remote actor post itself
* is not deleted, as it may be referenced by other comments or may be needed for
* future interactions.
*
* @param array $activity The delete activity.
*
* @return bool True on success, false otherwise.
*/
public static function maybe_delete_interaction( $activity ) {
if ( is_array( $activity['object'] ) ) {
$id = $activity['object']['id'];
} else {
$id = $activity['object'];
}
$id = object_to_uri( $activity['object'] );
$comments = Interactions::get_by_id( $id );
$comments = Interactions::get_interaction_by_id( $id );
if ( $comments && Http::is_tombstone( $id ) ) {
if ( $comments && Tombstone::exists( $id ) ) {
foreach ( $comments as $comment ) {
// WordPress will automatically delete all comment meta including _activitypub_remote_actor_id.
wp_delete_comment( $comment->comment_ID, true );
}
}
}
/**
* Defer signature verification for `Delete` requests.
*
* @param bool $defer Whether to defer signature verification.
* @param WP_REST_Request $request The request object.
*
* @return bool Whether to defer signature verification.
*/
public static function defer_signature_verification( $defer, $request ) {
$json = $request->get_json_params();
if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
return true;
}
return false;
}
/**
* Delete a post from the Posts collection.
*
* @param array $activity The delete activity.
*
* @return bool|\WP_Error True on success, false or WP_Error on failure.
*/
public static function maybe_delete_post( $activity ) {
$id = object_to_uri( $activity['object'] );
// Check if the object exists and is a tombstone.
if ( Tombstone::exists( $id ) ) {
return Remote_Posts::delete_by_guid( $id );
}
return false;
}
/**
* Skip inbox storage for `Delete` requests.
*
* @param bool $skip Whether to skip inbox storage.
* @param array $data The activity data array.
*
* @return bool Whether to skip inbox storage.
*/
public static function skip_inbox_storage( $skip, $data ) {
if ( isset( $data['type'] ) && 'Delete' === $data['type'] ) {
return true;
}
return $skip;
}
/**
* Defer signature verification for `Delete` requests.
*
* Endpoints that opt in to mandatory signing by calling
* `verify_signature( $request, true )` must not be overridden — the
* Delete carve-out is only for the default inbox path where the
* remote actor's keys may legitimately be gone before the Delete
* arrives.
*
* @since 8.2.0 The `$force_signature` parameter is now respected.
*
* @param bool $defer Whether to defer signature verification.
* @param \WP_REST_Request $request The request object.
* @param bool $force_signature Whether the caller has forced signature verification.
*
* @return bool Whether to defer signature verification.
*/
public static function defer_signature_verification( $defer, $request, $force_signature = false ) {
if ( $force_signature ) {
return $defer;
}
$json = $request->get_json_params();
if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
return true;
}
return $defer;
}
/**
* Set the object to the object ID.
*
* @param \Activitypub\Activity\Activity $activity The Activity object.
*
* @return \Activitypub\Activity\Activity The filtered Activity object.
*/
public static function outbox_activity( $activity ) {
@ -193,4 +340,28 @@ class Delete {
return $activity;
}
/**
* Add a URL to the tombstone registry when a Delete activity is sent.
*
* @param int $outbox_id The ID of the outbox activity.
* @param \Activitypub\Activity\Activity $activity The Activity object.
*/
public static function maybe_bury( $outbox_id, $activity ) {
if ( 'Delete' !== $activity->get_type() ) {
return;
}
$object = $activity->get_object();
if ( ! $object ) {
return;
}
Tombstone::bury( object_to_uri( $object ) );
if ( \is_object( $object ) ) {
Tombstone::bury( $object->get_id(), $object->get_url() );
}
}
}