Completed
Push — master ( 3755b2...b7facc )
by Gerhard
04:13 queued 03:34
created

WC_Abstract_Order::get_coupons()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Abstract Order
4
 *
5
 * Handles generic order data and database interaction which is extended by both
6
 * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders).
7
 *
8
 * @class       WC_Abstract_Order
9
 * @version     3.0.0
10
 * @package     WooCommerce/Classes
11
 */
12
13
defined( 'ABSPATH' ) || exit;
14
15
require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php';
16
17
/**
18
 * WC_Abstract_Order class.
19
 */
20
abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
21
22
	/**
23
	 * Order Data array. This is the core order data exposed in APIs since 3.0.0.
24
	 *
25
	 * Notes: cart_tax = cart_tax is the new name for the legacy 'order_tax'
26
	 * which is the tax for items only, not shipping.
27
	 *
28
	 * @since 3.0.0
29
	 * @var array
30
	 */
31
	protected $data = array(
32
		'parent_id'          => 0,
33
		'status'             => '',
34
		'currency'           => '',
35
		'version'            => '',
36
		'prices_include_tax' => false,
37
		'date_created'       => null,
38
		'date_modified'      => null,
39
		'discount_total'     => 0,
40
		'discount_tax'       => 0,
41
		'shipping_total'     => 0,
42
		'shipping_tax'       => 0,
43
		'cart_tax'           => 0,
44
		'total'              => 0,
45
		'total_tax'          => 0,
46
	);
47
48
	/**
49
	 * Order items will be stored here, sometimes before they persist in the DB.
50
	 *
51
	 * @since 3.0.0
52
	 * @var array
53
	 */
54
	protected $items = array();
55
56
	/**
57
	 * Order items that need deleting are stored here.
58
	 *
59
	 * @since 3.0.0
60
	 * @var array
61
	 */
62
	protected $items_to_delete = array();
63
64
	/**
65
	 * Stores meta in cache for future reads.
66
	 *
67
	 * A group must be set to to enable caching.
68
	 *
69
	 * @var string
70
	 */
71
	protected $cache_group = 'orders';
72
73
	/**
74
	 * Which data store to load.
75
	 *
76
	 * @var string
77
	 */
78
	protected $data_store_name = 'order';
79
80
	/**
81
	 * This is the name of this object type.
82
	 *
83
	 * @var string
84
	 */
85
	protected $object_type = 'order';
86
87
	/**
88
	 * Get the order if ID is passed, otherwise the order is new and empty.
89
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
90
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
91
	 * methods that will be maintained going forward.
92
	 *
93
	 * @param  int|object|WC_Order $order Order to read.
94
	 */
95 243
	public function __construct( $order = 0 ) {
96 243
		parent::__construct( $order );
97
98 243 View Code Duplication
		if ( is_numeric( $order ) && $order > 0 ) {
99 88
			$this->set_id( $order );
100 243
		} elseif ( $order instanceof self ) {
101
			$this->set_id( $order->get_id() );
102 243
		} elseif ( ! empty( $order->ID ) ) {
103
			$this->set_id( $order->ID );
104
		} else {
105 243
			$this->set_object_read( true );
106
		}
107
108 243
		$this->data_store = WC_Data_Store::load( $this->data_store_name );
109
110 243
		if ( $this->get_id() > 0 ) {
111 88
			$this->data_store->read( $this );
112
		}
113
	}
114
115
	/**
116
	 * Get internal type.
117
	 *
118
	 * @return string
119
	 */
120 161
	public function get_type() {
121 161
		return 'shop_order';
122
	}
123
124
	/**
125
	 * Get all class data in array format.
126
	 *
127
	 * @since 3.0.0
128
	 * @return array
129
	 */
130 1 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...
131 1
		return array_merge(
132
			array(
133 1
				'id' => $this->get_id(),
134
			),
135 1
			$this->data,
136
			array(
137 1
				'meta_data'      => $this->get_meta_data(),
138 1
				'line_items'     => $this->get_items( 'line_item' ),
139 1
				'tax_lines'      => $this->get_items( 'tax' ),
140 1
				'shipping_lines' => $this->get_items( 'shipping' ),
141 1
				'fee_lines'      => $this->get_items( 'fee' ),
142 1
				'coupon_lines'   => $this->get_items( 'coupon' ),
143
			)
144
		);
145
	}
146
147
	/*
148
	|--------------------------------------------------------------------------
149
	| CRUD methods
150
	|--------------------------------------------------------------------------
151
	|
152
	| Methods which create, read, update and delete orders from the database.
153
	| Written in abstract fashion so that the way orders are stored can be
154
	| changed more easily in the future.
155
	|
156
	| A save method is included for convenience (chooses update or create based
157
	| on if the order exists yet).
158
	|
159
	*/
160
161
	/**
162
	 * Save data to the database.
163
	 *
164
	 * @since 3.0.0
165
	 * @return int order ID
166
	 */
167 9 View Code Duplication
	public function save() {
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...
168 9
		if ( $this->data_store ) {
169
			// Trigger action before saving to the DB. Allows you to adjust object props before save.
170 9
			do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
171
172 9
			if ( $this->get_id() ) {
173 8
				$this->data_store->update( $this );
174
			} else {
175 9
				$this->data_store->create( $this );
176
			}
177
		}
178 9
		$this->save_items();
179 9
		return $this->get_id();
180
	}
181
182
	/**
183
	 * Save all order items which are part of this order.
184
	 */
185 160
	protected function save_items() {
186 160
		$items_changed = false;
187
188 160
		foreach ( $this->items_to_delete as $item ) {
189 12
			$item->delete();
190 12
			$items_changed = true;
191
		}
192 160
		$this->items_to_delete = array();
193
194
		// Add/save items.
195 160
		foreach ( $this->items as $item_group => $items ) {
196 128
			if ( is_array( $items ) ) {
197 128
				$items = array_filter( $items );
198 128
				foreach ( $items as $item_key => $item ) {
199 117
					$item->set_order_id( $this->get_id() );
200
201 117
					$item_id = $item->save();
202
203
					// If ID changed (new item saved to DB)...
204 117
					if ( $item_id !== $item_key ) {
205 113
						$this->items[ $item_group ][ $item_id ] = $item;
206
207 113
						unset( $this->items[ $item_group ][ $item_key ] );
208
209 113
						$items_changed = true;
210
					}
211
				}
212
			}
213
		}
214
215 160
		if ( $items_changed ) {
216 115
			delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
217
		}
218
	}
219
220
	/*
221
	|--------------------------------------------------------------------------
222
	| Getters
223
	|--------------------------------------------------------------------------
224
	*/
225
226
	/**
227
	 * Get parent order ID.
228
	 *
229
	 * @since 3.0.0
230
	 * @param  string $context View or edit context.
231
	 * @return integer
232
	 */
233 160
	public function get_parent_id( $context = 'view' ) {
234 160
		return $this->get_prop( 'parent_id', $context );
235
	}
236
237
	/**
238
	 * Gets order currency.
239
	 *
240
	 * @param  string $context View or edit context.
241
	 * @return string
242
	 */
243 162
	public function get_currency( $context = 'view' ) {
244 162
		return $this->get_prop( 'currency', $context );
245
	}
246
247
	/**
248
	 * Get order_version.
249
	 *
250
	 * @param  string $context View or edit context.
251
	 * @return string
252
	 */
253 161
	public function get_version( $context = 'view' ) {
254 161
		return $this->get_prop( 'version', $context );
255
	}
256
257
	/**
258
	 * Get prices_include_tax.
259
	 *
260
	 * @param  string $context View or edit context.
261
	 * @return bool
262
	 */
