Add a Custom Field to WooCommerce Order Items - php

Currently, WooCommerce Order Items within an order, have the following columns:
Item
Tax Class
Qty
Totals
Tax
See screenshot
What I'm aiming to do is add an extra column (or meta) which has a dropdown field for Product Status.
Does anyone have any ideas on how I might accomplish this?

In case someone else stumbles upon this, I needed to add some custom order meta to the order items meta box on the edit order screen. In 2017 this is how I solved this dilemma.
The file class-wc-meta-box-order-items.php, found under includes/admin/meta-boxes has changed slightly since 2014 but it does include the template file html-order-items.php.
It's in that last file that you will find two undocumented hooks, woocommerce_admin_order_item_headers, which you would use to add your custom column heading text and has access to the $order object and woocommerce_admin_order_item_values, which places your custom content right before the Cost column and has access to $product, $item and $item_id.
So to add a custom column it would look something like this.
add_action( 'woocommerce_admin_order_item_headers', 'pd_admin_order_items_headers' );
function pd_admin_order_items_headers($order){
?>
<th class="line_customtitle sortable" data-sort="your-sort-option">
Custom Title
</th>
<?php
}
where your-sort-option depends on what data you are wanting to sort. I used string-ins in my situation.
Than for the content in each line item you would have.
add_action( 'woocommerce_admin_order_item_values', 'pd_admin_order_item_values' );
function pd_admin_order_item_values( $product, $item, $item_id ) {
//Get what you need from $product, $item or $item_id
?>
<td class="line_customtitle">
<?php //your content here ?>
</td>
<?php
}
There are a few other hooks and filters in that template file that are definitely worth looking at if you need content in different places within that meta-box.

