Completed
Pull Request — master (#9826)
by Mike
08:43
created

WC_Shipping_Method::get_taxes_per_item()   C

Complexity

Conditions 11
Paths 3

Size

Total Lines 34
Code Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
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
	 * Availability - legacy. Used for method Availability.
86
	 * No longer useful for instance based shipping methods.
87
	 * @deprecated 2.6.0
88
	 * @var string
89
	 */
90
	public $availability;
91
92
	/**
93
	 * Availability countries - legacy. Used for method Availability.
94
	 * No longer useful for instance based shipping methods.
95
	 * @deprecated 2.6.0
96
	 * @var string
97
	 */
98
	public $countries          = array();
99
100
	/**
101
	 * Instance ID if used.
102
	 * @var int
103
	 */
104
	protected $instance_id     = 0;
105
106
	/**
107
	 * Instance settings.
108
	 * @var array
109
	 */
110
	public $instance_settings  = array();
111
112
	/**
113
	 * Constructor.
114
	 * @param int $instance_id
115
	 */
116
	public function __construct( $instance_id = 0 ) {
117
		$this->instance_id = absint( $instance_id );
118
	}
119
120
	/**
121
	 * Check if a shipping method supports a given feature.
122
	 *
123
	 * Methods should override this to declare support (or lack of support) for a feature.
124
	 *
125
	 * @param $feature string The name of a feature to test support for.
126
	 * @return bool True if the shipping method supports the feature, false otherwise.
127
	 */
128
	public function supports( $feature ) {
129
		return apply_filters( 'woocommerce_shipping_method_supports', in_array( $feature, $this->supports ), $feature, $this );
130
	}
131
132
	/**
133
	 * Called to calculate shipping rates for this method. Rates can be added using the add_rate() method.
134
	 */
135
	public function calculate_shipping() {}
136
137
	/**
138
	 * Whether or not we need to calculate tax on top of the shipping rate.
139
	 * @return boolean
140
	 */
141
	public function is_taxable() {
142
		return wc_tax_enabled() && 'taxable' === $this->tax_status && ! WC()->customer->is_vat_exempt();
143
	}
144
145
	/**
146
	 * Whether or not this method is enabled in settings.
147
	 * @since 2.6.0
148
	 * @return boolean
149
	 */
150
	public function is_enabled() {
151
		return 'yes' === $this->enabled;
152
	}
153
154
	/**
155
	 * Return the shipping method title.
156
	 * @since 2.6.0
157
	 * @return string
158
	 */
159
	public function get_method_title() {
160
		return apply_filters( 'woocommerce_shipping_method_title', $this->method_title, $this );
161
	}
162
163
	/**
164
	 * Return the shipping method description.
165
	 * @since 2.6.0
166
	 * @return string
167
	 */
168
	public function get_method_description() {
169
		return apply_filters( 'woocommerce_shipping_method_description', $this->method_description, $this );
170
	}
171
172
	/**
173
	 * Return the shipping title which is user set.
174
	 *
175
	 * @return string
176
	 */
177
	public function get_title() {
178
		return apply_filters( 'woocommerce_shipping_method_title', $this->title, $this->id );
179
	}
180
181
	/**
182
	 * Add a shipping rate. If taxes are not set they will be calculated based on cost.
183
	 * @param array $args (default: array())
184
	 */
185
	public function add_rate( $args = array() ) {
186
		$args = wp_parse_args( $args, array(
187
			'id'        => '',          // ID for the rate
188
			'label'     => '',          // Label for the rate
189
			'cost'      => '0',         // Amount or array of costs (per item shipping)
190
			'taxes'     => '',          // Pass taxes, nothing to have it calculated for you, or 'false' to calc no tax
191
			'calc_tax'  => 'per_order'  // Calc tax per_order or per_item. Per item needs an array of costs
192
		) );
193
194
		// ID and label are required
195
		if ( ! $args['id'] || ! $args['label'] ) {
196
			return;
197
		}
198
199
		// Handle cost
200
		$total_cost = is_array( $args['cost'] ) ? array_sum( $args['cost'] ) : $args['cost'];
201
		$taxes      = $args['taxes'];
202
203
		// Taxes - if not an array and not set to false, calc tax based on cost and passed calc_tax variable
204
		// This saves shipping methods having to do complex tax calculations
205
		if ( ! is_array( $taxes ) && $taxes !== false && $total_cost > 0 && $this->is_taxable() ) {
206
			switch ( $args['calc_tax'] ) {
207
				case "per_item" :
208
					$taxes = $this->get_taxes_per_item( $args['cost'] );
209
				break;
210
				default :
211
					$taxes = WC_Tax::calc_shipping_tax( $total_cost, WC_Tax::get_shipping_tax_rates() );
212
				break;
213
			}
214
		}
215
216
		$this->rates[] = new WC_Shipping_Rate( $args['id'], $args['label'], $total_cost, $taxes, $this->id );
217
	}
218
219
	/**
220
	 * Calc taxes per item being shipping in costs array.
221
	 * @since 2.6.0
222
	 * @access protected
223
	 * @param  array $costs
224
	 * @return array of taxes
225
	 */
226
	protected function get_taxes_per_item( $costs ) {
227
		$taxes = array();
228
229
		// If we have an array of costs we can look up each items tax class and add tax accordingly
230
		if ( is_array( $costs ) ) {
231
232
			$cart = WC()->cart->get_cart();
233
234
			foreach ( $costs as $cost_key => $amount ) {
235
				if ( ! isset( $cart[ $cost_key ] ) ) {
236
					continue;
237
				}
238
239
				$item_taxes = WC_Tax::calc_shipping_tax( $amount, WC_Tax::get_shipping_tax_rates( $cart[ $cost_key ]['data']->get_tax_class() ) );
240
241
				// Sum the item taxes
242
				foreach ( array_keys( $taxes + $item_taxes ) as $key ) {
243
					$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
244
				}
245
			}
246
247
			// Add any cost for the order - order costs are in the key 'order'
248
			if ( isset( $costs['order'] ) ) {
249
				$item_taxes = WC_Tax::calc_shipping_tax( $costs['order'], WC_Tax::get_shipping_tax_rates() );
250
251
				// Sum the item taxes
252
				foreach ( array_keys( $taxes + $item_taxes ) as $key ) {
253
					$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
254
				}
255
			}
256
		}
257
258
		return $taxes;
259
	}
260
261
	/**
262
	 * is_available function.
263
	 *
264
	 * @param array $package
265
	 * @return bool
266
	 */
267
	public function is_available( $package ) {
268
		if ( ! $this->is_enabled() ) {
269
			return false;
270
		}
271
272
		// Country availability
273
		$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...
274
275
		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...
276
			case 'specific' :
277
			case 'including' :
278
				$ship_to_countries = array_intersect( $countries, array_keys( WC()->countries->get_shipping_countries() ) );
279
			break;
280
			case 'excluding' :
281
				$ship_to_countries = array_diff( array_keys( WC()->countries->get_shipping_countries() ), $countries );
282
			break;
283
			default :
284
				$ship_to_countries = array_keys( WC()->countries->get_shipping_countries() );
285
			break;
286
		}
287
288
		if ( ! in_array( $package['destination']['country'], $ship_to_countries ) ) {
289
			return false;
290
		}
291
292
		return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', true, $package );
293
	}
