Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

woocommerce widget "filter products by attribute" displays unavailable products

I am using on of the Widgets included with WooCommerce called Filter products by attribute. I created a widgetized area on the category page in my Storefront-child-theme functions.php (see code below).

But when I filter by attribue size M for example, it lists products where size M is out of stock...

Any idea how to fix this?

EXAMPLE: Filtering by size M displays this product, which is not available:

enter image description here

/* FILTER BY SIZE WIDGET */
// Adding the widget area.
if (function_exists('register_sidebar')) {
    register_sidebar(array(
    'name' => 'Below category title',
    'id' => 'extra-widget-area',
    'description' => 'Below category title',
    'before_widget' => '<div class="widget below-cat-title-widget">',
    'after_widget' => '</div>',
    'before_title' => '<h6 class="below-cat-title-widget-title">',
    'after_title' => '</h6>'
    ));
}
// placing the widget
add_action( 'woocommerce_archive_description', 'add_my_widget_area', 31 );
function add_my_widget_area() {
  if (function_exists('dynamic_sidebar')) {
    dynamic_sidebar('Below category title');
  }
}

add_action( 'woocommerce_archive_description', 'filter_button_function', 21 );
function filter_button_function() {
    // if ( is_product_category() ) {
        // global $wp_query;
        // $cat_id = $wp_query->get_queried_object_id();
        // $cat_desc = term_description( $cat_id, 'product_cat' );
        if (get_locale() == 'fr_FR') {
            $filter_html = '<div class="filter_by_size" class="subtitle">'.'FILTRE PAR TAILLE'.'&nbsp;&nbsp;<span class="filter-icon"></span>'.'</div>';
        } else {
            $filter_html = '<div class="filter_by_size" class="subtitle">'.'FILTER BY SIZE'.'&nbsp;&nbsp;<span class="filter-icon"></span>'.'</div>';
        }

        echo $filter_html;
    // }
}
like image 395
Louis Avatar asked Oct 02 '20 07:10

Louis


2 Answers

I'll be direct : by default, you can't. It's not related to a certain theme or plugin, but to Woocommerce itself. This is a problem that exists in woocommerce for a very long time. It's not possible by default in woocommerce to handle variable products stock visibility (stock status) based on it's variations visibility as it's something necessary and needed by Woocommerce1.

A way of doing this is to add this action function :

add_action('woocommerce_before_shop_loop_item', 'out_of_stock_variations_loop');
function out_of_stock_variations_loop()
{
    global $product;
    $filter = 'size';
    if ($product->product_type === 'variable') {
        $available = $product->get_available_variations();
        if ($available) {
            foreach ($available as $instockvar) {
                if (isset($instockvar[ 'attributes' ][ 'attribute_pa_' . $filter ])) {
                    if ($_GET[ 'filter_' . $filter ]) {
                        if ( !in_array( $instockvar[ 'attributes' ][ 'attribute_pa_' . $filter ], explode(',', $_GET[ 'filter_' . $filter ]) , true ) || ($instockvar[ 'max_qty' ] <= 0) ) {
                            echo "<style>.post-" . $product->get_id() . " {display: none}</style>";
                        } else {
                            echo "<style>.post-" . $product->get_id() . " {display: list-item !important}</style>";
                        }
                    }
                }
            }
        }
    }
}
?>

Which will only show the list of products in stock. The problem with this is that it will leave blank spaces where the not-in-stock products are deleted by this query, and it can be difficult to circumvent this problem only with css because, on each row, first and last products have first and last classes that determines their layout in CSS. To overcome this, add this jQuery to your child-theme js script:

