Completed
Push — master ( fb5f9c...377d79 )
by Mike
64:31 queued 55:52
created

WC_Abstract_Order::set_item_discount_amounts()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
nc 5
nop 1
dl 0
loc 23
ccs 9
cts 9
cp 1
crap 6
rs 8.9297
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 244
	public function __construct( $order = 0 ) {
96 244
		parent::__construct( $order );
97
98 244 View Code Duplication
		if ( is_numeric( $order ) && $order > 0 ) {
99 86
			$this->set_id( $order );
100 244
		} elseif ( $order instanceof self ) {
101
			$this->set_id( $order->get_id() );
102 244
		} elseif ( ! empty( $order->ID ) ) {
103
			$this->set_id( $order->ID );
104
		} else {
105 244
			$this->set_object_read( true );
106
		}
107
108 244
		$this->data_store = WC_Data_Store::load( $this->data_store_name );
109
110 244
		if ( $this->get_id() > 0 ) {
111 86
			$this->data_store->read( $this );
112
		}
113
	}
114
115
	/**
116
	 * Get internal type.
117
	 *
118
	 * @return string
119
	 */
120 162
	public function get_type() {
121 162
		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 161
	protected function save_items() {
186 161
		$items_changed = false;
187
188 161
		foreach ( $this->items_to_delete as $item ) {
189 12
			$item->delete();
190 12
			$items_changed = true;
191
		}
192 161
		$this->items_to_delete = array();
193
194
		// Add/save items.
195 161
		foreach ( $this->items as $item_group => $items ) {
196 129
			if ( is_array( $items ) ) {
197 129
				$items = array_filter( $items );
198 129
				foreach ( $items as $item_key => $item ) {
199 118
					$item->set_order_id( $this->get_id() );
200
201 118
					$item_id = $item->save();
202
203
					// If ID changed (new item saved to DB)...
204 118
					if ( $item_id !== $item_key ) {
205 114
						$this->items[ $item_group ][ $item_id ] = $item;
206
207 114
						unset( $this->items[ $item_group ][ $item_key ] );
208
209 129
						$items_changed = true;
210
					}
211
				}
212
			}
213
		}
214
215 161
		if ( $items_changed ) {
216 116
			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 161
	public function get_parent_id( $context = 'view' ) {
234 161
		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 163
	public function get_currency( $context = 'view' ) {
244 163
		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 162
	public function get_version( $context = 'view' ) {
254 162
		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 162
	public function get_prices_include_tax( $context = 'view' ) {
264 162
		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 163
	public function get_date_created( $context = 'view' ) {
274 163
		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 167
	public function get_status( $context = 'view' ) {
294 167
		$status = $this->get_prop( 'status', $context );
295
296 167
		if ( empty( $status ) && 'view' === $context ) {
297
			// In view context, return the default status if no status has been set.
298 135
			$status = apply_filters( 'woocommerce_default_order_status', 'pending' );
299
		}
300 167
		return $status;
301
	}
302
303
	/**
304
	 * Get discount_total.
305
	 *
306
	 * @param  string $context View or edit context.
307
	 * @return string
308
	 */
309 163
	public function get_discount_total( $context = 'view' ) {
310 163
		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 163
	public function get_discount_tax( $context = 'view' ) {
320 163
		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 162
	public function get_shipping_total( $context = 'view' ) {
330 162
		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 164
	public function get_shipping_tax( $context = 'view' ) {
340 164
		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 164
	public function get_cart_tax( $context = 'view' ) {
350 164
		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 164
	public function get_total( $context = 'view' ) {
360 164
		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 3
	public function get_total_tax( $context = 'view' ) {
370 3
		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 117
	protected function get_valid_statuses() {
448 117
		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 86
	public function set_parent_id( $value ) {
489 86
		if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
490
			$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
491
		}
492 86
		$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 134
	public function set_status( $new_status ) {
503 134
		$old_status = $this->get_status();
504 134
		$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 134
		if ( true === $this->object_read ) {
508
			// Only allow valid new status.
509 117 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 117 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 134
		$this->set_prop( 'status', $new_status );
520
521
		return array(
522 134
			'from' => $old_status,
523 134
			'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 162
	public function set_version( $value ) {
534 162
		$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 163
	public function set_currency( $value ) {
544 163
		if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ), true ) ) {
545
			$this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
546
		}
547 163
		$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 129
	public function set_prices_include_tax( $value ) {
557 129
		$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 163
	public function set_date_created( $date = null ) {
567 163
		$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 87
	public function set_date_modified( $date = null ) {
577 87
		$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 135
	public function set_discount_total( $value ) {
587 135
		$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 135
	public function set_discount_tax( $value ) {
597 135
		$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 135
	public function set_shipping_total( $value ) {
607 135
		$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 138
	public function set_shipping_tax( $value ) {
617 138
		$this->set_prop( 'shipping_tax', wc_format_decimal( $value ) );
618 138
		$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 138
	public function set_cart_tax( $value ) {
628 138
		$this->set_prop( 'cart_tax', wc_format_decimal( $value ) );
629 138
		$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 139
	protected function set_total_tax( $value ) {
639 139
		$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 136
	public function set_total( $value, $deprecated = '' ) {
652 136
		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 136
		$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 140
	protected function type_to_group( $type ) {
695 140
		$type_to_group = apply_filters(
696 140
			'woocommerce_order_type_to_group',
697
			array(
698
				'line_item' => 'line_items',
699
				'tax'       => 'tax_lines',
700
				'shipping'  => 'shipping_lines',
701
				'fee'       => 'fee_lines',
702 140
				'coupon'    => 'coupon_lines',
703
			)
704
		);
705
		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 140
	 * @param string|array $types Types of line items to get (array or string).
712 140
	 * @return WC_Order_Item[]
713 140
	 */
714
	public function get_items( $types = 'line_item' ) {
715 140
		$items = array();
716 140
		$types = array_filter( (array) $types );
717
718 140
		foreach ( $types as $type ) {
719 140
			$group = $this->type_to_group( $type );
720 140
721
			if ( $group ) {
722
				if ( ! isset( $this->items[ $group ] ) ) {
723 140
					$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
				$items = $items + $this->items[ $group ];
727 140
			}
728
		}
729
730
		return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
731
	}
732
733
	/**
734
	 * Return an array of fees within this order.
735 36
	 *
736 36
	 * @return WC_Order_item_Fee[]
737
	 */
738
	public function get_fees() {
739
		return $this->get_items( 'fee' );
740
	}
741
742
	/**
743
	 * Return an array of taxes within this order.
744 38
	 *
745 38
	 * @return WC_Order_Item_Tax[]
746
	 */
747
	public function get_taxes() {
748
		return $this->get_items( 'tax' );
749
	}
750
751
	/**
752
	 * Return an array of shipping costs within this order.
753 43
	 *
754 43
	 * @return WC_Order_Item_Shipping[]
755
	 */
756
	public function get_shipping_methods() {
757
		return $this->get_items( 'shipping' );
758
	}
759
760
	/**
761
	 * Gets formatted shipping method title.
762 1
	 *
763 1
	 * @return string
764 1
	 */
765 1
	public function get_shipping_method() {
766
		$names = array();
767 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
768
			$names[] = $shipping_method->get_name();
769
		}
770
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
771
	}
772
773
	/**
774
	 * Get coupon codes only.
775 22
	 *
776 22
	 * @return array
777 22
	 */
778
	public function get_used_coupons() {
779 22
		$coupon_codes = array();
780 2
		$coupons      = $this->get_items( 'coupon' );
781 2
782
		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...
783
			foreach ( $coupons as $coupon ) {
784 22
				$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...
785
			}
786
		}
787
		return $coupon_codes;
788
	}
789
790
	/**
791
	 * Gets the count of order items of a certain type.
792
	 *
793 10
	 * @param string $item_type Item type to lookup.
794 10
	 * @return int|string
795 10
	 */
796
	public function get_item_count( $item_type = '' ) {
797 10
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
798 7
		$count = 0;
799
800
		foreach ( $items as $item ) {
801 10
			$count += $item->get_quantity();
802
		}
803
804
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
805
	}
806
807
	/**
808
	 * Get an order item object, based on its type.
809
	 *
810
	 * @since  3.0.0
811
	 * @param  int  $item_id ID of item to get.
812 20
	 * @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.
813 20
	 * @return WC_Order_Item|false
814 1
	 */
815
	public function get_item( $item_id, $load_from_db = true ) {
816
		if ( $load_from_db ) {
817
			return WC_Order_Factory::get_order_item( $item_id );
818 19
		}
819 16
820 16
		// Search for item id.
821 16
		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...
822
			foreach ( $this->items as $group => $items ) {
823
				if ( isset( $items[ $item_id ] ) ) {
824
					return $items[ $item_id ];
825
				}
826
			}
827 6
		}
828
829 6
		// Load all items of type and cache.
830
		$type = $this->data_store->get_order_item_type( $this, $item_id );
831
832
		if ( ! $type ) {
833 6
			return false;
834
		}
835 6
836
		$items = $this->get_items( $type );
837
838
		return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
839
	}
840
841
	/**
842
	 * Get key for where a certain item type is stored in _items.
843
	 *
844
	 * @since  3.0.0
845 121
	 * @param  string $item object Order item (product, shipping, fee, coupon, tax).
846 121
	 * @return string
847 114
	 */
848 113
	protected function get_items_key( $item ) {
849 4
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
850 111
			return 'line_items';
851 99
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
852 22
			return 'fee_lines';
853 10
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
854 17
			return 'shipping_lines';
855 17
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
856
			return 'tax_lines';
857 1
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
858
			return 'coupon_lines';
859
		}
860
		return apply_filters( 'woocommerce_get_items_key', '', $item );
861
	}
862
863
	/**
864
	 * Remove item from the order.
865
	 *
866 12
	 * @param int $item_id Item ID to delete.
867 12
	 * @return false|void
868 12
	 */
869
	public function remove_item( $item_id ) {
870 12
		$item      = $this->get_item( $item_id, false );
871
		$items_key = $item ? $this->get_items_key( $item ) : false;
872
873
		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...
874
			return false;
875 12
		}
876 12
877
		// Unset and remove later.
878
		$this->items_to_delete[] = $item;
879
		unset( $this->items[ $items_key ][ $item->get_id() ] );
880
	}
881
882
	/**
883
	 * Adds an order item to this order. The order item will not persist until save.
884
	 *
885
	 * @since 3.0.0
886 121
	 * @param WC_Order_Item $item Order item object (product, shipping, fee, coupon, tax).
887 121
	 * @return false|void
888
	 */
889 121
	public function add_item( $item ) {
890 1
		$items_key = $this->get_items_key( $item );
891
892
		if ( ! $items_key ) {
893
			return false;
894 121
		}
895 120
896
		// Make sure existing items are loaded so we can append this new one.
897
		if ( ! isset( $this->items[ $items_key ] ) ) {
898
			$this->items[ $items_key ] = $this->get_items( $item->get_type() );
899 121
		}
900
901
		// Set parent.
902 121
		$item->set_order_id( $this->get_id() );
903
904 121
		// Append new row with generated temporary ID.
905 104
		$item_id = $item->get_id();
906
907 116
		if ( $item_id ) {
908
			$this->items[ $items_key ][ $item_id ] = $item;
909
		} else {
910
			$this->items[ $items_key ][ 'new:' . $items_key . count( $this->items[ $items_key ] ) ] = $item;
911
		}
912
	}
913
914
	/**
915
	 * Apply a coupon to the order and recalculate totals.
916
	 *
917
	 * @since 3.2.0
918 11
	 * @param string|WC_Coupon $raw_coupon Coupon code or object.
919 11
	 * @return true|WP_Error True if applied, error if not.
920 3
	 */
921 8
	public function apply_coupon( $raw_coupon ) {
922 8
		if ( is_a( $raw_coupon, 'WC_Coupon' ) ) {
923 8
			$coupon = $raw_coupon;
924
		} elseif ( is_string( $raw_coupon ) ) {
925 8
			$code   = wc_format_coupon_code( $raw_coupon );
926
			$coupon = new WC_Coupon( $code );
927
928
			if ( $coupon->get_code() !== $code ) {
929 8
				return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) );
930 8
			}
931
932 8
			$discounts = new WC_Discounts( $this );
933 8
			$valid     = $discounts->is_coupon_valid( $coupon );
934
935
			if ( is_wp_error( $valid ) ) {
936
				return $valid;
937
			}
938
		} else {
939
			return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
940 10
		}
941 10
942 4
		// Check to make sure coupon is not already applied.
943 4
		$applied_coupons = $this->get_items( 'coupon' );
944
		foreach ( $applied_coupons as $applied_coupon ) {
945
			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...
946
				return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) );
947 10
			}
948 10
		}
949
950 10
		$discounts = new WC_Discounts( $this );
951
		$applied   = $discounts->apply_coupon( $coupon );
0 ignored issues
show
Bug introduced by
It seems like $coupon defined by $raw_coupon on line 923 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...
952
953
		if ( is_wp_error( $applied ) ) {
954 10
			return $applied;
955 10
		}
956
957
		$this->set_coupon_discount_amounts( $discounts );
958 10
		$this->save();
959
960
		// Recalculate totals and taxes.
961 10
		$this->recalculate_coupons();
962
963 10
		// Record usage so counts and validation is correct.
964
		$used_by = $this->get_user_id();
965
966
		if ( ! $used_by ) {
967 10
			$used_by = $this->get_billing_email();
968
		}
969 10
970
		$coupon->increase_usage_count( $used_by );
971
972
		return true;
973
	}
974
975
	/**
976
	 * Remove a coupon from the order and recalculate totals.
977
	 *
978
	 * Coupons affect line item totals, but there is no relationship between
979
	 * coupon and line total, so to remove a coupon we need to work from the
980
	 * line subtotal (price before discount) and re-apply all coupons in this
981
	 * order.
982
	 *
983
	 * Manual discounts are not affected; those are separate and do not affect
984
	 * stored line totals.
985
	 *
986
	 * @since  3.2.0
987 7
	 * @param  string $code Coupon code.
988 7
	 * @return void
989
	 */
990
	public function remove_coupon( $code ) {
991 7
		$coupons = $this->get_items( 'coupon' );
992 7
993 7
		// Remove the coupon line.
994 7
		foreach ( $coupons as $item_id => $coupon ) {
995 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...
996 7
				$this->remove_item( $item_id );
997 7
				$coupon_object = new WC_Coupon( $code );
998
				$coupon_object->decrease_usage_count( $this->get_user_id() );
999
				$this->recalculate_coupons();
1000
				break;
1001
			}
1002
		}
1003
	}
1004
1005
	/**
1006
	 * Apply all coupons in this order again to all line items.
1007 7
	 *
1008
	 * @since  3.2.0
1009 7
	 */
1010 7
	protected function recalculate_coupons() {
1011 7
		// Reset line item totals.
1012
		foreach ( $this->get_items() as $item ) {
1013
			$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...
1014 7
			$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...
1015
		}
1016 7
1017 4
		$discounts = new WC_Discounts( $this );
1018 4
1019
		foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
1020
			$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...
1021 4
			$coupon_id   = wc_get_coupon_id_by_code( $coupon_code );
1022 4
1023
			// 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.
1024
			if ( $coupon_id ) {
1025
				$coupon_object = new WC_Coupon( $coupon_id );
1026
1027 4
			} else {
1028 4
1029 4
				// 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.
1030 4
				$coupon_object = new WC_Coupon();
1031
				$coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
1032
				$coupon_object->set_code( $coupon_code );
1033 4
				$coupon_object->set_virtual( true );
1034
1035
				// If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
1036 4
				if ( ! $coupon_object->get_amount() ) {
1037 2
1038
					// If the order originally had prices including tax, remove the discount + discount tax.
1039 2
					if ( $this->get_prices_include_tax() ) {
1040
						$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...
1041 4
					} else {
1042
						$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...
1043
					}
1044
					$coupon_object->set_discount_type( 'fixed_cart' );
1045
				}
1046
			}
1047
1048
			/**
1049
			 * Allow developers to filter this coupon before it get's re-applied to the order.
1050 4
			 *
1051
			 * @since 3.2.0
1052 4
			 */
1053 4
			$coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
1054
1055
			if ( $coupon_object ) {
1056
				$discounts->apply_coupon( $coupon_object, false );
1057 7
			}
1058 7
		}
1059
1060
		$this->set_coupon_discount_amounts( $discounts );
1061 7
		$this->set_item_discount_amounts( $discounts );
1062
1063
		// Recalculate totals and taxes.
1064
		$this->calculate_totals( true );
1065
	}
1066
1067
	/**
1068
	 * After applying coupons via the WC_Discounts class, update line items.
1069
	 *
1070 14
	 * @since 3.2.0
1071 14
	 * @param WC_Discounts $discounts Discounts class.
1072
	 */
1073 14
	protected function set_item_discount_amounts( $discounts ) {
1074 14
		$item_discounts = $discounts->get_discounts_by_item();
1075 14
1076
		if ( $item_discounts ) {
1077
			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...
1078 14
				$item = $this->get_item( $item_id, false );
1079 2
1080 2
				// If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
1081 2
				if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1082
					$taxes = WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1083 14
1084
					if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1085
						$taxes = array_map( 'wc_round_tax_total', $taxes );
1086
					}
1087
1088
					$amount = $amount - array_sum( $taxes );
1089
					$item->set_total( max( 0, round( $item->get_total() - $amount, wc_get_price_decimals() ) ) );
1090
				} else {
1091
					$item->set_total( max( 0, $item->get_total() - $amount ) );
1092
				}
1093
			}
1094
		}
1095 14
	}
1096 14
1097 14
	/**
1098 14
	 * After applying coupons via the WC_Discounts class, update or create coupon items.
1099 14
	 *
1100
	 * @since 3.2.0
1101 14
	 * @param WC_Discounts $discounts Discounts class.
1102 14
	 */
1103 14
	protected function set_coupon_discount_amounts( $discounts ) {
1104
		$coupons           = $this->get_items( 'coupon' );
1105 14
		$coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
1106 10
		$all_discounts     = $discounts->get_discounts();
1107 10
		$coupon_discounts  = $discounts->get_discounts_by_coupon();
1108
1109 4
		if ( $coupon_discounts ) {
1110
			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...
1111
				$item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0;
1112 14
1113
				if ( ! $item_id ) {
1114
					$coupon_item = new WC_Order_Item_Coupon();
1115 14
					$coupon_item->set_code( $coupon_code );
1116 14
				} else {
1117
					$coupon_item = $this->get_item( $item_id, false );
1118 14
				}
1119 2
1120 2
				$discount_tax = 0;
1121 2
1122
				// Work out how much tax has been removed as a result of the discount from this coupon.
1123 14
				foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
1124
					$item = $this->get_item( $item_id, false );
1125
1126
					if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1127 14
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1128 14
1129
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1130 14
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1131
						}
1132
1133
						$discount_tax += array_sum( $taxes );
1134
						$amount        = $amount - array_sum( $taxes );
1135
					} else {
1136
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) );
1137
1138
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1139
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1140
						}
1141
1142
						$discount_tax += array_sum( $taxes );
1143
					}
1144
				}
1145 6
1146 6
				$coupon_item->set_discount( $amount );
1147
				$coupon_item->set_discount_tax( $discount_tax );
1148 6
1149 6
				$this->add_item( $coupon_item );
1150 6
			}
1151 6
		}
1152 6
	}
1153 6
1154 6
	/**
1155 6
	 * Add a product line item to the order. This is the only line item type with
1156
	 * its own method because it saves looking up order amounts (costs are added up for you).
1157
	 *
1158
	 * @param  WC_Product $product Product object.
1159
	 * @param  int        $qty Quantity to add.
1160
	 * @param  array      $args Args for the added product.
1161
	 * @return int
1162
	 * @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart.
1163 6
	 */
1164
	public function add_product( $product, $qty = 1, $args = array() ) {
1165
		if ( $product ) {
1166 6
			$default_args = array(
1167
				'name'         => $product->get_name(),
1168
				'tax_class'    => $product->get_tax_class(),
1169
				'product_id'   => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
1170
				'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
1171
				'variation'    => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
1172
				'subtotal'     => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1173
				'total'        => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1174
				'quantity'     => $qty,
1175
			);
1176
		} else {
1177
			$default_args = array(
1178 6
				'quantity' => $qty,
1179 6
			);
1180 6
		}
1181 6
1182 6
		$args = wp_parse_args( $args, $default_args );
1183 6
1184 6
		// BW compatibility with old args.
1185 6
		if ( isset( $args['totals'] ) ) {
1186 6
			foreach ( $args['totals'] as $key => $value ) {
1187
				if ( 'tax' === $key ) {
1188
					$args['total_tax'] = $value;
1189
				} elseif ( 'tax_data' === $key ) {
1190
					$args['taxes'] = $value;
1191
				} else {
1192
					$args[ $key ] = $value;
1193
				}
1194
			}
1195
		}
1196
1197
		$item = new WC_Order_Item_Product();
1198
		$item->set_props( $args );
1199
		$item->set_backorder_meta();
1200
		$item->set_order_id( $this->get_id() );
1201
		$item->save();
1202
		$this->add_item( $item );
1203
		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' );
1204
		delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
1205 3
		return $item->get_id();
1206 3
	}
1207 1
1208
	/*
1209
	|--------------------------------------------------------------------------
1210 3
	| Payment Token Handling
1211 3
	|--------------------------------------------------------------------------
1212 3
	|
1213
	| Payment tokens are hashes used to take payments by certain gateways.
1214 3
	|
1215 3
	*/
1216
1217
	/**
1218
	 * Add a payment token to an order
1219
	 *
1220
	 * @since 2.6
1221
	 * @param WC_Payment_Token $token Payment token object.
1222
	 * @return boolean|int The new token ID or false if it failed.
1223
	 */
1224 4
	public function add_payment_token( $token ) {
1225 4
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1226
			return false;
1227
		}
1228
1229
		$token_ids   = $this->data_store->get_payment_token_ids( $this );
1230
		$token_ids[] = $token->get_id();
1231
		$this->data_store->update_payment_token_ids( $this, $token_ids );
1232
1233
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1234
		return $token->get_id();
1235
	}
1236
1237
	/**
1238
	 * Returns a list of all payment tokens associated with the current order
1239
	 *
1240
	 * @since 2.6
1241
	 * @return array An array of payment token objects
1242
	 */
1243 1
	public function get_payment_tokens() {
1244 1
		return $this->data_store->get_payment_token_ids( $this );
1245
	}
1246 1
1247 1
	/*
1248
	|--------------------------------------------------------------------------
1249
	| Calculations.
1250 1
	|--------------------------------------------------------------------------
1251 1
	|
1252
	| These methods calculate order totals and taxes based on the current data.
1253 1
	|
1254
	*/
1255
1256
	/**
1257
	 * Calculate shipping total.
1258
	 *
1259
	 * @since 2.2
1260
	 * @return float
1261
	 */
1262 32
	public function calculate_shipping() {
1263 32
		$shipping_total = 0;
1264
1265 32
		foreach ( $this->get_shipping_methods() as $shipping ) {
1266 32
			$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...
1267 32
		}
1268
1269
		$this->set_shipping_total( $shipping_total );
1270
		$this->save();
1271 32
1272
		return $this->get_shipping_total();
1273
	}
1274
1275
	/**
1276
	 * Get all tax classes for items in the order.
1277
	 *
1278
	 * @since 2.6.3
1279
	 * @return array
1280
	 */
1281 31
	public function get_items_tax_classes() {
1282 31
		$found_tax_classes = array();
1283
1284 31
		foreach ( $this->get_items() as $item ) {
1285 18
			if ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) {
1286
				$found_tax_classes[] = $item->get_tax_class();
1287
			}
1288 31
		}
1289 31
1290 31
		return array_unique( $found_tax_classes );
1291 31
	}
1292 31
1293
	/**
1294
	 * Get tax location for this order.
1295
	 *
1296 31
	 * @since 3.2.0
1297 15
	 * @param array $args array Override the location.
1298 15
	 * @return array
1299 15
	 */
1300 15
	protected function get_tax_location( $args = array() ) {
1301 15
		$tax_based_on = get_option( 'woocommerce_tax_based_on' );
1302
1303
		if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
1304 31
			$tax_based_on = 'billing';
1305
		}
1306
1307
		$args = wp_parse_args(
1308
			$args,
1309
			array(
1310
				'country'  => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
1311
				'state'    => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
1312
				'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
1313
				'city'     => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(),
1314
			)
1315
		);
1316
1317 31
		// Default to base.
1318 31
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1319
			$default          = wc_get_base_location();
1320 31
			$args['country']  = $default['country'];
1321 31
			$args['state']    = $default['state'];
1322
			$args['postcode'] = '';
1323 31
			$args['city']     = '';
1324 31
		}
1325 31
1326
		return $args;
1327
	}
1328 31
1329
	/**
1330
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1331 31
	 *
1332 31
	 * If by default the taxes are based on the shipping address and the current order doesn't
1333 31
	 * have any, it would use the billing address rather than using the Shopping base location.
1334
	 *
1335 31
	 * Will use the base country unless customer addresses are set.
1336
	 *
1337
	 * @param array $args Added in 3.0.0 to pass things like location.
1338
	 */
1339 31
	public function calculate_taxes( $args = array() ) {
1340 19
		do_action( 'woocommerce_order_before_calculate_taxes', $args, $this );
1341 19
1342
		$calculate_tax_for  = $this->get_tax_location( $args );
1343 19
		$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1344
1345
		if ( 'inherit' === $shipping_tax_class ) {
1346
			$found_classes      = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
1347 31
			$shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
1348
		}
1349
1350
		$is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
1351
1352
		// Trigger tax recalculation for all items.
1353 38
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1354 38
			if ( ! $is_vat_exempt ) {
1355 38
				$item->calculate_taxes( $calculate_tax_for );
1356 38
			} else {
1357 38
				$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...
1358
			}
1359 38
		}
1360 31
1361 31
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1362 10
			if ( false !== $shipping_tax_class && ! $is_vat_exempt ) {
1363
				$item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
1364 10
			} else {
1365 10
				$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...
1366
			}
1367
		}
1368 31
1369
		$this->update_taxes();
1370
	}
1371
1372 38
	/**
1373 19
	 * Update tax lines for the order based on the line item taxes themselves.
1374 19
	 */
1375 4
	public function update_taxes() {
1376
		$cart_taxes     = array();
1377 4
		$shipping_taxes = array();
1378 4
		$existing_taxes = $this->get_taxes();
1379
		$saved_rate_ids = array();
1380
1381 19 View Code Duplication
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1382
			$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...
1383
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1384
				$tax_amount = (float) $tax;
1385 38
1386
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1387 5
					$tax_amount = wc_round_tax_total( $tax_amount );
1388 1
				}
1389 1
1390
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1391 4
			}
1392 4
		}
