7

I'm attempting to add layered navigation to a CMS page containing a widget that contains a custom product collection (products that are 'new'). Within this widget are fuctions that, given the correct parameters, will filter this collection. How do I pass this collection to the layered navigation block and have it's attributes show up?

This is how I am calling the layered navigation in the CMS custom layout XML:

<reference name="content">
        <!-- Layered Navigation Block -->
        <block type="catalog/layer_view" name="catalog.leftnav" before="-" template="catalog/layer/view.phtml" >
        </block>
</reference>

This is how i'm calling the product collection onto the CMS page:

{{widget type="catalog/product_widget_new" display_type="new_products" show_pager="1" products_count="60" template="catalog/product/widget/new/content/new_grid.phtml"}}

This is how i'm generating the product collection in the widgets block:

protected function _getProductCollection()
{
    $todayStartOfDayDate = Mage::app()->getLocale()->date()
        ->setTime('00:00:00')
        ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);
    $todayEndOfDayDate = Mage::app()->getLocale()->date()
        ->setTime('23:59:59')
        ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);
    /** @var $collection Mage_Catalog_Model_Resource_Product_Collection */
    $collection = Mage::getModel('catalog/product')->getCollection()->addAttributeToSelect('*')->setVisibility(Mage::getSingleton('catalog/product_visibility')->getVisibleInCatalogIds());
    $this->_addProductAttributesAndPrices($collection)
        ->addStoreFilter()
        ->addAttributeToFilter('news_from_date', array('or' => array(
            0 => array('date' => true, 'to' => $todayEndOfDayDate),
            1 => array('is' => new Zend_Db_Expr('null')))
        ), 'left')
        ->addAttributeToFilter('news_to_date', array('or' => array(
            0 => array('date' => true, 'from' => $todayStartOfDayDate),
            1 => array('is' => new Zend_Db_Expr('null')))
        ), 'left')
        ->addAttributeToFilter(
            array(
                array('attribute' => 'news_from_date', 'is' => new Zend_Db_Expr('not null')),
                array('attribute' => 'news_to_date', 'is' => new Zend_Db_Expr('not null'))
            )
        )
        ->setPageSize($this->getProductsCount())
        ->setCurPage(1);
    return $collection;
}

The widget's template page is a standard practice way to display price, reviews, pictures, etc in a pagesize varient format.

  • 1
    What you are trying to achieve is not possible, now with using cms pages and widgets and layout updates at least. Here is why. Magento has 2 layer models. One works if you are in the context of a category and one works when you are in the context of the search results. Your example does not fall in either of these categories. You would need a custom layer model that will handle your collection type (new products). – Marius Feb 19 '15 at 8:54
  • 1
    I might be able to come up with a solution but this involves writing a custom module, with a custom controller, block and layer model (and maybe others). But before looking for a solution I want to know if this works for you. I mean you will still be able to have an url www.mysite.com/new-products but this new-products will not be a cms page. It will be handled by a separate controller. And it will contain all the new products, paginated (maybe with sorting options if I find out how). Does this work for you? or you want a simple CMS page solution? – Marius Feb 19 '15 at 8:57
  • @Marius that would be a reasonable solution. I suppose I can insert that solution just as I would a widget onto a cms page, no? – easymoden00b Feb 19 '15 at 13:07
  • 1
    Not really. The solution I have in mind will create a separate page with a separate controller (some template files maybe). that cannot be changed like a csm page. It will just be there and work :) That's it. It involves creating a new module – Marius Feb 19 '15 at 13:41
  • @Marius I see, that may work as well. – easymoden00b Feb 19 '15 at 13:51
7
+150

Layered navigation needs an active category to work. Add the following block to you layout-xml on your cms-pages.

<cms_page>
    <block type="catalog/layer_view" name="catalog.leftnav" before="-" template="catalog/layer/view.phtml">
        <action method="setCategoryId"><category_id>2</category_id></action>
    </block>
