I have some code which check the purchased date of a product in woocommerce.
Here is the code:
function _cmk_check_ordered_product( $id ) {
// Get All order of current user
$orders = get_posts( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_type' => wc_get_order_types( 'view-orders' ),
'post_status' => array_keys( wc_get_order_statuses() )
) );
if ( !$orders ) return false; // return if no order found
$all_ordered_product = array(); // store products ordered in an array
foreach ( $orders as $order => $data ) { // Loop through each order
$order_data = new WC_Order( $data->ID ); // create new object for each order
foreach ( $order_data->get_items() as $key => $item ) { // loop through each order item
// store in array with product ID as key and order date a value
$all_ordered_product[ $item['product_id'] ] = $data->post_date;
}
}
if ( isset( $all_ordered_product[ $id ] ) ) { // check if defined ID is found in array
return 'You purchased this product on '. date('M. d, Y', strtotime( $all_ordered_product[ $id ] ) );
} else {
return 'Product Never Purchased';
}
}
So it just check the purchase date. Now I need to check if the current date is less than 15 days from the purchase date and if it is I echo: "this product is expired".
I am thinking about using PHP Date for this, something like if ($date < strtotime('-15 days')). But I am stuck. What is the right direction? How can I solve it?
Any help is very appreciated.
Below is a light and effective SQL query where you will get what you are expecting (and more):
It will check that the product has been already bought
It will check that the purchase date has been made in the defined number of remaining days.
As a customer can purchase multiple times a same iteme (product), the function will take in account the last purchase item for a defined product ID.
The code:
function check_ordered_product( $product_id = 0, $days = 15, $user_id = 0 ) {
global $wpdb;
$customer_id = $user_id == 0 || $user_id == '' ? get_current_user_id() : $user_id;
$statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$time_n_days_ago = strtotime("-$days days");
$found = $valid = false;
// The query
$results = $wpdb->get_col( "
SELECT p.post_date 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-", $statuses ) . "' )
AND pm.meta_key = '_customer_user'
AND pm.meta_value = $customer_id
AND woim.meta_key IN ( '_product_id', '_variation_id' )
AND woim.meta_value = $product_id
" );
if( count($results) > 0 ){
$found = true;
foreach( $results as $post_date ){
if( strtotime($post_date) > $time_n_days_ago ){
$valid = true;
break;
}
}
}
if ( $found && ! $valid ) {
$text = __( "This product has expired.", "woocommerce" );
} elseif ( $found && $valid ) {
$text = __( "This product is still valid.", "woocommerce" );
} else {
$text = __( "You have not purchased this product yet.", "woocommerce" );
}
echo '<p>'.$text.'</p>';
}
Usage (example) for a logged in user on front end.
For product ID 37 on past 15 days you will use:
check_ordered_product( 37, 15 );
It will display:
If product has been purchased after the 15 past days: This product has expired.
If product has been purchased before the 15 past days: This product is still valid.
If product has not been purchased yet: You have not purchased this product yet.
Related similar answer: WooCommerce - Check if user purchased a product in the past 60 days
Related
I want to show the last 10 products a user has ordered in the past.
The problem is, that I don't know how many products where in the last order.
So I maybe need to get more than one order.
I want to display everything in the cart. To let users buy products again.
Later I want to exclude procuts which are in the current cart.
For the last order I found a snippet here: https://stackoverflow.com/a/71501798/1788961
// For logged in users only
if ( is_user_logged_in() ) {
// The current user ID
$user_id = get_current_user_id();
// Get the last WC_Order Object instance from current customer
$last_order = wc_get_customer_last_order( $user_id );
// NOT empty
if ( ! empty( $last_order ) ) {
// Initalize
$product_ids = array();
// Loop
foreach ( $last_order->get_items() as $item ) {
// Get product ID
$product_id = $item->get_product_id();
$product_ids[] = $product_id;
}
echo '<pre>';
var_dump( $product_ids );
echo '</pre>';
}
}
Is there a way to extend the function to more orders?
You can indeed use wc_get_orders(), where you will retrieve the orders based on arguments, such as the user ID and order by date.
Also note that a limit of 10 is used. This is because we can assume that every order contains at least 1 product. So if you want to collect 10 products the limit will never be more than 10 orders:
// Args
$args = array(
'customer_id' => get_current_user_id(),
'limit' => 10,
'orderby' => 'date',
'order' => 'DESC',
'status' => array( 'wc-on-hold','wc-processing','wc-completed' ),
);
// Get orders
$orders = wc_get_orders( $args );
// NOT empty
if ( ! empty ( $orders ) ) {
// Initalize
$product_ids = array();
foreach ( $orders as $order ) {
// Loop through order items
foreach ( $order->get_items() as $item ) {
// Get product ID
$product_id = $item->get_product_id();
// Limit of 10 products
if ( count( $product_ids ) < 10 ) {
// Push to array
$product_ids[] = $product_id;
} else {
break;
}
}
}
// The output
echo '<pre>', print_r( $product_ids, 1 ), '</pre>';
}
A much lighter and faster solution is to use a custom SQL query:
global $wpdb;
$current_user = wp_get_current_user();
$customer_email = $current_user->user_email;
$result = $wpdb->get_col( "
SELECT oim.meta_value 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 oi ON p.ID = oi.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS oim ON oi.order_item_id = oim.order_item_id
WHERE p.post_status IN ( 'wc-on-hold','wc-processing','wc-completed' )
AND pm.meta_key = '_billing_email'
AND pm.meta_value = '$customer_email'
AND oim.meta_key = '_product_id'
AND oim.meta_value != 0
ORDER BY p.post_date DESC LIMIT 0, 10
" );
// The raw output
echo '<pre>', print_r( $result, 1 ), '</pre>';
Whichever option you prefer. These can in any case be expanded with, for example, the variants, of variable products or on the basis of multiple order statuses. Excluding duplicate products, etc.. So it depends on your needs
There is a custom post named 'project' and has acf fields.
The tile of the each post is user name.
user name is from logged in users' email id.
Eg, post title is jeff (jeff#gmail.com).
When a user Jeff purchased a product, I need to automatically add ‘50’ in the ACF field ‘field_690d1eis5xx89’ of the Jeff post.
The code to check whether a user purchased a product or not, works well:
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
LIMIT 1
", $meta_key, $meta_value ) );
// Return a boolean value based on orders count
return $count > 0 ? true : false;
}
This code doesn't update 50 to the acf field of the logged in user's post after the user purchased a product.
I tried both title and post_title but it still doesn't update.
I'm not sure but, maybe due to the $post->ID?
if( has_bought() ){
$current_user = wp_get_current_user();
$current_user_id = $current_user->display_name;
// query to get a post with the user
$args = array(
'post_type' => 'project',
'posts_per_page' => -1,
'title' =>$current_user_id
); // end $args
$query = new WP_Query($args);
// if posts are returned, update field
if ($query->have_posts()) {
global $post;
update_field('field_690d1eis5xx89', '50', $post->ID);
} else {
return;
}
} // end if have_posts
I’m a beginner, would you please help me?
Thank you
As per the documentation the update_field function accepts three parameter i.e. $selector $value and $post_id but it looks like in you code you are using $current_user_id which I think might not work.
Try to add $post_id in place of $current_user_id
Check: https://www.advancedcustomfields.com/resources/update_field/
In WooCommerce would like to disable purchases from defined products ( B, C and D ) during a month if customer has purchased a product ( A or B ), so products B, C and D should be disabled for a month for that specific user only.
Note: In the code below I am using has_bought_items() a custom function from Check if a user has purchased specific products in WooCommerce answer.
My code attempt:
$product_ids = array( 255, 256 );
$args2 = array(
'customer_id' => get_current_user_id()
);
$orders = wc_get_orders( $args2 );
$orders = has_bought_items( get_current_user_id(), $product_ids );
if($orders){
add_filter('woocommerce_is_purchasable', 'disable_product', 10, 2 );
function disable_product( $is_purchasable, $product ) {
if( in_array( $product->get_id(), array( 256, 257, 258 ) ) ) {
return false;
}
return $is_purchasable;
}
}
So far I am able to disable the products for the user. But I don't how can I get date from the purchase and add a month to it. Any help will be great.
You need to customize a bit the code from has_bought_items() to get the highest "post date" from a customer order that has purchased product id 255 or 256 (for the current user ID).
Then using strtotime() function you will be able to disable purchases on other defined products during a month.
The code using a customized SQL query:
add_filter('woocommerce_is_purchasable', 'disable_specific_product_conditionally', 10, 2 );
function disable_specific_product_conditionally( $is_purchasable, $product ) {
$targeted_ids = array( 256, 257, 258 ); // Related targeted products to be disabled
if( in_array( $product->get_id(), $targeted_ids ) && is_user_logged_in() ) {
global $wpdb;
$product_ids = array( 255, 256 ); // Check orders for this products
// Count the number of products
$date = $wpdb->get_var("
SELECT p.post_date 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-", array_map( 'esc_sql', wc_get_is_paid_statuses() ) ) . "' )
AND pm.meta_key = '_customer_user'
AND pm.meta_value = '".get_current_user_id()."'
AND woim.meta_key IN ( '_product_id', '_variation_id' )
AND woim.meta_value IN (".implode(',', $product_ids).")
ORDER BY p.post_date DESC
");
// When a date is found we disable during a month purchases from this date
return ! empty($date) && strtotime('now') > strtotime( $date . ' + 1 month') ? false : $is_purchasable;
}
return $is_purchasable;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
I am trying to show a custom message to new all customer (not returning customer) in Thankyou page and I am using "Checking if customer has already bought something in WooCommerce" answer code.
The issue is that can't detect if a customer has bought before. For example if I use the current code in thank You page the new customer become older customer.
SO my question is: How can I check if a customer bought earlier any product not in the current order?
Here is my code attempt, that doesn't work as it should:
function has_bought_before( $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
" );
// Count number of orders and return a boolean value depending if higher than 0
return count( $results ) > 0 ? true : false;
}
add_filter('woocommerce_thankyou_order_received_text', 'woo_change_order_received_text', 10, 2 );
function woo_change_order_received_text( $str, $order ) {
if( has_bought_before() ){
$new_str = $str . ' Welcome Back Again.';
}else{
$new_str = $str . ' Welcome To our site You will get 10% discount on your next order.';
}
return $new_str;
}
On thankyou page you are targeting the next paid order, so you need to make a little change in the conditional function has_bought_before(), at the end, replacing the line:
return count( $results ) > 0 ? true : false;
by:
return count( $results ) > 1 ? true : false;
It should be working as you expect now.
May be rename this conditional function thankyou_has_bought_before().
I am new to WooCommerce and wanted to check if a user has purchased a specific product (product id # 514) within the past 60 days. This is a product I let people purchase monthly, so it will reoccur a lot. I wanted to just see if they purchased a recent one (that I consider active)
The way I was thinking of doing it now was:
Get all orders from a user
For each order, check if it occurred in the past 60 days
and and get all products for this order
For each product, see if the id is x
While I'm sure this will work, I have a funny feeling there is a really concise get_posts(apply_filters( query that will save me some looping time.
Would anyone be wiling to share a few ideas or a solution?
Thanks!
Here is a conditional function partially based on the built-in woocommerce function wc_customer_bought_product source code query:
There is an 3 optional argument $user_id, $product_ids and $days:
$user_id will allow you to specify a defined user ID (when is not used for current logged in user);
$product_ids (string or an array) will allow to specify defined product Ids to check
$dayswill allow you to specify the number of days to search for (or the period if you prefer)…
The code function:
function has_bought_multi( $user_id = 0, $product_ids = 0, $days = 0 ) {
global $wpdb;
$customer_id = $user_id == 0 || $user_id == '' ? get_current_user_id() : $user_id;
$statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$date = date('Y-m-d H:i:s', strtotime("-$days day") );
if ( is_array( $product_ids ) )
$product_ids = implode(',', $product_ids);
if ( $product_ids != ( 0 || '' ) )
$query_line = "AND woim.meta_value IN ($product_ids)";
else
$query_line = "AND woim.meta_value != 0";
// Count the number of products
$product_count_query = $wpdb->get_col( "
SELECT COUNT(woim.meta_value) 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-", $statuses ) . "' )
AND p.post_date > '$date'
AND pm.meta_key = '_customer_user'
AND pm.meta_value = $customer_id
AND woim.meta_key IN ( '_product_id', '_variation_id' )
$query_line
" );
// Set the count in a string
$count = reset($product_count_query);
// Return a boolean value if count is higher than 0
return $count > 0 ? true : false;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested on Woocommerce 3+ and works.
Similar answer: Check if a customer has purchased a specific products in WooCommerce
USAGE EXAMPLE (Customer is logged in):
Detecting if current user has bought your product id # 514 in past 60 days:
if( has_bought_multi( '', 514, 60 ) ){
echo "<p>Customer has bought product id # 514 in past 60 days</p>";
// do something
} else {
echo "<p>Customer <strong>HAS NOT</strong> bought product id # 514 in past 60 days</p>";
// do something else
}