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

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

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

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

Loading history...
541
				$tax_totals[ $code ] = new stdClass();
542
				$tax_totals[ $code ]->amount = 0;
543
			}
544
545
			$tax_totals[ $code ]->id                = $key;
546
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
547
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
548
			$tax_totals[ $code ]->label             = $tax->get_label();
549
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
550
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
551
		}
552
553 View Code Duplication
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
554
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
555
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
556
		}
557
558
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
559
	}
560
561
	/*
562
	|--------------------------------------------------------------------------
563
	| Setters
564
	|--------------------------------------------------------------------------
565
	|
566
	| Functions for setting order data. These should not update anything in the
567
	| database itself and should only change what is stored in the class
568
	| object. However, for backwards compatibility pre 2.7.0 some of these
569
	| setters may handle both.
570
	|
571
	*/
572
573
	/**
574
	 * Set order ID.
575
	 * @since 2.7.0
576
	 * @param int $value
577
	 */
578
	public function set_id( $value ) {
579
		$this->_data['id'] = absint( $value );
580
	}
581
582
	/**
583
	 * Set parent order ID.
584
	 * @since 2.7.0
585
	 * @param int $value
586
	 */
587
	public function set_parent_id( $value ) {
588
		$this->_data['parent_id'] = absint( $value );
589
	}
590
591
	/**
592
<<<<<<< HEAD
593
	 * Set order status.
594
	 * @since 2.7.0
595
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
596
	 * @param array details of change
597
	 */
598
	 public function set_status( $new_status ) {
599
		 $old_status = $this->get_status();
600
		 $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
601
602
		// If the old status is unknown (e.g. draft) assume its pending for action usage.
603
		if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
604
			$old_status = 'pending';
605
		}
606
607
		 if ( in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) && $new_status !== $old_status ) {
608
			 $this->_data['status'] = 'wc-' . $new_status;
609
		 } else {
610
			$new_status = $old_status;
611
		}
612
613
		return array(
614
			'from' => $old_status,
615
			'to'   => $new_status
616
		);
617
	 }
618
619
	/**
620
	 * Set Order Type
621
	 * @param string $value
622
	 */
623
	public function set_order_type( $value ) {
624
		$this->_data['type'] = $value;
625
	}
626
627
	/**
628
	 * Set order_key.
629
	 * @param string $value Max length 20 chars.
630
	 */
631
	public function set_order_key( $value ) {
632
		$this->_data['order_key'] = substr( $value, 0, 20 );
633
	}
634
635
	/**
636
	 * Set order_version
637
	 * @param string $value
638
	 */
639
	public function set_version( $value ) {
640
		$this->_data['version'] = $value;
641
	}
642
643
	/**
644
	 * Set order_currency
645
	 * @param string $value
646
	 */
647
	public function set_currency( $value ) {
648
		$this->_data['currency'] = $value;
649
	}
650
651
	/**
652
	 * Set prices_include_tax
653
	 * @param bool $value
654
	 */
655
	public function set_prices_include_tax( $value ) {
656
		$this->_data['prices_include_tax'] = (bool) $value;
657
	}
658
659
	/**
660
	 * Set date_created
661
	 * @param string $timestamp Timestamp
662
	 */
663
	public function set_date_created( $timestamp ) {
664
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
665
	}
666
667
	/**
668
	 * Set date_modified
669
	 * @param string $timestamp
670
	 */
671
	public function set_date_modified( $timestamp ) {
672
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
673
	}
674
675
	/**
676
	 * Set customer_id
677
	 * @param int $value
678
	 */
679
	public function set_customer_id( $value ) {
680
		$this->_data['customer_id'] = absint( $value );
681
	}
682
683
	/**
684
	 * Set discount_total
685
	 * @param string $value
686
	 */
687
	public function set_discount_total( $value ) {
688
		$this->_data['discount_total'] = wc_format_decimal( $value );
689
	}
690
691
	/**
692
	 * Set discount_tax
693
	 * @param string $value
694
	 */
