4

I need to update category page collection by filtering restricted SKUs. As like

$collection->getSelect()->where("`e`.`sku` IN('24-MB04', '24-MB03', '24-MB02')");

Above query gives collection exatly what i need. But it won't update pagination block and layered navigation in category page

How to achieve this thing in Magento 2


I have tried to achieve these by Plugin, below are my code files

etc/frontend/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">    

    <type name="Magento\Catalog\Block\Product\ListProduct">
        <plugin name="mynamespace-myextension-block-product-list"
                type="Mynamespace\Myextension\Plugin\ProductList"
                sortOrder="10" />
    </type> 
</config>

And Plugin/ProductList.php

<?php
namespace Mynamespace\Myextension\Plugin;

use Magento\Framework\App\ObjectManager;
use Magento\Framework\Search\Request\EmptyRequestDataException;

class ProductList
{

    protected $scopeConfig;

    protected $_objectManager;

    protected $_catalogLayer;

    public function __construct(        
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Framework\ObjectManagerInterface $objectManager,
        \Magento\Catalog\Model\Layer\Resolver $layerResolver
        ) 
    {
        $this->scopeConfig = $scopeConfig;      
        $this->_objectManager = $objectManager;
        $this->_catalogLayer = $layerResolver->get();
    }

    public function beforeToHtml(
        \Magento\Catalog\Block\Product\ListProduct $subject
        )
    {   
        $skus = array('24-MB04', '24-MB03', '24-MB02');

        $pcollection = $this->_catalogLayer->getProductCollection()->addAttributeToFilter('sku', array('in' => $skus));

        $pcollection->getSize();
        $subject->setCollection($pcollection);      
    }
}

Please advise what I am missing

5

I have found answer to my question

To apply custom filter in category, please do as below

1] create custom SKU filter with the help of search_request.xml

create search_request.xml file in MyNamespace/MyModule/etc/search_request.xml

<?xml version="1.0"?>

<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request.xsd">

    <request query="catalog_view_container" index="catalogsearch_fulltext">        
        <queries>
            <query xsi:type="boolQuery" name="catalog_view_container" boost="1">                
                <queryReference clause="should" ref="sku_query"/>
            </query>
            <query name="sku_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="sku_query_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="termFilter" name="sku_query_filter" field="sku" value="$sku$"/>
        </filters>

        <from>0</from>
        <size>10000</size>
    </request>
</requests>

2] Override \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection in to your module

=> MyNamespace/MyModule/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    ...............

    <preference for="Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection" type="MyNamespace\MyModule\Model\ResourceModel\Fulltext\Collection" />

    ...............

</config>

=> MyNamespace\MyModule\Model\ResourceModel\Fulltext\Collection.php

<?php
namespace MyNamespace\MyModule\Model\ResourceModel\Fulltext;

use Magento\CatalogSearch\Model\Search\RequestGenerator;
use Magento\Framework\DB\Select;
use Magento\Framework\Exception\StateException;
use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
use Magento\Framework\Search\Response\QueryResponse;
use Magento\Framework\Search\Request\EmptyRequestDataException;
use Magento\Framework\Search\Request\NonExistingRequestNameException;
use Magento\Framework\Api\Search\SearchResultFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\ObjectManager;

