Completed
Pull Request — master (#11716)
by Claudio
08:25
created

WC_REST_Orders_Controller::set_coupon()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
dl 0
loc 36
rs 4.909
c 0
b 0
f 0
eloc 18
nc 12
nop 3
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
	 * @param WC_Order_item $item
130
	 * @return array
131
	 */
132
	protected function get_order_item_data( $item ) {
133
		$data           = $item->get_data();
134
		$format_decimal = array( 'subtotal', 'subtotal_tax', 'total', 'total_tax', 'tax_total', 'shipping_tax_total' );
135
136
		// Format decimal values.
137 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...
138
			if ( isset( $data[ $key ] ) ) {
139
				$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
140
			}
141
		}
142
143
		// Add meta, SKU and PRICE to products.
144
		if ( is_callable( array( $item, 'get_product' ) ) ) {
145
			$data['sku']   = $item->get_product() ? $item->get_product()->get_sku(): null;
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_product() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
146
			$data['price'] = $item->get_total() / max( 1, $item->get_quantity() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
147
148
			// Format meta data.
149
			if ( isset( $data['meta_data'] ) ) {
150
				$hideprefix = 'true' === $this->request['all_item_meta'] ? null : '_';
151
				$item_meta  = $item->get_formatted_meta_data( $hideprefix );
152
153
				foreach ( $item_meta as $key => $values ) {
154
					// Label was used in previous version of API - set it here.
155
					$item_meta[ $key ]->label = $values->display_key;
156
					unset( $item_meta[ $key ]->display_key );
157
					unset( $item_meta[ $key ]->display_value );
158
				}
159
160
				$data['meta'] = array_values( $item_meta );
161
			}
162
		}
163
164
		// Format taxes.
165
		if ( ! empty( $data['taxes']['total'] ) ) {
166
			$taxes = array();
167
168
			foreach ( $data['taxes']['total'] as $tax_rate_id => $tax ) {
169
				$taxes[] = array(
170
					'id'       => $tax_rate_id,
171
					'total'    => $tax,
172
					'subtotal' => isset( $data['taxes']['subtotal'][ $tax_rate_id ] ) ? $data['taxes']['subtotal'][ $tax_rate_id ] : '',
173
				);
174
			}
175
			$data['taxes'] = $taxes;
176
		} elseif ( isset( $data['taxes'] ) ) {
177
			$data['taxes'] = array();
178
		}
179
180
		// Remove props we don't want to expose.
181
		unset( $data['order_id'] );
182
		unset( $data['type'] );
183
184
		return $data;
185
	}
186
187
	/**
188
	 * Prepare a single order output for response.
189
	 *
190
	 * @param WP_Post $post Post object.
191
	 * @param WP_REST_Request $request Request object.
192
	 * @return WP_REST_Response $data
193
	 */
194
	public function prepare_item_for_response( $post, $request ) {
195
		$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...
196
		$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...
197
		$order             = wc_get_order( $post );
198
		$data              = $order->get_data();
199
		$format_decimal    = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'shipping_total', 'shipping_tax', 'cart_tax', 'total', 'total_tax' );
200
		$format_date       = array( 'date_created', 'date_modified', 'date_completed' );
201
		$format_line_items = array( 'line_items', 'tax_lines', 'shipping_lines', 'fee_lines', 'coupon_lines' );
202
203
		// Format decimal values.
204 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...
205
			$data[ $key ] = wc_format_decimal( $data[ $key ], $this->request['dp'] );
206
		}
207
208
		// Format date values.
209
		foreach ( $format_date as $key ) {
210
			$data[ $key ] = $data[ $key ] ? wc_rest_prepare_date_response( get_gmt_from_date( date( 'Y-m-d H:i:s', $data[ $key ] ) ) ) : false;
211
		}
212
213
		// Format the order status.
214
		$data['status'] = 'wc-' === substr( $data['status'], 0, 3 ) ? substr( $data['status'], 3 ) : $data['status'];
215
216
		// Format line items.
217
		foreach ( $format_line_items as $key ) {
218
			$data[ $key ] = array_values( array_map( array( $this, 'get_order_item_data' ), $data[ $key ] ) );
219
		}
220
221
		// Refunds.
222
		foreach ( $order->get_refunds() as $refund ) {
223
			$data['refunds'][] = array(
224
				'id'     => $refund->id,
225
				'refund' => $refund->get_refund_reason() ? $refund->get_refund_reason() : '',
226
				'total'  => '-' . wc_format_decimal( $refund->get_refund_amount(), $this->request['dp'] ),
227
			);
228
		}
229
230
		$context  = ! empty( $request['context'] ) ? $request['context'] : 'view';
231
		$data     = $this->add_additional_fields_to_object( $data, $request );
232
		$data     = $this->filter_response_by_context( $data, $context );
233
		$response = rest_ensure_response( $data );
234
		$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...
235
236
		/**
237
		 * Filter the data for a response.
238
		 *
239
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
240
		 * prepared for the response.
241
		 *
242
		 * @param WP_REST_Response   $response   The response object.
243
		 * @param WP_Post            $post       Post object.
244
		 * @param WP_REST_Request    $request    Request object.
245
		 */
246
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request );
247
	}
248
249
	/**
250
	 * Prepare links for the request.
251
	 *
252
	 * @param WC_Order $order Order object.
253
	 * @return array Links for the given order.
254
	 */
255
	protected function prepare_links( $order ) {
256
		$links = array(
257
			'self' => array(
258
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $order->get_id() ) ),
259
			),
260
			'collection' => array(
261
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
262
			),
263
		);
264 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...
265
			$links['customer'] = array(
266
				'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $order->get_user_id() ) ),
267
			);
268
		}
269 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...
270
			$links['up'] = array(
271
				'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $order->get_parent_id() ) ),
272
			);
273
		}
274
		return $links;
275
	}
276
277
	/**
278
	 * Query args.
279
	 *
280
	 * @param array $args
281
	 * @param WP_REST_Request $request
282
	 * @return array
283
	 */
284
	public function query_args( $args, $request ) {
285
		global $wpdb;
286
287
		// Set post_status.
288
		if ( 'any' !== $request['status'] ) {
289
			$args['post_status'] = 'wc-' . $request['status'];
290
		} else {
291
			$args['post_status'] = 'any';
292
		}
293
294 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...
295
			if ( ! empty( $args['meta_query'] ) ) {
296
				$args['meta_query'] = array();
297
			}
298
299
			$args['meta_query'][] = array(
300
				'key'   => '_customer_user',
301
				'value' => $request['customer'],
302
				'type'  => 'NUMERIC',
303
			);
304
		}
305
306
		// Search by product.
