Woocommerce Admin Order Details - Show custom data on order details page - php

I'm searching and trying it for 2 days with no success, please help.
I want to filter woocommerce orders to add additional details from db to order details page based on product attribute but I can't find the right woocommerce action/filter hook for this task.
Here suppose I've variable $is_customized = false;
If $is_customized == true then I need to add custom data from database to orders detail page.
NOTE: I don't want to add additional meta box instead I want to change order detail table for:
Replacing the default Product image with the image stored in database
and,
Adding a div containing custom attributes below product name.
I've all these values in my variables but I can't figure out which action hook should I use.
I've attached an image for clarification.
Just need to know if I can change / filter these order results and how ?
I appreciate for your time and help.
Thanks

Here's a start on how to display some extra data on the woocommerce_before_order_itemmeta hook:
add_action( 'woocommerce_before_order_itemmeta', 'so_32457241_before_order_itemmeta', 10, 3 );
function so_32457241_before_order_itemmeta( $item_id, $item, $_product ){
echo '<p>bacon</p>';
}
I don't know how you are saving your data, so I can't make more a more precise suggestion. Keep in mind that immediately following that hook, anything you've saved as order item meta will automatically display.
Filtering the image is more difficult. I've found this gist as a start, but it requires some custom conditional logic as you don't want to filter the thumbnail everywhere, but only in orders.
Edit: Currently the best I can do for filtering the item thumbnails:
add_filter( 'get_post_metadata', 'so_32457241_order_thumbnail', 10, 4 );
function so_32457241_order_thumbnail( $value, $post_id, $meta_key, $single ) {
// We want to pass the actual _thumbnail_id into the filter, so requires recursion
static $is_recursing = false;
// Only filter if we're not recursing and if it is a post thumbnail ID
if ( ! $is_recursing && $meta_key === '_thumbnail_id' ) {
$is_recursing = true; // prevent this conditional when get_post_thumbnail_id() is called
$value = get_post_thumbnail_id( $post_id );
$is_recursing = false;
$value = apply_filters( 'post_thumbnail_id', $value, $post_id ); // yay!
if ( ! $single ) {
$value = array( $value );
}
}
return $value;
}
add_filter( 'post_thumbnail_id', 'so_custom_order_item_thumbnail', 10, 2 );
function so_custom_order_item_thumbnail( $id, $post_id ){
if( is_admin() ){
$screen = get_current_screen();
if( $screen->base == 'post' && $screen->post_type == 'shop_order' ){
// this gets you the shop_order $post object
global $post;
// no really *good* way to check post item, but could possibly save
// some kind of array in the order meta
$id = 68;
}
}
return $id;
}

Related

Why is this conditional removal of WooCommerce billing field not working?

I've got the following code in my functions.php file that is supposed to check if certain products are in the cart and, if they are, remove a field from the billing set of fields at checkout. However, it's not removing the field, or any other field I try. I'm wondering why that is. Other tutorials I'm following are consistent with this code and say it should work, but for me it's not. What did I get wrong?
/**
* Check if a specific product ID is in the cart
*/
function dz_product_is_in_the_cart() {
// Add your special product IDs here
$ids = array( '10771', '10773', '10774', '10943', '10944', '10945', '10946', '10947', '10948', '10949', '10950', '10951', '10952', '10953', '10943', '10943', '10943', '10943');;
// Products currently in the cart
$cart_ids = array();
// Find each product in the cart and add it to the $cart_ids array
foreach( WC()->cart->get_cart() as $cart_item_key => $values ) {
$cart_product = $values['data'];
$cart_ids[] = $cart_product->id;
}
// If one of the special products are in the cart, return true.
if ( ! empty( array_intersect( $ids, $cart_ids ) ) ) {
return true;
} else {
return false;
}
}
/**
* Conditionally remove a checkout field based on products in the cart
*/
function dz_remove_checkout_field( $fields ) {
if ( dz_product_is_in_the_cart() ) {
unset( $fields['billing']['billing_first_name'] );
}
return $fields;
}
add_filter( 'woocommerce_checkout_fields' , 'dz_remove_checkout_field' );
The billing_first_name field is a required field. So do I need to do something to unrequire it before I try to unset it?
The problem was a plugin that was inserting this field was overriding my code that tried to unrequire and remove it. I opted to disable the plugin and add and conditionally control my own field(s) with the code here: https://woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/#section-7

