Completed
Pull Request — master (#11716)
by Mike
09:22
created

WC_REST_Orders_Controller::update_order()   B

Complexity

Conditions 5
Paths 14

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 3
b 0
f 0
nc 14
nop 1
dl 0
loc 20
rs 8.8571
1
<?php
2
/**
3
 * REST API Orders controller
4
 *
5
 * Handles requests to the /orders 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 Orders controller class.
19
 *
20
 * @package WooCommerce/API
21
 * @extends WC_REST_Posts_Controller
22
 */
23
class WC_REST_Orders_Controller extends WC_REST_Posts_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 = 'orders';
38
39
	/**
40
	 * Post type.
41
	 *
42
	 * @var string
43
	 */
44
	protected $post_type = 'shop_order';
45
46
	/**
47
	 * DP for rounding.
48
	 *
49
	 * @var int
50
	 */
51
	protected $dp = '2';
52
53
	/**
54
	 * Stores the request.
55
	 * @var array
56
	 */
57
	protected $request = array();
58
59
	/**
60
	 * Initialize orders actions.
61
	 */
62
	public function __construct() {
63
		add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 );
64
	}
65
66
	/**
67
	 * Register the routes for orders.
68
	 */
69
	public function register_routes() {
70
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
71
			array(
72
				'methods'             => WP_REST_Server::READABLE,
73
				'callback'            => array( $this, 'get_items' ),
74
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
75
				'args'                => $this->get_collection_params(),
76
			),
77
			array(
78
				'methods'             => WP_REST_Server::CREATABLE,
79
				'callback'            => array( $this, 'create_item' ),
80
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
81
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
82
			),
83
			'schema' => array( $this, 'get_public_item_schema' ),
84
		) );
85
86
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
87
			array(
88
				'methods'             => WP_REST_Server::READABLE,
89
				'callback'            => array( $this, 'get_item' ),
90
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
91
				'args'                => array(
92
					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
93
				),
94
			),
95
			array(
96
				'methods'             => WP_REST_Server::EDITABLE,
97
				'callback'            => array( $this, 'update_item' ),
98
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
99
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
100
			),
101
			array(
102
				'methods'             => WP_REST_Server::DELETABLE,
103
				'callback'            => array( $this, 'delete_item' ),
104
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
105
				'args'                => array(
106
					'force' => array(
107
						'default'     => false,
108
						'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
109
					),
110
					'reassign' => array(),
111
				),
112
			),
113
			'schema' => array( $this, 'get_public_item_schema' ),
114
		) );
115
116
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
117
			array(
118
				'methods'             => WP_REST_Server::EDITABLE,
119
				'callback'            => array( $this, 'batch_items' ),
120
				'permission_callback' => array( $this, 'batch_items_permissions_check' ),
121
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
122
			),
123
			'schema' => array( $this, 'get_public_batch_schema' ),
124
		) );
125
	}
126
127
	/**
128
	 * Expands an order item to get its data.
129
	 * @return array
130
	 */
131
	protected function get_order_item_data( $item ) {
132
		$data           = $item->get_data();
133
		$format_decimal = array( 'subtotal', 'subtotal_tax', 'total', 'total_tax', 'tax_total', 'shipping_tax_total' );
134
135
		// Format decimal values
136 View Code Duplication
		foreach ( $format_decimal as $key ) {
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...
137
			if ( isset( $data[ $key ] ) ) {
138
				$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
139
			}
140
		}
141
142
		// Add meta, SKU and PRICE to products
143
		if ( is_callable( array( $item, 'get_product' ) ) ) {
144
			$data['sku']   = $item->get_product() ? $item->get_product()->get_sku(): null;
145
			$data['price'] = $item->get_total() / max( 1, $item->get_quantity() );
146
147
			// Format meta data
148
			if ( isset( $data['meta_data'] ) ) {
149
				$hideprefix = 'true' === $this->request['all_item_meta'] ? null : '_';
150
				$item_meta  = $item->get_formatted_meta_data( $hideprefix );
151
152
				foreach ( $item_meta as $key => $values ) {
153
					// Label was used in previous version of API - set it here
154
					$item_meta[ $key ]->label = $values->display_key;
155
					unset( $item_meta[ $key ]->display_key );
156
					unset( $item_meta[ $key ]->display_value );
157
				}
158
159
				$data['meta'] = array_values( $item_meta );
160
			}
161
		}
162
163
		// Format taxes
164
		if ( ! empty( $data['taxes']['total'] ) ) {
165
			$taxes = array();
166
167
			foreach ( $data['taxes']['total'] as $tax_rate_id => $tax ) {
168
				$taxes[] = array(
169
					'id'       => $tax_rate_id,
170
					'total'    => $tax,
171
					'subtotal' => isset( $data['taxes']['subtotal'][ $tax_rate_id ] ) ? $data['taxes']['subtotal'][ $tax_rate_id ] : '',
172
				);
173
			}
174
			$data['taxes'] = $taxes;
175
		} elseif ( isset( $data['taxes'] ) ) {
176
			$data['taxes'] = array();
177
		}
178
179
		// Remove props we don't want to expose.
180
		unset( $data['order_id'] );
181
		unset( $data['type'] );
182
183
		return $data;
184
	}
185
186
	/**
187
	 * Prepare a single order output for response.
188
	 *
189
	 * @param WP_Post $post Post object.
190
	 * @param WP_REST_Request $request Request object.
191
	 * @return WP_REST_Response $data
192
	 */
193
	public function prepare_item_for_response( $post, $request ) {
194
		$this->request     = $request;
0 ignored issues
show
Documentation Bug introduced by
It seems like $request of type object<WP_REST_Request> is incompatible with the declared type array of property $request.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
195
		$statuses          = wc_get_order_statuses();
0 ignored issues
show
Unused Code introduced by
$statuses is not used, you could remove the assignment.

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

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

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

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

Loading history...
196
		$order             = wc_get_order( $post );
197
		$data              = $order->get_data();
198
		$format_decimal    = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' );
199
		$format_date       = array( 'date_created', 'date_modified', 'date_completed' );
200
		$format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' );
201
202
		// Format decimal values
203 View Code Duplication
		foreach ( $format_decimal as $key ) {
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...
204
			$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
205
		}
206
207
		// Format date values
208
		foreach ( $format_date as $key ) {
209
			$data[ $key ] = $data[ $key ] ? wc_rest_prepare_date_response( get_gmt_from_date( date( 'Y-m-d H:i:s', $data[ $key ] ) ) ) : false;
210
		}
211
212
		// Format the order status
213
		$data['status'] = 'wc-' === substr( $data['status'], 0, 3 ) ? substr( $data['status'], 3 ) : $data['status'];
214
215
		// Format line items
216
		foreach ( $format_line_items as $key ) {
217
			$data[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $data[ $key ] ) );
218
		}
219
220
		// Refunds
221
		foreach ( $order->get_refunds() as $refund ) {
222
			$data['refunds'][] = array(
223
				'id'     => $refund->id,
224
				'refund' => $refund->get_refund_reason() ? $refund->get_refund_reason() : '',
225
				'total'  => '-' . wc_format_decimal( $refund->get_refund_amount(), $this->request['dp'] ),
226
			);
227
		}
228
229
		$context  = ! empty( $request['context'] ) ? $request['context'] : 'view';