307
		if ( ! empty( $request['product'] ) ) {
308
			$order_ids = $wpdb->get_col( $wpdb->prepare( "
309
				SELECT order_id
310
				FROM {$wpdb->prefix}woocommerce_order_items
311
				WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
312
				AND order_item_type = 'line_item'
313
			 ", $request['product'] ) );
314
315
			// Force WP_Query return empty if don't found any order.
316
			$order_ids = ! empty( $order_ids ) ? $order_ids : array( 0 );
317
318
			$args['post__in'] = $order_ids;
319
		}
320
321
		// Search.
322
		if ( ! empty( $args['s'] ) ) {
323
			$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...
324
325
			if ( ! empty( $order_ids ) ) {
326
				unset( $args['s'] );
327
				$args['post__in'] =  array_merge( $order_ids, array( 0 ) );
328
			}
329
		}
330
331
		return $args;
332
	}
333
334
	/**
335
	 * Prepare a single order for create.
336
	 *
337
	 * @param  WP_REST_Request $request Request object.
338
	 * @return WP_Error|WC_Order $data Object.
339
	 */
340
	protected function prepare_item_for_database( $request ) {
341
		$id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
342
		$order     = new WC_Order( $id );
343
		$schema    = $this->get_item_schema();
344
		$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
345
346
		// Handle all writable props
347
		foreach ( $data_keys as $key ) {
348
			$value = $request[ $key ];
349
350
			if ( ! is_null( $value ) ) {
351
				switch ( $key ) {
352
					case 'billing' :
353
					case 'shipping' :
354
						$this->update_address( $order, $value, $key );
355
						break;
356
					case 'line_items' :
357
					case 'shipping_lines' :
358
					case 'fee_lines' :
359
					case 'coupon_lines' :
360
						if ( is_array( $value ) ) {
361
							foreach ( $value as $item ) {
362
								if ( is_array( $item ) ) {
363
									if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
364
										$order->remove_item( $item['id'] );
365
									} else {
366
										$this->set_item( $order, $key, $item );
367
									}
368
								}
369
							}
370
						}
371
						break;
372
					case 'meta_data' :
373
						if ( is_array( $value ) ) {
374
							foreach ( $value as $meta ) {
375
								$order->update_meta_data( $meta['key'], $meta['value'], $meta['meta_id'] );
376
							}
377
						}
378
						break;
379
					default :
380
						if ( is_callable( array( $order, "set_{$key}" ) ) ) {
381
							$order->{"set_{$key}"}( $value );
382
						}
383
						break;
384
				}
385
			}
386
		}
387
388
		/**
389
		 * Filter the data for the insert.
390
		 *
391
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
392
		 * prepared for the response.
393
		 *
394
		 * @param WC_Order           $order      The prder object.
395
		 * @param WP_REST_Request    $request    Request object.
396
		 */
397
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request );
398
	}
399
400
	/**
401
	 * Create base WC Order object.
402
	 * @deprecated 2.7.0
403
	 * @param array $data
404
	 * @return WC_Order
405
	 */
406
	protected function create_base_order( $data ) {
407
		return wc_create_order( $data );
408
	}
409
410
	/**
411
	 * Only reutrn writeable props from schema.
412
	 * @param  array $schema
413
	 * @return bool
414
	 */
415
	protected function filter_writable_props( $schema ) {
416
		return empty( $schema['readonly'] );
417
	}
418
419
	/**
420
	 * Create order.
421
	 *
422
	 * @param WP_REST_Request $request Full details about the request.
423
	 * @return int|WP_Error
424
	 */
425
	protected function create_order( $request ) {
426
		try {
427
			// Make sure customer exists.
428
			if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] && false === get_user_by( 'id', $request['customer_id'] ) ) {
429
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id',__( 'Customer ID is invalid.', 'woocommerce' ), 400 );
430
			}
431
432
			$order = $this->prepare_item_for_database( $request );
433
			$order->set_created_via( 'rest-api' );
434
			$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
435
			$order->calculate_totals();
436
			$order->save();
437
438
			// Handle set paid
439
			if ( true === $request['set_paid'] ) {
440
				$order->payment_complete( $request['transaction_id'] );
441
			}
442
443
			return $order->get_id();
444
		} catch ( WC_Data_Exception $e ) {
445
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
446
		} catch ( WC_REST_Exception $e ) {
447
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
448
		}
449
	}
450
451
	/**
452
	 * Update order.
453
	 *
454
	 * @param WP_REST_Request $request Full details about the request.
455
	 * @return int|WP_Error
456
	 */
457
	protected function update_order( $request ) {
458
		try {
459
			$order = $this->prepare_item_for_database( $request );
460
			$order->save();
461
462
			// Handle set paid
463
			if ( $order->needs_payment() && true === $request['set_paid'] ) {
464
				$order->payment_complete( $request['transaction_id'] );
465
			}
466
467
			// If items have changed, recalculate order totals.
468
			if ( isset( $request[ 'billing' ], $request[ 'shipping' ], $request[ 'line_items' ], $request[ 'shipping' ], $request[ 'fee' ], $request[ 'coupon' ] ) ) {
469
				$order->calculate_totals();
470
			}
471
472
			return $order->get_id();
473
		} catch ( WC_Data_Exception $e ) {
474
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
475
		} catch ( WC_REST_Exception $e ) {
476
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
477
		}
478
	}
479
480
	/**
481
	 * Update address.
482
	 *
483
	 * @param WC_Order $order
484
	 * @param array $posted
485
	 * @param string $type
486
	 */
487
	protected function update_address( $order, $posted, $type = 'billing' ) {
488
		foreach ( $posted as $key => $value ) {
489
			if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) {
490
				$order->{"set_{$type}_{$key}"}( $value );
491
			}
492
		}
493
	}
494
495
	/**
496
	 * Create or update a line item.
497
	 *
498
	 * @param array $posted Line item data.
499
	 * @param string $action 'create' to add line item or 'update' to update it.
500
	 * @throws WC_REST_Exception Invalid data, server error.
501
	 */
502
	protected function prepare_line_items( $posted, $action = 'create' ) {
503
		// Product is always required.
504
		if ( empty( $posted['product_id'] ) && empty( $posted['sku'] ) && empty( $posted['variation_id'] ) ) {
505
			throw new WC_REST_Exception( 'woocommerce_rest_required_product_reference', __( 'Product ID or SKU is required.', 'woocommerce' ), 400 );
506
		}
507
508
		// Get product from ID or sku
509
		if ( ! empty( $posted['sku'] ) ) {
510
			$product_id = (int) wc_get_product_id_by_sku( $posted['sku'] );
511
		} elseif ( ! empty( $posted['product_id'] ) && empty( $posted['variation_id'] ) ) {
512
			$product_id = (int) $posted['product_id'];
513
		} elseif ( ! empty( $posted['variation_id'] ) ) {
514
			$product_id = (int) $posted['variation_id'];
515
		}
516
517
		$item    = new WC_Order_Item_Product( ! empty( $posted['id'] ) ? $posted['id'] : '' );
518
		$product = wc_get_product( $product_id );
519
520
		if ( $product !== $item->get_product() ) {
521
			$item->set_product( $product );
522
523
			if ( 'create' === $action ) {
524
				$qty = isset( $posted['quantity'] ) ? $posted['quantity'] : 1;
525
				$item->set_total( $product->get_price() * $qty );
526
				$item->set_subtotal( $product->get_price() * $qty );
527
			}
528
		}
529
530
		if ( isset( $posted['name'] ) ) {
531
			$item->set_name( $posted['name'] );
532
		}
533
534
		if ( isset( $posted['quantity'] ) ) {
535
			$item->set_quantity( $posted['quantity'] );
536
		}
537
538
		if ( isset( $posted['total'] ) ) {
539
			$item->set_total( floatval( $posted['total'] ) );
540
		}
541
542
		if ( isset( $posted['subtotal'] ) ) {
543
			$item->set_subtotal( floatval( $posted['subtotal'] ) );
544
		}
545
546
		if ( isset( $posted['tax_class'] ) ) {
547
			$item->set_tax_class( $posted['tax_class'] );
548
		}
549
550
		return $item;
551
	}
