Issues (1182)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

abstracts/abstract-wc-rest-terms-controller.php (17 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
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * Abstract Rest Terms Controler Class
9
 *
10
 * @author   WooThemes
11
 * @category API
12
 * @package  WooCommerce/Abstracts
13
 * @version  2.6.0
14
 */
15
abstract class WC_REST_Terms_Controller extends WC_REST_Controller {
16
17
	/**
18
	 * Route base.
19
	 *
20
	 * @var string
21
	 */
22
	protected $rest_base = '';
23
24
	/**
25
	 * Taxonomy.
26
	 *
27
	 * @var string
28
	 */
29
	protected $taxonomy = '';
30
31
	/**
32
	 * Register the routes for terms.
33
	 */
34
	public function register_routes() {
35
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
36
			array(
37
				'methods'             => WP_REST_Server::READABLE,
38
				'callback'            => array( $this, 'get_items' ),
39
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
40
				'args'                => $this->get_collection_params(),
41
			),
42
			array(
43
				'methods'             => WP_REST_Server::CREATABLE,
44
				'callback'            => array( $this, 'create_item' ),
45
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
46
				'args'                => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
47
					'name' => array(
48
						'required' => true,
49
					),
50
				) ),
51
			),
52
			'schema' => array( $this, 'get_public_item_schema' ),
53
		));
54
55
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
56
			array(
57
				'methods'             => WP_REST_Server::READABLE,
58
				'callback'            => array( $this, 'get_item' ),
59
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
60
				'args'                => array(
61
					'context'         => $this->get_context_param( array( 'default' => 'view' ) ),
62
				),
63
			),
64
			array(
65
				'methods'             => WP_REST_Server::EDITABLE,
66
				'callback'            => array( $this, 'update_item' ),
67
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
68
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
69
			),
70
			array(
71
				'methods'             => WP_REST_Server::DELETABLE,
72
				'callback'            => array( $this, 'delete_item' ),
73
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
74
				'args'                => array(
75
					'force' => array(
76
						'default'     => false,
77
						'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
78
					),
79
				),
80
			),
81
			'schema' => array( $this, 'get_public_item_schema' ),
82
		) );
83
84
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
85
			array(
86
				'methods'             => WP_REST_Server::EDITABLE,
87
				'callback'            => array( $this, 'batch_items' ),
88
				'permission_callback' => array( $this, 'batch_items_permissions_check' ),
89
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
90
			),
91
			'schema' => array( $this, 'get_public_batch_schema' ),
92
		) );
93
	}
94
95
	/**
96
	 * Check if a given request has access to read the terms.
97
	 *
98
	 * @param  WP_REST_Request $request Full details about the request.
99
	 * @return WP_Error|boolean
100
	 */
101 View Code Duplication
	public function get_items_permissions_check( $request ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
102
		$permissions = $this->check_permissions( $request, 'read' );
103
		if ( is_wp_error( $permissions ) ) {
104
			return $permissions;
105
		}
106
107
		if ( ! $permissions ) {
108
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
109
		}
110
111
		return true;
112
	}
113
114
	/**
115
	 * Check if a given request has access to create a term.
116
	 *
117
	 * @param  WP_REST_Request $request Full details about the request.
118
	 * @return WP_Error|boolean
119
	 */
120 View Code Duplication
	public function create_item_permissions_check( $request ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
121
		$permissions = $this->check_permissions( $request, 'create' );
122
		if ( is_wp_error( $permissions ) ) {
123
			return $permissions;
124
		}
125
126
		if ( ! $permissions ) {
127
			return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you cannot create new resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
128
		}
129
130
		return true;
131
	}
132
133
	/**
134
	 * Check if a given request has access to read a term.
135
	 *
136
	 * @param  WP_REST_Request $request Full details about the request.
137
	 * @return WP_Error|boolean
138
	 */
139 View Code Duplication
	public function get_item_permissions_check( $request ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
140
		$permissions = $this->check_permissions( $request, 'read' );
141
		if ( is_wp_error( $permissions ) ) {
142
			return $permissions;
143
		}
144
145
		if ( ! $permissions ) {
146
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
147
		}
148
149
		return true;
150
	}
151
152
	/**
153
	 * Check if a given request has access to update a term.
154
	 *
155
	 * @param  WP_REST_Request $request Full details about the request.
156
	 * @return WP_Error|boolean
157
	 */
