How to save product attributes programmatically in Woocommerce - wordpress

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();

Related

Modify WooCommerce related products to display Cross-Sell items, then tag, then category

I want to change related products to display first Cross-Sell items, then same tag items, then same category items.
The code below is based on https://stackoverflow.com/questions/62293670/show-cross-sells-before-same-category-on-related-products-in-woocommerce.
$related_product_list = array();
function filter_woocommerce_related_products( $related_posts, $product_id, $args ) {
print_r($related_posts); echo " -- related_posts<br/>";
print_r($product_id); echo " -- product_id<br/>";
print_r($args); echo " -- args<br/>";
// Number of related products to show
$show_products = 4;
// Get product
$product = wc_get_product( $product_id );
// Get cross sell IDs
$cross_sell_ids = $product->get_cross_sell_ids();
print_r($cross_sell_ids); echo " -- cross_sell_ids<br/>";
// Calculate how many filler products are needed
$product_needed_count = $show_products - count( $cross_sell_ids );
// If additional product needed
if ( $product_needed_count >= 1 ) {
// Retrieves product term ids for a tag taxonomy.
$product_tag_ids = wc_get_product_term_ids( $product_id, 'product_tag' );
// Get product id(s) from a certain tag, by tag-id
print_r($product_tag_ids); echo " -- product_tag_ids<br/>";
$product_ids_from_tag_ids = get_posts( array(
'post_type' => 'product',
'numberposts' => $show_products + 1,
'post_status' => 'publish',
'fields' => 'ids',
'tax_query' => array(
array(
'taxonomy' => 'product_tag',
'field' => 'id',
'terms' => $product_tag_ids,
'operator' => 'IN',
)
),
));
print_r($product_ids_from_tag_ids); echo " -- product_ids_from_tag_ids<br/>";
// Retrieves product term ids for a cat taxonomy.
$product_cat_ids = wc_get_product_term_ids( $product_id, 'product_cat' );
print_r($product_cat_ids); echo " -- product_cat_ids<br/>";
// Get product id(s) from a certain tag, by cat-id
$product_ids_from_cat_ids = get_posts( array(
'post_type' => 'product',
'numberposts' => $show_products + 1,
'post_status' => 'publish',
'fields' => 'ids',
'tax_query' => array(
array(
'taxonomy' => 'product_cat',
'field' => 'id',
'terms' => $product_cat_ids,
'operator' => 'IN',
)
),
));
print_r($product_ids_from_cat_ids); echo " -- product_ids_from_cat_ids<br/>";
// Merge array, removing duplicates
$related_posts = array_unique( array_merge( $cross_sell_ids, $product_ids_from_tag_ids, $product_ids_from_cat_ids ) );
// Remove current product
$related_posts = array_diff( $related_posts, array($product_id) );
// Reduce number of items to show products
$related_posts = array_slice( $related_posts, 0, $show_products );
print_r($related_posts); echo " -- related_posts<br/>";
} else {
// Slice array until show products
$related_posts = array_slice( $cross_sell_ids, 0, $show_products );
}
global $related_product_list;
$related_product_list = $related_posts;
// Return
return $related_posts;
}
add_filter( 'woocommerce_related_products', 'filter_woocommerce_related_products', 10, 3 );
// Order by
function filter_woocommerce_output_related_products_args( $args ) {
global $related_product_list;
print_r($related_product_list); echo " -- related_product_list<br/>";
$args['post__in'] = $related_product_list;
$args['orderby'] = 'post__in';
//print_r($args);
return $args;
}
add_filter( 'woocommerce_output_related_products_args', 'filter_woocommerce_output_related_products_args', 10, 1 );
It works well except that the order_by function is called before the product list is generated, so the order of related items is not respected.

Update product with Woocommerce REST API by the SKU identifier

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

WooCommerce set attributes programmatically based on variations

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']);
}
};

How to attach custom taxonomy to a post in Wordpress?

