Completed
Branch develop (0e5e8d)
by
unknown
06:58
created

WP_REST_Comments_Controller::update_item()   C

Complexity

Conditions 10
Paths 10

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 27
nc 10
nop 1
dl 0
loc 47
rs 5.1578
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Access comments
5
 */
6
class WP_REST_Comments_Controller extends WP_REST_Controller {
7
8
	public function __construct() {
9
		$this->namespace = 'wp/v2';
10
		$this->rest_base = 'comments';
11
	}
12
13
	/**
14
	 * Register the routes for the objects of the controller.
15
	 */
16 View Code Duplication
	public function register_routes() {
17
18
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
19
			array(
20
				'methods'   => WP_REST_Server::READABLE,
21
				'callback'  => array( $this, 'get_items' ),
22
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
23
				'args'      => $this->get_collection_params(),
24
			),
25
			array(
26
				'methods'  => WP_REST_Server::CREATABLE,
27
				'callback' => array( $this, 'create_item' ),
28
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
29
				'args'     => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
30
			),
31
			'schema' => array( $this, 'get_public_item_schema' ),
32
		) );
33
34
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
35
			array(
36
				'methods'  => WP_REST_Server::READABLE,
37
				'callback' => array( $this, 'get_item' ),
38
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
39
				'args'     => array(
40
					'context'          => $this->get_context_param( array( 'default' => 'view' ) ),
41
				),
42
			),
43
			array(
44
				'methods'  => WP_REST_Server::EDITABLE,
45
				'callback' => array( $this, 'update_item' ),
46
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
47
				'args'     => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
48
			),
49
			array(
50
				'methods'  => WP_REST_Server::DELETABLE,
51
				'callback' => array( $this, 'delete_item' ),
52
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
53
				'args'     => array(
54
					'force'    => array(
55
						'default'     => false,
56
						'description' => __( 'Whether to bypass trash and force deletion.' ),
57
					),
58
				),
59
			),
60
			'schema' => array( $this, 'get_public_item_schema' ),
61
		) );
62
	}
63
64
	/**
65
	 * Check if a given request has access to read comments
66
	 *
67
	 * @param  WP_REST_Request $request Full details about the request.
68
	 * @return WP_Error|boolean
69
	 */
70
	public function get_items_permissions_check( $request ) {
71
72
		if ( ! empty( $request['post'] ) ) {
73
			foreach ( (array) $request['post'] as $post_id ) {
74
				$post = $this->get_post( $post_id );
75
				if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post ) ) {
76
					return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
77
				} else if ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) {
78
					return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) );
79
				}
80
			}
81
		}
82
83
		if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
84
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view comments with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
85
		}
86
87
		if ( ! current_user_can( 'edit_posts' ) ) {
88
			$protected_params = array( 'author', 'author_exclude', 'karma', 'author_email', 'type', 'status' );
89
			$forbidden_params = array();
90
			foreach ( $protected_params as $param ) {
91
				if ( 'status' === $param ) {
92
					if ( 'approve' !== $request[ $param ] ) {
93
						$forbidden_params[] = $param;
94
					}
95
				} else if ( 'type' === $param ) {
96
					if ( 'comment' !== $request[ $param ] ) {
97
						$forbidden_params[] = $param;
98
					}
99
				} else if ( ! empty( $request[ $param ] ) ) {
100
					$forbidden_params[] = $param;
101
				}
102
			}
103
			if ( ! empty( $forbidden_params ) ) {
104
				return new WP_Error( 'rest_forbidden_param', sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) );
105
			}
106
		}
107
108
		return true;
109
	}
110
111
	/**
112
	 * Get a list of comments.
113
	 *
114
	 * @param  WP_REST_Request $request Full details about the request.
115
	 * @return WP_Error|WP_REST_Response
116
	 */
117
	public function get_items( $request ) {
118
		$prepared_args = array(
119
			'author_email'    => isset( $request['author_email'] ) ? $request['author_email'] : '',
120
			'comment__in'     => $request['include'],
121
			'comment__not_in' => $request['exclude'],
122
			'karma'           => isset( $request['karma'] ) ? $request['karma'] : '',
123
			'number'          => $request['per_page'],
124
			'post__in'        => $request['post'],
125
			'parent__in'      => $request['parent'],
126
			'parent__not_in'  => $request['parent_exclude'],
127
			'search'          => $request['search'],
128
			'offset'          => $request['offset'],
129
			'orderby'         => $this->normalize_query_param( $request['orderby'] ),
130
			'order'           => $request['order'],
131
			'status'          => $request['status'],
132
			'type'            => $request['type'],
133
			'no_found_rows'   => false,
134
			'author__in'      => $request['author'],
135
			'author__not_in'  => $request['author_exclude'],
136
		);
137
138
		$prepared_args['date_query'] = array();
139
		// Set before into date query. Date query must be specified as an array of an array.
140
		if ( isset( $request['before'] ) ) {
141
			$prepared_args['date_query'][0]['before'] = $request['before'];
142
		}
143
144
		// Set after into date query. Date query must be specified as an array of an array.
145 View Code Duplication
		if ( isset( $request['after'] ) ) {
146
			$prepared_args['date_query'][0]['after'] = $request['after'];
147
		}
148
149
		if ( empty( $request['offset'] ) ) {
150
			$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
151
		}
152
		if ( empty( $request['search'] ) ) {
153
			$prepared_args['search'] = '';
154
		}
155
156
		/**
157
		 * Filter arguments, before passing to WP_Comment_Query, when querying comments via the REST API.
158
		 *
159
		 * @see https://developer.wordpress.org/reference/classes/wp_comment_query/
160
		 *
161
		 * @param array           $prepared_args Array of arguments for WP_Comment_Query.
162
		 * @param WP_REST_Request $request       The current request.
163
		 */
164
		$prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request );
