Completed
Push — master ( d60d3d...21ad19 )
by Mike
11:28
created

WC_Abstract_Order::get_items_key()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 13
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 15
rs 8.8571
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
		'order_key'          => '',
38
		'currency'           => '',
39
		'version'            => '',
40
		'prices_include_tax' => false,
41
		'date_created'       => '',
42
		'date_modified'      => '',
43
		'customer_id'        => 0,
44
		'discount_total'     => 0,
45
		'discount_tax'       => 0,
46
		'shipping_total'     => 0,
47
		'shipping_tax'       => 0,
48
		'cart_tax'           => 0,
49
		'total'              => 0,
50
		'total_tax'          => 0,
51
	);
52
53
	/**
54
	 * Data stored in meta keys, but not considered "meta" for an order.
55
	 * @since 2.7.0
56
	 * @var array
57
	 */
58
	protected $_internal_meta_keys = array(
59
		'_customer_user', '_order_key', '_order_currency', '_cart_discount',
60
		'_cart_discount_tax', '_order_shipping', '_order_shipping_tax',
61
		'_order_tax', '_order_total', '_order_version', '_prices_include_tax',
62
		'_payment_tokens',
63
	);
64
65
	/**
66
	 * Order items will be stored here, sometimes before they persist in the DB.
67
	 * @since 2.7.0
68
	 * @var array
69
	 */
70
	protected $_items = array(
71
		'line_items'     => null,
72
		'coupon_lines'   => null,
73
		'shipping_lines' => null,
74
		'fee_lines'      => null,
75
		'tax_lines'      => null,
76
	);
77
78
	/**
79
	 * Order items that need deleting are stored here.
80
	 * @since 2.7.0
81
	 * @var array
82
	 */
83
	protected $_items_to_delete = array();
84
85
	/**
86
	 *  Internal meta type used to store order data.
87
	 * @var string
88
	 */
89
	protected $_meta_type = 'post';
90
91
	/**
92
	 * Stores meta in cache for future reads.
93
	 * A group must be set to to enable caching.
94
	 * @var string
95
	 */
96
	protected $_cache_group = 'order';
97
98
	/**
99
	 * Get the order if ID is passed, otherwise the order is new and empty.
100
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
101
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
102
	 * methods that will be maintained going forward.
103
	 *
104
	 * @param  int|object|WC_Order $order Order to init.
105
	 */
106
	public function __construct( $order = 0 ) {
107
		if ( is_numeric( $order ) && $order > 0 ) {
108
			$this->read( $order );
109
		} elseif ( $order instanceof self ) {
110
			$this->read( absint( $order->get_id() ) );
111
		} elseif ( ! empty( $order->ID ) ) {
112
			$this->read( absint( $order->ID ) );
113
		}
114
		// Set default status if none were read.
115
		if ( ! $this->get_status() ) {
116
			$this->set_status( apply_filters( 'woocommerce_default_order_status', 'pending' ) );
117
		}
118
	}
119
120
	/*
121
	|--------------------------------------------------------------------------
122
	| CRUD methods
123
	|--------------------------------------------------------------------------
124
	|
125
	| Methods which create, read, update and delete orders from the database.
126
	| Written in abstract fashion so that the way orders are stored can be
127
	| changed more easily in the future.
128
	|
129
	| A save method is included for convenience (chooses update or create based
130
	| on if the order exists yet).
131
	|
132
	*/
133
134
	/**
135
	 * Get internal type (post type.)
136
	 * @return string
137
	 */
138
	public function get_type() {
139
		return 'shop_order';
140
	}
141
142
	/**
143
	 * Get a title for the new post type.
144
	 */
145
	protected function get_post_title() {
146
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
147
	}
148
149
	/**
150
	 * Insert data into the database.
151
	 * @since 2.7.0
152
	 */
153
	public function create() {
154
		$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
155
		$this->set_date_created( current_time( 'timestamp' ) );
156
157
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
158
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
159
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
160
			'post_type'     => $this->get_type(),
161
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
162
			'ping_status'   => 'closed',
163
			'post_author'   => 1,
164
			'post_title'    => $this->get_post_title(),
165
			'post_password' => uniqid( 'order_' ),
166
			'post_parent'   => $this->get_parent_id(),
167
		) ), true );
168
169
		if ( $order_id ) {
170
			$this->set_id( $order_id );
171
172
			// Set meta data
173
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
174
			$this->update_post_meta( '_order_currency', $this->get_currency() );
175
			$this->update_post_meta( '_order_key', $this->get_order_key() );
176
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
177
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
178
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
179
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
180
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
181
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
182
			$this->update_post_meta( '_order_version', $this->get_version() );
183
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
184
			$this->save_meta_data();
185
		}
186
	}
187
188
	/**
189
	 * Read from the database.
190
	 * @since 2.7.0
191
	 * @param int $id ID of object to read.
192
	 */
193
	public function read( $id ) {
194
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
195
			return;
196
		}
197
198
		// Map standard post data
199
		$this->set_id( $post_object->ID );
200
		$this->set_parent_id( $post_object->post_parent );
201
		$this->set_date_created( $post_object->post_date );
202
		$this->set_date_modified( $post_object->post_modified );
203
		$this->set_status( $post_object->post_status );
204
		$this->set_customer_id( get_post_meta( $this->get_id(), '_customer_user', true ) );
205
		$this->set_order_key( get_post_meta( $this->get_id(), '_order_key', true ) );
206
		$this->set_currency( get_post_meta( $this->get_id(), '_order_currency', true ) );
207
		$this->set_discount_total( get_post_meta( $this->get_id(), '_cart_discount', true ) );
208
		$this->set_discount_tax( get_post_meta( $this->get_id(), '_cart_discount_tax', true ) );
209
		$this->set_shipping_total( get_post_meta( $this->get_id(), '_order_shipping', true ) );
210
		$this->set_shipping_tax( get_post_meta( $this->get_id(), '_order_shipping_tax', true ) );
211
		$this->set_cart_tax( get_post_meta( $this->get_id(), '_order_tax', true ) );
212
		$this->set_total( get_post_meta( $this->get_id(), '_order_total', true ) );
213
214
		// Orders store the state of prices including tax when created.
215
		$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' ) );
216
217
		// Load meta data
218
		$this->read_meta_data();
219
	}
