How to add variation stock status to Woocommerce product variation dropdown
Asked Answered
B

5

9

I would like to show the stock status (eg. In Stock / Out of Stock) for each product variation shown in the drop down list of variations on the Woocommerce Product Page. I have copied the relevant function to my theme's functions.php file, and can edit the content, but am unsure how to pull out the required stock status for each variation.


// Updated Woocommerce Product Variation Select 

if ( ! function_exists( 'wc_dropdown_variation_attribute_options' ) ) {

    /**
     * Output a list of variation attributes for use in the cart forms.
     *
     * @param array $args
     * @since 2.4.0
     */

     /*

    function wc_dropdown_variation_attribute_options( $args = array() ) {
        $args = wp_parse_args( apply_filters( 'woocommerce_dropdown_variation_attribute_options_args', $args ), array(
            'options'          => false,
            'attribute'        => false,
            'product'          => false,
            'selected'         => false,
            'name'             => '',
            'id'               => '',
            'class'            => '',
            'show_option_none' => __( 'Choose an option', 'woocommerce' ),
        ) );

        $options               = $args['options'];
        $product               = $args['product'];
        $attribute             = $args['attribute'];
        $name                  = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute );
        $id                    = $args['id'] ? $args['id'] : sanitize_title( $attribute );
        $class                 = $args['class'];
        $show_option_none      = $args['show_option_none'] ? true : false;
        $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' ); // We'll do our best to hide the placeholder, but we'll need to show something when resetting options.

        if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) {
            $attributes = $product->get_variation_attributes();
            $options    = $attributes[ $attribute ];
        }

        $html = '';
        $html .= '' . esc_html( $show_option_none_text ) . '';

        if ( ! empty( $options ) ) {
            if ( $product && taxonomy_exists( $attribute ) ) {
                // Get terms if this is a taxonomy - ordered. We need the names too.
                $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) );



                foreach ( $terms as $term ) {
                    if ( in_array( $term->slug, $options ) ) {
                        $html .= 'slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) ) . ' ';
                    }
                }
            } else {
                foreach ( $options as $option ) {
                    // This handles lt 2.4.0 bw compatibility where text attributes were not sanitized.
                    $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false );

                    $html .= '' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) ) . '  Output Stock Details Here ';
                }
            }
        }

        $html .= '';

        echo apply_filters( 'woocommerce_dropdown_variation_attribute_options_html', $html, $args );
    }
}

I can pull out the stock level for the overall product, but now for each variation.

Any help would be greatly appreciated.

Burble answered 8/11, 2017 at 12:42 Comment(3)
It is the case, however, Ali_k's solution below appears to have done the trick.Burble
Ali_k's solution doesn't really works when there is multiple select fields in a variable product… I have maid an updated answer below. Check the screen shot at the end. Logically this can't work if you really think about it (for multiple select fields with multiple option values).Modred
For variable products that have more than one attribute (dropdown) on the product page: #67352547Albeit
S
7

Ok, first you'll need to get product variations like this:

$variations = $product->get_available_variations();

And inside options loop, you need to loop through the variations and find the current option stock status

foreach ($variations as $variation) {
    if($variation['attributes'][$name] == $option) {
        $stock = $variation['is_in_stock'];

    }
}

Outside the variations loop you need to add the wording for in-stock and out-of-stock variations

if( $stock == 1) {
    $stock_content = ' - In stock';
} else {
    $stock_content = ' - Out of stock';
}

Then change the html to include an additional variable ($stock_content)

$html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( $option  .  $stock_content ) . '</option>'; 

So a complete function will look like this:

add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'show_stock_status_in_dropdown', 10, 2);
function show_stock_status_in_dropdown( $html, $args ) {
    $options = $args['options']; 
    $product = $args['product']; 
    $attribute = $args['attribute']; 
    $name = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute ); 
    $id = $args['id'] ? $args['id'] : sanitize_title( $attribute ); 
    $class = $args['class']; 
    $show_option_none = $args['show_option_none'] ? true : false; 
    $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' ); 

  // Get all product variations
    $variations = $product->get_available_variations();

    if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) { 
        $attributes = $product->get_variation_attributes(); 
        $options = $attributes[ $attribute ]; 
    } 

    $html = '<select id="' . esc_attr( $id ) . '" class="' . esc_attr( $class ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '" data-show_option_none="' . ( $show_option_none ? 'yes' : 'no' ) . '">'; 
    $html .= '<option value="">' . esc_html( $show_option_none_text ) . '</option>'; 

    if ( ! empty( $options ) ) { 
        if ( $product && taxonomy_exists( $attribute ) ) { 
          // Get terms if this is a taxonomy - ordered. We need the names too. 
          $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) ); 

          foreach ( $terms as $term ) { 
                if ( in_array( $term->slug, $options ) ) { 
                    $html .= '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) ) . '</option>'; 
                } 
            }
        } else {
            foreach ( $options as $option ) {
                    foreach ($variations as $variation) {
                        if($variation['attributes'][$name] == $option) {
                            $stock = $variation['is_in_stock'];
                        }
                    }       
                if( $stock == 1) {
                    $stock_content = ' - (In Stock)';
                } else {
                    $stock_content = ' - (Out of Stock)';
                }
                 // This handles < 2.4.0 bw compatibility where text attributes were not sanitized. 
                $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false ); 

                $html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( $option  .  $stock_content ) . '</option>'; 

            }
        } 
    } 

    $html .= '</select>'; 

    return $html;
}
Scalp answered 8/11, 2017 at 15:42 Comment(1)
"Hello, this code isn't functioning as expected. Could you please assist with updating it?"Indomitability
M
10

Update 2021 (Only for variable products with 1 dropdown) - thanks to @Alex Banks

Anyway this will really work when there is ONLY ONE dropdown select field (so one attribute for the variations).

With multiple attributes (so multiple dropdown select fields) it displays something that can be wrong depending on the variations stock status attributes terms combination.

See the screenshot at the end that shows a wrong display case…

I have tried the code of Ali_k, but it was not working in my test server when variable products have multiple dropdowns.

So I have made some changes to the Ali_k's code to get this working in my test server (with last WooCommerce version).

To handle backorders see: Add backorders stock status to Woocommerce variable product dropdown

The code:

// Function that will check the stock status and display the corresponding additional text
function get_stock_status_text( $product, $name, $term_slug ){
    foreach ( $product->get_available_variations() as $variation ){
        if($variation['attributes'][$name] == $term_slug ) {
            $stock = $variation['is_in_stock'];
            break;
        }
    }
    return $stock == 1 ? ' - (In Stock)' : ' - (Out of Stock)';
}

// The hooked function that will add the stock status to the dropdown options elements.
add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'show_stock_status_in_dropdown', 10, 2);
function show_stock_status_in_dropdown( $html, $args ) {
    // Only if there is a unique variation attribute (one dropdown)
    if( sizeof($args['product']->get_variation_attributes()) == 1 ) :

    $options               = $args['options'];
    $product               = $args['product'];
    $attribute             = $args['attribute']; // The product attribute taxonomy
    $name                  = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute );
    $id                    = $args['id'] ? $args['id'] : sanitize_title( $attribute );
    $class                 = $args['class'];
    $show_option_none      = $args['show_option_none'] ? true : false;
    $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' );

    if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) {
        $attributes = $product->get_variation_attributes();
        $options    = $attributes[ $attribute ];
    }

    $html = '<select id="' . esc_attr( $id ) . '" class="' . esc_attr( $class ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '" data-show_option_none="' . ( $show_option_none ? 'yes' : 'no' ) . '">';
    $html .= '<option value="">' . esc_html( $show_option_none_text ) . '</option>';

    if ( ! empty( $options ) ) {
        if ( $product && taxonomy_exists( $attribute ) ) {
            $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) );

            foreach ( $terms as $term ) {
                if ( in_array( $term->slug, $options ) ) {
                    // HERE Added the function to get the text status
                    $stock_status = get_stock_status_text( $product, $name, $term->slug );
                    $html .= '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) . $stock_status ) . '</option>';
                }
            }
        } else {
            foreach ( $options as $option ) {
                $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false );
                // HERE Added the function to get the text status
                $stock_status = get_stock_status_text( $product, $name, $option );
                $html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) . $stock_status ) . '</option>';
            }
        }
    }
    $html .= '</select>';

    endif;

    return $html;
}

Code goes in function.php file of your active child theme (or theme) or also in any plugin file.

Tested and work in variable products that have only one attribute for variations

enter image description here


With the code of Ali_K, Here an example of a wrong displayed text for a variable product with multiple select fields (so multiple attributes for variations):

enter image description here

Modred answered 8/11, 2017 at 21:8 Comment(1)
After multiple back and forths with ChatGPT and multiple non-working solutions, this did the trick for me.Hollandia
S
7

Ok, first you'll need to get product variations like this:

$variations = $product->get_available_variations();

And inside options loop, you need to loop through the variations and find the current option stock status

foreach ($variations as $variation) {
    if($variation['attributes'][$name] == $option) {
        $stock = $variation['is_in_stock'];

    }
}

Outside the variations loop you need to add the wording for in-stock and out-of-stock variations

if( $stock == 1) {
    $stock_content = ' - In stock';
} else {
    $stock_content = ' - Out of stock';
}

Then change the html to include an additional variable ($stock_content)

$html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( $option  .  $stock_content ) . '</option>'; 

So a complete function will look like this:

add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'show_stock_status_in_dropdown', 10, 2);
function show_stock_status_in_dropdown( $html, $args ) {
    $options = $args['options']; 
    $product = $args['product']; 
    $attribute = $args['attribute']; 
    $name = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute ); 
    $id = $args['id'] ? $args['id'] : sanitize_title( $attribute ); 
    $class = $args['class']; 
    $show_option_none = $args['show_option_none'] ? true : false; 
    $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' ); 

  // Get all product variations
    $variations = $product->get_available_variations();

    if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) { 
        $attributes = $product->get_variation_attributes(); 
        $options = $attributes[ $attribute ]; 
    } 

    $html = '<select id="' . esc_attr( $id ) . '" class="' . esc_attr( $class ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '" data-show_option_none="' . ( $show_option_none ? 'yes' : 'no' ) . '">'; 
    $html .= '<option value="">' . esc_html( $show_option_none_text ) . '</option>'; 

    if ( ! empty( $options ) ) { 
        if ( $product && taxonomy_exists( $attribute ) ) { 
          // Get terms if this is a taxonomy - ordered. We need the names too. 
          $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) ); 

          foreach ( $terms as $term ) { 
                if ( in_array( $term->slug, $options ) ) { 
                    $html .= '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) ) . '</option>'; 
                } 
            }
        } else {
            foreach ( $options as $option ) {
                    foreach ($variations as $variation) {
                        if($variation['attributes'][$name] == $option) {
                            $stock = $variation['is_in_stock'];
                        }
                    }       
                if( $stock == 1) {
                    $stock_content = ' - (In Stock)';
                } else {
                    $stock_content = ' - (Out of Stock)';
                }
                 // This handles < 2.4.0 bw compatibility where text attributes were not sanitized. 
                $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false ); 

                $html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( $option  .  $stock_content ) . '</option>'; 

            }
        } 
    } 

    $html .= '</select>'; 

    return $html;
}
Scalp answered 8/11, 2017 at 15:42 Comment(1)
"Hello, this code isn't functioning as expected. Could you please assist with updating it?"Indomitability
H
3

@LoicTheAztec

I have been using your updated code with the backorder function replacement too but come across an error due to a non-existent function call.

get_the_stock_status() is now get_stock_status() is it not ?

Anyhow, the updated code below fixes the issue and an issue I had with a gift card plugin that was failing to load due to the above error.

// Function that will check the stock status and display the corresponding additional text
function get_stock_status_text( $product, $name, $term_slug ){
    foreach ( $product->get_available_variations() as $variation ){
        if($variation['attributes'][$name] == $term_slug ) {
            $is_in_stock = $variation['is_in_stock'];
            $backordered = get_post_meta( $variation['variation_id'], '_backorders', true );
            $stock_qty   = get_post_meta( $variation['variation_id'], '_stock', true );
            break;
        }
    }
    $stock_status_text = $is_in_stock == 1 ? ' - (In Stock)' : ' - (Out of Stock)';
    return $backordered !== 'no' && $stock_qty <= 0 ? ' - (On Backorder)' : $stock_status_text;
}

// The hooked function that will add the stock status to the dropdown options elements.
add_filter( 'woocommerce_dropdown_variation_attribute_options_html', 'show_stock_status_in_dropdown', 10, 2);
function show_stock_status_in_dropdown( $html, $args ) {

    // Only if there is a unique variation attribute (one dropdown)
    if( sizeof($args['product']->get_variation_attributes()) == 1 ) :

    $options               = $args['options'];
    $product               = $args['product'];
    $attribute             = $args['attribute']; // The product attribute taxonomy
    $name                  = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute );
    $id                    = $args['id'] ? $args['id'] : sanitize_title( $attribute );
    $class                 = $args['class'];
    $show_option_none      = $args['show_option_none'] ? true : false;
    $show_option_none_text = $args['show_option_none'] ? $args['show_option_none'] : __( 'Choose an option', 'woocommerce' );

    if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) {
        $attributes = $product->get_variation_attributes();
        $options    = $attributes[ $attribute ];
    }

    $html = '<select id="' . esc_attr( $id ) . '" class="' . esc_attr( $class ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '" data-show_option_none="' . ( $show_option_none ? 'yes' : 'no' ) . '">';
    $html .= '<option value="">' . esc_html( $show_option_none_text ) . '</option>';

    if ( ! empty( $options ) ) {
        if ( $product && taxonomy_exists( $attribute ) ) {
            $terms = wc_get_product_terms( $product->get_id(), $attribute, array( 'fields' => 'all' ) );

            foreach ( $terms as $term ) {
                if ( in_array( $term->slug, $options ) ) {
                    // HERE Added the function to get the text status
                    $stock_status = get_stock_status_text( $product, $name, $term->slug );
                    $html .= '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $term->name ) . $stock_status ) . '</option>';
                }
            }
        } else {
            foreach ( $options as $option ) {
                $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false );
                // HERE Added the function to get the text status
                $stock_status = get_stock_status( $product, $name, $option );
                $html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) . $stock_status ) . '</option>';
            }
        }
    }
    $html .= '</select>';

    endif;

    return $html;
}
Hagberry answered 25/9, 2019 at 9:29 Comment(1)
The function name get_the_stock_status( $product, $name, $option )was wrong in my code and need to be replaced with get_stock_status_text( $product, $name, $option ) but **NOT with get_stock_status()which is a WooCommerce WC_Product method and not a function.Modred
C
2

I adapted your code so that only in stock variations display in the dropdown. I made $stock return bool, then

if ($stock_status){
            $html .= '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) /*. $stock_status*/ ) . '</option>';
            }

