Completed
Push — master ( 407421...cb6f6e )
by Mike
10:21
created

WC_REST_Taxes_Controller::bulk_items()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 41
Code Lines 23

Duplication

Lines 3
Ratio 7.32 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 5
eloc 23
c 2
b 0
f 1
nc 6
nop 1
dl 3
loc 41
rs 8.439
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * REST API Taxes controller
4
 *
5
 * Handles requests to the /taxes endpoint.
6
 *
7
 * @author   WooThemes
8
 * @category API
9
 * @package  WooCommerce/API
10
 * @since    2.6.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * REST API Taxes controller class.
19
 *
20
 * @package WooCommerce/API
21
 * @extends WP_REST_Controller
22
 */
23
class WC_REST_Taxes_Controller extends WP_REST_Controller {
24
25
	/**
26
	 * Endpoint namespace.
27
	 *
28
	 * @var string
29
	 */
30
	protected $namespace = 'wc/v1';
31
32
	/**
33
	 * Route base.
34
	 *
35
	 * @var string
36
	 */
37
	protected $rest_base = 'taxes';
38
39
	/**
40
	 * Register the routes for taxes.
41
	 */
42
	public function register_routes() {
43
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
44
			array(
45
				'methods'             => WP_REST_Server::READABLE,
46
				'callback'            => array( $this, 'get_items' ),
47
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
48
				'args'                => $this->get_collection_params(),
49
			),
50
			array(
51
				'methods'             => WP_REST_Server::CREATABLE,
52
				'callback'            => array( $this, 'create_item' ),
53
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
54
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
55
			),
56
			'schema' => array( $this, 'get_public_item_schema' ),
57
		) );
58
59
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
60
			array(
61
				'methods'             => WP_REST_Server::READABLE,
62
				'callback'            => array( $this, 'get_item' ),
63
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
64
				'args'                => array(
65
					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
66
				),
67
			),
68
			array(
69
				'methods'             => WP_REST_Server::EDITABLE,
70
				'callback'            => array( $this, 'update_item' ),
71
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
72
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
73
			),
74
			array(
75
				'methods'             => WP_REST_Server::DELETABLE,
76
				'callback'            => array( $this, 'delete_item' ),
77
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
78
				'args'                => array(
79
					'force' => array(
80
						'default'     => false,
81
						'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
82
					),
83
				),
84
			),
85
			'schema' => array( $this, 'get_public_item_schema' ),
86
		) );
87
88
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/update_items', array(
89
			array(
90
				'methods'             => WP_REST_Server::EDITABLE,
91
				'callback'            => array( $this, 'update_items' ),
92
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
93
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
94
			),
95
			'schema' => array( $this, 'get_public_item_schema' ),
96
		) );
97
	}
98
99
	/**
100
	 * Check whether a given request has permission to read taxes.
101
	 *
102
	 * @param  WP_REST_Request $request Full details about the request.
103
	 * @return WP_Error|boolean
104
	 */
105 View Code Duplication
	public function get_items_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
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...
106
		if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
107
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list taxes.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
108
		}
109
110
		return true;
111
	}
112
113
	/**
114
	 * Check if a given request has access create taxes.
115
	 *
116
	 * @param  WP_REST_Request $request Full details about the request.
117
	 * @return boolean
118
	 */
119 View Code Duplication
	public function create_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
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...
120
		if ( ! wc_rest_check_manager_permissions( 'settings', 'create' ) ) {
121
			return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you are not allowed to create resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
122
		}
123
124
		return true;
125
	}
126
127
	/**
128
	 * Check if a given request has access to read a tax.
129
	 *
130
	 * @param  WP_REST_Request $request Full details about the request.
131
	 * @return WP_Error|boolean
132
	 */
133 View Code Duplication
	public function get_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
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...
134
		if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) {
135
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
136
		}
137
138
		return true;
139
	}
140
141
	/**
142
	 * Check if a given request has access update a tax.
143
	 *
144
	 * @param  WP_REST_Request $request Full details about the request.
145
	 * @return boolean
146
	 */
147 View Code Duplication
	public function update_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
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...
148
		if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
149
			return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Sorry, you are not allowed to edit this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
150
		}
151
152
		return true;
153
	}
154
155
	/**
156
	 * Check if a given request has access delete a tax.
157
	 *
158
	 * @param  WP_REST_Request $request Full details about the request.
159
	 * @return boolean
160
	 */