</cms_page>
  • 1
    What else do you use to populate your products ? – roman204 Feb 18 '15 at 21:25
  • 1
    Yes i see, in this case you must override the Mage_Catalog_Model__Layer:getProductCollection... i will see what i can do here to provide a solution. – roman204 Feb 19 '15 at 7:30
  • 2
    @easymoden00b Try to add the root category id. And make sure you made 'Is Anchor' to Yes for that category. – MagePsycho Feb 19 '15 at 8:03
  • 1
    @MagePsycho then the filterable attributes of all products will be available to filter, as i understand the question the filter should only include the products from the widget, please correct me if i'm wrong... – roman204 Feb 19 '15 at 8:14
  • 1
    AFAIK, OP wants global list of new products with layered navigation. – MagePsycho Feb 19 '15 at 8:17
28

As explained in the comments, I don't think there is a way to do this by using the cms pages because you have no layer model associated to the new products list. You only have layer models for the category context and the search context.
My solution (tested and works) involves writing a module that will list all the products marked as new in a page and having a layer model that handles the new products collection.
Here goes. be prepared it's a long one.
I called the extension StackExchange_NewProducts but feel free to change the name.
You will need the following files:

app/etc/modules/StackExchange_NewProducts.xml - the declaration file:

<?xml version="1.0"?>
<config>
    <modules>
        <StackExchange_NewProducts>
            <codePool>local</codePool>
            <active>true</active>
            <depends>
                <Mage_Catalog />
            </depends>
        </StackExchange_NewProducts>
    </modules>
</config>

app/code/local/StackExchange/NewProducts/etc/config.xml - the configuration file

<?xml version="1.0"?>
<config>
    <modules>
        <StackExchange_NewProducts>
            <version>1.0.0</version>
        </StackExchange_NewProducts>
    </modules>
    <global>
        <models>
            <stackexchange_newproducts>
                <class>StackExchange_NewProducts_Model</class>
            </stackexchange_newproducts>
        </models>
        <blocks>
            <stackexchange_newproducts>
                <class>StackExchange_NewProducts_Block</class>
            </stackexchange_newproducts>
        </blocks>
        <helpers>
            <stackexchange_newproducts>
                <class>StackExchange_NewProducts_Helper</class>
            </stackexchange_newproducts>
        </helpers>
        <events>
            <controller_front_init_routers> <!-- create a custom router to handle the 'new-products' url -->
                <observers>
                    <stackexchange_newproducts>
                        <class>StackExchange_NewProducts_Controller_Router</class>
                        <method>initControllerRouters</method>
                    </stackexchange_newproducts>
                </observers>
            </controller_front_init_routers>
        </events>
    </global>
    <frontend>
        <routers> <!-- declare a router -->
            <newproducts>
                <use>standard</use>
                <args>
                    <module>StackExchange_NewProducts</module>
                    <frontName>newproducts</frontName>
                </args>
            </newproducts>
        </routers>
        <layout>
            <updates>
                <stackexchange_newproducts>
                    <file>stackexchange_newproducts.xml</file>
                </stackexchange_newproducts>
            </updates>
        </layout>
    </frontend>
</config>

app/code/local/StackExchange/NewProducts/Controller/Router.php - the custom router that handles the url new-products and maps it to a controller and action

<?php
class StackExchange_NewProducts_Controller_Router extends Mage_Core_Controller_Varien_Router_Abstract
{
    const NEW_PRODUCTS_URL_KEY = 'new-products';
    public function initControllerRouters($observer)
    {
        $front = $observer->getEvent()->getFront();
        $front->addRouter('stackexchange_newproducts', $this);
        return $this;
    }

