Asked  7 Months ago    Answers:  5   Viewed   47 times

I am currently successfully adding a field to my WooCommerce product pages which is showing the value:

  • in the cart (front end),
  • on checkout page (front end),
  • on order page (front end),
  • and in admin individual order page (back end).

The problem: It isn't showing as a custom field in the admin order "custom fields" Metabox with the value inside it, but just as a text in the order page.

Here is my working code:

// Add the field to the product
add_action('woocommerce_before_add_to_cart_button', 'my_custom_checkout_field');

function my_custom_checkout_field() {
    echo '<div id="my_custom_checkout_field"><h3>'.__('My Field').'</h3>';
    echo  '<label>fill in this field</label> <input type="text" name="my_field_name">';
    echo '</div>';
}

// Store custom field
function save_my_custom_checkout_field( $cart_item_data, $product_id ) {
    if( isset( $_REQUEST['my_field_name'] ) ) {
        $cart_item_data[ 'my_field_name' ] = $_REQUEST['my_field_name'];
        /* below statement make sure every add to cart action as unique line item */
        $cart_item_data['unique_key'] = md5( microtime().rand() );
    }
    return $cart_item_data;
}
add_action( 'woocommerce_add_cart_item_data', 'save_my_custom_checkout_field', 10, 2 );

// Render meta on cart and checkout
function render_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
    $custom_items = array();
    /* Woo 2.4.2 updates */
    if( !empty( $cart_data ) ) {
        $custom_items = $cart_data;
    }
    if( isset( $cart_item['my_field_name'] ) ) {
        $custom_items[] = array( "name" => 'My Field', "value" => $cart_item['my_field_name'] );
    }
    return $custom_items;
}
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );

// This is what I think needs changing?

function subscription_order_meta_handler( $item_id, $values, $cart_item_key ) {
    if( isset( $values['my_field_name'] ) ) {
        wc_add_order_item_meta( $item_id, "My Field", $values['my_field_name'] );
    }
}
add_action( 'woocommerce_add_order_item_meta', 'subscription_order_meta_handler', 1, 3 );

I think it is this last bit of the code that needs changing. It currently shows the text under the order item, so perhaps I need to adjust wc_add_order_item_meta to something else?

I've tried everything but it doesn't seem to work. I can get it to work when my field is on the checkout page but not when I pull it from the product page.

Perhaps I am missing a checkout process snippet?

 Answers

34

UPDATE 2017/11/02 (Works perfectly in Woocommerce 3+)

First I have get everything working has expected, except getting the value for my_field_name in back end "Custom fields" Metabox within Order pages.

Then after a real nightmare, I have found a pretty nice working solution, better than before. In back end you have now a Custom metabox with the custom field my_field_name displaying the right value, like in this screenshot:

screenshot


My code is divided In 2 parts.

1) The backend Metabox in Order pages, with an editable field showing the correct value coming from a custom field on the product pages (in front end):

// Adding Meta container admin shop_order pages
add_action( 'add_meta_boxes', 'mv_add_meta_boxes' );
if ( ! function_exists( 'mv_add_meta_boxes' ) )
{
    function mv_add_meta_boxes()
    {
        add_meta_box( 'mv_other_fields', __('My Field','woocommerce'), 'mv_add_other_fields_for_packaging', 'shop_order', 'side', 'core' );
    }
}

// Adding Meta field in the meta container admin shop_order pages
if ( ! function_exists( 'mv_add_other_fields_for_packaging' ) )
{
    function mv_add_other_fields_for_packaging()
    {
        global $post;

        $meta_field_data = get_post_meta( $post->ID, '_my_field_slug', true ) ? get_post_meta( $post->ID, '_my_field_slug', true ) : '';

        echo '<input type="hidden" name="mv_other_meta_field_nonce" value="' . wp_create_nonce() . '">
        <p style="border-bottom:solid 1px #eee;padding-bottom:13px;">
            <input type="text" style="width:250px;";" name="my_field_name" placeholder="' . $meta_field_data . '" value="' . $meta_field_data . '"></p>';

    }
}