263 161
	public function get_prices_include_tax( $context = 'view' ) {
264 161
		return $this->get_prop( 'prices_include_tax', $context );
265
	}
266
267
	/**
268
	 * Get date_created.
269
	 *
270
	 * @param  string $context View or edit context.
271
	 * @return WC_DateTime|NULL object if the date is set or null if there is no date.
272
	 */
273 162
	public function get_date_created( $context = 'view' ) {
274 162
		return $this->get_prop( 'date_created', $context );
275
	}
276
277
	/**
278
	 * Get date_modified.
279
	 *
280
	 * @param  string $context View or edit context.
281
	 * @return WC_DateTime|NULL object if the date is set or null if there is no date.
282
	 */
283 1
	public function get_date_modified( $context = 'view' ) {
284 1
		return $this->get_prop( 'date_modified', $context );
285
	}
286
287
	/**
288
	 * Return the order statuses without wc- internal prefix.
289
	 *
290
	 * @param  string $context View or edit context.
291
	 * @return string
292
	 */
293 166
	public function get_status( $context = 'view' ) {
294 166
		$status = $this->get_prop( 'status', $context );
295
296 166
		if ( empty( $status ) && 'view' === $context ) {
297
			// In view context, return the default status if no status has been set.
298 133
			$status = apply_filters( 'woocommerce_default_order_status', 'pending' );
299
		}
300 166
		return $status;
301
	}
302
303
	/**
304
	 * Get discount_total.
305
	 *
306
	 * @param  string $context View or edit context.
307
	 * @return string
308
	 */
309 162
	public function get_discount_total( $context = 'view' ) {
310 162
		return $this->get_prop( 'discount_total', $context );
311
	}
312
313
	/**
314
	 * Get discount_tax.
315
	 *
316
	 * @param  string $context View or edit context.
317
	 * @return string
318
	 */
319 162
	public function get_discount_tax( $context = 'view' ) {
320 162
		return $this->get_prop( 'discount_tax', $context );
321
	}
322
323
	/**
324
	 * Get shipping_total.
325
	 *
326
	 * @param  string $context View or edit context.
327
	 * @return string
328
	 */
329 161
	public function get_shipping_total( $context = 'view' ) {
330 161
		return $this->get_prop( 'shipping_total', $context );
331
	}
332
333
	/**
334
	 * Get shipping_tax.
335
	 *
336
	 * @param  string $context View or edit context.
337
	 * @return string
338
	 */
339 163
	public function get_shipping_tax( $context = 'view' ) {
340 163
		return $this->get_prop( 'shipping_tax', $context );
341
	}
342
343
	/**
344
	 * Gets cart tax amount.
345
	 *
346
	 * @param  string $context View or edit context.
347
	 * @return float
348
	 */
349 163
	public function get_cart_tax( $context = 'view' ) {
350 163
		return $this->get_prop( 'cart_tax', $context );
351
	}
352
353
	/**
354
	 * Gets order grand total. incl. taxes. Used in gateways.
355
	 *
356
	 * @param  string $context View or edit context.
357
	 * @return float
358
	 */
359 163
	public function get_total( $context = 'view' ) {
360 163
		return $this->get_prop( 'total', $context );
361
	}
362
363
	/**
364
	 * Get total tax amount. Alias for get_order_tax().
365
	 *
366
	 * @param  string $context View or edit context.
367
	 * @return float
368
	 */
369 5
	public function get_total_tax( $context = 'view' ) {
370 5
		return $this->get_prop( 'total_tax', $context );
371
	}
372
373
	/*
374
	|--------------------------------------------------------------------------
375
	| Non-CRUD Getters
376
	|--------------------------------------------------------------------------
377
	*/
378
379
	/**
380
	 * Gets the total discount amount.
381
	 *
382
	 * @param  bool $ex_tax Show discount excl any tax.
383
	 * @return float
384
	 */
385 2
	public function get_total_discount( $ex_tax = true ) {
386 2
		if ( $ex_tax ) {
387 1
			$total_discount = $this->get_discount_total();
388
		} else {
389 2
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
390
		}
391 2
		return apply_filters( 'woocommerce_order_get_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
392
	}
393
394
	/**
395
	 * Gets order subtotal.
396
	 *
397
	 * @return float
398
	 */
399 11
	public function get_subtotal() {
400 11
		$subtotal = 0;
401
402 11
		foreach ( $this->get_items() as $item ) {
403 11
			$subtotal += $item->get_subtotal();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
404
		}
405
406 11
		return apply_filters( 'woocommerce_order_get_subtotal', (float) $subtotal, $this );
407
	}
408
409
	/**
410
	 * Get taxes, merged by code, formatted ready for output.
411
	 *
412
	 * @return array
413
	 */
414 1
	public function get_tax_totals() {
415 1
		$tax_totals = array();
416
417 1
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
418
			$code = $tax->get_rate_code();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
419
420 View Code Duplication
			if ( ! isset( $tax_totals[ $code ] ) ) {
421
				$tax_totals[ $code ]         = new stdClass();
422
				$tax_totals[ $code ]->amount = 0;
423
			}
424
425
			$tax_totals[ $code ]->id               = $key;
426
			$tax_totals[ $code ]->rate_id          = $tax->get_rate_id();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
427
			$tax_totals[ $code ]->is_compound      = $tax->is_compound();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method is_compound() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
428
			$tax_totals[ $code ]->label            = $tax->get_label();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_label() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
429
			$tax_totals[ $code ]->amount          += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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_Order_Item as the method get_shipping_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
430
			$tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
431
		}
432
433 1 View Code Duplication
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
434 1
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
435 1
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
436
		}
437
438 1
		return apply_filters( 'woocommerce_order_get_tax_totals', $tax_totals, $this );
439
	}
440
441
	/**
442
	 * Get all valid statuses for this order
443
	 *
444
	 * @since 3.0.0
445
	 * @return array Internal status keys e.g. 'wc-processing'
446
	 */
447 115
	protected function get_valid_statuses() {
448 115
		return array_keys( wc_get_order_statuses() );
449
	}
450
451
	/**
452
	 * Get user ID. Used by orders, not other order types like refunds.
453
	 *
454
	 * @param  string $context View or edit context.
455
	 * @return int
456
	 */
457
	public function get_user_id( $context = 'view' ) {
458
		return 0;
459
	}
460
461
	/**
462
	 * Get user. Used by orders, not other order types like refunds.
463
	 *
464
	 * @return WP_User|false
465
	 */
466
	public function get_user() {
467
		return false;
468
	}
469
470
	/*
471
	|--------------------------------------------------------------------------
472
	| Setters
473
	|--------------------------------------------------------------------------
474
	|
475
	| Functions for setting order data. These should not update anything in the
476
	| database itself and should only change what is stored in the class
477
	| object. However, for backwards compatibility pre 3.0.0 some of these
478
	| setters may handle both.
479
	*/
480
481
	/**
482
	 * Set parent order ID.
483
	 *
484
	 * @since 3.0.0
485
	 * @param int $value Value to set.
486
	 * @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid.
487
	 */
488 88
	public function set_parent_id( $value ) {
489 88
		if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
490
			$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
491
		}
492 88
		$this->set_prop( 'parent_id', absint( $value ) );
493
	}
494
495
	/**
496
	 * Set order status.
497
	 *
498
	 * @since 3.0.0
499
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
500
	 * @return array details of change
501
	 */
502 132
	public function set_status( $new_status ) {
503 132
		$old_status = $this->get_status();
504 132
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
505
506
		// If setting the status, ensure it's set to a valid status.
507 132
		if ( true === $this->object_read ) {
508
			// Only allow valid new status.
509 115 View Code Duplication
			if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && 'trash' !== $new_status ) {
510
				$new_status = 'pending';
511
			}
512
513
			// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
514 115 View Code Duplication
			if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && 'trash' !== $old_status ) {
515
				$old_status = 'pending';
516
			}
