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

api/v1/class-wc-rest-orders-controller.php (2 issues)

Upgrade to new PHP Analysis Engine

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

1
<?php
2
/**
3
 * 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    3.0.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_V1_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
	 * Initialize orders actions.
48
	 */
49
	public function __construct() {
50
		add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 );
51
	}
52
53
	/**
54
	 * Register the routes for orders.
55
	 */
56 View Code Duplication
	public function register_routes() {
57
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
58
			array(
59
				'methods'             => WP_REST_Server::READABLE,
60
				'callback'            => array( $this, 'get_items' ),
61
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
62
				'args'                => $this->get_collection_params(),
63
			),
64
			array(
65
				'methods'             => WP_REST_Server::CREATABLE,
66
				'callback'            => array( $this, 'create_item' ),
67
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
68
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
69
			),
70
			'schema' => array( $this, 'get_public_item_schema' ),
71
		) );
72
73
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
74
			'args' => array(
75
				'id' => array(
76
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
77
					'type'        => 'integer',
78
				),
79
			),
80
			array(
81
				'methods'             => WP_REST_Server::READABLE,
82
				'callback'            => array( $this, 'get_item' ),
83
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
84
				'args'                => array(
85
					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
86
				),
87
			),
88
			array(
89
				'methods'             => WP_REST_Server::EDITABLE,
90
				'callback'            => array( $this, 'update_item' ),
91
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
92
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
93
			),
94
			array(
95
				'methods'             => WP_REST_Server::DELETABLE,
96
				'callback'            => array( $this, 'delete_item' ),
97
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
98
				'args'                => array(
99
					'force' => array(
100
						'default'     => false,
101
						'type'        => 'boolean',
102
						'description' => __( 'Whether to bypass trash and force deletion.', 'woocommerce' ),
103
					),
104
				),
105
			),
106
			'schema' => array( $this, 'get_public_item_schema' ),
107
		) );
108
109
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
110
			array(
111
				'methods'             => WP_REST_Server::EDITABLE,
112
				'callback'            => array( $this, 'batch_items' ),
113
				'permission_callback' => array( $this, 'batch_items_permissions_check' ),
114
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
115
			),
116
			'schema' => array( $this, 'get_public_batch_schema' ),
117
		) );
118
	}
119
120
	/**
121
	 * Prepare a single order output for response.
122
	 *
123
	 * @param WP_Post $post Post object.
124
	 * @param WP_REST_Request $request Request object.
125
	 * @return WP_REST_Response $data
126
	 */
127
	public function prepare_item_for_response( $post, $request ) {
128
		$order = wc_get_order( $post );
129
		$dp    = is_null( $request['dp'] ) ? wc_get_price_decimals() : absint( $request['dp'] );
130
131
		$data = array(
132
			'id'                   => $order->get_id(),
133
			'parent_id'            => $order->get_parent_id(),
134
			'status'               => $order->get_status(),
135
			'order_key'            => $order->get_order_key(),
136
			'number'               => $order->get_order_number(),
137
			'currency'             => $order->get_currency(),
138
			'version'              => $order->get_version(),
139
			'prices_include_tax'   => $order->get_prices_include_tax(),
140
			'date_created'         => wc_rest_prepare_date_response( $order->get_date_created() ),  // v1 API used UTC.
141
			'date_modified'        => wc_rest_prepare_date_response( $order->get_date_modified() ), // v1 API used UTC.
142
			'customer_id'          => $order->get_customer_id(),
143
			'discount_total'       => wc_format_decimal( $order->get_total_discount(), $dp ),
144
			'discount_tax'         => wc_format_decimal( $order->get_discount_tax(), $dp ),
145
			'shipping_total'       => wc_format_decimal( $order->get_shipping_total(), $dp ),
146
			'shipping_tax'         => wc_format_decimal( $order->get_shipping_tax(), $dp ),
147
			'cart_tax'             => wc_format_decimal( $order->get_cart_tax(), $dp ),
148
			'total'                => wc_format_decimal( $order->get_total(), $dp ),
149
			'total_tax'            => wc_format_decimal( $order->get_total_tax(), $dp ),
150
			'billing'              => array(),
151
			'shipping'             => array(),
152
			'payment_method'       => $order->get_payment_method(),
153
			'payment_method_title' => $order->get_payment_method_title(),
154
			'transaction_id'       => $order->get_transaction_id(),
155
			'customer_ip_address'  => $order->get_customer_ip_address(),
156
			'customer_user_agent'  => $order->get_customer_user_agent(),
157
			'created_via'          => $order->get_created_via(),
158
			'customer_note'        => $order->get_customer_note(),
159
			'date_completed'       => wc_rest_prepare_date_response( $order->get_date_completed(), false ), // v1 API used local time.
160
			'date_paid'            => wc_rest_prepare_date_response( $order->get_date_paid(), false ), // v1 API used local time.
161
			'cart_hash'            => $order->get_cart_hash(),
162
			'line_items'           => array(),
163
			'tax_lines'            => array(),
164
			'shipping_lines'       => array(),
165
			'fee_lines'            => array(),
166
			'coupon_lines'         => array(),
167
			'refunds'              => array(),
168
		);
169
170
		// Add addresses.
171
		$data['billing']  = $order->get_address( 'billing' );
172
		$data['shipping'] = $order->get_address( 'shipping' );
173
174
		// Add line items.
175 View Code Duplication
		foreach ( $order->get_items() as $item_id => $item ) {
176
			$product      = $order->get_product_from_item( $item );
177
			$product_id   = 0;
178
			$variation_id = 0;
179
			$product_sku  = null;
180
181
			// Check if the product exists.
182
			if ( is_object( $product ) ) {
183
				$product_id   = $item->get_product_id();
184
				$variation_id = $item->get_variation_id();
185
				$product_sku  = $product->get_sku();
186
			}
187
188
			$item_meta = array();
189
190
			$hideprefix = 'true' === $request['all_item_meta'] ? null : '_';
191
192
			foreach ( $item->get_formatted_meta_data( $hideprefix, true ) as $meta_key => $formatted_meta ) {
193
				$item_meta[] = array(
194
					'key'   => $formatted_meta->key,
195
					'label' => $formatted_meta->display_key,
196
					'value' => wc_clean( $formatted_meta->display_value ),
197
				);
198
			}
199
200
			$line_item = array(
201
				'id'           => $item_id,
202
				'name'         => $item['name'],
203
				'sku'          => $product_sku,
204
				'product_id'   => (int) $product_id,
205
				'variation_id' => (int) $variation_id,
206
				'quantity'     => wc_stock_amount( $item['qty'] ),
207
				'tax_class'    => ! empty( $item['tax_class'] ) ? $item['tax_class'] : '',
208
				'price'        => wc_format_decimal( $order->get_item_total( $item, false, false ), $dp ),
209
				'subtotal'     => wc_format_decimal( $order->get_line_subtotal( $item, false, false ), $dp ),
210
				'subtotal_tax' => wc_format_decimal( $item['line_subtotal_tax'], $dp ),
211
				'total'        => wc_format_decimal( $order->get_line_total( $item, false, false ), $dp ),
212
				'total_tax'    => wc_format_decimal( $item['line_tax'], $dp ),
213
				'taxes'        => array(),
214
				'meta'         => $item_meta,
215
			);
216
217
			$item_line_taxes = maybe_unserialize( $item['line_tax_data'] );
218
			if ( isset( $item_line_taxes['total'] ) ) {
219
				$line_tax = array();
220
221
				foreach ( $item_line_taxes['total'] as $tax_rate_id => $tax ) {
222
					$line_tax[ $tax_rate_id ] = array(
223
						'id'       => $tax_rate_id,
224
						'total'    => $tax,
225
						'subtotal' => '',
226
					);
227
				}
228
229
				foreach ( $item_line_taxes['subtotal'] as $tax_rate_id => $tax ) {
230
					$line_tax[ $tax_rate_id ]['subtotal'] = $tax;
231
				}
232
233
				$line_item['taxes'] = array_values( $line_tax );
234
			}
235
236
			$data['line_items'][] = $line_item;
237
		}
238
239
		// Add taxes.
240
		foreach ( $order->get_items( 'tax' ) as $key => $tax ) {
241
			$tax_line = array(
242
				'id'                 => $key,
243
				'rate_code'          => $tax['name'],
244
				'rate_id'            => $tax['rate_id'],
245
				'label'              => isset( $tax['label'] ) ? $tax['label'] : $tax['name'],
246
				'compound'           => (bool) $tax['compound'],
247
				'tax_total'          => wc_format_decimal( $tax['tax_amount'], $dp ),
248
				'shipping_tax_total' => wc_format_decimal( $tax['shipping_tax_amount'], $dp ),
249
			);
250
251
			$data['tax_lines'][] = $tax_line;
252
		}
253
254
		// Add shipping.
255
		foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) {
256
			$shipping_line = array(
257
				'id'           => $shipping_item_id,
258
				'method_title' => $shipping_item['name'],
259
				'method_id'    => $shipping_item['method_id'],
260
				'total'        => wc_format_decimal( $shipping_item['cost'], $dp ),
261
				'total_tax'    => wc_format_decimal( '', $dp ),
262
				'taxes'        => array(),
263
			);
264
265
			$shipping_taxes = $shipping_item->get_taxes();
266
267
			if ( ! empty( $shipping_taxes['total'] ) ) {
268
				$shipping_line['total_tax'] = wc_format_decimal( array_sum( $shipping_taxes['total'] ), $dp );
269
270
				foreach ( $shipping_taxes['total'] as $tax_rate_id => $tax ) {
271
					$shipping_line['taxes'][] = array(
272
						'id'       => $tax_rate_id,
273
						'total'    => $tax,
274
					);
275
				}
276
			}
277
278
			$data['shipping_lines'][] = $shipping_line;
279
		}
