I am trying to modify the product images in the auto generated WooCommerce order e-mails. Note, I am trying to do this with hooks, rather than creating a modified e-mail template.
To start, I have a hidden input on the single product page. I have some JS that sets the ID of the image, which is based on the product's colour (a custom attribute I made with ACF).
<input class="js-col-img-id" type="hidden" name="product-col-img-id" value="">
For reference, here's what I've done to get this to work on the cart page:
// Add custom attributes to cart item data
add_action('woocommerce_add_cart_item_data', 'jwd_add_custom_attr_to_cart', 10, 3);
function jwd_add_custom_attr_to_cart($cart_item_data, $product_id, $variation_id) {
$attrs = array(...,'product-col-img-id');
foreach ($attrs as $attr) {
$san_attr = filter_input( INPUT_POST, $attr );
if ( !empty( $san_attr ) ) {
$cart_item_data[$attr] = $san_attr;
}
}
return $cart_item_data;
}
// This function sets the image ID in the cart, and it displays the image properly
add_action( 'woocommerce_before_calculate_totals', 'bwa_new_price' );
function bwa_new_price($cart) {
// ...
$cart_items = $cart->get_cart();
foreach($cart_items as $item) {
$data = $item['data'];
// product-col-img-id is sent through the hidden input when the add to cart form is submitted
$colour_img_ID = $item['product-col-img-id'] ?? false;
if ($colour_img_ID) {
$data->set_image_id($colour_img_ID);
}
}
}
// Add custom attributes to order
add_action( 'woocommerce_checkout_create_order_line_item', 'jwd_add_attr_to_order_items', 10, 4 );
function jwd_add_attr_to_order_items( $item, $cart_item_key, $values, $order ) {
if ( !empty( $values['product-colour'] ) ) {
$item->add_meta_data( 'Colour', $values['product-colour'] );
}
// I tried replicating this for the img ID. It does display it in the var dump mentioned below, and it does display it as an actual label in the e-mail (which I'd want to get rid of). The difficulty then becomes reading the ID through code and setting the product to display that image
if ( !empty( $values['product-col-img-id'] ) ) {
$item->add_meta_data( 'col_img_id', $values['product-col-img-id'] );
}
}
That works fine, but I'm having difficulty replicating this in the order e-mail. So far I've come across the following. This shows images in the e-mail, but it's the product's featured image. Of note is the "show_image" key being set to true.
// Add prod IMG to e-mails
function jwd_add_images_woocommerce_emails( $output, $order ) {
// set a flag so we don't recursively call this filter
static $run = 0;
// if we've already run this filter, bail out
if ( $run ) {
return $output;
}
$args = array(
'show_image' => true,
//'image_size' => array( 300, 300 ),
'image_size' => 'full',
);
// increment our flag so we don't run again
$run++;
// if first run, give WooComm our updated table
return wc_get_email_order_items($order, $args);
}
add_filter( 'woocommerce_email_order_items_table', 'jwd_add_images_woocommerce_emails', 10, 2 );
To modify the image, I found this filter, but I'm just having a difficult time figuring out what I need to do to replicate what I've done on the cart page. The closest I've got is having $item_data work, and I do see the product-col-img-id key in the var dump, though it's in a protected object, which I'm not sure how to access. Even if I could access, I'm not sure how I could even set the image from here.
add_filter('woocommerce_order_item_thumbnail', 'jwd_add_colour_img', 10, 2);
function jwd_add_colour_img($image, $item) {
$item_id = $item->get_id();
//var_dump($item->get_current_data());
$product = $item->get_product();
$product_id = $item->get_product_id();
$item_data = $item->get_data();
$col_img_id = false;
$item->set_image_id(1);
foreach ($item_data as $key => $item) {
//current_data
var_dump($item);
//echo "<br><br>";
$col_img_id = $item['product-col-img-id'] ?? false;
if ($col_img_id) {
break;
}
}
//var_dump($col_img_id);
//$item_id = $item->get_product_id();
//var_dump(wc_get_order_item_meta( $item_id, 'col_img_id', true ) );
return $image;
}
To get WooCommerce custom meta data, you can use WC_Data method get_meta().
Try the following instead to display your custom image on email notifications:
// Save custom image ID as order item meta
add_action( 'woocommerce_checkout_create_order_line_item', 'save_custom_image_id_to_order_item', 10, 4 );
function save_custom_image_id_to_order_item( $item, $cart_item_key, $values, $order ) {
if ( isset($values['product-colour']) && ! empty($values['product-colour']) ) {
$item->add_meta_data('Colour', $values['product-colour'] );
}
if ( isset($values['product-col-img-id']) && ! empty($values['product-col-img-id']) ) {
$item->add_meta_data('_col_img_id', $values['product-col-img-id'] );
}
}
// (optional) Force display item image on emails
add_filter( 'woocommerce_email_order_items_args', 'show_image_on_email_notifications' );
function show_image_on_email_notifications( $args ) {
$args['show_image'] = true;
return $args;
}
// Display custom image on emails
add_filter( 'woocommerce_order_item_thumbnail', 'display_email_order_item_custom_image', 10, 2 );
function display_email_order_item_custom_image( $image, $item ) {
// Only on email notifications
if( is_wc_endpoint_url() )
return $image;
$image_id = $item->get_meta('_col_img_id');
if( $image_id ) {
$image = wp_get_attachment_image( $image_id, array( 32, 32 ), false, array() );
}
return $image;
}
Code goes in functions.php file of the active child theme (or active theme). It should work.
Related
I would like to display the thumbnails of all items in the orders I received from my customers. I'm using Add products thumbnail to Woocommerce admin orders list answer code but sometimes I got error with the notice:
"Error message: Uncaught Error: Call to a member function
get_image_id() on bool in".
I wonder how can I solve this problem?
I am checking as you share the link, It's working fine but if you got an issue should be a versioning issue or something missing.
I have few/small changes, I hope this is helpful for you.
// Add a new custom column name "Product Image" to admin order list
add_filter( 'manage_edit-shop_order_columns', 'woo_admin_orders_list_add_column', 10, 1 );
function woo_admin_orders_list_add_column( $columns ){
$columns['custom_column'] = __( 'Product Image', 'woocommerce' );
return $columns;
}
// The data of the new custom column value in admin order list
add_action( 'manage_shop_order_posts_custom_column' , 'woo_add_product_image_order_column', 10, 2 );
function woo_add_product_image_order_column( $column, $post_id ){
global $order;
if( 'custom_column' === $column ){
$count = 0;
$order = wc_get_order($post_id);
// Loop through order items
foreach( $order->get_items() as $item ) {
$product = $item->get_product(); // The WC_Product Object
$style = $count > 0 ? ' style="padding-left:6px;"' : '';
// Display product thumbnail
printf( '<span%s>%s</span>', $style, $product->get_image( array( 50, 50 ) ) );
$count++;
}
}
}
In the Woocommerce admin screen, I'm attempting to use the order line meta data to display a button which will open up a new window with the URL of the dropship supplier. I have successfully pulled the supplier URL from the product on order and pushed it to the order line item.
I am able to change the meta data to a button but the consequence of that is the other custom fields which contain the custom options are wiped.
This is the full code which I have added to the functions.php file
add_action( 'woocommerce_checkout_create_order_line_item', 'custom_checkout_create_order_line_item', 20, 4 );
function custom_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
// Get a product custom field value
$custom_field_value = get_post_meta( $item->get_product_id(), 'supplier_url', true );
// Update order item meta
if ( ! empty( $custom_field_value ) ){
$item->update_meta_data( '_supplier', $custom_field_value );
}
}
add_filter('woocommerce_order_item_display_meta_key', 'filter_wc_order_item_display_meta_key', 20, 3 );
function filter_wc_order_item_display_meta_key( $display_key, $meta, $item ) {
// Change display_key
if( $meta->key === '_supplier' && is_admin() )
$display_key = __("Supplier", "woocommerce" );
return $display_key;
}
add_filter( 'woocommerce_order_item_display_meta_value', 'change_order_item_meta_value', 20, 3 );
function change_order_item_meta_value( $value, $meta, $item ) {
// Display supplier meta value as a button
if( $meta->key === '_supplier' && is_admin() ) {
$display_value = __('<a class="button" target="_blank" href="'.$value.'">Order</a>', 'woocommerce' );
return $display_value;
}
}
These images show the before and after of using the last block of code.
Before:
After:
Where have I gone wrong with my code and is what i'm trying to achieve possible?
The main mistake is on last function where $display_value should be replaced with just $value and then return $value; should be located at the end before last closing bracket.
I have also revisited all your code:
add_action( 'woocommerce_checkout_create_order_line_item', 'custom_checkout_create_order_line_item', 20, 4 );
function custom_checkout_create_order_line_item( $item, $cart_item_key, $values, $order ) {
$supplier_url = $values['data']->get_meta( 'supplier_url' ); // Get product custom field value
// add product custom field as custom order item meta data
if ( ! empty($supplier_url) ){
$item->update_meta_data( '_supplier', $supplier_url );
}
}
add_filter('woocommerce_order_item_display_meta_key', 'filter_wc_order_item_display_meta_key', 20, 3 );
function filter_wc_order_item_display_meta_key( $display_key, $meta, $item ) {
// Change displayed label for specific order item meta key
if( is_admin() && $item->get_type() === 'line_item' && $meta->key === '_supplier' ) {
$display_key = __("Supplier", "woocommerce" );
}
return $display_key;
}
add_filter( 'woocommerce_order_item_display_meta_value', 'change_order_item_meta_value', 20, 3 );
function change_order_item_meta_value( $value, $meta, $item ) {
// Change displayed value for specific order item meta key
if( is_admin() && $item->get_type() === 'line_item' && $meta->key === '_supplier' ) {
$value = __('<a class="button" target="_blank" href="'.$value.'">Order</a>', 'woocommerce' );
}
return $value;
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Need to add a field to the WooCommerce order details page where I can add a tracking number in the order page to be displayed for the customer.
I have added ACF to the order-details.php template as below, but it does not display. When I inspect page element all I can see is <h2></h2>. This is my current ACF code:
<h2><?php the_field('tracking_number'); ?></h2>
You can use the following to dis
// Display tracking information as a row on order totals everywhere
add_filter( 'woocommerce_get_order_item_totals', 'order_item_totals_tracking_row', 1000, 3 );
function order_item_totals_tracking_row( $total_rows, $order, $tax_display ){
if( $tracking_number = get_field('tracking_number', $order->get_id()) ) {
$new_total_rows = []; // Initializing
$has_tracking_row = false; // Initializing
$tracking_row = array( // tracking information array
'label' => __("Tracking number", "woocommerce"),
'value' => $tracking_number
);
// Loop through order total rows
foreach( $total_rows as $key => $values ) {
$new_total_rows[$key] = $values;
// Inserting tracking information array
if( $key === 'shipping' ) {
$new_total_rows['tracking'] = $tracking_row;
$has_tracking_row = true;
} elseif( $key === 'payment_method' && ! $has_tracking_row ) {
$new_total_rows['tracking'] = $tracking_row;
}
}
return $new_total_rows;
}
return $total_rows;
}
Code goes in functions.php file of the active child theme (or active theme). It should works.
I want to add some meta data to the order item in WooCommerce.
These meta fields are for internal use only and shouldn't be visible.
We have some extra fields in the product like an extra fee. I want to use that fee later to work with after I export the orders.
I found a very good answer here: https://stackoverflow.com/a/41988701/1788961
add_action('woocommerce_checkout_create_order_line_item', 'add_custom_hiden_order_item_meta_data', 20, 4 );
function add_custom_hiden_order_item_meta_data( $item, $cart_item_key, $values, $order ) {
// Set user meta custom field as order item meta
if( $meta_value = get_user_meta( $order->get_user_id(), 'billing_enumber', true ) )
$item->update_meta_data( 'pa_billing-e-number', $meta_value );
}
But with this example, the content from the meta fields will appear in the order details for the customer.
Is there a way to make these fields only visible in the backend and usable for internal functions?
Updated
The simple way set any meta value as hidden order item meta data only visible on admin Order edit pages is to add an underscore at the beginning of the meta key like:
add_action('woocommerce_checkout_create_order_line_item', 'add_custom_hiden_order_item_meta_data', 20, 4 );
function add_custom_hiden_order_item_meta_data( $item, $cart_item_key, $values, $order ) {
// Set user 'billing_enumber' custom field as admin order item meta (hidden from customer)
if( $meta_value = get_user_meta( $order->get_user_id(), 'billing_enumber', true ) )
$item->update_meta_data( '_billing_enumber', $meta_value );
}
Then to have a clean label name for this meta key on admin order items, you can use:
add_filter('woocommerce_order_item_display_meta_key', 'filter_wc_order_item_display_meta_key', 20, 3 );
function filter_wc_order_item_display_meta_key( $display_key, $meta, $item ) {
// Set user meta custom field as order item meta
if( $meta->key === '_billing_enumber' && is_admin() )
$display_key = __("Billing E Number", "woocommerce" );
return $display_key;
}
This code goes in function.php file of your active child theme (or cative theme). Tested and works.
add_action('woocommerce_add_order_item_meta','mau_add_values_to_order_item_meta',1,2);
if(!function_exists('mau_add_values_to_order_item_meta'))
{
function mau_add_values_to_order_item_meta($item_id, $values)
{
global $woocommerce,$wpdb;
$postcode = $values['postcode'];
$extend_date_delivery = $values['extend_date_delivery'];
$extend_date_collection = $values['extend_date_collection'];
$pro_rental_type_choose = $values['pro_rental_type_choose'];
if(!empty($postcode))
{
wc_add_order_item_meta($item_id,'postcode',$postcode);
}
if(!empty($extend_date_delivery))
{
wc_add_order_item_meta($item_id,'extend_date_delivery',$extend_date_delivery);
}
if(!empty($extend_date_collection))
{
wc_add_order_item_meta($item_id,'extend_date_collection',$extend_date_collection);
}
if(!empty($pro_rental_type_choose))
{
wc_add_order_item_meta($item_id,'pro_rental_type_choose',$pro_rental_type_choose);
}
}
}
add_filter( 'woocommerce_order_item_display_meta_key', 'mau_change_shipping_note_title', 20, 3 );
function mau_change_shipping_note_title( $key, $meta, $item ) {
if ( 'postcode' === $meta->key ) { $key = __( 'Postcode', 'your_textdomain'); }
if ( 'extend_date_delivery' === $meta->key ) { $key = __( 'Delivery Date', 'your_textdomain'); }
if ( 'extend_date_collection' === $meta->key ) { $key = __( 'Collection Date', 'your_textdomain'); }
if ( 'pro_rental_type_choose' === $meta->key ) { $key = __( 'Rental Type', 'your_textdomain'); }
return $key;
}
For Woocommerce, I found this piece of code but I need to make it conditional on product id:
add_action( 'woocommerce_thankyou', 'custom_thankyou_text', 1, 0);
function custom_thankyou_text(){
echo '<p class="thankyou-custom-text">If you do not see the download button(s) below, please refresh the page. Processing can take a few minutes.</p>';
}
How can I show a specific WooCommerce thank you page text based on the products they purchased?
Also I found this conditions example that would fit (without the redirection, which I don't need):
add_action( 'template_redirect', 'wc_custom_redirect_after_purchase' );
function wc_custom_redirect_after_purchase() {
if ( ! is_wc_endpoint_url( 'order-received' ) ) return;
// Define the product IDs in this array
$product_ids = array( 37, 25, 50 ); // or an empty array if not used
// Define the product categories (can be IDs, slugs or names)
$product_categories = array( 'clothing' ); // or an empty array if not used
$redirection = false;
global $wp;
$order_id = intval( str_replace( 'checkout/order-received/', '', $wp->request ) ); // Order ID
$order = wc_get_order( $order_id ); // Get an instance of the WC_Order Object
// Iterating through order items and finding targeted products
foreach( $order->get_items() as $item ){
if( in_array( $item->get_product_id(), $product_ids ) || has_term( $product_categories, 'product_cat', $item->get_product_id() ) ) {
$redirection = true;
break;
}
}
// Make the custom redirection when a targeted product has been found in the order
if( $redirection ){
wp_redirect( home_url( '/your-page/' ) );
exit;
}
}
Is there a way to combine the two for the needed result?
This can be done easily in the example below, where you will have to define the targeted product IDs and a Product category (for testing). So this example will display a custom message:
for specific product IDs
for specific product categories
for all other cases
The code:
add_action( 'woocommerce_thankyou', 'custom_thankyou_text', 1, 1);
function custom_thankyou_text( $order_id ){
// HERE Define Your product IDs below
$product_id1 = 30;
$product_id2 = 40;
// HERE Define Your product category (ID, slug or name)
$category = array('clothing');
// Get the WC_Order object (an instance)
$order = wc_get_order( $order_id );
$product_ids = array();
$has_category = false;
// Loop through the order items
foreach( $order->get_items() as $item ){
// PRODUCT ID: Store the product ID in an array
$product_ids[] = $item->get_product_id();
// PRODUCT CATEGORY
if( has_term( $category, 'product_cat', $item->get_product_id() ) )
$has_category = true;
}
// For first product ID
if( in_array( $product_id1, $product_ids ) ){
echo '<p class="thankyou-custom-text">Custom message for Product ID .'.$product_id1.'</p>';
}
// For Second product ID
elseif( in_array( $product_id2, $product_ids ) ){
echo '<p class="thankyou-custom-text">Custom message for Product ID .'.$product_id1.'</p>';
}
// For product category
elseif( $has_category ){
echo '<p class="thankyou-custom-text">Custom message for Product Category.</p>';
}
// For all other cases
else {
echo '<p class="thankyou-custom-text">If you do not see the download button(s) below, please refresh the page. Processing can take a few minutes.</p>';
}
}
Code goes in function.php file of the active child theme (or active theme).
Tested and works