WooCommerce editable custom checkout field and displayed in formatted address - php

I am adding mandatory shipping phone to woocommerce checkout page with
add_filter( 'woocommerce_checkout_fields', 'add_shipping_phone_to_checkout_page' );
function add_shipping_phone_to_checkout_page( $fields ) {
$fields['shipping']['shipping_phone'] = array(
'label' => 'Phone',
'required' => true,
'class' => array( 'form-row-wide' ),
'priority' => 25,
);
return $fields;
}
then display it in admin order panel
add_action( 'woocommerce_admin_order_data_after_shipping_address', 'shipping_phone_checkout_display_in_order_panel' );
function shipping_phone_checkout_display_in_order_panel( $order ){
echo '<p><b>Phone :</b> ' . get_post_meta( $order->get_id(), '_shipping_phone', true ) . '</p>';
}
and finally print it in email
add_action('woocommerce_email_customer_details','shipping_phone_display_in_order_email', 25, 4 );
function shipping_phone_display_in_order_email( $order, $sent_to_admin, $plain_text, $email ) {
$output = '';
$shipping_phone = get_post_meta( $order->id, '_shipping_phone', true );
if ( !empty($shipping_phone) )
$output = '<p><strong>' . __( "Phone:", "woocommerce" ) . '</strong> ' . $shipping_phone . '</p>';
echo $output;
}
All works as it should. I'd like to achieve 2 enhancements but I am unable to do:
Make the custom phone field editable in admin panel
In email, move the custom phone field value in shipping address block
Any help would be appreciated