/**
 * Fulltext Collection
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Collection extends \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection
{   
    /**
     * @var  QueryResponse
     * @deprecated
     */
    protected $queryResponse;

    /**
     * Catalog search data
     *
     * @var \Magento\Search\Model\QueryFactory
     * @deprecated
     */
    protected $queryFactory = null;

    /**
     * @var \Magento\Framework\Search\Request\Builder
     * @deprecated
     */
    private $requestBuilder;

    /**
     * @var \Magento\Search\Model\SearchEngine
     * @deprecated
     */
    private $searchEngine;

    /**
     * @var string
     */
    private $queryText;

    /**
     * @var string|null
     */
    private $order = null;

    /**
     * @var string
     */
    private $searchRequestName;

    /**
     * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory
     */
    private $temporaryStorageFactory;

    /**
     * @var \Magento\Search\Api\SearchInterface
     */
    private $search;

    /**
     * @var \Magento\Framework\Api\Search\SearchCriteriaBuilder
     */
    private $searchCriteriaBuilder;

    /**
     * @var \Magento\Framework\Api\Search\SearchResultInterface
     */
    private $searchResult;

    /**
     * @var SearchResultFactory
     */
    private $searchResultFactory;

    /**
     * @var \Magento\Framework\Api\FilterBuilder
     */
    private $filterBuilder;

    /**
     * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
     * @param \Magento\Framework\Event\ManagerInterface $eventManager
     * @param \Magento\Eav\Model\Config $eavConfig
     * @param \Magento\Framework\App\ResourceConnection $resource
     * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory
     * @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper
     * @param \Magento\Framework\Validator\UniversalFactory $universalFactory
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\Module\Manager $moduleManager
     * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory
     * @param \Magento\Catalog\Model\ResourceModel\Url $catalogUrl
     * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
     * @param \Magento\Customer\Model\Session $customerSession
     * @param \Magento\Framework\Stdlib\DateTime $dateTime
     * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement
     * @param \Magento\Search\Model\QueryFactory $catalogSearchData
     * @param \Magento\Framework\Search\Request\Builder $requestBuilder
     * @param \Magento\Search\Model\SearchEngine $searchEngine
     * @param \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory $temporaryStorageFactory
     * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
     * @param string $searchRequestName
     * @param SearchResultFactory $searchResultFactory
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        \Magento\Framework\Data\Collection\EntityFactory $entityFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy,
        \Magento\Framework\Event\ManagerInterface $eventManager,
        \Magento\Eav\Model\Config $eavConfig,
        \Magento\Framework\App\ResourceConnection $resource,
        \Magento\Eav\Model\EntityFactory $eavEntityFactory,
        \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper,
        \Magento\Framework\Validator\UniversalFactory $universalFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\Module\Manager $moduleManager,
        \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState,
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory,
        \Magento\Catalog\Model\ResourceModel\Url $catalogUrl,
        \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
        \Magento\Customer\Model\Session $customerSession,
        \Magento\Framework\Stdlib\DateTime $dateTime,
        \Magento\Customer\Api\GroupManagementInterface $groupManagement,
        \Magento\Search\Model\QueryFactory $catalogSearchData,
        \Magento\Framework\Search\Request\Builder $requestBuilder,
        \Magento\Search\Model\SearchEngine $searchEngine,
        \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory $temporaryStorageFactory,
        \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
        $searchRequestName = 'catalog_view_container',
        SearchResultFactory $searchResultFactory = null
    ) {
        $this->queryFactory = $catalogSearchData;
        if ($searchResultFactory === null) {
            $this->searchResultFactory = \Magento\Framework\App\ObjectManager::getInstance()
                ->get('Magento\Framework\Api\Search\SearchResultFactory');
        }
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $eavConfig,
            $resource,
            $eavEntityFactory,
            $resourceHelper,
            $universalFactory,
            $storeManager,
            $moduleManager,
            $catalogProductFlatState,
            $scopeConfig,
            $productOptionFactory,
            $catalogUrl,
            $localeDate,
            $customerSession,
            $dateTime,
            $groupManagement,
            $catalogSearchData,
            $requestBuilder,
            $searchEngine,
            $temporaryStorageFactory,
            $connection,
            $searchRequestName,
            $searchResultFactory
        );
        $this->requestBuilder = $requestBuilder;
        $this->searchEngine = $searchEngine;
        $this->temporaryStorageFactory = $temporaryStorageFactory;
        $this->searchRequestName = $searchRequestName;
    }

    /**
     * @deprecated
     * @return \Magento\Search\Api\SearchInterface
     */
    private function getSearch()
    {
        if ($this->search === null) {
            $this->search = ObjectManager::getInstance()->get('\Magento\Search\Api\SearchInterface');
        }
        return $this->search;
    }

    /**
     * @deprecated
     * @return \Magento\Framework\Api\Search\SearchCriteriaBuilder
     */
    private function getSearchCriteriaBuilder()
    {
        if ($this->searchCriteriaBuilder === null) {
            $this->searchCriteriaBuilder = ObjectManager::getInstance()
                ->get('\Magento\Framework\Api\Search\SearchCriteriaBuilder');
        }
        return $this->searchCriteriaBuilder;
    }

    /**
     * @deprecated
     * @return \Magento\Framework\Api\FilterBuilder
     */
    private function getFilterBuilder()
    {
        if ($this->filterBuilder === null) {
            $this->filterBuilder = ObjectManager::getInstance()->get('\Magento\Framework\Api\FilterBuilder');
        }
        return $this->filterBuilder;
    }

    /**
     * @inheritdoc
     */
    protected function _renderFiltersBefore()
    {
        $this->getSearchCriteriaBuilder();
        $this->getFilterBuilder();
        $this->getSearch();

        if ($this->queryText) {
            $this->filterBuilder->setField('search_term');
            $this->filterBuilder->setValue($this->queryText);
            $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
        }

        $priceRangeCalculation = $this->_scopeConfig->getValue(
            \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE
        );
        if ($priceRangeCalculation) {
            $this->filterBuilder->setField('price_dynamic_algorithm');
            $this->filterBuilder->setValue($priceRangeCalculation);
            $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
        }

        //------------------- Custom Filter ----------------[START]     

            $skus = array('24-MB04', '24-MB03', '24-MB02');

            $this->filterBuilder->setField('sku');
            $this->filterBuilder->setValue($skus);
            $this->filterBuilder->setConditionType('in');

            $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());

        //------------------- Custom Filter ----------------[END]

        $searchCriteria = $this->searchCriteriaBuilder->create();
        $searchCriteria->setRequestName($this->searchRequestName);
        try {
            $this->searchResult = $this->getSearch()->search($searchCriteria);
        } catch (EmptyRequestDataException $e) {
            /** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */
            $this->searchResult = $this->searchResultFactory->create()->setItems([]);
        } catch (NonExistingRequestNameException $e) {
            $this->_logger->error($e->getMessage());
            throw new LocalizedException(__('Sorry, something went wrong. You can find out more in the error log.'));
        }

        $temporaryStorage = $this->temporaryStorageFactory->create();
        $table = $temporaryStorage->storeApiDocuments($this->searchResult->getItems());

        $this->getSelect()->joinInner(
            [
                'search_result' => $table->getName(),
            ],
            'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
            []
        );

        $this->_totalRecords = $this->searchResult->getTotalCount();

        if ($this->order && 'relevance' === $this->order['field']) {
            $this->getSelect()->order('search_result.'. TemporaryStorage::FIELD_SCORE . ' ' . $this->order['dir']);
        }
        //return parent::_renderFiltersBefore();
        return \Magento\Catalog\Model\ResourceModel\Product\Collection::_renderFiltersBefore();
    }

    /**
     * Return field faceted data from faceted search result
     *
     * @param string $field
     * @return array
     * @throws StateException
     */
    public function getFacetedData($field)
    {
        $this->_renderFilters();
        $result = [];
        $aggregations = $this->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(__('Bucket does not exist'));
            }
        }
        return $result;
    }

}