165
166
		$query = new WP_Comment_Query;
167
		$query_result = $query->query( $prepared_args );
168
169
		$comments = array();
170 View Code Duplication
		foreach ( $query_result as $comment ) {
171
			if ( ! $this->check_read_permission( $comment ) ) {
172
				continue;
173
			}
174
175
			$data = $this->prepare_item_for_response( $comment, $request );
176
			$comments[] = $this->prepare_response_for_collection( $data );
177
		}
178
179
		$total_comments = (int) $query->found_comments;
180
		$max_pages = (int) $query->max_num_pages;
181
		if ( $total_comments < 1 ) {
182
			// Out-of-bounds, run the query again without LIMIT for total count
183
			unset( $prepared_args['number'] );
184
			unset( $prepared_args['offset'] );
185
			$query = new WP_Comment_Query;
186
			$prepared_args['count'] = true;
187
188
			$total_comments = $query->query( $prepared_args );
189
			$max_pages = ceil( $total_comments / $request['per_page'] );
190
		}
191
192
		$response = rest_ensure_response( $comments );
193
		$response->header( 'X-WP-Total', $total_comments );
194
		$response->header( 'X-WP-TotalPages', $max_pages );
195
196
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
197 View Code Duplication
		if ( $request['page'] > 1 ) {
198
			$prev_page = $request['page'] - 1;
199
			if ( $prev_page > $max_pages ) {
200
				$prev_page = $max_pages;
201
			}
202
			$prev_link = add_query_arg( 'page', $prev_page, $base );
203
			$response->link_header( 'prev', $prev_link );
204
		}
205 View Code Duplication
		if ( $max_pages > $request['page'] ) {
206
			$next_page = $request['page'] + 1;
207
			$next_link = add_query_arg( 'page', $next_page, $base );
208
			$response->link_header( 'next', $next_link );
209
		}
210
211
		return $response;
212
	}
213
214
	/**
215
	 * Check if a given request has access to read the comment
216
	 *
217
	 * @param  WP_REST_Request $request Full details about the request.
218
	 * @return WP_Error|boolean
219
	 */
220
	public function get_item_permissions_check( $request ) {
221
		$id = (int) $request['id'];
222
223
		$comment = get_comment( $id );
224
225
		if ( ! $comment ) {
226
			return true;
227
		}
228
229
		if ( ! $this->check_read_permission( $comment ) ) {
230
			return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read this comment.' ), array( 'status' => rest_authorization_required_code() ) );
231
		}
232
233
		$post = $this->get_post( $comment->comment_post_ID );
234
235 View Code Duplication
		if ( $post && ! $this->check_read_post_permission( $post ) ) {
236
			return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
237
		}
238
239
		if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
240
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this comment with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
241
		}
242
243
		return true;
244
	}
245
246
	/**
247
	 * Get a comment.
248
	 *
249
	 * @param  WP_REST_Request $request Full details about the request.
250
	 * @return WP_Error|WP_REST_Response
251
	 */
252
	public function get_item( $request ) {
253
		$id = (int) $request['id'];
254
255
		$comment = get_comment( $id );
256
		if ( empty( $comment ) ) {
257
			return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
258
		}
259
260
		if ( ! empty( $comment->comment_post_ID ) ) {
261
			$post = $this->get_post( $comment->comment_post_ID );
262
			if ( empty( $post ) ) {
263
				return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
264
			}
265
		}
266
267
		$data = $this->prepare_item_for_response( $comment, $request );
268
		$response = rest_ensure_response( $data );
269
270
		return $response;
271
	}
272
273
	/**
274
	 * Check if a given request has access to create a comment
275
	 *
276
	 * @param  WP_REST_Request $request Full details about the request.
277
	 * @return WP_Error|boolean
278
	 */
279
	public function create_item_permissions_check( $request ) {
280
281
		if ( ! is_user_logged_in() && get_option( 'comment_registration' ) ) {
282
			return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) );
283
		}
284
285
		// Limit who can set comment `author`, `karma` or `status` to anything other than the default.
286
		if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
287
			return new WP_Error( 'rest_comment_invalid_author', __( 'Comment author invalid.' ), array( 'status' => rest_authorization_required_code() ) );
288
		}
289
		if ( isset( $request['karma'] ) && $request['karma'] > 0 && ! current_user_can( 'moderate_comments' ) ) {
290
			return new WP_Error( 'rest_comment_invalid_karma', __( 'Sorry, you cannot set karma for comments.' ), array( 'status' => rest_authorization_required_code() ) );
291
		}
292
		if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
293
			return new WP_Error( 'rest_comment_invalid_status', __( 'Sorry, you cannot set status for comments.' ), array( 'status' => rest_authorization_required_code() ) );
