PluginProbe
ActivityPub / 3.2.4
ActivityPub v3.2.4
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/collection/class-interactions.php +116 -342 8.2.1 → 3.2.4 View file →
@@ -1,27 +1,18 @@
1 1 <?php
2 -/**
3 - * Interactions collection file.
4 - *
5 - * @package Activitypub
6 - */
7 -
8 2 namespace Activitypub\Collection;
9 3
4 +use WP_Error;
5 +use WP_Comment_Query;
10 6 use Activitypub\Comment;
11 -use Activitypub\Emoji;
12 -use Activitypub\Webfinger;
13 -use WP_Comment_Query;
14 7
15 -use function Activitypub\get_remote_metadata_by_actor;
16 -use function Activitypub\is_ap_post;
17 -use function Activitypub\is_post_disabled;
18 -use function Activitypub\object_id_to_comment;
19 8 use function Activitypub\object_to_uri;
20 9 use function Activitypub\url_to_commentid;
10 +use function Activitypub\object_id_to_comment;
11 +use function Activitypub\get_remote_metadata_by_actor;
21 12
22 13 /**
23 - * ActivityPub Interactions Collection.
14 + * ActivityPub Interactions Collection
24 15 */
25 16 class Interactions {
26 17 const INSERT = 'insert';
27 18 const UPDATE = 'update';
@@ -26,136 +17,87 @@
26 17 const INSERT = 'insert';
27 18 const UPDATE = 'update';
28 19
29 20 /**
30 - * Add a comment to a post.
21 + * Add a comment to a post
31 22 *
32 - * When $user_id is provided, comment author data is built from the
33 - * local WordPress user instead of fetching remote actor metadata.
23 + * @param array $activity The activity-object
34 24 *
35 - * @param array $activity The activity-object.
36 - * @param int|null $user_id Optional. Local user ID for outbox replies.
37 - *
38 - * @return int|false|\WP_Error The comment ID or false or WP_Error on failure.
25 + * @return array|false The commentdata or false on failure
39 26 */
40 - public static function add_comment( $activity, $user_id = null ) {
41 - $comment_data = self::activity_to_comment( $activity, $user_id );
27 + public static function add_comment( $activity ) {
28 + $commentdata = self::activity_to_comment( $activity );
42 29
43 - if ( ! $comment_data ) {
30 + if ( ! $commentdata || ! isset( $activity['object']['inReplyTo'] ) ) {
44 31 return false;
45 32 }
46 33
47 - // Determine target URL from reply or quote.
48 - $parent_comment_id = 0;
34 + $in_reply_to = \esc_url_raw( $activity['object']['inReplyTo'] );
35 + $comment_post_id = \url_to_postid( $in_reply_to );
36 + $parent_comment_id = url_to_commentid( $in_reply_to );
49 37
50 - if ( ! empty( $activity['object']['inReplyTo'] ) ) {
51 - // Regular reply.
52 - $target_url = object_to_uri( $activity['object']['inReplyTo'] );
53 - $parent_comment_id = url_to_commentid( $target_url );
54 - } else {
55 - // Check for quote.
56 - $target_url = self::get_quote_url( $activity );
57 -
58 - if ( ! $target_url ) {
59 - return false;
60 - }
61 -
62 - // Mark as quote and clean content.
63 - $comment_data['comment_type'] = 'quote';
64 -
65 - if ( ! empty( $activity['object']['content'] ) ) {
66 - $pattern = '/<p[^>]*class=["\']quote-inline["\'][^>]*>.*?<\/p>/is';
67 - $cleaned_content = \preg_replace( $pattern, '', $activity['object']['content'], 1 );
68 - $comment_data['comment_content'] = \wp_kses_post( $cleaned_content );
69 - }
70 - }
71 -
72 - // Get post ID from target URL.
73 - $target_url = \esc_url_raw( $target_url );
74 - $comment_post_id = \url_to_postid( $target_url );
75 -
76 - if ( ! $comment_post_id ) {
77 - // Check for `ap_post`.
78 - $comment_post = Remote_Posts::get_by_guid( $target_url );
79 - if ( $comment_post instanceof \WP_Post ) {
80 - $comment_post_id = $comment_post->ID;
81 - }
82 - }
83 -
84 - // Handle nested replies (replies to comments).
38 + // save only replys and reactions
85 39 if ( ! $comment_post_id && $parent_comment_id ) {
86 - $parent_comment = \get_comment( $parent_comment_id );
40 + $parent_comment = get_comment( $parent_comment_id );
87 41 $comment_post_id = $parent_comment->comment_post_ID;
88 42 }
89 43
44 + // not a reply to a post or comment
90 45 if ( ! $comment_post_id ) {
91 - // Not a reply to a post or comment.
92 46 return false;
93 47 }
94 48
95 - $comment_data['comment_post_ID'] = $comment_post_id;
96 - $comment_data['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
49 + $commentdata['comment_post_ID'] = $comment_post_id;
50 + $commentdata['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
97 51
98 - return self::persist( $comment_data );
52 + return self::persist( $commentdata, self::INSERT );
99 53 }
100 54
101 55 /**
102 - * Update a comment.
56 + * Update a comment
103 57 *
104 - * @param array $activity The activity object.
58 + * @param array $activity The activity-object
105 59 *
106 - * @return array|string|int|\WP_Error|false The comment data or false on failure.
60 + * @return array|string|int|\WP_Error|false The commentdata or false on failure
107 61 */
108 62 public static function update_comment( $activity ) {
109 63 $meta = get_remote_metadata_by_actor( $activity['actor'] );
110 64
111 - if ( \is_wp_error( $meta ) || ! \is_array( $meta ) ) {
112 - return $meta;
113 - }
65 + //Determine comment_ID
66 + $comment = object_id_to_comment( \esc_url_raw( $activity['object']['id'] ) );
67 + $commentdata = \get_comment( $comment, ARRAY_A );
114 68
115 - // Determine comment_ID.
116 - $comment = object_id_to_comment( \esc_url_raw( $activity['object']['id'] ) );
117 - $comment_data = \get_comment( $comment, ARRAY_A );
118 -
119 - if ( ! $comment_data ) {
69 + if ( ! $commentdata ) {
120 70 return false;
121 71 }
122 72
123 - // Found a local comment id.
124 - $comment_data['comment_author'] = \sanitize_text_field( empty( $meta['name'] ) ? $meta['preferredUsername'] : $meta['name'] );
73 + //found a local comment id
74 + $commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
75 + $commentdata['comment_content'] = \addslashes( $activity['object']['content'] );
125 76
126 - /*
127 - * Wrap emoji in content with blocks for runtime replacement.
128 - * Note: Remote images in comments are stripped for security (only emoji allowed).
129 - */
130 - $content = Emoji::wrap_in_content( $activity['object']['content'], $activity['object'] );
131 - $comment_data['comment_content'] = \addslashes( $content );
132 -
133 - return self::persist( $comment_data, self::UPDATE );
77 + return self::persist( $commentdata, self::UPDATE );
134 78 }
135 79
136 80 /**
137 81 * Adds an incoming Like, Announce, ... as a comment to a post.
138 82 *
139 - * @param array $activity Activity array.
83 + * @param array $activity Activity array.
140 84 *
141 - * @return array|string|int|\WP_Error|false Comment data or `false` on failure.
85 + * @return array|false Comment data or `false` on failure.
142 86 */
143 87 public static function add_reaction( $activity ) {
88 + $commentdata = self::activity_to_comment( $activity );
89 +
90 + if ( ! $commentdata ) {
91 + return false;
92 + }
93 +
144 94 $url = object_to_uri( $activity['object'] );
145 - $comment_post_id = \url_to_postid( $url );
95 + $comment_post_id = url_to_postid( $url );
146 96 $parent_comment_id = url_to_commentid( $url );
147 97
148 - if ( ! $comment_post_id ) {
149 - // Check for `ap_post`.
150 - $comment_post = Remote_Posts::get_by_guid( $url );
151 - if ( $comment_post instanceof \WP_Post ) {
152 - $comment_post_id = $comment_post->ID;
153 - }
154 - }
155 -
156 98 if ( ! $comment_post_id && $parent_comment_id ) {
157 - $parent_comment = \get_comment( $parent_comment_id );
99 + $parent_comment = get_comment( $parent_comment_id );
158 100 $comment_post_id = $parent_comment->comment_post_ID;
159 101 }
160 102
161 103 if ( ! $comment_post_id ) {
@@ -162,36 +104,34 @@
162 104 // Not a reply to a post or comment.
163 105 return false;
164 106 }
165 107
166 - $comment_type = Comment::get_comment_type_by_activity_type( $activity['type'] );
167 - if ( ! $comment_type ) {
108 + $type = $activity['type'];
109 +
110 + if ( ! Comment::is_registered_comment_type( $type ) ) {
168 111 // Not a valid comment type.
169 112 return false;
170 113 }
171 114
172 - $comment_data = self::activity_to_comment( $activity );
173 - if ( ! $comment_data ) {
174 - return false;
175 - }
115 + $comment_type = Comment::get_comment_type( $type );
116 + $comment_content = $comment_type['excerpt'];
176 117
177 - $comment_data['comment_post_ID'] = $comment_post_id;
178 - $comment_data['comment_parent'] = $parent_comment_id ? $parent_comment_id : 0;
179 - $comment_data['comment_content'] = \esc_html( $comment_type['excerpt'] );
180 - $comment_data['comment_type'] = \esc_attr( $comment_type['type'] );
181 - $comment_data['comment_meta']['source_id'] = \esc_url_raw( $activity['id'] );
118 + $commentdata['comment_post_ID'] = $comment_post_id;
119 + $commentdata['comment_content'] = \esc_html( $comment_content );
120 + $commentdata['comment_type'] = \esc_attr( $comment_type['type'] );
121 + $commentdata['comment_meta']['source_id'] = \esc_url_raw( $activity['id'] );
182 122
183 - return self::persist( $comment_data );
123 + return self::persist( $commentdata, self::INSERT );
184 124 }
185 125
186 126 /**
187 - * Get interaction(s) by ID.
127 + * Get interaction(s) for a given URL/ID.
188 128 *
189 - * @param string $url The URL/ID to get interactions for.
129 + * @param strin $url The URL/ID to get interactions for.
190 130 *
191 131 * @return array The interactions as WP_Comment objects.
192 132 */
193 - public static function get_by_id( $url ) {
133 + public static function get_interaction_by_id( $url ) {
194 134 $args = array(
195 135 'nopaging' => true,
196 136 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
197 137 'meta_query' => array(
@@ -218,33 +158,18 @@
218 158 return $query->comments;
219 159 }
220 160
221 161 /**
222 - * Get interaction(s) for a given URL/ID.
162 + * Get interaction(s) for a given actor.
223 163 *
224 - * @deprecated 7.6.0 Use {@see Interactions::get_by_id()}.
225 - *
226 - * @param string $url The URL/ID to get interactions for.
227 - *
228 - * @return array The interactions as WP_Comment objects.
229 - */
230 - public static function get_interaction_by_id( $url ) {
231 - \_deprecated_function( __METHOD__, '7.6.0', 'Activitypub\Collection\Interactions::get_by_id' );
232 -
233 - return self::get_by_id( $url );
234 - }
235 -
236 - /**
237 - * Get interaction(s) by actor.
238 - *
239 164 * @param string $actor The Actor-URL.
240 165 *
241 166 * @return array The interactions as WP_Comment objects.
242 167 */
243 - public static function get_by_actor( $actor ) {
168 + public static function get_interactions_by_actor( $actor ) {
244 169 $meta = get_remote_metadata_by_actor( $actor );
245 170
246 - // Get URL, because $actor seems to be the ID.
171 + // get URL, because $actor seems to be the ID
247 172 if ( $meta && ! is_wp_error( $meta ) && isset( $meta['url'] ) ) {
248 173 $actor = object_to_uri( $meta['url'] );
249 174 }
250 175
@@ -255,65 +180,21 @@
255 180 'meta_query' => array(
256 181 array(
257 182 'key' => 'protocol',
258 183 'value' => 'activitypub',
184 + 'compare' => '=',
259 185 ),
260 186 ),
261 187 );
262 -
263 - return \get_comments( $args );
188 + $comment_query = new WP_Comment_Query( $args );
189 + return $comment_query->comments;
264 190 }
265 191
266 192 /**
267 - * Get interaction(s) by remote actor ID.
268 - *
269 - * This is an optimized query that uses the remote actor post ID directly
270 - * instead of querying by author_url.
271 - *
272 - * @param int $remote_actor_id The remote actor post ID.
273 - *
274 - * @return array The interactions as WP_Comment objects.
275 - */
276 - public static function get_by_remote_actor_id( $remote_actor_id ) {
277 - $args = array(
278 - 'nopaging' => true,
279 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
280 - 'meta_query' => array(
281 - 'relation' => 'AND',
282 - array(
283 - 'key' => 'protocol',
284 - 'value' => 'activitypub',
285 - ),
286 - array(
287 - 'key' => '_activitypub_remote_actor_id',
288 - 'value' => $remote_actor_id,
289 - ),
290 - ),
291 - );
292 -
293 - return \get_comments( $args );
294 - }
295 -
296 - /**
297 - * Get interaction(s) for a given actor.
298 - *
299 - * @deprecated 7.6.0 Use {@see Interactions::get_by_actor()}.
300 - *
301 - * @param string $actor The Actor-URL.
302 - *
303 - * @return array The interactions as WP_Comment objects.
304 - */
305 - public static function get_interactions_by_actor( $actor ) {
306 - \_deprecated_function( __METHOD__, '7.6.0', 'Activitypub\Collection\Interactions::get_by_actor' );
307 -
308 - return self::get_by_actor( $actor );
309 - }
310 -
311 - /**
312 193 * Adds line breaks to the list of allowed comment tags.
313 194 *
314 195 * @param array $allowed_tags Allowed HTML tags.
315 - * @param string $context Optional. Context. Default empty.
196 + * @param string $context Context.
316 197 *
317 198 * @return array Filtered tag list.
318 199 */
319 200 public static function allowed_comment_html( $allowed_tags, $context = '' ) {
@@ -330,144 +211,87 @@
330 211 if ( ! array_key_exists( 'p', $allowed_tags ) ) {
331 212 $allowed_tags['p'] = array();
332 213 }
333 214
334 - // Add `img` for custom emoji support with strict validation.
335 - $emoji_html = Emoji::get_kses_allowed_html();
336 - if ( ! array_key_exists( 'img', $allowed_tags ) ) {
337 - $allowed_tags['img'] = $emoji_html['img'];
338 - }
339 -
340 215 return $allowed_tags;
341 216 }
342 217
343 218 /**
344 - * Convert an Activity to a WP_Comment.
219 + * Convert an Activity to a WP_Comment
345 220 *
346 - * When $user_id is provided, comment author data is built from the
347 - * local WordPress user instead of fetching remote actor metadata.
221 + * @param array $activity The Activity array
348 222 *
349 - * @param array $activity The Activity array.
350 - * @param int|null $user_id Optional. Local user ID for outbox comments.
351 - *
352 - * @return array|false The comment data or false on failure.
223 + * @return array|false The commentdata or false on failure
353 224 */
354 - public static function activity_to_comment( $activity, $user_id = null ) {
225 + public static function activity_to_comment( $activity ) {
355 226 $comment_content = null;
227 + $actor = object_to_uri( $activity['actor'] );
228 + $actor = get_remote_metadata_by_actor( $actor );
356 229
357 - if ( $user_id ) {
358 - // Outbox: resolve author from the local WordPress user.
359 - $user = \get_userdata( $user_id );
230 + // check Actor-Meta
231 + if ( ! $actor || is_wp_error( $actor ) ) {
232 + return false;
233 + }
360 234
361 - if ( ! $user ) {
362 - return false;
363 - }
364 -
365 - $comment_author = $user->display_name;
366 - $comment_author_url = $user->user_url;
367 - $comment_author_email = $user->user_email;
368 - $comment_content = \wp_kses_post( $activity['object']['content'] ?? '' );
235 + // check Actor-Name
236 + if ( isset( $actor['name'] ) ) {
237 + $comment_author = $actor['name'];
238 + } elseif ( isset( $actor['preferredUsername'] ) ) {
239 + $comment_author = $actor['preferredUsername'];
369 240 } else {
370 - // S2S: resolve author from remote actor metadata.
371 - $actor = object_to_uri( $activity['actor'] ?? null );
372 - $actor = get_remote_metadata_by_actor( $actor );
241 + return false;
242 + }
373 243
374 - if ( ! $actor || is_wp_error( $actor ) ) {
375 - return false;
376 - }
244 + $url = object_to_uri( $actor['url'] );
377 245
378 - $comment_author = null;
379 - if ( ! empty( $actor['name'] ) ) {
380 - $comment_author = $actor['name'];
381 - } elseif ( ! empty( $actor['preferredUsername'] ) ) {
382 - $comment_author = $actor['preferredUsername'];
383 - }
246 + if ( ! $url ) {
247 + object_to_uri( $actor['id'] );
248 + }
384 249
385 - if ( empty( $comment_author ) && \get_option( 'require_name_email' ) ) {
386 - return false;
387 - }
388 -
389 - $comment_author = $comment_author ?? __( 'Anonymous', 'activitypub' );
390 - $comment_author_url = \esc_url_raw( object_to_uri( $actor['url'] ?? $actor['id'] ) );
391 -
392 - $webfinger = Webfinger::uri_to_acct( $comment_author_url );
393 - if ( is_wp_error( $webfinger ) ) {
394 - $comment_author_email = '';
395 - } else {
396 - $comment_author_email = str_replace( 'acct:', '', $webfinger );
397 - }
398 -
399 - if ( isset( $activity['object']['content'] ) ) {
400 - /*
401 - * Wrap emoji in content with blocks for runtime replacement.
402 - * Note: Remote images in comments are stripped for security (only emoji allowed).
403 - */
404 - $content = Emoji::wrap_in_content( $activity['object']['content'], $activity['object'] );
405 - $comment_content = \addslashes( $content );
406 - }
250 + if ( isset( $activity['object']['content'] ) ) {
251 + $comment_content = \addslashes( $activity['object']['content'] );
407 252 }
408 253
409 - $published = $activity['object']['published'] ?? $activity['published'] ?? 'now';
410 - $gm_date = \gmdate( 'Y-m-d H:i:s', \strtotime( $published ) );
411 -
412 - $comment_data = array(
413 - 'comment_author' => $comment_author,
414 - 'comment_author_url' => $comment_author_url,
415 - 'comment_content' => $comment_content,
416 - 'comment_type' => 'comment',
417 - 'comment_author_email' => $comment_author_email,
418 - 'comment_date' => \get_date_from_gmt( $gm_date ),
419 - 'comment_date_gmt' => $gm_date,
420 - 'comment_meta' => array(),
254 + $commentdata = array(
255 + 'comment_author' => \esc_attr( $comment_author ),
256 + 'comment_author_url' => \esc_url_raw( $url ),
257 + 'comment_content' => $comment_content,
258 + 'comment_type' => 'comment',
259 + 'comment_author_email' => '',
260 + 'comment_meta' => array(
261 + 'source_id' => \esc_url_raw( object_to_uri( $activity['object'] ) ),
262 + 'protocol' => 'activitypub',
263 + ),
421 264 );
422 265
423 - if ( $user_id ) {
424 - $comment_data['user_id'] = $user_id;
425 - } else {
426 - $comment_data['comment_meta']['protocol'] = 'activitypub';
427 - $comment_data['comment_meta']['source_id'] = \esc_url_raw( object_to_uri( $activity['object'] ) );
266 + if ( isset( $actor['icon']['url'] ) ) {
267 + $commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $actor['icon']['url'] );
268 + }
428 269
429 - // Store reference to remote actor post.
430 - $actor_uri = object_to_uri( $activity['actor'] ?? null );
431 - if ( $actor_uri ) {
432 - $remote_actor = Remote_Actors::get_by_uri( $actor_uri );
433 - if ( ! \is_wp_error( $remote_actor ) ) {
434 - $comment_data['comment_meta']['_activitypub_remote_actor_id'] = $remote_actor->ID;
435 - }
436 - }
437 -
438 - if ( isset( $activity['object']['url'] ) ) {
439 - $comment_data['comment_meta']['source_url'] = \esc_url_raw( object_to_uri( $activity['object']['url'] ) );
440 - }
270 + if ( isset( $activity['object']['url'] ) ) {
271 + $commentdata['comment_meta']['source_url'] = \esc_url_raw( object_to_uri( $activity['object']['url'] ) );
441 272 }
442 273
443 - return $comment_data;
274 + return $commentdata;
444 275 }
445 276
446 277 /**
447 - * Persist a comment.
278 + * Persist a comment
448 279 *
449 - * @param array $comment_data The comment data array.
450 - * @param string $action Optional. Either 'insert' or 'update'. Default 'insert'.
280 + * @param array $commentdata The commentdata array
281 + * @param string $action Either 'insert' or 'update'
451 282 *
452 - * @return array|string|int|\WP_Error|false The comment data or false on failure
283 + * @return array|string|int|\WP_Error|false The commentdata or false on failure
453 284 */
454 - public static function persist( $comment_data, $action = self::INSERT ) {
455 - if (
456 - is_post_disabled( $comment_data['comment_post_ID'] ) &&
457 - ! is_ap_post( $comment_data['comment_post_ID'] )
458 - ) {
459 - return false;
460 - }
461 -
462 - // Disable flood control.
463 - \remove_action( 'check_comment_flood', 'check_comment_flood_db' );
464 - // Do not require email for AP entries.
285 + public static function persist( $commentdata, $action = self::INSERT ) {
286 + // disable flood control
287 + \remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
288 + // do not require email for AP entries
465 289 \add_filter( 'pre_option_require_name_email', '__return_false' );
466 - // No nonce possible for this submission route.
290 + // No nonce possible for this submission route
467 291 \add_filter(
468 292 'akismet_comment_nonce',
469 - static function () {
293 + function () {
470 294 return 'inactive';
471 295 }
472 296 );
473 297 \add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
@@ -472,71 +296,21 @@
472 296 );
473 297 \add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
474 298
475 299 if ( self::INSERT === $action ) {
476 - $state = \wp_new_comment( $comment_data, true );
300 + $state = \wp_new_comment( $commentdata, true );
477 301 } else {
478 - $state = \wp_update_comment( $comment_data, true );
302 + $state = \wp_update_comment( $commentdata, true );
479 303 }
480 304
481 - \remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ) );
305 + \remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
482 306 \remove_filter( 'pre_option_require_name_email', '__return_false' );
483 - // Restore flood control.
307 + // re-add flood control
484 308 \add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
485 309
486 310 if ( 1 === $state ) {
487 - return $comment_data;
311 + return $commentdata;
488 312 } else {
489 - return $state; // Either WP_Comment, false, a WP_Error, 0, or 1!
313 + return $state; // Either `WP_Comment`, `false` or a `WP_Error` instance or `0` or `1`!
490 314 }
491 - }
492 -
493 - /**
494 - * Get the total number of interactions by type for a given ID.
495 - *
496 - * @param int $post_id The post ID.
497 - * @param string $type The type of interaction to count.
498 - *
499 - * @return int The total number of interactions.
500 - */
501 - public static function count_by_type( $post_id, $type ) {
502 - return \get_comments(
503 - array(
504 - 'post_id' => $post_id,
505 - 'status' => 'approve',
506 - 'type' => $type,
507 - 'count' => true,
508 - 'paging' => false,
509 - 'fields' => 'ids',
510 - )
511 - );
512 - }
513 -
514 - /**
515 - * Get the quote URL from an activity.
516 - *
517 - * Checks for quote properties in priority order: quote -> quoteUrl -> quoteUri -> _misskey_quote.
518 - *
519 - * @param array $activity The activity array.
520 - *
521 - * @return string|false The quote URL or false if not found.
522 - */
523 - public static function get_quote_url( $activity ) {
524 - if ( ! empty( $activity['object']['quote'] ) ) {
525 - return object_to_uri( $activity['object']['quote'] );
526 - }
527 -
528 - if ( ! empty( $activity['object']['quoteUrl'] ) ) {
529 - return object_to_uri( $activity['object']['quoteUrl'] );
530 - }
531 -
532 - if ( ! empty( $activity['object']['quoteUri'] ) ) {
533 - return object_to_uri( $activity['object']['quoteUri'] );
534 - }
535 -
536 - if ( ! empty( $activity['object']['_misskey_quote'] ) ) {
537 - return object_to_uri( $activity['object']['_misskey_quote'] );
538 - }
539 -
540 - return false;
541 315 }
542 316 }