WP_REST_Comments_Controller   F
last analyzed

Complexity

Total Complexity 218

Size/Duplication

Total Lines 1626
Duplicated Lines 8.79 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
dl 143
loc 1626
rs 3.9999
c 0
b 0
f 0
wmc 218
lcom 1
cbo 11

25 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A register_routes() 0 62 1
D get_items_permissions_check() 0 43 19
F get_items() 32 135 16
B get_comment() 6 21 5
C get_item_permissions_check() 3 22 8
A get_item() 11 11 2
A update_item_permissions_check() 0 12 3
D update_item() 10 87 21
A delete_item_permissions_check() 0 11 3
B delete_item() 19 59 7
A prepare_item_for_response() 0 55 4
B prepare_links() 0 59 7
B normalize_query_param() 0 23 5
C prepare_status_response() 0 22 7
F prepare_item_for_database() 0 86 22
B get_item_schema() 22 151 3
B get_collection_params() 0 149 1
C handle_status_param() 0 36 11
C check_read_post_permission() 0 27 7
C check_read_permission() 0 24 10
A check_edit_permission() 0 11 3
A check_comment_author_email() 13 13 3
C create_item_permissions_check() 20 78 19
F create_item() 7 158 30

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_REST_Comments_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WP_REST_Comments_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * REST API: WP_REST_Comments_Controller class
4
 *
5
 * @package WordPress
6
 * @subpackage REST_API
7
 * @since 4.7.0
8
 */
9
10
/**
11
 * Core controller used to access comments via the REST API.
12
 *
13
 * @since 4.7.0
14
 *
15
 * @see WP_REST_Controller
16
 */
17
class WP_REST_Comments_Controller extends WP_REST_Controller {
18
19
	/**
20
	 * Instance of a comment meta fields object.
21
	 *
22
	 * @since 4.7.0
23
	 * @access protected
24
	 * @var WP_REST_Comment_Meta_Fields
25
	 */
26
	protected $meta;
27
28
	/**
29
	 * Constructor.
30
	 *
31
	 * @since 4.7.0
32
	 * @access public
33
	 */
34
	public function __construct() {
35
		$this->namespace = 'wp/v2';
36
		$this->rest_base = 'comments';
37
38
		$this->meta = new WP_REST_Comment_Meta_Fields();
39
	}
40
41
	/**
42
	 * Registers the routes for the objects of the controller.
43
	 *
44
	 * @since 4.7.0
45
	 * @access public
46
	 */
47
	public function register_routes() {
48
49
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
50
			array(
51
				'methods'   => WP_REST_Server::READABLE,
52
				'callback'  => array( $this, 'get_items' ),
53
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
54
				'args'      => $this->get_collection_params(),
55
			),
56
			array(
57
				'methods'  => WP_REST_Server::CREATABLE,
58
				'callback' => array( $this, 'create_item' ),
59
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
60
				'args'     => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
61
			),
62
			'schema' => array( $this, 'get_public_item_schema' ),
63
		) );
64
65
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
66
			'args' => array(
67
				'id' => array(
68
					'description' => __( 'Unique identifier for the object.' ),
69
					'type'        => 'integer',
70
				),
71
			),
72
			array(
73
				'methods'  => WP_REST_Server::READABLE,
74
				'callback' => array( $this, 'get_item' ),
75
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
76
				'args'     => array(
77
					'context'          => $this->get_context_param( array( 'default' => 'view' ) ),
78
					'password' => array(
79
						'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ),
80
						'type'        => 'string',
81
					),
82
				),
83
			),
84
			array(
85
				'methods'  => WP_REST_Server::EDITABLE,
86
				'callback' => array( $this, 'update_item' ),
87
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
88
				'args'     => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
89
			),
90
			array(
91
				'methods'  => WP_REST_Server::DELETABLE,
92
				'callback' => array( $this, 'delete_item' ),
93
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
94
				'args'     => array(
95
					'force'    => array(
96
						'type'        => 'boolean',
97
						'default'     => false,
98
						'description' => __( 'Whether to bypass trash and force deletion.' ),
99
					),
100
					'password' => array(
101
						'description' => __( 'The password for the parent post of the comment (if the post is password protected).' ),
102
						'type'        => 'string',
103
					),
104
				),
105
			),
106
			'schema' => array( $this, 'get_public_item_schema' ),
107
		) );
108
	}
109
110
	/**
111
	 * Checks if a given request has access to read comments.
112
	 *
113
	 * @since 4.7.0
114
	 * @access public
115
	 *
116
	 * @param WP_REST_Request $request Full details about the request.
117
	 * @return WP_Error|bool True if the request has read access, error object otherwise.
118
	 */
119
	public function get_items_permissions_check( $request ) {
120
121
		if ( ! empty( $request['post'] ) ) {
122
			foreach ( (array) $request['post'] as $post_id ) {
123
				$post = get_post( $post_id );
124
125
				if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post, $request ) ) {
126
					return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
127
				} elseif ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) {
128
					return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read comments without a post.' ), array( 'status' => rest_authorization_required_code() ) );
129
				}
130
			}
131
		}
132
133
		if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
134
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) );
135
		}
136
137
		if ( ! current_user_can( 'edit_posts' ) ) {
138
			$protected_params = array( 'author', 'author_exclude', 'author_email', 'type', 'status' );
139
			$forbidden_params = array();
140
141
			foreach ( $protected_params as $param ) {
142
				if ( 'status' === $param ) {
143
					if ( 'approve' !== $request[ $param ] ) {
144
						$forbidden_params[] = $param;
145
					}
146
				} elseif ( 'type' === $param ) {
147
					if ( 'comment' !== $request[ $param ] ) {
148
						$forbidden_params[] = $param;
149
					}
150
				} elseif ( ! empty( $request[ $param ] ) ) {
151
					$forbidden_params[] = $param;
152
				}
153
			}
154
155
			if ( ! empty( $forbidden_params ) ) {
156
				return new WP_Error( 'rest_forbidden_param', sprintf( __( 'Query parameter not permitted: %s' ), implode( ', ', $forbidden_params ) ), array( 'status' => rest_authorization_required_code() ) );
157
			}
158
		}
159
160
		return true;
161
	}
162
163
	/**
164
	 * Retrieves a list of comment items.
165
	 *
166
	 * @since 4.7.0
167
	 * @access public
168
	 *
169
	 * @param WP_REST_Request $request Full details about the request.
170
	 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure.
171
	 */
172
	public function get_items( $request ) {
173
174
		// Retrieve the list of registered collection query parameters.
175
		$registered = $this->get_collection_params();
176
177
		/*
178
		 * This array defines mappings between public API query parameters whose
179
		 * values are accepted as-passed, and their internal WP_Query parameter
180
		 * name equivalents (some are the same). Only values which are also
181
		 * present in $registered will be set.
182
		 */
183
		$parameter_mappings = array(
184
			'author'         => 'author__in',
185
			'author_email'   => 'author_email',
186
			'author_exclude' => 'author__not_in',
187
			'exclude'        => 'comment__not_in',
188
			'include'        => 'comment__in',
189
			'offset'         => 'offset',
190
			'order'          => 'order',
191
			'parent'         => 'parent__in',
192
			'parent_exclude' => 'parent__not_in',
193
			'per_page'       => 'number',
194
			'post'           => 'post__in',
195
			'search'         => 'search',
196
			'status'         => 'status',
197
			'type'           => 'type',
198
		);
199
200
		$prepared_args = array();
201
202
		/*
203
		 * For each known parameter which is both registered and present in the request,
204
		 * set the parameter's value on the query $prepared_args.
205
		 */
206 View Code Duplication
		foreach ( $parameter_mappings as $api_param => $wp_param ) {
207
			if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
208
				$prepared_args[ $wp_param ] = $request[ $api_param ];
209
			}
210
		}
211
212
		// Ensure certain parameter values default to empty strings.
