Woocommerce - buy one get one free not working PHP - php

So I'm trying to get this function to work in woocommerce.
Basically what it needs to do is give one product for free in the same category, if there are two items bought. I can't seem to get it to work though. Any idea what is wrong in my code?
So it needs to check the category,
If two products from the same category are in the cart. The cheapest one of the two needs to be free.
// Function to charge for only 1 product in a specific category when buying 2 products
function bogo_product_category() {
// Hook into the "woocommerce_before_calculate_totals" action
add_action( 'woocommerce_before_calculate_totals', 'bogo_product_category_calculate' );
add_action( 'woocommerce_before_cart_item_quantity_zero', 'bogo_product_category_return_price' );
}
function bogo_product_category_calculate( $cart ) {
// Set the target category ID
$category_id = 38;
// Initialize a variable to store the quantity of products in the target category
$target_category_quantity = 0;
$free_product = false;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
if ( has_term( $category_id, 'product_cat', $cart_item['product_id'] ) ) {
// If the item is in the target category
$target_category_quantity += $cart_item['quantity'];
}
}
// Loop through cart items again
foreach ( $cart->get_cart() as $cart_item ) {
if ( has_term( $category_id, 'product_cat', $cart_item['product_id'] ) ) {
// If the item is in the target category
if($target_category_quantity >= 2) {
if(!$free_product){
$free_product = true;
}else{
$cart_item['data']->set_price(0);
$cart_item['data']->set_regular_price(0);
$cart_item['data']->set_sale_price(0);
}
}
}
}
//Calculate the cart total
$cart->calculate_totals();
}
function bogo_product_category_return_price( $cart_item_key ) {
$cart = WC()->cart;
// Set the target category ID
$category_id = 38;
// Initialize a variable to store the quantity of products in the target category
$target_category_quantity = 0;
$free_product = false;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
if ( has_term( $category_id, 'product_cat', $cart_item['product_id'] ) ) {
// If the item is in the target category
$target_category_quantity += $cart_item['quantity'];
}
}
//If the target category quantity is less than 2 then return the price of the product
if($target_category_quantity < 2) {
$item = $cart->get_cart_item( $cart_item_key );
$item['data']->set_price($item['data']->get_regular_price());
$cart->calculate_totals();
}
}

Related

Change Shipping total in woocommerce Programmatically [duplicate]

