Completed
Push — master ( 9252c3...f95ffe )
by Mike
08:03
created

WC_Shipping_Method::get_taxes_per_item()   C

Complexity

Conditions 11
Paths 3

Size

Total Lines 34
Code Lines 15

Duplication

Lines 6
Ratio 17.65 %
Metric Value
dl 6
loc 34
rs 5.2653
cc 11
eloc 15
nc 3
nop 1

How to fix   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
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 18 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * WooCommerce Shipping Method Class.
9
 *
10
 * Extended by shipping methods to handle shipping calculations etc.
11
 *
12
 * @class       WC_Shipping_Method
13
 * @version     2.6.0
14
 * @package     WooCommerce/Abstracts
15
 * @category    Abstract Class
16
 * @author      WooThemes
17
 */
18
abstract class WC_Shipping_Method extends WC_Settings_API {
19
20
	/**
21
	 * Features this method supports. Possible features used by core:
22
	 * - shipping-zones Shipping zone functionality + instances
23
	 * - instance-settings Instance settings screens.
24
	 * - settings Non-instance settings screens. Enabled by default for BW compatibility with methods before instances existed.
25
	 * @var array
26
	 */
27
	public $supports = array( 'settings' );
28
29
	/**
30
	 * Unique ID for the shipping method - must be set.
31
	 * @var string
32
	 */
33
	public $id = '';
34
35
	/**
36
	 * Method title.
37
	 * @var string
38
	 */
39
	public $method_title = '';
40
41
	/**
42
	 * Method description.
43
	 * @var string
44
	 */
45
	public $method_description = '';
46
47
	/**
48
	 * yes or no based on whether the method is enabled.
49
	 * @var string
50
	 */
51
	public $enabled = 'yes';
52
53
	/**
54
	 * Shipping method title for the frontend.
55
	 * @var string
56
	 */
57
	public $title;
58
59
	/**
60
	 * This is an array of rates - methods must populate this array to register shipping costs.
61
	 * @var array
62
	 */
63
	public $rates = array();
64
65
	/**
66
	 * If 'taxable' tax will be charged for this method (if applicable).
67
	 * @var string
68
	 */
69
	public $tax_status = 'taxable';
70
71
	/**
72
	 * Fee for the method (if applicable).
73
	 * @var string
74
	 */
75
	public $fee = null;
76
77
	/** @var float Minimum fee for the method */
78
	/**
79
	 * Minimum fee for the method (if applicable).
80
	 * @var string
81
	 */
82
	public $minimum_fee = null;
83
84
	/**
85
	 * Instance ID if used.
86
	 * @var int
87
	 */
88
	public $instance_id = 0;
89
90
	/**
91
	 * Instance form fields.
92
	 * @var array
93
	 */
94
	public $instance_form_fields = array();
95
96
	/**
97
	 * Instance settings.
98
	 * @var array
99
	 */
100
	public $instance_settings = array();
101
102
	/**
103
	 * Availability - legacy. Used for method Availability.
104
	 * No longer useful for instance based shipping methods.
105
	 * @deprecated 2.6.0
106
	 * @var string
107
	 */
108
	public $availability;
109
110
	/**
111
	 * Availability countries - legacy. Used for method Availability.
112
	 * No longer useful for instance based shipping methods.
113
	 * @deprecated 2.6.0
114
	 * @var array
115
	 */
116
	public $countries = array();
117
118
	/**
119
	 * Constructor.
120
	 * @param int $instance_id
121
	 */
122
	public function __construct( $instance_id = 0 ) {
123
		$this->instance_id = absint( $instance_id );
124
	}
125
126
	/**
127
	 * Check if a shipping method supports a given feature.
128
	 *
129
	 * Methods should override this to declare support (or lack of support) for a feature.
130
	 *
131
	 * @param $feature string The name of a feature to test support for.
132
	 * @return bool True if the shipping method supports the feature, false otherwise.
133
	 */
134
	public function supports( $feature ) {
135
		return apply_filters( 'woocommerce_shipping_method_supports', in_array( $feature, $this->supports ), $feature, $this );
136
	}
137
138
	/**
139
	 * Called to calculate shipping rates for this method. Rates can be added using the add_rate() method.
140
	 */
141
	public function calculate_shipping( $package = array() ) {}
142
143
	/**
144
	 * Whether or not we need to calculate tax on top of the shipping rate.
145
	 * @return boolean
146
	 */
147
	public function is_taxable() {
148
		return wc_tax_enabled() && 'taxable' === $this->tax_status && ! WC()->customer->is_vat_exempt();
149
	}
150
151
	/**
152
	 * Whether or not this method is enabled in settings.
153
	 * @since 2.6.0
154
	 * @return boolean
155
	 */
156
	public function is_enabled() {
157
		return 'yes' === $this->enabled;
158
	}
159
160
	/**
161
	 * Return the shipping method instance ID.
162
	 * @since 2.6.0
163
	 * @return int
164
	 */
165
	public function get_instance_id() {
166
		return $this->instance_id;
167
	}
168
169
	/**
170
	 * Return the shipping method title.
171
	 * @since 2.6.0
172
	 * @return string
173
	 */
174
	public function get_method_title() {
175
		return apply_filters( 'woocommerce_shipping_method_title', $this->method_title, $this );
176
	}
177
178
	/**
179
	 * Return the shipping method description.
180
	 * @since 2.6.0
181
	 * @return string
182
	 */
183
	public function get_method_description() {
184
		return apply_filters( 'woocommerce_shipping_method_description', $this->method_description, $this );
185
	}
186
187
	/**
188
	 * Return the shipping title which is user set.
189
	 *
190
	 * @return string
191
	 */
192
	public function get_title() {
193
		return apply_filters( 'woocommerce_shipping_method_title', $this->title, $this->id );
194
	}
195
196
	/**
197
	 * Return calculated rates for a package.
198
	 * @since 2.6.0
199
	 * @param object $package
200
	 * @return array
201
	 */
202
	public function get_rates_for_package( $package ) {
203
		$this->rates = array();
204
		if ( $this->is_available( $package ) && ( empty( $package['ship_via'] ) || in_array( $this->id, $package['ship_via'] ) ) ) {
0 ignored issues
show
Documentation introduced by
$package is of type object, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
205
			$this->calculate_shipping( $package );
0 ignored issues
show
Documentation introduced by
$package is of type object, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
206
		}
207
		return $this->rates;
208
	}
209
210
	/**
211
	 * Add a shipping rate. If taxes are not set they will be calculated based on cost.
212
	 * @param array $args (default: array())
213
	 */
214
	public function add_rate( $args = array() ) {
215
		$args = wp_parse_args( $args, array(
216
			'id'        => '', // ID for the rate
217
			'label'     => '', // Label for the rate
218
			'cost'      => '0', // Amount or array of costs (per item shipping)
219
			'taxes'     => '', // Pass taxes, or leave empty to have it calculated for you, or 'false' to disable calculations
220
			'calc_tax'  => 'per_order', // Calc tax per_order or per_item. Per item needs an array of costs
221
			'meta_data' => array() // Array of misc meta data to store along with this rate - key value pairs.
222
		) );
223
224
		// ID and label are required
225
		if ( ! $args['id'] || ! $args['label'] ) {
226
			return;
227
		}
228
229
		// Total up the cost
230
		$total_cost = is_array( $args['cost'] ) ? array_sum( $args['cost'] ) : $args['cost'];
231
		$taxes      = $args['taxes'];
232
233
		// Taxes - if not an array and not set to false, calc tax based on cost and passed calc_tax variable. This saves shipping methods having to do complex tax calculations.
234
		if ( ! is_array( $taxes ) && $taxes !== false && $total_cost > 0 && $this->is_taxable() ) {
235
			$taxes = 'per_item' === $args['calc_tax'] ? $this->get_taxes_per_item( $args['cost'] ) : WC_Tax::calc_shipping_tax( $total_cost, WC_Tax::get_shipping_tax_rates() );
236
		}
237
238
		// Round the total cost after taxes have been calculated.
239
		$total_cost = wc_format_decimal( $total_cost, wc_get_price_decimals() );
240
241
		// Create rate object
242
		$rate = new WC_Shipping_Rate( $args['id'], $args['label'], $total_cost, $taxes, $this->id );
0 ignored issues
show
Documentation introduced by
$total_cost is of type string|array, but the function expects a double.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
243
244
		if ( ! empty( $args['meta_data'] ) ) {
245
			foreach ( $args['meta_data'] as $key => $value ) {
246
				$rate->add_meta_data( $key, $value );
247
			}
248
		}
249
250
		$this->rates[ $args['id'] ] = $rate;
251
	}
252
253
	/**
254
	 * Calc taxes per item being shipping in costs array.
255
	 * @since 2.6.0
256
	 * @access protected
257
	 * @param  array $costs
258
	 * @return array of taxes
259
	 */
260
	protected function get_taxes_per_item( $costs ) {
261
		$taxes = array();
262
263
		// If we have an array of costs we can look up each items tax class and add tax accordingly
264
		if ( is_array( $costs ) ) {
265
266
			$cart = WC()->cart->get_cart();
267
268
			foreach ( $costs as $cost_key => $amount ) {
269
				if ( ! isset( $cart[ $cost_key ] ) ) {
270
					continue;
271
				}
272
273
				$item_taxes = WC_Tax::calc_shipping_tax( $amount, WC_Tax::get_shipping_tax_rates( $cart[ $cost_key ]['data']->get_tax_class() ) );
274
275
				// Sum the item taxes
276 View Code Duplication
				foreach ( array_keys( $taxes + $item_taxes ) as $key ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
277
					$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
278
				}
279
			}
280
281
			// Add any cost for the order - order costs are in the key 'order'
282
			if ( isset( $costs['order'] ) ) {
283
				$item_taxes = WC_Tax::calc_shipping_tax( $costs['order'], WC_Tax::get_shipping_tax_rates() );
284
285
				// Sum the item taxes
286 View Code Duplication
				foreach ( array_keys( $taxes + $item_taxes ) as $key ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
287
					$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
288
				}
289
			}
290
		}
291
292
		return $taxes;
293
	}
294
295
	/**
296
	 * Is this method available?
297
	 * @param array $package
298
	 * @return bool
299
	 */
300
	public function is_available( $package ) {
301
		$available = $this->is_enabled();
302
303
		// Country availability (legacy, for non-zone based methods)
304
		if ( ! $this->instance_id ) {
305
			$countries = is_array( $this->countries ) ? $this->countries : array();
1 ignored issue
show
Deprecated Code introduced by
The property WC_Shipping_Method::$countries has been deprecated with message: 2.6.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
306
307
			switch ( $this->availability ) {
1 ignored issue
show
Deprecated Code introduced by
The property WC_Shipping_Method::$availability has been deprecated with message: 2.6.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
308
				case 'specific' :
309 View Code Duplication
				case 'including' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
310
					$available = in_array( $package['destination']['country'], array_intersect( $countries, array_keys( WC()->countries->get_shipping_countries() ) ) );
311
				break;
312 View Code Duplication
				case 'excluding' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
313
					$available = in_array( $package['destination']['country'], array_diff( array_keys( WC()->countries->get_shipping_countries() ), $countries ) );
314
				break;
315 View Code Duplication
				default :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
316
					$available = in_array( $package['destination']['country'], array_keys( WC()->countries->get_shipping_countries() ) );
317
				break;
318
			}
319
		}
320
321
		return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $available, $package );
322
	}
