Completed
Push — master ( 1c592b...3f9388 )
by Mike
15:22
created

WC_Abstract_Order::get_discount_tax()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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
175
		// Map standard post data
176
		$this->set_id( $post_object->ID );
177
		$this->set_parent_id( $post_object->post_parent );
178
		$this->set_date_created( $post_object->post_date );
179
		$this->set_date_modified( $post_object->post_modified );
180
		$this->set_status( $post_object->post_status );
181
		$this->set_order_type( $post_object->post_type );
182
		$this->set_customer_id( get_post_meta( $this->get_id(), '_customer_user', true ) );
183
		$this->set_order_key( get_post_meta( $this->get_id(), '_order_key', true ) );
184
		$this->set_currency( get_post_meta( $this->get_id(), '_order_currency', true ) );
185
		$this->set_discount_total( get_post_meta( $this->get_id(), '_cart_discount', true ) );
186
		$this->set_discount_tax( get_post_meta( $this->get_id(), '_cart_discount_tax', true ) );
187
		$this->set_shipping_total( get_post_meta( $this->get_id(), '_order_shipping', true ) );
188
		$this->set_shipping_tax( get_post_meta( $this->get_id(), '_order_shipping_tax', true ) );
189
		$this->set_cart_tax( get_post_meta( $this->get_id(), '_order_tax', true ) );
190
		$this->set_total( get_post_meta( $this->get_id(), '_order_total', true ) );
191
192
		// Orders store the state of prices including tax when created.
193
		$this->set_prices_include_tax( metadata_exists( 'post', $this->get_id(), '_prices_include_tax' ) ? 'yes' === get_post_meta( $this->get_id(), '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
194
195
		// Load meta data
196
		$this->read_meta_data();
197
	}
198
199
	/**
200
	 * Post meta update wrapper. Sets or deletes based on value.
201
	 * @since 2.7.0
202
	 * @return bool Was it changed?
203
	 */
204
	protected function update_post_meta( $key, $value ) {
205
		if ( '' !== $value ) {
206
			return update_post_meta( $this->get_id(), $key, $value );
207
		} else {
208
			return delete_post_meta( $this->get_id(), $key );
209
		}
210
	}
211
212
	/**
213
	 * Update data in the database.
214
	 * @since 2.7.0
215
	 */
216
	public function update() {
217
		global $wpdb;
218
219
		$order_id = $this->get_id();
220
221
		$wpdb->update(
222
			$wpdb->posts,
223
			array(
224
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
225
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
226
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
227
				'post_parent'   => $this->get_parent_id(),
228
			),
229
			array(
230
				'ID' => $order_id
231
			)
232
		);
233
234
		// Update meta data
235
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
236
		$this->update_post_meta( '_order_currency', $this->get_currency() );
237
		$this->update_post_meta( '_order_key', $this->get_order_key() );
238
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
239
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
240
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
241
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
242
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
243
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
244
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
245
		$this->save_meta_data();
246
	}
247
248
	/**
249
	 * Delete data from the database.
250
	 * @since 2.7.0
251
	 */
252
	public function delete() {
253
		wp_delete_post( $this->get_id() );
254
	}
255
256
	/**
257
	 * Save data to the database.
258
	 * @since 2.7.0
259
	 * @return int order ID
260
	 */
261
	public function save() {
262
		$this->set_version( WC_VERSION );
263
264
		if ( ! $this->get_id() ) {
265
			$this->create();
266
		} else {
267
			$this->update();
268
		}
269
270
		clean_post_cache( $this->get_id() );
271
		wc_delete_shop_order_transients( $this->get_id() );
272
273
		return $this->get_id();
274
	}
275
276
	/*
277
	|--------------------------------------------------------------------------
278
	| Getters
279
	|--------------------------------------------------------------------------
280
	|
281
	| Methods for getting data from the order object.
282
	|
283
	*/
284
285
	/**
286
	 * Get all class data in array format.
287
	 * @since 2.7.0
288
	 * @return array
289
	 */
290
	public function get_data() {
291
		return array_merge(
292
			$this->_data,
293
			array(
294
				'meta_data'      => $this->get_meta_data(),
295
				'line_items'     => $this->get_items( 'line_item' ),
296
				'tax_lines'      => $this->get_items( 'tax' ),
297
				'shipping_lines' => $this->get_items( 'shipping' ),
298
				'fee_lines'      => $this->get_items( 'fee' ),
299
				'coupon_lines'   => $this->get_items( 'coupon' ),
300
			)
301
		);
302
	}
303
304
	/**
305
	 * Get order ID.
306
	 * @since 2.7.0
307
	 * @return integer
308
	 */
309
	public function get_id() {
310
		return $this->_data['id'];
311
	}
312
313
	/**
314
	 * Get parent order ID.
315
	 * @since 2.7.0
316
	 * @return integer
317
	 */
318
	public function get_parent_id() {
319
		return $this->_data['parent_id'];
320
	}
321
322
	/**
323
	 * get_order_number function.
324
	 *
325
	 * Gets the order number for display (by default, order ID).
326
	 *
327
	 * @return string
328
	 */
329
	public function get_order_number() {
330
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
331
	}
332
333
	/**
334
	 * Get order key.
335
	 * @since 2.7.0
336
	 * @return string
337
	 */
338
	public function get_order_key() {
339
		return $this->_data['order_key'];
340
	}
341
342
	/**
343
	 * Gets order currency.
344
	 * @return string
345
	 */
346
	public function get_currency() {
347
		return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this );
348
	}
