Completed
Push — master ( deec93...29d630 )
by Claudio
31:19 queued 22:42
created

WC_Abstract_Order::apply_coupon()   C

Complexity

Conditions 13
Paths 29

Size

Total Lines 69

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 13.2134

Importance

Changes 0
Metric Value
cc 13
nc 29
nop 1
dl 0
loc 69
ccs 33
cts 37
cp 0.8919
crap 13.2134
rs 5.9696
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
		if ( is_numeric( $order ) && $order > 0 ) {
99 88
			$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 88
			$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 114
						$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 134
			$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 5
	public function get_total_tax( $context = 'view' ) {
370 5
		return $this->get_prop( 'total_tax', $context );
371
	}
372
373
	/*
374
	|--------------------------------------------------------------------------
375
	| Non-CRUD Getters
376
	|--------------------------------------------------------------------------
377
	*/
378
379
	/**
380
	 * Gets the total discount amount.
381
	 *
382
	 * @param  bool $ex_tax Show discount excl any tax.
383
	 * @return float
384
	 */
385 2
	public function get_total_discount( $ex_tax = true ) {
386 2
		if ( $ex_tax ) {
387 1
			$total_discount = $this->get_discount_total();
388
		} else {
389 2
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
390
		}
391 2
		return apply_filters( 'woocommerce_order_get_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
392
	}
393
394
	/**
395
	 * Gets order subtotal.
396
	 *
397
	 * @return float
398
	 */
399 12
	public function get_subtotal() {
400 12
		$subtotal = 0;
401
402 12
		foreach ( $this->get_items() as $item ) {
403 12
			$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 12
		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 116
	protected function get_valid_statuses() {
448 116
		return array_keys( wc_get_order_statuses() );
449
	}
450
451
	/**
452
	 * Get user ID. Used by orders, not other order types like refunds.
453
	 *
454
	 * @param  string $context View or edit context.
455
	 * @return int
456
	 */
457
	public function get_user_id( $context = 'view' ) {
458
		return 0;
459
	}
460
461
	/**
462
	 * Get user. Used by orders, not other order types like refunds.
463
	 *
464
	 * @return WP_User|false
465
	 */
466
	public function get_user() {
467
		return false;
468
	}
469
470
	/*
471
	|--------------------------------------------------------------------------
472
	| Setters
473
	|--------------------------------------------------------------------------
474
	|
475
	| Functions for setting order data. These should not update anything in the
476
	| database itself and should only change what is stored in the class
477
	| object. However, for backwards compatibility pre 3.0.0 some of these
478
	| setters may handle both.
479
	*/
480
481
	/**
482
	 * Set parent order ID.
483
	 *
484
	 * @since 3.0.0
485
	 * @param int $value Value to set.
486
	 * @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid.
487
	 */
488 88
	public function set_parent_id( $value ) {
489 88
		if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
490
			$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
491
		}
492 88
		$this->set_prop( 'parent_id', absint( $value ) );
493
	}
494
495
	/**
496
	 * Set order status.
497
	 *
498
	 * @since 3.0.0
499
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
500
	 * @return array details of change
501
	 */
502 133
	public function set_status( $new_status ) {
503 133
		$old_status = $this->get_status();
504 133
		$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 133
		if ( true === $this->object_read ) {
508
			// Only allow valid new status.
509 116 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 116 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 133
		$this->set_prop( 'status', $new_status );
520
521
		return array(
522 133
			'from' => $old_status,
523 133
			'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 128
	public function set_prices_include_tax( $value ) {
557 128
		$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 89
	public function set_date_modified( $date = null ) {
577 89
		$this->set_date_prop( 'date_modified', $date );
578
	}
579
580
	/**
581
	 * Set discount_total.
582
	 *
583
	 * @param string $value Value to set.
584
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
585
	 */
586 134
	public function set_discount_total( $value ) {
587 134
		$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 134
	public function set_discount_tax( $value ) {
597 134
		$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 134
	public function set_shipping_total( $value ) {
607 134
		$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 137
	public function set_shipping_tax( $value ) {
617 137
		$this->set_prop( 'shipping_tax', wc_format_decimal( $value ) );
618 137
		$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 137
	public function set_cart_tax( $value ) {
628 137
		$this->set_prop( 'cart_tax', wc_format_decimal( $value ) );
629 137
		$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 138
	protected function set_total_tax( $value ) {
639 138
		$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 135
	public function set_total( $value, $deprecated = '' ) {
652 135
		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 135
		$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 140
				'line_item' => 'line_items',
699
				'tax'       => 'tax_lines',
700
				'shipping'  => 'shipping_lines',
701
				'fee'       => 'fee_lines',
702
				'coupon'    => 'coupon_lines',
703
			)
704
		);
705 140
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
706
	}
707
708
	/**
709
	 * Return an array of items/products within this order.
710
	 *
711
	 * @param string|array $types Types of line items to get (array or string).
712
	 * @return WC_Order_Item[]
713
	 */
714 140
	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
721 140
			if ( $group ) {
722 140
				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 140
				$items = $items + $this->items[ $group ];
727
			}
728
		}
729
730 140
		return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
731
	}
732
733
	/**
734
	 * Return an array of coupons within this order.
735
	 *
736
	 * @since  3.7.0
737
	 * @return WC_Order_Item_Coupon[]
738
	 */
739 2
	public function get_coupons() {
740 2
		return $this->get_items( 'coupon' );
741
	}
742
743
	/**
744
	 * Return an array of fees within this order.
745
	 *
746
	 * @return WC_Order_item_Fee[]
747
	 */
748 37
	public function get_fees() {
749 37
		return $this->get_items( 'fee' );
750
	}
751
752
	/**
753
	 * Return an array of taxes within this order.
754
	 *
755
	 * @return WC_Order_Item_Tax[]
756
	 */
757 39
	public function get_taxes() {
758 39
		return $this->get_items( 'tax' );
759
	}
760
761
	/**
762
	 * Return an array of shipping costs within this order.
763
	 *
764
	 * @return WC_Order_Item_Shipping[]
765
	 */
766 44
	public function get_shipping_methods() {
767 44
		return $this->get_items( 'shipping' );
768
	}
769
770
	/**
771
	 * Gets formatted shipping method title.
772
	 *
773
	 * @return string
774
	 */
775 1
	public function get_shipping_method() {
776 1
		$names = array();
777 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
778 1
			$names[] = $shipping_method->get_name();
779
		}
780 1
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
781
	}
782
783
	/**
784
	 * Get used coupon codes only.
785
	 *
786
	 * @since 3.7.0
787
	 * @return array
788
	 */
789 22
	public function get_coupon_codes() {
790 22
		$coupon_codes = array();
791 22
		$coupons      = $this->get_items( 'coupon' );
792
793 22
		if ( $coupons ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $coupons of type WC_Order_Item[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
794 2
			foreach ( $coupons as $coupon ) {
795 2
				$coupon_codes[] = $coupon->get_code();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
796
			}
797
		}
798 22
		return $coupon_codes;
799
	}
800
801
	/**
802
	 * Gets the count of order items of a certain type.
803
	 *
804
	 * @param string $item_type Item type to lookup.
805
	 * @return int|string
806
	 */
807 10
	public function get_item_count( $item_type = '' ) {
808 10
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
809 10
		$count = 0;
810
811 10
		foreach ( $items as $item ) {
812 7
			$count += $item->get_quantity();
813
		}
814
815 10
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
816
	}
817
818
	/**
819
	 * Get an order item object, based on its type.
820
	 *
821
	 * @since  3.0.0
822
	 * @param  int  $item_id ID of item to get.
823
	 * @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.
824
	 * @return WC_Order_Item|false
825
	 */
826 21
	public function get_item( $item_id, $load_from_db = true ) {
827 21
		if ( $load_from_db ) {
828 1
			return WC_Order_Factory::get_order_item( $item_id );
829
		}
830
831
		// Search for item id.
832 20
		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...
833 17
			foreach ( $this->items as $group => $items ) {
834 17
				if ( isset( $items[ $item_id ] ) ) {
835 17
					return $items[ $item_id ];
836
				}
837
			}
838
		}
839
840
		// Load all items of type and cache.
841 5
		$type = $this->data_store->get_order_item_type( $this, $item_id );
842
843 5
		if ( ! $type ) {
844
			return false;
845
		}
846
847 5
		$items = $this->get_items( $type );
848
849 5
		return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
850
	}
851
852
	/**
853
	 * Get key for where a certain item type is stored in _items.
854
	 *
855
	 * @since  3.0.0
856
	 * @param  string $item object Order item (product, shipping, fee, coupon, tax).
857
	 * @return string
858
	 */
859 121
	protected function get_items_key( $item ) {
860 121
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
861 113
			return 'line_items';
862 113
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
863 4
			return 'fee_lines';
864 111
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
865 98
			return 'shipping_lines';
866 24
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
867 10
			return 'tax_lines';
868 19
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
869 19
			return 'coupon_lines';
870
		}
871 1
		return apply_filters( 'woocommerce_get_items_key', '', $item );
872
	}
873
874
	/**
875
	 * Remove item from the order.
876
	 *
877
	 * @param int $item_id Item ID to delete.
878
	 * @return false|void
879
	 */
880 12
	public function remove_item( $item_id ) {
881 12
		$item      = $this->get_item( $item_id, false );
882 12
		$items_key = $item ? $this->get_items_key( $item ) : false;
883
884 12
		if ( ! $items_key ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $items_key of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

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

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
957
				return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) );
958
			}
959
		}
960
961 11
		$discounts = new WC_Discounts( $this );
962 11
		$applied   = $discounts->apply_coupon( $coupon );
0 ignored issues
show
Bug introduced by
It seems like $coupon defined by $raw_coupon on line 934 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...
963
964 11
		if ( is_wp_error( $applied ) ) {
965
			return $applied;
966
		}
967
968 11
		$data_store = $coupon->get_data_store();
969
970
		// Check specific for guest checkouts here as well since WC_Cart handles that seperately in check_customer_coupons.
971 11
		if ( $data_store && 0 === $this->get_customer_id() ) {
972 1
			$usage_count = $data_store->get_usage_by_email( $coupon, $this->get_billing_email() );
973 1
			if ( 0 < $coupon->get_usage_limit_per_user() && $usage_count >= $coupon->get_usage_limit_per_user() ) {
974 1
				return new WP_Error(
975 1
					'invalid_coupon',
976 1
					$coupon->get_coupon_error( 106 ),
977
					array(
978 1
						'status' => 400,
979
					)
980
				);
981
			}
982
		}
983
984 11
		$this->set_coupon_discount_amounts( $discounts );
985 11
		$this->save();
986
987
		// Recalculate totals and taxes.
988 11
		$this->recalculate_coupons();
989
990
		// Record usage so counts and validation is correct.
991 11
		$used_by = $this->get_user_id();
992
993 11
		if ( ! $used_by ) {
994 1
			$used_by = $this->get_billing_email();
995
		}
996
997 11
		$coupon->increase_usage_count( $used_by );
998
999 11
		return true;
1000
	}
1001
1002
	/**
1003
	 * Remove a coupon from the order and recalculate totals.
1004
	 *
1005
	 * Coupons affect line item totals, but there is no relationship between
1006
	 * coupon and line total, so to remove a coupon we need to work from the
1007
	 * line subtotal (price before discount) and re-apply all coupons in this
1008
	 * order.
1009
	 *
1010
	 * Manual discounts are not affected; those are separate and do not affect
1011
	 * stored line totals.
1012
	 *
1013
	 * @since  3.2.0
1014
	 * @param  string $code Coupon code.
1015
	 * @return void
1016
	 */
1017 7
	public function remove_coupon( $code ) {
1018 7
		$coupons = $this->get_items( 'coupon' );
1019
1020
		// Remove the coupon line.
1021 7
		foreach ( $coupons as $item_id => $coupon ) {
1022 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...
1023 7
				$this->remove_item( $item_id );
1024 7
				$coupon_object = new WC_Coupon( $code );
1025 7
				$coupon_object->decrease_usage_count( $this->get_user_id() );
1026 7
				$this->recalculate_coupons();
1027 7
				break;
1028
			}
1029
		}
1030
	}
1031
1032
	/**
1033
	 * Apply all coupons in this order again to all line items.
1034
	 *
1035
	 * @since  3.2.0
1036
	 */
1037 15
	protected function recalculate_coupons() {
1038
		// Reset line item totals.
1039 15
		foreach ( $this->get_items() as $item ) {
1040 15
			$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...
1041 15
			$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...
1042
		}
1043
1044 15
		$discounts = new WC_Discounts( $this );
1045
1046 15
		foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
1047 15
			$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...
1048 15
			$coupon_id   = wc_get_coupon_id_by_code( $coupon_code );
1049
1050
			// 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.
1051 15
			if ( $coupon_id ) {
1052 15
				$coupon_object = new WC_Coupon( $coupon_id );
1053
1054
			} else {
1055
1056
				// 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.
1057 8
				$coupon_object = new WC_Coupon();
1058 8
				$coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
1059 8
				$coupon_object->set_code( $coupon_code );
1060 8
				$coupon_object->set_virtual( true );
1061
1062
				// If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
1063 8
				if ( ! $coupon_object->get_amount() ) {
1064
1065
					// If the order originally had prices including tax, remove the discount + discount tax.
1066 8
					if ( $this->get_prices_include_tax() ) {
1067 4
						$coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_discount() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_discount_tax() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1068
					} else {
1069 4
						$coupon_object->set_amount( $coupon_item->get_discount() );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_discount() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1070
					}
1071 8
					$coupon_object->set_discount_type( 'fixed_cart' );
1072
				}
1073
			}
1074
1075
			/**
1076
			 * Allow developers to filter this coupon before it get's re-applied to the order.
1077
			 *
1078
			 * @since 3.2.0
1079
			 */
1080 15
			$coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
1081
1082 15
			if ( $coupon_object ) {
1083 15
				$discounts->apply_coupon( $coupon_object, false );
1084
			}
1085
		}
1086
1087 15
		$this->set_coupon_discount_amounts( $discounts );
1088 15
		$this->set_item_discount_amounts( $discounts );
1089
1090
		// Recalculate totals and taxes.
1091 15
		$this->calculate_totals( true );
1092
	}
1093
1094
	/**
1095
	 * After applying coupons via the WC_Discounts class, update line items.
1096
	 *
1097
	 * @since 3.2.0
1098
	 * @param WC_Discounts $discounts Discounts class.
1099
	 */
1100 15
	protected function set_item_discount_amounts( $discounts ) {
1101 15
		$item_discounts = $discounts->get_discounts_by_item();
1102
1103 15
		if ( $item_discounts ) {
1104 15
			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...
1105 15
				$item = $this->get_item( $item_id, false );
1106
1107
				// If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
1108 15
				if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1109 2
					$taxes = WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1110
1111 2
					if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1112 2
						$taxes = array_map( 'wc_round_tax_total', $taxes );
1113
					}
1114
1115 2
					$amount = $amount - array_sum( $taxes );
1116 2
					$item->set_total( max( 0, round( $item->get_total() - $amount, wc_get_price_decimals() ) ) );
1117
				} else {
1118 13
					$item->set_total( max( 0, $item->get_total() - $amount ) );
1119
				}
1120
			}
1121
		}