Prevent User Role from changing woocommerce order status

We would like to prevent shop manager from changing order status, we found a help here in the link below Restrict user role to change only some order statuses in Woocommerce
But the issue here that it limits certain role ( shop Manager ) to some order statuses, we need to deny the shop manager from changing the order status completely not limit it to some order statuses.
Also the snippet we mentioned remove the the order statuses from the bulk action drop down & the order details here: https://prnt.sc/mpfl3b, we need to remove the statuses too from the quick action column here https://snipboard.io/B6SYHb.jpg
Simply we try to have the shop manager to when he try to change order status from bulk, order details page, or actions column to find there is no order statuses to select to change it or disable it completely.
Best Regards
As you can see in the example code the conditions of the statuses are determined in the if statement, because you want to apply this without a limit, it's just a matter of removing that if statement and returning empty arrays
p.s; if you mark my answer as the solution, then also vote for #LoicTheAztec original answer if you have not already done this, since his code just about contained the solution.
// Admin orders list: bulk order status change dropdown
function filter_dropdown_bulk_actions_shop_order( $actions ) {
// Targeting shop_manager
if( current_user_can( 'shop_manager' ) ) {
$actions = (array) null;
}
return $actions;
}
add_filter( 'bulk_actions-edit-shop_order', 'filter_dropdown_bulk_actions_shop_order', 20, 1 );
// Admin orders list: quick action
function filter_order_actions( $actions, $order ) {
// Targeting shop_manager
if( current_user_can( 'shop_manager' ) ) {
$actions = (array) null;
}
return $actions;
}
add_filter( 'woocommerce_admin_order_actions', 'filter_order_actions', 10, 2 );
// Admin order pages: order status dropdown
function filter_order_statuses( $order_statuses ) {
global $post, $pagenow;
if( $pagenow === 'post.php' || $pagenow === 'post-new.php' ) {
// Get ID
$order_id = $post->ID;
// Get an instance of the WC_Order object
$order = wc_get_order( $order_id );
// TRUE
if ( $order ) {
// Get current order status
$order_status = 'wc-' . $order->get_status();
// New order status
$new_order_statuses = array();
foreach ($order_statuses as $key => $option ) {
// Targeting "shop_manager"
if( current_user_can('shop_manager') && $key == $order_status ) {
$new_order_statuses[$key] = $option;
}
}
if( sizeof($new_order_statuses) > 0 ) {
return $new_order_statuses;
}
}
}
return $order_statuses;
}
add_filter('wc_order_statuses', 'filter_order_statuses', 10, 1 );
//Since the suggested answer apparently causes some new issues and doesn't solve the original issue in a couple of other cases, there are options to hide elements according to user type, something like the below - which is a bit of kludge, but might serve:
First, to load an admin style sheet applying only to Shop Managers:
/**
* SHOP MANAGER STYLES
* Front (Optional) and Back End stylesheet
* Style interface for users logged in with'shop_manager' role
* Add to theme functions.php
*/
add_action('admin_enqueue_scripts', 'shop_manager_styles');
//if front end stylesheet needs to be added to cover admin bar:
//add_action('wp_enqueue_scripts', 'shop_manager_styles' ) ;
function shop_manager_styles() {
$user = wp_get_current_user() ;
//uncomment following and remove next if not confined to admin
//if ( $user && in_array( 'shop_manager', $user->roles ) ) {
if ( in_array( 'shop_manager', $user->roles ) ) {
//time() as stylesheeet version to help bust caching - may not be necessary but doesn't hurt:
wp_enqueue_style(
'shop_manager_styles', get_stylesheet_directory_uri()
. '/css/shop_manager_styles.css', array(), time()
);
}
}
...and the css to hide the order-status label and menu completely, as well as related columns in shop_order sub-pages:
/** HIDE ORDER STATUS LABEL, SELECTION MENU IN ORDER EDIT
* AND RELATED COLUMNS IN shop_order SUB-PAGE
*/
.wc-order-status,
.column-order_status,
.column-wc_actions {
display: none;
}
You'd save that in your theme css folder in a new shop_manager_styles.css.
Now, you may have some need to show the order status to shop managers without their being able to edit it. That would also be doable with CSS, if also (even more) a kludge. It may be that you have other peculiarities in your installation that will prevent the above code or a minimally customized variation of it from working, but, even if it is a little less clean than removing the option via function, this kind of thing usually will work in a pinch.
(Edited to provide option to add stylesheet on front end - in case relevant options appearing in admin bar, otherwise no need to enqueue extra non-admin script.)

