WC_Order->get_items() returns empty items - php

Using the hook 'woocommerce_order_status_completed' I can get $order_id then get the WC_Order object with $order = wc_get_order($order_id). But the following $logger->add("send-order-debug", json_encode($order->get_items()) returns empty item objects
{"257":{},"258":{},"259":{}}
I have no idea why this happening because I can see from the woocommerce orders page that there are actual items inside this order. Does anyone have any idea what is going on?
My end goal is to filter out products that are of category "Subscription" but this is impossible if I can't do $item->get_product_id
function send_order($order_id) {
$order = wc_get_order($order_id);
$logger = wc_get_logger();
$logger->add("send-order-debug", json_encode($order->get_items()));
}
Contents of order object:

Update 1:
You can't use json_encode() on $order->get_items() as you will always get something like "257":{} (where 257 is the item ID) for each order item. So json_encode() fails encoding each order item data located in the array of items, as order items are protected.
Now the only way to JSON encode order items is to unprotect each order item using the WC_Data method get_data() and set it back in the order items array.
This can be done in a compact way using array_map() with a custom function like:
add_action( 'woocommerce_order_status_completed', 'send_order', 10, 2 );
function send_order( $order_id, $order ) {
// Unprotect each order item in the array of order items
$order_items_data = array_map( function($item){ return $item->get_data(); }, $order->get_items() );
$logger = wc_get_logger();
$logger->add("send-order-debug", json_encode($order_items_data));
}
Now it works.
Original answer:
The WC_Order Object is already an included argument in woocommerce_order_status_completed hook, so in your code it should be:
add_action( 'woocommerce_order_status_completed', 'send_order', 10, 2 );
function send_order( $order_id, $order ) {
$order_items = $order->get_items();
}
That works… see this related answers threads…
So the problem is maybe related in the way you try to send the order items using:
$logger->add($TAG, json_encode($order->get_items()));
But it's not possible to help as your code is not testable: the $logger and $TAG variables are not defined in your code.
Now to target subscription products you will use something like:
// Loop through order items
foreach( $order->get_items() as $item ) {
$product = $item->get_product(); // get the WC_Product Object
// Targeting subscription products only
if ( in_array( $product->get_type(), ['subscription', 'subscription_variation'] ) ) {
// Do something
}
}

Related

How to get product categories for the current order item?

I am trying to get the product categories for the woocommerce order item at woocommerce_checkout_create_order_line_item hook.
I am able to successfully get the product_id (thanks to help I got here) but now trying to get the product's categories the array comes back empty trying $product->get_categories() and alternatively trying wc_get_product_category_list($product->get_id().
I can't figure out what I am doing wrong.
add_action('woocommerce_checkout_create_order_line_item',
'add_order_item_custom_meta', 10, 4 );
function add_order_item_custom_meta( $item, $cart_item_key, $cart_item, $order ) {
$product = $item->get_product(); // The WC_Product instance Object
$cat_names = $product->get_categories(); // one attempt
$cat_names = wc_get_product_category_list($product->get_id()); // another attempt
$arrlength = count($cat_names);
for($x = 0; $x<$arrlength; $x++) {
$cat_name = $cat_names[$x];
}
$item->update_meta_data( '_raw_product_id', $product->get_id() );
$item->update_meta_data( '_raw_product_name', $cat_name );
}
So the add_action and the function work. The "$product=..." works and I can use it below in the 2nd to the last line of code as $product->get_id() and the correct value is stored as metadata as desired.
So since $product->get_id() works I thought it logical that $product->get_categories() would work. But it returns a null array.
I then read somewhere that it was deprecated and I should use wc_get_product_category_list. So I tried that and also no luck.
So I am now stuck and can't figure out what is wrong with my code. Thanks for any help.
As you mentioned get_categories() method of WC_Product class is deprecated. Instead you can use get_category_ids() method. However this method returns product category IDs, and It seems that you need category names so we can get the names from WP_Term objects.
Final code would be something like this:
add_action('woocommerce_checkout_create_order_line_item', 'add_order_item_custom_meta', 10, 4 );
function add_order_item_custom_meta($item, $cart_item_key, $cart_item, $order)
{
$product = $item->get_product(); // The WC_Product instance Object
$cat_ids = $product->get_category_ids(); // returns an array of cat IDs
$cat_names = [];
foreach ( (array) $cat_ids as $cat_id) {
$cat_term = get_term_by('id', (int)$cat_id, 'product_cat');
if($cat_term){
$cat_names[] = $cat_term->name; // You may want to get slugs by $cat_term->slug
}
}
$item->update_meta_data( '_raw_product_id', $product->get_id() );
$item->update_meta_data( '_raw_product_name', $cat_names );
}
Note: foreach loop is the preferred way of doing this kind of stuff (instead of using a for loop).

Display total number of items in basket on cart and checkout, and email order reciept

I am trying to display the total number of items in the cart on various locations
1) Cart Page
2) Checkout Page
3) Email order receipt to client
4) Email order receipt to admin
I am using the following function to calc the total nos of items in the cart
// function to calc total number items in basket
function gh_custom_checkout_field( $checkout ) {
return WC()->cart->get_cart_contents_count();
}
Does anyone know how I can display the value on the locations above?
I have tried using my_custom_checkout_field() ?> but this just gave an internal server error.
1) For cart and checkout
You can use your function (without $checkout variable as it's not needed) as a shortcode:
function get_cart_count() {
return WC()->cart->get_cart_contents_count();
}
add_shortcode( 'cart_count', 'get_cart_count');
Code goes in function.php file of your active child theme (or active theme).
You will use it:
In the WordPress text editor: [cart_count]
In php code: echo do_shortcode( "[cart_count] ");
In mixed php/html code: <?php echo do_shortcode( "[cart_count] "); ?>
2) For Orders and email notifications
As the corresponding cart object doesn't exist anymore, you need to get the order items count from the WC_Order object (or the Order ID if you dont have it).
You can use this custom function, with a mandatory defined argument that can be the WC_Order object or the Order ID. If not the function will return nothing:
function get_order_items_count( $mixed ) {
if( is_object( $mixed ) ){
// It's the WC_Order object
$order = $order_mixed;
} elseif ( ! is_object( $mixed ) && is_numeric( $mixed ) ) {
// It's the order ID
$order = wc_get_order( $mixed ); // We get an instance of the WC_order object
} else {
// It's not defined as an order ID or an order object: we exit
return;
}
$count = 0
foreach( $order->get_items() as $item ){
// Count items
$count += (int) $item->get_quantity()
}
return $count;
}
You will use it always setting as argument for the function an existing dynamic variable $order_id or $order like
echo get_order_items_count( $order_id ); // Dynamic Order ID variable
Or
echo get_order_items_count( $order ); // Dynamic Order object variable

