Completed
Push — master ( 7796fe...fc386f )
by Mike
08:50
created

WC_Abstract_Order::prefix_item_id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
include_once( 'abstract-wc-legacy-order.php' );
7
8
/**
9
 * Abstract Order
10
 *
11
 * Handles generic order data and database interaction which is extended by both
12
 * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders).
13
 *
14
 * @class       WC_Abstract_Order
15
 * @version     2.7.0
16
 * @package     WooCommerce/Classes
17
 * @category    Class
18
 * @author      WooThemes
19
 */
20
abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
21
22
	/**
23
	 * Order Data array, with defaults. This is the core order data exposed
24
	 * in APIs since 2.7.0.
25
	 *
26
	 * Notes:
27
	 * order_tax = Sum of all taxes.
28
	 * cart_tax = cart_tax is the new name for the legacy 'order_tax' which is the tax for items only, not shipping.
29
	 *
30
	 * @since 2.7.0
31
	 * @var array
32
	 */
33
	protected $_data = array(
34
		'id'                 => 0,
35
		'parent_id'          => 0,
36
		'status'             => '',
37
		'type'               => 'shop_order',
38
		'order_key'          => '',
39
		'currency'           => '',
40
		'version'            => '',
41
		'prices_include_tax' => false,
42
		'date_created'       => '',
43
		'date_modified'      => '',
44
		'customer_id'        => 0,
45
		'discount_total'     => 0,
46
		'discount_tax'       => 0,
47
		'shipping_total'     => 0,
48
		'shipping_tax'       => 0,
49
		'cart_tax'           => 0,
50
		'total'              => 0,
51
		'total_tax'          => 0,
52
	);
53
54
	/**
55
	 * Data stored in meta keys, but not considered "meta" for an order.
56
	 * @since 2.7.0
57
	 * @var array
58
	 */
59
	protected $_internal_meta_keys = array(
60
		'_customer_user', '_order_key', '_order_currency', '_cart_discount',
61
		'_cart_discount_tax', '_order_shipping', '_order_shipping_tax',
62
		'_order_tax', '_order_total', '_order_version', '_prices_include_tax',
63
		'_payment_tokens',
64
	);
65
66
	/**
67
	 * Order items will be stored here, sometimes before they persist in the DB.
68
	 * @since 2.7.0
69
	 * @var array
70
	 */
71
	protected $_items = array(
72
		'line_items'     => null,
73
		'coupon_lines'   => null,
74
		'shipping_lines' => null,
75
		'fee_lines'      => null,
76
		'tax_lines'      => null,
77
	);
78
79
	/**
80
	 *  Internal meta type used to store order data.
81
	 * @var string
82
	 */
83
	protected $_meta_type = 'post';
84
85
	/**
86
	 * Stores meta in cache for future reads.
87
	 * A group must be set to to enable caching.
88
	 * @var string
89
	 */
90
	protected $_cache_group = 'order';
91
92
	/**
93
	 * Get the order if ID is passed, otherwise the order is new and empty.
94
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
95
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
96
	 * methods that will be maintained going forward.
97
	 *
98
	 * @param  int|object|WC_Order $order Order to init.
99
	 */
100
	public function __construct( $order = 0 ) {
101
		if ( is_numeric( $order ) && $order > 0 ) {
102
			$this->read( $order );
103
		} elseif ( $order instanceof self ) {
104
			$this->read( absint( $order->get_id() ) );
105
		} elseif ( ! empty( $order->ID ) ) {
106
			$this->read( absint( $order->ID ) );
107
		}
108
		// Set default status if none were read.
109
		if ( ! $this->get_status() ) {
110
			$this->set_status( apply_filters( 'woocommerce_default_order_status', 'pending' ) );
111
		}
112
	}
113
114
	/*
115
	|--------------------------------------------------------------------------
116
	| CRUD methods
117
	|--------------------------------------------------------------------------
118
	|
119
	| Methods which create, read, update and delete orders from the database.
120
	| Written in abstract fashion so that the way orders are stored can be
121
	| changed more easily in the future.
122
	|
123
	| A save method is included for convenience (chooses update or create based
124
	| on if the order exists yet).
125
	|
126
	*/
127
128
	/**
129
	 * Get internal type (post type.)
130
	 * @return string
131
	 */
132
	public function get_type() {
133
		return 'shop_order';
134
	}
135
136
	/**
137
	 * Get a title for the new post type.
138
	 */
139
	protected function get_post_title() {
140
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
141
	}
142
143
	/**
144
	 * Insert data into the database.
145
	 * @since 2.7.0
146
	 */
147
	public function create() {
148
		$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
149
		$this->set_date_created( current_time( 'timestamp' ) );
150
151
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
152
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
153
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
154
			'post_type'     => $this->get_type(),
155
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
156
			'ping_status'   => 'closed',
157
			'post_author'   => 1,
158
			'post_title'    => $this->get_post_title(),
159
			'post_password' => uniqid( 'order_' ),
160
			'post_parent'   => $this->get_parent_id(),
161
		) ), true );
162
163
		if ( $order_id ) {
164
			$this->set_id( $order_id );
165
166
			// Set meta data
167
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
168
			$this->update_post_meta( '_order_currency', $this->get_currency() );
169
			$this->update_post_meta( '_order_key', $this->get_order_key() );
170
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
171
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
172
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
173
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
174
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
175
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
176
			$this->update_post_meta( '_order_version', $this->get_version() );
177
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
178
			$this->save_meta_data();
179
		}
180
	}
181
182
	/**
183
	 * Read from the database.
184
	 * @since 2.7.0
185
	 * @param int $id ID of object to read.
186
	 */
187
	public function read( $id ) {
188
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
189
			return;
190
		}
191
192
		// Map standard post data
193
		$this->set_id( $post_object->ID );
194
		$this->set_parent_id( $post_object->post_parent );
195
		$this->set_date_created( $post_object->post_date );
196
		$this->set_date_modified( $post_object->post_modified );
197
		$this->set_status( $post_object->post_status );
198
		$this->set_order_type( $post_object->post_type );
199
		$this->set_customer_id( get_post_meta( $this->get_id(), '_customer_user', true ) );
200
		$this->set_order_key( get_post_meta( $this->get_id(), '_order_key', true ) );
201
		$this->set_currency( get_post_meta( $this->get_id(), '_order_currency', true ) );
202
		$this->set_discount_total( get_post_meta( $this->get_id(), '_cart_discount', true ) );
203
		$this->set_discount_tax( get_post_meta( $this->get_id(), '_cart_discount_tax', true ) );
204
		$this->set_shipping_total( get_post_meta( $this->get_id(), '_order_shipping', true ) );
205
		$this->set_shipping_tax( get_post_meta( $this->get_id(), '_order_shipping_tax', true ) );
206
		$this->set_cart_tax( get_post_meta( $this->get_id(), '_order_tax', true ) );
207
		$this->set_total( get_post_meta( $this->get_id(), '_order_total', true ) );