349
350
	/**
351
	 * Get order_version
352
	 * @return string
353
	 */
354
	public function get_version() {
355
		return $this->_data['version'];
356
	}
357
358
	/**
359
	 * Get prices_include_tax
360
	 * @return bool
361
	 */
362
	public function get_prices_include_tax() {
363
		return $this->_data['prices_include_tax'];
364
	}
365
366
	/**
367
	 * Get Order Type
368
	 * @return string
369
	 */
370
	public function get_order_type() {
371
		return $this->_data['type'];
372
	}
373
374
	/**
375
	 * Get date_created
376
	 * @return int
377
	 */
378
	public function get_date_created() {
379
		return $this->_data['date_created'];
380
	}
381
382
	/**
383
	 * Get date_modified
384
	 * @return int
385
	 */
386
	public function get_date_modified() {
387
		return $this->_data['date_modified'];
388
	}
389
390
	/**
391
	 * Get customer_id
392
	 * @return int
393
	 */
394
	public function get_customer_id() {
395
		return $this->_data['customer_id'];
396
	}
397
398
	/**
399
	 * Return the order statuses without wc- internal prefix.
400
	 * @return string
401
	 */
402
	public function get_status() {
403
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this );
404
	}
405
406
	/**
407
	 * Alias for get_customer_id().
408
	 * @since  2.2
409
	 * @return int
410
	 */
411
	public function get_user_id() {
412
		return $this->get_customer_id();
413
	}
414
415
	/**
416
	 * Get the user associated with the order. False for guests.
417
	 *
418
	 * @since  2.2
419
	 * @return WP_User|false
420
	 */
421
	public function get_user() {
422
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
423
	}
424
425
	/**
426
	 * Get discount_total
427
	 * @param bool $raw Gets raw unfiltered value.
428
	 * @return string
429
	 */
430
	public function get_discount_total( $raw = false ) {
431
		$value = wc_format_decimal( $this->_data['discount_total'] );
432
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
433
	}
434
435
	/**
436
	 * Get discount_tax
437
	 * @param bool $raw Gets raw unfiltered value.
438
	 * @return string
439
	 */
440
	public function get_discount_tax( $raw = false ) {
441
		$value = wc_format_decimal( $this->_data['discount_tax'] );
442
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
443
	}