517
		}
518
519 132
		$this->set_prop( 'status', $new_status );
520
521
		return array(
522 132
			'from' => $old_status,
523 132
			'to'   => $new_status,
524
		);
525
	}
526
527
	/**
528
	 * Set order_version.
529
	 *
530
	 * @param string $value Value to set.
531
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
532
	 */
533 161
	public function set_version( $value ) {
534 161
		$this->set_prop( 'version', $value );
535
	}
536
537
	/**
538
	 * Set order_currency.
539
	 *
540
	 * @param string $value Value to set.
541
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
542
	 */
543 162
	public function set_currency( $value ) {
544 162
		if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ), true ) ) {
545
			$this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
546
		}
547 162
		$this->set_prop( 'currency', $value ? $value : get_woocommerce_currency() );
548
	}
549
550
	/**
551
	 * Set prices_include_tax.
552
	 *
553
	 * @param bool $value Value to set.
554
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
555
	 */
556 127
	public function set_prices_include_tax( $value ) {
557 127
		$this->set_prop( 'prices_include_tax', (bool) $value );
558
	}
559
560
	/**
561
	 * Set date_created.
562
	 *
563
	 * @param  string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
564
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
565
	 */
566 162
	public function set_date_created( $date = null ) {
567 162
		$this->set_date_prop( 'date_created', $date );
568
	}
569
570
	/**
571
	 * Set date_modified.
572
	 *
573
	 * @param  string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
574
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
575
	 */
576 89
	public function set_date_modified( $date = null ) {
577 89
		$this->set_date_prop( 'date_modified', $date );
578
	}
579
580
	/**
581
	 * Set discount_total.
582
	 *
583
	 * @param string $value Value to set.
584
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
585
	 */
586 133
	public function set_discount_total( $value ) {
587 133
		$this->set_prop( 'discount_total', wc_format_decimal( $value ) );
588
	}
589
590
	/**
591
	 * Set discount_tax.
592
	 *
593
	 * @param string $value Value to set.
594
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
595
	 */
596 133
	public function set_discount_tax( $value ) {
597 133
		$this->set_prop( 'discount_tax', wc_format_decimal( $value ) );
598
	}
599
600
	/**
601
	 * Set shipping_total.
602
	 *
603
	 * @param string $value Value to set.
604
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
605
	 */
606 133
	public function set_shipping_total( $value ) {
607 133
		$this->set_prop( 'shipping_total', wc_format_decimal( $value ) );
608
	}
609
610
	/**
611
	 * Set shipping_tax.
612
	 *
613
	 * @param string $value Value to set.
614
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
615
	 */
616 136
	public function set_shipping_tax( $value ) {
617 136
		$this->set_prop( 'shipping_tax', wc_format_decimal( $value ) );
618 136
		$this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
619
	}
620
621
	/**
622
	 * Set cart tax.
623
	 *
624
	 * @param string $value Value to set.
625
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
626
	 */
627 136
	public function set_cart_tax( $value ) {
628 136
		$this->set_prop( 'cart_tax', wc_format_decimal( $value ) );
629 136
		$this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
630
	}
631
632
	/**
633
	 * Sets order tax (sum of cart and shipping tax). Used internally only.
634
	 *
635
	 * @param string $value Value to set.
636
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
637
	 */
638 137
	protected function set_total_tax( $value ) {
639 137
		$this->set_prop( 'total_tax', wc_format_decimal( $value ) );
640
	}
641
642
	/**
643
	 * Set total.
644
	 *
645
	 * @param string $value Value to set.
646
	 * @param string $deprecated Function used to set different totals based on this.
647
	 *
648
	 * @return bool|void
649
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
650
	 */
651 134
	public function set_total( $value, $deprecated = '' ) {
652 134
		if ( $deprecated ) {
653
			wc_deprecated_argument( 'total_type', '3.0', 'Use dedicated total setter methods instead.' );
654
			return $this->legacy_set_total( $value, $deprecated );
655
		}
656 134
		$this->set_prop( 'total', wc_format_decimal( $value, wc_get_price_decimals() ) );
657
	}
658
659
	/*
660
	|--------------------------------------------------------------------------
661
	| Order Item Handling
662
	|--------------------------------------------------------------------------
663
	|
664
	| Order items are used for products, taxes, shipping, and fees within
665
	| each order.
666
	*/
667
668
	/**
669
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
670
	 *
671
	 * @param string $type Order item type. Default null.
672
	 */
673 1
	public function remove_order_items( $type = null ) {
674 1
		if ( ! empty( $type ) ) {
675
			$this->data_store->delete_items( $this, $type );
676
677
			$group = $this->type_to_group( $type );
678
679
			if ( $group ) {
680
				unset( $this->items[ $group ] );
681
			}
682
		} else {
683 1
			$this->data_store->delete_items( $this );
684 1
			$this->items = array();
685
		}
686
	}
687
688
	/**
689
	 * Convert a type to a types group.
690
	 *
691
	 * @param string $type type to lookup.
692
	 * @return string
693
	 */
694 139
	protected function type_to_group( $type ) {
695 139
		$type_to_group = apply_filters(
696 139
			'woocommerce_order_type_to_group',
697
			array(
698 139
				'line_item' => 'line_items',
699
				'tax'       => 'tax_lines',
700
				'shipping'  => 'shipping_lines',
701
				'fee'       => 'fee_lines',
702
				'coupon'    => 'coupon_lines',
703
			)
704
		);
705 139
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
706
	}
707
708
	/**
709
	 * Return an array of items/products within this order.
710
	 *
711
	 * @param string|array $types Types of line items to get (array or string).
712
	 * @return WC_Order_Item[]
713
	 */
714 139
	public function get_items( $types = 'line_item' ) {
715 139
		$items = array();
716 139
		$types = array_filter( (array) $types );
717
718 139
		foreach ( $types as $type ) {
719 139
			$group = $this->type_to_group( $type );
720
721 139
			if ( $group ) {
722 139
				if ( ! isset( $this->items[ $group ] ) ) {
723 139
					$this->items[ $group ] = array_filter( $this->data_store->read_items( $this, $type ) );
724
				}
725
				// Don't use array_merge here because keys are numeric.
726 139
				$items = $items + $this->items[ $group ];
727
			}
728
		}
729
730 139
		return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
731
	}
732
733
	/**
734
	 * Return an array of coupons within this order.
735
	 *
736
	 * @since  3.7.0
737
	 * @return WC_Order_Item_Coupon[]
738
	 */
739 1
	public function get_coupons() {
740 1
		return $this->get_items( 'coupon' );
741
	}
742
743
	/**
744
	 * Return an array of fees within this order.
745
	 *
746
	 * @return WC_Order_item_Fee[]
747
	 */
748 36
	public function get_fees() {
749 36
		return $this->get_items( 'fee' );
750
	}
751
752
	/**
753
	 * Return an array of taxes within this order.
754
	 *
755
	 * @return WC_Order_Item_Tax[]
756
	 */
757 38
	public function get_taxes() {
758 38
		return $this->get_items( 'tax' );
759
	}
760
761
	/**
762
	 * Return an array of shipping costs within this order.
763
	 *
764
	 * @return WC_Order_Item_Shipping[]
765
	 */
766 43
	public function get_shipping_methods() {
767 43
		return $this->get_items( 'shipping' );
768
	}
769
770
	/**
771
	 * Gets formatted shipping method title.
772
	 *
773
	 * @return string
774
	 */