213
		foreach ( array( 'author_email', 'search' ) as $param ) {
214
			if ( ! isset( $prepared_args[ $param ] ) ) {
215
				$prepared_args[ $param ] = '';
216
			}
217
		}
218
219
		if ( isset( $registered['orderby'] ) ) {
220
			$prepared_args['orderby'] = $this->normalize_query_param( $request['orderby'] );
221
		}
222
223
		$prepared_args['no_found_rows'] = false;
224
225
		$prepared_args['date_query'] = array();
226
227
		// Set before into date query. Date query must be specified as an array of an array.
228
		if ( isset( $registered['before'], $request['before'] ) ) {
229
			$prepared_args['date_query'][0]['before'] = $request['before'];
230
		}
231
232
		// Set after into date query. Date query must be specified as an array of an array.
233 View Code Duplication
		if ( isset( $registered['after'], $request['after'] ) ) {
234
			$prepared_args['date_query'][0]['after'] = $request['after'];
235
		}
236
237
		if ( isset( $registered['page'] ) && empty( $request['offset'] ) ) {
238
			$prepared_args['offset'] = $prepared_args['number'] * ( absint( $request['page'] ) - 1 );
239
		}
240
241
		/**
242
		 * Filters arguments, before passing to WP_Comment_Query, when querying comments via the REST API.
243
		 *
244
		 * @since 4.7.0
245
		 *
246
		 * @link https://developer.wordpress.org/reference/classes/wp_comment_query/
247
		 *
248
		 * @param array           $prepared_args Array of arguments for WP_Comment_Query.
249
		 * @param WP_REST_Request $request       The current request.
250
		 */
251
		$prepared_args = apply_filters( 'rest_comment_query', $prepared_args, $request );
252
253
		$query = new WP_Comment_Query;
254
		$query_result = $query->query( $prepared_args );
255
256
		$comments = array();
257
258 View Code Duplication
		foreach ( $query_result as $comment ) {
0 ignored issues
show
Bug introduced by
The expression $query_result of type integer|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
259
			if ( ! $this->check_read_permission( $comment, $request ) ) {
260
				continue;
261
			}
262
263
			$data = $this->prepare_item_for_response( $comment, $request );
264
			$comments[] = $this->prepare_response_for_collection( $data );
265
		}
266
267
		$total_comments = (int) $query->found_comments;
268
		$max_pages      = (int) $query->max_num_pages;
269
270
		if ( $total_comments < 1 ) {
271
			// Out-of-bounds, run the query again without LIMIT for total count.
272
			unset( $prepared_args['number'], $prepared_args['offset'] );
273
274
			$query = new WP_Comment_Query;
275
			$prepared_args['count'] = true;
276
277
			$total_comments = $query->query( $prepared_args );
278
			$max_pages = ceil( $total_comments / $request['per_page'] );
279
		}
280
281
		$response = rest_ensure_response( $comments );
282
		$response->header( 'X-WP-Total', $total_comments );
283
		$response->header( 'X-WP-TotalPages', $max_pages );
284
285
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
286
287 View Code Duplication
		if ( $request['page'] > 1 ) {
288
			$prev_page = $request['page'] - 1;
289
290
			if ( $prev_page > $max_pages ) {
291
				$prev_page = $max_pages;
292
			}
293
294
			$prev_link = add_query_arg( 'page', $prev_page, $base );
295
			$response->link_header( 'prev', $prev_link );
296
		}
297
298 View Code Duplication
		if ( $max_pages > $request['page'] ) {
299
			$next_page = $request['page'] + 1;
300
			$next_link = add_query_arg( 'page', $next_page, $base );
301
302
			$response->link_header( 'next', $next_link );
303
		}
304
305
		return $response;
306
	}
307
308
	/**
309
	 * Get the comment, if the ID is valid.
310
	 *
311
	 * @since 4.7.2
312
	 *
313
	 * @param int $id Supplied ID.
314
	 * @return WP_Comment|WP_Error Comment object if ID is valid, WP_Error otherwise.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|WP_Comment|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
315
	 */
316
	protected function get_comment( $id ) {
317
		$error = new WP_Error( 'rest_comment_invalid_id', __( 'Invalid comment ID.' ), array( 'status' => 404 ) );
318
		if ( (int) $id <= 0 ) {
319
			return $error;
320
		}
321
322
		$id = (int) $id;
323
		$comment = get_comment( $id );
324
		if ( empty( $comment ) ) {
325
			return $error;
326
		}
327
328 View Code Duplication
		if ( ! empty( $comment->comment_post_ID ) ) {
329
			$post = get_post( (int) $comment->comment_post_ID );
330
			if ( empty( $post ) ) {
331
				return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) );
332
			}
333
		}
334
335
		return $comment;
336
	}
337
338
	/**
339
	 * Checks if a given request has access to read the comment.
340
	 *
341
	 * @since 4.7.0
342
	 * @access public
343
	 *
344
	 * @param WP_REST_Request $request Full details about the request.
345
	 * @return WP_Error|bool True if the request has read access for the item, error object otherwise.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|WP_Comment|array|boolean? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
346
	 */
347
	public function get_item_permissions_check( $request ) {
348
		$comment = $this->get_comment( $request['id'] );
349
		if ( is_wp_error( $comment ) ) {
350
			return $comment;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $comment; (WP_Error|WP_Comment|array) is incompatible with the return type of the parent method WP_REST_Controller::get_item_permissions_check of type WP_Error|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
351
		}
352
353
		if ( ! empty( $request['context'] ) && 'edit' === $request['context'] && ! current_user_can( 'moderate_comments' ) ) {
354
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit comments.' ), array( 'status' => rest_authorization_required_code() ) );
355
		}
356
357
		$post = get_post( $comment->comment_post_ID );
358
359
		if ( ! $this->check_read_permission( $comment, $request ) ) {
360
			return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to read this comment.' ), array( 'status' => rest_authorization_required_code() ) );
361
		}
362
363 View Code Duplication
		if ( $post && ! $this->check_read_post_permission( $post, $request ) ) {
364
			return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
365
		}
366
367
		return true;
368
	}
369
370
	/**
371
	 * Retrieves a comment.
372
	 *
373
	 * @since 4.7.0
374
	 * @access public
375
	 *
376
	 * @param WP_REST_Request $request Full details about the request.
377
	 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure.
378
	 */
379 View Code Duplication
	public function get_item( $request ) {
380
		$comment = $this->get_comment( $request['id'] );
381
		if ( is_wp_error( $comment ) ) {
382
			return $comment;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $comment; (WP_Error|WP_Comment|array) is incompatible with the return type documented by WP_REST_Comments_Controller::get_item of type WP_Error|WP_REST_Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
383
		}
384
385
		$data = $this->prepare_item_for_response( $comment, $request );
386
		$response = rest_ensure_response( $data );
387
388
		return $response;
389
	}
390
391
	/**
392
	 * Checks if a given request has access to create a comment.
393
	 *
394
	 * @since 4.7.0
395
	 * @access public
396
	 *
397
	 * @param WP_REST_Request $request Full details about the request.
398
	 * @return WP_Error|bool True if the request has access to create items, error object otherwise.
399
	 */
400
	public function create_item_permissions_check( $request ) {
401
		if ( ! is_user_logged_in() ) {
402
			if ( get_option( 'comment_registration' ) ) {
403
				return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) );
404
			}
405
406
			/**
407
			 * Filter whether comments can be created without authentication.
408
			 *
409
			 * Enables creating comments for anonymous users.
410
			 *
411
			 * @since 4.7.0
412
			 *
413
			 * @param bool $allow_anonymous Whether to allow anonymous comments to
414
			 *                              be created. Default `false`.
415
			 * @param WP_REST_Request $request Request used to generate the
416
			 *                                 response.
417
			 */
418
			$allow_anonymous = apply_filters( 'rest_allow_anonymous_comments', false, $request );
419
			if ( ! $allow_anonymous ) {
420
				return new WP_Error( 'rest_comment_login_required', __( 'Sorry, you must be logged in to comment.' ), array( 'status' => 401 ) );
421
			}
