PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | json-endpoints/class.wpcom-json-api-list-comments-endpoint.php +396 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,396 @@
1 +<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 +
3 +if ( ! defined( 'ABSPATH' ) ) {
4 + exit( 0 );
5 +}
6 +
7 +/**
8 + * Comments Walker Class.
9 + */
10 +class WPCOM_JSON_API_List_Comments_Walker extends Walker {
11 +
12 + /**
13 + * Tree type.
14 + *
15 + * @var string
16 + */
17 + public $tree_type = 'comment';
18 +
19 + /**
20 + * Database fields.
21 + *
22 + * @var array
23 + */
24 + public $db_fields = array(
25 + 'parent' => 'comment_parent',
26 + 'id' => 'comment_ID',
27 + );
28 +
29 + /**
30 + * Start the element output.
31 + *
32 + * @param array $output - the output.
33 + * @param object $object - the object.
34 + * @param int $depth - depth.
35 + * @param array $args - the arguments.
36 + * @param int $current_object_id - the object ID.
37 + */
38 + public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
39 + if ( ! is_array( $output ) ) {
40 + $output = array();
41 + }
42 +
43 + $output[] = $object->comment_ID;
44 + }
45 +
46 + /**
47 + * Taken from WordPress's Walker_Comment::display_element()
48 + *
49 + * This function is designed to enhance Walker::display_element() to
50 + * display children of higher nesting levels than selected inline on
51 + * the highest depth level displayed. This prevents them being orphaned
52 + * at the end of the comment list.
53 + *
54 + * Example: max_depth = 2, with 5 levels of nested content.
55 + * 1
56 + * 1.1
57 + * 1.1.1
58 + * 1.1.1.1
59 + * 1.1.1.1.1
60 + * 1.1.2
61 + * 1.1.2.1
62 + * 2
63 + * 2.2
64 + *
65 + * @see Walker_Comment::display_element()
66 + * @see Walker::display_element()
67 + * @see wp_list_comments()
68 + *
69 + * @param object $element — Data object.
70 + * @param array $children_elements - List of elements to continue traversing (passed by reference).
71 + * @param int $max_depth — Max depth to traverse.
72 + * @param int $depth — Depth of current element.
73 + * @param array $args — An array of arguments.
74 + * @param string $output — Used to append additional content (passed by reference).
75 + */
76 + public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
77 +
78 + if ( ! $element ) {
79 + return;
80 + }
81 +
82 + $id_field = $this->db_fields['id'];
83 + $id = $element->$id_field;
84 +
85 + parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
86 +
87 + // If we're at the max depth, and the current element still has children, loop over those and display them at this level
88 + // This is to prevent them being orphaned to the end of the list.
89 + if ( $max_depth <= $depth + 1 && isset( $children_elements[ $id ] ) ) {
90 + foreach ( $children_elements[ $id ] as $child ) {
91 + $this->display_element( $child, $children_elements, $max_depth, $depth, $args, $output );
92 + }
93 +
94 + unset( $children_elements[ $id ] );
95 + }
96 + }
97 +}
98 +
99 +new WPCOM_JSON_API_List_Comments_Endpoint(
100 + array(
101 + 'description' => 'Get a list of recent comments.',
102 + 'group' => 'comments',
103 + 'stat' => 'comments',
104 +
105 + 'method' => 'GET',
106 + 'path' => '/sites/%s/comments/',
107 + 'path_labels' => array(
108 + '$site' => '(int|string) Site ID or domain',
109 + ),
110 +
111 + 'allow_fallback_to_jetpack_blog_token' => true,
112 +
113 + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/comments/?number=2',
114 + )
115 +);
116 +
117 +new WPCOM_JSON_API_List_Comments_Endpoint(
118 + array(
119 + 'description' => 'Get a list of recent comments on a post.',
120 + 'group' => 'comments',
121 + 'stat' => 'posts:1:replies',
122 +
123 + 'method' => 'GET',
124 + 'path' => '/sites/%s/posts/%d/replies/',
125 + 'path_labels' => array(
126 + '$site' => '(int|string) Site ID or domain',
127 + '$post_ID' => '(int) The post ID',
128 + ),
129 +
130 + 'allow_fallback_to_jetpack_blog_token' => true,
131 +
132 + 'example_request' => 'https://public-api.wordpress.com/rest/v1/sites/en.blog.wordpress.com/posts/7/replies/?number=2',
133 + )
134 +);
135 +
136 +/**
137 + * List comment endpoint.
138 + *
139 + * /sites/%s/comments/ -> $blog_id
140 + * /sites/%s/posts/%d/replies/ -> $blog_id, $post_id
141 + * /sites/%s/comments/%d/replies/ -> $blog_id, $comment_id
142 + *
143 + * @todo permissions
144 + *
145 + * @phan-constructor-used-for-side-effects
146 + */
147 +class WPCOM_JSON_API_List_Comments_Endpoint extends WPCOM_JSON_API_Comment_Endpoint { // phpcs:ignore
148 +
149 + /**
150 + * The response format.
151 + *
152 + * @var array
153 + */
154 + public $response_format = array(
155 + 'found' => '(int) The total number of comments found that match the request (ignoring limits, offsets, and pagination).',
156 + 'site_ID' => '(int) The site ID',
157 + 'comments' => '(array:comment) An array of comment objects.',
158 + );
159 +
160 + /**
161 + * Constructor function.
162 + *
163 + * @param array $args - the arguments.
164 + */
165 + public function __construct( $args ) {
166 + parent::__construct( $args );
167 + $this->query = array_merge(
168 + $this->query,
169 + array(
170 + 'number' => '(int=20) The number of comments to return. Limit: 100. When using hierarchical=1, number refers to the number of top-level comments returned.',
171 + 'offset' => '(int=0) 0-indexed offset. Not available if using hierarchical=1.',
172 + 'page' => '(int) Return the Nth 1-indexed page of comments. Takes precedence over the <code>offset</code> parameter. When using hierarchical=1, pagination is a bit different. See the note on the number parameter.',
173 + 'order' => array(
174 + 'DESC' => 'Return comments in descending order from newest to oldest.',
175 + 'ASC' => 'Return comments in ascending order from oldest to newest.',
176 + ),
177 + 'hierarchical' => array(
178 + 'false' => '',
179 + 'true' => '(BETA) Order the comment list hierarchically.',
180 + ),
181 + 'after' => '(ISO 8601 datetime) Return comments dated on or after the specified datetime. Not available if using hierarchical=1.',
182 + 'before' => '(ISO 8601 datetime) Return comments dated on or before the specified datetime. Not available if using hierarchical=1.',
183 + 'type' => array(
184 + 'any' => 'Return all comments regardless of type.',
185 + 'comment' => 'Return only regular comments.',
186 + 'trackback' => 'Return only trackbacks.',
187 + 'pingback' => 'Return only pingbacks.',
188 + 'pings' => 'Return both trackbacks and pingbacks.',
189 + ),
190 + 'status' => array(
191 + 'approved' => 'Return only approved comments.',
192 + 'unapproved' => 'Return only comments in the moderation queue.',
193 + 'spam' => 'Return only comments marked as spam.',
194 + 'trash' => 'Return only comments in the trash.',
195 + 'all' => 'Return comments of all statuses.',
196 + ),
197 + 'author_wpcom_data' => array(
198 + 'false' => 'Do not add wpcom_id and wpcom_login fields to comment author responses (default)',
199 + 'true' => 'Add wpcom_id and wpcom_login fields to comment author responses',
200 + ),
201 + )
202 + );
203 + }
204 +
205 + /**
206 + * The callback.
207 + *
208 + * @param string $path - the path.
209 + * @param int $blog_id - the blog ID.
210 + * @param int $object_id - the object ID.
211 + */
212 + public function callback( $path = '', $blog_id = 0, $object_id = 0 ) {
213 + $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
214 + if ( is_wp_error( $blog_id ) ) {
215 + return $blog_id;
216 + }
217 +
218 + $args = $this->query_args();
219 +
220 + if ( $args['number'] < 1 ) {
221 + $args['number'] = 20;
222 + } elseif ( 100 < $args['number'] ) {
223 + return new WP_Error( 'invalid_number', 'The NUMBER parameter must be less than or equal to 100.', 400 );
224 + }
225 +
226 + if ( str_contains( $path, '/posts/' ) ) {
227 + // We're looking for comments of a particular post.
228 + $post_id = $object_id;
229 + $comment_id = 0;
230 + } else {
231 + // We're looking for comments for the whole blog, or replies to a single comment.
232 + $comment_id = $object_id;
233 + $post_id = 0;
234 + }
235 +
236 + // We can't efficiently get the number of replies to a single comment.
237 + $count = false;
238 + $found = -1;
239 +
240 + if ( ! $comment_id ) {
241 + // We can get comment counts for the whole site or for a single post, but only for certain queries.
242 + if ( 'any' === $args['type'] && ! isset( $args['after'] ) && ! isset( $args['before'] ) ) {
243 + $count = $this->api->wp_count_comments( $post_id );
244 + }
245 + }
246 +
247 + switch ( $args['status'] ) {
248 + case 'approved':
249 + $status = 'approve';
250 + if ( $count ) {
251 + $found = $count->approved;
252 + }
253 + break;
254 + default:
255 + if ( ! current_user_can( 'edit_posts' ) ) {
256 + return new WP_Error( 'unauthorized', 'User cannot read non-approved comments', 403 );
257 + }
258 + if ( 'unapproved' === $args['status'] ) {
259 + $status = 'hold';
260 + $count_status = 'moderated';
261 + } elseif ( 'all' === $args['status'] ) {
262 + $status = 'all';
263 + $count_status = 'total_comments';
264 + } else {
265 + $status = $args['status'];
266 + $count_status = $args['status'];
267 + }
268 + if ( $count ) {
269 + $found = $count->$count_status;
270 + }
271 + }
272 +
273 + /** This filter is documented in class.json-api.php */
274 + $exclude = apply_filters(
275 + 'jetpack_api_exclude_comment_types',
276 + array( 'order_note', 'webhook_delivery', 'review', 'action_log' )
277 + );
278 +
279 + $query = array(
280 + 'order' => $args['order'],
281 + 'type' => 'any' === $args['type'] ? false : $args['type'],
282 + 'status' => $status,
283 + 'type__not_in' => $exclude,
284 + );
285 +
286 + if ( isset( $args['page'] ) ) {
287 + if ( $args['page'] < 1 ) {
288 + $args['page'] = 1;
289 + }
290 + } elseif ( $args['offset'] < 0 ) {
291 + $args['offset'] = 0;
292 + }
293 +
294 + if ( ! $args['hierarchical'] ) {
295 + $query['number'] = $args['number'];
296 +
297 + if ( isset( $args['page'] ) ) {
298 + $query['offset'] = ( $args['page'] - 1 ) * $args['number'];
299 + } else {
300 + $query['offset'] = $args['offset'];
301 + }
302 +
303 + $is_before = isset( $args['before_gmt'] );
304 + $is_after = isset( $args['after_gmt'] );
305 +
306 + if ( $is_before || $is_after ) {
307 + $query['date_query'] = array(
308 + 'column' => 'comment_date_gmt',
309 + 'inclusive' => true,
310 + );
311 +
312 + if ( $is_before ) {
313 + $query['date_query']['before'] = $args['before_gmt'];
314 + }
315 +
316 + if ( $is_after ) {
317 + $query['date_query']['after'] = $args['after_gmt'];
318 + }
319 + }
320 + }
321 +
322 + if ( $post_id ) {
323 + $post = get_post( $post_id );
324 + if ( ! $post || is_wp_error( $post ) ) {
325 + return new WP_Error( 'unknown_post', 'Unknown post', 404 );
326 + }
327 + $query['post_id'] = $post->ID;
328 + if ( $this->api->ends_with( $this->path, '/replies' ) ) {
329 + $query['parent'] = 0;
330 + }
331 + } elseif ( $comment_id ) {
332 + $comment = get_comment( $comment_id );
333 + if ( ! $comment || is_wp_error( $comment ) ) {
334 + return new WP_Error( 'unknown_comment', 'Unknown comment', 404 );
335 + }
336 + $query['parent'] = $comment_id;
337 + }
338 +
339 + // A single post's count uses the comment_post_ID index; site-wide typed counts would not.
340 + if ( -1 === $found && $post_id ) {
341 + $count_query = $query;
342 + $count_query['count'] = true;
343 + unset( $count_query['number'], $count_query['offset'] );
344 + $found = (int) get_comments( $count_query );
345 + }
346 +
347 + if ( $args['hierarchical'] && $found > 5000 ) {
348 + // Massive comment thread found; don't pre-load comment metadata to reduce memory used.
349 + $query['update_comment_meta_cache'] = false;
350 + }
351 +
352 + $comments = get_comments( $query );
353 +
354 + if ( $args['hierarchical'] ) {
355 + $walker = new WPCOM_JSON_API_List_Comments_Walker();
356 + $comment_ids = $walker->paged_walk( $comments, get_option( 'thread_comments_depth', -1 ), $args['page'] ?? 1, $args['number'] );
357 + '@phan-var int[] $comment_ids';
358 + if ( ! empty( $comment_ids ) ) {
359 + $comments = array_map( 'get_comment', $comment_ids );
360 + }
361 + }
362 +
363 + $return = array();
364 +
365 + foreach ( array_keys( $this->response_format ) as $key ) {
366 + switch ( $key ) {
367 + case 'found':
368 + $return[ $key ] = (int) $found;
369 + break;
370 + case 'site_ID':
371 + $return[ $key ] = (int) $blog_id;
372 + break;
373 + case 'comments':
374 + $return_comments = array();
375 + if ( ! empty( $comments ) ) {
376 + foreach ( $comments as $comment ) {
377 + $the_comment = $this->get_comment( $comment->comment_ID, $args['context'] );
378 + if ( $the_comment && ! is_wp_error( $the_comment ) ) {
379 + $return_comments[] = $the_comment;
380 + }
381 + }
382 + }
383 +
384 + if ( $return_comments ) {
385 + /** This action is documented in json-endpoints/class.wpcom-json-api-site-settings-endpoint.php */
386 + do_action( 'wpcom_json_api_objects', 'comments', count( $return_comments ) );
387 + }
388 +
389 + $return[ $key ] = $return_comments;
390 + break;
391 + }
392 + }
393 +
394 + return $return;
395 + }
396 +}