Get Woocommerce variation attributes - wordpress

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.

Related

Add suffix text to specific product category in Woocommerce [duplicate]

I need to add 'per metre' to the price on most of my online catalogue, I tried the code on this thread in my finctions.php but I cannot get it to omit/include particular categories- it seems to be all or nothing. What am I doing wrong?
I have edited the code as such:
/*add 'per metre' after selected items*/
add_filter( 'woocommerce_get_price_html', 'conditional_price_suffix', 20, 2 );
function conditional_price_suffix( $price, $product ) {
// HERE define your product categories (can be IDs, slugs or names)
$product_categories = array('fabric','haberdashery', 'lining',);
if( ! has_term( $product_categories, 'fasteners', 'patches', 'remnnants', $product->get_id() ) )
$price .= ' ' . __('per metre');
return $price;
}
I want 'fabrics', 'haberdashery', 'lining' to show per metre, and 'fasteners', 'patches', 'remnants' to NOT show the suffix.
I have tried variations of the code -my exclusions in the top bit and the inclusions in the second part, and with/without the "( ! has term" section, but whichever I do takes all the suffix messages away, or applies to all categories.
It would be amazing if I could get this to work as have previously been using a very bloated plug-in. I'm only basically capable in this stuff so please feel free to talk me through it as if I am an idiot.
There is a little mistake in your code in the has_term() function.
To handle parent product categories, we will use a custom conditional function instead of has_tem().
I have also added some code to handle the product variation selected price of variable products, So try this instead:
// Custom conditional function that handle parent product categories too
function has_product_categories( $categories, $product_id = 0 ) {
$parent_term_ids = $categories_ids = array(); // Initializing
$taxonomy = 'product_cat';
$product_id = $product_id == 0 ? get_the_id() : $product_id;
if( is_string( $categories ) ) {
$categories = (array) $categories; // Convert string to array
}
// Convert categories term names and slugs to categories term ids
foreach ( $categories as $category ){
$result = (array) term_exists( $category, $taxonomy );
if ( ! empty( $result ) ) {
$categories_ids[] = reset($result);
}
}
// Loop through the current product category terms to get only parent main category term
foreach( get_the_terms( $product_id, $taxonomy ) as $term ){
if( $term->parent > 0 ){
$parent_term_ids[] = $term->parent; // Set the parent product category
$parent_term_ids[] = $term->term_id; // (and the child)
} else {
$parent_term_ids[] = $term->term_id; // It is the Main category term and we set it.
}
}
return array_intersect( $categories_ids, array_unique($parent_term_ids) ) ? true : false;
}
add_filter( 'woocommerce_get_price_html', 'conditional_price_suffix', 10, 2 );
function conditional_price_suffix( $price, $product ) {
// Handling product variations
$product_id = $product->is_type('variation') ? $product->get_parent_id() : $product->get_id();
// HERE define your product categories (can be IDs, slugs or names)
$product_categories = array('fabric','haberdashery', 'lining');
if( has_product_categories( $product_categories, $product_id ) )
$price .= ' ' . __('per metre');
return $price;
}
Code goes in function.php file of your active child theme (or active theme). tested and works.

How to update similar variation stock after purchase in Woocommerce

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)
}
}
}
}

Set Wordpress excerpt and post thumbnail based on custom field

