Completed
Pull Request — master (#10520)
by Mike
11:07
created

WC_API_Orders::get_orders()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 12

Duplication

Lines 25
Ratio 100 %
Metric Value
dl 25
loc 25
rs 8.5806
cc 4
eloc 12
nc 6
nop 4
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 17 and the first side effect is on line 14.

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

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

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

Loading history...
2
/**
3
 * WooCommerce API Orders Class
4
 *
5
 * Handles requests to the /orders endpoint
6
 *
7
 * @author      WooThemes
8
 * @category    API
9
 * @package     WooCommerce/API
10
 * @since       2.1
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit; // Exit if accessed directly
15
}
16
17
class WC_API_Orders extends WC_API_Resource {
18
19
	/** @var string $base the route base */
20
	protected $base = '/orders';
21
22
	/** @var string $post_type the custom post type */
23
	protected $post_type = 'shop_order';
24
25
	/**
26
	 * Register the routes for this class
27
	 *
28
	 * GET|POST /orders
29
	 * GET /orders/count
30
	 * GET|PUT|DELETE /orders/<id>
31
	 * GET /orders/<id>/notes
32
	 *
33
	 * @since 2.1
34
	 * @param array $routes
35
	 * @return array
36
	 */
37
	public function register_routes( $routes ) {
38
39
		# GET|POST /orders
40
		$routes[ $this->base ] = array(
41
			array( array( $this, 'get_orders' ),     WC_API_Server::READABLE ),
42
			array( array( $this, 'create_order' ),   WC_API_Server::CREATABLE | WC_API_Server::ACCEPT_DATA ),
43
		);
44
45
		# GET /orders/count
46
		$routes[ $this->base . '/count' ] = array(
47
			array( array( $this, 'get_orders_count' ), WC_API_Server::READABLE ),
48
		);
49
50
		# GET /orders/statuses
51
		$routes[ $this->base . '/statuses' ] = array(
52
			array( array( $this, 'get_order_statuses' ), WC_API_Server::READABLE ),
53
		);
54
55
		# GET|PUT|DELETE /orders/<id>
56
		$routes[ $this->base . '/(?P<id>\d+)' ] = array(
57
			array( array( $this, 'get_order' ),  WC_API_Server::READABLE ),
58
			array( array( $this, 'edit_order' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
59
			array( array( $this, 'delete_order' ), WC_API_Server::DELETABLE ),
60
		);
61
62
		# GET|POST /orders/<id>/notes
63
		$routes[ $this->base . '/(?P<order_id>\d+)/notes' ] = array(
64
			array( array( $this, 'get_order_notes' ), WC_API_Server::READABLE ),
65
			array( array( $this, 'create_order_note' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
66
		);
67
68
		# GET|PUT|DELETE /orders/<order_id>/notes/<id>
69
		$routes[ $this->base . '/(?P<order_id>\d+)/notes/(?P<id>\d+)' ] = array(
70
			array( array( $this, 'get_order_note' ), WC_API_Server::READABLE ),
71
			array( array( $this, 'edit_order_note' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
72
			array( array( $this, 'delete_order_note' ), WC_API_SERVER::DELETABLE ),
73
		);
74
75
		# GET|POST /orders/<order_id>/refunds
76
		$routes[ $this->base . '/(?P<order_id>\d+)/refunds' ] = array(
77
			array( array( $this, 'get_order_refunds' ), WC_API_Server::READABLE ),
78
			array( array( $this, 'create_order_refund' ), WC_API_SERVER::CREATABLE | WC_API_Server::ACCEPT_DATA ),
79
		);
80
81
		# GET|PUT|DELETE /orders/<order_id>/refunds/<id>
82
		$routes[ $this->base . '/(?P<order_id>\d+)/refunds/(?P<id>\d+)' ] = array(
83
			array( array( $this, 'get_order_refund' ), WC_API_Server::READABLE ),
84
			array( array( $this, 'edit_order_refund' ), WC_API_SERVER::EDITABLE | WC_API_Server::ACCEPT_DATA ),
85
			array( array( $this, 'delete_order_refund' ), WC_API_SERVER::DELETABLE ),
86
		);
87
88
		# POST|PUT /orders/bulk
89
		$routes[ $this->base . '/bulk' ] = array(
90
			array( array( $this, 'bulk' ), WC_API_Server::EDITABLE | WC_API_Server::ACCEPT_DATA ),
91
		);
92
93
		return $routes;
94
	}
95
96
	/**
97
	 * Get all orders
98
	 *
99
	 * @since 2.1
100
	 * @param string $fields
101
	 * @param array $filter
102
	 * @param string $status
103
	 * @param int $page
104
	 * @return array
105
	 */
106 View Code Duplication
	public function get_orders( $fields = null, $filter = array(), $status = null, $page = 1 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
107
108
		if ( ! empty( $status ) ) {
109
			$filter['status'] = $status;
110
		}
111
112
		$filter['page'] = $page;
113
114
		$query = $this->query_orders( $filter );
115
116
		$orders = array();
117
118
		foreach ( $query->posts as $order_id ) {
119
120
			if ( ! $this->is_readable( $order_id ) ) {
121
				continue;
122
			}
123
124
			$orders[] = current( $this->get_order( $order_id, $fields, $filter ) );
0 ignored issues
show
Bug introduced by
It seems like $fields defined by parameter $fields on line 106 can also be of type string; however, WC_API_Orders::get_order() does only seem to accept array|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

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

An additional type check may prevent trouble.

Loading history...
125
		}
126
127
		$this->server->add_pagination_headers( $query );
128
129
		return array( 'orders' => $orders );
130
	}
131
132
133
	/**
134
	 * Get the order for the given ID.
135
	 *
136
	 * @since 2.1
137
	 * @param int $id The order ID.
138
	 * @param array $fields Request fields.
139
	 * @param array $filter Request filters.
140
	 * @return array
141
	 */
142
	public function get_order( $id, $fields = null, $filter = array() ) {
143
144
		// Ensure order ID is valid & user has permission to read.
145
		$id = $this->validate_request( $id, $this->post_type, 'read' );
146
147
		if ( is_wp_error( $id ) ) {
148
			return $id;
149
		}
150
151
		// Get the decimal precession.
152
		$dp         = ( isset( $filter['dp'] ) ? intval( $filter['dp'] ) : 2 );
153
		$order      = wc_get_order( $id );
154
		$order_post = get_post( $id );
155
		$expand     = array();
156
157
		if ( ! empty( $filter['expand'] ) ) {
158
			$expand = explode( ',', $filter['expand'] );
159
		}
160
161
		$order_data = array(
162
			'id'                        => $order->id,
163
			'order_number'              => $order->get_order_number(),
164
			'order_key'                 => $order->order_key,
165
			'created_at'                => $this->server->format_datetime( $order_post->post_date_gmt ),
166
			'updated_at'                => $this->server->format_datetime( $order_post->post_modified_gmt ),
167
			'completed_at'              => $this->server->format_datetime( $order->completed_date, true ),
168
			'status'                    => $order->get_status(),
169
			'currency'                  => $order->get_order_currency(),
170
			'total'                     => wc_format_decimal( $order->get_total(), $dp ),
171
			'subtotal'                  => wc_format_decimal( $order->get_subtotal(), $dp ),
172
			'total_line_items_quantity' => $order->get_item_count(),
173
			'total_tax'                 => wc_format_decimal( $order->get_total_tax(), $dp ),
174
			'total_shipping'            => wc_format_decimal( $order->get_total_shipping(), $dp ),
175
			'cart_tax'                  => wc_format_decimal( $order->get_cart_tax(), $dp ),
176
			'shipping_tax'              => wc_format_decimal( $order->get_shipping_tax(), $dp ),
177
			'total_discount'            => wc_format_decimal( $order->get_total_discount(), $dp ),
178
			'shipping_methods'          => $order->get_shipping_method(),
179
			'payment_details' => array(
180
				'method_id'    => $order->payment_method,
181
				'method_title' => $order->payment_method_title,
182
				'paid'         => isset( $order->paid_date ),
183
			),
184
			'billing_address' => array(
185
				'first_name' => $order->billing_first_name,
186
				'last_name'  => $order->billing_last_name,
187
				'company'    => $order->billing_company,
188
				'address_1'  => $order->billing_address_1,
189
				'address_2'  => $order->billing_address_2,
190
				'city'       => $order->billing_city,
191
				'state'      => $order->billing_state,
192
				'postcode'   => $order->billing_postcode,
193
				'country'    => $order->billing_country,
194
				'email'      => $order->billing_email,
195
				'phone'      => $order->billing_phone,
196
			),
197
			'shipping_address' => array(
198
				'first_name' => $order->shipping_first_name,
199
				'last_name'  => $order->shipping_last_name,
200
				'company'    => $order->shipping_company,
201
				'address_1'  => $order->shipping_address_1,
202
				'address_2'  => $order->shipping_address_2,
203
				'city'       => $order->shipping_city,
204
				'state'      => $order->shipping_state,
205
				'postcode'   => $order->shipping_postcode,
206
				'country'    => $order->shipping_country,
207
			),
208
			'note'                      => $order->customer_note,
209
			'customer_ip'               => $order->customer_ip_address,
210
			'customer_user_agent'       => $order->customer_user_agent,
211
			'customer_id'               => $order->get_user_id(),
212
			'view_order_url'            => $order->get_view_order_url(),
213
			'line_items'                => array(),
214
			'shipping_lines'            => array(),
215
			'tax_lines'                 => array(),
216
			'fee_lines'                 => array(),
217
			'coupon_lines'              => array(),
218
			'is_vat_exempt'             => $order->is_vat_exempt === 'yes' ? true : false,
219
		);
220
221
		// Add line items.
222
		foreach ( $order->get_items() as $item_id => $item ) {
223
			$product     = $order->get_product_from_item( $item );
224
			$product_id  = null;
225
			$product_sku = null;
226
227
			// Check if the product exists.
228 View Code Duplication
			if ( is_object( $product ) ) {
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...
229
				$product_id  = ( isset( $product->variation_id ) ) ? $product->variation_id : $product->id;
230
				$product_sku = $product->get_sku();
231
			}
232
233
			$meta = new WC_Order_Item_Meta( $item, $product );
234
235
			$item_meta = array();
236
237
			$hideprefix = ( isset( $filter['all_item_meta'] ) && 'true' === $filter['all_item_meta'] ) ? null : '_';
238
239 View Code Duplication
			foreach ( $meta->get_formatted( $hideprefix ) as $meta_key => $formatted_meta ) {
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...
240
				$item_meta[] = array(
241
					'key'   => $formatted_meta['key'],
242
					'label' => $formatted_meta['label'],
243
					'value' => $formatted_meta['value'],
244
				);
245
			}
246
247
			$line_item = array(
248
				'id'           => $item_id,
249
				'subtotal'     => wc_format_decimal( $order->get_line_subtotal( $item, false, false ), $dp ),
250
				'subtotal_tax' => wc_format_decimal( $item['line_subtotal_tax'], $dp ),
251
				'total'        => wc_format_decimal( $order->get_line_total( $item, false, false ), $dp ),
252
				'total_tax'    => wc_format_decimal( $item['line_tax'], $dp ),
253
				'price'        => wc_format_decimal( $order->get_item_total( $item, false, false ), $dp ),
254
				'quantity'     => wc_stock_amount( $item['qty'] ),
255
				'tax_class'    => ( ! empty( $item['tax_class'] ) ) ? $item['tax_class'] : null,
256
				'name'         => $item['name'],
257
				'product_id'   => $product_id,
258
				'sku'          => $product_sku,
259
				'meta'         => $item_meta,
260
			);
261
262 View Code Duplication
			if ( in_array( 'products', $expand ) ) {
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...
263
				$_product_data = WC()->api->WC_API_Products->get_product( $product_id );
264
265
				if ( isset( $_product_data['product'] ) ) {
266
					$line_item['product_data'] = $_product_data['product'];
267
				}
268
			}
269
270
			$order_data['line_items'][] = $line_item;
271
		}
272
273
		// Add shipping.
274 View Code Duplication
		foreach ( $order->get_shipping_methods() as $shipping_item_id => $shipping_item ) {
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...
275
			$order_data['shipping_lines'][] = array(
276
				'id'           => $shipping_item_id,
277
				'method_id'    => $shipping_item['method_id'],
278
				'method_title' => $shipping_item['name'],
279
				'total'        => wc_format_decimal( $shipping_item['cost'], $dp ),
280
			);
281
		}
282
283
		// Add taxes.
284
		foreach ( $order->get_tax_totals() as $tax_code => $tax ) {
285
			$tax_line = array(
286
				'id'       => $tax->id,
287
				'rate_id'  => $tax->rate_id,
288
				'code'     => $tax_code,
289
				'title'    => $tax->label,
290
				'total'    => wc_format_decimal( $tax->amount, $dp ),
291
				'compound' => (bool) $tax->is_compound,
292
			);
293
294
			if ( in_array( 'taxes', $expand ) ) {
295
				$_rate_data = WC()->api->WC_API_Taxes->get_tax( $tax->rate_id );
296
297
				if ( isset( $_rate_data['tax'] ) ) {
298
					$tax_line['rate_data'] = $_rate_data['tax'];
299
				}
300
			}
301
302
			$order_data['tax_lines'][] = $tax_line;
303
		}
304
305
		// Add fees.
306 View Code Duplication
		foreach ( $order->get_fees() as $fee_item_id => $fee_item ) {
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...
307
			$order_data['fee_lines'][] = array(
308
				'id'        => $fee_item_id,
309
				'title'     => $fee_item['name'],
310
				'tax_class' => ( ! empty( $fee_item['tax_class'] ) ) ? $fee_item['tax_class'] : null,
311
				'total'     => wc_format_decimal( $order->get_line_total( $fee_item ), $dp ),
312
				'total_tax' => wc_format_decimal( $order->get_line_tax( $fee_item ), $dp ),
313
			);
314
		}
315
316
		// Add coupons.
317
		foreach ( $order->get_items( 'coupon' ) as $coupon_item_id => $coupon_item ) {
318
			$coupon_line = array(
319
				'id'     => $coupon_item_id,
320
				'code'   => $coupon_item['name'],
321
				'amount' => wc_format_decimal( $coupon_item['discount_amount'], $dp ),
322
			);
323
324 View Code Duplication
			if ( in_array( 'coupons', $expand ) ) {
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...
325
				$_coupon_data = WC()->api->WC_API_Coupons->get_coupon_by_code( $coupon_item['name'] );
326
327
				if ( isset( $_coupon_data['coupon'] ) ) {
328
					$coupon_line['coupon_data'] = $_coupon_data['coupon'];
329
				}
330
			}
331
332
			$order_data['coupon_lines'][] = $coupon_line;
333
		}
334
335
		return array( 'order' => apply_filters( 'woocommerce_api_order_response', $order_data, $order, $fields, $this->server ) );
336
	}
337
338
	/**
339
	 * Get the total number of orders
340
	 *
341
	 * @since 2.4
342
	 * @param string $status
343
	 * @param array $filter
344
	 * @return array
345
	 */
346
	public function get_orders_count( $status = null, $filter = array() ) {
347
348
		try {
349
			if ( ! current_user_can( 'read_private_shop_orders' ) ) {
350
				throw new WC_API_Exception( 'woocommerce_api_user_cannot_read_orders_count', __( 'You do not have permission to read the orders count', 'woocommerce' ), 401 );
351
			}
352
353
			if ( ! empty( $status ) ) {
354
355
				if ( $status == 'any' ) {
356
357
					$order_statuses = array();
358
359
					foreach ( wc_get_order_statuses() as $slug => $name ) {
360
						$filter['status'] = str_replace( 'wc-', '', $slug );
361
						$query = $this->query_orders( $filter );
362
						$order_statuses[ str_replace( 'wc-', '', $slug ) ] = (int) $query->found_posts;
363
					}
364
365
					return array( 'count' => $order_statuses );
366
367
				} else {
368
					$filter['status'] = $status;
369
				}
370
			}
371
372
			$query = $this->query_orders( $filter );
373
374
			return array( 'count' => (int) $query->found_posts );
375
376
		} catch ( WC_API_Exception $e ) {
377
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
378
		}
379
	}
380
381
	/**
382
	 * Get a list of valid order statuses
383
	 *
384
	 * Note this requires no specific permissions other than being an authenticated
385
	 * API user. Order statuses (particularly custom statuses) could be considered
386
	 * private information which is why it's not in the API index.
387
	 *
388
	 * @since 2.1
389
	 * @return array
390
	 */
391
	public function get_order_statuses() {
392
393
		$order_statuses = array();
394
395
		foreach ( wc_get_order_statuses() as $slug => $name ) {
396
			$order_statuses[ str_replace( 'wc-', '', $slug ) ] = $name;
397
		}
398
399
		return array( 'order_statuses' => apply_filters( 'woocommerce_api_order_statuses_response', $order_statuses, $this ) );
400
	}
401
402
	/**
403
	 * Create an order
404
	 *
405
	 * @since 2.2
406
	 * @param array $data raw order data
407
	 * @return array
408
	 */
409
	public function create_order( $data ) {
410
		global $wpdb;
411
412
		wc_transaction_query( 'start' );
413
414
		try {
415 View Code Duplication
			if ( ! isset( $data['order'] ) ) {
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...
416
				throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order' ), 400 );
417
			}
418
419
			$data = $data['order'];
420
421
			// permission check
422
			if ( ! current_user_can( 'publish_shop_orders' ) ) {
423
				throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order', __( 'You do not have permission to create orders', 'woocommerce' ), 401 );
424
			}
425
426
			$data = apply_filters( 'woocommerce_api_create_order_data', $data, $this );
427
428
			// default order args, note that status is checked for validity in wc_create_order()
429
			$default_order_args = array(
430
				'status'        => isset( $data['status'] ) ? $data['status'] : '',
431
				'customer_note' => isset( $data['note'] ) ? $data['note'] : null,
432
			);
433
434
			// if creating order for existing customer
435
			if ( ! empty( $data['customer_id'] ) ) {
436
437
				// make sure customer exists
438
				if ( false === get_user_by( 'id', $data['customer_id'] ) ) {
439
					throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid', 'woocommerce' ), 400 );
440
				}
441
442
				$default_order_args['customer_id'] = $data['customer_id'];
443
			}
444
445
			// create the pending order
446
			$order = $this->create_base_order( $default_order_args, $data );
447
448 View Code Duplication
			if ( is_wp_error( $order ) ) {
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...
449
				throw new WC_API_Exception( 'woocommerce_api_cannot_create_order', sprintf( __( 'Cannot create order: %s', 'woocommerce' ), implode( ', ', $order->get_error_messages() ) ), 400 );
0 ignored issues
show
Bug introduced by
The method get_error_messages() does not seem to exist on object<WC_Order>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
450
			}
451
452
			// billing/shipping addresses
453
			$this->set_order_addresses( $order, $data );
454
455
			$lines = array(
456
				'line_item' => 'line_items',
457
				'shipping'  => 'shipping_lines',
458
				'fee'       => 'fee_lines',
459
				'coupon'    => 'coupon_lines',
460
			);
461
462 View Code Duplication
			foreach ( $lines as $line_type => $line ) {
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...
463
464
				if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) {
465
466
					$set_item = "set_{$line_type}";
467
468
					foreach ( $data[ $line ] as $item ) {
469
470
						$this->$set_item( $order, $item, 'create' );
471
					}
472
				}
473
			}
474
475
			// set is vat exempt
476
			if ( isset( $data['is_vat_exempt'] ) ) {
477
				update_post_meta( $order->id, '_is_vat_exempt', $data['is_vat_exempt'] ? 'yes' : 'no' );
478
			}
479
480
			// calculate totals and set them
481
			$order->calculate_totals();
482
483
			// payment method (and payment_complete() if `paid` == true)
0 ignored issues
show
Unused Code Comprehensibility introduced by
41% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
484 View Code Duplication
			if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) {
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...
485
486
				// method ID & title are required
487
				if ( empty( $data['payment_details']['method_id'] ) || empty( $data['payment_details']['method_title'] ) ) {
488
					throw new WC_API_Exception( 'woocommerce_invalid_payment_details', __( 'Payment method ID and title are required', 'woocommerce' ), 400 );
489
				}
490
491
				update_post_meta( $order->id, '_payment_method', $data['payment_details']['method_id'] );
492
				update_post_meta( $order->id, '_payment_method_title', $data['payment_details']['method_title'] );
493
494
				// mark as paid if set
495
				if ( isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) {
496
					$order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' );
497
				}
498
			}
499
500
			// set order currency
501 View Code Duplication
			if ( isset( $data['currency'] ) ) {
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...
502
503
				if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) {
504
					throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid', 'woocommerce'), 400 );
505
				}
506
507
				update_post_meta( $order->id, '_order_currency', $data['currency'] );
508
			}
509
510
			// set order meta
511 View Code Duplication
			if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) {
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...
512
				$this->set_order_meta( $order->id, $data['order_meta'] );
513
			}
514
515
			// HTTP 201 Created
516
			$this->server->send_status( 201 );
517
518
			wc_delete_shop_order_transients( $order->id );
519
520
			do_action( 'woocommerce_api_create_order', $order->id, $data, $this );
521
522
			wc_transaction_query( 'commit' );
523
524
			return $this->get_order( $order->id );
525
526
		} catch ( WC_API_Exception $e ) {
527
528
			wc_transaction_query( 'rollback' );
529
530
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
531
		}
532
	}
533
534
	/**
535
	 * Creates new WC_Order.
536
	 *
537
	 * Requires a separate function for classes that extend WC_API_Orders.
538
	 *
539
	 * @since 2.3
540
	 * @param $args array
541
	 * @return WC_Order
542
	 */
543
	protected function create_base_order( $args, $data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
544
		return wc_create_order( $args );
545
	}
546
547
	/**
548
	 * Edit an order
549
	 *
550
	 * @since 2.2
551
	 * @param int $id the order ID
552
	 * @param array $data
553
	 * @return array
554
	 */
555
	public function edit_order( $id, $data ) {
556
		try {
557 View Code Duplication
			if ( ! isset( $data['order'] ) ) {
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...
558
				throw new WC_API_Exception( 'woocommerce_api_missing_order_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order' ), 400 );
559
			}
560
561
			$data = $data['order'];
562
563
			$update_totals = false;
564
565
			$id = $this->validate_request( $id, $this->post_type, 'edit' );
566
567
			if ( is_wp_error( $id ) ) {
568
				return $id;
569
			}
570
571
			$data  = apply_filters( 'woocommerce_api_edit_order_data', $data, $id, $this );
572
			$order = wc_get_order( $id );
573
574
			if ( empty( $order ) ) {
575
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 );
576
			}
577
578
			$order_args = array( 'order_id' => $order->id );
579
580
			// Customer note.
581
			if ( isset( $data['note'] ) ) {
582
				$order_args['customer_note'] = $data['note'];
583
			}
584
585
			// Customer ID.
586 View Code Duplication
			if ( isset( $data['customer_id'] ) && $data['customer_id'] != $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...
587
				// Make sure customer exists.
588
				if ( false === get_user_by( 'id', $data['customer_id'] ) ) {
589
					throw new WC_API_Exception( 'woocommerce_api_invalid_customer_id', __( 'Customer ID is invalid', 'woocommerce' ), 400 );
590
				}
591
592
				update_post_meta( $order->id, '_customer_user', $data['customer_id'] );
593
			}
594
595
			// Billing/shipping address.
596
			$this->set_order_addresses( $order, $data );
597
598
			$lines = array(
599
				'line_item' => 'line_items',
600
				'shipping'  => 'shipping_lines',
601
				'fee'       => 'fee_lines',
602
				'coupon'    => 'coupon_lines',
603
			);
604
605 View Code Duplication
			foreach ( $lines as $line_type => $line ) {
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...
606
607
				if ( isset( $data[ $line ] ) && is_array( $data[ $line ] ) ) {
608
609
					$update_totals = true;
610
611
					foreach ( $data[ $line ] as $item ) {
612
613
						// Item ID is always required.
614
						if ( ! array_key_exists( 'id', $item ) ) {
615
							throw new WC_API_Exception( 'woocommerce_invalid_item_id', __( 'Order item ID is required', 'woocommerce' ), 400 );
616
						}
617
618
						// Create item.
619
						if ( is_null( $item['id'] ) ) {
620
							$this->set_item( $order, $line_type, $item, 'create' );
621
						} elseif ( $this->item_is_null( $item ) ) {
622
							// Delete item.
623
							wc_delete_order_item( $item['id'] );
624
						} else {
625
							// Update item.
626
							$this->set_item( $order, $line_type, $item, 'update' );
627
						}
628
					}
629
				}
630
			}
631
632
			// Payment method (and payment_complete() if `paid` == true and order needs payment).
633
			if ( isset( $data['payment_details'] ) && is_array( $data['payment_details'] ) ) {
634
635
				// Method ID.
636
				if ( isset( $data['payment_details']['method_id'] ) ) {
637
					update_post_meta( $order->id, '_payment_method', $data['payment_details']['method_id'] );
638
				}
639
640
				// Method title.
641
				if ( isset( $data['payment_details']['method_title'] ) ) {
642
					update_post_meta( $order->id, '_payment_method_title', $data['payment_details']['method_title'] );
643
				}
644
645
				// Mark as paid if set.
646 View Code Duplication
				if ( $order->needs_payment() && isset( $data['payment_details']['paid'] ) && true === $data['payment_details']['paid'] ) {
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...
647
					$order->payment_complete( isset( $data['payment_details']['transaction_id'] ) ? $data['payment_details']['transaction_id'] : '' );
648
				}
649
			}
650
651
			// Set order currency.
652 View Code Duplication
			if ( isset( $data['currency'] ) ) {
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...
653
				if ( ! array_key_exists( $data['currency'], get_woocommerce_currencies() ) ) {
654
					throw new WC_API_Exception( 'woocommerce_invalid_order_currency', __( 'Provided order currency is invalid', 'woocommerce' ), 400 );
655
				}
656
657
				update_post_meta( $order->id, '_order_currency', $data['currency'] );
658
			}
659
660
			// If items have changed, recalculate order totals.
661
			if ( $update_totals ) {
662
				$order->calculate_totals();
663
			}
664
665
			// Update order meta.
666 View Code Duplication
			if ( isset( $data['order_meta'] ) && is_array( $data['order_meta'] ) ) {
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...
667
				$this->set_order_meta( $order->id, $data['order_meta'] );
668
			}
669
670
			// Update the order post to set customer note/modified date.
671
			wc_update_order( $order_args );
672
673
			// Order status.
674 View Code Duplication
			if ( ! empty( $data['status'] ) ) {
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...
675
				$order->update_status( $data['status'], isset( $data['status_note'] ) ? $data['status_note'] : '' );
676
			}
677
678
			wc_delete_shop_order_transients( $order->id );
679
680
			do_action( 'woocommerce_api_edit_order', $order->id, $data, $this );
681
682
			return $this->get_order( $id );
683
		} catch ( WC_API_Exception $e ) {
684
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
685
		}
686
	}
687
688
	/**
689
	 * Delete an order
690
	 *
691
	 * @param int $id the order ID
692
	 * @param bool $force true to permanently delete order, false to move to trash
693
	 * @return array
694
	 */
695 View Code Duplication
	public function delete_order( $id, $force = false ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
696
697
		$id = $this->validate_request( $id, $this->post_type, 'delete' );
698
699
		if ( is_wp_error( $id ) ) {
700
			return $id;
701
		}
702
703
		wc_delete_shop_order_transients( $id );
704
705
		do_action( 'woocommerce_api_delete_order', $id, $this );
706
707
		return $this->delete( $id, 'order',  ( 'true' === $force ) );
708
	}
709
710
	/**
711
	 * Helper method to get order post objects
712
	 *
713
	 * @since 2.1
714
	 * @param array $args request arguments for filtering query
715
	 * @return WP_Query
716
	 */
717
	protected function query_orders( $args ) {
718
719
		// set base query arguments
720
		$query_args = array(
721
			'fields'      => 'ids',
722
			'post_type'   => $this->post_type,
723
			'post_status' => array_keys( wc_get_order_statuses() )
724
		);
725
726
		// add status argument
727 View Code Duplication
		if ( ! empty( $args['status'] ) ) {
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...
728
			$statuses                  = 'wc-' . str_replace( ',', ',wc-', $args['status'] );
729
			$statuses                  = explode( ',', $statuses );
730
			$query_args['post_status'] = $statuses;
731
732
			unset( $args['status'] );
733
		}
734
735
		if ( ! empty( $args['customer_id'] ) ) {
736
			$query_args['meta_query'] = array(
737
				array(
738
					'key'     => '_customer_user',
739
					'value'   => absint( $args['customer_id'] ),
740
					'compare' => '='
741
				)
742
			);
743
		}
744
745
		$query_args = $this->merge_query_args( $query_args, $args );
746
747
		return new WP_Query( $query_args );
748
	}
749
750
	/**
751
	 * Helper method to set/update the billing & shipping addresses for
752
	 * an order
753
	 *
754
	 * @since 2.1
755
	 * @param \WC_Order $order
756
	 * @param array $data
757
	 */
758 View Code Duplication
	protected function set_order_addresses( $order, $data ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
759
760
		$address_fields = array(
761
			'first_name',
762
			'last_name',
763
			'company',
764
			'email',
765
			'phone',
766
			'address_1',
767
			'address_2',
768
			'city',
769
			'state',
770
			'postcode',
771
			'country',
772
		);
773
774
		$billing_address = $shipping_address = array();
775
776
		// billing address
777
		if ( isset( $data['billing_address'] ) && is_array( $data['billing_address'] ) ) {
778
779
			foreach ( $address_fields as $field ) {
780
781
				if ( isset( $data['billing_address'][ $field ] ) ) {
782
					$billing_address[ $field ] = wc_clean( $data['billing_address'][ $field ] );
783
				}
784
			}
785
786
			unset( $address_fields['email'] );
787
			unset( $address_fields['phone'] );
788
		}
789
790
		// shipping address
791
		if ( isset( $data['shipping_address'] ) && is_array( $data['shipping_address'] ) ) {
792
793
			foreach ( $address_fields as $field ) {
794
795
				if ( isset( $data['shipping_address'][ $field ] ) ) {
796
					$shipping_address[ $field ] = wc_clean( $data['shipping_address'][ $field ] );
797
				}
798
			}
799
		}
800
801
		$order->set_address( $billing_address, 'billing' );
802
		$order->set_address( $shipping_address, 'shipping' );
803
804
		// update user meta
805
		if ( $order->get_user_id() ) {
806
			foreach ( $billing_address as $key => $value ) {
807
				update_user_meta( $order->get_user_id(), 'billing_' . $key, $value );
808
			}
809
			foreach ( $shipping_address as $key => $value ) {
810
				update_user_meta( $order->get_user_id(), 'shipping_' . $key, $value );
811
			}
812
		}
813
	}
814
815
	/**
816
	 * Helper method to add/update order meta, with two restrictions:
817
	 *
818
	 * 1) Only non-protected meta (no leading underscore) can be set
819
	 * 2) Meta values must be scalar (int, string, bool)
820
	 *
821
	 * @since 2.2
822
	 * @param int $order_id valid order ID
823
	 * @param array $order_meta order meta in array( 'meta_key' => 'meta_value' ) format
824
	 */
825 View Code Duplication
	protected function set_order_meta( $order_id, $order_meta ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
826
827
		foreach ( $order_meta as $meta_key => $meta_value ) {
828
829
			if ( is_string( $meta_key) && ! is_protected_meta( $meta_key ) && is_scalar( $meta_value ) ) {
830
				update_post_meta( $order_id, $meta_key, $meta_value );
831
			}
832
		}
833
	}
834
835
	/**
836
	 * Helper method to check if the resource ID associated with the provided item is null
837
	 *
838
	 * Items can be deleted by setting the resource ID to null
839
	 *
840
	 * @since 2.2
841
	 * @param array $item item provided in the request body
842
	 * @return bool true if the item resource ID is null, false otherwise
843
	 */
844
	protected function item_is_null( $item ) {
845
846
		$keys = array( 'product_id', 'method_id', 'title', 'code' );
847
848
		foreach ( $keys as $key ) {
849
			if ( array_key_exists( $key, $item ) && is_null( $item[ $key ] ) ) {
850
				return true;
851
			}
852
		}
853
854
		return false;
855
	}
856
857
	/**
858
	 * Wrapper method to create/update order items
859
	 *
860
	 * When updating, the item ID provided is checked to ensure it is associated
861
	 * with the order.
862
	 *
863
	 * @since 2.2
864
	 * @param \WC_Order $order order
865
	 * @param string $item_type
866
	 * @param array $item item provided in the request body
867
	 * @param string $action either 'create' or 'update'
868
	 * @throws WC_API_Exception if item ID is not associated with order
869
	 */
870 View Code Duplication
	protected function set_item( $order, $item_type, $item, $action ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
871
		global $wpdb;
872
873
		$set_method = "set_{$item_type}";
874
875
		// verify provided line item ID is associated with order
876
		if ( 'update' === $action ) {
877
878
			$result = $wpdb->get_row(
879
				$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d AND order_id = %d",
880
				absint( $item['id'] ),
881
				absint( $order->id )
882
			) );
883
884
			if ( is_null( $result ) ) {
885
				throw new WC_API_Exception( 'woocommerce_invalid_item_id', __( 'Order item ID provided is not associated with order', 'woocommerce' ), 400 );
886
			}
887
		}
888
889
		$this->$set_method( $order, $item, $action );
890
	}
