Assign orders from guest customers to particular user - wordpress

I am using the following code to solve this but this is not working for orders from guest customers . However this is working fine for the orders belonging to some registered user/customer but not for the orders belonging to guest customers.
Solution credit to LoicTheAztec for answer
function cristmas_bulk_editing_orders(){
if(!is_admin()) return; // Will work only from Admin Backed.
else {
$order_id = 9458;
$new_customer_id = 479;
// Getting the postmeta customer ID for 'order' post-type
$customer_id = get_post_meta( $order_id, '_customer_user', true );
var_dump($customer_id);
// If it's an existing order and doesn't have already this user ID
// It update the customer ID
if( !empty($customer_id) && $new_customer_id != $customer_id )
update_post_meta($order_id, '_customer_user', $new_customer_id,0);
echo 'order updated';
}
}
cristmas_bulk_editing_orders();
ORIGINAL ISSUE
We imported the orders via woocommerce order export & import plugin from woocommerce team ..
But in the process something went wrong.. Most of the orders were not assigned any customer ..
So now when ever a new customer registers he/she is assigned 1 of these orders automatically ..
So basicallly all of them see 1 order in their recent orders which belongs to some other guest cusotmer , then they have all the information about other customer . their email etc..
So one option is I find out all the orders(with issues that is no customer assisned to them ) and I assign them to admin ..but this also have some issuses.....
SO is there any other option that these new registered users don't get old orders assigned..
Please help

