Completed
Branch develop (98f6a3)
by
unknown
02:30
created

can_access_password_content()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 2
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
class WP_REST_Posts_Controller extends WP_REST_Controller {
4
5
	protected $post_type;
6
7 View Code Duplication
	public function __construct( $post_type ) {
8
		$this->post_type = $post_type;
9
		$this->namespace = 'wp/v2';
10
		$obj = get_post_type_object( $post_type );
11
		$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
12
	}
13
14
	/**
15
	 * Register the routes for the objects of the controller.
16
	 */
17
	public function register_routes() {
18
19
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
20
			array(
21
				'methods'         => WP_REST_Server::READABLE,
22
				'callback'        => array( $this, 'get_items' ),
23
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
24
				'args'            => $this->get_collection_params(),
25
			),
26
			array(
27
				'methods'         => WP_REST_Server::CREATABLE,
28
				'callback'        => array( $this, 'create_item' ),
29
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
30
				'args'            => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
31
			),
32
			'schema' => array( $this, 'get_public_item_schema' ),
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
					'password' => array(
42
						'description' => __( 'The password for the post if it is password protected.' ),
43
					),
44
				),
45
			),
46
			array(
47
				'methods'         => WP_REST_Server::EDITABLE,
48
				'callback'        => array( $this, 'update_item' ),
49
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
50
				'args'            => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
51
			),
52
			array(
53
				'methods'  => WP_REST_Server::DELETABLE,
54
				'callback' => array( $this, 'delete_item' ),
55
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
56
				'args'     => array(
57
					'force'    => array(
58
						'default'      => false,
59
						'description'  => __( 'Whether to bypass trash and force deletion.' ),
60
					),
61
				),
62
			),
63
			'schema' => array( $this, 'get_public_item_schema' ),
64
		) );
65
	}
66
67
	/**
68
	 * Check if a given request has access to read /posts.
69
	 *
70
	 * @param  WP_REST_Request $request Full details about the request.
71
	 * @return WP_Error|boolean
72
	 */
73 View Code Duplication
	public function get_items_permissions_check( $request ) {
74
75
		$post_type = get_post_type_object( $this->post_type );
76
77
		if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
78
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit these posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
79
		}
80
81
		return true;
82
	}
83
84
	/**
85
	 * Get a collection of posts.
86
	 *
87
	 * @param WP_REST_Request $request Full details about the request.
88
	 * @return WP_Error|WP_REST_Response
89
	 */
90
	public function get_items( $request ) {
91
92
		//Make sure a search string is set in case the orderby is set to 'relevace'
93
		if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) && empty( $request['filter']['s'] ) ) {
94
			return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) );
95
		}
96
97
		$args                         = array();
98
		$args['author__in']           = $request['author'];
99
		$args['author__not_in']       = $request['author_exclude'];
100
		$args['menu_order']           = $request['menu_order'];
101
		$args['offset']               = $request['offset'];
102
		$args['order']                = $request['order'];
103
		$args['orderby']              = $request['orderby'];
104
		$args['paged']                = $request['page'];
105
		$args['post__in']             = $request['include'];
106
		$args['post__not_in']         = $request['exclude'];
107
		$args['name']                 = $request['slug'];
108
		$args['post_parent__in']      = $request['parent'];
109
		$args['post_parent__not_in']  = $request['parent_exclude'];
110
		$args['post_status']          = $request['status'];
111
		$args['s']                    = $request['search'];
112
113
		$args['date_query'] = array();
114
		// Set before into date query. Date query must be specified as an array of an array.
115
		if ( isset( $request['before'] ) ) {
116
			$args['date_query'][0]['before'] = $request['before'];
117
		}
118
119
		// Set after into date query. Date query must be specified as an array of an array.
120 View Code Duplication
		if ( isset( $request['after'] ) ) {
121
			$args['date_query'][0]['after'] = $request['after'];
122
		}
123
124
		if ( is_array( $request['filter'] ) ) {
125
			$args = array_merge( $args, $request['filter'] );
126
			unset( $args['filter'] );
127
		}
128
129
		// Ensure our per_page parameter overrides filter.
130
		$args['posts_per_page'] = $request['per_page'];
131
132
		if ( isset( $request['sticky'] ) ) {
133
			$sticky_posts = get_option( 'sticky_posts', array() );
134
			if ( $sticky_posts && $request['sticky'] ) {
135
				// As post__in will be used to only get sticky posts,
136
				// we have to support the case where post__in was already
137
				// specified.
138
				$args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts;
139
140
				// If we intersected, but there are no post ids in common,
141
				// WP_Query won't return "no posts" for `post__in = array()`
142
				// so we have to fake it a bit.
143
				if ( ! $args['post__in'] ) {
144
					$args['post__in'] = array( -1 );
145
				}
146
			} elseif ( $sticky_posts ) {
147
				// As post___not_in will be used to only get posts that
148
				// are not sticky, we have to support the case where post__not_in
149
				// was already specified.
150
				$args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts );
151
			}
152
		}
153
154
		// Force the post_type argument, since it's not a user input variable.
155
		$args['post_type'] = $this->post_type;
156
157
		/**
158
		 * Filter the query arguments for a request.
159
		 *
160
		 * Enables adding extra arguments or setting defaults for a post
161
		 * collection request.
162
		 *
163
		 * @see https://developer.wordpress.org/reference/classes/wp_user_query/
164
		 *
165
		 * @param array           $args    Key value array of query var to query value.
166
		 * @param WP_REST_Request $request The request used.
167
		 */
168
		$args = apply_filters( "rest_{$this->post_type}_query", $args, $request );
169
		$query_args = $this->prepare_items_query( $args, $request );
170
171
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
172
		foreach ( $taxonomies as $taxonomy ) {
173
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
174
175
			if ( ! empty( $request[ $base ] ) ) {
176
				$query_args['tax_query'][] = array(
177
					'taxonomy'         => $taxonomy->name,
178
					'field'            => 'term_id',
179
					'terms'            => $request[ $base ],
180
					'include_children' => false,
181
				);
182
			}
183
		}
184
185
		$posts_query = new WP_Query();
186
		$query_result = $posts_query->query( $query_args );
187
188
		// Allow access to all password protected posts if the context is edit.
189
		if ( 'edit' === $request['context'] ) {
190
			add_filter( 'post_password_required', '__return_false' );
191
		}
192
193
		$posts = array();
194 View Code Duplication
		foreach ( $query_result as $post ) {
195
			if ( ! $this->check_read_permission( $post ) ) {
196
				continue;
197
			}
198
199
			$data = $this->prepare_item_for_response( $post, $request );
200
			$posts[] = $this->prepare_response_for_collection( $data );
201
		}
202
203
		// Reset filter.
204
		if ( 'edit' === $request['context'] ) {
205
			remove_filter( 'post_password_required', '__return_false' );
206
		}
207
208
		$page = (int) $query_args['paged'];
209
		$total_posts = $posts_query->found_posts;
210
211
		if ( $total_posts < 1 ) {
212
			// Out-of-bounds, run the query again without LIMIT for total count
213
			unset( $query_args['paged'] );
214
			$count_query = new WP_Query();
215
			$count_query->query( $query_args );
216
			$total_posts = $count_query->found_posts;
217
		}
218
219
		$max_pages = ceil( $total_posts / (int) $query_args['posts_per_page'] );
220
221
		$response = rest_ensure_response( $posts );
222
		$response->header( 'X-WP-Total', (int) $total_posts );
223
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
224
225
		$request_params = $request->get_query_params();
226
		if ( ! empty( $request_params['filter'] ) ) {
227
			// Normalize the pagination params.
228
			unset( $request_params['filter']['posts_per_page'] );
229
			unset( $request_params['filter']['paged'] );
230
		}
231
		$base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
232
233 View Code Duplication
		if ( $page > 1 ) {
234
			$prev_page = $page - 1;
235
			if ( $prev_page > $max_pages ) {
236
				$prev_page = $max_pages;
237
			}
238
			$prev_link = add_query_arg( 'page', $prev_page, $base );
239
			$response->link_header( 'prev', $prev_link );
240
		}
241 View Code Duplication
		if ( $max_pages > $page ) {
242
			$next_page = $page + 1;
243
			$next_link = add_query_arg( 'page', $next_page, $base );
244
			$response->link_header( 'next', $next_link );
245
		}
246
247
		return $response;
248
	}
249
250
	/**
251
	 * Check if a given request has access to read a post.
252
	 *
253
	 * @param  WP_REST_Request $request Full details about the request.
254
	 * @return WP_Error|boolean
255
	 */
256
	public function get_item_permissions_check( $request ) {
257
258
		$post = $this->get_post( (int) $request['id'] );
259
260
		if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
261
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post' ), array( 'status' => rest_authorization_required_code() ) );
262
		}
263
264 View Code Duplication
		if ( $post && ! empty( $request['password'] ) ) {
265
			// Check post password, and return error if invalid.
266
			if ( ! hash_equals( $post->post_password, $request['password'] ) ) {
267
				return new WP_Error( 'rest_post_incorrect_password', __( 'Incorrect post password.' ), array( 'status' => 403 ) );
268
			}
269
		}
270
271
		// Allow access to all password protected posts if the context is edit.
272
		if ( 'edit' === $request['context'] ) {
273
			add_filter( 'post_password_required', '__return_false' );
274
		}
275
276
		if ( $post ) {
277
			return $this->check_read_permission( $post );
278
		}
279
280
		return true;
281
	}
282
283
	/**
284
	 * Can the user access passworded content?
285
	 *
286
	 * This method determines whether we need to override the regular password
287
	 * check in core with a filter.
288
	 *
289
	 * @param WP_Post $post Post to check against.
290
	 * @param WP_REST_Request $request Request data to check.
291
	 * @return bool True if the user can access passworded content, false otherwise.
292
	 */
293
	protected function can_access_password_content( $post, $request ) {
294
		if ( empty( $post->post_password ) ) {
295
			// No filter required.
296
			return false;
297
		}
298
299
		// Edit context always gets access to passworded posts.
300
		if ( 'edit' === $request['context'] ) {
301
			return true;
302
		}
303
304
		// No password, no auth.
305
		if ( empty( $request['password'] ) ) {
306
			return false;
307
		}
308
309
		// Double-check the request password.
310
		return hash_equals( $post->post_password, $request['password'] );
311
	}
312
313
	/**
314
	 * Get a single post.
315
	 *
316
	 * @param WP_REST_Request $request Full details about the request.
317
	 * @return WP_Error|WP_REST_Response
318
	 */
319
	public function get_item( $request ) {
320
		$id = (int) $request['id'];
321
		$post = $this->get_post( $id );
322
323
		if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
324
			return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
325
		}
326
327
		$data = $this->prepare_item_for_response( $post, $request );
328
		$response = rest_ensure_response( $data );
329
330
		if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) {
331
			$response->link_header( 'alternate',  get_permalink( $id ), array( 'type' => 'text/html' ) );
332
		}
333
334
		return $response;
335
	}
336
337
	/**
338
	 * Check if a given request has access to create a post.
339
	 *
340
	 * @param  WP_REST_Request $request Full details about the request.
341
	 * @return WP_Error|boolean
342
	 */
343
	public function create_item_permissions_check( $request ) {
344
345
		$post_type = get_post_type_object( $this->post_type );
346
347
		if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
348
			return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
349
		}
350
351
		if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
352
			return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
353
		}
354
355
		if ( ! current_user_can( $post_type->cap->create_posts ) ) {
356
			return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create new posts.' ), array( 'status' => rest_authorization_required_code() ) );
357
		}
358
		return true;
359
	}
360
361
	/**
362
	 * Create a single post.
363
	 *
364
	 * @param WP_REST_Request $request Full details about the request.
365
	 * @return WP_Error|WP_REST_Response
366
	 */
367
	public function create_item( $request ) {
368
		if ( ! empty( $request['id'] ) ) {
369
			return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) );
370
		}
371
372
		$post = $this->prepare_item_for_database( $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->prepare_item_for_database($request); of type WP_Error|stdClass adds the type stdClass to the return on line 374 which is incompatible with the return type documented by WP_REST_Posts_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
373
		if ( is_wp_error( $post ) ) {
374
			return $post;
375
		}
376
377
		$post->post_type = $this->post_type;
378
		$post_id = wp_insert_post( $post, true );
379
380 View Code Duplication
		if ( is_wp_error( $post_id ) ) {
381
382
			if ( in_array( $post_id->get_error_code(), array( 'db_insert_error' ) ) ) {
383
				$post_id->add_data( array( 'status' => 500 ) );
384
			} else {
385
				$post_id->add_data( array( 'status' => 400 ) );
386
			}
387
			return $post_id;
388
		}
389
		$post->ID = $post_id;
390
391
		$schema = $this->get_item_schema();
392
393 View Code Duplication
		if ( ! empty( $schema['properties']['sticky'] ) ) {
394
			if ( ! empty( $request['sticky'] ) ) {
395
				stick_post( $post_id );
396
			} else {
397
				unstick_post( $post_id );
398
			}
399
		}
400
401 View Code Duplication
		if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
402
			$this->handle_featured_media( $request['featured_media'], $post->ID );
403
		}
404
405 View Code Duplication
		if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
406
			set_post_format( $post, $request['format'] );
407
		}
408
409 View Code Duplication
		if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
410
			$this->handle_template( $request['template'], $post->ID );
411
		}
412
		$terms_update = $this->handle_terms( $post->ID, $request );
413
		if ( is_wp_error( $terms_update ) ) {
414
			return $terms_update;
415
		}
416
417
		$post = $this->get_post( $post_id );
418
		$fields_update = $this->update_additional_fields_for_object( $post, $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->update_additional...bject($post, $request); of type boolean|WP_Error adds the type boolean to the return on line 420 which is incompatible with the return type documented by WP_REST_Posts_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
Bug introduced by
It seems like $post defined by $this->get_post($post_id) on line 417 can also be of type null; however, WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
419
		if ( is_wp_error( $fields_update ) ) {
420
			return $fields_update;
421
		}
422
423
		/**
424
		 * Fires after a single post is created or updated via the REST API.
425
		 *
426
		 * @param object          $post      Inserted Post object (not a WP_Post object).
427
		 * @param WP_REST_Request $request   Request object.
428
		 * @param boolean         $creating  True when creating post, false when updating.
429
		 */
430
		do_action( "rest_insert_{$this->post_type}", $post, $request, true );
431
432
		$request->set_param( 'context', 'edit' );
433
		$response = $this->prepare_item_for_response( $post, $request );
0 ignored issues
show
Bug introduced by
It seems like $post defined by $this->get_post($post_id) on line 417 can be null; however, WP_REST_Posts_Controller...are_item_for_response() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
434
		$response = rest_ensure_response( $response );
435
		$response->set_status( 201 );
436
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) );
437
438
		return $response;
439
	}
440
441
	/**
442
	 * Check if a given request has access to update a post.
443
	 *
444
	 * @param  WP_REST_Request $request Full details about the request.
445
	 * @return WP_Error|boolean
446
	 */
447
	public function update_item_permissions_check( $request ) {
448
449
		$post = $this->get_post( $request['id'] );
450
		$post_type = get_post_type_object( $this->post_type );
451
452
		if ( $post && ! $this->check_update_permission( $post ) ) {
453
			return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to update this post.' ), array( 'status' => rest_authorization_required_code() ) );;
454
		}
455
456
		if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
457
			return new WP_Error( 'rest_cannot_edit_others', __( 'You are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) );
458
		}
459
460
		if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) {
461
			return new WP_Error( 'rest_cannot_assign_sticky', __( 'You do not have permission to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) );
462
		}
463
464
		return true;
465
	}
466
467
	/**
468
	 * Update a single post.
469
	 *
470
	 * @param WP_REST_Request $request Full details about the request.
471
	 * @return WP_Error|WP_REST_Response
472
	 */
473
	public function update_item( $request ) {
474
		$id = (int) $request['id'];
475
		$post = $this->get_post( $id );
476
477
		if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
478
			return new WP_Error( 'rest_post_invalid_id', __( 'Post id is invalid.' ), array( 'status' => 404 ) );
479
		}
480
481
		$post = $this->prepare_item_for_database( $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->prepare_item_for_database($request); of type WP_Error|stdClass adds the type stdClass to the return on line 483 which is incompatible with the return type documented by WP_REST_Posts_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
482
		if ( is_wp_error( $post ) ) {
483
			return $post;
484
		}
485
		// convert the post object to an array, otherwise wp_update_post will expect non-escaped input
486
		$post_id = wp_update_post( (array) $post, true );
487 View Code Duplication
		if ( is_wp_error( $post_id ) ) {
488
			if ( in_array( $post_id->get_error_code(), array( 'db_update_error' ) ) ) {
489
				$post_id->add_data( array( 'status' => 500 ) );
490
			} else {
491
				$post_id->add_data( array( 'status' => 400 ) );
492
			}
493
			return $post_id;
494
		}
495
496
		$schema = $this->get_item_schema();
497
498 View Code Duplication
		if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) {
499
			set_post_format( $post, $request['format'] );
500
		}
501
502 View Code Duplication
		if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) {
503
			$this->handle_featured_media( $request['featured_media'], $post_id );
504
		}
505
506 View Code Duplication
		if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) {
507
			if ( ! empty( $request['sticky'] ) ) {
508
				stick_post( $post_id );
509
			} else {
510
				unstick_post( $post_id );
511
			}
512
		}
513
514 View Code Duplication
		if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) {
515
			$this->handle_template( $request['template'], $post->ID );
516
		}
517
518
		$terms_update = $this->handle_terms( $post->ID, $request );
519
		if ( is_wp_error( $terms_update ) ) {
520
			return $terms_update;
521
		}
522
523
		$post = $this->get_post( $post_id );
524
		$fields_update = $this->update_additional_fields_for_object( $post, $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->update_additional...bject($post, $request); of type boolean|WP_Error adds the type boolean to the return on line 526 which is incompatible with the return type documented by WP_REST_Posts_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
Bug introduced by
It seems like $post defined by $this->get_post($post_id) on line 523 can also be of type null; however, WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
525
		if ( is_wp_error( $fields_update ) ) {
526
			return $fields_update;
527
		}
528
529
		/* This action is documented in lib/endpoints/class-wp-rest-controller.php */
530
		do_action( "rest_insert_{$this->post_type}", $post, $request, false );
531
532
		$request->set_param( 'context', 'edit' );
533
		$response = $this->prepare_item_for_response( $post, $request );
0 ignored issues
show
Bug introduced by
It seems like $post defined by $this->get_post($post_id) on line 523 can be null; however, WP_REST_Posts_Controller...are_item_for_response() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
534
		return rest_ensure_response( $response );
535
	}
536
537
	/**
538
	 * Check if a given request has access to delete a post.
539
	 *
540
	 * @param  WP_REST_Request $request Full details about the request.
541
	 * @return bool|WP_Error
542
	 */
543
	public function delete_item_permissions_check( $request ) {
544
545
		$post = $this->get_post( $request['id'] );
546
547
		if ( $post && ! $this->check_delete_permission( $post ) ) {
548
			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete posts.' ), array( 'status' => rest_authorization_required_code() ) );
549
		}
550
551
		return true;
552
	}
553
554
	/**
555
	 * Delete a single post.
556
	 *
557
	 * @param WP_REST_Request $request Full details about the request.
558
	 * @return WP_REST_Response|WP_Error
559
	 */
560
	public function delete_item( $request ) {
561
		$id = (int) $request['id'];
562
		$force = (bool) $request['force'];
563
564
		$post = $this->get_post( $id );
565
566
		if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
567
			return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) );
568
		}
569
570
		$supports_trash = ( EMPTY_TRASH_DAYS > 0 );
571
		if ( $post->post_type === 'attachment' ) {
572
			$supports_trash = $supports_trash && MEDIA_TRASH;
573
		}
574
575
		/**
576
		 * Filter whether a post is trashable.
577
		 *
578
		 * Return false to disable trash support for the post.
579
		 *
580
		 * @param boolean $supports_trash Whether the post type support trashing.
581
		 * @param WP_Post $post           The Post object being considered for trashing support.
582
		 */
583
		$supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post );
584
585
		if ( ! $this->check_delete_permission( $post ) ) {
586
			return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) );
587
		}
588
589
		$request->set_param( 'context', 'edit' );
590
		$response = $this->prepare_item_for_response( $post, $request );
591
592
		// If we're forcing, then delete permanently.
593
		if ( $force ) {
594
			$result = wp_delete_post( $id, true );
595
		} else {
596
			// If we don't support trashing for this type, error out.
597
			if ( ! $supports_trash ) {
598
				return new WP_Error( 'rest_trash_not_supported', __( 'The post does not support trashing.' ), array( 'status' => 501 ) );
599
			}
600
601
			// Otherwise, only trash if we haven't already.
602 View Code Duplication
			if ( 'trash' === $post->post_status ) {
603
				return new WP_Error( 'rest_already_trashed', __( 'The post has already been deleted.' ), array( 'status' => 410 ) );
604
			}
605
606
			// (Note that internally this falls through to `wp_delete_post` if
607
			// the trash is disabled.)
608
			$result = wp_trash_post( $id );
609
		}
610
611
		if ( ! $result ) {
612
			return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
613
		}
614
615
		/**
616
		 * Fires after a single post is deleted or trashed via the REST API.
617
		 *
618
		 * @param object           $post     The deleted or trashed post.
619
		 * @param WP_REST_Response $response The response data.
620
		 * @param WP_REST_Request  $request  The request sent to the API.
621
		 */
622
		do_action( "rest_delete_{$this->post_type}", $post, $response, $request );
623
624
		return $response;
625
	}
626
627
	/**
628
	 * Determine the allowed query_vars for a get_items() response and
629
	 * prepare for WP_Query.
630
	 *
631
	 * @param array           $prepared_args
632
	 * @param WP_REST_Request $request
633
	 * @return array          $query_args
634
	 */
635
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
636
637
		$valid_vars = array_flip( $this->get_allowed_query_vars() );
638
		$query_args = array();
639
		foreach ( $valid_vars as $var => $index ) {
640
			if ( isset( $prepared_args[ $var ] ) ) {
641
				/**
642
				 * Filter the query_vars used in `get_items` for the constructed query.
643
				 *
644
				 * The dynamic portion of the hook name, $var, refers to the query_var key.
645
				 *
646
				 * @param mixed $prepared_args[ $var ] The query_var value.
647
				 *
648
				 */
649
				$query_args[ $var ] = apply_filters( "rest_query_var-{$var}", $prepared_args[ $var ] );
650
			}
651
		}
652
653
		if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) {
654
			$query_args['ignore_sticky_posts'] = true;
655
		}
656
657
		if ( 'include' === $query_args['orderby'] ) {
658
			$query_args['orderby'] = 'post__in';
659
		}
660
661
		return $query_args;
662
	}
663
664
	/**
665
	 * Get all the WP Query vars that are allowed for the API request.
666
	 *
667
	 * @return array
668
	 */
669
	protected function get_allowed_query_vars() {
670
		global $wp;
671
672
		/**
673
		 * Filter the publicly allowed query vars.
674
		 *
675
		 * Allows adjusting of the default query vars that are made public.
676
		 *
677
		 * @param array  Array of allowed WP_Query query vars.
678
		 */
679
		$valid_vars = apply_filters( 'query_vars', $wp->public_query_vars );
680
681
		$post_type_obj = get_post_type_object( $this->post_type );
682
		if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
683
			/**
684
			 * Filter the allowed 'private' query vars for authorized users.
685
			 *
686
			 * If the user has the `edit_posts` capability, we also allow use of
687
			 * private query parameters, which are only undesirable on the
688
			 * frontend, but are safe for use in query strings.
689
			 *
690
			 * To disable anyway, use
691
			 * `add_filter( 'rest_private_query_vars', '__return_empty_array' );`
692
			 *
693
			 * @param array $private_query_vars Array of allowed query vars for authorized users.
694
			 * }
695
			 */
696
			$private = apply_filters( 'rest_private_query_vars', $wp->private_query_vars );
697
			$valid_vars = array_merge( $valid_vars, $private );
698
		}
699
		// Define our own in addition to WP's normal vars.
700
		$rest_valid = array(
701
			'author__in',
702
			'author__not_in',
703
			'ignore_sticky_posts',
704
			'menu_order',
705
			'offset',
706
			'post__in',
707
			'post__not_in',
708
			'post_parent',
709
			'post_parent__in',
710
			'post_parent__not_in',
711
			'posts_per_page',
712
			'date_query',
713
		);
714
		$valid_vars = array_merge( $valid_vars, $rest_valid );
715
716
		/**
717
		 * Filter allowed query vars for the REST API.
718
		 *
719
		 * This filter allows you to add or remove query vars from the final allowed
720
		 * list for all requests, including unauthenticated ones. To alter the
721
		 * vars for editors only, {@see rest_private_query_vars}.
722
		 *
723
		 * @param array {
724
		 *    Array of allowed WP_Query query vars.
725
		 *
726
		 *    @param string $allowed_query_var The query var to allow.
727
		 * }
728
		 */
729
		$valid_vars = apply_filters( 'rest_query_vars', $valid_vars );
730
731
		return $valid_vars;
732
	}
733
734
	/**
735
	 * Check the post_date_gmt or modified_gmt and prepare any post or
736
	 * modified date for single post output.
737
	 *
738
	 * @param string       $date_gmt
739
	 * @param string|null  $date
740
	 * @return string|null ISO8601/RFC3339 formatted datetime.
741
	 */
742 View Code Duplication
	protected function prepare_date_response( $date_gmt, $date = null ) {
743
		// Use the date if passed.
744
		if ( isset( $date ) ) {
745
			return mysql_to_rfc3339( $date );
746
		}
747
748
		// Return null if $date_gmt is empty/zeros.
749
		if ( '0000-00-00 00:00:00' === $date_gmt ) {
750
			return null;
751
		}
752
753
		// Return the formatted datetime.
754
		return mysql_to_rfc3339( $date_gmt );
755
	}
756
757
	/**
758
	 * Prepare a single post for create or update.
759
	 *
760
	 * @param WP_REST_Request $request Request object.
761
	 * @return WP_Error|stdClass $prepared_post Post object.
762
	 */
763
	protected function prepare_item_for_database( $request ) {
764
		$prepared_post = new stdClass;
765
766
		// ID.
767
		if ( isset( $request['id'] ) ) {
768
			$prepared_post->ID = absint( $request['id'] );
769
		}
770
771
		$schema = $this->get_item_schema();
772
773
		// Post title.
774 View Code Duplication
		if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
775
			if ( is_string( $request['title'] ) ) {
776
				$prepared_post->post_title = wp_filter_post_kses( $request['title'] );
777
			} elseif ( ! empty( $request['title']['raw'] ) ) {
778
				$prepared_post->post_title = wp_filter_post_kses( $request['title']['raw'] );
779
			}
780
		}
781
782
		// Post content.
783 View Code Duplication
		if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) {
784
			if ( is_string( $request['content'] ) ) {
785
				$prepared_post->post_content = wp_filter_post_kses( $request['content'] );
786
			} elseif ( isset( $request['content']['raw'] ) ) {
787
				$prepared_post->post_content = wp_filter_post_kses( $request['content']['raw'] );
788
			}
789
		}
790
791
		// Post excerpt.
792 View Code Duplication
		if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) {
793
			if ( is_string( $request['excerpt'] ) ) {
794
				$prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt'] );
795
			} elseif ( isset( $request['excerpt']['raw'] ) ) {
796
				$prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt']['raw'] );
797
			}
798
		}
799
800
		// Post type.
801
		if ( empty( $request['id'] ) ) {
802
			// Creating new post, use default type for the controller.
803
			$prepared_post->post_type = $this->post_type;
804
		} else {
805
			// Updating a post, use previous type.
806
			$prepared_post->post_type = get_post_type( $request['id'] );
807
		}
808
		$post_type = get_post_type_object( $prepared_post->post_type );
809
810
		// Post status.
811
		if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) {
812
			$status = $this->handle_status_param( $request['status'], $post_type );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->handle_status_par...'status'], $post_type); of type WP_Error|string adds the type string to the return on line 814 which is incompatible with the return type documented by WP_REST_Posts_Controller...epare_item_for_database of type WP_Error|stdClass.
Loading history...
813
			if ( is_wp_error( $status ) ) {
814
				return $status;
815
			}
816
817
			$prepared_post->post_status = $status;
818
		}
819
820
		// Post date.
821
		if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) {
822
			$date_data = rest_get_date_with_gmt( $request['date'] );
823
824
			if ( ! empty( $date_data ) ) {
825
				list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
826
			}
827
		} elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) {
828
			$date_data = rest_get_date_with_gmt( $request['date_gmt'], true );
829
830
			if ( ! empty( $date_data ) ) {
831
				list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data;
832
			}
833
		}
834
		// Post slug.
835 View Code Duplication
		if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
836
			$prepared_post->post_name = $request['slug'];
837
		}
838
839
		// Author
840
		if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) {
841
			$post_author = (int) $request['author'];
842 View Code Duplication
			if ( get_current_user_id() !== $post_author ) {
843
				$user_obj = get_userdata( $post_author );
844
				if ( ! $user_obj ) {
845
					return new WP_Error( 'rest_invalid_author', __( 'Invalid author id.' ), array( 'status' => 400 ) );
846
				}
847
			}
848
			$prepared_post->post_author = $post_author;
849
		}
850
851
		// Post password.
852
		if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) && '' !== $request['password'] ) {
853
			$prepared_post->post_password = $request['password'];
854
855
			if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
856
				return new WP_Error( 'rest_invalid_field', __( 'A post can not be sticky and have a password.' ), array( 'status' => 400 ) );
857
			}
858
859
			if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) {
860
				return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), array( 'status' => 400 ) );
861
			}
862
		}
863
864
		if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) {
865
			if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) {
866
				return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), array( 'status' => 400 ) );
867
			}
868
		}
869
870
		// Parent.
871
		if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) {
872
			$parent = $this->get_post( (int) $request['parent'] );
873
			if ( empty( $parent ) ) {
874
				return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent id.' ), array( 'status' => 400 ) );
875
			}
876
877
			$prepared_post->post_parent = (int) $parent->ID;
878
		}
879
880
		// Menu order.
881 View Code Duplication
		if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) {
882
			$prepared_post->menu_order = (int) $request['menu_order'];
883
		}
884
885
		// Comment status.
886 View Code Duplication
		if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) {
887
			$prepared_post->comment_status = $request['comment_status'];
888
		}
889
890
		// Ping status.
891 View Code Duplication
		if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) {
892
			$prepared_post->ping_status = $request['ping_status'];
893
		}
894
		/**
895
		 * Filter a post before it is inserted via the REST API.
896
		 *
897
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
898
		 * prepared for insertion.
899
		 *
900
		 * @param stdClass        $prepared_post An object representing a single post prepared
901
		 *                                       for inserting or updating the database.
902
		 * @param WP_REST_Request $request       Request object.
903
		 */
904
		return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request );
905
906
	}
907
908
	/**
909
	 * Determine validity and normalize provided status param.
910
	 *
911
	 * @param string $post_status
912
	 * @param object $post_type
913
	 * @return WP_Error|string $post_status
914
	 */
915
	protected function handle_status_param( $post_status, $post_type ) {
916
917
		switch ( $post_status ) {
918
			case 'draft':
919
			case 'pending':
920
				break;
921 View Code Duplication
			case 'private':
922
				if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
923
					return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
924
				}
925
				break;
926
			case 'publish':
927 View Code Duplication
			case 'future':
928
				if ( ! current_user_can( $post_type->cap->publish_posts ) ) {
929
					return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type' ), array( 'status' => rest_authorization_required_code() ) );
930
				}
931
				break;
932
			default:
933
				if ( ! get_post_status_object( $post_status ) ) {
934
					$post_status = 'draft';
935
				}
936
				break;
937
		}
938
939
		return $post_status;
940
	}
941
942
	/**
943
	 * Determine the featured media based on a request param.
944
	 *
945
	 * @param int $featured_media
946
	 * @param int $post_id
947
	 *
948
	 * @return bool|WP_Error
949
	 */
950
	protected function handle_featured_media( $featured_media, $post_id ) {
951
952
		$featured_media = (int) $featured_media;
953
		if ( $featured_media ) {
954
			$result = set_post_thumbnail( $post_id, $featured_media );
955
			if ( $result ) {
956
				return true;
957
			} else {
958
				return new WP_Error( 'rest_invalid_featured_media', __( 'Invalid featured media id.' ), array( 'status' => 400 ) );
959
			}
960
		} else {
961
			return delete_post_thumbnail( $post_id );
962
		}
963
964
	}
965
966
	/**
967
	 * Set the template for a page.
968
	 *
969
	 * @param string $template
970
	 * @param integer $post_id
971
	 */
972
	public function handle_template( $template, $post_id ) {
973
		if ( in_array( $template, array_keys( wp_get_theme()->get_page_templates( $this->get_post( $post_id ) ) ) ) ) {
974
			update_post_meta( $post_id, '_wp_page_template', $template );
975
		} else {
976
			update_post_meta( $post_id, '_wp_page_template', '' );
977
		}
978
	}
979
980
	/**
981
	 * Update the post's terms from a REST request.
982
	 *
983
	 * @param  int             $post_id The post ID to update the terms form.
984
	 * @param  WP_REST_Request $request The request object with post and terms data.
985
	 * @return null|WP_Error   WP_Error on an error assigning any of ther terms.
986
	 */
987
	protected function handle_terms( $post_id, $request ) {
988
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
989
		foreach ( $taxonomies as $taxonomy ) {
990
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
991
992
			if ( ! isset( $request[ $base ] ) ) {
993
				continue;
994
			}
995
			$terms = array_map( 'absint', $request[ $base ] );
996
			$result = wp_set_object_terms( $post_id, $terms, $taxonomy->name );
997
			if ( is_wp_error( $result ) ) {
998
				return $result;
999
			}
1000
		}
1001
	}
1002
1003
	/**
1004
	 * Check if a given post type should be viewed or managed.
1005
	 *
1006
	 * @param object|string $post_type
1007
	 * @return boolean Is post type allowed?
1008
	 */
1009
	protected function check_is_post_type_allowed( $post_type ) {
1010
		if ( ! is_object( $post_type ) ) {
1011
			$post_type = get_post_type_object( $post_type );
1012
		}
1013
1014
		if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) {
1015
			return true;
1016
		}
1017
1018
		return false;
1019
	}
1020
1021
	/**
1022
	 * Check if we can read a post.
1023
	 *
1024
	 * Correctly handles posts with the inherit status.
1025
	 *
1026
	 * @param object $post Post object.
1027
	 * @return boolean Can we read it?
1028
	 */
1029
	public function check_read_permission( $post ) {
1030
		$post_type = get_post_type_object( $post->post_type );
1031
		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
1032
			return false;
1033
		}
1034
1035
		// Can we read the post?
1036
		if ( 'publish' === $post->post_status || current_user_can( $post_type->cap->read_post, $post->ID ) ) {
1037
			return true;
1038
		}
1039
1040
		$post_status_obj = get_post_status_object( $post->post_status );
1041
		if ( $post_status_obj && $post_status_obj->public ) {
1042
			return true;
1043
		}
1044
1045
		// Can we read the parent if we're inheriting?
1046
		if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
1047
			$parent = $this->get_post( $post->post_parent );
1048
			return $this->check_read_permission( $parent );
0 ignored issues
show
Bug introduced by
It seems like $parent defined by $this->get_post($post->post_parent) on line 1047 can be null; however, WP_REST_Posts_Controller::check_read_permission() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1049
		}
1050
1051
		// If we don't have a parent, but the status is set to inherit, assume
1052
		// it's published (as per get_post_status()).
1053
		if ( 'inherit' === $post->post_status ) {
1054
			return true;
1055
		}
1056
1057
		return false;
1058
	}
1059
1060
	/**
1061
	 * Check if we can edit a post.
1062
	 *
1063
	 * @param object $post Post object.
1064
	 * @return boolean Can we edit it?
1065
	 */
1066 View Code Duplication
	protected function check_update_permission( $post ) {
1067
		$post_type = get_post_type_object( $post->post_type );
1068
1069
		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
1070
			return false;
1071
		}
1072
1073
		return current_user_can( $post_type->cap->edit_post, $post->ID );
1074
	}
1075
1076
	/**
1077
	 * Check if we can create a post.
1078
	 *
1079
	 * @param object $post Post object.
1080
	 * @return boolean Can we create it?.
1081
	 */
1082 View Code Duplication
	protected function check_create_permission( $post ) {
1083
		$post_type = get_post_type_object( $post->post_type );
1084
1085
		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
1086
			return false;
1087
		}
1088
1089
		return current_user_can( $post_type->cap->create_posts );
1090
	}
1091
1092
	/**
1093
	 * Check if we can delete a post.
1094
	 *
1095
	 * @param object $post Post object.
1096
	 * @return boolean Can we delete it?
1097
	 */
1098 View Code Duplication
	protected function check_delete_permission( $post ) {
1099
		$post_type = get_post_type_object( $post->post_type );
1100
1101
		if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
1102
			return false;
1103
		}
1104
1105
		return current_user_can( $post_type->cap->delete_post, $post->ID );
1106
	}
1107
1108
	/**
1109
	 * Prepare a single post output for response.
1110
	 *
1111
	 * @param WP_Post $post Post object.
1112
	 * @param WP_REST_Request $request Request object.
1113
	 * @return WP_REST_Response $data
1114
	 */
1115
	public function prepare_item_for_response( $post, $request ) {
1116
		$GLOBALS['post'] = $post;
1117
		setup_postdata( $post );
1118
1119
		$schema = $this->get_item_schema();
1120
1121
		// Base fields for every post.
1122
		$data = array();
1123
1124
		if ( ! empty( $schema['properties']['id'] ) ) {
1125
			$data['id'] = $post->ID;
1126
		}
1127
1128 View Code Duplication
		if ( ! empty( $schema['properties']['date'] ) ) {
1129
			$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
1130
		}
1131
1132 View Code Duplication
		if ( ! empty( $schema['properties']['date_gmt'] ) ) {
1133
			$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
1134
		}
1135
1136 View Code Duplication
		if ( ! empty( $schema['properties']['guid'] ) ) {
1137
			$data['guid'] = array(
1138
				/** This filter is documented in wp-includes/post-template.php */
1139
				'rendered' => apply_filters( 'get_the_guid', $post->guid ),
1140
				'raw'      => $post->guid,
1141
			);
1142
		}
1143
1144 View Code Duplication
		if ( ! empty( $schema['properties']['modified'] ) ) {
1145
			$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
1146
		}
1147
1148 View Code Duplication
		if ( ! empty( $schema['properties']['modified_gmt'] ) ) {
1149
			$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
1150
		}
1151
1152
		if ( ! empty( $schema['properties']['password'] ) ) {
1153
			$data['password'] = $post->post_password;
1154
		}
1155
1156
		if ( ! empty( $schema['properties']['slug'] ) ) {
1157
			$data['slug'] = $post->post_name;
1158
		}
1159
1160
		if ( ! empty( $schema['properties']['status'] ) ) {
1161
			$data['status'] = $post->post_status;
1162
		}
1163
1164
		if ( ! empty( $schema['properties']['type'] ) ) {
1165
			$data['type'] = $post->post_type;
1166
		}
1167
1168
		if ( ! empty( $schema['properties']['link'] ) ) {
1169
			$data['link'] = get_permalink( $post->ID );
1170
		}
1171
1172
		if ( ! empty( $schema['properties']['title'] ) ) {
1173
			add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
1174
			$data['title'] = array(
1175
				'raw'      => $post->post_title,
1176
				'rendered' => get_the_title( $post->ID ),
1177
			);
1178
			remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
1179
		}
1180
1181
		$has_password_filter = false;
1182
		if ( $this->can_access_password_content( $post, $request ) ) {
1183
			// Allow access to the post, permissions already checked before.
1184
			add_filter( 'post_password_required', '__return_false' );
1185
			$has_password_filter = true;
1186
		}
1187
1188
		if ( ! empty( $schema['properties']['content'] ) ) {
1189
			$data['content'] = array(
1190
				'raw'       => $post->post_content,
1191
				/** This filter is documented in wp-includes/post-template.php */
1192
				'rendered'  => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ),
1193
				'protected' => (bool) $post->post_password,
1194
			);
1195
		}
1196
1197
		if ( ! empty( $schema['properties']['excerpt'] ) ) {
1198
			/** This filter is documented in wp-includes/post-template.php */
1199
			$excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) );
1200
			$data['excerpt'] = array(
1201
				'raw'       => $post->post_excerpt,
1202
				'rendered'  => post_password_required( $post ) ? '' : $excerpt,
1203
				'protected' => (bool) $post->post_password,
1204
			);
1205
		}
1206
1207
		if ( $has_password_filter ) {
1208
			// Reset filter.
1209
			remove_filter( 'post_password_required', '__return_false' );
1210
		}
