In WwooCommerce, I am trying to add the ship to different address information in my admin email.
How can I check if the checkbox to ship to different address from checkout page is checked?
I tried to use:
$ship_to_different_address = get_option( 'woocommerce_ship_to_destination' ) === 'shipping' ? 1 : 0;
if($ship_to_different_address == 1):
//the additional email text here
endif;
But this seems not working. Any ideas?
May be the best way is to emulate it comparing for the order the billing and the shipping addresses. In most of all available related email notification hooks, the $order object is included as a parameter.
Here is an example with this function hooked in woocommerce_email_order_details action hook, that will display something different depending on that:
add_action( 'woocommerce_email_order_details', 'custom_content_email_order_details', 10, 4 );
function custom_content_email_order_details( $order, $sent_to_admin, $plain_text, $email ){
// Only for "New Order" and admin email notification
if ( 'new_order' != $email->id && ! $sent_to_admin ) return;
// Displaying something related
if( $order->get_billing_address_1() != $order->get_shipping_address_1() ) {
echo '<p style="color:red;">Different billing and shipping addresses<p>';
} else {
echo '<p style="color:green;">Same billing and shipping addresses<p>';
}
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested in WooCommerce 3.1+ and works
You can also use (with different priorities) any of the following hooks in this code:
- woocommerce_email_before_order_table
- woocommerce_email_after_order_table
- woocommerce_email_order_meta
- woocommerce_email_customer_details
ahhh.. we can just check if $_POST['ship_to_different_address'] is set..
I needed the same kind of address checking and programmed myself a really good working solution which respects custom billing/shipping fields:
/**
* Verify if the shipping address is different
*
* #param WC_Order $order
*
* #return bool
*/
function is_different_shipping_address( WC_Order $order ): bool {
$billing_address = $order->get_address();
$shipping_address = $order->get_address( 'shipping' );
if ( ! empty( $billing_address ) && ! empty( $shipping_address ) ) {
foreach ( $billing_address as $billing_address_key => $billing_address_value ) {
if ( isset( $shipping_address[ $billing_address_key ] ) ) {
$shipping_address_value = $shipping_address[ $billing_address_key ];
if ( ! empty( $billing_address_value ) && ! empty( $shipping_address_value ) && strcmp( $billing_address_value, $shipping_address_value ) !== 0 ) {
return true;
}
}
}
}
return false;
}
First I'm requesting both addresses from the order object. After that I'm looping over the billing address by default. Now I'm getting the same value from the shipping address (if set) and compare both values. If they are different, I'm returning true, else false.
Hoping it helps someone too.
Related
I am using Change Woocommerce Order Status based on Shipping Method code and it works beautifully for re-assigning my custom order status "awaiting-pickup" in WooCommerce based on shipping method string.
Here is my code:
add_action( 'woocommerce_thankyou', 'shipping_method_update_order_status', 10, 1 );
function shipping_method_update_order_status( $order_id ) {
if ( ! $order_id ) return;
$search = 'local_pickup'; // The needle to search in the shipping method ID
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// When "pickup" method is used, we change the order to "awaiting-pickup" status
if( strpos( $shipping_item->get_method_title(), $search ) !== false ){
$order->update_status('awaiting-pickup');
$order->save();
break;
}
}
}
I need help extending this to apply a few different rules based on other shipping methods like for 'free_shipping' and 'flat_rate' that I would like to reassign as 'awaiting-delivery' too.
$search = 'flat_rate' OR 'free_shipping';
$order->update_status('awaiting-delivery');
The shipping instances are structured like so:
'local_pickup:2'
'local_pickup:5'
'local_pickup:7'
'local_pickup:10'
'flat_rate:3'
'flat_rate:6'
'flat_rate:9'
'free_shipping:11'
'free_shipping:12'
'free_shipping:13'
Every time I create a new shipping zone extra shipping instances that are attached to that zone will have new numbers attached the method type. Ultimately I need something that use the following logic:
IF 'local_pickup' IN string
THEN $order->update_status('awaiting-pickup');
ELSEIF 'flat_rate' OR 'free_shipping' IN string
THEN $order->update_status('awaiting-delivery');
END
Update 2
As you are using the real shipping method Id in here, you don't need to search for a string. Then it will be more simple to make it work for multiple shipping methods Ids as follows:
add_action( 'woocommerce_thankyou', 'shipping_method_update_order_status', 10, 1 );
function shipping_method_update_order_status( $order_id ) {
if ( ! $order_id ) return;
// Here define your shipping methods Ids
$shipping_methods_ids_1 = array('local_pickup');
$shipping_methods_ids_2 = array('flat_rate', 'free_shipping');
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// Get the WC_Order_Item_Shipping object data
foreach($order->get_shipping_methods() as $shipping_item ){
// For testing to check the shipping method slug (uncomment the line below):
// echo '<pre>'. print_r( $shipping_item->get_method_id(), true ) . '</pre>';
// When "Local pickup" method is used, we change the order to "awaiting-pickup" status
if( in_array( $shipping_item->get_method_id(), $shipping_methods_ids_1 ) && ! $order->has_status('awaiting-pickup') ){
$order->update_status('awaiting-pickup'); // Already use internally save() method
break; // stop the loop
}
// When 'Flat rate' or 'Free shipping' methods are used, we change the order to "awaiting-delivery" status
elseif( in_array( $shipping_item->get_method_id(), $shipping_methods_ids_2 ) && ! $order->has_status('awaiting-delivery') ){
$order->update_status('awaiting-delivery'); // Already use internally save() method
break; // stop the loop
}
}
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
Assuming new orders are always given the status of PROCESSING, then this is how I would do it:
add_action('woocommerce_order_status_changed', 'jds_auto_change_status_by_shipping_method');
function jds_auto_change_status_by_shipping_method($order_id) {
// If the status of an order is changed to PROCESSING and the shipping method contains specific text then change the status.
if ( ! $order_id ) {
return;
}
global $product;
$order = wc_get_order( $order_id );
if ($order->data['status'] == 'processing') { // if order status is processing
$shipping_method = $order->get_shipping_method();
if ( strpos($shipping_method, 'local_pickup') !== false ) { // if shipping method CONTAINS
$order->update_status('awaiting-pickup'); // change status
} else if ( strpos($shipping_method, 'flat_rate') !== false ) { // if shipping method CONTAINS
$order->update_status('awaiting-delivery'); // change status
} else if ( strpos($shipping_method, 'free_shipping') !== false ) { // if shipping method CONTAINS
$order->update_status('awaiting-delivery'); // change status
}
}
}
I will note that the $shipping_method returns the Human Readable text that the customer sees on the website when they checkout so you may need to adjust local_pickup to match the exact text the customer sees (like 'Local Pickup').
Need a bulk status update to Orders to change from On Hold to Completed but without sending confirmation emails. However, still need to retain the email functionality. This would be a new custom bulk action in addition to the standard WooCommerce bulk action to update to Completed (which still would send the confirmation emails). I have the extra option added with no problem but can't find a method that will preclude the email notification or a way to temporarily disable email notifications (which doesn't sound like a good approach anyway).
So far code is as below. Everything is fine except the $order->update_status('completed') triggers the confirmation email.
Have tried using set_status() but that produces the same result (update_status calls set_status).
/*
* Custom bulk action in dropdown - Change status to completed without sending Confirmation Email
*/
add_filter( 'bulk_actions-edit-shop_order', 'register_bulk_action' ); // edit-shop_order is the screen ID of the orders page
function register_bulk_action( $bulk_actions ) {
$bulk_actions['complete_with_no_email'] = 'Change status to completed (no confirmation emails)';
return $bulk_actions;
}
/*
* Bulk action handler
*/
add_action( 'admin_action_complete_with_no_email', 'bulk_process_custom_status' ); // admin_action_{action name}
function bulk_process_custom_status() {
// if an array with order IDs is not presented, exit the function
if( !isset( $_REQUEST['post'] ) && !is_array( $_REQUEST['post'] ) )
return;
// New order emails
foreach( $_REQUEST['post'] as $order_id ) {
$order = new WC_Order( $order_id );
$order_note = 'Changed Status to Completed via bulk edit (no confirmation email)';
$order->update_status('completed', $order_note); //STILL SENDS EMAIL
}
// of course using add_query_arg() is not required, you can build your URL inline
$location = add_query_arg( array(
'post_type' => 'shop_order',
'changed' => count( $_REQUEST['post'] ), // number of changed orders
'ids' => join( $_REQUEST['post'], ',' ),
'marked_fulfilled_no_emails' => 1,
'post_status' => 'all'
), 'edit.php' );
wp_redirect( admin_url( $location ) );
exit;
}
/*
* Notices for Bulk Action
*/
add_action('admin_notices', 'custom_order_status_notices');
function custom_order_status_notices() {
global $pagenow, $typenow;
if( $typenow == 'shop_order'
&& $pagenow == 'edit.php'
&& isset( $_REQUEST['marked_fulfilled_no_emails'] )
&& $_REQUEST['marked_fulfilled_no_emails'] == 1
&& isset( $_REQUEST['changed'] ) ) {
$message = sprintf( _n( 'Order status changed.', '%s order statuses changed.', $_REQUEST['changed'] ), number_format_i18n( $_REQUEST['changed'] ) );
echo "<div class=\"updated\"><p>{$message}</p></div>";
}
}
Wanting to avoid triggering confirmation emails when using a custom bulk edit option from orders.
The best way I found to solve this was to add a flag using update_post_meta when looping through the selected orders flagging them to bypass the email confirmation.This, together with a function that hooks into woocommerce_email_recipient_customer_completed_order to return nothing for those that have been flagged and at the same time removing the flag so that all other functionality still triggers the email confirmation as normal afterwards. The relevant functions here:
Add the hook to add a new option for bulk editing:
/*
* Custom bulk action in dropdown - Change status to completed without sending Confirmation Email
*/
add_filter( 'bulk_actions-edit-shop_order', 'register_bulk_action' ); // edit-shop_order is the screen ID of the orders page
function register_bulk_action( $bulk_actions ) {
$bulk_actions['complete_with_no_email'] = 'Change status to completed (no confirmation emails)';
return $bulk_actions;
}
Handle this new option here, making sure to flag each of the selected posts with update_user_meta so we can avoid emailing them
/*
* Bulk action handler
* admin_action_{action name}
*/
add_action( 'admin_action_complete_with_no_email', 'bulk_process_complete_no_email' );
function bulk_process_complete_no_email() {
// if an array with order IDs is not presented, exit the function
if( !isset( $_REQUEST['post'] ) && !is_array( $_REQUEST['post'] ) )
return;
// Loop through selected posts
foreach( $_REQUEST['post'] as $order_id ) {
// Use this flag later to avoid sending emails via hook woocommerce_email_recipient_customer_completed_order
update_post_meta($order_id, 'bypass_email_confirmation', true);
$order = new WC_Order( $order_id );
$order_note = 'Changed Status to Completed via bulk edit (no confirmation email)';
$order->update_status('completed', $order_note);
}
// of course using add_query_arg() is not required, you can build your URL inline
$location = add_query_arg( array(
'post_type' => 'shop_order',
'changed' => count( $_REQUEST['post'] ), // number of changed orders
'ids' => join( $_REQUEST['post'], ',' ),
'marked_fulfilled_no_emails' => 1,
'post_status' => 'all'
), 'edit.php' );
wp_redirect( admin_url( $location ) );
exit;
}
This just adds a notice after the bulk action is completed
/*
* Notices for the Bulk Action
* This just adds the notice after action has completed
*/
add_action('admin_notices', 'custom_order_status_notices');
function custom_order_status_notices() {
global $pagenow, $typenow;
if( $typenow == 'shop_order'
&& $pagenow == 'edit.php'
&& isset( $_REQUEST['marked_fulfilled_no_emails'] )
&& $_REQUEST['marked_fulfilled_no_emails'] == 1
&& isset( $_REQUEST['changed'] ) ) {
$message = sprintf( _n( 'Order status changed.', '%s order statuses changed.', $_REQUEST['changed'] ), number_format_i18n( $_REQUEST['changed'] ) );
echo "<div class=\"updated\"><p>{$message}</p></div>";
}
}
Finally, hook into every time the site is going to send a confirmation email and prevent it from doing so when the flag is present. Importantly, remove the flag here as well so only our bulk action above will prevent the confirmation email.
/*
* Hook into every time the site is going to email customer and stop it if the flag is present
*/
add_filter('woocommerce_email_recipient_customer_completed_order','handle_email_recipient_completed_order', 10, 2);
function handle_email_recipient_completed_order($recipient, $order) {
//if notifications are disabled (e.g. by bulk_process_complete_no_email)
$notifications_disabled = get_post_meta($order->get_id(), 'bypass_email_confirmation', true);
if ($notifications_disabled) {
update_post_meta($order->get_id(), 'bypass_email_confirmation', false);
return '';
} else {
return $recipient;
}
}
Hopefully this helps someone, I spent quite a while trying to find an answer to this particular question and couldn't find it elsewhere.
I want to be able to change who receives the Woocommerce email notifications based on what role the user is when ordering.
For example, If the user is logged in as a Wholesale Customer then a different email will be notified.
I've found how to change it when a new order is complete using the woocommerce_email_recipient_new_order hook but i can't find any hooks related to the failed or cancelled notifications.
add_filter( 'woocommerce_email_recipient_new_order', 'sv_conditional_email_recipient', 10, 2 );
function sv_conditional_email_recipient( $recipient, $order ) {
// Bail on WC settings pages since the order object isn't yet set yet
// Not sure why this is even a thing, but shikata ga nai
$page = $_GET['page'] = isset( $_GET['page'] ) ? $_GET['page'] : '';
if ( 'wc-settings' === $page ) {
return $recipient;
}
// just in case
if ( ! $order instanceof WC_Order ) {
return $recipient;
}
if ( in_array( 'wholesale_customer', (array) $user->roles ) ) {
$recipient .= ', shaun#example.com';
return $recipient;
}
return $recipient;
}
add_filter( 'woocommerce_email_recipient_new_order', 'sv_conditional_email_recipient', 10, 2 );
Can anyone help please?
The hook you are already using is a composite hook: woocommerce_email_recipient_{$this->id}, where {$this->id} is the WC_Email ID like new_order. So you can set any email ID instead to make it work for the desired email notification.
Below You have the 3 hooks for "New Order", "Cancelled Order" and "Failed Order" that you can use for the same hooked function.
In your function, I have removed some unnecessary code and completed the code to get the customer data (the user roles) related to the order:
add_filter( 'woocommerce_email_recipient_new_order', 'user_role_conditional_email_recipient', 10, 2 );
add_filter( 'woocommerce_email_recipient_cancelled_order', 'user_role_conditional_email_recipient', 10, 2 );
add_filter( 'woocommerce_email_recipient_failed_order', 'user_role_conditional_email_recipient', 10, 2 );
function user_role_conditional_email_recipient( $recipient, $order ) {
if ( ! is_a( $order, 'WC_Order' ) ) return $recipient;
// Get the customer ID
$user_id = $order->get_user_id();
// Get the user data
$user_data = get_userdata( $user_id );
// Adding an additional recipient for a custom user role
if ( in_array( 'wholesale_customer', $user_data->roles ) )
$recipient .= ', shaun#example.com';
return $recipient;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works.
I'm looking to allow the Order Again functionality to all statuses. By default WooCommerce only allows orders with a status of COMPLETED this functionality. It seems to be a two step process as the first step requires the button being shown to the user, this is completed by editing this file:
wc-template-functions.php
With this snippit of code:
function woocommerce_order_again_button( $order ) {
//if ( ! $order || ! $order->has_status( 'completed' ) || ! is_user_logged_in() ) {
// Allow 'Order Again' at all times.
if ( ! $order || ! is_user_logged_in() ) {
return;
}
wc_get_template( 'order/order-again.php', array(
'order' => $order
) );
}
By commenting out the validation of the $order->has_status() method, I'm able to show the button on the page. However, when trying clicking the Order Again button, it still does a check before adding the items to the cart.
Can anyone tell me where this code is stored to do a preliminary check on the $order->has_status()?
You can simply add difference order status to filter woocommerce_valid_order_statuses_for_order_again.
add_filter( 'woocommerce_valid_order_statuses_for_order_again', 'add_order_again_status', 10, 1);
function add_order_again_status($array){
$array = array_merge($array, array('on-hold', 'processing', 'pending-payment', 'cancelled', 'refunded'));
return $array;
}
Since the OP's original question, WooCommerce has added a filter to these statuses. The function can be found in includes/wc-template-functions.php
https://docs.woocommerce.com/wp-content/images/wc-apidocs/function-woocommerce_order_again_button.html
/**
* Display an 'order again' button on the view order page.
*
* #param object $order
* #subpackage Orders
*/
function woocommerce_order_again_button( $order ) {
if ( ! $order || ! $order->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_order_again', array( 'completed' ) ) ) || ! is_user_logged_in() ) {
return;
}
wc_get_template( 'order/order-again.php', array(
'order' => $order,
) );
}
So in order to filter the statuses you could do something like this (wc_get_order_statuses() just returns all order statuses in this case; you can set the $statuses variable to an array of an statuses you would like):
add_filter('woocommerce_valid_order_statuses_for_order_again', function( $statuses ){
$statuses = wc_get_order_statuses();
return $statuses;
}, 10, 2);
By default Woocommerce saves the billing and shipping address on the checkout page.
I am searching for a way to prevent Woocommerce from saving the values in the shipping address. So all the fields in the shipping address should be empty.
In another thread I found a partial solution. It works great, but it also makes the billing address empty:
add_filter('woocommerce_checkout_get_value','__return_empty_string',10);
Is there a way to do this only for shipping address?
Big thx!
You could change the code like this...
add_filter( 'woocommerce_checkout_get_value', 'reigel_empty_checkout_shipping_fields', 10, 2 );
function reigel_empty_checkout_shipping_fields( $value, $input ) {
/*
Method 1
you can check the field if it has 'shipping_' on it...
if ( strpos( $input, 'shipping_' ) !== FALSE ) {
$value = '';
}
Method 2
put all the fields you want in an array...
*/
$shipping_fields = array(
//'shipping_first_name',
//'shipping_last_name',
'shipping_company',
'shipping_country',
'shipping_address_1',
'shipping_address_2',
'shipping_city',
'shipping_state',
'shipping_country',
'shipping_postcode'
);
if ( in_array( $input, $shipping_fields ) ) {
$value = '';
}
return $value;
}