1393 4
1394 4 View Code Duplication
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1395
			$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...
1396
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1397 38
				$tax_amount = (float) $tax;
1398
1399
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1400 38
					$tax_amount = wc_round_tax_total( $tax_amount );
1401 10
				}
1402 10
1403 10
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1404 10
			}
1405 10
		}
1406
1407
		foreach ( $existing_taxes as $tax ) {
1408 38
			// Remove taxes which no longer exist for cart/shipping.
1409 38
			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...
1410 38
				$this->remove_item( $tax->get_id() );
1411
				continue;
1412
			}
1413
			$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...
1414
			$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...
1415
			$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...
1416 38
			$tax->save();
1417
		}
1418
1419
		$new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) );
1420
1421
		// New taxes.
1422
		foreach ( $new_rate_ids as $tax_rate_id ) {
1423
			$item = new WC_Order_Item_Tax();
1424
			$item->set_rate( $tax_rate_id );
1425
			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
1426 35
			$item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1427 35
			$this->add_item( $item );
1428
		}
1429 35
1430 35
		$this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) );
1431 35
		$this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) );
1432 35
		$this->save();
1433 35
	}
1434 35
1435
	/**
1436
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1437 35
	 *
1438 28
	 * @since 2.2
1439 28
	 * @param  bool $and_taxes Calc taxes if true.
1440
	 * @return float calculated grand total.
1441
	 */
