I'm using the latest WC and I really want prices to show next to each variation, for a better overview.
I have trouble finding the proper code for my functions.php to display prices next to each variation. I saw several older posts and noone of them really worked.
I tried the following:
add_filter( 'woocommerce_variation_option_name', 'display_price_in_variation_option_name' );
function display_price_in_variation_option_name( $term ) {
global $wpdb, $product;
if ( empty( $term ) ) return $term;
if ( empty( $product->id ) ) return $term;
$result = $wpdb->get_col( "SELECT slug FROM {$wpdb->prefix}terms WHERE name = '$term'" );
$term_slug = ( !empty( $result ) ) ? $result[0] : $term;
$query = "SELECT postmeta.post_id AS product_id
FROM {$wpdb->prefix}postmeta AS postmeta
LEFT JOIN {$wpdb->prefix}posts AS products ON ( products.ID = postmeta.post_id )
WHERE postmeta.meta_key LIKE 'attribute_%'
AND postmeta.meta_value = '$term_slug'
AND products.post_parent = $product->id";
$variation_id = $wpdb->get_col( $query );
$parent = wp_get_post_parent_id( $variation_id[0] );
if ( $parent > 0 ) {
$_product = new WC_Product_Variation( $variation_id[0] );
return $term . ' (' . wp_kses( woocommerce_price( $_product->get_price() ), array() ) . ')';
}
return $term;
}
This one didn't work. I couldn't see any changes after implementing one way or the other. But its for older versions of WC, although Im beginning to believe that my theme is blocking it somehow. Im using Shopera.
Since there is no id argument woocommerce_variation_option_name, we could do this with script.
add_action( "wp_head", "function_to_woo_script" );
function function_to_woo_script () {
?>
<script>
$ = jQuery;
$(document).ready(function(){
var json_product = jQuery.parseJSON( $(".variations_form").attr("data-product_variations") );
var currency = "$";
for( i = 0; i < json_product.length; i++ ) {
var attr_name = $(".variations select").attr("data-attribute_name");
$(".variations select option[value='"+json_product[i].attributes[attr_name]+"']").html( json_product[i].attributes[attr_name] + " - " + currency + json_product[i].display_regular_price );
}
});
</script>
<?php
}
Result
Related
I would like to add a quanity eg 2 which users are allowed to buy within its timeframe. Also the error notice message is showing on the product page. How would I go about letting the product added to cart and the error message showing on the cart page?
Based on Allow customers to buy only one product from defined WooCommerce product category answer code, here is my code attempt:
// Based partially on wc_customer_bought_product(), will return a boolean value based on orders count (false for O orders and true when there is at least one paid order)
function has_bought( $value = 0 ) {
if ( ! is_user_logged_in() && $value === 0 ) {
return false;
}
global $wpdb;
// Based on user ID (registered users)
if ( is_numeric( $value ) ) {
$meta_key = '_customer_user';
$meta_value = $value == 0 ? (int) get_current_user_id() : (int) $value;
}
// Based on billing email (Guest users)
else {
$meta_key = '_billing_email';
$meta_value = sanitize_email( $value );
}
$paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$count = $wpdb->get_var( $wpdb->prepare("
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '%s'
AND pm.meta_value = %s
AND UNIX_TIMESTAMP(p.post_date) >= (UNIX_TIMESTAMP(NOW()) - (86400 * 1))
LIMIT 1
", $meta_key, $meta_value ) );
// Return a boolean value based on orders count
return $count > 0 ? true : false;
}
function filter_woocommerce_add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = null, $variations = null ) {
// Set categories
$categories = array ( 'kitchen' );
// If passed & has category
if ( $passed && has_term( $categories, 'product_cat', $product_id ) ) {
// Initialize
$value = '';
// User logged in
if ( is_user_logged_in() ) {
// Get the current user's ID
$value = get_current_user_id();
} else {
// Get billing_email
$value = WC()->customer->get_billing_email();
// When empty
if ( empty ( $value ) ) {
// Get account email
$value = WC()->customer->get_email();
}
}
// NOT empty
if ( ! empty ( $value ) ) {
if ( has_bought( $value ) ) {
// Display an error message
wc_add_notice( __( 'My custom error message', 'woocommerce' ), 'error' );
// False
$passed = false;
}
}
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'filter_woocommerce_add_to_cart_validation', 10, 5 );
The code below displays a message on the product page that has the "free" tag to the logged in user
function add_text_after_excerpt_single_product_free2( $short_description ) {
global $product;
$terms = get_the_terms( $product->get_id(), 'product_tag' );
foreach ( $terms as $term ) {
if ( $term->name == 'Free' && is_user_logged_in() ) {
$short_description .= 'Downloading this file is free and does not require subscription';
}
}
return $short_description;
}
add_filter('woocommerce_short_description','add_text_after_excerpt_single_product_free2', 20, 1);
I want the above code to be executed if a user has purchased a specific product
For example, a user buys a product called Photoshop training, and from now on the above code will be executed for him
thanx
You can write a custom function to check whether user buy a specific product. Try the below code. code will go in your active theme functions.php file.
This below function is inspired from here - Check if a user/guest has purchased specific products in WooCommerce
function has_bought_items( $user_id = 0, $product_ids = 0 ) {
global $wpdb;
// Based on user ID (registered users)
$meta_key = '_customer_user';
$meta_value = $user_id;
$paid_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$product_ids = is_array( $product_ids ) ? implode(',', $product_ids) : $product_ids;
$line_meta_value = $product_ids != ( 0 || '' ) ? 'AND woim.meta_value IN ('.$product_ids.')' : 'AND woim.meta_value != 0';
// Count the number of products
$count = $wpdb->get_var( "
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
INNER JOIN {$wpdb->prefix}woocommerce_order_items AS woi ON p.ID = woi.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_statuses ) . "' )
AND pm.meta_key = '$meta_key'
AND pm.meta_value = '$meta_value'
AND woim.meta_key IN ( '_product_id', '_variation_id' ) $line_meta_value
" );
// Return true if count is higher than 0 (or false)
return $count > 0 ? true : false;
}
function add_text_after_excerpt_single_product_free2( $short_description ) {
// Define the targeted specific Products IDs
$product_ids = array( 74 );
if ( is_user_logged_in() && has_bought_items( get_current_user_id(), $product_ids ) ) {
$short_description .= 'Downloading this file is free and does not require subscription';
}
return $short_description;
}
add_filter( 'woocommerce_short_description','add_text_after_excerpt_single_product_free2', 20, 1 );
Tested and works
You'd need to store the data in the user, and then access it via their login session. It looks to me like
add_user_meta( int $user_id, string $meta_key, mixed $meta_value, bool $unique = false )
Would do the trick. For reference:
https://developer.wordpress.org/reference/functions/add_user_meta/
https://wordpress.stackexchange.com/questions/111839/add-user-meta-vs-update-user-meta
on my website I will like to display a custom message on the checkout page if a user have previously purchased the product they are about to buy. so I came across this code that actually displays a custom message if a user already bought the product, the code only display on the single product page. please how can I display this message on the checkout page?
add_action( 'woocommerce_before_add_to_cart_form', 'user_logged_in_product_already_bought', 30 );
function user_logged_in_product_already_bought() {
global $product;
if ( ! is_user_logged_in() ) return;
if ( wc_customer_bought_product( '', get_current_user_id(), $product->get_id() ) ) {
echo '<div>You purchased this in the past. Buy again?</div>';
}
}
As #Dmitry suggested you can use the woocommerce_before_checkout_form action hook. but you can't access global $product; on the checkout page. try the below code.
function user_logged_in_product_already_bought() {
global $woocommerce;
if ( ! is_user_logged_in() ) return;
// check if current user in specific role.
$user = wp_get_current_user();
if ( in_array( 'customer', (array) $user->roles ) ) {
}
$items = $woocommerce->cart->get_cart();
$has_bought = false;
foreach($items as $item => $values) {
if ( has_bought_items( get_current_user_id(), $values['data']->get_id() ) ) {
$has_bought = true;
break;
}
}
if( $has_bought ){
wc_print_notice( "You purchased this in the past. Buy again?", 'success' );
}
}
add_action( 'woocommerce_before_checkout_form', 'user_logged_in_product_already_bought' );
Tested and works
As per #7uc1f3r comment, I will like to prevent the double purchase of product per user. you can use the woocommerce_add_to_cart_validation action hook to prevent adding products from the cart.
function prevent_from_adding_cart_if_user_product_already_bought( $passed, $product_id, $quantity ) {
if ( has_bought_items( get_current_user_id(), $product_id ) ) {
wc_add_notice( __( "You purchased this in the past. Buy again?", 'woocommerce' ), 'error' );
$passed = false;
}
return $passed;
}
add_filter( 'woocommerce_add_to_cart_validation', 'prevent_from_adding_cart_if_user_product_already_bought', 10, 3 );
Tested and works
You can use this awesome function made by LoicTheAztec to check if a user has purchased specific products.
function has_bought_items( $user_var = 0, $product_ids = 0 ) {
global $wpdb;
// Based on user ID (registered users)
if ( is_numeric( $user_var) ) {
$meta_key = '_customer_user';
$meta_value = $user_var == 0 ? (int) get_current_user_id() : (int) $user_var;
}
// Based on billing email (Guest users)
else {
$meta_key = '_billing_email';
$meta_value = sanitize_email( $user_var );
}
$paid_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$product_ids = is_array( $product_ids ) ? implode(',', $product_ids) : $product_ids;
$line_meta_value = $product_ids != ( 0 || '' ) ? 'AND woim.meta_value IN ('.$product_ids.')' : 'AND woim.meta_value != 0';
// Count the number of products
$count = $wpdb->get_var( "
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
INNER JOIN {$wpdb->prefix}woocommerce_order_items AS woi ON p.ID = woi.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_statuses ) . "' )
AND pm.meta_key = '$meta_key'
AND pm.meta_value = '$meta_value'
AND woim.meta_key IN ( '_product_id', '_variation_id' ) $line_meta_value
" );
// Return true if count is higher than 0 (or false)
return $count > 0 ? true : false;
}
UPDATE as per OP request.
You can use the below code for check if the current user is in a specific role.
$user = wp_get_current_user();
if ( in_array( 'customer', (array) $user->roles ) ) {
}
and only products with the status of 'processing in has_bought_items function change the value of the $paid_statuses variable. as per your need.
for various reasons a webshop needs to allow guest orders, but then saves the data to an existing user if the email is already registered. However, a product (parent variable product) should not be bought twice.
In that case, the customer should be returned to the checkout page and get a notice that this product is already bought.
So far i have the following code (not complete):
add_action('woocommerce_checkout_process', function () {
if (!is_user_logged_in() && $_POST['billing_email'] && $_POST['createaccount'] == 1) {
$user = get_user_by('email', $_POST['billing_email']);
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id();
if (!empty($user)){
if (wc_customer_bought_product($user->user_email,$user->ID,$product_id))
return false;
wp_set_current_user($user->ID);
}
if (!empty($user)) {
if (wc_customer_bought_product($user->user_email,$user->ID,$product_id))
return true;
exit;
}
else{
///guest, continue
}
}}});
But that gives only an internal server error.
Anyone?
First, instead of using wc_customer_bought_product(), you can use the following enhanced conditional function from this existing answer (based on the same source code than wc_customer_bought_product()). This function will handle your variable products case.
// Conditional function( stackOverFlow answer: https://stackoverflow.com/a/46217461/3730754 )
function has_bought_items( $user_var = 0, $product_ids = 0 ) {
global $wpdb;
// Based on user ID (registered users)
if ( is_numeric( $user_var) ) {
$meta_key = '_customer_user';
$meta_value = $user_var == 0 ? (int) get_current_user_id() : (int) $user_var;
}
// Based on billing email (Guest users)
else {
$meta_key = '_billing_email';
$meta_value = sanitize_email( $user_var );
}
$paid_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$product_ids = is_array( $product_ids ) ? implode(',', $product_ids) : $product_ids;
$line_meta_value = $product_ids != ( 0 || '' ) ? 'AND woim.meta_value IN ('.$product_ids.')' : 'AND woim.meta_value != 0';
// Count the number of products
$count = $wpdb->get_var( "
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
INNER JOIN {$wpdb->prefix}woocommerce_order_items AS woi ON p.ID = woi.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_statuses ) . "' )
AND pm.meta_key = '$meta_key'
AND pm.meta_value = '$meta_value'
AND woim.meta_key IN ( '_product_id', '_variation_id' ) $line_meta_value
" );
// Return true if count is higher than 0 (or false)
return $count > 0 ? true : false;
}
You can't get yet the order or order items… Instead you will check cart items.
Then here is your revisited code that handle all kind of customers and variable products as asked:
add_action('woocommerce_checkout_process', 'wc_action_checkout_process' );
function wc_action_checkout_process() {
$user_id = get_current_user_id();
// 1. - For logged in users
if( $user_id > 0 ) {
$user_var = $user_id;
}
// 2. - For others
else {
if ( isset($_POST['billing_email']) && ! empty($_POST['billing_email']) ) {
$email = sanitize_email( $_POST['billing_email'] );
$user = get_user_by( 'email', $email );
// 2.a - When a user exist for the billing email return an error notice
if ( is_a($user, 'WP_User') ) {
wc_add_notice( sprintf( __('The email "%s" is already registered for "%s" user name. Please login.'), $email, $user->user_login ), 'error' );
}
// 2.b - User doesn't exits (real "guest")
else {
$user_var = $email;
}
}
}
// Loop through cart items to check products
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( has_bought_items( $user_var, $cart_item['product_id'] ) ) {
wc_add_notice( sprintf( __('You have already purchased "%s" product before. You can only purchase it once.'), $cart_item['data']->get_name() ), 'error' );
}
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
I am trying to automatically add a product to an order in WooCommerce if the customer is a new one. I am using "Checking if customer has already bought something in WooCommerce" answer code to check if the customer has already completed an order and add the product to his current order. I think this should be just before the confirmation page but cannot make it work.
// Check if user has already bought
function has_bought( $user_id = 0 ) {
global $wpdb;
$customer_id = $user_id == 0 ? get_current_user_id() : $user_id;
$paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$results = $wpdb->get_col( "
SELECT p.ID FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '_customer_user'
AND pm.meta_value = $customer_id
" );
}
if( has_bought() )
function add_the_product()
{
$order_id = intval($_POST['order_id']);
$product_id = intval($_POST['product_id']);
//getting order Object
$order = wc_get_order($order_id);
// gettting the product
$product = wc_get_product($product_id);
$back_data = array('error' => 0, 'msg' => '');
if($order !== false AND $product !== false)
{
// Add the product to the order
$order->add_product($product, 1);
// Save the order data
$order->save();
You can't get the current user ID using get_current_user_id(). Instead you will better get the user ID from the existing order (where you want to add a product).
Update: I have updated my original function has_bought() to handle guest customer orders from their billing email. Now as you don't ask in your question to handle guests, I haven't implemented that on the following answer.
// Check if user has already bought
function has_bought( $value = 0 ) {
global $wpdb;
// Based on user ID (registered users)
if ( is_numeric( $value) ) {
$meta_key = '_customer_user';
$meta_value = $value == 0 ? (int) get_current_user_id() : (int) $value;
}
// Based on billing email (Guest users)
else {
$meta_key = '_billing_email';
$meta_value = sanitize_email( $value );
}
$paid_order_statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
print_pr($meta_key); print_pr($meta_value);
$count = $wpdb->get_var( $wpdb->prepare("
SELECT COUNT(p.ID) FROM {$wpdb->prefix}posts AS p
INNER JOIN {$wpdb->prefix}postmeta AS pm ON p.ID = pm.post_id
WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $paid_order_statuses ) . "' )
AND p.post_type LIKE 'shop_order'
AND pm.meta_key = '%s'
AND pm.meta_value = %s
", $meta_key, $meta_value ) );
// Return a boolean value based on orders count
return $count > 0 ? true : false;
}
Here is your revisited function code:
function add_the_product()
{
// Checking posted data
if( ! ( isset($_POST['order_id']) && isset($_POST['product_id']) ) )
return; // Exit
$order_id = intval($_POST['order_id']);
$product_id = intval($_POST['product_id']);
// Getting the order Object
$order = wc_get_order($order_id);
// Getting the product Object
$product = wc_get_product($product_id);
$back_data = array('error' => 0, 'msg' => '');
// Checking order and product objects
if( ! is_a($order, 'WC_Order') || ! is_a($product, 'WC_Product') )
return; // Exit
// Checking that customer has not already bought something
if( has_bought( $order->get_customer_id() ) )
return; // Exit
// Add the product to the order
$order->add_product($product, 1);
// Calculate totals and save order data
$order->calculate_totals(); // the save() method is included
}
It should better work now.