552
553
	/**
554
	 * Create or update an order shipping method.
555
	 *
556
	 * @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...
557
	 * @param string $action 'create' to add shipping or 'update' to update it.
558
	 * @throws WC_REST_Exception Invalid data, server error.
559
	 */
560
	protected function prepare_shipping_lines( $posted, $action ) {
561
		$item = new WC_Order_Item_Shipping( ! empty( $posted['id'] ) ? $posted['id'] : '' );
562
563
		if ( 'create' === $action ) {
564
			if ( empty( $posted['method_id'] ) ) {
565
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 );
566
			}
567
		}
568
569
		if ( isset( $posted['method_id'] ) ) {
570
			$item->set_method_id( $posted['method_id'] );
571
		}
572
573
		if ( isset( $posted['method_title'] ) ) {
574
			$item->set_method_title( $posted['method_title'] );
575
		}
576
577
		if ( isset( $posted['total'] ) ) {
578
			$item->set_total( floatval( $posted['total'] ) );
579
		}
580
581
		return $item;
582
	}
583
584
	/**
585
	 * Create or update an order fee.
586
	 *
587
	 * @param array $posted Item data.
588
	 * @param string $action 'create' to add fee or 'update' to update it.
589
	 * @throws WC_REST_Exception Invalid data, server error.
590
	 */
591
	protected function prepare_fee_lines( $posted, $action ) {
592
		$item = new WC_Order_Item_Fee( ! empty( $posted['id'] ) ? $posted['id'] : '' );
593
594 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...
595
			if ( empty( $posted['name'] ) ) {
596
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_fee_item', __( 'Fee name is required.', 'woocommerce' ), 400 );
597
			}
598
		}
599
600
		if ( isset( $posted['name'] ) ) {
601
			$item->set_name( $posted['name'] );
602
		}
603
604
		if ( isset( $posted['tax_class'] ) ) {
605
			$item->set_tax_class( $posted['tax_class'] );
606
		}
607
608
		if ( isset( $posted['tax_status'] ) ) {
609
			$item->set_tax_status( $posted['tax_status'] );
610
		}
611
612
		if ( isset( $posted['total'] ) ) {
613
			$item->set_total( floatval( $posted['total'] ) );
614
		}
615
616
		return $item;
617
	}
618
619
	/**
620
	 * Create or update an order coupon.
621
	 *
622
	 * @param array $posted Item data.
623
	 * @param string $action 'create' to add coupon or 'update' to update it.
624
	 * @throws WC_REST_Exception Invalid data, server error.
625
	 */
626
	protected function prepare_coupon_lines( $posted, $action ) {
627
		$item = new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' );
628
629 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...
630
			if ( empty( $posted['code'] ) ) {
631
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
632
			}
633
		}
634
635
		if ( isset( $posted['code'] ) ) {
636
			$item->set_code( $posted['code'] );
637
		}
638
639
		if ( isset( $posted['discount'] ) ) {
640
			$item->set_discount( floatval( $posted['discount'] ) );
641
		}
642
643
		return $item;
644
	}
645
646
	/**
647
	 * Wrapper method to create/update order items.
648
	 * When updating, the item ID provided is checked to ensure it is associated
649
	 * with the order.
650
	 *
651
	 * @param WC_Order $order order
652
	 * @param string $item_type
653
	 * @param array $posted item provided in the request body
654
	 * @throws WC_REST_Exception If item ID is not associated with order
655
	 */
656
	protected function set_item( $order, $item_type, $posted ) {
657
		global $wpdb;
658
659
		if ( ! empty( $posted['id'] ) ) {
660
			$action = 'update';
661
		} else {
662
			$action = 'create';
663
		}
664
665
		$method = 'prepare_' . $item_type;
666
667
		// Verify provided line item ID is associated with order.
668
		if ( 'update' === $action ) {
669
			$result = $wpdb->get_row(
670
				$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d",
671
				absint( $posted['id'] ),
672
				absint( $order->get_id() )
673
			) );
674
			if ( is_null( $result ) ) {
675
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 );
676
			}
677
		}
678
679
		// Prepare item data
680
		$item = $this->$method( $posted, $action );
681
682
		// Save or add to order
683
		if ( 'create' === $action ) {
684
			$order->add_item( $item );
685
		} else {
686
			$item->save();
687
		}
688
	}
689
690
	/**
691
	 * Helper method to check if the resource ID associated with the provided item is null.
692
	 * Items can be deleted by setting the resource ID to null.
693
	 *
694
	 * @param array $item Item provided in the request body.
695
	 * @return bool True if the item resource ID is null, false otherwise.
696
	 */
697
	protected function item_is_null( $item ) {
698
		$keys = array( 'product_id', 'method_id', 'title', 'code' );
699
700
		foreach ( $keys as $key ) {
701
			if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
702
				return true;
703
			}
704
		}
705
706
		return false;
707
	}
708
709
	/**
710
	 * Create a single item.
711
	 *
712
	 * @param WP_REST_Request $request Full details about the request.
713
	 * @return WP_Error|WP_REST_Response
714
	 */
715
	public function create_item( $request ) {
716
		if ( ! empty( $request['id'] ) ) {
717
			return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) );
718
		}
719
720
		$order_id = $this->create_order( $request );
721
		if ( is_wp_error( $order_id ) ) {
722
			return $order_id;
723
		}
724
725
		$post = get_post( $order_id );
726
		$this->update_additional_fields_for_object( $post, $request );
727
728
		/**
729
		 * Fires after a single item is created or updated via the REST API.
730
		 *
731
		 * @param object          $post      Inserted object (not a WP_Post object).
732
		 * @param WP_REST_Request $request   Request object.
733
		 * @param boolean         $creating  True when creating item, false when updating.
734
		 */
735
		do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
736
		$request->set_param( 'context', 'edit' );
