I am using WordPress and WooCommerce and have an SQL query to call meta tags for a specific product type.
function get_last_order_id_from_product( $product_id ) {
global $wpdb;
return $wpdb->get_col( $wpdb->prepare( "
SELECT oi.order_id
FROM {$wpdb->prefix}woocommerce_order_items as oi
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as oim
ON oi.order_item_id = oim.order_item_id
LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as oim2
ON oi.order_item_id = oim2.order_item_id
LEFT JOIN {$wpdb->posts} AS p
ON oi.order_id = p.ID
WHERE p.post_type = 'shop_order'
AND oi.order_item_type = 'line_item'
AND oim.meta_key = '_product_id'
AND oim.meta_value = '%d'
AND oim2.meta_key = 'Ticket Number'
ORDER BY SUBSTRING_INDEX(oim2.meta_value, ' ', 10) DESC
LIMIT 1
", $product_id ) );
}
The code above takes a product id and outputs the meta tag, my issue arose when I realized that when a user buys more than a single quantity the meta value it creates for the 'Ticket Number' key is '10 11' which mysql views as a single string. I tried breaking it apart using SUBSTRING_INDEX as shown above but it wont view any orders past the first.
I need it to view the numbers as individual numbers so if a user buys ten of the item it can recognise the last number as the highest (say 21 22 23 24) and the next user to buy this product the sql query will recognise the 24 from the string as the highest number and make the new items meta be 25.
Here is my function as well if it is of any use, it currently runs but is only working from the meta value of 1 however many of the product is bought:
add_action('woocommerce_order_status_processing', 'action_woocommerce_order_status_completed');
function action_woocommerce_order_status_completed($order_id) {
$order = new WC_Order($order_id);
$items = $order->get_items();
foreach ($items as $key => $value) {
$get_last_order = get_last_order_id_from_product( 10 );
if ($get_last_order == null){
$gen_id_1 = "0";
} else {
$get_order_id = implode($get_last_order);
$lastorder = wc_get_order($get_order_id);
$lastitem = $lastorder->get_items();
foreach ($lastitem as $key2 => $value2) {
$custom_thing = $value2->get_meta('Ticket Number');
}
$gen_ids = explode(' ', $custom_thing);
$gen_id_1 = end($gen_ids);
}
$qua = $value->get_quantity();
for ($x = 1; $x <= $qua; $x++) {
$gen_id_1++;
$gen_id_2 .= " $gen_id_1";
};
$value->add_meta_data( "Ticket Number", $gen_id_2);
$value->save();
}
}
There is a different way, much more easy and efficient to make that work.
When an order change to processing status:
First, for each order item, I increase the number of tickets sold (item quantity) as an index at the product level (saved/updated as custom product meta data).
Then for each order item, I generate from the related product index (before updating it) the tickets numbers based on the item quantity, that I save as custom order item meta data.
And to finish, for each order item, I update the related product "index" adding the quantity sold to the current "index" value.
The code (commented):
add_action( 'woocommerce_order_status_processing', 'action_woocommerce_order_status_processing', 10, 2 );
function action_woocommerce_order_status_processing( $order_id, $order ) {
// Loop through order items
foreach ($order->get_items() as $item_id => $item ) {
// Check that tickets numbers haven't been generated yet for this item
if( $item->get_meta( "_tickets_number") )
continue;
$product = $item->get_product(); // Get the WC_Produt Object
$quantity = (int) $item->get_quantity(); // Get item quantity
// Get last ticket sold index from product meta data
$now_index = (int) $product->get_meta('_tickets_sold');
$tickets_ids = array(); // Initializing
// Generate an array of the customer tickets Ids from the product registered index (last ticket ID)
for ($i = 1; $i <= $quantity; $i++) {
$tickets_ids[] = $now_index + $i;
};
// Save the tickets numbers as order item custom meta data
$item->update_meta_data( "Tickets numbers", implode(' ', $tickets_ids) ); // Displayed string of tickets numbers on customer orders and emails
$item->update_meta_data( "_tickets_number", $tickets_ids ); // (Optional) Array of the ticket numbers (Not displayed to the customer)
$item->save(); // Save item meta data
// Update the Ticket index for the product (custom meta data)
$product->update_meta_data('_tickets_sold', $now_index + $quantity );
$product->save(); // Save product data
}
$order->save(); // Save all order data
}
Code goes in functions.php file of your active child theme (active theme). Tested and works.
The _tickets_number hidden custom order item meta data is optional and allow you to get the array of tickets, instead of a string of tickets using: $item->get_meta('_tickets_number');
If you want a global ticket system (not at product level) you will use the following instead:
add_action( 'woocommerce_order_status_processing', 'action_woocommerce_order_status_processing', 10, 2 );
function action_woocommerce_order_status_processing( $order_id, $order ) {
// Loop through order items
foreach ($order->get_items() as $item_id => $item ) {
$now_index = (int) get_option( "wc_tickets_number_index"); // Get last ticket number sold (globally)
$quantity = (int) $item->get_quantity(); // Get item quantity
$tickets_ids = array(); // Initializing
// Generate an array of the customer tickets Ids from the tickets index
for ($i = 1; $i <= $quantity; $i++) {
$tickets_ids[] = $now_index + $i;
};
// Save the tickets numbers as order item custom meta data
$item->update_meta_data( "Tickets numbers", implode(' ', $tickets_ids) ); // Displayed string of tickets numbers on customer orders and emails
$item->update_meta_data( "_tickets_number", $tickets_ids ); // (Optional) Array of the ticket numbers (Not displayed to the customer)
$item->save(); // Save item meta data
// Update last ticket number sold (globally)
update_option( 'wc_tickets_number_index', $now_index + $quantity );
}
$order->save(); // Save all order data
}
Code goes in functions.php file of your active child theme (active theme). It should work.
Related
In WooCommerce, I would like to:
Get the unique values for a certain product attribute (From all my products), assuming all products have that particular attribute set as something.
Get the list of unique attribute values with the highest product price.
For Example:
Product 1:
Product-Attribute = Green
Price = $100
Product 2:
Product-Attribute = Red
Price = $50
Product 3:
Product-Attribute = Green
Price = $50
Expected result (an array):
Green : $100
Red : $50
Any ideas on how to code this so that it returns an array?
For Variable products and its variation attributes name and term value name with the highest variation price in an array, you will try the following custom function:
function get_variations_attributes_values_highest_price(){
global $wpdb;
// SQL query
$all_attributes = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}woocommerce_attribute_taxonomies");
$product_attributes = array();
foreach( $all_attributes as $value ){
$product_attributes = 'pa_'.$value->attribute_name;
}
//$pa_taxonomies = "'".implode("','", $product_attributes)."'";
// The 2nd SQL query
$query = $wpdb->get_results( "
SELECT p.ID, pm.meta_key as attr, pm.meta_value as term, pm2.meta_value as price
FROM {$wpdb->prefix}postmeta as pm
INNER JOIN {$wpdb->prefix}posts as p ON pm.post_id = p.ID
INNER JOIN {$wpdb->prefix}postmeta as pm2 ON pm.post_id = pm2.post_id
WHERE p.post_type IN ('product_variation','product')
AND p.post_status LIKE 'publish'
AND pm.meta_key LIKE 'attribute_pa_%'
AND pm2.meta_key LIKE '_price'
AND pm2.meta_value != ''
ORDER BY pm.meta_key ASC, pm.meta_value ASC, CAST(replace(pm2.meta_value, 'MDT ', '') AS UNSIGNED) ASC
" );
//print_pr($query);
$ordered_results = $results = array();
// Loop 1: Filtering and removing duplicate terms keeping highest price
foreach( $query as $values ){
if( !empty($values->price)){
$filter_key = $values->attr .' '.$values->term;
$ordered_results[$filter_key] = (object) array( 'attr' => $values->attr,
'term' => $values->term, 'price' => $values->price );
}
}
// Loop 2: Get the real name values, formatting data
foreach( $ordered_results as $result ){
$taxonomy = str_replace('attribute_' , '', $result->attr );
$attr_name = get_taxonomy( $taxonomy )->labels->singular_name; // Attribute name
$value_name = get_term_by( 'slug', $result->term, $taxonomy )->name; // Attribute value term name
$results[$attr_name.' - '.$value_name] = wc_price($result->price);
}
return $results;
}
Code goes in function.php file of the active child theme (or active theme).
Tested and works.
## --- USAGE --- ##
// RAW ARRAY OUTPUT
echo '<pre>'; print_r(get_variations_attributes_values_highest_price()); echo '</pre>';
You can make little changes easily to match your needs (the honey for TheBear)…
i need a simply function that allow me to add or replace empty sku code for variation in my published product with parent sku of product.
is there a way to obtain this work?
Updated: To replace empty SKU code for variation in your published product with the parent SKU (the variable product SKU), try this function that will only work for admin user role.
Do a database backup before runing this
To run this function, browse your shop page:
It will update 2500 empty variations SKU each time.
A message will display the count of updated variations.
Each time you will need to reload the page to process 2000 variations SKUs.,
When all variations SKUs will be updated, a message let you know that the job is done.
The code:
add_action('woocommerce_before_main_content', 'update_variations_sku', 100 );
function update_variations_sku(){
global $wpdb;
$results = $results2 = array();
$limit = 2500; // Number of variations to process
// The SQL query (get the variations with empty sku and their parent product ID)
$query = $wpdb->get_results( "
SELECT p.ID, p.post_parent
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_variation'
AND p.post_status LIKE 'publish'
AND pm.meta_key LIKE '_sku'
LIMIT $limit
" );
if( count($query) == 0 ){
echo "<p><pre>The job is finished, you can remove the code</pre></p>";
return; // exit
}
// Loop through variation Ids and get the parent variable product ID
foreach( $query as $value )
$results[$value->post_parent][] = $value->ID;
// Loop through variation Ids and set the parent sku in related empty variations skus
foreach($results as $parent_id => $variation_ids){
$parent_sku = get_post_meta( $parent_id, '_sku', true );
$count = 0;
foreach($variation_ids as $variation_id){
$count++;
update_post_meta( $variation_id, '_sku', $parent_sku.'-'.$count );
}
}
if( count($query) < $limit ){
echo "<p><pre>The job is finished, you can remove the code <br>$count variations SKUs have been updated</pre></p>";
} else {
echo "<p><pre>$count variations SKUs have been updated (continue reloading the page again)</pre></p>";
}
}
Code goes in function.php file of your active child theme (or theme).
Tested and works.
I am trying to get previous order number from the specified order number for example there are 5 orders we have.
1780
1781
1782
1784
1786
Now if i specifiy the order number
wc_custom_get_previous_order_number(1784)
I should get 1782. I have tried multiple solutions. For example :
add_filter( 'woocommerce_order_number', 'custom_woocommerce_order_number', 1, 2 );
function custom_woocommerce_order_number( $oldnumber, $order ) {
$lastorderid = $order->id-1; // here is i am trying to get previous order number.
// other code...
}
You could build your own function with a SQL query with 2 arguments:
$order_id is the starting order ID.
$limit is the number of orders Ids to get from.
The function code:
function get_orders_from( $order_id, $limit = 1 ){
global $wpdb;
// The SQL query
$results = $wpdb->get_col( "
SELECT ID
FROM {$wpdb->prefix}posts
WHERE post_type LIKE 'shop_order'
AND ID < $order_id
ORDER BY ID DESC
LIMIT $limit
" );
return $limit == 1 ? reset( $results ) : $results;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
USAGE (examples):
1) To get the previous order ID from the current $order_id you will use in your code:
$previous_order_id = get_orders_from( $order_id );
You will get directly the previous order ID…
2) To get the 3 previous orders IDs from order ID 1784:
// Get the array of orders_ids
$orders_ids = get_orders_from( 1784, 3 );
// Converting and displaying a string of IDs (coma separated)
echo implode( ', ', $orders_ids );
You will get: 1780, 1781, 1782
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>';
I have this code to save product in cart.
I need to save product and size - quantity x price.
At this momment is storing product name, size, quantity and price, but it display the same row.
If I add 'product1' size M quanity 1, and if I add 'product1' size S quanitity 1, in the cart I get 'product1' size S (the last size added) quantity 1 (but in the tquan I get 2 products).
So how can I make to have for each size of a product, another row.
$_SESSION['CART']['order_token'] = $form_token;
$arrCart['id'] = $pID;
$arrCart['quantity'] = $_SESSION['CART']['PRODUCT'][$pID]['size']['quantity'] + $_POST['quantity'];
$chkQuan = mysqli_fetch_assoc(mysqli_query($link, "SELECT quantity FROM table WHERE id = '".$pID."'"));
$total =$_SESSION['CART']['tquan']+$_POST['quantity'];
$arrCart['pret'] = $_POST['product_price'];
$arrCart['size'] = $_POST['size'];
$_SESSION['CART']['tquan'] = $total;
$_SESSION['CART']['PRODUCT'][$pID] = $arrCart;
And this part is for displaying cart
if(isset($_SESSION['CART']) && $_SESSION['CART']!='')
{
$endsumm = '';
$product_total_price = '';
foreach($_SESSION['CART']['PRODUCT'] as $pID=>$pArr)
{
$prod = mysqli_fetch_assoc(mysqli_query($link, "SELECT * FROM table WHERE id='".$pID."'"));
$product_total_price = $pArr['quantity']*$pArr['pret'];
$endsumm += $pArr['quantity']*$pArr['pret'];
Before inserting product in the cart, you should check that product is already in the cart or not.
Here in your case you should check for the productid as well as size is available in the cart or not. If any one or both condition is not true then insert that product into the cart.Otherwise update your cart.
$total =$_SESSION['CART']['tquan']+$_POST['quantity'];
$arrCart['pret']= $_POST['product_price'];
$arrCart['size']= $_POST['size'];
$_SESSION['CART']['tquan'] = $total;
$_SESSION['CART']['PRODUCT'][$pID] = $arrCart
if(!in_array($pID, array_map(function($id){return $id[$pID];}, $_SESSION['CART']))){
if(!in_array($_POST['size'], array_map(function($size){return $size['size'];}, $_SESSION['CART']))){
//Here insert the product into the cart as you were inserted in the question
}
}