prepare_item_for_response()   F
last analyzed

Complexity

Conditions 10
Paths 512

Size

Total Lines 48
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 25
nc 512
nop 2
dl 0
loc 48
rs 3.646
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Access terms associated with a taxonomy
5
 */
6
class WP_REST_Terms_Controller extends WP_REST_Controller {
7
8
	protected $taxonomy;
9
10
	/**
11
	 * @param string $taxonomy
12
	 */
13 View Code Duplication
	public function __construct( $taxonomy ) {
14
		$this->taxonomy = $taxonomy;
15
		$this->namespace = 'wp/v2';
16
		$tax_obj = get_taxonomy( $taxonomy );
17
		$this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
18
	}
19
20
	/**
21
	 * Register the routes for the objects of the controller.
22
	 */
23
	public function register_routes() {
24
25
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
26
			array(
27
				'methods'             => WP_REST_Server::READABLE,
28
				'callback'            => array( $this, 'get_items' ),
29
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
30
				'args'                => $this->get_collection_params(),
31
			),
32
			array(
33
				'methods'     => WP_REST_Server::CREATABLE,
34
				'callback'    => array( $this, 'create_item' ),
35
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
36
				'args'        => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
37
			),
38
			'schema' => array( $this, 'get_public_item_schema' ),
39
		));
40
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
41
			array(
42
				'methods'             => WP_REST_Server::READABLE,
43
				'callback'            => array( $this, 'get_item' ),
44
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
45
				'args'                => array(
46
					'context'         => $this->get_context_param( array( 'default' => 'view' ) ),
47
				),
48
			),
49
			array(
50
				'methods'    => WP_REST_Server::EDITABLE,
51
				'callback'   => array( $this, 'update_item' ),
52
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
53
				'args'        => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
54
			),
55
			array(
56
				'methods'    => WP_REST_Server::DELETABLE,
57
				'callback'   => array( $this, 'delete_item' ),
58
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
59
				'args'       => array(
60
					'force'    => array(
61
						'default'     => false,
62
						'description' => __( 'Required to be true, as resource does not support trashing.' ),
63
					),
64
				),
65
			),
66
			'schema' => array( $this, 'get_public_item_schema' ),
67
		) );
68
	}
69
70
	/**
71
	 * Check if a given request has access to read the terms.
72
	 *
73
	 * @param  WP_REST_Request $request Full details about the request.
74
	 * @return WP_Error|boolean
75
	 */
76 View Code Duplication
	public function get_items_permissions_check( $request ) {
77
		$tax_obj = get_taxonomy( $this->taxonomy );
78
		if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
79
			return false;
80
		}
81
		if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
82
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
83
		}
84
		return true;
85
	}
86
87
	/**
88
	 * Get terms associated with a taxonomy
89
	 *
90
	 * @param WP_REST_Request $request Full details about the request
91
	 * @return WP_REST_Response|WP_Error
92
	 */
93
	public function get_items( $request ) {
94
		$prepared_args = array(
95
			'exclude'    => $request['exclude'],
96
			'include'    => $request['include'],
97
			'order'      => $request['order'],
98
			'orderby'    => $request['orderby'],
99
			'post'       => $request['post'],
100
			'hide_empty' => $request['hide_empty'],
101
			'number'     => $request['per_page'],
102
			'search'     => $request['search'],
103
			'slug'       => $request['slug'],
104
		);
105
106 View Code Duplication
		if ( ! empty( $request['offset'] ) ) {
107
			$prepared_args['offset'] = $request['offset'];
108
		} else {
109
			$prepared_args['offset']  = ( $request['page'] - 1 ) * $prepared_args['number'];
110
		}
111
112
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
113
114
		if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
115
			if ( 0 === $request['parent'] ) {
116
				// Only query top-level terms.
117
				$prepared_args['parent'] = 0;
118
			} else {
119
				if ( $request['parent'] ) {
120
					$prepared_args['parent'] = $request['parent'];
121
				}
122
			}
123
		}