294
		}
295
296
		if ( ! empty( $request['post'] ) && $post = $this->get_post( (int) $request['post'] ) ) {
297
298 View Code Duplication
			if ( ! $this->check_read_post_permission( $post ) ) {
299
				return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
300
			}
301
302
			if ( ! comments_open( $post->ID ) ) {
303
				return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed on this post.' ), array( 'status' => 403 ) );
304
			}
305
		}
306
307
		return true;
308
	}
309
310
	/**
311
	 * Create a comment.
312
	 *
313
	 * @param  WP_REST_Request $request Full details about the request.
314
	 * @return WP_Error|WP_REST_Response
315
	 */
316
	public function create_item( $request ) {
317
		if ( ! empty( $request['id'] ) ) {
318
			return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) );
319
		}
320
321
		$prepared_comment = $this->prepare_item_for_database( $request );
322
323
		// Setting remaining values before wp_insert_comment so we can
324
		// use wp_allow_comment().
325
		if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) {
326
			$prepared_comment['comment_date_gmt'] = current_time( 'mysql', true );
327
		}
328
329
		// Set author data if the user's logged in
330
		$missing_author = empty( $prepared_comment['user_id'] )
331
			&& empty( $prepared_comment['comment_author'] )
332
			&& empty( $prepared_comment['comment_author_email'] )
333
			&& empty( $prepared_comment['comment_author_url'] );
334
335
		if ( is_user_logged_in() && $missing_author ) {
336
			$user = wp_get_current_user();
337
			$prepared_comment['user_id'] = $user->ID;
338
			$prepared_comment['comment_author'] = $user->display_name;
339
			$prepared_comment['comment_author_email'] = $user->user_email;
340
			$prepared_comment['comment_author_url'] = $user->user_url;
341
		}
342
343
		if ( ! isset( $prepared_comment['comment_author_email'] ) ) {
344
			$prepared_comment['comment_author_email'] = '';
345
		}
346
		if ( ! isset( $prepared_comment['comment_author_url'] ) ) {
347
			$prepared_comment['comment_author_url'] = '';
348
		}
349
350
		$prepared_comment['comment_agent'] = '';
351
		$prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment );
352
353
		/**
354
		 * Filter a comment before it is inserted via the REST API.
355
		 *
356
		 * Allows modification of the comment right before it is inserted via `wp_insert_comment`.
357
		 *
358
		 * @param array           $prepared_comment The prepared comment data for `wp_insert_comment`.
359
		 * @param WP_REST_Request $request          Request used to insert the comment.
360
		 */
361
		$prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request );
362
363
		$comment_id = wp_insert_comment( $prepared_comment );
364
		if ( ! $comment_id ) {
365
			return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) );
366
		}
367
368
		if ( isset( $request['status'] ) ) {
369
			$comment = get_comment( $comment_id );
370
			$this->handle_status_param( $request['status'], $comment );
371
		}
372
373
		$comment = get_comment( $comment_id );
374
		$fields_update = $this->update_additional_fields_for_object( $comment, $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->update_additional...ct($comment, $request); of type boolean|WP_Error adds the type boolean to the return on line 376 which is incompatible with the return type documented by WP_REST_Comments_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
375
		if ( is_wp_error( $fields_update ) ) {
376
			return $fields_update;
377
		}
378
379
		$context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
380
		$request->set_param( 'context', $context );
381
		$response = $this->prepare_item_for_response( $comment, $request );
382
		$response = rest_ensure_response( $response );
383
		$response->set_status( 201 );
384
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) );
385
386
		/**
387
		 * Fires after a comment is created or updated via the REST API.
388
		 *
389
		 * @param array           $comment  Comment as it exists in the database.
390
		 * @param WP_REST_Request $request  The request sent to the API.
391
		 * @param boolean         $creating True when creating a comment, false when updating.
392
		 */
393
		do_action( 'rest_insert_comment', $comment, $request, true );
394
395
		return $response;
396
	}
397
398
	/**
399
	 * Check if a given request has access to update a comment
400
	 *
401
	 * @param  WP_REST_Request $request Full details about the request.
402
	 * @return WP_Error|boolean
403
	 */
404
	public function update_item_permissions_check( $request ) {
405
406
		$id = (int) $request['id'];
407
408
		$comment = get_comment( $id );
409
410
		if ( $comment && ! $this->check_edit_permission( $comment ) ) {
411
			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you can not edit this comment.' ), array( 'status' => rest_authorization_required_code() ) );
412
		}
413
414
		return true;
415
	}
416
417
	/**
418
	 * Edit a comment
419
	 *
420
	 * @param  WP_REST_Request $request Full details about the request.
421
	 * @return WP_Error|WP_REST_Response
422
	 */
423
	public function update_item( $request ) {
424
		$id = (int) $request['id'];
425
426
		$comment = get_comment( $id );
427
		if ( empty( $comment ) ) {
428
			return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
429
		}
430
431
		if ( isset( $request['type'] ) && $request['type'] !== $comment->comment_type ) {
432
			return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you cannot change the comment type.' ), array( 'status' => 404 ) );
433
		}
434
435
		$prepared_args = $this->prepare_item_for_database( $request );
436
437
		if ( empty( $prepared_args ) && isset( $request['status'] ) ) {
438
			// Only the comment status is being changed.
439
			$change = $this->handle_status_param( $request['status'], $comment );
440
			if ( ! $change ) {
441
				return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) );
442
			}