280
281
		// Add fees.
282
		foreach ( $order->get_fees() as $fee_item_id => $fee_item ) {
283
			$fee_line = array(
284
				'id'         => $fee_item_id,
285
				'name'       => $fee_item['name'],
286
				'tax_class'  => ! empty( $fee_item['tax_class'] ) ? $fee_item['tax_class'] : '',
287
				'tax_status' => 'taxable',
288
				'total'      => wc_format_decimal( $order->get_line_total( $fee_item ), $dp ),
289
				'total_tax'  => wc_format_decimal( $order->get_line_tax( $fee_item ), $dp ),
290
				'taxes'      => array(),
291
			);
292
293
			$fee_line_taxes = maybe_unserialize( $fee_item['line_tax_data'] );
294
			if ( isset( $fee_line_taxes['total'] ) ) {
295
				$fee_tax = array();
296
297
				foreach ( $fee_line_taxes['total'] as $tax_rate_id => $tax ) {
298
					$fee_tax[ $tax_rate_id ] = array(
299
						'id'       => $tax_rate_id,
300
						'total'    => $tax,
301
						'subtotal' => '',
302
					);
303
				}
304
305
				if ( isset( $fee_line_taxes['subtotal'] ) ) {
306
					foreach ( $fee_line_taxes['subtotal'] as $tax_rate_id => $tax ) {
307
						$fee_tax[ $tax_rate_id ]['subtotal'] = $tax;
308
					}
309
				}
310
311
				$fee_line['taxes'] = array_values( $fee_tax );
312
			}
313
314
			$data['fee_lines'][] = $fee_line;
315
		}
316
317
		// Add coupons.
318
		foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) {
319
			$coupon_line = array(
320
				'id'           => $coupon_item_id,
321
				'code'         => $coupon_item['name'],
322
				'discount'     => wc_format_decimal( $coupon_item['discount_amount'], $dp ),
323
				'discount_tax' => wc_format_decimal( $coupon_item['discount_amount_tax'], $dp ),
324
			);
325
326
			$data['coupon_lines'][] = $coupon_line;
327
		}
328
329
		// Add refunds.
330 View Code Duplication
		foreach ( $order->get_refunds() as $refund ) {
331
			$data['refunds'][] = array(
332
				'id'     => $refund->get_id(),
333
				'refund' => $refund->get_reason() ? $refund->get_reason() : '',
334
				'total'  => '-' . wc_format_decimal( $refund->get_amount(), $dp ),
335
			);
336
		}
337
338
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
339
		$data    = $this->add_additional_fields_to_object( $data, $request );
340
		$data    = $this->filter_response_by_context( $data, $context );
341
342
		// Wrap the data in a response object.
343
		$response = rest_ensure_response( $data );
344
345
		$response->add_links( $this->prepare_links( $order, $request ) );
346
347
		/**
348
		 * Filter the data for a response.
349
		 *
350
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
351
		 * prepared for the response.
352
		 *
353
		 * @param WP_REST_Response   $response   The response object.
354
		 * @param WP_Post            $post       Post object.
355
		 * @param WP_REST_Request    $request    Request object.
356
		 */
357
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $post, $request );
358
	}
359
360
	/**
361
	 * Prepare links for the request.
362
	 *
363
	 * @param WC_Order $order Order object.
364
	 * @param WP_REST_Request $request Request object.
365
	 * @return array Links for the given order.
366
	 */
367 View Code Duplication
	protected function prepare_links( $order, $request ) {
368
		$links = array(
369
			'self' => array(
370
				'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $order->get_id() ) ),
371
			),
372
			'collection' => array(
373
				'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
374
			),
375
		);
376
		if ( 0 !== (int) $order->get_user_id() ) {
377
			$links['customer'] = array(
378
				'href' => rest_url( sprintf( '/%s/customers/%d', $this->namespace, $order->get_user_id() ) ),
379
			);
380
		}
381
		if ( 0 !== (int) $order->get_parent_id() ) {
382
			$links['up'] = array(
383
				'href' => rest_url( sprintf( '/%s/orders/%d', $this->namespace, $order->get_parent_id() ) ),
384
			);
385
		}
386
		return $links;
387
	}
388
389
	/**
390
	 * Query args.
391
	 *
392
	 * @param array $args
393
	 * @param WP_REST_Request $request
394
	 * @return array
395
	 */
