PluginProbe
ActivityPub / 7.7.0
ActivityPub v7.7.0
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/class-comment.php +549 -57 2.1.17.7.0 View file →
@@ -1,14 +1,18 @@
1 1 <?php
2 +/**
3 + * ActivityPub Comment Class
4 + *
5 + * @package Activitypub
6 + */
2 7
3 8 namespace Activitypub;
4 9
5 -use WP_Comment_Query;
10 +use Activitypub\Collection\Actors;
11 +use Activitypub\Collection\Posts;
6 12
7 -use function Activitypub\is_user_disabled;
8 -
9 13 /**
10 - * ActivityPub Comment Class
14 + * ActivityPub Comment Class.
11 15 *
12 16 * This class is a helper/utils class that provides a collection of static
13 17 * methods that are used to handle comments.
14 18 */
@@ -13,25 +17,55 @@
13 17 * methods that are used to handle comments.
14 18 */
15 19 class Comment {
16 20 /**
17 - * Initialize the class, registering WordPress hooks
21 + * Initialize the class, registering WordPress hooks.
18 22 */
19 23 public static function init() {
24 + self::register_comment_types();
25 +
26 + \add_filter( 'map_meta_cap', array( self::class, 'map_meta_cap' ), 10, 4 );
20 27 \add_filter( 'comment_reply_link', array( self::class, 'comment_reply_link' ), 10, 3 );
21 28 \add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
22 - \add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
29 + \add_filter( 'comment_feed_where', array( static::class, 'comment_feed_where' ) );
30 + \add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 2 );
31 + \add_action( 'pre_get_comments', array( static::class, 'comment_query' ) );
32 + \add_filter( 'pre_comment_approved', array( static::class, 'pre_comment_approved' ), 10, 2 );
33 + \add_filter( 'get_avatar_comment_types', array( static::class, 'get_avatar_comment_types' ), 99 );
34 + \add_action( 'update_option_activitypub_allow_likes', array( self::class, 'maybe_update_comment_counts' ), 10, 2 );
35 + \add_action( 'update_option_activitypub_allow_reposts', array( self::class, 'maybe_update_comment_counts' ), 10, 2 );
36 + \add_filter( 'pre_wp_update_comment_count_now', array( static::class, 'pre_wp_update_comment_count_now' ), 10, 3 );
23 37 }
24 38
25 39 /**
40 + * Remove edit capabilities for comments received via ActivityPub.
41 + *
42 + * @param array $caps Array of capabilities.
43 + * @param string $cap Capability name.
44 + * @param int $user_id User ID.
45 + * @param array $args Array of arguments.
46 + *
47 + * @return array Modified array of capabilities.
48 + */
49 + public static function map_meta_cap( $caps, $cap, $user_id, $args ) {
50 + if ( 'edit_comment' === $cap && self::was_received( $args[0] ) ) {
51 + if ( ! \is_admin() || ( isset( $GLOBALS['current_screen'] ) && 'comment' === $GLOBALS['current_screen']->id ) ) {
52 + $caps[] = 'do_not_allow';
53 + }
54 + }
55 +
56 + return $caps;
57 + }
58 +
59 + /**
26 60 * Filter the comment reply link.
27 61 *
28 62 * We don't want to show the comment reply link for federated comments
29 63 * if the user is disabled for federation.
30 64 *
31 - * @param string $link The HTML markup for the comment reply link.
32 - * @param array $args An array of arguments overriding the defaults.
33 - * @param WP_Comment $comment The object of the comment being replied.
65 + * @param string $link The HTML markup for the comment reply link.
66 + * @param array $args An array of arguments overriding the defaults.
67 + * @param \WP_Comment $comment The object of the comment being replied.
34 68 *
35 69 * @return string The filtered HTML markup for the comment reply link.
36 70 */
37 71 public static function comment_reply_link( $link, $args, $comment ) {
@@ -38,9 +72,25 @@
38 72 if ( self::are_comments_allowed( $comment ) ) {
39 73 return $link;
40 74 }
41 75
42 - return apply_filters( 'activitypub_comment_reply_link', '' );
76 + if ( ! \WP_Block_Type_Registry::get_instance()->is_registered( 'activitypub/remote-reply' ) ) {
77 + \register_block_type_from_metadata( ACTIVITYPUB_PLUGIN_DIR . 'build/remote-reply' );
78 + }
79 +
80 + $attributes = array(
81 + 'selectedComment' => self::generate_id( $comment ),
82 + 'commentId' => $comment->comment_ID,
83 + );
84 +
85 + $block = \do_blocks( \sprintf( '<!-- wp:activitypub/remote-reply %s /-->', \wp_json_encode( $attributes ) ) );
86 +
87 + /**
88 + * Filters the HTML markup for the ActivityPub remote comment reply container.
89 + *
90 + * @param string $block The HTML markup for the remote reply container.
91 + */
92 + return \apply_filters( 'activitypub_comment_reply_link', $block );
43 93 }
44 94
45 95 /**
46 96 * Check if it is allowed to comment to a comment.
@@ -63,15 +113,14 @@
63 113 if ( ! $current_user ) {
64 114 return false;
65 115 }
66 116
67 - $is_user_disabled = is_user_disabled( $current_user );
68 -
69 - if ( $is_user_disabled ) {
70 - return false;
117 + if ( is_single_user() && \user_can( $current_user, 'publish_posts' ) ) {
118 + // On a single user site, comments by users with the `publish_posts` capability will be federated as the blog user.
119 + $current_user = Actors::BLOG_USER_ID;
71 120 }
72 121
73 - return true;
122 + return user_can_activitypub( $current_user );
74 123 }
75 124
76 125 /**
77 126 * Check if a comment is federated.
@@ -155,9 +204,9 @@
155 204 *
156 205 * @return boolean True if the comment should be federated, false otherwise.
157 206 */
158 207 public static function should_be_federated( $comment ) {
159 - // we should not federate federated comments
208 + // We should not federate federated comments.
160 209 if ( self::was_received( $comment ) ) {
161 210 return false;
162 211 }
163 212
@@ -163,26 +212,29 @@
163 212
164 213 $comment = \get_comment( $comment );
165 214 $user_id = $comment->user_id;
166 215
167 - // comments without user can't be federated
216 + // Comments without user can't be federated.
168 217 if ( ! $user_id ) {
169 218 return false;
170 219 }
171 220
172 - $is_user_disabled = is_user_disabled( $user_id );
221 + if ( is_single_user() && \user_can( $user_id, 'activitypub' ) ) {
222 + // On a single user site, comments by users with the `publish_posts` capability will be federated as the blog user.
223 + $user_id = Actors::BLOG_USER_ID;
224 + }
173 225
174 - // user is disabled for federation
175 - if ( $is_user_disabled ) {
226 + // User is not allowed to federate comments.
227 + if ( ! user_can_activitypub( $user_id ) ) {
176 228 return false;
177 229 }
178 230
179 - // it is a comment to the post and can be federated
231 + // It is a comment to the post and can be federated.
180 232 if ( empty( $comment->comment_parent ) ) {
181 233 return true;
182 234 }
183 235
184 - // check if parent comment is federated
236 + // Check if parent comment is federated.
185 237 $parent_comment = \get_comment( $comment->comment_parent );
186 238
187 239 return ! self::is_local( $parent_comment );
188 240 }
@@ -191,15 +243,17 @@
191 243 * Examine a comment ID and look up an existing comment it represents.
192 244 *
193 245 * @param string $id ActivityPub object ID (usually a URL) to check.
194 246 *
195 - * @return int|boolean Comment ID, or false on failure.
247 + * @return \WP_Comment|false Comment object, or false on failure.
196 248 */
197 249 public static function object_id_to_comment( $id ) {
198 - $comment_query = new WP_Comment_Query(
250 + $comment_query = new \WP_Comment_Query(
199 251 array(
200 252 'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
201 253 'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
254 + 'orderby' => 'comment_date',
255 + 'order' => 'DESC',
202 256 )
203 257 );
204 258
205 259 if ( ! $comment_query->comments ) {
@@ -205,34 +259,30 @@
205 259 if ( ! $comment_query->comments ) {
206 260 return false;
207 261 }
208 262
209 - if ( count( $comment_query->comments ) > 1 ) {
210 - return false;
211 - }
212 -
213 263 return $comment_query->comments[0];
214 264 }
215 265
216 266 /**
217 267 * Verify if URL is a local comment, or if it is a previously received
218 - * remote comment (For threading comments locally)
268 + * remote comment (For threading comments locally).
219 269 *
220 270 * @param string $url The URL to check.
221 271 *
222 - * @return int comment_ID or null if not found
272 + * @return string|null Comment ID or null if not found.
223 273 */
224 274 public static function url_to_commentid( $url ) {
225 - if ( ! $url || ! filter_var( $url, \FILTER_VALIDATE_URL ) ) {
275 + if ( ! $url || ! \filter_var( $url, \FILTER_VALIDATE_URL ) ) {
226 276 return null;
227 277 }
228 278
229 - // check for local comment
230 - if ( \wp_parse_url( \site_url(), \PHP_URL_HOST ) === \wp_parse_url( $url, \PHP_URL_HOST ) ) {
279 + // Check for local comment.
280 + if ( is_same_domain( $url ) ) {
231 281 $query = \wp_parse_url( $url, \PHP_URL_QUERY );
232 282
233 283 if ( $query ) {
234 - parse_str( $query, $params );
284 + \parse_str( $query, $params );
235 285
236 286 if ( ! empty( $params['c'] ) ) {
237 287 $comment = \get_comment( $params['c'] );
238 288
@@ -257,9 +307,9 @@
257 307 ),
258 308 ),
259 309 );
260 310
261 - $query = new WP_Comment_Query();
311 + $query = new \WP_Comment_Query();
262 312 $comments = $query->query( $args );
263 313
264 314 if ( $comments && is_array( $comments ) ) {
265 315 return $comments[0]->comment_ID;
@@ -277,9 +327,9 @@
277 327 *
278 328 * @return string[] An array of classes.
279 329 */
280 330 public static function comment_class( $classes, $css_class, $comment_id ) {
281 - // check if ActivityPub comment
331 + // Check if ActivityPub comment.
282 332 if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
283 333 $classes[] = 'activitypub-comment';
284 334 }
285 335
@@ -286,29 +336,98 @@
286 336 return $classes;
287 337 }
288 338
289 339 /**
340 + * Makes the comment feed filterable by comment type.
341 + *
342 + * Also excludes ActivityPub comment types from the feed when no type is specified.
343 + *
344 + * @param string $where The `WHERE` clause for the comment feed query.
345 + *
346 + * @return string The modified `WHERE` clause.
347 + */
348 + public static function comment_feed_where( $where ) {
349 + global $wpdb;
350 +
351 + $comment_type = \get_query_var( 'type' );
352 +
353 + if ( 'all' === $comment_type ) {
354 + return $where;
355 + }
356 +
357 + $comment_types = self::get_comment_type_slugs();
358 +
359 + if ( \in_array( $comment_type, $comment_types, true ) ) {
360 + $where .= $wpdb->prepare( ' AND comment_type = %s', $comment_type );
361 + } else {
362 + $comment_types = \array_map( 'esc_sql', $comment_types );
363 + $placeholders = implode( ', ', array_fill( 0, count( $comment_types ), '%s' ) );
364 + // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQL.NotPrepared
365 + $where .= $wpdb->prepare( sprintf( ' AND comment_type NOT IN (%s)', $placeholders ), ...$comment_types );
366 + }
367 +
368 + return $where;
369 + }
370 +
371 + /**
372 + * Gets the public comment id via the WordPress comments meta.
373 + *
374 + * @param int $wp_comment_id The internal WordPress comment ID.
375 + * @param bool $fallback Whether the code should fall back to `source_url` if `source_id` is not set.
376 + *
377 + * @return string|null The ActivityPub id/url of the comment.
378 + */
379 + public static function get_source_id( $wp_comment_id, $fallback = true ) {
380 + $comment_meta = \get_comment_meta( $wp_comment_id );
381 +
382 + if ( ! empty( $comment_meta['source_id'][0] ) ) {
383 + return $comment_meta['source_id'][0];
384 + } elseif ( ! empty( $comment_meta['source_url'][0] ) && $fallback ) {
385 + return $comment_meta['source_url'][0];
386 + }
387 +
388 + return null;
389 + }
390 +
391 + /**
392 + * Gets the public comment url via the WordPress comments meta.
393 + *
394 + * @param int $wp_comment_id The internal WordPress comment ID.
395 + * @param bool $fallback Whether the code should fall back to `source_id` if `source_url` is not set.
396 + *
397 + * @return string|null The ActivityPub id/url of the comment.
398 + */
399 + public static function get_source_url( $wp_comment_id, $fallback = true ) {
400 + $comment_meta = \get_comment_meta( $wp_comment_id );
401 +
402 + if ( ! empty( $comment_meta['source_url'][0] ) ) {
403 + return $comment_meta['source_url'][0];
404 + } elseif ( ! empty( $comment_meta['source_id'][0] ) && $fallback ) {
405 + return $comment_meta['source_id'][0];
406 + }
407 +
408 + return null;
409 + }
410 +
411 + /**
290 412 * Link remote comments to source url.
291 413 *
292 - * @param string $comment_link
293 - * @param object|WP_Comment $comment
414 + * @param string $comment_link The comment link.
415 + * @param object|\WP_Comment $comment The comment object.
294 416 *
295 417 * @return string $url
296 418 */
297 419 public static function remote_comment_link( $comment_link, $comment ) {
298 - if ( ! $comment || is_admin() ) {
420 + if ( ! $comment || \is_admin() || \is_search() ) {
299 421 return $comment_link;
300 422 }
301 423
302 - $comment_meta = \get_comment_meta( $comment->comment_ID );
303 -
304 - if ( ! empty( $comment_meta['source_url'][0] ) ) {
305 - return $comment_meta['source_url'][0];
306 - } elseif ( ! empty( $comment_meta['source_id'][0] ) ) {
307 - return $comment_meta['source_id'][0];
424 + $remote_comment_link = null;
425 + if ( 'comment' === $comment->comment_type ) {
426 + $remote_comment_link = self::get_source_url( $comment->comment_ID );
308 427 }
309 428
310 - return $comment_link;
429 + return $remote_comment_link ?? $comment_link;
311 430 }
312 431
313 432
314 433 /**
@@ -313,26 +432,399 @@
313 432
314 433 /**
315 434 * Generates an ActivityPub URI for a comment
316 435 *
317 - * @param WP_Comment|int $comment A comment object or comment ID
436 + * @param \WP_Comment|int $comment A comment object or comment ID.
318 437 *
319 438 * @return string ActivityPub URI for comment
320 439 */
321 440 public static function generate_id( $comment ) {
322 - $comment = get_comment( $comment );
441 + $comment = \get_comment( $comment );
323 442
324 - // show external comment ID if it exists
325 - $source_id = get_comment_meta( $comment->comment_ID, 'source_id', true );
326 - if ( ! empty( $source_id ) ) {
327 - return $source_id;
443 + // Show external comment ID if it exists.
444 + $public_comment_link = self::get_source_id( $comment->comment_ID );
445 +
446 + if ( $public_comment_link ) {
447 + return $public_comment_link;
328 448 }
329 449
330 - // generate URI based on comment ID
331 - return \add_query_arg(
450 + // Generate URI based on comment ID.
451 + return \add_query_arg( 'c', $comment->comment_ID, \home_url( '/' ) );
452 + }
453 +
454 + /**
455 + * Check if a post has remote comments
456 + *
457 + * @param int $post_id The post ID.
458 + *
459 + * @return bool True if the post has remote comments, false otherwise.
460 + */
461 + private static function post_has_remote_comments( $post_id ) {
462 + $comments = \get_comments(
332 463 array(
333 - 'c' => $comment->comment_ID,
334 - ),
335 - \trailingslashit( site_url() )
464 + 'post_id' => $post_id,
465 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
466 + 'meta_query' => array(
467 + 'relation' => 'AND',
468 + array(
469 + 'key' => 'protocol',
470 + 'value' => 'activitypub',
471 + 'compare' => '=',
472 + ),
473 + array(
474 + 'key' => 'source_id',
475 + 'compare' => 'EXISTS',
476 + ),
477 + ),
478 + )
336 479 );
480 +
481 + return ! empty( $comments );
482 + }
483 +
484 + /**
485 + * Get the comment type by activity type.
486 + *
487 + * @param string $activity_type The activity type.
488 + *
489 + * @return array|null The comment type.
490 + */
491 + public static function get_comment_type_by_activity_type( $activity_type ) {
492 + $activity_type = \strtolower( $activity_type );
493 + $activity_type = \sanitize_key( $activity_type );
494 + $comment_types = self::get_comment_types();
495 +
496 + foreach ( $comment_types as $comment_type ) {
497 + if ( in_array( $activity_type, $comment_type['activity_types'], true ) ) {
498 + return $comment_type;
499 + }
500 + }
501 +
502 + return null;
503 + }
504 +
505 + /**
506 + * Return the registered custom comment types.
507 + *
508 + * @return array The registered custom comment types
509 + */
510 + public static function get_comment_types() {
511 + global $activitypub_comment_types;
512 +
513 + return $activitypub_comment_types;
514 + }
515 +
516 + /**
517 + * Is this a registered comment type.
518 + *
519 + * @param string $slug The slug of the type.
520 + *
521 + * @return boolean True if registered.
522 + */
523 + public static function is_registered_comment_type( $slug ) {
524 + $slug = \strtolower( $slug );
525 + $slug = \sanitize_key( $slug );
526 +
527 + $comment_types = self::get_comment_types();
528 +
529 + return isset( $comment_types[ $slug ] );
530 + }
531 +
532 + /**
533 + * Return the registered custom comment type slugs.
534 + *
535 + * @return array The registered custom comment type slugs.
536 + */
537 + public static function get_comment_type_slugs() {
538 + if ( ! did_action( 'init' ) ) {
539 + _doing_it_wrong( __METHOD__, 'This function should not be called before the init action has run. Comment types are only available after init.', '7.5.0' );
540 +
541 + return array();
542 + }
543 +
544 + return array_keys( self::get_comment_types() );
545 + }
546 +
547 + /**
548 + * Get the custom comment type.
549 + *
550 + * Check if the type is registered, if not, check if it is a custom type.
551 + *
552 + * It looks for the array key in the registered types and returns the array.
553 + * If it is not found, it looks for the type in the custom types and returns the array.
554 + *
555 + * @param string $type The comment type.
556 + *
557 + * @return array The comment type.
558 + */
559 + public static function get_comment_type( $type ) {
560 + $type = strtolower( $type );
561 + $type = sanitize_key( $type );
562 +
563 + $comment_types = self::get_comment_types();
564 + $type_array = array();
565 +
566 + // Check array keys.
567 + if ( in_array( $type, array_keys( $comment_types ), true ) ) {
568 + $type_array = $comment_types[ $type ];
569 + }
570 +
571 + /**
572 + * Filter the comment type.
573 + *
574 + * @param array $type_array The comment type.
575 + */
576 + return apply_filters( "activitypub_comment_type_{$type}", $type_array );
577 + }
578 +
579 + /**
580 + * Get a comment type attribute.
581 + *
582 + * @param string $type The comment type.
583 + * @param string $attr The attribute to get.
584 + *
585 + * @return mixed The value of the attribute.
586 + */
587 + public static function get_comment_type_attr( $type, $attr ) {
588 + $type_array = self::get_comment_type( $type );
589 +
590 + if ( $type_array && isset( $type_array[ $attr ] ) ) {
591 + $value = $type_array[ $attr ];
592 + } else {
593 + $value = '';
594 + }
595 +
596 + /**
597 + * Filter the comment type attribute.
598 + *
599 + * @param mixed $value The value of the attribute.
600 + * @param string $type The comment type.
601 + */
602 + return apply_filters( "activitypub_comment_type_{$attr}", $value, $type );
603 + }
604 +
605 + /**
606 + * Register the comment types used by the ActivityPub plugin.
607 + */
608 + public static function register_comment_types() {
609 + register_comment_type(
610 + 'repost',
611 + array(
612 + 'label' => __( 'Reposts', 'activitypub' ),
613 + 'singular' => __( 'Repost', 'activitypub' ),
614 + 'description' => 'A repost (or Announce) is when a post appears in the timeline because someone else shared it, while still showing the original author as the source.',
615 + 'icon' => '♻️',
616 + 'class' => 'p-repost',
617 + 'type' => 'repost',
618 + 'collection' => 'reposts',
619 + 'activity_types' => array( 'announce' ),
620 + 'excerpt' => html_entity_decode( \__( '&hellip; reposted this!', 'activitypub' ) ),
621 + /* translators: %d: Number of reposts */
622 + 'count_single' => _x( '%d repost', 'number of reposts', 'activitypub' ),
623 + /* translators: %d: Number of reposts */
624 + 'count_plural' => _x( '%d reposts', 'number of reposts', 'activitypub' ),
625 + )
626 + );
627 +
628 + register_comment_type(
629 + 'like',
630 + array(
631 + 'label' => __( 'Likes', 'activitypub' ),
632 + 'singular' => __( 'Like', 'activitypub' ),
633 + 'description' => 'A like is a small positive reaction that shows appreciation for a post without sharing it further.',
634 + 'icon' => '👍',
635 + 'class' => 'p-like',
636 + 'type' => 'like',
637 + 'collection' => 'likes',
638 + 'activity_types' => array( 'like' ),
639 + 'excerpt' => html_entity_decode( \__( '&hellip; liked this!', 'activitypub' ) ),
640 + /* translators: %d: Number of likes */
641 + 'count_single' => _x( '%d like', 'number of likes', 'activitypub' ),
642 + /* translators: %d: Number of likes */
643 + 'count_plural' => _x( '%d likes', 'number of likes', 'activitypub' ),
644 + )
645 + );
646 +
647 + register_comment_type(
648 + 'quote',
649 + array(
650 + 'label' => __( 'Quotes', 'activitypub' ),
651 + 'singular' => __( 'Quote', 'activitypub' ),
652 + 'description' => 'A quote is when a post is shared along with an added comment, so the original post appears together with the sharer&#8217;s own words.',
653 + 'icon' => '❞',
654 + 'class' => 'p-quote',
655 + 'type' => 'quote',
656 + 'collection' => 'quotes',
657 + 'activity_types' => array( 'quote' ),
658 + 'excerpt' => html_entity_decode( \__( '&hellip; quoted this!', 'activitypub' ) ),
659 + /* translators: %d: Number of quotes */
660 + 'count_single' => _x( '%d quote', 'number of quotes', 'activitypub' ),
661 + /* translators: %d: Number of quotes */
662 + 'count_plural' => _x( '%d quotes', 'number of quotes', 'activitypub' ),
663 + )
664 + );
665 + }
666 +
667 + /**
668 + * Show avatars on Activities if set.
669 + *
670 + * @param array $types List of avatar enabled comment types.
671 + *
672 + * @return array show avatars on Activities
673 + */
674 + public static function get_avatar_comment_types( $types ) {
675 + $comment_types = self::get_comment_type_slugs();
676 + $types = array_merge( $types, $comment_types );
677 +
678 + return array_unique( $types );
679 + }
680 +
681 + /**
682 + * Excludes likes and reposts from comment queries.
683 + *
684 + * @author Jan Boddez
685 + *
686 + * @see https://github.com/janboddez/indieblocks/blob/a2d59de358031056a649ee47a1332ce9e39d4ce2/includes/functions.php#L423-L432
687 + *
688 + * @param \WP_Comment_Query $query Comment count.
689 + */
690 + public static function comment_query( $query ) {
691 + if ( ! $query instanceof \WP_Comment_Query ) {
692 + return;
693 + }
694 +
695 + // Do not exclude likes and reposts on ActivityPub requests.
696 + if ( defined( 'ACTIVITYPUB_REQUEST' ) && ACTIVITYPUB_REQUEST ) {
697 + return;
698 + }
699 +
700 + // Do not exclude likes and reposts on REST requests.
701 + if ( \wp_is_serving_rest_request() ) {
702 + return;
703 + }
704 +
705 + // Do only exclude interactions of `ap_post` post type.
706 + if ( \is_admin() ) {
707 + if ( \get_option( 'activitypub_create_posts', false ) ) {
708 + $query->query_vars['post_type'] = array_diff( \get_post_types_by_support( 'comments' ), array( Posts::POST_TYPE ) );
709 + }
710 + return;
711 + }
712 +
713 + // Do not exclude likes and reposts on non-singular pages.
714 + if ( ! \is_singular() ) {
715 + return;
716 + }
717 +
718 + // Do not exclude likes and reposts if the query is for comments.
719 + if ( ! empty( $query->query_vars['type__in'] ) || ! empty( $query->query_vars['type'] ) ) {
720 + return;
721 + }
722 +
723 + // Exclude likes and reposts by the ActivityPub plugin.
724 + $query->query_vars['type__not_in'] = self::get_comment_type_slugs();
725 + }
726 +
727 + /**
728 + * Filter the comment status before it is set.
729 + *
730 + * @param int|string|\WP_Error $approved The approved comment status.
731 + * @param array $comment_data The comment data.
732 + *
733 + * @return int|string|\WP_Error The approval status. 1, 0, 'spam', 'trash', or WP_Error.
734 + */
735 + public static function pre_comment_approved( $approved, $comment_data ) {
736 + if ( $approved || \is_wp_error( $approved ) ) {
737 + return $approved;
738 + }
739 +
740 + // Maybe auto-approve likes and reposts.
741 + if (
742 + \in_array( $comment_data['comment_type'], self::get_comment_type_slugs(), true ) &&
743 + '1' === \get_option( 'activitypub_auto_approve_reactions' )
744 + ) {
745 + return 1;
746 + }
747 +
748 + if ( '1' !== \get_option( 'comment_previously_approved' ) ) {
749 + return $approved;
750 + }
751 +
752 + if (
753 + empty( $comment_data['comment_meta']['protocol'] ) ||
754 + 'activitypub' !== $comment_data['comment_meta']['protocol']
755 + ) {
756 + return $approved;
757 + }
758 +
759 + global $wpdb;
760 +
761 + $author = $comment_data['comment_author'];
762 + $author_url = $comment_data['comment_author_url'];
763 + // phpcs:ignore
764 + $ok_to_comment = $wpdb->get_var( $wpdb->prepare( "SELECT comment_approved FROM $wpdb->comments WHERE comment_author = %s AND comment_author_url = %s and comment_approved = '1' LIMIT 1", $author, $author_url ) );
765 +
766 + if ( 1 === (int) $ok_to_comment ) {
767 + return 1;
768 + }
769 +
770 + // Auto approve reactions to an `ap_post`.
771 + if ( \get_option( 'activitypub_create_posts', false ) ) {
772 + $post_id = $comment_data['comment_post_ID'];
773 + $post = \get_post( $post_id );
774 +
775 + if ( $post && Posts::POST_TYPE === $post->post_type ) {
776 + return 1;
777 + }
778 + }
779 +
780 + return $approved;
781 + }
782 +
783 + /**
784 + * Update comment counts when interaction settings are disabled.
785 + *
786 + * Triggers a recount when likes or reposts are disabled to ensure accurate comment counts.
787 + *
788 + * @param mixed $old_value The old option value.
789 + * @param mixed $value The new option value.
790 + */
791 + public static function maybe_update_comment_counts( $old_value, $value ) {
792 + if ( '1' === $old_value && '1' !== $value ) {
793 + Migration::update_comment_counts();
794 + }
795 + }
796 +
797 + /**
798 + * Filters the comment count to exclude ActivityPub comment types.
799 + *
800 + * @param int|null $new_count The new comment count. Default null.
801 + * @param int $old_count The old comment count.
802 + * @param int $post_id Post ID.
803 + *
804 + * @return int|null The updated comment count, or null to use the default query.
805 + */
806 + public static function pre_wp_update_comment_count_now( $new_count, $old_count, $post_id ) {
807 + if ( null === $new_count ) {
808 + $excluded_types = array_filter( self::get_comment_type_slugs(), array( self::class, 'is_comment_type_enabled' ) );
809 +
810 + if ( ! empty( $excluded_types ) ) {
811 + global $wpdb;
812 +
813 + // phpcs:ignore WordPress.DB
814 + $new_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type NOT IN ('" . implode( "','", $excluded_types ) . "')", $post_id ) );
815 + }
816 + }
817 +
818 + return $new_count;
819 + }
820 +
821 + /**
822 + * Check if a comment type is enabled.
823 + *
824 + * @param string $comment_type The comment type.
825 + * @return bool True if the comment type is enabled.
826 + */
827 + public static function is_comment_type_enabled( $comment_type ) {
828 + return '1' === get_option( "activitypub_allow_{$comment_type}s", '1' );
337 829 }
338 830 }