// Save the data of the Meta field
add_action( 'save_post', 'mv_save_wc_order_other_fields', 10, 1 );
if ( ! function_exists( 'mv_save_wc_order_other_fields' ) )
{

    function mv_save_wc_order_other_fields( $post_id ) {

        // We need to verify this with the proper authorization (security stuff).

        // Check if our nonce is set.
        if ( ! isset( $_POST[ 'mv_other_meta_field_nonce' ] ) ) {
            return $post_id;
        }
        $nonce = $_REQUEST[ 'mv_other_meta_field_nonce' ];

        //Verify that the nonce is valid.
        if ( ! wp_verify_nonce( $nonce ) ) {
            return $post_id;
        }

        // If this is an autosave, our form has not been submitted, so we don't want to do anything.
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
            return $post_id;
        }

        // Check the user's permissions.
        if ( 'page' == $_POST[ 'post_type' ] ) {

            if ( ! current_user_can( 'edit_page', $post_id ) ) {
                return $post_id;
            }
        } else {

            if ( ! current_user_can( 'edit_post', $post_id ) ) {
                return $post_id;
            }
        }
        // --- Its safe for us to save the data ! --- //

        // Sanitize user input  and update the meta field in the database.
        update_post_meta( $post_id, '_my_field_slug', $_POST[ 'my_field_name' ] );
    }
}

2) Front end / Back end:

• The product page custom field (front end).
• Displaying this data on cart, checkout pages and thank you order (front end).
• Displaying data on order page (back end)

// Add the field to the product
add_action('woocommerce_before_add_to_cart_button', 'my_custom_product_field');
function my_custom_product_field() {
    echo '<div id="my_custom_field">
        <label>' . __( 'My Field') . ' </label>
        <input type="text" name="my_field_name" value="">
    </div><br>';
}

// Store custom field
add_filter( 'woocommerce_add_cart_item_data', 'save_my_custom_product_field', 10, 2 );
function save_my_custom_product_field( $cart_item_data, $product_id ) {
    if( isset( $_REQUEST['my_field_name'] ) ) {
        $cart_item_data[ 'my_field_name' ] = $_REQUEST['my_field_name'];
        // below statement make sure every add to cart action as unique line item
        $cart_item_data['unique_key'] = md5( microtime().rand() );
        WC()->session->set( 'my_order_data', $_REQUEST['my_field_name'] );
    }
    return $cart_item_data;
}

// Add a hidden field with the correct value to the checkout
add_action( 'woocommerce_after_order_notes', 'my_custom_checkout_field' );
function my_custom_checkout_field( $checkout ) {
    $value = WC()->session->get( 'my_order_data' );
    echo '<div id="my_custom_checkout_field">
            <input type="hidden" class="input-hidden" name="my_field_name" id="my_field_name" value="' . $value . '">
    </div>';
}

// Save the order meta with hidden field value
add_action( 'woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta' );
function my_custom_checkout_field_update_order_meta( $order_id ) {
    if ( ! empty( $_POST['my_field_name'] ) ) {
        update_post_meta( $order_id, '_my_field_slug', $_POST['my_field_name'] );
    }
}