443
		} else {
444
			$prepared_args['comment_ID'] = $id;
445
446
			$updated = wp_update_comment( $prepared_args );
447
			if ( 0 === $updated ) {
448
				return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) );
449
			}
450
451
			if ( isset( $request['status'] ) ) {
452
				$this->handle_status_param( $request['status'], $comment );
453
			}
454
		}
455
456
		$comment = get_comment( $id );
457
		$fields_update = $this->update_additional_fields_for_object( $comment, $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->update_additional...ct($comment, $request); of type boolean|WP_Error adds the type boolean to the return on line 459 which is incompatible with the return type documented by WP_REST_Comments_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
458
		if ( is_wp_error( $fields_update ) ) {
459
			return $fields_update;
460
		}
461
462
		$request->set_param( 'context', 'edit' );
463
		$response = $this->prepare_item_for_response( $comment, $request );
464
465
		/* This action is documented in lib/endpoints/class-wp-rest-comments-controller.php */
466
		do_action( 'rest_insert_comment', $comment, $request, false );
467
468
		return rest_ensure_response( $response );
469
	}
470
471
	/**
472
	 * Check if a given request has access to delete a comment
473
	 *
474
	 * @param  WP_REST_Request $request Full details about the request.
475
	 * @return WP_Error|boolean
476
	 */
477 View Code Duplication
	public function delete_item_permissions_check( $request ) {
478
		$id = (int) $request['id'];
479
		$comment = get_comment( $id );
480
		if ( ! $comment ) {
481
			return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
482
		}
483
		if ( ! $this->check_edit_permission( $comment ) ) {
484
			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you can not delete this comment.' ), array( 'status' => rest_authorization_required_code() ) );
485
		}
486
		return true;
487
	}
488
489
	/**
490
	 * Delete a comment.
491
	 *
492
	 * @param  WP_REST_Request $request Full details about the request.
493
	 * @return WP_Error|WP_REST_Response
494
	 */
495
	public function delete_item( $request ) {
496
		$id = (int) $request['id'];
497
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
498
499
		$comment = get_comment( $id );
500
		if ( empty( $comment ) ) {
501
			return new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment id.' ), array( 'status' => 404 ) );
502
		}
503
504
		/**
505
		 * Filter whether a comment is trashable.
506
		 *
507
		 * Return false to disable trash support for the post.
508
		 *
509
		 * @param boolean $supports_trash Whether the post type support trashing.
510
		 * @param WP_Post $comment        The comment object being considered for trashing support.
511
		 */
512
		$supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
513
514
		$request->set_param( 'context', 'edit' );
515
		$response = $this->prepare_item_for_response( $comment, $request );
516
517
		if ( $force ) {
518
			$result = wp_delete_comment( $comment->comment_ID, true );
519
		} else {
520
			// If we don't support trashing for this type, error out
521
			if ( ! $supports_trash ) {
522
				return new WP_Error( 'rest_trash_not_supported', __( 'The comment does not support trashing.' ), array( 'status' => 501 ) );
523
			}
524
525 View Code Duplication
			if ( 'trash' === $comment->comment_approved ) {
526
				return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) );
527
			}
528
529
			$result = wp_trash_comment( $comment->comment_ID );
530
		}
531
532
		if ( ! $result ) {
533
			return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
534
		}
535
536
		/**
537
		 * Fires after a comment is deleted via the REST API.
538
		 *
539
		 * @param object           $comment  The deleted comment data.
540
		 * @param WP_REST_Response $response The response returned from the API.
541
		 * @param WP_REST_Request  $request  The request sent to the API.
542
		 */
543
		do_action( 'rest_delete_comment', $comment, $response, $request );
544
545
		return $response;
546
	}
547
548
	/**
549
	 * Prepare a single comment output for response.
550
	 *
551
	 * @param  object          $comment Comment object.
552
	 * @param  WP_REST_Request $request Request object.
553
	 * @return WP_REST_Response $response
554
	 */
555
	public function prepare_item_for_response( $comment, $request ) {
556
		$data = array(
557
			'id'                 => (int) $comment->comment_ID,
558
			'post'               => (int) $comment->comment_post_ID,
559
			'parent'             => (int) $comment->comment_parent,
560
			'author'             => (int) $comment->user_id,
561
			'author_name'        => $comment->comment_author,
562
			'author_email'       => $comment->comment_author_email,
563
			'author_url'         => $comment->comment_author_url,
564
			'author_ip'          => $comment->comment_author_IP,
565
			'author_user_agent'  => $comment->comment_agent,
566
			'date'               => mysql_to_rfc3339( $comment->comment_date ),
567
			'date_gmt'           => mysql_to_rfc3339( $comment->comment_date_gmt ),
568
			'content'            => array(
569
				'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ),
570
				'raw'      => $comment->comment_content,
571
			),
572
			'karma'              => (int) $comment->comment_karma,
573
			'link'               => get_comment_link( $comment ),
574
			'status'             => $this->prepare_status_response( $comment->comment_approved ),
575
			'type'               => get_comment_type( $comment->comment_ID ),
576
		);
577
578
		$schema = $this->get_item_schema();
579
580
		if ( ! empty( $schema['properties']['author_avatar_urls'] ) ) {
581
			$data['author_avatar_urls'] = rest_get_avatar_urls( $comment->comment_author_email );
582
		}
583
584
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
585
		$data = $this->add_additional_fields_to_object( $data, $request );
586
		$data = $this->filter_response_by_context( $data, $context );
587
588
		// Wrap the data in a response object
589
		$response = rest_ensure_response( $data );
590
591
		$response->add_links( $this->prepare_links( $comment ) );
592
593
		/**
594
		 * Filter a comment returned from the API.
595
		 *
596
		 * Allows modification of the comment right before it is returned.
597
		 *
598
		 * @param WP_REST_Response  $response   The response object.
599
		 * @param object            $comment    The original comment object.
600
		 * @param WP_REST_Request   $request    Request used to generate the response.
601
		 */
602
		return apply_filters( 'rest_prepare_comment', $response, $comment, $request );
603
	}