1442
	public function calculate_totals( $and_taxes = true ) {
1443 35
		do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
1444 17
1445
		$cart_subtotal     = 0;
1446
		$cart_total        = 0;
1447 35
		$fee_total         = 0;
1448
		$shipping_total    = 0;
1449
		$cart_subtotal_tax = 0;
1450 35
		$cart_total_tax    = 0;
1451
1452
		// Sum line item costs.
1453
		foreach ( $this->get_items() as $item ) {
1454
			$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...
1455
			$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...
1456
		}
1457
1458
		// Sum shipping costs.
1459
		foreach ( $this->get_shipping_methods() as $shipping ) {
1460
			$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...
1461
		}
1462
1463
		$this->set_shipping_total( $shipping_total );
1464
1465
		// Sum fee costs.
1466 35
		foreach ( $this->get_fees() as $item ) {
1467 28
			$amount = $item->get_amount();
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_amount() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee. 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...
1468
1469
			if ( 0 > $amount ) {
1470
				$item->set_total( $amount );
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...
1471 35
				$max_discount = round( $cart_total + $fee_total + $shipping_total, wc_get_price_decimals() ) * -1;
1472 28
1473 28
				if ( $item->get_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 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...
1474
					$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...
1475
				}
1476 35
			}
1477 35
1478 35
			$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...
1479
		}
1480 35
1481
		// Calculate taxes for items, shipping, discounts. Note; this also triggers save().
1482 35
		if ( $and_taxes ) {
1483
			$this->calculate_taxes();
1484 35
		}
1485
1486
		// Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp.
1487
		foreach ( $this->get_items() as $item ) {
1488
			$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...
1489
1490
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1491
				$cart_total_tax += (float) $tax;
1492
			}
1493
1494
			foreach ( $taxes['subtotal'] as $tax_rate_id => $tax ) {
1495
				$cart_subtotal_tax += (float) $tax;
1496
			}
1497
		}