Get custom values of variations from order items in WooCommerce

I am trying to get the variation values selected by customer at checkout from an order (by id or otherwise) to map to variables $storage and $tier which are used to build an API url.
I have tried a variety of methods to get the data onto the variables but the API url is failing, which leads me to believe I do not have the indented values on my variables.
My current code is as follows (extract):
add_action( 'woocommerce_order_status_processing', 'my_function' );
function my_function( $order_id ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $key => $item ) {
$storage = get_post_meta( $key, 'attribute_addon-storage' );
$tier = get_post_meta( $key, 'attribute_subscription-type' );
Does anyone have any idea how I would grab the values of those two variables, addon-storage and subscription-type?
I can see the values I want to get in my database in the woocommerce_order_itemmeta table.
Can I get the values from there?
Edit:
So as per LoicTheAztec's advice, the right way to get the value of the data I see in the woocommerce_order_itemmeta table as $meta_key is:
add_action( 'woocommerce_order_status_processing', 'my_function', 10, 1 );
function my_function( $order_id ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $key => $item ) {
$storage = wc_get_order_item_meta( $key, 'addon-storage', true );
$tier = wc_get_order_item_meta( $key, 'subscription-type', true );
$anyMetaValue = wc_get_order_item_meta( $key, '$meta_key', true );
// Then I can create the variable I need in the the API url with:
$package = "$tier$storage";
Updated: Your are confusing order post meta data and order item meta data, which are very different things and located in different database tables.
Also your code is incomplete and you will not get anything using get_post_meta() with the $key (which is the item ID)...
The get_post_meta( $order_id, 'meta_key', true ) function will look for Order post meta data (not related to order items) and use Order ID as argument.
The wc_get_order_item_meta( $item_id, 'meta_key', true ) function will look for Order item meta data realated and will use the Item ID as argument (the $key in your code)…
So this function should need to be used in the foreach loop, where you get the order items data, instead.
Then 2 ways:
You should check in your database for the last order ID you have (via phpMyAdmin) in wp_postmeta and wp_woocommerce_order_itemmeta tables, to see where is located the data…
Or you can use the following code (just for testing) that will output the order items raw data where your data is located.
This raw data will be output in the shop, archive and product pages, only visible for logged in admins. You will need to define an order ID in it.
Here is this testing function
add_action( 'woocommerce_before_main_content', 'my_testing_order_function' );
function my_testing_order_function() {
// Define an Order ID
$order_id = 724;
// Only for admin user role
if( ! current_user_can('edit_products')) return;
$order = wc_get_order( $order_id ); // The order object
foreach ( $order->get_items() as $item_id => $item ) {
// Order item meta data Raw output
echo "<pre>ORDER ITEM META DATA - (Item_id $item_id):"; print_r($item->get_data()); echo'</pre>';
echo "<pre>ORDER ITEM META META DATA - (Item_id $item_id):"; print_r($item->get_meta_data()); echo'</pre>';
}
}
Now you have everything needed to locate and get the data. The code below should normally work for you, allowing you to get the data from your variations:
add_action( 'woocommerce_order_status_processing', 'my_function', 10, 1 );
function my_function( $order_id ) {
$order = wc_get_order( $order_id );
foreach ( $order->get_items() as $key => $item ) {
// get the data
$storage = wc_get_order_item_meta( $item_id, 'attribute_addon-storage', true );
$tier = wc_get_order_item_meta( $item_id, 'attribute_subscription-type', true );
}
}

Woocommerce get_item() function returns false

With WooCommerce 3+ introducing new API to fetch the order and it's details, a lot of things have changed and many things break as well.
Consider the following code in my plugin:
$order = wc_get_order($order_id);
$id= 27;
var_dump($order->get_item($id));
which gives me bool(false). I have checked the database and the order and the item does exist.
Also
var_dump($order) does return the entire order object with all the items.
So basically, only the function get_item does not seem to work.
The only explanation is that the ID you are using is not an item_id with a type "line_item"…
I have tried and it works normally as expected using WC_Abstract_Order get_item() method when the item_id is of type "line_item".
To get and check the correct "line_item" Item IDs from a defined Order ID, try:
// define an exiting order ID first
$order_id = 422;
$order = wc_get_order($order_id);
foreach($order->get_items() as $item_id => $item_values){
$item_ids_array[] = $item_id;
}
var_dump( $item_ids_array ); // will output all item IDs (of type "line_item") for this order
## ==> Then now you can try (to check get_item() method):
foreach( $item_ids_array as $item_id ){
var_dump( $order->get_item( $item_id ) ); // Will output each WC_Order_Item_Product Object …
}
This should clarify things.
As reference: How to get WooCommerce order details

Can not get the product details from order_id on the new order hook function

With WooCommerce, I have the following hook in my function.php after the new order is submitted:
add_action( 'woocommerce_new_order', 'create_job_openings');
function create_job_openings($order_id) {
$order = new WC_Order($order_id);
$items = $order->get_items();
foreach ($order->get_items() as $key => $item) {
$product_name = $item['name'];
var_dump($product_name);
}
}
The above code is not giving me any output i'e it is not entering inside the foreach loop that's why var_dump() not giving me any output, but if I mention the order_id specifically like create_job_openings($order_id=517) it works, even I tried echo $order_id before foreach loop, it is giving me the order_id, then why it is not entering the foreach loop?
note: when I try var_dump($items); before foreach loop its giving me
array(0) {
}
Why it is not able to get the product details even if there are products in it after new order is made?
Update 2 — The working solution (using an email notification hook)
The problem is when using email notification hook can fire an action 2 times, for example when a new order is made (notification for the Shop manager and notification for the customer).
You want to use this "New Order" event for Orders that are in "processing" status.
To avoid your action to be fired 2 times using New order notification WooCommerce event, we use 'customer_processing_order' instead of 'new_order' email ID (notification event).
Here we don't need to get the $order object, as we got it as an argument in this hooked function.
So here is your final functional code:
add_action( 'woocommerce_email_before_order_table', 'custom_action_on_completed_customer_email_notification', 10, 4 );
function custom_action_on_completed_customer_email_notification( $order, $sent_to_admin, $plain_text, $email ) {
if( 'customer_processing_order' == $email->id ){ // for processing order status customer notification…
foreach ($order->get_items() as $item_id => $item_values) {
$product_name = $item_values['name'];
echo $product_name;
break; // (optional) stop loop to first item
}
}
}
This is the validated and working answer to this question
Related Working Answers:
Sending an SMS for specific email notifications and order statuses
Avoid repetitive emails notification on some auto completed orders
Change the user role on purchase for specific products when order status is completed
Update 1 (hook alternative)
Trying using woocommerce_thankyou hook, that is fired on review order after order has been processed:
add_action( 'woocommerce_thankyou', 'create_job_openings', 10, 1 );
function create_job_openings( $order_id ) {
if ( ! $order_id )
return;
$order = wc_get_order( $order_id );
foreach ($order->get_items() as $item_id => $item_values) {
$product_name = $item_values['name'];
var_dump($product_name);
break; // (optional) stop loop to first item
}
}
(Not working for the OP)
You should try this instead wc_get_order() function this way and your code will be:
add_action( 'woocommerce_new_order', 'create_job_openings', 10, 1);
function create_job_openings($order_id) {
$order = wc_get_order( $order_id );
$order_items = $order->get_items();
foreach ($order_items as $item_id => $item_values) {
$product_name = $item_values['name'];
var_dump($product_name);
break; // (optional) stops on first item
}
}
You can have a look to
How to get WooCommerce order details where a lot of things are explained…
(Not working for the OP)
you can use save_post action when post is added in wordpress. for more visit: Link
function wc_order_add_action($post_id, $post, $update)
{
$post_type = get_post_type($post_id);
// If this isn't a 'shop_order' post, don't update it.
if ("shop_order" != $post_type) return;
$order = wc_get_order($post_id);
foreach($order -> get_items() as $key => $item)
{
$product_name = $item['name'];
var_dump($product_name);
}
}
add_action('save_post', 'wc_order_add_action');
To get the items and product details from the new order, you simply need to request two parameters for woocommerce_new_order. The second passed parameter will be the new order object. Example:
add_action( 'woocommerce_new_order', 'so41605256_new_order', 10, 2 );
function so41605256_new_order( $order_id, $order ) {
// Directly access items from $order variable, do not use wc_get_order...
foreach ($order->get_items() as $item_id => $item) {
//...
}
}
This works for both programmatically created and user created orders, regardless of WC email configuration.
Reference: create and update methods in WC_Order_Data_Store_CPT core class. Kudos to this post which helped me discover this.
Faced the same issue. I fixed it by changing the hook to woocommerce_checkout_order_processed. It works perfectly for both instant payment and cash on delivery since it runs as soon as an order is placed. Note that, this will run before payment completion. So, it doesn't have any relation with payment.
User clicks on proceed to checkout button and then this hook fires.

Categories