444
445
	/**
446
	 * Get shipping_total
447
	 * @param bool $raw Gets raw unfiltered value.
448
	 * @return string
449
	 */
450
	public function get_shipping_total( $raw = false ) {
451
		$value = wc_format_decimal( $this->_data['shipping_total'] );
452
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
453
	}
454
455
	/**
456
	 * Get shipping_tax.
457
	 * @param bool $raw Gets raw unfiltered value.
458
	 * @return string
459
	 */
460
	public function get_shipping_tax( $raw = false ) {
461
		$value = wc_format_decimal( $this->_data['shipping_tax'] );
462
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
463
	}
464
465
	/**
466
	 * Gets cart tax amount.
467
	 * @param bool $raw Gets raw unfiltered value.
468
	 * @return float
469
	 */
470
	public function get_cart_tax( $raw = false ) {
471
		$value = wc_format_decimal( $this->_data['cart_tax'] );
472
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
473
	}
474
475
	/**
476
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
477
	 * @param bool $raw Gets raw unfiltered value.
478
	 * @return float
479
	 */
480
	public function get_total( $raw = false ) {
481
		$value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() );
482
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this );
483
	}
484
485
	/**
486
	 * Get total tax amount. Alias for get_order_tax().
487
	 *
488
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
489
	 * these values being modified and then saved back to the DB. There are
490
	 * other, later hooks available to change totals on display. e.g.
491
	 * woocommerce_get_order_item_totals.
492
	 * @param bool $raw Gets raw unfiltered value.
493
	 * @return float
494
	 */
495
	public function get_total_tax( $raw = false ) {
496
		$value = wc_format_decimal( $this->_data['total_tax'] );
497
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
498
	}
499
500
	/**
501
	 * Gets the total discount amount.
502
	 * @param  bool $ex_tax Show discount excl any tax.
503
	 * @return float
504
	 */
505
	public function get_total_discount( $ex_tax = true ) {
506
		if ( $ex_tax ) {
507
			$total_discount = $this->get_discount_total();
508
		} else {
509
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
510
		}
511
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
512
	}
513
514
	/**
515
	 * Gets order subtotal.
516
	 * @return float
517
	 */
518
	public function get_subtotal() {
519
		$subtotal = 0;
520
521
		foreach ( $this->get_items() as $item ) {
522
			$subtotal += $item->get_subtotal();
523
		}
524
525
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
526
	}
527
528
	/**
529
	 * Get taxes, merged by code, formatted ready for output.
530
	 *
531
	 * @return array
532
	 */
533
	public function get_tax_totals() {
534
		$tax_totals = array();
535
536
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
537
			$code = $tax->get_rate_code();
538
539 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...
540
				$tax_totals[ $code ] = new stdClass();
541
				$tax_totals[ $code ]->amount = 0;
542
			}
543
544
			$tax_totals[ $code ]->id                = $key;
545
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
546
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
547
			$tax_totals[ $code ]->label             = $tax->get_label();
548
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
549
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
550
		}
551
552 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...
553
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
554
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
555
		}
556
557
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
558
	}
559
560
	/*
561
	|--------------------------------------------------------------------------
562
	| Setters
563
	|--------------------------------------------------------------------------
564
	|
565
	| Functions for setting order data. These should not update anything in the
566
	| database itself and should only change what is stored in the class
567
	| object. However, for backwards compatibility pre 2.7.0 some of these
568
	| setters may handle both.
569
	|
570
	*/
571
572
	/**
573
	 * Set order ID.
574
	 * @since 2.7.0
575
	 * @param int $value
576
	 */
577
	public function set_id( $value ) {
578
		$this->_data['id'] = absint( $value );
579
	}
580
581
	/**
582
	 * Set parent order ID.
583
	 * @since 2.7.0
584
	 * @param int $value
585
	 */
586
	public function set_parent_id( $value ) {
587
		$this->_data['parent_id'] = absint( $value );
588
	}