891
892
	/**
893
	 * Create or update a line item
894
	 *
895
	 * @since 2.2
896
	 * @param \WC_Order $order
897
	 * @param array $item line item data
898
	 * @param string $action 'create' to add line item or 'update' to update it
899
	 * @throws WC_API_Exception invalid data, server error
900
	 */
901
	protected function set_line_item( $order, $item, $action ) {
902
903
		$creating  = ( 'create' === $action );
904
		$item_args = array();
905
906
		// product is always required
907
		if ( ! isset( $item['product_id'] ) && ! isset( $item['sku'] ) ) {
908
			throw new WC_API_Exception( 'woocommerce_api_invalid_product_id', __( 'Product ID or SKU is required', 'woocommerce' ), 400 );
909
		}
910
911
		// when updating, ensure product ID provided matches
912 View Code Duplication
		if ( 'update' === $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...
913
914
			$item_product_id   = wc_get_order_item_meta( $item['id'], '_product_id' );
915
			$item_variation_id = wc_get_order_item_meta( $item['id'], '_variation_id' );
916
917
			if ( $item['product_id'] != $item_product_id && $item['product_id'] != $item_variation_id ) {
918
				throw new WC_API_Exception( 'woocommerce_api_invalid_product_id', __( 'Product ID provided does not match this line item', 'woocommerce' ), 400 );
919
			}
920
		}
921
922 View Code Duplication
		if ( isset( $item['product_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...
923
			$product_id = $item['product_id'];
924
		} elseif ( isset( $item['sku'] ) ) {
925
			$product_id = wc_get_product_id_by_sku( $item['sku'] );
926
		}
927
928
		// variations must each have a key & value
929
		$variation_id = 0;
930 View Code Duplication
		if ( isset( $item['variations'] ) && is_array( $item['variations'] ) ) {
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...
931
			foreach ( $item['variations'] as $key => $value ) {
932
				if ( ! $key || ! $value ) {
933
					throw new WC_API_Exception( 'woocommerce_api_invalid_product_variation', __( 'The product variation is invalid', 'woocommerce' ), 400 );
934
				}
935
			}
936
			$item_args['variation'] = $item['variations'];
937
			$variation_id = $this->get_variation_id( wc_get_product( $product_id ), $item_args['variation'] );
938
		}
939
940
		$product = wc_get_product( $variation_id ? $variation_id : $product_id );
941
942
		// must be a valid WC_Product
943
		if ( ! is_object( $product ) ) {
944
			throw new WC_API_Exception( 'woocommerce_api_invalid_product', __( 'Product is invalid', 'woocommerce' ), 400 );
945
		}
946
947
		// quantity must be positive float
948 View Code Duplication
		if ( isset( $item['quantity'] ) && floatval( $item['quantity'] ) <= 0 ) {
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...
949
			throw new WC_API_Exception( 'woocommerce_api_invalid_product_quantity', __( 'Product quantity must be a positive float', 'woocommerce' ), 400 );
950
		}
951
952
		// quantity is required when creating
953
		if ( $creating && ! isset( $item['quantity'] ) ) {
954
			throw new WC_API_Exception( 'woocommerce_api_invalid_product_quantity', __( 'Product quantity is required', 'woocommerce' ), 400 );
955
		}
956
957
		// quantity
958
		if ( isset( $item['quantity'] ) ) {
959
			$item_args['qty'] = $item['quantity'];
960
		}
961
962
		// total
963 View Code Duplication
		if ( isset( $item['total'] ) ) {
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...
964
			$item_args['totals']['total'] = floatval( $item['total'] );
965
		}
966
967
		// total tax
968 View Code Duplication
		if ( isset( $item['total_tax'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
969
			$item_args['totals']['tax'] = floatval( $item['total_tax'] );
970
		}
971
972
		// subtotal
973 View Code Duplication
		if ( isset( $item['subtotal'] ) ) {
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...
974
			$item_args['totals']['subtotal'] = floatval( $item['subtotal'] );
975
		}
976
977
		// subtotal tax
978 View Code Duplication
		if ( isset( $item['subtotal_tax'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
979
			$item_args['totals']['subtotal_tax'] = floatval( $item['subtotal_tax'] );
980
		}
981
982 View Code Duplication
		if ( $creating ) {
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...
983
984
			$item_id = $order->add_product( $product, $item_args['qty'], $item_args );
985
986
			if ( ! $item_id ) {
987
				throw new WC_API_Exception( 'woocommerce_cannot_create_line_item', __( 'Cannot create line item, try again', 'woocommerce' ), 500 );
988
			}
989
990
		} else {
991
992
			$item_id = $order->update_product( $item['id'], $product, $item_args );
993
994
			if ( ! $item_id ) {
995
				throw new WC_API_Exception( 'woocommerce_cannot_update_line_item', __( 'Cannot update line item, try again', 'woocommerce' ), 500 );
996
			}
997
		}
998
	}
999
1000
	/**
1001
	 * Given a product ID & API provided variations, find the correct variation ID to use for calculation
1002
	 * We can't just trust input from the API to pass a variation_id manually, otherwise you could pass
1003
	 * the cheapest variation ID but provide other information so we have to look up the variation ID.
1004
	 *
1005
	 * @param  WC_Product $product Product instance
1006
	 * @return int                 Returns an ID if a valid variation was found for this product
1007
	 */
1008 View Code Duplication
	public function get_variation_id( $product, $variations = array() ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1009
		$variation_id = null;
1010
		$variations_normalized = array();
1011
1012
		if ( $product->is_type( 'variable' ) && $product->has_child() ) {
1013
			if ( isset( $variations ) && is_array( $variations ) ) {
1014
				// start by normalizing the passed variations
1015
				foreach ( $variations as $key => $value ) {
1016
					$key = str_replace( 'attribute_', '', str_replace( 'pa_', '', $key ) ); // from get_attributes in class-wc-api-products.php
1017
					$variations_normalized[ $key ] = strtolower( $value );
1018
				}
1019
				// now search through each product child and see if our passed variations match anything
1020
				foreach ( $product->get_children() as $variation ) {
1021
					$meta = array();
1022
					foreach ( get_post_meta( $variation ) as $key => $value ) {
1023
						$value = $value[0];
1024
						$key = str_replace( 'attribute_', '', str_replace( 'pa_', '', $key ) );
1025
						$meta[ $key ] = strtolower( $value );
1026
					}
1027
					// if the variation array is a part of the $meta array, we found our match
1028
					if ( $this->array_contains( $variations_normalized, $meta ) ) {
1029
						$variation_id = $variation;
1030
						break;
1031
					}
1032
				}
1033
			}
1034
		}
1035
1036
		return $variation_id;
1037
	}
1038
1039
	/**
1040
	 * Utility function to see if the meta array contains data from variations
1041
	 */
1042 View Code Duplication
	protected function array_contains( $needles, $haystack ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1043
		foreach ( $needles as $key => $value ) {
1044
			if ( $haystack[ $key ] !== $value ) {
1045
				return false;
1046
			}
1047
		}
1048
		return true;
1049
	}
1050
1051
	/**
1052
	 * Create or update an order shipping method
1053
	 *
1054
	 * @since 2.2
1055
	 * @param \WC_Order $order
1056
	 * @param array $shipping item data
1057
	 * @param string $action 'create' to add shipping or 'update' to update it
1058
	 * @throws WC_API_Exception invalid data, server error
1059
	 */
1060
	protected function set_shipping( $order, $shipping, $action ) {
1061
1062
		// total must be a positive float
1063
		if ( isset( $shipping['total'] ) && floatval( $shipping['total'] ) < 0 ) {
1064
			throw new WC_API_Exception( 'woocommerce_invalid_shipping_total', __( 'Shipping total must be a positive amount', 'woocommerce' ), 400 );
1065
		}
1066
1067 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...
1068
1069
			// method ID is required
1070
			if ( ! isset( $shipping['method_id'] ) ) {
1071
				throw new WC_API_Exception( 'woocommerce_invalid_shipping_item', __( 'Shipping method ID is required', 'woocommerce' ), 400 );
1072
			}
1073
1074
			$rate = new WC_Shipping_Rate( $shipping['method_id'], isset( $shipping['method_title'] ) ? $shipping['method_title'] : '', isset( $shipping['total'] ) ? floatval( $shipping['total'] ) : 0, array(), $shipping['method_id'] );
1075
1076
			$shipping_id = $order->add_shipping( $rate );
1077
1078
			if ( ! $shipping_id ) {
1079
				throw new WC_API_Exception( 'woocommerce_cannot_create_shipping', __( 'Cannot create shipping method, try again', 'woocommerce' ), 500 );
1080
			}
1081
1082
		} else {
1083
1084
			$shipping_args = array();
1085
1086
			if ( isset( $shipping['method_id'] ) ) {
1087
				$shipping_args['method_id'] = $shipping['method_id'];
1088
			}
1089
1090
			if ( isset( $shipping['method_title'] ) ) {
1091
				$shipping_args['method_title'] = $shipping['method_title'];
1092
			}
1093
1094
			if ( isset( $shipping['total'] ) ) {
1095
				$shipping_args['cost'] = floatval( $shipping['total'] );
1096
			}
1097
1098
			$shipping_id = $order->update_shipping( $shipping['id'], $shipping_args );
1099
1100
			if ( ! $shipping_id ) {
1101
				throw new WC_API_Exception( 'woocommerce_cannot_update_shipping', __( 'Cannot update shipping method, try again', 'woocommerce' ), 500 );
1102
			}
1103
		}
1104
	}
1105
1106
	/**
1107
	 * Create or update an order fee
1108
	 *
1109
	 * @since 2.2
1110
	 * @param \WC_Order $order
1111
	 * @param array $fee item data
1112
	 * @param string $action 'create' to add fee or 'update' to update it
1113
	 * @throws WC_API_Exception invalid data, server error
1114
	 */
1115
	protected function set_fee( $order, $fee, $action ) {
1116
1117
		if ( 'create' === $action ) {
1118
1119
			// fee title is required
1120
			if ( ! isset( $fee['title'] ) ) {
1121
				throw new WC_API_Exception( 'woocommerce_invalid_fee_item', __( 'Fee title is required', 'woocommerce' ), 400 );
1122
			}
1123
1124
			$order_fee            = new stdClass();
1125
			$order_fee->id        = sanitize_title( $fee['title'] );
1126
			$order_fee->name      = $fee['title'];
1127
			$order_fee->amount    = isset( $fee['total'] ) ? floatval( $fee['total'] ) : 0;
1128
			$order_fee->taxable   = false;
1129
			$order_fee->tax       = 0;
1130
			$order_fee->tax_data  = array();
1131
			$order_fee->tax_class = '';
1132
1133
			// if taxable, tax class and total are required
1134 View Code Duplication
			if ( isset( $fee['taxable'] ) && $fee['taxable'] ) {
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...
1135
1136
				if ( ! isset( $fee['tax_class'] ) ) {
1137
					throw new WC_API_Exception( 'woocommerce_invalid_fee_item', __( 'Fee tax class is required when fee is taxable', 'woocommerce' ), 400 );
1138
				}
1139
1140
				$order_fee->taxable   = true;
1141
				$order_fee->tax_class = $fee['tax_class'];
1142
1143
				if ( isset( $fee['total_tax'] ) ) {
1144
					$order_fee->tax = isset( $fee['total_tax'] ) ? wc_format_refund_total( $fee['total_tax'] ) : 0;
1145
				}
1146
1147
				if ( isset( $fee['tax_data'] ) ) {
1148
					$order_fee->tax      = wc_format_refund_total( array_sum( $fee['tax_data'] ) );
1149
					$order_fee->tax_data = array_map( 'wc_format_refund_total', $fee['tax_data'] );
1150
				}
1151
			}
1152
1153
			$fee_id = $order->add_fee( $order_fee );
1154
1155
			if ( ! $fee_id ) {
1156
				throw new WC_API_Exception( 'woocommerce_cannot_create_fee', __( 'Cannot create fee, try again', 'woocommerce' ), 500 );
1157
			}
1158
1159 View Code Duplication
		} else {
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...
1160
1161
			$fee_args = array();
1162
1163
			if ( isset( $fee['title'] ) ) {
1164
				$fee_args['name'] = $fee['title'];
1165
			}
1166
1167
			if ( isset( $fee['tax_class'] ) ) {
1168
				$fee_args['tax_class'] = $fee['tax_class'];
1169
			}
1170
1171
			if ( isset( $fee['total'] ) ) {
1172
				$fee_args['line_total'] = floatval( $fee['total'] );
1173
			}
1174
1175
			if ( isset( $fee['total_tax'] ) ) {
1176
				$fee_args['line_tax'] = floatval( $fee['total_tax'] );
1177
			}
1178
1179
			$fee_id = $order->update_fee( $fee['id'], $fee_args );
1180
1181
			if ( ! $fee_id ) {
1182
				throw new WC_API_Exception( 'woocommerce_cannot_update_fee', __( 'Cannot update fee, try again', 'woocommerce' ), 500 );
1183
			}
1184
		}
1185
	}
1186
1187
	/**
1188
	 * Create or update an order coupon
1189
	 *
1190
	 * @since 2.2
1191
	 * @param \WC_Order $order
1192
	 * @param array $coupon item data
1193
	 * @param string $action 'create' to add coupon or 'update' to update it
1194
	 * @throws WC_API_Exception invalid data, server error
1195
	 */
1196
	protected function set_coupon( $order, $coupon, $action ) {
1197
1198
		// coupon amount must be positive float
1199
		if ( isset( $coupon['amount'] ) && floatval( $coupon['amount'] ) < 0 ) {
1200
			throw new WC_API_Exception( 'woocommerce_invalid_coupon_total', __( 'Coupon discount total must be a positive amount', 'woocommerce' ), 400 );
1201
		}
1202
1203 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...
1204
1205
			// coupon code is required
1206
			if ( empty( $coupon['code'] ) ) {
1207
				throw new WC_API_Exception( 'woocommerce_invalid_coupon_coupon', __( 'Coupon code is required', 'woocommerce' ), 400 );
1208
			}
1209
1210
			$coupon_id = $order->add_coupon( $coupon['code'], isset( $coupon['amount'] ) ? floatval( $coupon['amount'] ) : 0 );
1211
1212
			if ( ! $coupon_id ) {
1213
				throw new WC_API_Exception( 'woocommerce_cannot_create_order_coupon', __( 'Cannot create coupon, try again', 'woocommerce' ), 500 );
1214
			}
1215
1216
		} else {
1217
1218
			$coupon_args = array();
1219
1220
			if ( isset( $coupon['code'] ) ) {
1221
				$coupon_args['code'] = $coupon['code'];
1222
			}
1223
1224
			if ( isset( $coupon['amount'] ) ) {
1225
				$coupon_args['discount_amount'] = floatval( $coupon['amount'] );
1226
			}
1227
1228
			$coupon_id = $order->update_coupon( $coupon['id'], $coupon_args );
1229
1230
			if ( ! $coupon_id ) {
1231
				throw new WC_API_Exception( 'woocommerce_cannot_update_order_coupon', __( 'Cannot update coupon, try again', 'woocommerce' ), 500 );
1232
			}
1233
		}
1234
	}
1235
1236
	/**
1237
	 * Get the admin order notes for an order
1238
	 *
1239
	 * @since 2.1
1240
	 * @param string $order_id order ID
1241
	 * @param string|null $fields fields to include in response
1242
	 * @return array
1243
	 */
1244
	public function get_order_notes( $order_id, $fields = null ) {
1245
1246
		// ensure ID is valid order ID
1247
		$order_id = $this->validate_request( $order_id, $this->post_type, 'read' );
1248
1249
		if ( is_wp_error( $order_id ) ) {
1250
			return $order_id;
1251
		}
1252
1253
		$args = array(
1254
			'post_id' => $order_id,
1255
			'approve' => 'approve',
1256
			'type'    => 'order_note'
1257
		);
1258
1259
		remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
1260
1261
		$notes = get_comments( $args );
1262
1263
		add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ), 10, 1 );
1264
1265
		$order_notes = array();
1266
1267
		foreach ( $notes as $note ) {
1268
1269
			$order_notes[] = current( $this->get_order_note( $order_id, $note->comment_ID, $fields ) );
0 ignored issues
show
Bug introduced by
It seems like $order_id defined by $this->validate_request(...his->post_type, 'read') on line 1247 can also be of type integer; however, WC_API_Orders::get_order_note() 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...
1270
		}
1271
1272
		return array( 'order_notes' => apply_filters( 'woocommerce_api_order_notes_response', $order_notes, $order_id, $fields, $notes, $this->server ) );
1273
	}
1274
1275
	/**
1276
	 * Get an order note for the given order ID and ID
1277
	 *
1278
	 * @since 2.2
1279
	 * @param string $order_id order ID
1280
	 * @param string $id order note ID
1281
	 * @param string|null $fields fields to limit response to
1282
	 * @return array
1283
	 */
1284
	public function get_order_note( $order_id, $id, $fields = null ) {
1285
		try {
1286
			// Validate order ID
1287
			$order_id = $this->validate_request( $order_id, $this->post_type, 'read' );
1288
1289
			if ( is_wp_error( $order_id ) ) {
1290
				return $order_id;
1291
			}
1292
1293
			$id = absint( $id );
1294
1295
			if ( empty( $id ) ) {
1296
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 );
1297
			}
1298
1299
			$note = get_comment( $id );
1300
1301
			if ( is_null( $note ) ) {
1302
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 );
1303
			}
1304
1305
			$order_note = array(
1306
				'id'            => $note->comment_ID,
1307
				'created_at'    => $this->server->format_datetime( $note->comment_date_gmt ),
1308
				'note'          => $note->comment_content,
1309
				'customer_note' => get_comment_meta( $note->comment_ID, 'is_customer_note', true ) ? true : false,
1310
			);
1311
1312
			return array( 'order_note' => apply_filters( 'woocommerce_api_order_note_response', $order_note, $id, $fields, $note, $order_id, $this ) );
1313
		} catch ( WC_API_Exception $e ) {
1314
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1315
		}
1316
	}
