Asked  7 Months ago    Answers:  5   Viewed   41 times

We have an exclusive category X and others regular categories Y. What I would like:

  • When someone orders anything from category X, other category items cannot be added to cart and should display a warning
  • category Y products should not be mixed with X.

How could I achieve that?

I got this code from other post, but its outdated and not satisfactory:

<?php

 // Enforce single parent category items in cart at a time based on first item in cart
function get_product_top_level_category ( $product_id ) {

    $product_terms            =  get_the_terms ( $product_id, 'product_cat' );
    $product_category         =  $product_terms[0]->parent;
    $product_category_term    =  get_term ( $product_category, 'product_cat' );
    $product_category_parent  =  $product_category_term->parent;
    $product_top_category     =  $product_category_term->term_id;

    while ( $product_category_parent  !=  0 ) {
            $product_category_term    =  get_term ( $product_category_parent, 'product_cat' );
            $product_category_parent  =  $product_category_term->parent;
            $product_top_category     =  $product_category_term->term_id;
    }

    return $product_top_category;
}

add_filter ( 'woocommerce_before_cart', 'restrict_cart_to_single_category' );
function restrict_cart_to_single_category() {
    global $woocommerce;
    $cart_contents    =  $woocommerce->cart->get_cart( );
    $cart_item_keys   =  array_keys ( $cart_contents );
    $cart_item_count  =  count ( $cart_item_keys );

    // Do nothing if the cart is empty
    // Do nothing if the cart only has one item
    if ( ! $cart_contents || $cart_item_count == 1 ) {
            return null;
    }

    // Multiple Items in cart
    $first_item                    =  $cart_item_keys[0];
    $first_item_id                 =  $cart_contents[$first_item]['product_id'];
    $first_item_top_category       =  get_product_top_level_category ( $first_item_id );
    $first_item_top_category_term  =  get_term ( $first_item_top_category, 'product_cat' );
    $first_item_top_category_name  =  $first_item_top_category_term->name;

    // Now we check each subsequent items top-level parent category
    foreach ( $cart_item_keys as $key ) {
            if ( $key  ==  $first_item ) {
                    continue;
            }
            else {
                    $product_id            =  $cart_contents[$key]['product_id'];
                    $product_top_category  =  get_product_top_level_category( $product_id );

                    if ( $product_top_category  !=  $first_item_top_category ) {
                            $woocommerce->cart->set_quantity ( $key, 0, true );
                            $mismatched_categories  =  1;
                    }
            }
    }

    // we really only want to display this message once for anyone, including those that have carts already prefilled
    if ( isset ( $mismatched_categories ) ) {
            echo '<p class="woocommerce-error">Only one category allowed in cart at a time.<br />You are currently allowed only <strong>'.$first_item_top_category_name.'</strong> items in your cart.<br />To order a different category empty your cart first.</p>';
    }
}
?>

Thanks

 Answers

29

Updated (2019)

Like everything is turning around your exclusive category category X, you need to use a conditional for this category.

And you have chance because there is a special function that you can use in combination with woocommerce product categories. Lets say that **cat_x** is the slug for your exclusive category, as you know it yet product_cat is the argument to get products categories.

So with has_term () conditional function, you are going to use this:

if ( has_term ('cat_x', 'product_cat', $item_id ) ) { // or $product_id
    // doing something when product item has 'cat_x'
} else {
    // doing something when product item has NOT 'cat_x'
}

We need to run the cart items 2 times in a foreach loop:

  • To detect if there is a cat_x item in that car.
  • To remove other items if cat_x is detected for one item in the cart and to fire the right messages.

In the code below, I have changed to a more useful hook. This hook will check what you have in your cart. The idea is to removed other categories items in the cart when there is a 'cat_x' item added in cart.

The code is well commented. At the end you will find different notices that are fired. You will need to put your real text in each.