124
125
		/**
126
		 * Filter the query arguments, before passing them to `get_terms()`.
127
		 *
128
		 * Enables adding extra arguments or setting defaults for a terms
129
		 * collection request.
130
		 *
131
		 * @see https://developer.wordpress.org/reference/functions/get_terms/
132
		 *
133
		 * @param array           $prepared_args Array of arguments to be
134
		 *                                       passed to get_terms.
135
		 * @param WP_REST_Request $request       The current request.
136
		 */
137
		$prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );
138
139
		// Can we use the cached call?
140
		$use_cache = ! empty( $prepared_args['post'] )
0 ignored issues
show
Unused Code introduced by
$use_cache is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
141
			&& empty( $prepared_args['include'] )
142
			&& empty( $prepared_args['exclude'] )
143
			&& empty( $prepared_args['hide_empty'] )
144
			&& empty( $prepared_args['search'] )
145
			&& empty( $prepared_args['slug'] );
146
147
		if ( ! empty( $prepared_args['post'] )  ) {
148
			$query_result = $this->get_terms_for_post( $prepared_args );
149
			$total_terms = $this->total_terms;
0 ignored issues
show
Bug introduced by
The property total_terms does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
150
		} else {
151
			$query_result = get_terms( $this->taxonomy, $prepared_args );
152
153
			$count_args = $prepared_args;
154
			unset( $count_args['number'] );
155
			unset( $count_args['offset'] );
156
			$total_terms = wp_count_terms( $this->taxonomy, $count_args );
157
158
			// Ensure we don't return results when offset is out of bounds
159
			// see https://core.trac.wordpress.org/ticket/35935
160
			if ( $prepared_args['offset'] >= $total_terms ) {
161
				$query_result = array();
162
			}
163
164
			// wp_count_terms can return a falsy value when the term has no children
165
			if ( ! $total_terms ) {
166
				$total_terms = 0;
167
			}
168
		}
169
		$response = array();
170
		foreach ( $query_result as $term ) {
171
			$data = $this->prepare_item_for_response( $term, $request );
172
			$response[] = $this->prepare_response_for_collection( $data );
173
		}
174
175
		$response = rest_ensure_response( $response );
176
177
		// Store pagation values for headers then unset for count query.
178
		$per_page = (int) $prepared_args['number'];
179
		$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
180
181
		$response->header( 'X-WP-Total', (int) $total_terms );
182
		$max_pages = ceil( $total_terms / $per_page );
183
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
184
185
		$base = add_query_arg( $request->get_query_params(), rest_url( $this->namespace . '/' . $this->rest_base ) );
186 View Code Duplication
		if ( $page > 1 ) {
187
			$prev_page = $page - 1;
188
			if ( $prev_page > $max_pages ) {
189
				$prev_page = $max_pages;
190
			}
191
			$prev_link = add_query_arg( 'page', $prev_page, $base );
192
			$response->link_header( 'prev', $prev_link );
193
		}
194 View Code Duplication
		if ( $max_pages > $page ) {
195
			$next_page = $page + 1;
196
			$next_link = add_query_arg( 'page', $next_page, $base );
197
			$response->link_header( 'next', $next_link );
198
		}
199
200
		return $response;
201
	}
202
203
	/**
204
	 * Get the terms attached to a post.
205
	 *
206
	 * This is an alternative to `get_terms()` that uses `get_the_terms()`
207
	 * instead, which hits the object cache. There are a few things not
208
	 * supported, notably `include`, `exclude`. In `self::get_items()` these
209
	 * are instead treated as a full query.
210
	 *
211
	 * @param array $prepared_args Arguments for `get_terms()`
212
	 * @return array List of term objects. (Total count in `$this->total_terms`)
213
	 */
214
	protected function get_terms_for_post( $prepared_args ) {
215
		$query_result = get_the_terms( $prepared_args['post'], $this->taxonomy );
216
		if ( empty( $query_result ) ) {
217
			$this->total_terms = 0;
218
			return array();
219
		}
220
221
		// get_items() verifies that we don't have `include` set, and default
222
		// ordering is by `name`
223
		if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ) ) ) {
224
			switch ( $prepared_args['orderby'] ) {
225
				case 'id':
226
					$this->sort_column = 'term_id';
0 ignored issues
show
Bug introduced by
The property sort_column does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
227
					break;
228
229
				case 'slug':
230
				case 'term_group':
231
				case 'description':
232
				case 'count':
233
					$this->sort_column = $prepared_args['orderby'];
234
					break;
235
			}
236
			usort( $query_result, array( $this, 'compare_terms' ) );
237
		}
