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.
Related
I am setting up a wordpress/woocommerce website and would like users to only purchase a certain number of items just once per 30 days due to the price of the items but in a certain catergory which is 'microwaves', I have found this code on here and it works really really well but for ALL products I really just want ot to work for 'microwaves' catergory but keep all the other functions.
`
// Utility conditional function (Check if user has purchased in the passed week)
function has_week_purshases( $user_id = 0 ){
global $wpdb;
$customer_id = $user_id > 0 ? $user_id : get_current_user_id();
$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
WHERE p.post_type LIKE 'shop_order'
AND pm.meta_key LIKE '_customer_user'
AND pm.meta_value = $customer_id
AND UNIX_TIMESTAMP(p.post_date) >= (UNIX_TIMESTAMP(NOW()) - (86400 * 30))
" );
return $count > 0 ? true : false;
}
// Cart and checkout validation
add_action( 'woocommerce_check_cart_items', 'conditionally_allowing_checkout' );
add_action( 'woocommerce_checkout_process', 'conditionally_allowing_checkout' );
function conditionally_allowing_checkout() {
if ( is_user_logged_in() && has_week_purshases() ) {
// Display an error notice when customer is not allowed yet
wc_add_notice( __("You are not allowed yet to make purchases"), 'error' );
}
}
`
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.
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 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
}
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;