1317
1318
	/**
1319
	 * Create a new order note for the given order
1320
	 *
1321
	 * @since 2.2
1322
	 * @param string $order_id order ID
1323
	 * @param array $data raw request data
1324
	 * @return WP_Error|array error or created note response data
1325
	 */
1326
	public function create_order_note( $order_id, $data ) {
1327
		try {
1328 View Code Duplication
			if ( ! isset( $data['order_note'] ) ) {
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...
1329
				throw new WC_API_Exception( 'woocommerce_api_missing_order_note_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order_note' ), 400 );
1330
			}
1331
1332
			$data = $data['order_note'];
1333
1334
			// permission check
1335
			if ( ! current_user_can( 'publish_shop_orders' ) ) {
1336
				throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order_note', __( 'You do not have permission to create order notes', 'woocommerce' ), 401 );
1337
			}
1338
1339
			$order_id = $this->validate_request( $order_id, $this->post_type, 'edit' );
1340
1341
			if ( is_wp_error( $order_id ) ) {
1342
				return $order_id;
1343
			}
1344
1345
			$order = wc_get_order( $order_id );
1346
1347
			$data = apply_filters( 'woocommerce_api_create_order_note_data', $data, $order_id, $this );
1348
1349
			// note content is required
1350
			if ( ! isset( $data['note'] ) ) {
1351
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note', __( 'Order note is required', 'woocommerce' ), 400 );
1352
			}
1353
1354
			$is_customer_note = ( isset( $data['customer_note'] ) && true === $data['customer_note'] );
1355
1356
			// create the note
1357
			$note_id = $order->add_order_note( $data['note'], $is_customer_note );
0 ignored issues
show
Documentation introduced by
$is_customer_note is of type boolean, but the function expects a integer.

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...
1358
1359
			if ( ! $note_id ) {
1360
				throw new WC_API_Exception( 'woocommerce_api_cannot_create_order_note', __( 'Cannot create order note, please try again', 'woocommerce' ), 500 );
1361
			}
1362
1363
			// HTTP 201 Created
1364
			$this->server->send_status( 201 );
1365
1366
			do_action( 'woocommerce_api_create_order_note', $note_id, $order_id, $this );
1367
1368
			return $this->get_order_note( $order->id, $note_id );
1369
		} catch ( WC_API_Exception $e ) {
1370
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1371
		}
