First, thanks for your time.
We have certain products we offer a payment plan with, and it includes a $1 30day trial.
When a user adds a product to the cart with the "payment plan" category (id=41 below), I want the cart to display the price as $1 then X payments of $xx.xx. The back end only needs to pass the product SKU so this is purely for display reasons.
This code works, but loops for however many items are in the cart. Is there a way for me to stop the loop as soon as it senses the "payment plan" product category?
The Code:
<td><?php
function check_payment() {
//Check to see if user has product in cart
global $woocommerce;
//assigns a default negative value
$contains_special = false;
$wccart= WC()->cart->get_cart();
if ( !empty( $wccart ) ) {
foreach ( WC()->cart->get_cart() as $cart_item ) {
function is_item_special( $product_id ){
if( has_term( 'payment-plan', 'product_cat', $product_id ) ) {
return TRUE;
} else {
return false;
}
}//function
if ( is_item_special( $cart_item['product_id'] ) ) {
$contains_special = true;
$firstpayment = get_post_meta( $_product->id, 'firstpayment', true );
$getsubtotal = WC()->cart->get_cart_subtotal(); //get cart subtotal
$trimbefore = str_replace('<span class="amount">$', "", $getsubtotal); //$getsubtotal returns <span class="amount>XX.XX</span> this trims off the opening span tag
$intsubtotal = str_replace('</span>', "", $trimbefore); //trim closing span tag
$formatsubtotal = number_format((float)$intsubtotal, 2, '.', '');//makes an integer
$numberofpayments = get_post_meta( $_product->id, 'payments', true );
$afterfirsttotal = $formatsubtotal - 1.00;
$paymentamount = number_format((float)$afterfirsttotal, 2, '.', '') / $numberofpayments;
echo '$' . $firstpayment . '<br/> then ' . $numberofpayments . ' payments of $' . number_format((float)$paymentamount, 2, '.', '');
break;
} else {
wc_cart_totals_subtotal_html();
}//if/else statement
}//foreach loop
} //if cart isn't empty
} //function
?>
I'm semi-new to PHP and still trying to understand some things, so sorry if this is just obvious!
Here is something that I have used in the past (slightly modified from when I was checking for a product with a particular post meta field). From what I can tell from your question, it seems that you are looking for break which is how to quit a foreach loop once a particular condition has been satisfied.
The first function loops through the cart items looking for any item that matches our criteria. Note the if ( ! empty( WC()->cart->get_cart() ) ) which prevents the code from running on an empty cart.... which was the cause of the foreach error you were seeing earlier.
The second function checks a product for a particular condition: in your case whether or not it has a specific product category assigned. This could easily be handled inside the first function, but I refactored it out for readability.
/*
* loop through cart looking for specific item
*/
function kia_check_cart_for_specials() {
$contains_special = false;
if ( ! empty( WC()->cart->get_cart() ) ) {
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( kia_is_item_special( $cart_item['product_id'] ) ) {
$contains_special = true;
break; // found a special item so we can quit now
}
}
}
return $contains_special;
}
/*
* check if an item has special category
* change the term/condition to suit your needs
*/
function kia_is_item_special( $product_id ){
if( has_term( 'special-category', 'product_cat', $product_id ) ) {
return TRUE;
} else {
return false;
}
}
Related
I've made a custom plugin fo WooCommerce. I am trying to limit the number of products a customer can buy from a specific category. Most of it works, but I cannot get it to fire the redirect function once the condition is met.
Here is my code:
add_action( 'wp', 'check_limited_products_in_cart');
function check_limited_products_in_cart () {
// holds checks for all products in cart to see if they're in our category
$category_checks = array();
$limited_product_count = 1;
// check each cart item for our category
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$product_in_cat = false;
// replace 'instant-print' with your category's slug
if ( has_term( 'instant-print', 'product_cat', $product->id ) ) {
$product_in_cat = true;
}
array_push( $category_checks, $product_in_cat );
}
$filtered_array = count(array_filter($category_checks));
if ($filtered_array >= $limited_product_count) {
redirect_limited_products_page(); // this bit seems to be failing
}
}
function redirect_limited_products_page () {
$error_page = '/error';
$target_url = $error_page; // construct a target url
wp_redirect($target_url, 301); // permanent redirect
exit();
}
My second function is throwing too many redirects I think. The page is just churning so I am never seeing any useful console information.
I've tried various iterations of this, like trying to run the entire function in one block (which threw the same error), and then hoisting $filtered_array as a global variable so I could access it from the second function, but I could not get that to work either.
Any help much appreciated.
EDIT
OK a little progress.
I am now returning:
add_action( 'woocommerce_before_cart', 'check_limited_products_in_cart');
function check_limited_products_in_cart () {
// holds checks for all products in cart to see if they're in our category
$category_checks = array();
$limited_product_count = 1;
// check each cart item for our category
if( ! is_admin() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$product_in_cat = false;
// replace 'instant-print' with your category's slug
if ( has_term( 'instant-print', 'product_cat', $product->id ) ) {
$product_in_cat = true;
}
array_push( $category_checks, $product_in_cat );
}
}
$filtered_array = count(array_filter($category_checks));
if ($filtered_array >= $limited_product_count) {
echo '<p>condition met</p>';
redirect_limited_products_page();
}
}
function redirect_limited_products_page () {
echo '<p>Redirect page</p>';
$error_page = '/error';
$target_url = $error_page; // construct a target url
wp_redirect($target_url, 301); // permanent redirect
exit();
}
Before I was making a global wp function which rendered everywhere and probably explained the redirect loop.
So now when I add another product to the cart, I am seeing condition met as well as Redirect page but with the following error:
Warning: Cannot modify header information - headers already sent by (output started at my-site-url\wp-includes\class.wp-styles.php:290) in my-site-url\wp-includes\pluggable.php on line 1296
Warning: Cannot modify header information - headers already sent by (output started at my-site-url\wp-includes\class.wp-styles.php:290) in my-site-url\wp-includes\pluggable.php on line 1299
So this feels much closer.
** FURTHER UPDATE **
After reading about the issues with headers, I tried to tidy up my file, make sure there was no white space, malformed tags. I removed the closing php tag and checked my linting. I think the main culprit was an exho statement in the redirect function which was throwing the header issue. You can see here.
I am checking the current limited product count compared to the current number of limited products in the basket. It seems now the redirect never fires, so my assumption is that
add_action( 'woocommerce_before_cart', ...)
Might need to be
add_filter ('woocommerce_add_to_cart_validation', ...)
This also did not appear to work. The items get added to the cart item, even though the condition is showing as met and the redirect does not get fired. So I am not sure if I am trying to hook to the wrong function.
<?php
$error_page = '/product-limit-reached';
add_action( 'woocommerce_before_cart', 'check_limited_products_in_cart');
function check_limited_products_in_cart () {
// holds checks for all products in cart to see if they're in our category
$category_checks = array();
$limited_product_count = 1;
// check each cart item for our category
if( ! is_admin() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
$product_in_cat = false;
// replace 'instant-print' with your category's slug
if ( has_term( 'instant-print', 'product_cat', $product->id ) ) {
$product_in_cat = true;
}
array_push( $category_checks, $product_in_cat );
}
}
$filtered_array = count(array_filter($category_checks));
if ($filtered_array >= $limited_product_count) {
echo '<p>condition met</p><p>Limited product count: ' . $limited_product_count. '</p><p>Limited products in cart: ' . $filtered_array. '</p>';
redirect_limited_products_page();
}
}
function redirect_limited_products_page () {
$target_url = $error_page; // construct a target url
wp_redirect($target_url, 301); // permanent redirect
// exit();
}
Yes, you need to use woocommerce_add_to_cart_validation hook.
We can make a check when adding an item to cart. If the check fails, the item doesn't go to cart, and the user is shown an error.
Just like that:
function filter_woocommerce_add_to_cart_validation( $true, $product_id, $request_quantity, $variation_id = '', $request_variation = '' ) {
// holds checks for all products in cart to see if they're in our category
$category_checks = 0;
$limited_product_count = 1;
$limited_slug = 'instant-print'; //replace 'instant-print' with your category's slug
// check each cart item for our category
if( ! is_admin() ) {
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
$product = $cart_item['data'];
if ( has_term( $limited_slug, 'product_cat', $product->id ) ) {
$category_checks += $cart_item['quantity'];
}
}
}
// We also check the item that the user is trying to add to cart
if ( has_term( $limited_slug, 'product_cat', $product_id ) ) {
$category_checks += $request_quantity;
}
if ($category_checks >= $limited_product_count) {
$notice = '<p>condition met</p><p>Limited product count: ' . $limited_product_count. '</p><p>Limited products in cart: ' . $category_checks. '</p>';
wc_add_notice( __( $notice, 'textdomain' ), 'error' );
return false;
}
return $true;
};
add_filter( 'woocommerce_add_to_cart_validation', 'filter_woocommerce_add_to_cart_validation', 10, 3 );
I am trying display if a variation of a product is already in a cart or not (in single product page). A simple comparing of the product id with products in cart object is not working for variable product as the variation id is being loaded using ajax.
Here is my code that works in case the product type is other than variable.
<?php
/*
* Check if Product Already In Cart
*/
function woo_in_cart( $product_id ) {
global $woocommerce;
if ( !isset($product_id) ) {
return false;
}
foreach( $woocommerce->cart->get_cart() as $cart_item ) {
if ( $cart_item['product_id'] === $product_id ){
return true;
} else {
return false;
}
}
}
Is there any way to make it work without jQuery?
Do you mean $product_id could be the ID of a variation? If so, you can just get the parent ID if it exists:
/*
* Check if Product Already In Cart
*/
function woo_in_cart( $product_id ) {
global $woocommerce;
if ( ! isset( $product_id ) ) {
return false;
}
$parent_id = wp_get_post_parent_id( $product_id );
$product_id = $parent_id > 0 ? $parent_id : $product_id;
foreach ( $woocommerce->cart->get_cart() as $cart_item ) {
if ( $cart_item['product_id'] === $product_id ) {
return true;
} else {
return false;
}
}
}
If you mean your cart item is a variation, and $product_id is already the parent product ID, then your code should work already as is.
The $cart_item has 2 IDs: $cart_item['product_id'] and $cart_item['variation_id'].
So product_id will always be that of the parent product.
To handle product variations outside single product pages (and simple products everywhere):
// Check if Product Already In Cart (Work with product variations too)
function woo_in_cart( $product_id = 0 ) {
$found = false;
if ( isset($product_id) || 0 == $product_id )
return $found;
foreach( WC()->cart->get_cart() as $cart_item ) {
if ( $cart_item['data']->get_id() == $product_id )
$found = true;
}
return $found;
}
To handle product variations inside single product pages, javascript is needed.
Here is an example that will show a custom message, when the selected variation is already in cart:
// Frontend: custom select field in variable products single pages
add_action( 'wp_footer', 'action_before_add_to_cart_button' );
function action_before_add_to_cart_button() {
if( ! is_product() ) return;
global $product;
if( ! is_object($product) )
$product = wc_get_product( get_the_id() );
// Only for variable products when cart is not empty
if( ! ( $product->is_type('variable') && ! WC()->cart->is_empty() ) ) return; // Exit
$variation_ids_in_cart = array();
// Loop through cart items
foreach( WC()->cart->get_cart() as $cart_item ) {
// Collecting product variation IDs if they are in cart for this variable product
if ( $cart_item['variation_id'] > 0 && in_array( $cart_item['variation_id'], $product->get_children() ) )
$variation_ids_in_cart[] = $cart_item['variation_id'];
}
// Only if a variation ID for this variable product is in cart
if( sizeof($variation_ids_in_cart) == 0 ) return; // Exit
// Message to be displayed (if the selected variation match with a variation in cart
$message = __("my custom message goes here", "woocommerce");
$message = '<p class="custom woocommerce-message" style="display:none;">'.$message.'</p>';
// jQuery code
?>
<script>
(function($){
// Utility function that check if variation match and display message
function checkVariations(){
var a = 'p.woocommerce-message.custom', b = false;
$.each( <?php echo json_encode($variation_ids_in_cart); ?>, function( k, v ){
if( $('input[name="variation_id"]').val() == v ) b = true;
});
if(b) $(a).show(); else $(a).hide();
}
// On load (when DOM is rendered)
$('table.variations').after('<?php echo $message; ?>');
setTimeout(function(){
checkVariations();
}, 800);
// On live event: product attribute select fields "blur" event
$('.variations select').blur( function(){
checkVariations();
});
})(jQuery);
</script>
<?php
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Im trying to display additional button [Book Your appointment] on the WooCommerce Cart page that would take user to a page with a product for booking an appointment. This part works nicely. I also try to check if product ID 444908 is already in cart. Product ID 444908 is an appointment product, and if a person has already booked an appointment the button should not be displayed as the person already have booked product in the cart.
Seems like the problem is with my IF condition. When I'm using it it doesn't show button no matter if product 444908 is or isn't in cart.
What am I doing wrong?
add_action( 'woocommerce_after_cart_totals', 'my_continue_shopping_button' );
function my_continue_shopping_button() {
$product_id = 444908;
$product_cart_id = WC()->cart->generate_cart_id( $product_id );
$in_cart = WC()->cart->find_product_in_cart( $product_cart_id );
if ( $in_cart ) {
echo '<div class="bookbtn"><br/>';
echo ' <i class="fas fa-calendar-alt"></i> Book Your Appointment';
echo '</div>';
}
}
Here's something I've been using for a while now 🙃
function is_in_cart( $ids ) {
// Initialise
$found = false;
// Loop through cart items
foreach( WC()->cart->get_cart() as $cart_item ) {
// For an array of product IDs
if( is_array($ids) && ( in_array( $cart_item['product_id'], $ids ) || in_array( $cart_item['variation_id'], $ids ) ) ){
$found = true;
break;
}
// For a unique product ID (integer or string value)
elseif( ! is_array($ids) && ( $ids == $cart_item['product_id'] || $ids == $cart_item['variation_id'] ) ){
$found = true;
break;
}
}
return $found;
}
For single product ID:
if(is_in_cart($product_id)) {
// do something
}
For array of product/variation IDs:
if(is_in_cart(array(123,456,789))) {
// do something
}
...or...
if(is_in_cart($product_ids)) {
// do something
}
In the end I used external function:
function woo_is_in_cart($product_id) {
global $woocommerce;
foreach($woocommerce->cart->get_cart() as $key => $val ) {
$_product = $val['data'];
if($product_id == $_product->get_id() ) {
return true;
}
}
return false;
}
Then I check if the product is in cart using this:
if(woo_is_in_cart(5555) !=1) {
/* where 5555 is product ID */
find_product_in_cart returns a empty string if product is not found
so you need
if ( $in_cart !="" )
info
In WooCommerce, I would like to check if the products in the shopping cart have the attribute 'Varifocal' (so I can show/hide checkout fields).
I'm struggling to get an array of id's of all the variations which have the attribute 'varifocal'. It would be really appreciated if someone can point me in the right direction.
The taxonomy is pa_lenses.
I currently have the following function:
function varifocal() {
// Add product IDs here
$ids = array();
// Products currently in the cart
$cart_ids = array();
// Find each product in the cart and add it to the $cart_ids array
foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
$cart_product = $values['data'];
$cart_ids[] = $cart_product->get_id();
}
// If one of the special products are in the cart, return true.
if ( ! empty( array_intersect( $ids, $cart_ids ) ) ) {
return true;
} else {
return false;
}
}
Here is a custom conditional function that will return true when the a specific attribute argument is found in one cart item (product variation):
function is_attr_in_cart( $attribute_slug_term ){
$found = false; // Initializing
if( WC()->cart->is_empty() )
return $found; // Exit
else {
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ){
if( $cart_item['variation_id'] > 0 ){
// Loop through product attributes values set for the variation
foreach( $cart_item['variation'] as $term_slug ){
// comparing attribute term value with current attribute value
if ( $term_slug === $attribute_slug_term ) {
$found = true;
break; // Stop current loop
}
}
}
if ($found) break; // Stop the first loop
}
return $found;
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
USAGE (example)
if( is_attr_in_cart( 'Varifocal' ) ){
echo '"Varifocal" attribute value has been found in cart items<br>';
} else {
echo '"Varifocal" <strong>NOT FOUND!!!</strong><br>';
}
Advice: This conditional function will return false if cart is empty
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);