158 View Code Duplication
	public function update_item_permissions_check( $request ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
159
		$permissions = $this->check_permissions( $request, 'edit' );
160
		if ( is_wp_error( $permissions ) ) {
161
			return $permissions;
162
		}
163
164
		if ( ! $permissions ) {
165
			return new WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot update resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
166
		}
167
168
		return true;
169
	}
170
171
	/**
172
	 * Check if a given request has access to delete a term.
173
	 *
174
	 * @param  WP_REST_Request $request Full details about the request.
175
	 * @return WP_Error|boolean
176
	 */
177 View Code Duplication
	public function delete_item_permissions_check( $request ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
178
		$permissions = $this->check_permissions( $request, 'delete' );
179
		if ( is_wp_error( $permissions ) ) {
180
			return $permissions;
181
		}
182
183
		if ( ! $permissions ) {
184
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you cannot delete resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
185
		}
186
187
		return true;
188
	}
189
190
	/**
191
	 * Check if a given request has access batch create, update and delete items.
192
	 *
193
	 * @param  WP_REST_Request $request Full details about the request.
194
	 * @return boolean
195
	 */
196 View Code Duplication
	public function batch_items_permissions_check( $request ) {
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
		$permissions = $this->check_permissions( $request, 'batch' );
198
		if ( is_wp_error( $permissions ) ) {
199
			return $permissions;
200
		}
201
202
		if ( ! $permissions ) {
203
			return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to manipule this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
204
		}
205
206
		return true;
207
	}
208
209
	/**
210
	 * Check permissions.
211
	 *
212
	 * @param WP_REST_Request $request Full details about the request.
213
	 * @param string $context Request context.
214
	 * @return bool|WP_Error
215
	 */
