PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.2
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
← All changes | includes/handler/class-delete.php +259 -83 2.1.08.0.2 View file →
@@ -1,62 +1,66 @@
1 1 <?php
2 +/**
3 + * Delete handler file.
4 + *
5 + * @package Activitypub
6 + */
7 +
2 8 namespace Activitypub\Handler;
3 9
4 -use WP_Error;
5 -use WP_REST_Request;
6 -use Activitypub\Http;
7 -use Activitypub\Collection\Followers;
8 10 use Activitypub\Collection\Interactions;
11 +use Activitypub\Collection\Posts;
12 +use Activitypub\Collection\Remote_Actors;
13 +use Activitypub\Tombstone;
9 14
15 +use function Activitypub\object_to_uri;
16 +
10 17 /**
11 18 * Handles Delete requests.
12 19 */
13 20 class Delete {
14 21 /**
15 - * Initialize the class, registering WordPress hooks
22 + * Initialize the class, registering WordPress hooks.
16 23 */
17 24 public static function init() {
18 - \add_action(
19 - 'activitypub_inbox_delete',
20 - array( self::class, 'handle_delete' )
21 - );
25 + \add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ), 10, 2 );
26 + \add_filter( 'activitypub_skip_inbox_storage', array( self::class, 'skip_inbox_storage' ), 10, 2 );
27 + \add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 2 );
28 + \add_action( 'activitypub_delete_remote_actor_interactions', array( self::class, 'delete_interactions' ) );
29 + \add_action( 'activitypub_delete_remote_actor_posts', array( self::class, 'delete_posts' ) );
22 30
23 - // defer signature verification for `Delete` requests.
24 - \add_filter(
25 - 'activitypub_defer_signature_verification',
26 - array( self::class, 'defer_signature_verification' ),
27 - 10,
28 - 2
29 - );
30 -
31 - // side effect
32 - \add_action(
33 - 'activitypub_delete_actor_interactions',
34 - array( self::class, 'delete_interactions' )
35 - );
31 + \add_filter( 'activitypub_get_outbox_activity', array( self::class, 'outbox_activity' ) );
32 + \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'maybe_bury' ), 10, 2 );
36 33 }
37 34
38 35 /**
39 36 * Handles "Delete" requests.
40 37 *
41 - * @param array $activity The delete activity.
42 - * @param int $user_id The ID of the user performing the delete activity.
38 + * @param array $activity The delete activity.
39 + * @param int|int[] $user_ids The local user ID(s).
43 40 */
44 - public static function handle_delete( $activity ) {
45 - $object_type = isset( $activity['object']['type'] ) ? $activity['object']['type'] : '';
41 + public static function handle_delete( $activity, $user_ids ) {
42 + $object_type = $activity['object']['type'] ?? '';
46 43
47 44 switch ( $object_type ) {
48 - // Actor Types
49 - // @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
45 + /*
46 + * Actor Types.
47 + *
48 + * @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
49 + */
50 50 case 'Person':
51 51 case 'Group':
52 52 case 'Organization':
53 53 case 'Service':
54 54 case 'Application':
55 - self::maybe_delete_follower( $activity );
55 + self::delete_remote_actor( $activity, $user_ids );
56 56 break;
57 - // Object and Link Types
58 - // @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
57 +
58 + /*
59 + * Object and Link Types.
60 + *
61 + * @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
62 + */
59 63 case 'Note':
60 64 case 'Article':
61 65 case 'Image':
62 66 case 'Audio':
@@ -62,108 +66,241 @@
62 66 case 'Audio':
63 67 case 'Video':
64 68 case 'Event':
65 69 case 'Document':
66 - self::maybe_delete_interaction( $activity );
70 + self::delete_object( $activity, $user_ids );
67 71 break;
68 - // Tombstone Type
69 - // @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
72 +
73 + /*
74 + * Tombstone Type.
75 + *
76 + * @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
77 + */
70 78 case 'Tombstone':
71 - self::maybe_delete_interaction( $activity );
79 + self::delete_object( $activity, $user_ids );
72 80 break;
73 - // Minimal Activity
74 - // @see https://www.w3.org/TR/activitystreams-core/#example-1
81 +
82 + /*
83 + * Minimal Activity.
84 + *
85 + * @see https://www.w3.org/TR/activitystreams-core/#example-1
86 + */
75 87 default:
76 - // ignore non Minimal Activities.
77 - if ( ! is_string( $activity['object'] ) ) {
78 - return;
88 + // Check if Object is an Actor.
89 + if ( object_to_uri( $activity['object'] ) === $activity['actor'] ) {
90 + self::delete_remote_actor( $activity, $user_ids );
91 + } else { // Assume an object otherwise.
92 + self::delete_object( $activity, $user_ids );
79 93 }
94 + // Maybe handle Delete Activity for other Object Types.
95 + break;
96 + }
97 + }
80 98
81 - // check if Object is an Actor.
82 - if ( $activity['actor'] === $activity['object'] ) {
83 - self::maybe_delete_follower( $activity );
84 - } else { // assume a interaction otherwise.
85 - self::maybe_delete_interaction( $activity );
86 - }
87 - // maybe handle Delete Activity for other Object Types.
88 - break;
99 + /**
100 + * Delete an Object.
101 + *
102 + * @param array $activity The Activity object.
103 + * @param int|int[] $user_ids The user ID(s).
104 + */
105 + public static function delete_object( $activity, $user_ids ) {
106 + $result = self::maybe_delete_interaction( $activity );
107 +
108 + if ( ! $result ) {
109 + $result = self::maybe_delete_post( $activity );
89 110 }
111 +
112 + $success = ( $result && ! \is_wp_error( $result ) );
113 +
114 + /**
115 + * Fires after an ActivityPub Delete activity has been handled.
116 + *
117 + * @param array $activity The ActivityPub activity data.
118 + * @param int[] $user_ids The local user IDs.
119 + * @param bool $success True on success, false otherwise.
120 + * @param mixed|null $result The result of the delete operation.
121 + */
122 + \do_action( 'activitypub_handled_delete', $activity, (array) $user_ids, $success, $result );
90 123 }
91 124
92 125 /**
126 + * Delete an Actor.
127 + *
128 + * @param array $activity The Activity object.
129 + * @param int|int[] $user_ids The user ID(s).
130 + */
131 + public static function delete_remote_actor( $activity, $user_ids ) {
132 + $result = self::maybe_delete_follower( $activity );
133 + $success = ( $result && ! \is_wp_error( $result ) );
134 +
135 + /**
136 + * Fires after an ActivityPub Delete activity has been handled.
137 + *
138 + * @param array $activity The ActivityPub activity data.
139 + * @param int[] $user_ids The local user IDs.
140 + * @param bool $success True on success, false otherwise.
141 + * @param mixed|null $result The result of the delete operation.
142 + */
143 + \do_action( 'activitypub_handled_delete', $activity, (array) $user_ids, $success, $result );
144 +
145 + return $result;
146 + }
147 +
148 + /**
93 149 * Delete a Follower if Actor-URL is a Tombstone.
94 150 *
95 151 * @param array $activity The delete activity.
152 + *
153 + * @return bool True on success, false otherwise.
96 154 */
97 155 public static function maybe_delete_follower( $activity ) {
98 - $follower = Followers::get_follower_by_actor( $activity['actor'] );
156 + $follower = Remote_Actors::get_by_uri( $activity['actor'] );
99 157
100 - // verify if Actor is deleted.
101 - if ( $follower && Http::is_tombstone( $activity['actor'] ) ) {
102 - $follower->delete();
103 - self::maybe_delete_interactions( $activity );
158 + // Verify that Actor is deleted.
159 + if ( ! is_wp_error( $follower ) && Tombstone::exists( $activity['actor'] ) ) {
160 + self::maybe_delete_interactions( $follower->ID );
161 + self::maybe_delete_posts( $follower->ID );
162 + $state = Remote_Actors::delete( $follower->ID );
104 163 }
164 +
165 + return $state ?? false;
105 166 }
106 167
107 168 /**
108 - * Delete Reactions if Actor-URL is a Tombstone.
169 + * Schedule Deletion of Interactions of a Remote Actor.
109 170 *
110 - * @param array $activity The delete activity.
171 + * @param int $id The remote actor ID.
111 172 */
112 - public static function maybe_delete_interactions( $activity ) {
113 - // verify if Actor is deleted.
114 - if ( Http::is_tombstone( $activity['actor'] ) ) {
115 - \wp_schedule_single_event(
116 - \time(),
117 - 'activitypub_delete_actor_interactions',
118 - array( $activity['actor'] )
119 - );
173 + public static function maybe_delete_interactions( $id ) {
174 + \wp_schedule_single_event(
175 + \time(),
176 + 'activitypub_delete_remote_actor_interactions',
177 + array( $id )
178 + );
179 + }
180 +
181 + /**
182 + * Schedule Deletion of Reader Items of a Remote Actor.
183 + *
184 + * @param int $id The remote actor ID.
185 + */
186 + public static function maybe_delete_posts( $id ) {
187 + \wp_schedule_single_event(
188 + \time(),
189 + 'activitypub_delete_remote_actor_posts',
190 + array( $id )
191 + );
192 + }
193 +
194 + /**
195 + * Delete Interactions from a Remote Actor.
196 + *
197 + * @param int $id The ID of the actor whose comments to delete.
198 + *
199 + * @return bool True on success, false otherwise.
200 + */
201 + public static function delete_interactions( $id ) {
202 + $comments = Interactions::get_by_remote_actor_id( $id );
203 +
204 + foreach ( $comments as $comment ) {
205 + \wp_delete_comment( $comment, true );
120 206 }
207 +
208 + if ( $comments ) {
209 + return true;
210 + } else {
211 + return false;
212 + }
121 213 }
122 214
123 215 /**
124 - * Delete comments from an Actor.
216 + * Delete Reader Items from an Actor.
125 217 *
126 - * @param array $comments The comments to delete.
218 + * @param int $id The ID of the actor whose comments to delete.
219 + *
220 + * @return bool True on success, false otherwise.
127 221 */
128 - public static function delete_interactions( $actor ) {
129 - $comments = Interactions::get_interactions_by_actor( $actor );
222 + public static function delete_posts( $id ) {
223 + $posts = Posts::get_by_remote_actor_id( $id );
130 224
131 - if ( is_array( $comments ) ) {
132 - foreach ( $comments as $comment ) {
133 - wp_delete_comment( $comment->comment_ID );
134 - }
225 + foreach ( $posts as $post ) {
226 + Posts::delete( $post->ID );
135 227 }
228 +
229 + if ( $posts ) {
230 + return true;
231 + } else {
232 + return false;
233 + }
136 234 }
137 235
138 236 /**
139 237 * Delete a Reaction if URL is a Tombstone.
140 238 *
239 + * Note: When comments are deleted, WordPress automatically deletes all associated
240 + * comment meta including _activitypub_remote_actor_id. The remote actor post itself
241 + * is not deleted, as it may be referenced by other comments or may be needed for
242 + * future interactions.
243 + *
141 244 * @param array $activity The delete activity.
142 245 *
143 - * @return void
246 + * @return bool True on success, false otherwise.
144 247 */
145 248 public static function maybe_delete_interaction( $activity ) {
146 - if ( is_array( $activity['object'] ) ) {
147 - $id = $activity['object']['id'];
148 - } else {
149 - $id = $activity['object'];
150 - }
249 + $id = object_to_uri( $activity['object'] );
250 + $comments = Interactions::get_by_id( $id );
151 251
152 - $comments = Interactions::get_interaction_by_id( $id );
153 -
154 - if ( $comments && Http::is_tombstone( $id ) ) {
252 + if ( $comments && Tombstone::exists( $id ) ) {
155 253 foreach ( $comments as $comment ) {
254 + // WordPress will automatically delete all comment meta including _activitypub_remote_actor_id.
156 255 wp_delete_comment( $comment->comment_ID, true );
157 256 }
257 +
258 + return true;
158 259 }
260 +
261 + return false;
159 262 }
160 263
161 264 /**
265 + * Delete a post from the Posts collection.
266 + *
267 + * @param array $activity The delete activity.
268 + *
269 + * @return bool|\WP_Error True on success, false or WP_Error on failure.
270 + */
271 + public static function maybe_delete_post( $activity ) {
272 + $id = object_to_uri( $activity['object'] );
273 +
274 + // Check if the object exists and is a tombstone.
275 + if ( Tombstone::exists( $id ) ) {
276 + return Posts::delete_by_guid( $id );
277 + }
278 +
279 + return false;
280 + }
281 +
282 + /**
283 + * Skip inbox storage for `Delete` requests.
284 + *
285 + * @param bool $skip Whether to skip inbox storage.
286 + * @param array $data The activity data array.
287 + *
288 + * @return bool Whether to skip inbox storage.
289 + */
290 + public static function skip_inbox_storage( $skip, $data ) {
291 + if ( isset( $data['type'] ) && 'Delete' === $data['type'] ) {
292 + return true;
293 + }
294 +
295 + return $skip;
296 + }
297 +
298 + /**
162 299 * Defer signature verification for `Delete` requests.
163 300 *
164 - * @param bool $defer Whether to defer signature verification.
165 - * @param WP_REST_Request $request The request object.
301 + * @param bool $defer Whether to defer signature verification.
302 + * @param \WP_REST_Request $request The request object.
166 303 *
167 304 * @return bool Whether to defer signature verification.
168 305 */
169 306 public static function defer_signature_verification( $defer, $request ) {
@@ -172,7 +309,46 @@
172 309 if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
173 310 return true;
174 311 }
175 312
176 - return false;
313 + return $defer;
314 + }
315 +
316 + /**
317 + * Set the object to the object ID.
318 + *
319 + * @param \Activitypub\Activity\Activity $activity The Activity object.
320 + *
321 + * @return \Activitypub\Activity\Activity The filtered Activity object.
322 + */
323 + public static function outbox_activity( $activity ) {
324 + if ( 'Delete' === $activity->get_type() ) {
325 + $activity->set_object( object_to_uri( $activity->get_object() ) );
326 + }
327 +
328 + return $activity;
329 + }
330 +
331 + /**
332 + * Add a URL to the tombstone registry when a Delete activity is sent.
333 + *
334 + * @param int $outbox_id The ID of the outbox activity.
335 + * @param \Activitypub\Activity\Activity $activity The Activity object.
336 + */
337 + public static function maybe_bury( $outbox_id, $activity ) {
338 + if ( 'Delete' !== $activity->get_type() ) {
339 + return;
340 + }
341 +
342 + $object = $activity->get_object();
343 +
344 + if ( ! $object ) {
345 + return;
346 + }
347 +
348 + Tombstone::bury( object_to_uri( $object ) );
349 +
350 + if ( \is_object( $object ) ) {
351 + Tombstone::bury( $object->get_id(), $object->get_url() );
352 + }
177 353 }
178 354 }