775 1
	public function get_shipping_method() {
776 1
		$names = array();
777 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
778 1
			$names[] = $shipping_method->get_name();
779
		}
780 1
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
781
	}
782
783
	/**
784
	 * Get coupon codes only.
785
	 *
786
	 * @return array
787
	 */
788 22
	public function get_used_coupons() {
789 22
		$coupon_codes = array();
790 22
		$coupons      = $this->get_items( 'coupon' );
791
792 22
		if ( $coupons ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $coupons of type WC_Order_Item[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
793 2
			foreach ( $coupons as $coupon ) {
794 2
				$coupon_codes[] = $coupon->get_code();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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...
795
			}
796
		}
797 22
		return $coupon_codes;
798
	}
799
800
	/**
801
	 * Gets the count of order items of a certain type.
802
	 *
803
	 * @param string $item_type Item type to lookup.
804
	 * @return int|string
805
	 */
806 10
	public function get_item_count( $item_type = '' ) {
807 10
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
808 10
		$count = 0;
809
810 10
		foreach ( $items as $item ) {
811 7
			$count += $item->get_quantity();
812
		}
813
814 10
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
815
	}
816
817
	/**
818
	 * Get an order item object, based on its type.
819
	 *
820
	 * @since  3.0.0
821
	 * @param  int  $item_id ID of item to get.
822
	 * @param  bool $load_from_db Prior to 3.2 this item was loaded direct from WC_Order_Factory, not this object. This param is here for backwards compatility with that. If false, uses the local items variable instead.
823
	 * @return WC_Order_Item|false
824
	 */
825 20
	public function get_item( $item_id, $load_from_db = true ) {
826 20
		if ( $load_from_db ) {
827 1
			return WC_Order_Factory::get_order_item( $item_id );
828
		}
829
830
		// Search for item id.
831 19
		if ( $this->items ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->items of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
832 16
			foreach ( $this->items as $group => $items ) {
833 16
				if ( isset( $items[ $item_id ] ) ) {
834 16
					return $items[ $item_id ];
835
				}
836
			}
837
		}
838
839
		// Load all items of type and cache.
840 5
		$type = $this->data_store->get_order_item_type( $this, $item_id );
841
842 5
		if ( ! $type ) {
843
			return false;
844
		}
845
846 5
		$items = $this->get_items( $type );
847
848 5
		return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
849
	}
850
851
	/**
852
	 * Get key for where a certain item type is stored in _items.
853
	 *
854
	 * @since  3.0.0
855
	 * @param  string $item object Order item (product, shipping, fee, coupon, tax).
856
	 * @return string
857
	 */
858 120
	protected function get_items_key( $item ) {
859 120
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
860 112
			return 'line_items';
861 112
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
862 4
			return 'fee_lines';
863 110
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
864 97
			return 'shipping_lines';
865 23
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
866 10
			return 'tax_lines';
867 18
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
868 18
			return 'coupon_lines';
869
		}
870 1
		return apply_filters( 'woocommerce_get_items_key', '', $item );
871
	}
872
873
	/**
874
	 * Remove item from the order.
875
	 *
876
	 * @param int $item_id Item ID to delete.
877
	 * @return false|void
878
	 */
879 12
	public function remove_item( $item_id ) {
880 12
		$item      = $this->get_item( $item_id, false );
881 12
		$items_key = $item ? $this->get_items_key( $item ) : false;
882
883 12
		if ( ! $items_key ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items_key of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
884
			return false;
885
		}
886
887
		// Unset and remove later.
888 12
		$this->items_to_delete[] = $item;
889 12
		unset( $this->items[ $items_key ][ $item->get_id() ] );
890
	}
891
892
	/**
893
	 * Adds an order item to this order. The order item will not persist until save.
894
	 *
895
	 * @since 3.0.0
896
	 * @param WC_Order_Item $item Order item object (product, shipping, fee, coupon, tax).
897
	 * @return false|void
898
	 */
899 120
	public function add_item( $item ) {
900 120
		$items_key = $this->get_items_key( $item );
901
902 120
		if ( ! $items_key ) {
903 1
			return false;
904
		}
905
906
		// Make sure existing items are loaded so we can append this new one.
907 120
		if ( ! isset( $this->items[ $items_key ] ) ) {
908 119
			$this->items[ $items_key ] = $this->get_items( $item->get_type() );
909
		}
910
911
		// Set parent.
912 120
		$item->set_order_id( $this->get_id() );
913
914
		// Append new row with generated temporary ID.
915 120
		$item_id = $item->get_id();
916
917 120
		if ( $item_id ) {
918 102
			$this->items[ $items_key ][ $item_id ] = $item;
919
		} else {
920 115
			$this->items[ $items_key ][ 'new:' . $items_key . count( $this->items[ $items_key ] ) ] = $item;
921
		}
922
	}
923
924
	/**
925
	 * Apply a coupon to the order and recalculate totals.
926
	 *
927
	 * @since 3.2.0
928
	 * @param string|WC_Coupon $raw_coupon Coupon code or object.
929
	 * @return true|WP_Error True if applied, error if not.
930
	 */
931 11
	public function apply_coupon( $raw_coupon ) {
932 11
		if ( is_a( $raw_coupon, 'WC_Coupon' ) ) {
933 3
			$coupon = $raw_coupon;
934 8
		} elseif ( is_string( $raw_coupon ) ) {
935 8
			$code   = wc_format_coupon_code( $raw_coupon );
936 8
			$coupon = new WC_Coupon( $code );
937
938 8
			if ( $coupon->get_code() !== $code ) {
939
				return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) );
940
			}
941
942 8
			$discounts = new WC_Discounts( $this );
943 8
			$valid     = $discounts->is_coupon_valid( $coupon );
944
945 8
			if ( is_wp_error( $valid ) ) {
946 8
				return $valid;
947
			}
948
		} else {
949
			return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
950
		}
951
952
		// Check to make sure coupon is not already applied.
953 10
		$applied_coupons = $this->get_items( 'coupon' );
954 10
		foreach ( $applied_coupons as $applied_coupon ) {
955 4
			if ( $applied_coupon->get_code() === $coupon->get_code() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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...
956
				return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) );
957
			}
958
		}
959
960 10
		$discounts = new WC_Discounts( $this );
961 10
		$applied   = $discounts->apply_coupon( $coupon );
0 ignored issues
show
Bug introduced by
It seems like $coupon defined by $raw_coupon on line 933 can also be of type string; however, WC_Discounts::apply_coupon() does only seem to accept object<WC_Coupon>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
962
963 10
		if ( is_wp_error( $applied ) ) {
964
			return $applied;
965
		}
966
967 10
		$this->set_coupon_discount_amounts( $discounts );
968 10
		$this->save();
969
970
		// Recalculate totals and taxes.
971 10
		$this->recalculate_coupons();
972
973
		// Record usage so counts and validation is correct.
974 10
		$used_by = $this->get_user_id();
975
976 10
		if ( ! $used_by ) {
977
			$used_by = $this->get_billing_email();
978
		}
979
980 10
		$coupon->increase_usage_count( $used_by );
981
982 10
		return true;
983
	}
984
985
	/**
986
	 * Remove a coupon from the order and recalculate totals.
987
	 *
988
	 * Coupons affect line item totals, but there is no relationship between
989
	 * coupon and line total, so to remove a coupon we need to work from the
990
	 * line subtotal (price before discount) and re-apply all coupons in this
991
	 * order.
992
	 *
993
	 * Manual discounts are not affected; those are separate and do not affect
994
	 * stored line totals.
995
	 *
996
	 * @since  3.2.0
997
	 * @param  string $code Coupon code.
998
	 * @return void
999
	 */
