Completed
Branch develop (a2474f)
by
unknown
02:30
created

WP_REST_Comments_Controller::prepare_links()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 51
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 30
nc 32
nop 1
dl 0
loc 51
rs 6.9743
c 0
b 0
f 0

How to fix   Long Method   

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
	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
		// Only grab one comment to verify the comment has children.
650
		$comment_children = $comment->get_children( array( 'number' => 1, 'count' => true ) );
651
		if ( ! empty( $comment_children ) ) {
652
			$args = array( 'parent' => $comment->comment_ID );
653
			$rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) );
654
655
			$links['children'] = array(
656
				'href' => $rest_url,
657
			);
658
		}
659
660
		return $links;
661
	}
662
663
	/**
664
	 * Prepend internal property prefix to query parameters to match our response fields.
665
	 *
666
	 * @param  string $query_param
667
	 * @return string $normalized
668
	 */
669
	protected function normalize_query_param( $query_param ) {
670
		$prefix = 'comment_';
671
672
		switch ( $query_param ) {
673
			case 'id':
674
				$normalized = $prefix . 'ID';
675
				break;
676
			case 'post':
677
				$normalized = $prefix . 'post_ID';
678
				break;
679
			case 'parent':
680
				$normalized = $prefix . 'parent';
681
				break;
682
			case 'include':
683
				$normalized = 'comment__in';
684
				break;
685
			default:
686
				$normalized = $prefix . $query_param;
687
				break;
688
		}
689
690
		return $normalized;
691
	}
692
693
	/**
694
	 * Check comment_approved to set comment status for single comment output.
695
	 *
696
	 * @param  string|int $comment_approved
697
	 * @return string     $status
698
	 */
699
	protected function prepare_status_response( $comment_approved ) {
700
701
		switch ( $comment_approved ) {
702
			case 'hold':
703
			case '0':
704
				$status = 'hold';
705
				break;
706
707
			case 'approve':
708
			case '1':
709
				$status = 'approved';
710
				break;
711
712
			case 'spam':
713
			case 'trash':
714
			default:
715
				$status = $comment_approved;
716
				break;
717
		}
718
719
		return $status;
720
	}
721
722
	/**
723
	 * Prepare a single comment to be inserted into the database.
724
	 *
725
	 * @param  WP_REST_Request $request Request object.
726
	 * @return array|WP_Error  $prepared_comment
727
	 */
728
	protected function prepare_item_for_database( $request ) {
729
		$prepared_comment = array();
730
731
		if ( isset( $request['content'] ) ) {
732
			$prepared_comment['comment_content'] = $request['content'];
733
		}
734
735
		if ( isset( $request['post'] ) ) {
736
			$prepared_comment['comment_post_ID'] = (int) $request['post'];
737
		}
738
739
		if ( isset( $request['parent'] ) ) {
740
			$prepared_comment['comment_parent'] = $request['parent'];
741
		}
742
743
		if ( isset( $request['author'] ) ) {
744
			$prepared_comment['user_id'] = $request['author'];
745
		}
746
747
		if ( isset( $request['author_name'] ) ) {
748
			$prepared_comment['comment_author'] = $request['author_name'];
749
		}
750
751
		if ( isset( $request['author_email'] ) ) {
752
			$prepared_comment['comment_author_email'] = $request['author_email'];
753
		}
754
755
		if ( isset( $request['author_url'] ) ) {
756
			$prepared_comment['comment_author_url'] = $request['author_url'];
757
		}
758
759
		if ( isset( $request['author_ip'] ) ) {
760
			$prepared_comment['comment_author_IP'] = $request['author_ip'];
761
		}
762
763
		if ( isset( $request['type'] ) ) {
764
			$prepared_comment['comment_type'] = $request['type'];
765
		}
766
767
		if ( isset( $request['karma'] ) ) {
768
			$prepared_comment['comment_karma'] = $request['karma'] ;
769
		}
770
771
		if ( ! empty( $request['date'] ) ) {
772
			$date_data = rest_get_date_with_gmt( $request['date'] );
773
774
			if ( ! empty( $date_data ) ) {
775
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
776
			}
777
		} elseif ( ! empty( $request['date_gmt'] ) ) {
778
			$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
779
780
			if ( ! empty( $date_data ) ) {
781
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
782
			}
783
		}
784
785
		return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request );
