Completed
Push — master ( 15aa29...17da96 )
by Claudio
18:39 queued 11s
created

abstracts/abstract-wc-rest-terms-controller.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Abstract Rest Terms Controller
4
 *
5
 * @package  WooCommerce/Abstracts
6
 * @version  3.3.0
7
 */
8
9 1
if ( ! defined( 'ABSPATH' ) ) {
10
	exit;
11
}
12
13
/**
14
 * Terms controller class.
15
 */
16
abstract class WC_REST_Terms_Controller extends WC_REST_Controller {
17
18
	/**
19
	 * Route base.
20
	 *
21
	 * @var string
22
	 */
23
	protected $rest_base = '';
24
25
	/**
26
	 * Taxonomy.
27
	 *
28
	 * @var string
29
	 */
30
	protected $taxonomy = '';
31
32
	/**
33
	 * Register the routes for terms.
34
	 */
35 426 View Code Duplication
	public function register_routes() {
36 426
		register_rest_route(
37 426
			$this->namespace,
38 426
			'/' . $this->rest_base,
39
			array(
40
				array(
41 426
					'methods'             => WP_REST_Server::READABLE,
42 426
					'callback'            => array( $this, 'get_items' ),
43 426
					'permission_callback' => array( $this, 'get_items_permissions_check' ),
44 426
					'args'                => $this->get_collection_params(),
45
				),
46
				array(
47
					'methods'             => WP_REST_Server::CREATABLE,
48 426
					'callback'            => array( $this, 'create_item' ),
49 426
					'permission_callback' => array( $this, 'create_item_permissions_check' ),
50 426
					'args'                => array_merge(
51 426
						$this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
52
						array(
53
							'name' => array(
54 426
								'type'        => 'string',
55 426
								'description' => __( 'Name for the resource.', 'woocommerce' ),
56
								'required'    => true,
57
							),
58
						)
59
					),
60
				),
61 426
				'schema' => array( $this, 'get_public_item_schema' ),
0 ignored issues
show
Key specified for array entry; first entry has no key
Loading history...
62
			)
63
		);
64
65 426
		register_rest_route(
66 426
			$this->namespace,
67 426
			'/' . $this->rest_base . '/(?P<id>[\d]+)',
68
			array(
69
				'args'   => array(
70
					'id' => array(
71 426
						'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
72 426
						'type'        => 'integer',
73
					),
74
				),
75
				array(
76
					'methods'             => WP_REST_Server::READABLE,
77 426
					'callback'            => array( $this, 'get_item' ),
78 426
					'permission_callback' => array( $this, 'get_item_permissions_check' ),
79
					'args'                => array(
80 426
						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
81
					),
82
				),
83
				array(
84
					'methods'             => WP_REST_Server::EDITABLE,
85 426
					'callback'            => array( $this, 'update_item' ),
86 426
					'permission_callback' => array( $this, 'update_item_permissions_check' ),
87 426
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
88
				),
89
				array(
90
					'methods'             => WP_REST_Server::DELETABLE,
91 426
					'callback'            => array( $this, 'delete_item' ),
92 426
					'permission_callback' => array( $this, 'delete_item_permissions_check' ),
93
					'args'                => array(
94
						'force' => array(
95
							'default'     => false,
96 426
							'type'        => 'boolean',
97 426
							'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
98
						),
99
					),
100
				),
101 426
				'schema' => array( $this, 'get_public_item_schema' ),
102
			)
103
		);
104
105 426
		register_rest_route(
106 426
			$this->namespace,
107 426
			'/' . $this->rest_base . '/batch',
108
			array(
109
				array(
110 426
					'methods'             => WP_REST_Server::EDITABLE,
111 426
					'callback'            => array( $this, 'batch_items' ),
112 426
					'permission_callback' => array( $this, 'batch_items_permissions_check' ),
113 426
					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
114
				),
115 426
				'schema' => array( $this, 'get_public_batch_schema' ),
0 ignored issues
show
Key specified for array entry; first entry has no key
Loading history...
116
			)
117
		);
118
	}
119
120
	/**
121
	 * Check if a given request has access to read the terms.
122
	 *
123
	 * @param  WP_REST_Request $request Full details about the request.
124
	 * @return WP_Error|boolean
125
	 */
126 7 View Code Duplication
	public function get_items_permissions_check( $request ) {
127 7
		$permissions = $this->check_permissions( $request, 'read' );
128 7
		if ( is_wp_error( $permissions ) ) {
129 1
			return $permissions;
130
		}
131
132 6
		if ( ! $permissions ) {
133 1
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
134
		}
135
136 5
		return true;
137
	}
138
139
	/**
140
	 * Check if a given request has access to create a term.
141
	 *
142
	 * @param  WP_REST_Request $request Full details about the request.
143
	 * @return WP_Error|boolean
144
	 */
145 2 View Code Duplication
	public function create_item_permissions_check( $request ) {
146 2
		$permissions = $this->check_permissions( $request, 'create' );
147 2
		if ( is_wp_error( $permissions ) ) {
148
			return $permissions;
149
		}
150
151 2
		if ( ! $permissions ) {
152
			return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
153
		}
154
155 2
		return true;
156
	}
157
158
	/**
159
	 * Check if a given request has access to read a term.
160
	 *
161
	 * @param  WP_REST_Request $request Full details about the request.
162
	 * @return WP_Error|boolean
163
	 */
164 3 View Code Duplication
	public function get_item_permissions_check( $request ) {
165 3
		$permissions = $this->check_permissions( $request, 'read' );
166 3
		if ( is_wp_error( $permissions ) ) {
167 1
			return $permissions;
168
		}
169
170 2
		if ( ! $permissions ) {
171 1
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
172
		}
173
174 1
		return true;
175
	}
176
177
	/**
178
	 * Check if a given request has access to update a term.
179
	 *
180
	 * @param  WP_REST_Request $request Full details about the request.
181
	 * @return WP_Error|boolean
182
	 */
183 View Code Duplication
	public function update_item_permissions_check( $request ) {
184
		$permissions = $this->check_permissions( $request, 'edit' );
185
		if ( is_wp_error( $permissions ) ) {
186
			return $permissions;
187
		}
188
189
		if ( ! $permissions ) {
190
			return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
191
		}
192
193
		return true;
194
	}
195
196
	/**
197
	 * Check if a given request has access to delete a term.
198
	 *
199
	 * @param  WP_REST_Request $request Full details about the request.
200
	 * @return WP_Error|boolean
201
	 */
202 View Code Duplication
	public function delete_item_permissions_check( $request ) {
203
		$permissions = $this->check_permissions( $request, 'delete' );
204
		if ( is_wp_error( $permissions ) ) {
205
			return $permissions;
206
		}
207
208
		if ( ! $permissions ) {
209
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
210
		}
211
212
		return true;
213
	}
214
215
	/**
216
	 * Check if a given request has access batch create, update and delete items.
217
	 *
218
	 * @param  WP_REST_Request $request Full details about the request.
219
	 * @return boolean|WP_Error
220
	 */
221 View Code Duplication
	public function batch_items_permissions_check( $request ) {
222
		$permissions = $this->check_permissions( $request, 'batch' );
223
		if ( is_wp_error( $permissions ) ) {
224
			return $permissions;
225
		}
226
227
		if ( ! $permissions ) {
228
			return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to batch manipulate this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
229
		}
230
231
		return true;
232
	}
233
234
	/**
235
	 * Check permissions.
236
	 *
237
	 * @param WP_REST_Request $request Full details about the request.
238
	 * @param string          $context Request context.
239
	 * @return bool|WP_Error
240
	 */
241 2
	protected function check_permissions( $request, $context = 'read' ) {
242
		// Get taxonomy.
243 2
		$taxonomy = $this->get_taxonomy( $request );
244 2
		if ( ! $taxonomy || ! taxonomy_exists( $taxonomy ) ) {
245
			return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( 'Taxonomy does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
246
		}
247
248
		// Check permissions for a single term.
249 2
		$id = intval( $request['id'] );
250 2
		if ( $id ) {
251
			$term = get_term( $id, $taxonomy );
252
253
			if ( is_wp_error( $term ) || ! $term || $term->taxonomy !== $taxonomy ) {
254
				return new WP_Error( 'woocommerce_rest_term_invalid', __( 'Resource does not exist.', 'woocommerce' ), array( 'status' => 404 ) );
255
			}
256
257
			return wc_rest_check_product_term_permissions( $taxonomy, $context, $term->term_id );
258
		}
259
260 2
		return wc_rest_check_product_term_permissions( $taxonomy, $context );
261
	}
262
263
	/**
264
	 * Get terms associated with a taxonomy.
265
	 *
266
	 * @param WP_REST_Request $request Full details about the request.
267
	 * @return WP_REST_Response|WP_Error
268
	 */
269 3
	public function get_items( $request ) {
270 3
		$taxonomy      = $this->get_taxonomy( $request );
271
		$prepared_args = array(
272 3
			'exclude'    => $request['exclude'],
273 3
			'include'    => $request['include'],
274 3
			'order'      => $request['order'],
275 3
			'orderby'    => $request['orderby'],
276 3
			'product'    => $request['product'],
277 3
			'hide_empty' => $request['hide_empty'],
278 3
			'number'     => $request['per_page'],
279 3
			'search'     => $request['search'],
280 3
			'slug'       => $request['slug'],
281
		);
282
283 3 View Code Duplication
		if ( ! empty( $request['offset'] ) ) {
284
			$prepared_args['offset'] = $request['offset'];
285
		} else {
286 3
			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
287
		}
288
289 3
		$taxonomy_obj = get_taxonomy( $taxonomy );
290
291 3
		if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
292
			if ( 0 === $request['parent'] ) {
293
				// Only query top-level terms.
294
				$prepared_args['parent'] = 0;
295
			} else {
296
				if ( $request['parent'] ) {
297
					$prepared_args['parent'] = $request['parent'];
298
				}
299
			}
300
		}
301
302
		/**
303
		 * Filter the query arguments, before passing them to `get_terms()`.
304
		 *
305
		 * Enables adding extra arguments or setting defaults for a terms
306
		 * collection request.
307
		 *
308
		 * @see https://developer.wordpress.org/reference/functions/get_terms/
309
		 *
310
		 * @param array           $prepared_args Array of arguments to be
311
		 *                                       passed to get_terms.
312
		 * @param WP_REST_Request $request       The current request.
313
		 */
314 3
		$prepared_args = apply_filters( "woocommerce_rest_{$taxonomy}_query", $prepared_args, $request );
315
316 3
		if ( ! empty( $prepared_args['product'] ) ) {
317
			$query_result = $this->get_terms_for_product( $prepared_args, $request );
318
			$total_terms  = $this->total_terms;
319
		} else {
320 3
			$query_result = get_terms( $taxonomy, $prepared_args );
321
322 3
			$count_args = $prepared_args;
323 3
			unset( $count_args['number'] );
324 3
			unset( $count_args['offset'] );
325 3
			$total_terms = wp_count_terms( $taxonomy, $count_args );
326
327
			// Ensure we don't return results when offset is out of bounds.
328
			// See https://core.trac.wordpress.org/ticket/35935.
329 3
			if ( $prepared_args['offset'] && $prepared_args['offset'] >= $total_terms ) {
330
				$query_result = array();
331
			}
332
333
			// wp_count_terms can return a falsy value when the term has no children.
334 3
			if ( ! $total_terms ) {
335
				$total_terms = 0;
336
			}
337
		}
338 3
		$response = array();
339 3
		foreach ( $query_result as $term ) {
340 3
			$data       = $this->prepare_item_for_response( $term, $request );
341 3
			$response[] = $this->prepare_response_for_collection( $data );
342
		}
343
344 3
		$response = rest_ensure_response( $response );
345
346
		// Store pagination values for headers then unset for count query.
347 3
		$per_page = (int) $prepared_args['number'];
348 3
		$page     = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
349
350 3
		$response->header( 'X-WP-Total', (int) $total_terms );
351 3
		$max_pages = ceil( $total_terms / $per_page );
352 3
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
353
354 3
		$base  = str_replace( '(?P<attribute_id>[\d]+)', $request['attribute_id'], $this->rest_base );
355 3
		$base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $base ) );