422
		}
423
424
		// Limit who can set comment `author`, `author_ip` or `status` to anything other than the default.
425 View Code Duplication
		if ( isset( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( 'moderate_comments' ) ) {
426
			return new WP_Error( 'rest_comment_invalid_author',
427
				/* translators: %s: request parameter */
428
				sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author' ),
429
				array( 'status' => rest_authorization_required_code() )
430
			);
431
		}
432
433
		if ( isset( $request['author_ip'] ) && ! current_user_can( 'moderate_comments' ) ) {
434
			if ( empty( $_SERVER['REMOTE_ADDR'] ) || $request['author_ip'] !== $_SERVER['REMOTE_ADDR'] ) {
435
				return new WP_Error( 'rest_comment_invalid_author_ip',
436
					/* translators: %s: request parameter */
437
					sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'author_ip' ),
438
					array( 'status' => rest_authorization_required_code() )
439
				);
440
			}
441
		}
442
443 View Code Duplication
		if ( isset( $request['status'] ) && ! current_user_can( 'moderate_comments' ) ) {
444
			return new WP_Error( 'rest_comment_invalid_status',
445
				/* translators: %s: request parameter */
446
				sprintf( __( "Sorry, you are not allowed to edit '%s' for comments." ), 'status' ),
447
				array( 'status' => rest_authorization_required_code() )
448
			);
449
		}
450
451 View Code Duplication
		if ( empty( $request['post'] ) ) {
452
			return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) );
453
		}
454
455
		$post = get_post( (int) $request['post'] );
456
		if ( ! $post ) {
457
			return new WP_Error( 'rest_comment_invalid_post_id', __( 'Sorry, you are not allowed to create this comment without a post.' ), array( 'status' => 403 ) );
458
		}
459
460
		if ( 'draft' === $post->post_status ) {
461
			return new WP_Error( 'rest_comment_draft_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) );
462
		}
463
464
		if ( 'trash' === $post->post_status ) {
465
			return new WP_Error( 'rest_comment_trash_post', __( 'Sorry, you are not allowed to create a comment on this post.' ), array( 'status' => 403 ) );
466
		}
467
468 View Code Duplication
		if ( ! $this->check_read_post_permission( $post, $request ) ) {
469
			return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you are not allowed to read the post for this comment.' ), array( 'status' => rest_authorization_required_code() ) );
470
		}
471
472
		if ( ! comments_open( $post->ID ) ) {
473
			return new WP_Error( 'rest_comment_closed', __( 'Sorry, comments are closed for this item.' ), array( 'status' => 403 ) );
474
		}
475
476
		return true;
477
	}
478
479
	/**
480
	 * Creates a comment.
481
	 *
482
	 * @since 4.7.0
483
	 * @access public
484
	 *
485
	 * @param WP_REST_Request $request Full details about the request.
486
	 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure.
487
	 */
488
	public function create_item( $request ) {
489 View Code Duplication
		if ( ! empty( $request['id'] ) ) {
490
			return new WP_Error( 'rest_comment_exists', __( 'Cannot create existing comment.' ), array( 'status' => 400 ) );
491
		}
492
493
		// Do not allow comments to be created with a non-default type.
494
		if ( ! empty( $request['type'] ) && 'comment' !== $request['type'] ) {
495
			return new WP_Error( 'rest_invalid_comment_type', __( 'Cannot create a comment with that type.' ), array( 'status' => 400 ) );
496
		}
497
498
		$prepared_comment = $this->prepare_item_for_database( $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->prepare_item_for_database($request); of type array|WP_Error adds the type array to the return on line 500 which is incompatible with the return type documented by WP_REST_Comments_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
499
		if ( is_wp_error( $prepared_comment ) ) {
500
			return $prepared_comment;
501
		}
502
503
		$prepared_comment['comment_type'] = '';
504
505
		/*
506
		 * Do not allow a comment to be created with missing or empty
507
		 * comment_content. See wp_handle_comment_submission().
508
		 */
509
		if ( empty( $prepared_comment['comment_content'] ) ) {
510
			return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) );
511
		}
512
513
		// Setting remaining values before wp_insert_comment so we can use wp_allow_comment().
514
		if ( ! isset( $prepared_comment['comment_date_gmt'] ) ) {
515
			$prepared_comment['comment_date_gmt'] = current_time( 'mysql', true );
516
		}
517
518
		// Set author data if the user's logged in.
519
		$missing_author = empty( $prepared_comment['user_id'] )
520
			&& empty( $prepared_comment['comment_author'] )
521
			&& empty( $prepared_comment['comment_author_email'] )
522
			&& empty( $prepared_comment['comment_author_url'] );
523
524
		if ( is_user_logged_in() && $missing_author ) {
525
			$user = wp_get_current_user();
526
527
			$prepared_comment['user_id'] = $user->ID;
528
			$prepared_comment['comment_author'] = $user->display_name;
529
			$prepared_comment['comment_author_email'] = $user->user_email;
530
			$prepared_comment['comment_author_url'] = $user->user_url;
531
		}
532
533
		// Honor the discussion setting that requires a name and email address of the comment author.
534
		if ( get_option( 'require_name_email' ) ) {
535
			if ( empty( $prepared_comment['comment_author'] ) || empty( $prepared_comment['comment_author_email'] ) ) {
536
				return new WP_Error( 'rest_comment_author_data_required', __( 'Creating a comment requires valid author name and email values.' ), array( 'status' => 400 ) );
537
			}
538
		}
539
540
		if ( ! isset( $prepared_comment['comment_author_email'] ) ) {
541
			$prepared_comment['comment_author_email'] = '';
542
		}
543
544
		if ( ! isset( $prepared_comment['comment_author_url'] ) ) {
545
			$prepared_comment['comment_author_url'] = '';
546
		}
547
548
		if ( ! isset( $prepared_comment['comment_agent'] ) ) {
549
			$prepared_comment['comment_agent'] = '';
550
		}
551
552
		$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_comment );
553 View Code Duplication
		if ( is_wp_error( $check_comment_lengths ) ) {
554
			$error_code = $check_comment_lengths->get_error_code();
555
			return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) );
556
		}
557
558
		$prepared_comment['comment_approved'] = wp_allow_comment( $prepared_comment, true );
559
560
		if ( is_wp_error( $prepared_comment['comment_approved'] ) ) {
561
			$error_code    = $prepared_comment['comment_approved']->get_error_code();
562
			$error_message = $prepared_comment['comment_approved']->get_error_message();
563
564
			if ( 'comment_duplicate' === $error_code ) {
565
				return new WP_Error( $error_code, $error_message, array( 'status' => 409 ) );
566
			}
567
568
			if ( 'comment_flood' === $error_code ) {
569
				return new WP_Error( $error_code, $error_message, array( 'status' => 400 ) );
570
			}
571
572
			return $prepared_comment['comment_approved'];
573
		}
574
575
		/**
576
		 * Filters a comment before it is inserted via the REST API.
577
		 *
578
		 * Allows modification of the comment right before it is inserted via wp_insert_comment().
579
		 * Returning a WP_Error value from the filter will shortcircuit insertion and allow
580
		 * skipping further processing.
581
		 *
582
		 * @since 4.7.0
583
		 * @since 4.8.0 $prepared_comment can now be a WP_Error to shortcircuit insertion.
584
		 *
585
		 * @param array|WP_Error  $prepared_comment The prepared comment data for wp_insert_comment().
586
		 * @param WP_REST_Request $request          Request used to insert the comment.
587
		 */
588
		$prepared_comment = apply_filters( 'rest_pre_insert_comment', $prepared_comment, $request );
589
		if ( is_wp_error( $prepared_comment ) ) {
590
			return $prepared_comment;
591
		}
592
593
		$comment_id = wp_insert_comment( wp_filter_comment( wp_slash( (array) $prepared_comment ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash((array) $prepared_comment) targeting wp_slash() can also be of type string; however, wp_filter_comment() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
594
595
		if ( ! $comment_id ) {
596
			return new WP_Error( 'rest_comment_failed_create', __( 'Creating comment failed.' ), array( 'status' => 500 ) );
597
		}
598
599
		if ( isset( $request['status'] ) ) {
600
			$this->handle_status_param( $request['status'], $comment_id );
601
		}
602
603
		$comment = get_comment( $comment_id );
604
605
		/**
606
		 * Fires after a comment is created or updated via the REST API.
607
		 *
608
		 * @since 4.7.0
609
		 *
610
		 * @param WP_Comment      $comment  Inserted or updated comment object.
611
		 * @param WP_REST_Request $request  Request object.
612
		 * @param bool            $creating True when creating a comment, false
613
		 *                                  when updating.
614
		 */
615
		do_action( 'rest_insert_comment', $comment, $request, true );
616
617
		$schema = $this->get_item_schema();
618
619
		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
620
			$meta_update = $this->meta->update_value( $request['meta'], $comment_id );
621
622
			if ( is_wp_error( $meta_update ) ) {
623
				return $meta_update;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $meta_update; (WP_Error|boolean|true|null) is incompatible with the return type documented by WP_REST_Comments_Controller::create_item of type WP_Error|WP_REST_Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
624
			}
625
		}
626
627
		$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 630 which is incompatible with the return type documented by WP_REST_Comments_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
628
629
		if ( is_wp_error( $fields_update ) ) {
630
			return $fields_update;
631
		}
632
633
		$context = current_user_can( 'moderate_comments' ) ? 'edit' : 'view';
634
635
		$request->set_param( 'context', $context );
636
637
		$response = $this->prepare_item_for_response( $comment, $request );
638
		$response = rest_ensure_response( $response );
639
640
		$response->set_status( 201 );
641
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment_id ) ) );
642
643
644
		return $response;
645
	}
646
647
	/**
648
	 * Checks if a given REST request has access to update a comment.
649
	 *
650
	 * @since 4.7.0
651
	 * @access public
652
	 *
653
	 * @param WP_REST_Request $request Full details about the request.
654
	 * @return WP_Error|bool True if the request has access to update the item, error object otherwise.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|WP_Comment|array|boolean? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
655
	 */
656
	public function update_item_permissions_check( $request ) {
657
		$comment = $this->get_comment( $request['id'] );
658
		if ( is_wp_error( $comment ) ) {
659
			return $comment;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $comment; (WP_Error|WP_Comment|array) is incompatible with the return type of the parent method WP_REST_Controller::update_item_permissions_check of type WP_Error|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
660
		}
661
662
		if ( ! $this->check_edit_permission( $comment ) ) {
663
			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this comment.' ), array( 'status' => rest_authorization_required_code() ) );
664
		}
665
666
		return true;
667
	}
668
669
	/**
670
	 * Updates a comment.
671
	 *
672
	 * @since 4.7.0
673
	 * @access public
674
	 *
675
	 * @param WP_REST_Request $request Full details about the request.
676
	 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure.
677
	 */
678
	public function update_item( $request ) {
679
		$comment = $this->get_comment( $request['id'] );
680
		if ( is_wp_error( $comment ) ) {
681
			return $comment;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $comment; (WP_Error|WP_Comment|array) is incompatible with the return type documented by WP_REST_Comments_Controller::update_item of type WP_Error|WP_REST_Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
682
		}
683
684
		$id = $comment->comment_ID;
685
686 View Code Duplication
		if ( isset( $request['type'] ) && get_comment_type( $id ) !== $request['type'] ) {
687
			return new WP_Error( 'rest_comment_invalid_type', __( 'Sorry, you are not allowed to change the comment type.' ), array( 'status' => 404 ) );
688
		}
689
690
		$prepared_args = $this->prepare_item_for_database( $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->prepare_item_for_database($request); of type array|WP_Error adds the type array to the return on line 693 which is incompatible with the return type documented by WP_REST_Comments_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
691
692
		if ( is_wp_error( $prepared_args ) ) {
693
			return $prepared_args;
694
		}
695
696
		if ( ! empty( $prepared_args['comment_post_ID'] ) ) {
697
			$post = get_post( $prepared_args['comment_post_ID'] );
698
			if ( empty( $post ) ) {
699
				return new WP_Error( 'rest_comment_invalid_post_id', __( 'Invalid post ID.' ), array( 'status' => 403 ) );
700
			}
701
		}
702
703
		if ( empty( $prepared_args ) && isset( $request['status'] ) ) {
704
			// Only the comment status is being changed.
705
			$change = $this->handle_status_param( $request['status'], $id );
706
707
			if ( ! $change ) {
708
				return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment status failed.' ), array( 'status' => 500 ) );
709
			}
710
		} elseif ( ! empty( $prepared_args ) ) {
711
			if ( is_wp_error( $prepared_args ) ) {
712
				return $prepared_args;
713
			}
714
715 View Code Duplication
			if ( isset( $prepared_args['comment_content'] ) && empty( $prepared_args['comment_content'] ) ) {
716
				return new WP_Error( 'rest_comment_content_invalid', __( 'Invalid comment content.' ), array( 'status' => 400 ) );
717
			}
718
719
			$prepared_args['comment_ID'] = $id;
720
721
			$check_comment_lengths = wp_check_comment_data_max_lengths( $prepared_args );
722 View Code Duplication
			if ( is_wp_error( $check_comment_lengths ) ) {
723
				$error_code = $check_comment_lengths->get_error_code();
724
				return new WP_Error( $error_code, __( 'Comment field exceeds maximum length allowed.' ), array( 'status' => 400 ) );
725
			}
726
727
			$updated = wp_update_comment( wp_slash( (array) $prepared_args ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash((array) $prepared_args) targeting wp_slash() can also be of type string; however, wp_update_comment() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
728
729
			if ( false === $updated ) {
730
				return new WP_Error( 'rest_comment_failed_edit', __( 'Updating comment failed.' ), array( 'status' => 500 ) );
731
			}
732
733
			if ( isset( $request['status'] ) ) {
734
				$this->handle_status_param( $request['status'], $id );
735
			}
736
		}
737
738
		$comment = get_comment( $id );
739
740
		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php */
741
		do_action( 'rest_insert_comment', $comment, $request, false );
742
743
		$schema = $this->get_item_schema();
744
745
		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
746
			$meta_update = $this->meta->update_value( $request['meta'], $id );
747
748
			if ( is_wp_error( $meta_update ) ) {
749
				return $meta_update;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $meta_update; (WP_Error|boolean|true|null) is incompatible with the return type documented by WP_REST_Comments_Controller::update_item of type WP_Error|WP_REST_Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
750
			}
751
		}
752
753
		$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 756 which is incompatible with the return type documented by WP_REST_Comments_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
754
755
		if ( is_wp_error( $fields_update ) ) {
756
			return $fields_update;
757
		}
758
759
		$request->set_param( 'context', 'edit' );
760
761
		$response = $this->prepare_item_for_response( $comment, $request );
762
763
		return rest_ensure_response( $response );
764
	}
765
766
	/**
767
	 * Checks if a given request has access to delete a comment.
768
	 *
769
	 * @since 4.7.0
770
	 * @access public
771
	 *
772
	 * @param WP_REST_Request $request Full details about the request.
773
	 * @return WP_Error|bool True if the request has access to delete the item, error object otherwise.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|WP_Comment|array|boolean? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
774
	 */
775
	public function delete_item_permissions_check( $request ) {
776
		$comment = $this->get_comment( $request['id'] );
777
		if ( is_wp_error( $comment ) ) {
778
			return $comment;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $comment; (WP_Error|WP_Comment|array) is incompatible with the return type of the parent method WP_REST_Controller::delete_item_permissions_check of type WP_Error|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
779
		}
780
781
		if ( ! $this->check_edit_permission( $comment ) ) {
782
			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this comment.' ), array( 'status' => rest_authorization_required_code() ) );
783
		}
784
		return true;
785
	}
786
787
	/**
788
	 * Deletes a comment.
789
	 *
790
	 * @since 4.7.0
791
	 * @access public
792
	 *
793
	 * @param WP_REST_Request $request Full details about the request.
794
	 * @return WP_Error|WP_REST_Response Response object on success, or error object on failure.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|WP_Comment|array|WP_REST_Response? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
795
	 */
796
	public function delete_item( $request ) {
797
		$comment = $this->get_comment( $request['id'] );
798
		if ( is_wp_error( $comment ) ) {
799
			return $comment;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $comment; (WP_Error|WP_Comment|array) is incompatible with the return type of the parent method WP_REST_Controller::delete_item of type WP_Error|WP_REST_Response.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
800
		}
801
802
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
803
804
		/**
805
		 * Filters whether a comment can be trashed.
806
		 *
807
		 * Return false to disable trash support for the post.
808
		 *
809
		 * @since 4.7.0
810
		 *
811
		 * @param bool    $supports_trash Whether the post type support trashing.
812
		 * @param WP_Post $comment        The comment object being considered for trashing support.
813
		 */
814
		$supports_trash = apply_filters( 'rest_comment_trashable', ( EMPTY_TRASH_DAYS > 0 ), $comment );
815
816
		$request->set_param( 'context', 'edit' );
817
818 View Code Duplication
		if ( $force ) {
819
			$previous = $this->prepare_item_for_response( $comment, $request );
820
			$result = wp_delete_comment( $comment->comment_ID, true );
821
			$response = new WP_REST_Response();
822
			$response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) );
823
		} else {
824
			// If this type doesn't support trashing, error out.
825
			if ( ! $supports_trash ) {
826
				return new WP_Error( 'rest_trash_not_supported', __( 'The comment does not support trashing. Set force=true to delete.' ), array( 'status' => 501 ) );
827
			}
828
829
			if ( 'trash' === $comment->comment_approved ) {
830
				return new WP_Error( 'rest_already_trashed', __( 'The comment has already been trashed.' ), array( 'status' => 410 ) );
831
			}
832
833
			$result = wp_trash_comment( $comment->comment_ID );
834
			$comment = get_comment( $comment->comment_ID );
835
			$response = $this->prepare_item_for_response( $comment, $request );
836
		}
837
838
		if ( ! $result ) {
839
			return new WP_Error( 'rest_cannot_delete', __( 'The comment cannot be deleted.' ), array( 'status' => 500 ) );
840
		}
841
842
		/**
843
		 * Fires after a comment is deleted via the REST API.
844
		 *
845
		 * @since 4.7.0
846
		 *
847
		 * @param WP_Comment       $comment  The deleted comment data.
848
		 * @param WP_REST_Response $response The response returned from the API.
849
		 * @param WP_REST_Request  $request  The request sent to the API.
850
		 */
851
		do_action( 'rest_delete_comment', $comment, $response, $request );
852
853
		return $response;
854
	}
855
856
	/**
857
	 * Prepares a single comment output for response.
858
	 *
859
	 * @since 4.7.0
860
	 * @access public
861
	 *
862
	 * @param WP_Comment      $comment Comment object.
863
	 * @param WP_REST_Request $request Request object.
864
	 * @return WP_REST_Response Response object.
865
	 */
866
	public function prepare_item_for_response( $comment, $request ) {
867
		$data = array(
868
			'id'                 => (int) $comment->comment_ID,
869
			'post'               => (int) $comment->comment_post_ID,
870
			'parent'             => (int) $comment->comment_parent,
871
			'author'             => (int) $comment->user_id,
872
			'author_name'        => $comment->comment_author,
873
			'author_email'       => $comment->comment_author_email,
874
			'author_url'         => $comment->comment_author_url,
875
			'author_ip'          => $comment->comment_author_IP,
876
			'author_user_agent'  => $comment->comment_agent,
877
			'date'               => mysql_to_rfc3339( $comment->comment_date ),
878
			'date_gmt'           => mysql_to_rfc3339( $comment->comment_date_gmt ),
879
			'content'            => array(
880
				/** This filter is documented in wp-includes/comment-template.php */
881
				'rendered' => apply_filters( 'comment_text', $comment->comment_content, $comment ),
882
				'raw'      => $comment->comment_content,
883
			),
884
			'link'               => get_comment_link( $comment ),
885
			'status'             => $this->prepare_status_response( $comment->comment_approved ),
886
			'type'               => get_comment_type( $comment->comment_ID ),
887
		);
888
889
		$schema = $this->get_item_schema();
890
891
		if ( ! empty( $schema['properties']['author_avatar_urls'] ) ) {
892
			$data['author_avatar_urls'] = rest_get_avatar_urls( $comment->comment_author_email );
893
		}
894
895
		if ( ! empty( $schema['properties']['meta'] ) ) {
896
			$data['meta'] = $this->meta->get_value( $comment->comment_ID, $request );
897
		}
898
899
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
900
		$data    = $this->add_additional_fields_to_object( $data, $request );
901
		$data    = $this->filter_response_by_context( $data, $context );
902
903
		// Wrap the data in a response object.
904
		$response = rest_ensure_response( $data );
905
906
		$response->add_links( $this->prepare_links( $comment ) );
907
908
		/**
909
		 * Filters a comment returned from the API.
910
		 *
911
		 * Allows modification of the comment right before it is returned.
912
		 *
913
		 * @since 4.7.0
914
		 *
915
		 * @param WP_REST_Response  $response The response object.
916
		 * @param WP_Comment        $comment  The original comment object.
917
		 * @param WP_REST_Request   $request  Request used to generate the response.
918
		 */
919
		return apply_filters( 'rest_prepare_comment', $response, $comment, $request );
920
	}
921
922
	/**
923
	 * Prepares links for the request.
924
	 *
925
	 * @since 4.7.0
926
	 * @access protected
927
	 *
928
	 * @param WP_Comment $comment Comment object.
929
	 * @return array Links for the given comment.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<string,string|boolean>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
930
	 */
931
	protected function prepare_links( $comment ) {
932
		$links = array(
933
			'self' => array(
934
				'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_ID ) ),
935
			),
936
			'collection' => array(
937
				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
938
			),
939
		);
940
941
		if ( 0 !== (int) $comment->user_id ) {
942
			$links['author'] = array(
943
				'href'       => rest_url( 'wp/v2/users/' . $comment->user_id ),
944
				'embeddable' => true,
945
			);
946
		}
947
948
		if ( 0 !== (int) $comment->comment_post_ID ) {
949
			$post = get_post( $comment->comment_post_ID );
950
951
			if ( ! empty( $post->ID ) ) {
952
				$obj = get_post_type_object( $post->post_type );
953
				$base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
954
955
				$links['up'] = array(
956
					'href'       => rest_url( 'wp/v2/' . $base . '/' . $comment->comment_post_ID ),
957
					'embeddable' => true,
958
					'post_type'  => $post->post_type,
959
				);
960
			}
961
		}
962
963
		if ( 0 !== (int) $comment->comment_parent ) {
964
			$links['in-reply-to'] = array(
965
				'href'       => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $comment->comment_parent ) ),
966
				'embeddable' => true,
967
			);
968
		}
969
970
		// Only grab one comment to verify the comment has children.
971
		$comment_children = $comment->get_children( array(
972
			'number' => 1,
973
			'count'  => true
974
		) );
975
976
		if ( ! empty( $comment_children ) ) {
977
			$args = array(
978
				'parent' => $comment->comment_ID
979
			);
980
981
			$rest_url = add_query_arg( $args, rest_url( $this->namespace . '/' . $this->rest_base ) );
982
983
			$links['children'] = array(
984
				'href' => $rest_url,
985
			);
986
		}
987
988
		return $links;
989
	}
990
991
	/**
992
	 * Prepends internal property prefix to query parameters to match our response fields.
993
	 *
994
	 * @since 4.7.0
995
	 * @access protected
996
	 *
997
	 * @param string $query_param Query parameter.
998
	 * @return string The normalized query parameter.
999
	 */
1000
	protected function normalize_query_param( $query_param ) {
1001
		$prefix = 'comment_';
1002
1003
		switch ( $query_param ) {
1004
			case 'id':
1005
				$normalized = $prefix . 'ID';
1006
				break;
1007
			case 'post':
1008
				$normalized = $prefix . 'post_ID';
1009
				break;
1010
			case 'parent':
1011
				$normalized = $prefix . 'parent';
1012
				break;
1013
			case 'include':
1014
				$normalized = 'comment__in';
1015
				break;
1016
			default:
1017
				$normalized = $prefix . $query_param;
1018
				break;
1019
		}
1020
1021
		return $normalized;
1022
	}
1023
1024
	/**
1025
	 * Checks comment_approved to set comment status for single comment output.
1026
	 *
1027
	 * @since 4.7.0
1028
	 * @access protected
1029
	 *
1030
	 * @param string|int $comment_approved comment status.
1031
	 * @return string Comment status.
0 ignored issues
show
Documentation introduced by
Should the return type not be string|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1032
	 */
1033
	protected function prepare_status_response( $comment_approved ) {
1034
1035
		switch ( $comment_approved ) {
1036
			case 'hold':
1037
			case '0':
1038
				$status = 'hold';
1039
				break;
1040
1041
			case 'approve':
1042
			case '1':
1043
				$status = 'approved';
1044
				break;
1045
1046
			case 'spam':
1047
			case 'trash':
1048
			default:
1049
				$status = $comment_approved;
1050
				break;
1051
		}
1052
1053
		return $status;
1054
	}
1055
1056
	/**
1057
	 * Prepares a single comment to be inserted into the database.
1058
	 *
1059
	 * @since 4.7.0
1060
	 * @access protected
1061
	 *
1062
	 * @param WP_REST_Request $request Request object.
1063
	 * @return array|WP_Error Prepared comment, otherwise WP_Error object.
1064
	 */
1065
	protected function prepare_item_for_database( $request ) {
1066
		$prepared_comment = array();
1067
1068
		/*
1069
		 * Allow the comment_content to be set via the 'content' or
1070
		 * the 'content.raw' properties of the Request object.
1071
		 */
1072
		if ( isset( $request['content'] ) && is_string( $request['content'] ) ) {
1073
			$prepared_comment['comment_content'] = $request['content'];
1074
		} elseif ( isset( $request['content']['raw'] ) && is_string( $request['content']['raw'] ) ) {
1075
			$prepared_comment['comment_content'] = $request['content']['raw'];
1076
		}
1077
1078
		if ( isset( $request['post'] ) ) {
1079
			$prepared_comment['comment_post_ID'] = (int) $request['post'];
1080
		}
1081
1082
		if ( isset( $request['parent'] ) ) {
1083
			$prepared_comment['comment_parent'] = $request['parent'];
1084
		}
1085
1086
		if ( isset( $request['author'] ) ) {
1087
			$user = new WP_User( $request['author'] );
1088
1089
			if ( $user->exists() ) {
1090
				$prepared_comment['user_id'] = $user->ID;
1091
				$prepared_comment['comment_author'] = $user->display_name;
1092
				$prepared_comment['comment_author_email'] = $user->user_email;
1093
				$prepared_comment['comment_author_url'] = $user->user_url;
1094
			} else {
1095
				return new WP_Error( 'rest_comment_author_invalid', __( 'Invalid comment author ID.' ), array( 'status' => 400 ) );
1096
			}
1097
		}
1098
1099
		if ( isset( $request['author_name'] ) ) {
1100
			$prepared_comment['comment_author'] = $request['author_name'];
1101
		}
1102
1103
		if ( isset( $request['author_email'] ) ) {
1104
			$prepared_comment['comment_author_email'] = $request['author_email'];
1105
		}
1106
1107
		if ( isset( $request['author_url'] ) ) {
1108
			$prepared_comment['comment_author_url'] = $request['author_url'];
1109
		}
1110
1111
		if ( isset( $request['author_ip'] ) && current_user_can( 'moderate_comments' ) ) {
1112
			$prepared_comment['comment_author_IP'] = $request['author_ip'];
1113
		} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) && rest_is_ip_address( $_SERVER['REMOTE_ADDR'] ) ) {
1114
			$prepared_comment['comment_author_IP'] = $_SERVER['REMOTE_ADDR'];
1115
		} else {
1116
			$prepared_comment['comment_author_IP'] = '127.0.0.1';
1117
		}
1118
1119
		if ( ! empty( $request['author_user_agent'] ) ) {
1120
			$prepared_comment['comment_agent'] = $request['author_user_agent'];
1121
		} elseif ( $request->get_header( 'user_agent' ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $request->get_header('user_agent') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1122
			$prepared_comment['comment_agent'] = $request->get_header( 'user_agent' );
1123
		}
1124
1125
		if ( ! empty( $request['date'] ) ) {
1126
			$date_data = rest_get_date_with_gmt( $request['date'] );
1127
1128
			if ( ! empty( $date_data ) ) {
1129
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
1130
			}
1131
		} elseif ( ! empty( $request['date_gmt'] ) ) {
1132
			$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
1133
1134
			if ( ! empty( $date_data ) ) {
1135
				list( $prepared_comment['comment_date'], $prepared_comment['comment_date_gmt'] ) = $date_data;
1136
			}
1137
		}
1138
1139
		/**
1140
		 * Filters a comment after it is prepared for the database.
1141
		 *
1142
		 * Allows modification of the comment right after it is prepared for the database.
1143
		 *
1144
		 * @since 4.7.0
1145
		 *
1146
		 * @param array           $prepared_comment The prepared comment data for `wp_insert_comment`.
1147
		 * @param WP_REST_Request $request          The current request.
1148
		 */
1149
		return apply_filters( 'rest_preprocess_comment', $prepared_comment, $request );
1150
	}
1151
1152
	/**
1153
	 * Retrieves the comment's schema, conforming to JSON Schema.
1154
	 *
1155
	 * @since 4.7.0
1156
	 * @access public
1157
	 *
1158
	 * @return array
1159
	 */
1160
	public function get_item_schema() {
1161
		$schema = array(
1162
			'$schema'              => 'http://json-schema.org/schema#',
1163
			'title'                => 'comment',
1164
			'type'                 => 'object',
1165
			'properties'           => array(
1166
				'id'               => array(
1167
					'description'  => __( 'Unique identifier for the object.' ),
1168
					'type'         => 'integer',
1169
					'context'      => array( 'view', 'edit', 'embed' ),
1170
					'readonly'     => true,
1171
				),
1172
				'author'           => array(
1173
					'description'  => __( 'The ID of the user object, if author was a user.' ),
1174
					'type'         => 'integer',
1175
					'context'      => array( 'view', 'edit', 'embed' ),
1176
				),
1177
				'author_email'     => array(
1178
					'description'  => __( 'Email address for the object author.' ),
1179
					'type'         => 'string',
1180
					'format'       => 'email',
1181
					'context'      => array( 'edit' ),
1182
					'arg_options'  => array(
1183
						'sanitize_callback' => array( $this, 'check_comment_author_email' ),
1184
						'validate_callback' => null, // skip built-in validation of 'email'.
1185
					),
1186
				),
1187
				'author_ip'     => array(
1188
					'description'  => __( 'IP address for the object author.' ),
1189
					'type'         => 'string',
1190
					'format'       => 'ip',
1191
					'context'      => array( 'edit' ),
1192
				),
1193
				'author_name'     => array(
1194
					'description'  => __( 'Display name for the object author.' ),
1195
					'type'         => 'string',
1196
					'context'      => array( 'view', 'edit', 'embed' ),
1197
					'arg_options'  => array(
1198
						'sanitize_callback' => 'sanitize_text_field',
1199
					),
1200
				),
1201
				'author_url'       => array(
1202
					'description'  => __( 'URL for the object author.' ),
1203
					'type'         => 'string',
1204
					'format'       => 'uri',
1205
					'context'      => array( 'view', 'edit', 'embed' ),
1206
				),
1207
				'author_user_agent'     => array(
1208
					'description'  => __( 'User agent for the object author.' ),
1209
					'type'         => 'string',
1210
					'context'      => array( 'edit' ),
1211
					'arg_options'  => array(
1212
						'sanitize_callback' => 'sanitize_text_field',
1213
					),
1214
				),
1215
				'content'          => array(
1216
					'description'     => __( 'The content for the object.' ),
1217
					'type'            => 'object',
1218
					'context'         => array( 'view', 'edit', 'embed' ),
1219
					'arg_options'     => array(
1220
						'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
1221
					),
1222
					'properties'      => array(
1223
						'raw'         => array(
1224
							'description'     => __( 'Content for the object, as it exists in the database.' ),
1225
							'type'            => 'string',
1226
							'context'         => array( 'edit' ),
1227
						),
1228
						'rendered'    => array(
1229
							'description'     => __( 'HTML content for the object, transformed for display.' ),
1230
							'type'            => 'string',
1231
							'context'         => array( 'view', 'edit', 'embed' ),
1232
							'readonly'        => true,
1233
						),
1234
					),
1235
				),
1236
				'date'             => array(
1237
					'description'  => __( "The date the object was published, in the site's timezone." ),
1238
					'type'         => 'string',
1239
					'format'       => 'date-time',
1240
					'context'      => array( 'view', 'edit', 'embed' ),
1241
				),
1242
				'date_gmt'         => array(
1243
					'description'  => __( 'The date the object was published, as GMT.' ),
1244
					'type'         => 'string',
1245
					'format'       => 'date-time',
1246
					'context'      => array( 'view', 'edit' ),
1247
				),
1248
				'link'             => array(
1249
					'description'  => __( 'URL to the object.' ),
1250
					'type'         => 'string',
1251
					'format'       => 'uri',
1252
					'context'      => array( 'view', 'edit', 'embed' ),
1253
					'readonly'     => true,
1254
				),
1255
				'parent'           => array(
1256
					'description'  => __( 'The ID for the parent of the object.' ),
1257
					'type'         => 'integer',
1258
					'context'      => array( 'view', 'edit', 'embed' ),
1259
					'default'      => 0,
1260
				),
1261
				'post'             => array(
1262
					'description'  => __( 'The ID of the associated post object.' ),
1263
					'type'         => 'integer',
1264
					'context'      => array( 'view', 'edit' ),
1265
					'default'      => 0,
1266
				),
1267
				'status'           => array(
1268
					'description'  => __( 'State of the object.' ),
1269
					'type'         => 'string',
1270
					'context'      => array( 'view', 'edit' ),
1271
					'arg_options'  => array(
1272
						'sanitize_callback' => 'sanitize_key',
1273
					),
1274
				),
1275
				'type'             => array(
1276
					'description'  => __( 'Type of Comment for the object.' ),
1277
					'type'         => 'string',
1278
					'context'      => array( 'view', 'edit', 'embed' ),
1279
					'readonly'     => true,
1280
				),
1281
			),
1282
		);
1283
1284 View Code Duplication
		if ( get_option( 'show_avatars' ) ) {
1285
			$avatar_properties = array();
1286
1287
			$avatar_sizes = rest_get_avatar_sizes();
1288
			foreach ( $avatar_sizes as $size ) {
1289
				$avatar_properties[ $size ] = array(
1290
					/* translators: %d: avatar image size in pixels */
1291
					'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
1292
					'type'        => 'string',
1293
					'format'      => 'uri',
1294
					'context'     => array( 'embed', 'view', 'edit' ),
1295
				);
1296
			}
1297
1298
			$schema['properties']['author_avatar_urls'] = array(
1299
				'description'   => __( 'Avatar URLs for the object author.' ),
1300
				'type'          => 'object',
1301
				'context'       => array( 'view', 'edit', 'embed' ),
1302
				'readonly'      => true,
1303
				'properties'    => $avatar_properties,
1304
			);
1305
		}
1306
1307
		$schema['properties']['meta'] = $this->meta->get_field_schema();
1308
1309
		return $this->add_additional_fields_schema( $schema );
1310
	}
1311
1312
	/**
1313
	 * Retrieves the query params for collections.
1314
	 *
1315
	 * @since 4.7.0
1316
	 * @access public
1317
	 *
1318
	 * @return array Comments collection parameters.
1319
	 */
1320
	public function get_collection_params() {
1321
		$query_params = parent::get_collection_params();
1322
1323
		$query_params['context']['default'] = 'view';
1324
1325
		$query_params['after'] = array(
1326
			'description'       => __( 'Limit response to comments published after a given ISO8601 compliant date.' ),
1327
			'type'              => 'string',
1328
			'format'            => 'date-time',
1329
		);
1330
1331
		$query_params['author'] = array(
1332
			'description'       => __( 'Limit result set to comments assigned to specific user IDs. Requires authorization.' ),
1333
			'type'              => 'array',
1334
			'items'             => array(
1335
				'type'          => 'integer',
1336
			),
1337
		);
1338
1339
		$query_params['author_exclude'] = array(
1340
			'description'       => __( 'Ensure result set excludes comments assigned to specific user IDs. Requires authorization.' ),
1341
			'type'              => 'array',
1342
			'items'             => array(
1343
				'type'          => 'integer',
1344
			),
1345
		);
1346
1347
		$query_params['author_email'] = array(
1348
			'default'           => null,
1349
			'description'       => __( 'Limit result set to that from a specific author email. Requires authorization.' ),
1350
			'format'            => 'email',
1351
			'type'              => 'string',
1352
		);
1353
1354
		$query_params['before'] = array(
1355
			'description'       => __( 'Limit response to comments published before a given ISO8601 compliant date.' ),
1356
			'type'              => 'string',
1357
			'format'            => 'date-time',
1358
		);
1359
1360
		$query_params['exclude'] = array(
1361
			'description'        => __( 'Ensure result set excludes specific IDs.' ),
1362
			'type'               => 'array',
1363
			'items'              => array(
1364
				'type'           => 'integer',
1365
			),
1366
			'default'            => array(),
1367
		);
1368
1369
		$query_params['include'] = array(
1370
			'description'        => __( 'Limit result set to specific IDs.' ),
1371
			'type'               => 'array',
1372
			'items'              => array(
1373
				'type'           => 'integer',
1374
			),
1375
			'default'            => array(),
1376
		);
1377
1378
		$query_params['offset'] = array(
1379
			'description'        => __( 'Offset the result set by a specific number of items.' ),
1380
			'type'               => 'integer',
1381
		);
1382
1383
		$query_params['order']      = array(
1384
			'description'           => __( 'Order sort attribute ascending or descending.' ),
1385
			'type'                  => 'string',
1386
			'default'               => 'desc',
1387
			'enum'                  => array(
1388
				'asc',
1389
				'desc',
1390
			),
1391
		);
1392
1393
		$query_params['orderby']    = array(
1394
			'description'           => __( 'Sort collection by object attribute.' ),
1395
			'type'                  => 'string',
1396
			'default'               => 'date_gmt',
1397
			'enum'                  => array(
1398
				'date',
1399
				'date_gmt',
1400
				'id',
1401
				'include',
1402
				'post',
1403
				'parent',
1404
				'type',
1405
			),
1406
		);
1407
1408
		$query_params['parent'] = array(
1409
			'default'           => array(),
1410
			'description'       => __( 'Limit result set to comments of specific parent IDs.' ),
1411
			'type'              => 'array',
1412
			'items'             => array(
1413
				'type'          => 'integer',
1414
			),
1415
		);
1416
1417
		$query_params['parent_exclude'] = array(
1418
			'default'           => array(),
1419
			'description'       => __( 'Ensure result set excludes specific parent IDs.' ),
1420
			'type'              => 'array',
1421
			'items'             => array(
1422
				'type'          => 'integer',
1423
			),
1424
		);
1425
1426
		$query_params['post']   = array(
1427
			'default'           => array(),
1428
			'description'       => __( 'Limit result set to comments assigned to specific post IDs.' ),
1429
			'type'              => 'array',
1430
			'items'             => array(
1431
				'type'          => 'integer',
1432
			),
1433
		);
1434
1435
		$query_params['status'] = array(
1436
			'default'           => 'approve',
1437
			'description'       => __( 'Limit result set to comments assigned a specific status. Requires authorization.' ),
1438
			'sanitize_callback' => 'sanitize_key',
1439
			'type'              => 'string',
1440
			'validate_callback' => 'rest_validate_request_arg',
1441
		);
1442
1443
		$query_params['type'] = array(
1444
			'default'           => 'comment',
1445
			'description'       => __( 'Limit result set to comments assigned a specific type. Requires authorization.' ),
1446
			'sanitize_callback' => 'sanitize_key',
1447
			'type'              => 'string',
1448
			'validate_callback' => 'rest_validate_request_arg',
1449
		);
1450
1451
		$query_params['password'] = array(
1452
			'description' => __( 'The password for the post if it is password protected.' ),
1453
			'type'        => 'string',
1454
		);
1455
1456
		/**
1457
		 * Filter collection parameters for the comments controller.
1458
		 *
1459
		 * This filter registers the collection parameter, but does not map the
1460
		 * collection parameter to an internal WP_Comment_Query parameter. Use the
1461
		 * `rest_comment_query` filter to set WP_Comment_Query parameters.
1462
		 *
1463
		 * @since 4.7.0
1464
		 *
1465
		 * @param array $query_params JSON Schema-formatted collection parameters.
1466
		 */
1467
		return apply_filters( 'rest_comment_collection_params', $query_params );
1468
	}