As stated in the title, I would like to automatically save/update the value of post_excerpt and post_thumbnail based on an ACF custom field (mostly for compatibily reasons with other plugins). Now, while tring to accomplish this I encountered 2 issues, first being with the following function:
function test_FeaturedImageSetByACF() {
$current_screen = get_current_screen(); // Current admin screen needed to identify the current cpt
$current_cpt_name = $current_screen->post_type; // Current cpt name
$current_cpt_support = 'thumbnail'; // We want to check if the CPT supports this feature
$post_id = get_the_ID(); // Current post ID
$post_image_field = get_field('post_head_img'); // ACF field we want to sync
$post_image_id = $post_image_field['id']; // ACF image filed ID
$post_image_url = $post_image_field['url']; // ACF image filed URL
// If current cpt supports thumbnails/featured images
if ( post_type_supports( $current_cpt_name, $current_cpt_support ) ) {
if ( ( $post_image_url ) AND ( ( $post_image_url ) != ( get_the_post_thumbnail() ) ) ) {
delete_post_thumbnail( $post_id );
set_post_thumbnail( $post_id, $post_image_id );
}
}
}
add_action('save_post', 'test_FeaturedImageSetByACF', 13, 2 );
add_action('publish_post', 'test_FeaturedImageSetByACF', 10, 2 );
It does work, however sometimes it seems to update the value only the second time I save (which means I have to save twice). I understand I'm either using a wrong hook, a wrong priority or something like that, but I can't figure out which one it is.
Second issue I have, is I would like to accomplish something similar for post excerpts. Now the functions's will look alike the previous one quite a lot, but I don't know which value to update. Can anyone point me in the right direction?
Thanks in advance
You can use acf/save_post hook. Object ID is passed
add_action('acf/save_post', 'handle_acf', 20);
function handle_acf($object_id)
{
// Do your stuff
}
Since this became more of an ACF specific question rather than a generic one, I decided to post on ACF's support forums aswell were user John Huebner pointed me in the right direction. For anyone interested, the topic can be found at https://support.advancedcustomfields.com/forums/topic/set-wordpress-excerpt-and-post-thumbnail-based-on-custom-field/ while (in the event that post may get deleted or something) this is the code I used both for the excerpt part and the custom post thumbnail/featured image:
add_action('save_post', 'flex_CustomExcerptSetByACF', 50);
function flex_CustomExcerptSetByACF() {
global $post;
$post_id = ( $post->ID ); // Current post ID
$post_excerpt = get_field( 'post_excerpt', $post_id ); // ACF field
if ( ( $post_id ) AND ( $post_excerpt ) ) {
$post_array = array(
'ID' => $post_id,
'post_excerpt' => $post_excerpt
);
remove_action('save_post', 'flex_CustomExcerptSetByACF', 50); // Unhook this function so it doesn't loop infinitely
wp_update_post( $post_array );
add_action( 'save_post', 'flex_CustomExcerptSetByACF', 50); // Re-hook this function
}
}
add_action('save_post', 'flex_FeaturedImageSetByACF', 50);
function flex_FeaturedImageSetByACF() {
$current_screen = get_current_screen(); // Current admin screen needed to identify the current cpt
$current_cpt_name = $current_screen->post_type; // Current cpt name
$current_cpt_support = 'thumbnail'; // We want to check if the CPT supports this feature
global $post;
$post_id = ( $post->ID ); // Current post ID
$post_image_field = get_field('post_head_img', $post_id ); // ACF field
if ( ( $post_id ) AND ( $post_image_field ) ) {
$post_image_id = $post_image_field['id']; // ACF image filed ID
$post_image_url = $post_image_field['url']; // ACF image filed URL
// If current cpt supports thumbnails/featured images
if ( post_type_supports( $current_cpt_name, $current_cpt_support ) ) {
if ( ( $post_image_url ) AND ( ( $post_image_url ) != ( get_the_post_thumbnail() ) ) ) {
update_post_meta($post_id, '_thumbnail_id', $post_image_id);
}
}
}
}

How to get custom product attributes on custom product template in woocommerce

I've created a variable product using woocommerce plugin. In the variable product I've created a custom attribute "License Duration" with values "3 Months | 6 Months | 1 Year | Life Time".
Now I want to display this attribute on a custom single product template.
I've tried following solutions
1.
$licenseDurations = get_the_terms( $post->ID, 'attribute_license-duration' );
This shows following on var_dump($licenseDurations); `
object(WP_Error)[558]
public 'errors' =>
array (size=1)
'invalid_taxonomy' =>
array (size=1)
0 => string 'Invalid taxonomy' (length=16)
public 'error_data' =>
array (size=0)
empty`
and
2.
global $product;
$licenseDuration = $product->get_attribute( 'License Duration' ); // Here I also tried attribute slug `attribute_license-duration` instead of `License Duration` but no use
Ref: https://stackoverflow.com/a/13454788/2758870
and
3.
global $product;
$licenseDurations = get_the_terms( $product->id, 'License Duration');
foreach ( $licenseDurations as $licenseDuration )
{
echo $licenseDuration->name;
}
in both of above cases I got nothing because $product; is returning null on var_dump($product)
4. I've also tried this code http://isabelcastillo.com/woocommerce-product-attributes-functions
by directly calling isa_woo_get_one_pa() where I want to show values. but with no luck.
Anyone here please help...
You can try the following
1.get_post_meta to get the product attributes
$attr = get_post_meta( 123, '_product_attributes' ); // replace 123 with the actual product id
print_r( $attr );
2.Or you can create a product object and then using the get_attributes method
$p = new WC_Product( 123 ); // // replace 123 with the actual product id
print_r( $p->get_attributes() );
Not sure if there is changes in woocommerce, above did not give the attribute value. Following post from this page gave the correct answer
// replace pa_attribute with your attribute name, but keep the pa_ prefix
// e.g. pa_weight
$fabric_values = get_the_terms( $product->id, 'pa_attribute');
foreach ( $fabric_values as $fabric_value ) {
echo $fabric_value->name;
}
Here are all product attributes listing. Hope this helps you.
$product_attributes = get_post_meta( $post->ID, '_product_attributes' );
foreach ( $product_attributes as $group_attributes ) {
foreach ( $group_attributes as $attributes ) {
echo $attributes['name'].'=>'.$attributes['value'];
}
}