220
221
	/**
222
	 * Post meta update wrapper. Sets or deletes based on value.
223
	 * @since 2.7.0
224
	 * @return bool Was it changed?
225
	 */
226
	protected function update_post_meta( $key, $value ) {
227
		if ( '' !== $value ) {
228
			return update_post_meta( $this->get_id(), $key, $value );
229
		} else {
230
			return delete_post_meta( $this->get_id(), $key );
231
		}
232
	}
233
234
	/**
235
	 * Update data in the database.
236
	 * @since 2.7.0
237
	 */
238
	public function update() {
239
		global $wpdb;
240
241
		$order_id = $this->get_id();
242
243
		$wpdb->update(
244
			$wpdb->posts,
245
			array(
246
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
247
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
248
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
249
				'post_parent'   => $this->get_parent_id(),
250
			),
251
			array(
252
				'ID' => $order_id
253
			)
254
		);
255
256
		// Update meta data
257
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
258
		$this->update_post_meta( '_order_currency', $this->get_currency() );
259
		$this->update_post_meta( '_order_key', $this->get_order_key() );
260
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
261
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
262
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
263
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
264
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
265
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
266
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
267
		$this->save_meta_data();
268
	}
269
270
	/**
271
	 * Delete data from the database.
272
	 * @since 2.7.0
273
	 */
274
	public function delete() {
275
		wp_delete_post( $this->get_id() );
276
	}
277
278
	/**
279
	 * Save data to the database.
280
	 * @since 2.7.0
281
	 * @return int order ID
282
	 */
283
	public function save() {
284
		$this->set_version( WC_VERSION );
285
286
		if ( ! $this->get_id() ) {
287
			$this->create();
288
		} else {
289
			$this->update();
290
		}
291
292
		$this->save_items();
293
		clean_post_cache( $this->get_id() );
294
		wc_delete_shop_order_transients( $this->get_id() );
295
296
		return $this->get_id();
297
	}
298
299
	/**
300
	 * Save all order items which are part of this order.
301
	 */
302
	protected function save_items() {
303
		// remove items
304
		foreach ( $this->_items_to_delete as $item ) {
305
			$item->delete();
306
		}
307
308
		$this->_items_to_delete = array();
309
310
		// Add/save items
311
		foreach ( $this->_items as $item_group => $items ) {
312
			if ( is_array( $items ) ) {
313
				foreach ( $items as $item_key => $item ) {
314
					$item->set_order_id( $this->get_id() );
315
					$item_id = $item->save();
316
317
					// If ID changed (new item saved to DB)...
318
					if ( $item_id !== $item_key ) {
319
						$this->_items[ $item_group ][ $item_id ] = $item;
320
						unset( $this->_items[ $item_group ][ $item_key ] );
321
322
						// Legacy action handler
323
						switch ( $item_group ) {
324
							case 'fee_lines' :
325
								if ( isset( $item->legacy_fee, $item->legacy_fee_key ) ) {
326
									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.' );
327
								}
328
							break;
329
							case 'shipping_lines' :
330
								if ( isset( $item->legacy_package_key ) ) {
331
									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.' );
332
								}
333
							break;
334
							case 'line_items' :
335
								if ( isset( $item->legacy_values, $item->legacy_cart_item_key ) ) {
336
									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.' );
337
								}
338
							break;
339
						}
340
					}
341
				}
342
			}
343
		}
344
	}
345
346
	/*
347
	|--------------------------------------------------------------------------
348
	| Getters
349
	|--------------------------------------------------------------------------
350
	|
351
	| Methods for getting data from the order object.
352
	|
353
	*/
354
355
	/**
356
	 * Get all class data in array format.
357
	 * @since 2.7.0
358
	 * @return array
359
	 */
360
	public function get_data() {
361
		return array_merge(
362
			$this->_data,
363
			array(
364
				'number'         => $this->get_order_number(),
365
				'meta_data'      => $this->get_meta_data(),
366
				'line_items'     => $this->get_items( 'line_item' ),
367
				'tax_lines'      => $this->get_items( 'tax' ),
368
				'shipping_lines' => $this->get_items( 'shipping' ),
369
				'fee_lines'      => $this->get_items( 'fee' ),
370
				'coupon_lines'   => $this->get_items( 'coupon' ),
371
			)
372
		);
373
	}
374
375
	/**
376
	 * Get order ID.
377
	 * @since 2.7.0
378
	 * @return integer
379
	 */
380
	public function get_id() {
381
		return $this->_data['id'];
382
	}
383
384
	/**
385
	 * Get parent order ID.
386
	 * @since 2.7.0
387
	 * @return integer
388
	 */
389
	public function get_parent_id() {
390
		return $this->_data['parent_id'];
391
	}
392
393
	/**
394
	 * get_order_number function.
395
	 *
396
	 * Gets the order number for display (by default, order ID).
397
	 *
398
	 * @return string
399
	 */
400
	public function get_order_number() {
401
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
402
	}
403
404
	/**
405
	 * Get order key.
406
	 * @since 2.7.0
407
	 * @return string
408
	 */
409
	public function get_order_key() {
410
		return $this->_data['order_key'];
411
	}
412
413
	/**
414
	 * Gets order currency.
415
	 * @return string
416
	 */
417
	public function get_currency() {
418
		return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this );
419
	}
420
421
	/**
422
	 * Get order_version
423
	 * @return string
424
	 */
425
	public function get_version() {
426
		return $this->_data['version'];
427
	}
428
429
	/**
430
	 * Get prices_include_tax
431
	 * @return bool
432
	 */
433
	public function get_prices_include_tax() {
434
		return $this->_data['prices_include_tax'];
435
	}
436
437
	/**
438
	 * Get date_created
439
	 * @return int
440
	 */
441
	public function get_date_created() {
442
		return $this->_data['date_created'];
443
	}
444
445
	/**
446
	 * Get date_modified
447
	 * @return int
448
	 */
449
	public function get_date_modified() {
450
		return $this->_data['date_modified'];
451
	}
452
453
	/**
454
	 * Get customer_id
455
	 * @return int
456
	 */
457
	public function get_customer_id() {
458
		return $this->_data['customer_id'];
459
	}
460
461
	/**
462
	 * Return the order statuses without wc- internal prefix.
463
	 * @return string
464
	 */