786
	}
787
788
	/**
789
	 * Get the Comment's schema, conforming to JSON Schema
790
	 *
791
	 * @return array
792
	 */
793
	public function get_item_schema() {
794
		$schema = array(
795
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
796
			'title'                => 'comment',
797
			'type'                 => 'object',
798
			'properties'           => array(
799
				'id'               => array(
800
					'description'  => __( 'Unique identifier for the object.' ),
801
					'type'         => 'integer',
802
					'context'      => array( 'view', 'edit', 'embed' ),
803
					'readonly'     => true,
804
				),
805
				'author'           => array(
806
					'description'  => __( 'The id of the user object, if author was a user.' ),
807
					'type'         => 'integer',
808
					'context'      => array( 'view', 'edit', 'embed' ),
809
				),
810
				'author_email'     => array(
811
					'description'  => __( 'Email address for the object author.' ),
812
					'type'         => 'string',
813
					'format'       => 'email',
814
					'context'      => array( 'edit' ),
815
				),
816
				'author_ip'     => array(
817
					'description'  => __( 'IP address for the object author.' ),
818
					'type'         => 'string',
819
					'format'       => 'ipv4',
820
					'context'      => array( 'edit' ),
821
					'arg_options'  => array(
822
						'default'           => '127.0.0.1',
823
					),
824
				),
825
				'author_name'     => array(
826
					'description'  => __( 'Display name for the object author.' ),
827
					'type'         => 'string',
828
					'context'      => array( 'view', 'edit', 'embed' ),
829
					'arg_options'  => array(
830
						'sanitize_callback' => 'sanitize_text_field',
831
						'default'           => '',
832
					),
833
				),
834
				'author_url'       => array(
835
					'description'  => __( 'URL for the object author.' ),
836
					'type'         => 'string',
837
					'format'       => 'uri',
838
					'context'      => array( 'view', 'edit', 'embed' ),
839
				),
840
				'author_user_agent'     => array(
841
					'description'  => __( 'User agent for the object author.' ),
842
					'type'         => 'string',
843
					'context'      => array( 'edit' ),
844
					'readonly'     => true,
845
				),
846
				'content'          => array(
847
					'description'     => __( 'The content for the object.' ),
848
					'type'            => 'object',
849
					'context'         => array( 'view', 'edit', 'embed' ),
850
					'properties'      => array(
851
						'raw'         => array(
852
							'description'     => __( 'Content for the object, as it exists in the database.' ),
853
							'type'            => 'string',
854
							'context'         => array( 'edit' ),
855
						),
856
						'rendered'    => array(
857
							'description'     => __( 'HTML content for the object, transformed for display.' ),
858
							'type'            => 'string',
859
							'context'         => array( 'view', 'edit', 'embed' ),
860
						),
861
					),
862
					'arg_options'  => array(
863
						'sanitize_callback' => 'wp_filter_post_kses',
864
						'default'           => '',
865
					),
866
				),
867
				'date'             => array(
868
					'description'  => __( 'The date the object was published.' ),
869
					'type'         => 'string',
870
					'format'       => 'date-time',
871
					'context'      => array( 'view', 'edit', 'embed' ),
872
				),
873
				'date_gmt'         => array(
874
					'description'  => __( 'The date the object was published as GMT.' ),
875
					'type'         => 'string',
876
					'format'       => 'date-time',
877
					'context'      => array( 'view', 'edit' ),
878
				),
879
				'karma'             => array(
880
					'description'  => __( 'Karma for the object.' ),
881
					'type'         => 'integer',
882
					'context'      => array( 'edit' ),
883
				),
884
				'link'             => array(
885
					'description'  => __( 'URL to the object.' ),
886
					'type'         => 'string',
887
					'format'       => 'uri',
888
					'context'      => array( 'view', 'edit', 'embed' ),
889
					'readonly'     => true,
890
				),
891
				'parent'           => array(
892
					'description'  => __( 'The id for the parent of the object.' ),
893
					'type'         => 'integer',
894
					'context'      => array( 'view', 'edit', 'embed' ),
895
					'arg_options'  => array(
896
						'default'           => 0,
897
					),
898
				),
899
				'post'             => array(
900
					'description'  => __( 'The id of the associated post object.' ),
901
					'type'         => 'integer',
902
					'context'      => array( 'view', 'edit' ),
903
					'arg_options'  => array(
904
						'default'           => 0,
905
					),
906
				),
907
				'status'           => array(
908
					'description'  => __( 'State of the object.' ),
909
					'type'         => 'string',
910
					'context'      => array( 'view', 'edit' ),
911
					'arg_options'  => array(
912
						'sanitize_callback' => 'sanitize_key',
913
					),
914
				),
915
				'type'             => array(
916
					'description'  => __( 'Type of Comment for the object.' ),
917
					'type'         => 'string',
918
					'context'      => array( 'view', 'edit', 'embed' ),
919
					'arg_options'  => array(
920
						'sanitize_callback' => 'sanitize_key',
921
						'default'           => '',
922
					),
923
				),
924
			),
925
		);
926
927 View Code Duplication
		if ( get_option( 'show_avatars' ) ) {
928
			$avatar_properties = array();
929
930
			$avatar_sizes = rest_get_avatar_sizes();
931
			foreach ( $avatar_sizes as $size ) {
932
				$avatar_properties[ $size ] = array(
933
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
934
					'type'        => 'string',
935
					'format'      => 'uri',
936
					'context'     => array( 'embed', 'view', 'edit' ),
937
				);
938
			}
939
940
			$schema['properties']['author_avatar_urls'] = array(
941
				'description'   => __( 'Avatar URLs for the object author.' ),
942
				'type'          => 'object',
943
				'context'       => array( 'view', 'edit', 'embed' ),
944
				'readonly'      => true,
945
				'properties'    => $avatar_properties,
946
			);
947
		}
948
949
		return $this->add_additional_fields_schema( $schema );
950
	}
