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

845 lines 24.6 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 Activitypub\Collection\Posts;
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 return $link;
74 }
75
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 );
93 }
94
95 /**
96 * Check if it is allowed to comment to a comment.
97 *
98 * Checks if the comment is local only or if the user can comment federated comments.
99 *
100 * @param mixed $comment Comment object or ID.
101 *
102 * @return boolean True if the user can comment, false otherwise.
103 */
104 public static function are_comments_allowed( $comment ) {
105 $comment = \get_comment( $comment );
106
107 if ( ! self::was_received( $comment ) ) {
108 return true;
109 }
110
111 $current_user = get_current_user_id();
112
113 if ( ! $current_user ) {
114 return false;
115 }
116
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;
120 }
121
122 return user_can_activitypub( $current_user );
123 }
124
125 /**
126 * Check if a comment is federated.
127 *
128 * We consider a comment federated if comment was received via ActivityPub.
129 *
130 * Use this function to check if it is comment that was received via ActivityPub.
131 *
132 * @param mixed $comment Comment object or ID.
133 *
134 * @return boolean True if the comment is federated, false otherwise.
135 */
136 public static function was_received( $comment ) {
137 $comment = \get_comment( $comment );
138
139 if ( ! $comment ) {
140 return false;
141 }
142
143 $protocol = \get_comment_meta( $comment->comment_ID, 'protocol', true );
144
145 if ( 'activitypub' === $protocol ) {
146 return true;
147 }
148
149 return false;
150 }
151
152 /**
153 * Check if a comment was federated.
154 *
155 * This function checks if a comment was federated via ActivityPub.
156 *
157 * @param mixed $comment Comment object or ID.
158 *
159 * @return boolean True if the comment was federated, false otherwise.
160 */
161 public static function was_sent( $comment ) {
162 $comment = \get_comment( $comment );
163
164 if ( ! $comment ) {
165 return false;
166 }
167
168 $status = \get_comment_meta( $comment->comment_ID, 'activitypub_status', true );
169
170 if ( $status ) {
171 return true;
172 }
173
174 return false;
175 }
176
177 /**
178 * Check if a comment is local only.
179 *
180 * This function checks if a comment is local only and was not sent or received via ActivityPub.
181 *
182 * @param mixed $comment Comment object or ID.
183 *
184 * @return boolean True if the comment is local only, false otherwise.
185 */
186 public static function is_local( $comment ) {
187 if ( self::was_sent( $comment ) || self::was_received( $comment ) ) {
188 return false;
189 }
190
191 return true;
192 }
193
194 /**
195 * Check if a comment should be federated.
196 *
197 * We consider a comment should be federated if it is authored by a user that is
198 * not disabled for federation and if it is a reply directly to the post or to a
199 * federated comment.
200 *
201 * Use this function to check if a comment should be federated.
202 *
203 * @param mixed $comment Comment object or ID.
204 *
205 * @return boolean True if the comment should be federated, false otherwise.
206 */
207 public static function should_be_federated( $comment ) {
208 // We should not federate federated comments.
209 if ( self::was_received( $comment ) ) {
210 return false;
211 }
212
213 $comment = \get_comment( $comment );
214 $user_id = $comment->user_id;
215
216 // Comments without user can't be federated.
217 if ( ! $user_id ) {
218 return false;
219 }
220
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 }
225
226 // User is not allowed to federate comments.
227 if ( ! user_can_activitypub( $user_id ) ) {
228 return false;
229 }
230
231 // It is a comment to the post and can be federated.
232 if ( empty( $comment->comment_parent ) ) {
233 return true;
234 }
235
236 // Check if parent comment is federated.
237 $parent_comment = \get_comment( $comment->comment_parent );
238
239 return ! self::is_local( $parent_comment );
240 }
241
242 /**
243 * Examine a comment ID and look up an existing comment it represents.
244 *
245 * @param string $id ActivityPub object ID (usually a URL) to check.
246 *
247 * @return \WP_Comment|false Comment object, or false on failure.
248 */
249 public static function object_id_to_comment( $id ) {
250 $comment_query = new \WP_Comment_Query(
251 array(
252 'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
253 'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
254 'orderby' => 'comment_date',
255 'order' => 'DESC',
256 )
257 );
258
259 if ( ! $comment_query->comments ) {
260 return false;
261 }
262
263 return $comment_query->comments[0];
264 }
265
266 /**
267 * Verify if URL is a local comment, or if it is a previously received
268 * remote comment (For threading comments locally).
269 *
270 * @param string $url The URL to check.
271 *
272 * @return string|null Comment ID or null if not found.
273 */
274 public static function url_to_commentid( $url ) {
275 if ( ! $url || ! \filter_var( $url, \FILTER_VALIDATE_URL ) ) {
276 return null;
277 }
278
279 // Check for local comment.
280 if ( is_same_domain( $url ) ) {
281 $query = \wp_parse_url( $url, \PHP_URL_QUERY );
282
283 if ( $query ) {
284 \parse_str( $query, $params );
285
286 if ( ! empty( $params['c'] ) ) {
287 $comment = \get_comment( $params['c'] );
288
289 if ( $comment ) {
290 return $comment->comment_ID;
291 }
292 }
293 }
294 }
295
296 $args = array(
297 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
298 'meta_query' => array(
299 'relation' => 'OR',
300 array(
301 'key' => 'source_url',
302 'value' => $url,
303 ),
304 array(
305 'key' => 'source_id',
306 'value' => $url,
307 ),
308 ),
309 );
310
311 $query = new \WP_Comment_Query();
312 $comments = $query->query( $args );
313
314 if ( $comments && is_array( $comments ) ) {
315 return $comments[0]->comment_ID;
316 }
317
318 return null;
319 }
320
321 /**
322 * Filters the CSS classes to add an ActivityPub class.
323 *
324 * @param string[] $classes An array of comment classes.
325 * @param string[] $css_class An array of additional classes added to the list.
326 * @param string $comment_id The comment ID as a numeric string.
327 *
328 * @return string[] An array of classes.
329 */
330 public static function comment_class( $classes, $css_class, $comment_id ) {
331 // Check if ActivityPub comment.
332 if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
333 $classes[] = 'activitypub-comment';
334 }
335
336 return $classes;
337 }
338
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 /**
412 * Link remote comments to source url.
413 *
414 * @param string $comment_link The comment link.
415 * @param object|\WP_Comment $comment The comment object.
416 *
417 * @return string $url
418 */
419 public static function remote_comment_link( $comment_link, $comment ) {
420 if ( ! $comment || \is_admin() || \is_search() ) {
421 return $comment_link;
422 }
423
424 $remote_comment_link = null;
425 if ( 'comment' === $comment->comment_type ) {
426 $remote_comment_link = self::get_source_url( $comment->comment_ID );
427 }
428
429 return $remote_comment_link ?? $comment_link;
430 }
431
432
433 /**
434 * Generates an ActivityPub URI for a comment
435 *
436 * @param \WP_Comment|int $comment A comment object or comment ID.
437 *
438 * @return string ActivityPub URI for comment
439 */
440 public static function generate_id( $comment ) {
441 $comment = \get_comment( $comment );
442
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;
448 }
449
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(
463 array(
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 )
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 $query->query_vars['post_type'] = array_diff( \get_post_types_by_support( 'comments' ), self::hide_for() );
708 return;
709 }
710
711 // Do not exclude likes and reposts on non-singular pages.
712 if ( ! \is_singular() ) {
713 return;
714 }
715
716 // Do not exclude likes and reposts if the query is for comments.
717 if ( ! empty( $query->query_vars['type__in'] ) || ! empty( $query->query_vars['type'] ) ) {
718 return;
719 }
720
721 // Exclude likes and reposts by the ActivityPub plugin.
722 $query->query_vars['type__not_in'] = self::get_comment_type_slugs();
723 }
724
725 /**
726 * Filter the comment status before it is set.
727 *
728 * @param int|string|\WP_Error $approved The approved comment status.
729 * @param array $comment_data The comment data.
730 *
731 * @return int|string|\WP_Error The approval status. 1, 0, 'spam', 'trash', or WP_Error.
732 */
733 public static function pre_comment_approved( $approved, $comment_data ) {
734 if ( $approved || \is_wp_error( $approved ) ) {
735 return $approved;
736 }
737
738 // Maybe auto-approve likes and reposts.
739 if (
740 \in_array( $comment_data['comment_type'], self::get_comment_type_slugs(), true ) &&
741 '1' === \get_option( 'activitypub_auto_approve_reactions' )
742 ) {
743 return 1;
744 }
745
746 if ( '1' !== \get_option( 'comment_previously_approved' ) ) {
747 return $approved;
748 }
749
750 if (
751 empty( $comment_data['comment_meta']['protocol'] ) ||
752 'activitypub' !== $comment_data['comment_meta']['protocol']
753 ) {
754 return $approved;
755 }
756
757 global $wpdb;
758
759 $author = $comment_data['comment_author'];
760 $author_url = $comment_data['comment_author_url'];
761 // phpcs:ignore
762 $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 ) );
763
764 if ( 1 === (int) $ok_to_comment ) {
765 return 1;
766 }
767
768 $post_id = $comment_data['comment_post_ID'];
769 $post = \get_post( $post_id );
770
771 if ( $post && in_array( $post->post_type, self::hide_for(), true ) ) {
772 return 1;
773 }
774
775 return $approved;
776 }
777
778 /**
779 * Update comment counts when interaction settings are disabled.
780 *
781 * Triggers a recount when likes or reposts are disabled to ensure accurate comment counts.
782 *
783 * @param mixed $old_value The old option value.
784 * @param mixed $value The new option value.
785 */
786 public static function maybe_update_comment_counts( $old_value, $value ) {
787 if ( '1' === $old_value && '1' !== $value ) {
788 Migration::update_comment_counts();
789 }
790 }
791
792 /**
793 * Filters the comment count to exclude ActivityPub comment types.
794 *
795 * @param int|null $new_count The new comment count. Default null.
796 * @param int $old_count The old comment count.
797 * @param int $post_id Post ID.
798 *
799 * @return int|null The updated comment count, or null to use the default query.
800 */
801 public static function pre_wp_update_comment_count_now( $new_count, $old_count, $post_id ) {
802 if ( null === $new_count ) {
803 $excluded_types = array_filter( self::get_comment_type_slugs(), array( self::class, 'is_comment_type_enabled' ) );
804
805 if ( ! empty( $excluded_types ) ) {
806 global $wpdb;
807
808 // phpcs:ignore WordPress.DB
809 $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 ) );
810 }
811 }
812
813 return $new_count;
814 }
815
816 /**
817 * Check if a comment type is enabled.
818 *
819 * @param string $comment_type The comment type.
820 * @return bool True if the comment type is enabled.
821 */
822 public static function is_comment_type_enabled( $comment_type ) {
823 return '1' === get_option( "activitypub_allow_{$comment_type}s", '1' );
824 }
825
826 /**
827 * Get post types to hide comments for in admin.
828 *
829 * These are non-public post types whose comments should not appear
830 * in the main comments list in the WordPress admin.
831 *
832 * @return string[] Array of post type names to hide comments for.
833 */
834 public static function hide_for() {
835 $post_types = array( Posts::POST_TYPE );
836
837 /**
838 * Filters the list of post types to hide comments for.
839 *
840 * @param string[] $post_types Array of post type names to hide comments for.
841 */
842 return \apply_filters( 'activitypub_hide_comments_for', $post_types );
843 }
844 }
845