PluginProbe
ActivityPub / 9.2.1
ActivityPub v9.2.1
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 9.2.1, at includes/handler/class-delete.php

368 lines 9.8 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\Interactions;
11 use Activitypub\Collection\Remote_Actors;
12 use Activitypub\Collection\Remote_Posts;
13 use Activitypub\Tombstone;
14
15 use function Activitypub\object_to_uri;
16
17 /**
18 * Handles Delete requests.
19 */
20 class Delete {
21 /**
22 * Initialize the class, registering WordPress hooks.
23 */
24 public static function init() {
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, 3 );
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 );
33 }
34
35 /**
36 * Handles "Delete" requests.
37 *
38 * @param array $activity The delete activity.
39 * @param int|int[] $user_ids The local user ID(s).
40 */
41 public static function handle_delete( $activity, $user_ids ) {
42 $object_type = $activity['object']['type'] ?? '';
43
44 switch ( $object_type ) {
45 /*
46 * Actor Types.
47 *
48 * @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
49 */
50 case 'Person':
51 case 'Group':
52 case 'Organization':
53 case 'Service':
54 case 'Application':
55 self::delete_remote_actor( $activity, $user_ids );
56 break;
57
58 /*
59 * Object and Link Types.
60 *
61 * @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
62 */
63 case 'Note':
64 case 'Article':
65 case 'Image':
66 case 'Audio':
67 case 'Video':
68 case 'Event':
69 case 'Document':
70 self::delete_object( $activity, $user_ids );
71 break;
72
73 /*
74 * Tombstone Type.
75 *
76 * @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
77 */
78 case 'Tombstone':
79 self::delete_object( $activity, $user_ids );
80 break;
81
82 /*
83 * Minimal Activity.
84 *
85 * @see https://www.w3.org/TR/activitystreams-core/#example-1
86 */
87 default:
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 );
93 }
94 // Maybe handle Delete Activity for other Object Types.
95 break;
96 }
97 }
98
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 );
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 );
123 }
124
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 /**
149 * Delete a Follower if Actor-URL is a Tombstone.
150 *
151 * @param array $activity The delete activity.
152 *
153 * @return bool True on success, false otherwise.
154 */
155 public static function maybe_delete_follower( $activity ) {
156 $follower = Remote_Actors::get_by_uri( $activity['actor'] );
157
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 );
163 }
164
165 return $state ?? false;
166 }
167
168 /**
169 * Schedule Deletion of Interactions of a Remote Actor.
170 *
171 * @param int $id The remote actor ID.
172 */
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 );
206 }
207
208 if ( $comments ) {
209 return true;
210 } else {
211 return false;
212 }
213 }
214
215 /**
216 * Delete Reader Items from an Actor.
217 *
218 * @param int $id The ID of the actor whose comments to delete.
219 *
220 * @return bool True on success, false otherwise.
221 */
222 public static function delete_posts( $id ) {
223 $posts = Remote_Posts::get_by_remote_actor_id( $id );
224
225 foreach ( $posts as $post ) {
226 Remote_Posts::delete( $post->ID );
227 }
228
229 if ( $posts ) {
230 return true;
231 } else {
232 return false;
233 }
234 }
235
236 /**
237 * Delete a Reaction if URL is a Tombstone.
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 *
244 * @param array $activity The delete activity.
245 *
246 * @return bool True on success, false otherwise.
247 */
248 public static function maybe_delete_interaction( $activity ) {
249 $id = object_to_uri( $activity['object'] );
250 $comments = Interactions::get_by_id( $id );
251
252 if ( $comments && Tombstone::exists( $id ) ) {
253 foreach ( $comments as $comment ) {
254 // WordPress will automatically delete all comment meta including _activitypub_remote_actor_id.
255 \wp_delete_comment( $comment->comment_ID, true );
256 }
257
258 return true;
259 }
260
261 return false;
262 }
263
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 Remote_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 /**
299 * Defer signature verification for `Delete` requests.
300 *
301 * Endpoints that opt in to mandatory signing by calling
302 * `verify_signature( $request, true )` must not be overridden — the
303 * Delete carve-out is only for the default inbox path where the
304 * remote actor's keys may legitimately be gone before the Delete
305 * arrives.
306 *
307 * @since 8.2.0 The `$force_signature` parameter is now respected.
308 *
309 * @param bool $defer Whether to defer signature verification.
310 * @param \WP_REST_Request $request The request object.
311 * @param bool $force_signature Whether the caller has forced signature verification.
312 *
313 * @return bool Whether to defer signature verification.
314 */
315 public static function defer_signature_verification( $defer, $request, $force_signature = false ) {
316 if ( $force_signature ) {
317 return $defer;
318 }
319
320 $json = $request->get_json_params();
321
322 if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
323 return true;
324 }
325
326 return $defer;
327 }
328
329 /**
330 * Set the object to the object ID.
331 *
332 * @param \Activitypub\Activity\Activity $activity The Activity object.
333 *
334 * @return \Activitypub\Activity\Activity The filtered Activity object.
335 */
336 public static function outbox_activity( $activity ) {
337 if ( 'Delete' === $activity->get_type() ) {
338 $activity->set_object( object_to_uri( $activity->get_object() ) );
339 }
340
341 return $activity;
342 }
343
344 /**
345 * Add a URL to the tombstone registry when a Delete activity is sent.
346 *
347 * @param int $outbox_id The ID of the outbox activity.
348 * @param \Activitypub\Activity\Activity $activity The Activity object.
349 */
350 public static function maybe_bury( $outbox_id, $activity ) {
351 if ( 'Delete' !== $activity->get_type() ) {
352 return;
353 }
354
355 $object = $activity->get_object();
356
357 if ( ! $object ) {
358 return;
359 }
360
361 Tombstone::bury( object_to_uri( $object ) );
362
363 if ( \is_object( $object ) ) {
364 Tombstone::bury( $object->get_id(), $object->get_url() );
365 }
366 }
367 }
368