Skip to content

Instantly share code, notes, and snippets.

@mikejolley
Last active May 17, 2021 13:51
Show Gist options
  • Save mikejolley/347b8f162257f6736c4d to your computer and use it in GitHub Desktop.
Save mikejolley/347b8f162257f6736c4d to your computer and use it in GitHub Desktop.
WooCommerce - Split shipping class items into a new package and limit shipping methods
/**
* This function loops over cart items, and moves any item with shipping class 'special-class' into a new package.
* The new package in this example only takes flat rate shipping.
*/
function split_special_shipping_class_items( $packages ) {
$found_item = false;
$special_class = 'special-class'; // edit this with the slug of your shippig class
$new_package = current( $packages );
$new_package['contents'] = array();
$new_package['contents_cost'] = 0;
$new_package['applied_coupons'] = array();
$new_package['ship_via'] = array( 'flat_rate' ); // Only allow flat rate for items in special class
foreach ( WC()->cart->get_cart() as $item_key => $item ) {
// Is the product in the special class?
if ( $item['data']->needs_shipping() && $special_class === $item['data']->get_shipping_class() ) {
$found_item = true;
$new_package['contents'][ $item_key ] = $item;
$new_package['contents_cost'] += $item['line_total'];
// Remove from original package
$packages[0]['contents_cost'] = $packages[0]['contents_cost'] - $item['line_total'];
unset( $packages[0]['contents'][ $item_key ] );
// If there are no items left in the previous package, remove it completely.
if ( empty( $packages[0]['contents'] ) ) {
unset( $packages[0] );
}
}
}
if ( $found_item ) {
$packages[] = $new_package;
}
return $packages;
}
// Hook into shipping packages filter
add_filter( 'woocommerce_cart_shipping_packages', 'split_special_shipping_class_items' );
@androos
Copy link

androos commented May 7, 2017

I wan't items that match to be free shipping. But regardless what slug i'm using it always say, no shipping method available. Except 'flat_rate'.

Edit: ok it's not free shipping to all countries only to one. So I wanted to use advanced_free_shipping. I need this because the weight gets counted on the whole order if the items are not excluded from package.

@TomLuijts
Copy link

TomLuijts commented Aug 15, 2019

Hi! Works great in isolation but when I combine it with another snippet, it doesn’t work and I have no clue why (I’m a php rookie). The code I’m talking about can be found here: https://www.xadapter.com/hide-checkout-fields-based-selected-shipping-method/
I use the code on this github page disable the ‘local pickup’ shipping method from my product. What I end up with are 2 different flat rate shipping methods called ‘flat_rate:9’ end ‘flat_rate:10’. I then use the other code (see link above) to remove the checkout fields for only the ‘flat_rate:10’ shipping method. When I insert this shipping method in this other code (as $shipping_method), the checkout fields are removed regardless of the shipping method selected. When I insert ‘flat_rate:9’, no checkout fields get removed regardless of the shipping method selected. I don’t know why this happens but when I remove the code from this github page, everything works fine. Could anyone help me to find the problem and make these two snippets compatible? I'm guessing it has something to do with how shipping class items are split into a new package that doesn't play nice with the other code...

Copy link

ghost commented Aug 15, 2019

Hi works great when you are just trying to split the packages up. Im running into issues when im trying to get the sorted products back into the regular items.

I need help with writing code to offer partial free shipping. These are my requirements and product A and product B are the products in the cart

Product A = FREE SHIPPING WHEN 25.00 or more of the product is in the cart or when product B is in the cart

Product B = FLAT RATE SHIPPING ONLY

@Crytix
Copy link

Crytix commented Sep 4, 2019

@progbdb and @JimmyAppelt even if the answer comes quite late. Maybe some people are still interested if you stumble across this article. I use the function of @mikejolley and have added some more functions and overrides.

Here I create the new package for "local_pickup".

/**
 * This function loops over cart items, and moves any item with shipping class 'special-class' into a new package. 
 * The new package only takes local pickup.
 */
function split_special_shipping_class_items( $packages ) {
	$found_item                     = false;
	$special_class                  = 'special-class'; // edit this with the slug of your local pickup shippig class
	$new_package                    = current( $packages );
	$new_package['contents']        = array();
	$new_package['contents_cost']   = 0;
	$new_package['applied_coupons'] = array();
	$new_package['ship_via']        = array( 'local_pickup' ); // Only allow local pickup for items in special class
	
	foreach ( WC()->cart->get_cart() as $item_key => $item ) {
		// Is the product in the special class?
		if ( $item['data']->needs_shipping() && $special_class === $item['data']->get_shipping_class() ) {
			$new_package['name'] = 'PickUp'; // your custom label for this package
			$found_item                            = true;
			$new_package['contents'][ $item_key ]  = $item;
			$new_package['contents_cost']         += $item['line_total'];
			// Remove from original package
			$packages[0]['contents_cost'] = $packages[0]['contents_cost'] - $item['line_total'];
			unset( $packages[0]['contents'][ $item_key ] );
			// If there are no items left in the previous package, remove it completely.
			if ( empty( $packages[0]['contents'] ) ) {
				unset( $packages[0] );
			}
		}
	}
	if ( $found_item ) {
	   $packages[] = $new_package;
	}
	return $packages;
}

function rename_custom_package( $package_name, $i, $package ) {
 
    if ( ! empty( $package['name'] ) ) {
        $package_name = $package['name'];
    }
 
    return $package_name;
}

@TomLuijts
Hide the Local PickUp option for the package being shipped.

function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;
    // HERE define your shipping class to find
    $class = 77;
    // HERE define the shipping methods you want to hide
    $method_key_ids = array('local_pickup:8', 'local_pickup:9');
    // Checking in cart items
    foreach( $package['contents'] as $item ) {
        // If we find the shipping class
        if( $item['data']->get_shipping_class_id() == $class ){
            foreach( $method_key_ids as $method_key_id ){
                unset($rates[$method_key_id]); // Remove the targeted methods
            }
            break; // Stop the loop
        }
    }
    return $rates;
}

The following lines must be added so that the changes can also be displayed.

// hook into shipping packages filter
add_filter( 'woocommerce_cart_shipping_packages', 'split_special_shipping_class_items' );
// output the new package name
add_filter( 'woocommerce_shipping_package_name', 'rename_custom_package', 10, 3 );
// hide local pickup on dropshipping items
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );

I myself have specified the delivery time and have a message displayed during the checkout, which refers to the Local PickUp articles.

Thanks again to all who contributed directly and indirectly to the code like @dvelopit and @mikejolley .

A picture as it looks like and lines of code you find in my git https://github.com/Crytix/Woocommerce-Local-PickUp-and-Shipping-Split

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment