368 lines
9.8 KiB
PHP
368 lines
9.8 KiB
PHP
<?php
|
|
/**
|
|
* Delete handler file.
|
|
*
|
|
* @package Activitypub
|
|
*/
|
|
|
|
namespace Activitypub\Handler;
|
|
|
|
use Activitypub\Collection\Interactions;
|
|
use Activitypub\Collection\Remote_Actors;
|
|
use Activitypub\Collection\Remote_Posts;
|
|
use Activitypub\Tombstone;
|
|
|
|
use function Activitypub\object_to_uri;
|
|
|
|
/**
|
|
* Handles Delete requests.
|
|
*/
|
|
class Delete {
|
|
/**
|
|
* Initialize the class, registering WordPress hooks.
|
|
*/
|
|
public static function init() {
|
|
\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 int|int[] $user_ids The local user ID(s).
|
|
*/
|
|
public static function handle_delete( $activity, $user_ids ) {
|
|
$object_type = $activity['object']['type'] ?? '';
|
|
|
|
switch ( $object_type ) {
|
|
/*
|
|
* Actor Types.
|
|
*
|
|
* @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
|
|
*/
|
|
case 'Person':
|
|
case 'Group':
|
|
case 'Organization':
|
|
case 'Service':
|
|
case 'Application':
|
|
self::delete_remote_actor( $activity, $user_ids );
|
|
break;
|
|
|
|
/*
|
|
* Object and Link Types.
|
|
*
|
|
* @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
|
|
*/
|
|
case 'Note':
|
|
case 'Article':
|
|
case 'Image':
|
|
case 'Audio':
|
|
case 'Video':
|
|
case 'Event':
|
|
case 'Document':
|
|
self::delete_object( $activity, $user_ids );
|
|
break;
|
|
|
|
/*
|
|
* Tombstone Type.
|
|
*
|
|
* @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
|
|
*/
|
|
case 'Tombstone':
|
|
self::delete_object( $activity, $user_ids );
|
|
break;
|
|
|
|
/*
|
|
* Minimal Activity.
|
|
*
|
|
* @see https://www.w3.org/TR/activitystreams-core/#example-1
|
|
*/
|
|
default:
|
|
// Check if Object is an Actor.
|
|
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 ) {
|
|
$follower = Remote_Actors::get_by_uri( $activity['actor'] );
|
|
|
|
// Verify that Actor is deleted.
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Schedule Deletion of Interactions of a Remote Actor.
|
|
*
|
|
* @param int $id The remote actor ID.
|
|
*/
|
|
public static function maybe_delete_interactions( $id ) {
|
|
\wp_schedule_single_event(
|
|
\time(),
|
|
'activitypub_delete_remote_actor_interactions',
|
|
array( $id )
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Schedule Deletion of Reader Items of a Remote Actor.
|
|
*
|
|
* @param int $id The remote actor ID.
|
|
*/
|
|
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 );
|
|
}
|
|
|
|
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 ) {
|
|
$id = object_to_uri( $activity['object'] );
|
|
$comments = Interactions::get_by_id( $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 );
|
|
}
|
|
|
|
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 ) {
|
|
if ( 'Delete' === $activity->get_type() ) {
|
|
$activity->set_object( object_to_uri( $activity->get_object() ) );
|
|
}
|
|
|
|
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() );
|
|
}
|
|
}
|
|
}
|