skip to Main Content

I am trying to create a varible product with attributes and terms but its not working.

Here is my code

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


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
}

This is based on answer from Accepted Answer

I am facing two issue here

  1. its creating Attribute 2 only without their values
  2. Attribute is not getting assigned to product
  3. set_transient seems not working properly in first attempt

Any idea

2

Answers


  1. As I am checking your code, we have done few changes now it’s working as excepted.

    Source code

    add_action( 'admin_init', 'create_custom_product_attribute' );
    
    function create_custom_product_attribute(){
        
        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( 'Value1', 'Value2' ),
                'Attribute 2'   =>  array( 'Value1', 'Value2', 'Value3' ),
            ),
        ) );
    }
    
    
    /**
     * 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 ('add_custom_attribute') ) 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  ---------------------- ##
        // 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 ){
            $attr_name = ucfirst($key);
            $attr_slug = sanitize_title($key);
            $taxonomy = wc_attribute_taxonomy_name(wp_unslash($key));
            // NEW Attributes: Register and save them
            
            if (taxonomy_exists($taxonomy))
            {
                $attribute_id = wc_attribute_taxonomy_id_by_name($attr_slug);   
            }else{
                $attribute_id = add_custom_attribute($attr_name);
            }
            
            $product_attributes[$taxonomy] = array (
                'name'         => $taxonomy,
                'value'        => '',
                'position'     => '',
                'is_visible'   => 0,
                'is_variation' => 1,
                'is_taxonomy'  => 1
            );
    
            if($attribute_id){
                // Iterating through the variations attributes
                foreach ($terms as $term_name )
                {
                    $taxonomy = 'pa_'.$attr_slug; // The attribute taxonomy
            
                    // If taxonomy doesn't exists we create it (Thanks to Carl F. Corneil)
                    if( ! taxonomy_exists( $taxonomy ) ){
                        register_taxonomy(
                            $taxonomy,
                        'product_variation',
                            array(
                                'hierarchical' => false,
                                'label' => $attr_name,
                                'query_var' => true,
                                'rewrite' => array( 'slug' => $attr_slug), // The base slug
                            ),
                        );
                    }
            
                    // Check if the Term name exist and if not we create it.
                    if( ! term_exists( $term_name, $taxonomy ) ){
                        wp_insert_term( $term_name, $taxonomy ); // Create the term
                    }
                    //$term_slug = get_term_by('name', $term_name, $taxonomy )->slug; // Get the term slug
    
                    // Get the post Terms names from the parent variable product.
                    $post_term_names =  wp_get_post_terms( $product_id, $taxonomy, array('fields' => 'names') );
    
                    // Check if the post term exist and if not we set it in the parent variable product.
                    if( ! in_array( $term_name, $post_term_names ) )
                        wp_set_post_terms( $product_id, $term_name, $taxonomy, true );
    
                    // Set the attribute data in the product variation
                    //update_post_meta($variation_id, 'attribute_'.$taxonomy, $term_slug );
                }
            }
        }
        update_post_meta( $product_id, '_product_attributes', $product_attributes ); 
        $product->save(); // Save the data
    }
    
    /*
    * Register a global woocommerce product add attribute Class.
    *
    * @param str   $nam | name of attribute
    * @param arr   $vals | array of variations
    * 
    */
    function add_custom_attribute($nam){
    
        $attrs = array();      
        $attributes = wc_get_attribute_taxonomies(); 
    
        $slug = sanitize_title($nam);
    
        foreach ($attributes as $key => $value) {
            array_push($attrs,$attributes[$key]->attribute_name);                    
        } 
    
        if (!in_array( $nam, $attrs ) ) {          
            $args = array(
                'slug'    => $slug,
                'name'   => __( $nam, 'woocommerce' ),
                'type'    => 'select',
                'orderby' => 'menu_order',
                'has_archives'  => false,
            );                    
            return wc_create_attribute($args);
        }               
    }
    

    Now you can change add_action and attribute value accordingly.

    Attribute Page:
    enter image description here

    Product Page:
    enter image description here

    Login or Signup to reply.
  2. Final Solution

    Creating attributes with wc_create_attribute, and then we explicitly registering taxonomies solves the problem:

    /**
     * Create a new global attribute if doesn't exist.
     *
     * @since 3.2.0
     * @param string $name | Name of the attribute.
     * @param string $label | Label of the attribute.
     */
    function create_global_attribute($name, $label = '') {
        $attributes = wc_get_attribute_taxonomies();
    
        $slugs = wp_list_pluck( $attributes, 'attribute_name' );
    
        if ( ! in_array( $name, $slugs ) ) {
    
            $args = array(
                'slug'          => wc_sanitize_taxonomy_name( $name ),
                'name'          => ! empty( $label ) ? $label : ucfirst( $name ),
                'type'          => 'select',
                'orderby'       => 'menu_order',
                'has_archives'  => false,
            );
    
            $result = wc_create_attribute( $args );
    
        }
    }
    
    /**
     * Create a new variable product (with new attributes if they are).
     *
     * @since 3.0.0
     * @param array $data | The data to insert in the product.
     */
    function create_product_variation( $data ){
        if( ! function_exists ( 'create_global_attribute' ) ) return;
    
        $postname = sanitize_title( $data['title'] );
        $author = empty( $data['author'] ) ? '1' : $data['author'];
    
        $post_data = [
            '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
    
            // NEW Attributes: Create if doesn't exist and register taxonomy
            if( ! taxonomy_exists( $taxonomy ) ) {
                create_global_attribute( $key, $key );
                register_taxonomy( $taxonomy, array('product'), array(
                    'type'    => 'select',
                    'orderby' => 'menu_order',
                    'public'  => 0,
                ));
            }
    
            $product_attributes[$taxonomy] = [
                '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, ['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
    }
    

    Regarding adding atributes in your code

    The wc_get_attribute_taxonomies retrieves the last added attribute because you add the taxonomy incorrectly, namely you don’t register it but only insert directly to the DB, so the transient wc_attribute_taxonomies contains the last added attribute:

    ...
    if( empty($attribute_id) ) {
        /**
         * New taxonomy inserts directly here 
         * without getting registered in $wp_taxonomies 
         */
        $wpdb->insert(  "{$wpdb->prefix}woocommerce_attribute_taxonomies", $args );
        set_transient( 'wc_attribute_taxonomies', false );
    }
    
    if( $set ){
        $attributes = wc_get_attribute_taxonomies();
        /**
         * Here $attributes containes the last attribute 
         * because the wc_get_attribute_taxonomies
         * fetches the cached value.
         * @see https://woocommerce.github.io/code-reference/files/woocommerce-includes-wc-attribute-functions.html#source-view.55
         */
        $args['attribute_id'] = get_attribute_id_from_name( $name );
        $attributes[] = (object) $args;
        set_transient( 'wc_attribute_taxonomies', $attributes );
    } else {
        return;
    }
    ...
    

    You can fetch the attributes directly from the database as Woocommerce does in the mentioned function, so the code would be:

    ...
    if( empty($attribute_id) ) {
        /**
         * New taxonomy inserts directly here 
         * without getting registered in $wp_taxonomies 
         */
        $wpdb->insert(  "{$wpdb->prefix}woocommerce_attribute_taxonomies", $args );
        set_transient( 'wc_attribute_taxonomies', false );
    }
    
    if( $set ){
        $attributes = $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}woocommerce_attribute_taxonomies WHERE attribute_name != '' ORDER BY attribute_name ASC;" );
        $args['attribute_id'] = get_attribute_id_from_name( $name );
        $attributes[] = (object) $args;
        set_transient( 'wc_attribute_taxonomies', $attributes );
    } else {
        return;
    }
    ...
    

    Regarding adding terms in your code

    the wp_insert_term returns WP_Error when checks the taxonomy for this term exists. Even though you insert new taxonomy here:

    $wpdb->insert(  "{$wpdb->prefix}woocommerce_attribute_taxonomies", $args );
    

    as I said before, you do that directly, without registering it in $wp_taxonomies that’s why it doesn’t add new terms:

    // 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 ) );
        /**
         * wp_insert_term checks the given $taxonomy exists using taxonomy_exists()
         * @see https://github.com/WordPress/WordPress/blob/5.8/wp-includes/taxonomy.php#L2280
         * in turn taxonomomy_exists() checks it within $wp_taxonomies
         * @see https://github.com/WordPress/WordPress/blob/5.8/wp-includes/taxonomy.php#L309
         */
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search