1000 7
	public function remove_coupon( $code ) {
1001 7
		$coupons = $this->get_items( 'coupon' );
1002
1003
		// Remove the coupon line.
1004 7
		foreach ( $coupons as $item_id => $coupon ) {
1005 7
			if ( $coupon->get_code() === $code ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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...
1006 7
				$this->remove_item( $item_id );
1007 7
				$coupon_object = new WC_Coupon( $code );
1008 7
				$coupon_object->decrease_usage_count( $this->get_user_id() );
1009 7
				$this->recalculate_coupons();
1010 7
				break;
1011
			}
1012
		}
1013
	}
1014
1015
	/**
1016
	 * Apply all coupons in this order again to all line items.
1017
	 *
1018
	 * @since  3.2.0
1019
	 */
1020 14
	protected function recalculate_coupons() {
1021
		// Reset line item totals.
1022 14
		foreach ( $this->get_items() as $item ) {
1023 14
			$item->set_total( $item->get_subtotal() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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_Order_Item as the method set_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1024 14
			$item->set_total_tax( $item->get_subtotal_tax() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal_tax() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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_Order_Item as the method set_total_tax() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1025
		}
1026
1027 14
		$discounts = new WC_Discounts( $this );
1028
1029 14
		foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
1030 14
			$coupon_code = $coupon_item->get_code();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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...
1031 14
			$coupon_id   = wc_get_coupon_id_by_code( $coupon_code );
1032
1033
			// If we have a coupon ID (loaded via wc_get_coupon_id_by_code) we can simply load the new coupon object using the ID.
1034 14
			if ( $coupon_id ) {
1035 14
				$coupon_object = new WC_Coupon( $coupon_id );
1036
1037
			} else {
1038
1039
				// If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout.
1040 8
				$coupon_object = new WC_Coupon();
1041 8
				$coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
1042 8
				$coupon_object->set_code( $coupon_code );
1043 8
				$coupon_object->set_virtual( true );
1044
1045
				// If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
1046 8
				if ( ! $coupon_object->get_amount() ) {
1047
1048
					// If the order originally had prices including tax, remove the discount + discount tax.
1049 8
					if ( $this->get_prices_include_tax() ) {
1050 4
						$coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_discount() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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_Order_Item as the method get_discount_tax() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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...
1051
					} else {
1052 4
						$coupon_object->set_amount( $coupon_item->get_discount() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_discount() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. 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...
1053
					}
1054 8
					$coupon_object->set_discount_type( 'fixed_cart' );
1055
				}
1056
			}
1057
1058
			/**
1059
			 * Allow developers to filter this coupon before it get's re-applied to the order.
1060
			 *
1061
			 * @since 3.2.0
1062
			 */
1063 14
			$coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
1064
1065 14
			if ( $coupon_object ) {
1066 14
				$discounts->apply_coupon( $coupon_object, false );
1067
			}
1068
		}
1069
1070 14
		$this->set_coupon_discount_amounts( $discounts );
1071 14
		$this->set_item_discount_amounts( $discounts );
1072
1073
		// Recalculate totals and taxes.
1074 14
		$this->calculate_totals( true );
1075
	}
1076
1077
	/**
1078
	 * After applying coupons via the WC_Discounts class, update line items.
1079
	 *
1080
	 * @since 3.2.0
1081
	 * @param WC_Discounts $discounts Discounts class.
1082
	 */
1083 14
	protected function set_item_discount_amounts( $discounts ) {
1084 14
		$item_discounts = $discounts->get_discounts_by_item();
1085
1086 14
		if ( $item_discounts ) {
1087 14
			foreach ( $item_discounts as $item_id => $amount ) {
0 ignored issues
show
Bug introduced by
The expression $item_discounts of type double|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1088 14
				$item = $this->get_item( $item_id, false );
1089
1090
				// If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
1091 14
				if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1092 2
					$taxes = WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1093
1094 2
					if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1095 2
						$taxes = array_map( 'wc_round_tax_total', $taxes );
1096
					}
1097
1098 2
					$amount = $amount - array_sum( $taxes );
1099 2
					$item->set_total( max( 0, round( $item->get_total() - $amount, wc_get_price_decimals() ) ) );
1100
				} else {
1101 12
					$item->set_total( max( 0, $item->get_total() - $amount ) );
1102
				}
1103
			}
1104
		}
1105
	}
1106
1107
	/**
1108
	 * After applying coupons via the WC_Discounts class, update or create coupon items.
1109
	 *
1110
	 * @since 3.2.0
1111
	 * @param WC_Discounts $discounts Discounts class.
1112
	 */
1113 14
	protected function set_coupon_discount_amounts( $discounts ) {
1114 14
		$coupons           = $this->get_items( 'coupon' );
1115 14
		$coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
1116 14
		$all_discounts     = $discounts->get_discounts();
1117 14
		$coupon_discounts  = $discounts->get_discounts_by_coupon();
1118
1119 14
		if ( $coupon_discounts ) {
1120 14
			foreach ( $coupon_discounts as $coupon_code => $amount ) {
0 ignored issues
show
Bug introduced by
The expression $coupon_discounts of type double|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1121 14
				$item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0;
1122
1123 14
				if ( ! $item_id ) {
1124 10
					$coupon_item = new WC_Order_Item_Coupon();
1125 10
					$coupon_item->set_code( $coupon_code );
1126
				} else {
1127 14
					$coupon_item = $this->get_item( $item_id, false );
1128
				}
1129
1130 14
				$discount_tax = 0;
1131
1132
				// Work out how much tax has been removed as a result of the discount from this coupon.
1133 14
				foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
1134 14
					$item = $this->get_item( $item_id, false );
1135
1136 14
					if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1137 2
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1138
1139 2
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1140 2
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1141
						}
1142
1143 2
						$discount_tax += array_sum( $taxes );
1144 2
						$amount        = $amount - array_sum( $taxes );
1145
					} else {
1146 12
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) );
1147
1148 12
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1149 12
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1150
						}
1151
1152 12
						$discount_tax += array_sum( $taxes );
1153
					}
1154
				}
1155
1156 14
				$coupon_item->set_discount( $amount );
1157 14
				$coupon_item->set_discount_tax( $discount_tax );
1158
1159 14
				$this->add_item( $coupon_item );
1160
			}
1161
		}
1162
	}
1163
1164
	/**
1165
	 * Add a product line item to the order. This is the only line item type with
1166
	 * its own method because it saves looking up order amounts (costs are added up for you).
1167
	 *
1168
	 * @param  WC_Product $product Product object.
1169
	 * @param  int        $qty Quantity to add.
1170
	 * @param  array      $args Args for the added product.
1171
	 * @return int
1172
	 * @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart.
1173
	 */
1174 6
	public function add_product( $product, $qty = 1, $args = array() ) {
1175 6
		if ( $product ) {
1176
			$default_args = array(
1177 6
				'name'         => $product->get_name(),
1178 6
				'tax_class'    => $product->get_tax_class(),
1179 6
				'product_id'   => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
1180 6
				'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
1181 6
				'variation'    => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
1182 6
				'subtotal'     => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1183 6
				'total'        => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1184 6
				'quantity'     => $qty,
1185
			);
1186
		} else {
1187
			$default_args = array(
1188
				'quantity' => $qty,
1189
			);
1190
		}
1191
1192 6
		$args = wp_parse_args( $args, $default_args );
1193
1194
		// BW compatibility with old args.
1195 6
		if ( isset( $args['totals'] ) ) {
1196
			foreach ( $args['totals'] as $key => $value ) {
1197
				if ( 'tax' === $key ) {
1198
					$args['total_tax'] = $value;
1199
				} elseif ( 'tax_data' === $key ) {
1200
					$args['taxes'] = $value;
1201
				} else {
1202
					$args[ $key ] = $value;
1203
				}
1204
			}
1205
		}
1206
1207 6
		$item = new WC_Order_Item_Product();
1208 6
		$item->set_props( $args );
1209 6
		$item->set_backorder_meta();
1210 6
		$item->set_order_id( $this->get_id() );
1211 6
		$item->save();
1212 6
		$this->add_item( $item );
1213 6
		wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '3.0', 'woocommerce_new_order_item action instead' );
1214 6
		delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
1215 6
		return $item->get_id();
1216
	}
1217
1218
	/*
1219
	|--------------------------------------------------------------------------
1220
	| Payment Token Handling
1221
	|--------------------------------------------------------------------------
1222
	|
1223
	| Payment tokens are hashes used to take payments by certain gateways.
1224
	|
1225
	*/
1226
1227
	/**
1228
	 * Add a payment token to an order
1229
	 *
1230
	 * @since 2.6
1231
	 * @param WC_Payment_Token $token Payment token object.
1232
	 * @return boolean|int The new token ID or false if it failed.
1233
	 */
1234 3
	public function add_payment_token( $token ) {
1235 3
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1236 1
			return false;
1237
		}
1238
1239 3
		$token_ids   = $this->data_store->get_payment_token_ids( $this );
1240 3
		$token_ids[] = $token->get_id();
1241 3
		$this->data_store->update_payment_token_ids( $this, $token_ids );
1242
1243 3
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1244 3
		return $token->get_id();
1245
	}
1246
1247
	/**
1248
	 * Returns a list of all payment tokens associated with the current order
1249
	 *
1250
	 * @since 2.6
1251
	 * @return array An array of payment token objects
1252
	 */
1253 4
	public function get_payment_tokens() {
1254 4
		return $this->data_store->get_payment_token_ids( $this );
1255
	}
1256
1257
	/*
1258
	|--------------------------------------------------------------------------
1259
	| Calculations.
1260
	|--------------------------------------------------------------------------
1261
	|
1262
	| These methods calculate order totals and taxes based on the current data.
1263
	|
1264
	*/
1265
1266
	/**
1267
	 * Calculate shipping total.
1268
	 *
1269
	 * @since 2.2
1270
	 * @return float
1271
	 */
1272 1
	public function calculate_shipping() {
1273 1
		$shipping_total = 0;
1274
1275 1
		foreach ( $this->get_shipping_methods() as $shipping ) {
1276 1
			$shipping_total += $shipping->get_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1277
		}
1278
1279 1
		$this->set_shipping_total( $shipping_total );
1280 1
		$this->save();
1281
1282 1
		return $this->get_shipping_total();
1283
	}
1284
1285
	/**
1286
	 * Get all tax classes for items in the order.
1287
	 *
1288
	 * @since 2.6.3
1289
	 * @return array
1290
	 */
1291 32
	public function get_items_tax_classes() {
1292 32
		$found_tax_classes = array();
1293
1294 32
		foreach ( $this->get_items() as $item ) {
1295 32
			if ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) {
1296 32
				$found_tax_classes[] = $item->get_tax_class();
1297
			}
1298
		}
1299
1300 32
		return array_unique( $found_tax_classes );
1301
	}
1302
1303
	/**
1304
	 * Get tax location for this order.
1305
	 *
1306
	 * @since 3.2.0
1307
	 * @param array $args array Override the location.
1308
	 * @return array
1309
	 */
1310 31
	protected function get_tax_location( $args = array() ) {
1311 31
		$tax_based_on = get_option( 'woocommerce_tax_based_on' );
1312
1313 31
		if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
1314 18
			$tax_based_on = 'billing';
1315
		}
1316
1317 31
		$args = wp_parse_args(
1318 31
			$args,
1319
			array(
1320 31
				'country'  => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
1321 31
				'state'    => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
1322 31
				'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
1323 31
				'city'     => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(),
1324
			)
1325
		);
1326
1327
		// Default to base.
1328 31
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1329 15
			$default          = wc_get_base_location();
1330 15
			$args['country']  = $default['country'];
1331 15
			$args['state']    = $default['state'];
1332 15
			$args['postcode'] = '';
1333 15
			$args['city']     = '';
1334
		}
1335
1336 31
		return $args;
1337
	}
1338
1339
	/**
1340
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1341
	 *
1342
	 * If by default the taxes are based on the shipping address and the current order doesn't
1343
	 * have any, it would use the billing address rather than using the Shopping base location.
1344
	 *
1345
	 * Will use the base country unless customer addresses are set.
1346
	 *
1347
	 * @param array $args Added in 3.0.0 to pass things like location.
1348
	 */
1349 31
	public function calculate_taxes( $args = array() ) {
1350 31
		do_action( 'woocommerce_order_before_calculate_taxes', $args, $this );
1351
1352 31
		$calculate_tax_for  = $this->get_tax_location( $args );
1353 31
		$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1354
1355 31
		if ( 'inherit' === $shipping_tax_class ) {
1356 31
			$found_classes      = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
1357 31
			$shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
1358
		}
1359
1360 31
		$is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
1361
1362
		// Trigger tax recalculation for all items.
1363 31
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1364 31
			if ( ! $is_vat_exempt ) {
1365 31
				$item->calculate_taxes( $calculate_tax_for );
1366
			} else {
1367 1
				$item->set_taxes( false );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1368
			}
1369
		}
1370
1371 31
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1372 19
			if ( false !== $shipping_tax_class && ! $is_vat_exempt ) {
1373 19
				$item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
1374
			} else {
1375 1
				$item->set_taxes( false );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1376
			}
1377
		}
1378
1379 31
		$this->update_taxes();
1380
	}
1381
1382
	/**
1383
	 * Update tax lines for the order based on the line item taxes themselves.
1384
	 */
1385 38
	public function update_taxes() {
1386 38
		$cart_taxes     = array();
1387 38
		$shipping_taxes = array();
1388 38
		$existing_taxes = $this->get_taxes();
1389 38
		$saved_rate_ids = array();
1390
1391 38 View Code Duplication
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1392 31
			$taxes = $item->get_taxes();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1393 31
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1394 10
				$tax_amount = (float) $tax;
1395
1396 10
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1397 10
					$tax_amount = wc_round_tax_total( $tax_amount );
1398
				}
1399
1400 10
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1401
			}
1402
		}
1403
1404 38 View Code Duplication
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1405 19
			$taxes = $item->get_taxes();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1406 19
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1407 4
				$tax_amount = (float) $tax;
1408
1409 4
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1410 4
					$tax_amount = wc_round_tax_total( $tax_amount );
1411
				}
1412
1413 4
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1414
			}
1415
		}
1416
1417 38
		foreach ( $existing_taxes as $tax ) {
1418
			// Remove taxes which no longer exist for cart/shipping.
1419 5
			if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
1420 1
				$this->remove_item( $tax->get_id() );
1421 1
				continue;
1422
			}
1423 4
			$saved_rate_ids[] = $tax->get_rate_id();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
1424 4
			$tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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_Order_Item as the method set_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
1425 4
			$tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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_Order_Item as the method set_shipping_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
1426 4
			$tax->save();
1427
		}
1428
1429 38
		$new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) );
1430
1431
		// New taxes.
1432 38
		foreach ( $new_rate_ids as $tax_rate_id ) {
1433 10
			$item = new WC_Order_Item_Tax();
1434 10
			$item->set_rate( $tax_rate_id );
1435 10
			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
1436 10
			$item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1437 10
			$this->add_item( $item );
1438
		}
1439
1440 38
		$this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) );
