3
votes

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

2

2 Answers

3
votes

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)

0
votes

Answer in 2020

Recently, I need almost the same requirement. But instead of a single category, I have to check if the item is in a group of categories.

Consider that I have 6 categories. I will group 6 categories into 3 groups. My customer can only purchase items in a single category group (but multiple categories) in a single order.

The code snippet is given below.

function sa45er_category_group_validation($valid, $product_id, $quantity) {
    global $woocommerce;
    if($woocommerce->cart->cart_contents_count == 0){
         return $valid;
    }
    
    $target_cat_group = array(
        array(17,20), // Update your product category
        array(19,18), // Update your product category
    );
    
    $this_product_terms = get_the_terms( $product_id, 'product_cat' );
    foreach ($this_product_terms as $term) {
        $this_product_cat_ids[] = $term->term_id; 
    }        
            
    foreach ( $woocommerce->cart->get_cart() as $cart_item_key => $values ) {
        $_product = $values['data'];
        $terms = get_the_terms( $_product->get_ID(), 'product_cat' );
        foreach ($terms as $term) {
            $cart_cat_ids[] = $term->term_id;  
        }       
    }
    
    $all_cats = array_merge($this_product_cat_ids,$cart_cat_ids);
    
    $result = array();
    foreach($target_cat_group as $target_cat_group_item){
            
        $intrsct = array_intersect($all_cats, $target_cat_group_item);
        
        if( !empty( $intrsct ) ){
            $result[] = $intrsct;
        }
            
    }
    
    if(count($result) > 1){
        wc_add_notice( 'You can\'t add this product with your current cart items.', 'error' );
        return false;
    }
    
    return $valid;
}
add_filter( 'woocommerce_add_to_cart_validation', 'sa45er_category_group_validation',10,3);