1211
1212
		if ( ! empty( $schema['properties']['author'] ) ) {
1213
			$data['author'] = (int) $post->post_author;
1214
		}
1215
1216
		if ( ! empty( $schema['properties']['featured_media'] ) ) {
1217
			$data['featured_media'] = (int) get_post_thumbnail_id( $post->ID );
1218
		}
1219
1220
		if ( ! empty( $schema['properties']['parent'] ) ) {
1221
			$data['parent'] = (int) $post->post_parent;
1222
		}
1223
1224
		if ( ! empty( $schema['properties']['menu_order'] ) ) {
1225
			$data['menu_order'] = (int) $post->menu_order;
1226
		}
1227
1228
		if ( ! empty( $schema['properties']['comment_status'] ) ) {
1229
			$data['comment_status'] = $post->comment_status;
1230
		}
1231
1232
		if ( ! empty( $schema['properties']['ping_status'] ) ) {
1233
			$data['ping_status'] = $post->ping_status;
1234
		}
1235
1236
		if ( ! empty( $schema['properties']['sticky'] ) ) {
1237
			$data['sticky'] = is_sticky( $post->ID );
1238
		}
1239
1240 View Code Duplication
		if ( ! empty( $schema['properties']['template'] ) ) {
1241
			if ( $template = get_page_template_slug( $post->ID ) ) {
1242
				$data['template'] = $template;
1243
			} else {
1244
				$data['template'] = '';
1245
			}
1246
		}
1247
1248 View Code Duplication
		if ( ! empty( $schema['properties']['format'] ) ) {
1249
			$data['format'] = get_post_format( $post->ID );
1250
			// Fill in blank post format.
1251
			if ( empty( $data['format'] ) ) {
1252
				$data['format'] = 'standard';
1253
			}
1254
		}
1255
1256
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1257
		foreach ( $taxonomies as $taxonomy ) {
1258
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1259
			if ( ! empty( $schema['properties'][ $base ] ) ) {
1260
				$terms = get_the_terms( $post, $taxonomy->name );
1261
				$data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
1262
			}
1263
		}
1264
1265
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
1266
		$data = $this->add_additional_fields_to_object( $data, $request );
1267
		$data = $this->filter_response_by_context( $data, $context );
1268
1269
		// Wrap the data in a response object.
1270
		$response = rest_ensure_response( $data );
1271
1272
		$response->add_links( $this->prepare_links( $post ) );
1273
1274
		/**
1275
		 * Filter the post data for a response.
1276
		 *
1277
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
1278
		 * prepared for the response.
1279
		 *
1280
		 * @param WP_REST_Response   $response   The response object.
1281
		 * @param WP_Post            $post       Post object.
1282
		 * @param WP_REST_Request    $request    Request object.
1283
		 */
1284
		return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
1285
	}
1286
1287
	/**
1288
	 * Overwrite the default protected title format.
1289
	 *
1290
	 * By default WordPress will show password protected posts with a title of
1291
	 * "Protected: %s", as the REST API communicates the protected status of a post
1292
	 * in a machine readable format, we remove the "Protected: " prefix.
1293
	 *
1294
	 * @param  string $format
0 ignored issues
show
Bug introduced by
There is no parameter named $format. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1295
	 * @return string
1296
	 */
1297
	public function protected_title_format() {
1298
		return '%s';
1299
	}
1300
1301
	/**
1302
	 * Prepare links for the request.
1303
	 *
1304
	 * @param WP_Post $post Post object.
1305
	 * @return array Links for the given post.
1306
	 */
1307
	protected function prepare_links( $post ) {
1308
		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
1309
1310
		// Entity meta
1311
		$links = array(
1312
			'self' => array(
1313
				'href'   => rest_url( trailingslashit( $base ) . $post->ID ),
1314
			),
1315
			'collection' => array(
1316
				'href'   => rest_url( $base ),
1317
			),
1318
			'about'      => array(
1319
				'href'   => rest_url( 'wp/v2/types/' . $this->post_type ),
1320
			),
1321
		);
1322
1323 View Code Duplication
		if ( ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'author' ) )
1324
			&& ! empty( $post->post_author ) ) {
1325
			$links['author'] = array(
1326
				'href'       => rest_url( 'wp/v2/users/' . $post->post_author ),
1327
				'embeddable' => true,
1328
			);
1329
		};
1330
1331
		if ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'comments' ) ) {
1332
			$replies_url = rest_url( 'wp/v2/comments' );
1333
			$replies_url = add_query_arg( 'post', $post->ID, $replies_url );
1334
			$links['replies'] = array(
1335
				'href'         => $replies_url,
1336
				'embeddable'   => true,
1337
			);
1338
		}
1339
1340 View Code Duplication
		if ( in_array( $post->post_type, array( 'post', 'page' ) ) || post_type_supports( $post->post_type, 'revisions' ) ) {
1341
			$links['version-history'] = array(
1342
				'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ),
1343
			);
1344
		}
1345
		$post_type_obj = get_post_type_object( $post->post_type );
1346
		if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) {
1347
			$links['up'] = array(
1348
				'href'       => rest_url( trailingslashit( $base ) . (int) $post->post_parent ),
1349
				'embeddable' => true,
1350
			);
1351
		}
1352
1353
		// If we have a featured media, add that.
1354
		if ( $featured_media = get_post_thumbnail_id( $post->ID ) ) {
1355
			$image_url = rest_url( 'wp/v2/media/' . $featured_media );
1356
			$links['https://api.w.org/featuredmedia'] = array(
1357
				'href'       => $image_url,
1358
				'embeddable' => true,
1359
			);
1360
		}
1361
		if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ) ) ) {
1362
			$attachments_url = rest_url( 'wp/v2/media' );
1363
			$attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url );
1364
			$links['https://api.w.org/attachment'] = array(
1365
				'href'       => $attachments_url,
1366
			);
1367
		}
1368
1369
		$taxonomies = get_object_taxonomies( $post->post_type );
1370
		if ( ! empty( $taxonomies ) ) {
1371
			$links['https://api.w.org/term'] = array();
1372
1373
			foreach ( $taxonomies as $tax ) {
1374
				$taxonomy_obj = get_taxonomy( $tax );
1375
				// Skip taxonomies that are not public.
1376
				if ( empty( $taxonomy_obj->show_in_rest ) ) {
1377
					continue;
1378
				}
1379
1380
				$tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax;
1381
				$terms_url = add_query_arg(
1382
					'post',
1383
					$post->ID,
1384
					rest_url( 'wp/v2/' . $tax_base )
1385
				);
1386
1387
				$links['https://api.w.org/term'][] = array(
1388
					'href'       => $terms_url,
1389
					'taxonomy'   => $tax,
1390
					'embeddable' => true,
1391
				);
1392
			}
1393
		}
1394
1395
		return $links;
1396
	}
1397
1398
	/**
1399
	 * Get the Post's schema, conforming to JSON Schema.
1400
	 *
1401
	 * @return array
1402
	 */
