4

TL;DR: How to filter multiple select/dropdown attributes (multi-selects would be cumbersome)? Or use a different method?

Consider a product select/dropdown attribute whose values correspond to each letter of the alphabet (English in this case). The first option's label is "A", its value is 1. "B" is 2, "C" is 3, and so on:

I have this attribute assigned to control layered navigation filters. So a user can click the link for a specific value, and it will add ?my_attribute=2 into the request parameters, and the product list shows only products whose attribute value is "B".

I would like to, instead, display any products who's attribute value is greater than or equal to the selected option. So in the case a user clicks the "B" link in layered nav, the results would be every product from "B-Z" (2-26).

The URL structure: ?my_attribute=2&my_attribute_3&my_attribute=4... would be ugly and besides, it does not work, but the idea is there. ?my_attribute=2-26 would obviously be ideal, but again, this is not what Magento does.

Curiously, using advanced search provides this URL structure (through the advanced search controller instead of layered nav):

?my_attribute%5B%5D=7&my_attribute%5B%5D=8&my_attribute%5B%5D=9&my_attribute%5B%5D=10

This indeed does provide the desired results; however, this structure does not work with category/layered pages, only advanced search.

I am trying to decide which would be a better path at this point; Should I abandon all the built-in usefulness of layered navigation, and instead rewrite the product_list block's loaded product collection based on my attribute value? Or should I adapt some functionality from advanced search results to fit my needs?

It seems like it would be best to leave layered navigation functionality alone so that other product filters continue to work as expected; but it does not seem it will allow multiple values from a single attribute be selected at the same time. Alternatively, a workaround would be to use a multi-select attribute, and if a product would be assigned "B", it would have to be assigned all options from "B-Z". While this would provide the greatest instant results (everything will work without more modifications), it is certainly a sloppy solution that will be error-prone and clunky to manage in the future.

1

2 Answers 2

5

What I did in this case was to rewrite both the model and the corresponding resource, so first rewrite the class Mage_Catalog_Model_Layer_Filter_Attribute to be able to accept multiple values. In my case, I am using commas as a separator, so I can filter like ?my_attribute=1,2,3

In the method apply() I parse the attribute to put the different values in an array:

  public function apply(Zend_Controller_Request_Abstract $request, $filterBlock)
    {
        $filter = $request->getParam($this->_requestVar);
        if (is_array($filter)) {
            return $this;
        }

        // MP customized
        $filter = explode(',', $filter);
        if (count($filter) < 1) {
            return $this;
        } else if ($filter == '') {
            return $this;
        }

        if (count($filter) == 1) {
            $filter = $filter[0];
        }

        if ($filter == '') {
            return $this;
        }

        if ($filter) {
            $this->_initItems();
            $this->_getResource()->applyFilterToCollection($this, $filter);
            $text = '';
            foreach ($filter as $att) {
                ($text == '') ? $text = $this->_getOptionText($att) : $text .= ', '.$this->_getOptionText($att);
            }
            if (count($filter) == 1) {
                $text = $this->_getOptionText($filter);
            }
            $this->getLayer()->getState()->addFilter($this->_createItem($text, $filter));
        }
        // End MP customized
        return $this;
      }

Then in the file Mage_Catalog_Model_Resource_Eav_Mysql4_Layer_Filter_Attribute you have to make sure that you are able to filter for multiple values in your query, so I rewrote the following method like this:

     public function applyFilterToCollection($filter, $value)
    {
        $collection = $filter->getLayer()->getProductCollection();
        $attribute  = $filter->getAttributeModel();
        $connection = $this->_getReadAdapter();
        $tableAlias = $attribute->getAttributeCode() . '_idx';
        $conditions = array(
            "{$tableAlias}.entity_id = e.entity_id",
            $connection->quoteInto("{$tableAlias}.attribute_id = ?", $attribute->getAttributeId()),
            $connection->quoteInto("{$tableAlias}.store_id = ?", $collection->getStoreId()),
            $connection->quoteInto("{$tableAlias}.value IN (?)", $value)
        ); // Added IN (?) notation for arrays


        $collection->getSelect()->join(
            array($tableAlias => $this->getMainTable()),
            join(' AND ', $conditions),
            array()
            // MP Need distinct here to avoid getting the same config product from
            // different basic products when filtering for different values
            // in the same attribute
        )->distinct(true);

        return $this;
    }
1

Here is layered navigation with custom product collection

<?php
    require_once('app/Mage.php'); //Must have file to begin

    Mage::app()->setCurrentStore(Mage_Core_Model_App::ADMIN_STORE_ID); //Initialize store

    $catId = Mage::app()->getRequest()->getParam('categoryId'); //Pass categoryId in get variable
        $storeId = Mage::app()->getWebsite(true)->getDefaultGroup()->getDefaultStoreId();
        $page_no = Mage::app()->getRequest()->getParam('page_no');
        $params = Mage::app()->getRequest()->getParams(); //Pass attributes in key=>value form to filter results.
        $category = Mage::getModel('catalog/category')->load($catId);

        $layer = Mage::getModel("catalog/layer");
        $layer->setCurrentCategory($category);
        $attributes = $layer->getFilterableAttributes(); //get all filterable attributes available in selected category layered navigation
        $attr = array();
        foreach ($attributes as $attribute) {
            if ($attribute->getAttributeCode() == 'price') {
                $filterBlockName = 'catalog/layer_filter_price';
            } elseif ($attribute->getBackendType() == 'decimal') {
                $filterBlockName = 'catalog/layer_filter_decimal';
            } else {
                $filterBlockName = 'catalog/layer_filter_attribute';
            }
            $attr[$attribute->getAttributeCode()] = $attribute->getFrontendLabel();

        }
        $filters = array_intersect_key($params, $attr);
        $collection = $category->getProductCollection()
                        ->addAttributeToFilter(
                            'status', array('eq' => Mage_Catalog_Model_Product_Status::STATUS_ENABLED))
                        ->addAttributeToSelect('*');
        foreach ($filters as $key => $value) {
            if($key == 'price'){
                $priceFilter = explode('-', $value);
                $collection->addAttributeToFilter('price', array('gteq' => $priceFilter[0]));
                $collection->addAttributeToFilter('price', array('lteq' => $priceFilter[1]));
            }
            else{
$codeId = Mage::getResourceModel('eav/entity_attribute')
    ->getIdByCode('catalog_product', $key)
$collection->getSelect()->join(array('at_'.$key =>'catalog_product_index_eav'),'at_'.$key.'.entity_id = e.entity_id and at_'.$key.'.attribute_id = '.$codeId.' and at_'.$key.'.store_id = '.Mage::helper('mobile')->getStore()->getId())->where('at_'.$key.'.value = '.$value);
            }
        }
        $collection->setPage($page_no, 10);
    foreach($collection as $product){
       echo $product->getName();
    }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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