PluginProbe
ActivityPub / 3.2.2
ActivityPub v3.2.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 +85 -284 9.3.03.2.2 View file →
@@ -1,76 +1,62 @@
1 1 <?php
2 -/**
3 - * Delete handler file.
4 - *
5 - * @package Activitypub
6 - */
7 -
8 2 namespace Activitypub\Handler;
9 3
10 -use Activitypub\Collection\Inbox;
4 +use WP_Error;
5 +use WP_REST_Request;
6 +use Activitypub\Http;
7 +use Activitypub\Collection\Followers;
11 8 use Activitypub\Collection\Interactions;
12 -use Activitypub\Collection\Remote_Actors;
13 -use Activitypub\Collection\Remote_Posts;
14 -use Activitypub\Tombstone;
15 9
16 -use function Activitypub\object_to_uri;
17 -
18 10 /**
19 11 * Handles Delete requests.
20 12 */
21 13 class Delete {
22 14 /**
23 - * Initialize the class, registering WordPress hooks.
15 + * Initialize the class, registering WordPress hooks
24 16 */
25 17 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' ) );
18 + \add_action(
19 + 'activitypub_inbox_delete',
20 + array( self::class, 'handle_delete' )
21 + );
32 22
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 );
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 + );
35 36 }
36 37
37 38 /**
38 39 * Handles "Delete" requests.
39 40 *
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.
41 + * @param array $activity The delete activity.
42 + * @param int $user_id The ID of the user performing the delete activity.
44 43 */
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 - }
44 + public static function handle_delete( $activity ) {
45 + $object_type = isset( $activity['object']['type'] ) ? $activity['object']['type'] : '';
51 46
52 - $object_type = $activity['object']['type'] ?? '';
53 -
54 47 switch ( $object_type ) {
55 - /*
56 - * Actor Types.
57 - *
58 - * @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
59 - */
48 + // Actor Types
49 + // @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
60 50 case 'Person':
61 51 case 'Group':
62 52 case 'Organization':
63 53 case 'Service':
64 54 case 'Application':
65 - self::delete_remote_actor( $activity, $user_ids );
55 + self::maybe_delete_follower( $activity );
66 56 break;
67 -
68 - /*
69 - * Object and Link Types.
70 - *
71 - * @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
72 - */
57 + // Object and Link Types
58 + // @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
73 59 case 'Note':
74 60 case 'Article':
75 61 case 'Image':
76 62 case 'Audio':
@@ -76,258 +62,112 @@
76 62 case 'Audio':
77 63 case 'Video':
78 64 case 'Event':
79 65 case 'Document':
80 - self::delete_object( $activity, $user_ids );
66 + self::maybe_delete_interaction( $activity );
81 67 break;
82 -
83 - /*
84 - * Tombstone Type.
85 - *
86 - * @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
87 - */
68 + // Tombstone Type
69 + // @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
88 70 case 'Tombstone':
89 - self::delete_object( $activity, $user_ids );
71 + self::maybe_delete_interaction( $activity );
90 72 break;
73 + // Minimal Activity
74 + // @see https://www.w3.org/TR/activitystreams-core/#example-1
75 + default:
76 + // ignore non Minimal Activities.
77 + if ( ! is_string( $activity['object'] ) ) {
78 + return;
79 + }
91 80
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 );
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 );
103 86 }
104 - // Maybe handle Delete Activity for other Object Types.
87 + // maybe handle Delete Activity for other Object Types.
105 88 break;
106 89 }
107 90 }
108 91
109 92 /**
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 93 * Delete a Follower if Actor-URL is a Tombstone.
160 94 *
161 95 * @param array $activity The delete activity.
162 - *
163 - * @return bool True on success, false otherwise.
164 96 */
165 97 public static function maybe_delete_follower( $activity ) {
166 - $follower = Remote_Actors::get_by_uri( $activity['actor'] );
98 + $follower = Followers::get_follower_by_actor( $activity['actor'] );
167 99
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 );
100 + // verify if Actor is deleted.
101 + if ( $follower && Http::is_tombstone( $activity['actor'] ) ) {
102 + $follower->delete();
103 + self::maybe_delete_interactions( $activity );
173 104 }
174 -
175 - return $state ?? false;
176 105 }
177 106
178 107 /**
179 - * Schedule Deletion of Interactions of a Remote Actor.
108 + * Delete Reactions if Actor-URL is a Tombstone.
180 109 *
181 - * @param int $id The remote actor ID.
110 + * @param array $activity The delete activity.
182 111 */
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 );
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 + );
216 120 }
217 -
218 - if ( $comments ) {
219 - return true;
220 - } else {
221 - return false;
222 - }
223 121 }
224 122
225 123 /**
226 - * Delete Reader Items from an Actor.
124 + * Delete comments from an Actor.
227 125 *
228 - * @param int $id The ID of the actor whose comments to delete.
229 - *
230 - * @return bool True on success, false otherwise.
126 + * @param array $comments The comments to delete.
231 127 */
232 - public static function delete_posts( $id ) {
233 - $posts = Remote_Posts::get_by_remote_actor_id( $id );
128 + public static function delete_interactions( $actor ) {
129 + $comments = Interactions::get_interactions_by_actor( $actor );
234 130
235 - foreach ( $posts as $post ) {
236 - Remote_Posts::delete( $post->ID );
131 + if ( is_array( $comments ) ) {
132 + foreach ( $comments as $comment ) {
133 + wp_delete_comment( $comment->comment_ID );
134 + }
237 135 }
238 -
239 - if ( $posts ) {
240 - return true;
241 - } else {
242 - return false;
243 - }
244 136 }
245 137
246 138 /**
247 139 * Delete a Reaction if URL is a Tombstone.
248 140 *
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 141 * @param array $activity The delete activity.
255 142 *
256 - * @return bool True on success, false otherwise.
143 + * @return void
257 144 */
258 145 public static function maybe_delete_interaction( $activity ) {
259 - $id = object_to_uri( $activity['object'] );
260 - $comments = Interactions::get_by_id( $id );
146 + if ( is_array( $activity['object'] ) ) {
147 + $id = $activity['object']['id'];
148 + } else {
149 + $id = $activity['object'];
150 + }
261 151
262 - if ( $comments && Tombstone::exists( $id ) ) {
152 + $comments = Interactions::get_interaction_by_id( $id );
153 +
154 + if ( $comments && Http::is_tombstone( $id ) ) {
263 155 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 );
156 + wp_delete_comment( $comment->comment_ID, true );
266 157 }
267 -
268 - return true;
269 158 }
270 -
271 - return false;
272 159 }
273 160
274 161 /**
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 162 * Defer signature verification for `Delete` requests.
310 163 *
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.
164 + * @param bool $defer Whether to defer signature verification.
165 + * @param WP_REST_Request $request The request object.
316 166 *
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 167 * @return bool Whether to defer signature verification.
324 168 */
325 - public static function defer_signature_verification( $defer, $request, $force_signature = false ) {
326 - if ( $force_signature ) {
327 - return $defer;
328 - }
329 -
169 + public static function defer_signature_verification( $defer, $request ) {
330 170 $json = $request->get_json_params();
331 171
332 172 if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
333 173 return true;
@@ -332,46 +172,7 @@
332 172 if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
333 173 return true;
334 174 }
335 175
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 - }
176 + return false;
376 177 }
377 178 }