356 3
		if ( $page > 1 ) {
357
			$prev_page = $page - 1;
358
			if ( $prev_page > $max_pages ) {
359
				$prev_page = $max_pages;
360
			}
361
			$prev_link = add_query_arg( 'page', $prev_page, $base );
362
			$response->link_header( 'prev', $prev_link );
363
		}
364 3
		if ( $max_pages > $page ) {
365
			$next_page = $page + 1;
366
			$next_link = add_query_arg( 'page', $next_page, $base );
367
			$response->link_header( 'next', $next_link );
368
		}
369
370 3
		return $response;
371
	}
372
373
	/**
374
	 * Create a single term for a taxonomy.
375
	 *
376
	 * @param WP_REST_Request $request Full details about the request.
377
	 * @return WP_REST_Request|WP_Error
378
	 */
379 2
	public function create_item( $request ) {
380 2
		$taxonomy = $this->get_taxonomy( $request );
381 2
		$name     = $request['name'];
382 2
		$args     = array();
383 2
		$schema   = $this->get_item_schema();
384
385 2 View Code Duplication
		if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
386
			$args['description'] = $request['description'];
387
		}
388 2
		if ( isset( $request['slug'] ) ) {
389
			$args['slug'] = $request['slug'];
390
		}
391 2 View Code Duplication
		if ( isset( $request['parent'] ) ) {
392
			if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
393
				return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) );
