public function calculate_shipping_for_package( $package = array(), $package_key = 0 ) {
// If shipping is disabled or the package is invalid, return false.
if ( ! $this->enabled || empty( $package ) ) {
return false;
}
$package['rates'] = array();
// If the package is not shippable, e.g. trying to ship to an invalid country, do not calculate rates. We can return
// local pickup rates here however since those are not shipped.
$is_shippable = $this->is_package_shippable( $package );
// Check if we need to recalculate shipping for this package.
$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'] );
}
// Get rates stored in the WC session data for this package.
$wc_session_key = 'shipping_for_package_' . $package_key;
$stored_rates = WC()->session->get( $wc_session_key );
// Calculate the hash for this package so we can tell if it's changed since last calculation.
$package_hash = 'wc_ship_' . md5( wp_json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) {
foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
// If the package is not shippable and the shipping method does not support local pickup, skip it.
if ( ! $is_shippable && ! LocalPickupUtils::is_local_pickup_method( $shipping_method->id ) ) {
continue;
}
if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) {
/**
* Fires before getting shipping rates for a package.
*
* @since 4.3.0
* @param array $package Package of cart items.
* @param WC_Shipping_Method $shipping_method Shipping method instance.
*/
do_action( 'woocommerce_before_get_rates_for_package', $package, $shipping_method );
// Use + instead of array_merge to maintain numeric keys.
$package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package );
/**
* Fires after getting shipping rates for a package.
*
* @since 4.3.0
* @param array $package Package of cart items.
* @param WC_Shipping_Method $shipping_method Shipping method instance.
*/
do_action( 'woocommerce_after_get_rates_for_package', $package, $shipping_method );
}
}
// Hide shipping rates when free shipping is available.
if ( 'yes' === get_option( 'woocommerce_shipping_hide_rates_when_free', 'no' ) ) {
$free_shipping = array();
$local_pickup = array();
foreach ( $package['rates'] as $rate ) {
if ( 'free_shipping' === $rate->method_id ) {
$free_shipping[ $rate->id ] = $rate;
continue;
}
if ( $this->shipping_methods[ $rate->method_id ]->supports( 'local-pickup' ) || 'local_pickup' === $rate->method_id ) {
$local_pickup[ $rate->id ] = $rate;
}
}
if ( ! empty( $free_shipping ) ) {
$package['rates'] = array_merge( $free_shipping, $local_pickup );
}
}
/**
* Filter the calculated shipping rates.
*
* @see https://gist.github.com/woogists/271654709e1d27648546e83253c1a813 for cache invalidation methods.
* @since 2.0.0
* @param array $package['rates'] Package rates.
* @param array $package Package of cart items.
*/
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
// Package rates should be an array, if it was filtered into a non-array, reset it. Don't reset to the
// unfiltered value, as e.g. a 3pd could have set it to "false" to remove rates.
if ( ! is_array( $package['rates'] ) ) {
$package['rates'] = array();
}
// Store in session to avoid recalculation.
WC()->session->set(
$wc_session_key,
array(
'package_hash' => $package_hash,
'rates' => $package['rates'],
)
);
} else {
$package['rates'] = $stored_rates['rates'];
}
return $package;
}