238
		if ( strtolower( $prepared_args['order'] ) !== 'asc' ) {
239
			$query_result = array_reverse( $query_result );
240
		}
241
242
		// Pagination
243
		$this->total_terms = count( $query_result );
244
		$query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] );
245
246
		return $query_result;
247
	}
248
249
	/**
250
	 * Comparison function for sorting terms by a column.
251
	 *
252
	 * Uses `$this->sort_column` to determine field to sort by.
253
	 *
254
	 * @param stdClass $left Term object.
255
	 * @param stdClass $right Term object.
256
	 * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left.
257
	 */
258
	protected function compare_terms( $left, $right ) {
259
		$col = $this->sort_column;
260
		$left_val = $left->$col;
261
		$right_val = $right->$col;
262
263
		if ( is_int( $left_val ) && is_int( $right_val ) ) {
264
			return $left_val - $right_val;
265
		}
266
267
		return strcmp( $left_val, $right_val );
268
	}
269
270
	/**
271
	 * Check if a given request has access to read a term.
272
	 *
273
	 * @param  WP_REST_Request $request Full details about the request.
274
	 * @return WP_Error|boolean
275
	 */
276 View Code Duplication
	public function get_item_permissions_check( $request ) {
277
		$tax_obj = get_taxonomy( $this->taxonomy );
278
		if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
279
			return false;
280
		}
281
		if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
282
			return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
283
		}
284
		return true;
285
	}
286
287
	/**
288
	 * Get a single term from a taxonomy
289
	 *
290
	 * @param WP_REST_Request $request Full details about the request
291
	 * @return WP_REST_Request|WP_Error
292
	 */
293 View Code Duplication
	public function get_item( $request ) {
294
295
		$term = get_term( (int) $request['id'], $this->taxonomy );
296
		if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
297
			return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
298
		}
299
		if ( is_wp_error( $term ) ) {
300
			return $term;
301
		}
302
303
		$response = $this->prepare_item_for_response( $term, $request );
304
305
		return rest_ensure_response( $response );
306
	}
307
308
	/**
309
	 * Check if a given request has access to create a term
310
	 *
311
	 * @param  WP_REST_Request $request Full details about the request.
312
	 * @return WP_Error|boolean
313
	 */
314
	public function create_item_permissions_check( $request ) {
315
316
		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
317
			return false;
318
		}
319
320
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
321
		if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
322
			return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new resource.' ), array( 'status' => rest_authorization_required_code() ) );
323
		}
324
325
		return true;
326
	}
327
328
	/**
329
	 * Create a single term for a taxonomy
330
	 *
331
	 * @param WP_REST_Request $request Full details about the request
332
	 * @return WP_REST_Request|WP_Error
333
	 */
334
	public function create_item( $request ) {
335 View Code Duplication
		if ( isset( $request['parent'] ) ) {
336
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
337
				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
338
			}
339
340
			$parent = get_term( (int) $request['parent'], $this->taxonomy );
341
342
			if ( ! $parent ) {
343
				return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
344
			}
345
		}
346
347
		$prepared_term = $this->prepare_item_for_database( $request );
348
349
		$term = wp_insert_term( $prepared_term->name, $this->taxonomy, $prepared_term );
350
		if ( is_wp_error( $term ) ) {
351
352
			// If we're going to inform the client that the term exists, give them the identifier
353
			// they can actually use.
354
355
			if ( ( $term_id = $term->get_error_data( 'term_exists' ) ) ) {
356
				$existing_term = get_term( $term_id, $this->taxonomy );
357
				$term->add_data( $existing_term->term_id, 'term_exists' );
358
			}
359
360
			return $term;
361
		}
362
363
		$term = get_term( $term['term_id'], $this->taxonomy );