230
		$data     = $this->add_additional_fields_to_object( $data, $request );
231
		$data     = $this->filter_response_by_context( $data, $context );
232
		$response = rest_ensure_response( $data );
233
		$response->add_links( $this->prepare_links( $order ) );
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

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...
234
235
		/**
236
		 * Filter the data for a response.
237
		 *
238
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
239
		 * prepared for the response.
240
		 *
241
		 * @param WP_REST_Response   $response   The response object.
242
		 * @param WP_Post            $post       Post object.
243
		 * @param WP_REST_Request    $request    Request object.
244
		 */
245
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request );
246
	}
247
248
	/**
249
	 * Prepare links for the request.
250
	 *
251
	 * @param WC_Order $order Order object.
252
	 * @return array Links for the given order.
253
	 */
254
	protected function prepare_links( $order ) {
255
		$links = array(
256
			'self' => array(
257
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $order->get_id() ) ),
258
			),
259
			'collection' => array(
260
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
261
			),
262
		);
263 View Code Duplication
		if ( 0 !== (int) $order->get_user_id() ) {
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...
264
			$links['customer'] = array(
265
				'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $order->get_user_id() ) ),
266
			);
267
		}
268 View Code Duplication
		if ( 0 !== (int) $order->get_parent_id() ) {
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...
269
			$links['up'] = array(
270
				'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $order->get_parent_id() ) ),
271
			);
272
		}
273
		return $links;
274
	}
275
276
	/**
277
	 * Query args.
278
	 *
279
	 * @param array $args
280
	 * @param WP_REST_Request $request
281
	 * @return array
282
	 */
283
	public function query_args( $args, $request ) {
284
		global $wpdb;
285
286
		// Set post_status.
287
		if ( 'any' !== $request['status'] ) {
288
			$args['post_status'] = 'wc-' . $request['status'];
289
		} else {
290
			$args['post_status'] = 'any';
291
		}
292
293 View Code Duplication
		if ( ! empty( $request['customer'] ) ) {
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...
294
			if ( ! empty( $args['meta_query'] ) ) {
295
				$args['meta_query'] = array();
296
			}
297
298
			$args['meta_query'][] = array(
299
				'key'   => '_customer_user',
300
				'value' => $request['customer'],
301
				'type'  => 'NUMERIC',
302
			);
303
		}
304
305
		// Search by product.
306
		if ( ! empty( $request['product'] ) ) {
307
			$order_ids = $wpdb->get_col( $wpdb->prepare( "
308
				SELECT order_id
309
				FROM {$wpdb->prefix}woocommerce_order_items
310
				WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
311
				AND order_item_type = 'line_item'
312
			 ", $request['product'] ) );
313
314
			// Force WP_Query return empty if don't found any order.
315
			$order_ids = ! empty( $order_ids ) ? $order_ids : array( 0 );
316
317
			$args['post__in'] = $order_ids;
318
		}
319
320
		// Search.
321
		if ( ! empty( $args['s'] ) ) {
322
			$order_ids = wc_order_search( $args['s'] );
0 ignored issues
show
Bug introduced by
It seems like $args['s'] can also be of type array; however, wc_order_search() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
323
324
			if ( ! empty( $order_ids ) ) {
325
				unset( $args['s'] );
326
				$args['post__in'] =  array_merge( $order_ids, array( 0 ) );
327
			}
328
		}
329
330
		return $args;
331
	}
332
333
	/**
334
	 * Prepare a single order for create.
335
	 *
336
	 * @param  WP_REST_Request $request Request object.
337
	 * @return WP_Error|WC_Order $data Object.
338
	 */
339
	protected function prepare_item_for_database( $request ) {
340
		$id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
341
		$order     = new WC_Order( $id );
342
		$schema    = $this->get_item_schema();
343
		$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
344
345
		// Handle all writable props
346
		foreach ( $data_keys as $key ) {
347
			$value = $request[ $key ];
348
349
			if ( ! is_null( $value ) ) {
350
				switch ( $key ) {
351
					case 'billing' :
352
					case 'shipping' :
353
						$this->update_address( $order, $value, $key );
354
						break;
355
					case 'line_items' :
356
					case 'shipping_lines' :
357
					case 'fee_lines' :
358
					case 'coupon_lines' :
359
						if ( is_array( $value ) ) {
360
							foreach ( $value as $item ) {
361
								if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
362
									$order->remove_item( $item['id'] );
363
								} else {
364
									$this->set_item( $order, $key, $item );
365
								}
366
							}
367
						}
368
						break;
369
					case 'meta_data' :
370
						if ( is_array( $value ) ) {
371
							foreach ( $value as $meta ) {
372
								$order->update_meta_data( $meta['key'], $meta['value'], $meta['meta_id'] );
373
							}
374
						}
375
						break;
376
					default :
377
						if ( is_callable( array( $order, "set_{$key}" ) ) ) {
378
							$order->{"set_{$key}"}( $value );
379
						}
380
						break;
381
				}
382
			}
383
		}
384
385
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request );
386
	}
387
388
	/**
389
	 * Create base WC Order object.
390
	 * @deprecated 2.7.0
391
	 * @param array $data
392
	 * @return WC_Order
393
	 */
394
	protected function create_base_order( $data ) {
395
		return wc_create_order( $data );
396
	}
397
398
	/**
399
	 * Only reutrn writeable props from schema.
400
	 * @param  array $schema
401
	 * @return bool
402
	 */
403
	protected function filter_writable_props( $schema ) {
404
		return empty( $schema['readonly'] );
405
	}
406
407
	/**
408
	 * Create order.
409
	 *
410
	 * @param WP_REST_Request $request Full details about the request.
411
	 * @return int|WP_Error
412
	 */
413
	protected function create_order( $request ) {
414
		try {
415
			// Make sure customer exists.
416
			if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] && false === get_user_by( 'id', $request['customer_id'] ) ) {
417
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id',__( 'Customer ID is invalid.', 'woocommerce' ), 400 );
418
			}
419
420
			$order = $this->prepare_item_for_database( $request );
421
			$order->set_created_via( 'rest-api' );
422
			$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
423
			$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
424
			$order->set_customer_user_agent( wc_get_user_agent() );
425
			$order->calculate_totals();
426
			$order->save();
427
428
			// Handle set paid
429
			if ( true === $request['set_paid'] ) {
430
				$order->payment_complete( $request->get_param( 'transaction_id' ) );
431
			}
432
433
			return $order->get_id();
434
		} catch ( Exception $e ) {
435
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getErrorCode() does only exist in the following sub-classes of Exception: WC_CLI_Exception, WC_Data_Exception, WC_REST_Exception. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
436
		}
437
	}
438
439
	/**
440
	 * Update order.
441
	 *
442
	 * @param WP_REST_Request $request Full details about the request.
443
	 * @return int|WP_Error
444
	 */
445
	protected function update_order( $request ) {
446
		try {
447
			$order = $this->prepare_item_for_database( $request );
448
			$order->save();
449
450
			// Handle set paid
451
			if ( $order->needs_payment() && true === $request['set_paid'] ) {
452
				$order->payment_complete( $request->get_param( 'transaction_id' ) );
453
			}
454
455
			// If items have changed, recalculate order totals.
456
			if ( isset( $request[ 'billing' ], $request[ 'shipping' ], $request[ 'line_items' ], $request[ 'shipping' ], $request[ 'fee' ], $request[ 'coupon' ] ) ) {
457
				$order->calculate_totals();
458
			}
459
460
			return $order->get_id();
461
		} catch ( WC_REST_Exception $e ) {
462
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
463
		}
464
	}