1498
1499
		$this->set_discount_total( $cart_subtotal - $cart_total );
1500
		$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
1501
		$this->set_total( round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
1502
1503
		do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this );
1504
1505
		$this->save();
1506
1507
		return $this->get_total();
1508
	}
1509
1510
	/**
1511
	 * Get item subtotal - this is the cost before discount.
1512
	 *
1513
	 * @param object $item Item to get total from.
1514
	 * @param bool   $inc_tax (default: false).
1515
	 * @param bool   $round (default: true).
1516
	 * @return float
1517
	 */
1518 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...
1519
		$subtotal = 0;
1520
1521
		if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) {
1522
			if ( $inc_tax ) {
1523
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity();
1524
			} else {
1525
				$subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity();
1526
			}
1527
1528
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1529
		}
1530
1531
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1532
	}
1533
1534
	/**
1535
	 * Get line subtotal - this is the cost before discount.
1536
	 *
1537
	 * @param object $item Item to get total from.
1538
	 * @param bool   $inc_tax (default: false).
1539
	 * @param bool   $round (default: true).
1540
	 * @return float
1541
	 */
1542 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...
1543
		$subtotal = 0;
1544
1545
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1546
			if ( $inc_tax ) {
1547
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1548
			} else {
1549
				$subtotal = $item->get_subtotal();
1550
			}