// Display field value on the order edit page (not in custom fields metabox)
add_action( 'woocommerce_admin_order_data_after_billing_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 );
function my_custom_checkout_field_display_admin_order_meta($order){
    $my_custom_field = get_post_meta( $order->id, '_my_field_slug', true );
    if ( ! empty( $my_custom_field ) ) {
        echo '<p><strong>'. __("My Field", "woocommerce").':</strong> ' . get_post_meta( $order->id, '_my_field_slug', true ) . '</p>';
    }
}

// Render meta on cart and checkout
add_filter( 'woocommerce_get_item_data', 'render_meta_on_cart_and_checkout', 10, 2 );
function render_meta_on_cart_and_checkout( $cart_data, $cart_item = null ) {
    $custom_items = array();
    if( !empty( $cart_data ) ) $custom_items = $cart_data;

    if( isset( $cart_item['my_field_name'] ) )
        $custom_items[] = array( "name" => 'My Field', "value" => $cart_item['my_field_name'] );

    return $custom_items;
}

// Add the information as meta data so that it can be seen as part of the order
add_action('woocommerce_add_order_item_meta','add_values_to_order_item_meta', 10, 3 );
function add_values_to_order_item_meta( $item_id, $cart_item, $cart_item_key ) {
    // lets add the meta data to the order (with a label as key slug)
    if( ! empty( $cart_item['my_field_name'] ) )
        wc_add_order_item_meta($item_id, __('My field label name'), $cart_item['my_field_name'], true);
}

Everything is working as expected now.

Wednesday, March 31, 2021
 
conmen
answered 7 Months ago
49

Overriding core files is something prohibited for developers. So this is not the correct way to do it.

The way to do it is using the available hooks in the source code, instead of overriding this core files, as you will loose everything when the plugin will be updated.

  1. Replace all original core files
  2. Add this code instead (I have make some minor necessary changes).

Here is the replacement code + a hook to save the data to the order meta data:

add_action( 'woocommerce_admin_order_data_after_order_details', 'custom_code_after_order_details', 10, 1 );
function custom_code_after_order_details ( $order ) {
    // Get custom field value from '_vendor' meta key
    $value = $order->get_meta('_vendor');
    ?>
    <p> <label for="order_status">???? </label>
    <select name="vendor">
    <?php  global $wpdb;
        $user_count = $wpdb->get_var( "SELECT COUNT(*) FROM $wpdb->users" );
        echo '<option value="">Select a vendor</option>';
        for ( $i=1; $i<=$user_count; $i++ ) {
            $user_info = get_userdata($i);
            if ( in_array('vendor', $user_info->roles) ){
                $user_login = $user_info->user_login;
                $selected = $value == $user_login ? 'selected' : '';
                echo '<option '.$selected.' value="'.$user_login.'">'.$user_login.'</option>';
            }
        }
    ?>
    </select></p>
    <input type="hidden" name="custom_select_field_nonce" value="<?php echo wp_create_nonce(); ?>">
    <?php
}

add_action( 'save_post', 'save_custom_code_after_order_details', 10, 1 );
function save_custom_code_after_order_details( $post_id ) {

    // We need to verify this with the proper authorization (security stuff).

    // Check if our nonce is set.
    if ( ! isset( $_POST[ 'custom_select_field_nonce' ] ) ) {
        return $post_id;
    }
    $nonce = $_REQUEST[ 'custom_select_field_nonce' ];

    //Verify that the nonce is valid.
    if ( ! wp_verify_nonce( $nonce ) ) {
        return $post_id;
    }

    // If this is an autosave, our form has not been submitted, so we don't want to do anything.
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return $post_id;
    }

    // Check the user's permissions.
    if ( 'page' == $_POST[ 'post_type' ] ) {

        if ( ! current_user_can( 'edit_page', $post_id ) ) {
            return $post_id;
        }
    } else {

        if ( ! current_user_can( 'edit_post', $post_id ) ) {
            return $post_id;
        }
    }

    // Update the meta field in the database.
    update_post_meta( $post_id, '_vendor', $_POST[ 'vendor' ] );
}

Code goes in function.php file of your active child theme (or theme) or also in any plugin file.

This code is tested and works.

Saturday, May 29, 2021
 
ojrac
answered 5 Months ago
85

There is some errors and mistakes in your code. try the following instead:

// Output a custom editable field in backend edit order pages under general section
add_action( 'woocommerce_admin_order_data_after_order_details', 'editable_order_custom_field', 12, 1 );
function editable_order_custom_field( $order ){
    // Loop through order items
    foreach( $order->get_items() as $item_id => $item ){
        // Get "customer reference" from order item meta data
        if( $item->get_meta('Your Reference') ){
            // The "customer reference"
            $item_value = $item->get_meta('Your Reference');

            // We output a hidden field with the Item ID (to be able to update this order item meta data later)
            echo '<input type="hidden" name="item_id_ref" value="' . $item_id . '">';

            break; // We stop the loop
        }
    }

    // Get "customer reference" from meta data (not item meta data)
    $updated_value = $order->get_meta('_customer_ref');

    // Replace "customer reference" value by the meta data if it exist
    $value = $updated_value ? $updated_value : ( isset($item_value) ? $item_value : '');

    // Display the custom editable field
    woocommerce_wp_text_input( array(
        'id'            => 'customer_ref',
        'label'         => __("Customer Reference:", "woocommerce"),
        'value'         => $value,
        'wrapper_class' => 'form-field-wide',
    ) );
}

// Save the custom editable field value as order meta data and update order item meta data
add_action( 'woocommerce_process_shop_order_meta', 'save_order_custom_field_meta_data', 12, 2 );
function save_order_custom_field_meta_data( $post_id, $post ){
    if( isset( $_POST[ 'customer_ref' ] ) ){
        // Save "customer reference" as order meta data
        update_post_meta( $post_id, '_customer_ref', sanitize_text_field( $_POST[ 'customer_ref' ] ) );

        // Update the existing "customer reference" item meta data
        if( isset( $_POST[ 'item_id_ref' ] ) )
            wc_update_order_item_meta( $_POST[ 'item_id_ref' ], 'Your Reference', $_POST[ 'customer_ref' ] );
    }
}

