Completed
Pull Request — master (#11208)
by Mike
09:06
created

WC_Abstract_Order::calculate_totals()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
923
			_deprecated_function( 'Action: woocommerce_order_add_product', '2.7', 'Use woocommerce_new_order_item action instead.' );
924
			do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args );
925
		}
926
927
		return $item->get_id();
928
	}
929
930
	/**
931
	 * Add coupon code to the order.
932
	 * Order must be saved prior to adding items.
933
	 * @param array $args
934
	 * @param int $deprecated1 2.7.0 code, discount, tax were passed.
935
	 * @param int $deprecated2 2.7.0 code, discount, tax were passed.
936
	 * @return int order item ID
937
	 */
938
	public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
939
		if ( ! is_array( $args ) ) {
940
			_deprecated_argument( 'code', '2.7', 'Pass only an array of args' );
941
			$args = array(
942
				'code'         => $args,
943
				'discount'     => $deprecated1,
944
				'discount_tax' => $deprecated2,
945
			);
946
		}
947
948
		$args = wp_parse_args( $args, array(
949
			'code'         => '',
950
			'discount'     => 0,
951
			'discount_tax' => 0,
952
		) );
953
954
		$item = new WC_Order_Item_Coupon( $args );
955
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
956
		$item->save();
957
958
		if ( has_action( 'woocommerce_order_add_coupon' ) ) {
959
			_deprecated_function( 'Action: woocommerce_order_add_coupon', '2.7', 'Use woocommerce_new_order_item action instead.' );
960
			do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] );
961
		}
962
963
		return $item->get_id();
964
	}
965
966
	/**
967
	 * Add a tax row to the order.
968
	 * Order must be saved prior to adding items.
969
	 * @since 2.2
970
	 * @param array $args
971
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
972
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
973
	 * @return int order item ID
974
	 */
975
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
976
		if ( ! is_array( $args ) ) {
977
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
978
			$args = array(
979
				'rate_id'            => $args,
980
				'tax_total'          => $deprecated1,
981
				'shipping_tax_total' => $deprecated2,
982
			);
983
		}
984
985
		$args = wp_parse_args( $args, array(
986
			'rate_id'            => '',
987
			'tax_total'          => 0,
988
			'shipping_tax_total' => 0,
989
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
990
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
991
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
992
		) );
993
994
		$item = new WC_Order_Item_Tax( $args );
995
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
996
		$item->save();
997
998
		if ( has_action( 'woocommerce_order_add_tax' ) ) {
999
			_deprecated_function( 'Action: woocommerce_order_add_tax', '2.7', 'Use woocommerce_new_order_item action instead.' );
1000
			do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] );
1001
		}
1002
1003
		return $item->get_id();
1004
	}
1005
1006
	/**
1007
	 * Add a shipping row to the order.
1008
	 * Order must be saved prior to adding items.
1009
	 * @param WC_Shipping_Rate shipping_rate
1010
	 * @return int order item ID
1011
	 */
1012
	public function add_shipping( $shipping_rate ) {
1013
		$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...
1014
			'method_title' => $shipping_rate->label,
1015
			'method_id'    => $shipping_rate->id,
1016
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1017
			'taxes'        => $shipping_rate->taxes,
1018
			'meta_data'    => $shipping_rate->get_meta_data(),
1019
		) );
1020
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1021
		$item->save();
1022
1023 View Code Duplication
		if ( has_action( 'woocommerce_order_add_shipping' ) ) {
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...
1024
			_deprecated_function( 'Action: woocommerce_order_add_shipping', '2.7', 'Use woocommerce_new_order_item action instead.' );
1025
			do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate );
1026
		}
1027
1028
		return $item->get_id();
1029
	}
1030
1031
	/**
1032
	 * Add a fee to the order.
1033
	 * Order must be saved prior to adding items.
1034
	 * @param object $fee
1035
	 * @return int updated order item ID
1036
	 */
1037
	public function add_fee( $fee ) {
1038
		$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...
1039
			'name'      => $fee->name,
1040
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1041
			'total'     => $fee->amount,
1042
			'total_tax' => $fee->tax,
1043
			'taxes'     => array(
1044
				'total' => $fee->tax_data,
1045
			),
1046
		) );
1047
1048
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1049
		$item->save();