695
	public function set_discount_tax( $value ) {
696
		$this->_data['discount_tax'] = wc_format_decimal( $value );
697
	}
698
699
	/**
700
	 * Set shipping_total
701
	 * @param string $value
702
	 */
703
	public function set_shipping_total( $value ) {
704
		$this->_data['shipping_total'] = wc_format_decimal( $value );
705
	}
706
707
	/**
708
	 * Set shipping_tax
709
	 * @param string $value
710
	 */
711
	public function set_shipping_tax( $value ) {
712
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
713
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
714
	}
715
716
	/**
717
	 * Set cart tax
718
	 * @param string $value
719
	 */
720
	public function set_cart_tax( $value ) {
721
		$this->_data['cart_tax'] = wc_format_decimal( $value );
722
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
723
	}
724
725
	/**
726
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
727
	 * @param string $value
728
	 */
729
	protected function set_total_tax( $value ) {
730
		$this->_data['total_tax'] = wc_format_decimal( $value );
731
	}
732
733
	/**
734
	 * Set total
735
	 * @param string $value
736
	 * @param string $deprecated Function used to set different totals based on this.
737
	 */
738
	public function set_total( $value, $deprecated = '' ) {
739
		if ( $deprecated ) {
740
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
741
			return $this->legacy_set_total( $value, $deprecated );
742
		}
743
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
744
	}
745
746
	/*
747
	|--------------------------------------------------------------------------
748
	| Order Item Handling
749
	|--------------------------------------------------------------------------
750
	|
751
	| Order items are used for products, taxes, shipping, and fees within
752
	| each order.
753
	|
754
	*/
755
756
	/**
757
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
758
	 * @param string $type Order item type. Default null.
759
	 */
760
	public function remove_order_items( $type = null ) {
761
		global $wpdb;
762
		if ( ! empty( $type ) ) {
763
			$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 ) );
764
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
765
		} else {
766
			$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() ) );
767
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
768
		}
769
	}
770
771
	/**
772
	 * Return an array of items/products within this order.
773
	 * @param string|array $type Types of line items to get (array or string).
774
	 * @return Array of WC_Order_item
775
	 */
776
	public function get_items( $type = 'line_item' ) {
777
		global $wpdb;
778
		$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;";
779
		$items         = $wpdb->get_results( $get_items_sql );
780
781
		if ( ! empty( $items ) ) {
782
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
783
		} else {
784
			$items = array();
785
		}
786
787
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
788
	}
789
790
	/**
791
	 * Return an array of fees within this order.
792
	 * @return array
793
	 */
794
	public function get_fees() {
795
		return $this->get_items( 'fee' );
796
	}
797
798
	/**
799
	 * Return an array of taxes within this order.
800
	 * @return array
801
	 */
802
	public function get_taxes() {
803
		return $this->get_items( 'tax' );
804
	}
805
806
	/**
807
	 * Return an array of shipping costs within this order.
808
	 * @return array
809
	 */
810
	public function get_shipping_methods() {
811
		return $this->get_items( 'shipping' );
812
	}
813
814
	/**
815
	 * Gets formatted shipping method title.
816
	 * @return string
817
	 */
818
	public function get_shipping_method() {
819
		$names = array();
820
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
821
			$names[] = $shipping_method->get_name();
822
		}
823
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
824
	}
825
826
	/**
827
	 * Get coupon codes only.
828
	 * @return array
829
	 */
830
	public function get_used_coupons() {
831
		$coupon_codes = array();
832
		if ( $coupons = $this->get_items( 'coupon' ) ) {
833
			foreach ( $coupons as $coupon ) {
834
				$coupon_codes[] = $coupon->get_code();
835
			}
836
		}
837
		return $coupon_codes;
838
	}
839
840
	/**
841
	 * Gets the count of order items of a certain type.
842
	 *
843
	 * @param string $item_type
844
	 * @return string
845
	 */