A have a lot of posts with custom post types in the database. In the same time the theme created the taxonomy institution:
function my_taxonomies_institutions() {
$labels = array(
'name' => _x( 'Category', 'taxonomy general name' ),
'singular_name' => _x( 'Category', 'taxonomy singular name' ),
// and tothers
);
$args = array(
'labels' => $labels,
'hierarchical' => true,
'show_admin_column' => true,
'rewrite' => array( 'hierarchical' => true, 'slug' => 'institutions' ),
);
register_taxonomy( 'institutions', 'institution', $args );
}
add_action( 'init', 'my_taxonomies_institutions', 0 );
OK, there's a menu item Instituitions in admin zone, and a bit of Categories there, for example - Sections. Now in order to animate the theme that constructed for this taxonomy I need to go through all posts and to attach the Instituitions term to the post depending of it's post_type.
print term_exists('sections'); // 7
I tried the following
$ret = wp_set_post_terms($pid, 7, 'institution');
$ret = wp_set_post_terms($pid, 'sections', 'institution');
but result was
WP_Error Object ( [errors] => Array ( [invalid_taxonomy] => Array ( [0] => Неверная таксономия. ) ) [error_data] => Array ( ) )
What I'm doing wrong?
You registered taxonomy with name institutions but using institution by mistake, hence te error [invalid_taxonomy]. It should be like this
$ret = wp_set_post_terms($pid, array(7,), 'institutions');
$ret = wp_set_post_terms($pid, array('sections',), 'institutions');
To assign this term "sections" with term_id = 7 to all posts of type institution, do something like
$posts = get_posts(array(
'post_type' => 'institution',
'post_status' => 'publish',
'posts_per_page' => -1
));
foreach ( $posts as $post ) {
wp_set_post_terms( $post->ID, array(7,), 'institutions');
// OR
// wp_set_post_terms( $post->ID, array ('sections',), 'institutions');
}
I hope this will work.
Please have a look at this codex page for further info.
try something like:
$posts = get_posts([
'post_type' => 'institution',
'post_status' => 'publish',
'numberposts' => -1
]);
foreach ( $posts as $post ) {
wp_set_post_terms( $post->ID; array( ), 'institutions');
}
also if you want to wp_set_post_terms by id, you should use wp_set_post_terms( $post->ID; array( $id )) instead of wp_set_post_terms( $post->ID; $id) take a look here

Create WooCommerce Page with products on sale

I am trying to make a page, (default woocommerce archive style), that it will show only the products that are in Sale. Here to mention, that i have only, variable products and not simple.
I tried to make my custom shortcode
global $woocommerce_loop;
$atts = shortcode_atts( array(
'per_page' => '-1',
'columns' => '4',
'orderby' => 'title',
'order' => 'asc'
), $atts );
// Get products on sale
$product_ids_on_sale = wc_get_product_ids_on_sale();
$meta_query = WC()->query->get_meta_query();
$args = array(
'posts_per_page' => $atts['per_page'],
'orderby' => $atts['orderby'],
'order' => $atts['order'],
'no_found_rows' => 1,
'post_status' => 'publish',
'post_type' => 'product',
'meta_query' => $meta_query,
'post__in' => array_merge( array( 0 ), $product_ids_on_sale )
);
ob_start();
$products = new WP_Query( apply_filters( 'woocommerce_shortcode_products_query', $args, $atts ) );
$columns = absint( $atts['columns'] );
$woocommerce_loop['columns'] = $columns;
if ( $products->have_posts() ) :
woocommerce_product_loop_start();
while ( $products->have_posts() ) : $products->the_post();
wc_get_template_part( 'content', 'product' );
endwhile; // end of the loop.
woocommerce_product_loop_end();
endif;
wp_reset_postdata();
return '<div class="woocommerce columns-' . $columns . '">' . ob_get_clean() . '</div>';
but as a result i get only 2 products.
Any help or ideas?
Why are you trying to create a new shortcode? Woocommerce has provided its own shortcode to show the products on sale in its own archival style :
[sale_products per_page="12"]
You can see the whole list here
I found out a temporary solution in this by creating a custom shortcode. I don't know why i don't get all the sale products with the default woocommerce shortcode.
This worked for me:
function variable_sale_products( $atts ) {
global $woocommerce, $product;
$args = array(
'post_type' => 'product',
'post_status' => 'publish',
'posts_per_page' => -1
);
ob_start();
$loop = new WP_Query( $args );
if ( $loop->have_posts() ) {
woocommerce_product_loop_start();
while ( $loop->have_posts() ) : $loop->the_post();
$id = get_the_ID();
$_product = wc_get_product( $id );
if($_product->is_on_sale()){
wc_get_template_part( 'content', 'product' );
}
endwhile;
woocommerce_product_loop_end();
}
wp_reset_postdata();
return '<div class="woocommerce columns-4">' . ob_get_clean() . '</div>';
}
add_shortcode( 'variation_sale_product', 'variable_sale_products' );
If you have any other suggestion i'd like to hear from you
Try to expand $args (this code adds sale simple and variable products):
$args = array(
'posts_per_page' => -1,
'post_type' => 'product',
'meta_key' => 'total_sales',
'orderby' => 'meta_value_num',
'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'
)
)
);
I tried [sale_products per_page="12"] , but it was showing only only one product.
Below is the steps I did to show all the products on sale :
1- go to WooCommerce -> Status -> Tools tab and click the button next to generate lookup tables and give it some time to complete before checking again. If this didn't work so proceed with the next step.
2- Update the shortode to be [sale_products per_page="32" paginate="true" columns="4" orderby="random"]. Again if this didn't work implement the next step.
3- This is not logical but what I did is that I removed the "sale price" of the item which appears in the on sale page. After that all other items appeared !!
Hope this helps anybody

Resources