May be you could try this other similar function based on SQL queries, that should do the trick. Before starting, make a database backup. I have add controls (or limitations) like :
A max date (in YYYY-MM-DD 00:00:00 date time format)
Order number range
So here is that code:
function easter_bulk_editing_orders(){
if( ! is_admin() ) return; // Will work only from Admin Backed.
else {
$processed_orders = array();
// Define the replacement user ID
$replacement_user_id = '2500';
// Define the DATE LIMIT for guest orders max date limit
$max_date = '2017-12-09 00:00:00';
// Define an order range
$order_id_min = '0';
$order_id_max = '100000';
global $wpdb;
// Get all guest orders below a defined max date
$old_guest_orders = $wpdb->get_results( "
SELECT pm.*, p.post_date
FROM {$wpdb->prefix}postmeta AS pm
LEFT JOIN {$wpdb->prefix}posts AS p ON pm.post_id = p.ID
WHERE pm.post_id BETWEEN $order_id_min AND $order_id_max
AND pm.meta_key LIKE '_customer_user'
AND ( pm.meta_value LIKE '0' OR pm.meta_value LIKE '' )
AND p.post_date <= '$max_date'
" );
foreach( $old_guest_orders as $guest_order ){
$meta_id = $guest_order->meta_id;
$wpdb->query( "
UPDATE {$wpdb->prefix}postmeta as pm
SET pm.meta_value = '$replacement_user_id'
WHERE pm.meta_id = '$meta_id'
" );
// Set each order ID in an array
$processed_orders[] = $guest_order->post_id;
}
// Testing ( raw output of processed orders IDS )
var_dump($processed_orders);
}
}
// Run the function
easter_bulk_editing_orders();
Code goes in function.php file of your active child theme (active theme or in any plugin file).
You have to use this function only once and to remove it afterwards (see below).
USAGE:
Once this code is pasted and saved on function.php file, display or reload any Admin page from backend within your browser.
Now you can comment the function this way and save:
// easter_bulk_editing_orders();
Check that the orders have been changed as you want, and remove all this code.
Code is tested and works.

There is error in your importing so try to re-import. Have you tried WP All Import Premium. It will import anything with drag and drop titles, categories, and meta fields. you can also assign new meta name to post meta while importing. If you need more help then share more details.

Related

Is there a reason the "Emails" tab in WooCommerce Settings breaks after the use of custom code?

I have used the following custom code.
add_filter( 'woocommerce_email_recipient_new_order', 'custom_wc_email_recipient_new_order', 10, 2 );
function custom_wc_email_recipient_new_order( $recipient, $order ) {
// Get the user ID of the order's creator
$user_id = $order->get_user_id();
// Get the user's role
$user = get_userdata( $user_id );
$user_role = $user->roles[0];
// Only send the email to the admin email if the customer has the specified user role
if ( $user_role == 'role1' ) {
return $recipient .= ', admin#website.com';
}
// Return the original recipient for all other user roles
return $recipient;
}
The code works perfectly fined and does what it is required to do, however, once the code has been used once (so once an order has been placed) if I try to access the "Emails" tab in "WooCommerce > Settings", I get a fatal error on the website and all I see is the below image.
Is there a reason for this, and if so, a way I can fix it?
EDIT - Added part of the error log:
[20-Dec-2022 17:12:00 UTC] WordPress database error Unknown column 'wp_postmeta.post_id' in 'where clause' for query SELECT meta_id FROM wp_8hkq051x71_postmeta,
(SELECT DISTINCT post_id FROM wp_8hkq051x71_postmeta
WHERE (meta_key = '_billing_country' OR meta_key='_shipping_country') AND meta_value='UA')
AS states_in_country
WHERE (meta_key='_billing_state' OR meta_key='_shipping_state')
AND meta_value='CV'
AND wp_postmeta.post_id = states_in_country.post_id
LIMIT 100 made by do_action_ref_array('action_scheduler_run_queue'), WP_Hook->do_action, WP_Hook->apply_filters, ActionScheduler_QueueRunner->run, ActionScheduler_QueueRunner->do_batch, ActionScheduler_Abstract_QueueRunner->process_action, ActionScheduler_Action->execute, do_action_ref_array('woocommerce_run_update_callback'), WP_Hook->do_action, WP_Hook->apply_filters, WC_Install::run_update_callback, wc_update_721_adjust_ukraine_states, Automattic\WooCommerce\Database\Migrations\MigrationHelper::migrate_country_states, Automattic\WooCommerce\Database\Migrations\MigrationHelper::migrate_country_states_for_orders
I have many lines like the one above, the only part that seems to change on each line is the:
AND meta_value='CV'
The CV changes to CH, CK, KS, etc.
Second Edit - Code Fix
The initial problem with the code was a WooCommerce bug. With that bug solved, I still couldn't modify email recipients in the WooCommerce Emails tab in the Settings. To be able to modify recipients there you need to use the following modified code.
add_filter( 'woocommerce_email_recipient_new_order', 'custom_wc_email_recipient_new_order', 10, 2 );
function custom_wc_email_recipient_new_order( $recipient, $order ) {
if ( $order ) {
// Get the user ID of the order's creator
$user_id = $order->get_user_id();
// Get the user's role
$user = get_userdata( $user_id );
$user_role = $user->roles[0];
// Only send the email to the admin email if the customer has the specified user role
if ( $user_role == 'role1' ) {
return $recipient .= ', admin#website.com';
}
// Return the original recipient for all other user roles
return $recipient;
}
return $recipient;
}
Thanks for posting the error log, now it all makes sense. It's a database error, it says wp_postmeta.post_id is missing. This happens because you are using a custom database prefix wp_8hkq051x71 and some code that generated this SQL wasn't using $wpdb->postmeta or $wpdb->prefix. Instead, there was a hardcoded wp_postmeta value and that table really doesn't exists in your database.
But how that happened?
WooCommerce team made some changes to country states, in this case Ukrainian states and they made a bug inside the update script.
They already fixed it 19 hours ago: https://github.com/woocommerce/woocommerce/commit/6a1a7d7e15f488064f872020d42b7a58a2980c38
So just update WooCommerce to the latest version and the bug will disappear.
Also, I would highly recommend you to use only stable releases of WooCommerce instead of latest dev versions.
Current stable version is 7.1.1 and current dev version with included fix for this issue is 7.2.1 (https://github.com/woocommerce/woocommerce/releases/tag/7.2.1)
The problem is not related to your custom_wc_email_recipient_new_order at all. It's just a coincidence that you noticed this bug after you added your change.

Make a filter for WooCommerce Order Search

In WP I have a WooCommerce order database of about 35k orders. The wp_postmeta table is about 2 million records because all order data is stored as metadata. Therefore the order search is quite slow.
I changed the default wc order search to only search specific metadata fields with this snippet:
function custom_woocommerce_shop_order_search_fields( $search_fields ) {
// error_log( 'currently searching in these order fields: ' . print_r($search_fields, true) );
unset( $search_fields );
$search_fields[] = '_order_key';
$search_fields[] = '_billing_company';
$search_fields[] = 'serialnumbers';
$search_fields[] = 'information';
// error_log( 'now only searching in these order fields: ' . print_r($search_fields, true) );
return $search_fields;
}
add_filter( 'woocommerce_shop_order_search_fields', 'custom_woocommerce_shop_order_search_fields' );
So the wc order search now only searches these 4 types of metadata, but is still quite slow because of the large size of some of them.
I'd like to extend the wc order search with a dropdown filter 'orderkey, company, serials, info' so the search only searches for the metadata key selected in the dropdown. So if I select 'serialnumbers' in the dropdown, the wc order search will only look for my searchterm in the metadata serialnumbers and not in the others. Not only for speed but also for usability.
I'm not really a programmer so help is much appreciated!
Thanx for the help! In Filter orders by specific meta fields in WooCommerce admin orders list a similar problem was addressed. A custom filter was made to filter orders by meta key. The last (optional) part was to add all the meta keys from the filter to the search query.
I changed that last part to the snippet below. I now look for the selected value and if it's set I unset the order search and then only add the selected meta key from the dropdown. In the case below the search only searches for the 1 selected meta key. If nothing is selected it uses the default search.
// Make only the selected custom meta field searchable from the admin order list search field
add_filter( 'woocommerce_shop_order_search_fields', 'shop_order_meta_search_fields', 10, 1 );
function shop_order_meta_search_fields( $meta_keys ){
$filter_id = 'filter_shop_order_by_meta';
if ( isset( $_GET[$filter_id] ) && ! empty($_GET[$filter_id]) ) {
unset( $meta_keys );
$meta_keys[] = $_GET[$filter_id];
}
return $meta_keys;
}
Note: the the above snippet only works in combination with the rest of the code from the other topic to make the filter that's used in this snippet.

How to get ACF data from WooCommerce customers for order export

On a Wordpress-Shop I use WooCommerce (WC) with Advenced-Custom-Fields (ACF)
and WP-All-Import (WPAI) + WP-All-Export (WPAE).
I added a ACF field CustomerNumber to the WC-Customer (which enhanced the WP-User).
On the WPAI-XML-Import I set the CustomerNumber with a value from a ERP.
So all customers have a unique CustomerNumber.
I now need to export the WC-Orders (to import them in the ERP again).
The Order-XML must include the CustomerNumber from the Customer belongs to the Order.
As I see, the other standard fields from the customer – like name and address – are copied automatically to the order (by WooCommerce itself).
My question is now: How I have to do this for the ACF’s?
Did I have to do this by code on my own? Adding the same AC-fields to the WC-Order and hook into the order checkout and copy the values from the customer to the order?
Or is there some kind of setup which do that and which I did not recognize?
Thx
I did not really found an answer.
My current solution is now as I described it in my question.
I added a new rule for these acf to also make them available on the orders.
Then I added a hook to new created orders, determine the acf from the user and copied the necessary values into the order acf.
function di_woocommerce_new_order( $order_id ) {
$order = new WC_Order( $order_id );
if ( !$order ) return;
$customer_id = $order->get_customer_id();
if ( !$customer_id ) return;
$customer = new WC_Customer($customer_id);
if ( !$customer ) return;
$customer_number = $customer->get_meta('customernumber');
if ( !$customer_number ) return;
// add customer number
$order->add_meta_data('customernumber', $customer_number);
// update order modified timestamp
$order->add_meta_data('order_last_updated', current_time( 'mysql' ));
$order->save_meta_data();
}
add_action( 'woocommerce_checkout_order_processed', 'di_woocommerce_new_order', 10, 1);

Regenerating WooCommerce Download Permissions on older orders

I am trying to add some download permissions to all previous orders via a script to do them in batch. The script seems to work fine expect for one thing. Here is the script…
function update_download_permissions(){
$orders = get_posts( array(
'post_type' => 'shop_order',
'post_status' => 'wc-completed',
'posts_per_page' => -1
) );
foreach ( $orders as $order ) {
wc_downloadable_product_permissions( $order->ID, true );
}
}
The problem is the wc_downloadable_product_permissions function is producing duplicate entries in the wp_woocommerce_downloadable_product_permissions table.
I tried to set the second argument to false (the default) but that resulted in no permissions being created.
Does anybody have any ideas as to why duplicate download permissions are being set?
Cheers!
I came across your question after digging through some of the WooCommerce source code, while attempting to add an item to an existing order and then regenerate the permissions.
The reason wc_downloadable_product_permissions() will create duplicate permission entries is because it does not check for any existing permissions. It simply inserts another entry into the permissions table for every item in the order, which is no good because this will then show up as another download in both the admin and user account frontend.
The second force parameter (poorly documented), is related to a boolean flag that indicates whether wc_downloadable_product_permissions() has run before. The boolean is set to true at the end of the function via the set_download_permissions_granted method. If force is true, it will ignore the boolean. If force is false, and the boolean is true, the function will return near the start.
I created this function which uses the same functions as used by the admin Order action "Regenerate download permissions":
/**
* Regenerate the WooCommerce download permissions for an order
* #param Integer $order_id
*/
function regen_woo_downloadable_product_permissions( $order_id ){
// Remove all existing download permissions for this order.
// This uses the same code as the "regenerate download permissions" action in the WP admin (https://github.com/woocommerce/woocommerce/blob/3.5.2/includes/admin/meta-boxes/class-wc-meta-box-order-actions.php#L129-L131)
// An instance of the download's Data Store (WC_Customer_Download_Data_Store) is created and
// uses its method to delete a download permission from the database by order ID.
$data_store = WC_Data_Store::load( 'customer-download' );
$data_store->delete_by_order_id( $order_id );
// Run WooCommerce's built in function to create the permissions for an order (https://docs.woocommerce.com/wc-apidocs/function-wc_downloadable_product_permissions.html)
// Setting the second "force" argument to true makes sure that this ignores the fact that permissions
// have already been generated on the order.
wc_downloadable_product_permissions( $order_id, true );
}
I found the best way to update order download is to hook into the save_post action hook and check if it's a product that's being updated
there you can get order ids by product id and update just orders that relate to that specific product.
it's more efficient
function get_orders_ids_by_product_id($product_id) {
global $wpdb;
$orders_statuses = "'wc-completed', 'wc-processing', 'wc-on-hold'";
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"
);
}
// if you don't add 3 as as 4th argument, this will not work as expected
add_action('save_post', 'prefix_on_post_update', 10, 3);
function prefix_on_post_update($post_id, $post, $update) {
if ($post->post_type == 'product') {
$orders_ids = get_orders_ids_by_product_id($post_id);
foreach ($orders_ids as $order_id) {
$data_store = WC_Data_Store::load('customer-download');
$data_store->delete_by_order_id($order_id);
wc_downloadable_product_permissions($order_id, true);
}
}
}

Woocommerce Get the order_id from one of the item_id?

I try to get the related order (the order id) from one of the line items id.
One case for example:
When editing an order, and deleting a line item (done by Ajax) the Woocommerce only provide the hook "woocommerce_before_delete_order_item" and passes $item_id only. (The woo function performs sql and not WP enviroment). I need the "belonging" order id to make some action!
My solution so far, is to loop through all orders and compare the Items ID, and break and return order ID when the match occurs.
This is way to slow, or "bulky clumsy", when keeping 10000 orders and more alive in the current shop.
This does NOT work:
wp_get_post_parent_id ( $item_id )
But Im hoping there is a similar call, or do I need to make a DB SQL search? Any answer involving mySQL, please make it "copy and paste ready". Im great at PHP but dont handle or never write my own SQL.
Thanks for any help! Below is my solution so far:
$order_id = my_wc_get_order_from_item_id($item_id);
function my_wc_get_order_from_item_id($id) {
$orders = get_posts('post_type=shop_order&numberposts=-1&post_status=publish');
foreach($orders as $obj) {
$order = new WC_Order($obj->ID);
if ( count( $order->get_items('line_item') ) > 0 ) {
foreach($order->get_items('line_item') as $item_id => $item ) {
if($item_id == $id) $return_value = $obj->ID;
if(isset($return_value)) break;
}
}
unset($order);
if(isset($return_value)) break;
}
if(isset($return_value)) return $return_value;
else return 0;
}
Your approach didn't work because order items are not posts in the wp_posts table, so they can't have a post_parent.
However, all is not lost and this was easier than I expected considering that my SQL isn't that strong. Looking at the woocommerce_order_item table in the database you can see that order_id and order_item_id are both there:
so I borrowed a little SQL statement from WooCommerce and modified it to find the order_id given a particular order_item_id row. Let me know how that works.
function so_38286531_get_order_item_order_id( $item_id ) {
global $wpdb;
$order_id = $wpdb->get_var( $wpdb->prepare(
"SELECT order_id FROM {$wpdb->prefix}woocommerce_order_items
WHERE order_item_id = %d",
$item_id
) );
return $order_id;
}
Use the built-in woocommerce function wc_get_order_id_by_order_item_id($item_get_id) to get order ID
// define the woocommerce_before_delete_order_item callback
function my_func_before_delete_order_item( $item_get_id ) {
$order_id = wc_get_order_id_by_order_item_id($item_get_id);
// do what you need...
};
// add the action
add_action( 'woocommerce_before_delete_order_item', 'my_func_before_delete_order_item', 10, 1 );

Resources