394
			}
395
			$args['parent'] = $request['parent'];
396
		}
397
398 2
		$term = wp_insert_term( $name, $taxonomy, $args );
399 2
		if ( is_wp_error( $term ) ) {
400
			$error_data = array( 'status' => 400 );
401
402
			// If we're going to inform the client that the term exists,
403
			// give them the identifier they can actually use.
404
			$term_id = $term->get_error_data( 'term_exists' );
405
			if ( $term_id ) {
406
				$error_data['resource_id'] = $term_id;
407
			}
408
409
			return new WP_Error( $term->get_error_code(), $term->get_error_message(), $error_data );
410
		}
411
412 2
		$term = get_term( $term['term_id'], $taxonomy );
413
414 2
		$this->update_additional_fields_for_object( $term, $request );
415
416
		// Add term data.
417 2
		$meta_fields = $this->update_term_meta_fields( $term, $request );
418 2
		if ( is_wp_error( $meta_fields ) ) {
419
			wp_delete_term( $term->term_id, $taxonomy );
420
421
			return $meta_fields;
422
		}
423
424
		/**
425
		 * Fires after a single term is created or updated via the REST API.
426
		 *
427
		 * @param WP_Term         $term      Inserted Term object.
428
		 * @param WP_REST_Request $request   Request object.
429
		 * @param boolean         $creating  True when creating term, false when updating.
430
		 */
