|
1 <?php |
|
2 /** |
|
3 * REST API: WP_REST_Comments_Controller class |
|
4 * |
|
5 * @package WordPress |
|
6 * @subpackage REST_API |
|
7 * @since 4.7.0 |
|
8 */ |
|
9 |
|
10 /** |
|
11 * Core controller used to access comments via the REST API. |
|
12 * |
|
13 * @since 4.7.0 |
|
14 * |
|
15 * @see WP_REST_Controller |
|
16 */ |
|
17 class WP_REST_Comments_Controller extends WP_REST_Controller { |
|
18 |
|
19 /** |
|
20 * Instance of a comment meta fields object. |
|
21 * |
|
22 * @since 4.7.0 |
|
23 * @var WP_REST_Comment_Meta_Fields |
|
24 */ |
|
25 protected $meta; |
|
26 |
|
27 /** |
|
28 * Constructor. |
|
29 * |
|
30 * @since 4.7.0 |
|
31 */ |
|
32 public function __construct() { |
|
33 $this->namespace = 'wp/v2'; |
|
34 $this->rest_base = 'comments'; |
|
35 |
|
36 $this->meta = new WP_REST_Comment_Meta_Fields(); |
|
37 } |
|
38 |
|
39 /** |
|
40 * Registers the routes for the objects of the controller. |
|
41 * |
|
42 * @since 4.7.0 |
|
43 */ |
|
44 public function register_routes() { |
|
45 |
|
46 register_rest_route( $this->namespace, '/' . $this->rest_base, array( |
|
47 array( |
|
48 'methods' => WP_REST_Server::READABLE, |
|
49 'callback' => array( $this, 'get_items' ), |
|
50 'permission_callback' => array( $this, 'get_items_permissions_check' ), |
|
51 'args' => $this->get_collection_params(), |
|
52 ), |
|
53 array( |
|
54 'methods' => WP_REST_Server::CREATABLE, |
|
55 'callback' => array( $this, 'create_item' ), |
|
56 'permission_callback' => array( $this, 'create_item_permissions_check' ), |
|
57 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), |
|
58 ), |
|
59 'schema' => array( $this, 'get_public_item_schema' ), |
|
60 ) ); |
|
61 |
|
62 register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( |
|
63 'args' => array( |
|
64 'id' => array( |
|
65 'description' => __( 'Unique identifier for the object.' ), |
|
66 'type' => 'integer', |
|
67 ), |
|
68 ), |
|
69 array( |
|
70 'methods' => WP_REST_Server::READABLE, |
|
71 'callback' => array( $this, 'get_item' ), |
|
72 'permission_callback' => array( $this, 'get_item_permissions_check' ), |
|
73 'args' => array( |
|
74 'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
|
75 'password' => array( |
|
76 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), |
|
77 'type' => 'string', |
|
78 ), |
|
79 ), |
|
80 ), |
|
81 array( |
|
82 'methods' => WP_REST_Server::EDITABLE, |
|
83 'callback' => array( $this, 'update_item' ), |
|
84 'permission_callback' => array( $this, 'update_item_permissions_check' ), |
|
85 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), |
|
86 ), |
|
87 array( |
|
88 'methods' => WP_REST_Server::DELETABLE, |
|
89 'callback' => array( $this, 'delete_item' ), |
|
90 'permission_callback' => array( $this, 'delete_item_permissions_check' ), |
|
91 'args' => array( |
|
92 'force' => array( |
|
93 'type' => 'boolean', |
|
94 'default' => false, |
|
95 'description' => __( 'Whether to bypass trash and force deletion.' ), |
|
96 ), |
|
97 'password' => array( |
|
98 'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ), |
|
99 'type' => 'string', |
|
100 ), |
|
101 ), |
|
102 ), |
|
103 'schema' => array( $this, 'get_public_item_schema' ), |
|
104 ) ); |
|
105 } |
|
106 |
|
107 /** |
|
108 * Checks if a given request has access to read comments. |
|
109 * |
|
110 * @since 4.7.0 |
|
111 * |
|
112 * @param WP_REST_Request $request Full details about the request. |
|
113 * @return WP_Error|bool True if the request has read access, error object otherwise. |
|
114 */ |
|
115 public function get_items_permissions_check( $request ) { |
|
116 |
|
117 if ( ! empty( $request['post'] ) ) { |
|
118 foreach ( (array) $request['post'] as $post_id ) { |
|
119 $post = get_post( $post_id ); |
|
120 |
|
121 if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) { |
|
122 return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
123 } elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { |
|
124 return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
125 } |
|
126 } |
|
127 } |
|
128 |
|
129 if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { |
|
130 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
131 } |
|
132 |
|
133 if ( ! current_user_can( 'edit_posts' ) ) { |
|
134 $protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' ); |
|
135 $forbidden_params = array(); |
|
136 |
|
137 foreach ( $protected_params as $param ) { |
|
138 if ( 'status' === $param ) { |
|
139 if ( 'approve' !== $request[ $param ] ) { |
|
140 $forbidden_params[] = $param; |
|
141 } |
|
142 } elseif ( 'type' === $param ) { |
|
143 if ( 'comment' !== $request[ $param ] ) { |
|
144 $forbidden_params[] = $param; |
|
145 } |
|
146 } elseif ( ! empty( $request[ $param ] ) ) { |
|
147 $forbidden_params[] = $param; |
|
148 } |
|
149 } |
|
150 |
|
151 if ( ! empty( $forbidden_params ) ) { |
|
152 return new WP_Error( 'rest_forbidden_param', sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) ); |
|
153 } |
|
154 } |
|
155 |
|
156 return true; |
|
157 } |
|
158 |
|
159 /** |
|
160 * Retrieves a list of comment items. |
|
161 * |
|
162 * @since 4.7.0 |
|
163 * |
|
164 * @param WP_REST_Request $request Full details about the request. |
|
165 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure. |
|
166 */ |
|
167 public function get_items( $request ) { |
|
168 |
|
169 // Retrieve the list of registered collection query parameters. |
|
170 $registered = $this->get_collection_params(); |
|
171 |
|
172 /* |
|
173 * This array defines mappings between public API query parameters whose |
|
174 * values are accepted as-passed, and their internal WP_Query parameter |
|
175 * name equivalents (some are the same). Only values which are also |
|
176 * present in $registered will be set. |
|
177 */ |
|
178 $parameter_mappings = array( |
|
179 'author' => 'author__in', |
|
180 'author_email' => 'author_email', |
|
181 'author_exclude' => 'author__not_in', |
|
182 'exclude' => 'comment__not_in', |
|
183 'include' => 'comment__in', |
|
184 'offset' => 'offset', |
|
185 'order' => 'order', |
|
186 'parent' => 'parent__in', |
|
187 'parent_exclude' => 'parent__not_in', |
|
188 'per_page' => 'number', |
|
189 'post' => 'post__in', |
|
190 'search' => 'search', |
|
191 'status' => 'status', |
|
192 'type' => 'type', |
|
193 ); |
|
194 |
|
195 $prepared_args = array(); |
|
196 |
|
197 /* |
|
198 * For each known parameter which is both registered and present in the request, |
|
199 * set the parameter's value on the query $prepared_args. |
|
200 */ |
|
201 foreach ( $parameter_mappings as $api_param => $wp_param ) { |
|
202 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { |
|
203 $prepared_args[ $wp_param ] = $request[ $api_param ]; |
|
204 } |
|
205 } |
|
206 |
|
207 // Ensure certain parameter values default to empty strings. |
|
208 foreach ( array( 'author_email', 'search' ) as $param ) { |
|
209 if ( ! isset( $prepared_args[ $param ] ) ) { |
|
210 $prepared_args[ $param ] = ''; |
|
211 } |
|
212 } |
|
213 |
|
214 if ( isset( $registered['orderby'] ) ) { |
|
215 $prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] ); |
|
216 } |
|
217 |
|
218 $prepared_args['no_found_rows'] = false; |
|
219 |
|
220 $prepared_args['date_query'] = array(); |
|
221 |
|
222 // Set before into date query. Date query must be specified as an array of an array. |
|
223 if ( isset( $registered['before'], $request['before'] ) ) { |
|
224 $prepared_args['date_query'][0]['before'] = $request['before']; |
|
225 } |
|
226 |
|
227 // Set after into date query. Date query must be specified as an array of an array. |
|
228 if ( isset( $registered['after'], $request['after'] ) ) { |
|
229 $prepared_args['date_query'][0]['after'] = $request['after']; |
|
230 } |
|
231 |
|
232 if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) { |
|
233 $prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 ); |
|
234 } |
|
235 |
|
236 /** |
|
237 * Filters arguments, before passing to WP_Comment_Query, when querying comments via the REST API. |
|
238 * |
|
239 * @since 4.7.0 |
|
240 * |
|
241 * @link https://developer.wordpress.org/reference/classes/wp_comment_query/ |
|
242 * |
|
243 * @param array $prepared_args Array of arguments for WP_Comment_Query. |
|
244 * @param WP_REST_Request $request The current request. |
|
245 */ |
|
246 $prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request ); |
|
247 |
|
248 $query = new WP_Comment_Query; |
|
249 $query_result = $query->query( $prepared_args ); |
|
250 |
|
251 $comments = array(); |
|
252 |
|
253 foreach ( $query_result as $comment ) { |
|
254 if ( ! $this->check_read_permission( $comment, $request ) ) { |
|
255 continue; |
|
256 } |
|
257 |
|
258 $data = $this->prepare_item_for_response( $comment, $request ); |
|
259 $comments[] = $this->prepare_response_for_collection( $data ); |
|
260 } |
|
261 |
|
262 $total_comments = (int) $query->found_comments; |
|
263 $max_pages = (int) $query->max_num_pages; |
|
264 |
|
265 if ( $total_comments < 1 ) { |
|
266 // Out-of-bounds, run the query again without LIMIT for total count. |
|
267 unset( $prepared_args['number'], $prepared_args['offset'] ); |
|
268 |
|
269 $query = new WP_Comment_Query; |
|
270 $prepared_args['count'] = true; |
|
271 |
|
272 $total_comments = $query->query( $prepared_args ); |
|
273 $max_pages = ceil( $total_comments / $request['per_page'] ); |
|
274 } |
|
275 |
|
276 $response = rest_ensure_response( $comments ); |
|
277 $response->header( 'X-WP-Total', $total_comments ); |
|
278 $response->header( 'X-WP-TotalPages', $max_pages ); |
|
279 |
|
280 $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); |
|
281 |
|
282 if ( $request['page'] > 1 ) { |
|
283 $prev_page = $request['page'] - 1; |
|
284 |
|
285 if ( $prev_page > $max_pages ) { |
|
286 $prev_page = $max_pages; |
|
287 } |
|
288 |
|
289 $prev_link = add_query_arg( 'page', $prev_page, $base ); |
|
290 $response->link_header( 'prev', $prev_link ); |
|
291 } |
|
292 |
|
293 if ( $max_pages > $request['page'] ) { |
|
294 $next_page = $request['page'] + 1; |
|
295 $next_link = add_query_arg( 'page', $next_page, $base ); |
|
296 |
|
297 $response->link_header( 'next', $next_link ); |
|
298 } |
|
299 |
|
300 return $response; |
|
301 } |
|
302 |
|
303 /** |
|
304 * Get the comment, if the ID is valid. |
|
305 * |
|
306 * @since 4.7.2 |
|
307 * |
|
308 * @param int $id Supplied ID. |
|
309 * @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise. |
|
310 */ |
|
311 protected function get_comment( $id ) { |
|
312 $error = new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) ); |
|
313 if ( (int) $id <= 0 ) { |
|
314 return $error; |
|
315 } |
|
316 |
|
317 $id = (int) $id; |
|
318 $comment = get_comment( $id ); |
|
319 if ( empty( $comment ) ) { |
|
320 return $error; |
|
321 } |
|
322 |
|
323 if ( ! empty( $comment->comment_post_ID ) ) { |
|
324 $post = get_post( (int) $comment->comment_post_ID ); |
|
325 if ( empty( $post ) ) { |
|
326 return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); |
|
327 } |
|
328 } |
|
329 |
|
330 return $comment; |
|
331 } |
|
332 |
|
333 /** |
|
334 * Checks if a given request has access to read the comment. |
|
335 * |
|
336 * @since 4.7.0 |
|
337 * |
|
338 * @param WP_REST_Request $request Full details about the request. |
|
339 * @return WP_Error|bool True if the request has read access for the item, error object otherwise. |
|
340 */ |
|
341 public function get_item_permissions_check( $request ) { |
|
342 $comment = $this->get_comment( $request['id'] ); |
|
343 if ( is_wp_error( $comment ) ) { |
|
344 return $comment; |
|
345 } |
|
346 |
|
347 if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) { |
|
348 return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
349 } |
|
350 |
|
351 $post = get_post( $comment->comment_post_ID ); |
|
352 |
|
353 if ( ! $this->check_read_permission( $comment, $request ) ) { |
|
354 return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read this comment.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
355 } |
|
356 |
|
357 if ( $post && ! $this->check_read_post_permission( $post, $request ) ) { |
|
358 return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
359 } |
|
360 |
|
361 return true; |
|
362 } |
|
363 |
|
364 /** |
|
365 * Retrieves a comment. |
|
366 * |
|
367 * @since 4.7.0 |
|
368 * |
|
369 * @param WP_REST_Request $request Full details about the request. |
|
370 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure. |
|
371 */ |
|
372 public function get_item( $request ) { |
|
373 $comment = $this->get_comment( $request['id'] ); |
|
374 if ( is_wp_error( $comment ) ) { |
|
375 return $comment; |
|
376 } |
|
377 |
|
378 $data = $this->prepare_item_for_response( $comment, $request ); |
|
379 $response = rest_ensure_response( $data ); |
|
380 |
|
381 return $response; |
|
382 } |
|
383 |
|
384 /** |
|
385 * Checks if a given request has access to create a comment. |
|
386 * |
|
387 * @since 4.7.0 |
|
388 * |
|
389 * @param WP_REST_Request $request Full details about the request. |
|
390 * @return WP_Error|bool True if the request has access to create items, error object otherwise. |
|
391 */ |
|
392 public function create_item_permissions_check( $request ) { |
|
393 if ( ! is_user_logged_in() ) { |
|
394 if ( get_option( 'comment_registration' ) ) { |
|
395 return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); |
|
396 } |
|
397 |
|
398 /** |
|
399 * Filter whether comments can be created without authentication. |
|
400 * |
|
401 * Enables creating comments for anonymous users. |
|
402 * |
|
403 * @since 4.7.0 |
|
404 * |
|
405 * @param bool $allow_anonymous Whether to allow anonymous comments to |
|
406 * be created. Default `false`. |
|
407 * @param WP_REST_Request $request Request used to generate the |
|
408 * response. |
|
409 */ |
|
410 $allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request ); |
|
411 if ( ! $allow_anonymous ) { |
|
412 return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) ); |
|
413 } |
|
414 } |
|
415 |
|
416 // Limit who can set comment `author`, `author_ip` or `status` to anything other than the default. |
|
417 if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) { |
|
418 return new WP_Error( 'rest_comment_invalid_author', |
|
419 /* translators: %s: request parameter */ |
|
420 sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ), |
|
421 array( 'status' => rest_authorization_required_code() ) |
|
422 ); |
|
423 } |
|
424 |
|
425 if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) { |
|
426 if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) { |
|
427 return new WP_Error( 'rest_comment_invalid_author_ip', |
|
428 /* translators: %s: request parameter */ |
|
429 sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ), |
|
430 array( 'status' => rest_authorization_required_code() ) |
|
431 ); |
|
432 } |
|
433 } |
|
434 |
|
435 if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) { |
|
436 return new WP_Error( 'rest_comment_invalid_status', |
|
437 /* translators: %s: request parameter */ |
|
438 sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ), |
|
439 array( 'status' => rest_authorization_required_code() ) |
|
440 ); |
|
441 } |
|
442 |
|
443 if ( empty( $request['post'] ) ) { |
|
444 return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) ); |
|
445 } |
|
446 |
|
447 $post = get_post( (int) $request['post'] ); |
|
448 if ( ! $post ) { |
|
449 return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) ); |
|
450 } |
|
451 |
|
452 if ( 'draft' === $post->post_status ) { |
|
453 return new WP_Error( 'rest_comment_draft_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); |
|
454 } |
|
455 |
|
456 if ( 'trash' === $post->post_status ) { |
|
457 return new WP_Error( 'rest_comment_trash_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) ); |
|
458 } |
|
459 |
|
460 if ( ! $this->check_read_post_permission( $post, $request ) ) { |
|
461 return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
462 } |
|
463 |
|
464 if ( ! comments_open( $post->ID ) ) { |
|
465 return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed for this item.' ), array( 'status' => 403 ) ); |
|
466 } |
|
467 |
|
468 return true; |
|
469 } |
|
470 |
|
471 /** |
|
472 * Creates a comment. |
|
473 * |
|
474 * @since 4.7.0 |
|
475 * |
|
476 * @param WP_REST_Request $request Full details about the request. |
|
477 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure. |
|
478 */ |
|
479 public function create_item( $request ) { |
|
480 if ( ! empty( $request['id'] ) ) { |
|
481 return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) ); |
|
482 } |
|
483 |
|
484 // Do not allow comments to be created with a non-default type. |
|
485 if ( ! empty( $request['type'] ) && 'comment' !== $request['type'] ) { |
|
486 return new WP_Error( 'rest_invalid_comment_type', __( 'Cannot create a comment with that type.' ), array( 'status' => 400 ) ); |
|
487 } |
|
488 |
|
489 $prepared_comment = $this->prepare_item_for_database( $request ); |
|
490 if ( is_wp_error( $prepared_comment ) ) { |
|
491 return $prepared_comment; |
|
492 } |
|
493 |
|
494 $prepared_comment['comment_type'] = ''; |
|
495 |
|
496 /* |
|
497 * Do not allow a comment to be created with missing or empty |
|
498 * comment_content. See wp_handle_comment_submission(). |
|
499 */ |
|
500 if ( empty( $prepared_comment['comment_content'] ) ) { |
|
501 return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) ); |
|
502 } |
|
503 |
|
504 // Setting remaining values before wp_insert_comment so we can use wp_allow_comment(). |
|
505 if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) { |
|
506 $prepared_comment['comment_date_gmt'] = current_time( 'mysql', true ); |
|
507 } |
|
508 |
|
509 // Set author data if the user's logged in. |
|
510 $missing_author = empty( $prepared_comment['user_id'] ) |
|
511 && empty( $prepared_comment['comment_author'] ) |
|
512 && empty( $prepared_comment['comment_author_email'] ) |
|
513 && empty( $prepared_comment['comment_author_url'] ); |
|
514 |
|
515 if ( is_user_logged_in() && $missing_author ) { |
|
516 $user = wp_get_current_user(); |
|
517 |
|
518 $prepared_comment['user_id'] = $user->ID; |
|
519 $prepared_comment['comment_author'] = $user->display_name; |
|
520 $prepared_comment['comment_author_email'] = $user->user_email; |
|
521 $prepared_comment['comment_author_url'] = $user->user_url; |
|
522 } |
|
523 |
|
524 // Honor the discussion setting that requires a name and email address of the comment author. |
|
525 if ( get_option( 'require_name_email' ) ) { |
|
526 if ( empty( $prepared_comment['comment_author'] ) || empty( $prepared_comment['comment_author_email'] ) ) { |
|
527 return new WP_Error( 'rest_comment_author_data_required', __( 'Creating a comment requires valid author name and email values.' ), array( 'status' => 400 ) ); |
|
528 } |
|
529 } |
|
530 |
|
531 if ( ! isset( $prepared_comment['comment_author_email'] ) ) { |
|
532 $prepared_comment['comment_author_email'] = ''; |
|
533 } |
|
534 |
|
535 if ( ! isset( $prepared_comment['comment_author_url'] ) ) { |
|
536 $prepared_comment['comment_author_url'] = ''; |
|
537 } |
|
538 |
|
539 if ( ! isset( $prepared_comment['comment_agent'] ) ) { |
|
540 $prepared_comment['comment_agent'] = ''; |
|
541 } |
|
542 |
|
543 $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_comment ); |
|
544 if ( is_wp_error( $check_comment_lengths ) ) { |
|
545 $error_code = $check_comment_lengths->get_error_code(); |
|
546 return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) ); |
|
547 } |
|
548 |
|
549 $prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment, true ); |
|
550 |
|
551 if ( is_wp_error( $prepared_comment['comment_approved'] ) ) { |
|
552 $error_code = $prepared_comment['comment_approved']->get_error_code(); |
|
553 $error_message = $prepared_comment['comment_approved']->get_error_message(); |
|
554 |
|
555 if ( 'comment_duplicate' === $error_code ) { |
|
556 return new WP_Error( $error_code, $error_message, array( 'status' => 409 ) ); |
|
557 } |
|
558 |
|
559 if ( 'comment_flood' === $error_code ) { |
|
560 return new WP_Error( $error_code, $error_message, array( 'status' => 400 ) ); |
|
561 } |
|
562 |
|
563 return $prepared_comment['comment_approved']; |
|
564 } |
|
565 |
|
566 /** |
|
567 * Filters a comment before it is inserted via the REST API. |
|
568 * |
|
569 * Allows modification of the comment right before it is inserted via wp_insert_comment(). |
|
570 * Returning a WP_Error value from the filter will shortcircuit insertion and allow |
|
571 * skipping further processing. |
|
572 * |
|
573 * @since 4.7.0 |
|
574 * @since 4.8.0 $prepared_comment can now be a WP_Error to shortcircuit insertion. |
|
575 * |
|
576 * @param array|WP_Error $prepared_comment The prepared comment data for wp_insert_comment(). |
|
577 * @param WP_REST_Request $request Request used to insert the comment. |
|
578 */ |
|
579 $prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request ); |
|
580 if ( is_wp_error( $prepared_comment ) ) { |
|
581 return $prepared_comment; |
|
582 } |
|
583 |
|
584 $comment_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_comment ) ) ); |
|
585 |
|
586 if ( ! $comment_id ) { |
|
587 return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) ); |
|
588 } |
|
589 |
|
590 if ( isset( $request['status'] ) ) { |
|
591 $this->handle_status_param( $request['status'], $comment_id ); |
|
592 } |
|
593 |
|
594 $comment = get_comment( $comment_id ); |
|
595 |
|
596 /** |
|
597 * Fires after a comment is created or updated via the REST API. |
|
598 * |
|
599 * @since 4.7.0 |
|
600 * |
|
601 * @param WP_Comment $comment Inserted or updated comment object. |
|
602 * @param WP_REST_Request $request Request object. |
|
603 * @param bool $creating True when creating a comment, false |
|
604 * when updating. |
|
605 */ |
|
606 do_action( 'rest_insert_comment', $comment, $request, true ); |
|
607 |
|
608 $schema = $this->get_item_schema(); |
|
609 |
|
610 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { |
|
611 $meta_update = $this->meta->update_value( $request['meta'], $comment_id ); |
|
612 |
|
613 if ( is_wp_error( $meta_update ) ) { |
|
614 return $meta_update; |
|
615 } |
|
616 } |
|
617 |
|
618 $fields_update = $this->update_additional_fields_for_object( $comment, $request ); |
|
619 |
|
620 if ( is_wp_error( $fields_update ) ) { |
|
621 return $fields_update; |
|
622 } |
|
623 |
|
624 $context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view'; |
|
625 |
|
626 $request->set_param( 'context', $context ); |
|
627 |
|
628 $response = $this->prepare_item_for_response( $comment, $request ); |
|
629 $response = rest_ensure_response( $response ); |
|
630 |
|
631 $response->set_status( 201 ); |
|
632 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) ); |
|
633 |
|
634 |
|
635 return $response; |
|
636 } |
|
637 |
|
638 /** |
|
639 * Checks if a given REST request has access to update a comment. |
|
640 * |
|
641 * @since 4.7.0 |
|
642 * |
|
643 * @param WP_REST_Request $request Full details about the request. |
|
644 * @return WP_Error|bool True if the request has access to update the item, error object otherwise. |
|
645 */ |
|
646 public function update_item_permissions_check( $request ) { |
|
647 $comment = $this->get_comment( $request['id'] ); |
|
648 if ( is_wp_error( $comment ) ) { |
|
649 return $comment; |
|
650 } |
|
651 |
|
652 if ( ! $this->check_edit_permission( $comment ) ) { |
|
653 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this comment.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
654 } |
|
655 |
|
656 return true; |
|
657 } |
|
658 |
|
659 /** |
|
660 * Updates a comment. |
|
661 * |
|
662 * @since 4.7.0 |
|
663 * |
|
664 * @param WP_REST_Request $request Full details about the request. |
|
665 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure. |
|
666 */ |
|
667 public function update_item( $request ) { |
|
668 $comment = $this->get_comment( $request['id'] ); |
|
669 if ( is_wp_error( $comment ) ) { |
|
670 return $comment; |
|
671 } |
|
672 |
|
673 $id = $comment->comment_ID; |
|
674 |
|
675 if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) { |
|
676 return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you are not allowed to change the comment type.' ), array( 'status' => 404 ) ); |
|
677 } |
|
678 |
|
679 $prepared_args = $this->prepare_item_for_database( $request ); |
|
680 |
|
681 if ( is_wp_error( $prepared_args ) ) { |
|
682 return $prepared_args; |
|
683 } |
|
684 |
|
685 if ( ! empty( $prepared_args['comment_post_ID'] ) ) { |
|
686 $post = get_post( $prepared_args['comment_post_ID'] ); |
|
687 if ( empty( $post ) ) { |
|
688 return new WP_Error( 'rest_comment_invalid_post_id', __( 'Invalid post ID.' ), array( 'status' => 403 ) ); |
|
689 } |
|
690 } |
|
691 |
|
692 if ( empty( $prepared_args ) && isset( $request['status'] ) ) { |
|
693 // Only the comment status is being changed. |
|
694 $change = $this->handle_status_param( $request['status'], $id ); |
|
695 |
|
696 if ( ! $change ) { |
|
697 return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) ); |
|
698 } |
|
699 } elseif ( ! empty( $prepared_args ) ) { |
|
700 if ( is_wp_error( $prepared_args ) ) { |
|
701 return $prepared_args; |
|
702 } |
|
703 |
|
704 if ( isset( $prepared_args['comment_content'] ) && empty( $prepared_args['comment_content'] ) ) { |
|
705 return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) ); |
|
706 } |
|
707 |
|
708 $prepared_args['comment_ID'] = $id; |
|
709 |
|
710 $check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args ); |
|
711 if ( is_wp_error( $check_comment_lengths ) ) { |
|
712 $error_code = $check_comment_lengths->get_error_code(); |
|
713 return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) ); |
|
714 } |
|
715 |
|
716 $updated = wp_update_comment( wp_slash( (array) $prepared_args ) ); |
|
717 |
|
718 if ( false === $updated ) { |
|
719 return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) ); |
|
720 } |
|
721 |
|
722 if ( isset( $request['status'] ) ) { |
|
723 $this->handle_status_param( $request['status'], $id ); |
|
724 } |
|
725 } |
|
726 |
|
727 $comment = get_comment( $id ); |
|
728 |
|
729 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */ |
|
730 do_action( 'rest_insert_comment', $comment, $request, false ); |
|
731 |
|
732 $schema = $this->get_item_schema(); |
|
733 |
|
734 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { |
|
735 $meta_update = $this->meta->update_value( $request['meta'], $id ); |
|
736 |
|
737 if ( is_wp_error( $meta_update ) ) { |
|
738 return $meta_update; |
|
739 } |
|
740 } |
|
741 |
|
742 $fields_update = $this->update_additional_fields_for_object( $comment, $request ); |
|
743 |
|
744 if ( is_wp_error( $fields_update ) ) { |
|
745 return $fields_update; |
|
746 } |
|
747 |
|
748 $request->set_param( 'context', 'edit' ); |
|
749 |
|
750 $response = $this->prepare_item_for_response( $comment, $request ); |
|
751 |
|
752 return rest_ensure_response( $response ); |
|
753 } |
|
754 |
|
755 /** |
|
756 * Checks if a given request has access to delete a comment. |
|
757 * |
|
758 * @since 4.7.0 |
|
759 * |
|
760 * @param WP_REST_Request $request Full details about the request. |
|
761 * @return WP_Error|bool True if the request has access to delete the item, error object otherwise. |
|
762 */ |
|
763 public function delete_item_permissions_check( $request ) { |
|
764 $comment = $this->get_comment( $request['id'] ); |
|
765 if ( is_wp_error( $comment ) ) { |
|
766 return $comment; |
|
767 } |
|
768 |
|
769 if ( ! $this->check_edit_permission( $comment ) ) { |
|
770 return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this comment.' ), array( 'status' => rest_authorization_required_code() ) ); |
|
771 } |
|
772 return true; |
|
773 } |
|
774 |
|
775 /** |
|
776 * Deletes a comment. |
|
777 * |
|
778 * @since 4.7.0 |
|
779 * |
|
780 * @param WP_REST_Request $request Full details about the request. |
|
781 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure. |
|
782 */ |
|
783 public function delete_item( $request ) { |
|
784 $comment = $this->get_comment( $request['id'] ); |
|
785 if ( is_wp_error( $comment ) ) { |
|
786 return $comment; |
|
787 } |
|
788 |
|
789 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; |
|
790 |
|
791 /** |
|
792 * Filters whether a comment can be trashed. |
|
793 * |
|
794 * Return false to disable trash support for the post. |
|
795 * |
|
796 * @since 4.7.0 |
|
797 * |
|
798 * @param bool $supports_trash Whether the post type support trashing. |
|
799 * @param WP_Post $comment The comment object being considered for trashing support. |
|
800 */ |
|
801 $supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment ); |
|
802 |
|
803 $request->set_param( 'context', 'edit' ); |
|
804 |
|
805 if ( $force ) { |
|
806 $previous = $this->prepare_item_for_response( $comment, $request ); |
|
807 $result = wp_delete_comment( $comment->comment_ID, true ); |
|
808 $response = new WP_REST_Response(); |
|
809 $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) ); |
|
810 } else { |
|
811 // If this type doesn't support trashing, error out. |
|
812 if ( ! $supports_trash ) { |
|
813 /* translators: %s: force=true */ |
|
814 return new WP_Error( 'rest_trash_not_supported', sprintf( __( "The comment does not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); |
|
815 } |
|
816 |
|
817 if ( 'trash' === $comment->comment_approved ) { |
|
818 return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) ); |
|
819 } |
|
820 |
|
821 $result = wp_trash_comment( $comment->comment_ID ); |
|
822 $comment = get_comment( $comment->comment_ID ); |
|
823 $response = $this->prepare_item_for_response( $comment, $request ); |
|
824 } |
|
825 |
|
826 if ( ! $result ) { |
|
827 return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) ); |
|
828 } |
|
829 |
|
830 /** |
|
831 * Fires after a comment is deleted via the REST API. |
|
832 * |
|
833 * @since 4.7.0 |
|
834 * |
|
835 * @param WP_Comment $comment The deleted comment data. |
|
836 * @param WP_REST_Response $response The response returned from the API. |
|
837 * @param WP_REST_Request $request The request sent to the API. |
|
838 */ |
|
839 do_action( 'rest_delete_comment', $comment, $response, $request ); |
|
840 |
|
841 return $response; |
|
842 } |
|
843 |
|
844 /** |
|
845 * Prepares a single comment output for response. |
|
846 * |
|
847 * @since 4.7.0 |
|
848 * |
|
849 * @param WP_Comment $comment Comment object. |
|
850 * @param WP_REST_Request $request Request object. |
|
851 * @return WP_REST_Response Response object. |
|
852 */ |
|
853 public function prepare_item_for_response( $comment, $request ) { |
|
854 |
|
855 $fields = $this->get_fields_for_response( $request ); |
|
856 $data = array(); |
|
857 |
|
858 if ( in_array( 'id', $fields, true ) ) { |
|
859 $data['id'] = (int) $comment->comment_ID; |
|
860 } |
|
861 |
|
862 if ( in_array( 'post', $fields, true ) ) { |
|
863 $data['post'] = (int) $comment->comment_post_ID; |
|
864 } |
|
865 |
|
866 if ( in_array( 'parent', $fields, true ) ) { |
|
867 $data['parent'] = (int) $comment->comment_parent; |
|
868 } |
|
869 |
|
870 if ( in_array( 'author', $fields, true ) ) { |
|
871 $data['author'] = (int) $comment->user_id; |
|
872 } |
|
873 |
|
874 if ( in_array( 'author_name', $fields, true ) ) { |
|
875 $data['author_name'] = $comment->comment_author; |
|
876 } |
|
877 |
|
878 if ( in_array( 'author_email', $fields, true ) ) { |
|
879 $data['author_email'] = $comment->comment_author_email; |
|
880 } |
|
881 |
|
882 if ( in_array( 'author_url', $fields, true ) ) { |
|
883 $data['author_url'] = $comment->comment_author_url; |
|
884 } |
|
885 |
|
886 if ( in_array( 'author_ip', $fields, true ) ) { |
|
887 $data['author_ip'] = $comment->comment_author_IP; |
|
888 } |
|
889 |
|
890 if ( in_array( 'author_user_agent', $fields, true ) ) { |
|
891 $data['author_user_agent'] = $comment->comment_agent; |
|
892 } |
|
893 |
|
894 if ( in_array( 'date', $fields, true ) ) { |
|
895 $data['date'] = mysql_to_rfc3339( $comment->comment_date ); |
|
896 } |
|
897 |
|
898 if ( in_array( 'date_gmt', $fields, true ) ) { |
|
899 $data['date_gmt'] = mysql_to_rfc3339( $comment->comment_date_gmt ); |
|
900 } |
|
901 |
|
902 if ( in_array( 'content', $fields, true ) ) { |
|
903 $data['content'] = array( |
|
904 /** This filter is documented in wp-includes/comment-template.php */ |
|
905 'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ), |
|
906 'raw' => $comment->comment_content, |
|
907 ); |
|
908 } |
|
909 |
|
910 if ( in_array( 'link', $fields, true ) ) { |
|
911 $data['link'] = get_comment_link( $comment ); |
|
912 } |
|
913 |
|
914 if ( in_array( 'status', $fields, true ) ) { |
|
915 $data['status'] = $this->prepare_status_response( $comment->comment_approved ); |
|
916 } |
|
917 |
|
918 if ( in_array( 'type', $fields, true ) ) { |
|
919 $data['type'] = get_comment_type( $comment->comment_ID ); |
|
920 } |
|
921 |
|
922 if ( in_array( 'author_avatar_urls', $fields, true ) ) { |
|
923 $data['author_avatar_urls'] = rest_get_avatar_urls( $comment->comment_author_email ); |
|
924 } |
|
925 |
|
926 if ( in_array( 'meta', $fields, true ) ) { |
|
927 $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request ); |
|
928 } |
|
929 |
|
930 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; |
|
931 $data = $this->add_additional_fields_to_object( $data, $request ); |
|
932 $data = $this->filter_response_by_context( $data, $context ); |
|
933 |
|
934 // Wrap the data in a response object. |
|
935 $response = rest_ensure_response( $data ); |
|
936 |
|
937 $response->add_links( $this->prepare_links( $comment ) ); |
|
938 |
|
939 /** |
|
940 * Filters a comment returned from the API. |
|
941 * |
|
942 * Allows modification of the comment right before it is returned. |
|
943 * |
|
944 * @since 4.7.0 |
|
945 * |
|
946 * @param WP_REST_Response $response The response object. |
|
947 * @param WP_Comment $comment The original comment object. |
|
948 * @param WP_REST_Request $request Request used to generate the response. |
|
949 */ |
|
950 return apply_filters( 'rest_prepare_comment', $response, $comment, $request ); |
|
951 } |
|
952 |
|
953 /** |
|
954 * Prepares links for the request. |
|
955 * |
|
956 * @since 4.7.0 |
|
957 * |
|
958 * @param WP_Comment $comment Comment object. |
|
959 * @return array Links for the given comment. |
|
960 */ |
|
961 protected function prepare_links( $comment ) { |
|
962 $links = array( |
|
963 'self' => array( |
|
964 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ), |
|
965 ), |
|
966 'collection' => array( |
|
967 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), |
|
968 ), |
|
969 ); |
|
970 |
|
971 if ( 0 !== (int) $comment->user_id ) { |
|
972 $links['author'] = array( |
|
973 'href' => rest_url( 'wp/v2/users/' . $comment->user_id ), |
|
974 'embeddable' => true, |
|
975 ); |
|
976 } |
|
977 |
|
978 if ( 0 !== (int) $comment->comment_post_ID ) { |
|
979 $post = get_post( $comment->comment_post_ID ); |
|
980 |
|
981 if ( ! empty( $post->ID ) ) { |
|
982 $obj = get_post_type_object( $post->post_type ); |
|
983 $base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; |
|
984 |
|
985 $links['up'] = array( |
|
986 'href' => rest_url( 'wp/v2/' . $base . '/' . $comment->comment_post_ID ), |
|
987 'embeddable' => true, |
|
988 'post_type' => $post->post_type, |
|
989 ); |
|
990 } |
|
991 } |
|
992 |
|
993 if ( 0 !== (int) $comment->comment_parent ) { |
|
994 $links['in-reply-to'] = array( |
|
995 'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ), |
|
996 'embeddable' => true, |
|
997 ); |
|
998 } |
|
999 |
|
1000 // Only grab one comment to verify the comment has children. |
|
1001 $comment_children = $comment->get_children( array( |
|
1002 'number' => 1, |
|
1003 'count' => true |
|
1004 ) ); |
|
1005 |
|
1006 if ( ! empty( $comment_children ) ) { |
|
1007 $args = array( |
|
1008 'parent' => $comment->comment_ID |
|
1009 ); |
|
1010 |
|
1011 $rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) ); |
|
1012 |
|
1013 $links['children'] = array( |
|
1014 'href' => $rest_url, |
|
1015 ); |
|
1016 } |
|
1017 |
|
1018 return $links; |
|
1019 } |
|
1020 |
|
1021 /** |
|
1022 * Prepends internal property prefix to query parameters to match our response fields. |
|
1023 * |
|
1024 * @since 4.7.0 |
|
1025 * |
|
1026 * @param string $query_param Query parameter. |
|
1027 * @return string The normalized query parameter. |
|
1028 */ |
|
1029 protected function normalize_query_param( $query_param ) { |
|
1030 $prefix = 'comment_'; |
|
1031 |
|
1032 switch ( $query_param ) { |
|
1033 case 'id': |
|
1034 $normalized = $prefix . 'ID'; |
|
1035 break; |
|
1036 case 'post': |
|
1037 $normalized = $prefix . 'post_ID'; |
|
1038 break; |
|
1039 case 'parent': |
|
1040 $normalized = $prefix . 'parent'; |
|
1041 break; |
|
1042 case 'include': |
|
1043 $normalized = 'comment__in'; |
|
1044 break; |
|
1045 default: |
|
1046 $normalized = $prefix . $query_param; |
|
1047 break; |
|
1048 } |
|
1049 |
|
1050 return $normalized; |
|
1051 } |
|
1052 |
|
1053 /** |
|
1054 * Checks comment_approved to set comment status for single comment output. |
|
1055 * |
|
1056 * @since 4.7.0 |
|
1057 * |
|
1058 * @param string|int $comment_approved comment status. |
|
1059 * @return string Comment status. |
|
1060 */ |
|
1061 protected function prepare_status_response( $comment_approved ) { |
|
1062 |
|
1063 switch ( $comment_approved ) { |
|
1064 case 'hold': |
|
1065 case '0': |
|
1066 $status = 'hold'; |
|
1067 break; |
|
1068 |
|
1069 case 'approve': |
|
1070 case '1': |
|
1071 $status = 'approved'; |
|
1072 break; |
|
1073 |
|
1074 case 'spam': |
|
1075 case 'trash': |
|
1076 default: |
|
1077 $status = $comment_approved; |
|
1078 break; |
|
1079 } |
|
1080 |
|
1081 return $status; |
|
1082 } |
|
1083 |
|
1084 /** |
|
1085 * Prepares a single comment to be inserted into the database. |
|
1086 * |
|
1087 * @since 4.7.0 |
|
1088 * |
|
1089 * @param WP_REST_Request $request Request object. |
|
1090 * @return array|WP_Error Prepared comment, otherwise WP_Error object. |
|
1091 */ |
|
1092 protected function prepare_item_for_database( $request ) { |
|
1093 $prepared_comment = array(); |
|
1094 |
|
1095 /* |
|
1096 * Allow the comment_content to be set via the 'content' or |
|
1097 * the 'content.raw' properties of the Request object. |
|
1098 */ |
|
1099 if ( isset( $request['content'] ) && is_string( $request['content'] ) ) { |
|
1100 $prepared_comment['comment_content'] = $request['content']; |
|
1101 } elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) { |
|
1102 $prepared_comment['comment_content'] = $request['content']['raw']; |
|
1103 } |
|
1104 |
|
1105 if ( isset( $request['post'] ) ) { |
|
1106 $prepared_comment['comment_post_ID'] = (int) $request['post']; |
|
1107 } |
|
1108 |
|
1109 if ( isset( $request['parent'] ) ) { |
|
1110 $prepared_comment['comment_parent'] = $request['parent']; |
|
1111 } |
|
1112 |
|
1113 if ( isset( $request['author'] ) ) { |
|
1114 $user = new WP_User( $request['author'] ); |
|
1115 |
|
1116 if ( $user->exists() ) { |
|
1117 $prepared_comment['user_id'] = $user->ID; |
|
1118 $prepared_comment['comment_author'] = $user->display_name; |
|
1119 $prepared_comment['comment_author_email'] = $user->user_email; |
|
1120 $prepared_comment['comment_author_url'] = $user->user_url; |
|
1121 } else { |
|
1122 return new WP_Error( 'rest_comment_author_invalid', __( 'Invalid comment author ID.' ), array( 'status' => 400 ) ); |
|
1123 } |
|
1124 } |
|
1125 |
|
1126 if ( isset( $request['author_name'] ) ) { |
|
1127 $prepared_comment['comment_author'] = $request['author_name']; |
|
1128 } |
|
1129 |
|
1130 if ( isset( $request['author_email'] ) ) { |
|
1131 $prepared_comment['comment_author_email'] = $request['author_email']; |
|
1132 } |
|
1133 |
|
1134 if ( isset( $request['author_url'] ) ) { |
|
1135 $prepared_comment['comment_author_url'] = $request['author_url']; |
|
1136 } |
|
1137 |
|
1138 if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) { |
|
1139 $prepared_comment['comment_author_IP'] = $request['author_ip']; |
|
1140 } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) { |
|
1141 $prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR']; |
|
1142 } else { |
|
1143 $prepared_comment['comment_author_IP'] = '127.0.0.1'; |
|
1144 } |
|
1145 |
|
1146 if ( ! empty( $request['author_user_agent'] ) ) { |
|
1147 $prepared_comment['comment_agent'] = $request['author_user_agent']; |
|
1148 } elseif ( $request->get_header( 'user_agent' ) ) { |
|
1149 $prepared_comment['comment_agent'] = $request->get_header( 'user_agent' ); |
|
1150 } |
|
1151 |
|
1152 if ( ! empty( $request['date'] ) ) { |
|
1153 $date_data = rest_get_date_with_gmt( $request['date'] ); |
|
1154 |
|
1155 if ( ! empty( $date_data ) ) { |
|
1156 list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; |
|
1157 } |
|
1158 } elseif ( ! empty( $request['date_gmt'] ) ) { |
|
1159 $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); |
|
1160 |
|
1161 if ( ! empty( $date_data ) ) { |
|
1162 list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data; |
|
1163 } |
|
1164 } |
|
1165 |
|
1166 /** |
|
1167 * Filters a comment after it is prepared for the database. |
|
1168 * |
|
1169 * Allows modification of the comment right after it is prepared for the database. |
|
1170 * |
|
1171 * @since 4.7.0 |
|
1172 * |
|
1173 * @param array $prepared_comment The prepared comment data for `wp_insert_comment`. |
|
1174 * @param WP_REST_Request $request The current request. |
|
1175 */ |
|
1176 return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request ); |
|
1177 } |
|
1178 |
|
1179 /** |
|
1180 * Retrieves the comment's schema, conforming to JSON Schema. |
|
1181 * |
|
1182 * @since 4.7.0 |
|
1183 * |
|
1184 * @return array |
|
1185 */ |
|
1186 public function get_item_schema() { |
|
1187 $schema = array( |
|
1188 '$schema' => 'http://json-schema.org/draft-04/schema#', |
|
1189 'title' => 'comment', |
|
1190 'type' => 'object', |
|
1191 'properties' => array( |
|
1192 'id' => array( |
|
1193 'description' => __( 'Unique identifier for the object.' ), |
|
1194 'type' => 'integer', |
|
1195 'context' => array( 'view', 'edit', 'embed' ), |
|
1196 'readonly' => true, |
|
1197 ), |
|
1198 'author' => array( |
|
1199 'description' => __( 'The ID of the user object, if author was a user.' ), |
|
1200 'type' => 'integer', |
|
1201 'context' => array( 'view', 'edit', 'embed' ), |
|
1202 ), |
|
1203 'author_email' => array( |
|
1204 'description' => __( 'Email address for the object author.' ), |
|
1205 'type' => 'string', |
|
1206 'format' => 'email', |
|
1207 'context' => array( 'edit' ), |
|
1208 'arg_options' => array( |
|
1209 'sanitize_callback' => array( $this, 'check_comment_author_email' ), |
|
1210 'validate_callback' => null, // skip built-in validation of 'email'. |
|
1211 ), |
|
1212 ), |
|
1213 'author_ip' => array( |
|
1214 'description' => __( 'IP address for the object author.' ), |
|
1215 'type' => 'string', |
|
1216 'format' => 'ip', |
|
1217 'context' => array( 'edit' ), |
|
1218 ), |
|
1219 'author_name' => array( |
|
1220 'description' => __( 'Display name for the object author.' ), |
|
1221 'type' => 'string', |
|
1222 'context' => array( 'view', 'edit', 'embed' ), |
|
1223 'arg_options' => array( |
|
1224 'sanitize_callback' => 'sanitize_text_field', |
|
1225 ), |
|
1226 ), |
|
1227 'author_url' => array( |
|
1228 'description' => __( 'URL for the object author.' ), |
|
1229 'type' => 'string', |
|
1230 'format' => 'uri', |
|
1231 'context' => array( 'view', 'edit', 'embed' ), |
|
1232 ), |
|
1233 'author_user_agent' => array( |
|
1234 'description' => __( 'User agent for the object author.' ), |
|
1235 'type' => 'string', |
|
1236 'context' => array( 'edit' ), |
|
1237 'arg_options' => array( |
|
1238 'sanitize_callback' => 'sanitize_text_field', |
|
1239 ), |
|
1240 ), |
|
1241 'content' => array( |
|
1242 'description' => __( 'The content for the object.' ), |
|
1243 'type' => 'object', |
|
1244 'context' => array( 'view', 'edit', 'embed' ), |
|
1245 'arg_options' => array( |
|
1246 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() |
|
1247 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database() |
|
1248 ), |
|
1249 'properties' => array( |
|
1250 'raw' => array( |
|
1251 'description' => __( 'Content for the object, as it exists in the database.' ), |
|
1252 'type' => 'string', |
|
1253 'context' => array( 'edit' ), |
|
1254 ), |
|
1255 'rendered' => array( |
|
1256 'description' => __( 'HTML content for the object, transformed for display.' ), |
|
1257 'type' => 'string', |
|
1258 'context' => array( 'view', 'edit', 'embed' ), |
|
1259 'readonly' => true, |
|
1260 ), |
|
1261 ), |
|
1262 ), |
|
1263 'date' => array( |
|
1264 'description' => __( "The date the object was published, in the site's timezone." ), |
|
1265 'type' => 'string', |
|
1266 'format' => 'date-time', |
|
1267 'context' => array( 'view', 'edit', 'embed' ), |
|
1268 ), |
|
1269 'date_gmt' => array( |
|
1270 'description' => __( 'The date the object was published, as GMT.' ), |
|
1271 'type' => 'string', |
|
1272 'format' => 'date-time', |
|
1273 'context' => array( 'view', 'edit' ), |
|
1274 ), |
|
1275 'link' => array( |
|
1276 'description' => __( 'URL to the object.' ), |
|
1277 'type' => 'string', |
|
1278 'format' => 'uri', |
|
1279 'context' => array( 'view', 'edit', 'embed' ), |
|
1280 'readonly' => true, |
|
1281 ), |
|
1282 'parent' => array( |
|
1283 'description' => __( 'The ID for the parent of the object.' ), |
|
1284 'type' => 'integer', |
|
1285 'context' => array( 'view', 'edit', 'embed' ), |
|
1286 'default' => 0, |
|
1287 ), |
|
1288 'post' => array( |
|
1289 'description' => __( 'The ID of the associated post object.' ), |
|
1290 'type' => 'integer', |
|
1291 'context' => array( 'view', 'edit' ), |
|
1292 'default' => 0, |
|
1293 ), |
|
1294 'status' => array( |
|
1295 'description' => __( 'State of the object.' ), |
|
1296 'type' => 'string', |
|
1297 'context' => array( 'view', 'edit' ), |
|
1298 'arg_options' => array( |
|
1299 'sanitize_callback' => 'sanitize_key', |
|
1300 ), |
|
1301 ), |
|
1302 'type' => array( |
|
1303 'description' => __( 'Type of Comment for the object.' ), |
|
1304 'type' => 'string', |
|
1305 'context' => array( 'view', 'edit', 'embed' ), |
|
1306 'readonly' => true, |
|
1307 ), |
|
1308 ), |
|
1309 ); |
|
1310 |
|
1311 if ( get_option( 'show_avatars' ) ) { |
|
1312 $avatar_properties = array(); |
|
1313 |
|
1314 $avatar_sizes = rest_get_avatar_sizes(); |
|
1315 foreach ( $avatar_sizes as $size ) { |
|
1316 $avatar_properties[ $size ] = array( |
|
1317 /* translators: %d: avatar image size in pixels */ |
|
1318 'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ), |
|
1319 'type' => 'string', |
|
1320 'format' => 'uri', |
|
1321 'context' => array( 'embed', 'view', 'edit' ), |
|
1322 ); |
|
1323 } |
|
1324 |
|
1325 $schema['properties']['author_avatar_urls'] = array( |
|
1326 'description' => __( 'Avatar URLs for the object author.' ), |
|
1327 'type' => 'object', |
|
1328 'context' => array( 'view', 'edit', 'embed' ), |
|
1329 'readonly' => true, |
|
1330 'properties' => $avatar_properties, |
|
1331 ); |
|
1332 } |
|
1333 |
|
1334 $schema['properties']['meta'] = $this->meta->get_field_schema(); |
|
1335 |
|
1336 return $this->add_additional_fields_schema( $schema ); |
|
1337 } |
|
1338 |
|
1339 /** |
|
1340 * Retrieves the query params for collections. |
|
1341 * |
|
1342 * @since 4.7.0 |
|
1343 * |
|
1344 * @return array Comments collection parameters. |
|
1345 */ |
|
1346 public function get_collection_params() { |
|
1347 $query_params = parent::get_collection_params(); |
|
1348 |
|
1349 $query_params['context']['default'] = 'view'; |
|
1350 |
|
1351 $query_params['after'] = array( |
|
1352 'description' => __( 'Limit response to comments published after a given ISO8601 compliant date.' ), |
|
1353 'type' => 'string', |
|
1354 'format' => 'date-time', |
|
1355 ); |
|
1356 |
|
1357 $query_params['author'] = array( |
|
1358 'description' => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ), |
|
1359 'type' => 'array', |
|
1360 'items' => array( |
|
1361 'type' => 'integer', |
|
1362 ), |
|
1363 ); |
|
1364 |
|
1365 $query_params['author_exclude'] = array( |
|
1366 'description' => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ), |
|
1367 'type' => 'array', |
|
1368 'items' => array( |
|
1369 'type' => 'integer', |
|
1370 ), |
|
1371 ); |
|
1372 |
|
1373 $query_params['author_email'] = array( |
|
1374 'default' => null, |
|
1375 'description' => __( 'Limit result set to that from a specific author email. Requires authorization.' ), |
|
1376 'format' => 'email', |
|
1377 'type' => 'string', |
|
1378 ); |
|
1379 |
|
1380 $query_params['before'] = array( |
|
1381 'description' => __( 'Limit response to comments published before a given ISO8601 compliant date.' ), |
|
1382 'type' => 'string', |
|
1383 'format' => 'date-time', |
|
1384 ); |
|
1385 |
|
1386 $query_params['exclude'] = array( |
|
1387 'description' => __( 'Ensure result set excludes specific IDs.' ), |
|
1388 'type' => 'array', |
|
1389 'items' => array( |
|
1390 'type' => 'integer', |
|
1391 ), |
|
1392 'default' => array(), |
|
1393 ); |
|
1394 |
|
1395 $query_params['include'] = array( |
|
1396 'description' => __( 'Limit result set to specific IDs.' ), |
|
1397 'type' => 'array', |
|
1398 'items' => array( |
|
1399 'type' => 'integer', |
|
1400 ), |
|
1401 'default' => array(), |
|
1402 ); |
|
1403 |
|
1404 $query_params['offset'] = array( |
|
1405 'description' => __( 'Offset the result set by a specific number of items.' ), |
|
1406 'type' => 'integer', |
|
1407 ); |
|
1408 |
|
1409 $query_params['order'] = array( |
|
1410 'description' => __( 'Order sort attribute ascending or descending.' ), |
|
1411 'type' => 'string', |
|
1412 'default' => 'desc', |
|
1413 'enum' => array( |
|
1414 'asc', |
|
1415 'desc', |
|
1416 ), |
|
1417 ); |
|
1418 |
|
1419 $query_params['orderby'] = array( |
|
1420 'description' => __( 'Sort collection by object attribute.' ), |
|
1421 'type' => 'string', |
|
1422 'default' => 'date_gmt', |
|
1423 'enum' => array( |
|
1424 'date', |
|
1425 'date_gmt', |
|
1426 'id', |
|
1427 'include', |
|
1428 'post', |
|
1429 'parent', |
|
1430 'type', |
|
1431 ), |
|
1432 ); |
|
1433 |
|
1434 $query_params['parent'] = array( |
|
1435 'default' => array(), |
|
1436 'description' => __( 'Limit result set to comments of specific parent IDs.' ), |
|
1437 'type' => 'array', |
|
1438 'items' => array( |
|
1439 'type' => 'integer', |
|
1440 ), |
|
1441 ); |
|
1442 |
|
1443 $query_params['parent_exclude'] = array( |
|
1444 'default' => array(), |
|
1445 'description' => __( 'Ensure result set excludes specific parent IDs.' ), |
|
1446 'type' => 'array', |
|
1447 'items' => array( |
|
1448 'type' => 'integer', |
|
1449 ), |
|
1450 ); |
|
1451 |
|
1452 $query_params['post'] = array( |
|
1453 'default' => array(), |
|
1454 'description' => __( 'Limit result set to comments assigned to specific post IDs.' ), |
|
1455 'type' => 'array', |
|
1456 'items' => array( |
|
1457 'type' => 'integer', |
|
1458 ), |
|
1459 ); |
|
1460 |
|
1461 $query_params['status'] = array( |
|
1462 'default' => 'approve', |
|
1463 'description' => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ), |
|
1464 'sanitize_callback' => 'sanitize_key', |
|
1465 'type' => 'string', |
|
1466 'validate_callback' => 'rest_validate_request_arg', |
|
1467 ); |
|
1468 |
|
1469 $query_params['type'] = array( |
|
1470 'default' => 'comment', |
|
1471 'description' => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ), |
|
1472 'sanitize_callback' => 'sanitize_key', |
|
1473 'type' => 'string', |
|
1474 'validate_callback' => 'rest_validate_request_arg', |
|
1475 ); |
|
1476 |
|
1477 $query_params['password'] = array( |
|
1478 'description' => __( 'The password for the post if it is password protected.' ), |
|
1479 'type' => 'string', |
|
1480 ); |
|
1481 |
|
1482 /** |
|
1483 * Filter collection parameters for the comments controller. |
|
1484 * |
|
1485 * This filter registers the collection parameter, but does not map the |
|
1486 * collection parameter to an internal WP_Comment_Query parameter. Use the |
|
1487 * `rest_comment_query` filter to set WP_Comment_Query parameters. |
|
1488 * |
|
1489 * @since 4.7.0 |
|
1490 * |
|
1491 * @param array $query_params JSON Schema-formatted collection parameters. |
|
1492 */ |
|
1493 return apply_filters( 'rest_comment_collection_params', $query_params ); |
|
1494 } |
|
1495 |
|
1496 /** |
|
1497 * Sets the comment_status of a given comment object when creating or updating a comment. |
|
1498 * |
|
1499 * @since 4.7.0 |
|
1500 * |
|
1501 * @param string|int $new_status New comment status. |
|
1502 * @param int $comment_id Comment ID. |
|
1503 * @return bool Whether the status was changed. |
|
1504 */ |
|
1505 protected function handle_status_param( $new_status, $comment_id ) { |
|
1506 $old_status = wp_get_comment_status( $comment_id ); |
|
1507 |
|
1508 if ( $new_status === $old_status ) { |
|
1509 return false; |
|
1510 } |
|
1511 |
|
1512 switch ( $new_status ) { |
|
1513 case 'approved' : |
|
1514 case 'approve': |
|
1515 case '1': |
|
1516 $changed = wp_set_comment_status( $comment_id, 'approve' ); |
|
1517 break; |
|
1518 case 'hold': |
|
1519 case '0': |
|
1520 $changed = wp_set_comment_status( $comment_id, 'hold' ); |
|
1521 break; |
|
1522 case 'spam' : |
|
1523 $changed = wp_spam_comment( $comment_id ); |
|
1524 break; |
|
1525 case 'unspam' : |
|
1526 $changed = wp_unspam_comment( $comment_id ); |
|
1527 break; |
|
1528 case 'trash' : |
|
1529 $changed = wp_trash_comment( $comment_id ); |
|
1530 break; |
|
1531 case 'untrash' : |
|
1532 $changed = wp_untrash_comment( $comment_id ); |
|
1533 break; |
|
1534 default : |
|
1535 $changed = false; |
|
1536 break; |
|
1537 } |
|
1538 |
|
1539 return $changed; |
|
1540 } |
|
1541 |
|
1542 /** |
|
1543 * Checks if the post can be read. |
|
1544 * |
|
1545 * Correctly handles posts with the inherit status. |
|
1546 * |
|
1547 * @since 4.7.0 |
|
1548 * |
|
1549 * @param WP_Post $post Post object. |
|
1550 * @param WP_REST_Request $request Request data to check. |
|
1551 * @return bool Whether post can be read. |
|
1552 */ |
|
1553 protected function check_read_post_permission( $post, $request ) { |
|
1554 $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); |
|
1555 $post_type = get_post_type_object( $post->post_type ); |
|
1556 |
|
1557 $has_password_filter = false; |
|
1558 |
|
1559 // Only check password if a specific post was queried for or a single comment |
|
1560 $requested_post = ! empty( $request['post'] ) && ( !is_array( $request['post'] ) || 1 === count( $request['post'] ) ); |
|
1561 $requested_comment = ! empty( $request['id'] ); |
|
1562 if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) { |
|
1563 add_filter( 'post_password_required', '__return_false' ); |
|
1564 |
|
1565 $has_password_filter = true; |
|
1566 } |
|
1567 |
|
1568 if ( post_password_required( $post ) ) { |
|
1569 $result = current_user_can( $post_type->cap->edit_post, $post->ID ); |
|
1570 } else { |
|
1571 $result = $posts_controller->check_read_permission( $post ); |
|
1572 } |
|
1573 |
|
1574 if ( $has_password_filter ) { |
|
1575 remove_filter( 'post_password_required', '__return_false' ); |
|
1576 } |
|
1577 |
|
1578 return $result; |
|
1579 } |
|
1580 |
|
1581 /** |
|
1582 * Checks if the comment can be read. |
|
1583 * |
|
1584 * @since 4.7.0 |
|
1585 * |
|
1586 * @param WP_Comment $comment Comment object. |
|
1587 * @param WP_REST_Request $request Request data to check. |
|
1588 * @return bool Whether the comment can be read. |
|
1589 */ |
|
1590 protected function check_read_permission( $comment, $request ) { |
|
1591 if ( ! empty( $comment->comment_post_ID ) ) { |
|
1592 $post = get_post( $comment->comment_post_ID ); |
|
1593 if ( $post ) { |
|
1594 if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) { |
|
1595 return true; |
|
1596 } |
|
1597 } |
|
1598 } |
|
1599 |
|
1600 if ( 0 === get_current_user_id() ) { |
|
1601 return false; |
|
1602 } |
|
1603 |
|
1604 if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) { |
|
1605 return false; |
|
1606 } |
|
1607 |
|
1608 if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) { |
|
1609 return true; |
|
1610 } |
|
1611 |
|
1612 return current_user_can( 'edit_comment', $comment->comment_ID ); |
|
1613 } |
|
1614 |
|
1615 /** |
|
1616 * Checks if a comment can be edited or deleted. |
|
1617 * |
|
1618 * @since 4.7.0 |
|
1619 * |
|
1620 * @param object $comment Comment object. |
|
1621 * @return bool Whether the comment can be edited or deleted. |
|
1622 */ |
|
1623 protected function check_edit_permission( $comment ) { |
|
1624 if ( 0 === (int) get_current_user_id() ) { |
|
1625 return false; |
|
1626 } |
|
1627 |
|
1628 if ( ! current_user_can( 'moderate_comments' ) ) { |
|
1629 return false; |
|
1630 } |
|
1631 |
|
1632 return current_user_can( 'edit_comment', $comment->comment_ID ); |
|
1633 } |
|
1634 |
|
1635 /** |
|
1636 * Checks a comment author email for validity. |
|
1637 * |
|
1638 * Accepts either a valid email address or empty string as a valid comment |
|
1639 * author email address. Setting the comment author email to an empty |
|
1640 * string is allowed when a comment is being updated. |
|
1641 * |
|
1642 * @since 4.7.0 |
|
1643 * |
|
1644 * @param string $value Author email value submitted. |
|
1645 * @param WP_REST_Request $request Full details about the request. |
|
1646 * @param string $param The parameter name. |
|
1647 * @return WP_Error|string The sanitized email address, if valid, |
|
1648 * otherwise an error. |
|
1649 */ |
|
1650 public function check_comment_author_email( $value, $request, $param ) { |
|
1651 $email = (string) $value; |
|
1652 if ( empty( $email ) ) { |
|
1653 return $email; |
|
1654 } |
|
1655 |
|
1656 $check_email = rest_validate_request_arg( $email, $request, $param ); |
|
1657 if ( is_wp_error( $check_email ) ) { |
|
1658 return $check_email; |
|
1659 } |
|
1660 |
|
1661 return $email; |
|
1662 } |
|
1663 } |