465
	public function get_status() {
466
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this );
467
	}
468
469
	/**
470
	 * Alias for get_customer_id().
471
	 * @since  2.2
472
	 * @return int
473
	 */
474
	public function get_user_id() {
475
		return $this->get_customer_id();
476
	}
477
478
	/**
479
	 * Get the user associated with the order. False for guests.
480
	 *
481
	 * @since  2.2
482
	 * @return WP_User|false
483
	 */
484
	public function get_user() {
485
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
486
	}
487
488
	/**
489
	 * Get discount_total
490
	 * @param bool $raw Gets raw unfiltered value.
491
	 * @return string
492
	 */
493
	public function get_discount_total( $raw = false ) {
494
		$value = wc_format_decimal( $this->_data['discount_total'] );
495
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
496
	}
497
498
	/**
499
	 * Get discount_tax
500
	 * @param bool $raw Gets raw unfiltered value.
501
	 * @return string
502
	 */
503
	public function get_discount_tax( $raw = false ) {
504
		$value = wc_format_decimal( $this->_data['discount_tax'] );
505
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
506
	}
507
508
	/**
509
	 * Get shipping_total
510
	 * @param bool $raw Gets raw unfiltered value.
511
	 * @return string
512
	 */
513
	public function get_shipping_total( $raw = false ) {
514
		$value = wc_format_decimal( $this->_data['shipping_total'] );
515
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
516
	}
517
518
	/**
519
	 * Get shipping_tax.
520
	 * @param bool $raw Gets raw unfiltered value.
521
	 * @return string
522
	 */
523
	public function get_shipping_tax( $raw = false ) {
524
		$value = wc_format_decimal( $this->_data['shipping_tax'] );
525
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
526
	}
527
528
	/**
529
	 * Gets cart tax amount.
530
	 * @param bool $raw Gets raw unfiltered value.
531
	 * @return float
532
	 */
533
	public function get_cart_tax( $raw = false ) {
534
		$value = wc_format_decimal( $this->_data['cart_tax'] );
535
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
536
	}
537
538
	/**
539
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
540
	 * @param bool $raw Gets raw unfiltered value.
541
	 * @return float
542
	 */
543
	public function get_total( $raw = false ) {
544
		$value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() );
545
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this );
546
	}
547
548
	/**
549
	 * Get total tax amount. Alias for get_order_tax().
550
	 *
551
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
552
	 * these values being modified and then saved back to the DB. There are
553
	 * other, later hooks available to change totals on display. e.g.
554
	 * woocommerce_get_order_item_totals.
555
	 * @param bool $raw Gets raw unfiltered value.
556
	 * @return float
557
	 */
558
	public function get_total_tax( $raw = false ) {
559
		$value = wc_format_decimal( $this->_data['total_tax'] );
560
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
561
	}
562
563
	/**
564
	 * Gets the total discount amount.
565
	 * @param  bool $ex_tax Show discount excl any tax.
566
	 * @return float
567
	 */
568
	public function get_total_discount( $ex_tax = true ) {
569
		if ( $ex_tax ) {
570
			$total_discount = $this->get_discount_total();
571
		} else {
572
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
573
		}
574
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
575
	}
576
577
	/**
578
	 * Gets order subtotal.
579
	 * @return float
580
	 */
581
	public function get_subtotal() {
582
		$subtotal = 0;
583
584
		foreach ( $this->get_items() as $item ) {
585
			$subtotal += $item->get_subtotal();
586
		}
587
588
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
589
	}
590
591
	/**
592
	 * Get taxes, merged by code, formatted ready for output.
593
	 *
594
	 * @return array
595
	 */
596
	public function get_tax_totals() {
597
		$tax_totals = array();
598
599
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
600
			$code = $tax->get_rate_code();
601
602 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...
603
				$tax_totals[ $code ] = new stdClass();
604
				$tax_totals[ $code ]->amount = 0;
605
			}
606
607
			$tax_totals[ $code ]->id                = $key;
608
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
609
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
610
			$tax_totals[ $code ]->label             = $tax->get_label();
611
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
612
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
613
		}
614
615 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...
616
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
617
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
618
		}
619
620
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
621
	}
622
623
	/*
624
	|--------------------------------------------------------------------------
625
	| Setters
626
	|--------------------------------------------------------------------------
627
	|
628
	| Functions for setting order data. These should not update anything in the
629
	| database itself and should only change what is stored in the class
630
	| object. However, for backwards compatibility pre 2.7.0 some of these
631
	| setters may handle both.
632
	|
633
	*/
634
635
	/**
636
	 * Set order ID.
637
	 * @since 2.7.0
638
	 * @param int $value
639
	 */
640
	public function set_id( $value ) {
641
		$this->_data['id'] = absint( $value );
642
	}
643
644
	/**
645
	 * Set parent order ID.
646
	 * @since 2.7.0
647
	 * @param int $value
648
	 */
649
	public function set_parent_id( $value ) {
650
		$this->_data['parent_id'] = absint( $value );
651
	}
652
653
	/**
654
	 * Set order status.
655
	 * @since 2.7.0
656
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
657
	 * @param array details of change
658
	 */
659
	 public function set_status( $new_status ) {
660
		$old_status = $this->get_status();
661
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
662
663
		// Only allow valid new status
664
		if ( ! in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) ) {
665
			$new_status = 'pending';
666
		}
667
668
		$this->_data['status'] = 'wc-' . $new_status;
669
670
		// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
671
		if ( $old_status && ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
672
			$old_status = 'pending';
673
		}
674
675
		return array(
676
			'from' => $old_status,
677
			'to'   => $new_status
678
		);
679
	 }
680
681
	/**
682
	 * Set order_key.
683
	 * @param string $value Max length 20 chars.
684
	 */
685
	public function set_order_key( $value ) {
686
		$this->_data['order_key'] = substr( $value, 0, 20 );
687
	}
688
689
	/**
690
	 * Set order_version
691
	 * @param string $value
692
	 */
693
	public function set_version( $value ) {
694
		$this->_data['version'] = $value;
695
	}
696
697
	/**
698
	 * Set order_currency
699
	 * @param string $value
700
	 */
701
	public function set_currency( $value ) {
702
		if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ) ) ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
703
			//$this->throw_exception( 'invalid_currency', 'Invalid currency code' );
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
704
		}