396
	public function query_args( $args, $request ) {
397
		global $wpdb;
398
399
		// Set post_status.
400
		if ( 'any' !== $request['status'] ) {
401
			$args['post_status'] = 'wc-' . $request['status'];
402
		} else {
403
			$args['post_status'] = 'any';
404
		}
405
406
		if ( isset( $request['customer'] ) ) {
407
			if ( ! empty( $args['meta_query'] ) ) {
408
				$args['meta_query'] = array();
409
			}
410
411
			$args['meta_query'][] = array(
412
				'key'   => '_customer_user',
413
				'value' => $request['customer'],
414
				'type'  => 'NUMERIC',
415
			);
416
		}
417
418
		// Search by product.
419 View Code Duplication
		if ( ! empty( $request['product'] ) ) {
420
			$order_ids = $wpdb->get_col( $wpdb->prepare( "
421
				SELECT order_id
422
				FROM {$wpdb->prefix}woocommerce_order_items
423
				WHERE order_item_id IN ( SELECT order_item_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE meta_key = '_product_id' AND meta_value = %d )
424
				AND order_item_type = 'line_item'
425
			 ", $request['product'] ) );
426
427
			// Force WP_Query return empty if don't found any order.
428
			$order_ids = ! empty( $order_ids ) ? $order_ids : array( 0 );
429
430
			$args['post__in'] = $order_ids;
431
		}
432
433
		// Search.
434 View Code Duplication
		if ( ! empty( $args['s'] ) ) {
435
			$order_ids = wc_order_search( $args['s'] );
436
437
			if ( ! empty( $order_ids ) ) {
438
				unset( $args['s'] );
439
				$args['post__in'] = array_merge( $order_ids, array( 0 ) );
440
			}
441
		}
442
443
		return $args;
444
	}
445
446
	/**
447
	 * Prepare a single order for create.
448
	 *
449
	 * @param  WP_REST_Request $request Request object.
450
	 * @return WP_Error|WC_Order $data Object.
451
	 */
452
	protected function prepare_item_for_database( $request ) {
453
		$id        = isset( $request['id'] ) ? absint( $request['id'] ) : 0;
454
		$order     = new WC_Order( $id );
455
		$schema    = $this->get_item_schema();
456
		$data_keys = array_keys( array_filter( $schema['properties'], array( $this, 'filter_writable_props' ) ) );
457
458
		// Handle all writable props
459
		foreach ( $data_keys as $key ) {
460
			$value = $request[ $key ];
461
462
			if ( ! is_null( $value ) ) {
463
				switch ( $key ) {
464
					case 'billing' :
465
					case 'shipping' :
466
						$this->update_address( $order, $value, $key );
467
						break;
468
					case 'line_items' :
469
					case 'shipping_lines' :
470
					case 'fee_lines' :
471
					case 'coupon_lines' :
472
						if ( is_array( $value ) ) {
473
							foreach ( $value as $item ) {
474
								if ( is_array( $item ) ) {
475
									if ( $this->item_is_null( $item ) || ( isset( $item['quantity'] ) && 0 === $item['quantity'] ) ) {
476
										$order->remove_item( $item['id'] );
477
									} else {
478
										$this->set_item( $order, $key, $item );
479
									}
480
								}
481
							}
482
						}
483
						break;
484
					default :
485
						if ( is_callable( array( $order, "set_{$key}" ) ) ) {
486
							$order->{"set_{$key}"}( $value );
487
						}
488
						break;
489
				}
490
			}
491
		}
492
493
		/**
494
		 * Filter the data for the insert.
495
		 *
496
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
497
		 * prepared for the response.
498
		 *
499
		 * @param WC_Order           $order      The order object.
500
		 * @param WP_REST_Request    $request    Request object.
501
		 */
502
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $order, $request );
503
	}
504
505
	/**
506
	 * Create base WC Order object.
507
	 * @deprecated 3.0.0
508
	 * @param array $data
509
	 * @return WC_Order
510
	 */
511
	protected function create_base_order( $data ) {
512
		return wc_create_order( $data );
513
	}
514
515
	/**
516
	 * Only return writable props from schema.
517
	 * @param  array $schema
518
	 * @return bool
519
	 */
520
	protected function filter_writable_props( $schema ) {
521
		return empty( $schema['readonly'] );
522
	}
523
524
	/**
525
	 * Create order.
526
	 *
527
	 * @param WP_REST_Request $request Full details about the request.
528
	 * @return int|WP_Error
529
	 */
530
	protected function create_order( $request ) {
531
		try {
532
			// Make sure customer exists.
533
			if ( ! is_null( $request['customer_id'] ) && 0 !== $request['customer_id'] && false === get_user_by( 'id', $request['customer_id'] ) ) {
534
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_customer_id',__( 'Customer ID is invalid.', 'woocommerce' ), 400 );
535
			}
536
537
			// Make sure customer is part of blog.
538
			if ( is_multisite() && ! is_user_member_of_blog( $request['customer_id'] ) ) {
539
				add_user_to_blog( get_current_blog_id(), $request['customer_id'], 'customer' );
540
			}
541
542
			$order = $this->prepare_item_for_database( $request );
543
			$order->set_created_via( 'rest-api' );
544
			$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
545
			$order->calculate_totals();
546
			$order->save();
547
548
			// Handle set paid.
549
			if ( true === $request['set_paid'] ) {
550
				$order->payment_complete( $request['transaction_id'] );
551
			}
552
553
			return $order->get_id();
554
		} catch ( WC_Data_Exception $e ) {
555
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
556
		} catch ( WC_REST_Exception $e ) {
557
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
558
		}
559
	}
560
561
	/**
562
	 * Update order.
563
	 *
564
	 * @param WP_REST_Request $request Full details about the request.
565
	 * @return int|WP_Error
566
	 */
567
	protected function update_order( $request ) {
568
		try {
569
			$order = $this->prepare_item_for_database( $request );
570
			$order->save();
571
572
			// Handle set paid.
573
			if ( $order->needs_payment() && true === $request['set_paid'] ) {
574
				$order->payment_complete( $request['transaction_id'] );
575
			}
576
577
			// If items have changed, recalculate order totals.
578
			if ( isset( $request['billing'] ) || isset( $request['shipping'] ) || isset( $request['line_items'] ) || isset( $request['shipping_lines'] ) || isset( $request['fee_lines'] ) || isset( $request['coupon_lines'] ) ) {
579
				$order->calculate_totals( true );
580
			}
581
582
			return $order->get_id();
583
		} catch ( WC_Data_Exception $e ) {
584
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), $e->getErrorData() );
585
		} catch ( WC_REST_Exception $e ) {
586
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
587
		}
588
	}
589
590
	/**
591
	 * Update address.
592
	 *
593
	 * @param WC_Order $order
594
	 * @param array $posted
595
	 * @param string $type
596
	 */
597 View Code Duplication
	protected function update_address( $order, $posted, $type = 'billing' ) {
598
		foreach ( $posted as $key => $value ) {
599
			if ( is_callable( array( $order, "set_{$type}_{$key}" ) ) ) {
600
				$order->{"set_{$type}_{$key}"}( $value );
601
			}
602
		}
603
	}
604
605
	/**
606
	 * Gets the product ID from the SKU or posted ID.
607
	 *
608
	 * @param array $posted Request data
609
	 *
610
	 * @return int
611
	 * @throws WC_REST_Exception
612
	 */
613 View Code Duplication
	protected function get_product_id( $posted ) {
614
		if ( ! empty( $posted['sku'] ) ) {
615
			$product_id = (int) wc_get_product_id_by_sku( $posted['sku'] );
616
		} elseif ( ! empty( $posted['product_id'] ) && empty( $posted['variation_id'] ) ) {
617
			$product_id = (int) $posted['product_id'];
618
		} elseif ( ! empty( $posted['variation_id'] ) ) {
619
			$product_id = (int) $posted['variation_id'];
620
		} else {
621
			throw new WC_REST_Exception( 'woocommerce_rest_required_product_reference', __( 'Product ID or SKU is required.', 'woocommerce' ), 400 );
622
		}
623
		return $product_id;
624
	}
625
626
	/**
627
	 * Maybe set an item prop if the value was posted.
628
	 * @param WC_Order_Item $item
629
	 * @param string $prop
630
	 * @param array $posted Request data.
631
	 */
632
	protected function maybe_set_item_prop( $item, $prop, $posted ) {
633
		if ( isset( $posted[ $prop ] ) ) {
634
			$item->{"set_$prop"}( $posted[ $prop ] );
635
		}
636
	}
