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