846
	public function get_item_count( $item_type = '' ) {
847
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
848
		$count = 0;
849
850
		foreach ( $items as $item ) {
851
			$count += $item->get_qty();
852
		}
853
854
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
855
	}
856
857
	/**
858
	 * Get an order item object, based on it's type.
859
	 * @since  2.7.0
860
	 * @param  int $item_id
861
	 * @return WC_Order_Item
862
	 */
863
	public function get_item( $item_id ) {
864
		return WC_Order_Factory::get_order_item( $item_id );
865
	}
866
867
	/**
868
	 * Add a product line item to the order.
869
	 * Order must be saved prior to adding items.
870
	 * @param \WC_Product $product
871
	 * @param array $args
872
	 * @param array $deprecated qty was passed as arg 2 prior to 2.7.0
873
	 * @return int order item ID
874
	 */
875
	public function add_product( $product, $args = array(), $deprecated = array() ) {
876
		if ( ! is_array( $args ) ) {
877
			_deprecated_argument( 'qty', '2.7', 'Pass only product and args' );
878
			$qty         = $args;
879
			$args        = $deprecated;
880
			$args['qty'] = $qty;
881
		}
882
883
		$args = wp_parse_args( $args, array(
884
			'qty'          => 1,
885
			'name'         => $product ? $product->get_title() : '',
886
			'tax_class'    => $product ? $product->get_tax_class() : '',
887
			'product_id'   => $product ? $product->get_id() : '',
888
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
889
			'subtotal'     => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
890
			'total'        => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
891
			'subtotal_tax' => 0,
892
			'total_tax'    => 0,
893
			'variation'    => array(),
894
			'taxes'        => array(
895
				'subtotal' => array(),
896
				'total'    => array(),
897
			),
898
		) );
899
900
		// BW compatibility with old args
901 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...
902
			foreach ( $args['totals'] as $key => $value ) {
903
				if ( 'tax' === $key ) {
904
					$args['total_tax'] = $value;
905
				} elseif ( 'tax_data' === $key ) {
906
					$args['taxes'] = $value;
907
				} else {
908
					$args[ $key ] = $value;
909
				}
910
			}
911
		}
912
913
		$item = new WC_Order_Item_Product( $args );
914
915
		// Handle backorders
916 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...
917
			$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true );
918
		}
919
920
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
921
		$item->save();
922
923 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...
924
			_deprecated_function( 'Action: woocommerce_order_add_product', '2.7', 'Use woocommerce_new_order_item action instead.' );
925
			do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args );
926
		}
927
928
		return $item->get_id();
929
	}
930
931
	/**
932
	 * Add coupon code to the order.
933
	 * Order must be saved prior to adding items.
934
	 * @param array $args
935
	 * @param int $deprecated1 2.7.0 code, discount, tax were passed.
936
	 * @param int $deprecated2 2.7.0 code, discount, tax were passed.
937
	 * @return int order item ID
938
	 */
939
	public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
940
		if ( ! is_array( $args ) ) {
941
			_deprecated_argument( 'code', '2.7', 'Pass only an array of args' );
942
			$args = array(
943
				'code'         => $args,
944
				'discount'     => $deprecated1,
945
				'discount_tax' => $deprecated2,
946
			);
947
		}
948
949
		$args = wp_parse_args( $args, array(
950
			'code'         => '',
951
			'discount'     => 0,
952
			'discount_tax' => 0,
953
		) );
954
955
		$item = new WC_Order_Item_Coupon( $args );
956
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
957
		$item->save();
958
959
		if ( has_action( 'woocommerce_order_add_coupon' ) ) {
960
			_deprecated_function( 'Action: woocommerce_order_add_coupon', '2.7', 'Use woocommerce_new_order_item action instead.' );
961
			do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] );
962
		}
963
964
		return $item->get_id();
965
	}
966
967
	/**
968
	 * Add a tax row to the order.
969
	 * Order must be saved prior to adding items.
970
	 * @since 2.2
971
	 * @param array $args
972
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
973
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
974
	 * @return int order item ID
975
	 */
