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
Related
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.
After buying a product add this product order ID in this user meta. I can check the product bought status using this wc_customer_bought_product(). Now I need to get this product order id using UserID and product ID. How can I achieve this? My ultimate goal is after getting order id I will remove order by this function wp_delete_post()
$bronze = wc_customer_bought_product($current_user->user_email, $current_user->ID, 246014);
function get_customerorderid(){
global $post;
$order_id = $post->ID;
// Get an instance of the WC_Order object
$order = wc_get_order($order_id);
// Get the user ID from WC_Order methods
$user_id = $order->get_user_id(); // or $order->get_customer_id();
return $user_id;
}
get_customerorderid();
wp_delete_post(246014,true);
You can do that making a custom sql query embedded in a function, using WPDB Class, this way:
function get_completed_orders_for_user_from_product_id( $product_id, $user_id = 0 ) {
global $wpdb;
$order_status = 'wc-completed';
// If optional $user_id argument is not set, we use the current user ID
$customer_id = $user_id === 0 ? get_current_user_id() : $user_id;
// Return customer orders IDs containing the defined product ID
return $wpdb->get_col( $wpdb->prepare("
SELECT DISTINCT woi.order_id
FROM {$wpdb->prefix}posts p
INNER JOIN {$wpdb->prefix}postmeta pm
ON p.ID = pm.post_id
INNER JOIN {$wpdb->prefix}woocommerce_order_items woi
ON p.ID = woi.order_id
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta woim
ON woi.order_item_id = woi.order_item_id
WHERE p.post_status = '%s'
AND pm.meta_key = '_customer_user'
AND pm.meta_value = '%d'
AND woim.meta_key IN ( '_product_id', '_variation_id' )
AND woim.meta_value LIKE '%d'
ORDER BY woi.order_item_id DESC
", $order_status, $customer_id, $product_id ) );
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
USAGE with wp_delete_post() to remove related orders containing a specific product (id: 246014):
// Get all orders containing 246014 product ID for the current user
$orders_ids = get_completed_orders_for_user_from_product_id( 246014 );
// Checking that, the orders IDs array is not empty
if( count($orders_ids) > 0 ) {
// Loop through orders IDs
foreach ( $orders_ids as $order_id ) {
// Delete order post data
wp_delete_post( $order_id, true );
}
// Add the order(s) ID(s) in user meta (example)
update_user_meta( get_current_user_id(), 'item_246014', implode( ',', $orders_ids ) );
}
Related threads:
Get all Orders IDs from a product ID in Woocommerce
Changing WooCommerce processing orders status based on product stock quantity
I have the products ids as an array and I would like to get the list of order ids if the customer has purchased that product.
I have the customer purchased product ids with me. Somehow, I have to get the linked order id and cancel that order if customer purchases new product.
To check if a customer has purchased a product I am using the function has_bought_items() from this answer thread: Check if a customer has purchased a specific products in WooCommerce
May be it can be tweaked to get the desired output?
The following custom function made with a very light unique SQL query, will get all the Orders IDs from an array of products IDs (or a unique product ID) for a given customer.
Based on code from: Check if a customer has purchased a specific products in WooCommerce
function get_order_ids_from_bought_items( $product_ids = 0, $customer_id = 0 ) {
global $wpdb;
$customer_id = $customer_id == 0 || $customer_id == '' ? get_current_user_id() : $customer_id;
$statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
if ( is_array( $product_ids ) )
$product_ids = implode(',', $product_ids);
if ( $product_ids != ( 0 || '' ) )
$meta_query_line = "AND woim.meta_value IN ($product_ids)";
else
$meta_query_line = "AND woim.meta_value != 0";
// Get Orders IDs
$results = $wpdb->get_col( "
SELECT DISTINCT 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-", $statuses ) . "' )
AND pm.meta_key = '_customer_user'
AND pm.meta_value = $customer_id
AND woim.meta_key IN ( '_product_id', '_variation_id' )
$meta_query_line
" );
// Return an array of Order IDs or an empty array
return sizeof($results) > 0 ? $results : array();
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
USAGE Examples:
1) For the current logged in customer (and 2 product Ids in an array):
$product_ids = array(37,53);
$order_ids = get_order_ids_from_bought_items( $product_ids );
2) For a defined User ID and one product ID:
$product_id = 53;
$user_id = 72;
$order_ids = get_order_ids_from_bought_items( $product_id, $user_id );
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
How can I get an array with Order IDs by Product ID?
I mean receive all orders where specific product is presented.
I know how to do this by MySQL, but is there a way to do this by WP_Query function?
Updates:
2017 - SQL query changed to "SELECT DISTINCT" instead of "SELECT" to avoid duplicated Order IDs in the array (then no need of array_unique() to filter duplicates…).
2019 - Enabled product variation type support in the SQL Query
Then you can embed this in a custom function with $product_id as argument. You will have to set inside it, the order statuses that you are targeting.
So here is the function that will do the job:
function get_orders_ids_by_product_id( $product_id ) {
global $wpdb;
// Define HERE the orders status to include in <== <== <== <== <== <== <==
$orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";
# Get All defined statuses Orders IDs for a defined product ID (or variation ID)
return $wpdb->get_col( "
SELECT DISTINCT woi.order_id
FROM {$wpdb->prefix}woocommerce_order_itemmeta as woim,
{$wpdb->prefix}woocommerce_order_items as woi,
{$wpdb->prefix}posts as p
WHERE woi.order_item_id = woim.order_item_id
AND woi.order_id = p.ID
AND p.post_status IN ( $orders_statuses )
AND woim.meta_key IN ( '_product_id', '_variation_id' )
AND woim.meta_value LIKE '$product_id'
ORDER BY woi.order_item_id DESC"
);
}
This code goes in any php file.
This code is tested and works for WooCommerce version 2.5+, 2.6+ and 3+
USAGE EXAMPLES:
## This will display all orders containing this product ID in a coma separated string ##
// A defined product ID: 40
$product_id = 40;
// We get all the Orders for the given product ID in an arrray
$orders_ids_array = get_orders_ids_by_product_id( $product_id );
// We display the orders in a coma separated list
echo '<p>' . implode( ', ', $orders_ids_array ) . '</p>';
If you want your code to work in future WC updates, it is better to use functions provided by WC to get details from the DB, since WC often change the DB structure.
I'd try something like:
function get_orders_id_from_product_id($product_id, $args = array() ) {
//first get all the order ids
$query = new WC_Order_Query( $args );
$order_ids = $query->get_orders();
//iterate through order
$filtered_order_ids = array();
foreach ($order_ids as $order_id) {
$order = wc_get_order($order_id);
$order_items = $order->get_items();
//iterate through an order's items
foreach ($order_items as $item) {
//if one item has the product id, add it to the array and exit the loop
if ($item->get_product_id() == $product_id) {
array_push($filtered_order_ids, $order_id);
break;
}
}
}
return $filtered_order_ids;
}
Usage example:
$product_id = '2094';
// NOTE: With 'limit' => 10 you only search in the last 10 orders
$args = array(
'limit' => 10,
'orderby' => 'date',
'order' => 'DESC',
'return' => 'ids',
);
$filtered_order_ids = get_orders_id_from_product_id($product_id, $args);
print_r($filtered_order_ids);
I'd like to note that the above answer will return a duplicate of the order_id if the order has multiple items in it.
E.g.
If there was a product called "apples" with product_id=>1036
Customer puts "apples" 3 times in their cart and purchases it, creating order_id=>555
If I query product_id->1036, I will get array(555,555,555).
There's probably an SQL way of doing this which may be faster, (would appreciate anyone that could add to this), otherwise I used: array_unqiue() to merge the duplicates.
function retrieve_orders_ids_from_a_product_id( $product_id )
{
global $wpdb;
$table_posts = $wpdb->prefix . "posts";
$table_items = $wpdb->prefix . "woocommerce_order_items";
$table_itemmeta = $wpdb->prefix . "woocommerce_order_itemmeta";
// Define HERE the orders status to include in <== <== <== <== <== <== <==
$orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";
# Requesting All defined statuses Orders IDs for a defined product ID
$orders_ids = $wpdb->get_col( "
SELECT $table_items.order_id
FROM $table_itemmeta, $table_items, $table_posts
WHERE $table_items.order_item_id = $table_itemmeta.order_item_id
AND $table_items.order_id = $table_posts.ID
AND $table_posts.post_status IN ( $orders_statuses )
AND $table_itemmeta.meta_key LIKE '_product_id'
AND $table_itemmeta.meta_value LIKE '$product_id'
ORDER BY $table_items.order_item_id DESC"
);
// return an array of Orders IDs for the given product ID
$orders_ids = array_unique($orders_ids);
return $orders_ids;
}
Modified function to get specific user product ids
function retrieve_orders_ids_from_a_product_id( $product_id,$user_id )
{
global $wpdb;
$table_posts = $wpdb->prefix . "posts";
$table_postmeta = $wpdb->prefix . "postmeta";
$table_items = $wpdb->prefix . "woocommerce_order_items";
$table_itemmeta = $wpdb->prefix . "woocommerce_order_itemmeta";
// Define HERE the orders status to include in <== <== <== <== <== <== <==
$orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";
# Requesting All defined statuses Orders IDs for a defined product ID
$orders_ids = $wpdb->get_col( "
SELECT DISTINCT $table_items.order_id
FROM $table_itemmeta, $table_items, $table_posts , $table_postmeta
WHERE $table_items.order_item_id = $table_itemmeta.order_item_id
AND $table_items.order_id = $table_posts.ID
AND $table_posts.post_status IN ( $orders_statuses )
AND $table_postmeta.meta_key LIKE '_customer_user'
AND $table_postmeta.meta_value LIKE '$user_id '
AND $table_itemmeta.meta_key LIKE '_product_id'
AND $table_itemmeta.meta_value LIKE '$product_id'
ORDER BY $table_items.order_item_id DESC"
);
// return an array of Orders IDs for the given product ID
return $orders_ids;
}
Usage Example
## This will display all orders containing this product ID in a coma separated string ##
// A defined product ID: 40
$product_id = 40;
// Current User
$current_user = wp_get_current_user();
// We get all the Orders for the given product ID of current user in an arrray
$orders_ids_array = retrieve_orders_ids_from_a_product_id( $product_id, $current_user->ID );
// We display the orders in a coma separated list
echo '<p>' . implode( ', ', $orders_ids_array ) . '</p>';