294
295
	/**
296
	 * get_fee function.
297
	 *
298
	 * @param mixed $fee
299
	 * @param mixed $total
300
	 * @return float
301
	 */
302
	public function get_fee( $fee, $total ) {
303
		if ( strstr( $fee, '%' ) ) {
304
			$fee = ( $total / 100 ) * str_replace( '%', '', $fee );
305
		}
306
		if ( ! empty( $this->minimum_fee ) && $this->minimum_fee > $fee ) {
307
			$fee = $this->minimum_fee;
308
		}
309
		return $fee;
310
	}
311
312
	/**
313
	 * Does this method have a settings page?
314
	 * @return bool
315
	 */
316
	public function has_settings() {
317
		return $this->instance_id ? $this->supports( 'instance-settings' ) : $this->supports( 'settings' );
318
	}
319
320
	/**
321
	 * Output the shipping settings screen.
322
	 */
323
	public function admin_options() {
324
		if ( $this->instance_id ) {
325
			echo wp_kses_post( wpautop( $this->get_method_description() ) );
326
			echo '<table class="form-table">' . $this->generate_settings_html( $this->get_instance_form_fields(), false ) . '</table>';
327
		} else {
328
			echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
329
			echo wp_kses_post( wpautop( $this->get_method_description() ) );
330
			echo '<table class="form-table">' . $this->generate_settings_html( $this->get_form_fields(), false ) . '</table>';
331
		}
332
	}
333
334
	/**
335
	 * get_option function.
336
	 *
337
	 * Gets and option from the settings API, using defaults if necessary to prevent undefined notices.
338
	 *
339
	 * @param  string $key
340
	 * @param  mixed  $empty_value
341
	 * @return mixed  The value specified for the option or a default value for the option.
342
	 */
343
	public function get_option( $key, $empty_value = null ) {
344
		// Instance options take priority over global options
345
		if ( in_array( $key, array_keys( $this->get_instance_form_fields() ) ) ) {
346
			return $this->get_instance_option( $key, $empty_value );
347
		}
348
349
		// Return global option
350
		return parent::get_option( $key, $empty_value );
351
	}
352
353
	/**
354
	 * get_option function.
355
	 *
356
	 * Gets and option from the settings API, using defaults if necessary to prevent undefined notices.
357
	 *
358
	 * @param  string $key
359
	 * @param  mixed  $empty_value
360
	 * @return mixed  The value specified for the option or a default value for the option.
361
	 */
362 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...
363
		if ( empty( $this->instance_settings ) ) {
364
			$this->init_instance_settings();
365
		}
366
367
		// Get option default if unset.
368
		if ( ! isset( $this->instance_settings[ $key ] ) ) {
369
			$form_fields                     = $this->get_instance_form_fields();
370
			$this->instance_settings[ $key ] = $this->get_field_default( $form_fields[ $key ] );
371
		}
372
373
		if ( ! is_null( $empty_value ) && '' === $this->instance_settings[ $key ] ) {
374
			$this->instance_settings[ $key ] = $empty_value;
375
		}
376
377
		return $this->instance_settings[ $key ];
378
	}
379
380
	/**
381
	 * Get settings fields for instances of this shipping method (within zones).
382
	 * Should be overridden by shipping methods to add options.
383
	 * @since 2.6.0
384
	 * @return array
385
	 */
386
	public function get_instance_form_fields() {
387
		return array();
388
	}
389
390
	/**
391
	 * Return the name of the option in the WP DB.
392
	 * @since 2.6.0
393
	 * @return string
394
	 */
395
	public function get_instance_option_key() {
396
		return $this->instance_id ? $this->plugin_id . $this->id . $this->instance_id . '_settings' : '';
397
	}
398
399
	/**
400
	 * Initialise Settings for instances.
401
	 * @since 2.6.0
402
	 */
403 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...
404
		$this->instance_settings = get_option( $this->get_instance_option_key(), null );
405
406
		// If there are no settings defined, use defaults.
407
		if ( ! is_array( $this->instance_settings ) ) {
408
			$form_fields             = $this->get_instance_form_fields();
409
			$this->instance_settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) );
410
		}
411
	}
412
413
	/**
414
	 * Processes and saves options.
415
	 * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
416
	 * @since 2.6.0
417
	 * @return bool was anything saved?
418
	 */
419
	public function process_admin_options() {
420
		if ( $this->instance_id ) {
421
			$this->init_instance_settings();
422
423 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...
424
				if ( ! in_array( $this->get_field_type( $field ), array( 'title' ) ) ) {
425
					try {
426
						$this->instance_settings[ $key ] = $this->get_field_value( $key, $field );
427
					} catch ( Exception $e ) {
428
						$this->add_error( $e->getMessage() );
429
					}
430
				}
431
			}
432
433
			return update_option( $this->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_settings_values', $this->instance_settings, $this ) );
434
		} else {
435
			return parent::process_admin_options();
436
		}
437
	}
438
}
439