Hide WooCommerce product if already purchased by logged in user - php

I am creating an LMS using LearnDash + WooCommerce. So far so good. Now, I'd like to hide a Product when the logged-in user has already purchased it to a) prevent the user from purchasing it twice, and b) so that I can display a product grid for them without the already purchased item(s).
Another example: if a user who purchased ITEM A goes to the shop, ITEM A should not even be displayed for them.
Thanks so much!

So, I ended up with a nice workaround:
On the product page, I created a CUSTOM FIELD where I can input the COURSE ID.
Then, with PHP, I check the Product and retrieve the custom field for the COURSE ID.
LearnDash has a shortcode for students... so after retrieving the course ID, I can display a message/button for users who have registered already for this course.
This is the code:
<?php
global $product;
$postid = $product->get_id();
$courseid = get_post_meta( $product->get_id(), 'ID_del_curso', true );
$html = '"]<center><div class="alreadyPurchased"><i class="fas fa-graduation-cap paddingRightM"></i> Ya te has registrado</div></center>';
$openIT = '[student course_id="';
$closeIT = '[/student]';
echo do_shortcode( $openIT . $courseid . $html . $closeIT );
?>
Hope it helps!

There is a simpler solution.
Use this LearnDash shortcode...
[visitor]
Hello visitor.
Put your "Buy now" button here.
[/visitor]