465
466
	/**
467
	 * Update address.
468
	 *
469
	 * @param WC_Order $order
470
	 * @param array $posted
471
	 * @param string $type
472
	 */
473
	protected function update_address( $order, $posted, $type = 'billing' ) {
474
		foreach ( $posted as $key => $value ) {
475
			if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) {
476
				$order->{"set_{$type}_{$key}"}( $value );
477
			}
478
		}
479
	}
480
481
	/**
482
	 * Create or update a line item.
483
	 *
484
	 * @param array $posted Line item data.
485
	 * @param string $action 'create' to add line item or 'update' to update it.
486
	 * @throws WC_REST_Exception Invalid data, server error.
487
	 */
488
	protected function prepare_line_items( $posted, $action = 'create' ) {
489
		// Product is always required.
490
		if ( empty( $posted['product_id'] ) && empty( $posted['sku'] ) && empty( $posted['variation_id'] ) ) {
491
			throw new WC_REST_Exception( 'woocommerce_rest_required_product_reference', __( 'Product ID or SKU is required.', 'woocommerce' ), 400 );
492
		}
493
494
		// Get product from ID or sku
495
		if ( ! empty( $posted['sku'] ) ) {
496
			$product_id = (int) wc_get_product_id_by_sku( $posted['sku'] );
497
		} elseif ( ! empty( $posted['product_id'] ) && empty( $posted['variation_id'] ) ) {
498
			$product_id = (int) $posted['product_id'];
499
		} elseif ( ! empty( $posted['variation_id'] ) ) {
500
			$product_id = (int) $posted['variation_id'];
501
		}
502
503
		$item    = new WC_Order_Item_Product( ! empty( $posted['id'] ) ? $posted['id'] : '' );
504
		$product = wc_get_product( $product_id );
505
506
		if ( $product !== $item->get_product() ) {
507
			$item->set_product( $product );
508
509
			if ( 'create' === $action ) {
510
				$qty = isset( $posted['quantity'] ) ? $posted['quantity'] : 1;
511
				$item->set_total( $product->get_price() * $qty );
512
				$item->set_subtotal( $product->get_price() * $qty );
513
			}
514
		}
515
516
		if ( isset( $posted['name'] ) ) {
517
			$item->set_name( $posted['name'] );
518
		}
519
520
		if ( isset( $posted['quantity'] ) ) {
521
			$item->set_quantity( $posted['quantity'] );
522
		}
523
524
		if ( isset( $posted['total'] ) ) {
525
			$item->set_total( floatval( $posted['total'] ) );
526
		}
527
528
		if ( isset( $posted['subtotal'] ) ) {
529
			$item->set_subtotal( floatval( $posted['subtotal'] ) );
530
		}
531
532
		if ( isset( $posted['tax_class'] ) ) {
533
			$item->set_tax_class( $posted['tax_class'] );
534
		}
535
536
		return $item;
537
	}
538
539
	/**
540
	 * Create or update an order shipping method.
541
	 *
542
	 * @param $posted $shipping Item data.
0 ignored issues
show
Documentation introduced by
The doc-type $posted could not be parsed: Unknown type name "$posted" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
Bug introduced by
There is no parameter named $shipping. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
543
	 * @param string $action 'create' to add shipping or 'update' to update it.
544
	 * @throws WC_REST_Exception Invalid data, server error.
545
	 */
546
	protected function prepare_shipping_lines( $posted, $action ) {
547
		$item = new WC_Order_Item_Shipping( ! empty( $posted['id'] ) ? $posted['id'] : '' );
548
549
		if ( 'create' === $action ) {
550
			if ( empty( $posted['method_id'] ) ) {
551
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 );
552
			}
553
		}
554
555
		if ( isset( $posted['method_id'] ) ) {
556
			$item->set_method_id( $posted['method_id'] );
557
		}
558
559
		if ( isset( $posted['method_title'] ) ) {
560
			$item->set_method_title( $posted['method_title'] );
561
		}
562
563
		if ( isset( $posted['total'] ) ) {
564
			$item->set_total( floatval( $posted['total'] ) );
565
		}
566
567
		return $item;
568
	}
569
570
	/**
571
	 * Create or update an order fee.
572
	 *
573
	 * @param array $posted Item data.
574
	 * @param string $action 'create' to add fee or 'update' to update it.
575
	 * @throws WC_REST_Exception Invalid data, server error.
576
	 */
577
	protected function prepare_fee_lines( $posted, $action ) {
578
		$item = new WC_Order_Item_Fee( ! empty( $posted['id'] ) ? $posted['id'] : '' );
579
580 View Code Duplication
		if ( 'create' === $action ) {
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...
581
			if ( empty( $posted['name'] ) ) {
582
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_fee_item', __( 'Fee name is required.', 'woocommerce' ), 400 );
583
			}
584
		}
585
586
		if ( isset( $posted['name'] ) ) {
587
			$item->set_name( $posted['name'] );
588
		}
589
590
		if ( isset( $posted['tax_class'] ) ) {
591
			$item->set_tax_class( $posted['tax_class'] );
592
		}
593
594
		if ( isset( $posted['tax_status'] ) ) {
595
			$item->set_tax_status( $posted['tax_status'] );
596
		}
597
598
		if ( isset( $posted['total'] ) ) {
599
			$item->set_total( floatval( $posted['total'] ) );
600
		}
601
602
		return $item;
603
	}
604
605
	/**
606
	 * Create or update an order coupon.
607
	 *
608
	 * @param array $posted Item data.
609
	 * @param string $action 'create' to add coupon or 'update' to update it.
610
	 * @throws WC_REST_Exception Invalid data, server error.
611
	 */
612
	protected function prepare_coupon_lines( $posted, $action ) {
613
		$item = new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' );
614
615 View Code Duplication
		if ( 'create' === $action ) {
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...
616
			if ( empty( $posted['code'] ) ) {
617
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
618
			}
619
		}
620
621
		if ( isset( $posted['code'] ) ) {
622
			$item->set_code( $posted['code'] );
623
		}
624
625
		if ( isset( $posted['discount'] ) ) {
626
			$item->set_discount( floatval( $posted['discount'] ) );
627
		}
628
629
		return $item;
630
	}
631
632
	/**
633
	 * Wrapper method to create/update order items.
634
	 * When updating, the item ID provided is checked to ensure it is associated
635
	 * with the order.
636
	 *
637
	 * @param WC_Order $order order
638
	 * @param string $item_type
639
	 * @param array $posted item provided in the request body
640
	 * @throws WC_REST_Exception If item ID is not associated with order
641
	 */
642
	protected function set_item( $order, $item_type, $posted ) {
643
		global $wpdb;
644
645
		if ( ! empty( $posted['id'] ) ) {
646
			$action = 'update';
647
		} else {
648
			$action = 'create';
649
		}
650
651
		$method = 'prepare_' . $item_type;
652
653
		// Verify provided line item ID is associated with order.
654
		if ( 'update' === $action ) {
655
			$result = $wpdb->get_row(
656
				$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d",
657
				absint( $posted['id'] ),
658
				absint( $order->get_id() )
659
			) );
660
			if ( is_null( $result ) ) {
661
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 );
662
			}
