I need help with a custom email hook for woocommerce.
I am trying to send a different email depending on product ID whenever a product is completed.
My code, which is not working, is as follows:
/**************
DIFFERENT MESSAGES FOR DIFFERENT PRODUCTS
****************/
//hook our function to the new order email
add_action('woocommerce_email_order_details', 'uiwc_email_order_details_products', 1, 4);
function uiwc_email_order_details_products($order, $admin, $plain, $email) {
$status = $order->get_status();
// checking if it's the order status we want
if ( $status == "completed" ) {
$items = $order->get_items();
if ( $item['product_id'] == "3181") {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>To get started, please follow this link to complete the Policies form.<br><br>This is a really important first step, and only takes about 5 minutes. After completeing the Policies form, you will receive additional instructions on next steps.<br><br>Congratulations! Let your journey begin.<br><br>', 'uiwc' );
}
elseif ( $item['product_id'] == "3223") {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>Differnet product so differenct email....<br><br>', 'uiwc' );
}
}
}
Any suggestions is greatly appreciated
There is some mistakes in your code, instead try the following
//hook our function to the new order email
add_action( 'woocommerce_email_order_details', 'custom_email_order_details', 4, 4 );
function custom_email_order_details( $order, $admin, $plain, $email ) {
$domain = 'woocommerce';
// checking if it's the order status we want
if ( $order->has_status('completed') ) {
foreach( $order->get_items() as $item ){
if ( $item->get_product_id() == '3181' ) {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>To get started, please follow this link to complete the Policies form.<br><br>This is a really important first step, and only takes about 5 minutes. After completeing the Policies form, you will receive additional instructions on next steps.<br><br>Congratulations! Let your journey begin.<br><br>', $domain );
break;
}
elseif ( $item->get_product_id() == '3223' ) {
echo __( '<strong>IMPORTANT - NEXT STEP:</strong><br>Differnet product so differenct email....<br><br>', $domain );
break;
}
}
}
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
Related
I am trying to, based on shipping and if / if not the order note is empty, to add information to the customer complete order email.
Two different messages based on if the order notes field is filled in or not. I've placed test orders but nothing shows up.
This is the code I am trying to get to work:
add_action( 'woocommerce_email_order_details', 'local_pickup_order_instructions', 10, 4 );
function local_pickup_order_instructions( $order, $sent_to_admin, $plain_text, $email ) {
if ( 'customer_completed_order' != $email->id ) return;
foreach( $order->get_items('shipping') as $shipping_item ) {
$shipping_rate_id = $shipping_item->get_method_id();
$method_array = explode(':', $shipping_rate_id );
$shipping_method_id = reset($method_array);
if ('local_pickup' == $shipping_method_id && empty($_POST['order_comments'])){ ?>
<div style="">Your instructions text here</div>
<?php
break;
}
else {
if ('local_pickup' == $shipping_method_id && !empty($_POST['order_comments'] ) ) { ?>
<div style="">Your instructions text here</div>
<?php
}
}
}
}
There are some mistakes in your code… To display a different custom text when shipping method is "Local Pickup" if there is (or not) a customer note, use the following simplified and revisited code:
add_action( 'woocommerce_email_order_details', 'local_pickup_order_instructions', 10, 4 );
function local_pickup_order_instructions( $order, $sent_to_admin, $plain_text, $email ) {
if ( $email->id === 'customer_completed_order' ) {
$shipping_items = $order->get_items('shipping');
$shipping_item = reset($shipping_items); // Get first shipping item
$customer_note = $order->get_customer_note(); // Get customer note
// Targeting Local pickup shipping methods
if ( strpos( $shipping_item->get_method_id(), 'local_pickup' ) !== false ) {
if ( empty($customer_note) ) {
echo '<div style="color:red;">'.__("Instructions text here… (No customer note)").'</div>'; // Empty order note
} else {
echo '<div style="color:green;">'.__("Instructions text here… (has a customer note)").'</div>'; // Filled order note
}
}
}
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
I want to change the page titles (entry titles) for each page in the WooCommerce My Account section so that they relate to the actual page you are on rather than outputting a generic "My Account" on each page.
I have looked around and seen this solution in several places:
function wpb_woo_endpoint_title( $title, $id ) {
if ( is_wc_endpoint_url( 'downloads' ) && in_the_loop() ) { // add your endpoint urls
$title = "Download MP3s"; // change your entry-title
}
elseif ( is_wc_endpoint_url( 'orders' ) && in_the_loop() ) {
$title = "My Orders";
}
elseif ( is_wc_endpoint_url( 'edit-account' ) && in_the_loop() ) {
$title = "Change My Details";
}
return $title;
}
add_filter( 'the_title', 'wpb_woo_endpoint_title', 10, 2 );
This does not work unless you remove the in_the_loop check, which obviously isn't ideal as then it ends up changing other things on the page too.
Then I found this answer, as an example on how to change the title for the "Account Details" page:
add_filter( 'woocommerce_endpoint_edit-account_title', 'change_my_account_edit_account_title', 10, 2 );
function change_my_account_edit_account_title( $title, $endpoint ) {
$title = __( "Edit your account details", "woocommerce" );
return $title;
}
But this didn't work, it didn't even seem to go into the function at all.
Is there a way to do this that actually works?
Changing main my account page title and My account page title sub sections:
1) For "My Account: dashbord (main page):
To change the main "My account" page title, you just need to change the title of the page in backend directly because it's not an endpoint, and to check in Settings > Advanced section that the page (with the renamed title) is still assigned to "My account page".
2) For the other "endpoints" page titles in My account section:
Use woocommerce_endpoint_{$endpoint}_title composite dedicated filter hook, where {$endpoint} need to be replaced by the targeted endpoint (see available endpoints on Settings > Advanced section)
Example: To change My account "orders" endpoint page title you will use:
add_filter( 'woocommerce_endpoint_orders_title', 'change_my_account_orders_title', 10, 2 );
function change_my_account_orders_title( $title, $endpoint ) {
$title = __( "Your orders", "woocommerce" );
return $title;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
If it doesn't work, it's because something else is making trouble, like your own customizations, your theme's customizations, a plugin or something else…
Related StackOverFlow answers:
Changing the titles on My Account pages in Woocommerce
Rename My account tabbed menu items in Woocommerce
Reorder menu items in Woocommerce My Account section
Display custom content for a custom account menu item in Woocommerce 3
You can change you MyAccount item titles this way:
/**
* Rename WooCommerce MyAccount menu items
*/
add_filter( 'woocommerce_account_menu_items', 'rename_menu_items' );
function rename_menu_items( $items ) {
$items['downloads'] = 'Download MP3s';
$items['orders'] = 'My Orders';
$items['edit-account'] = 'Change My Details';
return $items;
}
To change the title on each account page as well, you need to add this too:
/**
* Change page titles
*/
add_filter( 'the_title', 'custom_account_endpoint_titles' );
function custom_account_endpoint_titles( $title ) {
global $wp_query;
if ( isset( $wp_query->query_vars['downloads'] ) && in_the_loop() ) {
return 'Download MP3s';
}
if ( isset( $wp_query->query_vars['orders'] ) && in_the_loop() ) {
return 'My Orders';
}
if ( isset( $wp_query->query_vars['edit-account'] ) && in_the_loop() ) {
return 'Change My Details';
}
return $title;
}
If you're using Yoast SEO, you need to add another function to set the correct page titles in the browser tab. If you also need this, I'll expand my answer.
Put this into you functions.php file. Tested and works.
I notice I had some issues implementing the is_wc_endpoint_url() && in_the_loop solution, it did not work out with the && in_the_loop(), removing is not a viable option since it created even more conflicts.
So instead I chose to work on the template file directly, I am sharing this because I noticed that some people had the same issues and this solution might help others achieve the similar results using a different approach.
I basically replaced the header in page-my-account.php with this code:
<header>
<?php
$main_title = get_the_title();
$endpoint = WC()->query->get_current_endpoint();
$endpoint_title = __( WC()->query->get_endpoint_title( $endpoint ), 'text-domain' );
if ( $endpoint_title ){
//If the endpoint exists use itself as the new custom title
$custom_title = $endpoint_title;
}
else{
// Here we can define the custom title for each endpoint we can use home_url() or strpos + add_query_arg()
global $wp;
if( basename( home_url($wp->request) ) == 'points-and-rewards' || ( strpos( (add_query_arg( $wp->query_vars)) , 'points-and-rewards' ) !== false ) ){
$custom_title = __( 'Points and rewards', 'text-domain' );
}
elseif( basename(home_url($wp->request)) == 'payment-methods' ){
$custom_title = __( 'Payment methods', 'text-domain' );
}
elseif( basename(home_url($wp->request)) == 'my-account' ){
$custom_title = __( 'Dashboard', 'text-domain' );
}
//Add more custom titles here
}
if ( !empty( $custom_title ) ){
//If there is a custom title
$new_endpoint_title = sprintf( '<h2>%s > %s</h2>', $main_title, $custom_title );
}
else{
//If there is no custom title default to get_title()
$new_endpoint_title = sprintf( '<h2>%s</h2>' , $main_title );
}
?>
<?php //Echo's the resulting title ?>
<?php echo $new_endpoint_title; ?>
</header>
Updated:
I'm building a WooCommerce site where the user selects a series of options from dropdowns in the single product page which then display in the cart page and, thanks to help I've received on here, in the checkout as well. The options selected also influence the price of the product. I start with two product variations: 'Print' and 'Original' (the site is selling antique maps).
Everything works fine up till the checkout where all the order details display correctly but, after having placed the order, the details don't appear on the 'order received' screen under 'order details' nor do they appear in the customer confirmation email.
To give some background, the different variations are selected using jQuery, and added to hidden fields a per example below:
$( ":root" ).find("#mapchest-custom-fields").append("<input type='hidden'
name='test' value='wibble'>");
...and these hidden fields are then referenced to add the details to the cart in the following manner:
add_filter('woocommerce_add_cart_item_data','add_custom_field_data', 20,3);
function add_custom_field_data($cart_item_data, $product_id, $variation_id)
{
if(isset($_REQUEST['test']) && ! empty( 'test' )) { // not
$mc_test = sanitize_text_field($_POST['test']);
$cart_item_data['custom_data']['test'] = array(
'label' => 'Test',
'value' => $mc_test
);
}
if(isset($_REQUEST['original_map_vendor_details']) && ! empty(
'original_map_vendor_details' )) {
$mc_original_map_size =
sanitize_text_field($_REQUEST['original_map_vendor_details']);
$cart_item_data['custom_data']['original_map_vendor_details'] =
array(
'label' => 'Vendor',
'value' => $mc_original_map_size
);
}
// process above repeated for other fields
return $cart_item_data;
}
The details are displayed in the cart and checkout using the following function:
add_filter('woocommerce_get_item_data','wdm_add_item_meta',10,2);
function wdm_add_item_meta($cart_data, $cart_item)
{
$custom_items = array();
if( !empty( $cart_data ) )
$custom_items = $cart_data;
if( isset( $cart_item['custom_data'] ) ) {
foreach( $cart_item['custom_data'] as $key => $custom_data ){
if( $key != 'key' ){
$custom_items[] = array(
'name' => $custom_data['label'],
'value' => $custom_data['value'],
);
}
}
}
return $custom_items;
}
What I want to do, as I say is have the details display in the Order Received page and the emails but I can't make it work. I know that for emails I need to hook it to one of the email hooks but I don't know how to access the data sent to the cart in the function above.
I've tried adding using the woocommerce_checkout_create_order_line_item hook to along these lines:
add_action( 'woocommerce_checkout_create_order_line_item',
'add_custom_order_line_item_meta', 20,4 );
function add_custom_order_line_item_meta($item, $cart_item_key, $values,
$order)
{
if( array_key_exists('test', $values['custom_data']) ){
$item->update_meta_data( 'Test', $values['custom_data']['test'] );
}
}
...but whilst I can see the data if I var_dump it in the email like this:
add_action('woocommerce_email_customer_details',
'add_custom_checkout_field_to_emails_notifications', 25, 4 );
function add_custom_checkout_field_to_emails_notifications( $order,
$sent_to_admin, $plain_text, $email ) {
var_dump($order);
}
So, in summary, I have the data working and displaying up to the point of the checkout. After that, I want it to display in customer confirmation emails and on the 'order received' page but I'm having trouble accessing the data. Having looked through other questions on the same subject i would have thought that this would happen automatically regarding the order received page but it doesn't. I suspect there's a stage missing in the code but I can't work out what it should be.
Any tips as to what I'm doing wrong here?
Thanks in advance.
ps. I've now managed to display the fields in the confirmation email (after a fashion) using the following functions:
add_action( 'woocommerce_checkout_create_order_line_item',
'add_custom_order_line_item_meta', 20,4 );
function add_custom_order_line_item_meta($item, $cart_item_key, $values,
$order)
{
if ( isset( $values['custom_data'] ) ) {
$item->update_meta_data( __('The Custom Data', 'woocommerce'),
$values['custom_data'] );
}
}
and
add_action('woocommerce_email_customer_details',
'add_custom_checkout_field_to_emails_notifications', 25, 4 );
function add_custom_checkout_field_to_emails_notifications( $order,
$sent_to_admin, $plain_text, $email ) {
// var_dump($order);
foreach( $order->get_items() as $item_id => $item ){
$custom_data = $item->get_meta( 'The Custom Data' );
foreach( $custom_data as $key => $value ){
foreach( $value as $key1 => $value1 ){
$output = '';
$output .= '<span class="text">' . $value1 . '</span>';
echo $output;
}
echo "<br>";
}
echo "<br><br>";
// var_dump($custom_data );
}
'</strong> <span class="text">' . $order->get_data() . '</span></div>';
}
but this is a hacky solution and doesn't address the underlying problem of why the information isn't appearing in the order received page or directly in the order line items in the email.
Okay, I've worked this out. I'm putting the answer here for the benefit of anyone else having the same problem. Basically, my process was missing a stage. In order to achieve the above you do as follows:
Define the value you wish to pass as meta data. In my own case I used a hidden field but this can equally be set with a text input, a dropdown or other input field. In my case I used jquery to append this to an empty div with id 'mapchest-custom-fields' which I hooked into the process before the cart button. It can equally be set with a static value.
<?php
function define_container_div() {
?>
<div id="mapchest-custom-fields"></div>
<?php
}
add_action( 'woocommerce_before_add_to_cart_button', 'define_container_div', 20 );
?>
...jQuery code to append the value. Value can be dynamic as well:
$( ":root" ).find("#mapchest-custom-fields").append("<input type='hidden' name='test' value='wibble'>");
Next you add the value to your cart item data:
function add_values_to_cart_item_data( $cart_item_data, $product_id, $variation_id )
{
if(isset($_POST['test']) && ! empty( 'test' )) {
$test = filter_input( INPUT_POST, 'test' );
$cart_item_data['test'] = $test;
}
return $cart_item_data;
}
add_filter( 'woocommerce_add_cart_item_data', 'add_values_to_cart_item_data', 10, 3);
Next, you display the value in your cart:
function display_data_in_cart( $item_data, $cart_item ) {
$item_data[] = array(
'key' => __( 'Test', 'mapchest' ),
'value' => wc_clean( $cart_item['test'] ),
);
return $item_data;
}
add_filter( 'woocommerce_get_item_data', 'display_data_in_cart', 10, 2 );
And finally, you add the data to your order items:
function add_data_to_order_items( $item, $cart_item_key, $values, $order ) {
$item->add_meta_data( __( 'Test', 'mapchest' ), $values['test'] );
}
add_action( 'woocommerce_checkout_create_order_line_item', 'add_data_to_order_items', 10, 4 );
The above process works for me. It displays the custom data in the cart and in the checkout and persists it through to the 'Order Received' page archived orders and the confirmation email (not checked other emails yet).
Thanks to https://iconicwp.com/blog/add-custom-cart-item-data-woocommerce/ for explaining this process to me.
I am trying to programatically add a fee to the woocommerce cart from a script which is executed on a form submission. As far as I am aware, I don't think I am able to use a hook as I need to apply the custom fee when the form on the page has been submitted (Custom API integration).
I have tried doing the following within the script:
add_action( 'woocommerce_cart_calculate_fees', 'woo_add_cart_fee' );
function woo_add_cart_fee( $cart ){
$valid = false;
if ( ! $_POST || ( is_admin() && ! is_ajax() ) ) {
return;
}
if (isset($_POST['coupon_code'])) {
$code = $_POST['coupon_code'];
$coupon = new WC_Coupon($code);
if($coupon->get_amount() != null){
$valid == true;
}
//if not then lets check to see if its a giftcard.
if($valid == false){
$api_login="xxxxxx";
$api_password="xxxxxx";
$url = "https://xxxxxx.com/xxxxx/xxxxx.svc";
$client = new SoapClient( $url . "?singleWsdl",
array(
"location" => $url,
"login" => $api_login,
"password" => $api_password,
"trace" => 1
)
);
$request = new StdClass();
$request->bonId = $code;
$request->bonType = 'GiftCard';
// call the correct database
$request->context = new StdClass();
$request->context->DatabaseId = 'xxxxx';
try {
$resu = $client->GetBonAvailableAmount($request);
if (isset($resu->GetBonAvailableAmountResult->Amount)) {
$amount = $resu->GetBonAvailableAmountResult->Amount;
$cart->add_fee('xxxxxx Gift Card ', floatval('-'.$amount * 0.83333), false, '' );
} else {
$response['status'] = 'error';
$response['message'] = 'Gift card not recognized.';
}
} catch (Exception $e) {
}
}
}
}
and I can see that when I echo the cart object there is a fee object which contains all the correct data.
It seems that the cart or totals are not updating, if i refresh the page, they still do not reflect the values i am expected.
I have trawled pretty much all Stack Overflow posts and cant seem to find anything which solves the issue.
Is there anything that I am missing here?
You need to use a custom function hooked in woocommerce_cart_calculate_fees action hook:
add_action( 'woocommerce_cart_calculate_fees', 'add_a_custom_fee', 10, 1 );
function add_a_custom_fee( $cart ) {
$amount = 20;
$cart->add_fee( __('Custom fee'), $amount );
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
I need send email instructions when customer select shipping zone id = 0 (The rest of the world).
I found code below, but its based on payment method:
add_action( 'woocommerce_email_before_order_table', 'add_order_email_instructions', 10, 2 );
function add_order_email_instructions( $order, $sent_to_admin ) {
if ( ! $sent_to_admin ) {
if ( 'cod' == $order->payment_method ) {
// cash on delivery method
echo '<p><strong>Instructions:</strong> Full payment is due immediately upon delivery: <em>cash only, no exceptions</em>.</p>';
} else {
// other methods (ie credit card)
echo '<p><strong>Instructions:</strong> Please look for "Madrigal Electromotive GmbH" on your next credit card statement.</p>';
}
}
}
How can I change it to specific shipping zone please?
How can I set specific email instructions based on shipping zone in WooCommerce?
Any help on this will be appreciated.
Get the shipping zone for an order is not so easy. It can be done with the following code example:
add_action( 'woocommerce_email_before_order_table', 'custom_text_in_email_shipping_zone_based', 10, 4 );
function custom_text_in_email_shipping_zone_based( $order, $sent_to_admin, $plain_text, $email ) {
if ( ! $sent_to_admin ) {
// Get the shipping method related data (we need the Instance ID)
$shipping_item = $order->get_items('shipping');
$item = reset($shipping_item);
$shipping_method_id = $item->get_method_id();
$method_arr = explode( ':', $shipping_method_id );
// Get the Zone ID and related data
$shipping_zone_object = WC_Shipping_Zones::get_zone_by( 'instance_id', $method_arr[1] );
$zone_id = $shipping_zone_object->get_id(); // Zone ID
$zone_name = $shipping_zone_object->get_zone_name(); // Zone name
// Get the zone locations codes and types (if needed)
foreach( $shipping_zone_object->get_zone_locations() as $zone_location ){
$zone_location_code = $zone_location->code;
$zone_location_type = $zone_location->type;
}
if ( '0' == $zone_id ) {
// Rest of the world
echo '<p><strong>Instructions:</strong> for Rest of the world.</p>';
}
elseif ( 'Mexico' == $zone_name ) {
// Mexico zone name
echo '<p><strong>Instructions:</strong> for Mexico.</p>';
}
else {
// All other zones
echo '<p><strong>Instructions:</strong> Other zones.</p>';
}
}
}
Code goes in function.php file of the active child theme (or active theme).
Tested and works.
According to the WC_Abstract_Order codex page https://docs.woocommerce.com/wc-apidocs/class-WC_Abstract_Order.html#_has_shipping_method you can check the shipping method using has_shipping_method().
So, your if statement should be something like
if ( $order->has_shipping_method('name_of_my_shipping_method') ) {
I couldn't find an equivalent for shipping zones though.
Hope that helps
add_action( 'woocommerce_email_before_order_table', 'custom_text_in_email_shipping_zone_based', 10, 4 );
function custom_text_in_email_shipping_zone_based( $order, $sent_to_admin, $plain_text, $email ) {
if ( ! $sent_to_admin ) {
// Get the shipping method related data (we need the Instance ID)
$shipping_item = $order->get_items('shipping');
$item = reset($shipping_item);
$shipping_method_id = $item->get_method_id();
$method_arr = explode( ':', $shipping_method_id );
// Get the Zone ID and related data
$shipping_zone_object = WC_Shipping_Zones::get_zone_by( 'instance_id', $method_arr[1] );
$zone_id = $shipping_zone_object->get_id(); // Zone ID
$zone_name = $shipping_zone_object->get_zone_name(); // Zone name
// Get the zone locations codes and types (if needed)
foreach( $shipping_zone_object->get_zone_locations() as $zone_location ){
$zone_location_code = $zone_location->code;
$zone_location_type = $zone_location->type;
}
if ( '0' == $zone_id ) {
// Rest of the world
echo '<p><strong>Instructions:</strong> for Rest of the world.</p>';
}
elseif ( 'Mexico' == $zone_name ) {
// Mexico zone name
echo '<p><strong>Instructions:</strong> for Mexico.</p>';
}
else {
// All other zones
echo '<p><strong>Instructions:</strong> Other zones.</p>';
}
}
}
The code showing above doesn't work even though the author has stated tried and tested, can anybody update the code.
No matter what zone you select you always end up with All other zones.