216
	protected function check_permissions( $request, $context = 'read' ) {
217
		// Get taxonomy.
218
		$taxonomy = $this->get_taxonomy( $request );
219
		if ( ! $taxonomy ) {
220
			return new WP_Error( 'woocommerce_rest_taxonomy_invalid', __( "Taxonomy doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
221
		}
222
223
		// Check permissions for a single term.
224
		if ( $id = intval( $request['id'] ) ) {
225
			$term = get_term( $id, $taxonomy );
226
227
			if ( ! $term || $term->taxonomy !== $taxonomy ) {
228
				return new WP_Error( 'woocommerce_rest_term_invalid', __( "Resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
229
			}
230
231
			return wc_rest_check_product_term_permissions( $taxonomy, $context, $term->term_id );
232
		}
233
234
		return wc_rest_check_product_term_permissions( $taxonomy, $context );
235
	}
236
237
	/**
238
	 * Get terms associated with a taxonomy.
239
	 *
240
	 * @param WP_REST_Request $request Full details about the request.
241
	 * @return WP_REST_Response|WP_Error
242
	 */
243
	public function get_items( $request ) {
244
		$taxonomy      = $this->get_taxonomy( $request );
245
		$prepared_args = array(
246
			'exclude'    => $request['exclude'],
247
			'include'    => $request['include'],
248
			'order'      => $request['order'],
249
			'orderby'    => $request['orderby'],
250
			'product'    => $request['product'],
251
			'hide_empty' => $request['hide_empty'],
252
			'number'     => $request['per_page'],
253
			'search'     => $request['search'],
254
			'slug'       => $request['slug'],
255
		);
256
257 View Code Duplication
		if ( ! empty( $request['offset'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
258
			$prepared_args['offset'] = $request['offset'];
259
		} else {
260
			$prepared_args['offset']  = ( $request['page'] - 1 ) * $prepared_args['number'];
261
		}
262
263
		$taxonomy_obj = get_taxonomy( $taxonomy );
264
265
		if ( $taxonomy_obj->hierarchical && isset( $request['parent'] ) ) {
266
			if ( 0 === $request['parent'] ) {
267
				// Only query top-level terms.
268
				$prepared_args['parent'] = 0;
269
			} else {
270
				if ( $request['parent'] ) {
271
					$prepared_args['parent'] = $request['parent'];
272
				}
273
			}
274
		}
275
276
		/**
277
		 * Filter the query arguments, before passing them to `get_terms()`.
278
		 *
279
		 * Enables adding extra arguments or setting defaults for a terms
280
		 * collection request.
281
		 *
282
		 * @see https://developer.wordpress.org/reference/functions/get_terms/
283
		 *
284
		 * @param array           $prepared_args Array of arguments to be
285
		 *                                       passed to get_terms.
286
		 * @param WP_REST_Request $request       The current request.
287
		 */
288
		$prepared_args = apply_filters( "woocommerce_rest_{$taxonomy}_query", $prepared_args, $request );
289
290
		if ( ! empty( $prepared_args['product'] )  ) {
291
			$query_result = $this->get_terms_for_product( $prepared_args );
292
			$total_terms = $this->total_terms;
293
		} else {
294
			$query_result = get_terms( $taxonomy, $prepared_args );
295
296
			$count_args = $prepared_args;
297
			unset( $count_args['number'] );
298
			unset( $count_args['offset'] );
299
			$total_terms = wp_count_terms( $taxonomy, $count_args );
300
301
			// Ensure we don't return results when offset is out of bounds.
302
			// See https://core.trac.wordpress.org/ticket/35935
303
			if ( $prepared_args['offset'] >= $total_terms ) {
304
				$query_result = array();
305
			}
306
307
			// wp_count_terms can return a falsy value when the term has no children.
308
			if ( ! $total_terms ) {
309
				$total_terms = 0;
310
			}
311
		}
312
		$response = array();
313
		foreach ( $query_result as $term ) {
314
			$data = $this->prepare_item_for_response( $term, $request );
315
			$response[] = $this->prepare_response_for_collection( $data );
316
		}
317
318
		$response = rest_ensure_response( $response );
319
320
		// Store pagation values for headers then unset for count query.
321
		$per_page = (int) $prepared_args['number'];
322
		$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
323
324
		$response->header( 'X-WP-Total', (int) $total_terms );
325
		$max_pages = ceil( $total_terms / $per_page );
326
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
327
328
		$base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $this->rest_base ) );
329 View Code Duplication
		if ( $page > 1 ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
330
			$prev_page = $page - 1;
331
			if ( $prev_page > $max_pages ) {
332
				$prev_page = $max_pages;
333
			}
334
			$prev_link = add_query_arg( 'page', $prev_page, $base );
335
			$response->link_header( 'prev', $prev_link );
336
		}
337 View Code Duplication
		if ( $max_pages > $page ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
338
			$next_page = $page + 1;
339
			$next_link = add_query_arg( 'page', $next_page, $base );
340
			$response->link_header( 'next', $next_link );
341
		}
342
343
		return $response;
344
	}
345
346
	/**
347
	 * Create a single term for a taxonomy.
348
	 *
349
	 * @param WP_REST_Request $request Full details about the request.
350
	 * @return WP_REST_Request|WP_Error
351
	 */
352
	public function create_item( $request ) {
353
		$taxonomy = $this->get_taxonomy( $request );
354
		$name     = $request['name'];
355
		$args     = array();
356
		$schema   = $this->get_item_schema();
357
358 View Code Duplication
		if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
359
			$args['description'] = $request['description'];
360
		}
361
		if ( isset( $request['slug'] ) ) {
362
			$args['slug'] = $request['slug'];
363
		}
364
365 View Code Duplication
		if ( isset( $request['parent'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
366
			if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
367
				return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) );
368
			}
369
370
			$parent = get_term( (int) $request['parent'], $taxonomy );
371
372
			if ( ! $parent ) {
373
				return new WP_Error( 'woocommerce_rest_term_invalid', __( "Parent resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
374
			}
375
376
			$args['parent'] = $parent->term_id;
377
		}
378
379
		$term = wp_insert_term( $name, $taxonomy, $args );
380
		if ( is_wp_error( $term ) ) {
381
382
			// If we're going to inform the client that the term exists, give them the identifier
383
			// they can actually use.
384
			if ( ( $term_id = $term->get_error_data( 'term_exists' ) ) ) {
385
				$existing_term = get_term( $term_id, $taxonomy );
386
				$term->add_data( $existing_term->term_id, 'term_exists' );
387
			}
388
389
			return $term;
390
		}
391
392
		$term = get_term( $term['term_id'], $taxonomy );
393
394
		$this->update_additional_fields_for_object( $term, $request );
395
396
		// Add term data.
397
		$meta_fields = $this->update_term_meta_fields( $term, $request );
398
		if ( is_wp_error( $meta_fields ) ) {
399
			return $meta_fields;
400
		}
401
402
		/**
403
		 * Fires after a single term is created or updated via the REST API.
404
		 *
405
		 * @param WP_Term         $term      Inserted Term object.
406
		 * @param WP_REST_Request $request   Request object.
407
		 * @param boolean         $creating  True when creating term, false when updating.
408
		 */
409
		do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, true );
410
411
		$request->set_param( 'context', 'edit' );
412
		$response = $this->prepare_item_for_response( $term, $request );
413
		$response = rest_ensure_response( $response );
414
		$response->set_status( 201 );
415
416
		$base = '/' . $this->namespace . '/' . $this->rest_base;
417 View Code Duplication
		if ( ! empty( $request['attribute_id'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
			$base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base );
419
		}
420
421
		$response->header( 'Location', rest_url( $base . '/' . $term->term_id ) );
422
423
		return $response;
424
	}
425
426
	/**
427
	 * Get a single term from a taxonomy.
428
	 *
429
	 * @param WP_REST_Request $request Full details about the request.
430
	 * @return WP_REST_Request|WP_Error
431
	 */
432
	public function get_item( $request ) {
433
		$taxonomy = $this->get_taxonomy( $request );
434
		$term     = get_term( (int) $request['id'], $taxonomy );
435
436
		if ( is_wp_error( $term ) ) {
437
			return $term;
438
		}
439
440
		$response = $this->prepare_item_for_response( $term, $request );
441
442
		return rest_ensure_response( $response );
443
	}
444
445
	/**
446
	 * Update a single term from a taxonomy.
447
	 *
448
	 * @param WP_REST_Request $request Full details about the request.
449
	 * @return WP_REST_Request|WP_Error
450
	 */
451
	public function update_item( $request ) {
452
		$taxonomy      = $this->get_taxonomy( $request );
453
		$term          = get_term( (int) $request['id'], $taxonomy );
454
		$schema        = $this->get_item_schema();
455
		$prepared_args = array();
456
457
		if ( isset( $request['name'] ) ) {
458
			$prepared_args['name'] = $request['name'];
459
		}
460 View Code Duplication
		if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
461
			$prepared_args['description'] = $request['description'];
462
		}
463
		if ( isset( $request['slug'] ) ) {
464
			$prepared_args['slug'] = $request['slug'];
465
		}
466
467 View Code Duplication
		if ( isset( $request['parent'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
468
			if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
469
				return new WP_Error( 'woocommerce_rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.', 'woocommerce' ), array( 'status' => 400 ) );
470
			}
471
472
			$parent = get_term( (int) $request['parent'], $taxonomy );
473
474
			if ( ! $parent ) {
475
				return new WP_Error( 'woocommerce_rest_term_invalid', __( "Parent resource doesn't exist.", 'woocommerce' ), array( 'status' => 400 ) );
476
			}
477
478
			$prepared_args['parent'] = $parent->term_id;
479
		}
480
481
		// Only update the term if we haz something to update.
482
		if ( ! empty( $prepared_args ) ) {
483
			$update = wp_update_term( $term->term_id, $term->taxonomy, $prepared_args );
484
			if ( is_wp_error( $update ) ) {
485
				return $update;
486
			}
487
		}
488
489
		$term = get_term( (int) $request['id'], $taxonomy );
490
491
		$this->update_additional_fields_for_object( $term, $request );
492
493
		// Update term data.
494
		$meta_fields = $this->update_term_meta_fields( $term, $request );
495
		if ( is_wp_error( $meta_fields ) ) {
496
			return $meta_fields;
497
		}
498
499
		/**
500
		 * Fires after a single term is created or updated via the REST API.
501
		 *
502
		 * @param WP_Term         $term      Inserted Term object.
503
		 * @param WP_REST_Request $request   Request object.
504
		 * @param boolean         $creating  True when creating term, false when updating.
505
		 */
506
		do_action( "woocommerce_rest_insert_{$taxonomy}", $term, $request, false );
507
508
		$request->set_param( 'context', 'edit' );
509
		$response = $this->prepare_item_for_response( $term, $request );
510
		return rest_ensure_response( $response );
511
	}
512
513
	/**
514
	 * Delete a single term from a taxonomy.
515
	 *
516
	 * @param WP_REST_Request $request Full details about the request.
517
	 * @return WP_REST_Response|WP_Error
518
	 */
519
	public function delete_item( $request ) {
520
		$taxonomy = $this->get_taxonomy( $request );
521
		$force    = isset( $request['force'] ) ? (bool) $request['force'] : false;
522
523
		// We don't support trashing for this type, error out.
524
		if ( ! $force ) {
525
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
526
		}
527
528
		$term = get_term( (int) $request['id'], $taxonomy );
529
		$request->set_param( 'context', 'edit' );
530
		$response = $this->prepare_item_for_response( $term, $request );
531
532
		$retval = wp_delete_term( $term->term_id, $term->taxonomy );
533
		if ( ! $retval ) {
534
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
535
		}
536
537
		/**
538
		 * Fires after a single term is deleted via the REST API.
539
		 *
540
		 * @param WP_Term          $term     The deleted term.
541
		 * @param WP_REST_Response $response The response data.
542
		 * @param WP_REST_Request  $request  The request sent to the API.
543
		 */
544
		do_action( "woocommerce_rest_delete_{$taxonomy}", $term, $response, $request );
545
546
		return $response;
547
	}
548
549
	/**
550
	 * Prepare links for the request.
551
	 *
552
	 * @param object $term Term object.
553
	 * @param WP_REST_Request $request Full details about the request.
554
	 * @return array Links for the given term.
555
	 */
556
	protected function prepare_links( $term, $request ) {
557
		$base = '/' . $this->namespace . '/' . $this->rest_base;
558
559 View Code Duplication
		if ( ! empty( $request['attribute_id'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
560
			$base = str_replace( '(?P<attribute_id>[\d]+)', (int) $request['attribute_id'], $base );
561
		}
562
563
		$links = array(
564
			'self' => array(
565
				'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
566
			),
567
			'collection' => array(
568
				'href' => rest_url( $base ),
569
			),
570
		);
571
572
		if ( $term->parent ) {
573
			$parent_term = get_term( (int) $term->parent, $term->taxonomy );
574
			if ( $parent_term ) {
575
				$links['up'] = array(
576
					'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
577
				);
578
			}
579
		}
580
581
		return $links;
582
	}
583
584
	/**
585
	 * Update term meta fields.
586
	 *
587
	 * @param WP_Term $term
588
	 * @param WP_REST_Request $request
589
	 * @return bool|WP_Error
590
	 */
591
	protected function update_term_meta_fields( $term, $request ) {
592
		return true;
593
	}
594
595
	/**
596
	 * Get the terms attached to a product.
597
	 *
598
	 * This is an alternative to `get_terms()` that uses `get_the_terms()`
599
	 * instead, which hits the object cache. There are a few things not
600
	 * supported, notably `include`, `exclude`. In `self::get_items()` these
601
	 * are instead treated as a full query.
602
	 *
603
	 * @param array $prepared_args Arguments for `get_terms()`.
604
	 * @return array List of term objects. (Total count in `$this->total_terms`).
605
	 */
606
	protected function get_terms_for_product( $prepared_args ) {
607
		$taxonomy = $this->get_taxonomy( $request );
608
609
		$query_result = get_the_terms( $prepared_args['product'], $taxonomy );
610
		if ( empty( $query_result ) ) {
611
			$this->total_terms = 0;
612
			return array();
613
		}
614
615
		// get_items() verifies that we don't have `include` set, and default.
616
		// ordering is by `name`.
617
		if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ) ) ) {
618
			switch ( $prepared_args['orderby'] ) {
619
				case 'id' :
620
					$this->sort_column = 'term_id';
621
					break;
622
623
				case 'slug' :
624
				case 'term_group' :
625
				case 'description' :
626
				case 'count' :
627
					$this->sort_column = $prepared_args['orderby'];
628
					break;
629
			}
630
			usort( $query_result, array( $this, 'compare_terms' ) );
631
		}
632
		if ( strtolower( $prepared_args['order'] ) !== 'asc' ) {
633
			$query_result = array_reverse( $query_result );
634
		}
635
636
		// Pagination.
637
		$this->total_terms = count( $query_result );
638
		$query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] );
639
640
		return $query_result;
641
	}
