PluginProbe
ActivityPub / 7.1.0
ActivityPub v7.1.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.1.0, at includes/class-comment.php

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