WC_Shipping_Method::init_instance_settings()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
dl 9
loc 9
rs 9.6666
c 0
b 0
f 0
eloc 5
nc 2
nop 0
1
<?php
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
	 * - instance-settings-modal Allows the instance settings to be loaded within a modal in the zones UI.
26
	 * @var array
27
	 */
28
	public $supports = array( 'settings' );
29
30
	/**
31
	 * Unique ID for the shipping method - must be set.
32
	 * @var string
33
	 */
34
	public $id = '';
35
36
	/**
37
	 * Method title.
38
	 * @var string
39
	 */
40
	public $method_title = '';
41
42
	/**
43
	 * Method description.
44
	 * @var string
45
	 */
46
	public $method_description = '';
47
48
	/**
49
	 * yes or no based on whether the method is enabled.
50
	 * @var string
51
	 */
52
	public $enabled = 'yes';
53
54
	/**
55
	 * Shipping method title for the frontend.
56
	 * @var string
57
	 */
58
	public $title;
59
60
	/**
61
	 * This is an array of rates - methods must populate this array to register shipping costs.
62
	 * @var array
63
	 */
64
	public $rates = array();
65
66
	/**
67
	 * If 'taxable' tax will be charged for this method (if applicable).
68
	 * @var string
69
	 */
70
	public $tax_status = 'taxable';
71
72
	/**
73
	 * Fee for the method (if applicable).
74
	 * @var string
75
	 */
76
	public $fee = null;
77
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
	 * Returns a rate ID based on this methods ID and instance, with an optional
212
	 * suffix if distinguishing between multiple rates.
213
	 * @since 2.6.0
214
	 * @param string $suffix
215
	 * @return string
216
	 */
217
	public function get_rate_id( $suffix = '' ) {
218
		$rate_id = array( $this->id );
219
220
		if ( $this->instance_id ) {
221
			$rate_id[] = $this->instance_id;
222
		}
223
224
		if ( $suffix ) {
225
			$rate_id[] = $suffix;
226
		}
227
228
		return implode( ':', $rate_id );
229
	}
230
231
	/**
232
	 * Add a shipping rate. If taxes are not set they will be calculated based on cost.
233
	 * @param array $args (default: array())
234
	 */
235
	public function add_rate( $args = array() ) {
236
		$args = wp_parse_args( $args, array(
237
			'id'        => $this->get_rate_id(), // ID for the rate. If not passed, this id:instance default will be used.
238
			'label'     => '', // Label for the rate
239
			'cost'      => '0', // Amount or array of costs (per item shipping)
240
			'taxes'     => '', // Pass taxes, or leave empty to have it calculated for you, or 'false' to disable calculations
241
			'calc_tax'  => 'per_order', // Calc tax per_order or per_item. Per item needs an array of costs
242
			'meta_data' => array(), // Array of misc meta data to store along with this rate - key value pairs.
243
			'package'   => false, // Package array this rate was generated for @since 2.6.0
244
		) );
245
246
		// ID and label are required
247
		if ( ! $args['id'] || ! $args['label'] ) {
248
			return;
249
		}
250
251
		// Total up the cost
252
		$total_cost = is_array( $args['cost'] ) ? array_sum( $args['cost'] ) : $args['cost'];
253
		$taxes      = $args['taxes'];
254
255
		// 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.
256
		if ( ! is_array( $taxes ) && $taxes !== false && $total_cost > 0 && $this->is_taxable() ) {
257
			$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() );
258
		}
259
260
		// Round the total cost after taxes have been calculated.
261
		$total_cost = wc_format_decimal( $total_cost, wc_get_price_decimals() );
262
263
		// Create rate object
264
		$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 integer.

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...
265
266
		if ( ! empty( $args['meta_data'] ) ) {
267
			foreach ( $args['meta_data'] as $key => $value ) {
268
				$rate->add_meta_data( $key, $value );
269
			}
270
		}
271
272
		// Store package data
273
		if ( $args['package'] ) {
274
			$items_in_package = array();
275
			foreach ( $args['package']['contents'] as $item ) {
276
				$product = $item['data'];
277
				$items_in_package[] = $product->get_title() . ' &times; ' . $item['quantity'];
278
			}
279
			$rate->add_meta_data( __( 'Items', 'woocommerce' ), implode( ', ', $items_in_package ) );
280
		}
