Exclude variations with 2 specific attribute terms from coupon usage in Woocommerce - php

I need to prevent coupons being used if customer have any specific product variations in their cart with following attribute terms:
attribute_pa_style => swirly
attribute_pa_style => circle
I've looked through the Woocommerce scripts that apply to restricting specific products and specific categories, but can't figure it out with regard to attributes and all coupons.
Any help is appreciated.

This can be done using woocommerce_coupon_is_valid filter hook this way:
add_filter( 'woocommerce_coupon_is_valid', 'check_if_coupons_are_valid', 10, 3 );
function check_if_coupons_are_valid( $is_valid, $coupon, $discount ){
// YOUR ATTRIBUTE SETTINGS BELOW:
$taxonomy = 'pa_style';
$term_slugs = array('swirly', 'circle');
// Loop through cart items and check for backordered items
foreach ( WC()->cart->get_cart() as $cart_item ) {
foreach( $cart_item['variation'] as $attribute => $term_slug ) {
if( $attribute === 'attribute_'.$taxonomy && in_array( $term_slug, $term_slugs ) ) {
$is_valid = false; // attribute found, coupons are not valid
break; // Stop and exit from the loop
}
}
}
return $is_valid;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.

You could also check each product in the cart and restrict it from the coupon with woocommerce_coupon_is_valid_for_product
/**
* exclude a product from an coupon by attribute value
*/
add_filter('woocommerce_coupon_is_valid_for_product', 'exclude_product_from_coupon_by_attribute', 12, 4);
function exclude_product_from_coupon_by_attribute($valid, $product, $coupon, $values ){
/**
* attribute Settings
*/
$taxonomy = 'pa_saison';
$term_slugs = array('SS22');
/**
* check if the product has the attribute and value
* and if yes restrict this product from the coupon
*/
if(in_array($product->get_attribute($taxonomy), $term_slugs)) {
$valid = false;
/**
* otherwise check if its a variation product
*/
} elseif($product->parent_id) {
/**
* set the parent product
*/
$parent = wc_get_product($product->parent_id);
/**
* check if parent has an attribute with this value
*/
if(in_array($parent->get_attribute($taxonomy), $term_slugs)) {
$valid = false;
}
/**
* for all other products which does not have the attribute with the value
* set the coupon to valid
*/
} else {
$valid = true;
}
return $valid;
}
I have tested it on my site and it works as expected.

Related

woocommerce order status stock reduction stop [duplicate]

I have a custom order status called 'quote', and I have added the following code to try and prevent stock levels being decremented for orders with this status.
function bw_do_not_reduce_quote_stock( $reduce_stock, $order ) {
if ( $order->has_status( 'quote' ) ) {
$reduce_stock = false;
}
return $reduce_stock;
}
add_filter( 'woocommerce_can_reduce_order_stock', 'bw_do_not_reduce_quote_stock', 10, 2 );
This works for orders placed on the front-end website. But if the admin adds or edits an order on the backend, the stock is decremented.
Is there an alternative hook for the backend? Or am I missing something else?
In addition to your current code, add the woocommerce_prevent_adjust_line_item_product_stock filter hook
/**
* Prevent adjust line item
*
* #param $prevent
* #param $item
* #param $quantity
*/
function filter_woocommerce_prevent_adjust_line_item_product_stock ( $prevent, $item, $quantity ) {
// Get order
$order = $item->get_order();
if ( $order->has_status( 'quote' ) ) {
$prevent = true;
}
return $prevent;
}
add_filter( 'woocommerce_prevent_adjust_line_item_product_stock', 'filter_woocommerce_prevent_adjust_line_item_product_stock', 10, 3 );

Export custom meta from Woocommerce to Shipstation

I have a problem with exporting products to shipstation, the fields that are generated using the Product Add-on Ultimate plugin are not exported to shipstation
How can I put together the correct function for exporting additional fields??
// Add this code to your theme functions.php file or a custom plugin
add_filter( 'woocommerce_shipstation_export_custom_field_2', 'shipstation_custom_field_2' );
function shipstation_custom_field_2() {
return '\_meta_key'; // Replace this with the key of your custom field
}
// This is for custom field 3
add_filter( 'woocommerce_shipstation_export_custom_field_3', 'shipstation_custom_field_3' );
function shipstation_custom_field_3() {
return '\_meta_key_2'; // Replace this with the key of your custom field
}
I found this solution in plugin documentation but it doesn't work for me
<?php
/**
* Get add-on metadata from each line item in the order
* #param $order_id
* #param $metakey The add-ons metakey (field label), usually prefixed by an underscore
*/
function prefix_get_addons_metadata_by_key( $order_id, $metakey=false ) {
$order = wc_get_order( $order_id );
$order_line_items = $order->get_items();
foreach( $order_line_items as $line_item ) {
// Here, we can iterate through all the meta for this line item
$all_meta = $line_item->get_meta_data();
if( $all_meta ) {
foreach( $all_meta as $meta ) {
$meta_id = $meta->id;
$meta_key = $meta->key;
$meta_value = $meta->value;
}
}
// Here, we can get the value by a specific metakey
if( $metakey ) {
$meta_value = $line_item->get_meta( $metakey );
}
}
}
This is from the plugin side.

WooCommerce Show checkout fields based on product category

I have added below code to show currency switcher dropdown on the WooCommerce checkout page which is working fine, but I don’t want to show currency switcher field if anyone has added product from "Games" category and use only default store currency
Code 1
add_action('woocommerce_before_checkout_billing_form', 'wps_add_select_checkout_field');
function wps_add_select_checkout_field( $checkout ) {
echo '<label for="payment_option" class="payment_option">'.__('Preferred currency').'</label>';
echo '<div class="own">', do_shortcode('[woocs]'), '</div>';
return $checkout;
}
//* Process the checkout
add_action('woocommerce_checkout_process', 'wps_select_checkout_field_process');
function wps_select_checkout_field_process() {
global $woocommerce;
// Check if set, if its not set add an error.
if ($_POST['payopt'] == "blank")
wc_add_notice( '<strong>Please select a currency</strong>', 'error' );
}
Based on this answer thread: Checking cart items for a product category in Woocommerce
I have tried below code and I think something is missing, If I am using below code it's not showing currency switcher at all even if product is from "Games" or other categories.
Code 2
add_action('woocommerce_before_cart', 'check_product_category_in_cart');
function check_product_category_in_cart() {
// Here set your product categories in the array (can be IDs, slugs or names)
$categories = array('games');
$found = false; // Initializing
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
// If product categories is found
if ( !has_term( $categories, 'product_cat', $cart_item['product_id'] ) ) {
$found = true; // Set to true
break; // Stop the loop
}
}
// If any defined product category is found, run below code
if ( $found ) {
add_action('woocommerce_before_checkout_billing_form', 'wps_add_select_checkout_field');
function wps_add_select_checkout_field( $checkout ) {
echo '<label for="payment_option" class="payment_option">'.__('Preferred currency').'</label>';
echo '<div class="own">', do_shortcode('[woocs]'), '</div>';
return $checkout;
}
//* Process the checkout
add_action('woocommerce_checkout_process', 'wps_select_checkout_field_process');
function wps_select_checkout_field_process() {
global $woocommerce;
// Check if set, if its not set add an error.
if ($_POST['payopt'] == "blank")
wc_add_notice( '<strong>Please select a currency</strong>', 'error' );
}
}
}
Do you have any other suggestion for the same? Where I can add currency switcher on checkout page based on cart product category. Code 1 is working fine on the checkout page but I don't want to run that code if product category is games.
You are not using the correct hook as woocommerce_before_cart action hook is only triggered in cart page, but not in checkout and it can't work this way. Instead try to use the following:
// Utility function that checks if at least a cart items remains to a product category
function has_product_category_in_cart( $product_category ) {
// Loop through cart items
foreach ( WC()->cart->get_cart() as $cart_item ) {
// If any product category is found in cart items
if ( has_term( $product_category, 'product_cat', $cart_item['product_id'] ) ) {
return true;
}
}
return false;
}
// Add a custom select field in checkout
add_action('woocommerce_before_checkout_billing_form', 'add_custom_checkout_select_field');
function add_custom_checkout_select_field( $checkout ) {
// Here set in the function your product category term ID, slugs, names or array
if ( ! has_product_category_in_cart( 'games' ) && shortcode_exists( 'woocs' ) ) {
echo '<label for="payment_option" class="payment_option">'.__('Preferred currency').'</label>';
echo '<div class="own">' . do_shortcode('[woocs]') . '</div>';
}
}
// Custom Checkout fields validation
add_action('woocommerce_checkout_process', 'custom_checkout_select_field_validation');
function custom_checkout_select_field_validation() {
if ( isset($_POST['payopt']) && empty($_POST['payopt']) )
wc_add_notice( '<strong>Please select a currency</strong>', 'error' );
}
Code goes in function.php file of your active child theme (active theme). Untested but it should works.

