Completed
Push — master ( 09e43c...a8e479 )
by Mike
11:28
created

WC_Abstract_Order::has_free_item()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

could be turned into

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

This is much more concise to read.

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

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

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

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

Loading history...
642
		}
643
		$this->_data['currency'] = $value;
644
	}
645
646
	/**
647
	 * Set prices_include_tax
648
	 * @param bool $value
649
	 */
650
	public function set_prices_include_tax( $value ) {
651
		$this->_data['prices_include_tax'] = (bool) $value;
652
	}
653
654
	/**
655
	 * Set date_created
656
	 * @param string $timestamp Timestamp
657
	 */
658
	public function set_date_created( $timestamp ) {
659
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
660
	}
661
662
	/**
663
	 * Set date_modified
664
	 * @param string $timestamp
665
	 */
666
	public function set_date_modified( $timestamp ) {
667
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
668
	}
669
670
	/**
671
	 * Set discount_total
672
	 * @param string $value
673
	 */
674
	public function set_discount_total( $value ) {
675
		$this->_data['discount_total'] = wc_format_decimal( $value );
676
	}
677
678
	/**
679
	 * Set discount_tax
680
	 * @param string $value
681
	 */
682
	public function set_discount_tax( $value ) {
683
		$this->_data['discount_tax'] = wc_format_decimal( $value );
684
	}
685
686
	/**
687
	 * Set shipping_total
688
	 * @param string $value
689
	 */
690
	public function set_shipping_total( $value ) {
691
		$this->_data['shipping_total'] = wc_format_decimal( $value );
692
	}
693
694
	/**
695
	 * Set shipping_tax
696
	 * @param string $value
697
	 */
698
	public function set_shipping_tax( $value ) {
699
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
700
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
701
	}
702
703
	/**
704
	 * Set cart tax
705
	 * @param string $value
706
	 */
707
	public function set_cart_tax( $value ) {
708
		$this->_data['cart_tax'] = wc_format_decimal( $value );
709
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
710
	}
711
712
	/**
713
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
714
	 * @param string $value
715
	 */
716
	protected function set_total_tax( $value ) {
717
		$this->_data['total_tax'] = wc_format_decimal( $value );
718
	}
719
720
	/**
721
	 * Set total
722
	 * @param string $value
723
	 * @param string $deprecated Function used to set different totals based on this.
724
	 */
725
	public function set_total( $value, $deprecated = '' ) {
726
		if ( $deprecated ) {
727
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
728
			return $this->legacy_set_total( $value, $deprecated );
729
		}
730
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
731
	}
732
733
	/*
734
	|--------------------------------------------------------------------------
735
	| Order Item Handling
736
	|--------------------------------------------------------------------------
737
	|
738
	| Order items are used for products, taxes, shipping, and fees within
739
	| each order.
740
	|
741
	*/
742
743
	/**
744
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
745
	 * @param string $type Order item type. Default null.
746
	 */
747
	public function remove_order_items( $type = null ) {
748
		global $wpdb;
749
		if ( ! empty( $type ) ) {
750
			$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 ) );
751
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
752
			if ( $group = $this->type_to_group( $type ) ) {
753
				$this->_items[ $group ] = null;
754
			}
755
		} else {
756
			$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() ) );
757
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
758
			$this->_items = array(
759
				'line_items'     => null,
760
				'coupon_lines'   => null,
761
				'shipping_lines' => null,
762
				'fee_lines'      => null,
763
				'tax_lines'      => null,
764
			);
765
		}
766
	}
767
768
	/**
769
	 * Convert a type to a types group.
770
	 * @param string $type
771
	 * @return string group
772
	 */
773
	protected function type_to_group( $type ) {
774
		$type_to_group = array(
775
			'line_item' => 'line_items',
776
			'tax'       => 'tax_lines',
777
			'shipping'  => 'shipping_lines',
778
			'fee'       => 'fee_lines',
779
			'coupon'    => 'coupon_lines',
780
		);
781
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
782
	}
783
784
	/**
785
	 * Return an array of items/products within this order.
786
	 * @param string|array $types Types of line items to get (array or string).
787
	 * @return Array of WC_Order_item
788
	 */
789
	public function get_items( $types = 'line_item' ) {
790
		$items = array();
791
		$types = array_filter( (array) $types );
792
793
		foreach ( $types as $type ) {
794
			if ( $group = $this->type_to_group( $type ) ) {
795
				if ( is_null( $this->_items[ $group ] ) ) {
796
					$this->_items[ $group ] = $this->get_items_from_db( $type );
797
				}
798
				// Don't use array_merge here because keys are numeric
799
				$items = $items + $this->_items[ $group ];
800
			}
801
		}
802
803
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
804
	}
805
806
	/**
807
	 * Gets items from the database by type.
808
	 * @param  string $type
809
	 * @return array
810
	 */
