Exclude product from all coupons in WooCommerce - php

I have searched all over the internet, but it doesn't seem to be any answer anywhere in spite of countless people asking the question.
Is there any way to exclude a specific product from ALL coupons?
I understand that you can do it on coupon level, but this is rather messy especially as a lot of people have automatic coupons, several people creating coupons etc…
TLDR: Any way to make a product excluded from all coupons at Product level.

Here is a nice way to automate this process.
1) We add a custom checkbox in product general settings metabox to disable the coupon functionality for the current product. So you will get this in Backend Edit Product pages:
All selected products will be saved in an array and will be used in the following…
2) This selected products will be excluded from coupon discount at product level and the product discount amount will be set to zero.
The code:
// Create and display the custom field in product general setting tab
add_action( 'woocommerce_product_options_general_product_data', 'add_custom_field_general_product_fields' );
function add_custom_field_general_product_fields(){
global $post;
echo '<div class="product_custom_field">';
// Custom Product Checkbox Field
woocommerce_wp_checkbox( array(
'id' => '_disabled_for_coupons',
'label' => __('Disabled for coupons', 'woocommerce'),
'description' => __('Disable this products from coupon discounts', 'woocommerce'),
'desc_tip' => 'true',
) );
echo '</div>';;
}
// Save the custom field and update all excluded product Ids in option WP settings
add_action( 'woocommerce_process_product_meta', 'save_custom_field_general_product_fields', 10, 1 );
function save_custom_field_general_product_fields( $post_id ){
$current_disabled = isset( $_POST['_disabled_for_coupons'] ) ? 'yes' : 'no';
$disabled_products = get_option( '_products_disabled_for_coupons' );
if( empty($disabled_products) ) {
if( $current_disabled == 'yes' )
$disabled_products = array( $post_id );
} else {
if( $current_disabled == 'yes' ) {
$disabled_products[] = $post_id;
$disabled_products = array_unique( $disabled_products );
} else {
if ( ( $key = array_search( $post_id, $disabled_products ) ) !== false )
unset( $disabled_products[$key] );
}
}
update_post_meta( $post_id, '_disabled_for_coupons', $current_disabled );
update_option( '_products_disabled_for_coupons', $disabled_products );
}
// Make coupons invalid at product level
add_filter('woocommerce_coupon_is_valid_for_product', 'set_coupon_validity_for_excluded_products', 12, 4);
function set_coupon_validity_for_excluded_products($valid, $product, $coupon, $values ){
if( ! count(get_option( '_products_disabled_for_coupons' )) > 0 ) return $valid;
$disabled_products = get_option( '_products_disabled_for_coupons' );
if( in_array( $product->get_id(), $disabled_products ) )
$valid = false;
return $valid;
}
// Set the product discount amount to zero
add_filter( 'woocommerce_coupon_get_discount_amount', 'zero_discount_for_excluded_products', 12, 5 );
function zero_discount_for_excluded_products($discount, $discounting_amount, $cart_item, $single, $coupon ){
if( ! count(get_option( '_products_disabled_for_coupons' )) > 0 ) return $discount;
$disabled_products = get_option( '_products_disabled_for_coupons' );
if( in_array( $cart_item['product_id'], $disabled_products ) )
$discount = 0;
return $discount;
}
Code goes in function.php file of your active child theme (or active theme) or in any plugin file.
Tested and perfectly works

Related

Hide all products with a specific stock status from WooCommerce catalog