1122
	}
1123
1124
	/**
1125
	 * After applying coupons via the WC_Discounts class, update or create coupon items.
1126
	 *
1127
	 * @since 3.2.0
1128
	 * @param WC_Discounts $discounts Discounts class.
1129
	 */
1130 15
	protected function set_coupon_discount_amounts( $discounts ) {
1131 15
		$coupons           = $this->get_items( 'coupon' );
1132 15
		$coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
1133 15
		$all_discounts     = $discounts->get_discounts();
1134 15
		$coupon_discounts  = $discounts->get_discounts_by_coupon();
1135
1136 15
		if ( $coupon_discounts ) {
1137 15
			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...
1138 15
				$item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0;
1139
1140 15
				if ( ! $item_id ) {
1141 11
					$coupon_item = new WC_Order_Item_Coupon();
1142 11
					$coupon_item->set_code( $coupon_code );
1143
				} else {
1144 15
					$coupon_item = $this->get_item( $item_id, false );
1145
				}
1146
1147 15
				$discount_tax = 0;
1148
1149
				// Work out how much tax has been removed as a result of the discount from this coupon.
1150 15
				foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
1151 15
					$item = $this->get_item( $item_id, false );
1152
1153 15
					if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1154 2
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1155
1156 2
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1157 2
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1158
						}
1159
1160 2
						$discount_tax += array_sum( $taxes );
1161 2
						$amount        = $amount - array_sum( $taxes );
1162
					} else {
1163 13
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) );
1164
1165 13
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1166 13
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1167
						}