604
605
	/**
606
	 * Prepare links for the request.
607
	 *
608
	 * @param object $comment Comment object.
609
	 * @return array Links for the given comment.
610
	 */
611
	protected function prepare_links( $comment ) {
612
		$links = array(
613
			'self' => array(
614
				'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ),
615
			),
616
			'collection' => array(
617
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
618
			),
619
		);
620
621
		if ( 0 !== (int) $comment->user_id ) {
622
			$links['author'] = array(
623
				'href'       => rest_url( 'wp/v2/users/' . $comment->user_id ),
624
				'embeddable' => true,
625
			);
626
		}
627
628
		if ( 0 !== (int) $comment->comment_post_ID ) {
629
			$post = $this->get_post( $comment->comment_post_ID );
630
			if ( ! empty( $post->ID ) ) {
631
				$obj = get_post_type_object( $post->post_type );
632
				$base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
633
634
				$links['up'] = array(
635
					'href'       => rest_url( 'wp/v2/' . $base . '/' . $comment->comment_post_ID ),
636
					'embeddable' => true,
637
					'post_type'  => $post->post_type,
638
				);
639
			}
640
		}
641
642
		if ( 0 !== (int) $comment->comment_parent ) {
643
			$links['in-reply-to'] = array(
644
				'href'       => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ),
645
				'embeddable' => true,
646
			);
647
		}
648
649
		return $links;
650
	}
651
652
	/**
653
	 * Prepend internal property prefix to query parameters to match our response fields.
654
	 *
655
	 * @param  string $query_param
656
	 * @return string $normalized
657
	 */
658
	protected function normalize_query_param( $query_param ) {
659
		$prefix = 'comment_';
660
661
		switch ( $query_param ) {
662
			case 'id':
663
				$normalized = $prefix . 'ID';
664
				break;
665
			case 'post':
666
				$normalized = $prefix . 'post_ID';
667
				break;
668
			case 'parent':
669
				$normalized = $prefix . 'parent';
670
				break;
671
			case 'include':
672
				$normalized = 'comment__in';
673
				break;
674
			default:
675
				$normalized = $prefix . $query_param;
676
				break;
677
		}
678
679
		return $normalized;
680
	}
681
682
	/**
683
	 * Check comment_approved to set comment status for single comment output.
684
	 *
685
	 * @param  string|int $comment_approved
686
	 * @return string     $status
687
	 */
688
	protected function prepare_status_response( $comment_approved ) {
689
690
		switch ( $comment_approved ) {
691
			case 'hold':
692
			case '0':
693
				$status = 'hold';
694
				break;
695
696
			case 'approve':
697
			case '1':
698
				$status = 'approved';
699
				break;
700
701
			case 'spam':
702
			case 'trash':
703
			default:
704
				$status = $comment_approved;
705
				break;
706
		}
707
708
		return $status;
709
	}
710
711
	/**
712
	 * Prepare a single comment to be inserted into the database.
713
	 *
714
	 * @param  WP_REST_Request $request Request object.
715
	 * @return array|WP_Error  $prepared_comment
716
	 */
717
	protected function prepare_item_for_database( $request ) {
718
		$prepared_comment = array();
719
720
		if ( isset( $request['content'] ) ) {
721
			$prepared_comment['comment_content'] = $request['content'];
722
		}
723
724
		if ( isset( $request['post'] ) ) {
725
			$prepared_comment['comment_post_ID'] = (int) $request['post'];
726
		}
727
728
		if ( isset( $request['parent'] ) ) {
729
			$prepared_comment['comment_parent'] = $request['parent'];
730
		}
731
732
		if ( isset( $request['author'] ) ) {
733
			$prepared_comment['user_id'] = $request['author'];
734
		}
735
736
		if ( isset( $request['author_name'] ) ) {
737
			$prepared_comment['comment_author'] = $request['author_name'];
738
		}
739
740
		if ( isset( $request['author_email'] ) ) {
741
			$prepared_comment['comment_author_email'] = $request['author_email'];
742
		}
743
744
		if ( isset( $request['author_url'] ) ) {
745
			$prepared_comment['comment_author_url'] = $request['author_url'];
746
		}
747
748
		if ( isset( $request['author_ip'] ) ) {
749
			$prepared_comment['comment_author_IP'] = $request['author_ip'];
750
		}
751
752
		if ( isset( $request['type'] ) ) {
753
			$prepared_comment['comment_type'] = $request['type'];
754
		}
755
756
		if ( isset( $request['karma'] ) ) {
757
			$prepared_comment['comment_karma'] = $request['karma'] ;
758
		}
759
760
		if ( ! empty( $request['date'] ) ) {
761
			$date_data = rest_get_date_with_gmt( $request['date'] );
762
763
			if ( ! empty( $date_data ) ) {
764
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
765
			}
766
		} elseif ( ! empty( $request['date_gmt'] ) ) {
767
			$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
768
769
			if ( ! empty( $date_data ) ) {
770
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
771
			}
772
		}
773
774
		return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request );
