| @@ -112,9 +112,9 @@ | ||
| 112 | 112 | // Logged-in user without ActivityPub capability - show warning instead of reply link. |
| 113 | 113 | if ( \is_user_logged_in() ) { |
| 114 | 114 | $author = \esc_html( $comment->comment_author ); |
| 115 | 115 | |
| 116 | - $message = sprintf( | |
| 116 | + $message = \sprintf( | |
| 117 | 117 | /* translators: %s: comment author name */ |
| 118 | 118 | \__( '%s is on the Fediverse. To reply to them, ask your administrator to enable ActivityPub for your account.', 'activitypub' ), |
| 119 | 119 | $author |
| 120 | 120 | ); |
| @@ -120,9 +120,9 @@ | ||
| 120 | 120 | ); |
| 121 | 121 | |
| 122 | 122 | // Add link to users page if current user can edit users. |
| 123 | 123 | if ( \current_user_can( 'edit_users' ) ) { |
| 124 | - $message = sprintf( | |
| 124 | + $message = \sprintf( | |
| 125 | 125 | /* translators: 1: comment author name, 2: URL to the users management page */ |
| 126 | 126 | \__( '%1$s is on the Fediverse. To reply to them, <a href="%2$s">enable ActivityPub for your account</a>.', 'activitypub' ), |
| 127 | 127 | $author, |
| 128 | 128 | \esc_url( \admin_url( 'users.php' ) ) |
| @@ -128,9 +128,9 @@ | ||
| 128 | 128 | \esc_url( \admin_url( 'users.php' ) ) |
| 129 | 129 | ); |
| 130 | 130 | } |
| 131 | 131 | |
| 132 | - $warning = sprintf( | |
| 132 | + $warning = \sprintf( | |
| 133 | 133 | '<p class="activitypub-reply-warning"><em>%s</em></p>', |
| 134 | 134 | \wp_kses( $message, array( 'a' => array( 'href' => array() ) ) ) |
| 135 | 135 | ); |
| 136 | 136 | |
| @@ -177,9 +177,9 @@ | ||
| 177 | 177 | if ( ! self::was_received( $comment ) ) { |
| 178 | 178 | return true; |
| 179 | 179 | } |
| 180 | 180 | |
| 181 | - $current_user = get_current_user_id(); | |
| 181 | + $current_user = \get_current_user_id(); | |
| 182 | 182 | |
| 183 | 183 | if ( ! $current_user ) { |
| 184 | 184 | return false; |
| 185 | 185 | } |
| @@ -298,8 +298,19 @@ | ||
| 298 | 298 | if ( ! user_can_activitypub( $user_id ) ) { |
| 299 | 299 | return false; |
| 300 | 300 | } |
| 301 | 301 | |
| 302 | + /* | |
| 303 | + * Do not federate brand-new comments on a post that is not federated itself | |
| 304 | + * (e.g. a private post, a post switched to local visibility, or a non-ActivityPub | |
| 305 | + * post type). This prevents leaking replies on content the post type's read rules | |
| 306 | + * would otherwise protect. Comments that were already sent are allowed through so | |
| 307 | + * their Update and Delete activities can still federate (and tear down remote copies). | |
| 308 | + */ | |
| 309 | + if ( ! self::was_sent( $comment ) && ! is_post_federated( $comment->comment_post_ID ) ) { | |
| 310 | + return false; | |
| 311 | + } | |
| 312 | + | |
| 302 | 313 | // It is a comment to the post and can be federated. |
| 303 | 314 | if ( empty( $comment->comment_parent ) ) { |
| 304 | 315 | return true; |
| 305 | 316 | } |
| @@ -312,22 +323,33 @@ | ||
| 312 | 323 | |
| 313 | 324 | /** |
| 314 | 325 | * Examine a comment ID and look up an existing comment it represents. |
| 315 | 326 | * |
| 316 | - * @param string $id ActivityPub object ID (usually a URL) to check. | |
| 327 | + * @since 9.1.0 Added the `$args` parameter. | |
| 317 | 328 | * |
| 329 | + * @param string $id ActivityPub object ID (usually a URL) to check. | |
| 330 | + * @param array $args Optional. Additional WP_Comment_Query arguments. Pass `array( 'status' => 'any' )` | |
| 331 | + * to also match comments in spam or trash, which the default status excludes. | |
| 332 | + * | |
| 318 | 333 | * @return \WP_Comment|false Comment object, or false on failure. |
| 319 | 334 | */ |
| 320 | - public static function object_id_to_comment( $id ) { | |
| 321 | - $comment_query = new \WP_Comment_Query( | |
| 335 | + public static function object_id_to_comment( $id, $args = array() ) { | |
| 336 | + $args = \wp_parse_args( | |
| 337 | + $args, | |
| 322 | 338 | array( |
| 323 | - 'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key | |
| 324 | - 'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value | |
| 325 | - 'orderby' => 'comment_date', | |
| 326 | - 'order' => 'DESC', | |
| 339 | + 'number' => 1, | |
| 340 | + 'orderby' => 'comment_date', | |
| 341 | + 'order' => 'DESC', | |
| 327 | 342 | ) |
| 328 | 343 | ); |
| 329 | 344 | |
| 345 | + // Force the lookup key and full comment objects, so callers cannot break the return contract. | |
| 346 | + $args['fields'] = 'all'; | |
| 347 | + $args['meta_key'] = 'source_id'; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key | |
| 348 | + $args['meta_value'] = $id; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value | |
| 349 | + | |
| 350 | + $comment_query = new \WP_Comment_Query( $args ); | |
| 351 | + | |
| 330 | 352 | if ( ! $comment_query->comments ) { |
| 331 | 353 | return false; |
| 332 | 354 | } |
| 333 | 355 | |
| @@ -381,9 +403,9 @@ | ||
| 381 | 403 | |
| 382 | 404 | $query = new \WP_Comment_Query(); |
| 383 | 405 | $comments = $query->query( $args ); |
| 384 | 406 | |
| 385 | - if ( $comments && is_array( $comments ) ) { | |
| 407 | + if ( $comments && \is_array( $comments ) ) { | |
| 386 | 408 | return $comments[0]->comment_ID; |
| 387 | 409 | } |
| 388 | 410 | |
| 389 | 411 | return null; |
| @@ -399,9 +421,9 @@ | ||
| 399 | 421 | * @return string[] An array of classes. |
| 400 | 422 | */ |
| 401 | 423 | public static function comment_class( $classes, $css_class, $comment_id ) { |
| 402 | 424 | // Check if ActivityPub comment. |
| 403 | - if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) { | |
| 425 | + if ( 'activitypub' === \get_comment_meta( $comment_id, 'protocol', true ) ) { | |
| 404 | 426 | $classes[] = 'activitypub-comment'; |
| 405 | 427 | } |
| 406 | 428 | |
| 407 | 429 | return $classes; |
| @@ -429,12 +451,11 @@ | ||
| 429 | 451 | |
| 430 | 452 | if ( \in_array( $comment_type, $comment_types, true ) ) { |
| 431 | 453 | $where .= $wpdb->prepare( ' AND comment_type = %s', $comment_type ); |
| 432 | 454 | } else { |
| 433 | - $comment_types = \array_map( 'esc_sql', $comment_types ); | |
| 434 | - $placeholders = implode( ', ', array_fill( 0, count( $comment_types ), '%s' ) ); | |
| 455 | + $placeholders = \implode( ', ', \array_fill( 0, \count( $comment_types ), '%s' ) ); | |
| 435 | 456 | // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQL.NotPrepared |
| 436 | - $where .= $wpdb->prepare( sprintf( ' AND comment_type NOT IN (%s)', $placeholders ), ...$comment_types ); | |
| 457 | + $where .= $wpdb->prepare( \sprintf( ' AND comment_type NOT IN (%s)', $placeholders ), ...$comment_types ); | |
| 437 | 458 | } |
| 438 | 459 | |
| 439 | 460 | return $where; |
| 440 | 461 | } |
| @@ -564,9 +585,9 @@ | ||
| 564 | 585 | $activity_type = \sanitize_key( $activity_type ); |
| 565 | 586 | $comment_types = self::get_comment_types(); |
| 566 | 587 | |
| 567 | 588 | foreach ( $comment_types as $comment_type ) { |
| 568 | - if ( in_array( $activity_type, $comment_type['activity_types'], true ) ) { | |
| 589 | + if ( \in_array( $activity_type, $comment_type['activity_types'], true ) ) { | |
| 569 | 590 | return $comment_type; |
| 570 | 591 | } |
| 571 | 592 | } |
| 572 | 593 | |
| @@ -605,15 +626,15 @@ | ||
| 605 | 626 | * |
| 606 | 627 | * @return array The registered custom comment type slugs. |
| 607 | 628 | */ |
| 608 | 629 | public static function get_comment_type_slugs() { |
| 609 | - if ( ! did_action( 'init' ) ) { | |
| 610 | - _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' ); | |
| 630 | + if ( ! \did_action( 'init' ) ) { | |
| 631 | + \_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' ); | |
| 611 | 632 | |
| 612 | 633 | return array(); |
| 613 | 634 | } |
| 614 | 635 | |
| 615 | - return array_keys( self::get_comment_types() ); | |
| 636 | + return \array_keys( self::get_comment_types() ); | |
| 616 | 637 | } |
| 617 | 638 | |
| 618 | 639 | /** |
| 619 | 640 | * Get the custom comment type. |
| @@ -627,16 +648,16 @@ | ||
| 627 | 648 | * |
| 628 | 649 | * @return array The comment type. |
| 629 | 650 | */ |
| 630 | 651 | public static function get_comment_type( $type ) { |
| 631 | - $type = strtolower( $type ); | |
| 632 | - $type = sanitize_key( $type ); | |
| 652 | + $type = \strtolower( $type ); | |
| 653 | + $type = \sanitize_key( $type ); | |
| 633 | 654 | |
| 634 | 655 | $comment_types = self::get_comment_types(); |
| 635 | 656 | $type_array = array(); |
| 636 | 657 | |
| 637 | 658 | // Check array keys. |
| 638 | - if ( in_array( $type, array_keys( $comment_types ), true ) ) { | |
| 659 | + if ( \in_array( $type, \array_keys( $comment_types ), true ) ) { | |
| 639 | 660 | $type_array = $comment_types[ $type ]; |
| 640 | 661 | } |
| 641 | 662 | |
| 642 | 663 | /** |
| @@ -643,9 +664,9 @@ | ||
| 643 | 664 | * Filter the comment type. |
| 644 | 665 | * |
| 645 | 666 | * @param array $type_array The comment type. |
| 646 | 667 | */ |
| 647 | - return apply_filters( "activitypub_comment_type_{$type}", $type_array ); | |
| 668 | + return \apply_filters( "activitypub_comment_type_{$type}", $type_array ); | |
| 648 | 669 | } |
| 649 | 670 | |
| 650 | 671 | /** |
| 651 | 672 | * Get a comment type attribute. |
| @@ -669,9 +690,9 @@ | ||
| 669 | 690 | * |
| 670 | 691 | * @param mixed $value The value of the attribute. |
| 671 | 692 | * @param string $type The comment type. |
| 672 | 693 | */ |
| 673 | - return apply_filters( "activitypub_comment_type_{$attr}", $value, $type ); | |
| 694 | + return \apply_filters( "activitypub_comment_type_{$attr}", $value, $type ); | |
| 674 | 695 | } |
| 675 | 696 | |
| 676 | 697 | /** |
| 677 | 698 | * Register the comment types used by the ActivityPub plugin. |
| @@ -679,10 +700,10 @@ | ||
| 679 | 700 | public static function register_comment_types() { |
| 680 | 701 | register_comment_type( |
| 681 | 702 | 'repost', |
| 682 | 703 | array( |
| 683 | - 'label' => __( 'Reposts', 'activitypub' ), | |
| 684 | - 'singular' => __( 'Repost', 'activitypub' ), | |
| 704 | + 'label' => \__( 'Reposts', 'activitypub' ), | |
| 705 | + 'singular' => \__( 'Repost', 'activitypub' ), | |
| 685 | 706 | '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.', |
| 686 | 707 | 'icon' => '♻️', |
| 687 | 708 | 'class' => 'p-repost', |
| 688 | 709 | 'type' => 'repost', |
| @@ -687,13 +708,13 @@ | ||
| 687 | 708 | 'class' => 'p-repost', |
| 688 | 709 | 'type' => 'repost', |
| 689 | 710 | 'collection' => 'reposts', |
| 690 | 711 | 'activity_types' => array( 'announce' ), |
| 691 | - 'excerpt' => html_entity_decode( \__( '… reposted this!', 'activitypub' ) ), | |
| 712 | + 'excerpt' => \html_entity_decode( \__( '… reposted this!', 'activitypub' ) ), | |
| 692 | 713 | /* translators: %d: Number of reposts */ |
| 693 | - 'count_single' => _x( '%d repost', 'number of reposts', 'activitypub' ), | |
| 714 | + 'count_single' => \_x( '%d repost', 'number of reposts', 'activitypub' ), | |
| 694 | 715 | /* translators: %d: Number of reposts */ |
| 695 | - 'count_plural' => _x( '%d reposts', 'number of reposts', 'activitypub' ), | |
| 716 | + 'count_plural' => \_x( '%d reposts', 'number of reposts', 'activitypub' ), | |
| 696 | 717 | ) |
| 697 | 718 | ); |
| 698 | 719 | |
| 699 | 720 | register_comment_type( |
| @@ -698,10 +719,10 @@ | ||
| 698 | 719 | |
| 699 | 720 | register_comment_type( |
| 700 | 721 | 'like', |
| 701 | 722 | array( |
| 702 | - 'label' => __( 'Likes', 'activitypub' ), | |
| 703 | - 'singular' => __( 'Like', 'activitypub' ), | |
| 723 | + 'label' => \__( 'Likes', 'activitypub' ), | |
| 724 | + 'singular' => \__( 'Like', 'activitypub' ), | |
| 704 | 725 | 'description' => 'A like is a small positive reaction that shows appreciation for a post without sharing it further.', |
| 705 | 726 | 'icon' => '👍', |
| 706 | 727 | 'class' => 'p-like', |
| 707 | 728 | 'type' => 'like', |
| @@ -706,13 +727,13 @@ | ||
| 706 | 727 | 'class' => 'p-like', |
| 707 | 728 | 'type' => 'like', |
| 708 | 729 | 'collection' => 'likes', |
| 709 | 730 | 'activity_types' => array( 'like' ), |
| 710 | - 'excerpt' => html_entity_decode( \__( '… liked this!', 'activitypub' ) ), | |
| 731 | + 'excerpt' => \html_entity_decode( \__( '… liked this!', 'activitypub' ) ), | |
| 711 | 732 | /* translators: %d: Number of likes */ |
| 712 | - 'count_single' => _x( '%d like', 'number of likes', 'activitypub' ), | |
| 733 | + 'count_single' => \_x( '%d like', 'number of likes', 'activitypub' ), | |
| 713 | 734 | /* translators: %d: Number of likes */ |
| 714 | - 'count_plural' => _x( '%d likes', 'number of likes', 'activitypub' ), | |
| 735 | + 'count_plural' => \_x( '%d likes', 'number of likes', 'activitypub' ), | |
| 715 | 736 | ) |
| 716 | 737 | ); |
| 717 | 738 | |
| 718 | 739 | register_comment_type( |
| @@ -717,10 +738,10 @@ | ||
| 717 | 738 | |
| 718 | 739 | register_comment_type( |
| 719 | 740 | 'quote', |
| 720 | 741 | array( |
| 721 | - 'label' => __( 'Quotes', 'activitypub' ), | |
| 722 | - 'singular' => __( 'Quote', 'activitypub' ), | |
| 742 | + 'label' => \__( 'Quotes', 'activitypub' ), | |
| 743 | + 'singular' => \__( 'Quote', 'activitypub' ), | |
| 723 | 744 | 'description' => 'A quote is when a post is shared along with an added comment, so the original post appears together with the sharer’s own words.', |
| 724 | 745 | 'icon' => '❞', |
| 725 | 746 | 'class' => 'p-quote', |
| 726 | 747 | 'type' => 'quote', |
| @@ -725,13 +746,13 @@ | ||
| 725 | 746 | 'class' => 'p-quote', |
| 726 | 747 | 'type' => 'quote', |
| 727 | 748 | 'collection' => 'quotes', |
| 728 | 749 | 'activity_types' => array( 'quote' ), |
| 729 | - 'excerpt' => html_entity_decode( \__( '… quoted this!', 'activitypub' ) ), | |
| 750 | + 'excerpt' => \html_entity_decode( \__( '… quoted this!', 'activitypub' ) ), | |
| 730 | 751 | /* translators: %d: Number of quotes */ |
| 731 | - 'count_single' => _x( '%d quote', 'number of quotes', 'activitypub' ), | |
| 752 | + 'count_single' => \_x( '%d quote', 'number of quotes', 'activitypub' ), | |
| 732 | 753 | /* translators: %d: Number of quotes */ |
| 733 | - 'count_plural' => _x( '%d quotes', 'number of quotes', 'activitypub' ), | |
| 754 | + 'count_plural' => \_x( '%d quotes', 'number of quotes', 'activitypub' ), | |
| 734 | 755 | ) |
| 735 | 756 | ); |
| 736 | 757 | } |
| 737 | 758 | |
| @@ -743,11 +764,11 @@ | ||
| 743 | 764 | * @return array show avatars on Activities |
| 744 | 765 | */ |
| 745 | 766 | public static function get_avatar_comment_types( $types ) { |
| 746 | 767 | $comment_types = self::get_comment_type_slugs(); |
| 747 | - $types = array_merge( $types, $comment_types ); | |
| 768 | + $types = \array_merge( $types, $comment_types ); | |
| 748 | 769 | |
| 749 | - return array_unique( $types ); | |
| 770 | + return \array_unique( $types ); | |
| 750 | 771 | } |
| 751 | 772 | |
| 752 | 773 | /** |
| 753 | 774 | * Excludes likes and reposts from comment queries. |
| @@ -763,9 +784,9 @@ | ||
| 763 | 784 | return; |
| 764 | 785 | } |
| 765 | 786 | |
| 766 | 787 | // Do not exclude likes and reposts on ActivityPub requests. |
| 767 | - if ( defined( 'ACTIVITYPUB_REQUEST' ) && ACTIVITYPUB_REQUEST ) { | |
| 788 | + if ( \defined( 'ACTIVITYPUB_REQUEST' ) && ACTIVITYPUB_REQUEST ) { | |
| 768 | 789 | return; |
| 769 | 790 | } |
| 770 | 791 | |
| 771 | 792 | // Do not exclude likes and reposts on REST requests (handled by rest_comment_query). |
| @@ -921,9 +942,9 @@ | ||
| 921 | 942 | * @return int|null The updated comment count, or null to use the default query. |
| 922 | 943 | */ |
| 923 | 944 | public static function pre_wp_update_comment_count_now( $new_count, $old_count, $post_id ) { |
| 924 | 945 | if ( null === $new_count ) { |
| 925 | - $excluded_types = array_filter( self::get_comment_type_slugs(), array( self::class, 'is_comment_type_enabled' ) ); | |
| 946 | + $excluded_types = \array_filter( self::get_comment_type_slugs(), array( self::class, 'is_comment_type_enabled' ) ); | |
| 926 | 947 | |
| 927 | 948 | if ( ! empty( $excluded_types ) ) { |
| 928 | 949 | /* |
| 929 | 950 | * Include 'note' type when Gutenberg's filter is registered, so a |
| @@ -945,14 +966,14 @@ | ||
| 945 | 966 | * @param string[] $excluded_types The comment type slugs to exclude. |
| 946 | 967 | * @param int $post_id The post ID. |
| 947 | 968 | */ |
| 948 | 969 | $excluded_types = \apply_filters( 'activitypub_excluded_comment_types', $excluded_types, $post_id ); |
| 949 | - $excluded_types = array_unique( array_filter( $excluded_types ) ); | |
| 970 | + $excluded_types = \array_unique( \array_filter( $excluded_types ) ); | |
| 950 | 971 | |
| 951 | 972 | global $wpdb; |
| 952 | 973 | |
| 953 | 974 | // phpcs:ignore WordPress.DB |
| 954 | - $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 ) ); | |
| 975 | + $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 ) ); | |
| 955 | 976 | } |
| 956 | 977 | } |
| 957 | 978 | |
| 958 | 979 | return $new_count; |
| @@ -964,9 +985,9 @@ | ||
| 964 | 985 | * @param string $comment_type The comment type. |
| 965 | 986 | * @return bool True if the comment type is enabled. |
| 966 | 987 | */ |
| 967 | 988 | public static function is_comment_type_enabled( $comment_type ) { |
| 968 | - return '1' === get_option( "activitypub_allow_{$comment_type}s", '1' ); | |
| 989 | + return '1' === \get_option( "activitypub_allow_{$comment_type}s", '1' ); | |
| 969 | 990 | } |
| 970 | 991 | |
| 971 | 992 | /** |
| 972 | 993 | * Get post types to hide comments for in admin. |