976
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
977
		if ( ! is_array( $args ) ) {
978
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
979
			$args = array(
980
				'rate_id'            => $args,
981
				'tax_total'          => $deprecated1,
982
				'shipping_tax_total' => $deprecated2,
983
			);
984
		}
985
986
		$args = wp_parse_args( $args, array(
987
			'rate_id'            => '',
988
			'tax_total'          => 0,
989
			'shipping_tax_total' => 0,
990
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
991
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
992
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
993
		) );
994
995
		$item = new WC_Order_Item_Tax( $args );
996
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
997
		$item->save();
998
999
		if ( has_action( 'woocommerce_order_add_tax' ) ) {
1000
			_deprecated_function( 'Action: woocommerce_order_add_tax', '2.7', 'Use woocommerce_new_order_item action instead.' );
1001
			do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] );
1002
		}
1003
1004
		return $item->get_id();
1005
	}
1006
1007
	/**
1008
	 * Add a shipping row to the order.
1009
	 * Order must be saved prior to adding items.
1010
	 * @param WC_Shipping_Rate shipping_rate
1011
	 * @return int order item ID
1012
	 */
1013
	public function add_shipping( $shipping_rate ) {
1014
		$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...
1015
			'method_title' => $shipping_rate->label,
1016
			'method_id'    => $shipping_rate->id,
1017
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1018
			'taxes'        => $shipping_rate->taxes,
1019
			'meta_data'    => $shipping_rate->get_meta_data(),
1020
		) );
1021
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1022
		$item->save();
1023
1024 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...
1025
			_deprecated_function( 'Action: woocommerce_order_add_shipping', '2.7', 'Use woocommerce_new_order_item action instead.' );
1026
			do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate );
1027
		}
1028
1029
		return $item->get_id();
1030
	}
1031
1032
	/**
1033
	 * Add a fee to the order.
1034
	 * Order must be saved prior to adding items.
1035
	 * @param object $fee
1036
	 * @return int updated order item ID
1037
	 */
1038
	public function add_fee( $fee ) {
1039
		$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...
1040
			'name'      => $fee->name,
1041
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1042
			'total'     => $fee->amount,
1043
			'total_tax' => $fee->tax,
1044
			'taxes'     => array(
1045
				'total' => $fee->tax_data,
1046
			),
1047
		) );
1048
1049
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1050
		$item->save();
1051
1052 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...
1053
			_deprecated_function( 'Action: woocommerce_order_add_fee', '2.7', 'Use woocommerce_new_order_item action instead.' );
1054
			do_action( 'woocommerce_order_add_fee', $this->get_id(), $item->get_id(), $fee );
1055
		}
1056
1057
		return $item->get_id();
1058
	}
1059
1060
	/**
1061
	 * Add a payment token to an order
1062
	 *
1063
	 * @since 2.6
1064
	 * @param  WC_Payment_Token   $token     Payment token object
1065
	 * @return boolean|int The new token ID or false if it failed.
1066
	 */
1067
	public function add_payment_token( $token ) {
1068
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1069
			return false;
1070
		}
1071
1072
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1073
1074
		if ( empty ( $token_ids ) ) {
1075
			$token_ids = array();
1076
		}
1077
1078
		$token_ids[] = $token->get_id();
1079
1080
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1081
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1082
		return $token->get_id();
1083
	}
1084
1085
	/**
1086
	 * Returns a list of all payment tokens associated with the current order
1087
	 *
1088
	 * @since 2.6
1089
	 * @return array An array of payment token objects
1090
	 */
1091
	public function get_payment_tokens() {
1092
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1093
	}
1094
1095
	/*
1096
	|--------------------------------------------------------------------------
1097
	| Calculations.
1098
	|--------------------------------------------------------------------------
1099
	|
1100
	| These methods calculate order totals and taxes based on the current data.
1101
	|
1102
	*/