If you can think of a better way to do this It would be much appreciated. P.S thank you for this solution, I can't believe this isn't default woocommerce behaviour to hide out of stock variants.

Cavender answered 14/10, 2020 at 20:4 Comment(0)
D
0

I have figured out a way to achieve this using RegEx (Regular Expressions) string replacement on the HTML output provided by the filter. It may be slightly complicated but the "advantage" here is that the template function is not directly modified. We only modify the output HTML of that function which it makes available for us through the filter.

Below is the full code:

function show_variation_stock_status ($html, $args) {

    // Adding stock status to each dropdown option only makes sense if there is 
    // a unique variation attribute (one dropdown)
    if( count($args['product']->get_variation_attributes()) > 1 ) {
        // Return the unfiltered HTML code
        return $html;
    }
    
    // Currently not implemented for the case where taxonomy terms are used. I haven't yet come
    // across this scenario
    if ( $args['product'] && taxonomy_exists($args["attribute"]) ) {
        // Return the unfiltered HTML code
        return $html;
    }
    
    # At this point, we will process the HTML code and append the stock information
    
    # Initialize the stock status strings to use for each case
    $stock_status_str               = "";
    $stock_status_str_in_stock      = " (In stock)";
    $stock_status_str_no_stock      = " (Out of stock)";
    
    // Get the stats array of all the available variations of this product
    $variations_stats   = $args['product']->get_available_variations();
    
    // Get the key name of the attribute
    $attr_key_name      = array_keys($variations_stats[0]["attributes"])[0];    # ie. we pick the name from the first variation
    
    // To make the stock status information align with each other for visual appeal, we would need to
    // know the longest name string and pad shorter ones with spaces or other character accordingly.
    $maxNameLen = 0;    # Initialise the variable
    foreach ($variations_stats as $variation_stat) {
        $currentNameLen     = strlen( $variation_stat["attributes"][$attr_key_name] );
        $maxNameLen         = ($currentNameLen > $maxNameLen) ? $currentNameLen : $maxNameLen;
    }
    $maxNameLen     = $maxNameLen + 6;      # Add some allowance
    
    
    /*
        The RegEx option name replacement code assumes that the output HTML of the select options is of the form:
        
            <option value="$optionTerm" [ selected='selected']>$optionTerm</option>
        
        Here,
            [ selected='selected']      in square brackets is to say the phrase may or may not be present in the HTML
            $optionTerm                 represents the attribute option name that was outputted
            
        
        Currently, I'm only interested in the actual display text (ie. the latter $optionTerm) so the regex
        matching should select it as follows
            (<option value="$optionTerm" [selected='selected']>)($optionTerm)(</option>)
        
        ie. the regex will be
            ~~(<option value.*?>)($ourOptionTerm)(</option>)~~
            
        Since PHP uses the forward-slash as the regex pattern delimitter, we have to escape forward slashes
        in the pattern, so finally, we have something like:
            ~~/(<option value.*?>)($escapedOptionTermToSearch)(<\/option>)/~~
            
        So that the new replacement string will be: '${1}' . $newOptionTerm . '${3}';
    */
    
    // Do the name replacement for each of the available variations we obtained
    foreach ($variations_stats as $variation_stat) {
        $nameToMatchRaw         = $variation_stat["attributes"][$attr_key_name];
        $nameToMatchEscaped     = preg_quote($nameToMatchRaw, "/");
        
        # Select the appropriate stock status string based on the current stock status
        if ( $variation_stat["is_in_stock"] ) {
            $stock_status_str   = $stock_status_str_in_stock;
        }
        else {
            $stock_status_str   = $stock_status_str_no_stock;
        }
        
        # Append the stock status string to the name, padding shorter names
        # Pad character "-" seems to look okay
        $updatedNameStr         = $nameToMatchRaw . " ";    # Append space before padding
        $updatedNameStr         = str_pad($updatedNameStr, $maxNameLen, "-") . $stock_status_str;

        # Prepare the regex replace patterns
        $regexMatchPattern      = "/" . "(<option value.*?>)" . "(" . $nameToMatchEscaped . ")" . "(<\/option>)" . "/"; # Note the escaped forward-slash
        $regexReplacePattern    = '${1}' . $updatedNameStr . '${3}';
        
        # Execute the RegEx replacement on the HTML code
        $html                   = preg_replace($regexMatchPattern, $regexReplacePattern, $html);        
    }

    // Return the filtered HTML code
    return $html;
}
# Apply the filter
add_filter("woocommerce_dropdown_variation_attribute_options_html", "show_variation_stock_status", 10, 2);

Result Screenshot 1: Working screenshot 1 Result Screenshot 2: Working screenshot 2

Would be happy to know your suggestions, for example if there is a more efficient regex pattern etc.

Duwalt answered 3/3, 2024 at 17:44 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.