737
		$response = $this->prepare_item_for_response( $post, $request );
738
		$response = rest_ensure_response( $response );
739
		$response->set_status( 201 );
740
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) );
741
742
		return $response;
743
	}
744
745
	/**
746
	 * Update a single order.
747
	 *
748
	 * @param WP_REST_Request $request Full details about the request.
749
	 * @return WP_Error|WP_REST_Response
750
	 */
751
	public function update_item( $request ) {
752
		try {
753
			$post_id = (int) $request['id'];
754
755 View Code Duplication
			if ( empty( $post_id ) || $this->post_type !== get_post_type( $post_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...
756
				return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
757
			}
758
759
			$order_id = $this->update_order( $request );
760
			if ( is_wp_error( $order_id ) ) {
761
				return $order_id;
762
			}
763
764
			$post = get_post( $order_id );
765
			$this->update_additional_fields_for_object( $post, $request );
766
767
			/**
768
			 * Fires after a single item is created or updated via the REST API.
769
			 *
770
			 * @param object          $post      Inserted object (not a WP_Post object).
771
			 * @param WP_REST_Request $request   Request object.
772
			 * @param boolean         $creating  True when creating item, false when updating.
773
			 */
774
			do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false );
775
			$request->set_param( 'context', 'edit' );
776
			$response = $this->prepare_item_for_response( $post, $request );
777
			return rest_ensure_response( $response );
778
779
		} catch ( Exception $e ) {
780
			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...
781
		}
782
	}
783
784
	/**
785
	 * Get order statuses without prefixes.
786
	 * @return array
787
	 */
788
	protected function get_order_statuses() {
789
		$order_statuses = array();
790
791
		foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
792
			$order_statuses[] = str_replace( 'wc-', '', $status );
793
		}
794
795
		return $order_statuses;
796
	}
797
798
	/**
799
	 * Get the Order's schema, conforming to JSON Schema.
800
	 *
801
	 * @return array
802
	 */