1372
	}
1373
1374
	/**
1375
	 * Edit the order note
1376
	 *
1377
	 * @since 2.2
1378
	 * @param string $order_id order ID
1379
	 * @param string $id note ID
1380
	 * @param array $data parsed request data
1381
	 * @return WP_Error|array error or edited note response data
1382
	 */
1383
	public function edit_order_note( $order_id, $id, $data ) {
1384
		try {
1385 View Code Duplication
			if ( ! isset( $data['order_note'] ) ) {
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...
1386
				throw new WC_API_Exception( 'woocommerce_api_missing_order_note_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order_note' ), 400 );
1387
			}
1388
1389
			$data = $data['order_note'];
1390
1391
			// Validate order ID
1392
			$order_id = $this->validate_request( $order_id, $this->post_type, 'edit' );
1393
1394
			if ( is_wp_error( $order_id ) ) {
1395
				return $order_id;
1396
			}
1397
1398
			$order = wc_get_order( $order_id );
1399
1400
			// Validate note ID
1401
			$id = absint( $id );
1402
1403
			if ( empty( $id ) ) {
1404
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 );
1405
			}
1406
1407
			// Ensure note ID is valid
1408
			$note = get_comment( $id );
1409
1410
			if ( is_null( $note ) ) {
1411
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 );
1412
			}