637
638
	/**
639
	 * Maybe set item props if the values were posted.
640
	 * @param WC_Order_Item $item
641
	 * @param string[] $props
642
	 * @param array $posted Request data.
643
	 */
644
	protected function maybe_set_item_props( $item, $props, $posted ) {
645
		foreach ( $props as $prop ) {
646
			$this->maybe_set_item_prop( $item, $prop, $posted );
647
		}
648
	}
649
650
	/**
651
	 * Create or update a line item.
652
	 *
653
	 * @param array $posted Line item data.
654
	 * @param string $action 'create' to add line item or 'update' to update it.
655
	 *
656
	 * @return WC_Order_Item_Product
657
	 * @throws WC_REST_Exception Invalid data, server error.
658
	 */
659
	protected function prepare_line_items( $posted, $action = 'create' ) {
660
		$item    = new WC_Order_Item_Product( ! empty( $posted['id'] ) ? $posted['id'] : '' );
661
		$product = wc_get_product( $this->get_product_id( $posted ) );
662
663 View Code Duplication
		if ( $product !== $item->get_product() ) {
664
			$item->set_product( $product );
665
666
			if ( 'create' === $action ) {
667
				$quantity = isset( $posted['quantity'] ) ? $posted['quantity'] : 1;
668
				$total    = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
669
				$item->set_total( $total );
670
				$item->set_subtotal( $total );
671
			}
672
		}
673
674
		$this->maybe_set_item_props( $item, array( 'name', 'quantity', 'total', 'subtotal', 'tax_class' ), $posted );
675
676
		return $item;
677
	}
678
679
	/**
680
	 * Create or update an order shipping method.
681
	 *
682
	 * @param $posted $shipping Item data.
683
	 * @param string $action 'create' to add shipping or 'update' to update it.
684
	 *
685
	 * @return WC_Order_Item_Shipping
686
	 * @throws WC_REST_Exception Invalid data, server error.
687
	 */
688 View Code Duplication
	protected function prepare_shipping_lines( $posted, $action ) {
689
		$item = new WC_Order_Item_Shipping( ! empty( $posted['id'] ) ? $posted['id'] : '' );
690
691
		if ( 'create' === $action ) {
692
			if ( empty( $posted['method_id'] ) ) {
693
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_shipping_item', __( 'Shipping method ID is required.', 'woocommerce' ), 400 );
694
			}
695
		}
696
697
		$this->maybe_set_item_props( $item, array( 'method_id', 'method_title', 'total' ), $posted );
698
699
		return $item;
700
	}
701
702
	/**
703
	 * Create or update an order fee.
704
	 *
705
	 * @param array $posted Item data.
706
	 * @param string $action 'create' to add fee or 'update' to update it.
707
	 *
708
	 * @return WC_Order_Item_Fee
709
	 * @throws WC_REST_Exception Invalid data, server error.
710
	 */
711 View Code Duplication
	protected function prepare_fee_lines( $posted, $action ) {
712
		$item = new WC_Order_Item_Fee( ! empty( $posted['id'] ) ? $posted['id'] : '' );
713
714
		if ( 'create' === $action ) {
715
			if ( empty( $posted['name'] ) ) {
716
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_fee_item', __( 'Fee name is required.', 'woocommerce' ), 400 );
717
			}
718
		}
719
720
		$this->maybe_set_item_props( $item, array( 'name', 'tax_class', 'tax_status', 'total' ), $posted );
721
722
		return $item;
723
	}
724
725
	/**
726
	 * Create or update an order coupon.
727
	 *
728
	 * @param array $posted Item data.
729
	 * @param string $action 'create' to add coupon or 'update' to update it.
730
	 *
731
	 * @return WC_Order_Item_Coupon
732
	 * @throws WC_REST_Exception Invalid data, server error.
733
	 */
734 View Code Duplication
	protected function prepare_coupon_lines( $posted, $action ) {
735
		$item = new WC_Order_Item_Coupon( ! empty( $posted['id'] ) ? $posted['id'] : '' );
736
737
		if ( 'create' === $action ) {
738
			if ( empty( $posted['code'] ) ) {
739
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_coupon_coupon', __( 'Coupon code is required.', 'woocommerce' ), 400 );
740
			}
741
		}
742
743
		$this->maybe_set_item_props( $item, array( 'code', 'discount' ), $posted );
744
745
		return $item;
746
	}
747
748
	/**
749
	 * Wrapper method to create/update order items.
750
	 * When updating, the item ID provided is checked to ensure it is associated
751
	 * with the order.
752
	 *
753
	 * @param WC_Order $order order
754
	 * @param string $item_type
755
	 * @param array $posted item provided in the request body
756
	 * @throws WC_REST_Exception If item ID is not associated with order
757
	 */
758
	protected function set_item( $order, $item_type, $posted ) {
759
		global $wpdb;
760
761
		if ( ! empty( $posted['id'] ) ) {
762
			$action = 'update';
763
		} else {
764
			$action = 'create';
765
		}
766
767
		$method = 'prepare_' . $item_type;
768
769
		// Verify provided line item ID is associated with order.
770
		if ( 'update' === $action ) {
771
			$result = $wpdb->get_row(
772
				$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d",
773
				absint( $posted['id'] ),
774
				absint( $order->get_id() )
775
			) );
776
			if ( is_null( $result ) ) {
777
				throw new WC_REST_Exception( 'woocommerce_rest_invalid_item_id', __( 'Order item ID provided is not associated with order.', 'woocommerce' ), 400 );
778
			}
779
		}
780
781
		// Prepare item data
782
		$item = $this->$method( $posted, $action );
783
784
		/**
785
		 * Action hook to adjust item before save.
786
		 * @since 3.0.0
787
		 */
788
		do_action( 'woocommerce_rest_set_order_item', $item, $posted );
789
790
		// Save or add to order
791
		if ( 'create' === $action ) {
792
			$order->add_item( $item );
793
		} else {
794
			$item->save();
795
		}
796
	}
797
798
	/**
799
	 * Helper method to check if the resource ID associated with the provided item is null.
800
	 * Items can be deleted by setting the resource ID to null.
801
	 *
802
	 * @param array $item Item provided in the request body.
803
	 * @return bool True if the item resource ID is null, false otherwise.
804
	 */
805 View Code Duplication
	protected function item_is_null( $item ) {
806
		$keys = array( 'product_id', 'method_id', 'method_title', 'name', 'code' );
807
808
		foreach ( $keys as $key ) {
809
			if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
810
				return true;
811
			}
812
		}
813
814
		return false;
815
	}
816
817
	/**
818
	 * Create a single item.
819
	 *
820
	 * @param WP_REST_Request $request Full details about the request.
821
	 * @return WP_Error|WP_REST_Response
822
	 */
823 View Code Duplication
	public function create_item( $request ) {
824
		if ( ! empty( $request['id'] ) ) {
825
			/* translators: %s: post type */
826
			return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) );
827
		}
828
829
		$order_id = $this->create_order( $request );
830
		if ( is_wp_error( $order_id ) ) {
831
			return $order_id;
832
		}
833
834
		$post = get_post( $order_id );
835
		$this->update_additional_fields_for_object( $post, $request );
836
837
		/**
838
		 * Fires after a single item is created or updated via the REST API.
839
		 *
840
		 * @param WP_Post         $post      Post object.
841
		 * @param WP_REST_Request $request   Request object.
842
		 * @param boolean         $creating  True when creating item, false when updating.
843
		 */
844
		do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
845
		$request->set_param( 'context', 'edit' );
846
		$response = $this->prepare_item_for_response( $post, $request );
847
		$response = rest_ensure_response( $response );
848
		$response->set_status( 201 );
849
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post->ID ) ) );
850
851
		return $response;
852
	}
853
854
	/**
855
	 * Update a single order.
856
	 *
857
	 * @param WP_REST_Request $request Full details about the request.
858
	 * @return WP_Error|WP_REST_Response
859
	 */
860 View Code Duplication
	public function update_item( $request ) {
861
		try {
862
			$post_id = (int) $request['id'];
863
864
			if ( empty( $post_id ) || get_post_type( $post_id ) !== $this->post_type ) {
865
				return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
866
			}
867
868
			$order_id = $this->update_order( $request );
869
			if ( is_wp_error( $order_id ) ) {
870
				return $order_id;
871
			}
872
873
			$post = get_post( $order_id );
874
			$this->update_additional_fields_for_object( $post, $request );
875
876
			/**
877
			 * Fires after a single item is created or updated via the REST API.
878
			 *
879
			 * @param WP_Post         $post      Post object.
880
			 * @param WP_REST_Request $request   Request object.
881
			 * @param boolean         $creating  True when creating item, false when updating.
882
			 */
883
			do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false );
884
			$request->set_param( 'context', 'edit' );
885
			$response = $this->prepare_item_for_response( $post, $request );
886
			return rest_ensure_response( $response );
887
888
		} catch ( Exception $e ) {
889
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
890
		}
891
	}
892
893
	/**
894
	 * Get order statuses without prefixes.
895
	 * @return array
896
	 */
897 View Code Duplication
	protected function get_order_statuses() {
898
		$order_statuses = array();
899
900
		foreach ( array_keys( wc_get_order_statuses() ) as $status ) {
901
			$order_statuses[] = str_replace( 'wc-', '', $status );
902
		}
903
904
		return $order_statuses;
905
	}
906
907
	/**
908
	 * Get the Order's schema, conforming to JSON Schema.
909
	 *
910
	 * @return array
911
	 */
912
	public function get_item_schema() {
913
		$schema = array(
914
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
915
			'title'      => $this->post_type,
916
			'type'       => 'object',
917
			'properties' => array(
918
				'id' => array(
919
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
920
					'type'        => 'integer',
921
					'context'     => array( 'view', 'edit' ),
922
					'readonly'    => true,
923
				),
924
				'parent_id' => array(
925
					'description' => __( 'Parent order ID.', 'woocommerce' ),
926
					'type'        => 'integer',
927
					'context'     => array( 'view', 'edit' ),
928
				),
929
				'status' => array(
930
					'description' => __( 'Order status.', 'woocommerce' ),
931
					'type'        => 'string',
932
					'default'     => 'pending',
933
					'enum'        => $this->get_order_statuses(),
934
					'context'     => array( 'view', 'edit' ),
935
				),
936
				'order_key' => array(
937
					'description' => __( 'Order key.', 'woocommerce' ),
938
					'type'        => 'string',
939
					'context'     => array( 'view', 'edit' ),
940
					'readonly'    => true,
941
				),
942
				'number' => array(
943
					'description' => __( 'Order number.', 'woocommerce' ),
944
					'type'        => 'string',
945
					'context'     => array( 'view', 'edit' ),
946
					'readonly'    => true,
947
				),
948
				'currency' => array(
949
					'description' => __( 'Currency the order was created with, in ISO format.', 'woocommerce' ),
950
					'type'        => 'string',
951
					'default'     => get_woocommerce_currency(),
952
					'enum'        => array_keys( get_woocommerce_currencies() ),
953
					'context'     => array( 'view', 'edit' ),
954
				),
955
				'version' => array(
956
					'description' => __( 'Version of WooCommerce which last updated the order.', 'woocommerce' ),
957
					'type'        => 'integer',
958
					'context'     => array( 'view', 'edit' ),
959
					'readonly'    => true,
960
				),
961
				'prices_include_tax' => array(
962
					'description' => __( 'True the prices included tax during checkout.', 'woocommerce' ),
963
					'type'        => 'boolean',
964
					'context'     => array( 'view', 'edit' ),
965
					'readonly'    => true,
966
				),
967
				'date_created' => array(
968
					'description' => __( "The date the order was created, as GMT.", 'woocommerce' ),
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal The date the order was created, as GMT. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
969
					'type'        => 'date-time',
970
					'context'     => array( 'view', 'edit' ),
971
					'readonly'    => true,
972
				),
973
				'date_modified' => array(
974
					'description' => __( "The date the order was last modified, as GMT.", 'woocommerce' ),
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal The date the order was last modified, as GMT. does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
975
					'type'        => 'date-time',
976
					'context'     => array( 'view', 'edit' ),
977
					'readonly'    => true,
978
				),
979
				'customer_id' => array(
980
					'description' => __( 'User ID who owns the order. 0 for guests.', 'woocommerce' ),
981
					'type'        => 'integer',
982
					'default'     => 0,
983
					'context'     => array( 'view', 'edit' ),
984
				),
985
				'discount_total' => array(
986
					'description' => __( 'Total discount amount for the order.', 'woocommerce' ),
987
					'type'        => 'string',
988
					'context'     => array( 'view', 'edit' ),
989
					'readonly'    => true,
990
				),
991
				'discount_tax' => array(
992
					'description' => __( 'Total discount tax amount for the order.', 'woocommerce' ),
993
					'type'        => 'string',
994
					'context'     => array( 'view', 'edit' ),
995
					'readonly'    => true,
996
				),
997
				'shipping_total' => array(
998
					'description' => __( 'Total shipping amount for the order.', 'woocommerce' ),
999
					'type'        => 'string',
1000
					'context'     => array( 'view', 'edit' ),
1001
					'readonly'    => true,
1002
				),
1003
				'shipping_tax' => array(
1004
					'description' => __( 'Total shipping tax amount for the order.', 'woocommerce' ),
1005
					'type'        => 'string',
1006
					'context'     => array( 'view', 'edit' ),
1007
					'readonly'    => true,
1008
				),
1009
				'cart_tax' => array(
1010
					'description' => __( 'Sum of line item taxes only.', 'woocommerce' ),
1011
					'type'        => 'string',
1012
					'context'     => array( 'view', 'edit' ),
1013
					'readonly'    => true,
1014
				),
1015
				'total' => array(
1016
					'description' => __( 'Grand total.', 'woocommerce' ),
1017
					'type'        => 'string',
1018
					'context'     => array( 'view', 'edit' ),
1019
					'readonly'    => true,
1020
				),
1021
				'total_tax' => array(
1022
					'description' => __( 'Sum of all taxes.', 'woocommerce' ),
1023
					'type'        => 'string',
1024
					'context'     => array( 'view', 'edit' ),
1025
					'readonly'    => true,
1026
				),
1027
				'billing' => array(
1028
					'description' => __( 'Billing address.', 'woocommerce' ),
1029
					'type'        => 'object',
1030
					'context'     => array( 'view', 'edit' ),
1031
					'properties'  => array(
1032
						'first_name' => array(
1033
							'description' => __( 'First name.', 'woocommerce' ),
1034
							'type'        => 'string',
1035
							'context'     => array( 'view', 'edit' ),
1036
						),
1037
						'last_name' => array(
1038
							'description' => __( 'Last name.', 'woocommerce' ),
1039
							'type'        => 'string',
1040
							'context'     => array( 'view', 'edit' ),
1041
						),
1042
						'company' => array(
1043
							'description' => __( 'Company name.', 'woocommerce' ),
1044
							'type'        => 'string',
1045
							'context'     => array( 'view', 'edit' ),
1046
						),
1047
						'address_1' => array(
1048
							'description' => __( 'Address line 1.', 'woocommerce' ),
1049
							'type'        => 'string',
1050
							'context'     => array( 'view', 'edit' ),
1051
						),
1052
						'address_2' => array(
1053
							'description' => __( 'Address line 2.', 'woocommerce' ),
1054
							'type'        => 'string',
1055
							'context'     => array( 'view', 'edit' ),
1056
						),
1057
						'city' => array(
1058
							'description' => __( 'City name.', 'woocommerce' ),
1059
							'type'        => 'string',
1060
							'context'     => array( 'view', 'edit' ),
1061
						),
1062
						'state' => array(
1063
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
1064
							'type'        => 'string',
1065
							'context'     => array( 'view', 'edit' ),
1066
						),
1067
						'postcode' => array(
1068
							'description' => __( 'Postal code.', 'woocommerce' ),
1069
							'type'        => 'string',
1070
							'context'     => array( 'view', 'edit' ),
1071
						),
1072
						'country' => array(
1073
							'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
1074
							'type'        => 'string',
1075
							'context'     => array( 'view', 'edit' ),
1076
						),
1077
						'email' => array(
1078
							'description' => __( 'Email address.', 'woocommerce' ),
1079
							'type'        => 'string',
1080
							'format'      => 'email',
1081
							'context'     => array( 'view', 'edit' ),
1082
						),
1083
						'phone' => array(
1084
							'description' => __( 'Phone number.', 'woocommerce' ),
1085
							'type'        => 'string',
1086
							'context'     => array( 'view', 'edit' ),
1087
						),
1088
					),
1089
				),
1090
				'shipping' => array(
1091
					'description' => __( 'Shipping address.', 'woocommerce' ),
1092
					'type'        => 'object',
1093
					'context'     => array( 'view', 'edit' ),
1094
					'properties'  => array(
1095
						'first_name' => array(
1096
							'description' => __( 'First name.', 'woocommerce' ),
1097
							'type'        => 'string',
1098
							'context'     => array( 'view', 'edit' ),
1099
						),
1100
						'last_name' => array(
1101
							'description' => __( 'Last name.', 'woocommerce' ),
1102
							'type'        => 'string',
1103
							'context'     => array( 'view', 'edit' ),
1104
						),
1105
						'company' => array(
1106
							'description' => __( 'Company name.', 'woocommerce' ),
1107
							'type'        => 'string',
1108
							'context'     => array( 'view', 'edit' ),
1109
						),
1110
						'address_1' => array(
1111
							'description' => __( 'Address line 1.', 'woocommerce' ),
1112
							'type'        => 'string',
1113
							'context'     => array( 'view', 'edit' ),
1114
						),
1115
						'address_2' => array(
1116
							'description' => __( 'Address line 2.', 'woocommerce' ),
1117
							'type'        => 'string',
1118
							'context'     => array( 'view', 'edit' ),
1119
						),
1120
						'city' => array(
1121
							'description' => __( 'City name.', 'woocommerce' ),
1122
							'type'        => 'string',
1123
							'context'     => array( 'view', 'edit' ),
1124
						),
1125
						'state' => array(
1126
							'description' => __( 'ISO code or name of the state, province or district.', 'woocommerce' ),
1127
							'type'        => 'string',
1128
							'context'     => array( 'view', 'edit' ),
1129
						),
1130
						'postcode' => array(
1131
							'description' => __( 'Postal code.', 'woocommerce' ),
1132
							'type'        => 'string',
1133
							'context'     => array( 'view', 'edit' ),
1134
						),
1135
						'country' => array(
1136
							'description' => __( 'Country code in ISO 3166-1 alpha-2 format.', 'woocommerce' ),
1137
							'type'        => 'string',
1138
							'context'     => array( 'view', 'edit' ),
1139
						),
1140
					),
1141
				),
1142
				'payment_method' => array(
1143
					'description' => __( 'Payment method ID.', 'woocommerce' ),
1144
					'type'        => 'string',
1145
					'context'     => array( 'view', 'edit' ),
1146
				),
1147
				'payment_method_title' => array(
1148
					'description' => __( 'Payment method title.', 'woocommerce' ),
1149
					'type'        => 'string',
1150
					'context'     => array( 'view', 'edit' ),
1151
					'arg_options' => array(
1152
						'sanitize_callback' => 'sanitize_text_field',
1153
					),
1154
				),
1155
				'set_paid' => array(
1156
					'description' => __( 'Define if the order is paid. It will set the status to processing and reduce stock items.', 'woocommerce' ),
1157
					'type'        => 'boolean',
1158
					'default'     => false,
1159
					'context'     => array( 'edit' ),
1160
				),
1161
				'transaction_id' => array(
1162
					'description' => __( 'Unique transaction ID.', 'woocommerce' ),
1163
					'type'        => 'string',
1164
					'context'     => array( 'view', 'edit' ),
1165
				),
1166
				'customer_ip_address' => array(
1167
					'description' => __( "Customer's IP address.", 'woocommerce' ),
1168
					'type'        => 'string',
1169
					'context'     => array( 'view', 'edit' ),
1170
					'readonly'    => true,
1171
				),
1172
				'customer_user_agent' => array(
1173
					'description' => __( 'User agent of the customer.', 'woocommerce' ),
1174
					'type'        => 'string',
1175
					'context'     => array( 'view', 'edit' ),
1176
					'readonly'    => true,
1177
				),
1178
				'created_via' => array(
1179
					'description' => __( 'Shows where the order was created.', 'woocommerce' ),
1180
					'type'        => 'string',
1181
					'context'     => array( 'view', 'edit' ),
1182
					'readonly'    => true,
1183
				),
1184
				'customer_note' => array(
1185
					'description' => __( 'Note left by customer during checkout.', 'woocommerce' ),
1186
					'type'        => 'string',
1187
					'context'     => array( 'view', 'edit' ),
1188
				),
1189
				'date_completed' => array(
1190
					'description' => __( "The date the order was completed, in the site's timezone.", 'woocommerce' ),
1191
					'type'        => 'date-time',
1192
					'context'     => array( 'view', 'edit' ),
1193
					'readonly'    => true,
1194
				),
1195
				'date_paid' => array(
1196
					'description' => __( "The date the order was paid, in the site's timezone.", 'woocommerce' ),
1197
					'type'        => 'date-time',
1198
					'context'     => array( 'view', 'edit' ),
1199
					'readonly'    => true,
1200
				),
1201
				'cart_hash' => array(
1202
					'description' => __( 'MD5 hash of cart items to ensure orders are not modified.', 'woocommerce' ),
1203
					'type'        => 'string',
1204
					'context'     => array( 'view', 'edit' ),
1205
					'readonly'    => true,
1206
				),
1207
				'line_items' => array(
1208
					'description' => __( 'Line items data.', 'woocommerce' ),
1209
					'type'        => 'array',
1210
					'context'     => array( 'view', 'edit' ),
1211
					'items'       => array(
1212
						'type'       => 'object',
1213
						'properties' => array(
1214
							'id' => array(
1215
								'description' => __( 'Item ID.', 'woocommerce' ),
1216
								'type'        => 'integer',
1217
								'context'     => array( 'view', 'edit' ),
1218
								'readonly'    => true,
1219
							),
1220
							'name' => array(
1221
								'description' => __( 'Product name.', 'woocommerce' ),
1222
								'type'        => 'mixed',
1223
								'context'     => array( 'view', 'edit' ),
1224
								'readonly'    => true,
1225
							),
1226
							'sku' => array(
1227
								'description' => __( 'Product SKU.', 'woocommerce' ),
1228
								'type'        => 'string',
1229
								'context'     => array( 'view', 'edit' ),
1230
								'readonly'    => true,
1231
							),
1232
							'product_id' => array(
1233
								'description' => __( 'Product ID.', 'woocommerce' ),
1234
								'type'        => 'mixed',
1235
								'context'     => array( 'view', 'edit' ),
1236
							),
1237
							'variation_id' => array(
1238
								'description' => __( 'Variation ID, if applicable.', 'woocommerce' ),
1239
								'type'        => 'integer',
1240
								'context'     => array( 'view', 'edit' ),
1241
							),
1242
							'quantity' => array(
1243
								'description' => __( 'Quantity ordered.', 'woocommerce' ),
1244
								'type'        => 'integer',
1245
								'context'     => array( 'view', 'edit' ),
1246
							),
1247
							'tax_class' => array(
1248
								'description' => __( 'Tax class of product.', 'woocommerce' ),
1249
								'type'        => 'string',
1250
								'context'     => array( 'view', 'edit' ),
1251
								'readonly'    => true,
1252
							),
1253
							'price' => array(
1254
								'description' => __( 'Product price.', 'woocommerce' ),
1255
								'type'        => 'string',
1256
								'context'     => array( 'view', 'edit' ),
1257
								'readonly'    => true,
1258
							),
1259
							'subtotal' => array(
1260
								'description' => __( 'Line subtotal (before discounts).', 'woocommerce' ),
1261
								'type'        => 'string',
1262
								'context'     => array( 'view', 'edit' ),
1263
							),
1264
							'subtotal_tax' => array(
1265
								'description' => __( 'Line subtotal tax (before discounts).', 'woocommerce' ),
1266
								'type'        => 'string',
1267
								'context'     => array( 'view', 'edit' ),
1268
							),
1269
							'total' => array(
1270
								'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1271
								'type'        => 'string',
1272
								'context'     => array( 'view', 'edit' ),
1273
							),
1274
							'total_tax' => array(
1275
								'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1276
								'type'        => 'string',
1277
								'context'     => array( 'view', 'edit' ),
1278
							),
1279
							'taxes' => array(
1280
								'description' => __( 'Line taxes.', 'woocommerce' ),
1281
								'type'        => 'array',
1282
								'context'     => array( 'view', 'edit' ),
1283
								'readonly'    => true,
1284
								'items'       => array(
1285
									'type'       => 'object',
1286
									'properties' => array(
1287
										'id' => array(
1288
											'description' => __( 'Tax rate ID.', 'woocommerce' ),
1289
											'type'        => 'integer',
1290
											'context'     => array( 'view', 'edit' ),
1291
											'readonly'    => true,
1292
										),
1293
										'total' => array(
1294
											'description' => __( 'Tax total.', 'woocommerce' ),
1295
											'type'        => 'string',
1296
											'context'     => array( 'view', 'edit' ),
1297
											'readonly'    => true,
1298
										),
1299
										'subtotal' => array(
1300
											'description' => __( 'Tax subtotal.', 'woocommerce' ),
1301
											'type'        => 'string',
1302
											'context'     => array( 'view', 'edit' ),
1303
											'readonly'    => true,
1304
										),
1305
									),
1306
								),
1307
							),
1308
							'meta' => array(
1309
								'description' => __( 'Line item meta data.', 'woocommerce' ),
1310
								'type'        => 'array',
1311
								'context'     => array( 'view', 'edit' ),
1312
								'readonly'    => true,
1313
								'items'       => array(
1314
									'type'       => 'object',
1315
									'properties' => array(
1316
										'key' => array(
1317
											'description' => __( 'Meta key.', 'woocommerce' ),
1318
											'type'        => 'string',
1319
											'context'     => array( 'view', 'edit' ),
1320
											'readonly'    => true,
1321
										),
1322
										'label' => array(
1323
											'description' => __( 'Meta label.', 'woocommerce' ),
1324
											'type'        => 'string',
1325
											'context'     => array( 'view', 'edit' ),
1326
											'readonly'    => true,
1327
										),
1328
										'value' => array(
1329
											'description' => __( 'Meta value.', 'woocommerce' ),
1330
											'type'        => 'mixed',
1331
											'context'     => array( 'view', 'edit' ),
1332
											'readonly'    => true,
1333
										),
1334
									),
1335
								),
1336
							),
1337
						),
1338
					),
1339
				),
1340
				'tax_lines' => array(
1341
					'description' => __( 'Tax lines data.', 'woocommerce' ),
1342
					'type'        => 'array',
1343
					'context'     => array( 'view', 'edit' ),
1344
					'readonly'    => true,
1345
					'items'       => array(
1346
						'type'       => 'object',
1347
						'properties' => array(
1348
							'id' => array(
1349
								'description' => __( 'Item ID.', 'woocommerce' ),
1350
								'type'        => 'integer',
1351
								'context'     => array( 'view', 'edit' ),
1352
								'readonly'    => true,
1353
							),
1354
							'rate_code' => array(
1355
								'description' => __( 'Tax rate code.', 'woocommerce' ),
1356
								'type'        => 'string',
1357
								'context'     => array( 'view', 'edit' ),
1358
								'readonly'    => true,
1359
							),
1360
							'rate_id' => array(
1361
								'description' => __( 'Tax rate ID.', 'woocommerce' ),
1362
								'type'        => 'string',
1363
								'context'     => array( 'view', 'edit' ),
1364
								'readonly'    => true,
1365
							),
1366
							'label' => array(
1367
								'description' => __( 'Tax rate label.', 'woocommerce' ),
1368
								'type'        => 'string',
1369
								'context'     => array( 'view', 'edit' ),
1370
								'readonly'    => true,
1371
							),
1372
							'compound' => array(
1373
								'description' => __( 'Show if is a compound tax rate.', 'woocommerce' ),
1374
								'type'        => 'boolean',
1375
								'context'     => array( 'view', 'edit' ),
1376
								'readonly'    => true,
1377
							),
1378
							'tax_total' => array(
1379
								'description' => __( 'Tax total (not including shipping taxes).', 'woocommerce' ),
1380
								'type'        => 'string',
1381
								'context'     => array( 'view', 'edit' ),
1382
								'readonly'    => true,
1383
							),
1384
							'shipping_tax_total' => array(
1385
								'description' => __( 'Shipping tax total.', 'woocommerce' ),
1386
								'type'        => 'string',
1387
								'context'     => array( 'view', 'edit' ),
1388
								'readonly'    => true,
1389
							),
1390
						),
1391
					),
1392
				),
1393
				'shipping_lines' => array(
1394
					'description' => __( 'Shipping lines data.', 'woocommerce' ),
1395
					'type'        => 'array',
1396
					'context'     => array( 'view', 'edit' ),
1397
					'items'       => array(
1398
						'type'       => 'object',
1399
						'properties' => array(
1400
							'id' => array(
1401
								'description' => __( 'Item ID.', 'woocommerce' ),
1402
								'type'        => 'integer',
1403
								'context'     => array( 'view', 'edit' ),
1404
								'readonly'    => true,
1405
							),
1406
							'method_title' => array(
1407
								'description' => __( 'Shipping method name.', 'woocommerce' ),
1408
								'type'        => 'mixed',
1409
								'context'     => array( 'view', 'edit' ),
1410
							),
1411
							'method_id' => array(
1412
								'description' => __( 'Shipping method ID.', 'woocommerce' ),
1413
								'type'        => 'mixed',
1414
								'context'     => array( 'view', 'edit' ),
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
								'items'       => array(
1433
									'type'       => 'object',
1434
									'properties' => array(
1435
										'id' => array(
1436
											'description' => __( 'Tax rate ID.', 'woocommerce' ),
1437
											'type'        => 'integer',
1438
											'context'     => array( 'view', 'edit' ),
1439
											'readonly'    => true,
1440
										),
1441
										'total' => array(
1442
											'description' => __( 'Tax total.', 'woocommerce' ),
1443
											'type'        => 'string',
1444
											'context'     => array( 'view', 'edit' ),
1445
											'readonly'    => true,
1446
										),
1447
									),
1448
								),
1449
							),
1450
						),
1451
					),
1452
				),
1453
				'fee_lines' => array(
1454
					'description' => __( 'Fee lines data.', 'woocommerce' ),
1455
					'type'        => 'array',
1456
					'context'     => array( 'view', 'edit' ),
1457
					'items'       => array(
1458
						'type'       => 'object',
1459
						'properties' => array(
1460
							'id' => array(
1461
								'description' => __( 'Item ID.', 'woocommerce' ),
1462
								'type'        => 'integer',
1463
								'context'     => array( 'view', 'edit' ),
1464
								'readonly'    => true,
1465
							),
1466
							'name' => array(
1467
								'description' => __( 'Fee name.', 'woocommerce' ),
1468
								'type'        => 'mixed',
1469
								'context'     => array( 'view', 'edit' ),
1470
							),
1471
							'tax_class' => array(
1472
								'description' => __( 'Tax class of fee.', 'woocommerce' ),
1473
								'type'        => 'string',
1474
								'context'     => array( 'view', 'edit' ),
1475
							),
1476
							'tax_status' => array(
1477
								'description' => __( 'Tax status of fee.', 'woocommerce' ),
1478
								'type'        => 'string',
1479
								'context'     => array( 'view', 'edit' ),
1480
								'enum'        => array( 'taxable', 'none' ),
1481
							),
1482
							'total' => array(
1483
								'description' => __( 'Line total (after discounts).', 'woocommerce' ),
1484
								'type'        => 'string',
1485
								'context'     => array( 'view', 'edit' ),
1486
							),
1487
							'total_tax' => array(
1488
								'description' => __( 'Line total tax (after discounts).', 'woocommerce' ),
1489
								'type'        => 'string',
1490
								'context'     => array( 'view', 'edit' ),
1491
							),
1492
							'taxes' => array(
1493
								'description' => __( 'Line taxes.', 'woocommerce' ),
1494
								'type'        => 'array',
1495
								'context'     => array( 'view', 'edit' ),
1496
								'readonly'    => true,
1497
								'items'       => array(
1498
									'type'       => 'object',
1499
									'properties' => array(
1500
										'id' => array(
1501
											'description' => __( 'Tax rate ID.', 'woocommerce' ),
1502
											'type'        => 'integer',
1503
											'context'     => array( 'view', 'edit' ),
1504
											'readonly'    => true,
1505
										),
1506
										'total' => array(
1507
											'description' => __( 'Tax total.', 'woocommerce' ),
1508
											'type'        => 'string',
1509
											'context'     => array( 'view', 'edit' ),
1510
											'readonly'    => true,
1511
										),
1512
										'subtotal' => array(
1513
											'description' => __( 'Tax subtotal.', 'woocommerce' ),
1514
											'type'        => 'string',
1515
											'context'     => array( 'view', 'edit' ),
1516
											'readonly'    => true,
1517
										),
1518
									),
1519
								),
1520
							),
1521
						),
1522
					),
1523
				),
1524
				'coupon_lines' => array(
1525
					'description' => __( 'Coupons line data.', 'woocommerce' ),
1526
					'type'        => 'array',
1527
					'context'     => array( 'view', 'edit' ),
1528
					'items'       => array(
1529
						'type'       => 'object',
1530
						'properties' => array(
1531
							'id' => array(
1532
								'description' => __( 'Item ID.', 'woocommerce' ),
1533
								'type'        => 'integer',
1534
								'context'     => array( 'view', 'edit' ),
1535
								'readonly'    => true,
1536
							),
1537
							'code' => array(
1538
								'description' => __( 'Coupon code.', 'woocommerce' ),
1539
								'type'        => 'mixed',
1540
								'context'     => array( 'view', 'edit' ),
1541
							),
1542
							'discount' => array(
1543
								'description' => __( 'Discount total.', 'woocommerce' ),
1544
								'type'        => 'string',
1545
								'context'     => array( 'view', 'edit' ),
1546
							),
1547
							'discount_tax' => array(
1548
								'description' => __( 'Discount total tax.', 'woocommerce' ),
1549
								'type'        => 'string',
1550
								'context'     => array( 'view', 'edit' ),
1551
								'readonly'    => true,
1552
							),
1553
						),
1554
					),
1555
				),
1556
				'refunds' => array(
1557
					'description' => __( 'List of refunds.', 'woocommerce' ),
1558
					'type'        => 'array',
1559
					'context'     => array( 'view', 'edit' ),
1560
					'readonly'    => true,
1561
					'items'       => array(
1562
						'type'       => 'object',
1563
						'properties' => array(
1564
							'id' => array(
1565
								'description' => __( 'Refund ID.', 'woocommerce' ),
1566
								'type'        => 'integer',
1567
								'context'     => array( 'view', 'edit' ),
1568
								'readonly'    => true,
1569
							),
1570
							'reason' => array(
1571
								'description' => __( 'Refund reason.', 'woocommerce' ),
1572
								'type'        => 'string',
1573
								'context'     => array( 'view', 'edit' ),
1574
								'readonly'    => true,
1575
							),
1576
							'total' => array(
1577
								'description' => __( 'Refund total.', 'woocommerce' ),
1578
								'type'        => 'string',
1579
								'context'     => array( 'view', 'edit' ),
1580
								'readonly'    => true,
1581
							),
1582
						),
1583
					),
1584
				),
1585
			),
1586
		);
1587
1588
		return $this->add_additional_fields_schema( $schema );
1589
	}