705
		$this->_data['currency'] = $value;
706
	}
707
708
	/**
709
	 * Set prices_include_tax
710
	 * @param bool $value
711
	 */
712
	public function set_prices_include_tax( $value ) {
713
		$this->_data['prices_include_tax'] = (bool) $value;
714
	}
715
716
	/**
717
	 * Set date_created
718
	 * @param string $timestamp Timestamp
719
	 */
720
	public function set_date_created( $timestamp ) {
721
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
722
	}
723
724
	/**
725
	 * Set date_modified
726
	 * @param string $timestamp
727
	 */
728
	public function set_date_modified( $timestamp ) {
729
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
730
	}
731
732
	/**
733
	 * Set customer_id
734
	 * @param int $value
735
	 */
736
	public function set_customer_id( $value ) {
737
		$this->_data['customer_id'] = absint( $value );
738
	}
739
740
	/**
741
	 * Set discount_total
742
	 * @param string $value
743
	 */
744
	public function set_discount_total( $value ) {
745
		$this->_data['discount_total'] = wc_format_decimal( $value );
746
	}
747
748
	/**
749
	 * Set discount_tax
750
	 * @param string $value
751
	 */
752
	public function set_discount_tax( $value ) {
753
		$this->_data['discount_tax'] = wc_format_decimal( $value );
754
	}
755
756
	/**
757
	 * Set shipping_total
758
	 * @param string $value
759
	 */
760
	public function set_shipping_total( $value ) {
761
		$this->_data['shipping_total'] = wc_format_decimal( $value );
762
	}
763
764
	/**
765
	 * Set shipping_tax
766
	 * @param string $value
767
	 */
768
	public function set_shipping_tax( $value ) {
769
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
770
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
771
	}
772
773
	/**
774
	 * Set cart tax
775
	 * @param string $value
776
	 */
777
	public function set_cart_tax( $value ) {
778
		$this->_data['cart_tax'] = wc_format_decimal( $value );
779
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
780
	}
781
782
	/**
783
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
784
	 * @param string $value
785
	 */
786
	protected function set_total_tax( $value ) {
787
		$this->_data['total_tax'] = wc_format_decimal( $value );
788
	}
789
790
	/**
791
	 * Set total
792
	 * @param string $value
793
	 * @param string $deprecated Function used to set different totals based on this.
794
	 */
795
	public function set_total( $value, $deprecated = '' ) {
796
		if ( $deprecated ) {
797
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
798
			return $this->legacy_set_total( $value, $deprecated );
799
		}
800
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
801
	}
802
803
	/*
804
	|--------------------------------------------------------------------------
805
	| Order Item Handling
806
	|--------------------------------------------------------------------------
807
	|
808
	| Order items are used for products, taxes, shipping, and fees within
809
	| each order.
810
	|
811
	*/
812
813
	/**
814
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
815
	 * @param string $type Order item type. Default null.
816
	 */
817
	public function remove_order_items( $type = null ) {
818
		global $wpdb;
819
		if ( ! empty( $type ) ) {
820
			$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 ) );
821
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
822
			if ( $group = $this->type_to_group( $type ) ) {
823
				$this->_items[ $group ] = null;
824
			}
825
		} else {
826
			$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() ) );
827
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
828
			$this->_items = array(
829
				'line_items'     => null,
830
				'coupon_lines'   => null,
831
				'shipping_lines' => null,
832
				'fee_lines'      => null,
833
				'tax_lines'      => null,
834
			);
835
		}
836
	}
837
838
	/**
839
	 * Convert a type to a types group.
840
	 * @param string $type
841
	 * @return string group
842
	 */
843
	protected function type_to_group( $type ) {
844
		$type_to_group = array(
845
			'line_item' => 'line_items',
846
			'tax'       => 'tax_lines',
847
			'shipping'  => 'shipping_lines',
848
			'fee'       => 'fee_lines',
849
			'coupon'    => 'coupon_lines',
850
		);
851
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
852
	}
853
854
	/**
855
	 * Return an array of items/products within this order.
856
	 * @param string|array $types Types of line items to get (array or string).
857
	 * @return Array of WC_Order_item
858
	 */
859
	public function get_items( $types = 'line_item' ) {
860
		$items = array();
861
		$types = array_filter( (array) $types );
862
863
		foreach ( $types as $type ) {
864
			if ( $group = $this->type_to_group( $type ) ) {
865
				if ( is_null( $this->_items[ $group ] ) ) {
866
					$this->_items[ $group ] = $this->get_items_from_db( $type );
867
				}
868
				// Don't use array_merge here because keys are numeric
869
				$items = $items + $this->_items[ $group ];
870
			}
871
		}
872
873
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
874
	}
875
876
	/**
877
	 * Gets items from the database by type.
878
	 * @param  string $type
879
	 * @return array
880
	 */
881
	protected function get_items_from_db( $type ) {
882
		global $wpdb;
883
884
		$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 ) ;
885
		$items         = $wpdb->get_results( $get_items_sql );
886
887
		if ( ! empty( $items ) ) {
888
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
889
		} else {
890
			$items = array();
891
		}
892
893
		return $items;
894
	}
895
896
	/**
897
	 * Return an array of fees within this order.
898
	 * @return array
899
	 */
900
	public function get_fees() {
901
		return $this->get_items( 'fee' );
902
	}
903
904
	/**
905
	 * Return an array of taxes within this order.
906
	 * @return array
907
	 */
908
	public function get_taxes() {
909
		return $this->get_items( 'tax' );
910
	}
911
912
	/**
913
	 * Return an array of shipping costs within this order.
914
	 * @return array
915
	 */
916
	public function get_shipping_methods() {
917
		return $this->get_items( 'shipping' );
918
	}
919
920
	/**
921
	 * Gets formatted shipping method title.
922
	 * @return string
923
	 */
924
	public function get_shipping_method() {
925
		$names = array();
926
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
927
			$names[] = $shipping_method->get_name();
928
		}
929
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
930
	}
931
932
	/**
933
	 * Get coupon codes only.
934
	 * @return array
935
	 */
936
	public function get_used_coupons() {
937
		$coupon_codes = array();
938
		if ( $coupons = $this->get_items( 'coupon' ) ) {
939
			foreach ( $coupons as $coupon ) {
940
				$coupon_codes[] = $coupon->get_code();
941
			}
942
		}
943
		return $coupon_codes;
944
	}
945
946
	/**
947
	 * Gets the count of order items of a certain type.
948
	 *
949
	 * @param string $item_type
950
	 * @return string
951
	 */
952
	public function get_item_count( $item_type = '' ) {
953
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
954
		$count = 0;
955
956
		foreach ( $items as $item ) {
957
			$count += $item->get_quantity();
958
		}
959
960
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
961
	}
962
963
	/**
964
	 * Get an order item object, based on it's type.
965
	 * @since  2.7.0
966
	 * @param  int $item_id
967
	 * @return WC_Order_Item
968
	 */
969
	public function get_item( $item_id ) {
970
		return WC_Order_Factory::get_order_item( $item_id );
971
	}
972
973
	/**
974
	 * Get key for where a certain item type is stored in _items.
975
	 * @since  2.7.0
976
	 * @param  $item object Order item (product, shipping, fee, coupon, tax)
977
	 * @return string
978
	 */
979
	protected function get_items_key( $item ) {
980
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
981
			return 'line_items';
982
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
983
			return 'fee_lines';
984
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
985
			return 'shipping_lines';
986
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
987
			return 'tax_lines';
988
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
989
			return 'coupon_lines';
990
		} else {
991
			return '';
992
		}
993
	}
994
995
	/**
996
	 * Remove item from the order.
997
	 * @param int $item_id
998
	 */
999
	public function remove_item( $item_id ) {
1000
		$item = $this->get_item( $item_id );
1001
1002
		if ( ! $item || ! ( $items_key = $this->get_items_key( $item ) ) ) {
1003
			return false;
1004
		}
1005
1006
		// Unset and remove later
1007
		$this->_items_to_delete[] = $item;
1008
		unset( $this->_items[ $items_key ][ $item->get_id() ] );
1009
	}
1010
1011
	/**
1012
	 * Adds an order item to this order. The order item will not persist until save.
1013
	 * @param object Order item (product, shipping, fee, coupon, tax)
1014
	 */
1015
	public function add_item( $item ) {
1016
		if ( ! $items_key = $this->get_items_key( $item ) ) {
1017
			return false;
1018
		}
1019
1020
		// Make sure existing items are loaded so we can append this new one.
1021
		if ( is_null( $this->_items[ $items_key ] ) ) {
1022
			$this->_items[ $items_key ] = $this->get_items( $item->get_type() );
1023
		}
1024
1025
		// Append new row with generated temporary ID
1026
		if ( $item->get_id() ) {
1027
			$this->_items[ $items_key ][ $item->get_id() ] = $item;
1028
		} else {
1029
			$this->_items[ $items_key ][ 'new:' . md5( json_encode( $item ) ) ] = $item;
1030
		}
1031
	}
1032
1033
	/**
1034
	 * Add a product line item to the order.
1035
	 * @param  \WC_Product $product
1036
	 * @param  int $qty
1037
	 * @param  array $args
1038
	 * @return int order item ID
1039
	 */
1040
	public function add_product( $product, $qty = 1, $args = array() ) {
1041
		$args = wp_parse_args( $args, array(
1042
			'quantity'     => $qty,
1043
			'name'         => $product ? $product->get_title() : '',
1044
			'tax_class'    => $product ? $product->get_tax_class() : '',
1045
			'product_id'   => $product ? $product->get_id() : '',
1046
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
1047
			'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...
1048
			'subtotal'     => $product ? $product->get_price_excluding_tax( $qty ) : '',
1049
			'total'        => $product ? $product->get_price_excluding_tax( $qty ) : '',
1050
			'subtotal_tax' => 0,
1051
			'total_tax'    => 0,
1052
			'taxes'        => array(
1053
				'subtotal' => array(),
1054
				'total'    => array(),
1055
			),
1056
		) );
1057
1058
		// BW compatibility with old args
1059 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...
1060
			foreach ( $args['totals'] as $key => $value ) {
1061
				if ( 'tax' === $key ) {
1062
					$args['total_tax'] = $value;
1063
				} elseif ( 'tax_data' === $key ) {
1064
					$args['taxes'] = $value;
1065
				} else {
1066
					$args[ $key ] = $value;
1067
				}
1068
			}
1069
		}
1070
1071
		$item = new WC_Order_Item_Product( $args );
1072
		$item->set_backorder_meta();
1073
		$item->set_order_id( $this->get_id() );
1074
		$item->save();
1075
		$this->add_item( $item );
1076
		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.' );
1077
		return $item->get_id();
1078
	}
1079
1080
	/**
1081
	 * Add coupon code to the order.
1082
	 * @param string $code
1083
	 * @param int $discount tax amount.
1084
	 * @param int $discount_tax amount.
1085
	 * @return int order item ID
1086
	 */
1087
	public function add_coupon( $code = array(), $discount = 0, $discount_tax = 0 ) {
1088
		$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...
1089
			'code'         => $code,
1090
			'discount'     => $discount,
1091
			'discount_tax' => $discount_tax,
1092
		) );
1093
		$item->set_order_id( $this->get_id() );
1094
		$item->save();
1095
		$this->add_item( $item );
1096
		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.' );
1097
		return $item->get_id();
1098
	}
1099
1100
	/**
1101
	 * Add a tax row to the order.
1102
	 * @param array $args
1103
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
1104
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
1105
	 * @return int order item ID
1106
	 */
1107
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1108
		if ( ! is_array( $args ) ) {
1109
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
1110
			$args = array(
1111
				'rate_id'            => $args,
1112
				'tax_total'          => $deprecated1,
1113
				'shipping_tax_total' => $deprecated2,
1114
			);
1115
		}
1116
		$args = wp_parse_args( $args, array(
1117
			'rate_id'            => '',
1118
			'tax_total'          => 0,
1119
			'shipping_tax_total' => 0,
1120
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
1121
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
1122
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
1123
		) );
1124
		$item = new WC_Order_Item_Tax( $args );
1125
		$item->set_order_id( $this->get_id() );
1126
		$item->save();
1127
		$this->add_item( $item );
1128
		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.' );
1129
		return $item->get_id();
1130
	}
1131
1132
	/**
1133
	 * Add a shipping row to the order.
1134
	 * @param WC_Shipping_Rate shipping_rate
1135
	 * @return int order item ID
1136
	 */
1137
	public function add_shipping( $shipping_rate ) {
1138
		$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...
1139
			'method_title' => $shipping_rate->label,
1140
			'method_id'    => $shipping_rate->id,
1141
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1142
			'taxes'        => $shipping_rate->taxes,
1143
			'meta_data'    => $shipping_rate->get_meta_data(),
1144
		) );
1145
		$item->set_order_id( $this->get_id() );
1146
		$item->save();
1147
		$this->add_item( $item );
1148
		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.' );
1149
		return $item->get_id();
1150
	}
1151
1152
	/**
1153
	 * Add a fee to the order.
1154
	 * Order must be saved prior to adding items.
1155
	 * @param object $fee
1156
	 * @return int updated order item ID
1157
	 */
1158
	public function add_fee( $fee ) {
1159
		$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...
1160
			'name'      => $fee->name,
1161
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1162
			'total'     => $fee->amount,
1163
			'total_tax' => $fee->tax,
1164
			'taxes'     => array(
1165
				'total' => $fee->tax_data,
1166
			),
1167
		) );
1168
		$item->set_order_id( $this->get_id() );
1169
		$item->save();
1170
		$this->add_item( $item );
1171
		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.' );
1172
		return $item->get_id();
1173
	}
1174
1175
	/*
1176
	|--------------------------------------------------------------------------
1177
	| Payment Token Handling
1178
	|--------------------------------------------------------------------------
1179
	|
1180
	| Payment tokens are hashes used to take payments by certain gateways.
1181
	|
1182
	*/
1183
1184
	/**
1185
	 * Add a payment token to an order
1186
	 *
1187
	 * @since 2.6
1188
	 * @param  WC_Payment_Token   $token     Payment token object
1189
	 * @return boolean|int The new token ID or false if it failed.
1190
	 */
1191
	public function add_payment_token( $token ) {
1192
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1193
			return false;
1194
		}
1195
1196
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1197
1198
		if ( empty ( $token_ids ) ) {
1199
			$token_ids = array();
1200
		}
1201
1202
		$token_ids[] = $token->get_id();
1203
1204
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1205
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1206
		return $token->get_id();
1207
	}
1208
1209
	/**
1210
	 * Returns a list of all payment tokens associated with the current order
1211
	 *
1212
	 * @since 2.6
1213
	 * @return array An array of payment token objects
1214
	 */
1215
	public function get_payment_tokens() {
1216
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1217
	}
1218
1219
	/*
1220
	|--------------------------------------------------------------------------
1221
	| Calculations.
1222
	|--------------------------------------------------------------------------
1223
	|
1224
	| These methods calculate order totals and taxes based on the current data.
1225
	|
1226
	*/
1227
1228
	/**
1229
	 * Calculate shipping total.
1230
	 *
1231
	 * @since 2.2
1232
	 * @return float
1233
	 */
1234
	public function calculate_shipping() {
1235
		$shipping_total = 0;
1236
1237
		foreach ( $this->get_shipping_methods() as $shipping ) {
1238
			$shipping_total += $shipping->get_total();
1239
		}
1240
1241
		$this->set_shipping_total( $shipping_total );
1242
		$this->save();
1243
1244
		return $this->get_shipping_total();
1245
	}
1246
1247
	/**
1248
	 * Get all tax classes for items in the order.
1249
	 *
1250
	 * @since 2.6.3
1251
	 * @return array
1252
	 */
1253
	public function get_items_tax_classes() {
1254
		$found_tax_classes = array();
1255
1256
		foreach ( $this->get_items() as $item ) {
1257
			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...
1258
				$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...
1259
			}
1260
		}
1261
1262
		return array_unique( $found_tax_classes );
1263
	}
1264
1265
	/**
1266
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1267
	 *
1268
	 * Will use the base country unless customer addresses are set.
1269
	 * @param $args array Added in 2.7.0 to pass things like location.
1270
	 */
1271
	public function calculate_taxes( $args = array() ) {
1272
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1273
		$args              = wp_parse_args( $args, array(
1274
			'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...
1275
			'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...
1276
			'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...
1277
			'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...
1278
		) );
1279
1280
		// Default to base
1281
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1282
			$default          = wc_get_base_location();
1283
			$args['country']  = $default['country'];
1284
			$args['state']    = $default['state'];
1285
			$args['postcode'] = '';
1286
			$args['city']     = '';
1287
		}
1288
1289
		// Calc taxes for line items
1290
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1291
			$tax_class           = $item->get_tax_class();
1292
			$tax_status          = $item->get_tax_status();
1293
1294
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1295
				$tax_rates = WC_Tax::find_rates( array(
1296
					'country'   => $args['country'],
1297
					'state'     => $args['state'],
1298
					'postcode'  => $args['postcode'],
1299
					'city'      => $args['city'],
1300
					'tax_class' => $tax_class,
1301
				) );
1302
1303
				$total = $item->get_total();
1304
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1305
1306
				if ( $item->is_type( 'line_item' ) ) {
1307
					$subtotal       = $item->get_subtotal();
1308
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1309
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1310
					$item->set_subtotal_tax( $subtotal_tax );
1311
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1312
				} else {
1313
					$item->set_taxes( array( 'total' => $taxes ) );
1314
				}
1315
				$item->save();
1316
			}
1317
		}
1318
1319
		// Calc taxes for shipping
1320
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1321
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1322
1323
			// Inherit tax class from items
1324
			if ( '' === $shipping_tax_class ) {
1325
				$tax_rates         = array();
1326
				$tax_classes       = array_merge( array( '' ), WC_Tax::get_tax_classes() );
1327
				$found_tax_classes = $this->get_items_tax_classes();
1328
1329
				foreach ( $tax_classes as $tax_class ) {
1330
					$tax_class = sanitize_title( $tax_class );
1331
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1332
						$tax_rates = WC_Tax::find_shipping_rates( array(
1333
							'country'   => $args['country'],
1334
							'state'     => $args['state'],
1335
							'postcode'  => $args['postcode'],
1336
							'city'      => $args['city'],
1337
							'tax_class' => $tax_class,
1338
						) );
1339
						break;
1340
					}
1341
				}
1342
			} else {
1343
				$tax_rates = WC_Tax::find_shipping_rates( array(
1344
					'country'   => $args['country'],
1345
					'state'     => $args['state'],
1346
					'postcode'  => $args['postcode'],
1347
					'city'      => $args['city'],
1348
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1349
				) );
1350
			}
1351
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1352
			$item->save();
1353
		}
1354
		$this->update_taxes();
1355
	}
1356
1357
	/**
1358
	 * Update tax lines for the order based on the line item taxes themselves.
1359
	 */
1360
	public function update_taxes() {
1361
		$cart_taxes     = array();
1362
		$shipping_taxes = array();
1363
1364
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1365
			$taxes = $item->get_taxes();
1366 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...
1367
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1368
			}
1369
		}
1370
1371
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1372
			$taxes = $item->get_taxes();
1373 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...
1374
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1375
			}
1376
		}
1377
1378
		// Remove old existing tax rows.
1379
		$this->remove_order_items( 'tax' );
1380
1381
		// Now merge to keep tax rows.
1382
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1383
			$this->add_tax( array(
1384
				'rate_id'            => $tax_rate_id,
1385
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1386
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1387
			) );
1388
		}
1389
1390
		// Save tax totals
1391
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1392
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1393
		$this->save();
1394
	}
1395
1396
	/**
1397
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1398
	 *
1399
	 * @since 2.2
1400
	 * @param  bool $and_taxes Calc taxes if true.
1401
	 * @return float calculated grand total.
1402
	 */
1403
	public function calculate_totals( $and_taxes = true ) {
1404
		$cart_subtotal     = 0;
1405
		$cart_total        = 0;
1406
		$fee_total         = 0;
1407
		$cart_subtotal_tax = 0;
1408
		$cart_total_tax    = 0;
1409
1410
		if ( $and_taxes && wc_tax_enabled() ) {
1411
			$this->calculate_taxes();
1412
		}
1413
1414
		// line items
1415
		foreach ( $this->get_items() as $item ) {
1416
			$cart_subtotal     += $item->get_subtotal();
1417
			$cart_total        += $item->get_total();
1418
			$cart_subtotal_tax += $item->get_subtotal_tax();
1419
			$cart_total_tax    += $item->get_total_tax();
1420
		}
1421
1422
		$this->calculate_shipping();
1423
1424
		foreach ( $this->get_fees() as $item ) {
1425
			$fee_total += $item->get_total();
1426
		}
1427
1428
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1429
1430
		$this->set_discount_total( $cart_subtotal - $cart_total );
1431
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1432
		$this->set_total( $grand_total );
1433
		$this->save();
1434
1435
		return $grand_total;
1436
	}
1437
1438
	/**
1439
	 * Get item subtotal - this is the cost before discount.
1440
	 *
1441
	 * @param object $item
1442
	 * @param bool $inc_tax (default: false).
1443
	 * @param bool $round (default: true).
1444
	 * @return float
1445
	 */
1446 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...
1447
		$subtotal = 0;
1448
1449
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1450
			if ( $inc_tax ) {
1451
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_quantity() );
1452
			} else {
1453
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_quantity() ) );
1454
			}
1455
1456
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1457
		}
1458
1459
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1460
	}
1461
1462
	/**
1463
	 * Get line subtotal - this is the cost before discount.
1464
	 *
1465
	 * @param object $item
1466
	 * @param bool $inc_tax (default: false).
1467
	 * @param bool $round (default: true).
1468
	 * @return float
1469
	 */
1470 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...
1471
		$subtotal = 0;
1472
1473
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1474
			if ( $inc_tax ) {
1475
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1476
			} else {
1477
				$subtotal = $item->get_subtotal();
1478
			}
1479
1480
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1481
		}
1482
1483
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1484
	}
1485
1486
	/**
1487
	 * Calculate item cost - useful for gateways.
1488
	 *
1489
	 * @param object $item
1490
	 * @param bool $inc_tax (default: false).
1491
	 * @param bool $round (default: true).
1492
	 * @return float
1493
	 */
1494 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...
1495
		$total = 0;
1496
1497
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1498
			if ( $inc_tax ) {
1499
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_quantity() );
1500
			} else {
1501
				$total = $item->get_total() / max( 1, $item->get_quantity() );
1502
			}
1503
1504
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1505
		}
1506
1507
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1508
	}
1509
1510
	/**
1511
	 * Calculate line total - useful for gateways.
1512
	 *
1513
	 * @param object $item
1514
	 * @param bool $inc_tax (default: false).
1515
	 * @param bool $round (default: true).
1516
	 * @return float
1517
	 */
1518 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...
1519
		$total = 0;
1520
1521
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1522
			// Check if we need to add line tax to the line total.
1523
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1524
1525
			// Check if we need to round.
1526
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1527
		}
1528
1529
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1530
	}
1531
1532
	/**
1533
	 * Get item tax - useful for gateways.
1534
	 *
1535
	 * @param mixed $item
1536
	 * @param bool $round (default: true).
1537
	 * @return float
1538
	 */
1539
	public function get_item_tax( $item, $round = true ) {
1540
		$tax = 0;
1541
1542
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1543
			$tax = $item->get_total_tax() / max( 1, $item->get_quantity() );
1544
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1545
		}
1546
1547
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1548
	}
1549
1550
	/**
1551
	 * Get line tax - useful for gateways.
1552
	 *
1553
	 * @param mixed $item
1554
	 * @return float
1555
	 */
1556
	public function get_line_tax( $item ) {
1557
		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 );
1558
	}
1559
1560
	/**
1561
	 * Gets line subtotal - formatted for display.
1562
	 *
1563
	 * @param array  $item
1564
	 * @param string $tax_display
1565
	 * @return string
1566
	 */
1567
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1568
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1569
1570
		if ( 'excl' == $tax_display ) {
1571
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1572
1573
			$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...
1574
		} else {
1575
			$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...
1576
		}
1577
1578
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1579
	}
1580
1581
	/**
1582
	 * Gets order total - formatted for display.
1583
	 * @return string
1584
	 */
1585
	public function get_formatted_order_total() {
1586
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1587
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1588
	}
1589
1590
	/**
1591
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1592
	 *
1593
	 * @param bool $compound (default: false).
1594
	 * @param string $tax_display (default: the tax_display_cart value).
1595
	 * @return string
1596
	 */