1413
1414
			// Ensure note ID is associated with given order
1415
			if ( $note->comment_post_ID != $order->id ) {
1416
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'The order note ID provided is not associated with the order', 'woocommerce' ), 400 );
1417
			}
1418
1419
			$data = apply_filters( 'woocommerce_api_edit_order_note_data', $data, $note->comment_ID, $order->id, $this );
1420
1421
			// Note content
1422
			if ( isset( $data['note'] ) ) {
1423
1424
				wp_update_comment(
1425
					array(
1426
						'comment_ID'      => $note->comment_ID,
1427
						'comment_content' => $data['note'],
1428
					)
1429
				);
1430
			}
1431
1432
			// Customer note
1433
			if ( isset( $data['customer_note'] ) ) {
1434
1435
				update_comment_meta( $note->comment_ID, 'is_customer_note', true === $data['customer_note'] ? 1 : 0 );
1436
			}
1437
1438
			do_action( 'woocommerce_api_edit_order_note', $note->comment_ID, $order->id, $this );
1439
1440
			return $this->get_order_note( $order->id, $note->comment_ID );
1441
		} catch ( WC_API_Exception $e ) {
1442
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1443
		}
1444
	}
1445
1446
	/**
1447
	 * Delete order note
1448
	 *
1449
	 * @since 2.2
1450
	 * @param string $order_id order ID
1451
	 * @param string $id note ID
1452
	 * @return WP_Error|array error or deleted message
1453
	 */
