PluginProbe
ActivityPub / trunk
ActivityPub vtrunk
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
activitypub / includes / handler / class-delete.php

class-delete.php in ActivityPub trunk, at includes/handler/class-delete.php

378 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Delete handler file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub\Handler;
9
10 use Activitypub\Collection\Inbox;
11 use Activitypub\Collection\Interactions;
12 use Activitypub\Collection\Remote_Actors;
13 use Activitypub\Collection\Remote_Posts;
14 use Activitypub\Tombstone;
15
16 use function Activitypub\object_to_uri;
17
18 /**
19 * Handles Delete requests.
20 */
21 class Delete {
22 /**
23 * Initialize the class, registering WordPress hooks.
24 */
25 public static function init() {
26 \add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ), 10, 4 );
27 \add_action( 'activitypub_inbox_shared_delete', array( self::class, 'handle_delete' ), 10, 4 );
28 \add_filter( 'activitypub_skip_inbox_storage', array( self::class, 'skip_inbox_storage' ), 10, 2 );
29 \add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 3 );
30 \add_action( 'activitypub_delete_remote_actor_interactions', array( self::class, 'delete_interactions' ) );
31 \add_action( 'activitypub_delete_remote_actor_posts', array( self::class, 'delete_posts' ) );
32
33 \add_filter( 'activitypub_get_outbox_activity', array( self::class, 'outbox_activity' ) );
34 \add_action( 'post_activitypub_add_to_outbox', array( self::class, 'maybe_bury' ), 10, 2 );
35 }
36
37 /**
38 * Handles "Delete" requests.
39 *
40 * @param array $activity The delete activity.
41 * @param int|int[] $user_ids The local user ID(s).
42 * @param \Activitypub\Activity\Activity|null $activity_object Optional. The activity object. Default null.
43 * @param string|null $context Optional. The inbox context. Default null.
44 */
45 public static function handle_delete( $activity, $user_ids, $activity_object = null, $context = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
46 // The shared inbox invokes this once per resolved recipient and once on the shared hook;
47 // handle that path only on the shared hook, so it runs once with the full recipient list.
48 if ( Inbox::CONTEXT_SHARED_INBOX === $context && 'activitypub_inbox_shared_delete' !== \current_filter() ) {
49 return;
50 }
51
52 $object_type = $activity['object']['type'] ?? '';
53
54 switch ( $object_type ) {
55 /*
56 * Actor Types.
57 *
58 * @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
59 */
60 case 'Person':
61 case 'Group':
62 case 'Organization':
63 case 'Service':
64 case 'Application':
65 self::delete_remote_actor( $activity, $user_ids );
66 break;
67
68 /*
69 * Object and Link Types.
70 *
71 * @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
72 */
73 case 'Note':
74 case 'Article':
75 case 'Image':
76 case 'Audio':
77 case 'Video':
78 case 'Event':
79 case 'Document':
80 self::delete_object( $activity, $user_ids );
81 break;
82
83 /*
84 * Tombstone Type.
85 *
86 * @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
87 */
88 case 'Tombstone':
89 self::delete_object( $activity, $user_ids );
90 break;
91
92 /*
93 * Minimal Activity.
94 *
95 * @see https://www.w3.org/TR/activitystreams-core/#example-1
96 */
97 default:
98 // Check if Object is an Actor.
99 if ( object_to_uri( $activity['object'] ) === $activity['actor'] ) {
100 self::delete_remote_actor( $activity, $user_ids );
101 } else { // Assume an object otherwise.
102 self::delete_object( $activity, $user_ids );
103 }
104 // Maybe handle Delete Activity for other Object Types.
105 break;
106 }
107 }
108
109 /**
110 * Delete an Object.
111 *
112 * @param array $activity The Activity object.
113 * @param int|int[] $user_ids The user ID(s).
114 */
115 public static function delete_object( $activity, $user_ids ) {
116 $result = self::maybe_delete_interaction( $activity );
117
118 if ( ! $result ) {
119 $result = self::maybe_delete_post( $activity );
120 }
121
122 $success = ( $result && ! \is_wp_error( $result ) );
123
124 /**
125 * Fires after an ActivityPub Delete activity has been handled.
126 *
127 * @param array $activity The ActivityPub activity data.
128 * @param int[] $user_ids The local user IDs.
129 * @param bool $success True on success, false otherwise.
130 * @param mixed|null $result The result of the delete operation.
131 */
132 \do_action( 'activitypub_handled_delete', $activity, (array) $user_ids, $success, $result );
133 }
134
135 /**
136 * Delete an Actor.
137 *
138 * @param array $activity The Activity object.
139 * @param int|int[] $user_ids The user ID(s).
140 */
141 public static function delete_remote_actor( $activity, $user_ids ) {
142 $result = self::maybe_delete_follower( $activity );
143 $success = ( $result && ! \is_wp_error( $result ) );
144
145 /**
146 * Fires after an ActivityPub Delete activity has been handled.
147 *
148 * @param array $activity The ActivityPub activity data.
149 * @param int[] $user_ids The local user IDs.
150 * @param bool $success True on success, false otherwise.
151 * @param mixed|null $result The result of the delete operation.
152 */
153 \do_action( 'activitypub_handled_delete', $activity, (array) $user_ids, $success, $result );
154
155 return $result;
156 }
157
158 /**
159 * Delete a Follower if Actor-URL is a Tombstone.
160 *
161 * @param array $activity The delete activity.
162 *
163 * @return bool True on success, false otherwise.
164 */
165 public static function maybe_delete_follower( $activity ) {
166 $follower = Remote_Actors::get_by_uri( $activity['actor'] );
167
168 // Verify that Actor is deleted.
169 if ( ! \is_wp_error( $follower ) && Tombstone::exists( $activity['actor'] ) ) {
170 self::maybe_delete_interactions( $follower->ID );
171 self::maybe_delete_posts( $follower->ID );
172 $state = Remote_Actors::delete( $follower->ID );
173 }
174
175 return $state ?? false;
176 }
177
178 /**
179 * Schedule Deletion of Interactions of a Remote Actor.
180 *
181 * @param int $id The remote actor ID.
182 */
183 public static function maybe_delete_interactions( $id ) {
184 \wp_schedule_single_event(
185 \time(),
186 'activitypub_delete_remote_actor_interactions',
187 array( $id )
188 );
189 }
190
191 /**
192 * Schedule Deletion of Reader Items of a Remote Actor.
193 *
194 * @param int $id The remote actor ID.
195 */
196 public static function maybe_delete_posts( $id ) {
197 \wp_schedule_single_event(
198 \time(),
199 'activitypub_delete_remote_actor_posts',
200 array( $id )
201 );
202 }
203
204 /**
205 * Delete Interactions from a Remote Actor.
206 *
207 * @param int $id The ID of the actor whose comments to delete.
208 *
209 * @return bool True on success, false otherwise.
210 */
211 public static function delete_interactions( $id ) {
212 $comments = Interactions::get_by_remote_actor_id( $id );
213
214 foreach ( $comments as $comment ) {
215 \wp_delete_comment( $comment, true );
216 }
217
218 if ( $comments ) {
219 return true;
220 } else {
221 return false;
222 }
223 }
224
225 /**
226 * Delete Reader Items from an Actor.
227 *
228 * @param int $id The ID of the actor whose comments to delete.
229 *
230 * @return bool True on success, false otherwise.
231 */
232 public static function delete_posts( $id ) {
233 $posts = Remote_Posts::get_by_remote_actor_id( $id );
234
235 foreach ( $posts as $post ) {
236 Remote_Posts::delete( $post->ID );
237 }
238
239 if ( $posts ) {
240 return true;
241 } else {
242 return false;
243 }
244 }
245
246 /**
247 * Delete a Reaction if URL is a Tombstone.
248 *
249 * Note: When comments are deleted, WordPress automatically deletes all associated
250 * comment meta including _activitypub_remote_actor_id. The remote actor post itself
251 * is not deleted, as it may be referenced by other comments or may be needed for
252 * future interactions.
253 *
254 * @param array $activity The delete activity.
255 *
256 * @return bool True on success, false otherwise.
257 */
258 public static function maybe_delete_interaction( $activity ) {
259 $id = object_to_uri( $activity['object'] );
260 $comments = Interactions::get_by_id( $id );
261
262 if ( $comments && Tombstone::exists( $id ) ) {
263 foreach ( $comments as $comment ) {
264 // WordPress will automatically delete all comment meta including _activitypub_remote_actor_id.
265 \wp_delete_comment( $comment->comment_ID, true );
266 }
267
268 return true;
269 }
270
271 return false;
272 }
273
274 /**
275 * Delete a post from the Posts collection.
276 *
277 * @param array $activity The delete activity.
278 *
279 * @return bool|\WP_Error True on success, false or WP_Error on failure.
280 */
281 public static function maybe_delete_post( $activity ) {
282 $id = object_to_uri( $activity['object'] );
283
284 // Check if the object exists and is a tombstone.
285 if ( Tombstone::exists( $id ) ) {
286 return Remote_Posts::delete_by_guid( $id );
287 }
288
289 return false;
290 }
291
292 /**
293 * Skip inbox storage for `Delete` requests.
294 *
295 * @param bool $skip Whether to skip inbox storage.
296 * @param array $data The activity data array.
297 *
298 * @return bool Whether to skip inbox storage.
299 */
300 public static function skip_inbox_storage( $skip, $data ) {
301 if ( isset( $data['type'] ) && 'Delete' === $data['type'] ) {
302 return true;
303 }
304
305 return $skip;
306 }
307
308 /**
309 * Defer signature verification for `Delete` requests.
310 *
311 * Endpoints that opt in to mandatory signing by calling
312 * `verify_signature( $request, true )` must not be overridden — the
313 * Delete carve-out is only for the default inbox path where the
314 * remote actor's keys may legitimately be gone before the Delete
315 * arrives.
316 *
317 * @since 8.2.0 The `$force_signature` parameter is now respected.
318 *
319 * @param bool $defer Whether to defer signature verification.
320 * @param \WP_REST_Request $request The request object.
321 * @param bool $force_signature Whether the caller has forced signature verification.
322 *
323 * @return bool Whether to defer signature verification.
324 */
325 public static function defer_signature_verification( $defer, $request, $force_signature = false ) {
326 if ( $force_signature ) {
327 return $defer;
328 }
329
330 $json = $request->get_json_params();
331
332 if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
333 return true;
334 }
335
336 return $defer;
337 }
338
339 /**
340 * Set the object to the object ID.
341 *
342 * @param \Activitypub\Activity\Activity $activity The Activity object.
343 *
344 * @return \Activitypub\Activity\Activity The filtered Activity object.
345 */
346 public static function outbox_activity( $activity ) {
347 if ( 'Delete' === $activity->get_type() ) {
348 $activity->set_object( object_to_uri( $activity->get_object() ) );
349 }
350
351 return $activity;
352 }
353
354 /**
355 * Add a URL to the tombstone registry when a Delete activity is sent.
356 *
357 * @param int $outbox_id The ID of the outbox activity.
358 * @param \Activitypub\Activity\Activity $activity The Activity object.
359 */
360 public static function maybe_bury( $outbox_id, $activity ) {
361 if ( 'Delete' !== $activity->get_type() ) {
362 return;
363 }
364
365 $object = $activity->get_object();
366
367 if ( ! $object ) {
368 return;
369 }
370
371 Tombstone::bury( object_to_uri( $object ) );
372
373 if ( \is_object( $object ) ) {
374 Tombstone::bury( $object->get_id(), $object->get_url() );
375 }
376 }
377 }
378