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