161 View Code Duplication
	public function delete_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
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...
162
		if ( ! wc_rest_check_manager_permissions( 'settings', 'delete' ) ) {
163
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
164
		}
165
166
		return true;
167
	}
168
169
	/**
170
	 * Get all taxes.
171
	 *
172
	 * @param WP_REST_Request $request Full details about the request.
173
	 * @return WP_Error|WP_REST_Response
174
	 */
175
	public function get_items( $request ) {
176
		global $wpdb;
177
178
		$prepared_args = array();
179
		$prepared_args['exclude'] = $request['exclude'];
180
		$prepared_args['include'] = $request['include'];
181
		$prepared_args['order']   = $request['order'];
182
		$prepared_args['number']  = $request['per_page'];
183 View Code Duplication
		if ( ! empty( $request['offset'] ) ) {
0 ignored issues
show
Duplication introduced by
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...
184
			$prepared_args['offset'] = $request['offset'];
185
		} else {
186
			$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
187
		}
188
		$orderby_possibles = array(
189
			'id'    => 'tax_rate_id',
190
			'order' => 'tax_rate_order',
191
		);
192
		$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
193
		$prepared_args['class']   = $request['class'];
194
195
		/**
196
		 * Filter arguments, before passing to $wpdb->get_results(), when querying taxes via the REST API.
197
		 *
198
		 * @param array           $prepared_args Array of arguments for $wpdb->get_results().
199
		 * @param WP_REST_Request $request       The current request.
200
		 */
201
		$prepared_args = apply_filters( 'woocommerce_rest_tax_query', $prepared_args, $request );
202
203
		$query = "
204
			SELECT *
205
			FROM {$wpdb->prefix}woocommerce_tax_rates
206
			WHERE 1 = 1
207
		";
208
209
		// Filter by tax class.
210 View Code Duplication
		if ( ! empty( $prepared_args['class'] ) ) {
0 ignored issues
show
Duplication introduced by
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...
211
			$class = 'standard' !== $prepared_args['class'] ? sanitize_title( $prepared_args['class'] ) : '';
212
			$query .= " AND tax_rate_class = '$class'";
213
		}
214
215
		// Order tax rates.
216
		$order_by = sprintf( ' ORDER BY %s', sanitize_key( $prepared_args['orderby'] ) );
217
218
		// Pagination.
219
		$pagination = sprintf( ' LIMIT %d, %d', $prepared_args['offset'], $prepared_args['number'] );
220
221
		// Query taxes.
222
		$results = $wpdb->get_results( $query . $order_by . $pagination );
223
224
		$taxes = array();
225
		foreach ( $results as $tax ) {
226
			$data = $this->prepare_item_for_response( $tax, $request );
227
			$taxes[] = $this->prepare_response_for_collection( $data );
228
		}
229
230
		$response = rest_ensure_response( $taxes );
231
232
		// Store pagation values for headers then unset for count query.
233
		$per_page = (int) $prepared_args['number'];
234
		$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
235
236
		// Query only for ids.
237
		$wpdb->get_results( str_replace( 'SELECT *', 'SELECT tax_rate_id', $query ) );
238
239
		// Calcule totals.
240
		$total_taxes = (int) $wpdb->num_rows;
241
		$response->header( 'X-WP-Total', (int) $total_taxes );
242
		$max_pages = ceil( $total_taxes / $per_page );
243
		$response->header( 'X-WP-TotalPages', (int) $max_pages );
244
245
		$base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) );
246 View Code Duplication
		if ( $page > 1 ) {
0 ignored issues
show
Duplication introduced by
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...
247
			$prev_page = $page - 1;
248
			if ( $prev_page > $max_pages ) {
249
				$prev_page = $max_pages;
250
			}
251
			$prev_link = add_query_arg( 'page', $prev_page, $base );
252
			$response->link_header( 'prev', $prev_link );
253
		}
254 View Code Duplication
		if ( $max_pages > $page ) {
0 ignored issues
show
Duplication introduced by
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...
255
			$next_page = $page + 1;
256
			$next_link = add_query_arg( 'page', $next_page, $base );
257
			$response->link_header( 'next', $next_link );
258
		}
259
260
		return $response;
261
	}
262
263
	/**
264
	 * Take tax data from the request and return the updated or newly created rate.
265
	 * @todo Replace with CRUD in 2.7.0
266
	 * @param  array $request
267
	 * @return object
268
	 */