1551
1552
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1553
		}
1554
1555
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1556
	}
1557
1558
	/**
1559
	 * Calculate item cost - useful for gateways.
1560
	 *
1561
	 * @param object $item Item to get total from.
1562
	 * @param bool   $inc_tax (default: false).
1563
	 * @param bool   $round (default: true).
1564
	 * @return float
1565
	 */
1566 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...
1567
		$total = 0;
1568
1569
		if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) {
1570
			if ( $inc_tax ) {
1571
				$total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity();
1572
			} else {
1573
				$total = floatval( $item->get_total() ) / $item->get_quantity();
1574
			}
1575
1576
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1577
		}
1578
1579
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1580
	}
1581
1582
	/**
1583
	 * Calculate line total - useful for gateways.
1584
	 *
1585
	 * @param object $item Item to get total from.
1586
	 * @param bool   $inc_tax (default: false).
1587
	 * @param bool   $round (default: true).
1588
	 * @return float
1589
	 */
1590 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...
1591
		$total = 0;
1592
1593
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1594
			// Check if we need to add line tax to the line total.
1595
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1596
1597
			// Check if we need to round.
1598
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1599
		}
1600
1601
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1602
	}
1603
1604
	/**
1605
	 * Get item tax - useful for gateways.
1606
	 *
1607
	 * @param mixed $item Item to get total from.
1608
	 * @param bool  $round (default: true).
1609
	 * @return float
1610
	 */