208
209
		// Orders store the state of prices including tax when created.
210
		$this->set_prices_include_tax( metadata_exists( 'post', $this->get_id(), '_prices_include_tax' ) ? 'yes' === get_post_meta( $this->get_id(), '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
211
212
		// Load meta data
213
		$this->read_meta_data();
214
	}
215
216
	/**
217
	 * Post meta update wrapper. Sets or deletes based on value.
218
	 * @since 2.7.0
219
	 * @return bool Was it changed?
220
	 */
221
	protected function update_post_meta( $key, $value ) {
222
		if ( '' !== $value ) {
223
			return update_post_meta( $this->get_id(), $key, $value );
224
		} else {
225
			return delete_post_meta( $this->get_id(), $key );
226
		}
227
	}
228
229
	/**
230
	 * Update data in the database.
231
	 * @since 2.7.0
232
	 */
233
	public function update() {
234
		global $wpdb;
235
236
		$order_id = $this->get_id();
237
238
		$wpdb->update(
239
			$wpdb->posts,
240
			array(
241
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
242
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
243
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
244
				'post_parent'   => $this->get_parent_id(),
245
			),
246
			array(
247
				'ID' => $order_id
248
			)
249
		);
250
251
		// Update meta data
252
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
253
		$this->update_post_meta( '_order_currency', $this->get_currency() );
254
		$this->update_post_meta( '_order_key', $this->get_order_key() );
255
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
256
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
257
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
258
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
259
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
260
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
261
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
262
		$this->save_meta_data();
263
	}
264
265
	/**
266
	 * Delete data from the database.
267
	 * @since 2.7.0
268
	 */
269
	public function delete() {
270
		wp_delete_post( $this->get_id() );
271
	}
272
273
	/**
274
	 * Save data to the database.
275
	 * @since 2.7.0
276
	 * @return int order ID
277
	 */
278
	public function save() {
279
		$this->set_version( WC_VERSION );
280
281
		if ( ! $this->get_id() ) {
282
			$this->create();
283
		} else {
284
			$this->update();
285
		}
286
287
		$this->save_items();
288
		clean_post_cache( $this->get_id() );
289
		wc_delete_shop_order_transients( $this->get_id() );
290
291
		return $this->get_id();
292
	}
293
294
	/**
295
	 * Save all order items which are part of this order.
296
	 */
297
	protected function save_items() {
298
		foreach ( $this->_items as $item_group => $items ) {
299
			if ( is_array( $items ) ) {
300
				foreach ( $items as $item_key => $item ) {
301
					$item->set_order_id( $this->get_id() );
302
					$item_id = $item->save();
303
304
					// If ID changed (new item saved to DB)...
305
					if ( $item_id !== $item_key ) {
306
						$this->_items[ $item_group ][ $item_id ] = $item;
307
						unset( $this->_items[ $item_group ][ $item_key ] );
308
309
						// Legacy action handler
310
						switch ( $item_group ) {
311
							case 'fee_lines' :
312
								if ( isset( $item->legacy_fee, $item->legacy_fee_key ) ) {
313
									wc_do_deprecated_action( 'woocommerce_add_order_fee_meta', array( $this->get_id(), $item_id, $item->legacy_fee, $item->legacy_fee_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
314
								}
315
							break;
316
							case 'shipping_lines' :
317
								if ( isset( $item->legacy_package_key ) ) {
318
									wc_do_deprecated_action( 'woocommerce_add_shipping_order_item', array( $item_id, $item->legacy_package_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
319
								}
320
							break;
321
							case 'line_items' :
322
								if ( isset( $item->legacy_values, $item->legacy_cart_item_key ) ) {
323
									wc_do_deprecated_action( 'woocommerce_add_order_item_meta', array( $item_id, $item->legacy_values, $item->legacy_cart_item_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
324
								}
325
							break;
326
						}
327
					}
328
				}
329
			}
330
		}
331
	}
332
333
	/*
334
	|--------------------------------------------------------------------------
335
	| Getters
336
	|--------------------------------------------------------------------------
337
	|
338
	| Methods for getting data from the order object.
339
	|
340
	*/
341
342
	/**
343
	 * Get all class data in array format.
344
	 * @since 2.7.0
345
	 * @return array
346
	 */
347
	public function get_data() {
348
		return array_merge(
349
			$this->_data,
350
			array(
351
				'meta_data'      => $this->get_meta_data(),
352
				'line_items'     => $this->get_items( 'line_item' ),
353
				'tax_lines'      => $this->get_items( 'tax' ),
354
				'shipping_lines' => $this->get_items( 'shipping' ),
355
				'fee_lines'      => $this->get_items( 'fee' ),
356
				'coupon_lines'   => $this->get_items( 'coupon' ),
357
			)
358
		);
359
	}
360
361
	/**
362
	 * Get order ID.
363
	 * @since 2.7.0
364
	 * @return integer
365
	 */
366
	public function get_id() {
367
		return $this->_data['id'];
368
	}
369
370
	/**
371
	 * Get parent order ID.
372
	 * @since 2.7.0
373
	 * @return integer
374
	 */
375
	public function get_parent_id() {
376
		return $this->_data['parent_id'];
377
	}
378
379
	/**
380
	 * get_order_number function.
381
	 *
382
	 * Gets the order number for display (by default, order ID).
383
	 *
384
	 * @return string
385
	 */
386
	public function get_order_number() {
387
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
388
	}
389
390
	/**
391
	 * Get order key.
392
	 * @since 2.7.0
393
	 * @return string
394
	 */
395
	public function get_order_key() {
396
		return $this->_data['order_key'];
397
	}
398
399
	/**
400
	 * Gets order currency.
401
	 * @return string
402
	 */
403
	public function get_currency() {
404
		return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this );
405
	}
406
407
	/**
408
	 * Get order_version
409
	 * @return string
410
	 */
411
	public function get_version() {
412
		return $this->_data['version'];
413
	}
414
415
	/**
416
	 * Get prices_include_tax
417
	 * @return bool
418
	 */
419
	public function get_prices_include_tax() {
420
		return $this->_data['prices_include_tax'];
421
	}
422
423
	/**
424
	 * Get Order Type
425
	 * @return string
426
	 */
427
	public function get_order_type() {
428
		return $this->_data['type'];
429
	}
430
431
	/**
432
	 * Get date_created
433
	 * @return int
434
	 */
435
	public function get_date_created() {
436
		return $this->_data['date_created'];
437
	}
438
439
	/**
440
	 * Get date_modified
441
	 * @return int
442
	 */
443
	public function get_date_modified() {
444
		return $this->_data['date_modified'];
445
	}
446
447
	/**
448
	 * Get customer_id
449
	 * @return int
450
	 */
451
	public function get_customer_id() {
452
		return $this->_data['customer_id'];
453
	}
454
455
	/**
456
	 * Return the order statuses without wc- internal prefix.
457
	 * @return string
458
	 */
459
	public function get_status() {
460
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this );
461
	}
462
463
	/**
464
	 * Alias for get_customer_id().
465
	 * @since  2.2
466
	 * @return int
467
	 */
468
	public function get_user_id() {
469
		return $this->get_customer_id();
470
	}
471
472
	/**
473
	 * Get the user associated with the order. False for guests.
474
	 *
475
	 * @since  2.2
476
	 * @return WP_User|false
477
	 */
478
	public function get_user() {
479
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
480
	}
481
482
	/**
483
	 * Get discount_total
484
	 * @param bool $raw Gets raw unfiltered value.
485
	 * @return string
486
	 */
487
	public function get_discount_total( $raw = false ) {
488
		$value = wc_format_decimal( $this->_data['discount_total'] );
489
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
490
	}
491
492
	/**
493
	 * Get discount_tax
494
	 * @param bool $raw Gets raw unfiltered value.
495
	 * @return string
496
	 */
497
	public function get_discount_tax( $raw = false ) {
498
		$value = wc_format_decimal( $this->_data['discount_tax'] );
499
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
500
	}
501
502
	/**
503
	 * Get shipping_total
504
	 * @param bool $raw Gets raw unfiltered value.
505
	 * @return string
506
	 */
507
	public function get_shipping_total( $raw = false ) {
508
		$value = wc_format_decimal( $this->_data['shipping_total'] );
509
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
510
	}
511
512
	/**
513
	 * Get shipping_tax.
514
	 * @param bool $raw Gets raw unfiltered value.
515
	 * @return string
516
	 */
517
	public function get_shipping_tax( $raw = false ) {
518
		$value = wc_format_decimal( $this->_data['shipping_tax'] );
519
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
520
	}
521
522
	/**
523
	 * Gets cart tax amount.
524
	 * @param bool $raw Gets raw unfiltered value.
525
	 * @return float
526
	 */
527
	public function get_cart_tax( $raw = false ) {
528
		$value = wc_format_decimal( $this->_data['cart_tax'] );
529
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
530
	}
531
532
	/**
533
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
534
	 * @param bool $raw Gets raw unfiltered value.
535
	 * @return float
536
	 */
537
	public function get_total( $raw = false ) {
538
		$value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() );
539
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this );
540
	}
541
542
	/**
543
	 * Get total tax amount. Alias for get_order_tax().
544
	 *
545
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
546
	 * these values being modified and then saved back to the DB. There are
547
	 * other, later hooks available to change totals on display. e.g.
548
	 * woocommerce_get_order_item_totals.
549
	 * @param bool $raw Gets raw unfiltered value.
550
	 * @return float
551
	 */
552
	public function get_total_tax( $raw = false ) {
553
		$value = wc_format_decimal( $this->_data['total_tax'] );
554
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
555
	}
556
557
	/**
558
	 * Gets the total discount amount.
559
	 * @param  bool $ex_tax Show discount excl any tax.
560
	 * @return float
561
	 */
562
	public function get_total_discount( $ex_tax = true ) {
563
		if ( $ex_tax ) {
564
			$total_discount = $this->get_discount_total();
565
		} else {
566
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
567
		}
568
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
569
	}
570
571
	/**
572
	 * Gets order subtotal.
573
	 * @return float
574
	 */
575
	public function get_subtotal() {
576
		$subtotal = 0;
577
578
		foreach ( $this->get_items() as $item ) {
579
			$subtotal += $item->get_subtotal();
580
		}
581
582
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
583
	}
584
585
	/**
586
	 * Get taxes, merged by code, formatted ready for output.
587
	 *
588
	 * @return array
589
	 */
590
	public function get_tax_totals() {
591
		$tax_totals = array();
592
593
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
594
			$code = $tax->get_rate_code();
595
596 View Code Duplication
			if ( ! isset( $tax_totals[ $code ] ) ) {
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...
597
				$tax_totals[ $code ] = new stdClass();
598
				$tax_totals[ $code ]->amount = 0;
599
			}
600
601
			$tax_totals[ $code ]->id                = $key;
602
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
603
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
604
			$tax_totals[ $code ]->label             = $tax->get_label();
605
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
606
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
607
		}
608
609 View Code Duplication
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
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...
610
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
611
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
612
		}
613
614
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
615
	}
616
617
	/*
618
	|--------------------------------------------------------------------------
619
	| Setters
620
	|--------------------------------------------------------------------------
621
	|
622
	| Functions for setting order data. These should not update anything in the
623
	| database itself and should only change what is stored in the class
624
	| object. However, for backwards compatibility pre 2.7.0 some of these
625
	| setters may handle both.
626
	|
627
	*/
628
629
	/**
630
	 * Set order ID.
631
	 * @since 2.7.0
632
	 * @param int $value
633
	 */
634
	public function set_id( $value ) {
635
		$this->_data['id'] = absint( $value );
636
	}
637
638
	/**
639
	 * Set parent order ID.
640
	 * @since 2.7.0
641
	 * @param int $value
642
	 */
643
	public function set_parent_id( $value ) {
644
		$this->_data['parent_id'] = absint( $value );
645
	}
646
647
	/**
648
	 * Set order status.
649
	 * @since 2.7.0
650
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
651
	 * @param array details of change
652
	 */
653
	 public function set_status( $new_status ) {
654
		$old_status = $this->get_status();
655
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
656
657
		// Only allow valid new status
658
		if ( ! in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) ) {
659
			$new_status = 'pending';
660
		}
661
662
		$this->_data['status'] = 'wc-' . $new_status;
663
664
		// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
665
		if ( $old_status && ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
666
			$old_status = 'pending';
667
		}
668
669
		return array(
670
			'from' => $old_status,
671
			'to'   => $new_status
672
		);
673
	 }
