Need some help with WooCommerce adding variations to product attributes.
I created an attribute called Data ( pa_data ), this attribute stores terms from a custom field added with ACF, this field is a date picker.
I want to create an event that is recurring, and for every recurrence, I want to add a variation with price and stock.
So the process is like this:
Add all ACF fields in an array.
Create Variations with this array.
Add variations to attributes.
The problems is that i don't know how to add variations to the attribute ( pa_data );
Here is how is displaying my attributes
https://i.imgur.com/YmbDFlO.png
Here is how i want to be displayed
https://i.imgur.com/oDNvuOD.png
$days = array
array(
'date' => 'April 02, 2020',
'price' => '10',
),
array(
'date' => 'April 03, 2020',
'price' => '20',
),
array(
'date' => 'April 04, 2020',
'price' => '10',
),
);
// This method inserts new data into the post after the post is saved
add_action('post_updated', 'ProductUpdate', 10, 3);
// Callback function after the post is saved
function ProductUpdate($post_id, $post_after, $post_before) {
if ( $post_after && get_post_type($post_id) == 'product' ) {
ProductSetVariations( $post_id, $postDataVariations );
}
};
function ProductSetVariations($productID, $days ) {
// Get product object
$product = wc_get_product($productID);
foreach ($days as $day ) {
$date = $day['date'];
$price = $day['price'];
$variationPost = array(
'post_title' => $product->get_title(),
'post_name' => 'product-' . $productID . '-variation',
'post_status' => 'publish',
'post_parent' => $productID,
'post_type' => 'product_variation',
'guid' => $product->get_permalink()
);
// Insert the new variation post;
$variationID = wp_insert_post($variationPost);
// Create the new post based on the id
$variation = new WC_Product_Variation($variationID);
$taxonomy = 'pa_data';
// If term dosent exsist in taxonomy than add it
if ( !term_exists( $date, $taxonomy )) {
wp_insert_term( $date, $taxonomy );
};
$term_slug = get_term_by('name', $date, $taxonomy)->slug; // Get the term slug
$post_term_names = wp_get_post_terms( $productID, $taxonomy, array('fields' => 'names') );
if( ! in_array( $date, $post_term_names ) ) {
wp_set_post_terms( $productID, $date, $taxonomy, true );
};
$term_slug = get_term_by('name', $date, $taxonomy)->slug; // Get the term slug
update_post_meta( $variationID, 'attribute_'.$taxonomy, $term_slug );
$variation->set_price($price);
$variation->save(); // Save the data
}
};
Resolved!
Solution:
The problem was with WordPress hook 'post_updated', it does save in the database the changes but it doesn't change it in admin.
Here is the solution, for someone who has to update posts from rest api or just post update.
This is only a simple demonstration.
add_action('woocommerce_update_product', 'ProductUpdate', 10, 3);
function ProductUpdate($post_id) {
// Insert Product Attributes
function insert_product_attributes ($post_id, $variations) {
$product = wc_get_product($post_id);
// Set up an array to store the current attributes values.
$values = array();
foreach ($variations as $variation) {
array_push($values, $variation['date']);
}
wp_set_object_terms($post_id, $values, 'pa_data', true );
$product_attributes_data = array('pa_data' =>
array(
'name' => 'pa_data',
'position' => '1',
'is_visible' => '1',
'is_variation' => '1',
'is_taxonomy' => '1',
);
);
update_post_meta($post_id, '_product_attributes', $product_attributes_data);
};
function insert_product_variations ($post_id, $variations) {
foreach ($variations as $index => $variation) {
$attribute_term = get_term_by('name', $variation['date'], 'pa_data');
if ( $variation['date'] == $attribute_term ) {
return;
}
$variation_post = array( // Setup the post data for the variation
'post_title' => 'Variation #'.$index.' of data for product#'. $post_id,
'post_name' => 'product-'.$post_id.'-variation-'.$index,
'post_status' => 'publish',
'post_parent' => $post_id,
'post_type' => 'product_variation',
'guid' => home_url() . '/?product_variation=product-' . $post_id . '-variation-' . $index
);
$variation_post_id = wp_insert_post($variation_post); // Insert the variation
// We need to insert the slug not the name into the variation post meta
update_post_meta($variation_post_id, 'attribute_pa_data', $attribute_term->slug);
update_post_meta($variation_post_id, '_price', $variation['price']);
update_post_meta($variation_post_id, '_regular_price', $variation['price']);
}
function ProductSetVariations($post_id ) {
// Get product object
$product = wc_get_product($post_id);
// Verify if the product is variable or simple product
if ( !$product->is_type( 'variable' ) ) {
return;
};
$product_data = array(
'days' => array(
array(
'sku' => '123SKU',
'date' => '1 April 2020',
'price' => '10',
'stock' => '20'
),
array(
'sku' => '456SKU',
'date' => '2 April 2020',
'price' => '10',
'stock' => '20'
),
array(
'sku' => '789SKU',
'date' => '3 April 2020',
'price' => '10',
'stock' => '20'
)
)
);
insert_product_attributes($post_id, $product_data['days']); // Insert variations passing the new post id & variations
insert_product_variations($post_id, $product_data['days']);
}
};
Related
I need to update the product with the REST API from the live website to staging. It works well if I add a static product ID. What I need is to match products across sites with the SKU, not by the ID because it's not the same.
Any ideas on how to do it? My code is below.
add_action( 'woocommerce_update_product', 'on_product_savex', 10, 1 );
function on_product_savex( $product_id ) {
$product = wc_get_product( $product_id );
$get_main_website_product_id = get_post_meta( $product_id, '_sku', true );
$live_ck = 'ck_xxxx';
$live_cs = 'cs_xxxx';
$live_url = 'https://web.com/wp-json/wc/v3/products?sku='.$get_main_website_product_id.'&consumer_key=' . $live_ck . '&consumer_secret=' . $live_cs;
$body = array(
'name' => $product->get_name(), // product title
'status' => 'private', // product status, default: publish
'regular_price' => $product->get_regular_price(),
'sale_price' => $product->get_sale_price(),
'description' => $product->get_description(),
'sku' => $product->get_sku(),
'weight' => $product->get_weight(),
'manage_stock' => true,
'stock_quantity' => 10,
);
print_r( $body );
$raw_response = wp_remote_post( $live_url,
array(
'headers' => array( 'Content-Type' => 'application/json' ),
'timeout' => 30,
'body' => json_encode( $body ),
)
);
}
Get remote product ID first using SKU, then update the remote product with live product data.
add_action( 'woocommerce_update_product', 'on_product_savex', 10, 1 );
function on_product_savex( $product_id ) {
$product = wc_get_product( $product_id );
$product_sku = $product->get_sku();
$remote_keys = "consumer_key=xxxxxx&consumer_secret=xxxxxx";
// retrieve product ID by SKU, return product properties
$remote_get = wp_remote_get("https://web.com/wp-json/wc/v3/products?sku={$product_sku}&{$remote_keys}");
$remote_product = json_decode($remote_get['body'])[0];
$remote_product_id = $remote_product->id;
$body = array(
'name' => $product->get_name(), // product title
'status' => 'private', // product status, default: publish
'regular_price' => $product->get_regular_price(),
'sale_price' => $product->get_sale_price(),
'description' => $product->get_description(),
'sku' => $product->get_sku(),
'weight' => $product->get_weight(),
'manage_stock' => true,
'stock_quantity' => 10,
);
$raw_response = wp_remote_post( "https://web.com/wp-json/wc/v3/products/{$remote_product_id}?{$remote_keys}",
array(
"headers" => array( "Content-Type" => "application/json" ),
"timeout" => 30,
"body" => json_encode( $body ),
)
);
}
If a webshop has variable products, in some cases the variations have unique GTINs. In our case, it is EAN.
How to add these different GTINS to the structured data? I made a function to insert multiple "offer" in "offers", but the GTIN is not recognized here.
This is the function:
//add structured data to the markup
function set_structured_data( $markup, $product ) {
if ($product->is_type('simple')){
$markup['brand'] = array('#type' => 'Brand', 'name' => get_post_meta( $product->get_id(), '_brand', true ) );
$markup['gtin8'] = get_post_meta( $product->get_id(), '_EAN', true );
}
if ($product->is_type('variable')){
$available_variations = $product->get_available_variations();
foreach ($available_variations as $variation) {
$variation_id = $variation['variation_id'];
$variproduct = wc_get_product($variation_id);
$i++;
$stock = $product->get_stock_status();
$offers[] = array(
'type' => 'Offer',
'price' => $variproduct->get_price(),
'priceValidUntil' => $priceuntil,
'priceSpecification' => array(
'price' => $variproduct->get_price(),
'priceCurrency' => get_woocommerce_currency(),
'valueAddedTaxIncluded' => 'http://schema.org/True'
),
'priceCurrency' => get_woocommerce_currency(),
'availability' => 'http://schema.org/'.$stock.'',
'url' => get_the_permalink(),
'seller' => array (
'type' => 'Organization',
'name' => 'HeatPerformance®',
'url' => get_the_permalink(),
));
}
$markup['offers'] = $offers;
$markup['brand'] = array('#type' => 'Brand', 'name' => get_post_meta( $product->get_id(), '_brand', true ) );
}
return $markup;
}
add_filter( 'woocommerce_structured_data_product', 'set_structured_data', 99, 2 );
Any ideas?
Ok after a couple of days puzzling i found the path to a solution here:
https://support.google.com/merchants/answer/6386198?hl=en
Result is that i corrected the standardized method from woocommerce and made each variation a unique product.
So first remove the markup in case of a variable product like this:
function set_structured_data( $markup, $product ) {
if ($product->is_type('variable')) {
$markup = array();
}
return $markup;
}
add_filter( 'woocommerce_structured_data_product', 'set_structured_data', 99, 2 );
And then build up the ld+json script again, but then the desired way:
function set_structured_data_variable() {
global $product;
if (isset($product)){
if ($product->is_type('variable') && !empty($product)){
$available_variations = $product->get_available_variations();
$date = date("Y-m-d",strtotime(" + 3months"));
$commenttext = '';
$commentauthor = '';
$commentdate = '';
$comments = get_comments(array( 'post_id' => $product->get_id(), 'number' => '1' ));
foreach($comments as $comment) {
$commenttext = $comment->comment_content;
$commentauthor = $comment->comment_author;
$commentdate = $comment->comment_date;
}
foreach ($available_variations as $variation) {
$variation_id = $variation['variation_id'];
$variproduct = wc_get_product($variation_id);
$i++;
$gtin = 0000000000001;
if (!empty(get_post_meta( $variation_id, '_EAN', true ))){
$gtin = get_post_meta( $variation_id, '_EAN', true );
}
$stock = $product->get_stock_status();
$arrays[$i] = array(
'#context' => 'https://schema.org/',
'#type' => 'Product',
'sku' => $variproduct->get_sku(),
'gtin13' => $gtin,
'image' => get_the_post_thumbnail_url($product->get_id()),
'name' => implode(" / ", $variproduct->get_variation_attributes()),
'description' => wp_strip_all_tags(get_the_excerpt($product->get_id())),
'brand' => array('#type' => 'Brand', 'name' => get_post_meta( $product->get_id(), '_brand', true ) ),
'review' => array(
'#type' => 'Review',
'reviewRating' => array (
'#type' => 'Rating',
'ratingValue' => $product->get_review_count(),
'bestRating' => '5',
'worstRating' => '1',
),
'author' => array(
'#type' => 'person',
'name' => $commentauthor,
'reviewBody' => $commenttext,
'datePublished' => $commentdate,
)),
'aggregateRating' => array (
'#type' => 'AggregateRating',
'ratingValue'=> $product->get_average_rating(),
'reviewCount' => $product->get_rating_count(),
),
'inProductGroupWithID' => $product->get_sku(),
'offers' => array(
'#type' => 'Offer',
'price' => $variproduct->get_price(),
'priceValidUntil' => $date,
'priceSpecification' => array(
'price' => $variproduct->get_price(),
'priceCurrency' => get_woocommerce_currency(),
'valueAddedTaxIncluded' => 'http://schema.org/True'
),
'priceCurrency' => get_woocommerce_currency(),
'availability' => 'http://schema.org/'.$stock.'',
'url' => get_the_permalink(),
'seller' => array (
'type' => 'Organization',
'name' => 'HeatPerformance®',
'url' => get_the_permalink(),
)));
}
echo '<script type="application/ld+json" class="buronoort">[';
$i = 0;
foreach ($arrays as $array){
$i++;
echo json_encode($array);
if ($i < array_key_last($arrays)){
echo ',';
}
}
echo ']</script>';
}
}
}
add_action('wp_head','set_structured_data_variable', 19);
Tested in the test tool for structured data and it is working.
A little concern is the product review however, i have to look into that what happens after multiple reviews.
Another concern is the EAN, i have set it standard to "0000000000001" as not all EANS are inputted yet. So this is a debatle solution, but if someone has a better idea, keep me posted.
I'm trying to figure out a direct link to display all sale items in the shop. URLs can usually filter out specific attributes and queries, so I was hopeful that this would be possible. So far, no luck.
My result turns up: no products found. But there are indeed products on sale.
I've tried the following:
add_filter( 'woocommerce_product_query_meta_query', 'filter_on_sale_products', 20, 1 );
function filter_on_sale_products( $meta_query ){
if( isset($_GET['onsale']) && $_GET['onsale'] ){
$meta_query[] = array(
'key' => '_sale_price',
'value' => 0,
'compare' => '>'
);
}
return $meta_query;
}
This should return all sale items by URL: https://www.example.com/shop/?onsale=1
Any advice would be appreciated
Your code contains some very minor errors.
You can use the woocommerce_product_query action hook instead. This should suffice:
function action_woocommerce_product_query( $q ) {
if ( is_admin() ) return;
// Isset & NOT empty
if ( isset( $_GET['onsale'] ) ) {
// Equal to 1
if ( $_GET['onsale'] == 1 ) {
// Function that returns an array containing the IDs of the products that are on sale.
$product_ids_on_sale = wc_get_product_ids_on_sale();
$q->set( 'post__in', $product_ids_on_sale );
}
}
}
add_action( 'woocommerce_product_query', 'action_woocommerce_product_query', 10, 1 );
check this link out
$args = array(
'post_type' => 'product',
'posts_per_page' => 8,
'meta_query' => array(
'relation' => 'OR',
array( // Simple products type
'key' => '_sale_price',
'value' => 0,
'compare' => '>',
'type' => 'numeric'
),
array( // Variable products type
'key' => '_min_variation_sale_price',
'value' => 0,
'compare' => '>',
'type' => 'numeric'
)
)
);
$loop = new WP_Query( $args );
Im using wp_insert_post() to add products programmatically in woocommerce
I got everything working except product attributes will not show on the product page view unless I click update from the admin.
Im adding tons of products so is there any way to get variations to show in the dropdown on the product page.
Heres my code it works fine everything gets placed in the correct fields it just wont show options on the product page without clicking update.
$new_post = array(
'ID' => '',
'post_author' => 1,
'post_title' => 'title of product',
'post_status' => 'publish',
'post_type' => 'product'
);
$post_id = wp_insert_post($new_post);
wp_set_object_terms($post_id, 'variable', 'product_type', false);
$my_post = array(
'post_title' => 'Variation # of ' . esc_attr(strip_tags( $post_title)),
'post_name' => 'product-' . $post_id . '-variation-',
'post_status' => 'publish',
'post_parent' => $post_id,
'post_type' => 'product_variation',
'guid' => home_url() . '/?product_variation=product-' . $post_id . '-variation-'
);
wp_insert_post( $my_post );
$variable_id = $post_id + 1;
update_post_meta( $variable_id, '_price', 8.50 );
update_post_meta( $variable_id, '_regular_price', '8.50');
$product_attributes['type'] = array(
'name' => htmlspecialchars(stripslashes('Options')),
'value' => "black|blue",
'position' => 1,
'is_visible' => 1,
'is_variation' => 1,
'is_taxonomy' => 0
);
update_post_meta( $post_id, '_product_attributes', $product_attributes);
This code will create product with 2 attributes (weight, brand) and 2 variations.
//Create main product
$product = new WC_Product_Variable();
$att_var = array();
//Create the attribute object with name weight
$attribute = new WC_Product_Attribute();
$attribute->set_id( 0 );
$attribute->set_name( 'weight' );
$attribute->set_options( array(
'50g',
'100g',
'150g'
) );
$attribute->set_position( 0 );
$attribute->set_visible( 1 );
$attribute->set_variation( 1 );
$att_var[] = $attribute;
//Create the attribute object with name brand
$attribute = new WC_Product_Attribute();
$attribute->set_name( 'brand' );
$attribute->set_options( array(
'Parle-G',
'Britania'
) );
$attribute->set_position( 1 );
$attribute->set_visible( 1 );
$attribute->set_variation( 1 );
$att_var[] = $attribute;
$product->set_attributes($att_var);
$product->set_name('Product 3');
$product->set_status('publish');
$product->set_sku(12345);
//Save main product to get its id
$product->set_category_ids([47, 56] );
$id = $product->save();
//variation 1
$variation = new WC_Product_Variation();
$variation->set_regular_price(10);
$variation->set_sale_price(10);
$variation->set_stock_quantity(12);
$variation->set_manage_stock(True);
$variation->set_weight('50g');
$variation->set_parent_id($id);
$variation->set_attributes(array(
'weight' => '50g',
'brand' => 'Parle-G'
));
//Save variation, returns variation id
$variation->save();
//variation 2
$variation_new = new WC_Product_Variation();
$variation_new->set_regular_price(15);
$variation_new->set_sale_price(12);
$variation_new->set_stock_quantity(20);
$variation_new->set_manage_stock(True);
$variation_new->set_weight('100g');
$variation_new->set_parent_id($id);
//Set attributes requires a key/value containing
$variation_new->set_attributes(array(
'weight' => '100g',
'brand' => 'Britania'
));
//Save variation, returns variation id
$variation_new->save();
WooCommerce comes with standard shortcode [product_category].
I wanted to get a shortcode with the same behavior, but filtering per vendor (a custom taxonomy) instead of per category.
So I have replicated and modified the code of [product_category], directly in woocommerce file class-wc-shortcodes.php to create [product_vendor].
This way, it is working fine. But I know it is not proper because my shortcodes will be erased at each upgrade.
So I tried to create my own plugin to host the code of [product_vendor].
But simply moving the code into it, is not working (my website gets down). I also tried to declare global $woocommerce at the begining. Not working either. I guess it is because the code is refering to elements of class WC_Shortcodes and woocommerce functions... but I am not at ease with class so I don't know how to solve the issue...!
Here is the code I wrote in class-wc-shortcodes.php. Could you tell me what are the changes I should do so that this code is working in my own plugin ?
Many thanks
class WC_Shortcodes {
/**
* Init shortcodes.
*/
public static function init() {
$shortcodes = array(
'product' => __CLASS__ . '::product',
'product_page' => __CLASS__ . '::product_page',
'product_category' => __CLASS__ . '::product_category',
'product_vendor' => __CLASS__ . '::product_vendor', //new shortcode
...
}
(...)
/**
* Shortcode : List of products per vendor.
*/
public static function product_vendor( $atts ) {
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'category',
'order' => 'asc',
'vendor' => '', // Slugs
'operator' => 'IN'
), $atts, 'product_vendor' );
if ( ! $atts['vendor'] ) {
return '';
}
// Default ordering args
$ordering_args = WC()->query->get_catalog_ordering_args( $atts['orderby'], $atts['order'] );
$meta_query = WC()->query->get_meta_query();
$query_args = array(
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'orderby' => $ordering_args['orderby'],
'order' => $ordering_args['order'],
'posts_per_page' => $atts['per_page'],
'meta_query' => $meta_query
);
$query_args = self::_maybe_add_vendor_args( $query_args, $atts['vendor'], $atts['operator'] );
if ( isset( $ordering_args['meta_key'] ) ) {
$query_args['meta_key'] = $ordering_args['meta_key'];
}
$return = self::product_loop( $query_args, $atts, 'product_cat' );
// Remove ordering query arguments
WC()->query->remove_ordering_args();
return $return;
}
(...)
private static function _maybe_add_vendor_args( $args, $vendor, $operator ) {
if ( ! empty( $vendor ) ) {
$args['tax_query'] = array(
array(
'taxonomy' => 'yith_shop_vendor',
'terms' => array_map( 'sanitize_title', explode( ',', $vendor ) ),
'field' => 'slug',
'operator' => $operator
)
);
}
return $args;
}
In my own plugin, I have "copy paste" the code, with minor changes. But if I activate this plugin, my site can't be launched at all. I also tried to include my function in "class WC_Shortcodes", or to add/remove the declaration of global variable $woocommerce... Here is what I wrote in my plugin :
function test_shortcode_in_my_plugin ($atts){
global $woocommerce;
$atts = shortcode_atts( array(
'per_page' => '12',
'columns' => '4',
'orderby' => 'category',
'order' => 'asc',
'vendor' => '', // Slugs
'operator' => 'IN' // Possible values are 'IN', 'NOT IN', 'AND'.
), $atts, 'product_vendor' );
if ( ! $atts['vendor'] ) {
return ''; //si aucune catégorie n'est spécifiée, ne rien retourner
}
// Default ordering args
$ordering_args = WC()->query->get_catalog_ordering_args( $atts['orderby'], $atts['order'] );
$meta_query = WC()->query->get_meta_query();
$query_args = array( // construction du tableau de résultats
'post_type' => 'product',
'post_status' => 'publish',
'ignore_sticky_posts' => 1,
'orderby' => $ordering_args['orderby'],
'order' => $ordering_args['order'],
'posts_per_page' => $atts['per_page'],
'meta_query' => $meta_query
);
$query_args = self::_maybe_add_vendor_args( $query_args, $atts['vendor'], $atts['operator'] );
// Fonction qui ajoute la condition sur le "vendor" dans la requete
if ( isset( $ordering_args['meta_key'] ) ) {
$query_args['meta_key'] = $ordering_args['meta_key'];
}
$return = self::product_loop( $query_args, $atts, 'product_cat' );
// Remove ordering query arguments
WC()->query->remove_ordering_args();
return $return;
}
add_shortcode( 'test_shortcode', 'test_shortcode_in_my_plugin' );