WooCommerce - Get the number of orders by a given customer - php

I need to find that a particular customer has done business with that store previously.
To achieve that I need to find the number of orders by a given customer.
How can I achieve that?
I tried Googling, but did not find any solution.

Just drop in the user id and you'll get your total number of orders:
$numorders = wc_get_customer_order_count( $userid );
To go a step further for my own purposes, I use this code to get the number of a customer's non-cancelled orders since I don't want to count failed order attempts:
// Get TOTAL number of orders for customer
$numorders = wc_get_customer_order_count( $userid );
// Get CANCELLED orders for customer
$args = array(
'customer_id' => $userid,
'post_status' => 'cancelled',
'post_type' => 'shop_order',
'return' => 'ids',
);
$numorders_cancelled = 0;
$numorders_cancelled = count( wc_get_orders( $args ) ); // count the array of orders
// NON-CANCELLED equals TOTAL minus CANCELLED
$num_not_cancelled = $numorders - $numorders_cancelled;

Modifying #MarkPraschan Code, which works well for me without throwing any notice, as i got 2 notice about undefined variable $user_id and i'm not passing the code through a function. Using below code worked for me (which gets the number of order transaction minus canceled orders);
$current_user = wp_get_current_user();
$numorders = wc_get_customer_order_count( $current_user->ID );
// Get CANCELLED orders for customer
$args = array(
'customer_id' => $current_user->ID,
'post_status' => 'cancelled',
'post_type' => 'shop_order',
'return' => 'ids',
);
$numorders_cancelled = 0;
$numorders_cancelled = count( wc_get_orders( $args ) ); // count the array of orders
// NON-CANCELLED equals TOTAL minus CANCELLED
$num_not_cancelled = $numorders - $numorders_cancelled;
if you intend to display both completed and non completed orders, you will use the first two line of the above code, which is;
$current_user = wp_get_current_user();
$numorders = wc_get_customer_order_count( $current_user->ID );
Tested and working on;
WP = v4.9.9
WC = v3.5.3