775
	}
776
777
	/**
778
	 * Get the Comment's schema, conforming to JSON Schema
779
	 *
780
	 * @return array
781
	 */
782
	public function get_item_schema() {
783
		$schema = array(
784
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
785
			'title'                => 'comment',
786
			'type'                 => 'object',
787
			'properties'           => array(
788
				'id'               => array(
789
					'description'  => __( 'Unique identifier for the object.' ),
790
					'type'         => 'integer',
791
					'context'      => array( 'view', 'edit', 'embed' ),
792
					'readonly'     => true,
793
				),
794
				'author'           => array(
795
					'description'  => __( 'The id of the user object, if author was a user.' ),
796
					'type'         => 'integer',
797
					'context'      => array( 'view', 'edit', 'embed' ),
798
				),
799
				'author_email'     => array(
800
					'description'  => __( 'Email address for the object author.' ),
801
					'type'         => 'string',
802
					'format'       => 'email',
803
					'context'      => array( 'edit' ),
804
				),
805
				'author_ip'     => array(
806
					'description'  => __( 'IP address for the object author.' ),
807
					'type'         => 'string',
808
					'format'       => 'ipv4',
809
					'context'      => array( 'edit' ),
810
					'arg_options'  => array(
811
						'default'           => '127.0.0.1',
812
					),
813
				),
814
				'author_name'     => array(
815
					'description'  => __( 'Display name for the object author.' ),
816
					'type'         => 'string',
817
					'context'      => array( 'view', 'edit', 'embed' ),
818
					'arg_options'  => array(
819
						'sanitize_callback' => 'sanitize_text_field',
820
						'default'           => '',
821
					),
822
				),
823
				'author_url'       => array(
824
					'description'  => __( 'URL for the object author.' ),
825
					'type'         => 'string',
826
					'format'       => 'uri',
827
					'context'      => array( 'view', 'edit', 'embed' ),
828
				),
829
				'author_user_agent'     => array(
830
					'description'  => __( 'User agent for the object author.' ),
831
					'type'         => 'string',
832
					'context'      => array( 'edit' ),
833
					'readonly'     => true,
834
				),
835
				'content'          => array(
836
					'description'     => __( 'The content for the object.' ),
837
					'type'            => 'object',
838
					'context'         => array( 'view', 'edit', 'embed' ),
839
					'properties'      => array(
840
						'raw'         => array(
841
							'description'     => __( 'Content for the object, as it exists in the database.' ),
842
							'type'            => 'string',
843
							'context'         => array( 'edit' ),
844
						),
845
						'rendered'    => array(
846
							'description'     => __( 'HTML content for the object, transformed for display.' ),
847
							'type'            => 'string',
848
							'context'         => array( 'view', 'edit', 'embed' ),
849
						),
850
					),
851
					'arg_options'  => array(
852
						'sanitize_callback' => 'wp_filter_post_kses',
853
						'default'           => '',
854
					),
855
				),
856
				'date'             => array(
857
					'description'  => __( 'The date the object was published.' ),
858
					'type'         => 'string',
859
					'format'       => 'date-time',
860
					'context'      => array( 'view', 'edit', 'embed' ),
861
				),
862
				'date_gmt'         => array(
863
					'description'  => __( 'The date the object was published as GMT.' ),
864
					'type'         => 'string',
865
					'format'       => 'date-time',
866
					'context'      => array( 'view', 'edit' ),
867
				),
868
				'karma'             => array(
869
					'description'  => __( 'Karma for the object.' ),
870
					'type'         => 'integer',
871
					'context'      => array( 'edit' ),
872
				),
873
				'link'             => array(
874
					'description'  => __( 'URL to the object.' ),
875
					'type'         => 'string',
876
					'format'       => 'uri',
877
					'context'      => array( 'view', 'edit', 'embed' ),
878
					'readonly'     => true,
879
				),
880
				'parent'           => array(
881
					'description'  => __( 'The id for the parent of the object.' ),
882
					'type'         => 'integer',
883
					'context'      => array( 'view', 'edit', 'embed' ),
884
					'arg_options'  => array(
885
						'default'           => 0,
886
					),
887
				),
888
				'post'             => array(
889
					'description'  => __( 'The id of the associated post object.' ),
890
					'type'         => 'integer',
891
					'context'      => array( 'view', 'edit' ),
892
					'arg_options'  => array(
893
						'default'           => 0,
894
					),
895
				),
896
				'status'           => array(
897
					'description'  => __( 'State of the object.' ),
898
					'type'         => 'string',
899
					'context'      => array( 'view', 'edit' ),
900
					'arg_options'  => array(
901
						'sanitize_callback' => 'sanitize_key',
902
					),
903
				),
904
				'type'             => array(
905
					'description'  => __( 'Type of Comment for the object.' ),
906
					'type'         => 'string',
907
					'context'      => array( 'view', 'edit', 'embed' ),
908
					'arg_options'  => array(
909
						'sanitize_callback' => 'sanitize_key',
910
						'default'           => '',
911
					),
912
				),
913
			),
914
		);
915
916 View Code Duplication
		if ( get_option( 'show_avatars' ) ) {
917
			$avatar_properties = array();
918
919
			$avatar_sizes = rest_get_avatar_sizes();
920
			foreach ( $avatar_sizes as $size ) {
921
				$avatar_properties[ $size ] = array(
922
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
923
					'type'        => 'string',
924
					'format'      => 'uri',
925
					'context'     => array( 'embed', 'view', 'edit' ),
926
				);
927
			}
928
929
			$schema['properties']['author_avatar_urls'] = array(
930
				'description'   => __( 'Avatar URLs for the object author.' ),
931
				'type'          => 'object',
932
				'context'       => array( 'view', 'edit', 'embed' ),
933
				'readonly'      => true,
934
				'properties'    => $avatar_properties,
935
			);
936
		}
937
938
		return $this->add_additional_fields_schema( $schema );
939
	}