I currently charge a flat fee for shipping using the default WooCommerce shipping setup. I want to offer free shipping for the entire order if the user purchases "x" products from a single category. I have some code that I put together but I need a bit of help to get it working.
// Free shipping if you purchase 12 or more from selected category
function wcs_my_free_shipping( $is_available ) {
global $woocommerce;
// HERE set your product categories in the array (can be IDs, slugs or names)
$categories = array('t-shirts');
// Initializing
$found = false;
$count = 0;
// 1st Loop: get category items count
foreach ( WC()->cart->get_cart() as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// get cart contents
$cart_items = $woocommerce->cart->get_cart();
// loop through the items looking for one in the eligible array
foreach ( $cart_items as $key => $item ) {
if( in_array( $item['product_id'], $eligible ) ) {
return true;
}
}
if ( $count > 11 ) {
// Apply free shipping
$shipping = 0;
}
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'wcs_my_free_shipping', 20 );
The use of 2 loops is not necessary
Take into account the product quantity per product
comment with explanation added in the code
function filter_woocommerce_shipping_free_shipping_is_available( $is_available, $package, $shipping_method ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 12;
/* END settings */
// Counter
$count = 0;
// Loop through cart items
foreach( $package['contents'] as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// Condition
if ( $count >= $minimum ) {
$notice = __( 'free shipping', 'woocommerce' );
$is_available = true;
} else {
$notice = __( 'NO free shipping', 'woocommerce' );
$is_available = false;
}
// Display notice
if ( isset( $notice ) ) {
wc_add_notice( $notice, 'notice' );
}
// Return
return $is_available;
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'filter_woocommerce_shipping_free_shipping_is_available', 10, 3 );
Additional question: hide other shipping methods when free shipping is available
function filter_woocommerce_package_rates( $rates, $package ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 11;
/* END settings */
// Counter
$count = 0;
// Loop through line items
foreach( $package['contents'] as $line_item ) {
// Get product id
$product_id = $line_item['product_id'];
// Check for category
if ( has_term( $categories, 'product_cat', $product_id ) ) {
$count += $line_item['quantity'];
}
}
// Condition
if ( $count > $minimum ) {
// Set
$free = array();
// Loop
foreach ( $rates as $rate_id => $rate ) {
// Rate method id = free shipping
if ( $rate->method_id === 'free_shipping' ) {
$free[ $rate_id ] = $rate;
break;
}
}
}
return ! empty( $free ) ? $free : $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 10, 2 );
Based on:
How to disable shipping method based on item count and a certain category
WooCommerce - Hide other shipping methods when FREE SHIPPING is available

Offer free shipping if user purchases "x" products from a single category

I currently charge a flat fee for shipping using the default WooCommerce shipping setup. I want to offer free shipping for the entire order if the user purchases "x" products from a single category. I have some code that I put together but I need a bit of help to get it working.
// Free shipping if you purchase 12 or more from selected category
function wcs_my_free_shipping( $is_available ) {
global $woocommerce;
// HERE set your product categories in the array (can be IDs, slugs or names)
$categories = array('t-shirts');
// Initializing
$found = false;
$count = 0;
// 1st Loop: get category items count
foreach ( WC()->cart->get_cart() as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// get cart contents
$cart_items = $woocommerce->cart->get_cart();
// loop through the items looking for one in the eligible array
foreach ( $cart_items as $key => $item ) {
if( in_array( $item['product_id'], $eligible ) ) {
return true;
}
}
if ( $count > 11 ) {
// Apply free shipping
$shipping = 0;
}
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'wcs_my_free_shipping', 20 );
The use of 2 loops is not necessary
Take into account the product quantity per product
comment with explanation added in the code
function filter_woocommerce_shipping_free_shipping_is_available( $is_available, $package, $shipping_method ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 12;
/* END settings */
// Counter
$count = 0;
// Loop through cart items
foreach( $package['contents'] as $cart_item ) {
// If product categories is found
if ( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$count += $cart_item['quantity'];
}
}
// Condition
if ( $count >= $minimum ) {
$notice = __( 'free shipping', 'woocommerce' );
$is_available = true;
} else {
$notice = __( 'NO free shipping', 'woocommerce' );
$is_available = false;
}
// Display notice
if ( isset( $notice ) ) {
wc_add_notice( $notice, 'notice' );
}
// Return
return $is_available;
}
add_filter( 'woocommerce_shipping_free_shipping_is_available', 'filter_woocommerce_shipping_free_shipping_is_available', 10, 3 );
Additional question: hide other shipping methods when free shipping is available
function filter_woocommerce_package_rates( $rates, $package ) {
// Set categories
$categories = array ( 't-shirts' );
// Set minimum
$minimum = 11;
/* END settings */
// Counter
$count = 0;
// Loop through line items
foreach( $package['contents'] as $line_item ) {
// Get product id
$product_id = $line_item['product_id'];
// Check for category
if ( has_term( $categories, 'product_cat', $product_id ) ) {
$count += $line_item['quantity'];
}
}
// Condition
if ( $count > $minimum ) {
// Set
$free = array();
// Loop
foreach ( $rates as $rate_id => $rate ) {
// Rate method id = free shipping
if ( $rate->method_id === 'free_shipping' ) {
$free[ $rate_id ] = $rate;
break;
}
}
}
return ! empty( $free ) ? $free : $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 10, 2 );
Based on:
How to disable shipping method based on item count and a certain category
WooCommerce - Hide other shipping methods when FREE SHIPPING is available

Cart total based on product category and items count in Woocommerce

I am trying to set the cart total to £10, when there are 4 items in the cart AND none of the items are from the 'christmas' category.
e.g.
4 items in cart, but 4 from christmas category. Ignore rule and follow per item pricing.
4 items in cart, but 4 from non-christmas category. Set cart price to £10.
4 items in cart, but 2 from christmas category. Ignore rule and follow per item pricing.
I have written code which currently works to set any 4 cart tems to £10:
add_filter( 'woocommerce_calculated_total', 'calculated_total', 10, 2 );
function calculated_total( $total, $cart ) {
$taster_item_count = 4;
if ( $cart->cart_contents_count == $taster_item_count ) {
return 10;
}
return $total;
}
However, when I try to add in the category conditional, it does not follow the rule?:
add_filter( 'woocommerce_calculated_total', 'calculated_total', 10, 2 );
// check each cart item for category
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
// ONLY EXECUTE BELOW FUNCTION IF DOESN'T CONTAIN CHRISTMAS CATEGORY
if ( !has_term( 'christmas', 'product_cat', $product->id ) ) {
function calculated_total( $total, $cart ) {
$taster_item_count = 4;
if ( $cart->cart_contents_count == $taster_item_count ) {
return 10;
}
return $total;
}
}
}
Updated: There are mistakes in your code, try this instead:
add_filter( 'woocommerce_calculated_total', 'calculated_total', 10, 2 );
function calculated_total( $total, $cart ) {
$taster_count = 4;
$item_count = $cart->get_cart_contents_count();
$chistm_count = 0;
foreach ( $cart->get_cart() as $cart_item ) {
if ( ! has_term( 'christmas', 'product_cat', $cart_item['product_id'] ) ) {
$chistm_count += $cart_item['quantity'];
}
}
if( $taster_count == $item_count && $chistm_count == $taster_count ) {
$total = 10;
}
return $total;
}
It should better work.

Allow only one item in cart from defined product categories in Woocommerce

In Woocommerce, I found a bit of code that restricts a users purchase to one per category for category a or b. So currently the user could purchase 2 items 1 from cat a and 1 from cat b. I would like to limit the user to only one product from category a or b. The code I am working with is below, thanks in advance.
add_filter( 'woocommerce_add_to_cart_validation', 'allowed_quantity_per_category_in_the_cart', 10, 2 );function allowed_quantity_per_category_in_the_cart( $passed, $product_id) {
$max_num_products = 1;// change the maximum allowed in the cart
$running_qty = 0;
$restricted_product_cats = array();
//Restrict particular category/categories by category slug
$restricted_product_cats[] = 'cat-a, cat-b';
// Getting the current product category slugs in an array
$product_cats_object = get_the_terms( $product_id, 'product_cat' );
foreach($product_cats_object as $obj_prod_cat) $current_product_cats[]=$obj_prod_cat->slug;
// Iterating through each cart item
foreach (WC()->cart->get_cart() as $cart_item_key=>$cart_item ){
// Restrict $max_num_products from each category
if( has_term( $current_product_cats, 'product_cat', $cart_item['product_id'] )) {
// Restrict $max_num_products from restricted product categories
//if( array_intersect($restricted_product_cats, $current_product_cats) && has_term( $restricted_product_cats, 'product_cat', $cart_item['product_id'] )) {
// count(selected category) quantity
$running_qty += (int) $cart_item['quantity'];
// More than allowed products in the cart is not allowed
if( $running_qty >= $max_num_products ) {
wc_add_notice( sprintf( 'Only %s '.($max_num_products>1?'products from this category are':'product from this category is').' allowed in the cart.', $max_num_products ), 'error' );
$passed = false; // don't add the new product to the cart
// We stop the loop
break;
}
}
}
return $passed;}
Try this (but it doesn't handle quantities as is not clear and much more complicated, because in cart page they can be altered):
add_filter( 'woocommerce_add_to_cart_validation', 'limit_cart_items_from_category', 10, 3 );
function limit_cart_items_from_category ( $passed, $product_id, $quantity )
{
// Accept when cart is empty
if( WC()->cart->is_empty() ) return $passed;
// HERE your product categories in this array (can be names, slugs or Ids)
$categories = array('T-shirts', 'Hoodies');
$found = $current = false;
// Check the current product
if( has_term( $categories, 'product_cat', $product_id ) ){
$current = true;
}
// Loop through cart items checking for product categories
foreach ( WC()->cart->get_cart() as $cart_item ){
if( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$found = true;
break; // stop the loop.
}
}
// Current product and a cart item match with defined product categories
if( $found && $current ){
$passed = false;
$cats_str = implode('" and "', $categories );
wc_add_notice( sprintf( __('Only one item is allowed from "%s" categories…', 'woocommerce' ), $cats_str ), 'error' );
}
return $passed;
}
Code goes in function.php file of your active child theme (or active theme). Tested and work.
Thank You For your Code I modified this code and make my customized code. when customer add to cart the product from defined category, after that they didnt allow to cart other category product.
you can set the maximum number of product easily by just changing the number.
Thank You once again.
add_filter( 'woocommerce_add_to_cart_validation', 'limit_cart_items_from_category', 10, 3 );
function limit_cart_items_from_category ( $passed, $product_id, $quantity )
{
// change the number for maximum product allowed in the cart
$max_num_products = 5; // Here change the number for maximum allowed product in the cart
// Accept when cart is empty
if( WC()->cart->is_empty() ) return $passed;
// HERE your product categories in this array (can be names, slugs or Ids)
$categories = array('Sea-Food-Chaasum');
$found = $current = false;
// Check the current product
if( has_term( $categories, 'product_cat', $product_id ) ){
$current = true;
}
// Loop through cart items checking for product categories
foreach ( WC()->cart->get_cart() as $cart_item ){
if( has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$found = true;
$current = true;
break; // stop the loop.
}
}
// count(selected category) quantity
$running_qty += (int) $cart_item['quantity'];
// More than allowed products in the cart is not allowed
if( $running_qty >= $max_num_products )
// Current product and a cart item match with defined product categories
if( $found && $current ){
$passed = false;
$cats_str = implode('" and "', $categories );
wc_add_notice( sprintf( __('Only one item is allowed from "%s" categories…', 'woocommerce' ), $cats_str ), 'error' );
}
return $passed;
}

WooCommerce Cart - Conditional Items categories validation

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
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)…
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);

Categories