1441 38
		$this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) );
1442 38
		$this->save();
1443
	}
1444
1445
	/**
1446
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1447
	 *
1448
	 * @since 2.2
1449
	 * @param  bool $and_taxes Calc taxes if true.
1450
	 * @return float calculated grand total.
1451
	 */
1452 35
	public function calculate_totals( $and_taxes = true ) {
1453 35
		do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
1454
1455 35
		$cart_subtotal     = 0;
1456 35
		$cart_total        = 0;
1457 35
		$fees_total         = 0;
1458 35
		$shipping_total    = 0;
1459 35
		$cart_subtotal_tax = 0;
1460 35
		$cart_total_tax    = 0;
1461
1462
		// Sum line item costs.
1463 35
		foreach ( $this->get_items() as $item ) {
1464 28
			$cart_subtotal += round( $item->get_subtotal(), wc_get_price_decimals() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
1465 28
			$cart_total    += round( $item->get_total(), wc_get_price_decimals() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1466
		}
1467
1468
		// Sum shipping costs.
1469 35
		foreach ( $this->get_shipping_methods() as $shipping ) {
1470 17
			$shipping_total += round( $shipping->get_total(), wc_get_price_decimals() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1471
		}
1472
1473 35
		$this->set_shipping_total( $shipping_total );
1474
1475
		// Sum fee costs.
1476 35
		foreach ( $this->get_fees() as $item ) {
1477
			$fee_total = $item->get_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1478
1479
			if ( 0 > $fee_total ) {
1480
				$max_discount = round( $cart_total + $fees_total + $shipping_total, wc_get_price_decimals() ) * -1;
1481
1482
				if ( $fee_total < $max_discount ) {
1483
					$item->set_total( $max_discount );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1484
				}
1485
			}
1486
1487
			$fees_total += $item->get_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1488
		}
1489
1490
		// Calculate taxes for items, shipping, discounts. Note; this also triggers save().
1491 35
		if ( $and_taxes ) {
1492 28
			$this->calculate_taxes();
1493
		}
1494
1495
		// Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp.
1496 35
		foreach ( $this->get_items() as $item ) {
1497 28
			$taxes = $item->get_taxes();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1498
1499 28
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1500 7
				$cart_total_tax += (float) $tax;
1501
			}
1502
1503 28
			foreach ( $taxes['subtotal'] as $tax_rate_id => $tax ) {
1504 7
				$cart_subtotal_tax += (float) $tax;
1505
			}
1506
		}
1507
1508 35
		$this->set_discount_total( $cart_subtotal - $cart_total );
1509 35
		$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
1510 35
		$this->set_total( round( $cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
1511
1512 35
		do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this );
1513
1514 35
		$this->save();
1515
1516 35
		return $this->get_total();
1517
	}
1518
1519
	/**
1520
	 * Get item subtotal - this is the cost before discount.
1521
	 *
1522
	 * @param object $item Item to get total from.
1523
	 * @param bool   $inc_tax (default: false).
1524
	 * @param bool   $round (default: true).
1525
	 * @return float
1526
	 */
1527 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...
1528
		$subtotal = 0;
1529
1530
		if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) {
1531
			if ( $inc_tax ) {
1532
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity();
1533
			} else {
1534
				$subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity();
1535
			}
1536
1537
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1538
		}
1539
1540
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1541
	}
1542
1543
	/**
1544
	 * Get line subtotal - this is the cost before discount.
1545
	 *
1546
	 * @param object $item Item to get total from.
1547
	 * @param bool   $inc_tax (default: false).
1548
	 * @param bool   $round (default: true).
1549
	 * @return float
1550
	 */
1551 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...
1552
		$subtotal = 0;
1553
1554
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1555
			if ( $inc_tax ) {
1556
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1557
			} else {
1558
				$subtotal = $item->get_subtotal();
1559
			}
1560
1561
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1562
		}
1563
1564
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1565
	}
1566
1567
	/**
1568
	 * Calculate item cost - useful for gateways.
1569
	 *
1570
	 * @param object $item Item to get total from.
1571
	 * @param bool   $inc_tax (default: false).
1572
	 * @param bool   $round (default: true).
1573
	 * @return float
1574
	 */
1575 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...
1576
		$total = 0;
1577
1578
		if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) {
1579
			if ( $inc_tax ) {
1580
				$total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity();
1581
			} else {
1582
				$total = floatval( $item->get_total() ) / $item->get_quantity();
1583
			}
1584
1585
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1586
		}
1587
1588
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1589
	}
1590
1591
	/**
1592
	 * Calculate line total - useful for gateways.
1593
	 *
1594
	 * @param object $item Item to get total from.
1595
	 * @param bool   $inc_tax (default: false).
1596
	 * @param bool   $round (default: true).
1597
	 * @return float
1598
	 */
1599 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...
1600
		$total = 0;
1601
1602
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1603
			// Check if we need to add line tax to the line total.
1604
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1605
1606
			// Check if we need to round.
1607
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1608
		}
1609
1610
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1611
	}
1612
1613
	/**
1614
	 * Get item tax - useful for gateways.
1615
	 *
1616
	 * @param mixed $item Item to get total from.
1617
	 * @param bool  $round (default: true).
1618
	 * @return float
1619
	 */
1620
	public function get_item_tax( $item, $round = true ) {
1621
		$tax = 0;
1622
1623
		if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) {
1624
			$tax = $item->get_total_tax() / $item->get_quantity();
1625
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1626
		}
1627
1628
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1629
	}
1630
1631
	/**
1632
	 * Get line tax - useful for gateways.
1633
	 *
1634
	 * @param mixed $item Item to get total from.
1635
	 * @return float
1636
	 */
1637
	public function get_line_tax( $item ) {
1638
		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 );
1639
	}
1640
1641
	/**
1642
	 * Gets line subtotal - formatted for display.
1643
	 *
1644
	 * @param array  $item Item to get total from.
1645
	 * @param string $tax_display Incl or excl tax display mode.
1646
	 * @return string
1647
	 */
1648
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1649
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1650
1651
		if ( 'excl' === $tax_display ) {
1652
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1653
1654
			$subtotal = wc_price(
1655
				$this->get_line_subtotal( $item ),
1656
				array(
1657
					'ex_tax_label' => $ex_tax_label,
1658
					'currency'     => $this->get_currency(),
1659
				)
1660
			);
1661
		} else {
1662
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
1663
		}
1664
1665
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1666
	}
1667
1668
	/**
1669
	 * Gets order total - formatted for display.
1670
	 *
1671
	 * @return string
1672
	 */
1673
	public function get_formatted_order_total() {
1674
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1675
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1676
	}
1677
1678
	/**
1679
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1680
	 *
1681
	 * @param bool   $compound (default: false).
1682
	 * @param string $tax_display (default: the tax_display_cart value).
1683
	 * @return string
1684
	 */
1685
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1686
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1687
		$subtotal    = 0;
1688
1689
		if ( ! $compound ) {
1690
			foreach ( $this->get_items() as $item ) {
1691
				$subtotal += $item->get_subtotal();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
1692
1693
				if ( 'incl' === $tax_display ) {
1694
					$subtotal += $item->get_subtotal_tax();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal_tax() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
1695
				}
1696
			}
1697
1698
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1699
1700 View Code Duplication
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) {
1701
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1702
			}