1168
1169 13
						$discount_tax += array_sum( $taxes );
1170
					}
1171
				}
1172
1173 15
				$coupon_item->set_discount( $amount );
1174 15
				$coupon_item->set_discount_tax( $discount_tax );
1175
1176 15
				$this->add_item( $coupon_item );
1177
			}
1178
		}
1179
	}
1180
1181
	/**
1182
	 * Add a product line item to the order. This is the only line item type with
1183
	 * its own method because it saves looking up order amounts (costs are added up for you).
1184
	 *
1185
	 * @param  WC_Product $product Product object.
1186
	 * @param  int        $qty Quantity to add.
1187
	 * @param  array      $args Args for the added product.
1188
	 * @return int
1189
	 * @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart.
1190
	 */
1191 6
	public function add_product( $product, $qty = 1, $args = array() ) {
1192 6
		if ( $product ) {
1193
			$default_args = array(
1194 6
				'name'         => $product->get_name(),
1195 6
				'tax_class'    => $product->get_tax_class(),
1196 6
				'product_id'   => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
1197 6
				'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
1198 6
				'variation'    => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
1199 6
				'subtotal'     => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1200 6
				'total'        => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1201 6
				'quantity'     => $qty,
1202
			);
1203
		} else {
1204
			$default_args = array(
1205
				'quantity' => $qty,
1206
			);
1207
		}
1208
1209 6
		$args = wp_parse_args( $args, $default_args );
1210
1211
		// BW compatibility with old args.
1212 6
		if ( isset( $args['totals'] ) ) {
1213
			foreach ( $args['totals'] as $key => $value ) {
1214
				if ( 'tax' === $key ) {
1215
					$args['total_tax'] = $value;
1216
				} elseif ( 'tax_data' === $key ) {
1217
					$args['taxes'] = $value;
1218
				} else {
1219
					$args[ $key ] = $value;
1220
				}
1221
			}
1222
		}
1223
1224 6
		$item = new WC_Order_Item_Product();
1225 6
		$item->set_props( $args );
1226 6
		$item->set_backorder_meta();
1227 6
		$item->set_order_id( $this->get_id() );
1228 6
		$item->save();
1229 6
		$this->add_item( $item );
1230 6
		wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '3.0', 'woocommerce_new_order_item action instead' );