951
952
	/**
953
	 * Get the query params for collections
954
	 *
955
	 * @return array
956
	 */
957
	public function get_collection_params() {
958
		$query_params = parent::get_collection_params();
959
960
		$query_params['context']['default'] = 'view';
961
962
		$query_params['after'] = array(
963
			'description'       => __( 'Limit response to resources published after a given ISO8601 compliant date.' ),
964
			'type'              => 'string',
965
			'format'            => 'date-time',
966
			'validate_callback' => 'rest_validate_request_arg',
967
		);
968
		$query_params['author'] = array(
969
			'description'       => __( 'Limit result set to comments assigned to specific user ids. Requires authorization.' ),
970
			'sanitize_callback' => 'wp_parse_id_list',
971
			'type'              => 'array',
972
			'validate_callback' => 'rest_validate_request_arg',
973
		);
974
		$query_params['author_exclude'] = array(
975
			'description'       => __( 'Ensure result set excludes comments assigned to specific user ids. Requires authorization.' ),
976
			'sanitize_callback' => 'wp_parse_id_list',
977
			'type'              => 'array',
978
			'validate_callback' => 'rest_validate_request_arg',
979
		);
980
		$query_params['author_email'] = array(
981
			'default'           => null,
982
			'description'       => __( 'Limit result set to that from a specific author email. Requires authorization.' ),
983
			'format'            => 'email',
984
			'sanitize_callback' => 'sanitize_email',
985
			'validate_callback' => 'rest_validate_request_arg',
986
			'type'              => 'string',
987
		);
988
		$query_params['before'] = array(
989
			'description'       => __( 'Limit response to resources published before a given ISO8601 compliant date.' ),
990
			'type'              => 'string',
991
			'format'            => 'date-time',
992
			'validate_callback' => 'rest_validate_request_arg',
993
		);
994
		$query_params['exclude'] = array(
995
			'description'        => __( 'Ensure result set excludes specific ids.' ),
996
			'type'               => 'array',
997
			'default'            => array(),
998
			'sanitize_callback'  => 'wp_parse_id_list',
999
			'validate_callback'  => 'rest_validate_request_arg',
1000
		);
1001
		$query_params['include'] = array(
1002
			'description'        => __( 'Limit result set to specific ids.' ),
1003
			'type'               => 'array',
1004
			'default'            => array(),
1005
			'sanitize_callback'  => 'wp_parse_id_list',
1006
			'validate_callback'  => 'rest_validate_request_arg',
1007
		);
1008
		$query_params['karma'] = array(
1009
			'default'           => null,
1010
			'description'       => __( 'Limit result set to that of a particular comment karma. Requires authorization.' ),
1011
			'sanitize_callback' => 'absint',
1012
			'type'              => 'integer',
1013
			'validate_callback'  => 'rest_validate_request_arg',
1014
		);
1015
		$query_params['offset'] = array(
1016
			'description'        => __( 'Offset the result set by a specific number of comments.' ),
1017
			'type'               => 'integer',
1018
			'sanitize_callback'  => 'absint',
1019
			'validate_callback'  => 'rest_validate_request_arg',
1020
		);
1021
		$query_params['order']      = array(
1022
			'description'           => __( 'Order sort attribute ascending or descending.' ),
1023
			'type'                  => 'string',
1024
			'sanitize_callback'     => 'sanitize_key',
1025
			'validate_callback'     => 'rest_validate_request_arg',
1026
			'default'               => 'desc',
1027
			'enum'                  => array(
1028
				'asc',
1029
				'desc',
1030
			),
1031
		);
1032
		$query_params['orderby']    = array(
1033
			'description'           => __( 'Sort collection by object attribute.' ),
1034
			'type'                  => 'string',
1035
			'sanitize_callback'     => 'sanitize_key',
1036
			'validate_callback'     => 'rest_validate_request_arg',
1037
			'default'               => 'date_gmt',
1038
			'enum'                  => array(
1039
				'date',
1040
				'date_gmt',
1041
				'id',
1042
				'include',
1043
				'post',
1044
				'parent',
1045
				'type',
1046
			),
1047
		);
1048
		$query_params['parent'] = array(
1049
			'default'           => array(),
1050
			'description'       => __( 'Limit result set to resources of specific parent ids.' ),
1051
			'sanitize_callback' => 'wp_parse_id_list',
1052
			'type'              => 'array',
1053
			'validate_callback' => 'rest_validate_request_arg',
1054
		);
1055
		$query_params['parent_exclude'] = array(
1056
			'default'           => array(),
1057
			'description'       => __( 'Ensure result set excludes specific parent ids.' ),
1058
			'sanitize_callback' => 'wp_parse_id_list',
1059
			'type'              => 'array',
1060
			'validate_callback' => 'rest_validate_request_arg',
1061
		);
1062
		$query_params['post']   = array(
1063
			'default'           => array(),
1064
			'description'       => __( 'Limit result set to resources assigned to specific post ids.' ),
1065
			'type'              => 'array',
1066
			'sanitize_callback' => 'wp_parse_id_list',
1067
			'validate_callback' => 'rest_validate_request_arg',
1068
		);
1069
		$query_params['status'] = array(
1070
			'default'           => 'approve',
1071
			'description'       => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
1072
			'sanitize_callback' => 'sanitize_key',
1073
			'type'              => 'string',
1074
			'validate_callback' => 'rest_validate_request_arg',
1075
		);
1076
		$query_params['type'] = array(
1077
			'default'           => 'comment',
1078
			'description'       => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ),
1079
			'sanitize_callback' => 'sanitize_key',
1080
			'type'              => 'string',
1081
			'validate_callback' => 'rest_validate_request_arg',
1082
		);