803
	public function get_item_schema() {
804
		$schema = array(
805
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
806
			'title'      => $this->post_type,
807
			'type'       => 'object',
808
			'properties' => array(
809
				'id' => array(
810
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
811
					'type'        => 'integer',
812
					'context'     => array( 'view', 'edit' ),
813
					'readonly'    => true,
814
				),
815
				'parent_id' => array(
816
					'description' => __( 'Parent order ID.', 'woocommerce' ),
817
					'type'        => 'integer',
818
					'context'     => array( 'view', 'edit' ),
819
				),
820
				'status' => array(
821
					'description' => __( 'Order status.', 'woocommerce' ),
822
					'type'        => 'string',
823
					'default'     => 'pending',
824
					'enum'        => $this->get_order_statuses(),
825
					'context'     => array( 'view', 'edit' ),
826
				),
827
				'order_key' => array(
828
					'description' => __( 'Order key.', 'woocommerce' ),
829
					'type'        => 'string',
830
					'context'     => array( 'view', 'edit' ),
831
					'readonly'    => true,
832
				),
833
				'currency' => array(
834
					'description' => __( 'Currency the order was created with, in ISO format.', 'woocommerce' ),
835
					'type'        => 'string',
836
					'default'     => get_woocommerce_currency(),
837
					'enum'        => array_keys( get_woocommerce_currencies() ),
838
					'context'     => array( 'view', 'edit' ),
839
				),
840
				'version' => array(
841
					'description' => __( 'Version of WooCommerce when the order was made.', 'woocommerce' ),
842
					'type'        => 'integer',
843
					'context'     => array( 'view', 'edit' ),
844
					'readonly'    => true,
845
				),
846
				'prices_include_tax' => array(
847
					'description' => __( 'True the prices included tax during checkout.', 'woocommerce' ),
848
					'type'        => 'boolean',
849
					'context'     => array( 'view', 'edit' ),
850
					'readonly'    => true,
851
				),
852
				'date_created' => array(
853
					'description' => __( "The date the order was created, in the site's timezone.", 'woocommerce' ),
854
					'type'        => 'date-time',
855
					'context'     => array( 'view', 'edit' ),
856
				),
857
				'date_modified' => array(
858
					'description' => __( "The date the order was last modified, in the site's timezone.", 'woocommerce' ),
859
					'type'        => 'date-time',
860
					'context'     => array( 'view', 'edit' ),
861
					'readonly'    => true,
862
				),
863
				'customer_id' => array(
864
					'description' => __( 'User ID who owns the order. 0 for guests.', 'woocommerce' ),
865
					'type'        => 'integer',
866
					'default'     => 0,
867
					'context'     => array( 'view', 'edit' ),
868
				),
869
				'discount_total' => array(
870
					'description' => __( 'Total discount amount for the order.', 'woocommerce' ),
871
					'type'        => 'string',
872
					'context'     => array( 'view', 'edit' ),
873
					'readonly'    => true,
874
				),
875
				'discount_tax' => array(
876
					'description' => __( 'Total discount tax amount for the order.', 'woocommerce' ),
877
					'type'        => 'string',
878
					'context'     => array( 'view', 'edit' ),
879
					'readonly'    => true,
880
				),
881
				'shipping_total' => array(
882
					'description' => __( 'Total shipping amount for the order.', 'woocommerce' ),
883
					'type'        => 'string',
884
					'context'     => array( 'view', 'edit' ),
885
					'readonly'    => true,
886
				),
887
				'shipping_tax' => array(
888
					'description' => __( 'Total shipping tax amount for the order.', 'woocommerce' ),
889
					'type'        => 'string',
890
					'context'     => array( 'view', 'edit' ),
891
					'readonly'    => true,
892
				),
893
				'cart_tax' => array(
894
					'description' => __( 'Sum of line item taxes only.', 'woocommerce' ),
895
					'type'        => 'string',
896
					'context'     => array( 'view', 'edit' ),
897
					'readonly'    => true,
898
				),
899
				'total' => array(
900
					'description' => __( 'Grand total.', 'woocommerce' ),
901
					'type'        => 'string',
902
					'context'     => array( 'view', 'edit' ),
903
					'readonly'    => true,
904
				),
905
				'total_tax' => array(
906
					'description' => __( 'Sum of all taxes.', 'woocommerce' ),
907
					'type'        => 'string',
908
					'context'     => array( 'view', 'edit' ),
909
					'readonly'    => true,
910
				),
911
				'billing' => array(
912
					'description' => __( 'Billing address.', 'woocommerce' ),
913
					'type'        => 'array',
914
					'context'     => array( 'view', 'edit' ),
915
					'properties'  => array(
916
						'first_name' => array(
917
							'description' => __( 'First name.', 'woocommerce' ),
918
							'type'        => 'string',
919
							'context'     => array( 'view', 'edit' ),
920
						),
921
						'last_name' => array(
922
							'description' => __( 'Last name.', 'woocommerce' ),
923
							'type'        => 'string',
924
							'context'     => array( 'view', 'edit' ),
925
						),
926
						'company' => array(
927
							'description' => __( 'Company name.', 'woocommerce' ),
928
							'type'        => 'string',
929
							'context'     => array( 'view', 'edit' ),
930
						),
931
						'address_1' => array(
932
							'description' => __( 'Address line 1.', 'woocommerce' ),
933
							'type'        => 'string',
934
							'context'     => array( 'view', 'edit' ),
935
						),
936
						'address_2' => array(
937
							'description' => __( 'Address line 2.', 'woocommerce' ),
938
							'type'        => 'string',
939
							'context'     => array( 'view', 'edit' ),
940
						),
941
						'city' => array(
942
							'description' => __( 'City name.', 'woocommerce' ),
943
							'type'        => 'string',
944
							'context'     => array( 'view', 'edit' ),
945
						),
946
						'state' => array(
947
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
948
							'type'        => 'string',
949
							'context'     => array( 'view', 'edit' ),
950
						),
951
						'postcode' => array(
952
							'description' => __( 'Postal code.', 'woocommerce' ),
953
							'type'        => 'string',
954
							'context'     => array( 'view', 'edit' ),
955
						),
956
						'country' => array(
957
							'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
958
							'type'        => 'string',
959
							'context'     => array( 'view', 'edit' ),
960
						),
961
						'email' => array(
962
							'description' => __( 'Email address.', 'woocommerce' ),
963
							'type'        => 'string',
964
							'format'      => 'email',
965
							'context'     => array( 'view', 'edit' ),
966
						),
967
						'phone' => array(
968
							'description' => __( 'Phone number.', 'woocommerce' ),
969
							'type'        => 'string',
970
							'context'     => array( 'view', 'edit' ),
971
						),
972
					),
973
				),
974
				'shipping' => array(
975
					'description' => __( 'Shipping address.', 'woocommerce' ),
976
					'type'        => 'array',
977
					'context'     => array( 'view', 'edit' ),
978
					'properties'  => array(
979
						'first_name' => array(
980
							'description' => __( 'First name.', 'woocommerce' ),
981
							'type'        => 'string',
982
							'context'     => array( 'view', 'edit' ),
983
						),
984
						'last_name' => array(
985
							'description' => __( 'Last name.', 'woocommerce' ),
986
							'type'        => 'string',
987
							'context'     => array( 'view', 'edit' ),
988
						),
989
						'company' => array(
990
							'description' => __( 'Company name.', 'woocommerce' ),
991
							'type'        => 'string',
992
							'context'     => array( 'view', 'edit' ),
993
						),
994
						'address_1' => array(
995
							'description' => __( 'Address line 1.', 'woocommerce' ),
996
							'type'        => 'string',
997
							'context'     => array( 'view', 'edit' ),
998
						),
999
						'address_2' => array(
1000
							'description' => __( 'Address line 2.', 'woocommerce' ),
1001
							'type'        => 'string',
1002
							'context'     => array( 'view', 'edit' ),
1003
						),
1004
						'city' => array(
1005
							'description' => __( 'City name.', 'woocommerce' ),
1006
							'type'        => 'string',
1007
							'context'     => array( 'view', 'edit' ),
1008
						),
1009
						'state' => array(
1010
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
1011
							'type'        => 'string',
1012
							'context'     => array( 'view', 'edit' ),
1013
						),
1014
						'postcode' => array(
1015
							'description' => __( 'Postal code.', 'woocommerce' ),
1016
							'type'        => 'string',
1017
							'context'     => array( 'view', 'edit' ),
1018
						),
1019
						'country' => array(
1020
							'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
1021
							'type'        => 'string',
1022
							'context'     => array( 'view', 'edit' ),
1023
						),
1024
					),
1025
				),
1026
				'payment_method' => array(
1027
					'description' => __( 'Payment method ID.', 'woocommerce' ),
1028
					'type'        => 'string',
1029
					'context'     => array( 'view', 'edit' ),
1030
				),
1031
				'payment_method_title' => array(
1032
					'description' => __( 'Payment method title.', 'woocommerce' ),
1033
					'type'        => 'string',
1034
					'context'     => array( 'view', 'edit' ),
1035
				),
1036
				'transaction_id' => array(
1037
					'description' => __( 'Unique transaction ID.', 'woocommerce' ),
1038
					'type'        => 'string',
1039
					'context'     => array( 'view', 'edit' ),
1040
				),
1041
				'customer_ip_address' => array(
1042
					'description' => __( "Customer's IP address.", 'woocommerce' ),
1043
					'type'        => 'string',
1044
					'context'     => array( 'view', 'edit' ),
1045
					'readonly'    => true,
1046
				),
1047
				'customer_user_agent' => array(
1048
					'description' => __( 'User agent of the customer.', 'woocommerce' ),
1049
					'type'        => 'string',
1050
					'context'     => array( 'view', 'edit' ),
1051
					'readonly'    => true,
1052
				),
1053
				'created_via' => array(
1054
					'description' => __( 'Shows where the order was created.', 'woocommerce' ),
1055
					'type'        => 'string',
1056
					'context'     => array( 'view', 'edit' ),
1057
					'readonly'    => true,
1058
				),
1059
				'customer_note' => array(
1060
					'description' => __( 'Note left by customer during checkout.', 'woocommerce' ),
1061
					'type'        => 'string',
1062
					'context'     => array( 'view', 'edit' ),
1063
				),
1064
				'date_completed' => array(
1065
					'description' => __( "The date the order was completed, in the site's timezone.", 'woocommerce' ),
1066
					'type'        => 'date-time',
1067
					'context'     => array( 'view', 'edit' ),
1068
				),
1069
				'date_paid' => array(
1070
					'description' => __( "The date the order has been paid, in the site's timezone.", 'woocommerce' ),
1071
					'type'        => 'date-time',
1072
					'context'     => array( 'view', 'edit' ),
1073
				),
1074
				'cart_hash' => array(
1075
					'description' => __( 'MD5 hash of cart items to ensure orders are not modified.', 'woocommerce' ),
1076
					'type'        => 'string',
1077
					'context'     => array( 'view', 'edit' ),
1078
					'readonly'    => true,
1079
				),
1080
				'number' => array(
1081
					'description' => __( 'Order number.', 'woocommerce' ),
1082
					'type'        => 'string',
1083
					'context'     => array( 'view', 'edit' ),
1084
					'readonly'    => true,
1085
				),
1086
				'meta_data' => array(
1087
					'description' => __( 'Order meta data.', 'woocommerce' ),
1088
					'type'        => 'array',
1089
					'context'     => array( 'view', 'edit' ),
1090
					'properties'  => array(
1091
						'key' => array(
1092
							'description' => __( 'Meta key.', 'woocommerce' ),
1093
							'type'        => 'string',
1094
							'context'     => array( 'view', 'edit' ),
1095
						),
1096
						'value' => array(
1097
							'description' => __( 'Meta value.', 'woocommerce' ),
1098
							'type'        => 'string',
1099
							'context'     => array( 'view', 'edit' ),
1100
						),
1101
						'meta_id' => array(
1102
							'description' => __( 'Meta ID.', 'woocommerce' ),
1103
							'type'        => 'int',
1104
							'context'     => array( 'view', 'edit' ),
1105
							'readonly'    => true,
1106
						),
1107
					),
1108
				),
1109
				'line_items' => array(
1110
					'description' => __( 'Line items data.', 'woocommerce' ),
1111
					'type'        => 'array',
1112
					'context'     => array( 'view', 'edit' ),
1113
					'properties'  => array(
1114
						'id' => array(
1115
							'description' => __( 'Item ID.', 'woocommerce' ),
1116
							'type'        => 'integer',
1117
							'context'     => array( 'view', 'edit' ),
1118
							'readonly'    => true,
1119
						),
1120
						'name' => array(
1121
							'description' => __( 'Product name.', 'woocommerce' ),
1122
							'type'        => 'string',
1123
							'context'     => array( 'view', 'edit' ),
1124
						),
1125
						'product_id' => array(
1126
							'description' => __( 'Product ID.', 'woocommerce' ),
1127
							'type'        => 'integer',
1128
							'context'     => array( 'view', 'edit' ),
1129
						),
1130
						'variation_id' => array(
1131
							'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
1132
							'type'        => 'integer',
1133
							'context'     => array( 'view', 'edit' ),
1134
						),
1135
						'quantity' => array(
1136
							'description' => __( 'Quantity ordered.', 'woocommerce' ),
1137
							'type'        => 'integer',
1138
							'context'     => array( 'view', 'edit' ),
1139
						),
1140
						'tax_class' => array(
1141
							'description' => __( 'Tax class of product.', 'woocommerce' ),
1142
							'type'        => 'integer',
1143
							'context'     => array( 'view', 'edit' ),
1144
						),
1145
						'subtotal' => array(
1146
							'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
1147
							'type'        => 'string',
1148
							'context'     => array( 'view', 'edit' ),
1149
						),
1150
						'subtotal_tax' => array(
1151
							'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),
1152
							'type'        => 'string',
1153
							'context'     => array( 'view', 'edit' ),
1154
							'readonly'    => true,
1155
						),
1156
						'total' => array(
1157
							'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1158
							'type'        => 'string',
1159
							'context'     => array( 'view', 'edit' ),
1160
						),
1161
						'total_tax' => array(
1162
							'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1163
							'type'        => 'string',
1164
							'context'     => array( 'view', 'edit' ),
1165
							'readonly'    => true,
1166
						),
1167
						'taxes' => array(
1168
							'description' => __( 'Line taxes.', 'woocommerce' ),
1169
							'type'        => 'array',
1170
							'context'     => array( 'view', 'edit' ),
1171
							'readonly'    => true,
1172
							'properties'  => array(
1173
								'id' => array(
1174
									'description' => __( 'Tax rate ID.', 'woocommerce' ),
1175
									'type'        => 'integer',
1176
									'context'     => array( 'view', 'edit' ),
1177
								),
1178
								'total' => array(
1179
									'description' => __( 'Tax total.', 'woocommerce' ),
1180
									'type'        => 'string',
1181
									'context'     => array( 'view', 'edit' ),
1182
								),
1183
								'subtotal' => array(
1184
									'description' => __( 'Tax subtotal.', 'woocommerce' ),
1185
									'type'        => 'string',
1186
									'context'     => array( 'view', 'edit' ),
1187
								),
1188
							),
1189
						),
1190
						'meta_data' => array(
1191
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1192
							'type'        => 'array',
1193
							'context'     => array( 'view', 'edit' ),
1194
							'properties'  => array(
1195
								'key' => array(
1196
									'description' => __( 'Meta key.', 'woocommerce' ),
1197
									'type'        => 'string',
1198
									'context'     => array( 'view', 'edit' ),
1199
								),
1200
								'value' => array(
1201
									'description' => __( 'Meta value.', 'woocommerce' ),
1202
									'type'        => 'string',
1203
									'context'     => array( 'view', 'edit' ),
1204
								),
1205
								'meta_id' => array(
1206
									'description' => __( 'Meta ID.', 'woocommerce' ),
1207
									'type'        => 'int',
1208
									'context'     => array( 'view', 'edit' ),
1209
									'readonly'    => true,
1210
								),
1211
							),
1212
						),
1213
						'sku' => array(
1214
							'description' => __( 'Product SKU.', 'woocommerce' ),
1215
							'type'        => 'string',
1216
							'context'     => array( 'view', 'edit' ),
1217
							'readonly'    => true,
1218
						),
1219
						'price' => array(
1220
							'description' => __( 'Product price.', 'woocommerce' ),
1221
							'type'        => 'string',
1222
							'context'     => array( 'view', 'edit' ),
1223
							'readonly'    => true,
1224
						),
1225
						'meta' => array(
1226
							'description' => __( 'Order item meta data (formatted).', 'woocommerce' ),
1227
							'type'        => 'array',
1228
							'context'     => array( 'view', 'edit' ),
1229
							'readonly'    => true,
1230
							'properties'  => array(
1231
								'key' => array(
1232
									'description' => __( 'Meta key.', 'woocommerce' ),
1233
									'type'        => 'string',
1234
									'context'     => array( 'view', 'edit' ),
1235
									'readonly'    => true,
1236
								),
1237
								'label' => array(
1238
									'description' => __( 'Meta label.', 'woocommerce' ),
1239
									'type'        => 'string',
1240
									'context'     => array( 'view', 'edit' ),
1241
									'readonly'    => true,
1242
								),
1243
								'value' => array(
1244
									'description' => __( 'Meta value.', 'woocommerce' ),
1245
									'type'        => 'string',
1246
									'context'     => array( 'view', 'edit' ),
1247
									'readonly'    => true,
1248
								),
1249
							),
1250
						),
1251
					),
1252
				),
1253
				'tax_lines' => array(
1254
					'description' => __( 'Tax lines data.', 'woocommerce' ),
1255
					'type'        => 'array',
1256
					'context'     => array( 'view', 'edit' ),
1257
					'readonly'    => true,
1258
					'properties'  => array(
1259
						'id' => array(
1260
							'description' => __( 'Item ID.', 'woocommerce' ),
1261
							'type'        => 'integer',
1262
							'context'     => array( 'view', 'edit' ),
1263
							'readonly'    => true,
1264
						),
1265
						'rate_code' => array(
1266
							'description' => __( 'Tax rate code.', 'woocommerce' ),
1267
							'type'        => 'string',
1268
							'context'     => array( 'view', 'edit' ),
1269
							'readonly'    => true,
1270
						),
1271
						'rate_id' => array(
1272
							'description' => __( 'Tax rate ID.', 'woocommerce' ),
1273
							'type'        => 'string',
1274
							'context'     => array( 'view', 'edit' ),
1275
							'readonly'    => true,
1276
						),
1277
						'label' => array(
1278
							'description' => __( 'Tax rate label.', 'woocommerce' ),
1279
							'type'        => 'string',
1280
							'context'     => array( 'view', 'edit' ),
1281
							'readonly'    => true,
1282
						),
1283
						'compound' => array(
1284
							'description' => __( 'Show if is a compound tax rate.', 'woocommerce' ),
1285
							'type'        => 'boolean',
1286
							'context'     => array( 'view', 'edit' ),
1287
							'readonly'    => true,
1288
						),
1289
						'tax_total' => array(
1290
							'description' => __( 'Tax total (not including shipping taxes).', 'woocommerce' ),
1291
							'type'        => 'string',
1292
							'context'     => array( 'view', 'edit' ),
1293
							'readonly'    => true,
1294
						),
1295
						'shipping_tax_total' => array(
1296
							'description' => __( 'Shipping tax total.', 'woocommerce' ),
1297
							'type'        => 'string',
1298
							'context'     => array( 'view', 'edit' ),
1299
							'readonly'    => true,
1300
						),
1301
						'meta_data' => array(
1302
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1303
							'type'        => 'array',
1304
							'context'     => array( 'view', 'edit' ),
1305
							'properties'  => array(
1306
								'key' => array(
1307
									'description' => __( 'Meta key.', 'woocommerce' ),
1308
									'type'        => 'string',
1309
									'context'     => array( 'view', 'edit' ),
1310
								),
1311
								'value' => array(
1312
									'description' => __( 'Meta value.', 'woocommerce' ),
1313
									'type'        => 'string',
1314
									'context'     => array( 'view', 'edit' ),
1315
								),
1316
								'meta_id' => array(
1317
									'description' => __( 'Meta ID.', 'woocommerce' ),
1318
									'type'        => 'int',
1319
									'context'     => array( 'view', 'edit' ),
1320
									'readonly'    => true,
1321
								),
1322
							),
1323
						),
1324
					),
1325
				),
1326
				'shipping_lines' => array(
1327
					'description' => __( 'Shipping lines data.', 'woocommerce' ),
1328
					'type'        => 'array',
1329
					'context'     => array( 'view', 'edit' ),
1330
					'properties'  => array(
1331
						'id' => array(
1332
							'description' => __( 'Item ID.', 'woocommerce' ),
1333
							'type'        => 'integer',
1334
							'context'     => array( 'view', 'edit' ),
1335
							'readonly'    => true,
1336
						),
1337
						'method_title' => array(
1338
							'description' => __( 'Shipping method name.', 'woocommerce' ),
1339
							'type'        => 'string',
1340
							'context'     => array( 'view', 'edit' ),
1341
						),
1342
						'method_id' => array(
1343
							'description' => __( 'Shipping method ID.', 'woocommerce' ),
1344
							'type'        => 'string',
1345
							'context'     => array( 'view', 'edit' ),
1346
						),
1347
						'total' => array(
1348
							'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1349
							'type'        => 'string',
1350
							'context'     => array( 'view', 'edit' ),
1351
						),
1352
						'total_tax' => array(
1353
							'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1354
							'type'        => 'string',
1355
							'context'     => array( 'view', 'edit' ),
1356
							'readonly'    => true,
1357
						),
1358
						'taxes' => array(
1359
							'description' => __( 'Line taxes.', 'woocommerce' ),
1360
							'type'        => 'array',
1361
							'context'     => array( 'view', 'edit' ),
1362
							'readonly'    => true,
1363
							'properties'  => array(
1364
								'id' => array(
1365
									'description' => __( 'Tax rate ID.', 'woocommerce' ),
1366
									'type'        => 'integer',
1367
									'context'     => array( 'view', 'edit' ),
1368
									'readonly'    => true,
1369
								),
1370
								'total' => array(
1371
									'description' => __( 'Tax total.', 'woocommerce' ),
1372
									'type'        => 'string',
1373
									'context'     => array( 'view', 'edit' ),
1374
									'readonly'    => true,
1375
								),
1376
							),
1377
						),
1378
						'meta_data' => array(
1379
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1380
							'type'        => 'array',
1381
							'context'     => array( 'view', 'edit' ),
1382
							'properties'  => array(
1383
								'key' => array(
1384
									'description' => __( 'Meta key.', 'woocommerce' ),
1385
									'type'        => 'string',
1386
									'context'     => array( 'view', 'edit' ),
1387
								),
1388
								'value' => array(
1389
									'description' => __( 'Meta value.', 'woocommerce' ),
1390
									'type'        => 'string',
1391
									'context'     => array( 'view', 'edit' ),
1392
								),
1393
								'meta_id' => array(
1394
									'description' => __( 'Meta ID.', 'woocommerce' ),
1395
									'type'        => 'int',
1396
									'context'     => array( 'view', 'edit' ),
1397
									'readonly'    => true,
1398
								),
1399
							),
1400
						),
1401
					),
1402
				),
1403
				'fee_lines' => array(
1404
					'description' => __( 'Fee lines data.', 'woocommerce' ),
1405
					'type'        => 'array',
1406
					'context'     => array( 'view', 'edit' ),
1407
					'properties'  => array(
1408
						'id' => array(
1409
							'description' => __( 'Item ID.', 'woocommerce' ),
1410
							'type'        => 'integer',
1411
							'context'     => array( 'view', 'edit' ),
1412
							'readonly'    => true,
1413
						),
1414
						'name' => array(
1415
							'description' => __( 'Fee name.', 'woocommerce' ),
1416
							'type'        => 'string',
1417
							'context'     => array( 'view', 'edit' ),
1418
						),
1419
						'tax_class' => array(
1420
							'description' => __( 'Tax class of fee.', 'woocommerce' ),
1421
							'type'        => 'string',
1422
							'context'     => array( 'view', 'edit' ),
1423
						),
1424
						'tax_status' => array(
1425
							'description' => __( 'Tax status of fee.', 'woocommerce' ),
1426
							'type'        => 'string',
1427
							'context'     => array( 'view', 'edit' ),
1428
							'enum'        => array( 'taxable', 'none' ),
1429
						),
1430
						'total' => array(
1431
							'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1432
							'type'        => 'string',
1433
							'context'     => array( 'view', 'edit' ),
1434
						),
1435
						'total_tax' => array(
1436
							'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1437
							'type'        => 'string',
1438
							'context'     => array( 'view', 'edit' ),
1439
							'readonly'    => true,
1440
						),
1441
						'taxes' => array(
1442
							'description' => __( 'Line taxes.', 'woocommerce' ),
1443
							'type'        => 'array',
1444
							'context'     => array( 'view', 'edit' ),
1445
							'readonly'    => true,
1446
							'properties'  => array(
1447
								'id' => array(
1448
									'description' => __( 'Tax rate ID.', 'woocommerce' ),
1449
									'type'        => 'integer',
1450
									'context'     => array( 'view', 'edit' ),
1451
									'readonly'    => true,
1452
								),
1453
								'total' => array(
1454
									'description' => __( 'Tax total.', 'woocommerce' ),
1455
									'type'        => 'string',
1456
									'context'     => array( 'view', 'edit' ),
1457
									'readonly'    => true,
1458
								),
1459
								'subtotal' => array(
1460
									'description' => __( 'Tax subtotal.', 'woocommerce' ),
1461
									'type'        => 'string',
1462
									'context'     => array( 'view', 'edit' ),
1463
									'readonly'    => true,
1464
								),
1465
							),
1466
						),
1467
						'meta_data' => array(
1468
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1469
							'type'        => 'array',
1470
							'context'     => array( 'view', 'edit' ),
1471
							'properties'  => array(
1472
								'key' => array(
1473
									'description' => __( 'Meta key.', 'woocommerce' ),
1474
									'type'        => 'string',
1475
									'context'     => array( 'view', 'edit' ),
1476
								),
1477
								'value' => array(
1478
									'description' => __( 'Meta value.', 'woocommerce' ),
1479
									'type'        => 'string',
1480
									'context'     => array( 'view', 'edit' ),
1481
								),
1482
								'meta_id' => array(
1483
									'description' => __( 'Meta ID.', 'woocommerce' ),
1484
									'type'        => 'int',
1485
									'context'     => array( 'view', 'edit' ),
1486
									'readonly'    => true,
1487
								),
1488
							),
1489
						),
1490
					),
1491
				),
1492
				'coupon_lines' => array(
1493
					'description' => __( 'Coupons line data.', 'woocommerce' ),
1494
					'type'        => 'array',
1495
					'context'     => array( 'view', 'edit' ),
1496
					'properties'  => array(
1497
						'id' => array(
1498
							'description' => __( 'Item ID.', 'woocommerce' ),
1499
							'type'        => 'integer',
1500
							'context'     => array( 'view', 'edit' ),
1501
							'readonly'    => true,
1502
						),
1503
						'code' => array(
1504
							'description' => __( 'Coupon code.', 'woocommerce' ),
1505
							'type'        => 'string',
1506
							'context'     => array( 'view', 'edit' ),
1507
						),
1508
						'discount' => array(
1509
							'description' => __( 'Discount total.', 'woocommerce' ),
1510
							'type'        => 'string',
1511
							'context'     => array( 'view', 'edit' ),
1512
						),
1513
						'discount_tax' => array(
1514
							'description' => __( 'Discount total tax.', 'woocommerce' ),
1515
							'type'        => 'string',
1516
							'context'     => array( 'view', 'edit' ),
1517
							'readonly'    => true,
1518
						),
1519
						'meta_data' => array(
1520
							'description' => __( 'Order item meta data.', 'woocommerce' ),
1521
							'type'        => 'array',
1522
							'context'     => array( 'view', 'edit' ),
1523
							'properties'  => array(
1524
								'key' => array(
1525
									'description' => __( 'Meta key.', 'woocommerce' ),
1526
									'type'        => 'string',
1527
									'context'     => array( 'view', 'edit' ),
1528
								),
1529
								'value' => array(
1530
									'description' => __( 'Meta value.', 'woocommerce' ),
1531
									'type'        => 'string',
1532
									'context'     => array( 'view', 'edit' ),
1533
								),
1534
								'meta_id' => array(
1535
									'description' => __( 'Meta ID.', 'woocommerce' ),
1536
									'type'        => 'int',
1537
									'context'     => array( 'view', 'edit' ),
1538
									'readonly'    => true,
1539
								),
1540
							),
1541
						),
1542
					),
1543
				),
1544
				'refunds' => array(
1545
					'description' => __( 'List of refunds.', 'woocommerce' ),
1546
					'type'        => 'array',
1547
					'context'     => array( 'view', 'edit' ),
1548
					'readonly'    => true,
1549
					'properties'  => array(
1550
						'id' => array(
1551
							'description' => __( 'Refund ID.', 'woocommerce' ),
1552
							'type'        => 'integer',
1553
							'context'     => array( 'view', 'edit' ),
1554
							'readonly'    => true,
1555
						),
1556
						'reason' => array(
1557
							'description' => __( 'Refund reason.', 'woocommerce' ),
1558
							'type'        => 'string',
1559
							'context'     => array( 'view', 'edit' ),
1560
							'readonly'    => true,
1561
						),
1562
						'total' => array(
1563
							'description' => __( 'Refund total.', 'woocommerce' ),
1564
							'type'        => 'string',
1565
							'context'     => array( 'view', 'edit' ),
1566
							'readonly'    => true,
1567
						),
1568
					),
1569
				),
1570
				'set_paid' => array(
1571
					'description' => __( 'Define if the order is paid. It will set the status to processing and reduce stock items.', 'woocommerce' ),
1572
					'type'        => 'boolean',
1573
					'default'     => false,
1574
					'context'     => array( 'edit' ),
1575
				),
1576
			),
1577
		);
1578
1579
		return $this->add_additional_fields_schema( $schema );
1580
	}
