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 +258 -69 1.3.08.0.2 View file →
@@ -1,49 +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 25 \add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ), 10, 2 );
19 - // defer signature verification for `Delete` requests.
26 + \add_filter( 'activitypub_skip_inbox_storage', array( self::class, 'skip_inbox_storage' ), 10, 2 );
20 27 \add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 2 );
21 - // side effect
22 - \add_action( 'activitypub_delete_actor_interactions', array( self::class, 'delete_interactions' ), 10, 1 );
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' ) );
30 +
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 );
23 33 }
24 34
25 35 /**
26 36 * Handles "Delete" requests.
27 37 *
28 - * @param array $activity The delete activity.
29 - * @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).
30 40 */
31 - public static function handle_delete( $activity, $user_id ) {
32 - $object_type = isset( $activity['object']['type'] ) ? $activity['object']['type'] : '';
41 + public static function handle_delete( $activity, $user_ids ) {
42 + $object_type = $activity['object']['type'] ?? '';
33 43
34 44 switch ( $object_type ) {
35 - // Actor Types
36 - // @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 + */
37 50 case 'Person':
38 51 case 'Group':
39 52 case 'Organization':
40 53 case 'Service':
41 54 case 'Application':
42 - self::maybe_delete_follower( $user_id, $activity );
55 + self::delete_remote_actor( $activity, $user_ids );
43 56 break;
44 - // Object and Link Types
45 - // @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 + */
46 63 case 'Note':
47 64 case 'Article':
48 65 case 'Image':
49 66 case 'Audio':
@@ -49,108 +66,241 @@
49 66 case 'Audio':
50 67 case 'Video':
51 68 case 'Event':
52 69 case 'Document':
53 - self::maybe_delete_interaction( $activity );
70 + self::delete_object( $activity, $user_ids );
54 71 break;
55 - // Tombstone Type
56 - // @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 + */
57 78 case 'Tombstone':
58 - self::maybe_delete_interaction( $activity );
79 + self::delete_object( $activity, $user_ids );
59 80 break;
60 - // Minimal Activity
61 - // @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 + */
62 87 default:
63 - // ignore non Minimal Activities.
64 - if ( ! is_string( $activity['object'] ) ) {
65 - 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 );
66 93 }
94 + // Maybe handle Delete Activity for other Object Types.
95 + break;
96 + }
97 + }
67 98
68 - // check if Object is an Actor.
69 - if ( $activity['actor'] === $activity['object'] ) {
70 - self::maybe_delete_follower( $activity );
71 - self::maybe_delete_interactions( $activity );
72 - } else { // assume a interaction otherwise.
73 - self::maybe_delete_interaction( $activity );
74 - }
75 - // maybe handle Delete Activity for other Object Types.
76 - 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 );
77 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 );
78 123 }
79 124
80 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 + /**
81 149 * Delete a Follower if Actor-URL is a Tombstone.
82 150 *
83 151 * @param array $activity The delete activity.
152 + *
153 + * @return bool True on success, false otherwise.
84 154 */
85 155 public static function maybe_delete_follower( $activity ) {
86 - $follower = Followers::get_follower_by_actor( $activity['actor'] );
156 + $follower = Remote_Actors::get_by_uri( $activity['actor'] );
87 157
88 - // verify if Actor is deleted.
89 - if ( $follower && Http::is_tombstone( $activity['actor'] ) ) {
90 - $follower->delete();
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 );
91 163 }
164 +
165 + return $state ?? false;
92 166 }
93 167
94 168 /**
95 - * Delete Reactions if Actor-URL is a Tombstone.
169 + * Schedule Deletion of Interactions of a Remote Actor.
96 170 *
97 - * @param array $activity The delete activity.
171 + * @param int $id The remote actor ID.
98 172 */
99 - public static function maybe_delete_interactions( $activity ) {
100 - // verify if Actor is deleted.
101 - if ( Http::is_tombstone( $activity['actor'] ) ) {
102 - \wp_schedule_single_event(
103 - \time(),
104 - 'activitypub_delete_actor_interactions',
105 - array( $activity['actor'] )
106 - );
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 );
107 206 }
207 +
208 + if ( $comments ) {
209 + return true;
210 + } else {
211 + return false;
212 + }
108 213 }
109 214
110 215 /**
111 - * Delete comments from an Actor.
216 + * Delete Reader Items from an Actor.
112 217 *
113 - * @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.
114 221 */
115 - public static function delete_interactions( $actor ) {
116 - $comments = Interactions::get_interactions_by_actor( $actor );
222 + public static function delete_posts( $id ) {
223 + $posts = Posts::get_by_remote_actor_id( $id );
117 224
118 - if ( is_array( $comments ) ) {
119 - foreach ( $comments as $comment ) {
120 - wp_delete_comment( $comment->comment_ID );
121 - }
225 + foreach ( $posts as $post ) {
226 + Posts::delete( $post->ID );
122 227 }
228 +
229 + if ( $posts ) {
230 + return true;
231 + } else {
232 + return false;
233 + }
123 234 }
124 235
125 236 /**
126 237 * Delete a Reaction if URL is a Tombstone.
127 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 + *
128 244 * @param array $activity The delete activity.
129 245 *
130 - * @return void
246 + * @return bool True on success, false otherwise.
131 247 */
132 248 public static function maybe_delete_interaction( $activity ) {
133 - if ( is_array( $activity['object'] ) ) {
134 - $id = $activity['object']['id'];
135 - } else {
136 - $id = $activity['object'];
137 - }
249 + $id = object_to_uri( $activity['object'] );
250 + $comments = Interactions::get_by_id( $id );
138 251
139 - $comments = Interactions::get_interaction_by_id( $id );
140 -
141 - if ( $comments && Http::is_tombstone( $id ) ) {
252 + if ( $comments && Tombstone::exists( $id ) ) {
142 253 foreach ( $comments as $comment ) {
254 + // WordPress will automatically delete all comment meta including _activitypub_remote_actor_id.
143 255 wp_delete_comment( $comment->comment_ID, true );
144 256 }
257 +
258 + return true;
145 259 }
260 +
261 + return false;
146 262 }
147 263
148 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 + /**
149 299 * Defer signature verification for `Delete` requests.
150 300 *
151 - * @param bool $defer Whether to defer signature verification.
152 - * @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.
153 303 *
154 304 * @return bool Whether to defer signature verification.
155 305 */
156 306 public static function defer_signature_verification( $defer, $request ) {
@@ -159,7 +309,46 @@
159 309 if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
160 310 return true;
161 311 }
162 312
163 - 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 + }
164 353 }
165 354 }