1050
1051 View Code Duplication
		if ( has_action( 'woocommerce_order_add_fee' ) ) {
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...
1052
			_deprecated_function( 'Action: woocommerce_order_add_fee', '2.7', 'Use woocommerce_new_order_item action instead.' );
1053
			do_action( 'woocommerce_order_add_fee', $this->get_id(), $item->get_id(), $fee );
1054
		}
1055
1056
		return $item->get_id();
1057
	}
1058
1059
	/**
1060
	 * Add a payment token to an order
1061
	 *
1062
	 * @since 2.6
1063
	 * @param  WC_Payment_Token   $token     Payment token object
1064
	 * @return boolean|int The new token ID or false if it failed.
1065
	 */
1066
	public function add_payment_token( $token ) {
1067
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1068
			return false;
1069
		}
1070
1071
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1072
1073
		if ( empty ( $token_ids ) ) {
1074
			$token_ids = array();
1075
		}
1076
1077
		$token_ids[] = $token->get_id();
1078
1079
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1080
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1081
		return $token->get_id();
1082
	}
1083
1084
	/**
1085
	 * Returns a list of all payment tokens associated with the current order
1086
	 *
1087
	 * @since 2.6
1088
	 * @return array An array of payment token objects
1089
	 */
1090
	public function get_payment_tokens() {
1091
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1092
	}
1093
1094
	/*
1095
	|--------------------------------------------------------------------------
1096
	| Calculations.
1097
	|--------------------------------------------------------------------------
1098
	|
1099
	| These methods calculate order totals and taxes based on the current data.
1100
	|
1101
	*/
1102
1103
	/**
1104
	 * Calculate shipping total.
1105
	 *
1106
	 * @since 2.2
1107
	 * @return float
1108
	 */
1109
	public function calculate_shipping() {
1110
		$shipping_total = 0;
1111
1112
		foreach ( $this->get_shipping_methods() as $shipping ) {
1113
			$shipping_total += $shipping->get_total();
1114
		}
1115
1116
		$this->set_shipping_total( $shipping_total );
1117
		$this->save();
1118
1119
		return $this->get_shipping_total();
1120
	}
1121
1122
	/**
1123
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1124
	 *
1125
	 * Will use the base country unless customer addresses are set.
1126
	 * @param $args array Added in 2.7.0 to pass things like location.
1127
	 */
1128
	public function calculate_taxes( $args = array() ) {
1129
		$found_tax_classes = array();
1130
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1131
		$args              = wp_parse_args( $args, array(
1132
			'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...
1133
			'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...
1134
			'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...
1135
			'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...
1136
		) );
1137
1138
		// Default to base
1139
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1140
			$default          = wc_get_base_location();
1141
			$args['country']  = $default['country'];
1142
			$args['state']    = $default['state'];
1143
			$args['postcode'] = '';
1144
			$args['city']     = '';
1145
		}
1146
1147
		// Calc taxes for line items
1148
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1149
			$tax_class           = $item->get_tax_class();
1150
			$tax_status          = $item->get_tax_status();
1151
			$found_tax_classes[] = $tax_class;
1152
1153
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1154
				$tax_rates = WC_Tax::find_rates( array(
1155
					'country'   => $args['country'],
1156
					'state'     => $args['state'],
1157
					'postcode'  => $args['postcode'],
1158
					'city'      => $args['city'],
1159
					'tax_class' => $tax_class,
1160
				) );
1161
1162
				$total = $item->get_total();
1163
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1164
1165
				if ( $item->is_type( 'line_item' ) ) {
1166
					$subtotal       = $item->get_subtotal();
1167
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1168
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1169
					$item->set_subtotal_tax( $subtotal_tax );
1170
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1171
				} else {
1172
					$item->set_taxes( array( 'total' => $taxes ) );
1173
				}
1174
				$item->save();
1175
			}
1176
		}
1177
1178
		// Calc taxes for shipping
1179
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1180
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1181
1182
			// Inherit tax class from items
1183
			if ( '' === $shipping_tax_class ) {
1184
				$tax_classes = WC_Tax::get_tax_classes();
1185
1186
				foreach ( $tax_classes as $tax_class ) {
1187
					$tax_class = sanitize_title( $tax_class );
1188
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1189
						$tax_rates = WC_Tax::find_shipping_rates( array(
1190
							'country'   => $args['country'],
1191
							'state'     => $args['state'],
1192
							'postcode'  => $args['postcode'],
1193
							'city'      => $args['city'],
1194
							'tax_class' => $tax_class,
1195
						) );
1196
						break;
1197
					}
1198
				}