364
365
		/**
366
		 * Fires after a single term is created or updated via the REST API.
367
		 *
368
		 * @param WP_Term         $term     Inserted Term object.
369
		 * @param WP_REST_Request $request   Request object.
370
		 * @param boolean         $creating  True when creating term, false when updating.
371
		 */
372
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
373
374
		$fields_update = $this->update_additional_fields_for_object( $term, $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->update_additional...bject($term, $request); of type boolean|WP_Error adds the type boolean to the return on line 376 which is incompatible with the return type documented by WP_REST_Terms_Controller::create_item of type WP_REST_Request|WP_Error.
Loading history...
375
		if ( is_wp_error( $fields_update ) ) {
376
			return $fields_update;
377
		}
378
379
		$request->set_param( 'context', 'view' );
380
		$response = $this->prepare_item_for_response( $term, $request );
381
		$response = rest_ensure_response( $response );
382
		$response->set_status( 201 );
383
		$response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
384
		return $response;
385
	}
386
387
	/**
388
	 * Check if a given request has access to update a term
389
	 *
390
	 * @param  WP_REST_Request $request Full details about the request.
391
	 * @return WP_Error|boolean
392
	 */
393 View Code Duplication
	public function update_item_permissions_check( $request ) {
394
395
		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
396
			return false;
397
		}
398
399
		$term = get_term( (int) $request['id'], $this->taxonomy );
400
		if ( ! $term ) {
401
			return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
402
		}
403
404
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
405
		if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
406
			return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update resource.' ), array( 'status' => rest_authorization_required_code() ) );
407
		}
408
409
		return true;
410
	}
411
412
	/**
413
	 * Update a single term from a taxonomy
414
	 *
415
	 * @param WP_REST_Request $request Full details about the request
416
	 * @return WP_REST_Request|WP_Error
417
	 */
418
	public function update_item( $request ) {
419 View Code Duplication
		if ( isset( $request['parent'] ) ) {
420
			if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
421
				return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
422
			}
423
424
			$parent = get_term( (int) $request['parent'], $this->taxonomy );
425
426
			if ( ! $parent ) {
427
				return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
428
			}
429
		}
430
431
		$prepared_term = $this->prepare_item_for_database( $request );
432
433
		$term = get_term( (int) $request['id'], $this->taxonomy );
434
435
		// Only update the term if we haz something to update.
436
		if ( ! empty( $prepared_term ) ) {
437
			$update = wp_update_term( $term->term_id, $term->taxonomy, (array) $prepared_term );
438
			if ( is_wp_error( $update ) ) {
439
				return $update;
440
			}
441
		}
442
443
		$term = get_term( (int) $request['id'], $this->taxonomy );
444
445
		/* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */
446
		do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
447
448
		$fields_update = $this->update_additional_fields_for_object( $term, $request );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->update_additional...bject($term, $request); of type boolean|WP_Error adds the type boolean to the return on line 450 which is incompatible with the return type documented by WP_REST_Terms_Controller::update_item of type WP_REST_Request|WP_Error.
Loading history...
449
		if ( is_wp_error( $fields_update ) ) {
450
			return $fields_update;
451
		}
452
453
		$request->set_param( 'context', 'view' );
454
		$response = $this->prepare_item_for_response( $term, $request );
455
		return rest_ensure_response( $response );
456
	}
457
458
	/**
459
	 * Check if a given request has access to delete a term
460
	 *
461
	 * @param  WP_REST_Request $request Full details about the request.
462
	 * @return WP_Error|boolean
463
	 */
464 View Code Duplication
	public function delete_item_permissions_check( $request ) {
465
		if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
466
			return false;
467
		}
468
		$term = get_term( (int) $request['id'], $this->taxonomy );
469
		if ( ! $term ) {
470
			return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
471
		}
472
		$taxonomy_obj = get_taxonomy( $this->taxonomy );
473
		if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
474
			return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete resource.' ), array( 'status' => rest_authorization_required_code() ) );
475
		}
476
		return true;
477
	}
478
479
	/**
480
	 * Delete a single term from a taxonomy
481
	 *
482
	 * @param WP_REST_Request $request Full details about the request
483
	 * @return WP_REST_Response|WP_Error
484
	 */