663
		}
664
665
		// Prepare item data
666
		$item = $this->$method( $posted, $action );
667
668
		// Save or add to order
669
		if ( 'create' === $action ) {
670
			$order->add_item( $item );
671
		} else {
672
			$item->save();
673
		}
674
	}
675
676
	/**
677
	 * Helper method to check if the resource ID associated with the provided item is null.
678
	 * Items can be deleted by setting the resource ID to null.
679
	 *
680
	 * @param array $item Item provided in the request body.
681
	 * @return bool True if the item resource ID is null, false otherwise.
682
	 */
683
	protected function item_is_null( $item ) {
684
		$keys = array( 'product_id', 'method_id', 'title', 'code' );
685
686
		foreach ( $keys as $key ) {
687
			if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
688
				return true;
689
			}
690
		}
691
692
		return false;
693
	}
694
695
	/**
696
	 * Create a single item.
697
	 *
698
	 * @param WP_REST_Request $request Full details about the request.
699
	 * @return WP_Error|WP_REST_Response
700
	 */
701
	public function create_item( $request ) {
702
		if ( ! empty( $request['id'] ) ) {
703
			return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) );
704
		}
705
706
		$order_id = $this->create_order( $request );
707
		if ( is_wp_error( $order_id ) ) {
708
			return $order_id;
709
		}
710
711
		$post = get_post( $order_id );
712
		$this->update_additional_fields_for_object( $post, $request );
713
714
		/**
715
		 * Fires after a single item is created or updated via the REST API.
716
		 *
717
		 * @param object          $post      Inserted object (not a WP_Post object).
718
		 * @param WP_REST_Request $request   Request object.
719
		 * @param boolean         $creating  True when creating item, false when updating.
720
		 */
721
		do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
722
		$request->set_param( 'context', 'edit' );
723
		$response = $this->prepare_item_for_response( $post, $request );
724
		$response = rest_ensure_response( $response );
725
		$response->set_status( 201 );
726
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) );
727
728
		return $response;
729
	}
730
731
	/**
732
	 * Update a single order.
733
	 *
734
	 * @param WP_REST_Request $request Full details about the request.
735
	 * @return WP_Error|WP_REST_Response
736
	 */
737
	public function update_item( $request ) {
738
		try {
739
			$post_id = (int) $request['id'];
740
741
			if ( empty( $post_id ) || $this->post_type !== get_post_type( $post_id ) ) {
742
				return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
743
			}
744
745
			$order_id = $this->update_order( $request );
746
			if ( is_wp_error( $order_id ) ) {
747
				return $order_id;
748
			}
749
750
			$post = get_post( $order_id );
751
			$this->update_additional_fields_for_object( $post, $request );
752
753
			/**
754
			 * Fires after a single item is created or updated via the REST API.
755
			 *
756
			 * @param object          $post      Inserted object (not a WP_Post object).
757
			 * @param WP_REST_Request $request   Request object.
758
			 * @param boolean         $creating  True when creating item, false when updating.
759
			 */
760
			do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false );
761
			$request->set_param( 'context', 'edit' );
762
			$response = $this->prepare_item_for_response( $post, $request );
763
			return rest_ensure_response( $response );
764
765
		} catch ( Exception $e ) {
766
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getErrorCode() does only exist in the following sub-classes of Exception: WC_CLI_Exception, WC_Data_Exception, WC_REST_Exception. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
767
		}
768
	}
769
770
	/**
771
	 * Get order statuses without prefixes.
772
	 * @return array
773
	 */
774
	protected function get_order_statuses() {
775
		$order_statuses = array();
776
777
		foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
778
			$order_statuses[] = str_replace( 'wc-', '', $status );
779
		}
780
781
		return $order_statuses;
782
	}
783
784
	/**
785
	 * Get the Order's schema, conforming to JSON Schema.
786
	 *
787
	 * @return array
788
	 */
