Based on that question Display custom product badge when a product is back in stock WooCommerce I created a snipped to display NEW or BACK IN STORE badge based on conditions. We want, if the product is published the first time to display the NEW badge for 15 days. IF a product is out of stock in the future and comes available again, then we want to display BACK IN STOCK badge. By default when we only use the modified date, then it will always display the NEW badge.
That's why we created this code below but it does not fully work :(
The NEW badge is displayed right for the given time. We have problems to display the BACK IN STORE badge.
Is there a logical error in my code or what i'm doing wrong?
// Check if product was out of stock
add_action( 'woocommerce_updated_product_stock', 'woo_updated_product_stock', 10, 3);
add_action( 'woocommerce_update_product', 'woo_updated_product_stock',10, 3 );
add_action('save_post_product', 'woo_updated_product_stock', 10, 3);
function woo_updated_product_stock( $product_id ) {
$product = wc_get_product( $product_id );
$stock_qty = $product->get_stock_quantity();
if ($stock_qty >=1) {
update_post_meta( $product_id, '_earlier_update', true);
}else{
update_post_meta( $product_id, '_earlier_update', false);
}
}
add_action('woocommerce_checkout_order_processed', 'stocktest');
function stocktest($order_id){
$order = wc_get_order( $order_id );
$order_item = $order->get_items();
foreach( $order_item as $product ) {
$productID = $product['product_id'];
//for the topic I programmed the IDs hardcoded
if($productID){
$quantity = $product['quantity'];
global $product;
$connected_product = wc_get_product($productID);
$connected_qty = $connected_product->get_stock_quantity();
if(isset($connected_qty)){
$qty = $connected_qty - $quantity;
if ($qty < 1) {
update_post_meta( $productID, '_outofstockproduct', true);
}
}
}
}
}
// Set "NEW" badge for new products (first time published)
function action_woocommerce_before_shop_loop_item_title() {
global $product;
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// 15 days in seconds
$days_count = 15 * 24 * 60 *60; //15 * 24 * 60 *60
// Get now datetime (from Woocommerce datetime object) + Get now timestamp
$datetime_now = new WC_DateTime();
$timestamp_now = $datetime_now->getTimestamp();
// Get product created datetime + product created timestamp
$datetime_created = $product->get_date_created();
$timestamp_created = $datetime_created->getTimestamp();
// print_r($datetime_created);
// exit();
// Difference in seconds between now and date created
$time_delta_created = $timestamp_now - $timestamp_created;
// If the difference is less than 10, apply "New" label
if ( $time_delta_created < $days_count && $product->is_in_stock() ) {
echo '<div class="badge_loop_wrapper"><img src="/wp-content/uploads/2021/01/new_badge.png"/></div>';
// echo '<span>' . __( 'New Label', 'woocommerce' ) . '</span>';
}
// Set "BACK IN STOCK" badge for products which are not published the first time and was out of stock
else {
global $product;
$newness_days = 15;
$product_id = $product->get_id();
$date_modified = $product->get_date_modified()->date('Y-m-d');
// Add 15 day on product modify date becase fresh back in stock will show 10 days from current modify dates
$back_in_stock = strtotime($date_modified. " + $newness_days day");
$today = strtotime(date('Y-m-d'));
$earlier_update = get_post_meta( $product_id, '_earlier_update', true );
$outofstockproduct = get_post_meta( $product_id, '_outofstockproduct', true );
if (($outofstockproduct && $earlier_update && $today < $back_in_stock) && !$product->is_on_sale() && $product->is_in_stock() && get_post_meta( $product->get_id(), 'custom_badge', true) !== 'yes' ) {
//echo '<div class="badge_loop_wrapper">'.__('Back in stock').'</div>';
echo '<div class="badge_loop_wrapper"><img src="/wp-content/uploads/2021/09/back_in_stock_badge.png"/></div>';
}
}
}
}
add_action( 'woocommerce_before_shop_loop_item_title', 'action_woocommerce_before_shop_loop_item_title', 10 );
You should add/update product meta data when update product:
Add/update custom product meta:
add_action( 'woocommerce_updated_product_stock', 'back_product_stock', 10, 3);
add_action( 'woocommerce_update_product', 'back_product_stock',10, 3 );
add_action('save_post_product', 'back_product_stock', 10, 3);
function back_product_stock( $product_id ) {
$product = wc_get_product( $product_id );
$stock_qty = $product->get_stock_quantity();
if ($stock_qty >=1) {
update_post_meta( $product_id, '_earlier_update', true);
}
else{
update_post_meta( $product_id, '_earlier_update', false);
}
}
Display custom product badge when a product is back in stock WooCommerce
add_action( 'woocommerce_before_shop_loop_item_title', function() {
global $product;
$newness_days = 10;
$product_id = $product->get_id();
$date_modified = $product->get_date_modified()->date('Y-m-d');
// Add 10 day on product modify date becase fresh back in stock will show 10 days from current modify dates
$back_in_stock = strtotime($date_modified. " + $newness_days day");
$today = strtotime(date('Y-m-d'));
$earlier_update = get_post_meta( $product_id, '_earlier_update', true );
if (($earlier_update && $today < $back_in_stock) && !$product->is_on_sale() && $product->is_in_stock() && get_post_meta( $product->get_id(), 'custom_badge', true) !== 'yes' ) {
echo '<div class="badge_loop_wrapper">'.__('Back in stock').'</div>';
}
});
You can change the action and condition accordingly.
Let me know if any other question.
Thanks!
Related
I know how to add a "new" badge for new created products. Here we use get_date_modified() because we not always publish products when we create them and want that they also have the "new" badge for 10 days.
Working solution for add "new" badge:
add_action( 'woocommerce_before_shop_loop_item_title', function() {
global $product;
$newness_days = 10;
$created = strtotime( $product->get_date_modified() );
if ( ( time() - ( 60 * 60 * 24 * $newness_days ) ) < $created && !$product->is_on_sale() && $product->is_in_stock() && get_post_meta( $product->get_id(), 'custom_badge', true) !== 'yes' ) {
echo '<div class="badge_loop_wrapper"><img src="https://xxx.ch/wp-content/uploads/2021/01/new_badge.png"/></div>';
}
});
Now, sometimes a product is out of stock. And after a product is back in stock, we want to show a badge like "Fresh back in stock". but with the current solution of get_date_modified() it always will display the "new" badge because of the get_date_modified() function.
Question:
Is there a way that I only can display a badge when a product gets from "out of stock" to "in stock"?
add_action( 'woocommerce_before_shop_loop_item_title', function() {
global $product;
$newness_days = 10;
$back_in_stock= strtotime( $product->get_date_modified() );
if ( ( time() - ( 60 * 60 * 24 * $newness_days ) ) < $back_in_stock && !$product->is_on_sale() && $product->is_in_stock() && get_post_meta( $product->get_id(), 'custom_badge', true) !== 'yes' ) {
echo '<div class="badge_loop_wrapper"><img src="https://xxxx.ch/wp-content/uploads/2021/01/back_in_stock_badge.png"/></div>';
}
});
You should add/update custom product meta when update product and product stock like this:
if ($stock_qty >=1) {
update_post_meta( $product_id, '_earlier_update', true);
}
else{
update_post_meta( $product_id, '_earlier_update', false);
}
You can add conditions from Fresh back in stock accordingly.
How to add/update custom product meta:
add_action( 'woocommerce_updated_product_stock', 'woo_updated_product_stock', 10, 3);
add_action( 'woocommerce_update_product', 'woo_updated_product_stock',10, 3 );
add_action('save_post_product', 'woo_updated_product_stock', 10, 3);
function woo_updated_product_stock( $product_id ) {
$product = wc_get_product( $product_id );
$stock_qty = $product->get_stock_quantity();
if ($stock_qty >=1) {
update_post_meta( $product_id, '_earlier_update', true);
}
else{
update_post_meta( $product_id, '_earlier_update', false);
}
}
Display custom product badge when a product is back in stock WooCommerce
add_action( 'woocommerce_before_shop_loop_item_title', function() {
global $product;
$newness_days = 10;
$product_id = $product->get_id();
$date_modified = $product->get_date_modified()->date('Y-m-d');
// Add 10 day on product modify date becase fresh back in stock will show 10 days from current modify dates
$back_in_stock = strtotime($date_modified. " + $newness_days day");
$today = strtotime(date('Y-m-d'));
$earlier_update = get_post_meta( $product_id, '_earlier_update', true );
if (($earlier_update && $today < $back_in_stock) && !$product->is_on_sale() && $product->is_in_stock() && get_post_meta( $product->get_id(), 'custom_badge', true) !== 'yes' ) {
echo '<div class="badge_loop_wrapper">'.__('Fresh back in stock').'</div>';
}
});
Where you can check $earlier_update in every product and if exist or is true, then you can show the badge 10 days from the last modified date to the current date
You can change action or condition accordingly.
I am making a custom add-on for my products on my WooCommerce webshop. Everything works as it should, but I only need one thing. When customers check the checkbox on the product page, $30 must be added to the original price of the selected variation.
add_filter( 'woocommerce_add_cart_item_data', 'add_cart_item_data', 10, 3 );
function add_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
$product = wc_get_product($product_id);
$price = $product->get_price();
if ($product->is_type( 'variable' )) {
$var_price = $product->get_price_html();
// extra pack checkbox
if( ! empty( $_POST['addon-card'] ) ) {
$cart_item_data['new_price'] = $var_price + 30;
}
} else {
// extra pack checkbox
if( ! empty( $_POST['addon-card'] ) ) {
$cart_item_data['new_price'] = $price + 30;
}
}
return $cart_item_data;
}
This part: if( ! empty( $_POST['addon-card'] ) ) check if the checkbox is checked.
My problem is here:
$var_price = $product->get_price_html();
// extra pack checkbox
if( ! empty( $_POST['addon-card'] ) ) {
$cart_item_data['new_price'] = $var_price + 30;
}
The value of $var_price is just 0.
So I try to figure out how I can get the price of the chosen variation.
I have tried with get_variation_prices() and get_variation_price() but without any luck...
UPDATE
I have tried to implement the code from this thread. With that code I can get the price of the chosen variation, but I can no longer add the $30.
Can you please try below WooCommerce hook to change price dynamically?
add_filter('woocommerce_variation_prices_price', 'custom_variation_price_change', 10, 3 );
add_filter('woocommerce_variation_prices_regular_price', 'custom_variation_price_change', 10, 3 );
function custom_variation_price_change( $price, $variation, $product ) {
// add some conditional code as per your requirement
$price = $price+30;
}
I've created two custom functions in my Woocommerce store. The idea is to limit a certain type of product to a maximum of 3. I want to set a cookie for one week to prevent more of the same type of product being purchased by the same customer. So if the customer buys 1 today, then for the next 6 days they could still purchase up to another 2 until the cookie expires. But 3 would always be the maximum.
The function works in that it will set a limit if more than 3 items are added to the basket in one session, and the cookie still exists after checkout. My hunch is it's not being read at the right time, or passed to the function which checks if it exists.
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
$lpc_cookie = $_COOKIE['lpc'];
if ( isset( $lpc_cookie ) ) {
$category_checks = $lpc_cookie;
} else {
$category_checks = 0;
}
$limited_product_count = 4;
$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 = 'Sorry, you have reached the maximum amount for this type of product that you can purchase during this period.';
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 );
function set_cookie_for_limited_products () {
$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'];
}
}
}
// $expire = time() + 60 * 60 * 24 * 7; // expires in one week
$expire = time() + 60 * 60; // expires in one hour
setcookie('lpc', $category_checks, $expire);
}
add_filter( 'woocommerce_before_checkout_form', 'set_cookie_for_limited_products' );
I hope this is something obvious. Initially I was trying to echo and var_dump variables, but I found that was causing other stuff to break.
It's easy, I can give you an example, but if guests can make purchases, you won't be able to save that information, it's an example that takes into account the two possible situations.
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
$limited_product_count = 4;
$limited_slug = 'instant-print'; //replace 'instant-print' with your category's slug
if(is_user_logged_in()) {
//Using database
$user_id = get_current_user_id();
$lpc_cookie = get_user_meta( $user_id, $limited_slug, true );
$expire = get_user_meta( $user_id, $limited_slug . '_expire', true );
}else{
//Using cookie
$lpc_cookie = $_COOKIE['lpc'];
}
//Check lpc_cookie and expire value
if ( isset( $lpc_cookie ) && $expire > time() ) {
$category_checks = $lpc_cookie;
} else {
$category_checks = 0;
}
// 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 = 'Sorry, you have reached the maximum amount for this type of product that you can purchase during this period.';
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 );
function set_cookie_for_limited_products () {
$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'];
}
}
}
// $expire = time() + 60 * 60 * 24 * 7; // expires in one week
$expire = time() + 60 * 60; // expires in one hour
if(is_user_logged_in()) {
//Using database
$user_id = get_current_user_id();
update_user_meta( $user_id, $limited_slug, $category_checks );
update_user_meta( $user_id, $limited_slug . '_expire', $expire );
}else{
//Using cookie
setcookie('lpc', $category_checks, $expire);
}
}
add_filter( 'woocommerce_before_checkout_form', 'set_cookie_for_limited_products' );
I am trying to find a solution to the following issue.
I have been using Woocommerce Dynamic Pricing in order to apply a 25% discount for a specific product category.
Then, I need to remove that discount if shipping option will be local_pickup.
I thought I can tweak the following code. It is missing something somewhere, so I am asking you for help.
function discount_table(){
// Set $cat_in_cart to false
$cat_in_cart = false;
// Loop through all products in the Cart
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if ( has_term( 'category_1', 'product_cat', $cart_item['product_id'] ) ) {
$cat_in_cart = true;
break;
}
}
$total = WC()->cart->subtotal;
$sconto_label = "";
if($total >= 300){
$sconto_label=15;
}elseif($total >= 220){
$sconto_label=10;
}elseif($total >= 150){
$sconto_label=8;
}elseif($total >= 100){
$sconto_label=4;
}
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = explode(':',$chosen_methods[0]);
if($chosen_shipping[0]=='local_pickup' && !$cat_in_cart){
$sconto_label=25;
}
else if ($chosen_shipping[0]=='local_pickup' && $cat_in_cart){
$sconto_label=25;
// there should be something here or not?
}
$sconto_cliente = (($total*$sconto_label)/100);
if($sconto_label!="")
$sconto_cliente_net = ($sconto_loison/1.1);
WC()->cart->add_fee( "Discount ($sconto_label%)", -$sconto_cliente_net, false );
}
add_action( 'woocommerce_cart_calculate_fees','discount_table' );
Is there a way to restore original price of one or more item of a specific category that have been already discounted when in cart?
To restore cart items price based on a product category and on a specific chosen shipping method, try the following (for Woocommerce Dynamic Pricing):
add_filter('woocommerce_add_cart_item_data', 'add_default_price_as_cart_item_custom_data', 50, 3 );
function add_default_price_as_cart_item_custom_data( $cart_item_data, $product_id, $variation_id ){
// HERE define your product category(ies)
$categories = array('t-shirts');
if ( has_term( $categories, 'product_cat', $product_id ) ) {
$product_id = $variation_id > 0 ? $variation_id : $product_id;
// The WC_Product Object
$product = wc_get_product($product_id);
// Get product default base price
$price = (float) $product->get_price();
// Set the Product default base price as custom cart item data
$cart_item_data['default_price'] = $price;
}
return $cart_item_data;
}
add_action( 'woocommerce_before_calculate_totals', 'restore_cart_item_price', 900, 1 );
function restore_cart_item_price( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// HERE set the targeted Chosen Shipping method
$targeted_shipping_method = 'local_pickup';
// Get the chosen shipping method
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping_method_id = explode(':', reset($chosen_methods) );
$chosen_shipping_method = reset($chosen_shipping_method_id);
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
if( $targeted_shipping_method == $chosen_shipping_method && isset($cart_item['default_price']) ){
// Set back the default cart item price
$cart_item['data']->set_price($cart_item['default_price']);
}
}
}
Code goes in function.php file of your active child theme (or active theme). It should works.
Note: When Using a negative fee (a cart discount), the tax is always applied.
I dig a little bit your code and made some changes. Found a bug thanks to a super dev! Here's the code:
function discount_table(){
$cat_in_cart = false;
$tot_cat = 0;
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( has_term( 'cat1', 'product_cat', $cart_item['product_id'] ) ) {
$cat_in_cart = true;
$tot_cat_price = $cart_item['data']->get_price();
$tot_cat_qty = $cart_item['quantity'];
$tot_cat += $tot_cat_price * $tot_cat_qty;
//break;
/* commented here, thanks to Michael Ramin: break will stop execution of the loop after found the first product belonging to the category; what if you have more than one? so remove break instruction */
}
}
global $product;
$total = WC()->cart->subtotal;
$discount_label = "";
if($total >= 300){
$discount_label=15;
}elseif($total >= 220){
$discount_label=10;
}elseif($total >= 150){
$discount_label=8;
}elseif($total >= 100){
$discount_label=4;
}
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$chosen_shipping = explode(':',$chosen_methods[0]);
if($chosen_shipping[0]=='local_pickup'){
$discount_label=25;
}
$discount_brand = ($total-$tot_cat)*$discount_label/100;
if($discount_label!=""){
$discount_brand_net = ($discount_brand/1.1);
if($discount_brand_net != 0) // show discount only if != 0; it may happen if cart includes only targeted category products;
WC()->cart->add_fee( "Discount ($discount_label%)", -$discount_brand_net, false );
}
}
add_action( 'woocommerce_cart_calculate_fees','discount_table' );
I have made 2 custom product fields - availability - since when/until when. So if the current date is between these set availability dates then product is purchasable, else - it's not. Everything works perfectly however only until I post a product with a variations. Then it's like product variations ignore these custom availability fields/values and still let to add variations to cart even if current date is not between set availability dates.
function hide_product_if_unavailable( $is_purchasable, $object ) {
$date_from = get_post_meta( $object->get_id(), '_availability_schedule_dates_from' );
$date_to = get_post_meta( $object->get_id(), '_availability_schedule_dates_to' );
$current_date = current_time('timestamp');
if ( strlen($date_from[0]) !== 0 ) {
if ( ( $current_date >= (int)$date_from[0] ) && ( $current_date <= (int)$date_to[0] ) ) {
return true;
} else {
return false;
}
} else {
# Let adding product to cart if Availability fields was not set at all
return true;
}
}
add_filter( 'woocommerce_is_purchasable', 'hide_product_if_unavailable', 10, 2 );
I tried to add another filter just below woocommerce_is_purchasable:
add_filter( 'woocommerce_variation_is_purchasable', 'hide_product_if_unavailable', 10, 2 );
But variations still ignore availability fields.
Try this revisited code for all product types (including product variations) :
add_filter( 'woocommerce_is_purchasable', 'purchasable_product_date_range', 20, 2 );
function purchasable_product_date_range( $purchasable, $product ) {
$date_from = (int) get_post_meta( $product->get_id(), '_availability_schedule_dates_from', true );
$date_to = (int) get_post_meta( $product->get_id(), '_availability_schedule_dates_to', true );
if( empty($date_from) || empty($date_to) )
return $purchasable; // Exit (fields are not set)
$current_date = (int) current_time('timestamp');
if( ! ( $current_date >= $date_from && $current_date <= $date_to ) )
$purchasable = false;
return $purchasable;
}
To make product variation work you need to get the parent product ID, because your variations don't have this date range custom fields:
add_filter( 'woocommerce_variation_is_purchasable', 'purchasable_variation_date_range', 20, 2 );
function purchasable_variation_date_range( $purchasable, $product ) {
$date_from = (int) get_post_meta( $product->get_parent_id(), '_availability_schedule_dates_from', true );
$date_to = (int) get_post_meta( $product->get_parent_id(), '_availability_schedule_dates_to', true );
if( empty($date_from) || empty($date_to) )
return $purchasable; // Exit (fields are not set)
$current_date = (int) current_time('timestamp');
if( ! ( $current_date >= $date_from && $current_date <= $date_to ) )
$purchasable = false;
return $purchasable;
}
Code goes in function.php file of your active child theme (or active theme). Tested and work.