485
	public function delete_item( $request ) {
486
487
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
488
489
		// We don't support trashing for this type, error out
490
		if ( ! $force ) {
491
			return new WP_Error( 'rest_trash_not_supported', __( 'Resource does not support trashing.' ), array( 'status' => 501 ) );
492
		}
493
494
		$term = get_term( (int) $request['id'], $this->taxonomy );
495
		$request->set_param( 'context', 'view' );
496
		$response = $this->prepare_item_for_response( $term, $request );
497
498
		$retval = wp_delete_term( $term->term_id, $term->taxonomy );
499
		if ( ! $retval ) {
500
			return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) );
501
		}
502
503
		/**
504
		 * Fires after a single term is deleted via the REST API.
505
		 *
506
		 * @param WP_Term          $term     The deleted term.
507
		 * @param WP_REST_Response $response The response data.
508
		 * @param WP_REST_Request  $request  The request sent to the API.
509
		 */
510
		do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
511
512
		return $response;
513
	}
514
515
	/**
516
	 * Prepare a single term for create or update
517
	 *
518
	 * @param WP_REST_Request $request Request object.
519
	 * @return object $prepared_term Term object.
520
	 */
521
	public function prepare_item_for_database( $request ) {
522
		$prepared_term = new stdClass;
523
524
		$schema = $this->get_item_schema();
525 View Code Duplication
		if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
526
			$prepared_term->name = $request['name'];
527
		}
528
529 View Code Duplication
		if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
530
			$prepared_term->slug = $request['slug'];
531
		}
532
533
		if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) {
534
			$prepared_term->taxonomy = $request['taxonomy'];
535
		}
536
537
		if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
538
			$prepared_term->description = $request['description'];
539
		}
540
541
		if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) {
542
			$parent_term_id = 0;
543
			$parent_term = get_term( (int) $request['parent'], $this->taxonomy );
544
545
			if ( $parent_term ) {
546
				$parent_term_id = $parent_term->term_id;
547
			}
548
549
			$prepared_term->parent = $parent_term_id;
550
		}
551
552
		/**
553
		 * Filter term data before inserting term via the REST API.
554
		 *
555
		 * @param object          $prepared_term Term object.
556
		 * @param WP_REST_Request $request       Request object.
557
		 */
558
		return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request );
559
	}
560
561
	/**
562
	 * Prepare a single term output for response
563
	 *
564
	 * @param obj $item Term object
565
	 * @param WP_REST_Request $request
566
	 * @return WP_REST_Response $response
567
	 */
568
	public function prepare_item_for_response( $item, $request ) {
569
570
		$schema = $this->get_item_schema();
571
		$data = array();
572
		if ( ! empty( $schema['properties']['id'] ) ) {
573
			$data['id'] = (int) $item->term_id;
574
		}
575
		if ( ! empty( $schema['properties']['count'] ) ) {
576
			$data['count'] = (int) $item->count;
577
		}
578
		if ( ! empty( $schema['properties']['description'] ) ) {
579
			$data['description'] = $item->description;
580
		}
581
		if ( ! empty( $schema['properties']['link'] ) ) {
582
			$data['link'] = get_term_link( $item );
583
		}
584
		if ( ! empty( $schema['properties']['name'] ) ) {
585
			$data['name'] = $item->name;
586
		}
587
		if ( ! empty( $schema['properties']['slug'] ) ) {
588
			$data['slug'] = $item->slug;
589
		}
590
		if ( ! empty( $schema['properties']['taxonomy'] ) ) {
591
			$data['taxonomy'] = $item->taxonomy;
592
		}
593
		if ( ! empty( $schema['properties']['parent'] ) ) {
594
			$data['parent'] = (int) $item->parent;
595
		}
596
597
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
598
		$data = $this->add_additional_fields_to_object( $data, $request );
599
		$data = $this->filter_response_by_context( $data, $context );
600
601
		$response = rest_ensure_response( $data );
602
603
		$response->add_links( $this->prepare_links( $item ) );
604
605
		/**
606
		 * Filter a term item returned from the API.
607
		 *
608
		 * Allows modification of the term data right before it is returned.
609
		 *
610
		 * @param WP_REST_Response  $response  The response object.
611
		 * @param object            $item      The original term object.
612
		 * @param WP_REST_Request   $request   Request used to generate the response.
613
		 */
614
		return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
615
	}