Note: Replase $skus array with your SKU values, $skus = array('24-MB04', '24-MB03', '24-MB02');

  • Nalin, Thanks for providing nice solution. It works fine with sku but it creates problem with custom attribute. I have added custom multiselect attribute called allowed_customer and it stores values in database like 1,2,3 so I want to filter with and match with current logged in customer. I tried using condition type like, finset but i didn't get proper results. If you have any idea from your end, I would be more thankful to you. I have also posted question here link – Prashant Gohil Jun 22 '17 at 18:18
  • @Nalin Savaliya :- i my case little different, i have my custom table where i need to join this with search table which has two column produtc_id(FK) and customsku, when customer search any key words and match with this custom sku data search result should contain this product also product also it something like custom attribute from custom table , how to do it ? – Pradeep Kumar Dec 8 '17 at 10:43
  • Can possible to overwrite the only function. because _renderFiltersBefore has three private methods. So I can not overwrite single function using this. – Dhaduk Mitesh Apr 10 '18 at 10:08
  • While class override you should override private members in new class with same code or your desired changes, but it's necessary to have private members in sub class – Nalin Savaliya Apr 10 '18 at 10:12
  • 1
    @NalinSavaliya, This answer helps me a lot, Thank you. upvote! – chirag Mar 28 '19 at 13:26
2

All credits to Mr. Rakesh Jesadiya for this answer.

As per question asked, I think following my answer can help you.

-> There is a Build(...) in Magento\CatalogSearch\Model\Search\IndexBuilder.php . That is actually used to build all layered navigation filters.

What I did is, made plugin to override that Build(...) function and create around method to it.

Added custom collection query as required and returned product ids from the query.

Further added where query in main select query for returning filters.

Create di.xml in your custom module.

Custom/Layernavigation/etc/di.xml

Add following code :

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <!-- Magento Catalog search Override IndexBuilder.php plugin file -->
    <type name="Magento\CatalogSearch\Model\Search\IndexBuilder">
        <plugin name="Custom_Layernavigation::CustomLayernavigation"
                type="Custom\Layernavigation\Plugin\CatalogSearch\Model\Search\IndexBuilder" />
    </type>
</config>

Now, create plugin file to override build function that builds all filters.

Custom/Layernavigation/Plugin/CatalogSearch/Model/Search/IndexBuilder.php

<?php