674
675
	/**
676
	 * Set Order Type
677
	 * @param string $value
678
	 */
679
	public function set_order_type( $value ) {
680
		$this->_data['type'] = $value;
681
	}
682
683
	/**
684
	 * Set order_key.
685
	 * @param string $value Max length 20 chars.
686
	 */
687
	public function set_order_key( $value ) {
688
		$this->_data['order_key'] = substr( $value, 0, 20 );
689
	}
690
691
	/**
692
	 * Set order_version
693
	 * @param string $value
694
	 */
695
	public function set_version( $value ) {
696
		$this->_data['version'] = $value;
697
	}
698
699
	/**
700
	 * Set order_currency
701
	 * @param string $value
702
	 */
703
	public function set_currency( $value ) {
704
		$this->_data['currency'] = $value;
705
	}
706
707
	/**
708
	 * Set prices_include_tax
709
	 * @param bool $value
710
	 */
711
	public function set_prices_include_tax( $value ) {
712
		$this->_data['prices_include_tax'] = (bool) $value;
713
	}
714
715
	/**
716
	 * Set date_created
717
	 * @param string $timestamp Timestamp
718
	 */
719
	public function set_date_created( $timestamp ) {
720
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
721
	}
722
723
	/**
724
	 * Set date_modified
725
	 * @param string $timestamp
726
	 */
727
	public function set_date_modified( $timestamp ) {
728
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
729
	}
730
731
	/**
732
	 * Set customer_id
733
	 * @param int $value
734
	 */
735
	public function set_customer_id( $value ) {
736
		$this->_data['customer_id'] = absint( $value );
737
	}
738
739
	/**
740
	 * Set discount_total
741
	 * @param string $value
742
	 */
743
	public function set_discount_total( $value ) {
744
		$this->_data['discount_total'] = wc_format_decimal( $value );
745
	}
746
747
	/**
748
	 * Set discount_tax
749
	 * @param string $value
750
	 */
751
	public function set_discount_tax( $value ) {
752
		$this->_data['discount_tax'] = wc_format_decimal( $value );
753
	}
754
755
	/**
756
	 * Set shipping_total
757
	 * @param string $value
758
	 */
759
	public function set_shipping_total( $value ) {
760
		$this->_data['shipping_total'] = wc_format_decimal( $value );
761
	}
762
763
	/**
764
	 * Set shipping_tax
765
	 * @param string $value
766
	 */
767
	public function set_shipping_tax( $value ) {
768
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
769
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
770
	}
771
772
	/**
773
	 * Set cart tax
774
	 * @param string $value
775
	 */
776
	public function set_cart_tax( $value ) {
777
		$this->_data['cart_tax'] = wc_format_decimal( $value );
778
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
779
	}
780
781
	/**
782
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
783
	 * @param string $value
784
	 */
785
	protected function set_total_tax( $value ) {
786
		$this->_data['total_tax'] = wc_format_decimal( $value );
787
	}
788
789
	/**
790
	 * Set total
791
	 * @param string $value
792
	 * @param string $deprecated Function used to set different totals based on this.
793
	 */
794
	public function set_total( $value, $deprecated = '' ) {
795
		if ( $deprecated ) {
796
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
797
			return $this->legacy_set_total( $value, $deprecated );
798
		}
799
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
800
	}
801
802
	/*
803
	|--------------------------------------------------------------------------
804
	| Order Item Handling
805
	|--------------------------------------------------------------------------
806
	|
807
	| Order items are used for products, taxes, shipping, and fees within
808
	| each order.
809
	|
810
	*/
811
812
	/**
813
	 * Prefixes an item key with a string so arrays are associative rather than numeric.
814
	 * @param  int $id
815
	 * @return string
816
	 */
817
	protected function prefix_item_id( $id ) {
818
		return 'item-' . $id;
819
	}
820
821
	/**
822
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
823
	 * @param string $type Order item type. Default null.
824
	 */
825
	public function remove_order_items( $type = null ) {
826
		global $wpdb;
827
		if ( ! empty( $type ) ) {
828
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->get_id(), $type ) );
829
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
830
			if ( $group = $this->type_to_group( $type ) ) {
831
				$this->_items[ $group ] = null;
832
			}
833
		} else {
834
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->get_id() ) );
835
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
836
			$this->_items = array(
837
				'line_items'     => null,
838
				'coupon_lines'   => null,
839
				'shipping_lines' => null,
840
				'fee_lines'      => null,
841
				'tax_lines'      => null,
842
			);
843
		}
844
	}
845
846
	/**
847
	 * Convert a type to a types group.
848
	 * @param string $type
849
	 * @return string group
850
	 */
851
	protected function type_to_group( $type ) {
852
		$type_to_group = array(
853
			'line_item' => 'line_items',
854
			'tax'       => 'tax_lines',
855
			'shipping'  => 'shipping_lines',
856
			'fee'       => 'fee_lines',
857
			'coupon'    => 'coupon_lines',
858
		);
859
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
860
	}
861
862
	/**
863
	 * Return an array of items/products within this order.
864
	 * @param string|array $types Types of line items to get (array or string).
865
	 * @return Array of WC_Order_item
866
	 */
867
	public function get_items( $types = 'line_item' ) {
868
		$items = array();
869
		$types = array_filter( (array) $types );
870
871
		foreach ( $types as $type ) {
872
			if ( $group = $this->type_to_group( $type ) ) {
873
				if ( is_null( $this->_items[ $group ] ) ) {
874
					$this->_items[ $group ] = $this->get_items_from_db( $type );
875
				}
876
				$items = array_merge( $items, $this->_items[ $group ] );
877
			}
878
		}
879
880
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
881
	}
882
883
	/**
884
	 * Gets items from the database by type.
885
	 * @param  string $type
886
	 * @return array
887
	 */
888
	protected function get_items_from_db( $type ) {
889
		global $wpdb;
890
891
		$get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s ORDER BY order_item_id;", $this->get_id(), $type ) ;
892
		$items         = $wpdb->get_results( $get_items_sql );
893
894
		if ( ! empty( $items ) ) {
895
			$items = array_map( array( $this, 'get_item' ), array_combine( array_map( array( $this, 'prefix_item_id' ), wp_list_pluck( $items, 'order_item_id' ) ), $items ) );
896
		} else {
897
			$items = array();
898
		}
899
900
		return $items;
901
	}
902
903
	/**
904
	 * Return an array of fees within this order.
905
	 * @return array
906
	 */
907
	public function get_fees() {
908
		return $this->get_items( 'fee' );
909
	}
910
911
	/**
912
	 * Return an array of taxes within this order.
913
	 * @return array
914
	 */
915
	public function get_taxes() {
916
		return $this->get_items( 'tax' );
917
	}
918
919
	/**
920
	 * Return an array of shipping costs within this order.
921
	 * @return array
922
	 */
923
	public function get_shipping_methods() {
924
		return $this->get_items( 'shipping' );
925
	}
926
927
	/**
928
	 * Gets formatted shipping method title.
929
	 * @return string
930
	 */
931
	public function get_shipping_method() {
932
		$names = array();
933
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
934
			$names[] = $shipping_method->get_name();
935
		}
936
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
937
	}
938
939
	/**
940
	 * Get coupon codes only.
941
	 * @return array
942
	 */
943
	public function get_used_coupons() {
944
		$coupon_codes = array();
945
		if ( $coupons = $this->get_items( 'coupon' ) ) {
946
			foreach ( $coupons as $coupon ) {
947
				$coupon_codes[] = $coupon->get_code();
948
			}
949
		}
950
		return $coupon_codes;
951
	}
952
953
	/**
954
	 * Gets the count of order items of a certain type.
955
	 *
956
	 * @param string $item_type
957
	 * @return string
958
	 */
959
	public function get_item_count( $item_type = '' ) {
960
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
961
		$count = 0;
962
963
		foreach ( $items as $item ) {
964
			$count += $item->get_qty();
965
		}
966
967
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
968
	}
969
970
	/**
971
	 * Get an order item object, based on it's type.
972
	 * @since  2.7.0
973
	 * @param  int $item_id
974
	 * @return WC_Order_Item
975
	 */