789
	public function get_item_schema() {
790
		$schema = array(
791
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
792
			'title'      => $this->post_type,
793
			'type'       => 'object',
794
			'properties' => array(
795
				'id' => array(
796
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
797
					'type'        => 'integer',
798
					'context'     => array( 'view', 'edit' ),
799
					'readonly'    => true,
800
				),
801
				'parent_id' => array(
802
					'description' => __( 'Parent order ID.', 'woocommerce' ),
803
					'type'        => 'integer',
804
					'context'     => array( 'view', 'edit' ),
805
				),
806
				'status' => array(
807
					'description' => __( 'Order status.', 'woocommerce' ),
808
					'type'        => 'string',
809
					'default'     => 'pending',
810
					'enum'        => $this->get_order_statuses(),
811
					'context'     => array( 'view', 'edit' ),
812
				),
813
				'order_key' => array(
814
					'description' => __( 'Order key.', 'woocommerce' ),
815
					'type'        => 'string',
816
					'context'     => array( 'view', 'edit' ),
817
					'readonly'    => true,
818
				),
819
				'currency' => array(
820
					'description' => __( 'Currency the order was created with, in ISO format.', 'woocommerce' ),
821
					'type'        => 'string',
822
					'default'     => get_woocommerce_currency(),
823
					'enum'        => array_keys( get_woocommerce_currencies() ),
824
					'context'     => array( 'view', 'edit' ),
825
				),
826
				'version' => array(
827
					'description' => __( 'Version of WooCommerce when the order was made.', 'woocommerce' ),
828
					'type'        => 'integer',
829
					'context'     => array( 'view', 'edit' ),
830
					'readonly'    => true,
831
				),
832
				'prices_include_tax' => array(
833
					'description' => __( 'True the prices included tax during checkout.', 'woocommerce' ),
834
					'type'        => 'boolean',
835
					'context'     => array( 'view', 'edit' ),
836
					'readonly'    => true,
837
				),
838
				'date_created' => array(
839
					'description' => __( "The date the order was created, in the site's timezone.", 'woocommerce' ),
840
					'type'        => 'date-time',
841
					'context'     => array( 'view', 'edit' ),
842
				),
843
				'date_modified' => array(
844
					'description' => __( "The date the order was last modified, in the site's timezone.", 'woocommerce' ),
845
					'type'        => 'date-time',
846
					'context'     => array( 'view', 'edit' ),
847
					'readonly'    => true,
848
				),
849
				'customer_id' => array(
850
					'description' => __( 'User ID who owns the order. 0 for guests.', 'woocommerce' ),
851
					'type'        => 'integer',
852
					'default'     => 0,
853
					'context'     => array( 'view', 'edit' ),
854
				),
855
				'discount_total' => array(
856
					'description' => __( 'Total discount amount for the order.', 'woocommerce' ),
857
					'type'        => 'string',
858
					'context'     => array( 'view', 'edit' ),
859
					'readonly'    => true,
860
				),
861
				'discount_tax' => array(
862
					'description' => __( 'Total discount tax amount for the order.', 'woocommerce' ),
863
					'type'        => 'string',
864
					'context'     => array( 'view', 'edit' ),
865
					'readonly'    => true,
866
				),
867
				'shipping_total' => array(
868
					'description' => __( 'Total shipping amount for the order.', 'woocommerce' ),
869
					'type'        => 'string',
870
					'context'     => array( 'view', 'edit' ),
871
					'readonly'    => true,
872
				),
873
				'shipping_tax' => array(
874
					'description' => __( 'Total shipping tax amount for the order.', 'woocommerce' ),
875
					'type'        => 'string',
876
					'context'     => array( 'view', 'edit' ),
877
					'readonly'    => true,
878
				),
879
				'cart_tax' => array(
880
					'description' => __( 'Sum of line item taxes only.', 'woocommerce' ),
881
					'type'        => 'string',
882
					'context'     => array( 'view', 'edit' ),
883
					'readonly'    => true,
884
				),
885
				'total' => array(
886
					'description' => __( 'Grand total.', 'woocommerce' ),
887
					'type'        => 'string',
888
					'context'     => array( 'view', 'edit' ),
889
					'readonly'    => true,
890
				),
891
				'total_tax' => array(
892
					'description' => __( 'Sum of all taxes.', 'woocommerce' ),
893
					'type'        => 'string',
894
					'context'     => array( 'view', 'edit' ),
895
					'readonly'    => true,
896
				),
897
				'billing' => array(
898
					'description' => __( 'Billing address.', 'woocommerce' ),
899
					'type'        => 'array',
900
					'context'     => array( 'view', 'edit' ),
901
					'properties'  => array(
902
						'first_name' => array(
903
							'description' => __( 'First name.', 'woocommerce' ),
904
							'type'        => 'string',
905
							'context'     => array( 'view', 'edit' ),
906
						),
907
						'last_name' => array(
908
							'description' => __( 'Last name.', 'woocommerce' ),
909
							'type'        => 'string',
910
							'context'     => array( 'view', 'edit' ),
911
						),
912
						'company' => array(
913
							'description' => __( 'Company name.', 'woocommerce' ),
914
							'type'        => 'string',
915
							'context'     => array( 'view', 'edit' ),
916
						),
917
						'address_1' => array(
918
							'description' => __( 'Address line 1.', 'woocommerce' ),
919
							'type'        => 'string',
920
							'context'     => array( 'view', 'edit' ),
921
						),
922
						'address_2' => array(
923
							'description' => __( 'Address line 2.', 'woocommerce' ),
924
							'type'        => 'string',
925
							'context'     => array( 'view', 'edit' ),
926
						),
927
						'city' => array(
928
							'description' => __( 'City name.', 'woocommerce' ),
929
							'type'        => 'string',
930
							'context'     => array( 'view', 'edit' ),
931
						),
932
						'state' => array(
933
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
934
							'type'        => 'string',
935
							'context'     => array( 'view', 'edit' ),
936
						),
937
						'postcode' => array(
938
							'description' => __( 'Postal code.', 'woocommerce' ),
939
							'type'        => 'string',
940
							'context'     => array( 'view', 'edit' ),
941
						),
942
						'country' => array(
943
							'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
944
							'type'        => 'string',
945
							'context'     => array( 'view', 'edit' ),
946
						),
947
						'email' => array(
948
							'description' => __( 'Email address.', 'woocommerce' ),
949
							'type'        => 'string',
950
							'format'      => 'email',
951
							'context'     => array( 'view', 'edit' ),
952
						),
953
						'phone' => array(
954
							'description' => __( 'Phone number.', 'woocommerce' ),
955
							'type'        => 'string',
956
							'context'     => array( 'view', 'edit' ),
957
						),
958
					),
959
				),
960
				'shipping' => array(
961
					'description' => __( 'Shipping address.', 'woocommerce' ),
962
					'type'        => 'array',
963
					'context'     => array( 'view', 'edit' ),
964
					'properties'  => array(
965
						'first_name' => array(
966
							'description' => __( 'First name.', 'woocommerce' ),
967
							'type'        => 'string',
968
							'context'     => array( 'view', 'edit' ),
969
						),
970
						'last_name' => array(
971
							'description' => __( 'Last name.', 'woocommerce' ),
972
							'type'        => 'string',
973
							'context'     => array( 'view', 'edit' ),
974
						),
975
						'company' => array(
976
							'description' => __( 'Company name.', 'woocommerce' ),
977
							'type'        => 'string',
978
							'context'     => array( 'view', 'edit' ),
979
						),
980
						'address_1' => array(
981
							'description' => __( 'Address line 1.', 'woocommerce' ),
982
							'type'        => 'string',
983
							'context'     => array( 'view', 'edit' ),
984
						),
985
						'address_2' => array(
986
							'description' => __( 'Address line 2.', 'woocommerce' ),
987
							'type'        => 'string',
988
							'context'     => array( 'view', 'edit' ),
989
						),
990
						'city' => array(
991
							'description' => __( 'City name.', 'woocommerce' ),
992
							'type'        => 'string',
993
							'context'     => array( 'view', 'edit' ),
994
						),
995
						'state' => array(
996
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
997
							'type'        => 'string',
998
							'context'     => array( 'view', 'edit' ),
999
						),
1000
						'postcode' => array(
1001
							'description' => __( 'Postal code.', 'woocommerce' ),
1002
							'type'        => 'string',
1003
							'context'     => array( 'view', 'edit' ),
1004
						),
1005
						'country' => array(
1006
							'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
1007
							'type'        => 'string',
1008
							'context'     => array( 'view', 'edit' ),
1009
						),
1010
					),
1011
				),
1012
				'payment_method' => array(
1013
					'description' => __( 'Payment method ID.', 'woocommerce' ),
1014
					'type'        => 'string',
1015
					'context'     => array( 'view', 'edit' ),
1016
				),
1017
				'payment_method_title' => array(
1018
					'description' => __( 'Payment method title.', 'woocommerce' ),
1019
					'type'        => 'string',
1020
					'context'     => array( 'view', 'edit' ),
1021
				),
1022
				'transaction_id' => array(
1023
					'description' => __( 'Unique transaction ID.', 'woocommerce' ),
1024
					'type'        => 'string',
1025
					'context'     => array( 'view', 'edit' ),
1026
				),
1027
				'customer_ip_address' => array(
1028
					'description' => __( "Customer's IP address.", 'woocommerce' ),
1029
					'type'        => 'string',
1030
					'context'     => array( 'view', 'edit' ),
1031
					'readonly'    => true,
1032
				),
1033
				'customer_user_agent' => array(
1034
					'description' => __( 'User agent of the customer.', 'woocommerce' ),
1035
					'type'        => 'string',
1036
					'context'     => array( 'view', 'edit' ),
1037
					'readonly'    => true,
1038
				),
1039
				'created_via' => array(
1040
					'description' => __( 'Shows where the order was created.', 'woocommerce' ),
1041
					'type'        => 'string',
1042
					'context'     => array( 'view', 'edit' ),
1043
					'readonly'    => true,
1044
				),
1045
				'customer_note' => array(
1046
					'description' => __( 'Note left by customer during checkout.', 'woocommerce' ),
1047
					'type'        => 'string',
1048
					'context'     => array( 'view', 'edit' ),
1049
				),
1050
				'date_completed' => array(
1051
					'description' => __( "The date the order was completed, in the site's timezone.", 'woocommerce' ),
1052
					'type'        => 'date-time',
1053
					'context'     => array( 'view', 'edit' ),
1054
				),
1055
				'date_paid' => array(
1056
					'description' => __( "The date the order has been paid, in the site's timezone.", 'woocommerce' ),
1057
					'type'        => 'date-time',
1058
					'context'     => array( 'view', 'edit' ),
1059
				),
1060
				'cart_hash' => array(
1061
					'description' => __( 'MD5 hash of cart items to ensure orders are not modified.', 'woocommerce' ),
1062
					'type'        => 'string',
1063
					'context'     => array( 'view', 'edit' ),
1064
					'readonly'    => true,
1065
				),
1066
				'number' => array(
1067
					'description' => __( 'Order number.', 'woocommerce' ),
1068
					'type'        => 'string',
1069
					'context'     => array( 'view', 'edit' ),
1070
					'readonly'    => true,
1071
				),
1072
				'meta_data' => array(
1073
					'description' => __( 'Order meta data.', 'woocommerce' ),
1074
					'type'        => 'array',
1075
					'context'     => array( 'view', 'edit' ),
1076
					'properties'  => array(
1077
						'key' => array(
1078
							'description' => __( 'Meta key.', 'woocommerce' ),
1079
							'type'        => 'string',
1080
							'context'     => array( 'view', 'edit' ),
1081
						),
1082
						'value' => array(
1083
							'description' => __( 'Meta value.', 'woocommerce' ),
1084
							'type'        => 'string',
1085
							'context'     => array( 'view', 'edit' ),
1086
						),
1087
						'meta_id' => array(
1088
							'description' => __( 'Meta ID.', 'woocommerce' ),
1089
							'type'        => 'int',
1090
							'context'     => array( 'view', 'edit' ),
1091
							'readonly'    => true,
1092
						),
1093
					),
1094
				),
1095
				'line_items' => array(
1096
					'description' => __( 'Line items data.', 'woocommerce' ),
1097
					'type'        => 'array',
1098
					'context'     => array( 'view', 'edit' ),
1099
					'properties'  => array(
1100
						'id' => array(
1101
							'description' => __( 'Item ID.', 'woocommerce' ),
1102
							'type'        => 'integer',
1103
							'context'     => array( 'view', 'edit' ),
1104
							'readonly'    => true,
1105
						),
1106
						'name' => array(
1107
							'description' => __( 'Product name.', 'woocommerce' ),
1108
							'type'        => 'string',
1109
							'context'     => array( 'view', 'edit' ),
1110
						),
1111
						'product_id' => array(
1112
							'description' => __( 'Product ID.', 'woocommerce' ),
1113
							'type'        => 'integer',
1114
							'context'     => array( 'view', 'edit' ),
1115
						),
1116
						'variation_id' => array(
1117
							'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
1118
							'type'        => 'integer',
1119
							'context'     => array( 'view', 'edit' ),
1120
						),
1121
						'quantity' => array(
1122
							'description' => __( 'Quantity ordered.', 'woocommerce' ),
1123
							'type'        => 'integer',
1124
							'context'     => array( 'view', 'edit' ),
1125
						),
1126
						'tax_class' => array(
1127
							'description' => __( 'Tax class of product.', 'woocommerce' ),
1128
							'type'        => 'integer',
1129
							'context'     => array( 'view', 'edit' ),
1130
						),
1131
						'subtotal' => array(
1132
							'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
1133
							'type'        => 'string',
1134
							'context'     => array( 'view', 'edit' ),
1135
						),
1136
						'subtotal_tax' => array(
1137
							'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),
1138
							'type'        => 'string',
1139
							'context'     => array( 'view', 'edit' ),
1140
							'readonly'    => true,
1141
						),
1142
						'total' => array(
1143
							'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1144
							'type'        => 'string',
1145
							'context'     => array( 'view', 'edit' ),
1146
						),
1147
						'total_tax' => array(
1148
							'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1149
							'type'        => 'string',
1150
							'context'     => array( 'view', 'edit' ),
1151
							'readonly'    => true,
1152
						),
1153
						'taxes' => array(
1154
							'description' => __( 'Line taxes.', 'woocommerce' ),
1155
							'type'        => 'array',
1156
							'context'     => array( 'view', 'edit' ),
1157
							'readonly'    => true,
1158
							'properties'  => array(
1159
								'id' => array(
1160
									'description' => __( 'Tax rate ID.', 'woocommerce' ),
1161
									'type'        => 'integer',
1162
									'context'     => array( 'view', 'edit' ),
1163
								),
1164
								'total' => array(
1165
									'description' => __( 'Tax total.', 'woocommerce' ),
1166
									'type'        => 'string',
1167
									'context'     => array( 'view', 'edit' ),
1168
								),
1169
								'subtotal' => array(
1170
									'description' => __( 'Tax subtotal.', 'woocommerce' ),
1171
									'type'        => 'string',
1172
									'context'     => array( 'view', 'edit' ),
1173
								),
1174
							),
1175
						),
1176
						'meta_data' => array(
1177
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1178
							'type'        => 'array',
1179
							'context'     => array( 'view', 'edit' ),
1180
							'properties'  => array(
1181
								'key' => array(
1182
									'description' => __( 'Meta key.', 'woocommerce' ),
1183
									'type'        => 'string',
1184
									'context'     => array( 'view', 'edit' ),
1185
								),
1186
								'value' => array(
1187
									'description' => __( 'Meta value.', 'woocommerce' ),
1188
									'type'        => 'string',
1189
									'context'     => array( 'view', 'edit' ),
1190
								),
1191
								'meta_id' => array(
1192
									'description' => __( 'Meta ID.', 'woocommerce' ),
1193
									'type'        => 'int',
1194
									'context'     => array( 'view', 'edit' ),
1195
									'readonly'    => true,
1196
								),
1197
							),
1198
						),
1199
						'sku' => array(
1200
							'description' => __( 'Product SKU.', 'woocommerce' ),
1201
							'type'        => 'string',
1202
							'context'     => array( 'view', 'edit' ),
1203
							'readonly'    => true,
1204
						),
1205
						'price' => array(
1206
							'description' => __( 'Product price.', 'woocommerce' ),
1207
							'type'        => 'string',
1208
							'context'     => array( 'view', 'edit' ),
1209
							'readonly'    => true,
1210
						),
1211
						'meta' => array(
1212
							'description' => __( 'Order item meta data (formatted).', 'woocommerce' ),
1213
							'type'        => 'array',
1214
							'context'     => array( 'view', 'edit' ),
1215
							'readonly'    => true,
1216
							'properties'  => array(
1217
								'key' => array(
1218
									'description' => __( 'Meta key.', 'woocommerce' ),
1219
									'type'        => 'string',
1220
									'context'     => array( 'view', 'edit' ),
1221
									'readonly'    => true,
1222
								),
1223
								'label' => array(
1224
									'description' => __( 'Meta label.', 'woocommerce' ),
1225
									'type'        => 'string',
1226
									'context'     => array( 'view', 'edit' ),
1227
									'readonly'    => true,
1228
								),
1229
								'value' => array(
1230
									'description' => __( 'Meta value.', 'woocommerce' ),
1231
									'type'        => 'string',
1232
									'context'     => array( 'view', 'edit' ),
1233
									'readonly'    => true,
1234
								),
1235
							),
1236
						),
1237
					),
1238
				),
1239
				'tax_lines' => array(
1240
					'description' => __( 'Tax lines data.', 'woocommerce' ),
1241
					'type'        => 'array',
1242
					'context'     => array( 'view', 'edit' ),
1243
					'readonly'    => true,
1244
					'properties'  => array(
1245
						'id' => array(
1246
							'description' => __( 'Item ID.', 'woocommerce' ),
1247
							'type'        => 'integer',
1248
							'context'     => array( 'view', 'edit' ),
1249
							'readonly'    => true,
1250
						),
1251
						'rate_code' => array(
1252
							'description' => __( 'Tax rate code.', 'woocommerce' ),
1253
							'type'        => 'string',
1254
							'context'     => array( 'view', 'edit' ),
1255
							'readonly'    => true,
1256
						),
1257
						'rate_id' => array(
1258
							'description' => __( 'Tax rate ID.', 'woocommerce' ),
1259
							'type'        => 'string',
1260
							'context'     => array( 'view', 'edit' ),
1261
							'readonly'    => true,
1262
						),
1263
						'label' => array(
1264
							'description' => __( 'Tax rate label.', 'woocommerce' ),
1265
							'type'        => 'string',
1266
							'context'     => array( 'view', 'edit' ),
1267
							'readonly'    => true,
1268
						),
1269
						'compound' => array(
1270
							'description' => __( 'Show if is a compound tax rate.', 'woocommerce' ),
1271
							'type'        => 'boolean',
1272
							'context'     => array( 'view', 'edit' ),
1273
							'readonly'    => true,
1274
						),
1275
						'tax_total' => array(
1276
							'description' => __( 'Tax total (not including shipping taxes).', 'woocommerce' ),
1277
							'type'        => 'string',
1278
							'context'     => array( 'view', 'edit' ),
1279
							'readonly'    => true,
1280
						),
1281
						'shipping_tax_total' => array(
1282
							'description' => __( 'Shipping tax total.', 'woocommerce' ),
1283
							'type'        => 'string',
1284
							'context'     => array( 'view', 'edit' ),
1285
							'readonly'    => true,
1286
						),
1287
						'meta_data' => array(
1288
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1289
							'type'        => 'array',
1290
							'context'     => array( 'view', 'edit' ),
1291
							'properties'  => array(
1292
								'key' => array(
1293
									'description' => __( 'Meta key.', 'woocommerce' ),
1294
									'type'        => 'string',
1295
									'context'     => array( 'view', 'edit' ),
1296
								),
1297
								'value' => array(
1298
									'description' => __( 'Meta value.', 'woocommerce' ),
1299
									'type'        => 'string',
1300
									'context'     => array( 'view', 'edit' ),
1301
								),
1302
								'meta_id' => array(
1303
									'description' => __( 'Meta ID.', 'woocommerce' ),
1304
									'type'        => 'int',
1305
									'context'     => array( 'view', 'edit' ),
1306
									'readonly'    => true,
1307
								),
1308
							),
1309
						),
1310
					),
1311
				),
1312
				'shipping_lines' => array(
1313
					'description' => __( 'Shipping lines data.', 'woocommerce' ),
1314
					'type'        => 'array',
1315
					'context'     => array( 'view', 'edit' ),
1316
					'properties'  => array(
1317
						'id' => array(
1318
							'description' => __( 'Item ID.', 'woocommerce' ),
1319
							'type'        => 'integer',
1320
							'context'     => array( 'view', 'edit' ),
1321
							'readonly'    => true,
1322
						),
1323
						'method_title' => array(
1324
							'description' => __( 'Shipping method name.', 'woocommerce' ),
1325
							'type'        => 'string',
1326
							'context'     => array( 'view', 'edit' ),
1327
						),
1328
						'method_id' => array(
1329
							'description' => __( 'Shipping method ID.', 'woocommerce' ),
1330
							'type'        => 'string',
1331
							'context'     => array( 'view', 'edit' ),
1332
						),
1333
						'total' => array(
1334
							'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1335
							'type'        => 'string',
1336
							'context'     => array( 'view', 'edit' ),
1337
						),
1338
						'total_tax' => array(
1339
							'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1340
							'type'        => 'string',
1341
							'context'     => array( 'view', 'edit' ),
1342
							'readonly'    => true,
1343
						),
1344
						'taxes' => array(
1345
							'description' => __( 'Line taxes.', 'woocommerce' ),
1346
							'type'        => 'array',
1347
							'context'     => array( 'view', 'edit' ),
1348
							'readonly'    => true,
1349
							'properties'  => array(
1350
								'id' => array(
1351
									'description' => __( 'Tax rate ID.', 'woocommerce' ),
1352
									'type'        => 'integer',
1353
									'context'     => array( 'view', 'edit' ),
1354
									'readonly'    => true,
1355
								),
1356
								'total' => array(
1357
									'description' => __( 'Tax total.', 'woocommerce' ),
1358
									'type'        => 'string',
1359
									'context'     => array( 'view', 'edit' ),
1360
									'readonly'    => true,
1361
								),
1362
							),
1363
						),
1364
						'meta_data' => array(
1365
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1366
							'type'        => 'array',
1367
							'context'     => array( 'view', 'edit' ),
1368
							'properties'  => array(
1369
								'key' => array(
1370
									'description' => __( 'Meta key.', 'woocommerce' ),
1371
									'type'        => 'string',
1372
									'context'     => array( 'view', 'edit' ),
1373
								),
1374
								'value' => array(
1375
									'description' => __( 'Meta value.', 'woocommerce' ),
1376
									'type'        => 'string',
1377
									'context'     => array( 'view', 'edit' ),
1378
								),
1379
								'meta_id' => array(
1380
									'description' => __( 'Meta ID.', 'woocommerce' ),
1381
									'type'        => 'int',
1382
									'context'     => array( 'view', 'edit' ),
1383
									'readonly'    => true,
1384
								),
1385
							),
1386
						),
1387
					),
1388
				),
1389
				'fee_lines' => array(
1390
					'description' => __( 'Fee lines data.', 'woocommerce' ),
1391
					'type'        => 'array',
1392
					'context'     => array( 'view', 'edit' ),
1393
					'properties'  => array(
1394
						'id' => array(
1395
							'description' => __( 'Item ID.', 'woocommerce' ),
1396
							'type'        => 'integer',
1397
							'context'     => array( 'view', 'edit' ),
1398
							'readonly'    => true,
1399
						),
1400
						'name' => array(
1401
							'description' => __( 'Fee name.', 'woocommerce' ),
1402
							'type'        => 'string',
1403
							'context'     => array( 'view', 'edit' ),
1404
						),
1405
						'tax_class' => array(
1406
							'description' => __( 'Tax class of fee.', 'woocommerce' ),
1407
							'type'        => 'string',
1408
							'context'     => array( 'view', 'edit' ),
1409
						),
1410
						'tax_status' => array(
1411
							'description' => __( 'Tax status of fee.', 'woocommerce' ),
1412
							'type'        => 'string',
1413
							'context'     => array( 'view', 'edit' ),
1414
							'enum'        => array( 'taxable', 'none' ),
1415
						),
1416
						'total' => array(
1417
							'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1418
							'type'        => 'string',
1419
							'context'     => array( 'view', 'edit' ),
1420
						),
1421
						'total_tax' => array(
1422
							'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1423
							'type'        => 'string',
1424
							'context'     => array( 'view', 'edit' ),
1425
							'readonly'    => true,
1426
						),
1427
						'taxes' => array(
1428
							'description' => __( 'Line taxes.', 'woocommerce' ),
1429
							'type'        => 'array',
1430
							'context'     => array( 'view', 'edit' ),
1431
							'readonly'    => true,
1432
							'properties'  => array(
1433
								'id' => array(
1434
									'description' => __( 'Tax rate ID.', 'woocommerce' ),
1435
									'type'        => 'integer',
1436
									'context'     => array( 'view', 'edit' ),
1437
									'readonly'    => true,
1438
								),
1439
								'total' => array(
1440
									'description' => __( 'Tax total.', 'woocommerce' ),
1441
									'type'        => 'string',
1442
									'context'     => array( 'view', 'edit' ),
1443
									'readonly'    => true,
1444
								),
1445
								'subtotal' => array(
1446
									'description' => __( 'Tax subtotal.', 'woocommerce' ),
1447
									'type'        => 'string',
1448
									'context'     => array( 'view', 'edit' ),
1449
									'readonly'    => true,
1450
								),
1451
							),
1452
						),
1453
						'meta_data' => array(
1454
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1455
							'type'        => 'array',
1456
							'context'     => array( 'view', 'edit' ),
1457
							'properties'  => array(
1458
								'key' => array(
1459
									'description' => __( 'Meta key.', 'woocommerce' ),
1460
									'type'        => 'string',
1461
									'context'     => array( 'view', 'edit' ),
1462
								),
1463
								'value' => array(
1464
									'description' => __( 'Meta value.', 'woocommerce' ),
1465
									'type'        => 'string',
1466
									'context'     => array( 'view', 'edit' ),
1467
								),
1468
								'meta_id' => array(
1469
									'description' => __( 'Meta ID.', 'woocommerce' ),
1470
									'type'        => 'int',
1471
									'context'     => array( 'view', 'edit' ),
1472
									'readonly'    => true,
1473
								),
1474
							),
1475
						),
1476
					),
1477
				),
1478
				'coupon_lines' => array(
1479
					'description' => __( 'Coupons line data.', 'woocommerce' ),
1480
					'type'        => 'array',
1481
					'context'     => array( 'view', 'edit' ),
1482
					'properties'  => array(
1483
						'id' => array(
1484
							'description' => __( 'Item ID.', 'woocommerce' ),
1485
							'type'        => 'integer',
1486
							'context'     => array( 'view', 'edit' ),
1487
							'readonly'    => true,
1488
						),
1489
						'code' => array(
1490
							'description' => __( 'Coupon code.', 'woocommerce' ),
1491
							'type'        => 'string',
1492
							'context'     => array( 'view', 'edit' ),
1493
						),
1494
						'discount' => array(
1495
							'description' => __( 'Discount total.', 'woocommerce' ),
1496
							'type'        => 'string',
1497
							'context'     => array( 'view', 'edit' ),
1498
						),
1499
						'discount_tax' => array(
1500
							'description' => __( 'Discount total tax.', 'woocommerce' ),
1501
							'type'        => 'string',
1502
							'context'     => array( 'view', 'edit' ),
1503
							'readonly'    => true,
1504
						),
1505
						'meta_data' => array(
1506
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1507
							'type'        => 'array',
1508
							'context'     => array( 'view', 'edit' ),
1509
							'properties'  => array(
1510
								'key' => array(
1511
									'description' => __( 'Meta key.', 'woocommerce' ),
1512
									'type'        => 'string',
1513
									'context'     => array( 'view', 'edit' ),
1514
								),
1515
								'value' => array(
1516
									'description' => __( 'Meta value.', 'woocommerce' ),
1517
									'type'        => 'string',
1518
									'context'     => array( 'view', 'edit' ),
1519
								),
1520
								'meta_id' => array(
1521
									'description' => __( 'Meta ID.', 'woocommerce' ),
1522
									'type'        => 'int',
1523
									'context'     => array( 'view', 'edit' ),
1524
									'readonly'    => true,
1525
								),
1526
							),
1527
						),
1528
					),
1529
				),
1530
				'refunds' => array(
1531
					'description' => __( 'List of refunds.', 'woocommerce' ),
1532
					'type'        => 'array',
1533
					'context'     => array( 'view', 'edit' ),
1534
					'readonly'    => true,
1535
					'properties'  => array(
1536
						'id' => array(
1537
							'description' => __( 'Refund ID.', 'woocommerce' ),
1538
							'type'        => 'integer',
1539
							'context'     => array( 'view', 'edit' ),
1540
							'readonly'    => true,
1541
						),
1542
						'reason' => array(
1543
							'description' => __( 'Refund reason.', 'woocommerce' ),
1544
							'type'        => 'string',
1545
							'context'     => array( 'view', 'edit' ),
1546
							'readonly'    => true,
1547
						),
1548
						'total' => array(
1549
							'description' => __( 'Refund total.', 'woocommerce' ),
1550
							'type'        => 'string',
1551
							'context'     => array( 'view', 'edit' ),
1552
							'readonly'    => true,
1553
						),
1554
					),
1555
				),
1556
				'set_paid' => array(
1557
					'description' => __( 'Define if the order is paid. It will set the status to processing and reduce stock items.', 'woocommerce' ),
1558
					'type'        => 'boolean',
1559
					'default'     => false,
1560
					'context'     => array( 'edit' ),
1561
				),
1562
			),
1563
		);
1564
1565
		return $this->add_additional_fields_schema( $schema );
1566
	}