1403
	public function get_item_schema() {
1404
1405
		$schema = array(
1406
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
1407
			'title'      => $this->post_type,
1408
			'type'       => 'object',
1409
			/*
1410
			 * Base properties for every Post.
1411
			 */
1412
			'properties' => array(
1413
				'date'            => array(
1414
					'description' => __( "The date the object was published, in the site's timezone." ),
1415
					'type'        => 'string',
1416
					'format'      => 'date-time',
1417
					'context'     => array( 'view', 'edit', 'embed' ),
1418
				),
1419
				'date_gmt'        => array(
1420
					'description' => __( 'The date the object was published, as GMT.' ),
1421
					'type'        => 'string',
1422
					'format'      => 'date-time',
1423
					'context'     => array( 'view', 'edit' ),
1424
				),
1425
				'guid'            => array(
1426
					'description' => __( 'The globally unique identifier for the object.' ),
1427
					'type'        => 'object',
1428
					'context'     => array( 'view', 'edit' ),
1429
					'readonly'    => true,
1430
					'properties'  => array(
1431
						'raw'      => array(
1432
							'description' => __( 'GUID for the object, as it exists in the database.' ),
1433
							'type'        => 'string',
1434
							'context'     => array( 'edit' ),
1435
							'readonly'    => true,
1436
						),
1437
						'rendered' => array(
1438
							'description' => __( 'GUID for the object, transformed for display.' ),
1439
							'type'        => 'string',
1440
							'context'     => array( 'view', 'edit' ),
1441
							'readonly'    => true,
1442
						),
1443
					),
1444
				),
1445
				'id'              => array(
1446
					'description' => __( 'Unique identifier for the object.' ),
1447
					'type'        => 'integer',
1448
					'context'     => array( 'view', 'edit', 'embed' ),
1449
					'readonly'    => true,
1450
				),
1451
				'link'            => array(
1452
					'description' => __( 'URL to the object.' ),
1453
					'type'        => 'string',
1454
					'format'      => 'uri',
1455
					'context'     => array( 'view', 'edit', 'embed' ),
1456
					'readonly'    => true,
1457
				),
1458
				'modified'        => array(
1459
					'description' => __( "The date the object was last modified, in the site's timezone." ),
1460
					'type'        => 'string',
1461
					'format'      => 'date-time',
1462
					'context'     => array( 'view', 'edit' ),
1463
					'readonly'    => true,
1464
				),
1465
				'modified_gmt'    => array(
1466
					'description' => __( 'The date the object was last modified, as GMT.' ),
1467
					'type'        => 'string',
1468
					'format'      => 'date-time',
1469
					'context'     => array( 'view', 'edit' ),
1470
					'readonly'    => true,
1471
				),
1472
				'slug'            => array(
1473
					'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
1474
					'type'        => 'string',
1475
					'context'     => array( 'view', 'edit', 'embed' ),
1476
					'arg_options' => array(
1477
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
1478
					),
1479
				),
1480
				'status'          => array(
1481
					'description' => __( 'A named status for the object.' ),
1482
					'type'        => 'string',
1483
					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
1484
					'context'     => array( 'edit' ),
1485
				),
1486
				'type'            => array(
1487
					'description' => __( 'Type of Post for the object.' ),
1488
					'type'        => 'string',
1489
					'context'     => array( 'view', 'edit', 'embed' ),
1490
					'readonly'    => true,
1491
				),
1492
			),
1493
		);
1494
1495
		$post_type_obj = get_post_type_object( $this->post_type );
1496 View Code Duplication
		if ( $post_type_obj->hierarchical ) {
1497
			$schema['properties']['parent'] = array(
1498
				'description' => __( 'The id for the parent of the object.' ),
1499
				'type'        => 'integer',
1500
				'context'     => array( 'view', 'edit' ),
1501
			);
1502
		}
1503
1504
		$post_type_attributes = array(
1505
			'title',
1506
			'editor',
1507
			'author',
1508
			'excerpt',
1509
			'thumbnail',
1510
			'comments',
1511
			'revisions',
1512
			'page-attributes',
1513
			'post-formats',
1514
		);
1515
		$fixed_schemas = array(
1516
			'post' => array(
1517
				'title',
1518
				'editor',
1519
				'author',
1520
				'excerpt',
1521
				'thumbnail',
1522
				'comments',
1523
				'revisions',
1524
				'post-formats',
1525
			),
1526
			'page' => array(
1527
				'title',
1528
				'editor',
1529
				'author',
1530
				'excerpt',
1531
				'thumbnail',
1532
				'comments',
1533
				'revisions',
1534
				'page-attributes',
1535
			),
1536
			'attachment' => array(
1537
				'title',
1538
				'author',
1539
				'comments',
1540
				'revisions',
1541
			),
1542
		);
1543
		foreach ( $post_type_attributes as $attribute ) {
1544
			if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ] ) ) {
1545
				continue;
1546
			} elseif ( ! in_array( $this->post_type, array_keys( $fixed_schemas ) ) && ! post_type_supports( $this->post_type, $attribute ) ) {
1547
				continue;
1548
			}
1549
1550
			switch ( $attribute ) {
1551
1552
				case 'title':
1553
					$schema['properties']['title'] = array(
1554
						'description' => __( 'The title for the object.' ),
1555
						'type'        => 'object',
1556
						'context'     => array( 'view', 'edit', 'embed' ),
1557
						'properties'  => array(
1558
							'raw' => array(
1559
								'description' => __( 'Title for the object, as it exists in the database.' ),
1560
								'type'        => 'string',
1561
								'context'     => array( 'edit' ),
1562
							),
1563
							'rendered' => array(
1564
								'description' => __( 'HTML title for the object, transformed for display.' ),
1565
								'type'        => 'string',
1566
								'context'     => array( 'view', 'edit', 'embed' ),
1567
								'readonly'    => true,
1568
							),
1569
						),
1570
					);
1571
					break;
1572
1573 View Code Duplication
				case 'editor':
1574
					$schema['properties']['content'] = array(
1575
						'description' => __( 'The content for the object.' ),
1576
						'type'        => 'object',
1577
						'context'     => array( 'view', 'edit' ),
1578
						'properties'  => array(
1579
							'raw' => array(
1580
								'description' => __( 'Content for the object, as it exists in the database.' ),
1581
								'type'        => 'string',
1582
								'context'     => array( 'edit' ),
1583
							),
1584
							'rendered' => array(
1585
								'description' => __( 'HTML content for the object, transformed for display.' ),
1586
								'type'        => 'string',
1587
								'context'     => array( 'view', 'edit' ),
1588
								'readonly'    => true,
1589
							),
1590
							'protected'       => array(
1591
								'description' => __( 'Whether the content is protected with a password.' ),
1592
								'type'        => 'boolean',
1593
								'context'     => array( 'view', 'edit', 'embed' ),
1594
								'readonly'    => true,
1595
							),
1596
						),
1597
					);
1598
					break;
1599
1600 View Code Duplication
				case 'author':
1601
					$schema['properties']['author'] = array(
1602
						'description' => __( 'The id for the author of the object.' ),
1603
						'type'        => 'integer',
1604
						'context'     => array( 'view', 'edit', 'embed' ),
1605
					);
1606
					break;
1607
1608 View Code Duplication
				case 'excerpt':
1609
					$schema['properties']['excerpt'] = array(
1610
						'description' => __( 'The excerpt for the object.' ),
1611
						'type'        => 'object',
1612
						'context'     => array( 'view', 'edit', 'embed' ),
1613
						'properties'  => array(
1614
							'raw' => array(
1615
								'description' => __( 'Excerpt for the object, as it exists in the database.' ),
1616
								'type'        => 'string',
1617
								'context'     => array( 'edit' ),
1618
							),
1619
							'rendered' => array(
1620
								'description' => __( 'HTML excerpt for the object, transformed for display.' ),
1621
								'type'        => 'string',
1622
								'context'     => array( 'view', 'edit', 'embed' ),
1623
								'readonly'    => true,
1624
							),
1625
							'protected'       => array(
1626
								'description' => __( 'Whether the excerpt is protected with a password.' ),
1627
								'type'        => 'boolean',
1628
								'context'     => array( 'view', 'edit', 'embed' ),
1629
								'readonly'    => true,
1630
							),
1631
						),
1632
					);
1633
					break;
1634
1635 View Code Duplication
				case 'thumbnail':
1636
					$schema['properties']['featured_media'] = array(
1637
						'description' => __( 'The id of the featured media for the object.' ),
1638
						'type'        => 'integer',
1639
						'context'     => array( 'view', 'edit' ),
1640
					);
1641
					break;
1642
1643
				case 'comments':
1644
					$schema['properties']['comment_status'] = array(
1645
						'description' => __( 'Whether or not comments are open on the object.' ),
1646
						'type'        => 'string',
1647
						'enum'        => array( 'open', 'closed' ),
1648
						'context'     => array( 'view', 'edit' ),
1649
					);
1650
					$schema['properties']['ping_status'] = array(
1651
						'description' => __( 'Whether or not the object can be pinged.' ),
1652
						'type'        => 'string',
1653
						'enum'        => array( 'open', 'closed' ),
1654
						'context'     => array( 'view', 'edit' ),
1655
					);
1656
					break;
1657
1658 View Code Duplication
				case 'page-attributes':
1659
					$schema['properties']['menu_order'] = array(
1660
						'description' => __( 'The order of the object in relation to other object of its type.' ),
1661
						'type'        => 'integer',
1662
						'context'     => array( 'view', 'edit' ),
1663
					);
1664
					break;
1665
1666
				case 'post-formats':
1667
					$schema['properties']['format'] = array(
1668
						'description' => __( 'The format for the object.' ),
1669
						'type'        => 'string',
1670
						'enum'        => array_values( get_post_format_slugs() ),
1671
						'context'     => array( 'view', 'edit' ),
1672
					);
1673
					break;
1674
1675
			}