940
941
	/**
942
	 * Get the query params for collections
943
	 *
944
	 * @return array
945
	 */
946
	public function get_collection_params() {
947
		$query_params = parent::get_collection_params();
948
949
		$query_params['context']['default'] = 'view';
950
951
		$query_params['after'] = array(
952
			'description'       => __( 'Limit response to resources published after a given ISO8601 compliant date.' ),
953
			'type'              => 'string',
954
			'format'            => 'date-time',
955
			'validate_callback' => 'rest_validate_request_arg',
956
		);
957
		$query_params['author'] = array(
958
			'description'       => __( 'Limit result set to comments assigned to specific user ids. Requires authorization.' ),
959
			'sanitize_callback' => 'wp_parse_id_list',
960
			'type'              => 'array',
961
			'validate_callback' => 'rest_validate_request_arg',
962
		);
963
		$query_params['author_exclude'] = array(
964
			'description'       => __( 'Ensure result set excludes comments assigned to specific user ids. Requires authorization.' ),
965
			'sanitize_callback' => 'wp_parse_id_list',
966
			'type'              => 'array',
967
			'validate_callback' => 'rest_validate_request_arg',
968
		);
969
		$query_params['author_email'] = array(
970
			'default'           => null,
971
			'description'       => __( 'Limit result set to that from a specific author email. Requires authorization.' ),
972
			'format'            => 'email',
973
			'sanitize_callback' => 'sanitize_email',
974
			'validate_callback' => 'rest_validate_request_arg',
975
			'type'              => 'string',
976
		);
977
		$query_params['before'] = array(
978
			'description'       => __( 'Limit response to resources published before a given ISO8601 compliant date.' ),
979
			'type'              => 'string',
980
			'format'            => 'date-time',
981
			'validate_callback' => 'rest_validate_request_arg',
982
		);
983
		$query_params['exclude'] = array(
984
			'description'        => __( 'Ensure result set excludes specific ids.' ),
985
			'type'               => 'array',
986
			'default'            => array(),
987
			'sanitize_callback'  => 'wp_parse_id_list',
988
			'validate_callback'  => 'rest_validate_request_arg',
989
		);
990
		$query_params['include'] = array(
991
			'description'        => __( 'Limit result set to specific ids.' ),
992
			'type'               => 'array',
993
			'default'            => array(),
994
			'sanitize_callback'  => 'wp_parse_id_list',
995
			'validate_callback'  => 'rest_validate_request_arg',
996
		);
997
		$query_params['karma'] = array(
998
			'default'           => null,
999
			'description'       => __( 'Limit result set to that of a particular comment karma. Requires authorization.' ),
1000
			'sanitize_callback' => 'absint',
1001
			'type'              => 'integer',
1002
			'validate_callback'  => 'rest_validate_request_arg',
1003
		);
1004
		$query_params['offset'] = array(
1005
			'description'        => __( 'Offset the result set by a specific number of comments.' ),
1006
			'type'               => 'integer',
1007
			'sanitize_callback'  => 'absint',
1008
			'validate_callback'  => 'rest_validate_request_arg',
1009
		);
1010
		$query_params['order']      = array(
1011
			'description'           => __( 'Order sort attribute ascending or descending.' ),
1012
			'type'                  => 'string',
1013
			'sanitize_callback'     => 'sanitize_key',
1014
			'validate_callback'     => 'rest_validate_request_arg',
1015
			'default'               => 'desc',
1016
			'enum'                  => array(
1017
				'asc',
1018
				'desc',
1019
			),
1020
		);
1021
		$query_params['orderby']    = array(
1022
			'description'           => __( 'Sort collection by object attribute.' ),
1023
			'type'                  => 'string',
1024
			'sanitize_callback'     => 'sanitize_key',
1025
			'validate_callback'     => 'rest_validate_request_arg',
1026
			'default'               => 'date_gmt',
1027
			'enum'                  => array(
1028
				'date',
1029
				'date_gmt',
1030
				'id',
1031
				'include',
1032
				'post',
1033
				'parent',
1034
				'type',
1035
			),
1036
		);
1037
		$query_params['parent'] = array(
1038
			'default'           => array(),
1039
			'description'       => __( 'Limit result set to resources of specific parent ids.' ),
1040
			'sanitize_callback' => 'wp_parse_id_list',
1041
			'type'              => 'array',
1042
			'validate_callback' => 'rest_validate_request_arg',
1043
		);
1044
		$query_params['parent_exclude'] = array(
1045
			'default'           => array(),
1046
			'description'       => __( 'Ensure result set excludes specific parent ids.' ),
1047
			'sanitize_callback' => 'wp_parse_id_list',
1048
			'type'              => 'array',
1049
			'validate_callback' => 'rest_validate_request_arg',
1050
		);
1051
		$query_params['post']   = array(
1052
			'default'           => array(),
1053
			'description'       => __( 'Limit result set to resources assigned to specific post ids.' ),
1054
			'type'              => 'array',
1055
			'sanitize_callback' => 'wp_parse_id_list',
1056
			'validate_callback' => 'rest_validate_request_arg',
1057
		);
1058
		$query_params['status'] = array(
1059
			'default'           => 'approve',
1060
			'description'       => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
1061
			'sanitize_callback' => 'sanitize_key',
1062
			'type'              => 'string',
1063
			'validate_callback' => 'rest_validate_request_arg',
1064
		);
1065
		$query_params['type'] = array(
1066
			'default'           => 'comment',
1067
			'description'       => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ),
1068
			'sanitize_callback' => 'sanitize_key',
1069
			'type'              => 'string',
1070
			'validate_callback' => 'rest_validate_request_arg',
1071
		);