1581
1582
	/**
1583
	 * Get the query params for collections.
1584
	 *
1585
	 * @return array
1586
	 */
1587
	public function get_collection_params() {
1588
		$params = parent::get_collection_params();
1589
1590
		$params['status'] = array(
1591
			'default'           => 'any',
1592
			'description'       => __( 'Limit result set to orders assigned a specific status.', 'woocommerce' ),
1593
			'type'              => 'string',
1594
			'enum'              => array_merge( array( 'any' ), $this->get_order_statuses() ),
1595
			'sanitize_callback' => 'sanitize_key',
1596
			'validate_callback' => 'rest_validate_request_arg',
1597
		);
1598
		$params['customer'] = array(
1599
			'description'       => __( 'Limit result set to orders assigned a specific customer.', 'woocommerce' ),
1600
			'type'              => 'integer',
1601
			'sanitize_callback' => 'absint',
1602
			'validate_callback' => 'rest_validate_request_arg',
1603
		);
1604
		$params['product'] = array(
1605
			'description'       => __( 'Limit result set to orders assigned a specific product.', 'woocommerce' ),
1606
			'type'              => 'integer',
1607
			'sanitize_callback' => 'absint',
1608
			'validate_callback' => 'rest_validate_request_arg',
1609
		);
1610
		$params['dp'] = array(
1611
			'default'           => 2,
1612
			'description'       => __( 'Number of decimal points to use in each resource.', 'woocommerce' ),
1613
			'type'              => 'integer',
1614
			'sanitize_callback' => 'absint',
1615
			'validate_callback' => 'rest_validate_request_arg',
1616
		);
1617
1618
		return $params;
1619
	}
1620
}
1621