1454
	public function delete_order_note( $order_id, $id ) {
1455
		try {
1456
			$order_id = $this->validate_request( $order_id, $this->post_type, 'delete' );
1457
1458
			if ( is_wp_error( $order_id ) ) {
1459
				return $order_id;
1460
			}
1461
1462
			// Validate note ID
1463
			$id = absint( $id );
1464
1465
			if ( empty( $id ) ) {
1466
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'Invalid order note ID', 'woocommerce' ), 400 );
1467
			}
1468
1469
			// Ensure note ID is valid
1470
			$note = get_comment( $id );
1471
1472
			if ( is_null( $note ) ) {
1473
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'An order note with the provided ID could not be found', 'woocommerce' ), 404 );
1474
			}
1475
1476
			// Ensure note ID is associated with given order
1477
			if ( $note->comment_post_ID != $order_id ) {
1478
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_note_id', __( 'The order note ID provided is not associated with the order', 'woocommerce' ), 400 );
1479
			}
1480
1481
			// Force delete since trashed order notes could not be managed through comments list table
1482
			$result = wp_delete_comment( $note->comment_ID, true );
1483
1484
			if ( ! $result ) {
1485
				throw new WC_API_Exception( 'woocommerce_api_cannot_delete_order_note', __( 'This order note cannot be deleted', 'woocommerce' ), 500 );
1486
			}