589
590
	/**
591
<<<<<<< HEAD
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
	 * Get all tax classes for items in the order.
1124
	 *
1125
	 * @since 2.6.3
1126
	 * @return array
1127
	 */
1128
	public function get_items_tax_classes() {
1129
		$found_tax_classes = array();
1130
1131
		foreach ( $this->get_items() as $item ) {
1132
			if ( $_product = $this->get_product_from_item( $item ) ) {
0 ignored issues
show
Deprecated Code introduced by
The method WC_Abstract_Legacy_Order::get_product_from_item() has been deprecated with message: Add deprecation notices in future release. Replaced with $item->get_product()

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

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

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

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

Loading history...
1134
			}
1135
		}
1136
1137
		return array_unique( $found_tax_classes );
1138
	}
1139
1140
	/**
1141
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1142
	 *
1143
	 * Will use the base country unless customer addresses are set.
1144
	 * @param $args array Added in 2.7.0 to pass things like location.
1145
	 */
1146
	public function calculate_taxes( $args = array() ) {
1147
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1148
		$args              = wp_parse_args( $args, array(
1149
			'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...
1150
			'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...
1151
			'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...
1152
			'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...
1153
		) );
1154
1155
		// Default to base
1156
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1157
			$default          = wc_get_base_location();
1158
			$args['country']  = $default['country'];
1159
			$args['state']    = $default['state'];
1160
			$args['postcode'] = '';
1161
			$args['city']     = '';
1162
		}
1163
1164
		// Calc taxes for line items
1165
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1166
			$tax_class           = $item->get_tax_class();
1167
			$tax_status          = $item->get_tax_status();
1168
1169
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1170
				$tax_rates = WC_Tax::find_rates( array(
1171
					'country'   => $args['country'],
1172
					'state'     => $args['state'],
1173
					'postcode'  => $args['postcode'],
1174
					'city'      => $args['city'],
1175
					'tax_class' => $tax_class,
1176
				) );
1177
1178
				$total = $item->get_total();
1179
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1180
1181
				if ( $item->is_type( 'line_item' ) ) {
1182
					$subtotal       = $item->get_subtotal();
1183
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1184
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1185
					$item->set_subtotal_tax( $subtotal_tax );
1186
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1187
				} else {
1188
					$item->set_taxes( array( 'total' => $taxes ) );
1189
				}
1190
				$item->save();
1191
			}
1192
		}
1193
1194
		// Calc taxes for shipping
1195
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1196
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1197
1198
			// Inherit tax class from items
1199
			if ( '' === $shipping_tax_class ) {
1200
				$tax_classes       = WC_Tax::get_tax_classes();
1201
				$found_tax_classes = $this->get_items_tax_classes();
1202
1203
				foreach ( $tax_classes as $tax_class ) {
1204
					$tax_class = sanitize_title( $tax_class );
1205
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1206
						$tax_rates = WC_Tax::find_shipping_rates( array(
1207
							'country'   => $args['country'],
1208
							'state'     => $args['state'],
1209
							'postcode'  => $args['postcode'],
1210
							'city'      => $args['city'],
1211
							'tax_class' => $tax_class,
1212
						) );
1213
						break;
1214
					}
1215
				}
1216
			} else {
1217
				$tax_rates = WC_Tax::find_shipping_rates( array(
1218
					'country'   => $args['country'],
1219
					'state'     => $args['state'],
1220
					'postcode'  => $args['postcode'],
1221
					'city'      => $args['city'],
1222
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1223
				) );
1224
			}
1225
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1226
			$item->save();
1227
		}
1228
		$this->update_taxes();
1229
	}
1230
1231
	/**
1232
	 * Update tax lines for the order based on the line item taxes themselves.
1233
	 */
1234
	public function update_taxes() {
1235
		$cart_taxes     = array();
1236
		$shipping_taxes = array();
1237
1238
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1239
			$taxes = $item->get_taxes();
1240 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...
1241
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1242
			}