1103
1104
	/**
1105
	 * Calculate shipping total.
1106
	 *
1107
	 * @since 2.2
1108
	 * @return float
1109
	 */
1110
	public function calculate_shipping() {
1111
		$shipping_total = 0;
1112
1113
		foreach ( $this->get_shipping_methods() as $shipping ) {
1114
			$shipping_total += $shipping->get_total();
1115
		}
1116
1117
		$this->set_shipping_total( $shipping_total );
1118
		$this->save();
1119
1120
		return $this->get_shipping_total();
1121
	}
1122
1123
	/**
1124
	 * Get all tax classes for items in the order.
1125
	 *
1126
	 * @since 2.6.3
1127
	 * @return array
1128
	 */
1129
	public function get_items_tax_classes() {
1130
		$found_tax_classes = array();
1131
1132
		foreach ( $this->get_items() as $item ) {
1133
			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...
1134
				$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...
1135
			}
1136
		}
1137
1138
		return array_unique( $found_tax_classes );
1139
	}
1140
1141
	/**
1142
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1143
	 *
1144
	 * Will use the base country unless customer addresses are set.
1145
	 * @param $args array Added in 2.7.0 to pass things like location.
1146
	 */
1147
	public function calculate_taxes( $args = array() ) {
1148
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1149
		$args              = wp_parse_args( $args, array(
1150
			'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...
1151
			'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...
1152
			'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...
1153
			'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...
1154
		) );
1155
1156
		// Default to base
1157
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1158
			$default          = wc_get_base_location();
1159
			$args['country']  = $default['country'];
1160
			$args['state']    = $default['state'];
1161
			$args['postcode'] = '';
1162
			$args['city']     = '';
1163
		}
1164
1165
		// Calc taxes for line items
1166
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1167
			$tax_class           = $item->get_tax_class();
1168
			$tax_status          = $item->get_tax_status();
1169
1170
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1171
				$tax_rates = WC_Tax::find_rates( array(
1172
					'country'   => $args['country'],
1173
					'state'     => $args['state'],
1174
					'postcode'  => $args['postcode'],
1175
					'city'      => $args['city'],
1176
					'tax_class' => $tax_class,
1177
				) );
1178
1179
				$total = $item->get_total();
1180
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1181
1182
				if ( $item->is_type( 'line_item' ) ) {
1183
					$subtotal       = $item->get_subtotal();
1184
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1185
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1186
					$item->set_subtotal_tax( $subtotal_tax );
1187
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1188
				} else {
1189
					$item->set_taxes( array( 'total' => $taxes ) );
1190
				}
1191
				$item->save();
1192
			}
1193
		}
1194
1195
		// Calc taxes for shipping
1196
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1197
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1198
1199
			// Inherit tax class from items
1200
			if ( '' === $shipping_tax_class ) {
1201
				$tax_classes       = WC_Tax::get_tax_classes();
1202
				$found_tax_classes = $this->get_items_tax_classes();
1203
1204
				foreach ( $tax_classes as $tax_class ) {
1205
					$tax_class = sanitize_title( $tax_class );
1206
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1207
						$tax_rates = WC_Tax::find_shipping_rates( array(
1208
							'country'   => $args['country'],
1209
							'state'     => $args['state'],
1210
							'postcode'  => $args['postcode'],
1211
							'city'      => $args['city'],
1212
							'tax_class' => $tax_class,
1213
						) );
1214
						break;
1215
					}
1216
				}
1217
			} else {
1218
				$tax_rates = WC_Tax::find_shipping_rates( array(
1219
					'country'   => $args['country'],
1220
					'state'     => $args['state'],
1221
					'postcode'  => $args['postcode'],
1222
					'city'      => $args['city'],
1223
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1224
				) );
1225
			}
1226
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1227
			$item->save();
1228
		}
1229
		$this->update_taxes();
1230
	}
1231
1232
	/**
1233
	 * Update tax lines for the order based on the line item taxes themselves.
1234
	 */
