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.
Related
On WooCommerce products, I have a custom meta field called "amazon_price" and I wanna hide the products from showing on front-end if no price is set for this "amazon_price" custom field.
I used this code to do the filtering which works fine for simple products but it doesn't show variable products even if they have "amazon_price" field set , My guess the reason behind that issue is that this code maybe looking for "amazon_price" field value on the main post ID for the product so I think this could might be looking for the main post id for the variable product instead of looking for that field into the variable id, but even if my guess was true I still dont know how to fix that.
add_action( 'woocommerce_product_query', 'apm_products_meta_query' );
function apm_products_meta_query( $q ){
$meta_query = $q->get( 'meta_query' );
$meta_query[] = array(
'key' => 'amazon_price',
'value' => 0,
'compare' => '>'
);
$q->set( 'meta_query', $meta_query );
}
I don't think that is possible to make a WC meta query for product variations of variable products. But a with a custom light SQL query you can make everything at the same time:
// The custom SQL query
function custom_query_incl_ids() {
global $wpdb;
$meta_key = 'amazon_price';
return $wpdb->get_col( "
SELECT DISTINCT p.ID FROM {$wpdb->prefix}posts p
LEFT JOIN {$wpdb->prefix}postmeta as pm ON p.ID = pm.post_id
WHERE p.post_type = 'product' AND p.post_status = 'publish'
AND ( ( pm.meta_key = '$meta_key' AND pm.meta_value > 0 )
OR p.ID IN (
SELECT DISTINCT v.post_parent FROM {$wpdb->prefix}posts v
LEFT JOIN {$wpdb->prefix}postmeta as vm ON v.ID = vm.post_id
WHERE v.post_type = 'product_variation'
AND v.post_status = 'publish' AND v.post_parent > 0
AND vm.meta_key = '$meta_key' AND vm.meta_value > 0
) )
");
}
// The WC query
add_action( 'woocommerce_product_query', 'product_query_action_callback' );
function product_query_action_callback( $q ){
$q->set( 'post__in', (array) custom_query_incl_ids() );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
I'm trying to get the ranking the number of meta_key at specific custom post type.
But I'm not great at SQL so therefor not great with using $wpdb.
For example, if I write something like this,
<?php
$tarms = array( ‘sweet, sour’ );
echo get_count_ranking( $tarms );
?>
Then I would like to display rankings from comment("custom post of reply") of "custom post of fluits" with "term of sweet" and "term of sour" in order of "meta_key of count".
Here is my code:
function get_count_ranking( $tarms ){
global $wpdb;
$counts = $wpdb->get_results( $wpdb->prepare( "
SELECT p.post_author AS user_id, sum(m.meta_value) AS SumUser
FROM $wpdb->posts AS p, $wpdb->postmeta AS m
WHERE p.ID = m.post_ID
AND p.ID IN (
SELECT tr.object_id
FROM $wpdb->term_relationships AS tr, $wpdb->posts AS p, $wpdb->term_taxonomy AS tt
WHERE p.post_type = 'reply'
AND tt.term_id = %s
AND p.id = tr.object_id
AND tr.term_taxonomy_id = tt.term_taxonomy_id
)
AND p.post_status = 'publish'
AND m.meta_key = 'count'
GROUP BY p.post_author
ORDER BY m.meta_value DESC LIMIT 10
", $tarms ) );
$result = '';
foreach ( $counts as $count ) {
$result .= '<li><img>'.get_avatar($count->user_id, 30).'<span></span></li>';
}
return $result;
}
I am sorry that my English is so bad.
So I attach that image for your reference.
Thanks.
enter image description here
--
Updated code:
function get_count_ranking( $tarms ){
$customPostArg = array(
'posts_per_page' => 5,
'post_type' => 'fluits',
'tax_query' => array(
array(
'taxonomy' => 'taste-tag',
'field' => 'slug',
'terms' => $tarms
)
)
);
$array_with_post_ids = get_posts($customPostArg);
$argsp = array(
'post__in' => $array_with_post_ids
);
$commentsp = get_comments( $argsp );
$needed_data_array = array();
foreach ($comments as $key => $comment) {
$ranking = get_comment_meta($comment->ID, 'count', $return_single_value = true);
$author_id = $comment->user_id;
// make sure we have an author id
if($author_id) {
$needed_data_array[$author_id][] = $ranking;
}
}
}
$tarms = array( ‘sweet, sour’ );
echo get_count_ranking( $tarms );
I would not use $wpdb for this.
Also, it looks like you save rankings to a POST meta field, I would use a comment meta field.
Please NOTE, i'm not going to help you write everything, but here are
some pointers how I would accomplish this.
Register the custom post types with comment capability.
Extend the add comment to include a ranking setter. Save this ranking data in a comment_meta field. Now the comment and ranking meta are linked. When you remove the comment, the ranking meta data is also removed from the DB.
Now post comments and rankings are saved.
Collecting data
WP has a get_comments() function, this function accepts many arguments. Sadly, I miss an argument to get comments from posts with a certain taxonomy. So we have to collect all the posts first:
Collect all the posts with 'sweet' and/or 'sour' taxonomy, use get_posts().
Build an array with post id's.
Use get_comments() to get all comments connected to the posts.
Example:
$args = array(
'post__in' => $array_with_post_ids,
);
$comments = get_comments( $args );
Now you have all the comments you need for creating overviews, I would loop (iterate) through them and build an array with author_names and their rankings.
Example:
$needed_data_array = array();
foreach ($comments as $key => $comment) {
$ranking = get_comment_meta($comment->ID, 'ranking_meta_key', $return_single_value = true);
$author_id = $comment->user_id;
// make sure we have an author id
if($author_id) {
$needed_data_array[$author_id][] = $ranking;
}
}
//
// Now the $needed_data_array holds all authors
// and their post rankings, you can count them
// to get ranking totals for each comment-author.
//
I'm trying to figure out a function which get current user total number of purchased items (not total sum but items) across as all placed orders. So far I have found this (which doesn't work) - but again this function should get total sum and not items. Been trying to edit it to work but no success so far.
public function get_customer_total_order() {
$customer_orders = get_posts( array(
'numberposts' => - 1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_type' => array( 'shop_order' ),
'post_status' => array( 'wc-completed' )
) );
$total = 0;
foreach ( $customer_orders as $customer_order ) {
$order = wc_get_order( $customer_order );
$total += $order->get_total();
}
return $total;
}
Any ideas?
Updated (Taking in account the item quantity)
The following very lightweight function will get the total purchased items count by a customer:
function get_user_total_purchased_items( $user_id = 0 ){
global $wpdb;
$customer_id = $user_id === 0 ? get_current_user_id() : (int) $user_id;
return (int) $wpdb->get_var( "
SELECT SUM(woim.meta_value)
FROM {$wpdb->prefix}woocommerce_order_items AS woi
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
INNER JOIN {$wpdb->prefix}posts as p ON woi.order_id = p.ID
INNER JOIN {$wpdb->prefix}postmeta as pm ON woi.order_id = pm.post_id
WHERE woi.order_item_type = 'line_item'
AND p.post_type LIKE 'shop_order'
AND p.post_status IN ('wc-completed')
AND pm.meta_key LIKE '_customer_user'
AND pm.meta_value LIKE '$customer_id'
AND woim.meta_key LIKE '_qty'
" );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
USAGE Example
1) Display the current user total purchased items count:
<?php echo '<p>Total purchased items: ' . get_user_total_purchased_items() . '</p>'; ?>
2) Display the total purchased items count for a given user ID:
// Here the user ID is 105
<?php echo '<p>Total purchased items: ' . get_user_total_purchased_items(105) . '</p>'; ?>
function get_user_total_purchased_items_by_email( $email ){
global $wpdb;
if (empty($email)) {
return;
}
return (int) $wpdb->get_var( "
SELECT SUM(woim.meta_value)
FROM {$wpdb->prefix}woocommerce_order_items AS woi
INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS woim ON woi.order_item_id = woim.order_item_id
INNER JOIN {$wpdb->prefix}posts as p ON woi.order_id = p.ID
INNER JOIN {$wpdb->prefix}postmeta as pm ON woi.order_id = pm.post_id
WHERE woi.order_item_type = 'line_item'
AND p.post_type LIKE 'shop_order'
AND p.post_status IN ('wc-completed')
AND pm.meta_key LIKE '_customer_user'
AND pm.meta_value LIKE '$email'
AND woim.meta_key LIKE '_qty'
" );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Usage Example
<?php echo '<p>Total purchased items: ' . get_user_total_purchased_items_by_email('youremail#mail.com') . '</p>'; ?>
Try this below code
global $wpdb;
$customer_orders = get_posts( array(
'numberposts' => - 1,
'meta_key' => '_customer_user',
'meta_value' => get_current_user_id(),
'post_type' => array( 'shop_order' ),
'post_status' => array( 'wc-completed' )
) );
$customer_orders = json_decode(json_encode($customer_orders),true);
$total_product_count = 0;
foreach ( $customer_orders as $customer_order_data ) {
$Order_ID=$customer_order_data['ID'];
$Select_Order_Details = $wpdb->get_results( "SELECT COUNT(order_item_id) AS total_product_count FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_type='line_item' and order_id = $Order_ID");
$Select_Order_Details = json_decode(json_encode($Select_Order_Details),true);
foreach($Select_Order_Details as $Product_Count)
{
$total_product_count=$total_product_count+$Product_Count['total_product_count'];
}
}
echo ($total_product_count); exit;
I have a site where products are considered trade/deal. Therefore, when someone take a trade (buy a product), it become out of stock.
What would be the PHP snippet to display the remaining numbers of product currently available (basically In Stock) ?
ex: Hurry Up! Only 10 trades (woocommerce -> products) available!
Thanks in advance!
I tried the code provided :
function fp2() {
global $wpdb;
$stock = get_post_meta( $post->ID, '_stock', true );
echo '<span style="color:#fff;text-align:center;font-size:12px">Remaining Trade:' . $stock;
}
add_shortcode('fp7', 'fp2');
Updated (2021)
Here is a custom function with a SQL query that will return the products "instock" count:
function get_instock_products_count(){
global $wpdb;
// The SQL query
$result = $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 '%product%'
AND p.post_status = 'publish'
AND pm.meta_key = '_stock_status'
AND pm.meta_value = 'instock'
" );
return reset($result);
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and working
Usage example (in any php file):
$count = get_instock_products_count();
$message = sprintf( __( 'Hurry Up! Only %s remaining trades' ), $count );
echo '<div class="woocommerce-message">'.$message.'</div>';
will display something like:
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;