1597
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1598
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1599
		$subtotal    = 0;
1600
1601
		if ( ! $compound ) {
1602
			foreach ( $this->get_items() as $item ) {
1603
				$subtotal += $item->get_subtotal();
1604
1605
				if ( 'incl' === $tax_display ) {
1606
					$subtotal += $item->get_subtotal_tax();
1607
				}
1608
			}
1609
1610
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1611
1612
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1613
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1614
			}
1615
1616
		} else {
1617
			if ( 'incl' === $tax_display ) {
1618
				return '';
1619
			}
1620
1621
			foreach ( $this->get_items() as $item ) {
1622
				$subtotal += $item->get_subtotal();
1623
			}
1624
1625
			// Add Shipping Costs.
1626
			$subtotal += $this->get_shipping_total();
1627
1628
			// Remove non-compound taxes.
1629
			foreach ( $this->get_taxes() as $tax ) {
1630
				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...
1631
					continue;
1632
				}
1633
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1634
			}
1635
1636
			// Remove discounts.
1637
			$subtotal = $subtotal - $this->get_total_discount();
1638
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1639
		}
1640
1641
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1642
	}
1643
1644
	/**
1645
	 * Gets shipping (formatted).
1646
	 *
1647
	 * @return string
1648
	 */
1649
	public function get_shipping_to_display( $tax_display = '' ) {
1650
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1651
1652
		if ( $this->get_shipping_total() != 0 ) {
1653
1654
			if ( $tax_display == 'excl' ) {
1655
1656
				// Show shipping excluding tax.
1657
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1658
1659 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...
1660
					$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 );
1661
				}
1662
1663
			} else {
1664
1665
				// Show shipping including tax.
1666
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1667
1668 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...
1669
					$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 );
1670
				}
1671
1672
			}
1673
1674
			$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 );
1675
1676
		} elseif ( $this->get_shipping_method() ) {
1677
			$shipping = $this->get_shipping_method();
1678
		} else {
1679
			$shipping = __( 'Free!', 'woocommerce' );
1680
		}
1681
1682
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1683
	}
1684
1685
	/**
1686
	 * Get the discount amount (formatted).
1687
	 * @since  2.3.0
1688
	 * @return string
1689
	 */
1690
	public function get_discount_to_display( $tax_display = '' ) {
1691
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1692
		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 );
1693
	}
1694
1695
	/**
1696
	 * Get totals for display on pages and in emails.
1697
	 *
1698
	 * @param mixed $tax_display
1699
	 * @return array
1700
	 */
1701
	public function get_order_item_totals( $tax_display = '' ) {
1702
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1703
		$total_rows  = array();
1704
1705
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1706
			$total_rows['cart_subtotal'] = array(
1707
				'label' => __( 'Subtotal:', 'woocommerce' ),
1708
				'value'    => $subtotal,
1709
			);
1710
		}
1711
1712
		if ( $this->get_total_discount() > 0 ) {
1713
			$total_rows['discount'] = array(
1714
				'label' => __( 'Discount:', 'woocommerce' ),
1715
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1716
			);
1717
		}
1718
1719
		if ( $this->get_shipping_method() ) {
1720
			$total_rows['shipping'] = array(
1721
				'label' => __( 'Shipping:', 'woocommerce' ),
1722
				'value'    => $this->get_shipping_to_display( $tax_display ),
1723
			);
1724
		}
1725
1726
		if ( $fees = $this->get_fees() ) {
1727
			foreach ( $fees as $id => $fee ) {
1728
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1729
					continue;
1730
				}
1731
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1732
					'label' => $fee->get_name() . ':',
1733
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1734
				);
1735
			}
1736
		}
1737
1738
		// Tax for tax exclusive prices.
1739
		if ( 'excl' === $tax_display ) {
1740
1741
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1742
1743
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1744
1745
					$total_rows[ sanitize_title( $code ) ] = array(
1746
						'label' => $tax->label . ':',
1747
						'value'    => $tax->formatted_amount,
1748
					);
1749
				}
1750
1751
			} else {
1752
1753
				$total_rows['tax'] = array(
1754
					'label' => WC()->countries->tax_or_vat() . ':',
1755
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1756
				);
1757
			}
1758
		}
1759
1760
		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...
1761
			$total_rows['payment_method'] = array(
1762
				'label' => __( 'Payment Method:', 'woocommerce' ),
1763
				'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...
1764
			);
1765
		}
1766
1767
		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...
1768
			foreach ( $refunds as $id => $refund ) {
1769
				$total_rows[ 'refund_' . $id ] = array(
1770
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1771
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1772
				);
1773
			}
1774
		}
1775
1776
		$total_rows['order_total'] = array(
1777
			'label' => __( 'Total:', 'woocommerce' ),
1778
			'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...
1779
		);
1780
1781
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1782
	}
1783
1784
	/*
1785
	|--------------------------------------------------------------------------
1786
	| Conditionals
1787
	|--------------------------------------------------------------------------
1788
	|
1789
	| Checks if a condition is true or false.
1790
	|
1791
	*/
1792
1793
	/**
1794
	 * Checks the order status against a passed in status.
1795
	 *
1796
	 * @return bool
1797
	 */
1798
	public function has_status( $status ) {
1799
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1800
	}
1801
1802
	/**
1803
	 * Check whether this order has a specific shipping method or not.
1804
	 *
1805
	 * @param string $method_id
1806
	 * @return bool
1807
	 */
1808
	public function has_shipping_method( $method_id ) {
1809
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1810
			if ( $shipping_method->get_method_id() === $method_id ) {
1811
				return true;
1812
			}
1813
		}
1814
		return false;
1815
	}
1816
1817
	/**
1818
	 * Check if an order key is valid.
1819
	 *
1820
	 * @param mixed $key
1821
	 * @return bool
1822
	 */
1823
	public function key_is_valid( $key ) {
1824
		return $key === $this->get_order_key();
1825
	}
1826
1827
	/**
1828
	 * Returns true if the order contains a free product.
1829
	 * @since 2.5.0
1830
	 * @return bool
1831
	 */
1832
	public function has_free_item() {
1833
		foreach ( $this->get_items() as $item ) {
1834
			if ( ! $item->get_total() ) {
1835
				return true;
1836
			}
1837
		}
1838
		return false;
1839
	}
1840
}
1841