Attribute values for product variations

I'm facing a big problem with product variations and their attributes in woocommerce. I'm trying to display a table with each attribute for each availabe product variation. But Woocommerce saves the attributes in post meta complete in lowercase, replaces slashes and german special characters like ü,ö,ä etc. I get the attributes with $variation->get_variation_attributes().
I've searched the database for the save values you can see for example in the dropdown in the admin panel, but they are saved like this without a link to the variation they are assigned to:
a:5:{s:10:"bestell-nr";a:6:{s:4:"name";s:11:"Bestell-Nr.";s:5:"value";s:9:"1 | 2 | and so on...
How can I get the attributes in their correct format to display?
Actually product attributes are actually terms in custom taxonomies, so you just need to get the terms in that particular taxonomy. All attribute taxonomies are prefaced with 'pa_'. So a size attribute would be a 'pa_size' taxonomy. And the variation ID is the post ID for a variation.
But depending on how you want to display it, WooCommerce has a built-in function for displaying all a variation's attributes:
The following will display a definition list of all of a variations attributes.
echo wc_get_formatted_variation( $product->get_variation_attributes() );
And passing a second parameter of true will display a flat list:
echo wc_get_formatted_variation( $product->get_variation_attributes(), true );
This seems to work for me. Hope this helps.
$post = get_post();
$id = $post->ID;
$product_variations = new WC_Product_Variable( $id );
$product_variations = $product_variations->get_available_variations();
print_r($product_variations);
This can be found in the class-wc-product-variable.php
So basically if you look around on that page you can find a bunch of useful functions.
Here is something i put together.
$product_children = $product_variations->get_children();
$child_variations = array();
foreach ($product_children as $child){
$child_variations[] = $product_variations->get_available_variation($child);
}
print_r($child_variations);
I only wanted to publish one of the variation attributes rather than all of them; contributing this code in case it's helpful to anyone else.
get_variation_attributes() gets all the attributes
wc_get_formatted_variation() returns a formatted version of the array it's handed
$attributes = $productVariation->get_variation_attributes() ;
if ( $attributes [ 'attribute_pa_colour' ] ) {
$colour = [ 'attribute_pa_colour' => $attributes [ 'attribute_pa_colour'] ];
echo wc_get_formatted_variation ( $colour );
}
The way I do it is by using "get_post_meta":
echo get_post_meta( $variation_id, 'attribute_name_field', true);
Hopes this helps someone.
I used wp_get_post_terms to get correct variance attributes.
global $product;
$variations = $product->get_available_variations();
$var = [];
foreach ($variations as $variation) {
$var[] = $variation['attributes'];
}
var_dump($var);
//xxx to get attribute values with correct lower-upper-mixed-case
foreach ($var as $key => $arr) {
foreach ($arr as $orig_code => $lowercase_value) {
$terms_arr = wp_get_post_terms( $product->id, str_replace('attribute_','',$orig_code), array( 'fields' => 'names' ) );
foreach ($terms_arr as $term) {
if (strtolower($term) == $lowercase_value) {
$var[$key][$orig_code] = $term;
break;
}
}
}
}
var_dump($var);
The results:
Before hard code
array (size=1)
0 =>
array (size=2)
'attribute_pa_width' => string 'none' (length=4)
'attribute_pa_code' => string 'valancese' (length=9)
After hard code:
array (size=1)
0 =>
array (size=2)
'attribute_pa_width' => string 'None' (length=4)
'attribute_pa_code' => string 'ValanceSe' (length=9)
Just went through this ...here is my solution. It handles multiple attributes and all the variations. You just have to know the attribute slug.
$items = $order->get_items();
foreach( $items as $item_id => $product )
{
$ProductName = $product->get_name(); /
if($product->is_type( 'simple' ))
$CoreProductName = $ProductName; // No variance
else
{
list($CoreProductName, $ThrowAway) = explode(" - ",$ProductName, 2);
$variation_id = $product['variation_id'];
$variation = new WC_Product_Variation($variation_id);
$attributes = $variation->get_variation_attributes();
$SelectedAttribute1 = $attributes['attribute_YOUR_SLUG1_PER_PRODUCT'];
$SelectedAttribute2 = $attributes['attribute_YOUR_SLUG2_PER_PRODUCT'];
}
}

Resources