1231 6
		delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
1232 6
		return $item->get_id();
1233
	}
1234
1235
	/*
1236
	|--------------------------------------------------------------------------
1237
	| Payment Token Handling
1238
	|--------------------------------------------------------------------------
1239
	|
1240
	| Payment tokens are hashes used to take payments by certain gateways.
1241
	|
1242
	*/
1243
1244
	/**
1245
	 * Add a payment token to an order
1246
	 *
1247
	 * @since 2.6
1248
	 * @param WC_Payment_Token $token Payment token object.
1249
	 * @return boolean|int The new token ID or false if it failed.
1250
	 */
1251 3
	public function add_payment_token( $token ) {
1252 3
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1253 1
			return false;
1254
		}
1255
1256 3
		$token_ids   = $this->data_store->get_payment_token_ids( $this );
1257 3
		$token_ids[] = $token->get_id();
1258 3
		$this->data_store->update_payment_token_ids( $this, $token_ids );
1259
1260 3
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1261 3
		return $token->get_id();
1262
	}
1263
1264
	/**
1265
	 * Returns a list of all payment tokens associated with the current order
1266
	 *
1267
	 * @since 2.6
1268
	 * @return array An array of payment token objects
1269
	 */
1270 4
	public function get_payment_tokens() {
1271 4
		return $this->data_store->get_payment_token_ids( $this );
1272
	}
