MdMasud

WordPress, Laravel, Flutter

Category: Wordpress

  • The Ultimate Guide to WooCommerce Order Status Tweaks: Custom Statuses, Editable Orders, Custom Fields and Order Table Columns

    Managing orders in WooCommerce is flexible, but many developers need extra control beyond the default workflow. This guide brings together several advanced yet practical customisations you can apply to WooCommerce, including adding custom order statuses, setting a default status, making custom statuses editable, adding custom fields to the order details page, and creating custom columns in the order list table.

    This is a complete, step-by-step guide with real working code. All examples use standard WordPress and WooCommerce hooks, and are written for small and medium-sized custom shop workflows.


    1. Adding Custom Order Statuses in WooCommerce

    WooCommerce has built-in statuses such as Pending, Processing, Completed and Cancelled. However, many businesses need more tailored stages, such as Price Request or Customer Visit.

    To add custom statuses, use register_post_status():

    add_action('init', 'add_more_status_to_order');
    
    function add_more_status_to_order()
    {
        register_post_status('wc-price-request', [
            'label'                     => _x('Price request', 'Order status', 'my-domain'),
            'public'                    => true,
            'exclude_from_search'       => false,
            'show_in_admin_all_list'    => true,
            'show_in_admin_status_list' => true,
            'label_count'               => _n_noop('Price request <span class="count">(%s)</span>', 'Price request <span class="count">(%s)</span>', 'my-domain'),
        ]);
    
        register_post_status('wc-quoted', [
            'label'                     => _x('Quoted', 'Order status', 'my-domain'),
            'public'                    => true,
            'exclude_from_search'       => false,
            'show_in_admin_all_list'    => true,
            'show_in_admin_status_list' => true,
            'label_count'               => _n_noop('Quoted <span class="count">(%s)</span>', 'Quoted <span class="count">(%s)</span>', 'my-domain'),
        ]);
    }


    These statuses behave just like normal WooCommerce statuses, including appearing in reports and the admin table if configured.


    2. Displaying Custom Statuses in the WooCommerce Status Dropdown


    After registering new statuses, WooCommerce will not show them in the order dropdown unless you merge them manually.

    add_filter('wc_order_statuses', 'show_new_statuses_in_dropdown', 99, 1);
    
    function show_new_statuses_in_dropdown($statuses)
    {
        return array_merge(
            ['wc-price-request' => __('Price request', 'my-domain')],
            ['wc-pending-pricing' => __('Pending pricing', 'my-domain')],
            ['wc-quoted' => __('Quoted', 'my-domain')],
            ['wc-customer-visit' => __('Customer visit', 'my-domain')],
            $statuses
        );
    }


    This ensures your statuses appear alongside the built-in ones on the order details screen.


    3. Setting a Custom Default Order Status


    By default, new orders in WooCommerce start as Pending Payment. For some businesses, this is not suitable. You can override this behaviour and set your own default status.

    add_action('woocommerce_new_order', 'set_default_order_status', 99, 1);
    
    function set_default_order_status($order_id)
    {
        $order = wc_get_order($order_id);
    
        if ($order && $order->get_status() === 'pending') {
            $order->update_status('price-request', 'Default status.');
        }
    }


    Now, every new order will automatically be marked as Price Request instead of Pending.


    4. Allowing Orders with Custom Statuses to Be Editable

    Many custom statuses cannot be edited by default. WooCommerce only allows editing in a few core statuses.

    Use this filter to allow editing in your custom statuses:

    add_filter('wc_order_is_editable', 'add_statuses_to_order_be_editable',10, 2);
    
    function add_statuses_to_order_be_editable($editable, $order)
    {
        $editable_status = [
            'price-request' => true,
            'pending-pricing' => true,
            'quoted'=> true,
            'customer-visit' => true
        ];
    
        if ( current_user_can("manage_options")) {
            if ($order && $editable_status[$order->get_status()]) {
                return true;
            }
        }
    
        return $editable;
    }


    This allows administrators to make changes to orders using these statuses.


    5. Adding Custom Fields to the WooCommerce Order Details Page


    Sometimes you need to store extra data per order, such as a visit date. You can add a custom field directly to the order details box inside the admin area.


    Display the custom field

    add_action( 'woocommerce_admin_order_data_after_order_details', 'add_custom_field_for_order_details', 10, 1 );
    
    function add_custom_field_for_order_details($order)
    {
        $meta_key = '_customer_visit_date_time';
        $prev_value = get_post_meta($order->get_id(), $meta_key, true);
    
        $field_value_date = '';
        $field_value_hour = '';
        $field_value_minute = '';
    
        if ($prev_value) {
            $field_value_date = date('Y-m-d', strtotime($prev_value));
            $field_value_hour = date('h', strtotime($prev_value));
            $field_value_minute = date('i', strtotime($prev_value));
        }
        ?>
        <p class="form-field form-field-wide wc-order-vist-date">
            <label for="customer_visit_date">Visit Date:</label>
            <input type="text" class="date-picker" placeholder="yyyy-mm-dd" 
                   name="visit_date_date" maxlength="10" value="<?= $field_value_date ?>">
            @
            <input type="number" class="hour" placeholder="h"
                   name="visit_date_hour" min="0" max="23"
                   value="<?= $field_value_hour ?>">
            :
            <input type="number" class="minute" placeholder="m"
                   name="visit_date_minute" min="0" max="59"
                   value="<?= $field_value_minute ?>">
            <br/><span class="description">Select a date and time (24h format)</span>
        </p>
        <?php
    }


    Saving the custom field

    add_action('woocommerce_process_shop_order_meta', 'save_custom_fields_for_order_details');
    
    function save_custom_fields_for_order_details($order_id)
    {
        $order = wc_get_order($order_id);
    
        $visit_date_date = wc_clean($_POST['visit_date_date']);
        $visit_date_hour = wc_clean($_POST['visit_date_hour']);
        $visit_date_minute = wc_clean($_POST['visit_date_minute']);
    
        $visit_date_time = $visit_date_date.' '.$visit_date_hour.':'.$visit_date_minute;
    
        $order->update_meta_data('_customer_visit_date_time', $visit_date_time);
        $order->save();
    }


    This stores the value as standard order meta.


    6. Adding Custom Columns in the WooCommerce Orders List Table


    To make your workflow faster, you may want to see custom information directly in the orders list in the admin dashboard. You can add new columns and populate them with meta values.


    Add a new column

    add_filter('manage_edit-shop_order_columns', 'update_shop_order_columns', 20, 1);
    
    function update_shop_order_columns($columns)
    {
        $new_columns = array();
    
        foreach ($columns as $key => $label) {
            if ('order_date' === $key) {
                $label = __('Order Date', 'my-domain');
            }
    
            $new_columns[$key] = $label;
    
            if ('order_status' === $key) {
                $new_columns['visit_date'] = __('Visit Date', 'my-domain');
            }
        }
    
        return $new_columns;
    }


    Display the value in the column

    add_filter('manage_shop_order_posts_custom_column', 'update_shop_order_columns_content', 20, 2);
    
    function update_shop_order_columns_content($column, $order_post_id)
    {
        if ('visit_date' !== $column) {
            return;
        }
    
        $visit_date = get_post_meta($order_post_id, '_customer_visit_date_time', true);
    
        if (!$visit_date) {
            echo '<span>Add now</span>';
            return;
        }
    
        $timestamp = strtotime($visit_date);
    
        if ($timestamp) {
            echo '<span style="color:#008a00;">'.date('Y-m-d @ H:i', $timestamp).'</span>';
        } else {
            echo '<span style="color:#d63638;">Not valid</span>';
        }
    }


    This displays the date in green when valid, and a warning in red if not.

    This guide brings together a full set of WooCommerce order management enhancements that you can use to build a more tailored workflow:

    • Adding custom order statuses
    • Showing them in the admin dropdown
    • Setting a custom default order status
    • Keeping orders editable in specific statuses
    • Adding custom fields to order details
    • Showing those fields in the order list table

  • How to add and save Custom Fields in the WooCommerce Order Details page


    Sometimes you need to store extra information for an order, such as a customer visit date. WooCommerce allows you to add your own fields to the order edit screen and save that information as order meta.


    Adding a Custom Date and Time Field

    add_action( 'woocommerce_admin_order_data_after_order_details', 'add_custom_field_for_order_details', 10, 1 );
    
    function add_custom_field_for_order_details($order)
    {
        $meta_key = '_customer_visit_date_time';
        $prev_value = get_post_meta($order->get_id(), $meta_key, true);
    
        $field_value_date = '';
        $field_value_hour = '';
        $field_value_minute = '';
    
        if ($prev_value) {
            $field_value_date = date('Y-m-d', strtotime($prev_value));
            $field_value_hour = date('h', strtotime($prev_value));
            $field_value_minute = date('i', strtotime($prev_value));
        }
        ?>
        <p class="form-field form-field-wide wc-order-vist-date">
            <label for="customer_visit_date">Visit Date:</label>
            <input type="text" class="date-picker" placeholder="yyyy-mm-dd"
                   name="visit_date_date" maxlength="10" value="<?= $field_value_date ?>">
            @
            <input type="number" class="hour" placeholder="h" name="visit_date_hour"
                   min="0" max="23" value="<?= $field_value_hour ?>">
            :
            <input type="number" class="minute" placeholder="m" name="visit_date_minute"
                   min="0" max="59" value="<?= $field_value_minute ?>">
            <br/><span class="description">Select a date and time (24h format)</span>
        </p>
        <?php
    }


    Saving the Custom Field

    add_action('woocommerce_process_shop_order_meta', 'save_custom_fields_for_order_details');
    
    function save_custom_fields_for_order_details($order_id)
    {
        $order = wc_get_order($order_id);
    
        $visit_date_date = wc_clean($_POST['visit_date_date']);
        $visit_date_hour = wc_clean($_POST['visit_date_hour']);
        $visit_date_minute = wc_clean($_POST['visit_date_minute']);
    
        $visit_date_time = $visit_date_date.' '.$visit_date_hour.':'.$visit_date_minute;
    
        $order->update_meta_data('_customer_visit_date_time', $visit_date_time);
        $order->save();
    }


    Adding custom fields to the WooCommerce order details screen is straightforward and allows you to store important data directly with each order.

  • How to Allow WooCommerce Orders with Custom Statuses to Stay Editable

    By default custom statuses prevent the order from being edited.
    WooCommerce only allows editing in a few built-in statuses, but you can override this and allow editing for your own workflow.


    Allow Editing in Custom Statuses

    add_filter('wc_order_is_editable', 'add_statuses_to_order_be_editable',10, 2);
    
    function add_statuses_to_order_be_editable($editable, $order)
    {
        $editable_status = [
            'price-request' => true,
            'pending-pricing' => true,
            'quoted'=> true,
            'customer-visit' => true
        ];
    
        if ( current_user_can("manage_options")) {
            if ($order && $editable_status[$order->get_status()]) {
                return true;
            }
        }
        return $editable;
    }


    This allows users with admin rights to edit orders even when they are in one of the custom statuses.


    If your order lifecycle requires edits at different steps, enabling edit access for custom statuses ensures your workflow stays smooth.

  • How to add WooCommerce custom Order Statuses and display them in the dropdown


    WooCommerce will not automatically show your custom statuses in the admin dropdown unless you explicitly add them. Below you can find how to register the statuses and make them appear in the status selection dropdown inside the order details screen.


    Showing Your Statuses in the Admin Dropdown

    add_filter('wc_order_statuses', 'show_new_statuses_in_dropdown', 99, 1);
    
    function show_new_statuses_in_dropdown($statuses)
    {
        return array_merge(
            ['wc-price-request' => __('Price request', 'my-domain')],
            ['wc-pending-pricing' => __('Pending pricing', 'my-domain')],
            ['wc-quoted' => __('Quoted', 'my-domain')],
            ['wc-customer-visit' => __('Customer visit', 'my-domain')],
            $statuses
        );
    }


    This merges your custom statuses with the existing ones in a clean way.


    Once your statuses are registered and added to the dropdown, your WooCommerce order management becomes more flexible and easier to track.

  • How to add a custom Order Status in WooCommerce and set it as the default

    WooCommerce has default order statuses such as Pending Payment, Processing, and Completed.
    However, Some online businesses need their own custom workflow. For example, you may want new orders to start in a “Price Request” status instead of “Pending”.

    In this guide, I explain how to register new order statuses and set one of them as the default when an order is created.


    Registering custom Order Statuses

    WooCommerce uses register_post_status() to add new statuses.
    Below we add four custom statuses to WordPress:

    add_action('init', 'add_more_status_to_order');
    
    function add_more_status_to_order()
    {
        register_post_status('wc-price-request', [
            'label'                     => _x('Price request', 'Order status', 'my-domain'),
            'public'                    => true,
            'exclude_from_search'       => false,
            'show_in_admin_all_list'    => true,
            'show_in_admin_status_list' => true,
            'label_count'               => _n_noop('Price request <span class="count">(%s)</span>', 'Price request <span class="count">(%s)</span>', 'my-domain'),
        ]);
    
        register_post_status('wc-quoted', [
            'label'                     => _x('Quoted', 'Order status', 'my-domain'),
            'public'                    => true,
            'exclude_from_search'       => false,
            'show_in_admin_all_list'    => true,
            'show_in_admin_status_list' => true,
            'label_count'               => _n_noop('Quoted <span class="count">(%s)</span>', 'Quoted <span class="count">(%s)</span>', 'my-domain'),
        ]);
    
        register_post_status('wc-pending-pricing', [
            'label'                     => _x('Pending pricing', 'Order status', 'my-domain'),
            'public'                    => true,
            'exclude_from_search'       => false,
            'show_in_admin_all_list'    => true,
            'show_in_admin_status_list' => true,
            'label_count'               => _n_noop('Pending pricing <span class="count">(%s)</span>', 'Pending pricing <span class="count">(%s)</span>', 'my-domain'),
        ]);
    }


    Setting a Custom Default Order Status

    By default, WooCommerce sets all new orders to “Pending”.
    If you want every new order to start as “Price Request”, use this hook:

    add_action('woocommerce_new_order', 'set_default_order_status', 99, 1);
    
    function set_default_order_status($order_id)
    {
        $order = wc_get_order($order_id);
        if ($order && $order->get_status() === 'pending') {
            $order->update_status('price-request', 'Default status.');
        }
    }


    This helps when your business process is different from a standard retail store.


    By registering custom statuses and setting one as the default, you can build an order workflow that matches the business requirements.

  • How to Group Hierarchical WordPress Taxonomy Terms in a Select Dropdown Without Extra SQL Queries

    How to Group Hierarchical WordPress Taxonomy Terms in a Select Dropdown Without Extra SQL Queries

    If you’re building a custom dropdown menu for a hierarchical taxonomy in WordPress — for example, letting users choose a role or category — you might want to:

    • Show parent terms as group labels (like <optgroup>)
    • Show **child terms as selectable <option>s
    • Include terms that aren’t attached to any posts
    • Avoid running SQL queries inside loops

    Seems simple, right? But it’s trickier than it looks.

    Let me show you the cleanest way to build it — no unnecessary queries, no missing terms, and no over-complicated logic.

    The Issue

    If you use get_terms() and loop through the results, it’s tempting to grab each parent and then call get_terms() again to fetch its children.

    $parents = get_terms([... 'parent' => 0 ...]);
    
    foreach ($parents as $parent) {
        $children = get_terms([... 'parent' => $parent->term_id ...]); // Inefficient
    }
    

    This works — but it creates a new SQL query for every parent term. That’s a classic performance problem, especially on larger sites.

    The Efficient Solution

    Instead, just call get_terms() once — and group the results in PHP.

    Here’s the 10-line approach that avoids all the extra queries:

    $grouped_terms = [];
    $terms = get_terms(['taxonomy' => 'your_taxonomy', 'hide_empty' => false]);
    
    if ($terms) {
        foreach ($terms as $term) {
            if ($term->parent == 0) {
                $grouped_terms[$term->term_id]['name'] = $term->name;
            } else {
                $grouped_terms[$term->parent]['children'][] = $term;
            }
        }
    }
    

    Its better way

    • Only one query to fetch all terms (parents + children)
    • No need to pre-sort the results
    • If a child term’s parent wasn’t processed yet, that’s okay — it’ll still get grouped
    • Even unused terms (not attached to posts) are included thanks to 'hide_empty' => false

    Rendering the Select Dropdown

    Now that you’ve grouped your terms, render the dropdown with <optgroup> for parents and <option> for children:

    echo '<select name="your_field_name">';
    foreach ($grouped_terms as $group) {
        if (empty($group['children'])) continue;
    
        $label = $group['name'] ?? 'Other';
    
        echo '<optgroup label="' . esc_html($label) . '">';
        foreach ($group['children'] as $child) {
            echo '<option value="' . esc_attr($child->term_id) . '">' . esc_html($child->name) . '</option>';
        }
        echo '</optgroup>';
    }
    echo '</select>';
    

    This gives you a clean, user-friendly dropdown grouped by parent terms.

    Use Cases

    • Admin filters in meta boxes
    • Frontend submission forms
    • Custom post type selectors (e.g. “Job Roles”, “Product Categories”)
    • Anywhere you want a taxonomy-based grouped select

    When working with WordPress taxonomies, efficiency matters. Avoiding unnecessary SQL calls can make your plugins or themes faster, lighter, and more scalable.

    This technique helps you build a clean, scalable dropdown for any hierarchical taxonomy — with zero extra queries, no missing terms, and just 10 lines of code.

  • DataTables in WordPress: Use Callback Data to Calculate Totals and Update the Page (Frontend‑Only)

    DataTables in WordPress: Use Callback Data to Calculate Totals and Update the Page (Frontend‑Only)

    When you’re building dashboards in WordPress, you often need to use the data currently loaded into your DataTable—not just display it. This post shows how to sum values from all rows (across pages), split totals by Payment Method (Cash vs Card), and push those numbers into elements elsewhere on the page using a reusable callback function.

    What you’ll achieve

    • Fetch table data via AJAX (already set up in your site).
    • Initialise DataTables in WordPress.
    • Use a separate, reusable function to:
      • Iterate all rows, ignoring pagination.
      • Parse currency like £45.00.
      • Update three on‑page counters: #log_cash, #log_card, #log_total.

    HTML markup

    <div class="table-container">
      <table id="todaysLogs" class="table display" style="width:100%">
        <thead>
          <tr>
            <th>Time</th>
            <th>Driver</th>
            <th>Sold By</th>
            <th>Amount</th>
            <th>Discount</th>
            <th>Payment Method</th>
            <th>Action</th>
          </tr>
        </thead>
      </table>
    </div>
    
    <!-- Totals display anywhere on the page -->
    <div class="totals">
      <div>Cash: <strong id="log_cash">£0.00</strong></div>
      <div>Card: <strong id="log_card">£0.00</strong></div>
      <div>Total: <strong id="log_total">£0.00</strong></div>
    </div>
    

    Reusable totals function + DataTable initialisation

    function updateLogTotals(tableApi) {
        var parseValue = function (val) {
    	if (typeof val === 'string') {
    		return parseFloat(val.replace(/[£,]/g, '')) || 0;
    	}
    	return typeof val === 'number' ? val : 0;
       };
       var totalCash = 0;
       var totalCard = 0;
       tableApi.rows().every(function () {
            var row = this.data();
    	var value  = row[3];
    	var type   = row[5];
    	if (type === 'Cash') {
    		totalCash += parseValue(value);
    	} else {
    		totalCard += parseValue(value);
    	}
       });
    
       $('#log_cash').text('£' + totalCash.toFixed(2));
       $('#log_card').text('£' + totalCard.toFixed(2));
       $('#log_total').text('£' + (totalCash + totalCard).toFixed(2));
    }
    
    var todaysLogsTable = $('#todaysLogs').DataTable({
    	dom: 'Bfrtip',
    	pageLength: 9,
    	buttons: [
    		'copyHtml5',
    		'excelHtml5',
    		'csvHtml5',
    		'pdfHtml5'
    	],
    	columns: [
    		null,
    		null,
    		null,
    		null,
    		null,
    		null,
    		{ orderable: false }
    	],
    	ajax: {
    		url: ajaxurl + '?action=pos&post_action=get_todays_log'
    		},
    		responsive: true,
    		drawCallback: function (settings) {
    			updateLogTotals(this.api());
    		}
    });

    Make it truly reusable

    You can call updateLogTotals(todaysLogsTable) from anywhere—buttons, tabs, or additional DataTables events:

    $('#todaysLogs').on('search.dt order.dt page.dt', function () {
        updateLogTotals(todaysLogsTable);
    });
    

    Only count visible (filtered) rows? Swap:

    tableApi.rows().every(…)

    for:

    tableApi.rows({ filter: 'applied' }).every(…)
    

    Currency parsing

    parseFloat(val.replace(/[£,]/g, ''))
    • If your data can include other currencies or locale formats, either:
      • Normalise in your renderer (e.g. store a hidden numeric value and display £ in the cell), or
      • Expand the regex/logic to handle your formats.

    Troubleshooting

    • Totals don’t change: Ensure your callback runs on drawCallback or bind to search.dt/order.dt/page.dt.
    • NaN totals: Check the column indexes and the currency format in your cells.
    • Wrong values: Confirm your table’s JSON returns the columns in the order you expect.

    Takeaway

    With a small reusable function and DataTables’ API, you can read current table data on the client and push real‑time totals into any element on your WordPress page—no page refreshes, no backend edits required here.

  • How to Keep Dropdown Submenu Open After Gravity Form AJAX Submit

    While working on a WordPress site, I embedded a Gravity Form inside a dropdown submenu of the navigation menu. The submenu appears on hover, and inside it, users can submit an enquiry form via Gravity Forms using AJAX.

    Everything worked fine, until the form was successfully submitted.

    Good : If validation failed, the form stayed open and errors were visible.

    Bad : But when the form successfully submitted, the confirmation message would briefly flash — and the submenu would immediately disappear, making it look like nothing happened.

    This wasn’t a great user experience, especially if users didn’t realise their form had gone through.

    Why It Happens

    When Gravity Forms submits via AJAX, it dynamically replaces the form’s content with the confirmation message. This causes a DOM update that disrupts the hover state of the submenu, especially if it’s tied to :hover CSS.

    So, the submenu disappears — not because of an error, but because the browser no longer sees the user as “hovering” during or right after the form update.

    The Solution

    The fix is to manually reapply a class to keep the submenu open when the form is successfully submitted, then remove that class when the user moves the mouse away.

    You don’t need any complex JavaScript frameworks — just a few lines of jQuery and a little CSS.

    jQuery(document).ready(function($) {
     var menuItem = $('#menu-item-1798'); // ID of the parent menu item
    
     // When the form is successfully submitted via AJAX
     $(document).on('gform_confirmation_loaded', function(event, formId) {
      if (formId === 5) { // Replace with your actual form ID
       menuItem.addClass('keep-open');
      }
     });
    
     // When user moves their mouse out of the menu item, close it
     menuItem.on('mouseleave', function() {
      if (menuItem.hasClass('keep-open')) {
       menuItem.removeClass('keep-open');
      }
     });
    });

    Optional CSS

    Add proper style to your stylesheet to ensure the menu stays visible when the keep-open class is active.

    The Output

    With this small enhancement:

    • The submenu remains open after a successful Gravity Form submission.
    • Users can clearly see the confirmation message.
    • The menu closes cleanly once the user moves away.

     It works beautifully without any plugin overhead or redesigns.

    Have you run into a similar problem? Got a cleaner approach ? Drop your thoughts in the comments!