1072
		return $query_params;
1073
	}
1074
1075
	/**
1076
	 * Set the comment_status of a given comment object when creating or updating a comment.
1077
	 *
1078
	 * @param string|int $new_status
1079
	 * @param object     $comment
1080
	 * @return boolean   $changed
1081
	 */
1082
	protected function handle_status_param( $new_status, $comment ) {
1083
		$old_status = wp_get_comment_status( $comment->comment_ID );
1084
1085
		if ( $new_status === $old_status ) {
1086
			return false;
1087
		}
1088
1089
		switch ( $new_status ) {
1090
			case 'approved' :
1091
			case 'approve':
1092
			case '1':
1093
				$changed = wp_set_comment_status( $comment->comment_ID, 'approve' );
1094
				break;
1095
			case 'hold':
1096
			case '0':
1097
				$changed = wp_set_comment_status( $comment->comment_ID, 'hold' );
1098
				break;
1099
			case 'spam' :
1100
				$changed = wp_spam_comment( $comment->comment_ID );
1101
				break;
1102
			case 'unspam' :
1103
				$changed = wp_unspam_comment( $comment->comment_ID );
1104
				break;
1105
			case 'trash' :
1106
				$changed = wp_trash_comment( $comment->comment_ID );
1107
				break;
1108
			case 'untrash' :
1109
				$changed = wp_untrash_comment( $comment->comment_ID );
1110
				break;
1111
			default :
1112
				$changed = false;
1113
				break;
1114
		}
1115
1116
		return $changed;
1117
	}
1118
1119
	/**
1120
	 * Check if we can read a post.
1121
	 *
1122
	 * Correctly handles posts with the inherit status.
1123
	 *
1124
	 * @param  WP_Post $post Post Object.
1125
	 * @return boolean Can we read it?
1126
	 */
1127
	protected function check_read_post_permission( $post ) {
1128
		$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
1129
1130
		return $posts_controller->check_read_permission( $post );
1131
	}
1132
1133
	/**
1134
	 * Check if we can read a comment.
1135
	 *
1136
	 * @param  object  $comment Comment object.
1137
	 * @return boolean Can we read it?
1138
	 */
1139
	protected function check_read_permission( $comment ) {
1140
1141
		if ( 1 === (int) $comment->comment_approved ) {
1142
			return true;
1143
		}
1144
1145
		if ( 0 === get_current_user_id() ) {
1146
			return false;
1147
		}
1148
1149
		if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) {
1150
			return false;
1151
		}
1152
1153
		$post = $this->get_post( $comment->comment_post_ID );
1154
		if ( $comment->comment_post_ID && $post ) {
1155
			if ( ! $this->check_read_post_permission( $post ) ) {
1156
				return false;
1157
			}
1158
		}
1159
1160
		if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
1161
			return true;
1162
		}
1163
1164
		return current_user_can( 'edit_comment', $comment->comment_ID );
1165
	}
1166
1167
	/**
1168
	 * Check if we can edit or delete a comment.
1169
	 *
1170
	 * @param  object  $comment Comment object.
1171
	 * @return boolean Can we edit or delete it?
1172
	 */
1173
	protected function check_edit_permission( $comment ) {
1174
		if ( 0 === (int) get_current_user_id() ) {
1175
			return false;
1176
		}
1177
1178
		if ( ! current_user_can( 'moderate_comments' ) ) {
1179
			return false;
1180
		}
1181
1182
		return current_user_can( 'edit_comment', $comment->comment_ID );
1183
	}
1184
}
1185