1273
1274
	/*
1275
	|--------------------------------------------------------------------------
1276
	| Calculations.
1277
	|--------------------------------------------------------------------------
1278
	|
1279
	| These methods calculate order totals and taxes based on the current data.
1280
	|
1281
	*/
1282
1283
	/**
1284
	 * Calculate shipping total.
1285
	 *
1286
	 * @since 2.2
1287
	 * @return float
1288
	 */
1289 1
	public function calculate_shipping() {
1290 1
		$shipping_total = 0;
1291
1292 1
		foreach ( $this->get_shipping_methods() as $shipping ) {
1293 1
			$shipping_total += $shipping->get_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1294
		}
1295
1296 1
		$this->set_shipping_total( $shipping_total );
1297 1
		$this->save();
1298
1299 1
		return $this->get_shipping_total();
1300
	}
1301
1302
	/**
1303
	 * Get all tax classes for items in the order.
1304
	 *
1305
	 * @since 2.6.3
1306
	 * @return array
1307
	 */
1308 33
	public function get_items_tax_classes() {
1309 33
		$found_tax_classes = array();
1310
1311 33
		foreach ( $this->get_items() as $item ) {
1312 33
			if ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) {
1313 33
				$found_tax_classes[] = $item->get_tax_class();
1314
			}
1315
		}
1316
1317 33
		return array_unique( $found_tax_classes );
1318
	}
1319
1320
	/**
1321
	 * Get tax location for this order.
1322
	 *
1323
	 * @since 3.2.0
1324
	 * @param array $args array Override the location.
1325
	 * @return array
1326
	 */
1327 32
	protected function get_tax_location( $args = array() ) {
1328 32
		$tax_based_on = get_option( 'woocommerce_tax_based_on' );
1329
1330 32
		if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
1331 19
			$tax_based_on = 'billing';
1332
		}
1333
1334 32
		$args = wp_parse_args(
1335 32
			$args,
1336
			array(
1337 32
				'country'  => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
1338 32
				'state'    => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
1339 32
				'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
1340 32
				'city'     => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(),
1341
			)
1342
		);
1343
1344
		// Default to base.
1345 32
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1346 15
			$args['country']  = WC()->countries->get_base_country();
1347 15
			$args['state']    = WC()->countries->get_base_state();
1348 15
			$args['postcode'] = WC()->countries->get_base_postcode();
1349 15
			$args['city']     = WC()->countries->get_base_city();
1350
		}
1351
1352 32
		return $args;
1353
	}
1354
1355
	/**
1356
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1357
	 *
1358
	 * If by default the taxes are based on the shipping address and the current order doesn't
1359
	 * have any, it would use the billing address rather than using the Shopping base location.
1360
	 *
1361
	 * Will use the base country unless customer addresses are set.
1362
	 *
1363
	 * @param array $args Added in 3.0.0 to pass things like location.
1364
	 */
1365 32
	public function calculate_taxes( $args = array() ) {
1366 32
		do_action( 'woocommerce_order_before_calculate_taxes', $args, $this );
1367
1368 32
		$calculate_tax_for  = $this->get_tax_location( $args );
1369 32
		$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1370
1371 32
		if ( 'inherit' === $shipping_tax_class ) {
1372 32
			$found_classes      = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
1373 32
			$shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
1374
		}
1375
1376 32
		$is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
1377
1378
		// Trigger tax recalculation for all items.
1379 32
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1380 32
			if ( ! $is_vat_exempt ) {
1381 32
				$item->calculate_taxes( $calculate_tax_for );
1382
			} else {
1383 1
				$item->set_taxes( false );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1384
			}
1385
		}
1386
1387 32
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1388 20
			if ( false !== $shipping_tax_class && ! $is_vat_exempt ) {
1389 20
				$item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
1390
			} else {
1391 1
				$item->set_taxes( false );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_taxes() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1392
			}
1393
		}
1394
1395 32
		$this->update_taxes();
1396
	}
1397
1398
	/**
1399
	 * Update tax lines for the order based on the line item taxes themselves.
1400
	 */
1401 39
	public function update_taxes() {
1402 39
		$cart_taxes     = array();
1403 39
		$shipping_taxes = array();
1404 39
		$existing_taxes = $this->get_taxes();
1405 39
		$saved_rate_ids = array();
1406
1407 39 View Code Duplication
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1408 32
			$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...
1409 32
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1410 10
				$tax_amount = (float) $tax;
1411
1412 10
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1413 10
					$tax_amount = wc_round_tax_total( $tax_amount );
1414
				}
1415
1416 10
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1417
			}
1418
		}
1419
1420 39 View Code Duplication
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1421 20
			$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...
1422 20
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1423 4
				$tax_amount = (float) $tax;
1424
1425 4
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1426 4
					$tax_amount = wc_round_tax_total( $tax_amount );
1427
				}
1428
1429 4
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1430
			}
1431
		}
