updated plugin ActivityPub version 8.3.0
This commit is contained in:
@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user