431 2
		do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, true );
432
433 2
		$request->set_param( 'context', 'edit' );
434 2
		$response = $this->prepare_item_for_response( $term, $request );
435 2
		$response = rest_ensure_response( $response );
436 2
		$response->set_status( 201 );
437
438 2
		$base = '/' . $this->namespace . '/' . $this->rest_base;
439 2 View Code Duplication
		if ( ! empty( $request['attribute_id'] ) ) {
440
			$base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base );
441
		}
442
443 2
		$response->header( 'Location', rest_url( $base . '/' . $term->term_id ) );
444
445 2
		return $response;
446
	}
447
448
	/**
449
	 * Get a single term from a taxonomy.
450
	 *
451
	 * @param WP_REST_Request $request Full details about the request.
452
	 * @return WP_REST_Request|WP_Error
453
	 */
454 1
	public function get_item( $request ) {
455 1
		$taxonomy = $this->get_taxonomy( $request );
456 1
		$term     = get_term( (int) $request['id'], $taxonomy );
457
458 1
		if ( is_wp_error( $term ) ) {
459
			return $term;
460
		}
461
462 1
		$response = $this->prepare_item_for_response( $term, $request );
463
464 1
		return rest_ensure_response( $response );
465
	}
466
467
	/**
468
	 * Update a single term from a taxonomy.
469
	 *
470
	 * @param WP_REST_Request $request Full details about the request.
471
	 * @return WP_REST_Request|WP_Error
472
	 */