281
282
		$this->rates[ $args['id'] ] = $rate;
283
	}
284
285
	/**
286
	 * Calc taxes per item being shipping in costs array.
287
	 * @since 2.6.0
288
	 * @access protected
289
	 * @param  array $costs
290
	 * @return array of taxes
291
	 */
292
	protected function get_taxes_per_item( $costs ) {
293
		$taxes = array();
294
295
		// If we have an array of costs we can look up each items tax class and add tax accordingly
296
		if ( is_array( $costs ) ) {
297
298
			$cart = WC()->cart->get_cart();
299
300
			foreach ( $costs as $cost_key => $amount ) {
301
				if ( ! isset( $cart[ $cost_key ] ) ) {
302
					continue;
303
				}
304
305
				$item_taxes = WC_Tax::calc_shipping_tax( $amount, WC_Tax::get_shipping_tax_rates( $cart[ $cost_key ]['data']->get_tax_class() ) );
306
307
				// Sum the item taxes
308 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...
309
					$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
310
				}
311
			}
312
313
			// Add any cost for the order - order costs are in the key 'order'
314
			if ( isset( $costs['order'] ) ) {
315
				$item_taxes = WC_Tax::calc_shipping_tax( $costs['order'], WC_Tax::get_shipping_tax_rates() );
316
317
				// Sum the item taxes
318 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...
319
					$taxes[ $key ] = ( isset( $item_taxes[ $key ] ) ? $item_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
320
				}
321
			}
322
		}
323
324
		return $taxes;
325
	}
326
327
	/**
328
	 * Is this method available?
329
	 * @param array $package
330
	 * @return bool
331
	 */
332
	public function is_available( $package ) {
333
		$available = $this->is_enabled();
334
335
		// Country availability (legacy, for non-zone based methods)
336
		if ( ! $this->instance_id ) {
337
			$countries = is_array( $this->countries ) ? $this->countries : array();
338
339
			switch ( $this->availability ) {
340
				case 'specific' :
341 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...
342
					$available = in_array( $package['destination']['country'], array_intersect( $countries, array_keys( WC()->countries->get_shipping_countries() ) ) );
343
				break;
344 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...
345
					$available = in_array( $package['destination']['country'], array_diff( array_keys( WC()->countries->get_shipping_countries() ), $countries ) );
346
				break;
347 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...
348
					$available = in_array( $package['destination']['country'], array_keys( WC()->countries->get_shipping_countries() ) );
349
				break;
350
			}
351
		}
352
353
		return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $available, $package );
354
	}
355
356
	/**
357
	 * Get fee to add to shipping cost.
358
	 * @param string|float $fee
359
	 * @param float $total
360
	 * @return float
361
	 */
362
	public function get_fee( $fee, $total ) {
363
		if ( strstr( $fee, '%' ) ) {
364
			$fee = ( $total / 100 ) * str_replace( '%', '', $fee );
365
		}
366
		if ( ! empty( $this->minimum_fee ) && $this->minimum_fee > $fee ) {
367
			$fee = $this->minimum_fee;
368
		}
369
		return $fee;
370
	}
371
372
	/**
373
	 * Does this method have a settings page?
374
	 * @return bool
375
	 */
376
	public function has_settings() {
377
		return $this->instance_id ? $this->supports( 'instance-settings' ) : $this->supports( 'settings' );
378
	}
379
380
	/**
381
	 * Return admin options as a html string.
382
	 * @return string
383
	 */
384
	public function get_admin_options_html() {
385
		if ( $this->instance_id ) {
386
			$settings_html = $this->generate_settings_html( $this->get_instance_form_fields(), false );
387
		} else {
388
			$settings_html = $this->generate_settings_html( $this->get_form_fields(), false );
389
		}
390
391
		return '<table class="form-table">' . $settings_html . '</table>';
392
	}
393
394
	/**
395
	 * Output the shipping settings screen.
396
	 */