    public function match(Zend_Controller_Request_Http $request)
    {
        if (!Mage::isInstalled()) {
            Mage::app()->getFrontController()->getResponse()
                ->setRedirect(Mage::getUrl('install'))
                ->sendResponse();
            exit;
        }
        $urlKey = trim($request->getPathInfo(), '/');
        if ($urlKey == self::NEW_PRODUCTS_URL_KEY) {
            $request->setModuleName('newproducts')
                ->setControllerName('index')
                ->setActionName('index');
            $request->setAlias(
                Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS,
                $urlKey
            );
            return true;
        }
        return false;
    }
}

app/code/local/StackExchange/NewProducts/controllers/IndexController.php - the actual controller that displays the new products

<?php
class StackExchange_NewProducts_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        $this->loadLayout();
        $this->renderLayout();
    }
}

app/code/local/StackExchange/NewProducts/Helper/Data.php - the module general helper

<?php
class StackExchange_NewProducts_Helper_Data extends Mage_Core_Helper_Abstract
{

}

app/design/frontend/base/default/layout/stackexchange_newproducts.xml - the module layout file that defines the content of the page

<?xml version="1.0"?>
<layout>
    <newproducts_index_index>
        <reference name="root">
            <action method="setTemplate">
                <template>page/2columns-left.phtml</template>
            </action>
        </reference>
        <reference name="left">
            <block type="stackexchange_newproducts/layer_new" name="catalog.leftnav" before="-" template="catalog/layer/view.phtml" />
        </reference>
        <reference name="content">
            <block type="core/template" name="new_products_container" as="new_products_container" template="stackexchange_newproducts/container.phtml">
                <action method="setTitle" translate="title" module="stackexchange_newproducts">
                    <title>New Products</title>
                </action>
                <block type="stackexchange_newproducts/new" name="new_product" as="new_products" template="catalog/product/list.phtml">
                    <block type="catalog/product_list_toolbar" name="product_list_toolbar" template="catalog/product/list/toolbar.phtml">
                        <block type="page/html_pager" name="product_list_toolbar_pager"/>
                    </block>
                    <action method="addColumnCountLayoutDepend"><layout>empty</layout><count>6</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>one_column</layout><count>5</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>two_columns_left</layout><count>4</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>two_columns_right</layout><count>4</count></action>
                    <action method="addColumnCountLayoutDepend"><layout>three_columns</layout><count>3</count></action>
                    <action method="setToolbarBlockName"><name>product_list_toolbar</name></action>
                </block>
            </block>
        </reference>

    </newproducts_index_index>
</layout>

app/code/local/StackExchange/NewProducts/Block/New.php - the block that should render the new products collection:

<?php
class StackExchange_NewProducts_Block_New extends Mage_Catalog_Block_Product_List
{
    public function __construct()
    {
        parent::__construct();
    }
    public function getModuleName()
    {
        return 'Mage_Catalog';
    }
    protected function _getProductCollection()
    {
        if (is_null($this->_productCollection)) {
            $this->_productCollection = $this->getLayer()->getProductCollection();
        }
        return $this->_productCollection;
    }
    public function getLayer()
    {
        $layer = Mage::registry('current_layer');
        if ($layer) {
            return $layer;
        }
        return Mage::getSingleton('stackexchange_newproducts/layer');
    }
}

app/code/local/StackExchange/NewProducts/Block/Layer/New.php - the layer block that should be displayed on the left side (filters)

