19

I would like to programmatically create a variable product ("parent" product) with two new variante attributes - all that from a WordPress plugin (so no HTTP Request to an API).

These two variante attributes should also be created on the fly.

How can this be done ?

(with WooCommerce version 3)


Update : I have written more lines of code on this that I wished, and tried many things to solve it, using wooCommerce objects, and added missing data about terms, termmeta, the relationship from term with post, in the database using the WordPress database object - but nothing has sufficed to make it work. And I couldn't pin-point where I went wrong - that is why I couldn't provide a narrower problem - things for which stackoverflow is more made for.

starball
  • 20,030
  • 7
  • 43
  • 238
Cedric
  • 5,135
  • 11
  • 42
  • 61
  • 1
    Have you tried using the api to get the parent product values and then generate a new product object? – LordNeo Nov 27 '17 at 19:29
  • I have tried many things, including this one, but have always failed.. – Cedric Nov 27 '17 at 19:41
  • Also note that there is two parts in the question - a variante attribute's option should also be created, and "attached" to the variable product. – Cedric Nov 27 '17 at 19:42
  • the creating of the attribute has never been an issue. attaching it does unless i do 2 calls, one to create the "parent" product and one for the variants. – LordNeo Nov 27 '17 at 20:04
  • I omitted to specify it shall be done with a Wordpress plugin - so not with a HTTP request call toward the API – Cedric Nov 27 '17 at 20:32
  • 7
    Sharing your research helps everyone. Tell us what you've tried and why it didn’t meet your needs. This demonstrates that you’ve taken the time to try to help yourself, it saves us from reiterating obvious answers, and most of all it helps you get a more specific and relevant answer! See also: [ask] – j08691 Dec 11 '17 at 21:29
  • This is totally true @j08691 - even if I shouldn't have the mess I have to create a parent, I should've explained the ways tried. – Cedric Dec 12 '17 at 18:05
  • This seems more like "do my work in exchange for reputation" than "help me overcome this issue" as it stands. – Eligos Dec 14 '17 at 09:44
  • You are right, it seems like it. At the time, I had written more lines of codes on this that I wished, using woocommerce objects (WC_Product_Variable, its parent, and added missing data in the database using the wordpress database object - but nothing has sufficed to make it wor correctly. – Cedric Dec 14 '17 at 10:50
  • @Cedric Can you include details about the attributes for the variants ? I can provide the complete code to you. – Shahbaz A. Dec 14 '17 at 11:13

3 Answers3

32

After: Create programmatically a WooCommerce product variation with new attribute values

Here you get the way to create a new variable product with new product attributes + values:

/**
 * Save a new product attribute from his name (slug).
 *
 * @since 3.0.0
 * @param string $name  | The product attribute name (slug).
 * @param string $label | The product attribute label (name).
 */
function save_product_attribute_from_name( $name, $label='', $set=true ){
    if( ! function_exists ('get_attribute_id_from_name') ) return;

    global $wpdb;

    $label = $label == '' ? ucfirst($name) : $label;
    $attribute_id = get_attribute_id_from_name( $name );

    if( empty($attribute_id) ){
        $attribute_id = NULL;
    } else {
        $set = false;
    }
    $args = array(
        'attribute_id'      => $attribute_id,
        'attribute_name'    => $name,
        'attribute_label'   => $label,
        'attribute_type'    => 'select',
        'attribute_orderby' => 'menu_order',
        'attribute_public'  => 0,
    );


    if( empty($attribute_id) ) {
        $wpdb->insert(  "{$wpdb->prefix}woocommerce_attribute_taxonomies", $args );
        set_transient( 'wc_attribute_taxonomies', false );
    }

    if( $set ){
        $attributes = wc_get_attribute_taxonomies();
        $args['attribute_id'] = get_attribute_id_from_name( $name );
        $attributes[] = (object) $args;
        //print_r($attributes);
        set_transient( 'wc_attribute_taxonomies', $attributes );
    } else {
        return;
    }
}

/**
 * Get the product attribute ID from the name.
 *
 * @since 3.0.0
 * @param string $name | The name (slug).
 */
function get_attribute_id_from_name( $name ){
    global $wpdb;
    $attribute_id = $wpdb->get_col("SELECT attribute_id
    FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
    WHERE attribute_name LIKE '$name'");
    return reset($attribute_id);
}

/**
 * Create a new variable product (with new attributes if they are).
 * (Needed functions:
 *
 * @since 3.0.0
 * @param array $data | The data to insert in the product.
 */

function create_product_variation( $data ){
    if( ! function_exists ('save_product_attribute_from_name') ) return;

    $postname = sanitize_title( $data['title'] );
    $author = empty( $data['author'] ) ? '1' : $data['author'];

    $post_data = array(
        'post_author'   => $author,
        'post_name'     => $postname,
        'post_title'    => $data['title'],
        'post_content'  => $data['content'],
        'post_excerpt'  => $data['excerpt'],
        'post_status'   => 'publish',
        'ping_status'   => 'closed',
        'post_type'     => 'product',
        'guid'          => home_url( '/product/'.$postname.'/' ),
    );

    // Creating the product (post data)
    $product_id = wp_insert_post( $post_data );

    // Get an instance of the WC_Product_Variable object and save it
    $product = new WC_Product_Variable( $product_id );
    $product->save();

    ## ---------------------- Other optional data  ---------------------- ##
    ##     (see WC_Product and WC_Product_Variable setters methods)

    // THE PRICES (No prices yet as we need to create product variations)

    // IMAGES GALLERY
    if( ! empty( $data['gallery_ids'] ) && count( $data['gallery_ids'] ) > 0 )
        $product->set_gallery_image_ids( $data['gallery_ids'] );

    // SKU
    if( ! empty( $data['sku'] ) )
        $product->set_sku( $data['sku'] );

    // STOCK (stock will be managed in variations)
    $product->set_stock_quantity( $data['stock'] ); // Set a minimal stock quantity
    $product->set_manage_stock(true);
    $product->set_stock_status('');

    // Tax class
    if( empty( $data['tax_class'] ) )
        $product->set_tax_class( $data['tax_class'] );

    // WEIGHT
    if( ! empty($data['weight']) )
        $product->set_weight(''); // weight (reseting)
    else
        $product->set_weight($data['weight']);

    $product->validate_props(); // Check validation

    ## ---------------------- VARIATION ATTRIBUTES ---------------------- ##

    $product_attributes = array();

    foreach( $data['attributes'] as $key => $terms ){
        $taxonomy = wc_attribute_taxonomy_name($key); // The taxonomy slug
        $attr_label = ucfirst($key); // attribute label name
        $attr_name = ( wc_sanitize_taxonomy_name($key)); // attribute slug

        // NEW Attributes: Register and save them
        if( ! taxonomy_exists( $taxonomy ) )
            save_product_attribute_from_name( $attr_name, $attr_label );

        $product_attributes[$taxonomy] = array (
            'name'         => $taxonomy,
            'value'        => '',
            'position'     => '',
            'is_visible'   => 0,
            'is_variation' => 1,
            'is_taxonomy'  => 1
        );

        foreach( $terms as $value ){
            $term_name = ucfirst($value);
            $term_slug = sanitize_title($value);

            // Check if the Term name exist and if not we create it.
            if( ! term_exists( $value, $taxonomy ) )
                wp_insert_term( $term_name, $taxonomy, array('slug' => $term_slug ) ); // Create the term

            // Set attribute values
            wp_set_post_terms( $product_id, $term_name, $taxonomy, true );
        }
    }
    update_post_meta( $product_id, '_product_attributes', $product_attributes );
    $product->save(); // Save the data
}

Code goes in function.php file of your active child theme (or active theme). Tested and works.


USAGE (example with 2 new attributes + values):

create_product_variation( array(
    'author'        => '', // optional
    'title'         => 'Woo special one',
    'content'       => '<p>This is the product content <br>A very nice product, soft and clear…<p>',
    'excerpt'       => 'The product short description…',
    'regular_price' => '16', // product regular price
    'sale_price'    => '', // product sale price (optional)
    'stock'         => '10', // Set a minimal stock quantity
    'image_id'      => '', // optional
    'gallery_ids'   => array(), // optional
    'sku'           => '', // optional
    'tax_class'     => '', // optional
    'weight'        => '', // optional
    // For NEW attributes/values use NAMES (not slugs)
    'attributes'    => array(
        'Attribute 1'   =>  array( 'Value 1', 'Value 2' ),
        'Attribute 2'   =>  array( 'Value 1', 'Value 2', 'Value 3' ),
    ),
) );

Tested and works.


Related:

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • I managed to get it working, however, I am facing a new problem with WPML. I need to create translated product attributes as well. Product attributes get's imported just fine on the English (Native) site, however, on my translated post (Danish), the product attribute field is there, but the value is empty. I'm guessing it's because I need to translate the attribute as well. Any ideas? @LoicTheAztec? – FooBar Oct 07 '19 at 09:03
  • @FooBar Ask WPML in their very nice and effective support. – LoicTheAztec Oct 07 '19 at 10:08
  • I do, however, I managed to solve it before receiving answers from them (append "-language_code" to the slug). My main trouble with above code is, that on the variations under the product, the values are not selected. E.g. I have a "size" attribute. If I click "Variations" all the variations are created, however, I need to manually select the "Size" attribute for each variation and then save. How can this be done in the script? – FooBar Oct 07 '19 at 14:37
  • I have found that calling `$product->set_attributes(array( $taxonomy => $terms[0] ));` after adding attributes solves this – FooBar Oct 07 '19 at 14:46
  • I am having some issues when running above script, reagarding "Invalid Taxonomy", because an attribute is trying to be set (wp_insert_term) to an invalid taxonomy. I am getting error: "Invalid taxonomy.". I have verified that the taxonomy does indeed exist inside `wp_woocommerce_attribute_taxonomies`. Perhaps the issue is because the attribute does not exist in the "wordpress taxonomy" fields? I am running this script in wp-ajax. Perhaps some inits are not runned (like register taxonomy?) – FooBar Oct 08 '19 at 08:44
  • I noticed that register_taxonomy does indeed get called. The `WC_Post_Types::register_taxonomies()` functions is triggered, and it register its taxonomies. However, I have noticed that the attribute is missing from `wc_get_attribute_taxonomies()` function. My attribute is indeed inside `woocomerce_attribute_taxonomies` but it is not returned upon calling `wc_get_attribute_taxonomies` as the other taxonomies does. It has exactly the same properties as the other taxonomies, except that name and label are different. Hmmm – FooBar Oct 08 '19 at 09:04
  • Aha! I noticed that the old values were still being cached. So I went in and made sure that `wc_get_attribute_taxonomies()` would fetch directly from database, instead of from cache, and this solved the issue. However, it is still weird, because I notice `set_transient( 'wc_attribute_taxonomies', $attributes );` is called in the code.. – FooBar Oct 08 '19 at 09:08
  • Adding `set_transient( 'wc_attribute_taxonomies', false );` after wp_insert() in `save_product_attribute_from_name` function seems to fix the issue – FooBar Oct 08 '19 at 09:10
  • 1
    Sorry, I am back to this issue. I tried deleting all product attributes, and then I experience the following bug. Calling the `save_product_attribute_from_name` and then assigning a term afterwards, results in "Invalid Taxonomy" error. If I make a page refresh it works just fine. But in the same request as `save_product_attribute_from_name` was called, it will die and return a "Invalid Taxonomy". Any suggestions here? – FooBar Oct 08 '19 at 14:28
  • 1
    @LoicTheAztec Your code is not working 100% perfect because of Attributes is not creating their values due to use of set_transient. – kuldip Makadiya Nov 30 '20 at 13:08
  • @kuldipMakadiya Are you able to mange or solve it – Aayush Bansal Sep 20 '21 at 05:53
  • @AayushBansal yes i can – kuldip Makadiya Sep 20 '21 at 16:48
  • I can also confirm it doesn't work properly. I am getting `Invalid taxonomy` error generated by `wp_insert_term()`. But only on the first run. It creates a taxonomy, but it can't insert terms, because that taxonomy is not registered yet, and the new taxonomy is not retrieved by `wc_get_attribute_taxonomies()`. However, on the second run (when the taxonomy was already created on previous run) it creates the terms just fine. @kuldipMakadiya @LoicTheAztec - any ideas guys? – Kristián Filo Oct 22 '21 at 13:49
12

You can also achieve this using the new native functions for setting/getting data from postmeta.

Here is an example that works ( based on the default dummy products of Woocommerce 3)

//Create main product
$product = new WC_Product_Variable();

//Create the attribute object
$attribute = new WC_Product_Attribute();
//pa_size tax id
$attribute->set_id( 1 );
//pa_size slug
$attribute->set_name( 'pa_size' );

//Set terms slugs
$attribute->set_options( array(
        'blue',
        'grey'
) );
$attribute->set_position( 0 );

//If enabled
$attribute->set_visible( 1 );

//If we are going to use attribute in order to generate variations
$attribute->set_variation( 1 );

$product->set_attributes(array($attribute));

//Save main product to get its id
$id = $product->save();


$variation = new WC_Product_Variation();
$variation->set_regular_price(5);
$variation->set_parent_id($id);

//Set attributes requires a key/value containing
// tax and term slug
$variation->set_attributes(array(
        'pa_size' => 'blue'
));

//Save variation, returns variation id
$variation->save();

You can add things like weight, tax, sku etc using the native functions available from Woocommerce 3 and on like set_price, set_sku ect.

Wrap it within a function and you are good to go.

sarakinos
  • 666
  • 11
  • 28
7

Sarakinos answer only worked with two small but important modifications.

  1. The $attribute->set_id() needs to be set to 0.
  2. The $attribute->set_name(); and $variation->set_attributes() do not need the pa_ prefix. The methods already handle the prefixes.

So a working code would be:

//Create main product
$product = new WC_Product_Variable();

//Create the attribute object
$attribute = new WC_Product_Attribute();

//pa_size tax id
$attribute->set_id( 0 ); // -> SET to 0

//pa_size slug
$attribute->set_name( 'size' ); // -> removed 'pa_' prefix

//Set terms slugs
$attribute->set_options( array(
    'blue',
    'grey'
) );

$attribute->set_position( 0 );

//If enabled
$attribute->set_visible( 1 );

//If we are going to use attribute in order to generate variations
$attribute->set_variation( 1 );

$product->set_attributes(array($attribute));

//Save main product to get its id
$id = $product->save();

$variation = new WC_Product_Variation();
$variation->set_regular_price(10);
$variation->set_parent_id($id);

//Set attributes requires a key/value containing
// tax and term slug
$variation->set_attributes(array(
    'size' => 'blue' // -> removed 'pa_' prefix
));

//Save variation, returns variation id
echo get_permalink ( $variation->save() );


// echo get_permalink( $id ); // -> returns a link to check the newly created product

Keep in mind, the product will be bare bones, and will be named 'product'. You need to set those values in the $product before you save it with $id = $product->save();.

dee_ell
  • 81
  • 1
  • 2
  • 1
    For `set_attributes()` **make sure** you use the slug version of the attribute name. You can pass the name through `sanitize_title()` to get it. I wasted a couple of hours due to this. – TheStoryCoder Apr 19 '22 at 12:59