Magento – Hide Out of Stock Products in Layered Navigation, but Not Category

layered-navigationstock

I want to be able to filter by sizes that are in stock via the layered navigation, eg I select size large and the filtered results only show me configurable products with an in stock size large associated simple product.

I can achieve this by setting the admin to not show out of stock products, however I do still want out of stock products to be visible in an unfiltered category.

Does anyone know how to override the hide out of stock admin setting, maybe from within list.phtml? So the setting is still applied to the layered navigation, but does not affect the category before any filters are applied.

In a perfect world, only the size attribute would cause out of stock child products to be filtered out, but that may be asking too much.

I'm open to other solutions too, including paid extensions if need be.

Thanks in advance for any help, and below is the default list.phtml for reference.

<?php
    $_productCollection=$this->getLoadedProductCollection();
    $_helper = $this->helper('catalog/output');
?>
<?php if(!$_productCollection->count()): ?>
<p class="note-msg"><?php echo $this->__('There are no products matching the selection.') ?></p>
<?php else: ?>
<?php echo $this->getToolbarHtml() ?>
<?php echo $this->getAdditionalHtml() ?>
<div class="category-products">
    <?php // List mode ?>
    <?php if($this->getMode()!='grid'): ?>
    <?php $_iterator = 0; ?>
    <ol class="products-list" id="products-list">
    <?php foreach ($_productCollection as $_product): ?>
        <li class="item<?php if( ++$_iterator == sizeof($_productCollection) ): ?> last<?php endif; ?>">
            <?php // Product Image ?>
            <a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->stripTags($this->getImageLabel($_product, 'small_image'), null, true) ?>" class="product-image"><img src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize(166); ?>" width="166" height="166" alt="<?php echo $this->stripTags($this->getImageLabel($_product, 'small_image'), null, true) ?>" /></a>
            <?php // Product description ?>
            <div class="product-shop">
                <div class="f-fix">
                    <?php $_productNameStripped = $this->stripTags($_product->getName(), null, true); ?>
                    <h2 class="product-name"><a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $_productNameStripped; ?>"><?php echo $_helper->productAttribute($_product, $_product->getName() , 'name'); ?></a></h2>
                    <?php if($_product->getRatingSummary()): ?>
                    <?php echo $this->getReviewsSummaryHtml($_product) ?>
                    <?php endif; ?>
                    <?php echo $this->getPriceHtml($_product, true) ?>
                    <div class="desc std">
                        <?php echo $_helper->productAttribute($_product, $_product->getShortDescription(), 'short_description') ?>
                        <a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $_productNameStripped ?>" class="link-learn"><?php echo $this->__('Learn More') ?></a>
                    </div>
                    <?php if($_product->isSaleable()): ?>
                        <button type="button" title="<?php echo $this->__('Add to Cart') ?>" class="button btn-cart" onclick="setLocation('<?php echo $this->getAddToCartUrl($_product) ?>')"><span><span><?php echo $this->__('Add to Cart') ?></span></span></button>

                    <?php else: ?>
                        <?php if ($_product->getIsSalable()): ?>
                            <p class="availability in-stock"><span><?php echo $this->__('In stock') ?></span></p>
                        <?php else: ?>
                            <p class="availability out-of-stock"><span><?php echo $this->__('Out of stock') ?></span></p>
                        <?php endif; ?>
                    <?php endif; ?>
                        <ul class="add-to-links">
                            <?php if ($this->helper('wishlist')->isAllow()) : ?>
                                <li><a href="<?php echo $this->helper('wishlist')->getAddUrl($_product) ?>" class="link-wishlist"><?php echo $this->__('Add to Wishlist') ?></a></li>
                            <?php endif; ?>
                            <?php if($_compareUrl=$this->getAddToCompareUrl($_product)): ?>
                                <li><a href="<?php echo $_compareUrl ?>" class="link-compare"><?php echo $this->__('Add to Compare') ?></a></li>
                            <?php endif; ?>
                        </ul>
                </div>
            </div>
        </li>
    <?php endforeach; ?>
    </ol>
    <script type="text/javascript">decorateList('products-list', 'none-recursive')</script>

<?php else: ?>

<?php // Grid Mode ?>

<?php $_collectionSize = $_productCollection->count() ?>
<?php $_columnCount = $this->getColumnCount(); ?>
<?php $i=0; foreach ($_productCollection as $_product): ?>
    <?php if ($i++%$_columnCount==0): ?>
    <ul class="products-grid">
    <?php endif ?>
        <li class="item<?php if(($i-1)%$_columnCount==0): ?> first<?php elseif($i%$_columnCount==0): ?> last<?php endif; ?>">
            <a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->stripTags($this->getImageLabel($_product, 'small_image'), null, true) ?>" class="product-image"><img src="<?php echo $this->helper('catalog/image')->init($_product, 'small_image')->resize(135); ?>" width="135" height="135" alt="<?php echo $this->stripTags($this->getImageLabel($_product, 'small_image'), null, true) ?>" /></a>
            <h2 class="product-name"><a href="<?php echo $_product->getProductUrl() ?>" title="<?php echo $this->stripTags($_product->getName(), null, true) ?>"><?php echo $_helper->productAttribute($_product, $_product->getName(), 'name') ?></a></h2>
            <?php if($_product->getRatingSummary()): ?>
            <?php echo $this->getReviewsSummaryHtml($_product, 'short') ?>
            <?php endif; ?>
            <?php echo $this->getPriceHtml($_product, true) ?>
            <div class="actions">
                <?php if($_product->isSaleable()): ?>
                    <button type="button" title="<?php echo $this->__('Add to Cart') ?>" class="button btn-cart" onclick="setLocation('<?php echo $this->getAddToCartUrl($_product) ?>')"><span><span><?php echo $this->__('Add to Cart') ?></span></span></button>
                <?php else: ?>
                    <?php if ($_product->getIsSalable()): ?>
                        <p class="availability in-stock"><span><?php echo $this->__('In stock') ?></span></p>
                    <?php else: ?>
                        <p class="availability out-of-stock"><span><?php echo $this->__('Out of stock') ?></span></p>
                    <?php endif; ?>
                <?php endif; ?>
                    <ul class="add-to-links">
                        <?php if ($this->helper('wishlist')->isAllow()) : ?>
                            <li><a href="<?php echo $this->helper('wishlist')->getAddUrl($_product) ?>" class="link-wishlist"><?php echo $this->__('Add to Wishlist') ?></a></li>
                        <?php endif; ?>
                        <?php if($_compareUrl=$this->getAddToCompareUrl($_product)): ?>
                            <li><a href="<?php echo $_compareUrl ?>" class="link-compare"><?php echo $this->__('Add to Compare') ?></a></li>
                        <?php endif; ?>
                    </ul>
            </div>
        </li>
    <?php if ($i%$_columnCount==0 || $i==$_collectionSize): ?>
    </ul>
    <?php endif ?>
    <?php endforeach ?>
    <script type="text/javascript">decorateGeneric($$('ul.products-grid'), ['odd','even','first','last'])</script>
<?php endif; ?>
</div>
<div class="toolbar-bottom">
    <?php echo $this->getToolbarHtml() ?>
</div>
<?php endif; ?>

Best Answer

Best place to add filters to product collection programmatically is

Mage_Catalog_Model_Layer::prepareProductCollection()

not phtml template.

At the moment catalog/product/list.phtml is rendered, product collection is already loaded and if you don't plan to render products it is better not to load them at all.

The logic should sound like:

public function prepareProductCollection($collection)
{
    $collection
        ->addAttributeToSelect(Mage::getSingleton('catalog/config')->getProductAttributes())
        ->addMinimalPrice()
        ->addFinalPrice()
        ->addTaxPercents()
        ->addUrlRewrite($this->getCurrentCategory()->getId());

    Mage::getSingleton('catalog/product_status')->addVisibleFilterToCollection($collection);
    Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($collection);

    // your custom filter
    if ($this->_areFiltersApplied()) {
        $this->_addInStockFilter($collection);
    }

    return $this;
}

protected function _areFiltersApplied() {
    return count($this->getState()->getFilters()) > 0;
}

protected function _addInStockFilter($collection) {
    Mage::getModel('cataloginventory/stock_status')->addIsInStockFilterToCollection($collection);
}
Related Topic