616
617
	/**
618
	 * Prepare links for the request.
619
	 *
620
	 * @param object $term Term object.
621
	 * @return array Links for the given term.
622
	 */
623
	protected function prepare_links( $term ) {
624
		$base = $this->namespace . '/' . $this->rest_base;
625
		$links = array(
626
			'self'       => array(
627
				'href'       => rest_url( trailingslashit( $base ) . $term->term_id ),
628
			),
629
			'collection' => array(
630
				'href'       => rest_url( $base ),
631
			),
632
			'about'      => array(
633
				'href'       => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
634
			),
635
		);
636
637
		if ( $term->parent ) {
638
			$parent_term = get_term( (int) $term->parent, $term->taxonomy );
639
			if ( $parent_term ) {
640
				$links['up'] = array(
641
					'href'       => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
642
					'embeddable' => true,
643
				);
644
			}
645
		}
646
647
		$taxonomy_obj = get_taxonomy( $term->taxonomy );
648
		if ( empty( $taxonomy_obj->object_type ) ) {
649
			return $links;
650
		}
651
652
		$post_type_links = array();
653
		foreach ( $taxonomy_obj->object_type as $type ) {
654
			$post_type_object = get_post_type_object( $type );
655
			if ( empty( $post_type_object->show_in_rest ) ) {
656
				continue;
657
			}
658
			$rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
659
			$post_type_links[] = array(
660
				'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ),
661
			);
662
		}
663
		if ( ! empty( $post_type_links ) ) {
664
			$links['https://api.w.org/post_type'] = $post_type_links;
665
		}
666
667
		return $links;
668
	}
669
670
	/**
671
	 * Get the Term's schema, conforming to JSON Schema
672
	 *
673
	 * @return array
674
	 */
675
	public function get_item_schema() {
676
		$schema = array(
677
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
678
			'title'                => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
679
			'type'                 => 'object',
680
			'properties'           => array(
681
				'id'               => array(
682
					'description'  => __( 'Unique identifier for the resource.' ),
683
					'type'         => 'integer',
684
					'context'      => array( 'view', 'embed', 'edit' ),
685
					'readonly'     => true,
686
				),
687
				'count'            => array(
688
					'description'  => __( 'Number of published posts for the resource.' ),
689
					'type'         => 'integer',
690
					'context'      => array( 'view', 'edit' ),
691
					'readonly'     => true,
692
				),
693
				'description'      => array(
694
					'description'  => __( 'HTML description of the resource.' ),
695
					'type'         => 'string',
696
					'context'      => array( 'view', 'edit' ),
697
					'arg_options'  => array(
698
						'sanitize_callback' => 'wp_filter_post_kses',
699
					),
700
				),
701
				'link'             => array(
702
					'description'  => __( 'URL to the resource.' ),
703
					'type'         => 'string',
704
					'format'       => 'uri',
705
					'context'      => array( 'view', 'embed', 'edit' ),
706
					'readonly'     => true,
707
				),
708
				'name'             => array(
709
					'description'  => __( 'HTML title for the resource.' ),
710
					'type'         => 'string',
711
					'context'      => array( 'view', 'embed', 'edit' ),
712
					'arg_options'  => array(
713
						'sanitize_callback' => 'sanitize_text_field',
714
					),
715
					'required'     => true,
716
				),
717
				'slug'             => array(
718
					'description'  => __( 'An alphanumeric identifier for the resource unique to its type.' ),
719
					'type'         => 'string',
720
					'context'      => array( 'view', 'embed', 'edit' ),
721
					'arg_options'  => array(
722
						'sanitize_callback' => array( $this, 'sanitize_slug' ),
723
					),
724
				),
725
				'taxonomy'         => array(
726
					'description'  => __( 'Type attribution for the resource.' ),
727
					'type'         => 'string',
728
					'enum'         => array_keys( get_taxonomies() ),
729
					'context'      => array( 'view', 'embed', 'edit' ),
730
					'readonly'     => true,
731
				),
732
			),
733
		);
734
		$taxonomy = get_taxonomy( $this->taxonomy );
735 View Code Duplication
		if ( $taxonomy->hierarchical ) {
736
			$schema['properties']['parent'] = array(
737
				'description'  => __( 'The id for the parent of the resource.' ),
738
				'type'         => 'integer',
739
				'context'      => array( 'view', 'edit' ),
740
			);
741
		}
742
		return $this->add_additional_fields_schema( $schema );
743
	}
