Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 132
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_JSON_API_Comment_Endpoint
0.00% covered (danger)
0.00%
0 / 130
0.00% covered (danger)
0.00%
0 / 2
2756
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
6
 get_comment
0.00% covered (danger)
0.00%
0 / 127
0.00% covered (danger)
0.00%
0 / 1
2550
1<?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * Comment endpoint.
4 *
5 * @todo - can this file be written without overriding global variables?
6 *
7 * @phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited
8 */
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 );
12}
13
14/**
15 * Comment endpoint class.
16 */
17abstract class WPCOM_JSON_API_Comment_Endpoint extends WPCOM_JSON_API_Endpoint {
18    /**
19     * Comment object array.
20     *
21     * @var $comment_object_format
22     */
23    public $comment_object_format = array(
24        // explicitly document and cast all output.
25        'ID'           => '(int) The comment ID.',
26        'post'         => "(object>post_reference) A reference to the comment's post.",
27        'author'       => '(object>author) The author of the comment.',
28        'date'         => "(ISO 8601 datetime) The comment's creation time.",
29        'URL'          => '(URL) The full permalink URL to the comment.',
30        'short_URL'    => '(URL) The wp.me short URL.',
31        'content'      => '(HTML) <code>context</code> dependent.',
32        'raw_content'  => '(string) Raw comment content.',
33        'status'       => array(
34            'approved'   => 'The comment has been approved.',
35            'unapproved' => 'The comment has been held for review in the moderation queue.',
36            'spam'       => 'The comment has been marked as spam.',
37            'trash'      => 'The comment is in the trash.',
38        ),
39        'parent'       => "(object>comment_reference|false) A reference to the comment's parent, if it has one.",
40        'type'         => array(
41            'comment'   => 'The comment is a regular comment.',
42            'trackback' => 'The comment is a trackback.',
43            'pingback'  => 'The comment is a pingback.',
44            'review'    => 'The comment is a product review.',
45        ),
46        'like_count'   => '(int) The number of likes for this comment.',
47        'i_like'       => '(bool) Does the current user like this comment?',
48        'meta'         => '(object) Meta data',
49        'can_moderate' => '(bool) Whether current user can moderate the comment.',
50        'i_replied'    => '(bool) Has the current user replied to this comment?',
51    );
52
53    /**
54     * Class constructor.
55     *
56     * @param object $args - arguments passed to constructor.
57     */
58    public function __construct( $args ) {
59        if ( ! $this->response_format ) {
60            $this->response_format =& $this->comment_object_format;
61        }
62        parent::__construct( $args );
63    }
64
65    /**
66     * Get the comment.
67     *
68     * @param int    $comment_id - the ID of the comment.
69     * @param string $context - the context of the comment (displayed or edited).
70     */
71    public function get_comment( $comment_id, $context ) {
72        global $blog_id;
73
74        $comment = get_comment( $comment_id );
75        if ( ! $comment || is_wp_error( $comment ) ) {
76            return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
77        }
78
79        /**
80         * Filter the comment types that are allowed to be returned.
81         *
82         * @since 14.2
83         *
84         * @module json-api
85         *
86         * @param array $types Array of comment types.
87         */
88        $types = apply_filters( 'jetpack_json_api_comment_types', array( '', 'comment', 'pingback', 'trackback', 'review' ) );
89
90        // @todo - can we make this comparison strict without breaking anything?
91        // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
92        if ( ! in_array( $comment->comment_type, $types ) ) {
93            return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
94        }
95
96        $post = get_post( $comment->comment_post_ID );
97        if ( ! $post || is_wp_error( $post ) ) {
98            return new WP_Error( 'unknown_post', 'Unknown post', 404 );
99        }
100
101        $status = wp_get_comment_status( $comment->comment_ID );
102
103        // Permissions.
104        switch ( $context ) {
105            case 'edit':
106                if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
107                    return new WP_Error( 'unauthorized', 'User cannot edit comment', 403 );
108                }
109
110                $GLOBALS['post'] = $post;
111                $comment         = get_comment_to_edit( $comment->comment_ID );
112                foreach ( array( 'comment_author', 'comment_author_email', 'comment_author_url' ) as $field ) {
113                    $comment->$field = htmlspecialchars_decode( $comment->$field, ENT_QUOTES );
114                }
115                break;
116            case 'display':
117                if ( 'approved' !== $status ) {
118                    $current_user_id       = get_current_user_id();
119                    $user_can_read_comment = false;
120                    // @todo - can we make this comparison strict without breaking anything?
121                    // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
122                    if ( $current_user_id && $comment->user_id && $current_user_id == $comment->user_id ) {
123                        $user_can_read_comment = true;
124                    } elseif (
125                    $comment->comment_author_email && $comment->comment_author
126                    &&
127                    isset( $this->api->token_details['user'] )
128                    &&
129                    isset( $this->api->token_details['user']['user_email'] )
130                    &&
131                    $this->api->token_details['user']['user_email'] === $comment->comment_author_email
132                    &&
133                    $this->api->token_details['user']['display_name'] === $comment->comment_author
134                    ) {
135                        $user_can_read_comment = true;
136                    } else {
137                        $user_can_read_comment = current_user_can( 'edit_posts' );
138                    }
139
140                    if ( ! $user_can_read_comment ) {
141                        return new WP_Error( 'unauthorized', 'User cannot read unapproved comment', 403 );
142                    }
143                }
144
145                $GLOBALS['post'] = $post;
146                setup_postdata( $post );
147                break;
148            default:
149                return new WP_Error( 'invalid_context', 'Invalid API CONTEXT', 400 );
150        }
151
152        $can_view = $this->user_can_view_post( $post->ID );
153        if ( ! $can_view || is_wp_error( $can_view ) ) {
154            return $can_view;
155        }
156
157        $GLOBALS['comment'] = $comment;
158        $response           = array();
159
160        foreach ( array_keys( $this->comment_object_format ) as $key ) {
161            switch ( $key ) {
162                case 'ID':
163                    // explicitly cast all output.
164                    $response[ $key ] = (int) $comment->comment_ID;
165                    break;
166                case 'post':
167                    $response[ $key ] = (object) array(
168                        'ID'    => (int) $post->ID,
169                        'title' => (string) get_the_title( $post->ID ),
170                        'type'  => (string) $post->post_type,
171                        'link'  => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $post->ID ),
172                    );
173                    break;
174                case 'author':
175                    $response[ $key ] = (object) $this->get_author( $comment, current_user_can( 'edit_comment', $comment->comment_ID ) );
176                    break;
177                case 'date':
178                    $response[ $key ] = (string) $this->format_date( $comment->comment_date_gmt, $comment->comment_date );
179                    break;
180                case 'URL':
181                    $response[ $key ] = (string) esc_url_raw( get_comment_link( $comment->comment_ID ) );
182                    break;
183                case 'short_URL':
184                    // @todo - pagination
185                    $response[ $key ] = (string) esc_url_raw( wp_get_shortlink( $post->ID ) . "%23comment-{$comment->comment_ID}" );
186                    break;
187                case 'content':
188                    if ( 'display' === $context ) {
189                        ob_start();
190                        comment_text();
191                        $response[ $key ] = (string) ob_get_clean();
192                    } else {
193                        $response[ $key ] = (string) $comment->comment_content;
194                    }
195                    break;
196                case 'raw_content':
197                    $response[ $key ] = (string) $comment->comment_content;
198                    break;
199                case 'status':
200                    $response[ $key ] = (string) $status;
201                    break;
202                case 'parent': // May be object or false.
203                    $parent = $comment->comment_parent ? get_comment( $comment->comment_parent ) : null;
204                    if ( $parent ) {
205                        $response[ $key ] = (object) array(
206                            'ID'   => (int) $parent->comment_ID,
207                            'type' => (string) ( $parent->comment_type ? $parent->comment_type : 'comment' ),
208                            'link' => (string) $this->links->get_comment_link( $blog_id, $parent->comment_ID ),
209                        );
210                    } else {
211                        $response[ $key ] = false;
212                    }
213                    break;
214                case 'type':
215                    $response[ $key ] = (string) ( $comment->comment_type ? $comment->comment_type : 'comment' );
216                    break;
217                case 'like_count':
218                    if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
219                        $response[ $key ] = (int) $this->api->comment_like_count( $blog_id, $post->ID, $comment->comment_ID );
220                    }
221                    break;
222                case 'i_like':
223                    if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
224                        $response[ $key ] = (bool) Likes::comment_like_current_user_likes( $blog_id, (int) $comment->comment_ID );
225                    }
226                    break;
227                case 'meta':
228                    $response[ $key ] = (object) array(
229                        'links' => (object) array(
230                            'self'    => (string) $this->links->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID ),
231                            'help'    => (string) $this->links->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'help' ),
232                            'site'    => (string) $this->links->get_site_link( $this->api->get_blog_id_for_output() ),
233                            'post'    => (string) $this->links->get_post_link( $this->api->get_blog_id_for_output(), $comment->comment_post_ID ),
234                            'replies' => (string) $this->links->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'replies/' ),
235                            'likes'   => (string) $this->links->get_comment_link( $this->api->get_blog_id_for_output(), $comment->comment_ID, 'likes/' ),
236                        ),
237                    );
238                    break;
239                case 'can_moderate':
240                    $response[ $key ] = (bool) current_user_can( 'edit_comment', $comment_id );
241                    break;
242                case 'i_replied':
243                    $response[ $key ] = (bool) 0 < get_comments(
244                        array(
245                            'user_id' => get_current_user_id(),
246                            'parent'  => $comment->comment_ID,
247                            'count'   => true,
248                        )
249                    );
250                    break;
251            }
252        }
253
254        unset( $GLOBALS['comment'], $GLOBALS['post'] );
255        return $response;
256    }
257}