1243
		}
1244
1245
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1246
			$taxes = $item->get_taxes();
1247 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...
1248
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1249
			}
1250
		}
1251
1252
		// Remove old existing tax rows.
1253
		$this->remove_order_items( 'tax' );
1254
1255
		// Now merge to keep tax rows.
1256
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1257
			$this->add_tax( array(
1258
				'rate_id'            => $tax_rate_id,
1259
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1260
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1261
			) );
1262
		}
1263
1264
		// Save tax totals
1265
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1266
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1267
		$this->save();
1268
	}
1269
1270
	/**
1271
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1272
	 *
1273
	 * @since 2.2
1274
	 * @param  bool $and_taxes Calc taxes if true.
1275
	 * @return float calculated grand total.
1276
	 */
1277
	public function calculate_totals( $and_taxes = true ) {
1278
		$cart_subtotal     = 0;
1279
		$cart_total        = 0;
1280
		$fee_total         = 0;
1281
		$cart_subtotal_tax = 0;
1282
		$cart_total_tax    = 0;
1283
1284
		if ( $and_taxes && wc_tax_enabled() ) {
1285
			$this->calculate_taxes();
1286
		}
1287
1288
		// line items
1289
		foreach ( $this->get_items() as $item ) {
1290
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1291
			$cart_total        += wc_format_decimal( $item->get_total() );
1292
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1293
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1294
		}
1295
1296
		$this->calculate_shipping();
1297
1298
		foreach ( $this->get_fees() as $item ) {
1299
			$fee_total += $item->get_total();
1300
		}
1301
1302
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1303
1304
		$this->set_discount_total( $cart_subtotal - $cart_total );
1305
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1306
		$this->set_total( $grand_total );
1307
		$this->save();
1308
1309
		return $grand_total;
1310
	}
1311
1312
	/**
1313
	 * Get item subtotal - this is the cost before discount.
1314
	 *
1315
	 * @param object $item
1316
	 * @param bool $inc_tax (default: false).
1317
	 * @param bool $round (default: true).
1318
	 * @return float
1319
	 */
1320 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...
1321
		$subtotal = 0;
1322
1323
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1324
			if ( $inc_tax ) {
1325
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1326
			} else {
1327
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1328
			}
1329
1330
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1331
		}
1332
1333
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1334
	}
1335
1336
	/**
1337
	 * Get line subtotal - this is the cost before discount.
1338
	 *
1339
	 * @param object $item
1340
	 * @param bool $inc_tax (default: false).
1341
	 * @param bool $round (default: true).
1342
	 * @return float
1343
	 */
1344 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...
1345
		$subtotal = 0;
1346
1347
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1348
			if ( $inc_tax ) {
1349
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1350
			} else {
1351
				$subtotal = $item->get_subtotal();
1352
			}
1353
1354
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1355
		}
1356
1357
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1358
	}
1359
1360
	/**
1361
	 * Calculate item cost - useful for gateways.
1362
	 *
1363
	 * @param object $item
1364
	 * @param bool $inc_tax (default: false).
1365
	 * @param bool $round (default: true).
1366
	 * @return float
1367
	 */
1368 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...
1369
		$total = 0;
1370
1371
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1372
			if ( $inc_tax ) {
1373
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1374
			} else {
1375
				$total = $item->get_total() / max( 1, $item->get_qty() );
1376
			}
1377
1378
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1379
		}
1380
1381
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1382
	}
1383
1384
	/**
1385
	 * Calculate line total - useful for gateways.
1386
	 *
1387
	 * @param object $item
1388
	 * @param bool $inc_tax (default: false).
1389
	 * @param bool $round (default: true).
1390
	 * @return float
1391
	 */
1392 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...
1393
		$total = 0;
1394
1395
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1396
			// Check if we need to add line tax to the line total.
1397
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1398
1399
			// Check if we need to round.
1400
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1401
		}
1402
1403
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1404
	}