1676
		}
1677
1678
		if ( 'post' === $this->post_type ) {
1679
			$schema['properties']['sticky'] = array(
1680
				'description' => __( 'Whether or not the object should be treated as sticky.' ),
1681
				'type'        => 'boolean',
1682
				'context'     => array( 'view', 'edit' ),
1683
			);
1684
1685
			$schema['properties']['password'] = array(
1686
				'description' => __( 'A password to protect access to the content and excerpt.' ),
1687
				'type'        => 'string',
1688
				'context'     => array( 'edit' ),
1689
			);
1690
		}
1691
1692
		if ( 'page' === $this->post_type ) {
1693
			$schema['properties']['template'] = array(
1694
				'description' => __( 'The theme file to use to display the object.' ),
1695
				'type'        => 'string',
1696
				'enum'        => array_keys( wp_get_theme()->get_page_templates() ),
1697
				'context'     => array( 'view', 'edit' ),
1698
			);
1699
		}
1700
1701
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1702 View Code Duplication
		foreach ( $taxonomies as $taxonomy ) {
1703
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1704
			$schema['properties'][ $base ] = array(
1705
				'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),
1706
				'type'        => 'array',
1707
				'context'     => array( 'view', 'edit' ),
1708
			);
1709
		}
1710
1711
		return $this->add_additional_fields_schema( $schema );
1712
	}
1713
1714
	/**
1715
	 * Get the query params for collections of attachments.
1716
	 *
1717
	 * @return array
1718
	 */
1719
	public function get_collection_params() {
1720
		$params = parent::get_collection_params();
1721
1722
		$params['context']['default'] = 'view';
1723
1724
		$params['after'] = array(
1725
			'description'        => __( 'Limit response to resources published after a given ISO8601 compliant date.' ),
1726
			'type'               => 'string',
1727
			'format'             => 'date-time',
1728
			'validate_callback'  => 'rest_validate_request_arg',
1729
		);
1730
		if ( post_type_supports( $this->post_type, 'author' ) ) {
1731
			$params['author'] = array(
1732
				'description'         => __( 'Limit result set to posts assigned to specific authors.' ),
1733
				'type'                => 'array',
1734
				'default'             => array(),
1735
				'sanitize_callback'   => 'wp_parse_id_list',
1736
				'validate_callback'   => 'rest_validate_request_arg',
1737
			);
1738
			$params['author_exclude'] = array(
1739
				'description'         => __( 'Ensure result set excludes posts assigned to specific authors.' ),
1740
				'type'                => 'array',
1741
				'default'             => array(),
1742
				'sanitize_callback'   => 'wp_parse_id_list',
1743
				'validate_callback'   => 'rest_validate_request_arg',
1744
			);
1745
		}
1746
		$params['before'] = array(
1747
			'description'        => __( 'Limit response to resources published before a given ISO8601 compliant date.' ),
1748
			'type'               => 'string',
1749
			'format'             => 'date-time',
1750
			'validate_callback'  => 'rest_validate_request_arg',
1751
		);
1752
		$params['exclude'] = array(
1753
			'description'        => __( 'Ensure result set excludes specific ids.' ),
1754
			'type'               => 'array',
1755
			'default'            => array(),
1756
			'sanitize_callback'  => 'wp_parse_id_list',
1757
		);
1758
		$params['include'] = array(
1759
			'description'        => __( 'Limit result set to specific ids.' ),
1760
			'type'               => 'array',
1761
			'default'            => array(),
1762
			'sanitize_callback'  => 'wp_parse_id_list',
1763
		);
1764
		if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
1765
			$params['menu_order'] = array(
1766
				'description'        => __( 'Limit result set to resources with a specific menu_order value.' ),
1767
				'type'               => 'integer',
1768
				'sanitize_callback'  => 'absint',
1769
				'validate_callback'  => 'rest_validate_request_arg',
1770
			);
1771
		}
1772
		$params['offset'] = array(
1773
			'description'        => __( 'Offset the result set by a specific number of items.' ),
1774
			'type'               => 'integer',
1775
			'sanitize_callback'  => 'absint',
1776
			'validate_callback'  => 'rest_validate_request_arg',
1777
		);
1778
		$params['order'] = array(
1779
			'description'        => __( 'Order sort attribute ascending or descending.' ),
1780
			'type'               => 'string',
1781
			'default'            => 'desc',
1782
			'enum'               => array( 'asc', 'desc' ),
1783
			'validate_callback'  => 'rest_validate_request_arg',
1784
		);
1785
		$params['orderby'] = array(
1786
			'description'        => __( 'Sort collection by object attribute.' ),
1787
			'type'               => 'string',
1788
			'default'            => 'date',
1789
			'enum'               => array(
1790
				'date',
1791
				'relevance',
1792
				'id',
1793
				'include',
1794
				'title',
1795
				'slug',
1796
			),
1797
			'validate_callback'  => 'rest_validate_request_arg',
1798
		);
1799
		if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) {
1800
			$params['orderby']['enum'][] = 'menu_order';
1801
		}
1802
1803
		$post_type_obj = get_post_type_object( $this->post_type );
1804
		if ( $post_type_obj->hierarchical || 'attachment' === $this->post_type ) {
1805
			$params['parent'] = array(
1806
				'description'       => __( 'Limit result set to those of particular parent ids.' ),
1807
				'type'              => 'array',
1808
				'sanitize_callback' => 'wp_parse_id_list',
1809
				'default'           => array(),
1810
			);
1811
			$params['parent_exclude'] = array(
1812
				'description'       => __( 'Limit result set to all items except those of a particular parent id.' ),
1813
				'type'              => 'array',
1814
				'sanitize_callback' => 'wp_parse_id_list',
1815
				'default'           => array(),
1816
			);
1817
		}
1818
1819
		$params['slug'] = array(
1820
			'description'       => __( 'Limit result set to posts with a specific slug.' ),
1821
			'type'              => 'string',
1822
			'validate_callback' => 'rest_validate_request_arg',
1823
		);
1824
		$params['status'] = array(
1825
			'default'           => 'publish',
1826
			'description'       => __( 'Limit result set to posts assigned a specific status; can be comma-delimited list of status types.' ),
1827
			'enum'              => array_merge( array_keys( get_post_stati() ), array( 'any' ) ),
1828
			'sanitize_callback' => 'sanitize_key',
1829
			'type'              => 'string',
1830
			'validate_callback' => array( $this, 'validate_user_can_query_private_statuses' ),
1831
		);
1832
		$params['filter'] = array(
1833
			'description'       => __( 'Use WP Query arguments to modify the response; private query vars require appropriate authorization.' ),
1834
		);
1835
1836
		$taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
1837 View Code Duplication
		foreach ( $taxonomies as $taxonomy ) {
1838
			$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
1839
1840
			$params[ $base ] = array(
1841
				'description'       => sprintf( __( 'Limit result set to all items that have the specified term assigned in the %s taxonomy.' ), $base ),
1842
				'type'              => 'array',
1843
				'sanitize_callback' => 'wp_parse_id_list',
1844
				'default'           => array(),
1845
			);
1846
		}
1847
1848
		if ( 'post' === $this->post_type ) {
1849
			$params['sticky'] = array(
1850
				'description'       => __( 'Limit result set to items that are sticky.' ),
1851
				'type'              => 'boolean',
1852
				'sanitize_callback' => 'rest_parse_request_arg',
1853
			);
1854
		}
1855
1856
		return $params;
1857
	}
1858
1859
	/**
1860
	 * Validate whether the user can query private statuses
1861
	 *
1862
	 * @param  mixed $value
1863
	 * @param  WP_REST_Request $request
1864
	 * @param  string $parameter
1865
	 * @return WP_Error|boolean
1866
	 */
1867 View Code Duplication
	public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
1868
		if ( 'publish' === $value ) {
1869
			return true;
1870
		}
1871
		$post_type_obj = get_post_type_object( $this->post_type );
1872
		if ( current_user_can( $post_type_obj->cap->edit_posts ) ) {
1873
			return true;
1874
		}
1875
		return new WP_Error( 'rest_forbidden_status', __( 'Status is forbidden' ), array( 'status' => rest_authorization_required_code() ) );
1876
	}
1877
}
1878