1611
	public function get_item_tax( $item, $round = true ) {
1612
		$tax = 0;
1613
1614
		if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) {
1615
			$tax = $item->get_total_tax() / $item->get_quantity();
1616
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1617
		}
1618
1619
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1620
	}
1621
1622
	/**
1623
	 * Get line tax - useful for gateways.
1624
	 *
1625
	 * @param mixed $item Item to get total from.
1626
	 * @return float
1627
	 */
1628
	public function get_line_tax( $item ) {
1629
		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 );
1630
	}
1631
1632
	/**
1633
	 * Gets line subtotal - formatted for display.
1634
	 *
1635
	 * @param array  $item Item to get total from.
1636
	 * @param string $tax_display Incl or excl tax display mode.
1637
	 * @return string
1638
	 */
1639
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1640
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1641
1642
		if ( 'excl' === $tax_display ) {
1643
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1644
1645
			$subtotal = wc_price(
1646
				$this->get_line_subtotal( $item ),
1647
				array(
1648
					'ex_tax_label' => $ex_tax_label,
1649
					'currency'     => $this->get_currency(),
1650
				)
1651
			);
1652
		} else {
1653
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
1654
		}
1655
1656
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1657
	}
1658
1659
	/**
1660
	 * Gets order total - formatted for display.
1661
	 *
1662
	 * @return string
1663
	 */