I am in the middle of some major adjustments to this table as well, and I haven't figured it all out yet, but I know this much, if you review the class-wc-meta-box-order-items.php within the plugin directory, you will find this snippet of code:
// List order items
$order_items = $order->get_items( apply_filters( 'woocommerce_admin_order_item_types', array( 'line_item', 'fee' ) ) );
foreach ( $order_items as $item_id => $item ) {
switch ( $item['type'] ) {
case 'line_item' :
$_product = $order->get_product_from_item( $item );
$item_meta = $order->get_item_meta( $item_id );
include( 'views/html-order-item.php' );
break;
case 'fee' :
include( 'views/html-order-fee.php' );
break;
}
do_action( 'woocommerce_order_item_' . $item['type'] . '_html', $item_id, $item );
}
DO NOT EDIT THIS SNIPPET!!!
From here you can begin to see what makes up that table. I believe (from what I know of WooCommerce so far) is you can create a function that hooks in at the filter there: woocommerce_admin_order_item_types();
That's what I would do something like this:
# Admin Panel Updates
add_filter( 'woocommerce_add_order_item_meta', array( $this, 'display_order_item_meta' ), 10, 2 );
public function display_order_item_meta( $order_items ) {
array_push($order_items,'event'); //Not sure how to manipulate this yet
return $order_items;
}
Also it looks like we would need to do something with the action hook (woocommerce_order_item_' . $item['type'] . '_html', $item_id, $item), perhaps something along these lines:
add_filter( 'woocommerce_order_item_event_html', array( $this, 'display_event_item_meta' ), 10, 2 );
public function display_event_item_meta( $item_id, $item) {
switch ( $item['type'] ) {
case 'event' :
include( 'views/html-order-event-item.php' );
break;
}
}
That's my best guess so far, but I'm positive this is the right snippet of code to be dissecting.

Related

Insert a custom field value as a new row on email order totals table in Woocommerce

I'm helping my mother create her website for her store i Denmark.
It has gone okay, but now i'm stuck with a problem.
I need to be able to make a custom field in the order complete email, that displays a track & trace number, i've inserted on the admin order page. Everything i've done up until now, haven't worked, so therefore i seek your help.
I've added a custom field already called Track & Trace Pakkenr. (see screenshot 1)
But the problem is getting this in the order complete email, under shipping (forsendelse in danish, see screenshot 2)
I am also a complete and utter beginner in coding so if any of you can give any help or advice, please make it almost foolproof to follow.
Here are the screenshot 1 and screenshot 2.
To get this order custom field value displayed in order totals table on email notifications, use the following:
add_filter( 'woocommerce_get_order_item_totals', 'insert_custom_line_order_item_totals', 10, 3 );
function insert_custom_line_order_item_totals( $total_rows, $order, $tax_display ){
// Only on emails notifications
if( is_wc_endpoint_url() ) return $total_rows; // Exit
$tracking_label = 'Track & Trace Pakkenr.'; // The tracking label name
$tracking_value = $order->get_meta( $tracking_label ); // Get the tracking value (custom field).
if( empty($tracking_value) ) return $total_rows; // Exit
$new_total_rows = array(); // Initializing
// Loop through total rows
foreach( $total_rows as $key => $value ){
if( 'payment_method' == $key && ! empty($tracking_value) ) {
$new_total_rows['tracking_parcel'] = array(
'label' => $tracking_label,
'value' => $tracking_value,
);
}
$new_total_rows[$key] = $total_rows[$key];
}
return sizeof($new_total_rows) > 0 ? $new_total_rows : $total_rows;
}
Code goes in function.php file of your active child theme (or active theme). tested and works.
You will get something like:
From this:
You can add custom fields to the order email, you need to use the field key where the code has meta_key.
/**
* Add a custom field (in an order) to the emails
*/
add_filter( 'woocommerce_email_order_meta_fields', 'custom_woocommerce_email_order_meta_fields', 10, 3 );
function custom_woocommerce_email_order_meta_fields( $fields, $sent_to_admin, $order ) {
$fields['meta_key'] = array(
'label' => __( 'Label' ),
'value' => get_post_meta( $order->id, 'meta_key', true ),
);
return $fields;
}
Ref: https://docs.woocommerce.com/document/add-a-custom-field-in-an-order-to-the-emails/
Below is a simple yet functional example of adding custom meta fields to order email
add_filter( 'woocommerce_email_order_meta_fields', 'woocommerce_email_order_meta_fields_func', 10, 3 );
function woocommerce_email_order_meta_fields_func( $fields, $sent_to_admin, $order ) {
$fields['Track_Field'] = array(
'label' => __( 'Track', 'woocommerce' ),
'value' => wptexturize( get_post_meta( $order->id, 'Track_Field', true ) )
);
//... more meta fields goes here
return $fields;
}
In email after order email table:
add_action( 'woocommerce_email_after_order_table', 'woocommerce_email_after_order_table_func' );
function woocommerce_email_after_order_table_func( $order ) {
?>
<h3>Track</h3>
<table>
<tr>
<td>Track Fields </td>
<td><?php echo wptexturize( get_post_meta( $order->id, 'Track_Field', true ) ); ?></td>
</tr>
<!--additional custom meta and HTML code goes here-->
</table>
<?php
}
For more help see this link : Click here

Woo-commerce 3.0 single product price is not changing properly

I made a new post type named "sub_products" containing the meta tag "unit_price".
After assigning a new field for every Woocommerce product containing a list of all the "sub_products" posts, the goal was to update every product price based on the selected "sub_products" meta "unit_price".
function kulcskron_edit_post( $p1, $p2 )
{
if ( !is_admin() )
return;
if ( get_post_type() != 'product' )
return;
$sub_product_ids = $p2->get_meta( 'sub_products' );
if ( empty($sub_product_ids) )
return;
$product_regular_price = 0;
foreach ( $sub_product_ids as $id )
$product_regular_price += get_post_meta( $id, 'unit_price', true );
if ( $p1 == $product_regular_price )
return;
$p2->set_regular_price( $product_regular_price );
$p2->save();
}
add_action( 'woocommerce_product_get_price', 'kulcskron_edit_post', 10, 2 );
I tried every possible hook to make this work:
add_action( 'the_post', 'kulcskron_edit_post', 9, 1 );
add_action( 'edit_post', 'kulcskron_edit_post', 10, 2 );
add_action( 'pre_get_posts', 'kulcskron_edit_post' );
add_action( 'save_post', 'kulcskron_edit_post' );
This code updates the price but in a strange way:
Admin single product edit view: The price is not updated right away, just after I revisit the edit screen.
Admin product listing view: Every price is 0.
Front-end product page view: The displayed price is 0.
How do I update the product price based on the assigned "sub_products" "unit_price" post meta when a single product is saved in admin view?
You may use the hook "woocommefrce_get_price_html" it should work
I managed to solve the price update problem.
I used the save_post action:
save_post is an action triggered whenever a post or page is created or updated, which could be from an import, post/page edit form, xmlrpc, or post by email.
The final code:
function kulcskron_update_product( $product_obj )
{
if ( !is_admin() )
return;
if ( get_post_type() != 'product' )
return;
$product = wc_get_product( $product_obj );
if ( !$product->meta_exists( 'sub_products' ) )
return;
$sub_product_ids = $product->get_meta( 'sub_products' );
if ( empty($sub_product_ids) )
return;
_update_product_price( $product_obj, $sub_product_ids );
}
add_action( 'save_post', 'kulcskron_update_product' );
And for the sake of completeness here is the rest of code:
function _update_product_price( $product_obj, $sub_product_ids )
{
$product = wc_get_product( $product_obj );
$product_regular_price = 0;
foreach ( $sub_product_ids as $id )
$product_regular_price += get_post_meta( $id, 'kulcskron_unit_price', true );
$product->set_regular_price( $product_regular_price );
$product->set_price( $product_regular_price );
$product->save();
}
This way basically everything can be edited and updated. Example:
function _update_product_stock( $product_obj, $sub_product_ids )
{
$product = wc_get_product( $product_obj );
$sub_product_stocks = array();
foreach ( $sub_product_ids as $id )
$sub_product_stocks[] = get_post_meta( $id, 'kulcskron_free_stock_quantity', true );
$product->set_manage_stock( true );
$product->set_stock_quantity( min($sub_product_stocks) );
$product->save();
}
I would love to post a link to all the methods to edit a product but I have no rep for that.
But sadly this is not all...
In order to fully update the price, we need to filter the price HTML to return the newly updated price.
function kulcskron_price_html( $priceHtml, $product )
{
$symbol = get_woocommerce_currency_symbol();
$price = $product->get_regular_price();
$html = '<span class="woocommerce-Price-amount amount">'. $price .' <span class="woocommerce-Price-currencySymbol">'. $symbol .'</span></span>';
return $html;
};
add_filter( 'woocommerce_get_price_html', 'kulcskron_price_html', 10, 2 );
A little backstory. We need to sync all the products from an external database through an XML file. But there is a catch, the XML file only contains product parts and they are not displayed, searched, filtered and certainly can't be ordered individually. None of the Woocommerce build in functionality meet these requirements.
In order to solve this, I registered a new post type and imported all the product parts into this newly created post type. After that, I registered a new field for every WC Product with the Advanced Custom Fields plugin.
It looks like this:
Newly registered custom post type with the custom fields
The code above makes easy to update the WC Products (e.g. Prices) based on the selected Sub products.

Alternative way to orderby grouped product child since filter `woocommerce_grouped_children_args` was removed from WooCommerce 3

In Wordpress Woocommerce I have created a grouped product, with lots of sub products (children) in it. I tried searching everywhere, but I can not find a working solution how to orderby them by either SKU or product name. It seems that it's orderby is generated by "Menu Order" only. Although as I have 30+ sub products in these multiple grouped products, it would be extremely time wasting to order them by Menu Order value.
I tried the following code, but it seems that it worked in WC 2.5, but not 3.0+ .
add_filter( 'woocommerce_grouped_children_args',
'so_22661392_grouped_children_args' );
function so_22661392_grouped_children_args( $args ){
$args['meta_key'] = 'sku';
$args['orderby'] = 'meta_value';
$args['order'] = 'ASC';
return $args;
}
I also searched Google for explanations but could not find any. I tried to clear transients, this doesn't work as well:
WooCommerce>System Status>Tools>Clear Transients
The grouped product can be seen on https://plastmet.ee/uus/toode/umartoru-kork-zzo-pealekaiv/ . The html table should match the children below, but it does not. SKU for children is "563/9005", "567/9005" etc..
Any help would be greatly appreciated!
Hopefully I understand the problem correctly:
If we have overriden the template file single-product/add-to-cart/grouped.php, then we could use e.g.:
if( $grouped_products )
usort( $grouped_products, 'wc_products_array_orderby_title' );
to sort the grouped products by title, instead of the default menu ordering.
We could also unregister:
add_action( 'woocommerce_grouped_add_to_cart', 'woocommerce_grouped_add_to_cart', 30 );
with a custom callback instead.
As a last resort one could override the woocommerce_grouped_add_to_cart() function, e.g. within a plugin, to change the menu ordering.
It's defined as:
if ( ! function_exists( 'woocommerce_grouped_add_to_cart' ) ) {
function woocommerce_grouped_add_to_cart() {
global $product;
$products = array_filter( array_map( 'wc_get_product', $product->get_children() ) );
if ( $products ) {
usort( $products, 'wc_products_array_orderby_menu_order' );
wc_get_template( 'single-product/add-to-cart/grouped.php', array(
'grouped_product' => $product,
'grouped_products' => $products,
'quantites_required' => false,
) );
}
}
}
where one could e.g. use wc_products_array_orderby_title instead.

Which hook(s) are triggered when WooCommerce product stock is updated

I have a custom field in my products, that needs to be updated through a function, whenever the stock in any of the variations change.
Are there hooks for this? If so, which ones and what is their output ($post_id for example)?
I think you are looking for woocommerce_reduce_order_stock action.
More info about this hook.
Here is a whole list of available hooks.
-- EDIT
Function should look like this:
function test( $order ) { // you get an object $order as an argument
$items = $order->get_items();
$items_ids = array();
foreach( $items as $item ) {
$items_ids[] = $item['product_id'];
}
die( print_r($items_ids) ); // it should break script while reduce stock
}
add_action( 'woocommerce_reduce_order_stock', 'test' );
I have achieved the same using woocommerce_product_set_stock and woocommerce_variation_set_stock hook.
These hooks run when the stock is changed (either increased, or decreased). Even after the stock is decreased after product purchase.
add_action( 'woocommerce_product_set_stock', 'stock_changed' );
add_action( 'woocommerce_variation_set_stock', 'stock_changed' );
function stock_changed( $product ) {
// Do something
}
Since WooCommerce 4.9+, woocommerce_product_before_set_stock and woocommerce_variation_before_set_stock are added to signal that the value of stock_quantity for a product/variation is about to change.
add_action( 'woocommerce_product_before_set_stock', 'stock_about_to_change' );
add_action( 'woocommerce_variation_before_set_stock', 'stock_about_to_change' );
function stock_about_to_change( $product ) {
// Do something
}

WooCommerce: Moving column position in admin product list

I have created a custom column "Cost" in Backend WooCommerce Products list panel using a hook in functions.php file.
I want to move that column after "Price" column:
How can I achieve this?
There is many ways to achieve this. I have chosen the simplest way to do it. You can use the code snippet below to change "Cost" column location before "price" column, in product post_type backend list:
// ADDING A CUSTOM COLUMN TITLE TO ADMIN PRODUCTS LIST AFTER "Price" COLUMN
add_filter( 'manage_edit-product_columns', 'custom_product_column',11);
function custom_product_column($columns)
{
$new_columns = array();
foreach( $columns as $key => $column ){
$new_columns[$key] = $columns[$key];
if( $key === 'price' )
$new_columns['cost'] = __( 'Cost','woocommerce');
}
return $new_columns;
}
// ADDING THE DATA FOR EACH PRODUCTS BY COLUMN (EXAMPLE)
add_action( 'manage_product_posts_custom_column' , 'custom_product_list_column_content', 10, 2 );
function custom_product_list_column_content( $column, $product_id )
{
global $post;
switch ( $column )
{
case 'cost' :
// Get and display the value for each row
echo get_post_meta( $product_id, '_cost', true );
break;
}
}
Tested and works.
Use the add_filter(manage_edit-posttype_columns, yourFunction) where posttype is your post type. Then list your columns in yourFunction the order you want. The best tutorial I've seen on this is here.

Categories