namespace Custom\Layernavigation\Plugin\CatalogSearch\Model\Search;

use Magento\Framework\DB\Select;
use Magento\Framework\Search\Request\FilterInterface;
use Magento\Framework\Search\Request\Filter\BoolExpression;
use Magento\Framework\Search\Request\Query\Filter;
use Magento\Framework\Search\RequestInterface;
use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
use Magento\Framework\App\ResourceConnection;

class IndexBuilder
{
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    protected $scopeConfig;

    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;


    public function __construct(
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
        \Magento\Catalog\Model\Product\Visibility $productVisibility,
        \Magento\Catalog\Helper\Category $categoryHelper,
        \Magento\Framework\Registry $registry
    ) {
        $this->storeManager = $storeManager;
        $this->_productCollectionFactory = $productCollectionFactory; 
        $this->_productVisibility = $productVisibility;
        $this->categoryHelper = $categoryHelper;
        $this->registry = $registry;
    }

    /**
     * Build index query
     *
     * @param $subject
     * @param callable $proceed
     * @param RequestInterface $request
     * @return Select
     * @SuppressWarnings(PHPMD.UnusedFormatParameter)
     */
    public function aroundBuild($subject, callable $proceed, RequestInterface $request)
    {
        $select = $proceed($request);
        $storeId = $this->storeManager->getStore()->getStoreId();
        $rootCatId = $this->storeManager->getStore($storeId)->getRootCategoryId();
        $productUniqueIds = $this->getCustomCollectionQuery();
        $select->where('search_index.entity_id IN (' . join(',', $productUniqueIds) . ')');

        return $select;
    }

    /**
     *
     * @return ProductIds[]
     */
    public function getCustomCollectionQuery() {
        /* get all category ids of current store */
        $websiteId = $this->storeManager->getStore()->getWebsiteId();
        $currentStoreAllCategories = $this->categoryHelper->getStoreCategories(false,true,true);

        $collection = $this->_productCollectionFactory->create();
        $collection->addAttributeToSelect(array('entity_id','sku'));
        // filter current website products
        $collection->addWebsiteFilter($websiteId);
        // set visibility filter
        $collection->setVisibility($this->_productVisibility->getVisibleInSiteIds());

        /*SKU Filter Here*/
        $sku = array('24-MB04', '24-MB03', '24-MB02');
        $collection->addAttributeToFilter('sku', array('in' => $sku));

        $getProductAllIds = $collection->getAllIds();
        $getProductUniqueIds = array_unique($getProductAllIds);
        return $getProductUniqueIds;
    }

}

Please correct me If I'm wrong.

  • 1
    Thank you, this solution works perfect for me, Custom product collection and Layered Navigation filters are working as expected. – Ronak Parmar Nov 26 '18 at 7:51
  • @RonakParmar not working for me in magento 2.2 and no error – Yasar Arafath Dec 18 '18 at 5:45
1

Use Magento's dependency injection. Init Catalog_product model then getCollection and apply attributes to filters functions.

 protected $_catalogLayer;


 public function __construct(

    \Magento\Catalog\Model\Layer\Resolver $layerResolver

    ) {

          $this->_catalogLayer = $layerResolver->get();
    }

 public function _getFilteredCollection(){

           $sku = array('24-MB04', '24-MB03', '24-MB02');              

           $collection =  $this->_catalogLayer->getProductCollection()
                         ->addAttributeToFilter('sku', array('in' => $sku));

           $collection->getSize();

           return $collection;

 }

It';s important to do getSize() on the collection for you to be able to use the toolbar correctly. This will set the size of this collection for the toolbar

         $block \Magento\Catalog\Block\Product\ProductList\Toolbar 
         toolbar.phtml             
         <?php if ($block->getCollection()->getSize()): ?>
  • It's working for Toolbar and also updates layered navigation but in layered navigation it displays wrong data. – Nalin Savaliya Nov 7 '16 at 9:57
  • If you could be a little more specific. What is showing? different attributes? cause you can add more filters – Wolfgang Leon Nov 7 '16 at 13:07
  • 1
    Many thanks for your reply. I have updated question with my codes. It filters products based on given SKUs, also pagination and toolbar is working fine. When it comes to layered navigation, it updates count of filters as per collection but, gives wrong data. Suppose in Layered navigation there is Style attribute, after filter it displays two options of Style 1] Luggage (1) 2] Duffel (2) . Now, when I click on Luggage in layered navigation then it show No products, It's because the layered was not updated based on filtered collection. Also Price filter won't get updated – Nalin Savaliya Nov 8 '16 at 5:30

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.