I know this is an old question, but thought I'd share my code/info anyways.
The customer will be connected to the order via the postmeta key _customer_user in the wp_postmeta table.
You can lookup all orders with the status completed and processing for a specific user ID with the following query, where 279 is the user ID:
SELECT COUNT(p.ID)
FROM wp_posts AS p
INNER JOIN wp_postmeta AS m ON m.post_id = p.ID AND m.meta_key = '_customer_user' AND m.meta_value = 279
WHERE p.post_status IN ('wc-completed', 'wc-processing') AND p.post_type = 'shop_order'
When translated into PHP code that can be used on any WP installation simply by placing the code at the bottom of your theme functions.php file.
This example displays the total orders for a customer on the order page in the back-end of WordPress, directly below the customer selection/dropdown. For instance if you need to know if this is the first order a customer has placed/done you can display a message. Obviously you will want to change the order status filter to something that suits your needs. Doing a direct query like below is more efficient I believe.
// Hook into the order back-end WooCommerce > Orders > [Edit]
// The output will be placed under the dropdown to choose/connect a customer to the order
add_action('woocommerce_admin_order_data_after_order_details', 'f4d_customer_order_count');
function f4d_customer_order_count($order){
global $wpdb;
// Retrieve customer ID based on current order
$customerId = $order->get_customer_id();
// When the order was placed by a guest, just return
if($customerId===0) return;
// When the order is connected to a user/customer, query the total orders
// Database tables we will use in our query (also grab the table prefix)
$postsTable = $wpdb->prefix.'posts';
$postsMetaTable = $wpdb->prefix.'postmeta';
// Filter orders by specific status
$orderStatusFilter = array('wc-completed', 'wc-processing');
// Connect the array into a string that is compatible with our query (IN() query statement)
$orderStatusFilter = "'".implode("','", $orderStatusFilter)."'";
// Only get the single variable from the database
$totalOrders = $wpdb->get_var("
SELECT COUNT(p.ID)
FROM $postsTable AS p
INNER JOIN $postsMetaTable AS m ON m.post_id = p.ID AND m.meta_key = '_customer_user' AND m.meta_value = $customerId
WHERE p.post_status IN ($orderStatusFilter) AND p.post_type = 'shop_order'");
echo '<p class="form-field form-field-wide wc-customer-order-count">';
if($totalOrders===1){
// When this is the first order, display a message to our admin to give a first time offer
echo '<span style="color:white;background-color:red;padding:20px;">FIRST TIME OFFER</span>';
}else{
// Otherwise just display the total orders the customer has placed in the past
echo '<span>'.esc_html__( 'Total Orders', 'super-forms' ) . ': '.$totalOrders.'</span>';
}
echo '</p>';
}
If you need a list format of multiple customers/users, then you can use the $wpdb->get_results() instead of $wpdb->get_var() and loop over the results (table rows).

Found a way.
$args = [
'author' => 'id',
'post_status' => 'any',
'post_type' => 'shop_order'
];
$query = new WP_Query($args);
$orderCountByCustomer = $query->found_posts;

Related

Get products from last orders in WooCommerce

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

How to get coupons from email restrictions with efficiency in WooCommerce

I have the following loop for getting Woocommerce coupons on a page within the my account section of a customers dashboard.
Currently we have 10k+ coupons and just by performing this loop, it's a huge drain on resources and not very efficient causing time outs. Are there any obvious ways in which I can improve the efficiency of it?
Is there a way I can limit the loop to the only search for emails in the "Allowed emails" field (as each coupon is tied to an email address)?
<?php $smart_coupons = get_posts( array(
'posts_per_page' => -1,
'orderby' => 'name',
'order' => 'desc',
'post_type' => 'shop_coupon',
'post_status' => 'publish'
) );
if ( $smart_coupons ) {
foreach( $smart_coupons as $smart_coupon) {
$strcode = strtolower($smart_coupon->post_title);
$full_coupon = new WC_Coupon( $strcode ); ?>
<?php if($full_coupon->discount_type == "smart_coupon"){
$emails = $full_coupon->get_email_restrictions();
if (in_array($current_email, $emails)) {
if($full_coupon->usage_count < $full_coupon->usage_limit){ ?>
coupon content
<?php }
}
}
}
}
As email restrictions are in an array (so an indexed array in the database) it is not possible to get that from a meta query in your WP_Query for many technical reasons.
Now instead you can do a custom very light and effective SQL query to get the "smart" coupons that belong to an email, using the WPDB Class.
I have embedded this SQL query in the function below (where the $discount_type argument is already set buy default to "smart_coupon"):
function get_coupons_from_email( $current_email, $discount_type = 'smart_coupon' ) {
global $wpdb;
return $wpdb->get_col( $wpdb->prepare("
SELECT p.post_name
FROM {$wpdb->prefix}posts p
INNER JOIN {$wpdb->prefix}postmeta pm
ON p.ID = pm.post_id
INNER JOIN {$wpdb->prefix}postmeta pm2
ON p.ID = pm2.post_id
WHERE p.post_type = 'shop_coupon'
AND p.post_status = 'publish'
AND pm.meta_key = 'discount_type'
AND pm.meta_value = '%s'
AND pm2.meta_key = 'customer_email'
AND pm2.meta_value LIKE '%s'
ORDER BY p.post_name DESC
", $discount_type, '%'.$current_email.'%' ) );
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Now you can use it in your code as follows:
// Get smart coupons from email
$smart_coupon_codes = get_coupons_from_email( $current_email );
if ( count($smart_coupon_codes) > 0 ) {
// Loop through smart coupons code
foreach ( $smart_coupon_codes as $coupon_code ) {
$coupon = new WC_Coupon( $coupon_code ); // Get the WC_Coupon Object
if( $coupon->get_usage_count() < $coupon->get_usage_limit() ){
?>
<p>coupon content</p>
<?php
}
}
}
It should work smoothly now.

Display different notices based on Customer order count in Woocommerce

I'm looking for a lightweight way to count all customer orders with "Completed" status and based on the order count, display different messages using wc_print_notice.
Doing the count works fine, but I'm hoping that someone has a more lightweight way of doing it.
Displaying the first message works, but it does not show the 2nd message (when complete orders are 2 or more).
The idea is to extend this into a total of 10 different messages and to provide the customer with various discount codes throughout their shopping on the site.
Hopefully, by looking at the code, you understand what I'm trying to achieve. So, what I'm asking for is a combination of TWO things;
how do I make the elseif work so that it displays different messages and not all messages at the same time?
is there a more lightweight way of counting the order total?
Here's my code:
add_action( 'woocommerce_before_my_account', 'shop_loyalty_program' );
add_action( 'woocommerce_before_shop_loop', 'shop_loyalty_program' );
add_action( 'woocommerce_before_single_product_summary', 'shop_loyalty_program' );
function shop_loyalty_program() {
$customer = wp_get_current_user();
// count how many orders by the customer with "completed" status
// we only count the completed status since completed = paid
$customer_orders = get_posts( array(
'numberposts' => -1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_type' => 'shop_order',
'post_status' => 'wc-completed' // only "completed" as completed = paid
) );
// discount counter for our loyalty system
$first_order = 0;
$third_order = 2;
// messages to be shown depending on how many completed orders
// FIRST ORDER message when 0 orders exist
$first_order_message = sprintf( 'Hey %1$s 😀 For your first order, we\'ll give you a 10 percent discount and with that we say - WELCOME to our store!', $customer->display_name, $first_order );
// THIRD ORDER message when 2 orders exist
$third_order_message = sprintf( 'Hey %1$s 😀 We noticed you\'ve placed more than %2$s orders with us – thanks for being a loyal customer!', $customer->display_name, $third_order );
// discount control
if ( count( $customer_orders ) >= $first_order ) {
wc_print_notice( $first_order_message, 'notice' );
}
elseif ( count( $customer_orders ) >= $third_order ) {
wc_print_notice( $third_order_message, 'notice' );
}
}
The following revisited code should work as expected (with a much more light SQL query for orders count):
add_action( 'woocommerce_before_my_account', 'shop_loyalty_program' );
add_action( 'woocommerce_before_shop_loop', 'shop_loyalty_program' );
add_action( 'woocommerce_before_single_product_summary', 'shop_loyalty_program' );
function shop_loyalty_program() {
global $wpdb;
// Get current WP User Object instance
$user = wp_get_current_user();
// Only for logged in users
if( $user->ID == 0 ) return false;
// Count customer orders with "Completed" status (Paid)
$orders_count = $wpdb->get_var( "
SELECT COUNT(ID) FROM {$wpdb->prefix}posts as p
INNER JOIN {$wpdb->prefix}postmeta as pm ON p.ID = pm.post_id
WHERE p.post_status LIKE 'wc-completed' AND p.post_type LIKE 'shop_order'
AND pm.meta_key LIKE '_customer_user' AND pm.meta_value = {$user->ID}
" );
// FIRST ORDER Message (0 orders)
if ( $orders_count == 0 ) {
$message = sprintf( __("Hey %s 😀 For your first order, we'll give you a 10 %% discount and with that we say - WELCOME to our store!"), $user->display_name );
}
// TWO ORDERS AT LEAST Message (when 2 orders or more exist)
elseif ( $orders_count >= 2 ) {
$message = sprintf( __("Hey %s 😀 We noticed you've placed at least 2 orders with us – Thanks for being a loyal customer!"), $user->display_name );
}
// Display message
if ( isset($message) ) {
wc_print_notice( $message, 'notice' );
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and work.

Can't make MySQL query that also links categories/tags in WP

My goal is:
allow customers to make no more than 60 pre-orders per day in a given time slot.
The way I try to solve the problem is:
when on checkout page, get amount of previously made orders via php and mysql. Pass it to js array
user selects date, js compares it to a date in the array, checks the amount of products in a cart, adds
shows an error message if total exceeds maximum
It seems to work correctly, except it counts all the products, though I need to limit the result to 1 category only. I tried for few hours to link it but with no success.
The main problem is that category info is stored in a table wp_termmeta/wp_terms and I can't understand how is it linked to the product ID or other columns that I currently use in the query.
SQL query:
SELECT REPLACE(a.meta_value,'-','') AS Date, SUM(c.meta_value) AS Quantity
FROM postmeta a, woocommerce_order_items b, woocommerce_order_itemmeta c
WHERE a.post_id = b.order_id AND b.order_item_id = c.order_item_id
AND b.order_item_type = 'line_item' AND a.meta_key='_deliverydate'
AND c.meta_key = '_qty' AND a.meta_value='$today'
My MySQL / PHP for the context that works:
add_action( 'wp_enqueue_scripts', 'menu_scripts' );
function menu_scripts() {
// Register the script
wp_register_script( 'custom_script', get_stylesheet_directory_uri() . '/script.js');
wp_enqueue_script( 'custom_script' );
$currenthour = date("H");
$today = date("Y-m-d");
$cartcount = WC()->cart->get_cart_contents_count();
$passphp = array(
'hour' => $currenthour,
'today' => $today,
'incart' => $cartcount
);
for($i=0;$i<61;$i++){
global $wpdb;
$prefix = $wpdb->prefix;
$result = $wpdb->get_results("
SELECT REPLACE(a.meta_value,'-','') AS Date, SUM(c.meta_value) AS Quantity
FROM ".$prefix."postmeta a, ".$prefix."woocommerce_order_items b,
".$prefix."woocommerce_order_itemmeta c
WHERE a.post_id = b.order_id AND b.order_item_id = c.order_item_id
AND b.order_item_type = 'line_item' AND a.meta_key='_deliverydate'
AND c.meta_key = '_qty' AND a.meta_value='$today'");
foreach($result as $row) {
$row->Date = str_replace("‐",",",$row->Date);
$passphp['x'.$row->Date] = $row->Quantity;
}
$today = date('Y-m-d', strtotime($today. ' + 1 days'));
}
// Localize the script with new data
wp_localize_script( 'custom_script', 'passphp' , $passphp);
}
Maybe there is some better workaround to achieve the goal I can't think of?
off-topic: I did try to use LEFT JOIN instead of query you see, but I am new to MySQL syntax and for now, given up breaking the code that works while trying to figure it out.

Custom WP_Query order by post_meta and (author) user_meta

I am having an issue that i couldn't find on google and elsewhere.
To put problem in the context.
I am making a wordpress site where user can put their tutor listing for giving a tutorials, and i am implementing the system of evaluation of the tutors them self and their listening.
So I have tutor_listing post type and each tutor_listing has a 'post_score' meta value, and each tutor (user who puts his tutorials) has user_meta value called 'user_points'.
So I need a WP_Query that will get from the database all published tutor_listing but sorted by (post_score + user_points). Just to emphasis 'post_score' is a post_meta of the post_type tutor_listing and 'user_points' is user_meta of author of that post (tutor_listing).
Also i need that value to show in the loop.
I can get all the tutor_listing by
$args = array(
'post_type' => 'job_listing',
'post_status' => 'publish'
);
$query = new WP_Query( $args );
I can get the result from the custom wordpress sql query like this:
SELECT p1.ID, p1.post_author, p1.post_title, p1.post_date, (um1.meta_value + pm1.meta_value) AS total_score
FROM $wpdb->posts p1
JOIN $wpdb->users u1 ON (p1.post_author = u1.ID)
JOIN $wpdb->usermeta um1 ON (um1.user_id = u1.ID)
JOIN $wpdb->postmeta pm1 ON (pm1.post_id = p1.ID)
WHERE p1.post_type = 'tutor_listing'
AND p1.post_status = 'publish'
AND um1.meta_key = 'user_points'
AND pm1.meta_key = 'post_score'";
Can WP_Query provide such a functionality?
I need WP_Query to use its all extra functionality like pagination ect.
Or is there a way i could all the result done with the my custom sql give to WP_Query object?
Thank you

Categories