1235
	public function update_taxes() {
1236
		$cart_taxes     = array();
1237
		$shipping_taxes = array();
1238
1239
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1240
			$taxes = $item->get_taxes();
1241 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...
1242
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1243
			}
1244
		}
1245
1246
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1247
			$taxes = $item->get_taxes();
1248 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...
1249
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1250
			}
1251
		}
1252
1253
		// Remove old existing tax rows.
1254
		$this->remove_order_items( 'tax' );
1255
1256
		// Now merge to keep tax rows.
1257
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1258
			$this->add_tax( array(
1259
				'rate_id'            => $tax_rate_id,
1260
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1261
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1262
			) );
1263
		}
1264
1265
		// Save tax totals
1266
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1267
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1268
		$this->save();
1269
	}
1270
1271
	/**
1272
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1273
	 *
1274
	 * @since 2.2
1275
	 * @param  bool $and_taxes Calc taxes if true.
1276
	 * @return float calculated grand total.
1277
	 */
1278
	public function calculate_totals( $and_taxes = true ) {
1279
		$cart_subtotal     = 0;
1280
		$cart_total        = 0;
1281
		$fee_total         = 0;
1282
		$cart_subtotal_tax = 0;
1283
		$cart_total_tax    = 0;
1284
1285
		if ( $and_taxes && wc_tax_enabled() ) {
1286
			$this->calculate_taxes();
1287
		}
1288
1289
		// line items
1290
		foreach ( $this->get_items() as $item ) {
1291
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1292
			$cart_total        += wc_format_decimal( $item->get_total() );
1293
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1294
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1295
		}
1296
1297
		$this->calculate_shipping();
1298
1299
		foreach ( $this->get_fees() as $item ) {
1300
			$fee_total += $item->get_total();
1301
		}
1302
1303
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1304
1305
		$this->set_discount_total( $cart_subtotal - $cart_total );
1306
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1307
		$this->set_total( $grand_total );
1308
		$this->save();
1309
1310
		return $grand_total;
1311
	}
1312
1313
	/**
1314
	 * Get item subtotal - this is the cost before discount.
1315
	 *
1316
	 * @param object $item
1317
	 * @param bool $inc_tax (default: false).
1318
	 * @param bool $round (default: true).
1319
	 * @return float
1320
	 */
1321 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...
1322
		$subtotal = 0;
1323
1324
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1325
			if ( $inc_tax ) {
1326
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1327
			} else {
1328
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1329
			}
1330
1331
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1332
		}
1333
1334
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1335
	}
1336
1337
	/**
1338
	 * Get line subtotal - this is the cost before discount.
1339
	 *
1340
	 * @param object $item
1341
	 * @param bool $inc_tax (default: false).
1342
	 * @param bool $round (default: true).
1343
	 * @return float
1344
	 */
1345 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...
1346
		$subtotal = 0;
1347
1348
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1349
			if ( $inc_tax ) {
1350
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1351
			} else {
1352
				$subtotal = $item->get_subtotal();
1353
			}
1354
1355
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1356
		}
1357
1358
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1359
	}
1360
1361
	/**
1362
	 * Calculate item cost - useful for gateways.
1363
	 *
1364
	 * @param object $item
1365
	 * @param bool $inc_tax (default: false).
1366
	 * @param bool $round (default: true).
1367
	 * @return float
1368
	 */
1369 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...
1370
		$total = 0;
1371
1372
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1373
			if ( $inc_tax ) {
1374
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1375
			} else {
1376
				$total = $item->get_total() / max( 1, $item->get_qty() );
1377
			}
1378
1379
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1380
		}
1381
1382
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1383
	}
1384
1385
	/**
1386
	 * Calculate line total - useful for gateways.
1387
	 *
1388
	 * @param object $item
1389
	 * @param bool $inc_tax (default: false).
1390
	 * @param bool $round (default: true).
1391
	 * @return float
1392
	 */
1393 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...
1394
		$total = 0;
1395
1396
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1397
			// Check if we need to add line tax to the line total.
1398
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1399
1400
			// Check if we need to round.
1401
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1402
		}
1403
1404
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1405
	}
1406
1407
	/**
1408
	 * Get item tax - useful for gateways.
1409
	 *
1410
	 * @param mixed $item
1411
	 * @param bool $round (default: true).
1412
	 * @return float
1413
	 */
1414
	public function get_item_tax( $item, $round = true ) {
1415
		$tax = 0;
1416
1417
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1418
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1419
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1420
		}
1421
1422
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1423
	}
1424
1425
	/**
1426
	 * Get line tax - useful for gateways.
1427
	 *
1428
	 * @param mixed $item
1429
	 * @return float
1430
	 */
1431
	public function get_line_tax( $item ) {
1432
		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 );
1433
	}
1434
1435
	/**
1436
	 * Gets line subtotal - formatted for display.
1437
	 *
1438
	 * @param array  $item
1439
	 * @param string $tax_display
1440
	 * @return string
1441
	 */
1442
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1443
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1444
1445
		if ( 'excl' == $tax_display ) {
1446
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1447
1448
			$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...
1449
		} else {
1450
			$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...
1451
		}
1452
1453
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1454
	}
1455
1456
	/**
1457
	 * Gets order total - formatted for display.
1458
	 * @return string
1459
	 */
1460
	public function get_formatted_order_total() {
1461
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1462
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1463
	}
1464
1465
	/**
1466
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1467
	 *
1468
	 * @param bool $compound (default: false).
1469
	 * @param string $tax_display (default: the tax_display_cart value).
1470
	 * @return string
1471
	 */
1472
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1473
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1474
		$subtotal    = 0;
1475
1476
		if ( ! $compound ) {
1477
			foreach ( $this->get_items() as $item ) {
1478
				$subtotal += $item->get_subtotal();
1479
1480
				if ( 'incl' === $tax_display ) {
1481
					$subtotal += $item->get_subtotal_tax();
1482
				}
1483
			}
1484
1485
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1486
1487
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1488
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1489
			}
1490
1491
		} else {
1492
			if ( 'incl' === $tax_display ) {
1493
				return '';
1494
			}
1495
1496
			foreach ( $this->get_items() as $item ) {
1497
				$subtotal += $item->get_subtotal();
1498
			}
1499
1500
			// Add Shipping Costs.
1501
			$subtotal += $this->get_shipping_total();
1502
1503
			// Remove non-compound taxes.
1504
			foreach ( $this->get_taxes() as $tax ) {
1505
				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...
1506
					continue;
1507
				}
1508
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1509
			}
1510
1511
			// Remove discounts.
1512
			$subtotal = $subtotal - $this->get_total_discount();
1513
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1514
		}
1515
1516
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1517
	}
1518
1519
	/**
1520
	 * Gets shipping (formatted).
1521
	 *
1522
	 * @return string
1523
	 */
1524
	public function get_shipping_to_display( $tax_display = '' ) {
1525
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1526
1527
		if ( $this->get_shipping_total() != 0 ) {
1528
1529
			if ( $tax_display == 'excl' ) {
1530
1531
				// Show shipping excluding tax.
1532
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1533
1534 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...
1535
					$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 );
1536
				}
1537
1538
			} else {
1539
1540
				// Show shipping including tax.
1541
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1542
1543 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...
1544
					$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 );
1545
				}
1546
1547
			}
1548
1549
			$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 );
1550
1551
		} elseif ( $this->get_shipping_method() ) {
1552
			$shipping = $this->get_shipping_method();
1553
		} else {
1554
			$shipping = __( 'Free!', 'woocommerce' );
1555
		}
1556
1557
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1558
	}
1559
1560
	/**
1561
	 * Get the discount amount (formatted).
1562
	 * @since  2.3.0
1563
	 * @return string
1564
	 */
1565
	public function get_discount_to_display( $tax_display = '' ) {
1566
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1567
		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 );
1568
	}