744
745
	/**
746
	 * Get the query params for collections
747
	 *
748
	 * @return array
749
	 */
750
	public function get_collection_params() {
751
		$query_params = parent::get_collection_params();
752
		$taxonomy = get_taxonomy( $this->taxonomy );
753
754
		$query_params['context']['default'] = 'view';
755
756
		$query_params['exclude'] = array(
757
			'description'        => __( 'Ensure result set excludes specific ids.' ),
758
			'type'               => 'array',
759
			'default'            => array(),
760
			'sanitize_callback'  => 'wp_parse_id_list',
761
		);
762
		$query_params['include'] = array(
763
			'description'        => __( 'Limit result set to specific ids.' ),
764
			'type'               => 'array',
765
			'default'            => array(),
766
			'sanitize_callback'  => 'wp_parse_id_list',
767
		);
768 View Code Duplication
		if ( ! $taxonomy->hierarchical ) {
769
			$query_params['offset'] = array(
770
				'description'        => __( 'Offset the result set by a specific number of items.' ),
771
				'type'               => 'integer',
772
				'sanitize_callback'  => 'absint',
773
				'validate_callback'  => 'rest_validate_request_arg',
774
			);
775
		}
776
		$query_params['order']      = array(
777
			'description'           => __( 'Order sort attribute ascending or descending.' ),
778
			'type'                  => 'string',
779
			'sanitize_callback'     => 'sanitize_key',
780
			'default'               => 'asc',
781
			'enum'                  => array(
782
				'asc',
783
				'desc',
784
			),
785
			'validate_callback'     => 'rest_validate_request_arg',
786
		);
787
		$query_params['orderby']    = array(
788
			'description'           => __( 'Sort collection by resource attribute.' ),
789
			'type'                  => 'string',
790
			'sanitize_callback'     => 'sanitize_key',
791
			'default'               => 'name',
792
			'enum'                  => array(
793
				'id',
794
				'include',
795
				'name',
796
				'slug',
797
				'term_group',
798
				'description',
799
				'count',
800
			),
801
			'validate_callback'     => 'rest_validate_request_arg',
802
		);
803
		$query_params['hide_empty'] = array(
804
			'description'           => __( 'Whether to hide resources not assigned to any posts.' ),
805
			'type'                  => 'boolean',
806
			'default'               => false,
807
			'validate_callback'     => 'rest_validate_request_arg',
808
		);
809 View Code Duplication
		if ( $taxonomy->hierarchical ) {
810
			$query_params['parent'] = array(
811
				'description'        => __( 'Limit result set to resources assigned to a specific parent.' ),
812
				'type'               => 'integer',
813
				'sanitize_callback'  => 'absint',
814
				'validate_callback'  => 'rest_validate_request_arg',
815
			);
816
		}
817
		$query_params['post'] = array(
818
			'description'           => __( 'Limit result set to resources assigned to a specific post.' ),
819
			'type'                  => 'integer',
820
			'default'               => null,
821
			'validate_callback'     => 'rest_validate_request_arg',
822
		);
823
		$query_params['slug']    = array(
824
			'description'        => __( 'Limit result set to resources with a specific slug.' ),
825
			'type'               => 'string',
826
			'validate_callback'  => 'rest_validate_request_arg',
827
		);
828
		return $query_params;
829
	}
830
831
	/**
832
	 * Check that the taxonomy is valid
833
	 *
834
	 * @param string
835
	 * @return WP_Error|boolean
836
	 */
837
	protected function check_is_taxonomy_allowed( $taxonomy ) {
838
		$taxonomy_obj = get_taxonomy( $taxonomy );
839
		if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) {
840
			return true;
841
		}
842
		return false;
843
	}
844
}
845