1432
1433 39
		foreach ( $existing_taxes as $tax ) {
1434
			// Remove taxes which no longer exist for cart/shipping.
1435 5
			if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1436 1
				$this->remove_item( $tax->get_id() );
1437 1
				continue;
1438
			}
1439 4
			$saved_rate_ids[] = $tax->get_rate_id();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1440 4
			$tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1441 4
			$tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 );
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_rate_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method set_shipping_tax_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Tax. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1442 4
			$tax->save();
1443
		}
1444
1445 39
		$new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) );
1446
1447
		// New taxes.
1448 39
		foreach ( $new_rate_ids as $tax_rate_id ) {
1449 10
			$item = new WC_Order_Item_Tax();
1450 10
			$item->set_rate( $tax_rate_id );
1451 10
			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
1452 10
			$item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1453 10
			$this->add_item( $item );
1454
		}
1455
1456 39
		$this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) );
1457 39
		$this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) );
1458 39
		$this->save();
1459
	}
1460
1461
	/**
1462
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1463
	 *
1464
	 * @since 2.2
1465
	 * @param  bool $and_taxes Calc taxes if true.
1466
	 * @return float calculated grand total.
1467
	 */
1468 36
	public function calculate_totals( $and_taxes = true ) {
1469 36
		do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
1470
1471 36
		$cart_subtotal     = 0;
1472 36
		$cart_total        = 0;
1473 36
		$fees_total         = 0;
1474 36
		$shipping_total    = 0;
1475 36
		$cart_subtotal_tax = 0;
1476 36
		$cart_total_tax    = 0;
1477
1478
		// Sum line item costs.
1479 36
		foreach ( $this->get_items() as $item ) {
1480 29
			$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...
1481 29
			$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...
1482
		}
1483
1484
		// Sum shipping costs.
1485 36
		foreach ( $this->get_shipping_methods() as $shipping ) {
1486 18
			$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...
1487
		}
1488
1489 36
		$this->set_shipping_total( $shipping_total );
1490
1491
		// Sum fee costs.
1492 36
		foreach ( $this->get_fees() as $item ) {
1493
			$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...
1494
1495
			if ( 0 > $fee_total ) {
1496
				$max_discount = round( $cart_total + $fees_total + $shipping_total, wc_get_price_decimals() ) * -1;
1497
1498
				if ( $fee_total < $max_discount ) {
1499
					$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...
1500
				}
1501
			}
1502
1503
			$fees_total += $item->get_total();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1504
		}
1505
1506
		// Calculate taxes for items, shipping, discounts. Note; this also triggers save().
1507 36
		if ( $and_taxes ) {
1508 29
			$this->calculate_taxes();
1509
		}
1510
1511
		// Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp.
1512 36
		foreach ( $this->get_items() as $item ) {
1513 29
			$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...
1514
1515 29
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1516 7
				$cart_total_tax += (float) $tax;
1517
			}
1518
1519 29
			foreach ( $taxes['subtotal'] as $tax_rate_id => $tax ) {
1520 7
				$cart_subtotal_tax += (float) $tax;
1521
			}
1522
		}
1523
1524 36
		$this->set_discount_total( $cart_subtotal - $cart_total );
1525 36
		$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
1526 36
		$this->set_total( round( $cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
1527
1528 36
		do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this );
1529
1530 36
		$this->save();
1531
1532 36
		return $this->get_total();
1533
	}
1534
1535
	/**
1536
	 * Get item subtotal - this is the cost before discount.
1537
	 *
1538
	 * @param object $item Item to get total from.
1539
	 * @param bool   $inc_tax (default: false).
1540
	 * @param bool   $round (default: true).
1541
	 * @return float
1542
	 */
1543 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...
1544
		$subtotal = 0;
1545
1546
		if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) {
1547
			if ( $inc_tax ) {
1548
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity();
1549
			} else {
1550
				$subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity();
1551
			}
1552
1553
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1554
		}
1555
1556
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1557
	}
1558
1559
	/**
1560
	 * Get line subtotal - this is the cost before discount.
1561
	 *
1562
	 * @param object $item Item to get total from.
1563
	 * @param bool   $inc_tax (default: false).
1564
	 * @param bool   $round (default: true).
1565
	 * @return float
1566
	 */
1567 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...
1568
		$subtotal = 0;
1569
1570
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1571
			if ( $inc_tax ) {
1572
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1573
			} else {
1574
				$subtotal = $item->get_subtotal();
1575
			}
1576
1577
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1578
		}
1579
1580
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1581
	}
1582
1583
	/**
1584
	 * Calculate item cost - useful for gateways.
1585
	 *
1586
	 * @param object $item Item to get total from.
1587
	 * @param bool   $inc_tax (default: false).
1588
	 * @param bool   $round (default: true).
1589
	 * @return float
1590
	 */
1591 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...
1592
		$total = 0;
1593
1594
		if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) {
1595
			if ( $inc_tax ) {
1596
				$total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity();
1597
			} else {
1598
				$total = floatval( $item->get_total() ) / $item->get_quantity();
1599
			}
1600
1601
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1602
		}
1603
1604
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1605
	}
1606
1607
	/**
1608
	 * Calculate line total - useful for gateways.
1609
	 *
1610
	 * @param object $item Item to get total from.
1611
	 * @param bool   $inc_tax (default: false).
1612
	 * @param bool   $round (default: true).
1613
	 * @return float
1614
	 */