<?php
class StackExchange_NewProducts_Block_Layer_New extends Mage_Catalog_Block_Layer_View
{
    public function getLayer()
    {
        if (!$this->hasData('_layer')){
            $layer = Mage::getSingleton('stackexchange_newproducts/layer');
            $this->setData('_layer', $layer);
        }
        return $this->getData('_layer');
    }
    protected function _initBlocks()
    {
        parent::_initBlocks();
        $this->_attributeFilterBlockName    = 'stackexchange_newproducts/layer_filter_attribute';
        $this->_priceFilterBlockName        = 'stackexchange_newproducts/layer_filter_price';
        $this->_decimalFilterBlockName      = 'stackexchange_newproducts/layer_filter_decimal';
    }
    protected function _prepareLayout()
    {
        $stateBlock = $this->getLayout()->createBlock($this->_stateBlockName)
            ->setLayer($this->getLayer());
        $this->setChild('layer_state', $stateBlock);

        $filterableAttributes = $this->_getFilterableAttributes();
        foreach ($filterableAttributes as $attribute) {
            if ($attribute->getAttributeCode() == 'price') {
                $filterBlockName = $this->_priceFilterBlockName;
            } elseif ($attribute->getBackendType() == 'decimal') {
                $filterBlockName = $this->_decimalFilterBlockName;
            } else {
                $filterBlockName = $this->_attributeFilterBlockName;
            }

            $this->setChild($attribute->getAttributeCode() . '_filter',
                $this->getLayout()->createBlock($filterBlockName)
                    ->setLayer($this->getLayer())
                    ->setAttributeModel($attribute)
                    ->init());
        }

        $this->getLayer()->apply();
        return $this;
    }
}

now each attribute type needs a filter block associated to it:

app/code/local/StackExchange/NewProducts/Block/Layer/Filter/Attribute.php - general filter block for attributes

<?php 
class StackExchange_NewProducts_Block_Layer_Filter_Attribute extends Mage_Catalog_Block_Layer_Filter_Attribute
{
    public function __construct()
    {
        parent::__construct();
        $this->_filterModelName = 'stackexchange_newproducts/layer_filter_attribute';
    }
}

app/code/local/StackExchange/NewProducts/Block/Layer/Filter/Decimal.php - filter block for decimal attributes

<?php 
class StackExchange_NewProducts_Block_Layer_Filter_Decimal extends Mage_Catalog_Block_Layer_Filter_Decimal
{
    public function __construct()
    {
        parent::__construct();
        $this->_filterModelName = 'stackexchange_newproducts/layer_filter_decimal';
    }
}

app/code/local/StackExchange/NewProducts/Block/Layer/Filter/Price.php - filter block for price attribute

<?php 
class StackExchange_NewProducts_Block_Layer_Filter_Price extends Mage_Catalog_Block_Layer_Filter_Price
{
    public function __construct()
    {
        parent::__construct();
        $this->_filterModelName = 'stackexchange_newproducts/layer_filter_price';
    }
}

app/code/local/StackExchange/NewProducts/Model/Layer.php - the layer model for handling new products

<?php
class StackExchange_NewProducts_Model_Layer extends Mage_Catalog_Model_Layer
{
    public function getStateKey()
    {
        if ($this->_stateKey === null) {
            $this->_stateKey = 'STORE_'.Mage::app()->getStore()->getId()
                . '_NEW_PRODUCTS_'
                . '_CUSTGROUP_' . Mage::getSingleton('customer/session')->getCustomerGroupId();
        }

        return $this->_stateKey;
    }

    public function getStateTags(array $additionalTags = array())
    {
        $additionalTags = array_merge($additionalTags, array('new_products'));
        return $additionalTags;
    }

    public function getProductCollection()
    {
        if (isset($this->_productCollections['new_products'])) {
            $collection = $this->_productCollections['new_products'];
        } else {
            $collection = $this->_getCollection();
            $this->prepareProductCollection($collection);
            $this->_productCollections['new_products'] = $collection;
        }

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

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

    protected function _getCollection()
    {
        $todayStartOfDayDate  = Mage::app()->getLocale()->date()
            ->setTime('00:00:00')
            ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);

        $todayEndOfDayDate  = Mage::app()->getLocale()->date()
            ->setTime('23:59:59')
            ->toString(Varien_Date::DATETIME_INTERNAL_FORMAT);

        $collection = Mage::getModel('catalog/product')->getCollection()
            ->addStoreFilter()
            ->addAttributeToFilter('news_from_date', array('or'=> array(
                0 => array('date' => true, 'to' => $todayEndOfDayDate),
                1 => array('is' => new Zend_Db_Expr('null')))
            ), 'left')
            ->addAttributeToFilter('news_to_date', array('or'=> array(
                0 => array('date' => true, 'from' => $todayStartOfDayDate),
                1 => array('is' => new Zend_Db_Expr('null')))
            ), 'left')
            ->addAttributeToFilter(
                array(
                    array('attribute' => 'news_from_date', 'is'=>new Zend_Db_Expr('not null')),
                    array('attribute' => 'news_to_date', 'is'=>new Zend_Db_Expr('not null'))
                )
            )
            ->addAttributeToSort('news_from_date', 'desc');
        return $collection;
    }
}

