Home > Magento, Programming > Yet Another “Hiding Out-Of-Stock Products” Update

Yet Another “Hiding Out-Of-Stock Products” Update

September 28th, 2009

Update: Bug fix

It turns out that when layered navigation indexes are refreshed the website_id is 0 which causes the filtered results to be empty which makes layered navigation disappear. The fix is to simply place an if statement around the filter dependent on the website id no being 0. The Magento Connect module has been updated with this fix (0.3.1).


Ok, I think this will finally be the last update to this module… I’ve learned a lot about Magento since the first version of this module, and think I finally did it right this time. This update adds support for advanced search (thanks saho for the bug report) and also every other aspect of the store I could find.

I ended up doing a search like so:

app/code/core/Mage $ grep -rl addVisibleIn.\*FilterToCollection .
./Checkout/Block/Cart/Crosssell.php
./Tag/Block/Product/Result.php
./Tag/Block/Customer/View.php
./Adminhtml/Block/Sales/Order/Create/Search/Grid.php
./Adminhtml/Model/Sales/Order/Random.php
./Bundle/Block/Catalog/Product/List/Partof.php
./Bundle/Model/Observer.php
./Reports/Block/Product/Abstract.php
./Wishlist/Block/Share/Email/Items.php
./Wishlist/Block/Share/Wishlist.php
./Wishlist/Block/Customer/Wishlist.php
./Wishlist/Block/Customer/Sidebar.php
./Wishlist/Model/Mysql4/Wishlist.php
./CatalogSearch/Model/Layer.php
./CatalogSearch/Model/Advanced.php
./Rss/Block/Catalog/New.php
./Rss/Block/Catalog/Special.php
./CatalogIndex/Model/Indexer.php
./Sales/Model/Order.php
./Catalog/Block/Seo/Sitemap/Product.php
./Catalog/Block/Product/Compare/List.php
./Catalog/Block/Product/New.php
./Catalog/Block/Product/List/Related.php
./Catalog/Block/Product/List/Crosssell.php
./Catalog/Block/Product/List/Upsell.php
./Catalog/Block/Product/Bestsellers.php
./Catalog/Model/Layer.php
./Catalog/Model/Product/Visibility.php
./Catalog/Helper/Product/Compare.php

and determined (obviously) that it would be best to simply override the addVisibleIn*FilterToCollection functions rather than to try to override all of the above. I actually discovered the following code which turned out to not be what I wanted, but I think that is due to it not being updated when Varien added the stock_status table:

Mage::getSingleton('cataloginventory/stock')->addInStockFilterToCollection($collection);

However, I liked the idea of having such a function available that actually works as intended (the above does not work on configurable products) so I did just that.

First, the function that can be used on any product collection to filter out of stock products:
app/code/local/Lucky/InStockOnly/Model/Stock.php

<?php

class Lucky_InStockOnly_Model_Stock {

  /**
   * Add a statusInStock requirement for visibility
   */
  public function addInStockFilterToCollection($collection)
  {
    if($websiteId = Mage::app()->getWebsite()->getWebsiteId()) {
      $collection->joinField(
        'stock_status',
        'cataloginventory/stock_status',
        'stock_status',
        'product_id=entity_id', array(
          'stock_status' => Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK,
          'website_id' => $websiteId
        )
      );
    }
  }

}

Next we add some functions that override the addVisibleIn*FilterToCollection functions that are used heavily throughout Magento:
app/code/local/Lucky/InStockOnly/Model/Visibility.php

<?php

class Lucky_InStockOnly_Model_Visibility extends Mage_Catalog_Model_Product_Visibility {

  public function addVisibleInCatalogFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
  {
    parent::addVisibleInCatalogFilterToCollection($collection);
    Mage::getSingleton('cataloginventory/instockonly')->addInStockFilterToCollection($collection);
    return $this;
  }

  public function addVisibleInSearchFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
  {
    parent::addVisibleInSearchFilterToCollection($collection);
    Mage::getSingleton('cataloginventory/instockonly')->addInStockFilterToCollection($collection);
    return $this;
  }

  public function addVisibleInSiteFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
  {
    parent::addVisibleInSiteFilterToCollection($collection);
    Mage::getSingleton('cataloginventory/instockonly')->addInStockFilterToCollection($collection);
    return $this;
  }

}

Now the model rewrites:
app/code/local/Lucky/InStockOnly/etc/config.xml

<?xml version="1.0"?>
<config>

<global>
  <models>

    <catalog>
      <rewrite>
        <product_visibility>Lucky_InStockOnly_Model_Visibility</product_visibility>
      </rewrite>
    </catalog>
    <cataloginventory>
      <rewrite>
        <instockonly>Lucky_InStockOnly_Model_Stock</instockonly>
      </rewrite>
    </cataloginventory>

  </models>