Custom post type data and order item data in WooCommerce

I am using a plugin, WooCommerce Custom Post Type Manager, along with WooCommerce that enables the use of custom post types as products. This is working pretty well for the most part, but I want to be able to control inventory and I am able to see the problem. In the database under the order_item_meta table, the product_id = 0. Because the product_id is empty, there is no way to update the stock on a completed purchase.
I know that WooCommerce made some changes to where it searched if a post_type was a 'product' and if not, certain things failed. I am wondering if there is a filter hook or another way to add the product id with a custom function at checkout?
This is the function I am trying to create to control a custom type of inventory, we are selling event "seats". And no, even the regular "stock" does not work, probably because of the same reason.
function update_course_seats( $order_id ){
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
$product_id = $item->get_product_id(); // this returns 0
if($product_id != 0) {
wc_update_order_item_meta( $item, '_seats', 10 ); // example number
}
}
//add_action( 'woocommerce_payment_complete', 'update_course_seats');
First you should use woocommerce_add_cart_item_data action hook, to store your Custom Post Type (CTP) Id on add to cart… But for that you will need to display, on your CTP pages, inside the add to cart form, a hidden field with the CTP Id like:
<input type="hidden" name="ctpost_id" value="<?php echo get_the_id(); ?>">
Now you can add this hooked function, that will add as custom cart item data your CTP Id:
add_filter( 'woocommerce_add_cart_item_data', 'add_custom_cart_item_data', 10, 3 );
function add_custom_cart_item_data( $cart_item_data, $product_id, $variation_id ){
if( isset( $_POST['ctpost_id'] ) ) {
$cart_item_data['ctpost_id'] = wc_clean( $_POST['ctpost_id'] );
}
return $cart_item_data;
}
The Second hook to be used is woocommerce_checkout_create_order_line_item action hook, to add custom order item meta data (or to make changes to order item data). This hook is triggered before payment gateways process during order creation.
You can use WC_data add_meta_data() method to save your CTP Id as a custom CTP ID like:
add_action( 'woocommerce_checkout_create_order_line_item', 'save_cpt_id_to_order_item_data', 10, 4 );
function save_cpt_id_to_order_item_data( $item, $cart_item_key, $cart_item, $order ){
if( isset($cart_item['ctpost_id']) && $cart_item['ctpost_id'] > 0 ) {
// Add the custom CTP post ID
$item->add_meta_data('_ctpost_id', $cart_item['ctpost_id'] );
}
// And here for example you add seats as custom cart item data
if( isset($cart_item['quantity']) ) {
$item->add_meta_data( 'Seats', $cart_item['quantity'], true );
}
}
When the order will get paid, you will be able to update everything required, as you will have your CTP post ID as custom order item data.
Then finally, you can use woocommerce_payment_complete as follow:
add_action( 'woocommerce_payment_complete', 'action_payment_complete_callback', 10, 1 );
function action_payment_complete_callback( $order_id ){
$order = wc_get_order();
// Loop through order items
foreach ( $order->get_items() as $item_id => $item ) {
$ctp_id = $item->get_meta('_ctpost_id'); // Get the CTP post ID
// Your code goes here
}
}
Or woocommerce_order_status_changed hooks like:
add_action( 'woocommerce_order_status_changed', 'action_order_status_changed_callback', 10, 4 );
function action_order_status_changed_callback( $order_id, $status_from, $status_to, $order ){
if( in_array( $status_to, ['processing','completed'] ) ) {
// Loop through order items
foreach ( $order->get_items() as $item_id => $item ) {
$ctp_id = $item->get_meta('_ctpost_id'); // Get the CTP post ID
// Your code goes here
}
}
}
Related: Get Order items and WC_Order_Item_Product in Woocommerce 3.