Like each attribute needed a block for filters it also needs a model for handling filters:

app/code/local/StackExchange/NewProducts/Model/Layer/Filter/Attribute.php - the general attribute filter model

<?php 
class StackExchange_NewProducts_Model_Layer_Filter_Attribute extends Mage_Catalog_Model_Layer_Filter_Attribute
{
    protected function _createItem($label, $value, $count = 0)
    {
        return Mage::getModel('stackexchange_newproducts/layer_filter_item')
            ->setFilter($this)
            ->setLabel($label)
            ->setValue($value)
            ->setCount($count);
    }
}

app/code/local/StackExchange/NewProducts/Model/Layer/Filter/Decimal.php - the decimal attributes filter model

<?php 
class StackExchange_NewProducts_Model_Layer_Filter_Decimal extends Mage_Catalog_Model_Layer_Filter_Attribute
{
    protected function _createItem($label, $value, $count = 0)
    {
        return Mage::getModel('stackexchange_newproducts/layer_filter_item')
            ->setFilter($this)
            ->setLabel($label)
            ->setValue($value)
            ->setCount($count);
    }
}

app/code/local/StackExchange/NewProducts/Model/Layer/Filter/Price.php - the price attribute filter model

<?php 
class StackExchange_NewProducts_Model_Layer_Filter_Price extends Mage_Catalog_Model_Layer_Filter_Price
{
    protected function _createItem($label, $value, $count = 0)
    {
        return Mage::getModel('stackexchange_newproducts/layer_filter_item')
            ->setFilter($this)
            ->setLabel($label)
            ->setValue($value)
            ->setCount($count);
    }
}

app/code/local/StackExchange/NewProducts/Model/Layer/Filter/Item.php - the filters require a collection of item objects. this is the item object - not sure if this is needed. I think that the default catalog item model can be used.

<?php 
class StackExchange_NewProducts_Model_Layer_Filter_Item extends Mage_Catalog_Model_Layer_Filter_Item
{

}

app/design/frontend/base/default/template/stackexchange_newproducts/container.phtml - a template file that acts as a container for the new products so you can set a title and show session messages.

<?php if ($this->getTitle()) : ?>
    <div class="page-title category-title">
        <h1><?php echo $this->getTitle() ?></h1>
    </div>
<?php endif;?>
<?php echo $this->getMessagesBlock()->getGroupedHtml() ?>
<?php echo $this->getChildHtml();?>

That's it. Clear the cache after you create all the files and disable the compilation.
Here is a screenshot of how it looks on my side
screenshot. (using ce 1.7 with sample data)

  • 4
    The only thing that is missing is the translation file. – Marius Feb 19 '15 at 14:11
  • 1
    good module naming there :) – David Manners Feb 19 '15 at 14:20
  • 3
    @DavidManners. Yeah. I don't know where the name came, but it just hit me :). I hope you don't ask for copyright compensations. – Marius Feb 19 '15 at 14:24
  • 3
    a beer will do ;) – David Manners Feb 19 '15 at 14:25
  • 2
    chair up @Marius – +1votw – Amit Bera Feb 20 '15 at 14:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.