269
	protected function create_or_update_tax( $request ) {
270
		$id          = absint( isset( $request['id'] ) ? $request['id'] : 0 );
271
		$current_tax = $id ? WC_Tax::_get_tax_rate( $id, OBJECT ) : false;
272
		$data        = array();
273
		$fields        = array(
274
			'tax_rate_country',
275
			'tax_rate_state',
276
			'tax_rate',
277
			'tax_rate_name',
278
			'tax_rate_priority',
279
			'tax_rate_compound',
280
			'tax_rate_shipping',
281
			'tax_rate_order',
282
			'tax_rate_class',
283
		);
284
285
		foreach ( $fields as $key ) {
286
			// Remove data that was not posted.
287
			if ( ! isset( $request[ $key ] ) ) {
288
				continue;
289
			}
290
291
			// Test new data against current data.
292
			if ( $current_tax && $current_tax->$key === $request[ $key ] ) {
293
				continue;
294
			}
295
296
			// Add to data array
297
			switch ( $key ) {
298
				case 'tax_rate_priority' :
299
				case 'tax_rate_compound' :
300
				case 'tax_rate_shipping' :
301
				case 'tax_rate_order' :
302
					$data[ $key ] = absint( $request[ $key ] );
303
					break;
304
				case 'tax_rate_class' :
305
					$data[ $key ] = 'standard' !== $request['tax_rate_class'] ? $request['tax_rate_class'] : '';
306
					break;
307
				default :
308
					$data[ $key ] = wc_clean( $request[ $key ] );
309
					break;
310
			}
311
		}
312
313
		if ( $id ) {
314
			WC_Tax::_update_tax_rate( $id, $data );
315
		} else {
316
			$id = WC_Tax::_insert_tax_rate( $data );
317
		}
318
319
		// Add locales.
320
		if ( ! empty( $request['postcode'] ) ) {
321
			WC_Tax::_update_tax_rate_postcodes( $id, wc_clean( $request['postcode'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($request['postcode']) targeting wc_clean() can also be of type array; however, WC_Tax::_update_tax_rate_postcodes() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
322
		}
323
		if ( ! empty( $request['city'] ) ) {
324
			WC_Tax::_update_tax_rate_cities( $id, wc_clean( $request['city'] ) );
1 ignored issue
show
Bug introduced by
It seems like wc_clean($request['city']) targeting wc_clean() can also be of type array; however, WC_Tax::_update_tax_rate_cities() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
325
		}
326
327
		return WC_Tax::_get_tax_rate( $id, OBJECT );
328
	}
329
330
	/**
331
	 * Create a single tax.
332
	 *
333
	 * @param WP_REST_Request $request Full details about the request.
334
	 * @return WP_Error|WP_REST_Response
335
	 */
336
	public function create_item( $request ) {
337
		if ( ! empty( $request['id'] ) ) {
338
			return new WP_Error( 'woocommerce_rest_tax_exists', __( 'Cannot create existing resource.', 'woocommerce' ), array( 'status' => 400 ) );
339
		}
340
341
		$tax = $this->create_or_update_tax( $request );
342
343
		$this->update_additional_fields_for_object( $tax, $request );
344
345
		/**
346
		 * Fires after a tax is created or updated via the REST API.
347
		 *
348
		 * @param stdClass        $tax       Data used to create the tax.
349
		 * @param WP_REST_Request $request   Request object.
350
		 * @param boolean         $creating  True when creating tax, false when updating tax.
351
		 */
352
		do_action( 'woocommerce_rest_insert_tax', $tax, $request, true );
353
354
		$request->set_param( 'context', 'edit' );
355
		$response = $this->prepare_item_for_response( $tax, $request );
0 ignored issues
show
Documentation introduced by
$tax is of type array, but the function expects a object<stdClass>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
356
		$response = rest_ensure_response( $response );
357
		$response->set_status( 201 );
358
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
359
360
		return $response;
361
	}
362
363
	/**
364
	 * Get a single tax.
365
	 *
366
	 * @param WP_REST_Request $request Full details about the request.
367
	 * @return WP_Error|WP_REST_Response
368
	 */
369 View Code Duplication
	public function get_item( $request ) {
0 ignored issues
show
Duplication introduced by
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...
370
		$id       = (int) $request['id'];
371
		$tax_obj = WC_Tax::_get_tax_rate( $id, OBJECT );
372
373
		if ( empty( $id ) || empty( $tax_obj ) ) {
374
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 404 ) );
375
		}
376
377
		$tax = $this->prepare_item_for_response( $tax_obj, $request );
0 ignored issues
show
Documentation introduced by
$tax_obj is of type array, but the function expects a object<stdClass>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
378
		$response = rest_ensure_response( $tax );
379
380
		return $response;
381
	}
382
383
	/**
384
	 * Update a single tax.
385
	 *
386
	 * @param WP_REST_Request $request Full details about the request.
387
	 * @return WP_Error|WP_REST_Response
388
	 */
389
	public function update_item( $request ) {
390
		$tax = $this->create_or_update_tax( $request );
391
392
		$this->update_additional_fields_for_object( $tax, $request );
393
394
		/**
395
		 * Fires after a tax is created or updated via the REST API.
396
		 *
397
		 * @param stdClass        $tax       Data used to create the tax.
398
		 * @param WP_REST_Request $request   Request object.
399
		 * @param boolean         $creating  True when creating tax, false when updating tax.
400
		 */
401
		do_action( 'woocommerce_rest_insert_tax', $tax, $request, false );
402
403
		$request->set_param( 'context', 'edit' );
404
		$response = $this->prepare_item_for_response( $tax, $request );
0 ignored issues
show
Documentation introduced by
$tax is of type array, but the function expects a object<stdClass>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
405
		$response = rest_ensure_response( $response );
406
407
		return $response;
408
	}
409
410
	/**
411
	 * Delete a single tax.
412
	 *
413
	 * @param WP_REST_Request $request Full details about the request.
414
	 * @return WP_Error|WP_REST_Response
415
	 */
416
	public function delete_item( $request ) {
417
		global $wpdb;
418
419
		$id    = (int) $request['id'];
420
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
421
422
		// We don't support trashing for this type, error out.
423
		if ( ! $force ) {
424
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Taxes do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
425
		}
426
427
		$tax = WC_Tax::_get_tax_rate( $id, OBJECT );
428
429
		if ( empty( $id ) || empty( $tax ) ) {
430
			return new WP_Error( 'woocommerce_rest_invalid_id', __( 'Invalid resource id.', 'woocommerce' ), array( 'status' => 400 ) );
431
		}
432
433
		$request->set_param( 'context', 'edit' );
434
		$response = $this->prepare_item_for_response( $tax, $request );
0 ignored issues
show
Documentation introduced by
$tax is of type array, but the function expects a object<stdClass>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
435
436
		WC_Tax::_delete_tax_rate( $id );
437
438
		if ( 0 === $wpdb->rows_affected ) {
439
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
440
		}
441
442
		/**
443
		 * Fires after a tax is deleted via the REST API.
444
		 *
445
		 * @param stdClass         $tax      The tax data.
446
		 * @param WP_REST_Response $response The response returned from the API.
447
		 * @param WP_REST_Request  $request  The request sent to the API.
448
		 */
449
		do_action( 'woocommerce_rest_delete_tax', $tax, $response, $request );
450
451
		return $response;
452
	}
453
454
	/**
455
	 * Prepare a single tax output for response.
456
	 *
457
	 * @param stdClass $tax Tax object.
458
	 * @param WP_REST_Request $request Request object.
459
	 * @return WP_REST_Response $response Response data.
460
	 */
461
	public function prepare_item_for_response( $tax, $request ) {
462
		global $wpdb;
463
464
		$id   = (int) $tax->tax_rate_id;
465
		$data = array(
466
			'id'       => $id,
467
			'country'  => $tax->tax_rate_country,
468
			'state'    => $tax->tax_rate_state,
469
			'postcode' => '',
470
			'city'     => '',
471
			'rate'     => $tax->tax_rate,
472
			'name'     => $tax->tax_rate_name,
473
			'priority' => (int) $tax->tax_rate_priority,
474
			'compound' => (bool) $tax->tax_rate_compound,
475
			'shipping' => (bool) $tax->tax_rate_shipping,
476
			'order'    => (int) $tax->tax_rate_order,
477
			'class'    => $tax->tax_rate_class ? $tax->tax_rate_class : 'standard',
478
		);
479
480
		// Get locales from a tax rate.
481
		$locales = $wpdb->get_results( $wpdb->prepare( "
482
			SELECT location_code, location_type
483
			FROM {$wpdb->prefix}woocommerce_tax_rate_locations
484
			WHERE tax_rate_id = %d
485
		", $id ) );
486
487 View Code Duplication
		if ( ! is_wp_error( $tax ) && ! is_null( $tax ) ) {
0 ignored issues
show
Duplication introduced by
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...
488
			foreach ( $locales as $locale ) {
489
				$data[ $locale->location_type ] = $locale->location_code;
490
			}
491
		}
492
493
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
494
		$data    = $this->add_additional_fields_to_object( $data, $request );
495
		$data    = $this->filter_response_by_context( $data, $context );
496
497
		// Wrap the data in a response object.
498
		$response = rest_ensure_response( $data );
499
500
		$response->add_links( $this->prepare_links( $tax ) );
501
502
		/**
503
		 * Filter tax object returned from the REST API.
504
		 *
505
		 * @param WP_REST_Response $response The response object.
506
		 * @param stdClass         $tax      Tax object used to create response.
507
		 * @param WP_REST_Request  $request  Request object.
508
		 */
509
		return apply_filters( 'woocommerce_rest_prepare_tax', $response, $tax, $request );
510
	}
511
512
	/**
513
	 * Prepare links for the request.
514
	 *
515
	 * @param stdClass $tax Tax object.
516
	 * @return array Links for the given tax.
517
	 */
518 View Code Duplication
	protected function prepare_links( $tax ) {
0 ignored issues
show
Duplication introduced by
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...
519
		$links = array(
520
			'self' => array(
521
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $tax->tax_rate_id ) ),
522
			),
523
			'collection' => array(
524
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
525
			),
526
		);
527
528
		return $links;
529
	}
530
531
	/**
532
	 * Bulk update or create items.
533
	 *
534
	 * @param WP_REST_Request $request Full details about the request.
535
	 * @return array Of WP_Error or WP_REST_Response.
536
	 */
537
	public function update_items( $request ) {
538
		/** @var WP_REST_Server $wp_rest_server */
539
		global $wp_rest_server;
540
541
		// Get the request params.
542
		$items = array_filter( $request->get_params() );
543
544
		// Limit bulk operation.
545
		$limit = apply_filters( 'woocommerce_rest_bulk_items_limit', 100, 'taxes' );
546 View Code Duplication
		if ( count( $items ) > $limit ) {
0 ignored issues
show
Duplication introduced by
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...
547
			return new WP_Error( 'woocommerce_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'woocommerce' ), $limit ), array( 'status' => 413 ) );
548
		}
549
550
		$response = array();
551
552
		foreach ( $items as $item ) {
553
			// Item exists.
554
			if ( ! empty( $item['id'] ) ) {
555
				$_item = new WP_REST_Request( 'PUT' );
556
				$_item->set_body_params( $item );
557
				$_response = $this->update_item( $_item );
558
559
			// Item does not exist.
560
			} else {
561
				$_item  = new WP_REST_Request( 'POST' );
562
				$_item->set_body_params( $item );
563
				$_response = $this->create_item( $_item );
564
			}
565
566
			if ( is_wp_error( $_response ) ) {
567
				$response[] = array(
568
					'id'    => $item['id'],
569
					'error' => array( 'code' => $_response->get_error_code(), 'message' => $_response->get_error_message(), 'data' => $_response->get_error_data() ),
570
				);
571
			} else {
572
				$response[] = $wp_rest_server->response_to_data( $_response, '' );
573
			}
574
		}
575
576
		return $response;
577
	}
578
579
	/**
580
	 * Get the Taxes schema, conforming to JSON Schema.
581
	 *
582
	 * @return array
583
	 */
584
	public function get_item_schema() {
585
		$schema = array(
586
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
587
			'title'      => 'tax',
588
			'type'       => 'object',
589
			'properties' => array(
590
				'id' => array(
591
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
592
					'type'        => 'integer',
593
					'context'     => array( 'view', 'edit' ),
594
					'readonly'    => true,
595
				),
596
				'country' => array(
597
					'description' => __( 'Country ISO 3166 code.', 'woocommerce' ),
598
					'type'        => 'string',
599
					'context'     => array( 'view', 'edit' ),
600
				),
601
				'state' => array(
602
					'description' => __( 'State code.', 'woocommerce' ),
603
					'type'        => 'string',
604
					'context'     => array( 'view', 'edit' ),
605
				),
606
				'postcode' => array(
607
					'description' => __( 'Postcode/ZIP.', 'woocommerce' ),
608
					'type'        => 'string',
609
					'context'     => array( 'view', 'edit' ),
610
				),
611
				'city' => array(
612
					'description' => __( 'City name.', 'woocommerce' ),
613
					'type'        => 'string',
614
					'context'     => array( 'view', 'edit' ),
615
				),
616
				'rate' => array(
617
					'description' => __( 'Tax rate.', 'woocommerce' ),
618
					'type'        => 'float',
619
					'context'     => array( 'view', 'edit' ),
620
				),
621
				'name' => array(
622
					'description' => __( 'Tax rate name.', 'woocommerce' ),
623
					'type'        => 'string',
624
					'context'     => array( 'view', 'edit' ),
625
				),
626
				'priority' => array(
627
					'description' => __( 'Tax priority.', 'woocommerce' ),
628
					'type'        => 'integer',
629
					'default'     => 1,
630
					'context'     => array( 'view', 'edit' ),
631
				),
632
				'compound' => array(
633
					'description' => __( 'Whether or not this is a compound rate.', 'woocommerce' ),
634
					'type'        => 'boolean',
635
					'default'     => false,
636
					'context'     => array( 'view', 'edit' ),
637
				),
638
				'shipping' => array(
639
					'description' => __( 'Whether or not this tax rate also gets applied to shipping.', 'woocommerce' ),
640
					'type'        => 'boolean',
641
					'default'     => true,
642
					'context'     => array( 'view', 'edit' ),
643
				),
644
				'order' => array(
645
					'description' => __( 'Indicates the order that will appear in queries.', 'woocommerce' ),
646
					'type'        => 'integer',
647
					'context'     => array( 'view', 'edit' ),
648
				),
649
				'class' => array(
650
					'description' => __( 'Tax class.', 'woocommerce' ),
651
					'type'        => 'string',
652
					'default'     => 'standard',
653
					'enum'        => array_merge( array( 'standard' ), array_map( 'sanitize_title', WC_Tax::get_tax_classes() ) ),
654
					'context'     => array( 'view', 'edit' ),
655
				),
656
			),
657
		);
658
659
		return $this->add_additional_fields_schema( $schema );
660
	}
661
662
	/**
663
	 * Get the query params for collections.
664
	 *
665
	 * @return array
666
	 */
667
	public function get_collection_params() {
668
		$params = parent::get_collection_params();
669
670
		$params['context']['default'] = 'view';
671
672
		$params['exclude'] = array(
673
			'description'        => __( 'Ensure result set excludes specific ids.', 'woocommerce' ),
674
			'type'               => 'array',
675
			'default'            => array(),
676
			'sanitize_callback'  => 'wp_parse_id_list',
677
		);
678
		$params['include'] = array(
679
			'description'        => __( 'Limit result set to specific ids.', 'woocommerce' ),
680
			'type'               => 'array',
681
			'default'            => array(),
682
			'sanitize_callback'  => 'wp_parse_id_list',
683
		);
684
		$params['offset'] = array(
685
			'description'        => __( 'Offset the result set by a specific number of items.', 'woocommerce' ),
686
			'type'               => 'integer',
687
			'sanitize_callback'  => 'absint',
688
			'validate_callback'  => 'rest_validate_request_arg',
689
		);
690
		$params['order'] = array(
691
			'default'            => 'asc',
692
			'description'        => __( 'Order sort attribute ascending or descending.', 'woocommerce' ),
693
			'enum'               => array( 'asc', 'desc' ),
694
			'sanitize_callback'  => 'sanitize_key',
695
			'type'               => 'string',
696
			'validate_callback'  => 'rest_validate_request_arg',
697
		);
698
		$params['orderby'] = array(
699
			'default'            => 'order',
700
			'description'        => __( 'Sort collection by object attribute.', 'woocommerce' ),
701
			'enum'               => array(
702
				'id',
703
				'order',
704
			),
705
			'sanitize_callback'  => 'sanitize_key',
706
			'type'               => 'string',
707
			'validate_callback'  => 'rest_validate_request_arg',
708
		);
709
		$params['class'] = array(
710
			'description'        => __( 'Sort by tax class.', 'woocommerce' ),
711
			'enum'               => array_merge( array( 'standard' ), array_map( 'sanitize_title', WC_Tax::get_tax_classes() ) ),
712
			'sanitize_callback'  => 'sanitize_title',
713
			'type'               => 'string',
714
			'validate_callback'  => 'rest_validate_request_arg',
715
		);
716
717
		return $params;
718
	}
719
}
720