function openLayeredNavFilterIfSelected() {

  if (jQuery('.wc-layered-nav-term').hasClass('woocommerce-widget-layered-nav-list__item--chosen')) {

    /*keep layered nav filter open, if at least an attribute is selected*/
    jQuery('.woocommerce-widget-layered-nav-list__item--chosen').parent().parent().show();
    if (!jQuery('.filter-icon').hasClass('arrow_up')) {
      jQuery('.filter-icon').addClass('arrow_up');
    }

    /*redistribute products rows to avoid missing spaces*/
    jQuery('.site-main ul.products.columns-3 li.product').removeClass('first').removeClass('last');
    jQuery('.site-main ul.products.columns-3 li.product:visible').each(function(i) {
      if (i % 3 == 0) jQuery(this).addClass('first'); //add class first to firsts products of rows
      if (i % 3 == 2) jQuery(this).addClass('last'); //add class last to lasts products of rows
    });
  }
}

openLayeredNavFilterIfSelected();
jQuery(window).resize(openLayeredNavFilterIfSelected);

And you should be good to go.

1WOOF Products Filter plugin and out of stock variations issue in Woocommerce

Related :

- filter products by attribute and hide out of stock items of variable products

- How to prevent `out of stock` items to show as a filter option in the sidebar (layered nav widget ) in WooCommerce?

- https://wordpress.org/support/topic/hide-out-of-stock-variations-when-filtering/#post-7718128

- https://xtemos.com/forums/topic/filter-attributes-dont-show-out-of-stock-variations/

- https://wordpress.org/support/topic/exclude-out-of-stock-products-from-filter/

like image 52
Alexandre Elshobokshy Avatar answered Sep 21 '22 00:09

Alexandre Elshobokshy


Not a really good answer here.

As @Islam Elshobokshy said, it's a bit complex to edit the product loop query to remove out of stock variations.

Still, you can trick using CSS to "hide" out of stock variations from the list. But it may "break" your product list/grid: if there is enough product for 2 pages (10 items/page), but one product is hidden on page 1 by this filter, page 1 will only have 9 products. We are after the product query.

I quickly tested and wrote something inspired from this:

  • this works for a size attribute (it could be adapted to handle all attributes from Woocommerce, or URL parameters "filter_*", or hardcoded for every attribute used)

Code:

add_action('woocommerce_before_shop_loop_item', 'out_of_stock_variations_loop');
function out_of_stock_variations_loop()
{
    global $product;

    $filter = 'size'; //to edit

    if ($product->product_type === 'variable') {
        $available = $product->get_available_variations();
        if ($available) {
            foreach ($available as $instockvar) {
                if (isset($instockvar[ 'attributes' ][ 'attribute_pa_' . $filter ])) {
                    if (($instockvar[ 'attributes' ][ 'attribute_pa_' . $filter ] === $_GET[ 'filter_' . $filter ]) && ($instockvar[ 'max_qty' ] <= 0)) {
                        //sadly echo some inline CSS :(
                        echo "<style>.post-" . $product->get_id() . " {display: none}</style>";

                        //some tests using is_visible() filters, but we are too late here
                        //add_filter( 'woocommerce_product_is_visible', function($visible, $id) use ($the_id) {return $id === $the_id ? false : $visible;}, 50, 2);
                    }
                }
            }
        }
    }
}

To achieve the same without inline CSS, you can:

  • override /woocommerce/template/content-product.php to your-theme/woocommerce/content-product.php
  • move the code below in a method($product), edit it a bit to return true/false only for required products
  • Call it in the "Ensure visibility" block like if ( empty( $product ) || ! $product->is_visible() || !$method($product) )
  • this will prevent outputting the product, but will not resolve the grid/list issue.

In the end, updating the loop query to match your need may be complex, and negatively impact performance. But, to explore more, I would start something like:

  • add a query_var by detecting GET filter parameter (or directly detecting wp_query parameter matching the filter/attribute you know that will filter)
  • find and hook around the woocommerce product wp_query
  • hook a method that will detect your custom query_var
  • edit the query, but if it comes to raw SQL, it may be painful..
like image 31
Mtxz Avatar answered Sep 20 '22 00:09

Mtxz