323
324
	/**
325
	 * Get fee to add to shipping cost.
326
	 * @param string|float $fee
327
	 * @param float $total
328
	 * @return float
329
	 */
330
	public function get_fee( $fee, $total ) {
331
		if ( strstr( $fee, '%' ) ) {
332
			$fee = ( $total / 100 ) * str_replace( '%', '', $fee );
333
		}
334
		if ( ! empty( $this->minimum_fee ) && $this->minimum_fee > $fee ) {
335
			$fee = $this->minimum_fee;
336
		}
337
		return $fee;
338
	}
339
340
	/**
341
	 * Does this method have a settings page?
342
	 * @return bool
343
	 */
344
	public function has_settings() {
345
		return $this->instance_id ? $this->supports( 'instance-settings' ) : $this->supports( 'settings' );
346
	}
347
348
	/**
349
	 * Output the shipping settings screen.
350
	 */
351
	public function admin_options() {
352
		if ( $this->instance_id ) {
353
			echo wp_kses_post( wpautop( $this->get_method_description() ) );
354
			echo '<table class="form-table">' . $this->generate_settings_html( $this->get_instance_form_fields(), false ) . '</table>';
355
		} else {
356
			echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
357
			echo wp_kses_post( wpautop( $this->get_method_description() ) );
358
			echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>';
359
		}
360
	}