1664
	public function get_formatted_order_total() {
1665
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1666
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1667
	}
1668
1669
	/**
1670
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1671
	 *
1672
	 * @param bool   $compound (default: false).
1673
	 * @param string $tax_display (default: the tax_display_cart value).
1674
	 * @return string
1675
	 */
1676
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1677
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1678
		$subtotal    = 0;
1679
1680
		if ( ! $compound ) {
1681
			foreach ( $this->get_items() as $item ) {
1682
				$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...
1683
1684
				if ( 'incl' === $tax_display ) {
1685
					$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...
1686
				}
1687
			}
1688
1689
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1690
1691 View Code Duplication
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) {
1692
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1693
			}
1694
		} else {
1695
			if ( 'incl' === $tax_display ) {
1696
				return '';
1697
			}
1698
1699
			foreach ( $this->get_items() as $item ) {
1700
				$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...
1701
			}
1702
1703
			// Add Shipping Costs.
1704
			$subtotal += $this->get_shipping_total();
1705
1706
			// Remove non-compound taxes.
1707
			foreach ( $this->get_taxes() as $tax ) {
1708
				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...
1709
					continue;
1710
				}
1711
				$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...
1712
			}
1713
1714
			// Remove discounts.
1715
			$subtotal = $subtotal - $this->get_total_discount();
1716
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1717
		}
1718
1719
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1720
	}
1721
1722
	/**
1723
	 * Gets shipping (formatted).
1724
	 *
1725
	 * @param string $tax_display Excl or incl tax display mode.
1726
	 * @return string
1727
	 */
1728
	public function get_shipping_to_display( $tax_display = '' ) {
1729
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1730
1731
		if ( 0 < abs( (float) $this->get_shipping_total() ) ) {
1732
1733
			if ( 'excl' === $tax_display ) {
1734
1735
				// Show shipping excluding tax.
1736
				$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
1737
1738 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
1739
					$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 );
1740
				}
1741
			} else {
1742
1743
				// Show shipping including tax.
1744
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
1745
1746 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
1747
					$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 );
1748
				}
1749
			}
1750
1751
			/* translators: %s: method */
1752
			$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 );
1753
1754
		} elseif ( $this->get_shipping_method() ) {
1755
			$shipping = $this->get_shipping_method();
1756
		} else {
1757
			$shipping = __( 'Free!', 'woocommerce' );
1758
		}
1759
1760
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this, $tax_display );
1761
	}
1762
1763
	/**
1764
	 * Get the discount amount (formatted).
1765
	 *
1766
	 * @since  2.3.0
1767
	 * @param string $tax_display Excl or incl tax display mode.
1768
	 * @return string
1769
	 */
1770
	public function get_discount_to_display( $tax_display = '' ) {
1771
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1772
		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 );
1773
	}
1774
1775
	/**
1776
	 * Add total row for subtotal.
1777
	 *
1778
	 * @param array  $total_rows Reference to total rows array.
1779
	 * @param string $tax_display Excl or incl tax display mode.
1780
	 */