642
643
	/**
644
	 * Comparison function for sorting terms by a column.
645
	 *
646
	 * Uses `$this->sort_column` to determine field to sort by.
647
	 *
648
	 * @param stdClass $left Term object.
649
	 * @param stdClass $right Term object.
650
	 * @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left.
651
	 */
652
	protected function compare_terms( $left, $right ) {
653
		$col       = $this->sort_column;
654
		$left_val  = $left->$col;
655
		$right_val = $right->$col;
656
657
		if ( is_int( $left_val ) && is_int( $right_val ) ) {
658
			return $left_val - $right_val;
659
		}
660
661
		return strcmp( $left_val, $right_val );
662
	}
663
664
	/**
665
	 * Get the query params for collections
666
	 *
667
	 * @return array
668
	 */
669
	public function get_collection_params() {
670
		$params = parent::get_collection_params();
671
672
		if ( '' !== $this->taxonomy ) {
673
			$taxonomy = get_taxonomy( $this->taxonomy );
674
		} else {
675
			$taxonomy = new stdClass();
676
			$taxonomy->hierarchical = true;
677
		}
678
679
		$params['context']['default'] = 'view';
680
681
		$params['exclude'] = array(
682
			'description'        => __( 'Ensure result set excludes specific ids.', 'woocommerce' ),
683
			'type'               => 'array',
684
			'default'            => array(),
685
			'sanitize_callback'  => 'wp_parse_id_list',
686
		);
687
		$params['include'] = array(
688
			'description'        => __( 'Limit result set to specific ids.', 'woocommerce' ),
689
			'type'               => 'array',
690
			'default'            => array(),
691
			'sanitize_callback'  => 'wp_parse_id_list',
692
		);
693 View Code Duplication
		if ( ! $taxonomy->hierarchical ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
694
			$params['offset'] = array(
695
				'description'        => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
696
				'type'               => 'integer',
697
				'sanitize_callback'  => 'absint',
698
				'validate_callback'  => 'rest_validate_request_arg',
699
			);
700
		}
701
		$params['order']      = array(
702
			'description'           => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
703
			'type'                  => 'string',
704
			'sanitize_callback'     => 'sanitize_key',
705
			'default'               => 'asc',
706
			'enum'                  => array(
707
				'asc',
708
				'desc',
709
			),
710
			'validate_callback'     => 'rest_validate_request_arg',
711
		);
712
		$params['orderby']    = array(
713
			'description'           => __( 'Sort collection by resource attribute.', 'woocommerce' ),
714
			'type'                  => 'string',
715
			'sanitize_callback'     => 'sanitize_key',
716
			'default'               => 'name',
717
			'enum'                  => array(
718
				'id',
719
				'include',
720
				'name',
721
				'slug',
722
				'term_group',
723
				'description',
724
				'count',
725
			),
726
			'validate_callback'     => 'rest_validate_request_arg',
727
		);
728
		$params['hide_empty'] = array(
729
			'description'           => __( 'Whether to hide resources not assigned to any products.', 'woocommerce' ),
730
			'type'                  => 'boolean',
731
			'default'               => false,
732
			'validate_callback'     => 'rest_validate_request_arg',
733
		);
734 View Code Duplication
		if ( $taxonomy->hierarchical ) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
735
			$params['parent'] = array(
736
				'description'        => __( 'Limit result set to resources assigned to a specific parent.', 'woocommerce' ),
737
				'type'               => 'integer',
738
				'sanitize_callback'  => 'absint',
739
				'validate_callback'  => 'rest_validate_request_arg',
740
			);
741
		}