1469
1470
	/**
1471
	 * Sets the comment_status of a given comment object when creating or updating a comment.
1472
	 *
1473
	 * @since 4.7.0
1474
	 * @access protected
1475
	 *
1476
	 * @param string|int $new_status New comment status.
1477
	 * @param int        $comment_id Comment ID.
1478
	 * @return bool Whether the status was changed.
0 ignored issues
show
Documentation introduced by
Should the return type not be WP_Error|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1479
	 */
1480
	protected function handle_status_param( $new_status, $comment_id ) {
1481
		$old_status = wp_get_comment_status( $comment_id );
1482
1483
		if ( $new_status === $old_status ) {
1484
			return false;
1485
		}
1486
1487
		switch ( $new_status ) {
1488
			case 'approved' :
1489
			case 'approve':
1490
			case '1':
1491
				$changed = wp_set_comment_status( $comment_id, 'approve' );
1492
				break;
1493
			case 'hold':
1494
			case '0':
1495
				$changed = wp_set_comment_status( $comment_id, 'hold' );
1496
				break;
1497
			case 'spam' :
1498
				$changed = wp_spam_comment( $comment_id );
1499
				break;
1500
			case 'unspam' :
1501
				$changed = wp_unspam_comment( $comment_id );
1502
				break;
1503
			case 'trash' :
1504
				$changed = wp_trash_comment( $comment_id );
1505
				break;
1506
			case 'untrash' :
1507
				$changed = wp_untrash_comment( $comment_id );
1508
				break;
1509
			default :
1510
				$changed = false;
1511
				break;
1512
		}
1513
1514
		return $changed;
1515
	}
1516
1517
	/**
1518
	 * Checks if the post can be read.
1519
	 *
1520
	 * Correctly handles posts with the inherit status.
1521
	 *
1522
	 * @since 4.7.0
1523
	 * @access protected
1524
	 *
1525
	 * @param WP_Post         $post    Post object.
1526
	 * @param WP_REST_Request $request Request data to check.
1527
	 * @return bool Whether post can be read.
1528
	 */
1529
	protected function check_read_post_permission( $post, $request ) {
1530
		$posts_controller = new WP_REST_Posts_Controller( $post->post_type );
1531
		$post_type = get_post_type_object( $post->post_type );
1532
1533
		$has_password_filter = false;
1534
1535
		// Only check password if a specific post was queried for or a single comment
1536
		$requested_post = ! empty( $request['post'] ) && 1 === count( $request['post'] );
1537
		$requested_comment = ! empty( $request['id'] );
1538
		if ( ( $requested_post || $requested_comment ) && $posts_controller->can_access_password_content( $post, $request ) ) {
1539
			add_filter( 'post_password_required', '__return_false' );
1540
1541
			$has_password_filter = true;
1542
		}
1543
1544
		if ( post_password_required( $post ) ) {
1545
			$result = current_user_can( $post_type->cap->edit_post, $post->ID );
1546
		} else {
1547
			$result = $posts_controller->check_read_permission( $post );
1548
		}
1549
1550
		if ( $has_password_filter ) {
1551
			remove_filter( 'post_password_required', '__return_false' );
1552
		}
1553
1554
		return $result;
1555
	}
1556
1557
	/**
1558
	 * Checks if the comment can be read.
1559
	 *
1560
	 * @since 4.7.0
1561
	 * @access protected
1562
	 *
1563
	 * @param WP_Comment      $comment Comment object.
1564
	 * @param WP_REST_Request $request Request data to check.
1565
	 * @return bool Whether the comment can be read.
1566
	 */
1567
	protected function check_read_permission( $comment, $request ) {
1568
		if ( ! empty( $comment->comment_post_ID ) ) {
1569
			$post = get_post( $comment->comment_post_ID );
1570
			if ( $post ) {
1571
				if ( $this->check_read_post_permission( $post, $request ) && 1 === (int) $comment->comment_approved ) {
1572
					return true;
1573
				}
1574
			}
1575
		}
1576
1577
		if ( 0 === get_current_user_id() ) {
1578
			return false;
1579
		}
1580
1581
		if ( empty( $comment->comment_post_ID ) && ! current_user_can( 'moderate_comments' ) ) {
1582
			return false;
1583
		}
1584
1585
		if ( ! empty( $comment->user_id ) && get_current_user_id() === (int) $comment->user_id ) {
1586
			return true;
1587
		}
1588
1589
		return current_user_can( 'edit_comment', $comment->comment_ID );
1590
	}
1591
1592
	/**
1593
	 * Checks if a comment can be edited or deleted.
1594
	 *
1595
	 * @since 4.7.0
1596
	 * @access protected
1597
	 *
1598
	 * @param object $comment Comment object.
1599
	 * @return bool Whether the comment can be edited or deleted.
1600
	 */
1601
	protected function check_edit_permission( $comment ) {
1602
		if ( 0 === (int) get_current_user_id() ) {
1603
			return false;
1604
		}
1605
1606
		if ( ! current_user_can( 'moderate_comments' ) ) {
1607
			return false;
1608
		}
1609
1610
		return current_user_can( 'edit_comment', $comment->comment_ID );
1611
	}
1612
1613
	/**
1614
	 * Checks a comment author email for validity.
1615
	 *
1616
	 * Accepts either a valid email address or empty string as a valid comment
1617
	 * author email address. Setting the comment author email to an empty
1618
	 * string is allowed when a comment is being updated.
1619
	 *
1620
	 * @since 4.7.0
1621
	 * @access public
1622
	 *
1623
	 * @param string          $value   Author email value submitted.
1624
	 * @param WP_REST_Request $request Full details about the request.
1625
	 * @param string          $param   The parameter name.
1626
	 * @return WP_Error|string The sanitized email address, if valid,
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean|true|WP_Error?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1627
	 *                         otherwise an error.
1628
	 */
1629 View Code Duplication
	public function check_comment_author_email( $value, $request, $param ) {
1630
		$email = (string) $value;
1631
		if ( empty( $email ) ) {
1632
			return $email;
1633
		}
1634
1635
		$check_email = rest_validate_request_arg( $email, $request, $param );
1636
		if ( is_wp_error( $check_email ) ) {
1637
			return $check_email;
1638
		}
1639
1640
		return $email;
1641
	}
1642
}
1643