You need to make some changes in your code… The following code will display the shipping phone field in:
Checkout
My Account > Address > Edit shipping address
Admin order edit pages
The code will also add the shipping phone to formatted displayed shipping address on emails shipping address section.
// display shipping phone in checkout and my account edit shipping address
add_filter( 'woocommerce_shipping_fields', 'add_shipping_phone_field' );
function add_shipping_phone_field( $fields ) {
$fields['shipping_phone'] = array(
'label' => __('Phone (Shipping)'),
'required' => true,
'class' => array( 'form-row-wide' ),
'priority' => 25,
);
return $fields;
}
// Editable field on admin order edit pages inside edit shipping section
add_filter( 'woocommerce_admin_shipping_fields' , 'add_order_admin_edit_shipping_phone' );
function add_order_admin_edit_shipping_phone( $fields ) {
// Include shipping phone as editable field
$fields['phone'] = array( 'label' => __("Shipping phone"), 'show' => '0' );
return $fields;
}
// Adding custom placeholder to woocommerce formatted address only on Backend
add_filter( 'woocommerce_localisation_address_formats', 'admin_localisation_address_formats', 50, 1 );
function admin_localisation_address_formats( $address_formats ){
// Only in backend (Admin)
if( is_admin() || ! is_wc_endpoint_url() ) {
foreach( $address_formats as $country_code => $address_format ) {
$address_formats[$country_code] .= "\n{phone}";
}
}
return $address_formats;
}
// Custom placeholder replacement to woocommerce formatted address
add_filter( 'woocommerce_formatted_address_replacements', 'custom_formatted_address_replacements', 10, 2 );
function custom_formatted_address_replacements( $replacements, $args ) {
$replacements['{phone}'] = ! empty($args['phone']) ? $args['phone'] : '';
return $replacements;
}
// Add the shipping phone value to be displayed on email notifications under shipping address
add_filter( 'woocommerce_order_formatted_shipping_address', 'add_shipping_phone_to_formatted_shipping_address', 100, 2 );
function add_shipping_phone_to_formatted_shipping_address( $shipping_address, $order ) {
global $pagenow, $post_type;
// Not on admin order edit pages (as it's already displayed).
if( ! ( $pagenow === 'post.php' && $post_type === 'shop_order' && isset($_GET['action']) && $_GET['action'] === 'edit' ) ) {
// Include shipping phone on formatted shipping address
$shipping_address['phone'] = $order->get_meta('_shipping_phone');
}
return $shipping_address;
}
// Remove double billing phone from email notifications (and admin) under billing address
add_filter( 'woocommerce_order_formatted_billing_address', 'remove_billing_phone_from_formatted_billing_address', 100, 2 );
function remove_billing_phone_from_formatted_billing_address( $billing_address, $order ) {
unset($billing_address['phone']);
return $billing_address;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
For billing custom fields, you will replace the hooks:
woocommerce_shipping_fields by woocommerce_billing_fields
woocommerce_admin_shipping_fields by woocommerce_admin_billing_fields
woocommerce_order_formatted_shipping_address by woocommerce_order_formatted_billing_address
(don't use the last function).
For the front endpoints:
On order received (thank you), order-pay, and myaccount / order-view, you will have to override via your active theme the template order/order-details-customer.php.
You will add inside the html tag <address> after line 52 the following:
<?php if ( $shipping_phone = $order->get_meta('_shipping_phone') ) : ?>
<p class="woocommerce-customer-details--phone"><?php echo esc_html( $shipping_phone ); ?></p>
<?php endif; ?>
On admin side, the shipping phone is displayed and editable:
On order-view, order-received and email notifications, the shipping phone is displayed at the end of the shipping address section:

Related

Make fields optional in WooCommerce My account edit addresses

How to make all fields optional in WooCommerce My account edit addresses only?
How to disable the required notice:
And the required html:
How to remove required? Can you help me?
To make all My account > addresses fields optional use the following:
// Make all My account addresses fields optional
add_filter( 'woocommerce_default_address_fields' , 'filter_my_account_addresses_fields', 999 );
add_filter( 'woocommerce_billing_fields', 'filter_my_account_addresses_fields', 999 );
function filter_my_account_addresses_fields( $fields ) {
// Only on My account edit addresses
if ( is_wc_endpoint_url( 'edit-address' ) ) {
// Loop through existing fields
foreach( $fields as $field_key => $field_data ) {
// if they are required
if( $fields[$field_key]['required'] ) {
// Make them optional
$fields[$field_key]['required'] = false;
}
}
}
return $fields;
}
// Optionaly remove ("optional)" text from My account addresses fields
add_filter( 'woocommerce_form_field' , 'remove_account_addresses_optional_fields_label', 10, 4 );
function remove_account_addresses_optional_fields_label( $field, $key, $args, $value ) {
// Only on My account edit addresses
if ( is_wc_endpoint_url( 'edit-address' ) ) {
$optional = ' <span class="optional">(' . esc_html__( 'optional', 'woocommerce' ) . ')</span>';
$field = str_replace( $optional, '', $field );
}
return $field;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
works for option on checkout page "kurumsal"
works for option on checkout page "bireysel"
my account > addresses - does not select and hide option and does not work
my account > addresses - does not select and hide option and does not work
you can understand in the picture

Custom WooCommerce datepicker checkout field saved and displayed on orders and emails

I have added a new custom datepicker field on the WooCommerce checkout page.
I am using Enabling a date-picker in Woocommerce checkout fields. All it's OK On checkout page.
But now I really don't know how can I save and add this new field on the order notes. Any help is appreciated.
Section 1
The following will display a custom datepicker field on checkout, it will validate the field and save It.
The chosen date will be displayed on admin order, customer order and emails
To include this date on customer note, use the code from "section 2" at the end.
The code:
// Register main datepicker jQuery plugin script
add_action( 'wp_enqueue_scripts', 'enabling_date_picker' );
function enabling_date_picker() {
// Only on front-end and checkout page
if( is_admin() || ! is_checkout() ) return;
// Load the datepicker jQuery-ui plugin script
wp_enqueue_script('jquery-ui-datepicker');
wp_enqueue_style('jquery-ui');
}
// Add custom checkout datepicker field
add_action( 'woocommerce_before_order_notes', 'checkout_display_datepicker_custom_field' );
function checkout_display_datepicker_custom_field( $checkout ) {
$field_id = 'my_datepicker';
echo '<div id="datepicker-wrapper">';
woocommerce_form_field( $field_id, array(
'type' => 'text',
'class'=> array( 'form-row-wide'),
'label' => __('Choose a date'),
'required' => true, // Or false
), '' );
echo '<br></div>';
// Jquery: Enable the Datepicker
?>
<script language="javascript">
jQuery( function($){
var a = '#<?php echo $field_id ?>';
$(a).datepicker({
dateFormat: 'dd-mm-yy', // ISO formatting date
});
});
</script>
<?php
}
// Field validation
add_action( 'woocommerce_after_checkout_validation', 'checkout_datepicker_custom_field_validation', 10, 2 );
function checkout_datepicker_custom_field_validation( $data, $errors ) {
$field_id = 'my_datepicker';
if ( isset($_POST[$field_id]) && empty($_POST[$field_id]) ) {
$errors->add( 'validation', __('You must choose a date on datepicker field.', 'woocommerce') );
}
}
// Save field
add_action( 'woocommerce_checkout_create_order', 'save_datepicker_custom_field_value', 10, 2 );
function save_datepicker_custom_field_value( $order, $data ){
$field_id = 'my_datepicker';
$meta_key = '_'.$field_id;
if ( isset($_POST[$field_id]) && ! empty($_POST[$field_id]) ) {
$order->update_meta_data( $meta_key, esc_attr($_POST[$field_id]) );
}
}
// Display custom field value in admin order pages
add_action( 'woocommerce_admin_order_data_after_billing_address', 'admin_display_date_custom_field_value', 10, 1 );
function admin_display_date_custom_field_value( $order ) {
$meta_key = '_my_datepicker';
$meta_value = $order->get_meta( $meta_key ); // Get carrier company
if( ! empty($meta_value) ) {
// Display
echo '<p><strong>' . __("Date", "woocommerce") . '</strong>: ' . $meta_value . '</p>';
}
}
// Display custom field value after shipping line everywhere (orders and emails)
add_filter( 'woocommerce_get_order_item_totals', 'display_date_custom_field_value_on_order_item_totals', 10, 3 );
function display_date_custom_field_value_on_order_item_totals( $total_rows, $order, $tax_display ){
$field_id = 'my_datepicker';
$meta_key = '_my_datepicker';
$meta_value = $order->get_meta( $meta_key ); // Get carrier company
if( ! empty($meta_value) ) {
$new_total_rows = [];
// Loop through order total rows
foreach( $total_rows as $key => $values ) {
$new_total_rows[$key] = $values;
// Inserting the carrier company under shipping method
if( $key === 'shipping' ) {
$new_total_rows[$field_id] = array(
'label' => __("Date", "woocommerce") . ':',
'value' => $meta_value,
);
}
}
return $new_total_rows;
}
return $total_rows;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Section 2
To add this field to Customer order notes you will use the following instead:
// Register main datepicker jQuery plugin script
add_action( 'wp_enqueue_scripts', 'enabling_date_picker' );
function enabling_date_picker() {
// Only on front-end and checkout page
if( is_admin() || ! is_checkout() ) return;
// Load the datepicker jQuery-ui plugin script
wp_enqueue_script('jquery-ui-datepicker');
wp_enqueue_style('jquery-ui');
}
// Add custom checkout datepicker field
add_action( 'woocommerce_before_order_notes', 'checkout_display_datepicker_custom_field' );
function checkout_display_datepicker_custom_field( $checkout ) {
$field_id = 'my_datepicker';
echo '<div id="datepicker-wrapper">';
woocommerce_form_field( $field_id, array(
'type' => 'text',
'class'=> array( 'form-row-wide'),
'label' => __('Choose a date'),
'required' => true, // Or false
), '' );
echo '<br></div>';
// Jquery: Enable the Datepicker
?>
<script language="javascript">
jQuery( function($){
var a = '#<?php echo $field_id ?>';
$(a).datepicker({
dateFormat: 'dd-mm-yy', // ISO formatting date
});
});
</script>
<?php
}
// Field validation
add_action( 'woocommerce_after_checkout_validation', 'checkout_datepicker_custom_field_validation', 10, 2 );
function checkout_datepicker_custom_field_validation( $data, $errors ) {
$field_id = 'my_datepicker';
if ( isset($_POST[$field_id]) && empty($_POST[$field_id]) ) {
$errors->add( 'validation', __('You must choose a date on datepicker field.', 'woocommerce') );
}
}
// Save field
add_action( 'woocommerce_checkout_create_order', 'save_datepicker_custom_field_value', 10, 2 );
function save_datepicker_custom_field_value( $order, $data ){
$field_id = 'my_datepicker';
$meta_key = '_'.$field_id;
if ( isset($_POST[$field_id]) && ! empty($_POST[$field_id]) ) {
$date = esc_attr($_POST[$field_id]);
$order->update_meta_data( $meta_key, $date ); // Save date as order meta data
$note = sprintf(__("Chosen date: %s.", "woocommerce"), $date );
$note = isset($data['order_comments']) && ! empty($data['order_comments']) ? $data['order_comments'] . '. ' . $note : $note;
// Save date on customer order note
$order->set_customer_note( $note );
}
}
// Display custom field value in admin order pages
add_action( 'woocommerce_admin_order_data_after_billing_address', 'admin_display_date_custom_field_value', 10, 1 );
function admin_display_date_custom_field_value( $order ) {
$meta_key = '_my_datepicker';
$meta_value = $order->get_meta( $meta_key ); // Get carrier company
if( ! empty($meta_value) ) {
// Display
echo '<p><strong>' . __("Chosen date", "woocommerce") . '</strong>: ' . $meta_value . '</p>';
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Add a dropdown Filter for a custom metakey on WooCommerce admin orders

I have the below custom metakey which is an optin checkbox during checkout:
//1. ADD OPT IN OPTION IN CHECKOUT AND SAVE IN THE ORDER
// Add checkbox optin before T&Cs
add_action( 'woocommerce_checkout_before_terms_and_conditions', 'marketing_opting_field' );
function marketing_opting_field() {
echo '<div id="marketing_opting_field">';
woocommerce_form_field( 'marketing_opting', array(
'type' => 'checkbox',
'class' => array('input-checkbox'),
'label' => __('Yes, sign me up'),
'default' => 1,
), WC()->checkout->get_value( 'marketing_opting' ) );
echo '</div>';
}
// Save the optin field in the order meta, when checkbox has been checked
add_action( 'woocommerce_checkout_update_order_meta', 'custom_checkout_field_update_order_meta', 10, 1 );
function custom_checkout_field_update_order_meta( $order_id ) {
if ( ! empty( $_POST['marketing_opting'] ) )
update_post_meta( $order_id, 'marketing_opting', $_POST['marketing_opting'] );
}
// Display the result of the checked optin in the order under billing address
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_custom_field_on_order_edit_pages', 10, 1 );
function display_custom_field_on_order_edit_pages( $order ){
$marketing_opting = get_post_meta( $order->get_id(), 'marketing_opting', true );
if( $marketing_opting == 1 )
echo '<p><strong>Has opted in for marketing purposes.</p>';
}
// 2. SHOW CUSTOM COLUMN FOR THE OPTIN OPTION
// Adding custom column title
add_filter( 'manage_edit-shop_order_columns', 'custom_shop_order_column', 12, 1 );
function custom_shop_order_column($columns)
{
$action_column = $columns['order_actions'];
unset($columns['order_actions']);
//add the new column "Opt in"
$columns['order_marketing'] = '<p align="center">Opted in?</p>'; // title
$columns['order_actions'] = $action_column;
return $columns;
}
// Add the data for each order
add_action( 'manage_shop_order_posts_custom_column' , 'custom_order_list_column_content', 10, 2 );
function custom_order_list_column_content( $column, $post_id ){
$marketing_opting = get_post_meta( $post_id, 'marketing_opting', true );
if( $marketing_opting == 1)
switch($column){
case 'order_marketing' : echo '<p align="center"><span class="dashicons dashicons-yes"></span><span style="color: #F21891; font-weight: 600;">Signed Up</span></p>';
break;
}
}
So above is working and shows below the column, but I would like to have a filter in the admin bar and a search for Signed Up gives the desired result:
The search is not working, because the value of the checkbox for checked is 1 and does not resognise other words. I have added below, but it's not giving the result:
add_filter( 'woocommerce_shop_order_search_fields', 'marketing_search_fields', 10, 1 );
function marketing_search_fields( $meta_keys ){
$meta_keys[] = 'marketing_opting';
return $meta_keys;
}
The admin bar filter; the only related posts I could find are all about order statuses and not a custom metakey. I am not sure how to add this correctly, I started with the below, but there are obvious errors, and I am stuck.
add_filter( 'views_edit-shop_order' , 'marketing_opt_in_filter', 10, 1);
function marketing_opt_in_filter( $views ) {
$marketing_opting = get_post_meta( $post_id, 'marketing_opting', true );
if( $marketing_opting == 1)
$query_string = admin_url( 'edit.php?post_type=shop_order' ) ;
$query_string = add_query_arg( 'marketing_opting' , 'yes' , $query_string ) ;
$views[ 'marketing_opting' ] = 'Opted In (%s)' ;
return $views ;
}
I have revisited your existing code a bit and added a dropdown filter for the "marketing optin" custom field:
//1. ADD OPT IN OPTION IN CHECKOUT AND SAVE IN THE ORDER
// Add checkbox optin before T&Cs
add_action( 'woocommerce_checkout_before_terms_and_conditions', 'marketing_opting_field' );
function marketing_opting_field() {
echo '<div id="marketing_opting_field">';
woocommerce_form_field( 'marketing_opting', array(
'type' => 'checkbox',
'class' => array('input-checkbox'),
'label' => __('Yes, sign me up'),
'default' => 1,
), WC()->checkout->get_value( 'marketing_opting' ) );
echo '</div>';
}
// Save the optin field as custom order meta, when checkbox has been checked
add_action( 'woocommerce_checkout_create_order', 'action_checkout_update_order_meta', 10, 2 );
function action_checkout_update_order_meta( $order, $data ) {
if( isset($_POST['marketing_opting']) )
$order->update_meta_data( '_marketing_opting', empty($_POST['marketing_opting']) ? 'no' : 'yes' );
}
// Save the optin field as custom user meta, when checkbox has been checked
add_action( 'woocommerce_checkout_update_customer', 'action_checkout_update_customer_meta', 10, 2 );
function action_checkout_update_customer_meta( $customer, $data ) {
if( isset($_POST['marketing_opting']) )
$customer->update_meta_data( 'marketing_opting', empty($_POST['marketing_opting']) ? 'no' : 'yes' );
}
// Display the result of the checked optin in the order under billing address
add_action( 'woocommerce_admin_order_data_after_billing_address', 'display_custom_field_on_order_edit_pages', 10, 1 );
function display_custom_field_on_order_edit_pages( $order ){
if( $order->get_meta( '_marketing_opting' ) === 'yes' )
echo '<p><strong>Has opted in for marketing purposes.</p>';
}
// 2. SHOW CUSTOM COLUMN FOR THE OPTIN OPTION
// Adding custom column title
add_filter( 'manage_edit-shop_order_columns', 'custom_shop_order_column', 12, 1 );
function custom_shop_order_column($columns)
{
$action_column = $columns['order_actions'];
unset($columns['order_actions']);
//add the new column "Opt in"
$columns['order_marketing'] = '<div align="center">' .__("Opted in?") . '</div>'; // title
$columns['order_actions'] = $action_column;
return $columns;
}
// Add the data for each order
add_action( 'manage_shop_order_posts_custom_column' , 'custom_order_list_column_content', 10, 2 );
function custom_order_list_column_content( $column, $post_id ){
global $post, $the_order;
if ($column ==='order_marketing') {
$value = $the_order->get_meta( '_marketing_opting' );
$label = $value === 'yes' ? __('Signed Up') : ucfirst($value);
$color = $value === 'yes' ? 'color:#00cc00;' : 'color:#bbbbbb;';
echo '<p align="center" style="'.$color.'"><span class="dashicons dashicons-'.$value.'"></span><span style="font-weight:600;">'.$label.'</span></p>';
}
}
// 3. Make marketing optin meta searchable from search field (can't work very well for 'yes' or 'no' values!)
// Make a custom meta field searchable from the admin order list search field
add_filter( 'woocommerce_shop_order_search_fields', 'marketing_search_fields', 10, 1 );
function marketing_search_fields( $meta_keys ){
$meta_keys[] = '_marketing_opting';
return $meta_keys;
}
// 4. Add a dropdown filter to get orders by marketing optin meta value
// Add a dropdown to filter orders by Marketing optin
add_action( 'restrict_manage_posts', 'display_admin_shop_order_marketing_opting_filter' );
function display_admin_shop_order_marketing_opting_filter(){
global $pagenow, $post_type;
if( 'shop_order' === $post_type && 'edit.php' === $pagenow ) {
$domain = 'woocommerce';
$current = isset($_GET['filter_shop_order_marketing'])? $_GET['filter_shop_order_marketing'] : '';
echo '<select name="filter_shop_order_marketing">
<option value="">' . __('Filter Marketing optin', $domain) . '</option>';
$options = ['yes' => __('Signed Up'), 'no' => __('No')];
foreach ( $options as $key => $label ) {
printf( '<option value="%s"%s>%s</option>', $key,
$key === $current ? '" selected="selected"' : '', $label );
}
echo '</select>';
}
}
// Process the filter dropdown for orders by Marketing optin
add_filter( 'request', 'process_admin_shop_order_marketing_opting_filter', 99 );
function process_admin_shop_order_marketing_opting_filter( $vars ) {
global $pagenow, $typenow;
if ( $pagenow == 'edit.php' && isset( $_GET['filter_shop_order_marketing'] )
&& $_GET['filter_shop_order_marketing'] != '' && 'shop_order' === $typenow ) {
$vars['meta_key'] = '_marketing_opting';
$vars['meta_value'] = wc_clean( $_GET['filter_shop_order_marketing'] );
}
return $vars;
}
Note: I have change the order meta_key to _marketing_opting starting with an underscore as most other existing metakeys...
Also I have added a function that register that "marketing optin" value in user meta data, as it will be used by checkout on WC()->checkout->get_value( 'marketing_opting' ) for customers that have already made an order.
Field validation (optional)
If you make this checkout field required, you will need field validation… Then add the following:
// Custom Checkout field validation
add_action('woocommerce_checkout_process', 'custom_checkout_field_validation');
function custom_checkout_field_validation() {
if ( isset($_POST['marketing_opting']) ) {
wc_add_notice( '<strong>'. __("Please select a value", "woocommerce") . '</strong> | '.$_POST['marketing_opting'], 'error' );
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Show custom data in emails, order details woocommerce

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.

WooCommerce: update custom fields after checkout validation failure

In my project I'm customizing some of the WooCommerce features.
My "shipping methods" are:
1. delivery
2. take away
I also added a custom field in the checkout page that is a <select> populated with the valid times for delivery (case "1") or for the take away (case "2").
It may happen that a user selects 2. take away in the cart page, then selects a time valid for "2", but then changes to 1. delivery and the selected time may not be valid anymore, neither the option list and the custom field label.
Of course i'm using the woocommerce_checkout_process hook to warn the user via wc_add_notice(), but even if the woocommerce_checkout_fields hook is triggered (that's where i create the select list), the <select> values are not updated.
I think there is an AJAX call that is related only to the shipping method and doesn't update the other checkout fields, although woocommerce_checkout_fields hook is triggered.
How to update the custom fields?
Do i need some js/jquery/AJAX?
Or: can a custom field be related to a shipping method (and get updated via AJAX with it)? How?
EDIT
Custom field code:
add_filter( 'woocommerce_checkout_fields', 'fty_filter_checkout_fields' );
function my_filter_checkout_fields($fields) {
$must_deliver = WC()->cart->shipping_total > 0.0; // true=deliver, false=take away
// some complex code to calculate time lists omitted, samples array instead:
$delivery_time_list = array(
"deliver 10:00",
"deliver 11:00",
"deliver 12:00",
"deliver 13:00"
);
$takeaway_time_list = array(
"takeaway 10:00",
"takeaway 10:30",
"takeaway 11:00",
"takeaway 11:30",
"takeaway 12:00",
"takeaway 12:30",
"takeaway 13:00",
"takeaway 13:30"
);
// add the new conditional field
if($must_deliver) {
$fields['my_delivery_datetime'] = array(
'my_delivery_time' => array(
'type' => 'select',
'options' => $delivery_time_list,
'required' => true,
'label' => __('Delivery time')
)
);
} else {
$fields['my_delivery_time'] = array(
'my_delivery_time' => array(
'type' => 'select',
'options' => $takeaway_time_list,
'required' => true,
'label' => __('Take away time')
)
);
}
return $fields;
}
an idea of the validation code:
add_action('woocommerce_checkout_process', 'my_checkout_date_time_validation', 30, 1);
function my_checkout_date_time_validation($doh) {
$time = filter_input(INPUT_POST, 'my_delivery_time');
$shipping = filter_input(INPUT_POST, 'shipping_method', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
if(strpos($time, "deliver")!==FALSE && strpos($shipping[0], "local_pickup")!==FALSE) {
wc_add_notice('Please re-select take away time', 'error');
} else if(strpos($time, "takeaway")!==FALSE && strpos($shipping[0], "distance_based_rate")!==FALSE) {
wc_add_notice('Please re-select delivery time', 'error');
}
}
here's about shipping methods;
add_action( 'woocommerce_flat_rate_shipping_add_rate', 'add_distance_based_delivery_rate', 10, 2 );
function add_distance_based_delivery_rate( $method, $rate ) {
$new_rate = $rate;
$new_rate['id'] .= ':' . 'distance_based_rate';
$new_rate['label'] = 'delivery'; // Rename to 'Rushed Shipping'.
// incredibly complex code used to calculate delivery costs omitted
$dist_cost = 1000;
$new_rate['cost'] += $dist_cost;
$method->add_rate( $new_rate );
}
Thanx!
The code provided was mostly un-useful… I have make a lot of changes and optimizations. All my code is commented, it's tested on WooCommerce 3+ and perfectly works.
You will have to add your "incredibly complex code used to calculate delivery costs omitted"…
1) JAVASCRIPT FOR CONDITIONAL CHECKOUT FIELDS LIVE EVENTS
The only way to get the hand on customer live events (browser side) is javascript/jQuery. So this is not easy because WooCommerce use already a lot of javascript/jQuery/Ajax on checkout page…
I have included the javascript code into the hooked function, but you should save it in a separate file and register this script file with classic WordPress registering script function, like in this thread:
Checkout fields: Hiding and showing existing fields
2) USE EXISTING AVAILABLE SHIPPING METHODS (DYNAMIC PRICES CALCULATION):
You don't need to create any shipping rate. You can use:
local_pickup available method for your "TAKE WAY"
flat_rate available method for your "Delivery" (with dynamic price calculations)
For each of your shipping zones, enable, set and rename (label name) the 2 methods in Woocommerce > Settings > Shipping:
For the flat rate you can set any minimal amount (that will be overwritten by your calculations)…
If you make changes you need to refresh shipping cached data: disable, save and enable, save those methods for the current shipping zone.
3) SAVING THE SHIPPING TIME TO ORDER META DATA:
I have add some code for that and it's save in 2 custom meta fields:
One for the chosen shipping
The other for the time
4) DISPLAYING THE CHOSEN SHIPPING TYPE AND TIME IN A METABOX (IN ORDER EDIT PAGES):
I have also add some code for that.
FINALY HERE IS THE CODE:
add_action( 'woocommerce_after_order_notes', 'my_custom_checkout_field' );
function my_custom_checkout_field( $checkout ) {
// The 2 Options arrays in imput select
$delivery_time_list[''] = $takeaway_time_list[''] = __('Select an hour');
for($h = 10, $i = 0; $i < 8; $i++ ){
if( $i % 2 == 0 ){
$time = $h.':00';
$delivery_time_list[$time] = 'deliver '.$time;
} else {
$time = $h.':30';
$h++;
}
$takeaway_time_list[$time] = 'takeaway '.$time;
}
echo '<div id="delivery_checkout_fields"><h3>' . __('Shipping time options') . '</h3>';
woocommerce_form_field( 'delivery_time', array(
'type' => 'select',
'class' => array('delivery-time form-row-wide'),
'label' => __('Delivery time'),
'options' => $delivery_time_list,
), $checkout->get_value( 'delivery_time' ) );
woocommerce_form_field( 'takeaway_time', array(
'type' => 'select',
'class' => array('takeaway-time form-row-wide'),
'label' => __('Take away time'),
'options' => $takeaway_time_list,
), $checkout->get_value( 'takeaway_time' ) );
echo '</div>';
$required = esc_attr__( 'required', 'woocommerce' );
?>
<script>
jQuery(function($){
var choosenShipMethod = $('input[name^="shipping_method"]:checked').val().split(':')[0], // Choosen shipping method slug
required = '<abbr class="required" title="<?php echo $required; ?>">*</abbr>'; // Required html
// TESTING: displaying in console the choosen shipping
console.log('Chosen shipping: '+choosenShipMethod);
// Function that shows or hide imput select fields
function showHide( actionToDo='show', selector='' ){
if( actionToDo == 'show' )
$(selector).show(function(){
$(this).addClass("validate-required");
$(this).removeClass("woocommerce-validated");
$(this).removeClass("woocommerce-invalid woocommerce-invalid-required-field");
$(selector+' label').append(required);
//console.log('Selector (show): '+selector);
});
else
$(selector).hide(function(){
$(this).removeClass("validate-required");
$(this).removeClass("woocommerce-validated");
$(this).removeClass("woocommerce-invalid woocommerce-invalid-required-field");
$(selector+' label > .required').remove();
//console.log('Selector (hide): '+selector);
});
}
// Initialising at start (Based on the choosen shipping method)
if( choosenShipMethod == 'flat_rate' ) // Choosen "Delivery" (Hidding "Take away")
{
showHide('show','#delivery_time_field' );
showHide('hide','#takeaway_time_field' );
}
else if( choosenShipMethod == 'local_pickup' ) // Choosen "Take away" (Hidding "Delivery")
{
showHide('show','#takeaway_time_field' );
showHide('hide','#delivery_time_field' );
}
else // No shipping choosen yet (Hidding BOTH shipping dropdown hour selectors
{
showHide('hide','#delivery_time_field' );
showHide('hide','#takeaway_time_field' );
$('#delivery_checkout_fields').hide();
}
// When shipping method is changed (Live event)
$( 'form.checkout' ).on( 'change', 'input[name^="shipping_method"]', function() {
var changedShipMethod = $('input[name^="shipping_method"]:checked').val().split(':')[0];
if( changedShipMethod == 'flat_rate' )
{
// Choose "Delivery" | Show "Delivery" and Hide "Take away"
$('#delivery_checkout_fields').show();
showHide('show','#delivery_time_field' );
showHide('hide','#takeaway_time_field' );
}
else if( changedShipMethod == 'local_pickup' )
{
// Choose "Take away" | Show "Take away" and Hide "Delivery"
$('#delivery_checkout_fields').show();
showHide('show','#takeaway_time_field' );
showHide('hide','#delivery_time_field' );
}
console.log("Chosen shipping: "+changedShipMethod);
});
// When an hour is selected (LIVE event)
$('#delivery_checkout_fields select').change( function(){
if( $(this).val() != '')
$(this).parent().removeClass("validate-required");
else
$(this).parent().addClass("validate-required");
console.log("Selector value: "+$(this).val());
});
// "select.shipping_method, input[name^="shipping_method"], #ship-to-different-address input, .update_totals_on_change select, .update_totals_on_change input[type="radio"], .update_totals_on_change input[type="checkbox"]"
//"function (){t.reset_update_checkout_timer(),t.dirtyInput=!1,e(document.body).trigger("update_checkout")}"
});
</script>
<?php
}
// Process the checkout (Checking if required fields are not empty)
add_action('woocommerce_checkout_process', 'ba_custom_checkout_field_process');
function ba_custom_checkout_field_process() {
$delivery_time = $takeaway_time = 0;
if ( $_POST['delivery_time'] ) $delivery_time = 1;
if ( $_POST['takeaway_time'] ) $takeaway_time = 1;
// Only one message is possible for both
if ( ( $delivery_time + $takeaway_time ) == 0 ){
wc_add_notice( __('Please select a <strong>shipping time</strong>.' ), 'error');
}
}
## CALCULATING THE DELIVERY FEE (BASED ON COUNTING THE DIFFERENT DATES For all items) ##
add_filter( 'woocommerce_package_rates', 'custom_shipping_flat_rate_cost_calculation', 10, 2 );
function custom_shipping_flat_rate_cost_calculation( $rates, $package )
{
## --- CALCULATIONS Based on CART DATA (if needed) --- ##
foreach(WC()->cart->get_cart() as $cart_item ):
// HERE your incredibly complex code used to calculate delivery costs
endforeach;
## --- CHANGING DYNAMICALLY THE METHODS COSTS --- ##
foreach($rates as $rate_key => $rate_values):
$method_id = $rate_values->method_id;
$rate_id = $rate_values->id;
// "DELIVERY" - "local_pickup" method (if needed)
if ( 'flat_rate' === $method_id ){
// HERE your incredibly complex code used to calculate delivery costs
// Change the price cost
$price_excl_tax = $rates[$rate_id]->cost + 2.5;
$rates[$rate_id]->cost = number_format($price_excl_tax, 2);
$tax_calculation = $rates[$rate_id]->taxes[0] * 0.1;
$rates[$rate_id]->taxes[0] = number_format($tax_calculation, 2);
}
// "TAKE WAY" - "local_pickup" method (if needed)
elseif ( 'local_pickup' === $method_id )
{
// do something if needed
}
endforeach;
return $rates;
}
// Save the "shipping time" in order meta data
add_action( 'woocommerce_checkout_update_order_meta', 'save_shipping_time_in_order_meta', 100, 1 );
function save_shipping_time_in_order_meta( $order_id ) {
// Take away time
$takeaway_time = $_POST['takeaway_time'];
if ( ! empty( $takeaway_time ) ){
add_post_meta( $order_id, '_shipping_time', $takeaway_time );
add_post_meta( $order_id, '_shipping_type', __('Take away', 'woocommerce' ) );
}
// Delivery time
$delivery_time = $_POST['delivery_time'];
if ( ! empty( $delivery_time ) ){
add_post_meta( $order_id, '_shipping_time', $delivery_time );
add_post_meta( $order_id, '_shipping_type', __('Delivery', 'woocommerce' ) );
}
}
// Adding shipping time metabox (on right side) to Order edit pages
add_action( 'add_meta_boxes', 'add_order_shipping_time_meta_boxe' );
function add_order_shipping_time_meta_boxe(){
add_meta_box(
'woocommerce-order-shipping-time-values', __( 'Shipping type and time', 'woocommerce' ),
'order_shipping_time_values', 'shop_order', 'side', 'default'
);
}
// Adding content to shipping time metabox to Order edit pages
function order_shipping_time_values(){
global $post;
$type = get_post_meta($post->ID, '_shipping_type', true);
$time = get_post_meta($post->ID, '_shipping_time', true);
echo "<p><strong>Type:</strong> $type | <strong>time:</strong> $time</p>";
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested on WooCommerce 3+ and works.

Categories