/**
* Disables repeat purchase for products / variations
*
* #param bool $purchasable true if product can be purchased
* #param \WC_Product $product the WooCommerce product
* #return bool $purchasable the updated is_purchasable check
*/
function sv_disable_repeat_purchase( $purchasable, $product ) {
// Don't run on parents of variations,
// function will already check variations separately
if ( $product->is_type( 'variable' ) ) {
return $purchasable;
}
// Get the ID for the current product (passed in)
$product_id = $product->get_id();
// return false if the customer has bought the product / variation
if ( wc_customer_bought_product( wp_get_current_user()->user_email, get_current_user_id(), $product_id ) ) {
$purchasable = false;
}
return $purchasable;
}
add_filter( 'woocommerce_is_purchasable', 'sv_disable_repeat_purchase', 10, 2 );
/**
* Shows a "purchase disabled" message to the customer
*/
function sv_purchase_disabled_message() {
global $product; // get the current product to see if it has been purchased
if ( $product->is_type( 'variable' ) ) {
foreach ( $product->get_children() as $variation_id ) {
// Render the purchase restricted message if it has been purchased
if ( wc_customer_bought_product( wp_get_current_user()->user_email, get_current_user_id(), $variation_id ) ) {
sv_render_variation_non_purchasable_message( $product, $variation_id );
}
}
} else {
if ( wc_customer_bought_product( wp_get_current_user()->user_email, get_current_user_id(), $product->get_id() ) ) {
echo '<div class="woocommerce"><div class="woocommerce-info wc-nonpurchasable-message">You\'ve already purchased this product! It can only be purchased once.</div></div>';
}
}
}
add_action( 'woocommerce_single_product_summary', 'sv_purchase_disabled_message', 31 );
/**
* Generates a "purchase disabled" message to the customer for specific variations
*
* #param \WC_Product $product the WooCommerce product
* #param int $no_repeats_id the id of the non-purchasable product
*/
function sv_render_variation_non_purchasable_message( $product, $no_repeats_id ) {
// Double-check we're looking at a variable product
if ( $product->is_type( 'variable' ) && $product->has_child() ) {
$variation_purchasable = true;
foreach ( $product->get_available_variations() as $variation ) {
// only show this message for non-purchasable variations matching our ID
if ( $no_repeats_id === $variation['variation_id'] ) {
$variation_purchasable = false;
echo '<div class="woocommerce"><div class="woocommerce-info wc-nonpurchasable-message js-variation-' . sanitize_html_class( $variation['variation_id'] ) . '">You\'ve already purchased this product! It can only be purchased once.</div></div>';
}
}
}
if ( ! $variation_purchasable ) {
wc_enqueue_js("
jQuery('.variations_form')
.on( 'woocommerce_variation_select_change', function( event ) {
jQuery('.wc-nonpurchasable-message').hide();
})
.on( 'found_variation', function( event, variation ) {
jQuery('.wc-nonpurchasable-message').hide();
if ( ! variation.is_purchasable ) {
jQuery( '.wc-nonpurchasable-message.js-variation-' + variation.variation_id ).show();
}
})
.find( '.variations select' ).change();
");
}
}
Gist snippet got from here - please check

Related

Add Woocommerce Subscription switch to cart programmatically

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.

WooCommerce: If specific product is the only item in cart, clear cart

If users purchase a product on my site, I give them the option of adding a discounted product to their cart at checkout. However, users are able to remove the original product from cart and the discounted product still remains within the cart.
Is it possible to have it such that if the discounted product is the only one in the cart, then the cart should be empty? I haven't found a way to do this.
What I have tried
Currently, I use the following code from bekarice found on Github:
/**
* Renders a notice and prevents checkout if the cart
* only contains products in a specific category
*/
function sv_wc_prevent_checkout_for_category() {
// set the slug of the category for which we disallow checkout
$category = 'clothing';
// get the product category
$product_cat = get_term_by( 'slug', $category, 'product_cat' );
// sanity check to prevent fatals if the term doesn't exist
if ( is_wp_error( $product_cat ) ) {
return;
}
$category_name = '' . $product_cat->name . '';
// check if this category is the only thing in the cart
if ( sv_wc_is_category_alone_in_cart( $category ) ) {
// render a notice to explain why checkout is blocked
wc_add_notice( sprintf( 'Hi there! Looks like your cart only contains products from the %1$s category – you must purchase a product from another category to check out.', $category_name ), 'error' );
}
}
add_action( 'woocommerce_check_cart_items', 'sv_wc_prevent_checkout_for_category' );
/**
* Checks if a cart contains exclusively products in a given category
*
* #param string $category the slug of the product category
* #return bool - true if the cart only contains the given category
*/
function sv_wc_is_category_alone_in_cart( $category ) {
// check each cart item for our category
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
// if a product is not in our category, bail out since we know the category is not alone
if ( ! has_term( $category, 'product_cat', $cart_item['data']->id ) ) {
return false;
}
}
// if we're here, all items in the cart are in our category
return true;
}
But I don't think it as good a method. Basically, it doesn't allow users to checkout if cart only contains items from a certain category.
You can use the following code that will check on adding to cart your specific discounted product that cart is not empty and that it's not alone in cart, removing it from cart:
// Add to cart validation for the discounted product
add_filter( 'woocommerce_add_to_cart_validation', 'check_specific_discounted_product', 10, 3 );
function check_specific_discounted_product( $passed, $product_id, $quantity ) {
// Settings
$discounted_product_id = 53;
if( WC()->cart->is_empty() && $discounted_product_id == $product_id ) {
wc_add_notice( __("This product can't be purchased alone."), 'notice' );
return false;
}
return $passed;
}
// Removing the discounted product if it's alone in cart
add_action( 'woocommerce_before_calculate_totals', 'conditionally_remove_a_discounted_product' );
function conditionally_remove_a_discounted_product( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Settings
$discounted_product_id = 53;
// Initializing variables
$discounted_item_key = false;
// Loop through cart items (first loop)
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ){
// When free productis is cart
if ( in_array( $discounted_product_id, array($cart_item['product_id'], $cart_item['variation_id']) ) ) {
$discounted_item_key = $cart_item_key;
}
// if any other product is in cart: EXIT
else {
return;
}
}
// When the discounted product is alone in cart, remove it
if( $discounted_item_key ) {
// display notice on removal (optional)
wc_clear_notices();
wc_add_notice( __("The discounted product can't be purchased alone and has been removed."), 'notice' );
$cart->remove_cart_item( $discounted_item_key ); // Remove
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

How to display message wc_print_notice on a custom page

I have a snippet I got from https://businessbloomer.com/woocommerce-check-product-category-cart/ which is:
/*
* #snippet Check if Product Category is in the Cart - WooCommerce
* #how-to Watch tutorial # https://businessbloomer.com/?p=19055
* #sourcecode https://businessbloomer.com/?p=72900
* #author Rodolfo Melogli
* #testedwith WooCommerce 3.1.2
*/
add_action('woocommerce_before_cart', 'bbloomer_check_category_in_cart');
function bbloomer_check_category_in_cart() {
// 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 Cart has category "download", set $cat_in_cart to true
if ( has_term( 'download', 'product_cat', $cart_item['product_id'] ) ) {
$cat_in_cart = true;
break;
}
}
// Do something if category "download" is in the Cart
if ( $cat_in_cart ) {
// For example, print a notice
wc_print_notice( 'Category Downloads is in the Cart!', 'notice' );
// Or maybe run your own function...
// ..........
}
}
It displays a notice if a product category is in the cart but it display the message via wc_print_notice on the cart page. I would like to display this message on a custom theme page template either via a short-code or any suggestions are welcome.
How about:
// modify the function above
if ( $cat_in_cart )
{
$msg = 'Category Downloads is in the Cart!';
wc_print_notice( $msg, 'notice' );
return $msg;
}
return null;
...
// create a new function for the shortcode
// it can be after the above function
add_shortcode( 'myshortcode', 'myshortcode_func' );
function myshortcode_func( $atts ) {
return (($msg = bbloomer_check_category_in_cart()) !== null)
? '<div class="msg">' . esc_html ($msg) . '</div>'
: ''
;
}
Then just add the shortcode in the page content.
[myshortcode]

How to skip cart page on woocomerce for certain products only?

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;
}
}

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