1487
1488
			do_action( 'woocommerce_api_delete_order_note', $note->comment_ID, $order_id, $this );
1489
1490
			return array( 'message' => __( 'Permanently deleted order note', 'woocommerce' ) );
1491
		} catch ( WC_API_Exception $e ) {
1492
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1493
		}
1494
	}
1495
1496
	/**
1497
	 * Get the order refunds for an order
1498
	 *
1499
	 * @since 2.2
1500
	 * @param string $order_id order ID
1501
	 * @param string|null $fields fields to include in response
1502
	 * @return array
1503
	 */
1504
	public function get_order_refunds( $order_id, $fields = null ) {
1505
1506
		// Ensure ID is valid order ID
1507
		$order_id = $this->validate_request( $order_id, $this->post_type, 'read' );
1508
1509
		if ( is_wp_error( $order_id ) ) {
1510
			return $order_id;
1511
		}
1512
1513
		$refund_items = wc_get_orders( array(
1514
			'type'   => 'shop_order_refund',
1515
			'parent' => $order_id,
1516
			'limit'  => -1,
1517
			'return' => 'ids',
1518
		) );
1519
		$order_refunds = array();
1520
1521
		foreach ( $refund_items as $refund_id ) {
1522
			$order_refunds[] = current( $this->get_order_refund( $order_id, $refund_id, $fields ) );
0 ignored issues
show
Bug introduced by
It seems like $order_id defined by $this->validate_request(...his->post_type, 'read') on line 1507 can also be of type integer; however, WC_API_Orders::get_order_refund() 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...
1523
		}
1524
1525
		return array( 'order_refunds' => apply_filters( 'woocommerce_api_order_refunds_response', $order_refunds, $order_id, $fields, $refund_items, $this ) );
1526
	}
1527
1528
	/**
1529
	 * Get an order refund for the given order ID and ID
1530
	 *
1531
	 * @since 2.2
1532
	 * @param string $order_id order ID
1533
	 * @param string|null $fields fields to limit response to
1534
	 * @return array
1535
	 */
1536
	public function get_order_refund( $order_id, $id, $fields = null ) {
1537
		try {
1538
			// Validate order ID
1539
			$order_id = $this->validate_request( $order_id, $this->post_type, 'read' );
1540
1541
			if ( is_wp_error( $order_id ) ) {
1542
				return $order_id;
1543
			}
1544
1545
			$id = absint( $id );
1546
1547
			if ( empty( $id ) ) {
1548
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID', 'woocommerce' ), 400 );
1549
			}
1550
1551
			$order  = wc_get_order( $order_id );
1552
			$refund = wc_get_order( $id );
1553
1554
			if ( ! $refund ) {
1555
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found', 'woocommerce' ), 404 );
1556
			}
1557
1558
			$line_items = array();
1559
1560
			// Add line items
1561
			foreach ( $refund->get_items( 'line_item' ) as $item_id => $item ) {
1562
1563
				$product   = $order->get_product_from_item( $item );
1564
				$meta      = new WC_Order_Item_Meta( $item, $product );
1565
				$item_meta = array();
1566
1567 View Code Duplication
				foreach ( $meta->get_formatted() as $meta_key => $formatted_meta ) {
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...
1568
					$item_meta[] = array(
1569
						'key' => $meta_key,
1570
						'label' => $formatted_meta['label'],
1571
						'value' => $formatted_meta['value'],
1572
					);
1573
				}
1574
1575
				$line_items[] = array(
1576
					'id'               => $item_id,
1577
					'subtotal'         => wc_format_decimal( $order->get_line_subtotal( $item ), 2 ),
1578
					'subtotal_tax'     => wc_format_decimal( $item['line_subtotal_tax'], 2 ),
1579
					'total'            => wc_format_decimal( $order->get_line_total( $item ), 2 ),
1580
					'total_tax'        => wc_format_decimal( $order->get_line_tax( $item ), 2 ),
1581
					'price'            => wc_format_decimal( $order->get_item_total( $item ), 2 ),
1582
					'quantity'         => (int) $item['qty'],
1583
					'tax_class'        => ( ! empty( $item['tax_class'] ) ) ? $item['tax_class'] : null,
1584
					'name'             => $item['name'],
1585
					'product_id'       => ( isset( $product->variation_id ) ) ? $product->variation_id : $product->id,
1586
					'sku'              => is_object( $product ) ? $product->get_sku() : null,
1587
					'meta'             => $item_meta,
1588
					'refunded_item_id' => (int) $item['refunded_item_id'],
1589
				);
1590
			}
1591
1592
			$order_refund = array(
1593
				'id'         => $refund->id,
1594
				'created_at' => $this->server->format_datetime( $refund->date ),
1595
				'amount'     => wc_format_decimal( $refund->get_refund_amount(), 2 ),
0 ignored issues
show
Bug introduced by
The method get_refund_amount() does not seem to exist on object<WC_Order>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1596
				'reason'     => $refund->get_refund_reason(),
0 ignored issues
show
Bug introduced by
The method get_refund_reason() does not seem to exist on object<WC_Order>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1597
				'line_items' => $line_items
1598
			);
1599
1600
			return array( 'order_refund' => apply_filters( 'woocommerce_api_order_refund_response', $order_refund, $id, $fields, $refund, $order_id, $this ) );
1601
		} catch ( WC_API_Exception $e ) {
1602
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1603
		}
1604
	}
1605
1606
	/**
1607
	 * Create a new order refund for the given order
1608
	 *
1609
	 * @since 2.2
1610
	 * @param string $order_id order ID
1611
	 * @param array $data raw request data
1612
	 * @param bool $api_refund do refund using a payment gateway API
1613
	 * @return WP_Error|array error or created refund response data
1614
	 */