1405
1406
	/**
1407
	 * Get item tax - useful for gateways.
1408
	 *
1409
	 * @param mixed $item
1410
	 * @param bool $round (default: true).
1411
	 * @return float
1412
	 */
1413
	public function get_item_tax( $item, $round = true ) {
1414
		$tax = 0;
1415
1416
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1417
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1418
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1419
		}
1420
1421
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1422
	}
1423
1424
	/**
1425
	 * Get line tax - useful for gateways.
1426
	 *
1427
	 * @param mixed $item
1428
	 * @return float
1429
	 */
1430
	public function get_line_tax( $item ) {
1431
		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 );
1432
	}
1433
1434
	/**
1435
	 * Gets line subtotal - formatted for display.
1436
	 *
1437
	 * @param array  $item
1438
	 * @param string $tax_display
1439
	 * @return string
1440
	 */
1441
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1442
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1443
1444
		if ( 'excl' == $tax_display ) {
1445
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1446
1447
			$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...
1448
		} else {
1449
			$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...
1450
		}
1451
1452
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1453
	}
1454
1455
	/**
1456
	 * Gets order total - formatted for display.
1457
	 * @return string
1458
	 */
1459
	public function get_formatted_order_total() {
1460
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1461
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1462
	}
1463
1464
	/**
1465
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1466
	 *
1467
	 * @param bool $compound (default: false).
1468
	 * @param string $tax_display (default: the tax_display_cart value).
1469
	 * @return string
1470
	 */
1471
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1472
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1473
		$subtotal    = 0;
1474
1475
		if ( ! $compound ) {
1476
			foreach ( $this->get_items() as $item ) {
1477
				$subtotal += $item->get_subtotal();
1478
1479
				if ( 'incl' === $tax_display ) {
1480
					$subtotal += $item->get_subtotal_tax();
1481
				}
1482
			}
1483
1484
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1485
1486
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1487
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1488
			}
1489
1490
		} else {
1491
			if ( 'incl' === $tax_display ) {
1492
				return '';
1493
			}
1494
1495
			foreach ( $this->get_items() as $item ) {
1496
				$subtotal += $item->get_subtotal();
1497
			}
1498
1499
			// Add Shipping Costs.
1500
			$subtotal += $this->get_shipping_total();
1501
1502
			// Remove non-compound taxes.
1503
			foreach ( $this->get_taxes() as $tax ) {
1504
				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...
1505
					continue;
1506
				}
1507
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1508
			}
1509
1510
			// Remove discounts.
1511
			$subtotal = $subtotal - $this->get_total_discount();
1512
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1513
		}
1514
1515
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1516
	}
1517
1518
	/**
1519
	 * Gets shipping (formatted).
1520
	 *
1521
	 * @return string
1522
	 */
1523
	public function get_shipping_to_display( $tax_display = '' ) {
1524
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1525
1526
		if ( $this->get_shipping_total() != 0 ) {
1527
1528
			if ( $tax_display == 'excl' ) {
1529
1530
				// Show shipping excluding tax.
1531
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1532
1533 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...
1534
					$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 );
1535
				}
1536
1537
			} else {
1538
1539
				// Show shipping including tax.
1540
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1541
1542 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...
1543
					$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 );
1544
				}
1545
1546
			}
1547
1548
			$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 );
1549
1550
		} elseif ( $this->get_shipping_method() ) {
1551
			$shipping = $this->get_shipping_method();
1552
		} else {
1553
			$shipping = __( 'Free!', 'woocommerce' );
1554
		}
1555
1556
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1557
	}
1558
1559
	/**
1560
	 * Get the discount amount (formatted).
1561
	 * @since  2.3.0
1562
	 * @return string
1563
	 */
1564
	public function get_discount_to_display( $tax_display = '' ) {
1565
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1566
		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 );
1567
	}
1568
1569
	/**
1570
	 * Get totals for display on pages and in emails.
1571
	 *
1572
	 * @param mixed $tax_display
1573
	 * @return array
1574
	 */