Code goes in function.php file of your active child theme (or theme). tested and works.

enter image description here

Saturday, May 29, 2021
 
NaeiKinDus
answered 5 Months ago
53

This is a working solution for WooCommerce 2.6+ to extend and manipulate the tabbed "My Account" page endpoints (See this reference at the end of this answer), so here it is what you can do to achieve this:

add_action( 'init', 'custom_new_wc_endpoint' );
function custom_new_wc_endpoint() {
    add_rewrite_endpoint( 'edit-order', EP_ROOT | EP_PAGES );
}

add_filter( 'query_vars', 'custom_query_vars', 0 );
function custom_query_vars( $vars ) {
    $vars[] = 'edit-order';
    return $vars;
}

add_action( 'after_switch_theme', 'custom_flush_rewrite_rules' );    
function custom_flush_rewrite_rules() {
    flush_rewrite_rules();
}

// The custom template location
add_action( 'woocommerce_account_edit-order_endpoint', 'custom_endpoint_content' );
function custom_endpoint_content() {
    include 'woocommerce/myaccount/edit-order.php'; 
}

Then you will need, to Insert the new Edit order endpoint into the My Account menu:

add_filter( 'woocommerce_account_menu_items', 'custom_my_account_menu_items' );
function custom_my_account_menu_items( $items ) {
    // Remove the orders menu item.
    $orders_item = $items['orders']; // first we keep it in a variable
    unset( $items['orders'] ); // we unset it then

    // Insert your custom endpoint.
    $items['edit-order'] = __( 'Edit Order', 'woocommerce' );

    // Insert back the logout item.
    $items['orders'] = $orders_item; // we set it back

    return $items;
}

Important: You will need to flush the rewrite rules (2 ways):

  • Go to the Permalinks options page and re-save the permalinks (thanks to helgatheviking)
  • You can also disable/enable your theme.

References:

  • Tabbed My Account page (WC 2.6+): Creating new endpoints

  • WooCommerce: Assigning an endpoint to a custom template in my account pages

  • How to add a new endpoint in woocommerce (old and incomplete)

Thursday, July 29, 2021
 
Isky
answered 3 Months ago
42

The correct hook to get this done is woocommerce_admin_order_preview_actions filter hook.

You will need to define in the function below in a multidimensional array your custom order statuses data, one by one as follow, to get an action button for each:

  • The status slug (without starting with "wc-") as a key
  • The status label name
  • The allowed statuses array slugs (to display the current status action button for)

The example code (here for 2 custom fake statuses "Custom One" and "Custom two"):

add_filter( 'woocommerce_admin_order_preview_actions', 'additional_admin_order_preview_buttons_actions', 25, 2 );
function additional_admin_order_preview_buttons_actions( $actions, $order ){
    // Below set your custom order statuses (key / label / allowed statuses) that needs a button
    $custom_statuses = array(
        'custom_one' => array( // The key (slug without "wc-")
            'label'     => __("Custom One", "woocommerce"), // Label name
            'allowed'   => array( 'pending', 'on-hold', 'processing', 'custom_two' ), // Button displayed for this statuses (slugs without "wc-")
        ),
        'custom_two' => array( // The key (slug without "wc-")
            'label'     => __("Custom Two", "woocommerce"), // Label name
            'allowed'   => array( 'pending', 'on-hold', 'processing', 'custom_one' ), // Button displayed for this statuses (slugs without "wc-")
        ),
    );

    // Loop through your custom orders Statuses
    foreach ( $custom_statuses as $status_slug => $values ){
        if ( $order->has_status( $values['allowed'] ) ) {
            $actions['status']['actions'][$status_slug] = array(
                'url'    => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status='.$status_slug.'&order_id=' . $order->get_id() ), 'woocommerce-mark-order-status' ),
                'name'   => $values['label'],
                'title'  => __( 'Change order status to', 'woocommerce' ) . ' ' . strtolower($values['label']),
                'action' => $status_slug,
            );
        }
    }
    return $actions;
}

Code goes in function.php file of your active child theme (active theme). Tested and works.

enter image description here

Friday, August 20, 2021
 
steros
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share