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 );
Related
I'm looking for a way to programmatically add a switch between two Woocommerce subscription variations to the cart.
We're building a headless WP site, so I don't want to do it with a link, as described in this question
I've tried the following simple way, but it won't allow it that way, since the user is already subscribed:
WC()->cart->add_to_cart( 1907, 1, 1908 );
I want to mimic what is happening when the user goes to the upgrade/downgrade page, selects a new variation and presses Switch subscription. Usually that means that the user is sent to checkout with the switch in cart, I just want it to be added to the cart, since we have our own checkout.
The main issue is that Woocommerce thinks that a subscription product is unpurchaseable when it is added to the cart any other way than through the subscription switch form. The form sends a couple of GET-parameters which sends WC through some extra validation, and then allows the item to be added to the cart if everything else is valid.
My solution to this is to mimic this validation with some functions of my own, mostly stolen from the subscription plugin.
/**
* Must be set to true before programmatically adding subscription switch to cart.
*
* #var bool
*/
$kbhl_adding_switch_to_cart = false;
/**
* Array filled with data about current subscription.
* Should only be retrieved by kbhl_get_current_subscription_data().
*
* #var array
*/
$kbhl_global_current_subscription_data = array();
/**
* Cache whether a given product is purchasable or not to save running lots of queries for the same product in the same request
*
* $is_purchasable_cache taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #var array
*/
$kbhl_purchasable_cache = array();
/**
* $user_subscriptions_to_product taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #var array
*/
$kbhl_user_subscriptions_to_product = array();
/**
* If a product is being marked as not purchasable because it is limited and the customer has a subscription,
* but the current request is to switch the subscription, then mark it as purchasable.
*
* Function is_purchasable_switch() taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #param bool $is_purchasable Current purchasable status.
* #param obj $product Product being checked.
*
* #return bool New purchasable status.
*/
function kbhl_is_purchasable_switch( $is_purchasable, $product ) {
global $kbhl_purchasable_cache;
$kbhl_current_subscription_data = kbhl_get_current_subscription_data();
// Only process this filter if running custom add switch function.
if ( ! empty( $kbhl_current_subscription_data['id'] ) ) {
return $is_purchasable;
}
$product_key = wcs_get_canonical_product_id( $product );
// Set an empty cache if one isn't set yet.
if ( ! isset( $kbhl_purchasable_cache[ $product_key ] ) ) {
$kbhl_purchasable_cache[ $product_key ] = array();
}
// Exit early if we've already determined this product's purchasability via switching.
if ( isset( $kbhl_purchasable_cache[ $product_key ]['switch'] ) ) {
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
// If the product is already purchasble, we don't need to determine it's purchasibility via switching/auto-switching.
if ( true === $is_purchasable || ! is_user_logged_in() || ! wcs_is_product_switchable_type( $product ) || ! WC_Subscriptions_Product::is_subscription( $product->get_id() ) ) {
$kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
$user_id = get_current_user_id();
$product_limitation = wcs_get_product_limitation( $product );
if ( 'no' == $product_limitation || ! wcs_user_has_subscription( $user_id, $product->get_id(), wcs_get_product_limitation( $product ) ) ) {
$kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
// Adding to cart.
if ( array_key_exists( $kbhl_current_subscription_data['id'], kbhl_get_user_subscriptions_to_product( $product, $user_id, $product_limitation ) ) ) {
$is_purchasable = true;
}
$kbhl_purchasable_cache[ $product_key ]['switch'] = $is_purchasable;
return $kbhl_purchasable_cache[ $product_key ]['switch'];
}
add_filter( 'woocommerce_subscription_is_purchasable', 'kbhl_is_purchasable_switch', 13, 2 );
/**
* Gets a list of the customer subscriptions to a product with a particular limited status.
*
* Function get_user_subscriptions_to_product() taken from plugins\woocommerce-subscriptions\includes\class-wcs-limiter.php
*
* #param WC_Product|int $product The product object or product ID.
* #param int $user_id The user's ID.
* #param string $limit_status The limit status.
*
* #return WC_Subscription[] An array of a customer's subscriptions with a specific status and product.
*/
function kbhl_get_user_subscriptions_to_product( $product, $user_id, $limit_status ) {
global $user_subscriptions_to_product;
$product_id = is_object( $product ) ? $product->get_id() : $product;
$cache_key = "{$product_id}_{$user_id}_{$limit_status}";
if ( ! isset( $user_subscriptions_to_product[ $cache_key ] ) ) {
// Getting all the customers subscriptions and removing ones without the product is more performant than querying for subscriptions with the product.
$subscriptions = wcs_get_subscriptions(
array(
'customer_id' => $user_id,
'status' => $limit_status,
)
);
foreach ( $subscriptions as $subscription_id => $subscription ) {
if ( ! $subscription->has_product( $product_id ) ) {
unset( $subscriptions[ $subscription_id ] );
}
}
$user_subscriptions_to_product[ $cache_key ] = $subscriptions;
}
return $user_subscriptions_to_product[ $cache_key ];
}
/**
* When a subscription switch is added to the cart, store a record of pertinent meta about the switch.
*
* #since 1.4
*/
/**
* When a subscription switch is added to the cart, store a record of pertinent meta about the switch.
*
* Function set_switch_details_in_cart() taken from plugins\woocommerce-subscriptions\includes\class-wc-subscriptions-switcher.php
*
* #param array $cart_item_data Current cart item data.
* #param int $product_id ID of current product.
* #param int $variation_id ID of current product variation.
*
* #return array Updated cart item data.
*/
function kbhl_set_switch_details_in_cart( $cart_item_data, $product_id, $variation_id ) {
try {
$kbhl_current_subscription_data = kbhl_get_current_subscription_data();
if ( empty( $kbhl_current_subscription_data['id'] ) || empty( $kbhl_current_subscription_data['item'] ) ) {
return $cart_item_data;
}
$subscription = wcs_get_subscription( $kbhl_current_subscription_data['id'] );
// Requesting a switch for someone elses subscription.
if ( ! current_user_can( 'switch_shop_subscription', $subscription->get_id() ) ) {
wc_add_notice( __( 'You can not switch this subscription. It appears you do not own the subscription.', 'woocommerce-subscriptions' ), 'error' );
WC()->cart->empty_cart( true );
return array();
}
$item = wcs_get_order_item( absint( $kbhl_current_subscription_data['item'] ), $subscription );
// Else it's a valid switch.
$product = wc_get_product( $item['product_id'] );
$parent_products = WC_Subscriptions_Product::get_parent_ids( $product );
$child_products = array();
if ( ! empty( $parent_products ) ) {
foreach ( $parent_products as $parent_id ) {
$child_products = array_unique( array_merge( $child_products, wc_get_product( $parent_id )->get_children() ) );
}
}
if ( $product_id != $item['product_id'] && ! in_array( $item['product_id'], $child_products ) ) {
return $cart_item_data;
}
$next_payment_timestamp = $subscription->get_time( 'next_payment' );
// If there are no more payments due on the subscription, because we're in the last billing period, we need to use the subscription's expiration date, not next payment date.
if ( false == $next_payment_timestamp ) {
$next_payment_timestamp = $subscription->get_time( 'end' );
}
$cart_item_data['subscription_switch'] = array(
'subscription_id' => $subscription->get_id(),
'item_id' => absint( $kbhl_current_subscription_data['item'] ),
'next_payment_timestamp' => $next_payment_timestamp,
'upgraded_or_downgraded' => '',
);
return $cart_item_data;
} catch ( Exception $e ) {
wc_add_notice( __( 'There was an error locating the switch details.', 'woocommerce-subscriptions' ), 'error' );
WC()->cart->empty_cart( true );
return array();
}
}
add_filter( 'woocommerce_add_cart_item_data', 'kbhl_set_switch_details_in_cart', 11, 3 );
/**
* Gets subscription data for current user.
*
* #return array Subscription data, also stored to global variable $kbhl_global_current_subscription_data
*/
function kbhl_get_current_subscription_data() {
global $kbhl_adding_switch_to_cart, $kbhl_global_current_subscription_data;
if ( ! $kbhl_adding_switch_to_cart ) {
return array();
}
if ( ! empty( $kbhl_global_current_subscription_data ) ) {
return $kbhl_global_current_subscription_data;
}
$subscription_data = array();
$subs = wcs_get_users_subscriptions();
if ( ! empty( $subs ) ) {
foreach ( $subs as $sub ) {
$subscription_data['id'] = $sub->get_id();
foreach ( $sub->get_items() as $item_id => $item ) {
$subscription_data['item'] = $item_id;
break; // There should only be 1 order item.
}
break; // There should only be 1 subscription.
}
}
$kbhl_global_current_subscription_data = $subscription_data;
return $kbhl_global_current_subscription_data;
}
With this added you can add the switch to the cart as follows:
global $kbhl_adding_switch_to_cart; // If run inside a function.
WC()->cart->empty_cart( true );
$kbhl_adding_switch_to_cart = true;
WC()->cart->add_to_cart( 1907, 1, 1927 );
$kbhl_adding_switch_to_cart = false; // Reset after to get back to default validation.
I'm looking to allow the Order Again functionality to all statuses. By default WooCommerce only allows orders with a status of COMPLETED this functionality. It seems to be a two step process as the first step requires the button being shown to the user, this is completed by editing this file:
wc-template-functions.php
With this snippit of code:
function woocommerce_order_again_button( $order ) {
//if ( ! $order || ! $order->has_status( 'completed' ) || ! is_user_logged_in() ) {
// Allow 'Order Again' at all times.
if ( ! $order || ! is_user_logged_in() ) {
return;
}
wc_get_template( 'order/order-again.php', array(
'order' => $order
) );
}
By commenting out the validation of the $order->has_status() method, I'm able to show the button on the page. However, when trying clicking the Order Again button, it still does a check before adding the items to the cart.
Can anyone tell me where this code is stored to do a preliminary check on the $order->has_status()?
You can simply add difference order status to filter woocommerce_valid_order_statuses_for_order_again.
add_filter( 'woocommerce_valid_order_statuses_for_order_again', 'add_order_again_status', 10, 1);
function add_order_again_status($array){
$array = array_merge($array, array('on-hold', 'processing', 'pending-payment', 'cancelled', 'refunded'));
return $array;
}
Since the OP's original question, WooCommerce has added a filter to these statuses. The function can be found in includes/wc-template-functions.php
https://docs.woocommerce.com/wp-content/images/wc-apidocs/function-woocommerce_order_again_button.html
/**
* Display an 'order again' button on the view order page.
*
* #param object $order
* #subpackage Orders
*/
function woocommerce_order_again_button( $order ) {
if ( ! $order || ! $order->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_order_again', array( 'completed' ) ) ) || ! is_user_logged_in() ) {
return;
}
wc_get_template( 'order/order-again.php', array(
'order' => $order,
) );
}
So in order to filter the statuses you could do something like this (wc_get_order_statuses() just returns all order statuses in this case; you can set the $statuses variable to an array of an statuses you would like):
add_filter('woocommerce_valid_order_statuses_for_order_again', function( $statuses ){
$statuses = wc_get_order_statuses();
return $statuses;
}, 10, 2);
In WooCommerce any order placed with the BACS (direct bank transfer) is set to "on-hold".
How would one go about changing this automatically to processing?
I wan't it to work inside the functions.php
I have the following code but that doesn't work:
add_filter( 'woocommerce_payment_complete_order_status', 'rfvc_update_order_status', 10, 2 );
function rfvc_update_order_status( $order_status, $order_id ) {
$order = new WC_Order( $order_id );
if ( 'on-hold' == $order_status && 'on-hold' == $order->status ) {
return 'processing';
}
return $order_status;
}
Any help would be great!
New 2020 update
WooCommerce version 3.4 has introduced a much better hook than woocommerce_thankyou or woocommerce_thankyou_bacs, that allows to change the default order status for BACS payment method.
Using this hook will:
clearly lighten the necessary code,
avoid "on-hold" notification to the customer when a BACS order is placed.
So use instead the following:
add_filter( 'woocommerce_bacs_process_payment_order_status','filter_bacs_process_payment_order_status_callback', 10, 2 );
function filter_bacs_process_payment_order_status_callback( $status, $order ) {
return 'processing';
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Original answer:
Update (added a version for woocommerce 3+ at the end)
It seems that woocommerce_payment_complete_order_status action hook doesn't trigger with BACS payment method.
Based on this thread, 'woocommerce_thankyou' action hook does the job:
add_action( 'woocommerce_thankyou', 'bacs_order_payment_processing_order_status', 10, 1 );
function bacs_order_payment_processing_order_status( $order_id ) {
if ( ! $order_id ) {
return;
}
// Get an instance of the WC_Order object
$order = new WC_Order( $order_id );
if ( ( get_post_meta($order->id, '_payment_method', true) == 'bacs' ) && ('on-hold' == $order->status || 'pending' == $order->status) ) {
$order->update_status('processing');
} else {
return;
}
}
Code goes in function.php file of your active child theme (or active theme). tested and works.
For woocommerce 3+ versions:
Here we use the similar composite hook woocommerce_thankyou_{$order->get_payment_method()}:
add_action( 'woocommerce_thankyou_bacs', 'bacs_order_payment_processing_order_status', 10, 1 );
function bacs_order_payment_processing_order_status( $order_id ) {
if ( ! $order_id ) {
return;
}
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
if ( in_array( $order->get_status(), array('on-hold', 'pending') ) ) {
$order->update_status('processing');
} else {
return;
}
}
Code goes in function.php file of your active child theme (or active theme). tested and works.
There is a new filter which will allow you to set the status when BACS 'payment' is processed.
/**
* Change the default status when BACS 'payment' is processed.
*
* #see WC_Gateway_BACS::process_payment()
* woocommerce/includes/gateways/bacs/class-wc-gateway-bacs.php:362
* #since Mar 8, 2018
* #link https://github.com/woocommerce/woocommerce/blob/750fda3b1b55c55645f626d3873d956282e3ac1b/includes/gateways/bacs/class-wc-gateway-bacs.php#L364
*
* #filter woocommerce_bacs_process_payment_order_status
* #priority 10
* #args 2
*
* #param string $status Status to filter. Default 'on-hold'.
* #param WC_Order $order
* #return string New status 'processing'.
*/
add_filter( 'woocommerce_bacs_process_payment_order_status', function( $status = 'on_hold', $order = null ) {
return 'processing';
}, 10, 2 );
Try changing the code to this:
function rfvc_update_order_status( $order_status, $order_id ) {
$order = new WC_Order( $order_id );
if ( 'on-hold' == $order_status && 'on-hold' == $order->status ) {
$order->update_status('processing', 'order_note');
}
return $order_status;
}
The key change here is this:
$order->update_status('processing', 'order_note');
You can add order note too if you prefer.
I added this to my functions.php file :
add_filter ('woocommerce_add_to_cart_redirect', 'woo_redirect_to_checkout');
function woo_redirect_to_checkout() {
$checkout_url = WC()->cart->get_checkout_url();
return $checkout_url;
}
But now, all the products are re-directing strait to check-out. I would like to have this option only in one product. Is that a way I can add a product ID to that same filer?
Thank you!
You need to get the product when It is just added to cart , then check if for this product you want to redirect the cart page to checkout page . You need to change $desire_product = 'certain_product'; line on below code and It will definitely work.
add_filter( 'woocommerce_add_to_cart_redirect', 'woo_redirect_checkout' );
function woo_redirect_checkout() {
global $woocommerce;
$desire_product = 'certain_product';
//Get product ID
$product_id = (int) apply_filters( 'woocommerce_add_to_cart_product_id', $_POST['add-to-cart'] );
//Check if current product is subscription
if ( $product_id == $desire_product ){
$checkout_url = $woocommerce->cart->get_checkout_url();
return $checkout_url;
exit;
} else {
$cart_url = $woocommerce->cart->get_cart_url();
return $cart_url;
exit;
}
}
I wrote a little plugin for this, sharing it here. The plugin adds a small checkbox to the product metabox, so you can specify which products should trigger the automatic skip to checkout. Basically using the same woocommerce_add_to_cart_redirect filter as in the other answers, but providing the admin backend option to determine which products trigger the redirection.
<?php
/**
* Plugin Name: Redirect to checkout
* Plugin URI: http://stackoverflow.com/q/32962653/383847
* Description: redirect to checkout for certain products
* Version: 1.0
* Author: Kathy Darling
* Author URI: http://kathyisawesome.com
* Requires at least: 3.8
* Tested up to: 3.9
*
* Text Domain: kia-redirect-to-checkout
* Domain Path: /languages/
*
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/*
* Add text inputs to product metabox
*/
function kia_add_to_wc_metabox(){
global $post;
echo '<div class="options_group">';
// Suggested Price
echo woocommerce_wp_checkbox( array(
'id' => '_redirect_to_checkout',
'label' => __( 'Redirect to checkout', 'kia-redirect-to-checkout' ) ,
'description' => __( 'When this item is added to the cart, re-direct the customer to checkout immediately.', 'kia-redirect-to-checkout' )
)
);
echo '</div>';
}
add_action( 'woocommerce_product_options_general_product_data', 'kia_add_to_wc_metabox' );
/*
* Save extra meta info
*/
function kia_process_wc_meta_box( $post_id, $post ) {
if ( isset( $_POST['_redirect_to_checkout'] ) ) {
update_post_meta( $post_id, '_redirect_to_checkout', 'yes' );
} else {
update_post_meta( $post_id, '_redirect_to_checkout', 'no' );
}
}
add_action( 'woocommerce_process_product_meta', 'kia_process_wc_meta_box', 1, 2 );
/*
* Redirect to checkout
*/
function kia_add_to_cart_redirect( $url ){
// If product is one of our special types
if ( is_numeric( $_REQUEST['add-to-cart'] ) && kia_maybe_redirect_cart( (int) $_REQUEST['add-to-cart'] ) ) {
// Remove default cart message
WC()->clear_messages();
// Redirect to checkout
$url = WC()->cart->get_checkout_url();
}
return $url;
}
add_filter( 'woocommerce_add_to_cart_redirect', 'kia_add_to_cart_redirect' );
/*
* check if an item has custom field
*/
function kia_maybe_redirect_cart( $product_id ){
if ( 'yes' == get_post_meta( $product_id, '_redirect_to_checkout', true ) ){
return TRUE;
} else {
return false;
}
}
Updating WooCommerce 3.0+
<?php
/**
* Plugin Name: WC Redirect to checkout
* Plugin URI: http://stackoverflow.com/q/32962653/383847
* Description: Redirect to checkout for certain products
* Version: 1.0
* Author: Kathy Darling
* Author URI: http://kathyisawesome.com
* Requires at least: 3.8
* Tested up to: 3.9
* WC requires at least: 3.1.0
* WC tested up to: 4.0.1
*
* Text Domain: kia-redirect-to-checkout
* Domain Path: /languages/
*
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Add text inputs to product metabox
*/
function kia_add_to_wc_metabox(){
global $post;
echo '<div class="options_group">';
// Suggested Price
echo woocommerce_wp_checkbox( array(
'id' => '_redirect_to_checkout',
'label' => __( 'Redirect to checkout', 'kia-redirect-to-checkout' ) ,
'description' => __( 'When this item is added to the cart, re-direct the customer to checkout immediately.', 'kia-redirect-to-checkout' )
)
);
echo '</div>';
}
add_action( 'woocommerce_product_options_general_product_data', 'kia_add_to_wc_metabox' );
/**
* Save extra meta info
*
* #param WC_Product $product
*/
function kia_process_wc_meta_box( $product ) {
if ( isset( $_POST['_redirect_to_checkout'] ) ) {
$product->update_meta_data( '_redirect_to_checkout', 'yes' );
} else {
$product->update_meta_data( '_redirect_to_checkout', 'no' );
}
}
add_action( 'woocommerce_admin_process_product_object', 'kia_process_wc_meta_box' );
/**
* Redirect to checkout
*
* #param WC_Product $product
*/
function kia_add_to_cart_redirect( $url, $product ) {
// If product is one of our special products.
if ( kia_maybe_redirect_cart( $product ) ) {
// Remove default cart message.
wc_clear_notices();
// Redirect to checkout.
$url = wc_get_checkout_url();
}
return $url;
}
add_filter( 'woocommerce_add_to_cart_redirect', 'kia_add_to_cart_redirect', 10, 2 );
/**
* Check if an item has custom field.
*
* #param WC_Product $product
*/
function kia_maybe_redirect_cart( $product ) {
return wc_string_to_bool( $product instanceof WC_Product && $product->get_meta( '_redirect_to_checkout', true ) );
}
https://gist.github.com/helgatheviking/f76b97d7d19813538e32b8f5f2dae6ec
There are a few action hooks as well that you can use, for eg: woocommerce_add_to_cart which passes the product id to the callback function:
add_action( 'woocommerce_add_to_cart', 'custom_add_to_cart', 10, 2 );
function custom_add_to_cart( $cart_item_key, $product_id ) {
// replace 123 with a valid product id
if( 123 == $product_id ) {
wp_redirect( WC()->cart->get_checkout_url() );
exit;
}
}
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;
}