1199
			} else {
1200
				$tax_rates = WC_Tax::find_shipping_rates( array(
1201
					'country'   => $args['country'],
1202
					'state'     => $args['state'],
1203
					'postcode'  => $args['postcode'],
1204
					'city'      => $args['city'],
1205
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1206
				) );
1207
			}
1208
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1209
			$item->save();
1210
		}
1211
		$this->update_taxes();
1212
	}
1213
1214
	/**
1215
	 * Update tax lines for the order based on the line item taxes themselves.
1216
	 */
1217
	public function update_taxes() {
1218
		$cart_taxes     = array();
1219
		$shipping_taxes = array();
1220
1221
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1222
			$taxes = $item->get_taxes();
1223 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...
1224
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1225
			}
1226
		}
1227
1228
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1229
			$taxes = $item->get_taxes();
1230 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...
1231
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1232
			}
1233
		}
1234
1235
		// Remove old existing tax rows.
1236
		$this->remove_order_items( 'tax' );
1237
1238
		// Now merge to keep tax rows.
1239
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1240
			$this->add_tax( array(
1241
				'rate_id'            => $tax_rate_id,
1242
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1243
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1244
			) );
1245
		}
1246
1247
		// Save tax totals
1248
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1249
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1250
		$this->save();
1251
	}
1252
1253
	/**
1254
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1255
	 *
1256
	 * @since 2.2
1257
	 * @param  bool $and_taxes Calc taxes if true.
1258
	 * @return float calculated grand total.
1259
	 */
1260
	public function calculate_totals( $and_taxes = true ) {
1261
		$cart_subtotal     = 0;
1262
		$cart_total        = 0;
1263
		$fee_total         = 0;
1264
		$cart_subtotal_tax = 0;
1265
		$cart_total_tax    = 0;
1266
1267
		if ( $and_taxes && wc_tax_enabled() ) {
1268
			$this->calculate_taxes();
1269
		}
1270
1271
		// line items
1272
		foreach ( $this->get_items() as $item ) {
1273
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1274
			$cart_total        += wc_format_decimal( $item->get_total() );
1275
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1276
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1277
		}
1278
1279
		$this->calculate_shipping();
1280
1281
		foreach ( $this->get_fees() as $item ) {
1282
			$fee_total += $item->get_total();
1283
		}
1284
1285
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1286
1287
		$this->set_discount_total( $cart_subtotal - $cart_total );
1288
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1289
		$this->set_total( $grand_total );
1290
		$this->save();
1291
1292
		return $grand_total;
1293
	}
1294
1295
	/**
1296
	 * Get item subtotal - this is the cost before discount.
1297
	 *
1298
	 * @param object $item
1299
	 * @param bool $inc_tax (default: false).
1300
	 * @param bool $round (default: true).
1301
	 * @return float
1302
	 */
1303 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...
1304
		$subtotal = 0;
1305
1306
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1307
			if ( $inc_tax ) {
1308
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1309
			} else {
1310
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1311
			}
1312
1313
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1314
		}
1315
1316
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1317
	}
1318
1319
	/**
1320
	 * Get line subtotal - this is the cost before discount.
1321
	 *
1322
	 * @param object $item
1323
	 * @param bool $inc_tax (default: false).
1324
	 * @param bool $round (default: true).
1325
	 * @return float
1326
	 */
1327 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...
1328
		$subtotal = 0;
1329
1330
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1331
			if ( $inc_tax ) {
1332
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1333
			} else {
1334
				$subtotal = $item->get_subtotal();
1335
			}
1336
1337
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1338
		}
1339
1340
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1341
	}
1342
1343
	/**
1344
	 * Calculate item cost - useful for gateways.
1345
	 *
1346
	 * @param object $item
1347
	 * @param bool $inc_tax (default: false).
1348
	 * @param bool $round (default: true).
1349
	 * @return float
1350
	 */
1351 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...
1352
		$total = 0;
