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
|
|
|
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
|
|
View Code Duplication |
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'] ) |
|
|
|
|
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; |
|
|
|
|
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'; |
|
|
|
|
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
|
|
|
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
|
|
|
$this->update_additional_fields_for_object( $term, $request ); |
375
|
|
|
$request->set_param( 'context', 'view' ); |
376
|
|
|
$response = $this->prepare_item_for_response( $term, $request ); |
377
|
|
|
$response = rest_ensure_response( $response ); |
378
|
|
|
$response->set_status( 201 ); |
379
|
|
|
$response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) ); |
380
|
|
|
return $response; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Check if a given request has access to update a term |
385
|
|
|
* |
386
|
|
|
* @param WP_REST_Request $request Full details about the request. |
387
|
|
|
* @return WP_Error|boolean |
388
|
|
|
*/ |
389
|
|
View Code Duplication |
public function update_item_permissions_check( $request ) { |
390
|
|
|
|
391
|
|
|
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { |
392
|
|
|
return false; |
|
|
|
|
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
$term = get_term( (int) $request['id'], $this->taxonomy ); |
396
|
|
|
if ( ! $term ) { |
397
|
|
|
return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) ); |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
$taxonomy_obj = get_taxonomy( $this->taxonomy ); |
401
|
|
|
if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) { |
402
|
|
|
return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update resource.' ), array( 'status' => rest_authorization_required_code() ) ); |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
return true; |
|
|
|
|
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Update a single term from a taxonomy |
410
|
|
|
* |
411
|
|
|
* @param WP_REST_Request $request Full details about the request |
412
|
|
|
* @return WP_REST_Request|WP_Error |
413
|
|
|
*/ |
414
|
|
|
public function update_item( $request ) { |
415
|
|
View Code Duplication |
if ( isset( $request['parent'] ) ) { |
416
|
|
|
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { |
417
|
|
|
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
$parent = get_term( (int) $request['parent'], $this->taxonomy ); |
421
|
|
|
|
422
|
|
|
if ( ! $parent ) { |
423
|
|
|
return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) ); |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
$prepared_term = $this->prepare_item_for_database( $request ); |
428
|
|
|
|
429
|
|
|
$term = get_term( (int) $request['id'], $this->taxonomy ); |
430
|
|
|
|
431
|
|
|
// Only update the term if we haz something to update. |
432
|
|
|
if ( ! empty( $prepared_term ) ) { |
433
|
|
|
$update = wp_update_term( $term->term_id, $term->taxonomy, (array) $prepared_term ); |
434
|
|
|
if ( is_wp_error( $update ) ) { |
435
|
|
|
return $update; |
436
|
|
|
} |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
$term = get_term( (int) $request['id'], $this->taxonomy ); |
440
|
|
|
|
441
|
|
|
/* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */ |
442
|
|
|
do_action( "rest_insert_{$this->taxonomy}", $term, $request, false ); |
443
|
|
|
|
444
|
|
|
$this->update_additional_fields_for_object( $term, $request ); |
445
|
|
|
$request->set_param( 'context', 'view' ); |
446
|
|
|
$response = $this->prepare_item_for_response( $term, $request ); |
447
|
|
|
return rest_ensure_response( $response ); |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* Check if a given request has access to delete a term |
452
|
|
|
* |
453
|
|
|
* @param WP_REST_Request $request Full details about the request. |
454
|
|
|
* @return WP_Error|boolean |
455
|
|
|
*/ |
456
|
|
View Code Duplication |
public function delete_item_permissions_check( $request ) { |
457
|
|
|
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { |
458
|
|
|
return false; |
|
|
|
|
459
|
|
|
} |
460
|
|
|
$term = get_term( (int) $request['id'], $this->taxonomy ); |
461
|
|
|
if ( ! $term ) { |
462
|
|
|
return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) ); |
463
|
|
|
} |
464
|
|
|
$taxonomy_obj = get_taxonomy( $this->taxonomy ); |
465
|
|
|
if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) { |
466
|
|
|
return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete resource.' ), array( 'status' => rest_authorization_required_code() ) ); |
467
|
|
|
} |
468
|
|
|
return true; |
|
|
|
|
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
/** |
472
|
|
|
* Delete a single term from a taxonomy |
473
|
|
|
* |
474
|
|
|
* @param WP_REST_Request $request Full details about the request |
475
|
|
|
* @return WP_REST_Response|WP_Error |
476
|
|
|
*/ |
477
|
|
|
public function delete_item( $request ) { |
478
|
|
|
|
479
|
|
|
$force = isset( $request['force'] ) ? (bool) $request['force'] : false; |
480
|
|
|
|
481
|
|
|
// We don't support trashing for this type, error out |
482
|
|
|
if ( ! $force ) { |
483
|
|
|
return new WP_Error( 'rest_trash_not_supported', __( 'Resource does not support trashing.' ), array( 'status' => 501 ) ); |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
$term = get_term( (int) $request['id'], $this->taxonomy ); |
487
|
|
|
$request->set_param( 'context', 'view' ); |
488
|
|
|
$response = $this->prepare_item_for_response( $term, $request ); |
489
|
|
|
|
490
|
|
|
$retval = wp_delete_term( $term->term_id, $term->taxonomy ); |
491
|
|
|
if ( ! $retval ) { |
492
|
|
|
return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) ); |
493
|
|
|
} |
494
|
|
|
|
495
|
|
|
/** |
496
|
|
|
* Fires after a single term is deleted via the REST API. |
497
|
|
|
* |
498
|
|
|
* @param WP_Term $term The deleted term. |
499
|
|
|
* @param WP_REST_Response $response The response data. |
500
|
|
|
* @param WP_REST_Request $request The request sent to the API. |
501
|
|
|
*/ |
502
|
|
|
do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request ); |
503
|
|
|
|
504
|
|
|
return $response; |
|
|
|
|
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Prepare a single term for create or update |
509
|
|
|
* |
510
|
|
|
* @param WP_REST_Request $request Request object. |
511
|
|
|
* @return object $prepared_term Term object. |
512
|
|
|
*/ |
513
|
|
|
public function prepare_item_for_database( $request ) { |
514
|
|
|
$prepared_term = new stdClass; |
515
|
|
|
|
516
|
|
|
$schema = $this->get_item_schema(); |
517
|
|
View Code Duplication |
if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { |
518
|
|
|
$prepared_term->name = $request['name']; |
519
|
|
|
} |
520
|
|
|
|
521
|
|
View Code Duplication |
if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) { |
522
|
|
|
$prepared_term->slug = $request['slug']; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) { |
526
|
|
|
$prepared_term->taxonomy = $request['taxonomy']; |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) { |
530
|
|
|
$prepared_term->description = $request['description']; |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) { |
534
|
|
|
$parent_term_id = 0; |
535
|
|
|
$parent_term = get_term( (int) $request['parent'], $this->taxonomy ); |
536
|
|
|
|
537
|
|
|
if ( $parent_term ) { |
538
|
|
|
$parent_term_id = $parent_term->term_id; |
539
|
|
|
} |
540
|
|
|
|
541
|
|
|
$prepared_term->parent = $parent_term_id; |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
/** |
545
|
|
|
* Filter term data before inserting term via the REST API. |
546
|
|
|
* |
547
|
|
|
* @param object $prepared_term Term object. |
548
|
|
|
* @param WP_REST_Request $request Request object. |
549
|
|
|
*/ |
550
|
|
|
return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request ); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* Prepare a single term output for response |
555
|
|
|
* |
556
|
|
|
* @param obj $item Term object |
557
|
|
|
* @param WP_REST_Request $request |
558
|
|
|
* @return WP_REST_Response $response |
559
|
|
|
*/ |
560
|
|
|
public function prepare_item_for_response( $item, $request ) { |
561
|
|
|
|
562
|
|
|
$schema = $this->get_item_schema(); |
563
|
|
|
$data = array(); |
564
|
|
|
if ( ! empty( $schema['properties']['id'] ) ) { |
565
|
|
|
$data['id'] = (int) $item->term_id; |
566
|
|
|
} |
567
|
|
|
if ( ! empty( $schema['properties']['count'] ) ) { |
568
|
|
|
$data['count'] = (int) $item->count; |
569
|
|
|
} |
570
|
|
|
if ( ! empty( $schema['properties']['description'] ) ) { |
571
|
|
|
$data['description'] = $item->description; |
572
|
|
|
} |
573
|
|
|
if ( ! empty( $schema['properties']['link'] ) ) { |
574
|
|
|
$data['link'] = get_term_link( $item ); |
575
|
|
|
} |
576
|
|
|
if ( ! empty( $schema['properties']['name'] ) ) { |
577
|
|
|
$data['name'] = $item->name; |
578
|
|
|
} |
579
|
|
|
if ( ! empty( $schema['properties']['slug'] ) ) { |
580
|
|
|
$data['slug'] = $item->slug; |
581
|
|
|
} |
582
|
|
|
if ( ! empty( $schema['properties']['taxonomy'] ) ) { |
583
|
|
|
$data['taxonomy'] = $item->taxonomy; |
584
|
|
|
} |
585
|
|
|
if ( ! empty( $schema['properties']['parent'] ) ) { |
586
|
|
|
$data['parent'] = (int) $item->parent; |
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view'; |
590
|
|
|
$data = $this->add_additional_fields_to_object( $data, $request ); |
591
|
|
|
$data = $this->filter_response_by_context( $data, $context ); |
592
|
|
|
|
593
|
|
|
$response = rest_ensure_response( $data ); |
594
|
|
|
|
595
|
|
|
$response->add_links( $this->prepare_links( $item ) ); |
596
|
|
|
|
597
|
|
|
/** |
598
|
|
|
* Filter a term item returned from the API. |
599
|
|
|
* |
600
|
|
|
* Allows modification of the term data right before it is returned. |
601
|
|
|
* |
602
|
|
|
* @param WP_REST_Response $response The response object. |
603
|
|
|
* @param object $item The original term object. |
604
|
|
|
* @param WP_REST_Request $request Request used to generate the response. |
605
|
|
|
*/ |
606
|
|
|
return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request ); |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Prepare links for the request. |
611
|
|
|
* |
612
|
|
|
* @param object $term Term object. |
613
|
|
|
* @return array Links for the given term. |
614
|
|
|
*/ |
615
|
|
|
protected function prepare_links( $term ) { |
616
|
|
|
$base = '/' . $this->namespace . '/' . $this->rest_base; |
617
|
|
|
$links = array( |
618
|
|
|
'self' => array( |
619
|
|
|
'href' => rest_url( trailingslashit( $base ) . $term->term_id ), |
620
|
|
|
), |
621
|
|
|
'collection' => array( |
622
|
|
|
'href' => rest_url( $base ), |
623
|
|
|
), |
624
|
|
|
'about' => array( |
625
|
|
|
'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ), |
626
|
|
|
), |
627
|
|
|
); |
628
|
|
|
|
629
|
|
|
if ( $term->parent ) { |
630
|
|
|
$parent_term = get_term( (int) $term->parent, $term->taxonomy ); |
631
|
|
|
if ( $parent_term ) { |
632
|
|
|
$links['up'] = array( |
633
|
|
|
'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ), |
634
|
|
|
'embeddable' => true, |
635
|
|
|
); |
636
|
|
|
} |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
$taxonomy_obj = get_taxonomy( $term->taxonomy ); |
640
|
|
|
if ( empty( $taxonomy_obj->object_type ) ) { |
641
|
|
|
return $links; |
642
|
|
|
} |
643
|
|
|
|
644
|
|
|
$post_type_links = array(); |
645
|
|
|
foreach ( $taxonomy_obj->object_type as $type ) { |
646
|
|
|
$post_type_object = get_post_type_object( $type ); |
647
|
|
|
if ( empty( $post_type_object->show_in_rest ) ) { |
648
|
|
|
continue; |
649
|
|
|
} |
650
|
|
|
$rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; |
651
|
|
|
$post_type_links[] = array( |
652
|
|
|
'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ), |
653
|
|
|
); |
654
|
|
|
} |
655
|
|
|
if ( ! empty( $post_type_links ) ) { |
656
|
|
|
$links['https://api.w.org/post_type'] = $post_type_links; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
return $links; |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/** |
663
|
|
|
* Get the Term's schema, conforming to JSON Schema |
664
|
|
|
* |
665
|
|
|
* @return array |
666
|
|
|
*/ |
667
|
|
|
public function get_item_schema() { |
668
|
|
|
$schema = array( |
669
|
|
|
'$schema' => 'http://json-schema.org/draft-04/schema#', |
670
|
|
|
'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy, |
671
|
|
|
'type' => 'object', |
672
|
|
|
'properties' => array( |
673
|
|
|
'id' => array( |
674
|
|
|
'description' => __( 'Unique identifier for the resource.' ), |
675
|
|
|
'type' => 'integer', |
676
|
|
|
'context' => array( 'view', 'embed', 'edit' ), |
677
|
|
|
'readonly' => true, |
678
|
|
|
), |
679
|
|
|
'count' => array( |
680
|
|
|
'description' => __( 'Number of published posts for the resource.' ), |
681
|
|
|
'type' => 'integer', |
682
|
|
|
'context' => array( 'view', 'edit' ), |
683
|
|
|
'readonly' => true, |
684
|
|
|
), |
685
|
|
|
'description' => array( |
686
|
|
|
'description' => __( 'HTML description of the resource.' ), |
687
|
|
|
'type' => 'string', |
688
|
|
|
'context' => array( 'view', 'edit' ), |
689
|
|
|
'arg_options' => array( |
690
|
|
|
'sanitize_callback' => 'wp_filter_post_kses', |
691
|
|
|
), |
692
|
|
|
), |
693
|
|
|
'link' => array( |
694
|
|
|
'description' => __( 'URL to the resource.' ), |
695
|
|
|
'type' => 'string', |
696
|
|
|
'format' => 'uri', |
697
|
|
|
'context' => array( 'view', 'embed', 'edit' ), |
698
|
|
|
'readonly' => true, |
699
|
|
|
), |
700
|
|
|
'name' => array( |
701
|
|
|
'description' => __( 'HTML title for the resource.' ), |
702
|
|
|
'type' => 'string', |
703
|
|
|
'context' => array( 'view', 'embed', 'edit' ), |
704
|
|
|
'arg_options' => array( |
705
|
|
|
'sanitize_callback' => 'sanitize_text_field', |
706
|
|
|
), |
707
|
|
|
'required' => true, |
708
|
|
|
), |
709
|
|
|
'slug' => array( |
710
|
|
|
'description' => __( 'An alphanumeric identifier for the resource unique to its type.' ), |
711
|
|
|
'type' => 'string', |
712
|
|
|
'context' => array( 'view', 'embed', 'edit' ), |
713
|
|
|
'arg_options' => array( |
714
|
|
|
'sanitize_callback' => 'sanitize_title', |
715
|
|
|
), |
716
|
|
|
), |
717
|
|
|
'taxonomy' => array( |
718
|
|
|
'description' => __( 'Type attribution for the resource.' ), |
719
|
|
|
'type' => 'string', |
720
|
|
|
'enum' => array_keys( get_taxonomies() ), |
721
|
|
|
'context' => array( 'view', 'embed', 'edit' ), |
722
|
|
|
'readonly' => true, |
723
|
|
|
), |
724
|
|
|
), |
725
|
|
|
); |
726
|
|
|
$taxonomy = get_taxonomy( $this->taxonomy ); |
727
|
|
View Code Duplication |
if ( $taxonomy->hierarchical ) { |
728
|
|
|
$schema['properties']['parent'] = array( |
729
|
|
|
'description' => __( 'The id for the parent of the resource.' ), |
730
|
|
|
'type' => 'integer', |
731
|
|
|
'context' => array( 'view', 'edit' ), |
732
|
|
|
); |
733
|
|
|
} |
734
|
|
|
return $this->add_additional_fields_schema( $schema ); |
735
|
|
|
} |
736
|
|
|
|
737
|
|
|
/** |
738
|
|
|
* Get the query params for collections |
739
|
|
|
* |
740
|
|
|
* @return array |
741
|
|
|
*/ |
742
|
|
|
public function get_collection_params() { |
743
|
|
|
$query_params = parent::get_collection_params(); |
744
|
|
|
$taxonomy = get_taxonomy( $this->taxonomy ); |
745
|
|
|
|
746
|
|
|
$query_params['context']['default'] = 'view'; |
747
|
|
|
|
748
|
|
|
$query_params['exclude'] = array( |
749
|
|
|
'description' => __( 'Ensure result set excludes specific ids.' ), |
750
|
|
|
'type' => 'array', |
751
|
|
|
'default' => array(), |
752
|
|
|
'sanitize_callback' => 'wp_parse_id_list', |
753
|
|
|
); |
754
|
|
|
$query_params['include'] = array( |
755
|
|
|
'description' => __( 'Limit result set to specific ids.' ), |
756
|
|
|
'type' => 'array', |
757
|
|
|
'default' => array(), |
758
|
|
|
'sanitize_callback' => 'wp_parse_id_list', |
759
|
|
|
); |
760
|
|
View Code Duplication |
if ( ! $taxonomy->hierarchical ) { |
761
|
|
|
$query_params['offset'] = array( |
762
|
|
|
'description' => __( 'Offset the result set by a specific number of items.' ), |
763
|
|
|
'type' => 'integer', |
764
|
|
|
'sanitize_callback' => 'absint', |
765
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
766
|
|
|
); |
767
|
|
|
} |
768
|
|
|
$query_params['order'] = array( |
769
|
|
|
'description' => __( 'Order sort attribute ascending or descending.' ), |
770
|
|
|
'type' => 'string', |
771
|
|
|
'sanitize_callback' => 'sanitize_key', |
772
|
|
|
'default' => 'asc', |
773
|
|
|
'enum' => array( |
774
|
|
|
'asc', |
775
|
|
|
'desc', |
776
|
|
|
), |
777
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
778
|
|
|
); |
779
|
|
|
$query_params['orderby'] = array( |
780
|
|
|
'description' => __( 'Sort collection by resource attribute.' ), |
781
|
|
|
'type' => 'string', |
782
|
|
|
'sanitize_callback' => 'sanitize_key', |
783
|
|
|
'default' => 'name', |
784
|
|
|
'enum' => array( |
785
|
|
|
'id', |
786
|
|
|
'include', |
787
|
|
|
'name', |
788
|
|
|
'slug', |
789
|
|
|
'term_group', |
790
|
|
|
'description', |
791
|
|
|
'count', |
792
|
|
|
), |
793
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
794
|
|
|
); |
795
|
|
|
$query_params['hide_empty'] = array( |
796
|
|
|
'description' => __( 'Whether to hide resources not assigned to any posts.' ), |
797
|
|
|
'type' => 'boolean', |
798
|
|
|
'default' => false, |
799
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
800
|
|
|
); |
801
|
|
View Code Duplication |
if ( $taxonomy->hierarchical ) { |
802
|
|
|
$query_params['parent'] = array( |
803
|
|
|
'description' => __( 'Limit result set to resources assigned to a specific parent.' ), |
804
|
|
|
'type' => 'integer', |
805
|
|
|
'sanitize_callback' => 'absint', |
806
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
807
|
|
|
); |
808
|
|
|
} |
809
|
|
|
$query_params['post'] = array( |
810
|
|
|
'description' => __( 'Limit result set to resources assigned to a specific post.' ), |
811
|
|
|
'type' => 'integer', |
812
|
|
|
'default' => null, |
813
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
814
|
|
|
); |
815
|
|
|
$query_params['slug'] = array( |
816
|
|
|
'description' => __( 'Limit result set to resources with a specific slug.' ), |
817
|
|
|
'type' => 'string', |
818
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
819
|
|
|
); |
820
|
|
|
return $query_params; |
821
|
|
|
} |
822
|
|
|
|
823
|
|
|
/** |
824
|
|
|
* Check that the taxonomy is valid |
825
|
|
|
* |
826
|
|
|
* @param string |
827
|
|
|
* @return WP_Error|boolean |
828
|
|
|
*/ |
829
|
|
|
protected function check_is_taxonomy_allowed( $taxonomy ) { |
830
|
|
|
$taxonomy_obj = get_taxonomy( $taxonomy ); |
831
|
|
|
if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) { |
832
|
|
|
return true; |
833
|
|
|
} |
834
|
|
|
return false; |
835
|
|
|
} |
836
|
|
|
} |
837
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.