1569
1570
	/**
1571
	 * Get totals for display on pages and in emails.
1572
	 *
1573
	 * @param mixed $tax_display
1574
	 * @return array
1575
	 */
1576
	public function get_order_item_totals( $tax_display = '' ) {
1577
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1578
		$total_rows  = array();
1579
1580
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1581
			$total_rows['cart_subtotal'] = array(
1582
				'label' => __( 'Subtotal:', 'woocommerce' ),
1583
				'value'    => $subtotal,
1584
			);
1585
		}
1586
1587
		if ( $this->get_total_discount() > 0 ) {
1588
			$total_rows['discount'] = array(
1589
				'label' => __( 'Discount:', 'woocommerce' ),
1590
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1591
			);
1592
		}
1593
1594
		if ( $this->get_shipping_method() ) {
1595
			$total_rows['shipping'] = array(
1596
				'label' => __( 'Shipping:', 'woocommerce' ),
1597
				'value'    => $this->get_shipping_to_display( $tax_display ),
1598
			);
1599
		}
1600
1601
		if ( $fees = $this->get_fees() ) {
1602
			foreach ( $fees as $id => $fee ) {
1603
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1604
					continue;
1605
				}
1606
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1607
					'label' => $fee->get_name() . ':',
1608
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1609
				);
1610
			}
1611
		}
1612
1613
		// Tax for tax exclusive prices.
1614
		if ( 'excl' === $tax_display ) {
1615
1616
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1617
1618
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1619
1620
					$total_rows[ sanitize_title( $code ) ] = array(
1621
						'label' => $tax->label . ':',
1622
						'value'    => $tax->formatted_amount,
1623
					);
1624
				}
1625
1626
			} else {
1627
1628
				$total_rows['tax'] = array(
1629
					'label' => WC()->countries->tax_or_vat() . ':',
1630
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1631
				);
1632
			}
1633
		}
1634
1635
		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...
1636
			$total_rows['payment_method'] = array(
1637
				'label' => __( 'Payment Method:', 'woocommerce' ),
1638
				'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...
1639
			);
1640
		}
1641
1642
		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...
1643
			foreach ( $refunds as $id => $refund ) {
1644
				$total_rows[ 'refund_' . $id ] = array(
1645
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1646
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1647
				);
1648
			}
1649
		}
1650
1651
		$total_rows['order_total'] = array(
1652
			'label' => __( 'Total:', 'woocommerce' ),
1653
			'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...
1654
		);
1655
1656
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1657
	}
1658
1659
	/*
1660
	|--------------------------------------------------------------------------
1661
	| Conditionals
1662
	|--------------------------------------------------------------------------
1663
	|
1664
	| Checks if a condition is true or false.
1665
	|
1666
	*/
1667
1668
	/**
1669
	 * Checks the order status against a passed in status.
1670
	 *
1671
	 * @return bool
1672
	 */
1673
	public function has_status( $status ) {
1674
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1675
	}
1676
1677
	/**
1678
	 * Check whether this order has a specific shipping method or not.
1679
	 *
1680
	 * @param string $method_id
1681
	 * @return bool
1682
	 */
1683
	public function has_shipping_method( $method_id ) {
1684
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1685
			if ( $shipping_method->get_method_id() === $method_id ) {
1686
				return true;
1687
			}
1688
		}
1689
		return false;
1690
	}
1691
1692
	/**
1693
	 * Check if an order key is valid.
1694
	 *
1695
	 * @param mixed $key
1696
	 * @return bool
1697
	 */
1698
	public function key_is_valid( $key ) {
1699
		return $key === $this->get_order_key();
1700
	}
1701
1702
	/**
1703
	 * Returns true if the order contains a free product.
1704
	 * @since 2.5.0
1705
	 * @return bool
1706
	 */
1707
	public function has_free_item() {
1708
		foreach ( $this->get_items() as $item ) {
1709
			if ( ! $item->get_total() ) {
1710
				return true;
1711
			}
1712
		}
1713
		return false;
1714
	}
1715
}
1716