Woocommerce Sort by Attribute Value

In my store I have a attribute called "pieces" that phpmyadmin lists as "pa_pieces" in wp_postmeta.
My store lists toys so I want to be able to sort it by the amount of pieces/the attribute. So by this I mean instead of sorting by price from low to high I want to add sort by number of pieces. It should be available on every shop page.
I found the code below online (actually very recent) and added it to functions.php and modified it for my taxonomy name. The name of my sort options shows up, but when I try to sort by it, it just shows "No products were found matching your selection."
I updated my products and every item contains at least 1 piece.
I know woocommerce/wordpress has a filter where I can filter to only show selected numbers of pieces. But with ~500 different values it isn't really an option.
I am thankful for any help.
/**
* Save product attributes to post metadata when a product is saved.
*
* #param int $post_id The post ID.
* #param post $post The post object.
* #param bool $update Whether this is an existing post being updated or not.
*
* Refrence: https://codex.wordpress.org/Plugin_API/Action_Reference/save_post
*/
function wh_save_product_custom_meta($post_id, $post, $update) {
$post_type = get_post_type($post_id);
// If this isn't a 'product' post, don't update it.
if ($post_type != 'product')
return;
if (!empty($_POST['attribute_names']) && !empty($_POST['attribute_values'])) {
$attribute_names = $_POST['attribute_names'];
$attribute_values = $_POST['attribute_values'];
foreach ($attribute_names as $key => $attribute_name) {
switch ($attribute_name) {
//for lenght (int)
case 'pa_length':
if (!empty($attribute_values[$key][0])) {
update_post_meta($post_id, 'pa_length', $attribute_values[$key][0]);
}
break;
default:
break;
}
}
}
}
add_action( 'save_post', 'wh_save_product_custom_meta', 10, 3);
/**
* Main ordering logic for orderby attribute
* Refrence: https://docs.woocommerce.com/document/custom-sorting-options-ascdesc/
*/
add_filter('woocommerce_get_catalog_ordering_args', 'wh_catalog_ordering_args');
function wh_catalog_ordering_args($args) {
global $wp_query;
if (isset($_GET['orderby'])) {
switch ($_GET['orderby']) {
//for attribute/taxonomy=pa_length
case 'pa_length_asc' :
$args['order'] = 'ASC';
$args['meta_key'] = 'pa_length';
$args['orderby'] = 'meta_value_num';
break;
case 'pa_length_desc' :
$args['order'] = 'DESC';
$args['meta_key'] = 'pa_length';
$args['orderby'] = 'meta_value_num';
break;
}
}
return $args;
}
/**
* Lets add the created sorting order to the dropdown list.
* Refrence: http://hookr.io/filters/woocommerce_catalog_orderby/
*/
//To under Default Product Sorting in Dashboard > WooCommerce > Settings > Products > Display.
add_filter( 'woocommerce_default_catalog_orderby_options', 'wh_catalog_orderby' );
add_filter('woocommerce_catalog_orderby', 'wh_catalog_orderby');
function wh_catalog_orderby($sortby) {
$sortby['pa_length_asc'] = 'Sort by pieces: Low - High';
$sortby['pa_length_desc'] = 'Sort by pieces: High - Low';
return $sortby;
}

