PluginProbe
ActivityPub / 7.2.0
ActivityPub v7.2.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 7.2.0, at includes/class-comment.php

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