473
	public function update_item( $request ) {
474
		$taxonomy      = $this->get_taxonomy( $request );
475
		$term          = get_term( (int) $request['id'], $taxonomy );
476
		$schema        = $this->get_item_schema();
477
		$prepared_args = array();
478
479
		if ( isset( $request['name'] ) ) {
480
			$prepared_args['name'] = $request['name'];
481
		}
482 View Code Duplication
		if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
483
			$prepared_args['description'] = $request['description'];
484
		}
485
		if ( isset( $request['slug'] ) ) {
486
			$prepared_args['slug'] = $request['slug'];
487
		}
488 View Code Duplication
		if ( isset( $request['parent'] ) ) {
489
			if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
490
				return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) );
491
			}
492
			$prepared_args['parent'] = $request['parent'];
493
		}
494
495
		// Only update the term if we haz something to update.
496
		if ( ! empty( $prepared_args ) ) {
497
			$update = wp_update_term( $term->term_id, $term->taxonomy, $prepared_args );
498
			if ( is_wp_error( $update ) ) {
499
				return $update;
500
			}
501
		}
502
503
		$term = get_term( (int) $request['id'], $taxonomy );
504
505
		$this->update_additional_fields_for_object( $term, $request );
506
507
		// Update term data.
508
		$meta_fields = $this->update_term_meta_fields( $term, $request );
509
		if ( is_wp_error( $meta_fields ) ) {
510
			return $meta_fields;
511
		}
512
513
		/**
514
		 * Fires after a single term is created or updated via the REST API.
515
		 *
516
		 * @param WP_Term         $term      Inserted Term object.
517
		 * @param WP_REST_Request $request   Request object.
518
		 * @param boolean         $creating  True when creating term, false when updating.
519
		 */
520
		do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, false );
521
522
		$request->set_param( 'context', 'edit' );
523
		$response = $this->prepare_item_for_response( $term, $request );
524
		return rest_ensure_response( $response );
525
	}
526
527
	/**
528
	 * Delete a single term from a taxonomy.
529
	 *
530
	 * @param WP_REST_Request $request Full details about the request.
531
	 * @return WP_REST_Response|WP_Error
532
	 */
533
	public function delete_item( $request ) {
534
		$taxonomy = $this->get_taxonomy( $request );
535
		$force    = isset( $request['force'] ) ? (bool) $request['force'] : false;
536
537
		// We don't support trashing for this type, error out.
538
		if ( ! $force ) {
539
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
540
		}
541
542
		$term = get_term( (int) $request['id'], $taxonomy );
543
		// Get default category id.
544
		$default_category_id = absint( get_option( 'default_product_cat', 0 ) );
545
546
		// Prevent deleting the default product category.
547
		if ( $default_category_id === (int) $request['id'] ) {
548
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Default product category cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
549
		}
550
551
		$request->set_param( 'context', 'edit' );
552
		$response = $this->prepare_item_for_response( $term, $request );
553
554
		$retval = wp_delete_term( $term->term_id, $term->taxonomy );
555
		if ( ! $retval ) {
556
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
557
		}
558
559
		/**
560
		 * Fires after a single term is deleted via the REST API.
561
		 *
562
		 * @param WP_Term          $term     The deleted term.
563
		 * @param WP_REST_Response $response The response data.
564
		 * @param WP_REST_Request  $request  The request sent to the API.
565
		 */
566
		do_action( "woocommerce_rest_delete_{$taxonomy}", $term, $response, $request );
567
568
		return $response;
569
	}
570
571
	/**
572
	 * Prepare links for the request.
573
	 *
574
	 * @param object          $term   Term object.
575
	 * @param WP_REST_Request $request Full details about the request.
576
	 * @return array Links for the given term.
577
	 */
