Skip to content

Commit 1570996

Browse files
authored
Handle quote comment deletion with Reject activity (#2460)
1 parent addd717 commit 1570996

File tree

6 files changed

+397
-5
lines changed

6 files changed

+397
-5
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
Send a Reject activity when a quote comment is deleted, revoking previous quote permissions and ensuring consistent inbox handling.

includes/activity/class-activity.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,14 @@ public function set_object( $data ) {
206206
public function pre_fill_activity_from_object() {
207207
$object = $this->get_object();
208208

209-
// Check if `$data` is a URL and use it to generate an ID then.
209+
// Check if `$object` is a URL and use it to generate an ID then.
210210
if ( is_string( $object ) && filter_var( $object, FILTER_VALIDATE_URL ) && ! $this->get_id() ) {
211211
$this->set( 'id', $object . '#activity-' . strtolower( $this->get_type() ) . '-' . time() );
212212

213213
return;
214214
}
215215

216-
// Check if `$data` is an object and copy some properties otherwise do nothing.
216+
// Check if `$object` is an object and copy some properties otherwise do nothing.
217217
if ( ! is_object( $object ) ) {
218218
return;
219219
}

includes/class-post-types.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ public static function register_inbox_post_type() {
144144
'single' => true,
145145
'show_in_rest' => true,
146146
'sanitize_callback' => function ( $value ) {
147-
$value = ucfirst( strtolower( $value ) );
148147
$schema = array(
149148
'type' => 'string',
150149
'enum' => Activity::TYPES,
@@ -248,7 +247,6 @@ public static function register_outbox_post_type() {
248247
'single' => true,
249248
'show_in_rest' => true,
250249
'sanitize_callback' => function ( $value ) {
251-
$value = ucfirst( strtolower( $value ) );
252250
$schema = array(
253251
'type' => 'string',
254252
'enum' => Activity::TYPES,

includes/collection/class-inbox.php

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ public static function add( $activity, $recipients ) {
8383
$title = self::get_object_title( $activity->get_object() );
8484
$visibility = is_activity_public( $activity ) ? ACTIVITYPUB_CONTENT_VISIBILITY_PUBLIC : ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE;
8585

86+
/*
87+
* For QuoteRequest activities, we store the instrument URL as the object_id.
88+
* This allows efficient querying by instrument (the quote post URL).
89+
* For all other activities, we store the object URL as before.
90+
*/
91+
if ( 'QuoteRequest' === $activity->get_type() && $activity->get_instrument() ) {
92+
$object_id = object_to_uri( $activity->get_instrument() );
93+
} else {
94+
$object_id = object_to_uri( $activity->get_object() );
95+
}
96+
8697
$inbox_item = array(
8798
'post_type' => self::POST_TYPE,
8899
'post_title' => sprintf(
@@ -96,7 +107,7 @@ public static function add( $activity, $recipients ) {
96107
'post_status' => 'publish',
97108
'guid' => $activity->get_id(),
98109
'meta_input' => array(
99-
'_activitypub_object_id' => object_to_uri( $activity->get_object() ),
110+
'_activitypub_object_id' => $object_id,
100111
'_activitypub_activity_type' => $activity->get_type(),
101112
'_activitypub_activity_remote_actor' => object_to_uri( $activity->get_actor() ),
102113
'activitypub_content_visibility' => $visibility,
@@ -369,6 +380,51 @@ public static function get_by_guid_and_recipient( $guid, $user_id ) {
369380
return $post;
370381
}
371382

383+
/**
384+
* Get an inbox item by activity type and object ID.
385+
*
386+
* This is useful for finding specific activity types (like QuoteRequest)
387+
* by their object identifier. For QuoteRequest activities, the object_id
388+
* is the instrument URL (the quote post).
389+
*
390+
* @param string $activity_type The activity type (e.g., 'QuoteRequest').
391+
* @param string $object_id The object identifier to search for.
392+
*
393+
* @return \WP_Post|\WP_Error The inbox item or WP_Error if not found.
394+
*/
395+
public static function get_by_type_and_object( $activity_type, $object_id ) {
396+
$posts = \get_posts(
397+
array(
398+
'post_type' => self::POST_TYPE,
399+
'posts_per_page' => 1,
400+
'orderby' => 'ID',
401+
'order' => 'DESC',
402+
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Necessary for querying by activity type and object ID.
403+
'meta_query' => array(
404+
'relation' => 'AND',
405+
array(
406+
'key' => '_activitypub_activity_type',
407+
'value' => $activity_type,
408+
),
409+
array(
410+
'key' => '_activitypub_object_id',
411+
'value' => $object_id,
412+
),
413+
),
414+
)
415+
);
416+
417+
if ( empty( $posts ) ) {
418+
return new \WP_Error(
419+
'activitypub_inbox_item_not_found',
420+
\__( 'Inbox item not found', 'activitypub' ),
421+
array( 'status' => 404 )
422+
);
423+
}
424+
425+
return $posts[0];
426+
}
427+
372428
/**
373429
* Deduplicate inbox items with the same GUID.
374430
*

includes/handler/class-quote-request.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Activitypub\Activity\Activity;
1111
use Activitypub\Collection\Actors;
1212
use Activitypub\Collection\Followers;
13+
use Activitypub\Collection\Inbox;
1314
use Activitypub\Collection\Remote_Actors;
1415

1516
use function Activitypub\add_to_outbox;
@@ -27,6 +28,7 @@ class Quote_Request {
2728
public static function init() {
2829
\add_action( 'activitypub_inbox_quote_request', array( self::class, 'handle_quote_request' ), 10, 2 );
2930
\add_action( 'activitypub_rest_inbox_disallowed', array( self::class, 'handle_blocked_request' ), 10, 3 );
31+
\add_action( 'delete_comment', array( self::class, 'handle_quote_delete' ), 10, 2 );
3032

3133
\add_filter( 'activitypub_validate_object', array( self::class, 'validate_object' ), 10, 3 );
3234
}
@@ -100,6 +102,86 @@ public static function handle_blocked_request( $activity, $user_ids, $type ) {
100102
self::queue_reject( $activity, $user_id );
101103
}
102104

105+
/**
106+
* Handle deletion of a quote comment.
107+
*
108+
* When a local quote comment is deleted, send a Reject activity to revoke
109+
* the previously accepted QuoteRequest.
110+
*
111+
* @param int $comment_id The comment ID being deleted.
112+
* @param \WP_Comment $comment The comment object.
113+
*/
114+
public static function handle_quote_delete( $comment_id, $comment ) {
115+
// Only handle quote comments.
116+
if ( 'quote' !== $comment->comment_type ) {
117+
return;
118+
}
119+
120+
// Get the post being quoted.
121+
$post_id = $comment->comment_post_ID;
122+
if ( ! $post_id ) {
123+
return;
124+
}
125+
126+
// Get the instrument URL (the quote post URL) from comment meta.
127+
$instrument_url = \get_comment_meta( $comment_id, 'source_url', true );
128+
if ( ! $instrument_url ) {
129+
$instrument_url = \get_comment_meta( $comment_id, 'source_id', true );
130+
}
131+
132+
if ( ! $instrument_url ) {
133+
return;
134+
}
135+
136+
// Get the post author (who accepted the quote).
137+
$post = \get_post( $post_id );
138+
if ( ! $post || ! $post->post_author ) {
139+
return;
140+
}
141+
142+
/*
143+
* Try to retrieve the original QuoteRequest from the inbox.
144+
* For QuoteRequest activities, the inbox stores the instrument URL
145+
* in _activitypub_object_id, so we can query by that.
146+
*/
147+
$activity_object = null;
148+
$inbox_item = Inbox::get_by_type_and_object( 'QuoteRequest', $instrument_url );
149+
150+
if ( $inbox_item instanceof \WP_Post ) {
151+
$activity_object = \json_decode( $inbox_item->post_content, true );
152+
if ( JSON_ERROR_NONE !== \json_last_error() ) {
153+
$activity_object = null;
154+
}
155+
}
156+
157+
// Fallback: If inbox item not found, reconstruct from available data.
158+
if ( ! $activity_object ) {
159+
$activity_object = array(
160+
'type' => 'QuoteRequest',
161+
'actor' => $comment->comment_author_url,
162+
'object' => \get_permalink( $post_id ),
163+
'instrument' => $instrument_url,
164+
'published' => \gmdate( 'c' ),
165+
);
166+
}
167+
168+
// Remove from _activitypub_quoted_by meta.
169+
\delete_post_meta( $post_id, '_activitypub_quoted_by', $instrument_url );
170+
171+
// Send Reject activity to revoke the quote permission.
172+
self::queue_reject( $activity_object, $post->post_author );
173+
174+
/**
175+
* Fires after a quote comment has been deleted and Reject activity sent.
176+
*
177+
* @param int $comment_id The deleted comment ID.
178+
* @param int $post_id The post ID that was quoted.
179+
* @param string $instrument_url The instrument URL (quote post).
180+
* @param array $activity_object The QuoteRequest activity that was rejected.
181+
*/
182+
\do_action( 'activitypub_quote_comment_deleted', $comment_id, $post_id, $instrument_url, $activity_object );
183+
}
184+
103185
/**
104186
* Send an Accept activity in response to the QuoteRequest.
105187
*

0 commit comments

Comments
 (0)