Filter product collection by multiple categories?
The following code example works for me to show only particular two categories (7 and 8) products. Hope it will work for you:
$productCollection = Mage::getModel('catalog/product')
->getCollection()
->joinField(
'category_id', 'catalog/category_product', 'category_id',
'product_id = entity_id', null, 'left'
)
->addAttributeToSelect('*')
->addAttributeToFilter('category_id', array(
array('finset' => array('7', '8', )),
))
->addAttributeToSort('created_at', 'desc');
foreach($productCollection as $product){
echo $product->getId() . "<br/>\n";
};
Another alternative:
In the example, $categoryIds
is an array of category Ids. (I usually use $collection->getFlag('category_ids')
but changed for this example, see explanantion lower down)
$currentStoreId
is populated by any means that can get the current store id. (in my observer event I use $collection->getFlag('store_id')
)
$conditions = array(
'cat_index.product_id=e.entity_id',
$collection->getConnection()->quoteInto('cat_index.category_id IN (' . $categoryIds . ')', "")
);
if (!Mage::app()->getStore()->isAdmin()) {
$conditions[] = $collection->getConnection()->quoteInto('cat_index.store_id=?', $currentStoreId );
}
$joinCond = join(' AND ', $conditions);
$fromPart = $collection->getSelect()->getPart(Zend_Db_Select::FROM);
if (isset($fromPart['cat_index'])) {
$fromPart['cat_index']['joinCondition'] = $joinCond;
$collection->getSelect()->setPart(Zend_Db_Select::FROM, $fromPart);
} else {
$collection->getSelect()->join(
array('cat_index' => $collection->getTable('catalog/category_product_index')), $joinCond, array('cat_index_category_id' => 'category_id')
);
}
I use the above code in an observer to filter with multiple categories AFTER the built in call Mage_Catalog_Model_Resource_Product_Collection::_applyProductLimitations()
does it's thing, as it sends an event called 'catalog_product_collection_apply_limitations_after'
The _applyProductLimitations
method essentially applies the filters set by the call to addCategoryFilter
.
By using a flag in the $collection
, it is an easy matter to store the multiple category ids for the observer.
Thus whenever I have the need to filter by multiple categories, I set a flag on the collection at some point
an example would be to rewrite Mage_Catalog_Model_Category::getProductCollection()
public function getProductCollection() {
$collection = parent::getProductCollection();
$collection->setFlag('category_ids',array(19,243,42,54)); //the array of values can be from somehwre, this hard coded array serves purely as an example
return $collection;
}
Now, when magento fires the event, my code replaces the category filter with my multiple filter :)
This is essentially how my Dynamic Category Products module does its thing for its multiple category filters.
Ref The core code of addCategoryFilter
:
/**
* Specify category filter for product collection
*
* @param Mage_Catalog_Model_Category $category
* @return Mage_Catalog_Model_Resource_Product_Collection
*/
public function addCategoryFilter(Mage_Catalog_Model_Category $category)
{
$this->_productLimitationFilters['category_id'] = $category->getId();
if ($category->getIsAnchor()) {
unset($this->_productLimitationFilters['category_is_anchor']);
} else {
$this->_productLimitationFilters['category_is_anchor'] = 1;
}
if ($this->getStoreId() == Mage_Catalog_Model_Abstract::DEFAULT_STORE_ID) {
$this->_applyZeroStoreProductLimitations();
} else {
$this->_applyProductLimitations();
}
return $this;
}
I worked with the techniques offered in both @monojit banik and @ProxiBlue's answers, and got the "Item (Mage_Catalog_Model_Product) with the same id "xx" already exist" error identified in @MTM's comment with both.
To avoid the error, which occurs outside the collection load in the layered navigation, I tried a couple of different methods to get a distinct set of products in a single pass, but nothing I came up with worked.
Ultimately, (EDIT: based on this blog post, https://www.muddyweb.com/general-ramblings/filtering-a-magento-product-collection-by-multiple-categories/) I ended up using two separate collection loads to get the distinct product set and avoid the error as follows. There may be a performance penalty to using two passes, but nothing was noticeable.
$category_ids = [helper method to get desired category id array];
//Need distinct products even if products are assigned to more than one of
// the selected categories.
$id_collection = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('id')
->addAttributeToFilter('status', 1)
->addAttributeToFilter('visibility', 4)
->addStoreFilter();
$conditions = array();
foreach ($category_ids as $categoryId) {
if (is_numeric($categoryId)) {
$conditions[] = "{{table}}.category_id = $categoryId";
}
}
$id_collection->distinct(true)
->joinField('category_id', 'catalog/category_product', null,
'product_id = entity_id', implode(" OR ", $conditions), 'inner');
$ids = $id_collection->getAllIds();
//Now, use the product ids array obtained above to get the final product collection.
/** @var Mage_Catalog_Model_Resource_Product_Collection $collection */
$collection = Mage::getResourceModel('catalog/product_collection')
->setStoreId($this->getStoreId())
->addAttributeToSelect('*')
->addAttributeToFilter('entity_id', array('in' => $ids) )
;