Search code examples
phpwordpresswoocommerceattributesproduct-variations

Bulk rename WooCommerce product attribute for variation term value


In WooCommerce, I have 100 variable products with a variation using custom attribute for variation "Volume" with "50ml" as value. I want to change/update the value from "50ml" to "60ml".

Here is the code that I have so far:

add_action('woocommerce_before_variations_form', 'p4wp_default_variation_value');
function p4wp_default_variation_value() {
    global $product;

    // Check if the product is a variable product
    if ($product->is_type('variable')) {
        $product_attributes = $product->get_attributes();
        $attribute_value_to_find = '60ml';

        // Get the variations with the specified attribute value
        $variations = wc_get_products(array(
            'type'           => 'variation',
            'status'         => 'publish',
            'limit'          => -1,
            'parent'         => $product->get_id(),
            'meta_query'     => array(
                array(
                    'key'     => 'attribute_volume',
                    'value'   => $attribute_value_to_find,
                    'compare' => '=',
                ),
            ),
        ));

        // Loop through the variations and get their IDs
        $variation_ids = array();
        foreach ($variations as $variation) {
            $variation_ids[] = $variation->get_id();
        }

        echo '<pre>';
        print_r($variation_ids);
        echo '</pre>';

        // Check if there are attributes
        if (count($product_attributes)) {
            $product_attributes = get_post_meta($product->get_id(), '_product_attributes', true);

            // Check if the 'volume' attribute exists
            if (isset($product_attributes['volume'])) {
                // Update the 'value' for '50ml' to '60ml'
                $product_attributes['volume']['value'] = str_replace('60ml', '50ml', $product_attributes['volume']['value']);
                // Update the '_product_attributes' meta field
                update_post_meta($product->get_id(), '_product_attributes', $product_attributes);
                //update_post_meta($variation_ids[0], 'attribute_volume', '60ml');
            }
        }
    }
    //Update post meta for post ID 3631
    update_post_meta($variation_ids[0], 'attribute_volume', '50ml');
}

But the code is not working at all. What I am doing wrong? How to change/update the attribute value from "50ml" to "60ml" keeping all product variations functional?

Any help is appreciated.


Solution

  • First, always make a database backup (mandatory).

    The following untested code should update, for all your product variations, "volume" variation attribute value from "50ml" to "60ml" keeping all product variations functional.

    The code is lightweight as it makes one or two direct SQL update queries to the database on all matching products without involving PHP in heavy processes with multiple read/update database queries.

    The code is only active for administrator user roles (see Usage at the end).

    The code work for global product attributes and custom product attributes. Note that global product attributes taxonomy always start with "pa_" (+ the attribute slug).

    add_action('init', 'update_variation_attribute_volume_value');
    function update_variation_attribute_volume_value() {
        global $wpdb, $current_user;
    
        // Restricted to administrator user role
        if ( ! in_array('administrator', $current_user->roles) ) return;
    
        $attribute = 'volume'; // Here define the attribute taxonomy (start with "pa_") or the custom attribute (slug)
        $name_from = '50ml'; // Here define the existing attribute term name or custom attribute name value
        $slug_from = '50ml'; // Here define the existing attribute term slug or custom attribute name value
        $name_to   = '60ml'; // Here define the replacement attribute term name or custom attribute name value
        $slug_to   = '60ml'; // Here define the replacement attribute term slug or custom attribute name value
    
        // Lightweight bulk update all product variations related variation attribute, and sync variable product title
        $query = $wpdb->query( "
            UPDATE {$wpdb->prefix}postmeta pm
            INNER JOIN {$wpdb->prefix}posts p ON pm.post_id =  p.ID
            SET pm.meta_value = '{$slug_to}',
                p.post_title  = REPLACE(p.post_title, '{$name_from}', '{$name_to}'),
                p.post_excerpt = REPLACE(p.post_excerpt, '{$name_from}', '{$name_to}')
            WHERE pm.meta_key = 'attribute_{$attribute}'
            AND pm.meta_value = '{$slug_from}'
            AND p.post_type   = 'product_variation'
        " );
        error_log( "Query (product variation)): \n" . print_r($query, true) ); // DEBUG
    
        // For global attribute (taxonomy): Update the product attribute term
        if ( taxonomy_exists($attribute) && term_exists($slug_from, $attribute) ) {
    
            $term   = get_term_by('slug',$slug_from, $attribute );
            $update = wp_update_term( $term->term_id, $attribute, array('name' => $slug_to, 'slug' => $slug_to) ); // Update term
            error_log( "Taxonomy term update: \n" . print_r($update, true) ); // DEBUG
        } 
        // For custom attribute: Lightweight bulk update the variable product attribute
        else {
            $query2 = $wpdb->query( "
                UPDATE {$wpdb->prefix}postmeta pm
                INNER JOIN {$wpdb->prefix}posts p ON pm.post_id =  p.ID
                SET pm.meta_value = REPLACE(pm.meta_value, '{$slug_from}', '{$slug_to}')
                WHERE pm.meta_key = '_product_attributes'
                AND pm.meta_value LIKE '%{$slug_from}%'
                AND p.post_type = 'product'
            " );
            error_log( "Query two (variable product)): \n" . print_r($query2, true) ); // DEBUG
        }
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). It should work.

    USAGE: Browse any page on your website (front or backend), then remove the code.