1 <?php
  2 /**
  3  * WooCommerce Shipping Class
  4  *
  5  * Handles shipping and loads shipping methods via hooks.
  6  *
  7  * @class       WC_Shipping
  8  * @version     1.6.4
  9  * @package     WooCommerce/Classes/Shipping
 10  * @category    Class
 11  * @author      WooThemes
 12  */
 13 
 14 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 15 
 16 class WC_Shipping {
 17 
 18     /** @var bool True if shipping is enabled. */
 19     var $enabled                    = false;
 20 
 21     /** @var array Stores methods loaded into woocommerce. */
 22     var $shipping_methods           = array();
 23 
 24     /** @var float Stores the cost of shipping */
 25     var $shipping_total             = 0;
 26 
 27     /**  @var array Stores an array of shipping taxes. */
 28     var $shipping_taxes             = array();
 29 
 30     /** @var array Stores the shipping classes. */
 31     var $shipping_classes           = array();
 32 
 33     /** @var array Stores packages to ship and to get quotes for. */
 34     var $packages                   = array();
 35 
 36     /**
 37      * @var WooCommerce The single instance of the class
 38      * @since 2.1
 39      */
 40     protected static $_instance = null;
 41 
 42     /**
 43      * Main WooCommerce Instance
 44      *
 45      * Ensures only one instance of WooCommerce is loaded or can be loaded.
 46      *
 47      * @since 2.1
 48      * @static
 49      * @see WC()
 50      * @return Main WooCommerce instance
 51      */
 52     public static function instance() {
 53         if ( is_null( self::$_instance ) )
 54             self::$_instance = new self();
 55         return self::$_instance;
 56     }
 57 
 58     /**
 59      * Cloning is forbidden.
 60      *
 61      * @since 2.1
 62      */
 63     public function __clone() {
 64         _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
 65     }
 66 
 67     /**
 68      * Unserializing instances of this class is forbidden.
 69      *
 70      * @since 2.1
 71      */
 72     public function __wakeup() {
 73         _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'woocommerce' ), '2.1' );
 74     }
 75 
 76     /**
 77      * __construct function.
 78      *
 79      * @access public
 80      * @return void
 81      */
 82     public function __construct() {
 83         $this->init();
 84     }
 85 
 86     /**
 87      * init function.
 88      *
 89      * @access public
 90      */
 91     public function init() {
 92         do_action( 'woocommerce_shipping_init' );
 93 
 94         $this->enabled = ( get_option('woocommerce_calc_shipping') == 'no' ) ? false : true;
 95     }
 96 
 97     /**
 98      * load_shipping_methods function.
 99      *
100      * Loads all shipping methods which are hooked in. If a $package is passed some methods may add themselves conditionally.
101      *
102      * Methods are sorted into their user-defined order after being loaded.
103      *
104      * @access public
105      * @param array $package
106      * @return array
107      */
108     public function load_shipping_methods( $package = array() ) {
109 
110         $this->unregister_shipping_methods();
111 
112         // Methods can register themselves through this hook
113         do_action( 'woocommerce_load_shipping_methods', $package );
114 
115         // Register methods through a filter
116         $shipping_methods_to_load = apply_filters( 'woocommerce_shipping_methods', array(
117             'WC_Shipping_Flat_Rate',
118             'WC_Shipping_Free_Shipping',
119             'WC_Shipping_International_Delivery',
120             'WC_Shipping_Local_Delivery',
121             'WC_Shipping_Local_Pickup'
122         ) );
123 
124         foreach ( $shipping_methods_to_load as $method )
125             $this->register_shipping_method( $method );
126 
127         $this->sort_shipping_methods();
128 
129         return $this->shipping_methods;
130     }
131 
132     /**
133      * Register a shipping method for use in calculations.
134      *
135      * @access public
136      * @param  object|string $method Either the name of the method's class, or an instance of the method's class
137      * @return void
138      */
139     public function register_shipping_method( $method ) {
140 
141         if ( ! is_object( $method ) )
142             $method = new $method();
143 
144         $id = empty( $method->instance_id ) ? $method->id : $method->instance_id;
145 
146         $this->shipping_methods[ $id ] = $method;
147     }
148 
149     /**
150      * unregister_shipping_methods function.
151      *
152      * @access public
153      * @return void
154      */
155     public function unregister_shipping_methods() {
156         unset( $this->shipping_methods );
157     }
158 
159     /**
160      * sort_shipping_methods function.
161      *
162      * Sorts shipping methods into the user defined order.
163      *
164      * @access public
165      * @return array
166      */
167     public function sort_shipping_methods() {
168 
169         $sorted_shipping_methods = array();
170 
171         // Get order option
172         $ordering   = (array) get_option('woocommerce_shipping_method_order');
173         $order_end  = 999;
174 
175         // Load shipping methods in order
176         foreach ( $this->shipping_methods as $method ) {
177 
178             if ( isset( $ordering[ $method->id ] ) && is_numeric( $ordering[ $method->id ] ) ) {
179                 // Add in position
180                 $sorted_shipping_methods[ $ordering[ $method->id ] ][] = $method;
181             } else {
182                 // Add to end of the array
183                 $sorted_shipping_methods[ $order_end ][] = $method;
184             }
185         }
186 
187         ksort( $sorted_shipping_methods );
188 
189         $this->shipping_methods = array();
190 
191         foreach ( $sorted_shipping_methods as $methods )
192             foreach ( $methods as $method ) {
193                 $id = empty( $method->instance_id ) ? $method->id : $method->instance_id;
194                 $this->shipping_methods[ $id ] = $method;
195             }
196 
197         return $this->shipping_methods;
198     }
199 
200     /**
201      * get_shipping_methods function.
202      *
203      * Returns all registered shipping methods for usage.
204      *
205      * @access public
206      * @return array
207      */
208     public function get_shipping_methods() {
209         return $this->shipping_methods;
210     }
211 
212     /**
213      * get_shipping_classes function.
214      *
215      * Load shipping classes taxonomy terms.
216      *
217      * @access public
218      * @return array
219      */
220     public function get_shipping_classes() {
221         if ( empty( $this->shipping_classes ) )
222             $this->shipping_classes = ( $classes = get_terms( 'product_shipping_class', array( 'hide_empty' => '0' ) ) ) ? $classes : array();
223 
224         return $this->shipping_classes;
225     }
226 
227     /**
228      * calculate_shipping function.
229      *
230      * Calculate shipping for (multiple) packages of cart items.
231      *
232      * @access public
233      * @param array $packages multi-dimensional array of cart items to calc shipping for
234      */
235     public function calculate_shipping( $packages = array() ) {
236         if ( ! $this->enabled || empty( $packages ) )
237             return;
238 
239         $this->shipping_total   = null;
240         $this->shipping_taxes   = array();
241         $this->packages         = array();
242 
243         // Calculate costs for passed packages
244         $package_keys       = array_keys( $packages );
245         $package_keys_size  = sizeof( $package_keys );
246 
247         for ( $i = 0; $i < $package_keys_size; $i ++ )
248             $this->packages[ $package_keys[ $i ] ] = $this->calculate_shipping_for_package( $packages[ $package_keys[ $i ] ] );
249 
250         // Get all chosen methods
251         $chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
252         $method_counts  = WC()->session->get( 'shipping_method_counts' );
253 
254         // Get chosen methods for each package
255         foreach ( $this->packages as $i => $package ) {
256 
257             $_cheapest_cost   = false;
258             $_cheapest_method = false;
259             $chosen_method    = false;
260             $method_count     = false;
261 
262             if ( ! empty( $chosen_methods[ $i ] ) )
263                 $chosen_method = $chosen_methods[ $i ];
264 
265             if ( ! empty( $method_counts[ $i ] ) )
266                 $method_count = $method_counts[ $i ];
267 
268             // Get available methods for package
269             $_available_methods = $package['rates'];
270 
271             if ( sizeof( $_available_methods ) > 0 ) {
272 
273                 // If not set, not available, or available methods have changed, set to the default option
274                 if ( empty( $chosen_method ) || ! isset( $_available_methods[ $chosen_method ] ) || $method_count != sizeof( $_available_methods ) ) {
275 
276                     $chosen_method = apply_filters( 'woocommerce_shipping_chosen_method', get_option( 'woocommerce_default_shipping_method' ), $_available_methods );
277 
278                     // Loops methods and find a match
279                     if ( ! empty( $chosen_method ) && ! isset( $_available_methods[ $chosen_method ] ) ) {
280                         foreach ( $_available_methods as $method_id => $method ) {
281                             if ( strpos( $method->id, $chosen_method ) === 0 ) {
282                                 $chosen_method = $method->id;
283                                 break;
284                             }
285                         }
286                     }
287 
288                     if ( empty( $chosen_method ) || ! isset( $_available_methods[ $chosen_method ] ) ) {
289                         // Default to cheapest
290                         foreach ( $_available_methods as $method_id => $method ) {
291                             if ( $method->cost < $_cheapest_cost || ! is_numeric( $_cheapest_cost ) ) {
292                                 $_cheapest_cost     = $method->cost;
293                                 $_cheapest_method   = $method_id;
294                             }
295                         }
296                         $chosen_method = $_cheapest_method;
297                     }
298 
299                     // Store chosen method
300                     $chosen_methods[ $i ] = $chosen_method;
301                     $method_counts[ $i ]  = sizeof( $_available_methods );
302 
303                     // Do action for this chosen method
304                     do_action( 'woocommerce_shipping_method_chosen', $chosen_method );
305                 }
306 
307                 // Store total costs
308                 if ( $chosen_method ) {
309                     $rate = $_available_methods[ $chosen_method ];
310 
311                     // Merge cost and taxes - label and ID will be the same
312                     $this->shipping_total += $rate->cost;
313 
314                     foreach ( array_keys( $this->shipping_taxes + $rate->taxes ) as $key ) {
315                         $this->shipping_taxes[ $key ] = ( isset( $rate->taxes[$key] ) ? $rate->taxes[$key] : 0 ) + ( isset( $this->shipping_taxes[$key] ) ? $this->shipping_taxes[$key] : 0 );
316                     }
317                 }
318             }
319         }
320 
321         // Save all chosen methods (array)
322         WC()->session->set( 'chosen_shipping_methods', $chosen_methods );
323         WC()->session->set( 'shipping_method_counts', $method_counts );
324     }
325 
326     /**
327      * calculate_shipping_for_package function.
328      *
329      * Calculates each shipping methods cost. Rates are cached based on the package to speed up calculations.
330      *
331      * @access public
332      * @param array $package cart items
333      * @return array
334      * @todo Return array() instead of false for consistent return type?
335      */
336     public function calculate_shipping_for_package( $package = array() ) {
337         if ( ! $this->enabled ) return false;
338         if ( ! $package ) return false;
339 
340         // Check if we need to recalculate shipping for this package
341         $package_hash   = 'wc_ship_' . md5( json_encode( $package ) );
342         $status_options = get_option( 'woocommerce_status_options', array() );
343 
344         if ( false === ( $stored_rates = get_transient( $package_hash ) ) || ( ! empty( $status_options['shipping_debug_mode'] ) && current_user_can( 'manage_options' ) ) ) {
345 
346             // Calculate shipping method rates
347             $package['rates'] = array();
348 
349             foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
350 
351                 if ( $shipping_method->is_available( $package ) && ( empty( $package['ship_via'] ) || in_array( $shipping_method->id, $package['ship_via'] ) ) ) {
352 
353                     // Reset Rates
354                     $shipping_method->rates = array();
355 
356                     // Calculate Shipping for package
357                     $shipping_method->calculate_shipping( $package );
358 
359                     // Place rates in package array
360                     if ( ! empty( $shipping_method->rates ) && is_array( $shipping_method->rates ) )
361                         foreach ( $shipping_method->rates as $rate )
362                             $package['rates'][ $rate->id ] = $rate;
363                 }
364             }
365 
366             // Filter the calculated rates
367             $package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
368 
369             // Store
370             set_transient( $package_hash, $package['rates'], 60 * 60 ); // Cached for an hour
371 
372         } else {
373 
374             $package['rates'] = $stored_rates;
375 
376         }
377 
378         return $package;
379     }
380 
381     /**
382      * Get packages
383      * @return array
384      */
385     public  function get_packages() {
386         return $this->packages;
387     }
388 
389 
390     /**
391      * reset_shipping function.
392      *
393      * Reset the totals for shipping as a whole.
394      *
395      * @access public
396      * @return void
397      */
398     public function reset_shipping() {
399         unset( WC()->session->chosen_shipping_methods );
400         $this->shipping_total = null;
401         $this->shipping_taxes = array();
402         $this->packages = array();
403     }
404 
405 
406     /**
407      * process_admin_options function.
408      *
409      * Saves options on the shipping setting page.
410      *
411      * @access public
412      * @return void
413      */
414     public function process_admin_options() {
415 
416         $default_shipping_method = ( isset( $_POST['default_shipping_method'] ) ) ? esc_attr( $_POST['default_shipping_method'] ) : '';
417         $method_order = ( isset( $_POST['method_order'] ) ) ? $_POST['method_order'] : '';
418 
419         $order = array();
420 
421         if ( is_array( $method_order ) && sizeof( $method_order ) > 0 ) {
422             $loop = 0;
423             foreach ($method_order as $method_id) {
424                 $order[$method_id] = $loop;
425                 $loop++;
426             }
427         }
428 
429         update_option( 'woocommerce_default_shipping_method', $default_shipping_method );
430         update_option( 'woocommerce_shipping_method_order', $order );
431     }
432 
433 }
434 
435 /**
436  * Register a shipping method
437  *
438  * Registers a shipping method ready to be loaded. Accepts a class name (string) or a class object.
439  *
440  * @package     WooCommerce/Classes/Shipping
441  * @since 1.5.7
442  */
443 function woocommerce_register_shipping_method( $shipping_method ) {
444     $GLOBALS['woocommerce']->shipping->register_shipping_method( $shipping_method );
445 }
WooCommerce API documentation generated by ApiGen 2.8.0