Add Custom attributes from a variable product to cart in WooCommerce

I am trying to redirect my custom shop page to cart page after user select custom attributes (liability and hours) and then clicks the purchase button.
I have written custom JS code which retrieves the variation_id and attributes value and append it to the URL so that it automatically gets added to cart and get's redirected to cart page.
href="yourdomain.com/?add-to-cart=47&variation_id=88&quantity=3&attribute_pa_colour=blue&attribute_pa_size=m"
This is the URL format which I have found in a blog: (https://businessbloomer.com/woocommerce-custom-add-cart-urls-ultimate-guide/) and I made my link in this format.
My link is:
localhost/wordpress/cart/?add-to-cart=1185&variation_id=1641&quantity=1&attribute_pa_liability=2_million&attribute_pa_hours=500_hours
Where liability and hours is my custom attribute which has values as 1 million, 2 million and 500 hours, 750 hours respectively.
But when it get's redirected to cart page woocommerce give me an error and shows it's alert box which shows the error message as "liability and hours are required fields".
I think woocommerce is unable to get the attribute values through my URL.
Can anyone explain why is this happening and what is the error if there is any?
Updated
This "Business Bloomer" guide is a little outdated… You need 2 things:
1). The correct URL
You don't need to addto cart the product and the variation Id with all related attributes. You just need to add to cart the variation ID + your custom attributes like this: localhost/wordpress/cart/?add-to-cart=1641&quantity=1&pa_liability=2_million&pa_hours=500_hours
2). Registering (and display) your custom attributes:
// Store the custom data to cart object
add_filter( 'woocommerce_add_cart_item_data', 'save_custom_product_data', 10, 2 );
function save_custom_product_data( $cart_item_data, $product_id ) {
$bool = false;
$data = array();
if( isset( $_REQUEST['pa_liability'] ) ) {
$cart_item_data['custom_data']['pa_liability'] = $_REQUEST['pa_liability'];
$data['pa_liability'] = $_REQUEST['pa_liability'];
$bool = true;
}
if( isset( $_REQUEST['pa_hours'] ) ) {
$cart_item_data['custom_data']['pa_hours'] = $_REQUEST['pa_hours'];
$data['pa_hours'] = $_REQUEST['pa_hours'];
$bool = true;
}
if( $bool ) {
// below statement make sure every add to cart action as unique line item
$cart_item_data['custom_data']['unique_key'] = md5( microtime().rand() );
WC()->session->set( 'custom_variations', $data );
}
return $cart_item_data;
}
// Displaying the custom attributes in cart and checkout items
add_filter( 'woocommerce_get_item_data', 'customizing_cart_item_data', 10, 2 );
function customizing_cart_item_data( $cart_data, $cart_item ) {
$custom_items = array();
if( ! empty( $cart_data ) ) $custom_items = $cart_data;
// Get the data (custom attributes) and set them
if( ! empty( $cart_item['custom_data']['pa_liability'] ) )
$custom_items[] = array(
'name' => 'pa_liability',
'value' => $cart_item['custom_data']['pa_liability'],
);
if( ! empty( $cart_item['custom_data']['pa_hours'] ) )
$custom_items[] = array(
'name' => 'pa_hours',
'value' => $cart_item['custom_data']['pa_hours'],
);
return $custom_items;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works
You will need to add this data to the order items too
First uncheck the ajax add to cart option from admin panel
Then you need to make variation with your attributes then find out product id, quantity, attribute slug and attribute name(used to show in cart) and variation id and then make your cart url like given below.
The add to cart link with attribute is : https://www.your-domain.com/?add-to-cart=(product id)&quantity=(numeric quantity)&attribute_pa_(attribute slug)=(attribute name)&variation_id=(variation id)
For more details you can visit http://www.codemystery.com/wordpress-woocommerce-add-cart-link-variations/
The other answers are correct in regards to the add to cart url format (should be: /add-to-cart=product_id&my_custom_attr=whatever&my_custom_attr2=somthing)
Then there is a very good plugin to handle this type of functionality for storing the custom product attributes called WC Fields Factory.
And the writer of the plugin also has a great article on how to do this w/o need of a plugin. As well as an answer on a similar question 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.

Categories