361
362
	/**
363
	 * get_option function.
364
	 *
365
	 * Gets and option from the settings API, using defaults if necessary to prevent undefined notices.
366
	 *
367
	 * @param  string $key
368
	 * @param  mixed  $empty_value
369
	 * @return mixed  The value specified for the option or a default value for the option.
370
	 */
371
	public function get_option( $key, $empty_value = null ) {
372
		// Instance options take priority over global options
373
		if ( array_key_exists( $key, $this->get_instance_form_fields() ) ) {
374
			return $this->get_instance_option( $key, $empty_value );
375
		}
376
377
		// Return global option
378
		return parent::get_option( $key, $empty_value );
379
	}
380
381
	/**
382
	 * Gets an option from the settings API, using defaults if necessary to prevent undefined notices.
383
	 *
384
	 * @param  string $key
385
	 * @param  mixed  $empty_value
386
	 * @return mixed  The value specified for the option or a default value for the option.
387
	 */
388 View Code Duplication
	public function get_instance_option( $key, $empty_value = null ) {
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...
389
		if ( empty( $this->instance_settings ) ) {
390
			$this->init_instance_settings();
391
		}
392
393
		// Get option default if unset.
394
		if ( ! isset( $this->instance_settings[ $key ] ) ) {
395
			$form_fields                     = $this->get_instance_form_fields();
396
			$this->instance_settings[ $key ] = $this->get_field_default( $form_fields[ $key ] );
397
		}
398
399
		if ( ! is_null( $empty_value ) && '' === $this->instance_settings[ $key ] ) {
400
			$this->instance_settings[ $key ] = $empty_value;
401
		}
402
403
		return $this->instance_settings[ $key ];
404
	}
405
406
	/**
407
	 * Get settings fields for instances of this shipping method (within zones).
408
	 * Should be overridden by shipping methods to add options.
409
	 * @since 2.6.0
410
	 * @return array
411
	 */
412
	public function get_instance_form_fields() {
413
		return apply_filters( 'woocommerce_shipping_instance_form_fields_' . $this->id, $this->instance_form_fields );
414
	}
415
416
	/**
417
	 * Return the name of the option in the WP DB.
418
	 * @since 2.6.0
419
	 * @return string
420
	 */
421
	public function get_instance_option_key() {
422
		return $this->instance_id ? $this->plugin_id . $this->id . '_' . $this->instance_id . '_settings' : '';
423
	}
424
425
	/**
426
	 * Initialise Settings for instances.
427
	 * @since 2.6.0
428
	 */
429 View Code Duplication
	public function init_instance_settings() {
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...
430
		$this->instance_settings = get_option( $this->get_instance_option_key(), null );
431
432
		// If there are no settings defined, use defaults.
433
		if ( ! is_array( $this->instance_settings ) ) {
434
			$form_fields             = $this->get_instance_form_fields();
435
			$this->instance_settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) );
436
		}
437
	}
438
439
	/**
440
	 * Processes and saves options.
441
	 * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
442
	 * @since 2.6.0
443
	 * @return bool was anything saved?
444
	 */
445
	public function process_admin_options() {
446
		if ( $this->instance_id ) {
447
			$this->init_instance_settings();
448
449 View Code Duplication
			foreach ( $this->get_instance_form_fields() as $key => $field ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
450
				if ( 'title' !== $this->get_field_type( $field ) ) {
451
					try {
452
						$this->instance_settings[ $key ] = $this->get_field_value( $key, $field );
453
					} catch ( Exception $e ) {
454
						$this->add_error( $e->getMessage() );
455
					}
456
				}
457
			}
458
459
			return update_option( $this->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_settings_values', $this->instance_settings, $this ) );
460
		} else {
461
			return parent::process_admin_options();
462
		}
463
	}
464
}
465