1703
		} else {
1704
			if ( 'incl' === $tax_display ) {
1705
				return '';
1706
			}
1707
1708
			foreach ( $this->get_items() as $item ) {
1709
				$subtotal += $item->get_subtotal();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_subtotal() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Product. 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...
1710
			}
1711
1712
			// Add Shipping Costs.
1713
			$subtotal += $this->get_shipping_total();
1714
1715
			// Remove non-compound taxes.
1716
			foreach ( $this->get_taxes() as $tax ) {
1717
				if ( $tax->is_compound() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method is_compound() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
1718
					continue;
1719
				}
1720
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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_Order_Item as the method get_shipping_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. 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...
1721
			}
1722
1723
			// Remove discounts.
1724
			$subtotal = $subtotal - $this->get_total_discount();
1725
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1726
		}
1727
1728
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1729
	}
1730
1731
	/**
1732
	 * Gets shipping (formatted).
1733
	 *
1734
	 * @param string $tax_display Excl or incl tax display mode.
1735
	 * @return string
1736
	 */
1737
	public function get_shipping_to_display( $tax_display = '' ) {
1738
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1739
1740
		if ( 0 < abs( (float) $this->get_shipping_total() ) ) {
1741
1742
			if ( 'excl' === $tax_display ) {
1743
1744
				// Show shipping excluding tax.
1745
				$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
1746
1747 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
1748
					$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 );
1749
				}
1750
			} else {
1751
1752
				// Show shipping including tax.
1753
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
1754
1755 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
1756
					$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 );
1757
				}
1758
			}
1759
1760
			/* translators: %s: method */
1761
			$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 );
1762
1763
		} elseif ( $this->get_shipping_method() ) {
1764
			$shipping = $this->get_shipping_method();
1765
		} else {
1766
			$shipping = __( 'Free!', 'woocommerce' );
1767
		}
1768
1769
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this, $tax_display );
1770
	}
1771
1772
	/**
1773
	 * Get the discount amount (formatted).
1774
	 *
1775
	 * @since  2.3.0
1776
	 * @param string $tax_display Excl or incl tax display mode.
1777
	 * @return string
1778
	 */
1779
	public function get_discount_to_display( $tax_display = '' ) {
1780
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1781
		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 );
1782
	}
1783
1784
	/**
1785
	 * Add total row for subtotal.
1786
	 *
1787
	 * @param array  $total_rows Reference to total rows array.
1788
	 * @param string $tax_display Excl or incl tax display mode.
1789
	 */
1790
	protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) {
1791
		$subtotal = $this->get_subtotal_to_display( false, $tax_display );
1792
1793
		if ( $subtotal ) {
1794
			$total_rows['cart_subtotal'] = array(
1795
				'label' => __( 'Subtotal:', 'woocommerce' ),
1796
				'value' => $subtotal,
1797
			);
1798
		}
1799
	}
1800
1801
	/**
1802
	 * Add total row for discounts.
1803
	 *
1804
	 * @param array  $total_rows Reference to total rows array.
1805
	 * @param string $tax_display Excl or incl tax display mode.
1806
	 */
1807
	protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) {
1808
		if ( $this->get_total_discount() > 0 ) {
1809
			$total_rows['discount'] = array(
1810
				'label' => __( 'Discount:', 'woocommerce' ),
1811
				'value' => '-' . $this->get_discount_to_display( $tax_display ),
1812
			);
1813
		}
1814
	}
1815
1816
	/**
1817
	 * Add total row for shipping.
1818
	 *
1819
	 * @param array  $total_rows Reference to total rows array.
1820
	 * @param string $tax_display Excl or incl tax display mode.
1821
	 */
1822
	protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) {
1823
		if ( $this->get_shipping_method() ) {
1824
			$total_rows['shipping'] = array(
1825
				'label' => __( 'Shipping:', 'woocommerce' ),
1826
				'value' => $this->get_shipping_to_display( $tax_display ),
1827
			);
1828
		}
1829
	}
1830
1831
	/**
1832
	 * Add total row for fees.
1833
	 *
1834
	 * @param array  $total_rows Reference to total rows array.
1835
	 * @param string $tax_display Excl or incl tax display mode.
1836
	 */
1837
	protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) {
1838
		$fees = $this->get_fees();
1839
1840
		if ( $fees ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fees of type WC_Order_Item[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1841
			foreach ( $fees as $id => $fee ) {
1842
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1843
					continue;
1844
				}
1845
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1846
					'label' => $fee->get_name() . ':',
1847
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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_Order_Item as the method get_total_tax() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1848
				);
1849
			}
1850
		}
1851
	}
1852
1853
	/**
1854
	 * Add total row for taxes.
1855
	 *
1856
	 * @param array  $total_rows Reference to total rows array.
1857
	 * @param string $tax_display Excl or incl tax display mode.
1858
	 */
1859
	protected function add_order_item_totals_tax_rows( &$total_rows, $tax_display ) {
1860
		// Tax for tax exclusive prices.
1861
		if ( 'excl' === $tax_display && wc_tax_enabled() ) {
1862
			if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
1863
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1864
					$total_rows[ sanitize_title( $code ) ] = array(
1865
						'label' => $tax->label . ':',
1866
						'value' => $tax->formatted_amount,
1867
					);
1868
				}
1869
			} else {
1870
				$total_rows['tax'] = array(
1871
					'label' => WC()->countries->tax_or_vat() . ':',
1872
					'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1873
				);
1874
			}
1875
		}
1876
	}
1877
1878
	/**
1879
	 * Add total row for grand total.
1880
	 *
1881
	 * @param array  $total_rows Reference to total rows array.
1882
	 * @param string $tax_display Excl or incl tax display mode.
1883
	 */
1884
	protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) {
1885
		$total_rows['order_total'] = array(
1886
			'label' => __( 'Total:', 'woocommerce' ),
1887
			'value' => $this->get_formatted_order_total( $tax_display ),
1888
		);
1889
	}
1890
1891
	/**
1892
	 * Get totals for display on pages and in emails.
1893
	 *
1894
	 * @param mixed $tax_display Excl or incl tax display mode.
1895
	 * @return array
1896
	 */
1897
	public function get_order_item_totals( $tax_display = '' ) {
1898
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1899
		$total_rows  = array();
1900
1901
		$this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
1902
		$this->add_order_item_totals_discount_row( $total_rows, $tax_display );
1903
		$this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
1904
		$this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
1905
		$this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
1906
		$this->add_order_item_totals_total_row( $total_rows, $tax_display );
1907
1908
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display );
1909
	}
1910
1911
	/*
1912
	|--------------------------------------------------------------------------
1913
	| Conditionals
1914
	|--------------------------------------------------------------------------
1915
	|
1916
	| Checks if a condition is true or false.
1917
	|
1918
	*/
1919
1920
	/**
1921
	 * Checks the order status against a passed in status.
1922
	 *
1923
	 * @param array|string $status Status to check.
1924
	 * @return bool
1925
	 */
1926 30
	public function has_status( $status ) {
1927 30
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status );
1928
	}
1929
1930
	/**
1931
	 * Check whether this order has a specific shipping method or not.
1932
	 *
1933
	 * @param string $method_id Method ID to check.
1934
	 * @return bool
1935
	 */
1936 1
	public function has_shipping_method( $method_id ) {
1937 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1938 1
			if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_method_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Shipping. 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...
introduced by
Found "=== 0". Use Yoda Condition checks, you must
Loading history...
1939 1
				return true;
1940
			}
1941
		}
1942 1
		return false;
1943
	}
1944
1945
	/**
1946
	 * Returns true if the order contains a free product.
1947
	 *
1948
	 * @since 2.5.0
1949
	 * @return bool
1950
	 */
1951 2
	public function has_free_item() {
1952 2
		foreach ( $this->get_items() as $item ) {
1953 1
			if ( ! $item->get_total() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. 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...
1954 1
				return true;
1955
			}
1956
		}
1957 2
		return false;
1958
	}
1959
}
1960