1575
	public function get_order_item_totals( $tax_display = '' ) {
1576
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1577
		$total_rows  = array();
1578
1579
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1580
			$total_rows['cart_subtotal'] = array(
1581
				'label' => __( 'Subtotal:', 'woocommerce' ),
1582
				'value'    => $subtotal,
1583
			);
1584
		}
1585
1586
		if ( $this->get_total_discount() > 0 ) {
1587
			$total_rows['discount'] = array(
1588
				'label' => __( 'Discount:', 'woocommerce' ),
1589
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1590
			);
1591
		}
1592
1593
		if ( $this->get_shipping_method() ) {
1594
			$total_rows['shipping'] = array(
1595
				'label' => __( 'Shipping:', 'woocommerce' ),
1596
				'value'    => $this->get_shipping_to_display( $tax_display ),
1597
			);
1598
		}
1599
1600
		if ( $fees = $this->get_fees() ) {
1601
			foreach ( $fees as $id => $fee ) {
1602
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1603
					continue;
1604
				}
1605
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1606
					'label' => $fee->get_name() . ':',
1607
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1608
				);
1609
			}
1610
		}
1611
1612
		// Tax for tax exclusive prices.
1613
		if ( 'excl' === $tax_display ) {
1614
1615
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1616
1617
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1618
1619
					$total_rows[ sanitize_title( $code ) ] = array(
1620
						'label' => $tax->label . ':',
1621
						'value'    => $tax->formatted_amount,
1622
					);
1623
				}
1624
1625
			} else {
1626
1627
				$total_rows['tax'] = array(
1628
					'label' => WC()->countries->tax_or_vat() . ':',
1629
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1630
				);
1631
			}
1632
		}
1633
1634
		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...
1635
			$total_rows['payment_method'] = array(
1636
				'label' => __( 'Payment Method:', 'woocommerce' ),
1637
				'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...
1638
			);
1639
		}
1640
1641
		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...
1642
			foreach ( $refunds as $id => $refund ) {
1643
				$total_rows[ 'refund_' . $id ] = array(
1644
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1645
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1646
				);
1647
			}
1648
		}
1649
1650
		$total_rows['order_total'] = array(
1651
			'label' => __( 'Total:', 'woocommerce' ),
1652
			'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...
1653
		);
1654
1655
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1656
	}
1657
1658
	/*
1659
	|--------------------------------------------------------------------------
1660
	| Conditionals
1661
	|--------------------------------------------------------------------------
1662
	|
1663
	| Checks if a condition is true or false.
1664
	|
1665
	*/
1666
1667
	/**
1668
	 * Checks the order status against a passed in status.
1669
	 *
1670
	 * @return bool
1671
	 */
1672
	public function has_status( $status ) {
1673
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1674
	}
1675
1676
	/**
1677
	 * Check whether this order has a specific shipping method or not.
1678
	 *
1679
	 * @param string $method_id
1680
	 * @return bool
1681
	 */
1682
	public function has_shipping_method( $method_id ) {
1683
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1684
			if ( $shipping_method->get_method_id() === $method_id ) {
1685
				return true;
1686
			}
1687
		}
1688
		return false;
1689
	}
1690
1691
	/**
1692
	 * Check if an order key is valid.
1693
	 *
1694
	 * @param mixed $key
1695
	 * @return bool
1696
	 */
1697
	public function key_is_valid( $key ) {
1698
		return $key === $this->get_order_key();
1699
	}
1700
1701
	/**
1702
	 * Returns true if the order contains a free product.
1703
	 * @since 2.5.0
1704
	 * @return bool
1705
	 */
1706
	public function has_free_item() {
1707
		foreach ( $this->get_items() as $item ) {
1708
			if ( ! $item->get_total() ) {
1709
				return true;
1710
			}
1711
		}
1712
		return false;
1713
	}
1714
}
1715