1567
1568
	/**
1569
	 * Get the query params for collections.
1570
	 *
1571
	 * @return array
1572
	 */
1573
	public function get_collection_params() {
1574
		$params = parent::get_collection_params();
1575
1576
		$params['status'] = array(
1577
			'default'           => 'any',
1578
			'description'       => __( 'Limit result set to orders assigned a specific status.', 'woocommerce' ),
1579
			'type'              => 'string',
1580
			'enum'              => array_merge( array( 'any' ), $this->get_order_statuses() ),
1581
			'sanitize_callback' => 'sanitize_key',
1582
			'validate_callback' => 'rest_validate_request_arg',
1583
		);
1584
		$params['customer'] = array(
1585
			'description'       => __( 'Limit result set to orders assigned a specific customer.', 'woocommerce' ),
1586
			'type'              => 'integer',
1587
			'sanitize_callback' => 'absint',
1588
			'validate_callback' => 'rest_validate_request_arg',
1589
		);
1590
		$params['product'] = array(
1591
			'description'       => __( 'Limit result set to orders assigned a specific product.', 'woocommerce' ),
1592
			'type'              => 'integer',
1593
			'sanitize_callback' => 'absint',
1594
			'validate_callback' => 'rest_validate_request_arg',
1595
		);
1596
		$params['dp'] = array(
1597
			'default'           => 2,
1598
			'description'       => __( 'Number of decimal points to use in each resource.', 'woocommerce' ),
1599
			'type'              => 'integer',
1600
			'sanitize_callback' => 'absint',
1601
			'validate_callback' => 'rest_validate_request_arg',
1602
		);
1603
1604
		return $params;
1605
	}
1606
}
1607