1615 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...
1616
		$total = 0;
1617
1618
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1619
			// Check if we need to add line tax to the line total.
1620
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1621
1622
			// Check if we need to round.
1623
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1624
		}
1625
1626
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1627
	}
1628
1629
	/**
1630
	 * Get item tax - useful for gateways.
1631
	 *
1632
	 * @param mixed $item Item to get total from.
1633
	 * @param bool  $round (default: true).
1634
	 * @return float
1635
	 */
1636
	public function get_item_tax( $item, $round = true ) {
1637
		$tax = 0;
1638
1639
		if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) {
1640
			$tax = $item->get_total_tax() / $item->get_quantity();
1641
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1642
		}
1643
1644
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1645
	}
1646
1647
	/**
1648
	 * Get line tax - useful for gateways.
1649
	 *
1650
	 * @param mixed $item Item to get total from.
1651
	 * @return float
1652
	 */
1653
	public function get_line_tax( $item ) {
1654
		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 );
1655
	}
1656
1657
	/**
1658
	 * Gets line subtotal - formatted for display.
1659
	 *
1660
	 * @param object $item Item to get total from.
1661
	 * @param string $tax_display Incl or excl tax display mode.
1662
	 * @return string
1663
	 */
1664
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1665
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1666
1667
		if ( 'excl' === $tax_display ) {
1668
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1669
1670
			$subtotal = wc_price(
1671
				$this->get_line_subtotal( $item ),
1672
				array(
1673
					'ex_tax_label' => $ex_tax_label,
1674
					'currency'     => $this->get_currency(),
1675
				)
1676
			);
1677
		} else {
1678
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
1679
		}
1680
1681
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1682
	}
1683
1684
	/**
1685
	 * Gets order total - formatted for display.
1686
	 *
1687
	 * @return string
1688
	 */
1689
	public function get_formatted_order_total() {
1690
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1691
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1692
	}
1693
1694
	/**
1695
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1696
	 *
1697
	 * @param bool   $compound (default: false).
1698
	 * @param string $tax_display (default: the tax_display_cart value).
1699
	 * @return string
1700
	 */
1701
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1702
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1703
		$subtotal    = 0;
1704
1705
		if ( ! $compound ) {
1706
			foreach ( $this->get_items() as $item ) {
1707
				$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...
1708
1709
				if ( 'incl' === $tax_display ) {
1710
					$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...
1711
				}
1712
			}
1713
1714
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1715
1716 View Code Duplication
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) {
1717
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1718
			}
1719
		} else {
1720
			if ( 'incl' === $tax_display ) {
1721
				return '';
1722
			}
1723
1724
			foreach ( $this->get_items() as $item ) {
1725
				$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...
1726
			}
1727
1728
			// Add Shipping Costs.
1729
			$subtotal += $this->get_shipping_total();
1730
1731
			// Remove non-compound taxes.
1732
			foreach ( $this->get_taxes() as $tax ) {
1733
				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...
1734
					continue;
1735
				}
1736
				$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...
1737
			}
1738
1739
			// Remove discounts.
1740
			$subtotal = $subtotal - $this->get_total_discount();
1741
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1742
		}
1743
1744
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1745
	}
1746
1747
	/**
1748
	 * Gets shipping (formatted).
1749
	 *
1750
	 * @param string $tax_display Excl or incl tax display mode.
1751
	 * @return string
1752
	 */
1753
	public function get_shipping_to_display( $tax_display = '' ) {
1754
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1755
1756
		if ( 0 < abs( (float) $this->get_shipping_total() ) ) {
1757
1758
			if ( 'excl' === $tax_display ) {
1759
1760
				// Show shipping excluding tax.
1761
				$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
1762
1763 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
1764
					$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 );
1765
				}
1766
			} else {
1767
1768
				// Show shipping including tax.
1769
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
1770
1771 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
1772
					$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 );
1773
				}
1774
			}
1775
1776
			/* translators: %s: method */
1777
			$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 );
1778
1779
		} elseif ( $this->get_shipping_method() ) {
1780
			$shipping = $this->get_shipping_method();
1781
		} else {
1782
			$shipping = __( 'Free!', 'woocommerce' );
1783
		}
1784
1785
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this, $tax_display );
1786
	}
1787
1788
	/**
1789
	 * Get the discount amount (formatted).
1790
	 *
1791
	 * @since  2.3.0
1792
	 * @param string $tax_display Excl or incl tax display mode.
1793
	 * @return string
1794
	 */
1795
	public function get_discount_to_display( $tax_display = '' ) {
1796
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1797
		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 );
1798
	}
1799
1800
	/**
1801
	 * Add total row for subtotal.
1802
	 *
1803
	 * @param array  $total_rows Reference to total rows array.
1804
	 * @param string $tax_display Excl or incl tax display mode.
1805
	 */
1806
	protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) {
1807
		$subtotal = $this->get_subtotal_to_display( false, $tax_display );
1808
1809
		if ( $subtotal ) {
1810
			$total_rows['cart_subtotal'] = array(
1811
				'label' => __( 'Subtotal:', 'woocommerce' ),
1812
				'value' => $subtotal,
1813
			);
1814
		}
1815
	}