578 6
	protected function prepare_links( $term, $request ) {
579 6
		$base = '/' . $this->namespace . '/' . $this->rest_base;
580
581 6 View Code Duplication
		if ( ! empty( $request['attribute_id'] ) ) {
582 2
			$base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base );
583
		}
584
585
		$links = array(
586
			'self'       => array(
587 6
				'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
588
			),
589
			'collection' => array(
590 6
				'href' => rest_url( $base ),
591
			),
592
		);
593
594 6
		if ( $term->parent ) {
595 1
			$parent_term = get_term( (int) $term->parent, $term->taxonomy );
596 1
			if ( $parent_term ) {
597 1
				$links['up'] = array(
598 1
					'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
599
				);
600
			}
601
		}
602
603 6
		return $links;
604
	}
605
606
	/**
607
	 * Update term meta fields.
608
	 *
609
	 * @param WP_Term         $term    Term object.
610
	 * @param WP_REST_Request $request Full details about the request.
611
	 * @return bool|WP_Error
612
	 */
613 2
	protected function update_term_meta_fields( $term, $request ) {
614 2
		return true;
615
	}
616
617
	/**
618
	 * Get the terms attached to a product.
619
	 *
620
	 * This is an alternative to `get_terms()` that uses `get_the_terms()`
621
	 * instead, which hits the object cache. There are a few things not
622
	 * supported, notably `include`, `exclude`. In `self::get_items()` these
623
	 * are instead treated as a full query.
624
	 *
625
	 * @param array           $prepared_args Arguments for `get_terms()`.
626
	 * @param WP_REST_Request $request       Full details about the request.
627
	 * @return array List of term objects. (Total count in `$this->total_terms`).
628
	 */
629
	protected function get_terms_for_product( $prepared_args, $request ) {
630
		$taxonomy = $this->get_taxonomy( $request );
631
632
		$query_result = get_the_terms( $prepared_args['product'], $taxonomy );
633
		if ( empty( $query_result ) ) {
634
			$this->total_terms = 0;
635
			return array();
636
		}
637
638
		// get_items() verifies that we don't have `include` set, and default.
639
		// ordering is by `name`.
640
		if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ), true ) ) {
641
			switch ( $prepared_args['orderby'] ) {
642
				case 'id':
643
					$this->sort_column = 'term_id';
644
					break;
645
				case 'slug':
646
				case 'term_group':
647
				case 'description':
648
				case 'count':
649
					$this->sort_column = $prepared_args['orderby'];
650
					break;
651
			}
652
			usort( $query_result, array( $this, 'compare_terms' ) );
653
		}
654
		if ( strtolower( $prepared_args['order'] ) !== 'asc' ) {
0 ignored issues
show
Found "!== '". Use Yoda Condition checks, you must
Loading history...
655
			$query_result = array_reverse( $query_result );
656
		}
657
658
		// Pagination.
659
		$this->total_terms = count( $query_result );
660
		$query_result      = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] );
661
662
		return $query_result;
663
	}
664
665
	/**
666
	 * Comparison function for sorting terms by a column.
667
	 *
668
	 * Uses `$this->sort_column` to determine field to sort by.
669
	 *
670
	 * @param stdClass $left Term object.
671
	 * @param stdClass $right Term object.
672
	 * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left.
673
	 */
674
	protected function compare_terms( $left, $right ) {
675
		$col       = $this->sort_column;
676
		$left_val  = $left->$col;
677
		$right_val = $right->$col;
678
679
		if ( is_int( $left_val ) && is_int( $right_val ) ) {
680
			return $left_val - $right_val;
681
		}
682
683
		return strcmp( $left_val, $right_val );
684
	}
685
686
	/**
687
	 * Get the query params for collections
688
	 *
689
	 * @return array
690
	 */