1590
1591
	/**
1592
	 * Get the query params for collections.
1593
	 *
1594
	 * @return array
1595
	 */
1596 View Code Duplication
	public function get_collection_params() {
1597
		$params = parent::get_collection_params();
1598
1599
		$params['status'] = array(
1600
			'default'           => 'any',
1601
			'description'       => __( 'Limit result set to orders assigned a specific status.', 'woocommerce' ),
1602
			'type'              => 'string',
1603
			'enum'              => array_merge( array( 'any' ), $this->get_order_statuses() ),
1604
			'sanitize_callback' => 'sanitize_key',
1605
			'validate_callback' => 'rest_validate_request_arg',
1606
		);
1607
		$params['customer'] = array(
1608
			'description'       => __( 'Limit result set to orders assigned a specific customer.', 'woocommerce' ),
1609
			'type'              => 'integer',
1610
			'sanitize_callback' => 'absint',
1611
			'validate_callback' => 'rest_validate_request_arg',
1612
		);
1613
		$params['product'] = array(
1614
			'description'       => __( 'Limit result set to orders assigned a specific product.', 'woocommerce' ),
1615
			'type'              => 'integer',
1616
			'sanitize_callback' => 'absint',
1617
			'validate_callback' => 'rest_validate_request_arg',
1618
		);
1619
		$params['dp'] = array(
1620
			'default'           => wc_get_price_decimals(),
1621
			'description'       => __( 'Number of decimal points to use in each resource.', 'woocommerce' ),
1622
			'type'              => 'integer',
1623
			'sanitize_callback' => 'absint',
1624
			'validate_callback' => 'rest_validate_request_arg',
1625
		);
1626
1627
		return $params;
1628
	}
1629
}
1630