1816
1817
	/**
1818
	 * Add total row for discounts.
1819
	 *
1820
	 * @param array  $total_rows Reference to total rows array.
1821
	 * @param string $tax_display Excl or incl tax display mode.
1822
	 */
1823
	protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) {
1824
		if ( $this->get_total_discount() > 0 ) {
1825
			$total_rows['discount'] = array(
1826
				'label' => __( 'Discount:', 'woocommerce' ),
1827
				'value' => '-' . $this->get_discount_to_display( $tax_display ),
1828
			);
1829
		}
1830
	}
1831
1832
	/**
1833
	 * Add total row for shipping.
1834
	 *
1835
	 * @param array  $total_rows Reference to total rows array.
1836
	 * @param string $tax_display Excl or incl tax display mode.
1837
	 */
1838
	protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) {
1839
		if ( $this->get_shipping_method() ) {
1840
			$total_rows['shipping'] = array(
1841
				'label' => __( 'Shipping:', 'woocommerce' ),
1842
				'value' => $this->get_shipping_to_display( $tax_display ),
1843
			);
1844
		}
1845
	}
1846
1847
	/**
1848
	 * Add total row for fees.
1849
	 *
1850
	 * @param array  $total_rows Reference to total rows array.
1851
	 * @param string $tax_display Excl or incl tax display mode.
1852
	 */
1853
	protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) {
1854
		$fees = $this->get_fees();
1855
1856
		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...
1857
			foreach ( $fees as $id => $fee ) {
1858
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1859
					continue;
1860
				}
1861
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1862
					'label' => $fee->get_name() . ':',
1863
					'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...
1864
				);
1865
			}
1866
		}
1867
	}
1868
1869
	/**
1870
	 * Add total row for taxes.
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_tax_rows( &$total_rows, $tax_display ) {
1876
		// Tax for tax exclusive prices.
1877
		if ( 'excl' === $tax_display && wc_tax_enabled() ) {
1878
			if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
1879
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1880
					$total_rows[ sanitize_title( $code ) ] = array(
1881
						'label' => $tax->label . ':',
1882
						'value' => $tax->formatted_amount,
1883
					);
1884
				}
1885
			} else {
1886
				$total_rows['tax'] = array(
1887
					'label' => WC()->countries->tax_or_vat() . ':',
1888
					'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1889
				);
1890
			}
1891
		}
1892
	}
1893
1894
	/**
1895
	 * Add total row for grand total.
1896
	 *
1897
	 * @param array  $total_rows Reference to total rows array.
1898
	 * @param string $tax_display Excl or incl tax display mode.
1899
	 */
1900
	protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) {
1901
		$total_rows['order_total'] = array(
1902
			'label' => __( 'Total:', 'woocommerce' ),
1903
			'value' => $this->get_formatted_order_total( $tax_display ),
1904
		);
1905
	}
1906
1907
	/**
1908
	 * Get totals for display on pages and in emails.
1909
	 *
1910
	 * @param mixed $tax_display Excl or incl tax display mode.
1911
	 * @return array
1912
	 */
1913
	public function get_order_item_totals( $tax_display = '' ) {
1914
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1915
		$total_rows  = array();
1916
1917
		$this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
1918
		$this->add_order_item_totals_discount_row( $total_rows, $tax_display );
1919
		$this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
1920
		$this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
1921
		$this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
1922
		$this->add_order_item_totals_total_row( $total_rows, $tax_display );
1923
1924
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display );
1925
	}
1926
1927
	/*
1928
	|--------------------------------------------------------------------------
1929
	| Conditionals
1930
	|--------------------------------------------------------------------------
1931
	|
1932
	| Checks if a condition is true or false.
1933
	|
1934
	*/
1935
1936
	/**
1937
	 * Checks the order status against a passed in status.
1938
	 *
1939
	 * @param array|string $status Status to check.
1940
	 * @return bool
1941
	 */
1942 30
	public function has_status( $status ) {
1943 30
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status );
1944
	}
1945
1946
	/**
1947
	 * Check whether this order has a specific shipping method or not.
1948
	 *
1949
	 * @param string $method_id Method ID to check.
1950
	 * @return bool
1951
	 */
1952 1
	public function has_shipping_method( $method_id ) {
1953 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1954 1
			if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_method_id() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Shipping. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
introduced by
Found "=== 0". Use Yoda Condition checks, you must
Loading history...
1955 1
				return true;
1956
			}
1957
		}
1958 1
		return false;
1959
	}
1960
1961
	/**
1962
	 * Returns true if the order contains a free product.
1963
	 *
1964
	 * @since 2.5.0
1965
	 * @return bool
1966
	 */
1967 2
	public function has_free_item() {
1968 2
		foreach ( $this->get_items() as $item ) {
1969 1
			if ( ! $item->get_total() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_total() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Fee, WC_Order_Item_Product, WC_Order_Item_Shipping. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
1970 1
				return true;
1971
			}
1972
		}
1973 2
		return false;
1974
	}
1975
}
1976