976
	public function get_item( $item_id ) {
977
		return WC_Order_Factory::get_order_item( $item_id );
978
	}
979
980
	/**
981
	 * Adds an order item to this order. The order item will not persist until save.
982
	 * @param object Order item (product, shipping, fee, coupon, tax)
983
	 */
984
	public function add_item( $item ) {
985
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
986
			$item_type = 'line_item';
987
			$items_key = 'line_items';
988
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
989
			$item_type = 'fee';
990
			$items_key = 'fee_lines';
991
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
992
			$item_type = 'shipping';
993
			$items_key = 'shipping_lines';
994
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
995
			$item_type = 'tax';
996
			$items_key = 'tax_lines';
997
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
998
			$item_type = 'coupon';
999
			$items_key = 'coupon_lines';
1000
		} else {
1001
			return false;
1002
		}
1003
1004
		// Make sure existing items are loaded so we can append this new one.
1005
		if ( is_null( $this->_items[ $items_key ] ) ) {
1006
			$this->_items[ $items_key ] = $this->get_items( $item_type );
1007
		}
1008
1009
		// Append new row with generated temporary ID
1010
		if ( $item->get_id() ) {
1011
			$this->_items[ $items_key ][ $this->prefix_item_id( $item->get_id() ) ] = $item;
1012
		} else {
1013
			$this->_items[ $items_key ][ 'new:' . md5( json_encode( $item ) ) ] = $item;
1014
		}
1015
	}
1016
1017
	/**
1018
	 * Add a product line item to the order.
1019
	 * @param  \WC_Product $product
1020
	 * @param  int $qty
1021
	 * @param  array $args
1022
	 * @return int order item ID
1023
	 */
1024
	public function add_product( $product, $qty = 1, $args = array() ) {
1025
		$args = wp_parse_args( $args, array(
1026
			'qty'          => $qty,
1027
			'name'         => $product ? $product->get_title() : '',
1028
			'tax_class'    => $product ? $product->get_tax_class() : '',
1029
			'product_id'   => $product ? $product->get_id() : '',
1030
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
1031
			'variation'    => $product && isset( $product->variation_id ) ? $product->get_variation_attributes() : array(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_attributes() does only exist in the following sub-classes of WC_Product: WC_Product_Variable, WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1032
			'subtotal'     => $product ? $product->get_price_excluding_tax( $qty ) : '',
1033
			'total'        => $product ? $product->get_price_excluding_tax( $qty ) : '',
1034
			'subtotal_tax' => 0,
1035
			'total_tax'    => 0,
1036
			'taxes'        => array(
1037
				'subtotal' => array(),
1038
				'total'    => array(),
1039
			),
1040
		) );
1041
1042
		// BW compatibility with old args
1043 View Code Duplication
		if ( isset( $args['totals'] ) ) {
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...
1044
			foreach ( $args['totals'] as $key => $value ) {
1045
				if ( 'tax' === $key ) {
1046
					$args['total_tax'] = $value;
1047
				} elseif ( 'tax_data' === $key ) {
1048
					$args['taxes'] = $value;
1049
				} else {
1050
					$args[ $key ] = $value;
1051
				}
1052
			}
1053
		}
1054
1055
		$item = new WC_Order_Item_Product( $args );
1056
		$item->set_backorder_meta();
1057
		$item->set_order_id( $this->get_id() );
1058
		$item->save();
1059
		$this->add_item( $item );
1060
		wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '2.7', 'Use woocommerce_new_order_item action instead.' );
1061
		return $item->get_id();
1062
	}
1063
1064
	/**
1065
	 * Add coupon code to the order.
1066
	 * @param string $code
1067
	 * @param int $discount tax amount.
1068
	 * @param int $discount_tax amount.
1069
	 * @return int order item ID
1070
	 */
1071
	public function add_coupon( $code = array(), $discount = 0, $discount_tax = 0 ) {
1072
		$item = new WC_Order_Item_Coupon( array(
0 ignored issues
show
Documentation introduced by
array('code' => $code, '..._tax' => $discount_tax) is of type array<string,string|arra...scount_tax":"integer"}>, 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...
1073
			'code'         => $code,
1074
			'discount'     => $discount,
1075
			'discount_tax' => $discount_tax,
1076
		) );
1077
		$item->set_order_id( $this->get_id() );
1078
		$item->save();
1079
		$this->add_item( $item );
1080
		wc_do_deprecated_action( 'woocommerce_order_add_coupon', array( $this->get_id(), $item->get_id(), $code, $discount, $discount_tax ), '2.7', 'Use woocommerce_new_order_item action instead.' );
1081
		return $item->get_id();
1082
	}
1083
1084
	/**
1085
	 * Add a tax row to the order.
1086
	 * @param array $args
1087
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
1088
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
1089
	 * @return int order item ID
1090
	 */
1091
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1092
		if ( ! is_array( $args ) ) {
1093
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
1094
			$args = array(
1095
				'rate_id'            => $args,
1096
				'tax_total'          => $deprecated1,
1097
				'shipping_tax_total' => $deprecated2,
1098
			);
1099
		}
1100
		$args = wp_parse_args( $args, array(
1101
			'rate_id'            => '',
1102
			'tax_total'          => 0,
1103
			'shipping_tax_total' => 0,
1104
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
1105
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
1106
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
1107
		) );
1108
		$item = new WC_Order_Item_Tax( $args );
1109
		$item->set_order_id( $this->get_id() );
1110
		$item->save();
1111
		$this->add_item( $item );
1112
		wc_do_deprecated_action( 'woocommerce_order_add_tax', array( $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] ), '2.7', 'Use woocommerce_new_order_item action instead.' );
1113
		return $item->get_id();
1114
	}
1115
1116
	/**
1117
	 * Add a shipping row to the order.
1118
	 * @param WC_Shipping_Rate shipping_rate
1119
	 * @return int order item ID
1120
	 */
1121
	public function add_shipping( $shipping_rate ) {
1122
		$item = new WC_Order_Item_Shipping( array(
0 ignored issues
show
Documentation introduced by
array('method_title' => ..._rate->get_meta_data()) is of type array<string,?,{"method_...":"?","meta_data":"?"}>, 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...
1123
			'method_title' => $shipping_rate->label,
1124
			'method_id'    => $shipping_rate->id,
1125
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1126
			'taxes'        => $shipping_rate->taxes,
1127
			'meta_data'    => $shipping_rate->get_meta_data(),
1128
		) );
1129
		$item->set_order_id( $this->get_id() );
1130
		$item->save();
1131
		$this->add_item( $item );
1132
		wc_do_deprecated_action( 'woocommerce_order_add_shipping', array( $this->get_id(), $item->get_id(), $shipping_rate ), '2.7', 'Use woocommerce_new_order_item action instead.' );
1133
		return $item->get_id();
1134
	}
1135
1136
	/**
1137
	 * Add a fee to the order.
1138
	 * Order must be saved prior to adding items.
1139
	 * @param object $fee
1140
	 * @return int updated order item ID
1141
	 */
1142
	public function add_fee( $fee ) {
1143
		$item = new WC_Order_Item_Fee( array(
0 ignored issues
show
Documentation introduced by
array('name' => $fee->na...al' => $fee->tax_data)) is of type array<string,?,{"name":"...?,{\"total\":\"?\"}>"}>, 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...
1144
			'name'      => $fee->name,
1145
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1146
			'total'     => $fee->amount,
1147
			'total_tax' => $fee->tax,
1148
			'taxes'     => array(
1149
				'total' => $fee->tax_data,
1150
			),
1151
		) );
1152
		$item->set_order_id( $this->get_id() );
1153
		$item->save();
1154
		$this->add_item( $item );
1155
		wc_do_deprecated_action( 'woocommerce_order_add_fee', array( $this->get_id(), $item->get_id(), $fee ), '2.7', 'Use woocommerce_new_order_item action instead.' );
1156
		return $item->get_id();
1157
	}
1158
1159
	/*
1160
	|--------------------------------------------------------------------------
1161
	| Payment Token Handling
1162
	|--------------------------------------------------------------------------
1163
	|
1164
	| Payment tokens are hashes used to take payments by certain gateways.
1165
	|
1166
	*/
1167
1168
	/**
1169
	 * Add a payment token to an order
1170
	 *
1171
	 * @since 2.6
1172
	 * @param  WC_Payment_Token   $token     Payment token object
1173
	 * @return boolean|int The new token ID or false if it failed.
1174
	 */
1175
	public function add_payment_token( $token ) {
1176
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1177
			return false;
1178
		}
1179
1180
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1181
1182
		if ( empty ( $token_ids ) ) {
1183
			$token_ids = array();
1184
		}
1185
1186
		$token_ids[] = $token->get_id();
1187
1188
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1189
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1190
		return $token->get_id();
1191
	}
1192
1193
	/**
1194
	 * Returns a list of all payment tokens associated with the current order
1195
	 *
1196
	 * @since 2.6
1197
	 * @return array An array of payment token objects
1198
	 */
1199
	public function get_payment_tokens() {
1200
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1201
	}
1202
1203
	/*
1204
	|--------------------------------------------------------------------------
1205
	| Calculations.
1206
	|--------------------------------------------------------------------------
1207
	|
1208
	| These methods calculate order totals and taxes based on the current data.
1209
	|
1210
	*/
1211
1212
	/**
1213
	 * Calculate shipping total.
1214
	 *
1215
	 * @since 2.2
1216
	 * @return float
1217
	 */
1218
	public function calculate_shipping() {
1219
		$shipping_total = 0;
1220
1221
		foreach ( $this->get_shipping_methods() as $shipping ) {
1222
			$shipping_total += $shipping->get_total();
1223
		}
1224
1225
		$this->set_shipping_total( $shipping_total );
1226
		$this->save();
1227
1228
		return $this->get_shipping_total();
1229
	}
1230
1231
	/**
1232
	 * Get all tax classes for items in the order.
1233
	 *
1234
	 * @since 2.6.3
1235
	 * @return array
1236
	 */
1237
	public function get_items_tax_classes() {
1238
		$found_tax_classes = array();
1239
1240
		foreach ( $this->get_items() as $item ) {
1241
			if ( $_product = $this->get_product_from_item( $item ) ) {
0 ignored issues
show
Deprecated Code introduced by
The method WC_Abstract_Legacy_Order::get_product_from_item() has been deprecated with message: Add deprecation notices in future release. Replaced with $item->get_product()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1242
				$found_tax_classes[] = $_product->get_tax_class();
0 ignored issues
show
Bug introduced by
The method get_tax_class cannot be called on $_product (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1243
			}
1244
		}
1245
1246
		return array_unique( $found_tax_classes );
1247
	}
1248
1249
	/**
1250
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1251
	 *
1252
	 * Will use the base country unless customer addresses are set.
1253
	 * @param $args array Added in 2.7.0 to pass things like location.
1254
	 */
1255
	public function calculate_taxes( $args = array() ) {
1256
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1257
		$args              = wp_parse_args( $args, array(
1258
			'country'  => 'billing' === $tax_based_on ? $this->get_billing_country()  : $this->get_shipping_country(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_country() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_country() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1259
			'state'    => 'billing' === $tax_based_on ? $this->get_billing_state()    : $this->get_shipping_state(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_state() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_state() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1260
			'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_postcode() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_postcode() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1261
			'city'     => 'billing' === $tax_based_on ? $this->get_billing_city()     : $this->get_shipping_city(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_city() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_city() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1262
		) );
1263
1264
		// Default to base
1265
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1266
			$default          = wc_get_base_location();
1267
			$args['country']  = $default['country'];
1268
			$args['state']    = $default['state'];
1269
			$args['postcode'] = '';
1270
			$args['city']     = '';
1271
		}
1272
1273
		// Calc taxes for line items
1274
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1275
			$tax_class           = $item->get_tax_class();
1276
			$tax_status          = $item->get_tax_status();
1277
1278
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1279
				$tax_rates = WC_Tax::find_rates( array(
1280
					'country'   => $args['country'],
1281
					'state'     => $args['state'],
1282
					'postcode'  => $args['postcode'],
1283
					'city'      => $args['city'],
1284
					'tax_class' => $tax_class,
1285
				) );
1286
1287
				$total = $item->get_total();
1288
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1289
1290
				if ( $item->is_type( 'line_item' ) ) {
1291
					$subtotal       = $item->get_subtotal();
1292
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1293
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1294
					$item->set_subtotal_tax( $subtotal_tax );
1295
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1296
				} else {
1297
					$item->set_taxes( array( 'total' => $taxes ) );
1298
				}
1299
				$item->save();
1300
			}
1301
		}
1302
1303
		// Calc taxes for shipping
1304
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1305
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1306
1307
			// Inherit tax class from items
1308
			if ( '' === $shipping_tax_class ) {
1309
				$tax_rates         = array();
1310
				$tax_classes       = array_merge( array( '' ), WC_Tax::get_tax_classes() );
1311
				$found_tax_classes = $this->get_items_tax_classes();
1312
1313
				foreach ( $tax_classes as $tax_class ) {
1314
					$tax_class = sanitize_title( $tax_class );
1315
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1316
						$tax_rates = WC_Tax::find_shipping_rates( array(
1317
							'country'   => $args['country'],
1318
							'state'     => $args['state'],
1319
							'postcode'  => $args['postcode'],
1320
							'city'      => $args['city'],
1321
							'tax_class' => $tax_class,
1322
						) );
1323
						break;
1324
					}
1325
				}
1326
			} else {
1327
				$tax_rates = WC_Tax::find_shipping_rates( array(
1328
					'country'   => $args['country'],
1329
					'state'     => $args['state'],
1330
					'postcode'  => $args['postcode'],
1331
					'city'      => $args['city'],
1332
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1333
				) );
1334
			}
1335
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1336
			$item->save();
1337
		}
1338
		$this->update_taxes();
1339
	}
1340
1341
	/**
1342
	 * Update tax lines for the order based on the line item taxes themselves.
1343
	 */
1344
	public function update_taxes() {
1345
		$cart_taxes     = array();
1346
		$shipping_taxes = array();
1347
1348
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1349
			$taxes = $item->get_taxes();
1350 View Code Duplication
			foreach ( $taxes['total'] as $tax_rate_id => $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...
1351
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1352
			}
1353
		}
1354
1355
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1356
			$taxes = $item->get_taxes();
1357 View Code Duplication
			foreach ( $taxes['total'] as $tax_rate_id => $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...
1358
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1359
			}
1360
		}
1361
1362
		// Remove old existing tax rows.
1363
		$this->remove_order_items( 'tax' );
1364
1365
		// Now merge to keep tax rows.
1366
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1367
			$this->add_tax( array(
1368
				'rate_id'            => $tax_rate_id,
1369
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1370
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1371
			) );
1372
		}
1373
1374
		// Save tax totals
1375
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1376
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1377
		$this->save();
1378
	}
1379
1380
	/**
1381
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1382
	 *
1383
	 * @since 2.2
1384
	 * @param  bool $and_taxes Calc taxes if true.
1385
	 * @return float calculated grand total.
1386
	 */
1387
	public function calculate_totals( $and_taxes = true ) {
1388
		$cart_subtotal     = 0;
1389
		$cart_total        = 0;
1390
		$fee_total         = 0;
1391
		$cart_subtotal_tax = 0;
1392
		$cart_total_tax    = 0;
1393
1394
		if ( $and_taxes && wc_tax_enabled() ) {
1395
			$this->calculate_taxes();
1396
		}
1397
1398
		// line items
1399
		foreach ( $this->get_items() as $item ) {
1400
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1401
			$cart_total        += wc_format_decimal( $item->get_total() );
1402
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1403
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1404
		}
1405
1406
		$this->calculate_shipping();
1407
1408
		foreach ( $this->get_fees() as $item ) {
1409
			$fee_total += $item->get_total();
1410
		}
1411
1412
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1413
1414
		$this->set_discount_total( $cart_subtotal - $cart_total );
1415
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1416
		$this->set_total( $grand_total );
1417
		$this->save();
1418
1419
		return $grand_total;
1420
	}
1421
1422
	/**
1423
	 * Get item subtotal - this is the cost before discount.
1424
	 *
1425
	 * @param object $item
1426
	 * @param bool $inc_tax (default: false).
1427
	 * @param bool $round (default: true).
1428
	 * @return float
1429
	 */
1430 View Code Duplication
	public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
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...
1431
		$subtotal = 0;
1432
1433
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1434
			if ( $inc_tax ) {
1435
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1436
			} else {
1437
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1438
			}
1439
1440
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1441
		}
1442
1443
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1444
	}
1445
1446
	/**
1447
	 * Get line subtotal - this is the cost before discount.
1448
	 *
1449
	 * @param object $item
1450
	 * @param bool $inc_tax (default: false).
1451
	 * @param bool $round (default: true).
1452
	 * @return float
1453
	 */
1454 View Code Duplication
	public function get_line_subtotal( $item, $inc_tax = false, $round = true ) {
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...
1455
		$subtotal = 0;
1456
1457
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1458
			if ( $inc_tax ) {
1459
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1460
			} else {
1461
				$subtotal = $item->get_subtotal();
1462
			}
1463
1464
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1465
		}
1466
1467
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1468
	}
1469
1470
	/**
1471
	 * Calculate item cost - useful for gateways.
1472
	 *
1473
	 * @param object $item
1474
	 * @param bool $inc_tax (default: false).
1475
	 * @param bool $round (default: true).
1476
	 * @return float
1477
	 */
1478 View Code Duplication
	public function get_item_total( $item, $inc_tax = false, $round = true ) {
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...
1479
		$total = 0;
1480
1481
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1482
			if ( $inc_tax ) {
1483
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1484
			} else {
1485
				$total = $item->get_total() / max( 1, $item->get_qty() );
1486
			}
1487
1488
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1489
		}
1490
1491
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1492
	}
1493
1494
	/**
1495
	 * Calculate line total - useful for gateways.
1496
	 *
1497
	 * @param object $item
1498
	 * @param bool $inc_tax (default: false).
1499
	 * @param bool $round (default: true).
1500
	 * @return float
1501
	 */
1502 View Code Duplication
	public function get_line_total( $item, $inc_tax = false, $round = true ) {
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...
1503
		$total = 0;
1504
1505
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1506
			// Check if we need to add line tax to the line total.
1507
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1508
1509
			// Check if we need to round.
1510
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1511
		}
1512
1513
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1514
	}
1515
1516
	/**
1517
	 * Get item tax - useful for gateways.
1518
	 *
1519
	 * @param mixed $item
1520
	 * @param bool $round (default: true).
1521
	 * @return float
1522
	 */
1523
	public function get_item_tax( $item, $round = true ) {
1524
		$tax = 0;
1525
1526
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1527
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1528
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1529
		}
1530
1531
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1532
	}
1533
1534
	/**
1535
	 * Get line tax - useful for gateways.
1536
	 *
1537
	 * @param mixed $item
1538
	 * @return float
1539
	 */
1540
	public function get_line_tax( $item ) {
1541
		return apply_filters( 'woocommerce_order_amount_line_tax', is_callable( array( $item, 'get_total_tax' ) ) ? wc_round_tax_total( $item->get_total_tax() ) : 0, $item, $this );
1542
	}
1543
1544
	/**
1545
	 * Gets line subtotal - formatted for display.
1546
	 *
1547
	 * @param array  $item
1548
	 * @param string $tax_display
1549
	 * @return string
1550
	 */
1551
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1552
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1553
1554
		if ( 'excl' == $tax_display ) {
1555
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1556
1557
			$subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_currency() ) );
0 ignored issues
show
Documentation introduced by
$item is of type array, but the function expects a object.

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...
1558
		} else {
1559
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_currency()) );
0 ignored issues
show
Documentation introduced by
$item is of type array, but the function expects a object.

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...
1560
		}
1561
1562
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1563
	}
1564
1565
	/**
1566
	 * Gets order total - formatted for display.
1567
	 * @return string
1568
	 */
1569
	public function get_formatted_order_total() {
1570
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1571
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1572
	}
1573
1574
	/**
1575
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1576
	 *
1577
	 * @param bool $compound (default: false).
1578
	 * @param string $tax_display (default: the tax_display_cart value).
1579
	 * @return string
1580
	 */
1581
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1582
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1583
		$subtotal    = 0;
1584
1585
		if ( ! $compound ) {
1586
			foreach ( $this->get_items() as $item ) {
1587
				$subtotal += $item->get_subtotal();
1588
1589
				if ( 'incl' === $tax_display ) {
1590
					$subtotal += $item->get_subtotal_tax();
1591
				}
1592
			}
1593
1594
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1595
1596
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1597
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1598
			}