811
	protected function get_items_from_db( $type ) {
812
		global $wpdb;
813
814
		$get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s ORDER BY order_item_id;", $this->get_id(), $type ) ;
815
		$items         = $wpdb->get_results( $get_items_sql );
816
817
		if ( ! empty( $items ) ) {
818
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
819
		} else {
820
			$items = array();
821
		}
822
823
		return $items;
824
	}
825
826
	/**
827
	 * Return an array of fees within this order.
828
	 * @return array
829
	 */
830
	public function get_fees() {
831
		return $this->get_items( 'fee' );
832
	}
833
834
	/**
835
	 * Return an array of taxes within this order.
836
	 * @return array
837
	 */
838
	public function get_taxes() {
839
		return $this->get_items( 'tax' );
840
	}
841
842
	/**
843
	 * Return an array of shipping costs within this order.
844
	 * @return array
845
	 */
846
	public function get_shipping_methods() {
847
		return $this->get_items( 'shipping' );
848
	}
849
850
	/**
851
	 * Gets formatted shipping method title.
852
	 * @return string
853
	 */
854
	public function get_shipping_method() {
855
		$names = array();
856
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
857
			$names[] = $shipping_method->get_name();
858
		}
859
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
860
	}
861
862
	/**
863
	 * Get coupon codes only.
864
	 * @return array
865
	 */
866
	public function get_used_coupons() {
867
		$coupon_codes = array();
868
		if ( $coupons = $this->get_items( 'coupon' ) ) {
869
			foreach ( $coupons as $coupon ) {
870
				$coupon_codes[] = $coupon->get_code();
871
			}
872
		}
873
		return $coupon_codes;
874
	}
875
876
	/**
877
	 * Gets the count of order items of a certain type.
878
	 *
879
	 * @param string $item_type
880
	 * @return string
881
	 */
882
	public function get_item_count( $item_type = '' ) {
883
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
884
		$count = 0;
885
886
		foreach ( $items as $item ) {
887
			$count += $item->get_quantity();
888
		}
889
890
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
891
	}
892
893
	/**
894
	 * Get an order item object, based on it's type.
895
	 * @since  2.7.0
896
	 * @param  int $item_id
897
	 * @return WC_Order_Item
898
	 */
899
	public function get_item( $item_id ) {
900
		return WC_Order_Factory::get_order_item( $item_id );
901
	}
902
903
	/**
904
	 * Get key for where a certain item type is stored in _items.
905
	 * @since  2.7.0
906
	 * @param  $item object Order item (product, shipping, fee, coupon, tax)
907
	 * @return string
908
	 */
909
	protected function get_items_key( $item ) {
910
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
911
			return 'line_items';
912
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
913
			return 'fee_lines';
914
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
915
			return 'shipping_lines';
916
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
917
			return 'tax_lines';
918
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
919
			return 'coupon_lines';
920
		} else {
921
			return '';
922
		}
923
	}
924
925
	/**
926
	 * Remove item from the order.
927
	 * @param int $item_id
928
	 */
929
	public function remove_item( $item_id ) {
930
		$item = $this->get_item( $item_id );
931
932
		if ( ! $item || ! ( $items_key = $this->get_items_key( $item ) ) ) {
933
			return false;
934
		}
935
936
		// Unset and remove later
937
		$this->_items_to_delete[] = $item;
938
		unset( $this->_items[ $items_key ][ $item->get_id() ] );
939
	}
940
941
	/**
942
	 * Adds an order item to this order. The order item will not persist until save.
943
	 * @since 2.7.0
944
	 * @param WC_Order_Item Order item object (product, shipping, fee, coupon, tax)
945
	 */
946
	public function add_item( $item ) {
947
		if ( ! $items_key = $this->get_items_key( $item ) ) {
948
			return false;
949
		}
950
951
		// Make sure existing items are loaded so we can append this new one.
952
		if ( is_null( $this->_items[ $items_key ] ) ) {
953
			$this->_items[ $items_key ] = $this->get_items( $item->get_type() );
954
		}
955
956
		// Append new row with generated temporary ID
957
		if ( $item->get_id() ) {
958
			$this->_items[ $items_key ][ $item->get_id() ] = $item;
959
		} else {
960
			$this->_items[ $items_key ][ 'new:' . sizeof( $this->_items[ $items_key ] ) ] = $item;
961
		}
962
	}
963
964
	/**
965
	 * Add a product line item to the order. This is the only line item type with
966
	 * it's own method because it saves looking up order amounts (costs are added up for you).
967
	 * @param  \WC_Product $product
968
	 * @param  int $qty
969
	 * @param  array $args
970
	 * @return int order item ID
971
	 */
972
	public function add_product( $product, $qty = 1, $args = array() ) {
973
		if ( $product ) {
974
			$default_args = array(
975
				'name'         => $product->get_title(),
976
				'tax_class'    => $product->get_tax_class(),
977
				'product_id'   => $product->get_id(),
978
				'variation_id' => isset( $product->variation_id ) ? $product->variation_id : 0,
979
				'variation'    => isset( $product->variation_id ) ? $product->get_variation_attributes() : array(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_attributes() does only exist in the following sub-classes of WC_Product: WC_Product_Variable, WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

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