Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 71
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCom_Comments_Likes
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 9
600
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 get_instance
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 init
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 is_comment_likes_enabled
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 do_comment_like_api_request
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 add_like_class
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 enable_likes
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_scripts
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 register_like_api
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php // phpcs:ignore
2/**
3 * Plugin Name: Jetpack MU WPCom Comment Likes
4 * Description: Adds a "Like" action to comment rows and enqueues the necessary assets in the admin area.
5 *
6 * @package automattic/jetpack-mu-wpcom
7 */
8
9if ( ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) && ( ! defined( 'IS_ATOMIC' ) || ! IS_ATOMIC ) ) {
10    return;
11}
12
13/**
14 * WPCom Comments Likes functionality in a singleton pattern.
15 */
16class WPCom_Comments_Likes {
17    /**
18     * Singleton instance.
19     *
20     * @var WPCom_Comments_Likes
21     */
22    private static $instance = null;
23
24    /**
25     * Flag to track if hooks have been initialized.
26     *
27     * @var bool
28     */
29    private $initialized = false;
30
31    /**
32     * Private constructor to prevent direct instantiation.
33     */
34    private function __construct() {
35        // Register REST API for Atomic sites.
36        if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) {
37            add_action( 'rest_api_init', array( $this, 'register_like_api' ) );
38        }
39
40        add_action( 'current_screen', array( $this, 'init' ) );
41    }
42
43    /**
44     * Get the singleton instance.
45     *
46     * @return WPCom_Comments_Likes
47     */
48    public static function get_instance() {
49        if ( null === self::$instance ) {
50            self::$instance = new self();
51        }
52        return self::$instance;
53    }
54
55    /**
56     * Initialize the comment likes functionality if enabled.
57     */
58    public function init() {
59        // Only initialize if comment likes are enabled.
60        if ( ! $this->is_comment_likes_enabled() ) {
61            return;
62        }
63
64        if ( ! is_admin() ) {
65            return;
66        }
67
68        $screen = get_current_screen();
69        if ( ! $screen || 'edit-comments' !== $screen->id ) {
70            return;
71        }
72
73        add_filter( 'comment_class', array( $this, 'add_like_class' ), 10, 3 );
74        add_filter( 'comment_row_actions', array( $this, 'enable_likes' ), 10, 2 );
75        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ), 10, 2 );
76    }
77
78    /**
79     * Check if comment likes are enabled.
80     *
81     * This is currently only expected to work for WoW sites.
82     *
83     * @return bool True if comment likes are enabled, false otherwise.
84     */
85    private function is_comment_likes_enabled() {
86        // @phan-suppress-next-line PhanUndeclaredClassReference
87        if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'is_module_active' ) ) {
88            // @phan-suppress-next-line PhanUndeclaredClassMethod
89            return Jetpack::is_module_active( 'comment-likes' );
90        }
91
92        return false;
93    }
94
95    /**
96     * Do a comment like API request.
97     *
98     * @param int $blog_id    The blog ID.
99     * @param int $comment_id The comment ID.
100     * @return bool|WP_Error True if the comment is liked, false otherwise, or a WP_Error if the request fails.
101     */
102    private function do_comment_like_api_request( $blog_id, $comment_id ) {
103        $response = Automattic\Jetpack\Connection\Client::wpcom_json_api_request_as_user(
104            "/sites/$blog_id/comments/$comment_id/likes",
105            'v1.1',
106            array( 'method' => 'GET' ),
107            null,
108            'rest'
109        );
110
111        // If the request fails, simply return the unmodified classes.
112        if ( is_wp_error( $response ) ) {
113            return $response;
114        }
115
116        $response_data = json_decode( wp_remote_retrieve_body( $response ), true );
117
118        return ! empty( $response_data['i_like'] ?? false );
119    }
120
121    /**
122     * Adds a "liked" class to comments that the current user has liked.
123     *
124     * @param array  $classes    Array of comment classes.
125     * @param string $css_class  Unused.
126     * @param int    $comment_id The comment ID.
127     * @return array Modified array of comment classes.
128     */
129    public function add_like_class( $classes, $css_class, $comment_id ) {
130        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
131            $blog_id = get_current_blog_id();
132            $liked   = Likes::comment_like_current_user_likes( $blog_id, $comment_id );
133        } else {
134            $blog_id = Jetpack_Options::get_option( 'id' );
135
136            $liked = $this->do_comment_like_api_request( $blog_id, $comment_id );
137
138            if ( is_wp_error( $liked ) ) {
139                return $classes;
140            }
141        }
142
143        // Append the 'liked' class if the comment is liked.
144        if ( $liked ) {
145            $classes[] = 'liked';
146        }
147
148        return $classes;
149    }
150
151    /**
152     * Adds "Like" and "Unlike" action buttons to comment rows.
153     *
154     * @param array      $actions Array of actions for the comment.
155     * @param WP_Comment $comment The comment object.
156     * @return array Modified actions array.
157     */
158    public function enable_likes( $actions, $comment ) {
159        $actions['like'] = sprintf(
160            '<button class="button-link comment-like-button" data-comment-id="%d" aria-label="%s">%s</button>',
161            $comment->comment_ID,
162            esc_attr__( 'Like this comment', 'jetpack-mu-wpcom' ),
163            esc_html__( 'Like', 'jetpack-mu-wpcom' )
164        );
165
166        $actions['unlike'] = sprintf(
167            '<button class="button-link comment-unlike-button" data-comment-id="%d" aria-label="%s">%s</button>',
168            $comment->comment_ID,
169            esc_attr__( 'Unlike this comment', 'jetpack-mu-wpcom' ),
170            esc_html__( 'Liked by you', 'jetpack-mu-wpcom' )
171        );
172
173        return $actions;
174    }
175
176    /**
177     * Enqueues the comment like assets (JavaScript and CSS) on the Edit Comments screen.
178     *
179     * @param string $hook The current admin page hook.
180     */
181    public function enqueue_scripts( $hook ) {
182        // Only enqueue assets on the edit-comments screen.
183        if ( 'edit-comments.php' !== $hook ) {
184            return;
185        }
186
187        // Enqueue the assets using the Jetpack MU WPCom helper function.
188        jetpack_mu_wpcom_enqueue_assets( 'wpcom-comment-like', array( 'js', 'css' ) );
189
190        // Localize the script with error messages.
191        wp_localize_script(
192            'jetpack-mu-wpcom-wpcom-comment-like',
193            'wpcomCommentLikesData',
194            array(
195                'post_like_error'     => __( 'Something went wrong when attempting to like that comment. Please try again.', 'jetpack-mu-wpcom' ),
196                'post_unlike_error'   => __( 'Something went wrong when attempting to unlike that comment. Please try again.', 'jetpack-mu-wpcom' ),
197                'dismiss_notice_text' => __( 'Dismiss this notice', 'jetpack-mu-wpcom' ),
198            )
199        );
200    }
201
202    /**
203     * Register an API to handle Likes on Atomic sites.
204     */
205    public function register_like_api() {
206        require_once __DIR__ . '/class-wp-rest-comment-like.php';
207        $controller = new WP_REST_Comment_Like();
208        $controller->register_routes();
209    }
210}
211
212WPCom_Comments_Likes::get_instance();