1599
1600
		} else {
1601
			if ( 'incl' === $tax_display ) {
1602
				return '';
1603
			}
1604
1605
			foreach ( $this->get_items() as $item ) {
1606
				$subtotal += $item->get_subtotal();
1607
			}
1608
1609
			// Add Shipping Costs.
1610
			$subtotal += $this->get_shipping_total();
1611
1612
			// Remove non-compound taxes.
1613
			foreach ( $this->get_taxes() as $tax ) {
1614
				if ( $this->is_compound() ) {
0 ignored issues
show
Bug introduced by
The method is_compound() does not seem to exist on object<WC_Abstract_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...
1615
					continue;
1616
				}
1617
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1618
			}
1619
1620
			// Remove discounts.
1621
			$subtotal = $subtotal - $this->get_total_discount();
1622
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1623
		}
1624
1625
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1626
	}
1627
1628
	/**
1629
	 * Gets shipping (formatted).
1630
	 *
1631
	 * @return string
1632
	 */
1633
	public function get_shipping_to_display( $tax_display = '' ) {
1634
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1635
1636
		if ( $this->get_shipping_total() != 0 ) {
1637
1638
			if ( $tax_display == 'excl' ) {
1639
1640
				// Show shipping excluding tax.
1641
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1642
1643 View Code Duplication
				if ( $this->get_shipping_tax() != 0 && $this->get_prices_include_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...
1644
					$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display );
1645
				}
1646
1647
			} else {
1648
1649
				// Show shipping including tax.
1650
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1651
1652 View Code Duplication
				if ( $this->get_shipping_tax() != 0 && ! $this->get_prices_include_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...
1653
					$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display );
1654
				}
1655
1656
			}
1657
1658
			$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf( __( 'via %s', 'woocommerce' ), $this->get_shipping_method() ) . '</small>', $this );
1659
1660
		} elseif ( $this->get_shipping_method() ) {
1661
			$shipping = $this->get_shipping_method();
1662
		} else {
1663
			$shipping = __( 'Free!', 'woocommerce' );
1664
		}
1665
1666
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1667
	}
1668
1669
	/**
1670
	 * Get the discount amount (formatted).
1671
	 * @since  2.3.0
1672
	 * @return string
1673
	 */
1674
	public function get_discount_to_display( $tax_display = '' ) {
1675
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1676
		return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( 'excl' === $tax_display && 'excl' === get_option( 'woocommerce_tax_display_cart' ) ), array( 'currency' => $this->get_currency() ) ), $this );
1677
	}
1678
1679
	/**
1680
	 * Get totals for display on pages and in emails.
1681
	 *
1682
	 * @param mixed $tax_display
1683
	 * @return array
1684
	 */
1685
	public function get_order_item_totals( $tax_display = '' ) {
1686
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1687
		$total_rows  = array();
1688
1689
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1690
			$total_rows['cart_subtotal'] = array(
1691
				'label' => __( 'Subtotal:', 'woocommerce' ),
1692
				'value'    => $subtotal,
1693
			);
1694
		}
1695
1696
		if ( $this->get_total_discount() > 0 ) {
1697
			$total_rows['discount'] = array(
1698
				'label' => __( 'Discount:', 'woocommerce' ),
1699
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1700
			);
1701
		}
1702
1703
		if ( $this->get_shipping_method() ) {
1704
			$total_rows['shipping'] = array(
1705
				'label' => __( 'Shipping:', 'woocommerce' ),
1706
				'value'    => $this->get_shipping_to_display( $tax_display ),
1707
			);
1708
		}
1709
1710
		if ( $fees = $this->get_fees() ) {
1711
			foreach ( $fees as $id => $fee ) {
1712
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1713
					continue;
1714
				}
1715
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1716
					'label' => $fee->get_name() . ':',
1717
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1718
				);
1719
			}
1720
		}
1721
1722
		// Tax for tax exclusive prices.
1723
		if ( 'excl' === $tax_display ) {
1724
1725
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1726
1727
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1728
1729
					$total_rows[ sanitize_title( $code ) ] = array(
1730
						'label' => $tax->label . ':',
1731
						'value'    => $tax->formatted_amount,
1732
					);
1733
				}
1734
1735
			} else {
1736
1737
				$total_rows['tax'] = array(
1738
					'label' => WC()->countries->tax_or_vat() . ':',
1739
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1740
				);
1741
			}
1742
		}
1743
1744
		if ( $this->get_total() > 0 && $this->get_payment_method_title() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_payment_method_title() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1745
			$total_rows['payment_method'] = array(
1746
				'label' => __( 'Payment Method:', 'woocommerce' ),
1747
				'value' => $this->get_payment_method_title(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_payment_method_title() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1748
			);
1749
		}
1750
1751
		if ( $refunds = $this->get_refunds() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_refunds() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1752
			foreach ( $refunds as $id => $refund ) {
1753
				$total_rows[ 'refund_' . $id ] = array(
1754
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1755
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1756
				);
1757
			}
1758
		}
1759
1760
		$total_rows['order_total'] = array(
1761
			'label' => __( 'Total:', 'woocommerce' ),
1762
			'value'    => $this->get_formatted_order_total( $tax_display ),
0 ignored issues
show
Unused Code introduced by
The call to WC_Abstract_Order::get_formatted_order_total() has too many arguments starting with $tax_display.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1763
		);
1764
1765
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1766
	}
1767
1768
	/*
1769
	|--------------------------------------------------------------------------
1770
	| Conditionals
1771
	|--------------------------------------------------------------------------
1772
	|
1773
	| Checks if a condition is true or false.
1774
	|
1775
	*/
1776
1777
	/**
1778
	 * Checks the order status against a passed in status.
1779
	 *
1780
	 * @return bool
1781
	 */
1782
	public function has_status( $status ) {
1783
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1784
	}
1785
1786
	/**
1787
	 * Check whether this order has a specific shipping method or not.
1788
	 *
1789
	 * @param string $method_id
1790
	 * @return bool
1791
	 */
1792
	public function has_shipping_method( $method_id ) {
1793
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1794
			if ( $shipping_method->get_method_id() === $method_id ) {
1795
				return true;
1796
			}
1797
		}
1798
		return false;
1799
	}
1800
1801
	/**
1802
	 * Check if an order key is valid.
1803
	 *
1804
	 * @param mixed $key
1805
	 * @return bool
1806
	 */
1807
	public function key_is_valid( $key ) {
1808
		return $key === $this->get_order_key();
1809
	}
1810
1811
	/**
1812
	 * Returns true if the order contains a free product.
1813
	 * @since 2.5.0
1814
	 * @return bool
1815
	 */
1816
	public function has_free_item() {
1817
		foreach ( $this->get_items() as $item ) {
1818
			if ( ! $item->get_total() ) {
1819
				return true;
1820
			}
1821
		}
1822
		return false;
1823
	}
1824
}
1825