397
	public function admin_options() {
398
		if ( ! $this->instance_id ) {
399
			echo '<h2>' . esc_html( $this->get_method_title() ) . '</h2>';
400
		}
401
		echo wp_kses_post( wpautop( $this->get_method_description() ) );
402
		echo $this->get_admin_options_html();
403
	}
404
405
	/**
406
	 * get_option function.
407
	 *
408
	 * Gets and option from the settings API, using defaults if necessary to prevent undefined notices.
409
	 *
410
	 * @param  string $key
411
	 * @param  mixed  $empty_value
412
	 * @return mixed  The value specified for the option or a default value for the option.
413
	 */
414
	public function get_option( $key, $empty_value = null ) {
415
		// Instance options take priority over global options
416
		if ( array_key_exists( $key, $this->get_instance_form_fields() ) ) {
417
			return $this->get_instance_option( $key, $empty_value );
418
		}
419
420
		// Return global option
421
		return parent::get_option( $key, $empty_value );
422
	}
423
424
	/**
425
	 * Gets an option from the settings API, using defaults if necessary to prevent undefined notices.
426
	 *
427
	 * @param  string $key
428
	 * @param  mixed  $empty_value
429
	 * @return mixed  The value specified for the option or a default value for the option.
430
	 */
431 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...
432
		if ( empty( $this->instance_settings ) ) {
433
			$this->init_instance_settings();
434
		}
435
436
		// Get option default if unset.
437
		if ( ! isset( $this->instance_settings[ $key ] ) ) {
438
			$form_fields                     = $this->get_instance_form_fields();
439
			$this->instance_settings[ $key ] = $this->get_field_default( $form_fields[ $key ] );
440
		}
441
442
		if ( ! is_null( $empty_value ) && '' === $this->instance_settings[ $key ] ) {
443
			$this->instance_settings[ $key ] = $empty_value;
444
		}
445
446
		return $this->instance_settings[ $key ];
447
	}
448
449
	/**
450
	 * Get settings fields for instances of this shipping method (within zones).
451
	 * Should be overridden by shipping methods to add options.
452
	 * @since 2.6.0
453
	 * @return array
454
	 */
455
	public function get_instance_form_fields() {
456
		return apply_filters( 'woocommerce_shipping_instance_form_fields_' . $this->id, $this->instance_form_fields );
457
	}
458
459
	/**
460
	 * Return the name of the option in the WP DB.
461
	 * @since 2.6.0
462
	 * @return string
463
	 */
464
	public function get_instance_option_key() {
465
		return $this->instance_id ? $this->plugin_id . $this->id . '_' . $this->instance_id . '_settings' : '';
466
	}
467
468
	/**
469
	 * Initialise Settings for instances.
470
	 * @since 2.6.0
471
	 */
472 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...
473
		$this->instance_settings = get_option( $this->get_instance_option_key(), null );
474
475
		// If there are no settings defined, use defaults.
476
		if ( ! is_array( $this->instance_settings ) ) {
477
			$form_fields             = $this->get_instance_form_fields();
478
			$this->instance_settings = array_merge( array_fill_keys( array_keys( $form_fields ), '' ), wp_list_pluck( $form_fields, 'default' ) );
479
		}
480
	}
481
482
	/**
483
	 * Processes and saves options.
484
	 * If there is an error thrown, will continue to save and validate fields, but will leave the erroring field out.
485
	 * @since 2.6.0
486
	 * @return bool was anything saved?
487
	 */
488
	public function process_admin_options() {
489
		if ( $this->instance_id ) {
490
			$this->init_instance_settings();
491
492
			$post_data = $this->get_post_data();
493
494 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...
495
				if ( 'title' !== $this->get_field_type( $field ) ) {
496
					try {
497
						$this->instance_settings[ $key ] = $this->get_field_value( $key, $field, $post_data );
498
					} catch ( Exception $e ) {
499
						$this->add_error( $e->getMessage() );
500
					}
501
				}
502
			}
503
504
			return update_option( $this->get_instance_option_key(), apply_filters( 'woocommerce_shipping_' . $this->id . '_instance_settings_values', $this->instance_settings, $this ) );
505
		} else {
506
			return parent::process_admin_options();
507
		}
508
	}
509
}
510