742
		$params['product'] = array(
743
			'description'           => __( 'Limit result set to resources assigned to a specific product.', 'woocommerce' ),
744
			'type'                  => 'integer',
745
			'default'               => null,
746
			'validate_callback'     => 'rest_validate_request_arg',
747
		);
748
		$params['slug']    = array(
749
			'description'        => __( 'Limit result set to resources with a specific slug.', 'woocommerce' ),
750
			'type'               => 'string',
751
			'validate_callback'  => 'rest_validate_request_arg',
752
		);
753
754
		return $params;
755
	}
756
757
	/**
758
	 * Get taxonomy.
759
	 *
760
	 * @param WP_REST_Request $request Full details about the request.
761
	 * @return int|WP_Error
762
	 */
763
	protected function get_taxonomy( $request ) {
764
		// Check if taxonomy is defined.
765
		// Prevents check for attribute taxonomy more than one time for each query.
766
		if ( '' !== $this->taxonomy ) {
767
			return $this->taxonomy;
768
		}
769
770
		if ( ! empty( $request['attribute_id'] ) ) {
771
			$taxonomy = wc_attribute_taxonomy_name_by_id( (int) $request['attribute_id'] );
772
773
			$this->taxonomy = $taxonomy;
774
		}
775
776
		return $this->taxonomy;
777
	}
778
}
779