2012-08-12 14:12:52 +00:00
< ? php
2011-08-10 17:11:11 +00:00
/**
2015-11-03 13:53:50 +00:00
* WooCommerce Shipping Class
2012-08-12 14:12:52 +00:00
*
2011-08-10 17:11:11 +00:00
* Handles shipping and loads shipping methods via hooks .
*
2012-01-27 16:38:39 +00:00
* @ class WC_Shipping
2015-12-15 15:00:45 +00:00
* @ version 2.6 . 0
2012-08-14 22:43:48 +00:00
* @ package WooCommerce / Classes / Shipping
2013-02-20 17:14:46 +00:00
* @ category Class
2012-08-14 22:43:48 +00:00
* @ author WooThemes
2012-08-12 14:12:52 +00:00
*/
2012-10-15 10:57:58 +00:00
2014-09-20 19:01:02 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
2015-12-15 15:00:45 +00:00
exit ;
2014-09-20 19:01:02 +00:00
}
2012-10-15 10:57:58 +00:00
2015-12-15 15:00:45 +00:00
/**
* WC_Shipping
*/
2012-01-27 16:38:39 +00:00
class WC_Shipping {
2012-08-12 14:12:52 +00:00
2012-08-15 17:08:42 +00:00
/** @var bool True if shipping is enabled. */
2015-12-15 15:39:00 +00:00
public $enabled = false ;
2012-08-14 22:43:48 +00:00
2016-06-07 13:24:10 +00:00
/** @var array|null Stores methods loaded into woocommerce. */
2015-12-15 15:39:00 +00:00
public $shipping_methods = null ;
2012-08-14 22:43:48 +00:00
2012-08-15 17:08:42 +00:00
/** @var float Stores the cost of shipping */
2015-12-15 15:39:00 +00:00
public $shipping_total = 0 ;
2012-08-14 22:43:48 +00:00
2015-12-15 15:39:00 +00:00
/** @var array Stores an array of shipping taxes. */
public $shipping_taxes = array ();
2012-08-14 22:43:48 +00:00
2012-08-15 17:08:42 +00:00
/** @var array Stores the shipping classes. */
2015-12-15 15:39:00 +00:00
public $shipping_classes = array ();
2012-08-14 22:43:48 +00:00
2012-08-15 17:08:42 +00:00
/** @var array Stores packages to ship and to get quotes for. */
2015-12-15 15:39:00 +00:00
public $packages = array ();
2011-12-19 14:05:32 +00:00
2013-09-12 13:41:02 +00:00
/**
2014-06-19 19:43:05 +00:00
* @ var WC_Shipping The single instance of the class
2013-09-12 13:41:02 +00:00
* @ since 2.1
*/
protected static $_instance = null ;
/**
2015-11-03 13:31:20 +00:00
* Main WC_Shipping Instance .
2013-09-12 13:41:02 +00:00
*
2014-06-19 19:43:05 +00:00
* Ensures only one instance of WC_Shipping is loaded or can be loaded .
2013-09-12 13:41:02 +00:00
*
* @ since 2.1
* @ static
2014-06-19 19:43:05 +00:00
* @ return WC_Shipping Main instance
2013-09-12 13:41:02 +00:00
*/
public static function instance () {
2015-12-11 14:11:12 +00:00
if ( is_null ( self :: $_instance ) ) {
2013-09-12 13:41:02 +00:00
self :: $_instance = new self ();
2015-12-11 14:11:12 +00:00
}
2013-09-12 13:41:02 +00:00
return self :: $_instance ;
}
/**
* Cloning is forbidden .
*
* @ since 2.1
*/
public function __clone () {
2014-02-17 13:14:41 +00:00
_doing_it_wrong ( __FUNCTION__ , __ ( 'Cheatin’ huh?' , 'woocommerce' ), '2.1' );
2013-09-12 13:41:02 +00:00
}
/**
* Unserializing instances of this class is forbidden .
*
* @ since 2.1
*/
public function __wakeup () {
2014-02-17 13:14:41 +00:00
_doing_it_wrong ( __FUNCTION__ , __ ( 'Cheatin’ huh?' , 'woocommerce' ), '2.1' );
2013-09-12 13:41:02 +00:00
}
2013-01-07 17:23:09 +00:00
/**
2015-07-16 19:55:48 +00:00
* Initialize shipping .
2013-01-07 17:23:09 +00:00
*/
public function __construct () {
2016-01-05 11:23:15 +00:00
$this -> enabled = wc_shipping_enabled ();
2015-12-15 15:00:45 +00:00
if ( $this -> enabled ) {
$this -> init ();
}
2013-01-07 17:23:09 +00:00
}
2016-07-11 14:56:35 +00:00
/**
* Initialize shipping .
*/
public function init () {
2012-05-29 14:02:18 +00:00
do_action ( 'woocommerce_shipping_init' );
}
2012-08-12 14:12:52 +00:00
2015-12-11 14:11:12 +00:00
/**
* Shipping methods register themselves by returning their main class name through the woocommerce_shipping_methods filter .
* @ return array
*/
public function get_shipping_method_class_names () {
// Unique Method ID => Method Class name
2016-02-10 21:22:44 +00:00
$shipping_methods = array (
2016-02-10 13:21:16 +00:00
'flat_rate' => 'WC_Shipping_Flat_Rate' ,
'free_shipping' => 'WC_Shipping_Free_Shipping' ,
'local_pickup' => 'WC_Shipping_Local_Pickup' ,
2016-02-10 21:22:44 +00:00
);
2015-12-18 14:24:34 +00:00
// For backwards compatibility with 2.5.x we load any ENABLED legacy shipping methods here
$maybe_load_legacy_methods = array ( 'flat_rate' , 'free_shipping' , 'international_delivery' , 'local_delivery' , 'local_pickup' );
foreach ( $maybe_load_legacy_methods as $method ) {
$options = get_option ( 'woocommerce_' . $method . '_settings' );
if ( $options && isset ( $options [ 'enabled' ] ) && 'yes' === $options [ 'enabled' ] ) {
$shipping_methods [ 'legacy_' . $method ] = 'WC_Shipping_Legacy_' . $method ;
}
}
2016-02-10 21:25:05 +00:00
return apply_filters ( 'woocommerce_shipping_methods' , $shipping_methods );
2015-12-11 14:11:12 +00:00
}
2012-05-29 14:02:18 +00:00
/**
* Loads all shipping methods which are hooked in . If a $package is passed some methods may add themselves conditionally .
*
2015-12-15 15:39:00 +00:00
* Loads all shipping methods which are hooked in .
* If a $package is passed some methods may add themselves conditionally and zones will be used .
2012-08-12 14:12:52 +00:00
*
2014-02-12 11:54:43 +00:00
* @ param array $package
2012-05-29 14:02:18 +00:00
* @ return array
*/
2014-02-12 11:54:43 +00:00
public function load_shipping_methods ( $package = array () ) {
2016-06-06 18:39:23 +00:00
if ( ! empty ( $package ) ) {
2016-06-16 10:28:53 +00:00
$status_options = get_option ( 'woocommerce_status_options' , array () );
2015-12-15 15:00:45 +00:00
$shipping_zone = WC_Shipping_Zones :: get_zone_matching_package ( $package );
2016-03-15 17:23:06 +00:00
$this -> shipping_methods = $shipping_zone -> get_shipping_methods ( true );
2016-06-16 10:28:53 +00:00
// Debug output
2016-06-20 12:22:58 +00:00
if ( ! empty ( $status_options [ 'shipping_debug_mode' ] ) && ! defined ( 'WOOCOMMERCE_CHECKOUT' ) && ! wc_has_notice ( 'Customer matched zone "' . $shipping_zone -> get_zone_name () . '"' ) ) {
2016-06-16 10:28:53 +00:00
wc_add_notice ( 'Customer matched zone "' . $shipping_zone -> get_zone_name () . '"' );
}
2015-12-15 15:00:45 +00:00
} else {
$this -> shipping_methods = array ();
}
2012-08-12 14:12:52 +00:00
2015-12-15 15:39:00 +00:00
// For the settings in the backend, and for non-shipping zone methods, we still need to load any registered classes here.
2015-12-15 15:00:45 +00:00
foreach ( $this -> get_shipping_method_class_names () as $method_id => $method_class ) {
$this -> register_shipping_method ( $method_class );
2015-04-13 14:32:54 +00:00
}
2012-08-12 14:12:52 +00:00
2015-12-15 15:39:00 +00:00
// Methods can register themselves manually through this hook if necessary.
2015-12-15 15:00:45 +00:00
do_action ( 'woocommerce_load_shipping_methods' , $package );
2012-08-12 14:12:52 +00:00
2015-12-15 15:00:45 +00:00
// Return loaded methods
return $this -> get_shipping_methods ();
2012-05-29 14:02:18 +00:00
}
2012-08-12 14:12:52 +00:00
2012-05-29 14:02:18 +00:00
/**
2015-12-15 15:00:45 +00:00
* Register a shipping method .
2012-08-12 14:12:52 +00:00
*
2015-12-15 15:00:45 +00:00
* @ param object | string $method Either the name of the method 's class, or an instance of the method' s class .
2012-05-29 14:02:18 +00:00
*/
2013-11-28 15:20:02 +00:00
public function register_shipping_method ( $method ) {
2015-04-13 14:32:54 +00:00
if ( ! is_object ( $method ) ) {
2015-12-16 16:24:58 +00:00
if ( ! class_exists ( $method ) ) {
return false ;
}
2012-06-03 12:13:52 +00:00
$method = new $method ();
2015-04-13 14:32:54 +00:00
}
2016-06-07 13:24:10 +00:00
if ( is_null ( $this -> shipping_methods ) ) {
$this -> shipping_methods = array ();
}
2015-12-15 15:00:45 +00:00
$this -> shipping_methods [ $method -> id ] = $method ;
2012-05-29 14:02:18 +00:00
}
2012-08-12 14:12:52 +00:00
2012-05-29 14:02:18 +00:00
/**
2015-07-16 19:55:48 +00:00
* Unregister shipping methods .
2012-05-29 14:02:18 +00:00
*/
2013-11-28 15:20:02 +00:00
public function unregister_shipping_methods () {
2016-05-26 12:03:40 +00:00
$this -> shipping_methods = null ;
2012-05-29 14:02:18 +00:00
}
/**
* Returns all registered shipping methods for usage .
*
* @ access public
2013-11-29 18:50:31 +00:00
* @ return array
2012-05-29 14:02:18 +00:00
*/
2013-11-28 15:20:02 +00:00
public function get_shipping_methods () {
2015-12-11 14:11:12 +00:00
if ( is_null ( $this -> shipping_methods ) ) {
$this -> load_shipping_methods ();
}
2012-05-29 14:02:18 +00:00
return $this -> shipping_methods ;
2012-08-12 14:12:52 +00:00
}
2012-04-13 11:16:24 +00:00
/**
2015-12-15 15:39:00 +00:00
* Get an array of shipping classes .
2012-04-13 11:16:24 +00:00
*
* @ access public
* @ return array
*/
2013-11-28 15:20:02 +00:00
public function get_shipping_classes () {
2015-04-13 14:32:54 +00:00
if ( empty ( $this -> shipping_classes ) ) {
2016-01-13 17:08:53 +00:00
$classes = get_terms ( 'product_shipping_class' , array ( 'hide_empty' => '0' , 'orderby' => 'name' ) );
2015-12-15 15:39:00 +00:00
$this -> shipping_classes = ! is_wp_error ( $classes ) ? $classes : array ();
2015-04-13 14:32:54 +00:00
}
2016-05-19 11:38:28 +00:00
return apply_filters ( 'woocommerce_get_shipping_classes' , $this -> shipping_classes );
2012-08-12 14:12:52 +00:00
}
2015-04-13 14:32:54 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get the default method .
2015-04-13 14:32:54 +00:00
* @ param array $available_methods
* @ param boolean $current_chosen_method
* @ return string
*/
private function get_default_method ( $available_methods , $current_chosen_method = false ) {
if ( ! empty ( $available_methods ) ) {
2015-12-15 15:49:35 +00:00
if ( ! empty ( $current_chosen_method ) ) {
if ( isset ( $available_methods [ $current_chosen_method ] ) ) {
return $available_methods [ $current_chosen_method ] -> id ;
} else {
foreach ( $available_methods as $method_key => $method ) {
if ( strpos ( $method -> id , $current_chosen_method ) === 0 ) {
return $method -> id ;
}
2015-04-13 14:32:54 +00:00
}
}
}
2015-12-15 15:49:35 +00:00
return current ( $available_methods ) -> id ;
2015-04-13 14:32:54 +00:00
}
2015-12-15 15:49:35 +00:00
return '' ;
2015-04-13 14:32:54 +00:00
}
2012-04-13 11:16:24 +00:00
/**
2012-08-12 14:12:52 +00:00
* Calculate shipping for ( multiple ) packages of cart items .
*
2013-03-03 17:07:31 +00:00
* @ param array $packages multi - dimensional array of cart items to calc shipping for
2012-04-13 11:16:24 +00:00
*/
2013-11-28 15:20:02 +00:00
public function calculate_shipping ( $packages = array () ) {
2015-04-13 14:32:54 +00:00
$this -> shipping_total = null ;
$this -> shipping_taxes = array ();
$this -> packages = array ();
2012-08-12 14:12:52 +00:00
2015-10-21 20:35:58 +00:00
if ( ! $this -> enabled || empty ( $packages ) ) {
return ;
}
2012-04-13 11:16:24 +00:00
// Calculate costs for passed packages
2015-12-15 16:22:32 +00:00
foreach ( $packages as $package_key => $package ) {
2016-05-06 16:00:01 +00:00
$this -> packages [ $package_key ] = $this -> calculate_shipping_for_package ( $package , $package_key );
2016-03-04 01:26:59 +00:00
}
2016-03-15 20:35:39 +00:00
/**
* Allow packages to be reorganized after calculate the shipping .
*
* This filter can be used to apply some extra manipulation after the shipping costs are calculated for the packages
* but before Woocommerce does anything with them . A good example of usage is to merge the shipping methods for multiple
* packages for marketplaces .
*
2016-03-15 23:26:46 +00:00
* @ since 2.6 . 0
2016-03-15 20:35:39 +00:00
*
* @ param array $packages The array of packages after shipping costs are calculated .
*/
$this -> packages = apply_filters ( 'woocommerce_shipping_packages' , $this -> packages );
2016-03-04 01:26:59 +00:00
if ( ! is_array ( $this -> packages ) || empty ( $this -> packages ) ) {
return ;
2015-03-04 10:25:02 +00:00
}
2012-08-12 14:12:52 +00:00
2013-08-14 20:00:34 +00:00
// Get all chosen methods
$chosen_methods = WC () -> session -> get ( 'chosen_shipping_methods' );
$method_counts = WC () -> session -> get ( 'shipping_method_counts' );
// Get chosen methods for each package
foreach ( $this -> packages as $i => $package ) {
$chosen_method = false ;
$method_count = false ;
2012-08-12 14:12:52 +00:00
2015-03-04 10:25:02 +00:00
if ( ! empty ( $chosen_methods [ $i ] ) ) {
2013-08-14 20:00:34 +00:00
$chosen_method = $chosen_methods [ $i ];
2015-03-04 10:25:02 +00:00
}
2012-08-12 14:12:52 +00:00
2015-03-04 10:25:02 +00:00
if ( ! empty ( $method_counts [ $i ] ) ) {
2015-12-15 16:22:32 +00:00
$method_count = absint ( $method_counts [ $i ] );
2015-03-04 10:25:02 +00:00
}
2012-08-12 14:12:52 +00:00
2015-12-15 16:22:32 +00:00
if ( sizeof ( $package [ 'rates' ] ) > 0 ) {
2012-08-12 14:12:52 +00:00
2015-04-13 14:32:54 +00:00
// If not set, not available, or available methods have changed, set to the DEFAULT option
2015-12-15 16:22:32 +00:00
if ( empty ( $chosen_method ) || ! isset ( $package [ 'rates' ][ $chosen_method ] ) || $method_count !== sizeof ( $package [ 'rates' ] ) ) {
2016-06-15 13:38:08 +00:00
$chosen_method = apply_filters ( 'woocommerce_shipping_chosen_method' , $this -> get_default_method ( $package [ 'rates' ], false ), $package [ 'rates' ], $chosen_method );
2013-08-14 20:00:34 +00:00
$chosen_methods [ $i ] = $chosen_method ;
2015-12-15 16:22:32 +00:00
$method_counts [ $i ] = sizeof ( $package [ 'rates' ] );
2013-08-14 20:00:34 +00:00
do_action ( 'woocommerce_shipping_method_chosen' , $chosen_method );
2012-04-13 11:16:24 +00:00
}
2012-11-27 16:22:47 +00:00
2013-08-14 20:00:34 +00:00
// Store total costs
if ( $chosen_method ) {
2015-12-15 16:22:32 +00:00
$rate = $package [ 'rates' ][ $chosen_method ];
2012-11-27 16:22:47 +00:00
2013-08-14 20:00:34 +00:00
// Merge cost and taxes - label and ID will be the same
$this -> shipping_total += $rate -> cost ;
2012-04-13 11:16:24 +00:00
2015-08-12 10:28:24 +00:00
if ( ! empty ( $rate -> taxes ) && is_array ( $rate -> taxes ) ) {
foreach ( array_keys ( $this -> shipping_taxes + $rate -> taxes ) as $key ) {
$this -> shipping_taxes [ $key ] = ( isset ( $rate -> taxes [ $key ] ) ? $rate -> taxes [ $key ] : 0 ) + ( isset ( $this -> shipping_taxes [ $key ] ) ? $this -> shipping_taxes [ $key ] : 0 );
}
2013-08-14 20:00:34 +00:00
}
}
2012-04-13 11:16:24 +00:00
}
}
2013-04-25 15:18:18 +00:00
2013-08-14 20:00:34 +00:00
// Save all chosen methods (array)
WC () -> session -> set ( 'chosen_shipping_methods' , $chosen_methods );
WC () -> session -> set ( 'shipping_method_counts' , $method_counts );
2011-12-02 18:54:52 +00:00
}
2011-09-06 11:11:22 +00:00
2016-08-10 11:04:52 +00:00
/**
* See if package is shippable .
* @ param array $package
* @ return boolean
*/
protected function is_package_shippable ( $package ) {
$allowed = array_keys ( WC () -> countries -> get_shipping_countries () );
return in_array ( $package [ 'destination' ][ 'country' ], $allowed );
}
2012-04-13 11:16:24 +00:00
/**
2015-11-13 20:53:44 +00:00
* Calculate shipping rates for a package ,
2012-04-13 11:16:24 +00:00
*
2015-11-13 20:53:44 +00:00
* Calculates each shipping methods cost . Rates are stored in the session based on the package hash to avoid re - calculation every page load .
2012-08-12 14:12:52 +00:00
*
2012-04-13 11:16:24 +00:00
* @ param array $package cart items
2016-05-06 16:00:01 +00:00
* @ param int $package_key Index of the package being calculated . Used to cache multiple package rates .
2013-11-28 16:49:30 +00:00
* @ return array
2012-04-13 11:16:24 +00:00
*/
2016-05-06 16:00:01 +00:00
public function calculate_shipping_for_package ( $package = array (), $package_key = 0 ) {
2016-08-10 11:04:52 +00:00
if ( ! $this -> enabled || empty ( $package ) || ! $this -> is_package_shippable ( $package ) ) {
2015-11-13 20:53:44 +00:00
return false ;
}
2012-08-12 14:12:52 +00:00
2012-04-13 11:16:24 +00:00
// Check if we need to recalculate shipping for this package
2016-08-02 16:10:26 +00:00
$package_to_hash = $package ;
// Remove data objects so hashes are consistent
foreach ( $package_to_hash [ 'contents' ] as $item_id => $item ) {
unset ( $package_to_hash [ 'contents' ][ $item_id ][ 'data' ] );
}
$package_hash = 'wc_ship_' . md5 ( json_encode ( $package_to_hash ) . WC_Cache_Helper :: get_transient_version ( 'shipping' ) );
2014-05-01 13:40:28 +00:00
$status_options = get_option ( 'woocommerce_status_options' , array () );
2016-05-06 16:00:01 +00:00
$session_key = 'shipping_for_package_' . $package_key ;
$stored_rates = WC () -> session -> get ( $session_key );
2012-08-12 14:12:52 +00:00
2015-11-13 20:53:44 +00:00
if ( ! is_array ( $stored_rates ) || $package_hash !== $stored_rates [ 'package_hash' ] || ! empty ( $status_options [ 'shipping_debug_mode' ] ) ) {
2012-04-13 11:16:24 +00:00
// Calculate shipping method rates
$package [ 'rates' ] = array ();
2012-08-12 14:12:52 +00:00
2012-05-29 14:02:18 +00:00
foreach ( $this -> load_shipping_methods ( $package ) as $shipping_method ) {
2015-12-15 16:22:32 +00:00
// Shipping instances need an ID
if ( ! $shipping_method -> supports ( 'shipping-zones' ) || $shipping_method -> get_instance_id () ) {
2016-01-05 15:06:39 +00:00
$package [ 'rates' ] = $package [ 'rates' ] + $shipping_method -> get_rates_for_package ( $package ); // + instead of array_merge maintains numeric keys
2012-04-13 11:16:24 +00:00
}
}
2012-11-27 16:22:47 +00:00
2012-11-07 16:54:38 +00:00
// Filter the calculated rates
$package [ 'rates' ] = apply_filters ( 'woocommerce_package_rates' , $package [ 'rates' ], $package );
2012-08-12 14:12:52 +00:00
2015-11-13 20:53:44 +00:00
// Store in session to avoid recalculation
2016-05-06 16:00:01 +00:00
WC () -> session -> set ( $session_key , array (
2015-11-13 20:53:44 +00:00
'package_hash' => $package_hash ,
'rates' => $package [ 'rates' ]
) );
2012-04-13 11:16:24 +00:00
} else {
2015-11-13 20:53:44 +00:00
$package [ 'rates' ] = $stored_rates [ 'rates' ];
2012-04-13 11:16:24 +00:00
}
2012-08-12 14:12:52 +00:00
2012-04-13 11:16:24 +00:00
return $package ;
2011-08-09 15:16:18 +00:00
}
2012-08-12 14:12:52 +00:00
2012-04-13 11:16:24 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get packages .
2012-04-13 11:16:24 +00:00
* @ return array
*/
2015-11-13 20:53:44 +00:00
public function get_packages () {
2013-08-14 20:00:34 +00:00
return $this -> packages ;
}
2012-04-13 11:16:24 +00:00
/**
2015-07-16 19:55:48 +00:00
* Reset shipping .
2012-08-12 14:12:52 +00:00
*
2012-04-13 11:16:24 +00:00
* Reset the totals for shipping as a whole .
*/
2013-11-28 15:20:02 +00:00
public function reset_shipping () {
2013-11-25 14:01:32 +00:00
unset ( WC () -> session -> chosen_shipping_methods );
2013-08-14 20:00:34 +00:00
$this -> shipping_total = null ;
2012-01-02 00:49:00 +00:00
$this -> shipping_taxes = array ();
2012-04-13 11:16:24 +00:00
$this -> packages = array ();
2011-08-09 15:16:18 +00:00
}
2012-08-12 14:12:52 +00:00
2015-12-15 15:00:45 +00:00
/**
* @ deprecated 2.6 . 0 Was previously used to determine sort order of methods , but this is now controlled by zones and thus unused .
*/
public function sort_shipping_methods () {
_deprecated_function ( 'sort_shipping_methods' , '2.6' , '' );
return $this -> shipping_methods ;
}
2014-09-20 19:01:02 +00:00
}