1083
		return $query_params;
1084
	}
1085
1086
	/**
1087
	 * Set the comment_status of a given comment object when creating or updating a comment.
1088
	 *
1089
	 * @param string|int $new_status
1090
	 * @param object     $comment
1091
	 * @return boolean   $changed
1092
	 */
1093
	protected function handle_status_param( $new_status, $comment ) {
1094
		$old_status = wp_get_comment_status( $comment->comment_ID );
1095
1096
		if ( $new_status === $old_status ) {
1097
			return false;
1098
		}
1099
1100
		switch ( $new_status ) {
1101
			case 'approved' :
1102
			case 'approve':
1103
			case '1':
1104
				$changed = wp_set_comment_status( $comment->comment_ID, 'approve' );
1105
				break;
1106
			case 'hold':
1107
			case '0':
1108
				$changed = wp_set_comment_status( $comment->comment_ID, 'hold' );
1109
				break;
1110
			case 'spam' :
1111
				$changed = wp_spam_comment( $comment->comment_ID );
1112
				break;
1113
			case 'unspam' :
1114
				$changed = wp_unspam_comment( $comment->comment_ID );
1115
				break;
1116
			case 'trash' :
1117
				$changed = wp_trash_comment( $comment->comment_ID );
1118
				break;
1119
			case 'untrash' :
1120
				$changed = wp_untrash_comment( $comment->comment_ID );
1121
				break;
1122
			default :
1123
				$changed = false;
1124
				break;
1125
		}
1126
1127
		return $changed;
1128
	}