</global>

</config>

And of course we need to enable the module (same as before):

app/etc/modules/Lucky_InStockOnly.xml

<config>
  <modules>
    <Lucky_InStockOnly>
      <active>true</active>
      <codePool>local</codePool>
    </Lucky_InStockOnly>
  </modules>
</config>

Known Bugs

If you have attributes that are used to create configurable products and are also used in layered navigation, out of stock products will still show up when layered navigation is in use for an attribute where sibling products are in stock. I don't think there is a good fix for this other than having an observer disable products entirely when they go out of stock, but this has some side-effects which might negatively affect some people.

Download

This version has been packaged as a proper Magento Connect extension and is available here: In Stock Only

Magento, Programming

  • http://colin.mollenhour.com/2009/07/hiding-out-of-stock-items-in-layered-navigation/ Colin Mollenhour’s Technical Blog » Hiding “Out of Stock” items in Layered Navigation

    […] the latest post in this series: Yet Another “Hiding Out-Of-Stock Products” Update, for an update that fixes advanced search, tags, up-sells, cross-sells, […]

  • Pooch

    Thanks for this Colin,

    A few things… first, what versions of Magento have you tested this with? I can’t seem to get the layered navigation to work once I’ve installed this. Tried refreshing all cache items including the Layered Nav Indices and all I can get to show up now in the layered nav is the Price Attribute. Using 1.3.2.1

    Second, regarding your known issue, I came across your plugin because I was looking for a solution to the Layered Nav showing items in it that aren’t in stock for configurables. Assuming I can get the plugin to work will your known issue only occur on Attributes that are used to create the configurable product or on all attributes of configurable products?

  • http://colin.mollenhour.com colin

    It was developed on 1.3.2.1, I’m not sure what would be causing your problem, can you provide more information?

    I believe the known limitation will occur on all attributes since they are all thrown into the catalogindex together.

  • Kaspart

    I have done everything for the Lucky Out of stock module – All my products are virtual. This doesn’t seem to work for them. Any suggestions?

  • http://colin.mollenhour.com colin

    I have not tested yet with virtual products but I don’t know offhand why that would matter.. Do you possibly have another module that conflicts? Specifically this module overrides the catalog/product_visibility model .

  • Kaspart

    I have facebook link and configurable checkout loaded so far. I don’t think those would have anything to do with it. I also have a free template from templates-master that has quite a few layout and template files in it. Maybe the template files (I edited a bunch to comment out the ifsaleable function) – I deleted my skin files for view and index so they will see default.

  • Kaspart

    PS – I am on version 1.3.2.4

  • http://colin.mollenhour.com colin

    I just tested virtual products using the sample data. I changed the 3-yr warranty to visible in Catalog and Search and added it to the Cell Phones category and it appeared. I changed the inventory qty to 0 and it disappeared. I also tested with and without flat categories and products and no problems. Can you confirm the module is being loaded by going to System > Configuration > Advanced and see if Lucky_InStockOnly is listed in the “Disable modules output” section?

    I didn’t have to do this on my installation, but you could try refreshing the “Inventory Stock Status” on the cache management page.

  • http://colin.mollenhour.com colin

    Does the catalog/product/list.phtml template use $this->getLoadedProductCollection() like the default template? As long as that is being used and the Catalog/Block/Product/List.php is not being overridden with a function that skips the addVisibleInCatalogFilterToCollection call then it should work…

  • Kaspart

    Yes, it does. Since my last post – I have deleted all template files, all changes to any file – restored the core for this version, and reinstalled your module. View and List showing in stock. The only place that shows out of stock is on my featured items – front page – custom. I fugured it has to do with $this->getLoadedProductCollection() That is no problem – I can edit that.

    However, I still have no addtocart on Product page – I thought this error was related to the out of stock problem – but ?? The weird thing is that it shows on the list/grid view.

    Thanks for your module!

  • Kaspart

    Thank you for answering my posts. So many developers of modules don’t take the time. FYI – I copied the original view and list files, added to my template – made the edits the template needed and voila! problem solved.

  • http://colin.mollenhour.com colin

    @Pooch
    It turns out that the website_id filter causes the layered navigation indexes to be empty when they are refreshed so that is what caused your problem. Bug is fixed now (0.3.1 on Magento Connect).

  • drew.gillson

    Colin, thanks for the awesome script. This has gotten me much closer than I would have ever done myself… but still one more thing I need to do. Can you give me any insight into how I would remove attribute choices from the dropdowns on a configurable product page?

    For instance, I have a configurable product ‘jacket’ that might have 20 simple products associated with it. If the XXL jacket is out of stock, I want the XXL option to disappear from my Size dropdown.

    Any ideas?

  • http://colin.mollenhour.com colin

    @drew: Isn’t this already the case? On my test store (the sample data) this is already how it works.

  • point4design

    Quick question. I installed this on my site and it did hide the options that are out of stock on my configurable products, but I just setup a brand new product and one of the items is out of stock (I don’t have inventory for it yet). But it’s showing as an option in the drop down?

  • http://colin.mollenhour.com colin

    I believe even the default Magneto installation should hide out of stock options on the configurable dropdowns. Make sure you actually set the simple product to “Out Of Stock” because it is possible for it to be “In Stock” with 0 qty if you set it that way initially.

  • saho

    Hi Colin,

    I could not live without this extension. Great work!!
    I was wondering if this class could be used in other places for instance I am using a gbase type feed but it pulls all configurable products regardless of whether or not they have out of stock associated products.

    You mention above “First, the function that can be used on any product collection to filter out of stock products:”

    Is it possible it could be used in a feed script in this way?

    //GET THE PRODUCTS
    $products = Mage::getModel(‘catalog/product’)->getCollection();
    $products->addAttributeToFilter(‘status’, 1); // enabled
    $products->addAttributeToFilter(‘visibility’, 4); // catalog, search
    $products->addAttributeToSelect(‘*’);
    $prodIds = $products->getAllIds();

    Thanks in advance for any insight.

  • http://colin.mollenhour.com colin

    saho, the filter can be applied like so:

    Mage::getSingleton('cataloginventory/instockonly')->addInStockFilterToCollection($products);

    Although the above is not necessary if you are already applying the typical “visibility” filter since the extension overrides those methods. Example:

    Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($products);

    That is the proper replacement for the addAttributeToFilter('visibility', 4) in your code.

  • point4design

    I’m having a problem with the extension. I have a ‘size’ attribute that I use on many of my configurable products. I have some of these configurable items in a clearance section and this is the only category I use layered navigation on (just activated it). I’ve noticed that in the layered navigation for the size attribute it is showing products that don’t have any stock. Category is here: http://www.wildernessrunning.com/shop/index.php/screaming-deals. If you click the ‘extra large’ in the layered nav on the right, the first product listed shouldn’t be there as it doesn’t have any extra large in stock. Can you help me figure this out?

  • http://colin.mollenhour.com colin

    This is the “Known Bug” which is really a limitation of Magento’s query design. I fixed this for a client (triercopenhagen.com) a few months ago but it took *forever* and the solution is not something that is readily distributable and it isn’t tested with grouped products and simple products and various other Magento features. The way the layered nav query is constructed is completely backwards from the original and it is quite complex so I don’t know if I can fully recommend going down that path.. I think Varien made a huge design mistake when they decided to not have the ability to hide out of stock products built into the core.

  • point4design

    Thanks for the quick response. Do you know a way then to easily ‘disable’ products that are out of stock?

  • point4design

    For example, is there a script I could run (by cron) to set products to disabled if the stock equals zero?

  • http://colin.mollenhour.com colin

    I would instead observe an event. Off the top of my head I think cataloginventory_stock_status_save_after would be a good starting point. If the status is changed to out of stock then disable the related product. I haven’t fully thought this through or tested but I think it could work. Note, then, that you would no longer need the Lucky_InStockOnly module.

  • http://www.proverb2.co.uk Jon

    Hi Colin, Thanks for writing this module, it is very useful. One question, for certain products I want them to be able to be backordered. Is there anyway I can disable the hiding for certain products?

  • http://colin.mollenhour.com Colin Mollenhour

    Yes, you would just need to add a product attribute (can be done via

    Catalog > Attributes) and then modify the filter to account for that

    attribute's value.

  • http://www.magento-themes.jextn.com magento themes

    Bug fixing is a very important aspect in programming.It also caused several sleepless nights for me.Coding may be different i can get the idea.Thanks for sharing.

  • studio de moda

    I have a question, i just want to hide the products out of stock when i use the search, how i do it?

  • oreales

    you got this function:

    Mage::getModel(‘cataloginventory/stock_status’)->addIsInStockFilterToCollection($productCollection);

    that uses table stock_status that is updated for configurable products. When a configurable´s associated product becomes out of stock, the configurable status in this table is updated to be also out of stock….. so, I think use this model / table is the right way.

  • Mike Henze

    I will pay for getting this implemented on my magento 1.7 shop.
    Please contact me if you can.

  • rohit patel

    i am apply filter in the list.php file like this
    protected function _beforeToHtml()
    {
    $collection->addAttributeToFilter(‘listhidden’, array(‘eq’=> 0 )) //custom attrib ID
    ->addAttributeToSelect(‘*’)
    ->setVisibility(Mage::getSingleton(‘catalog/product_visibility’)->getVisibleInCatalogIds());

    it is work good but in the layer category filter they show incorrect product count
    can you please help for this..