WooCommerce Pre-Orders Plugin

I'm looking into purchasing WooCommerce Pre Orders and have been looking at a friend's copy to demo. It's a great plugin, but I have an issue I can't seem to solve.
When ordering, you can only order 1 variation of a product at a time. You can edit the quantity of that variation in the cart but you can't add 2 variations of the same product to the same cart at the same time. If you do, it will empty the cart and replace it with the current selection. Since I'd be taking pre-orders for screen printed clothing (similar to teespring), ordering multiple variations (sizes in this instance) at one time is important. Making them make multiple orders from the same product would just drive them away.
I don't want to let customers order from multiple preorders at once since each preordered product has a different release/ship date, but I want to let them order multiple variations, i.e. a Small Tee, a Medium Tee, and a Large Tee, of a particular product since they would all ship at the same time.
I hope all of that made sense.
Here is the code that is responsible for the cart restrictions. Any help is much appreciated.
/**
* When a pre-order is added to the cart, remove any other products
*
* #since 1.0
* #param bool $valid
* #param $product_id
* #return bool
*/
public function validate_cart( $valid, $product_id ) {
global $woocommerce;
if ( WC_Pre_Orders_Product::product_can_be_pre_ordered( $product_id ) ) {
// if a pre-order product is being added to cart, check if the cart already contains other products and empty it if it does
if( $woocommerce->cart->get_cart_contents_count() >= 1 ) {
$woocommerce->cart->empty_cart();
$string = __( 'Your previous cart was emptied because pre-orders must be purchased separately.', 'wc-pre-orders' );
// Backwards compatible (pre 2.1) for outputting notice
if ( function_exists( 'wc_add_notice' ) ) {
wc_add_notice( $string );
} else {
$woocommerce->add_message( $string );
}
}
// return what was passed in, allowing the pre-order to be added
return $valid;
} else {
// if there's a pre-order in the cart already, prevent anything else from being added
if ( $this->cart_contains_pre_order() ) {
// Backwards compatible (pre 2.1) for outputting notice
if ( function_exists( 'wc_add_notice' ) ) {
wc_add_notice( __( 'This product cannot be added to your cart because it already contains a pre-order, which must be purchased separately.', 'wc-pre-orders' ) );
} else {
$woocommerce->add_error( __( 'This product cannot be added to your cart because it already contains a pre-order, which must be purchased separately.', 'wc-pre-orders' ) );
}
$valid = false;
}
}
return $valid;
}
/**
* Add any applicable pre-order fees when calculating totals
*
* #since 1.0
*/
public function maybe_add_pre_order_fee() {
global $woocommerce;
// Only add pre-order fees if the cart contains a pre-order
if ( ! $this->cart_contains_pre_order() ) {
return;
}
// Make sure the pre-order fee hasn't already been added
if ( $this->cart_contains_pre_order_fee() ) {
return;
}
$product = self::get_pre_order_product();
// Get pre-order amount
$amount = WC_Pre_Orders_Product::get_pre_order_fee( $product );
if ( 0 >= $amount ) {
return;
}
$fee = apply_filters( 'wc_pre_orders_fee', array(
'label' => __( 'Pre-Order Fee', 'wc-pre-orders' ),
'amount' => $amount,
'tax_status' => WC_Pre_Orders_Product::get_pre_order_fee_tax_status( $product ), // pre order fee inherits tax status of product
) );
// Add fee
$woocommerce->cart->add_fee( $fee['label'], $fee['amount'], $fee['tax_status'] );
}
/**
* Checks if the current cart contains a product with pre-orders enabled
*
* #since 1.0
* #return bool true if the cart contains a pre-order, false otherwise
*/
public static function cart_contains_pre_order() {
global $woocommerce;
$contains_pre_order = false;
if ( ! empty( $woocommerce->cart->cart_contents ) ) {
foreach ( $woocommerce->cart->cart_contents as $cart_item ) {
if ( WC_Pre_Orders_Product::product_can_be_pre_ordered( $cart_item['product_id'] ) ) {
$contains_pre_order = true;
break;
}
}
}
return $contains_pre_order;
}
/**
* Checks if the current cart contains a pre-order fee
*
* #since 1.0
* #return bool true if the cart contains a pre-order fee, false otherwise
*/
public static function cart_contains_pre_order_fee() {
global $woocommerce;
foreach ( $woocommerce->cart->get_fees() as $fee ) {
if ( is_object( $fee ) && 'pre-order-fee' == $fee->id )
return true;
}
return false;
}
/**
* Since a cart may only contain a single pre-ordered product, this returns the pre-ordered product object or
* null if the cart does not contain a pre-order
*
* #since 1.0
* #return object|null the pre-ordered product object, or null if the cart does not contain a pre-order
*/
public static function get_pre_order_product() {
global $woocommerce;
if ( self::cart_contains_pre_order() ) {
foreach ( $woocommerce->cart->cart_contents as $cart_item ) {
if ( WC_Pre_Orders_Product::product_can_be_pre_ordered( $cart_item['product_id'] ) ) {
// return the product object
return get_product( $cart_item['variation_id'] ? $cart_item['variation_id'] : $cart_item['product_id'] );
}
}
} else {
// cart doesn't contain pre-order
return null;
}
}
I know this post is old. But come to this same problem and i have solved my problem like this.
I have changed validate_cart() function in woocommerce-pre-orders/classes/class-wc-pre-orders-cart.php
It is like this :
public function validate_cart( $valid, $product_id ) {
global $woocommerce;
if ( WC_Pre_Orders_Product::product_can_be_pre_ordered( $product_id ) ) {
if( $woocommerce->cart->get_cart_contents_count() >= 1 ) {
if ( $this->cart_contains_pre_order() ) {
return $valid;
}
$string = __( 'Your cart contains items, please complete that order first and then purchase pre-order items, because pre-orders must be purchased separately.', 'wc-pre-orders' );
// Backwards compatible (pre 2.1) for outputting notice
if ( function_exists( 'wc_add_notice' ) ) {
wc_add_notice( $string );
} else {
$woocommerce->add_message( $string );
}
$valid = false;
return $valid;
}
else
{
return $valid;
}
} else {
// if there's a pre-order in the cart already, prevent anything else from being added
if ( $this->cart_contains_pre_order() ) {
// Backwards compatible (pre 2.1) for outputting notice
if ( function_exists( 'wc_add_notice' ) ) {
wc_add_notice( __( 'This product cannot be added to your cart because it already contains a pre-order, which must be purchased separately.', 'wc-pre-orders' ) );
} else {
$woocommerce->add_error( __( 'This product cannot be added to your cart because it already contains a pre-order, which must be purchased separately.', 'wc-pre-orders' ) );
}
$valid = false;
}
}
return $valid;
}
Note : I know this is not the right way for implementation. Because i
have edit in plugin directly. So when plugin will update, the changes are no longer there. And you can use any 'return $valid' or 'return true' or 'return false' as your choice.
Thank you.
I've been having the same issue and just found an answer (I hope) here:
Pre-orders can only purchase one at a time
I managed to implement hortongroup's plugin fix as described in the comments.
There was a slight error the shortcode line in the description, it should read:
echo do_shortcode('[pre_order_fix]');
It now seems to be working perfectly, I'll have wait for the next update to WooCommerce Pre Orders to see if the plugin fix still works.
Ideally by doing it this way we won't have to alter WooCommerce Pre Orders after every update.
Here's the code I used for the custom plugin:
<?php
/**
* Plugin Name: Woo Pre-Order Fix
* Plugin URI:
* Description: Fix the one item only issue with Woocommerce Pre-Orders
* Version: 1.0
* Author: hortongroup
* Author URI:
* License: GPL12
*/
function pre_order_fix_shortcode() {
if ( in_array( 'woocommerce-pre-orders/woocommerce-pre-orders.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
remove_filter( 'woocommerce_add_to_cart_validation', array( $GLOBALS['wc_pre_orders']->cart, 'validate_cart' ), 15, 2 );
}
}
add_shortcode('pre_order_fix', 'pre_order_fix_shortcode');
?>
Hopefully this will work for you too:)
Kind regards,
JP
I know that it's been a long time but I think this could still be useful for someone. If you have a child theme you can just add this to functions.php:
//remove pre-order limitations --> only one item per order
add_action( 'init', 'remove_validation_cart' );
function remove_validation_cart(){
remove_filter( 'woocommerce_add_to_cart_validation', array( $GLOBALS['wc_pre_orders']->cart, 'validate_cart' ), 15, 2 );
}
This avoids the need of adding a plugin
Since this issue still exists today and my scenario was slightly different, I've used the following filter to fix my issue.
I want pre-orders to be made but not one pre-order item per order, there could be multiple quantities and different pre-order products in one order. The only scenario I want to prevent is that regular products are being mixed with pre-orders (which shouldn't be possible).
Maybe anyone else could use this approach (going to check for something custom in the future which you can add to your child-theme) which would be better since it could now be overwritten with an update.
/**
* When a pre-order is added to the cart, remove any other products
*
* #since 1.0
* #param bool $valid
* #param $product_id
* #return bool
*/
public function validate_cart( $valid, $product_id ) {
global $woocommerce;
if ( WC_Pre_Orders_Product::product_can_be_pre_ordered( $product_id ) ) {
// if a pre-order product is being added to cart, check if the cart already contains other products and empty it if it does
if( $woocommerce->cart->get_cart_contents_count() >= 1 ) {
// count the amount of regular items in the cart
$regularCount = 0;
foreach ($woocommerce->cart->get_cart() as $item) {
// continue of the product is a pre-order product...
if (WC_Pre_Orders_Product::product_can_be_pre_ordered( $item['product_id'] )) {
continue;
}
$regularCount++;
}
// only clear the cart if the current items in it are having regular products...
if ($regularCount > 0) {
$woocommerce->cart->empty_cart();
$string = __( 'Your previous cart was emptied because pre-orders must be purchased separately.', 'wc-pre-orders' );
// Backwards compatible (pre 2.1) for outputting notice
if ( function_exists( 'wc_add_notice' ) ) {
wc_add_notice( $string );
} else {
$woocommerce->add_message( $string );
}
}
}
// return what was passed in, allowing the pre-order to be added
return $valid;
} else {
// if there's a pre-order in the cart already, prevent anything else from being added
if ( $this->cart_contains_pre_order() ) {
// Backwards compatible (pre 2.1) for outputting notice
if ( function_exists( 'wc_add_notice' ) ) {
wc_add_notice( __( 'This product cannot be added to your cart because it already contains a pre-order, which must be purchased separately.', 'wc-pre-orders' ) );
} else {
$woocommerce->add_error( __( 'This product cannot be added to your cart because it already contains a pre-order, which must be purchased separately.', 'wc-pre-orders' ) );
}
$valid = false;
}
}
return $valid;
}

Categories