1353
1354
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1355
			if ( $inc_tax ) {
1356
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1357
			} else {
1358
				$total = $item->get_total() / max( 1, $item->get_qty() );
1359
			}
1360
1361
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1362
		}
1363
1364
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1365
	}
1366
1367
	/**
1368
	 * Calculate line total - useful for gateways.
1369
	 *
1370
	 * @param object $item
1371
	 * @param bool $inc_tax (default: false).
1372
	 * @param bool $round (default: true).
1373
	 * @return float
1374
	 */
1375 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...
1376
		$total = 0;
1377
1378
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1379
			// Check if we need to add line tax to the line total.
1380
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1381
1382
			// Check if we need to round.
1383
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1384
		}
1385
1386
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1387
	}
1388
1389
	/**
1390
	 * Get item tax - useful for gateways.
1391
	 *
1392
	 * @param mixed $item
1393
	 * @param bool $round (default: true).
1394
	 * @return float
1395
	 */
1396
	public function get_item_tax( $item, $round = true ) {
1397
		$tax = 0;
1398
1399
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1400
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1401
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1402
		}
1403
1404
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1405
	}
1406
1407
	/**
1408
	 * Get line tax - useful for gateways.
1409
	 *
1410
	 * @param mixed $item
1411
	 * @return float
1412
	 */
1413
	public function get_line_tax( $item ) {
1414
		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 );
1415
	}
1416
1417
	/**
1418
	 * Gets line subtotal - formatted for display.
1419
	 *
1420
	 * @param array  $item
1421
	 * @param string $tax_display
1422
	 * @return string
1423
	 */
1424
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1425
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1426
1427
		if ( 'excl' == $tax_display ) {
1428
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1429
1430
			$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...
1431
		} else {
1432
			$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...
1433
		}
1434
1435
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1436
	}
1437
1438
	/**
1439
	 * Gets order total - formatted for display.
1440
	 * @return string
1441
	 */
1442
	public function get_formatted_order_total() {
1443
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1444
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1445
	}
1446
1447
	/**
1448
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1449
	 *
1450
	 * @param bool $compound (default: false).
1451
	 * @param string $tax_display (default: the tax_display_cart value).
1452
	 * @return string
1453
	 */
1454
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1455
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1456
		$subtotal    = 0;
1457
1458
		if ( ! $compound ) {
1459
			foreach ( $this->get_items() as $item ) {
1460
				$subtotal += $item->get_subtotal();
1461
1462
				if ( 'incl' === $tax_display ) {
1463
					$subtotal += $item->get_subtotal_tax();
1464
				}
1465
			}
1466
1467
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1468
1469
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1470
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1471
			}
1472
1473
		} else {
1474
			if ( 'incl' === $tax_display ) {
1475
				return '';
1476
			}
1477
1478
			foreach ( $this->get_items() as $item ) {
1479
				$subtotal += $item->get_subtotal();
1480
			}
1481
1482
			// Add Shipping Costs.
1483
			$subtotal += $this->get_shipping_total();
1484
1485
			// Remove non-compound taxes.
1486
			foreach ( $this->get_taxes() as $tax ) {
1487
				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...
1488
					continue;
1489
				}
1490
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1491
			}
1492
1493
			// Remove discounts.
1494
			$subtotal = $subtotal - $this->get_total_discount();
1495
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1496
		}
1497
1498
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1499
	}
1500
1501
	/**
1502
	 * Gets shipping (formatted).
1503
	 *
1504
	 * @return string
1505
	 */
1506
	public function get_shipping_to_display( $tax_display = '' ) {
1507
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1508
1509
		if ( $this->get_shipping_total() != 0 ) {
1510
1511
			if ( $tax_display == 'excl' ) {
1512
1513
				// Show shipping excluding tax.
1514
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1515
1516 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...
1517
					$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 );
1518
				}
1519
1520
			} else {
1521
1522
				// Show shipping including tax.
1523
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1524
1525 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...
1526
					$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 );
1527
				}
1528
1529
			}
1530
1531
			$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 );
1532
1533
		} elseif ( $this->get_shipping_method() ) {
1534
			$shipping = $this->get_shipping_method();
1535
		} else {
1536
			$shipping = __( 'Free!', 'woocommerce' );
1537
		}
1538
1539
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1540
	}
1541
1542
	/**
1543
	 * Get the discount amount (formatted).
1544
	 * @since  2.3.0
1545
	 * @return string
1546
	 */