691 426
	public function get_collection_params() {
692 426
		$params = parent::get_collection_params();
693
694 426
		if ( '' !== $this->taxonomy && taxonomy_exists( $this->taxonomy ) ) {
695 426
			$taxonomy = get_taxonomy( $this->taxonomy );
696
		} else {
697 426
			$taxonomy               = new stdClass();
698 426
			$taxonomy->hierarchical = true;
699
		}
700
701 426
		$params['context']['default'] = 'view';
702
703 426
		$params['exclude'] = array(
704 426
			'description'       => __( 'Ensure result set excludes specific IDs.', 'woocommerce' ),
705 426
			'type'              => 'array',
706
			'items'             => array(
707
				'type' => 'integer',
708
			),
709
			'default'           => array(),
710 426
			'sanitize_callback' => 'wp_parse_id_list',
711
		);
712 426
		$params['include'] = array(
713 426
			'description'       => __( 'Limit result set to specific ids.', 'woocommerce' ),
714 426
			'type'              => 'array',
715
			'items'             => array(
716
				'type' => 'integer',
717
			),
718
			'default'           => array(),
719 426
			'sanitize_callback' => 'wp_parse_id_list',
720
		);
721 426 View Code Duplication
		if ( ! $taxonomy->hierarchical ) {
722 426
			$params['offset'] = array(
723 426
				'description'       => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
724 426
				'type'              => 'integer',
725 426
				'sanitize_callback' => 'absint',
726 426
				'validate_callback' => 'rest_validate_request_arg',
727
			);
728
		}
729 426
		$params['order']      = array(
730 426
			'description'       => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
731 426
			'type'              => 'string',
732 426
			'sanitize_callback' => 'sanitize_key',
733 426
			'default'           => 'asc',
734
			'enum'              => array(
735
				'asc',
736
				'desc',
737
			),
738 426
			'validate_callback' => 'rest_validate_request_arg',
739
		);
740 426
		$params['orderby']    = array(
741 426
			'description'       => __( 'Sort collection by resource attribute.', 'woocommerce' ),
742 426
			'type'              => 'string',
743 426
			'sanitize_callback' => 'sanitize_key',
744 426
			'default'           => 'name',
745
			'enum'              => array(
746
				'id',
747
				'include',
748
				'name',
749
				'slug',
750
				'term_group',
751
				'description',
752
				'count',
753
			),
754 426
			'validate_callback' => 'rest_validate_request_arg',
755
		);
756 426
		$params['hide_empty'] = array(
757 426
			'description'       => __( 'Whether to hide resources not assigned to any products.', 'woocommerce' ),
758 426
			'type'              => 'boolean',
759
			'default'           => false,
760 426
			'validate_callback' => 'rest_validate_request_arg',
761
		);
762 426 View Code Duplication
		if ( $taxonomy->hierarchical ) {
763 426
			$params['parent'] = array(
764 426
				'description'       => __( 'Limit result set to resources assigned to a specific parent.', 'woocommerce' ),
765 426
				'type'              => 'integer',
766 426
				'sanitize_callback' => 'absint',
767 426
				'validate_callback' => 'rest_validate_request_arg',
768
			);
769
		}
770 426
		$params['product'] = array(
771 426
			'description'       => __( 'Limit result set to resources assigned to a specific product.', 'woocommerce' ),
772 426
			'type'              => 'integer',
773
			'default'           => null,
774 426
			'validate_callback' => 'rest_validate_request_arg',
775
		);
776 426
		$params['slug']    = array(
777 426
			'description'       => __( 'Limit result set to resources with a specific slug.', 'woocommerce' ),
778 426
			'type'              => 'string',
779 426
			'validate_callback' => 'rest_validate_request_arg',
780
		);
781
782 426
		return $params;
783
	}
784
785
	/**
786
	 * Get taxonomy.
787
	 *
788
	 * @param WP_REST_Request $request Full details about the request.
789
	 * @return int|WP_Error
790
	 */
791 10
	protected function get_taxonomy( $request ) {
792
		// Check if taxonomy is defined.
793
		// Prevents check for attribute taxonomy more than one time for each query.
794 10
		if ( '' !== $this->taxonomy ) {
795 9
			return $this->taxonomy;
796
		}
797
798 4
		if ( ! empty( $request['attribute_id'] ) ) {
799 4
			$taxonomy = wc_attribute_taxonomy_name_by_id( (int) $request['attribute_id'] );
800
801 4
			$this->taxonomy = $taxonomy;
802
		}
803
804 4
		return $this->taxonomy;
805
	}
806
}
807