I'm currently having an issue with WooCommerce that randomly a product attribute is or is not written to the database. Result is that in the order for some items I can't see the attributes. Hence, I thought about having an intermediate solution by dumping the cart into an email to ourselves as soon as the customer hits the "order now" button.
But doing so, I'm struggling to get the attributes right.
We're selling coffee for which I have 2 attributes: coffee_bag_size and coffee_ground_for. Both attributes are set for variations, but I have created 2 only variations based on coffee_bag_size (for 250 grammes and 500 grammes bag) with different prices whereas the coffee_ground_for can be any value. I just have to know what the customer chooses.
I read in another post that in case an attribute is not used in variations, one has to get it from the parent, so I did:
foreach ( WC()->cart->get_cart() as $cart_item )
{
$product = $cart_item['data'];
$bagsize = $product->get_attribute('pa_coffee_bag_size') ;
// For Product Variation type
if( $product->get_variation_id() > 0 )
{
$parent = wc_get_product($product->get_parent_id());
$ground = $parent->get_attribute('pa_coffee_ground_for') ;
}
// For other Product types
else
{
$ground = $product->get_attribute('pa_coffee_ground_for') ;
}
}
Getting the coffee_bag_size attribute is no problem.
Getting the coffee_ground_for is the problem. If I get it from the product it's empty, if I get it from the parent then I get a comma-separated list of all possible values. But I only need the value that was chosen. How do I do that?
I tried a few things more .... they all give me an empty string back:
$ground = $product->get_meta('pa_coffee_ground_for',true);
$ground = $cart_item['variation']['pa_coffee_ground_for'];
$attributes = $product->get_attributes();
foreach ( $attributes as $attribute => $attribute_term )
{
$term = get_term_by('name','pa_coffee_ground_for', $attribute);
$ground = $term->term;
}
Check out slug of you attribute it may be 'pa_coffee-ground-for'
$product = $cart_item['data'];
if( $product->is_type( 'variation' ) )
{
$product = wc_get_product($product->get_parent_id());
}
$ground = $product->get_attribute( 'pa_coffee-ground-for' );
Tested and working
Related
I have a product where it has custom attribute(delivaryDate is attribute name) but no variations are created with it.
On the product page, the attribute is selected.
On the cart page, I am able to retrieve data on all attributes where it has variations. But the attribute where variations are not created cannot be retrieved.
Code used :
foreach ( WC()->cart->get_cart() as $cart_item ) {
$item_data = $cart_item['data'];
echo $item_data;
}
Screenshot of dump for reference.
So is a way to retrieve all attributes values even variation not created with those attributes.
I am looking to get attributeName1_attaributeName2 as a value from each product on cart. Any help would be appericated.
Thanks in advance.
When working with WooCommerce, it is always preferred to use their methods.
For getting attributes this would be, continuing as from $cart_item['data']; that you already have
// Get the product object for the cart item
$product = $cart_item['data'];
// Get the product attributes
$attributes = $product->get_attributes();
// Iterate through each attribute
foreach ( $attributes as $attribute ) {
// Get the attribute name only
$attribute_name = $attribute->get_name();
// If you want to print the attribute name too
echo $attribute_name;
}
so - I have a couple of meta_data which I don't want to get transmitted. Actually with this snippet I am able to do this on woocommerce order view. As I have connected an ERP this won't work there.
add_filter( "woocommerce_order_item_get_formatted_meta_data","unset_specific_order_item_meta_data", 10, 2);
function unset_specific_order_item_meta_data($formatted_meta, $item){
foreach( $formatted_meta as $key => $meta ){
if( in_array( $meta->key, array("length", "surface", "amount") ) )
unset($formatted_meta[$key]);
}
return $formatted_meta;
}
Is there a way to achieve this with woocommerce_checkout_update_order_meta? So that the specific meta_data (like length, surface, amount) isn't in the order in the first place?
The woocommerce_checkout_update_order_meta hook is meant to save custom meta data, unsetting meta data doesn't work here. However you can use delete_meta_data like this:
add_action("woocommerce_checkout_update_order_meta", "custom_woocommerce_checkout_update_order_meta", 10, 2);
function custom_woocommerce_checkout_update_order_meta($order_id, $data) {
$order = wc_get_order($order_id);
if ($order) {
$meta_data_keys = array("length", "surface", "amount");
foreach ($meta_data_keys as $meta_data_key) {
$order->delete_meta_data($meta_data_key);
}
}
}
I try to save custom meta data after creating an order in WooCommerce. I try it with the woocommerce_new_order_item hook and in general it works. But I need to store a custom attribute from the ordered product..but I can´t get it.
What I tried:
add_action('woocommerce_new_order_item','add_basic_meta_for_new_quote',10,3); // add extra order metas
function add_basic_meta_for_new_quote($item_id, $values, $cart_item_key)
{
$angebotstext = get_post_meta($item_id, 'angebotstext', false);
wc_add_order_item_meta($item_id, 'angebotstext', $angebotstext);
}
or
add_action('woocommerce_new_order_item','add_basic_meta_for_new_quote',10,3); // add extra order metas
function add_basic_meta_for_new_quote($item_id, $values, $cart_item_key)
{
global $product;
$angebotstext = $product->get_attribute( 'pa_angebotstext' );
wc_add_order_item_meta($item_id, 'angebotstext', $angebotstext);
}
The attribute is saved here:
... and the result for meta_value is always empty, NULL or a:0:{}
Do you have any idea?
-----EDIT----
It worked with this way:
add_action('woocommerce_new_order_item','add_basic_meta_for_new_quote',10,3); // add extra order metas
function add_basic_meta_for_new_quote($item_id, $item, $order_id )
{
if ($order_id) {
$order = wc_get_order( $order_id );
}
# Iterating through each order items (WC_Order_Item_Product objects in WC 3+)
if ($order) {
foreach ( $order->get_items() as $item_id => $item_values ) {
// Product_id
$product_id = $item_values->get_product_id();
$product = wc_get_product($product_id);
$angebotstext = $product->get_attribute('Angebotstext');
if ( !empty($angebotstext) && $angebotstext != NULL ) {
wc_add_order_item_meta($item_id, 'Angebotstext' , $angebotstext);
}
}
}
}
BUT: This code adds my custom attribute two times as meta.... why that? 🤔
The main issue here is if the order contains multiple products, so you're going to face the issue of multiple updates for the same order.
To avoid that or to choose to deal with it the right way, here are some ideas:
To make this works for multiple products:
Changed this part:
wc_add_order_item_meta($item_id, 'Angebotstext _'.$product_id , $angebotstext);
This will add a new custom meta for each product by adding the product ID at the end of each meta_key and the meta_value will be the product unique value for that attribute.
so you're going to find this custom fields on the order page:
Angebotstext_123 = product attribute.
Angebotstext_485 = product
attribute. Angebotstext_951 = product attribute.
the '123', '485' are the product id and the meta value for that meta_key will be that product attribute.
To prevent it from updating multiple times:
if ($order) {
$angebotstext ='';
foreach ( $order->get_items() as $item_id => $item_values ) {
// Product_id
$product_id = $item_values->get_product_id();
$product = wc_get_product($product_id);
$angebotstext = $product->get_attribute('Angebotstext');
$prev_angebotstext = $angebotstext;
if ( !empty($angebotstext) && $angebotstext != NULL && $angebotstext != $prev_angebotstext ) {
wc_add_order_item_meta($item_id, 'Angebotstext' , $angebotstext);
}
}
}
"$prev_angebotstext" is the previous item attribute.
if ( !empty($angebotstext) && $angebotstext != NULL && $angebotstext != $prev_angebotstext )
This condition will check if the current product attribute is equal to the previous product attribute, if yes it will not update the order again.
Thank you!
I struggle to find a way to update the stock of the similar variation of a product.
All my product have, say, a main variation, "Black" (30$) or "white" (250$) but sometimes they have another variation which is a "date start", so date_start:"12th june", date_start: "30th july", etc. I need to update the stock of the "date" variation when it's present (if there is no date, woocommerce update the main stock, no problem).
If someone choose "Black"+"12th June" I need the stock of "12 June" to be decreased also for "white"...
Before someone ask, "black" and "white" have different price per product... And "date" change also per product, that's why I need to use variation (and not addon attribute with a plugin).
Maybe someone have a better idea for organising this, but I try many other solution, always a caveat. This one seems the simpler, just have to find the good "hook" and "code"
Here is some pseudo code I made for this:
if(Product is sold):
VarSoldVariation = getSoldVariation(product->ID);
OtherVariationWithSameDate = getVariations (VarSoldVariation->pa_dates);
foreach(OtherVariationWithSameDate)updateStockVariation();
endif;
OK, it seems a little weird for not using metadata/attributes for this case, instead of variations. However, I've done more unusual stuff with variations than this one, so without judging your decision:
At first, you have to find a suitable action hook which fires after an order takes place. Some of them are:
woocommerce_order_status_$STATUS_TRANSITION[from]_to_$STATUS_TRANSITION[to]
woocommerce_order_status_$STATUS_TRANSITION[to] (e.g. woocommerce_order_status_completed)
woocommerce_order_status_changed
woocommerce_payment_complete
woocommerce_thankyou
Update 2:
I rewrite the code with some improvements:
In my initial answer, I used a WordPress get_posts function (which uses WP_Query) with a meta_query parameter, which you should definitely change to tax_query in this case, due to performance considerations. And we also know that it's better practice to use wc_get_products and WC_Product_Query where it's possible. However in this case it's not even needed to do a direct post query on db and it's possible to get the variations from get_available_variations method of WC_Product_Variable
Now it checks for quantity on order item and uses it on other date variations stock update.
Now it uses WC classes and functions wherever is possible, instead of direct updating of metadata, stock quantity, stock status, etc.
The Updated Code:
add_action('woocommerce_order_status_processing', 'sync_variations_stock');
/**
* Update same date variations on customer order place
* #param $order_id
* #return void
*/
function sync_variations_stock($order_id)
{
if (is_admin()) return; // make sure it's a user order and we aren't on admin dashboard
$order = wc_get_order( $order_id );
foreach( $order->get_items() as $item ) {
if ($item->get_type() !== 'line_item') continue; //if $item is not a product or variation
$order_variation_count = $item->get_quantity();
$order_product_id = $item->get_product_id();
$order_variation_id = $item->get_variation_id();
if ( ! $order_variation_id ) continue; // if the item isn't a variation
$order_variation = wc_get_product($order_variation_id);
$order_variation_attribs = $order_variation->get_variation_attributes();
if ( isset($order_variation_attribs['attribute_pa_date']) ) {
$current_date_attrib = $order_variation_attribs['attribute_pa_date'];
} else {
continue; // stop if the variation in the order doesn't have 'pa_date' attrib
}
$product = wc_get_product( $order_product_id );
$variations = $product->get_available_variations();
foreach ( $variations as $variation ) {
if ( $variation['variation_id'] == $order_variation_id ) {
continue; //if this variation is the one we have in our order
}
if ( ! isset( $variation['attributes']['attribute_pa_color'] ) || !isset( $variation['attributes']['attribute_pa_date'] ) ) {
continue; //if this variation does not have the color or date attrib
}
if ( $variation['attributes']['attribute_pa_date'] == $current_date_attrib ) {
/*
* wc_update_product_stock function sets the stock quantity if the variation stock management is enabled
* NOTE: This function may cause a negative stock even if the variation backorder is set to false
*/
wc_update_product_stock( $variation['variation_id'], $order_variation_count, 'decrease' );
wc_delete_product_transients($variation['variation_id']); // Clear/refresh the variation cache (optionally if needed)
}
}
}
}
Tested and It's Working!
My First Answer:
For this example, I will use the last one. However you should be careful about this hook, since it fires on every page load of the 'WC Thank You page'. It would be a good idea to use one of these hooks instead:
woocommerce_order_status_processing
woocommerce_order_status_completed
woocommerce_payment_complete
Final code would be something like this:
add_action('woocommerce_thankyou', 'sync_variations_stock');
function sync_variations_stock($order_id)
{
$order = wc_get_order( $order_id );
foreach( $order->get_items() as $item ){
$product_id = $item->get_product_id();
$product_variation_id = $item->get_variation_id();
if (!$product_variation_id) return; // if the item isn't a variation
$date_variation = get_post_meta( $product_variation_id, 'attribute_pa_date', true);
$color_variation = get_post_meta( $product_variation_id, 'attribute_pa_color', true);
if ( ! $date_variation && ! $color_variation ) return; //if the variation doesn't have date and color attributes
$args = array(
'post_parent' => $product_id,
'post_type' => 'product_variation',
'posts_per_page' => -1,
'meta_query' => array(
array(
'key' => 'attribute_pa_date',
'value' => $date_variation,
'compare' => '='
),
array(
'key' => 'attribute_pa_color',
'value' => $color_variation,
'compare' => '!='
)
),
);
$other_date_variations = get_posts($args);
if( is_array($other_date_variations) && !empty($other_date_variations) ){
foreach ($other_date_variations as $date_variation) {
// do your stock updating proccess here. (updateStockVariation() as you write in your code)
$variation_id = $date_variation->ID;
$date_variation_stock = (int) get_post_meta( $variation_id, '_stock', true);
if ($date_variation_stock > 0) { //to prevent backorders
$date_variation_stock = $date_variation_stock - 1;
update_post_meta($variation_id, '_stock', $date_variation_stock);
// if the variation is now out-of-stock, set it as so
if ($date_variation_stock === 0) {
update_post_meta($variation_id, '_stock_status', 'outofstock');
wp_set_post_terms( $variation_id, 'outofstock', 'product_visibility', true );
}
}
}
}
}
}
Note: You have to replace attribute_pa_date & attribute_pa_color to match your attribute slugs.
Update 1
There are other consideration in this topic. WC Variation stock quantities may be changed in other senarios and circumstances, such as order edit on dashboard, order refunds, direct product edit, etc. Before going live, you have to think about these too.
Whoever as I said, there may be other ways to do what you are trying to. But I couldn't understand your setup and the relation between your variations and the dates. I think it's better to ask a approach related question for this, on WB.SE
I also just made a small change. In your code, if people refresh the page, the stock of the other variation are decreased... As woocommerce will always decrease the stock of the bought variation first, I go get this stock variation number and update other one with it. So I'm sure everything stays the same. :)
Here is the code updated:
function sync_variations_stock($order_id)
{
if (is_admin()) return; // make sure it's a user order and we aren't on admin dashboard
$order = wc_get_order( $order_id );
foreach( $order->get_items() as $item ) {
if ($item->get_type() !== 'line_item') continue; //if $item is not a product or variation
$order_variation_count = $item->get_quantity();
$order_product_id = $item->get_product_id();
$order_variation_id = $item->get_variation_id();
if ( ! $order_variation_id ) continue; // if the item isn't a variation
$order_variation = wc_get_product($order_variation_id);
$order_variation_attribs = $order_variation->get_variation_attributes();
if ( isset($order_variation_attribs['attribute_pa_dates']) ) {
$current_date_attrib = $order_variation_attribs['attribute_pa_dates'];
//Get the stock of the current variation for updating others.
$new_stock = $order_variation->get_stock_quantity();
} else {
continue; // stop if the variation in the order doesn't have 'pa_dates' attrib
}
$product = wc_get_product( $order_product_id );
$variations = $product->get_available_variations();
foreach ( $variations as $variation ) {
if ( $variation['variation_id'] == $order_variation_id ) {
continue; //if this variation is the one we have in our order
}
if ( ! isset( $variation['attributes']['attribute_pa_admissible-emploi-quebec'] ) || !isset( $variation['attributes']['attribute_pa_dates'] ) ) {
continue; //if this variation does not have the color or date attrib
}
if ( $variation['attributes']['attribute_pa_dates'] == $current_date_attrib ) {
/*
* wc_update_product_stock function sets the stock quantity if the variation stock management is enabled
* NOTE: This function may cause a negative stock even if the variation backorder is set to false
*/
//wc_update_product_stock( $variation['variation_id'], $order_variation_count, 'decrease' );
//Update stock of other variation with the stock number of the one just bought
wc_update_product_stock( $variation['variation_id'], $new_stock, 'set' );
wc_delete_product_transients($variation['variation_id']); // Clear/refresh the variation cache (optionally if needed)
}
}
}
}
I am trying to get variations of a variable product on custom product page. I have two attributes, one for sizes as select and the other for colors as swatches. The problem that I cannot display the attribute that I need to display, and when I use the following code it returns a text names of sizes or colors not the select dropdown for sizes or the colors swatches. Any help please ?!
echo implode(', ', wc_get_product_terms( $product_id, 'pa_colors' ));
This is a brief code to solve your question, I let you entire code, you can use only that you need.
The first is check if get_product function exists, and check the product type, to create a correct product object with the id (in my case $idProduct).
It work on woocommerce 3.x, I don't test it on woocommerce < 3.x.
if( function_exists('get_product') ) {
$product = get_product( $idProduct );
if ( $product->is_type( 'variable' ) ) {
$product = new WC_Product_Variable( $idProduct );
$available_variations = $product->get_available_variations(); //get all child variations
$variation_variations = $product- >get_variation_attributes(); // get all attributes by variations
// taxonomy => terms
// pa_attribute-color => array('blue', 'red', green)
// Use ex: get_taxonomy('pa_attribute-color')->labels; to get the Name and not the slug to attributes, it can be the taxonomy
// Use ex: get_term_by('name', 'pa_attribute-color', 'pa_attribute-color); to get the Name/label
$result = array( $available_variations , $attributes); // only to see the result you can use var_dump, error_log, etc.
//...
//...
}elseif ( $product->is_type( 'bundle' ) && class_exists( 'WC_Product_Bundle' ) ) {
$product = new WC_Product_Bundle( $idProduct );
}else{
$product = new WC_Product( $idProduct );
}
}
Also you try with:
$product->get_attribute( $key );
wc_attribute_label($key);
where $key can be pa_color , pa_size, etc
I hope help you.