PluginProbe
ActivityPub / 5.7.0
ActivityPub v5.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
activitypub / includes / class-comment.php

class-comment.php in ActivityPub 5.7.0, at includes/class-comment.php

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