1129
1130
	/**
1131
	 * Check if we can read a post.
1132
	 *
1133
	 * Correctly handles posts with the inherit status.
1134
	 *
1135
	 * @param  WP_Post $post Post Object.
1136
	 * @return boolean Can we read it?
1137
	 */
1138
	protected function check_read_post_permission( $post ) {
1139
		$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
1140
1141
		return $posts_controller->check_read_permission( $post );
1142
	}
1143
1144
	/**
1145
	 * Check if we can read a comment.
1146
	 *
1147
	 * @param  object  $comment Comment object.
1148
	 * @return boolean Can we read it?
1149
	 */
1150
	protected function check_read_permission( $comment ) {
1151
1152
		if ( 1 === (int) $comment->comment_approved ) {
1153
			return true;
1154
		}
1155
1156
		if ( 0 === get_current_user_id() ) {
1157
			return false;
1158
		}
1159
1160
		if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) {
1161
			return false;
1162
		}
1163
1164
		$post = $this->get_post( $comment->comment_post_ID );
1165
		if ( $comment->comment_post_ID && $post ) {
1166
			if ( ! $this->check_read_post_permission( $post ) ) {
1167
				return false;
1168
			}
1169
		}
1170
1171
		if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
1172
			return true;
1173
		}
1174
1175
		return current_user_can( 'edit_comment', $comment->comment_ID );
1176
	}
1177
1178
	/**
1179
	 * Check if we can edit or delete a comment.
1180
	 *
1181
	 * @param  object  $comment Comment object.
1182
	 * @return boolean Can we edit or delete it?
1183
	 */
1184
	protected function check_edit_permission( $comment ) {
1185
		if ( 0 === (int) get_current_user_id() ) {
1186
			return false;
1187
		}
1188
1189
		if ( ! current_user_can( 'moderate_comments' ) ) {
1190
			return false;
1191
		}
1192
1193
		return current_user_can( 'edit_comment', $comment->comment_ID );
1194
	}
1195
}
1196