WC_Shipping

WC_Shipping.

Defined (1)

The class is defined in the following location(s).

/includes/class-wc-shipping.php  
  1. class WC_Shipping { 
  2.  
  3. /** @var bool True if shipping is enabled. */ 
  4. public $enabled = false; 
  5.  
  6. /** @var array|null Stores methods loaded into woocommerce. */ 
  7. public $shipping_methods = null; 
  8.  
  9. /** @var float Stores the cost of shipping */ 
  10. public $shipping_total = 0; 
  11.  
  12. /** @var array Stores an array of shipping taxes. */ 
  13. public $shipping_taxes = array(); 
  14.  
  15. /** @var array Stores the shipping classes. */ 
  16. public $shipping_classes = array(); 
  17.  
  18. /** @var array Stores packages to ship and to get quotes for. */ 
  19. public $packages = array(); 
  20.  
  21. /** 
  22. * @var WC_Shipping The single instance of the class 
  23. * @since 2.1 
  24. */ 
  25. protected static $_instance = null; 
  26.  
  27. /** 
  28. * Main WC_Shipping Instance. 
  29. * Ensures only one instance of WC_Shipping is loaded or can be loaded. 
  30. * @since 2.1 
  31. * @static 
  32. * @return WC_Shipping Main instance 
  33. */ 
  34. public static function instance() { 
  35. if ( is_null( self::$_instance ) ) { 
  36. self::$_instance = new self(); 
  37. return self::$_instance; 
  38.  
  39. /** 
  40. * Cloning is forbidden. 
  41. * @since 2.1 
  42. */ 
  43. public function __clone() { 
  44. wc_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce' ), '2.1' ); 
  45.  
  46. /** 
  47. * Unserializing instances of this class is forbidden. 
  48. * @since 2.1 
  49. */ 
  50. public function __wakeup() { 
  51. wc_doing_it_wrong( __FUNCTION__, __( 'Cheatin’ huh?', 'woocommerce' ), '2.1' ); 
  52.  
  53. /** 
  54. * Initialize shipping. 
  55. */ 
  56. public function __construct() { 
  57. $this->enabled = wc_shipping_enabled(); 
  58.  
  59. if ( $this->enabled ) { 
  60. $this->init(); 
  61.  
  62. /** 
  63. * Initialize shipping. 
  64. */ 
  65. public function init() { 
  66. do_action( 'woocommerce_shipping_init' ); 
  67.  
  68. /** 
  69. * Shipping methods register themselves by returning their main class name through the woocommerce_shipping_methods filter. 
  70. * @return array 
  71. */ 
  72. public function get_shipping_method_class_names() { 
  73. // Unique Method ID => Method Class name 
  74. $shipping_methods = array( 
  75. 'flat_rate' => 'WC_Shipping_Flat_Rate',  
  76. 'free_shipping' => 'WC_Shipping_Free_Shipping',  
  77. 'local_pickup' => 'WC_Shipping_Local_Pickup',  
  78. ); 
  79.  
  80. // For backwards compatibility with 2.5.x we load any ENABLED legacy shipping methods here 
  81. $maybe_load_legacy_methods = array( 'flat_rate', 'free_shipping', 'international_delivery', 'local_delivery', 'local_pickup' ); 
  82.  
  83. foreach ( $maybe_load_legacy_methods as $method ) { 
  84. $options = get_option( 'woocommerce_' . $method . '_settings' ); 
  85. if ( $options && isset( $options['enabled'] ) && 'yes' === $options['enabled'] ) { 
  86. $shipping_methods[ 'legacy_' . $method ] = 'WC_Shipping_Legacy_' . $method; 
  87.  
  88. return apply_filters( 'woocommerce_shipping_methods', $shipping_methods ); 
  89.  
  90. /** 
  91. * Loads all shipping methods which are hooked in. If a $package is passed some methods may add themselves conditionally. 
  92. * Loads all shipping methods which are hooked in. 
  93. * If a $package is passed some methods may add themselves conditionally and zones will be used. 
  94. * @param array $package 
  95. * @return array 
  96. */ 
  97. public function load_shipping_methods( $package = array() ) { 
  98. if ( ! empty( $package ) ) { 
  99. $debug_mode = 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ); 
  100. $shipping_zone = WC_Shipping_Zones::get_zone_matching_package( $package ); 
  101. $this->shipping_methods = $shipping_zone->get_shipping_methods( true ); 
  102.  
  103. // Debug output 
  104. if ( $debug_mode && ! defined( 'WOOCOMMERCE_CHECKOUT' ) && ! wc_has_notice( 'Customer matched zone "' . $shipping_zone->get_zone_name() . '"' ) ) { 
  105. wc_add_notice( 'Customer matched zone "' . $shipping_zone->get_zone_name() . '"' ); 
  106. } else { 
  107. $this->shipping_methods = array(); 
  108.  
  109. // For the settings in the backend, and for non-shipping zone methods, we still need to load any registered classes here. 
  110. foreach ( $this->get_shipping_method_class_names() as $method_id => $method_class ) { 
  111. $this->register_shipping_method( $method_class ); 
  112.  
  113. // Methods can register themselves manually through this hook if necessary. 
  114. do_action( 'woocommerce_load_shipping_methods', $package ); 
  115.  
  116. // Return loaded methods 
  117. return $this->get_shipping_methods(); 
  118.  
  119. /** 
  120. * Register a shipping method. 
  121. * @param object|string $method Either the name of the method's class, or an instance of the method's class. 
  122. */ 
  123. public function register_shipping_method( $method ) { 
  124. if ( ! is_object( $method ) ) { 
  125. if ( ! class_exists( $method ) ) { 
  126. return false; 
  127. $method = new $method(); 
  128. if ( is_null( $this->shipping_methods ) ) { 
  129. $this->shipping_methods = array(); 
  130. $this->shipping_methods[ $method->id ] = $method; 
  131.  
  132. /** 
  133. * Unregister shipping methods. 
  134. */ 
  135. public function unregister_shipping_methods() { 
  136. $this->shipping_methods = null; 
  137.  
  138. /** 
  139. * Returns all registered shipping methods for usage. 
  140. * @access public 
  141. * @return array 
  142. */ 
  143. public function get_shipping_methods() { 
  144. if ( is_null( $this->shipping_methods ) ) { 
  145. $this->load_shipping_methods(); 
  146. return $this->shipping_methods; 
  147.  
  148. /** 
  149. * Get an array of shipping classes. 
  150. * @access public 
  151. * @return array 
  152. */ 
  153. public function get_shipping_classes() { 
  154. if ( empty( $this->shipping_classes ) ) { 
  155. $classes = get_terms( 'product_shipping_class', array( 'hide_empty' => '0', 'orderby' => 'name' ) ); 
  156. $this->shipping_classes = ! is_wp_error( $classes ) ? $classes : array(); 
  157. return apply_filters( 'woocommerce_get_shipping_classes', $this->shipping_classes ); 
  158.  
  159. /** 
  160. * Get the default method. 
  161. * @param array $available_methods 
  162. * @param boolean $current_chosen_method 
  163. * @return string 
  164. */ 
  165. private function get_default_method( $available_methods, $current_chosen_method = false ) { 
  166. if ( ! empty( $available_methods ) ) { 
  167. if ( ! empty( $current_chosen_method ) ) { 
  168. if ( isset( $available_methods[ $current_chosen_method ] ) ) { 
  169. return $available_methods[ $current_chosen_method ]->id; 
  170. } else { 
  171. foreach ( $available_methods as $method_key => $method ) { 
  172. if ( strpos( $method->id, $current_chosen_method ) === 0 ) { 
  173. return $method->id; 
  174. return current( $available_methods )->id; 
  175. return ''; 
  176.  
  177. /** 
  178. * Calculate shipping for (multiple) packages of cart items. 
  179. * @param array $packages multi-dimensional array of cart items to calc shipping for 
  180. */ 
  181. public function calculate_shipping( $packages = array() ) { 
  182. $this->shipping_total = 0; 
  183. $this->shipping_taxes = array(); 
  184. $this->packages = array(); 
  185.  
  186. if ( ! $this->enabled || empty( $packages ) ) { 
  187. return; 
  188.  
  189. // Calculate costs for passed packages 
  190. foreach ( $packages as $package_key => $package ) { 
  191. $this->packages[ $package_key ] = $this->calculate_shipping_for_package( $package, $package_key ); 
  192.  
  193. /** 
  194. * Allow packages to be reorganized after calculate the shipping. 
  195. * This filter can be used to apply some extra manipulation after the shipping costs are calculated for the packages 
  196. * but before Woocommerce does anything with them. A good example of usage is to merge the shipping methods for multiple 
  197. * packages for marketplaces. 
  198. * @since 2.6.0 
  199. * @param array $packages The array of packages after shipping costs are calculated. 
  200. */ 
  201. $this->packages = apply_filters( 'woocommerce_shipping_packages', $this->packages ); 
  202.  
  203. if ( ! is_array( $this->packages ) || empty( $this->packages ) ) { 
  204. return; 
  205.  
  206. // Get all chosen methods 
  207. $chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); 
  208. $method_counts = WC()->session->get( 'shipping_method_counts' ); 
  209.  
  210. // Get chosen methods for each package 
  211. foreach ( $this->packages as $i => $package ) { 
  212. $chosen_method = false; 
  213. $method_count = false; 
  214.  
  215. if ( ! empty( $chosen_methods[ $i ] ) ) { 
  216. $chosen_method = $chosen_methods[ $i ]; 
  217.  
  218. if ( ! empty( $method_counts[ $i ] ) ) { 
  219. $method_count = absint( $method_counts[ $i ] ); 
  220.  
  221. if ( sizeof( $package['rates'] ) > 0 ) { 
  222.  
  223. // If not set, not available, or available methods have changed, set to the DEFAULT option 
  224. if ( empty( $chosen_method ) || ! isset( $package['rates'][ $chosen_method ] ) || sizeof( $package['rates'] ) !== $method_count ) { 
  225. $chosen_method = apply_filters( 'woocommerce_shipping_chosen_method', $this->get_default_method( $package['rates'], false ), $package['rates'], $chosen_method ); 
  226. $chosen_methods[ $i ] = $chosen_method; 
  227. $method_counts[ $i ] = sizeof( $package['rates'] ); 
  228. do_action( 'woocommerce_shipping_method_chosen', $chosen_method ); 
  229.  
  230. // Store total costs 
  231. if ( $chosen_method && isset( $package['rates'][ $chosen_method ] ) ) { 
  232. $rate = $package['rates'][ $chosen_method ]; 
  233.  
  234. // Merge cost and taxes - label and ID will be the same 
  235. $this->shipping_total += $rate->cost; 
  236.  
  237. if ( ! empty( $rate->taxes ) && is_array( $rate->taxes ) ) { 
  238. foreach ( array_keys( $this->shipping_taxes + $rate->taxes ) as $key ) { 
  239. $this->shipping_taxes[ $key ] = ( isset( $rate->taxes[ $key ] ) ? $rate->taxes[ $key ] : 0 ) + ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ); 
  240.  
  241. // Save all chosen methods (array) 
  242. WC()->session->set( 'chosen_shipping_methods', $chosen_methods ); 
  243. WC()->session->set( 'shipping_method_counts', $method_counts ); 
  244.  
  245. /** 
  246. * See if package is shippable. 
  247. * @param array $package 
  248. * @return boolean 
  249. */ 
  250. protected function is_package_shippable( $package ) { 
  251. $allowed = array_keys( WC()->countries->get_shipping_countries() ); 
  252. return in_array( $package['destination']['country'], $allowed ); 
  253.  
  254. /** 
  255. * Calculate shipping rates for a package,  
  256. * Calculates each shipping methods cost. Rates are stored in the session based on the package hash to avoid re-calculation every page load. 
  257. * @param array $package cart items 
  258. * @param int $package_key Index of the package being calculated. Used to cache multiple package rates. 
  259. * @return array 
  260. */ 
  261. public function calculate_shipping_for_package( $package = array(), $package_key = 0 ) { 
  262. if ( ! $this->enabled || empty( $package ) || ! $this->is_package_shippable( $package ) ) { 
  263. return false; 
  264.  
  265. // Check if we need to recalculate shipping for this package 
  266. $package_to_hash = $package; 
  267.  
  268. // Remove data objects so hashes are consistent 
  269. foreach ( $package_to_hash['contents'] as $item_id => $item ) { 
  270. unset( $package_to_hash['contents'][ $item_id ]['data'] ); 
  271.  
  272. $package_hash = 'wc_ship_' . md5( json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) ); 
  273. $session_key = 'shipping_for_package_' . $package_key; 
  274. $stored_rates = WC()->session->get( $session_key ); 
  275.  
  276. if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) { 
  277. // Calculate shipping method rates 
  278. $package['rates'] = array(); 
  279.  
  280. foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) { 
  281. // Shipping instances need an ID 
  282. if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) { 
  283. $package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package ); // + instead of array_merge maintains numeric keys 
  284.  
  285. // Filter the calculated rates 
  286. $package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package ); 
  287.  
  288. // Store in session to avoid recalculation 
  289. WC()->session->set( $session_key, array( 
  290. 'package_hash' => $package_hash,  
  291. 'rates' => $package['rates'],  
  292. ) ); 
  293. } else { 
  294. $package['rates'] = $stored_rates['rates']; 
  295.  
  296. return $package; 
  297.  
  298. /** 
  299. * Get packages. 
  300. * @return array 
  301. */ 
  302. public function get_packages() { 
  303. return $this->packages; 
  304.  
  305. /** 
  306. * Reset shipping. 
  307. * Reset the totals for shipping as a whole. 
  308. */ 
  309. public function reset_shipping() { 
  310. unset( WC()->session->chosen_shipping_methods ); 
  311. $this->shipping_total = 0; 
  312. $this->shipping_taxes = array(); 
  313. $this->packages = array(); 
  314.  
  315. /** 
  316. * @deprecated 2.6.0 Was previously used to determine sort order of methods, but this is now controlled by zones and thus unused. 
  317. */ 
  318. public function sort_shipping_methods() { 
  319. wc_deprecated_function( 'sort_shipping_methods', '2.6' ); 
  320. return $this->shipping_methods;