I'm using Flatsome template on Wordpress and Woocommerce site. I also create custom stock status (example noproduzione, used when a product is not created anymore from manufacturer). But I don't want to show this products (noproduzione stock status) on my site (only in admin pages).
I'm using this code that I write here (I put it in my functions.php file), but it seems that on flatsome does not works. How to apply it on this template?
add_action( 'woocommerce_product_query', 'qc_action_product_query', 10, 2 );
function qc_action_product_query( $q, $query ) {
// Get any existing meta query
$meta_query = $q->get( 'meta_query');
// Define an additional meta query
$q->set( 'meta_query', array( array(
'key' => '_stock_status',
'value' => 'noproduzione',
'compare' => 'NOT LIKE',
) ) );
// Set the new merged meta query
$q->set( 'meta_query', $meta_query );
}
To create custom code I used some of codes found on StackOverflow
// Add new stock status options
add_filter( 'woocommerce_product_stock_status_options', 'filter_woocommerce_product_stock_status_options', 10, 1 );
function filter_woocommerce_product_stock_status_options( $status ) {
// Add new statuses
$status['10days'] = __( 'Disponibile entro 10 giorni', 'woocommerce' );
$status['inarrivo'] = __( 'In arrivo', 'woocommerce' );
$status['noproduzione'] = __( 'Fuori produzione', 'woocommerce' );
return $status;
}
// Availability text
add_filter( 'woocommerce_get_availability_text', 'filter_woocommerce_get_availability_text', 10, 2 );
function filter_woocommerce_get_availability_text( $availability, $product) {
switch( $product->get_stock_status() ) {
case '10days':
$availability = __( 'Disponibile entro 10 giorni', 'woocommerce' );
break;
case 'inarrivo':
$availability = __( 'In arrivo', 'woocommerce' );
break;
case 'noproduzione':
$availability = __( 'Fuori produzione', 'woocommerce' );
break;
}
return $availability;
}
I added code to show the text of availability, and also to hide the cart button if the product is noproduzione stock status
// Display the stock status on other page
add_action( 'woocommerce_after_shop_loop_item_title', 'wcs_stock_text_shop_page', 25 );
function wcs_stock_text_shop_page() {
//returns an array with 2 items availability and class for CSS
global $product;
$availability = $product->get_stock_status();
//check if availability in the array = string 'noproduzione'
//if so display on page.//if you want to display the 'in stock' messages as well just leave out this, == 'Out of stock'
if ( $product->get_stock_status() === 'noproduzione') {
echo '<span style="color:#b20000;">Fuori produzione!</span>';
}
else if ( $product->get_stock_status() === 'onbackorder') {
echo '<span style="color:#13b200;">Disponibile su ordinazione</span>';
}
else if ( $product->get_stock_status() === '10days') {
echo '<span style="color:#13b200;">Disponibile in 10 giorni</span>';
}
else if ( $product->get_stock_status() === 'inarrivo') {
echo '<span style="color:#e0c81d;">In arrivo</span>';
}
else if ( $product->get_stock_status() === 'outofstock') {
echo '<span style="color:#b20000;">Terminato!</span>';
}
else {
echo '<span style="color:#53af00;">Disponibile!</span>';
}
}
// Hide the cart button if stock status is 'noproduzione'
add_filter('woocommerce_is_purchasable', 'filter_is_purchasable_callback', 10, 2 );
add_filter('woocommerce_variation_is_purchasable', 'filter_is_purchasable_callback', 10, 2 );
function filter_is_purchasable_callback( $purchasable, $product ) {
if ( $product->get_stock_status() === 'noproduzione' ) {
return false;
}
return $purchasable;
}
Last, I also added this code, is useful because order the RELATED PRODUCT by "instock" status, and for me is good because do not show the other custom stock status. But I want to apply this to all of my frontend site.
//show, in the related products, only instock products!
add_filter( 'woocommerce_related_products', 'filter_woocommerce_related_products', 10, 3 );
function filter_woocommerce_related_products( $related_posts, $product_id, $args ) {
foreach( $related_posts as $key => $related_post ) {
// Get product
$related_product = wc_get_product( $related_post );
// Is a WC product
if ( is_a( $related_product, 'WC_Product' ) ) {
// Stock status
$stock_status = $related_product->get_stock_status();
// NOT instock
if ( $stock_status != 'instock' ) {
unset( $related_posts[$key] );
}
}
}
return $related_posts;
}
So, the question is: how to hide this products (noproduzione stock status) on my site (and show only in admin pages)? Noproduzione is different from OUTOFSTOCK!
You should better use dedicated woocommerce_product_query_meta_query filter hook as follows, to hide, from your store, all products that have a specific stock status (works with custom stock status):
add_action( 'woocommerce_product_query_meta_query', 'custom_product_query_meta_query', 1000 );
function custom_product_query_meta_query( $meta_query ) {
if ( ! is_admin() ) {
$meta_query[] = array(
'key' => '_stock_status',
'value' => 'noproduzione',
'compare' => '!=',
);
}
return $meta_query;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works on all versions since WooCommerce 3.

Process custom bulk action on admin Orders list in Woocommerce

I have added a custom action in my woocommerce orders page like shown below, and I also have a custom order field "number of meals". Now what I want is when I select the orders in bulk and use that custom action then count of number of meals should get decreased by 1.
For example if order id 1 & 2 had 15 & 12 number of meals respectively then after using the action it should become 14 & 11…
The screenshot of my orders page, the custom hook and the custom order field I created:
My code:
add_filter( 'bulk_actions-edit-shop_order', 'decrease_number_of_meals_by_1' );
function decrease_number_of_meals_by_1( $bulk_actions ) {
$bulk_actions['decrease_number_of_meals'] = 'Decrease Number of Meals by 1';
return $bulk_actions;
}
add_action( 'admin_action_decrease_number_of_meals', 'fire_my_hook' );
function fire_my_hook() {
if( !isset( $_REQUEST['post'] ) && !is_array( $_REQUEST['post'] ) )
return;
foreach( $_REQUEST['post'] as $order_id ) {
$order = new WC_Order( $order_id );
$no_of_meals = $order->get_post_meta( $order_id, '_wc_acof_{3}', true );
}
}
I am stuck here and have no idea on how to do it further.
Please guide me on how can I achieve this.
You are not using the right way and hooks. Also the right custom field meta key should be _wc_acof_3 when using WooCommerce Admin Custom Order Fields plugin.
So try the following instead:
// Add a bulk action to Orders bulk actions dropdown
add_filter( 'bulk_actions-edit-shop_order', 'decrease_meals_orders_bulk_actions' );
function decrease_meals_orders_bulk_actions( $bulk_actions ) {
$bulk_actions['decrease_meals'] = 'Decrease Number of Meals by 1';
return $bulk_actions;
}
// Process the bulk action from selected orders
add_filter( 'handle_bulk_actions-edit-shop_order', 'decrease_meals_bulk_action_edit_shop_order', 10, 3 );
function decrease_meals_bulk_action_edit_shop_order( $redirect_to, $action, $post_ids ) {
if ( $action === 'decrease_meals' ){
$processed_ids = array(); // Initializing
foreach ( $post_ids as $post_id ) {
// Get number of meals
$nb_meal = (int) get_post_meta( $post_id, '_wc_acof_3', true );
// Save the decreased number of meals ($meals - 1)
update_post_meta( $post_id, '_wc_acof_3', $nb_meal - 1 );
$processed_ids[] = $post_id; // Adding processed order IDs to an array
}
// Adding the right query vars to the returned URL
$redirect_to = add_query_arg( array(
'decrease_meals' => '1',
'processed_count' => count( $processed_ids ),
'processed_ids' => implode( ',', $processed_ids ),
), $redirect_to );
}
return $redirect_to;
}
// Display the results notice from bulk action on orders
add_action( 'admin_notices', 'decrease_meals_bulk_action_admin_notice' );
function decrease_meals_bulk_action_admin_notice() {
global $pagenow;
if ( 'edit.php' === $pagenow && isset($_GET['post_type'])
&& 'shop_order' === $_GET['post_type'] && isset($_GET['decrease_meals']) {
$count = intval( $_REQUEST['processed_count'] );
printf( '<div class="notice notice-success fade is-dismissible"><p>' .
_n( 'Decreased meals for %s Order.',
'Decreased meals for %s Orders.',
$count,
'woocommerce'
) . '</p></div>', $count );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

Make coupon field mandatory for a product category in WooCommerce

I am trying to make it so the coupon field is mandatory on Woocommerce for a product category. I have tried to use the code from this answer but it only works with a set of coupon code. I need it to work with any valid coupon code.
Thank you for any help.
Try the following that will make the coupon field mandatory when a cart item from a specific product category is found:
add_action( 'woocommerce_check_cart_items', 'mandatory_coupon_code' );
function mandatory_coupon_code() {
// Set your product categories in the array (can be term IDs, slugs or names)
$product_categories = array( 'clothing' );
$found = false;
// Loop through cart items to check for the product categories
foreach ( WC()->cart->get_cart() as $cart_item ){
if( has_term( $product_categories, 'product_cat', $cart_item['product_id'] ) ){
$found = true; // cart item from the product category is found
break; // We can stop the loop
}
}
// If not found we exit
if( ! $found ) return; // exit
$applied_coupons = WC()->cart->get_applied_coupons();
// Coupon not applied and product category found
if( is_array($applied_coupons) && sizeof($applied_coupons) == 0 ) {
// Display an error notice preventing checkout
$message = __( 'Please enter a coupon code to be able to checkout.', 'woocommerce' );
wc_add_notice( $message, 'error' );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works
Thought I'd share my version - it adds a required checkbox field so you can specify which coupons active this message. Used a combination of the solution posted here and this one.
// Add a custom checkbox to Admin coupon settings pages
add_action( 'woocommerce_coupon_options', 'add_coupon_option_checkbox', 10 );
function add_coupon_option_checkbox() {
woocommerce_wp_checkbox( array(
'id' => 'items_mandatory',
'label' => __( 'Force specific items', 'woocommerce' ),
'description' => __( 'Make this coupon mandatory for specific items.', 'woocommerce' ),
'desc_tip' => false,
) );
}
// Save the custom checkbox value from Admin coupon settings pages
add_action( 'woocommerce_coupon_options_save', 'save_coupon_option_checkbox', 10, 2 );
function save_coupon_option_checkbox( $post_id, $coupon ) {
update_post_meta( $post_id, 'items_mandatory', isset( $_POST['items_mandatory'] ) ? 'yes' : 'no' );
}
// Force Coupon codes for Woocommerce
add_action( 'woocommerce_check_cart_items', 'mandatory_coupon_code' );
function mandatory_coupon_code() {
// Set your product categories in the array (can be term IDs, slugs or names)
$product_categories = array( '58');
$applied_coupons = WC()->cart->get_applied_coupons();
$found = false;
if( sizeof($applied_coupons) > 0 ) {
// Loop through applied coupons
foreach( $applied_coupons as $coupon_code ) {
$coupon = new WC_Coupon( $coupon_code );
if( $coupon->get_meta('items_mandatory') === 'yes' ) {
$coupon_applied = true;
break;
}
}
}
// Loop through cart items to check for the product categories
foreach ( WC()->cart->get_cart() as $cart_item ){
if( has_term( $product_categories, 'product_cat', $cart_item['product_id'] ) ){
$found = true; // cart item from the product category is found
break; // We can stop the loop
}
}
// If not found we exit
if( ! $found ) return; // exit
// Coupon not applied and product category found
if( ! $coupon_applied) {
// Display an error notice preventing checkout
$message = __( 'The product "%s" requires a coupon for checkout.' );
wc_add_notice( sprintf($message, $cart_item['data']->get_name() ), 'error' );
}
}

Add multiple products to cart if they are not already in cart

In WooCommerce I've implemented #jtsternberg's WooCommerce: Allow adding multiple products to the cart via the add-to-cart query string to allow adding multiple products at once, but I've received many complaints from customers who actually try to use one of the links containing multiple products.
For starters, if the customer clicks checkout and then clicks the browser "back" button, all the item quantities increment. I solved this by redirecting the user to the cart URL stripped of any additional parameters after the add-to-cart behavior completes, but it's not ideal.
What I really want is to check if the item is in the cart first and only add to cart if it isn't there already. Has anyone done something similar?
Working Update:
I ended up modifying the code from #jtsternberg to use a completely separate param name in order to avoid conflict with the default add-to-cart behavior. Then I was able to use #LoicTheAztec's suggested code below by wrapping the behavior in a check to see if that new param exists. Here's the full section:
function custom_product_link() {
if (empty( $_REQUEST['multi-product-add'])) {
return;
}
$product_ids = explode( ',', $_REQUEST['multi-product-add'] );
foreach ( $product_ids as $product_id ) {
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', absint( $product_id ) );
$was_added_to_cart = false;
$adding_to_cart = wc_get_product( $product_id );
if ( ! $adding_to_cart ) {
continue;
}
$add_to_cart_handler = apply_filters( 'woocommerce_add_to_cart_handler', $adding_to_cart->product_type, $adding_to_cart );
if ( 'simple' !== $add_to_cart_handler ) {
continue;
}
// For now, quantity applies to all products.. This could be changed easily enough, but I didn't need this feature.
$quantity = empty( $_REQUEST['quantity'] ) ? 1 : wc_stock_amount( $_REQUEST['quantity'] );
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( $passed_validation && false !== WC()->cart->add_to_cart( $product_id, $quantity ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
}
if ( wc_notice_count( 'error' ) === 0 ) {
// If has custom URL redirect there
if ( $url = apply_filters( 'woocommerce_add_to_cart_redirect', false ) ) {
wp_safe_redirect( $url );
exit;
} elseif ( get_option( 'woocommerce_cart_redirect_after_add' ) === 'yes' ) {
wp_safe_redirect( wc_get_cart_url() );
exit;
}
}
}
function check_product_added_to_cart( $passed, $product_id, $quantity) {
if (!empty( $_REQUEST['multi-product-add'])) {
foreach (WC()->cart->get_cart() as $cart_key => $cart_item ){
// if products are already in cart:
if( $cart_item['product_id'] == $product_id ) {
// Set the verification variable to "not passed" (false)
$passed = false;
// (Optionally) Displays a notice if product(s) are already in cart
// wc_add_notice( '<strong>' . $btn['label'] . '</strong> ' . __( 'This product is already in your cart.', 'woocommerce' ), 'error' );
// Stop the function returning "false", so the products will not be added again
return $passed;
}
}
}
return $passed;
}
add_action( 'wp_loaded', 'custom_product_link', 15 );
add_action( 'woocommerce_add_to_cart_validation', 'check_product_added_to_cart', 10, 3 );
As in the code you are using you have woocommerce_add_to_cart_validation filter hook inside it, you can use it in a custom hooked function to check if products rare already in cart with something like:
add_action( 'woocommerce_add_to_cart_validation', 'check_product_added_to_cart', 10, 3 );
function check_product_added_to_cart( $passed, $product_id, $quantity) {
foreach (WC()->cart->get_cart() as $cart_key => $cart_item ){
// if products are already in cart:
if( $cart_item['data']->get_id() == $product_id ) {
// Set the verification variable to "not passed" (false)
$passed = false;
// (Optionally) Displays a notice if product(s) are already in cart
wc_add_notice( '<strong>' . $btn['label'] . '</strong> ' . __( 'This product is already in your cart.', 'woocommerce' ), 'error' );
// Stop the function returning "false", so the products will not be added again
return $passed;
}
}
return $passed;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Code is tested and this normally should works with your customization…
For product quantities, you can use the $quantity argument with $cart_item['quantity'] in some conditions…

WooCommerce ACF Display custom meta data on Cart and Checkout page

I would like to show the value of my custom field created with Advanced Custom Fields plugin, at the same time in WooCommerce cart and checkout pages.
I'm using the code below in the functions.php page of my theme, which displays only in the single page of the product:
add_action( 'woocommerce_before_add_to_cart_button', 'add_custom_field', 0 );
function add_custom_field() {
global $post;
echo "<div class='produto-informacoes-complementares'>";
echo get_field( 'info_complementar', $product_id, true );
echo "</div>";
return true;
}
Thank you all in advanced for all the help given and excuse my english.
I can't test this on your web shop, so I am not completely sure:
Displaying custom field value in single product page (your function):
add_action( 'woocommerce_before_add_to_cart_button', 'add_custom_field', 0 );
function add_custom_field() {
global $product; // Changed this
// Added this too (compatibility with WC +3)
$product_id = method_exists( $product, 'get_id' ) ? $product->get_id() : $product->id;
echo "<div class='produto-informacoes-complementares'>";
echo get_field( 'info_complementar', $product_id );
echo "</div>";
return true;
}
(updated) Storing this custom field into cart and session:
add_filter( 'woocommerce_add_cart_item_data', 'save_my_custom_product_field', 10, 2 );
function save_my_custom_product_field( $cart_item_data, $product_id ) {
$custom_field_value = get_field( 'info_complementar', $product_id, true );
if( !empty( $custom_field_value ) )
{
$cart_item_data['info_complementar'] = $custom_field_value;
// below statement make sure every add to cart action as unique line item
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
(updated) Render meta on cart and checkout:
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );
function render_meta_on_cart_and_checkout( $cart_data, $cart_item ) {
$custom_items = array();
// Woo 2.4.2 updates
if( !empty( $cart_data ) ) {
$custom_items = $cart_data;
}
if( isset( $cart_item['info_complementar'] ) ) {
$custom_items[] = array( "name" => "Info complementar", "value" => $cart_item['info_complementar'] );
}
return $custom_items;
}
There was some mistakes in the 2 last hooks that was making this not working… Now it should work.

Categories