Completed
Pull Request — master (#9826)
by Mike
15:37
created

WC_Shipping::process_admin_options()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 18
rs 8.8571
cc 6
eloc 13
nc 8
nop 0
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 21 and the first side effect is on line 15.

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
 * WooCommerce Shipping Class
4
 *
5
 * Handles shipping and loads shipping methods via hooks.
6
 *
7
 * @class 		WC_Shipping
8
 * @version		2.6.0
9
 * @package		WooCommerce/Classes/Shipping
10
 * @category	Class
11
 * @author 		WooThemes
12
 */
13
14
if ( ! defined( 'ABSPATH' ) ) {
15
	exit;
16
}
17
18
/**
19
 * WC_Shipping
20
 */
21
class WC_Shipping {
22
23
	/** @var bool True if shipping is enabled. */
24
	public $enabled					 = false;
25
26
	/** @var array Stores methods loaded into woocommerce. */
27
	public $shipping_methods         = null;
28
29
	/** @var float Stores the cost of shipping */
30
	public $shipping_total 			 = 0;
31
32
	/** @var array Stores an array of shipping taxes. */
33
	public $shipping_taxes			 = array();
34
35
	/** @var array Stores the shipping classes. */
36
	public $shipping_classes		 = array();
37
38
	/** @var array Stores packages to ship and to get quotes for. */
39
	public $packages				 = array();
40
41
	/**
42
	 * @var WC_Shipping The single instance of the class
43
	 * @since 2.1
44
	 */
45
	protected static $_instance = null;
46
47
	/**
48
	 * Main WC_Shipping Instance.
49
	 *
50
	 * Ensures only one instance of WC_Shipping is loaded or can be loaded.
51
	 *
52
	 * @since 2.1
53
	 * @static
54
	 * @return WC_Shipping Main instance
55
	 */
56
	public static function instance() {
57
		if ( is_null( self::$_instance ) ) {
58
			self::$_instance = new self();
59
		}
60
		return self::$_instance;
61
	}
62
63
	/**
64
	 * Cloning is forbidden.
65
	 *
66
	 * @since 2.1
67
	 */
68
	public function __clone() {
69
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
70
	}
71
72
	/**
73
	 * Unserializing instances of this class is forbidden.
74
	 *
75
	 * @since 2.1
76
	 */
77
	public function __wakeup() {
78
		_doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
79
	}
80
81
	/**
82
	 * Initialize shipping.
83
	 */
84
	public function __construct() {
85
		$this->enabled = 'yes' === get_option( 'woocommerce_calc_shipping' );
86
87
		if ( $this->enabled ) {
88
			$this->init();
89
		}
90
	}
91
92
    /**
93
     * init function.
94
     */
95
    public function init() {
96
		do_action( 'woocommerce_shipping_init' );
97
	}
98
99
	/**
100
	 * Shipping methods register themselves by returning their main class name through the woocommerce_shipping_methods filter.
101
	 * @return array
102
	 */
103
	public function get_shipping_method_class_names() {
104
		// Unique Method ID => Method Class name
105
		$shipping_methods = apply_filters( 'woocommerce_shipping_methods', array(
106
			'flat_rate'              => 'WC_Shipping_Flat_Rate',
107
			'free_shipping'                 => 'WC_Shipping_Free_Shipping',
108
			'local_pickup'                  => 'WC_Shipping_Local_Pickup'
109
		) );
110
111
		// For backwards compatibility with 2.5.x we load any ENABLED legacy shipping methods here
112
		$maybe_load_legacy_methods = array( 'flat_rate', 'free_shipping', 'international_delivery', 'local_delivery', 'local_pickup' );
113
114
		foreach ( $maybe_load_legacy_methods as $method ) {
115
			$options = get_option( 'woocommerce_' . $method . '_settings' );
116
			if ( $options && isset( $options['enabled'] ) && 'yes' === $options['enabled'] ) {
117
				$shipping_methods[ 'legacy_' . $method ] = 'WC_Shipping_Legacy_' . $method;
118
			}
119
		}
120
121
		return $shipping_methods;
122
	}
123
124
	/**
125
	 * Load shipping methods.
126
	 *
127
	 * Loads all shipping methods which are hooked in.
128
	 * If a $package is passed some methods may add themselves conditionally and zones will be used.
129
	 *
130
	 * @param array $package
131
	 * @return array
132
	 */
133
	public function load_shipping_methods( $package = array() ) {
134
		if ( $package ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $package 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...
135
			$shipping_zone          = WC_Shipping_Zones::get_zone_matching_package( $package );
0 ignored issues
show
Documentation introduced by
$package is of type array, but the function expects a object.

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...
136
			$this->shipping_methods = $shipping_zone->get_shipping_methods();
137
		} else {
138
			$this->shipping_methods = array();
139
		}
140
141
		// For the settings in the backend, and for non-shipping zone methods, we still need to load any registered classes here.
142
		foreach ( $this->get_shipping_method_class_names() as $method_id => $method_class ) {
143
			$this->register_shipping_method( $method_class );
144
		}
145
146
		// Methods can register themselves manually through this hook if necessary.
147
		do_action( 'woocommerce_load_shipping_methods', $package );
148
149
		// Return loaded methods
150
		return $this->get_shipping_methods();
151
	}
152
153
	/**
154
	 * Register a shipping method.
155
	 *
156
	 * @param object|string $method Either the name of the method's class, or an instance of the method's class.
157
	 */
158
	public function register_shipping_method( $method ) {
159
		if ( ! is_object( $method ) ) {
160
			if ( ! class_exists( $method ) ) {
161
				return false;
162
			}
163
			$method = new $method();
164
		}
165
		$this->shipping_methods[ $method->id ] = $method;
166
	}
167
168
	/**
169
	 * Unregister shipping methods.
170
	 */
171
	public function unregister_shipping_methods() {
172
		$this->shipping_methods = array();
173
	}
174
175
	/**
176
	 * Returns all registered shipping methods.
177
	 *
178
	 * @access public
179
	 * @return array
180
	 */
181
	public function get_shipping_methods() {
182
		if ( is_null( $this->shipping_methods ) ) {
183
			$this->load_shipping_methods();
184
		}
185
		return $this->shipping_methods;
186
	}
187
188
	/**
189
	 * Get an array of shipping classes.
190
	 *
191
	 * @access public
192
	 * @return array
193
	 */
194
	public function get_shipping_classes() {
195
		if ( empty( $this->shipping_classes ) ) {
196
			$classes                = get_terms( 'product_shipping_class', array( 'hide_empty' => '0' ) );
197
			$this->shipping_classes = ! is_wp_error( $classes ) ? $classes : array();
198
		}
199
		return $this->shipping_classes;
200
	}
201
202
	/**
203
	 * Get the default method.
204
	 * @param  array  $available_methods
205
	 * @param  boolean $current_chosen_method
206
	 * @return string
207
	 */
208
	private function get_default_method( $available_methods, $current_chosen_method = false ) {
209
		if ( ! empty( $available_methods ) ) {
210
			if ( ! empty( $current_chosen_method ) ) {
211
				if ( isset( $available_methods[ $current_chosen_method ] ) ) {
212
					return $available_methods[ $current_chosen_method ]->id;
213
				} else {
214
					foreach ( $available_methods as $method_key => $method ) {
215
						if ( strpos( $method->id, $current_chosen_method ) === 0 ) {
216
							return $method->id;
217
						}
218
					}
219
				}
220
			}
221
			return current( $available_methods )->id;
222
		}
223
		return '';
224
	}
225
226
	/**
227
	 * Calculate shipping for (multiple) packages of cart items.
228
	 *
229
	 * @param array $packages multi-dimensional array of cart items to calc shipping for
230
	 */
231
	public function calculate_shipping( $packages = array() ) {
232
		$this->shipping_total = null;
233
		$this->shipping_taxes = array();
234
		$this->packages       = array();
235
236
		if ( ! $this->enabled || empty( $packages ) ) {
237
			return;
238
		}
239
240
		// Calculate costs for passed packages
241
		foreach ( $packages as $package_key => $package ) {
242
			$this->packages[ $package_key ] = $this->calculate_shipping_for_package( $package );
243
		}
244
245
		// Get all chosen methods
246
		$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
247
		$method_counts  = WC()->session->get( 'shipping_method_counts' );
248
249
		// Get chosen methods for each package
250
		foreach ( $this->packages as $i => $package ) {
251
			$chosen_method    = false;
252
			$method_count     = false;
253
254
			if ( ! empty( $chosen_methods[ $i ] ) ) {
255
				$chosen_method = $chosen_methods[ $i ];
256
			}
257
258
			if ( ! empty( $method_counts[ $i ] ) ) {
259
				$method_count = absint( $method_counts[ $i ] );
260
			}
261
262
			if ( sizeof( $package['rates'] ) > 0 ) {
263
264
				// If not set, not available, or available methods have changed, set to the DEFAULT option
265
				if ( empty( $chosen_method ) || ! isset( $package['rates'][ $chosen_method ] ) || $method_count !== sizeof( $package['rates'] ) ) {
266
					$chosen_method        = apply_filters( 'woocommerce_shipping_chosen_method', $this->get_default_method( $package['rates'], $chosen_method ), $package['rates'] );
267
					$chosen_methods[ $i ] = $chosen_method;
268
					$method_counts[ $i ]  = sizeof( $package['rates'] );
269
					do_action( 'woocommerce_shipping_method_chosen', $chosen_method );
270
				}
271
272
				// Store total costs
273
				if ( $chosen_method ) {
274
					$rate = $package['rates'][ $chosen_method ];
275
276
					// Merge cost and taxes - label and ID will be the same
277
					$this->shipping_total += $rate->cost;
278
279
					if ( ! empty( $rate->taxes ) && is_array( $rate->taxes ) ) {
280 View Code Duplication
						foreach ( array_keys( $this->shipping_taxes + $rate->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...
281
							$this->shipping_taxes[ $key ] = ( isset( $rate->taxes[$key] ) ? $rate->taxes[$key] : 0 ) + ( isset( $this->shipping_taxes[$key] ) ? $this->shipping_taxes[$key] : 0 );
282
						}
283
					}
284
				}
285
			}
286
		}
287
288
		// Save all chosen methods (array)
289
		WC()->session->set( 'chosen_shipping_methods', $chosen_methods );
290
		WC()->session->set( 'shipping_method_counts', $method_counts );
291
	}
292
293
	/**
294
	 * Calculate shipping rates for a package,
295
	 *
296
	 * Calculates each shipping methods cost. Rates are stored in the session based on the package hash to avoid re-calculation every page load.
297
	 *
298
	 * @param array $package cart items
299
	 * @return array
300
	 */
301
	public function calculate_shipping_for_package( $package = array() ) {
302
		if ( ! $this->enabled || ! $package ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $package 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...
303
			return false;
304
		}
305
306
		// Check if we need to recalculate shipping for this package
307
		$package_hash   = 'wc_ship_' . md5( json_encode( $package ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
308
		$status_options = get_option( 'woocommerce_status_options', array() );
309
		$stored_rates   = WC()->session->get( 'shipping_for_package' );
310
311
		if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || ! empty( $status_options['shipping_debug_mode'] ) ) {
312
			// Calculate shipping method rates
313
			$package['rates'] = array();
314
315
			foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
0 ignored issues
show
Bug introduced by
The expression $this->load_shipping_methods($package) of type null|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...
316
				// Shipping instances need an ID
317
				if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) {
318
					$package['rates'] = array_merge( $package['rates'], $shipping_method->get_rates_for_package( $package ) );
319
				}
320
			}
321
322
			// Filter the calculated rates
323
			$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
324
325
			// Store in session to avoid recalculation
326
			WC()->session->set( 'shipping_for_package', array(
327
				'package_hash' => $package_hash,
328
				'rates'        => $package['rates']
329
			) );
330
		} else {
331
			$package['rates'] = $stored_rates['rates'];
332
		}
333
334
		return $package;
335
	}
336
337
	/**
338
	 * Get packages.
339
	 * @return array
340
	 */
341
	public function get_packages() {
342
		return $this->packages;
343
	}
344
345
	/**
346
	 * Reset shipping.
347
	 *
348
	 * Reset the totals for shipping as a whole.
349
	 */
350
	public function reset_shipping() {
351
		unset( WC()->session->chosen_shipping_methods );
352
		$this->shipping_total = null;
353
		$this->shipping_taxes = array();
354
		$this->packages = array();
355
	}
356
357
	/**
358
	 * @deprecated 2.6.0 Was previously used to determine sort order of methods, but this is now controlled by zones and thus unused.
359
	 */
360
	public function sort_shipping_methods() {
361
		_deprecated_function( 'sort_shipping_methods', '2.6', '' );
362
		return $this->shipping_methods;
363
	}
364
}
365