1547
	public function get_discount_to_display( $tax_display = '' ) {
1548
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1549
		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 );
1550
	}
1551
1552
	/**
1553
	 * Get totals for display on pages and in emails.
1554
	 *
1555
	 * @param mixed $tax_display
1556
	 * @return array
1557
	 */
1558
	public function get_order_item_totals( $tax_display = '' ) {
1559
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1560
		$total_rows  = array();
1561
1562
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1563
			$total_rows['cart_subtotal'] = array(
1564
				'label' => __( 'Subtotal:', 'woocommerce' ),
1565
				'value'    => $subtotal,
1566
			);
1567
		}
1568
1569
		if ( $this->get_total_discount() > 0 ) {
1570
			$total_rows['discount'] = array(
1571
				'label' => __( 'Discount:', 'woocommerce' ),
1572
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1573
			);
1574
		}
1575
1576
		if ( $this->get_shipping_method() ) {
1577
			$total_rows['shipping'] = array(
1578
				'label' => __( 'Shipping:', 'woocommerce' ),
1579
				'value'    => $this->get_shipping_to_display( $tax_display ),
1580
			);
1581
		}
1582
1583
		if ( $fees = $this->get_fees() ) {
1584
			foreach ( $fees as $id => $fee ) {
1585
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1586
					continue;
1587
				}
1588
				$total_rows[ 'fee_' . $fee->get_order_item_id() ] = array(
1589
					'label' => $fee->get_name() . ':',
1590
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1591
				);
1592
			}
1593
		}
1594
1595
		// Tax for tax exclusive prices.
1596
		if ( 'excl' === $tax_display ) {
1597
1598
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1599
1600
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1601
1602
					$total_rows[ sanitize_title( $code ) ] = array(
1603
						'label' => $tax->label . ':',
1604
						'value'    => $tax->formatted_amount,
1605
					);
1606
				}
1607
1608
			} else {
1609
1610
				$total_rows['tax'] = array(
1611
					'label' => WC()->countries->tax_or_vat() . ':',
1612
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1613
				);
1614
			}
1615
		}
1616
1617
		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...
1618
			$total_rows['payment_method'] = array(
1619
				'label' => __( 'Payment Method:', 'woocommerce' ),
1620
				'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...
1621
			);
1622
		}
1623
1624
		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...
1625
			foreach ( $refunds as $id => $refund ) {
1626
				$total_rows[ 'refund_' . $id ] = array(
1627
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1628
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1629
				);
1630
			}
1631
		}
1632
1633
		$total_rows['order_total'] = array(
1634
			'label' => __( 'Total:', 'woocommerce' ),
1635
			'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...
1636
		);
1637
1638
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1639
	}
1640
1641
	/*
1642
	|--------------------------------------------------------------------------
1643
	| Conditionals
1644
	|--------------------------------------------------------------------------
1645
	|
1646
	| Checks if a condition is true or false.
1647
	|
1648
	*/
1649
1650
	/**
1651
	 * Checks the order status against a passed in status.
1652
	 *
1653
	 * @return bool
1654
	 */
1655
	public function has_status( $status ) {
1656
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1657
	}
1658
1659
	/**
1660
	 * Check whether this order has a specific shipping method or not.
1661
	 *
1662
	 * @param string $method_id
1663
	 * @return bool
1664
	 */
1665
	public function has_shipping_method( $method_id ) {
1666
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1667
			if ( $shipping_method->get_method_id() === $method_id ) {
1668
				return true;
1669
			}
1670
		}
1671
		return false;
1672
	}
1673
1674
	/**
1675
	 * Check if an order key is valid.
1676
	 *
1677
	 * @param mixed $key
1678
	 * @return bool
1679
	 */
1680
	public function key_is_valid( $key ) {
1681
		return $key === $this->get_order_key();
1682
	}
1683
1684
	/**
1685
	 * Returns true if the order contains a free product.
1686
	 * @since 2.5.0
1687
	 * @return bool
1688
	 */
1689
	public function has_free_item() {
1690
		foreach ( $this->get_items() as $item ) {
1691
			if ( ! $item->get_total() ) {
1692
				return true;
1693
			}
1694
		}
1695
		return false;
1696
	}
1697
}
1698