1615
	public function create_order_refund( $order_id, $data, $api_refund = true ) {
1616
		try {
1617 View Code Duplication
			if ( ! isset( $data['order_refund'] ) ) {
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...
1618
				throw new WC_API_Exception( 'woocommerce_api_missing_order_refund_data', sprintf( __( 'No %1$s data specified to create %1$s', 'woocommerce' ), 'order_refund' ), 400 );
1619
			}
1620
1621
			$data = $data['order_refund'];
1622
1623
			// Permission check
1624
			if ( ! current_user_can( 'publish_shop_orders' ) ) {
1625
				throw new WC_API_Exception( 'woocommerce_api_user_cannot_create_order_refund', __( 'You do not have permission to create order refunds', 'woocommerce' ), 401 );
1626
			}
1627
1628
			$order_id = absint( $order_id );
1629
1630
			if ( empty( $order_id ) ) {
1631
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_id', __( 'Order ID is invalid', 'woocommerce' ), 400 );
1632
			}
1633
1634
			$data = apply_filters( 'woocommerce_api_create_order_refund_data', $data, $order_id, $this );
1635
1636
			// Refund amount is required
1637
			if ( ! isset( $data['amount'] ) ) {
1638
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund', __( 'Refund amount is required', 'woocommerce' ), 400 );
1639
			} elseif ( 0 > $data['amount'] ) {
1640
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund', __( 'Refund amount must be positive', 'woocommerce' ), 400 );
1641
			}
1642
1643
			$data['order_id']  = $order_id;
1644
			$data['refund_id'] = 0;
1645
1646
			// Create the refund
1647
			$refund = wc_create_refund( $data );
1648
1649
			if ( ! $refund ) {
1650
				throw new WC_API_Exception( 'woocommerce_api_cannot_create_order_refund', __( 'Cannot create order refund, please try again', 'woocommerce' ), 500 );
1651
			}
1652
1653
			// Refund via API
1654 View Code Duplication
			if ( $api_refund ) {
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...
1655
				if ( WC()->payment_gateways() ) {
1656
					$payment_gateways = WC()->payment_gateways->payment_gateways();
1657
				}
1658
1659
				$order = wc_get_order( $order_id );
1660
1661
				if ( isset( $payment_gateways[ $order->payment_method ] ) && $payment_gateways[ $order->payment_method ]->supports( 'refunds' ) ) {
1662
					$result = $payment_gateways[ $order->payment_method ]->process_refund( $order_id, $refund->get_refund_amount(), $refund->get_refund_reason() );
1663
1664
					if ( is_wp_error( $result ) ) {
1665
						return $result;
1666
					} elseif ( ! $result ) {
1667
						throw new WC_API_Exception( 'woocommerce_api_create_order_refund_api_failed', __( 'An error occurred while attempting to create the refund using the payment gateway API', 'woocommerce' ), 500 );
1668
					}
1669
				}
1670
			}
1671
1672
			// HTTP 201 Created
1673
			$this->server->send_status( 201 );
1674
1675
			do_action( 'woocommerce_api_create_order_refund', $refund->id, $order_id, $this );
1676
1677
			return $this->get_order_refund( $order_id, $refund->id );
1678
		} catch ( WC_API_Exception $e ) {
1679
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1680
		}
1681
	}
1682
1683
	/**
1684
	 * Edit an order refund
1685
	 *
1686
	 * @since 2.2
1687
	 * @param string $order_id order ID
1688
	 * @param string $id refund ID
1689
	 * @param array $data parsed request data
1690
	 * @return WP_Error|array error or edited refund response data
1691
	 */
1692
	public function edit_order_refund( $order_id, $id, $data ) {
1693
		try {
1694 View Code Duplication
			if ( ! isset( $data['order_refund'] ) ) {
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...
1695
				throw new WC_API_Exception( 'woocommerce_api_missing_order_refund_data', sprintf( __( 'No %1$s data specified to edit %1$s', 'woocommerce' ), 'order_refund' ), 400 );
1696
			}
1697
1698
			$data = $data['order_refund'];
1699
1700
			// Validate order ID
1701
			$order_id = $this->validate_request( $order_id, $this->post_type, 'edit' );
1702
1703
			if ( is_wp_error( $order_id ) ) {
1704
				return $order_id;
1705
			}
1706
1707
			// Validate refund ID
1708
			$id = absint( $id );
1709
1710
			if ( empty( $id ) ) {
1711
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID', 'woocommerce' ), 400 );
1712
			}
1713
1714
			// Ensure order ID is valid
1715
			$refund = get_post( $id );
1716
1717
			if ( ! $refund ) {
1718
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found', 'woocommerce' ), 404 );
1719
			}
1720
1721
			// Ensure refund ID is associated with given order
1722
			if ( $refund->post_parent != $order_id ) {
1723
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'The order refund ID provided is not associated with the order', 'woocommerce' ), 400 );
1724
			}
1725
1726
			$data = apply_filters( 'woocommerce_api_edit_order_refund_data', $data, $refund->ID, $order_id, $this );
1727
1728
			// Update reason
1729
			if ( isset( $data['reason'] ) ) {
1730
				$updated_refund = wp_update_post( array( 'ID' => $refund->ID, 'post_excerpt' => $data['reason'] ) );
1731
1732
				if ( is_wp_error( $updated_refund ) ) {
1733
					return $updated_refund;
1734
				}
1735
			}
1736
1737
			// Update refund amount
1738
			if ( isset( $data['amount'] ) && 0 < $data['amount'] ) {
1739
				update_post_meta( $refund->ID, '_refund_amount', wc_format_decimal( $data['amount'] ) );
1740
			}
1741
1742
			do_action( 'woocommerce_api_edit_order_refund', $refund->ID, $order_id, $this );
1743
1744
			return $this->get_order_refund( $order_id, $refund->ID );
0 ignored issues
show
Bug introduced by
It seems like $order_id defined by $this->validate_request(...his->post_type, 'edit') on line 1701 can also be of type integer; however, WC_API_Orders::get_order_refund() 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...
1745
		} catch ( WC_API_Exception $e ) {
1746
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1747
		}
1748
	}
1749
1750
	/**
1751
	 * Delete order refund
1752
	 *
1753
	 * @since 2.2
1754
	 * @param string $order_id order ID
1755
	 * @param string $id refund ID
1756
	 * @return WP_Error|array error or deleted message
1757
	 */
1758
	public function delete_order_refund( $order_id, $id ) {
1759
		try {
1760
			$order_id = $this->validate_request( $order_id, $this->post_type, 'delete' );
1761
1762
			if ( is_wp_error( $order_id ) ) {
1763
				return $order_id;
1764
			}
1765
1766
			// Validate refund ID
1767
			$id = absint( $id );
1768
1769
			if ( empty( $id ) ) {
1770
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'Invalid order refund ID', 'woocommerce' ), 400 );
1771
			}
1772
1773
			// Ensure refund ID is valid
1774
			$refund = get_post( $id );
1775
1776
			if ( ! $refund ) {
1777
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'An order refund with the provided ID could not be found', 'woocommerce' ), 404 );
1778
			}
1779
1780
			// Ensure refund ID is associated with given order
1781
			if ( $refund->post_parent != $order_id ) {
1782
				throw new WC_API_Exception( 'woocommerce_api_invalid_order_refund_id', __( 'The order refund ID provided is not associated with the order', 'woocommerce' ), 400 );
1783
			}
1784
1785
			wc_delete_shop_order_transients( $order_id );
1786
1787
			do_action( 'woocommerce_api_delete_order_refund', $refund->ID, $order_id, $this );
1788
1789
			return $this->delete( $refund->ID, 'refund', true );
1790
		} catch ( WC_API_Exception $e ) {
1791
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1792
		}
1793
	}
1794
1795
	/**
1796
	 * Bulk update or insert orders
1797
	 * Accepts an array with orders in the formats supported by
1798
	 * WC_API_Orders->create_order() and WC_API_Orders->edit_order()
1799
	 *
1800
	 * @since 2.4.0
1801
	 * @param array $data
1802
	 * @return array
1803
	 */
1804 View Code Duplication
	public function bulk( $data ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1805
1806
		try {
1807
			if ( ! isset( $data['orders'] ) ) {
1808
				throw new WC_API_Exception( 'woocommerce_api_missing_orders_data', sprintf( __( 'No %1$s data specified to create/edit %1$s', 'woocommerce' ), 'orders' ), 400 );
1809
			}
1810
1811
			$data  = $data['orders'];
1812
			$limit = apply_filters( 'woocommerce_api_bulk_limit', 100, 'orders' );
1813
1814
			// Limit bulk operation
1815
			if ( count( $data ) > $limit ) {
1816
				throw new WC_API_Exception( 'woocommerce_api_orders_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request', 'woocommerce' ), $limit ), 413 );
1817
			}
1818
1819
			$orders = array();
1820
1821
			foreach ( $data as $_order ) {
1822
				$order_id = 0;
1823
1824
				// Try to get the order ID
1825
				if ( isset( $_order['id'] ) ) {
1826
					$order_id = intval( $_order['id'] );
1827
				}
1828
1829
				// Order exists / edit order
1830
				if ( $order_id ) {
1831
					$edit = $this->edit_order( $order_id, array( 'order' => $_order ) );
1832
1833
					if ( is_wp_error( $edit ) ) {
1834
						$orders[] = array(
1835
							'id'    => $order_id,
1836
							'error' => array( 'code' => $edit->get_error_code(), 'message' => $edit->get_error_message() )
1837
						);
1838
					} else {
1839
						$orders[] = $edit['order'];
1840
					}
1841
				}
1842
1843
				// Order don't exists / create order
1844
				else {
1845
					$new = $this->create_order( array( 'order' => $_order ) );
1846
1847
					if ( is_wp_error( $new ) ) {
1848
						$orders[] = array(
1849
							'id'    => $order_id,
1850
							'error' => array( 'code' => $new->get_error_code(), 'message' => $new->get_error_message() )
1851
						);
1852
					} else {
1853
						$orders[] = $new['order'];
1854
					}
1855
				}
1856
			}
1857
1858
			return array( 'orders' => apply_filters( 'woocommerce_api_orders_bulk_response', $orders, $this ) );
1859
		} catch ( WC_API_Exception $e ) {
1860
			return new WP_Error( $e->getErrorCode(), $e->getMessage(), array( 'status' => $e->getCode() ) );
1861
		}
1862
	}
1863
}
1864