1781
	protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) {
1782
		$subtotal = $this->get_subtotal_to_display( false, $tax_display );
1783
1784
		if ( $subtotal ) {
1785
			$total_rows['cart_subtotal'] = array(
1786
				'label' => __( 'Subtotal:', 'woocommerce' ),
1787
				'value' => $subtotal,
1788
			);
1789
		}
1790
	}
1791
1792
	/**
1793
	 * Add total row for discounts.
1794
	 *
1795
	 * @param array  $total_rows Reference to total rows array.
1796
	 * @param string $tax_display Excl or incl tax display mode.
1797
	 */
1798
	protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) {
1799
		if ( $this->get_total_discount() > 0 ) {
1800
			$total_rows['discount'] = array(
1801
				'label' => __( 'Discount:', 'woocommerce' ),
1802
				'value' => '-' . $this->get_discount_to_display( $tax_display ),
1803
			);
1804
		}
1805
	}
1806
1807
	/**
1808
	 * Add total row for shipping.
1809
	 *
1810
	 * @param array  $total_rows Reference to total rows array.
1811
	 * @param string $tax_display Excl or incl tax display mode.
1812
	 */
1813
	protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) {
1814
		if ( $this->get_shipping_method() ) {
1815
			$total_rows['shipping'] = array(
1816
				'label' => __( 'Shipping:', 'woocommerce' ),
1817
				'value' => $this->get_shipping_to_display( $tax_display ),
1818
			);
1819
		}
1820
	}
1821
1822
	/**
1823
	 * Add total row for fees.
1824
	 *
1825
	 * @param array  $total_rows Reference to total rows array.
1826
	 * @param string $tax_display Excl or incl tax display mode.
1827
	 */
1828
	protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) {
1829
		$fees = $this->get_fees();
1830
1831
		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...
1832
			foreach ( $fees as $id => $fee ) {
1833
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1834
					continue;
1835
				}
1836
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1837
					'label' => $fee->get_name() . ':',
1838
					'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...
1839
				);
1840
			}
1841
		}
1842
	}
1843
1844
	/**
1845
	 * Add total row for taxes.
1846
	 *
1847
	 * @param array  $total_rows Reference to total rows array.
1848
	 * @param string $tax_display Excl or incl tax display mode.
1849
	 */
1850
	protected function add_order_item_totals_tax_rows( &$total_rows, $tax_display ) {
1851
		// Tax for tax exclusive prices.
1852
		if ( 'excl' === $tax_display ) {
1853
			if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
1854
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1855
					$total_rows[ sanitize_title( $code ) ] = array(
1856
						'label' => $tax->label . ':',
1857
						'value' => $tax->formatted_amount,
1858
					);
1859
				}
1860
			} else {
1861
				$total_rows['tax'] = array(
1862
					'label' => WC()->countries->tax_or_vat() . ':',
1863
					'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1864
				);
1865
			}
1866
		}
1867
	}
1868
1869
	/**
1870
	 * Add total row for grand total.
1871
	 *
1872
	 * @param array  $total_rows Reference to total rows array.
1873
	 * @param string $tax_display Excl or incl tax display mode.
1874
	 */
1875
	protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) {
1876
		$total_rows['order_total'] = array(
1877
			'label' => __( 'Total:', 'woocommerce' ),
1878
			'value' => $this->get_formatted_order_total( $tax_display ),
1879
		);
1880
	}
1881
1882
	/**
1883
	 * Get totals for display on pages and in emails.
1884
	 *
1885
	 * @param mixed $tax_display Excl or incl tax display mode.
1886
	 * @return array
1887
	 */
1888
	public function get_order_item_totals( $tax_display = '' ) {
1889
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1890
		$total_rows  = array();
1891 30
1892 30
		$this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
1893
		$this->add_order_item_totals_discount_row( $total_rows, $tax_display );
1894
		$this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
1895
		$this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
1896
		$this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
1897
		$this->add_order_item_totals_total_row( $total_rows, $tax_display );
1898
1899
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display );
1900
	}
1901 1
1902 1
	/*
1903 1
	|--------------------------------------------------------------------------
1904 1
	| Conditionals
1905
	|--------------------------------------------------------------------------
1906
	|
1907 1
	| Checks if a condition is true or false.
1908
	|
1909
	*/
1910
1911
	/**
1912
	 * Checks the order status against a passed in status.
1913
	 *
1914
	 * @param array|string $status Status to check.
1915
	 * @return bool
1916 2
	 */
1917 2
	public function has_status( $status ) {
1918 1
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status );
1919 1
	}
1920
1921
	/**
1922 2
	 * Check whether this order has a specific shipping method or not.
1923
	 *
1924
	 * @param string $method_id Method ID to check.
1925
	 * @return bool
1926
	 */
1927
	public function has_shipping_method( $method_id ) {
1928
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1929
			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...
1930
				return true;
1931
			}
1932
		}
1933
		return false;
1934
	}
1935
1936
	/**
1937
	 * Returns true if the order contains a free product.
1938
	 *
1939
	 * @since 2.5.0
1940
	 * @return bool
1941
	 */
1942
	public function has_free_item() {
1943
		foreach ( $this->get_items() as $item ) {
1944
			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...
1945
				return true;
1946
			}
1947
		}
1948
		return false;
1949
	}
1950
}
1951