0

I need the attributes present in Layered Navigation to remain available after the results are further filtered.

Example

In the search results or by selecting a category, the manufacturer attribute appears in Layered Navigation with three options:

  • Brand 1
  • Brand 2
  • Brand 3

After selecting Brand 1 I should get the following result:

Now shopping by

x Manufacturer: Brand 1

Shopping Options

  • Manufacturer
    • Brand 1
    • Brand 2
    • Brand 3

In Magento 1.9 I managed to achieve this by modifying the getCount() function in Mage_Catalog_Model_Resource_Layer_Filter_Attribute:

public function getCount($filter)
{
    // clone select from collection with filters
    $select = clone $filter->getLayer()->getProductCollection()->getSelect();
    // reset columns, order and limitation conditions
    $select->reset(Zend_Db_Select::COLUMNS);
    $select->reset(Zend_Db_Select::ORDER);
    $select->reset(Zend_Db_Select::LIMIT_COUNT);
    $select->reset(Zend_Db_Select::LIMIT_OFFSET);
    $select->reset(Zend_Db_Select::GROUP);

    // Custom Editing START
    $fromPart = $select->getPart(Zend_Db_Select::FROM);
    foreach ($fromPart as $key => $part) {
        if (preg_match('/_idx/',$key)) {
            unset($fromPart[$key]);
        }
    }
    $select->reset(Zend_Db_Select::FROM);
    $select->setPart(Zend_Db_Select::FROM, $fromPart);
    // Custom Editing END

    $connection = $this->_getReadAdapter();
    $attribute  = $filter->getAttributeModel();
    $tableAlias = sprintf('%s_idx', $attribute->getAttributeCode());
    $conditions = array(
        "{$tableAlias}.entity_id = e.entity_id",
        $connection->quoteInto("{$tableAlias}.attribute_id = ?", $attribute->getAttributeId()),
        $connection->quoteInto("{$tableAlias}.store_id = ?", $filter->getStoreId()),
    );

    $select
        ->join(
            array($tableAlias => $this->getMainTable()),
            join(' AND ', $conditions),
            array('value', 'count' => new Zend_Db_Expr("COUNT(DISTINCT {$tableAlias}.entity_id)")))
        ->group("{$tableAlias}.value");

    return $connection->fetchPairs($select);
}

In Magento 2, even if this function is present, it seems to be no longer used.

By default, Attribute FilterList depends on Magento\CatalogSearch\Model\Layer\Filter\Attribute, in the _getItemsData() function:

protected function _getItemsData()
{
    $attribute = $this->getAttributeModel();
    /** @var \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection $productCollection */
    $productCollection = $this->getLayer()
        ->getProductCollection();
        
    $optionsFacetedData = $productCollection->getFacetedData($attribute->getAttributeCode());

    $isAttributeFilterable =
        $this->getAttributeIsFilterable($attribute) === static::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS;

    if (count($optionsFacetedData) === 0 && !$isAttributeFilterable) {
        return $this->itemDataBuilder->build();
    }

    $options = $attribute->getFrontend()
        ->getSelectOptions();
    foreach ($options as $option) {
        $this->buildOptionData($option, $isAttributeFilterable, $optionsFacetedData);
    }

    return $this->itemDataBuilder->build();
}

In this function, $productCollection is already filtered by SearchCriteria.

What I need is to remove the active attribute filters from this collection.

Any help is appreciated.

(Using existing extensions for Layered Navigation is not the purpose of this topic)


EDIT

I am trying to remove the current attribute filters from the collection:

    $productCollection = clone $this->getLayer()
        ->getProductCollection();
        
    $productCollection->clear();
    $productCollection->addCategoryFilter($this->getLayer()->getCurrentCategory());
        
    foreach($this->getLayer()->getState()->getFilters() as $filter){
        if($filter->getFilter()->getRequestVar() == $attribute->getAttributeCode()){
            continue;
        } else {
            $productCollection->addFieldToFilter($filter->getFilter()->getRequestVar(), $filter->getValue());
        }
    }
    $productCollection->load();

but it gives me the following error:

Fatal error: Uncaught Error: Call to a member function getAggregations() on null


EDIT

I achieved the desired result by modifying the getFacetedData() function in \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection, but to do this I had to clone the entire class as the function contains the private variable $this->searchResult.

I definitely don't like this approach but I haven't found any other way.

Anyway, below are the changes I made

/**
 * Return field faceted data from faceted search result
 *
 * @param string $field
 * @return array
 * @throws StateException
 */
public function getFacetedData($field)
{
    $this->_renderFilters();
    $result = [];
    
    $searchResult = $this->updateSearchResult($field) ?: $this->searchResult;
    $aggregations = $searchResult->getAggregations();
    
    // This behavior is for case with empty object when we got EmptyRequestDataException
    if (null !== $aggregations) {
        $bucket = $aggregations->getBucket($field . RequestGenerator::BUCKET_SUFFIX);
        if ($bucket) {
            foreach ($bucket->getValues() as $value) {
                $metrics = $value->getMetrics();
                $result[$metrics['value']] = $metrics;
            }
        } else {
            throw new StateException(__("The bucket doesn't exist."));
        }
    }
    return $result;
}

/**
 * Return Search Results without specific filter
 * @params string $field
 * return \Magento\Framework\Api\Search\SearchResultInterface
 */
private function updateSearchResult($field){
    $update = false;
    $searchCriteria = $this->searchResult->getSearchCriteria();
    $searchCriteriaBuilder = ObjectManager::getInstance()
            ->create(\Magento\Framework\Api\Search\SearchCriteriaBuilder::class);
    foreach($searchCriteria->getFilterGroups() as $group){
        foreach($group->getFilters() as $filter){
            if($filter->getField() != $field) {
                $searchCriteriaBuilder->addFilter($filter);
            } else {
                $update = true;
            }
        }
    }
    
    if(!$update){
        return false;
    }
    
    $result = $this->getSearch()->search($searchCriteriaBuilder->create()->setRequestName($searchCriteria->getRequestName()));
    
    return $result;
}

If someone has a better solution, they will be welcome.

For now, that's how it works

Your Answer

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

Browse other questions tagged or ask your own question.