Skip to content

Commit 7bc9606

Browse files
authored
Delete Reader Item (#2313)
1 parent 4c09cb8 commit 7bc9606

File tree

6 files changed

+353
-26
lines changed

6 files changed

+353
-26
lines changed

includes/class-post-types.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ public static function register_outbox_post_type() {
348348
}
349349

350350
/**
351-
* Register the Object post type.
351+
* Register the Post post type.
352352
*/
353353
public static function register_post_post_type() {
354354
\register_post_type(

includes/class-scheduler.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ public static function cleanup_remote_actors() {
222222
\wp_delete_post( $actor->ID );
223223
} elseif ( empty( $meta ) || ! is_array( $meta ) || \is_wp_error( $meta ) ) {
224224
if ( Remote_Actors::count_errors( $actor->ID ) >= 5 ) {
225-
\wp_schedule_single_event( \time(), 'activitypub_delete_actor_interactions', array( $actor->guid ) );
225+
\wp_schedule_single_event( \time(), 'activitypub_delete_remote_actor_interactions', array( $actor->guid ) );
226+
\wp_schedule_single_event( \time(), 'activitypub_delete_remote_actor_posts', array( $actor->guid ) );
226227
\wp_delete_post( $actor->ID );
227228
} else {
228229
Remote_Actors::add_error( $actor->ID, $meta );

includes/collection/class-posts.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,33 @@ public static function update( $activity ) {
119119
return \get_post( $post_id );
120120
}
121121

122+
/**
123+
* Delete an object from the collection.
124+
*
125+
* @param int $id The object ID.
126+
*
127+
* @return bool|int|null The deleted post ID, false on failure, or null if no post to delete.
128+
*/
129+
public static function delete( $id ) {
130+
return \wp_delete_post( $id, true );
131+
}
132+
133+
/**
134+
* Delete an object from the collection by its GUID.
135+
*
136+
* @param string $guid The object GUID.
137+
*
138+
* @return bool|int|null The deleted post ID, false on failure, or null if no post to delete.
139+
*/
140+
public static function delete_by_guid( $guid ) {
141+
$post = self::get_by_guid( $guid );
142+
if ( \is_wp_error( $post ) ) {
143+
return $post;
144+
}
145+
146+
return self::delete( $post->ID );
147+
}
148+
122149
/**
123150
* Convert an activity to a post array.
124151
*
@@ -164,4 +191,31 @@ private static function add_taxonomies( $post_id, $activity_object ) {
164191

165192
\wp_set_post_terms( $post_id, $tags, 'ap_tag' );
166193
}
194+
195+
/**
196+
* Get posts by remote actor.
197+
*
198+
* @param string $actor The remote actor URI.
199+
*
200+
* @return array Array of WP_Post objects.
201+
*/
202+
public static function get_by_remote_actor( $actor ) {
203+
$remote_actor = Remote_Actors::fetch_by_uri( $actor );
204+
if ( \is_wp_error( $remote_actor ) ) {
205+
return array();
206+
}
207+
208+
$query = new \WP_Query(
209+
array(
210+
'post_type' => self::POST_TYPE,
211+
'posts_per_page' => -1,
212+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
213+
'meta_key' => '_activitypub_remote_actor_id',
214+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
215+
'meta_value' => $remote_actor->ID,
216+
)
217+
);
218+
219+
return $query->posts;
220+
}
167221
}

includes/handler/class-delete.php

Lines changed: 115 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
namespace Activitypub\Handler;
99

1010
use Activitypub\Collection\Interactions;
11+
use Activitypub\Collection\Posts;
1112
use Activitypub\Collection\Remote_Actors;
1213
use Activitypub\Tombstone;
1314

15+
use function Activitypub\is_activity_reply;
1416
use function Activitypub\object_to_uri;
1517

1618
/**
@@ -23,7 +25,8 @@ class Delete {
2325
public static function init() {
2426
\add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ), 10, 2 );
2527
\add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 2 );
26-
\add_action( 'activitypub_delete_actor_interactions', array( self::class, 'delete_interactions' ) );
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' ) );
2730

2831
\add_filter( 'activitypub_get_outbox_activity', array( self::class, 'outbox_activity' ) );
2932
\add_action( 'post_activitypub_add_to_outbox', array( self::class, 'post_add_to_outbox' ), 10, 2 );
@@ -37,8 +40,6 @@ public static function init() {
3740
*/
3841
public static function handle_delete( $activity, $user_id ) {
3942
$object_type = $activity['object']['type'] ?? '';
40-
$success = false;
41-
$result = null;
4243

4344
switch ( $object_type ) {
4445
/*
@@ -51,7 +52,7 @@ public static function handle_delete( $activity, $user_id ) {
5152
case 'Organization':
5253
case 'Service':
5354
case 'Application':
54-
$result = self::maybe_delete_follower( $activity );
55+
self::delete_remote_actor( $activity, $user_id );
5556
break;
5657

5758
/*
@@ -66,7 +67,7 @@ public static function handle_delete( $activity, $user_id ) {
6667
case 'Video':
6768
case 'Event':
6869
case 'Document':
69-
$result = self::maybe_delete_interaction( $activity );
70+
self::delete_object( $activity, $user_id );
7071
break;
7172

7273
/*
@@ -75,7 +76,7 @@ public static function handle_delete( $activity, $user_id ) {
7576
* @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
7677
*/
7778
case 'Tombstone':
78-
$result = self::maybe_delete_interaction( $activity );
79+
self::delete_object( $activity, $user_id );
7980
break;
8081

8182
/*
@@ -84,32 +85,65 @@ public static function handle_delete( $activity, $user_id ) {
8485
* @see https://www.w3.org/TR/activitystreams-core/#example-1
8586
*/
8687
default:
87-
// Ignore non Minimal Activities.
88-
if ( ! is_string( $activity['object'] ) ) {
89-
return;
90-
}
91-
9288
// Check if Object is an Actor.
93-
if ( $activity['actor'] === $activity['object'] ) {
94-
$result = self::maybe_delete_follower( $activity );
95-
} else { // Assume an interaction otherwise.
96-
$result = self::maybe_delete_interaction( $activity );
89+
if ( object_to_uri( $activity['object'] ) === $activity['actor'] ) {
90+
self::delete_remote_actor( $activity, $user_id );
91+
} else { // Assume an object otherwise.
92+
self::delete_object( $activity, $user_id );
9793
}
9894
// Maybe handle Delete Activity for other Object Types.
9995
break;
10096
}
97+
}
98+
99+
/**
100+
* Delete an Object.
101+
*
102+
* @param array $activity The Activity object.
103+
* @param int $user_id The user ID.
104+
*/
105+
public static function delete_object( $activity, $user_id ) {
106+
// Check for private and/or direct messages.
107+
if ( is_activity_reply( $activity ) ) {
108+
$result = self::maybe_delete_interaction( $activity );
109+
} else {
110+
$result = self::maybe_delete_post( $activity );
111+
}
112+
113+
$success = ( $result && ! \is_wp_error( $result ) );
114+
115+
/**
116+
* Fires after an ActivityPub Delete activity has been handled.
117+
*
118+
* @param array $activity The ActivityPub activity data.
119+
* @param int $user_id The local user ID.
120+
* @param bool $success True on success, false otherwise.
121+
* @param mixed|null $result The result of the delete operation.
122+
*/
123+
\do_action( 'activitypub_handled_delete', $activity, $user_id, $success, $result );
124+
}
101125

102-
$success = (bool) $result;
126+
/**
127+
* Delete an Actor.
128+
*
129+
* @param array $activity The Activity object.
130+
* @param int $user_id The user ID.
131+
*/
132+
public static function delete_remote_actor( $activity, $user_id ) {
133+
$result = self::maybe_delete_follower( $activity );
134+
$success = ( $result && ! \is_wp_error( $result ) );
103135

104136
/**
105137
* Fires after an ActivityPub Delete activity has been handled.
106138
*
107139
* @param array $activity The ActivityPub activity data.
108140
* @param int $user_id The local user ID.
109141
* @param bool $success True on success, false otherwise.
110-
* @param mixed|null $result The result of the delete operation (e.g., WP_Comment object or deletion status).
142+
* @param mixed|null $result The result of the delete operation.
111143
*/
112144
\do_action( 'activitypub_handled_delete', $activity, $user_id, $success, $result );
145+
146+
return $result;
113147
}
114148

115149
/**
@@ -126,6 +160,7 @@ public static function maybe_delete_follower( $activity ) {
126160
if ( ! is_wp_error( $follower ) && Tombstone::exists( $activity['actor'] ) ) {
127161
$state = Remote_Actors::delete( $follower->ID );
128162
self::maybe_delete_interactions( $activity );
163+
self::maybe_delete_posts( $activity );
129164
}
130165

131166
return $state ?? false;
@@ -143,7 +178,29 @@ public static function maybe_delete_interactions( $activity ) {
143178
if ( Tombstone::exists( $activity['actor'] ) ) {
144179
\wp_schedule_single_event(
145180
\time(),
146-
'activitypub_delete_actor_interactions',
181+
'activitypub_delete_remote_actor_interactions',
182+
array( $activity['actor'] )
183+
);
184+
185+
return true;
186+
}
187+
188+
return false;
189+
}
190+
191+
/**
192+
* Delete Reactions if Actor-URL is a Tombstone.
193+
*
194+
* @param array $activity The delete activity.
195+
*
196+
* @return bool True on success, false otherwise.
197+
*/
198+
public static function maybe_delete_posts( $activity ) {
199+
// Verify that Actor is deleted.
200+
if ( Tombstone::exists( $activity['actor'] ) ) {
201+
\wp_schedule_single_event(
202+
\time(),
203+
'activitypub_delete_remote_actor_posts',
147204
array( $activity['actor'] )
148205
);
149206

@@ -164,7 +221,7 @@ public static function delete_interactions( $actor ) {
164221
$comments = Interactions::get_by_actor( $actor );
165222

166223
foreach ( $comments as $comment ) {
167-
wp_delete_comment( $comment, true );
224+
\wp_delete_comment( $comment, true );
168225
}
169226

170227
if ( $comments ) {
@@ -174,6 +231,27 @@ public static function delete_interactions( $actor ) {
174231
}
175232
}
176233

234+
/**
235+
* Delete comments from an Actor.
236+
*
237+
* @param string $actor The URL of the actor whose comments to delete.
238+
*
239+
* @return bool True on success, false otherwise.
240+
*/
241+
public static function delete_posts( $actor ) {
242+
$posts = Posts::get_by_remote_actor( $actor );
243+
244+
foreach ( $posts as $post ) {
245+
Posts::delete( $post->ID );
246+
}
247+
248+
if ( $posts ) {
249+
return true;
250+
} else {
251+
return false;
252+
}
253+
}
254+
177255
/**
178256
* Delete a Reaction if URL is a Tombstone.
179257
*
@@ -201,6 +279,24 @@ public static function maybe_delete_interaction( $activity ) {
201279
return false;
202280
}
203281

282+
/**
283+
* Delete a post from the Posts collection.
284+
*
285+
* @param array $activity The delete activity.
286+
*
287+
* @return bool|\WP_Error True on success, false or WP_Error on failure.
288+
*/
289+
public static function maybe_delete_post( $activity ) {
290+
$id = object_to_uri( $activity['object'] );
291+
292+
// Check if the object exists and is a tombstone.
293+
if ( Tombstone::exists( $id ) ) {
294+
return Posts::delete_by_guid( $id );
295+
}
296+
297+
return false;
298+
}
299+
204300
/**
205301
* Defer signature verification for `Delete` requests.
206302
*

tests/phpunit/tests/includes/class-test-scheduler.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ function () {
700700
\add_filter(
701701
'schedule_event',
702702
function ( $event ) use ( &$scheduled_events ) {
703-
if ( 'activitypub_delete_actor_interactions' === $event->hook ) {
703+
if ( 'activitypub_delete_remote_actor_interactions' === $event->hook ) {
704704
$scheduled_events[] = array(
705705
'hook' => $event->hook,
706706
'args' => $event->args,
@@ -722,7 +722,7 @@ function () {
722722

723723
// Verify that the event was scheduled with the actor URL as parameter.
724724
$this->assertCount( 1, $scheduled_events, 'Should schedule 1 event' );
725-
$this->assertEquals( 'activitypub_delete_actor_interactions', $scheduled_events[0]['hook'], 'Should schedule the correct hook' );
725+
$this->assertEquals( 'activitypub_delete_remote_actor_interactions', $scheduled_events[0]['hook'], 'Should schedule the correct hook' );
726726
$this->assertCount( 1, $scheduled_events[0]['args'], 'Should have 1 argument' );
727727
$this->assertEquals( 'https://example.com/users/test', $scheduled_events[0]['args'][0], 'Should pass actor URL as parameter' );
728728

0 commit comments

Comments
 (0)