add_action( 'woocommerce_check_cart_items', 'checking_cart_items' );
function checking_cart_items() {
    // Set your product category slug
    $category = 'cat_x';

    $number_of_items = sizeof( WC()->cart->get_cart() );
    $found  = false; // Initializing
    $notice = ''; // Initializing

    if ( $number_of_items > 0 ) {

        // Loop through cart items
        foreach ( WC()->cart->get_cart() as $cart_item ) {
            $product = $cart_item['data'];
            $product_id = $cart_item['product_id'];

            // Detecting if the defined category is in cart
            if ( has_term( $category, 'product_cat', $product_id ) ) {
                $found = true;
                break; // Stop the loop
            }
        }

        // Re-loop through cart items
        foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
            $product = $cart_item['data'];
            $product_id = $cart_item['product_id'];

            if ( $found ) // If the defined product category is in cart
            { 
                if ( $number_of_items == 1 ) { // only one defined product category
                    if ( empty( $notice ) ){
                        $notice = '1';
                    }
                }
                if ( $number_of_items >= 2 ) { // The defined product category + other categories in cart

                    // removing other categories items from cart
                    if ( ! has_term( $category, 'product_cat', $product_id ) ) {
                        WC()->cart->remove_cart_item( $cart_item_key ); // removing item from cart
                        if ( empty( $notice ) || $notice == '1' ){
                            $notice = '2';
                        }
                    }
                }
            } else { // Only other categories items

                if ( empty( $notice ) ){
                    $notice = '3';
                }
            }
        }

        // Firing woocommerce notices
        if ( $notice == '1' ) { // message for an 'cat_x' item only (alone)
            wc_add_notice( sprintf( '<p class="woocommerce-error">bla bla bla one category X item in the cart</p>' ), 'success' );
        } elseif ( $notice == '2' ) { // message for an 'cat_x' item and other ones => removed other ones 
            wc_add_notice( sprintf( '<p class="woocommerce-error">bla bla bla ther is already category X in the cart => Other category items has been removed</p>' ), 'error' );
        } elseif ( $notice == '3' ) { // message for other categories items (if needed)
            wc_add_notice( sprintf( '<p class="woocommerce-error">bla bla bla NOT category X in the cart</p>' ), 'success' );
        }
    }
}       

Is not possible for me to really test this code (but it doesn't throws errors)…


@edit

We can use something else than notices… everything is possible. But it's a good starting solution, to fine tune.

You will need to replace 'cat_x' by your real category slug (in the beginning)

Wednesday, March 31, 2021
 
mcography
answered 7 Months ago
97

Figured out how to solve this. I manually added a woocommerce_cart_calculate_fee of $3.00 then to minus this off the total to act as a "negative fee" i.e. A discount.

    add_filter( 'woocommerce_cart_calculate_fees', 'add_recurring_postage_fees2', 10, 1 );

function add_recurring_postage_fees2(WC_Cart $cart ) {

  $items_count  = WC()->cart->get_cart_contents_count();

    if ( $items_count == 4 ) {
        $discount = 3.00;
        $cart->add_fee( 'Taster Box Free Shipping', -$discount);
        return;
    }
}
Wednesday, March 31, 2021
 
JakeGR
answered 7 Months ago
30

Updated for WooCommerce version 3 and above

Since WooCommerce 3, get_variation_description() is now deprecated and replaced by get_description() WC_Product method.

To get your product item variation description (filtering variation product type condition), you can use the following hooked function instead:

// Cart page (and mini cart)
add_filter( 'woocommerce_cart_item_name', 'cart_item_product_description', 20, 3);
function cart_item_product_description( $item_name, $cart_item, $cart_item_key ) {
    if ( ! is_checkout() ) {
        if( $cart_item['variation_id'] > 0 ) {
            $description = $cart_item['data']->get_description(); // variation description
        } else {
            $description = $cart_item['data']->get_short_description(); // product short description (for others)
        }

        if ( ! empty($description) ) {
            return $item_name . '<br><div class="description">
                <strong>' . __( 'Description', 'woocommerce' ) . '</strong>: '. $description . '
            </div>';
        }
    }
    return $item_name;
}

// Checkout page
add_filter( 'woocommerce_checkout_cart_item_quantity', 'cart_item_checkout_product_description', 20, 3);
function cart_item_checkout_product_description( $item_quantity, $cart_item, $cart_item_key ) {
    if( $cart_item['variation_id'] > 0 ) {
        $description = $cart_item['data']->get_description(); // variation description
    } else {
        $description = $cart_item['data']->get_short_description(); // product short description (for others)
    }

    if ( ! empty($description) ) {
        return $item_quantity . '<br><div class="description">
            <strong>' . __( 'Description', 'woocommerce' ) . '</strong>: '. $description . '
        </div>';
    }

    return $item_quantity;
}

Now the description is just displayed between the title and the variation attributes values (if there is any).

Code goes in functions.php file of the active child theme (or active theme). Tested and works.

Saturday, July 3, 2021
 
eliotlencelot
answered 4 Months ago
27

Update - October 2018 (code improved)

Yes its possible to use a trick, to achieve this. Normally for discounts on cart we use in WooCommerce coupons. Here coupons are not appropriated. I will use here a negative conditional fee, that becomes a discount.

The calculation:
— The item count is based on quantity by item and total of items in cart
— The percent is 0.05 (5%) and it grows with each additional item (as you asked)
— We use the discounted subtotal (to avoid adding multiple collapsing discounts made by coupons)

The code:

add_action( 'woocommerce_cart_calculate_fees', 'cart_progressive_discount', 50, 1 );
function cart_progressive_discount( $cart ) {
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    // For 1 item (quantity 1) we EXIT;
    if( $cart->get_cart_contents_count() == 1 )
        return;

    ## ------ Settings below ------- ##

    $percent = 5; // Percent rate: Progressive discount by steps of 5%
    $max_percentage = 50; // 50% (so for 10 items as 5 x 10 = 50)
    $discount_text = __( 'Quantity discount', 'woocommerce' ); // Discount Text

    ## ----- ----- ----- ----- ----- ##

    $cart_items_count = $cart->get_cart_contents_count();
    $cart_lines_total = $cart->get_subtotal() - $cart->get_discount_total();

    // Dynamic percentage calculation
    $percentage = $percent * ($cart_items_count - 1);

    // Progressive discount from 5% to 45% (Between 2 and 10 items)
    if( $percentage < $max_percentage ) {
        $discount_text .=  ' (' . $percentage . '%)';
        $discount = $cart_lines_total * $percentage / 100;
        $cart->add_fee( $discount_text, -$discount );
    }
    // Fixed discount at 50% (11 items and more)
    else {
        $discount_text .=  ' (' . $max_percentage . '%)';
        $discount = $cart_lines_total * $max_percentage / 100;
        $cart->add_fee( $discount_text, -$discount );
    }
}

Code goes in function.php file of your active child theme. Tested and works.

When using FEE API for discounts (a negative fee), taxes are always applied.


References:

  • WooCommerce - Adding shipping fee for free user plan
  • WooCommerce - Make a set of coupons adding a fixed fee to an order
  • WooCommerce class - WC_Cart - add_fee() method
Friday, July 30, 2021
 
Troncoso
answered 3 Months ago
79

Updated (on April 2018)

You can only use partially the code you have picked up from my answer, for 2 or more categories, using an array of product categories this way:

// Function that define the mandatory product category
function your_mandatory_category_slug(){
// DEFINE HERE the 2 SLUGs of the needed product categories
    $categories = array('cxsuite-download-option','cxsuite-hosted-option');
    return $categories;
}

// Conditional function that returns true if the mandatory products categories are in cart
function has_mandatory_category(){
    $categories_needed = your_mandatory_category_slug();
    $has_cat = false;

    // Iterrating each item in cart and detecting…
    foreach ( WC()->cart->get_cart() as $item ) {

        // iterating both categories
        foreach ( $categories_needed as $category_needed ) {

            // Detects if one of the needed product categories is in cart items
            if ( has_term($category_needed, 'product_cat', $item['product_id'] ) )
                $has_cat = true;
        }
        if($has_cat) 
            break;
    }
    return $has_cat;
}

// Function that display a message if there is not in cart a mandatory product category
function mandatory_category_display_message() {
    $categories_needed = your_mandatory_category_slug();

    // check that cart is not empty (for cart and product category archives)
    if( !WC()->cart->is_empty() && ( is_cart() || is_product_category( $categories_needed[0] ) || is_product_category( $categories_needed[1] ) ) ){
        $category_name = array();
        $category_url = array();

        // iterating both categories
        foreach($categories_needed as $key => $category_needed){
            $category_obj = get_term_by( 'slug', $category_needed, 'product_cat' );
            if ( is_wp_error( $category_obj ) ) 
                return;

            $category_name[$key] = $category_obj->name;
            $category_url[$key] = get_term_link( $category_needed, 'product_cat' );
        }
        // Display message when one of the product categories is not in cart items
        if ( !has_mandatory_category() ) {
            // render a notice to explain why checkout is blocked
            wc_add_notice( sprintf( __( '<strong>Reminder:</strong> You have to add in your cart, a product from "%1$s" or from "%3$s" category, to be allowed to check out. Please return <a href="%2$s"> here to "%1$s"</a> or <a href="%4$s"> here to "%3$s"</a> product pages', 'your_theme_domain'), $category_name[0], $category_url[0], $category_name[1], $category_url[1] ), 'error' );
        }
    }
}
add_action( 'woocommerce_before_main_content', 'mandatory_category_display_message', 30 ); // for product mandatory category archives pages
add_action( 'woocommerce_check_cart_items', 'mandatory_category_display_message' ); // for cart page

But you will have to choose for the last function, between one of the 2 product categories for the redirection…

// Function that redirect from checkout to mandatory product category archives pages
function mandatory_category_checkout_redirect() {

    // If cart is not empty on checkout page
    if( !WC()->cart->is_empty() && is_checkout() ){
        $categories_needed = your_mandatory_category_slug();

        // If missing product category => redirect to the products category page
        if ( !has_mandatory_category() )
            wp_redirect( get_term_link( $categories_needed[0], 'product_cat' ) ); // or $categories_needed[1]
    }
}
add_action('template_redirect', 'mandatory_category_checkout_redirect');

This goes in function.php file of your active child theme (or theme) or also in any plugin file.

This code is based on this functional answer:
Allow checkout only when a product of a mandatory category is in cart

It is untested, but it should work…

Saturday, July 31, 2021
 
xrock
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :