27

I need to add an additional filter based on created_at attribute for sorting product list by latest product. I tried to figure it using below file

app/design/frontend/Vendor/ThemeName/Magento_Catalog/templates/product/list/toolbar/sorter.phtml  

but how can add our entity id to getAvailableOrders() ?

0

7 Answers 7

26

If you want to use an attribute like created_at that it isn't in admin->stores->(attribute) product, because attributes defined in admin have the setting Sorting in Product Listing = Yes/No, you have to work with these two files:

\vendor\magento\module-catalog\Block\Product\ProductList\Toolbar.php \vendor\magento\module-catalog\Model\Config.php

In Toolbar.php you can see

$this->_availableOrder = $this->_catalogConfig->getAttributeUsedForSortByArray();

it calls getAttributeUsedForSortByArray() from Config.php that returns array of available attributes to sort listing collection.

Now, you have to add your created_at attribute here. How? I did it with a plugin

/**
 * Add sort order option created_at to frontend
 */
public function afterGetAttributeUsedForSortByArray(
    \Magento\Catalog\Model\Config $catalogConfig,
    $options
) {
    $options['created_at'] = __('New');
    return $options;
}

You inserted created_at in available attributes to sort, now you have only to build your custom collection to use it. Here i choose to override \vendor\magento\module-catalog\Block\Product\ProductList\Toolbar.php with mine Toolbar.php and override setCollection()

/**
 * Set collection to pager
 *
 * @param \Magento\Framework\Data\Collection $collection
 * @return $this
 */
 public function setCollection($collection) {
    $this->_collection = $collection;
    $this->_collection->setCurPage($this->getCurrentPage());

    // we need to set pagination only if passed value integer and more that 0
    $limit = (int)$this->getLimit();
    if ($limit) {
        $this->_collection->setPageSize($limit);
    }

    // switch between sort order options
    if ($this->getCurrentOrder()) {
        // create custom query for created_at option
        switch ($this->getCurrentOrder()) {
            case 'created_at':
                if ($this->getCurrentDirection() == 'desc') {
                    $this->_collection
                        ->getSelect()
                        ->order('e.created_at DESC');
                } elseif ($this->getCurrentDirection() == 'asc') {
                    $this->_collection
                        ->getSelect()
                        ->order('e.created_at ASC');           
                }
                break;
            default:
                $this->_collection->setOrder($this->getCurrentOrder(), $this->getCurrentDirection());
                break;
        }
    }

    // echo '<pre>';
    // var_dump($this->getCurrentOrder());
    // var_dump((string) $this->_collection->getSelect());
    // die;

    return $this;        
}

That's all, for me works like a charm.

9
  • If anyone wants to default to ascending, then change } elseif ( $this->getCurrentDirection() == 'asc' ) { to } else {.
    – thdoan
    Commented Nov 3, 2016 at 2:02
  • 2
    Also, if you don't want to use a plugin, you can also use the built-in public function $block->addOrderToAvailableOrders('created_at', 'New') in your sorter template.
    – thdoan
    Commented Nov 3, 2016 at 3:04
  • Can you have the solution for sorting custom product price? @Luca Commented Apr 16, 2018 at 11:45
  • @DhadukMitesh sure, you could just use the above code and change attribute code created_at with your custom price attribute code
    – LucScu
    Commented Apr 17, 2018 at 14:46
  • I have no custom price attribute. I use default price sort by. I only change in core file where price is sorting. and I want to set my custom price for a collection. but I can't set custom price in collection. Commented Apr 18, 2018 at 4:42
24

We can achieve it by using Plugins. Please create following files in your module.

app/code/Package/CustomToolbar/etc/di.xml

<type name="Magento\Catalog\Model\Config">
    <plugin name="Package_CustomToolbar::addCustomOptions" type="Package\CustomToolbar\Plugin\Model\Config" />
</type>
<type name="Magento\Catalog\Block\Product\ProductList\Toolbar">
    <plugin name="Package_CustomToolbar::addPriceDecendingFilterInToolbar" type="Package\CustomToolbar\Plugin\Product\ProductList\Toolbar" />
</type>

app/code/Package/CustomToolbar/Plugin/Model/Config.php

namespace Package\CustomToolbar\Plugin\Model;
use Magento\Store\Model\StoreManagerInterface;
class Config
{
    protected $_storeManager;

public function __construct(
    StoreManagerInterface $storeManager
) {
    $this->_storeManager = $storeManager;

}

/**
 * Adding custom options and changing labels
 *
 * @param \Magento\Catalog\Model\Config $catalogConfig
 * @param [] $options
 * @return []
 */
public function afterGetAttributeUsedForSortByArray(\Magento\Catalog\Model\Config $catalogConfig, $options)
{
    $store = $this->_storeManager->getStore();
    $currencySymbol = $store->getCurrentCurrency()->getCurrencySymbol();

    //Remove specific default sorting options
    unset($options['position']);
    unset($options['name']);
    unset($options['price']);

    //Changing label
    $customOption['position'] = __('Relevance');

    //New sorting options
    $customOption['price_desc'] = __($currencySymbol.' (High to Low)');
    $customOption['price_asc'] = __($currencySymbol.' (Low to High)');

    //Merge default sorting options with custom options
    $options = array_merge($customOption, $options);

    return $options;
}
}

app/code/Package/CustomToolbar/Plugin/Product/ProductList/Toolbar.php

namespace Package\CustomToolbar\Plugin\Product\ProductList;
class Toolbar
{
    /**
     * Plugin
     *
     * @param \Magento\Catalog\Block\Product\ProductList\Toolbar $subject
     * @param \Closure $proceed
     * @param \Magento\Framework\Data\Collection $collection
     * @return \Magento\Catalog\Block\Product\ProductList\Toolbar
     */
    public function aroundSetCollection(
        \Magento\Catalog\Block\Product\ProductList\Toolbar $subject,
        \Closure $proceed,
        $collection
    ) {
        $currentOrder = $subject->getCurrentOrder();
        $result = $proceed($collection);

        if ($currentOrder) {
            if ($currentOrder == 'price_desc') {
                $subject->getCollection()->setOrder('price', 'desc');
            } elseif ($currentOrder == 'price_asc') {
                $subject->getCollection()->setOrder('price', 'asc');
            }
        }

        return $result;
    }
}

This is working fine for me without rewriting any Magento class.

5
  • this doesn't address created_at and not working for 2.1.9 - for me at least
    – dawhoo
    Commented Oct 30, 2017 at 18:44
  • Could you please elaborate on how aroundSetCollection works? Commented Nov 26, 2018 at 14:10
  • hi @Sumit, can you update the same for Best seller sort Commented May 22, 2020 at 5:58
  • 1
    Hi @sumit, It's not working in Magento 2,4,1 version for me. Can you help? Commented Jan 7, 2021 at 5:12
  • Its not working for me with same files can you please check magento.stackexchange.com/questions/354786/…
    – Prits
    Commented Apr 12, 2022 at 13:20
5

Step 1: First you should create registration.php

Vendor name: Arun

Module name: NewSorting

Vendor/Modulename/registration.php

<?php \Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE, 'Arun_NewSorting',
__DIR__
);?>

Step 2: You create module.xml

Vendor/Modulename/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Arun_NewSorting" setup_version="0.0.1">
        <sequence>
            <module name="Magento_Catalog"/>
        </sequence>
    </module>
</config>

Step 3: You create plugin

Vendor/Modulename/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">
    <type name="Magento\Catalog\Model\Config">
        <plugin name="Arun_NewSorting::addCustomOptions" type="Arun\NewSorting\Plugin\Model\Config" />
    </type>
    <type name="Magento\Catalog\Block\Product\ProductList\Toolbar">
        <plugin name="Arun_NewSorting::addPriceDecendingFilterInToolbar" type="Arun\NewSorting\Plugin\Product\ProductList\Toolbar" />
    </type>
</config>

Step 4: then create config.php

Vendor/Modulename/Plugin/Model/Config.php

<?php
namespace Arun\NewSorting\Plugin\Model;

use Magento\Store\Model\StoreManagerInterface;

class Config  {


    protected $_storeManager;

    public function __construct(
        StoreManagerInterface $storeManager
    ) {
        $this->_storeManager = $storeManager;
    }


    public function afterGetAttributeUsedForSortByArray(\Magento\Catalog\Model\Config $catalogConfig, $options)
    {
        $store = $this->_storeManager->getStore();
        $currencySymbol = $store->getCurrentCurrency()->getCurrencySymbol();

        // Remove specific default sorting options
        $default_options = [];
        $default_options['name'] = $options['name'];

        unset($options['position']);
        unset($options['name']);
        unset($options['price']);

        //Changing label
        $customOption['position'] = __( 'Relevance' );

        //New sorting options
        $customOption['created_at'] = __( ' New' );


        $customOption['name'] = $default_options['name'];

        //Merge default sorting options with custom options
        $options = array_merge($customOption, $options);

        return $options;
    }
}

Step 5: Override the Toolbar.php***

Vendor/Modulename/Plugin/Product/ProductList/Toolbar.php

<?php
namespace Arun\NewSorting\Plugin\Product\ProductList;

class Toolbar
{

    public function aroundSetCollection(
        \Magento\Catalog\Block\Product\ProductList\Toolbar $subject,
        \Closure $proceed,
        $collection
    ) {
        $currentOrder = $subject->getCurrentOrder();
        $result = $proceed($collection);

        if ($currentOrder) {
            if ($currentOrder == 'created_at') {
                $subject->getCollection()->setOrder('created_at', 'desc');
            } 
        }

        return $result;
    }
}

it's work perfectly

9
  • Any commands to be run in CLI after updating these files please? Commented Nov 21, 2018 at 12:15
  • Need to run following CLI setup upgrade, static-content deploy, cache clean, reindex Commented Nov 22, 2018 at 7:02
  • Thanks MSA but when I run the upgrade command it says 'nothing to update'. Using 2.2.5. Copied all of the above... but wondered what is in Registration.php file you mention and where to locate it? Commented Nov 22, 2018 at 15:38
  • I updated Registration.php file content path:Vendor/Modulename/registration.php Commented Nov 23, 2018 at 11:18
  • 1
    Added module exactly as above and the 'new' option shows on front end. It seems to have replaced the 'position' option is that expected? I cannot see the option in catalog in admin panel, as I would like to make this default option... Thank you. Commented Nov 26, 2018 at 17:13
5

If you want to use just Create At attribute, you can activate this attribute in admin panel in sorting options.

Example:

<?php

namespace Vendor\Module\Setup;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;

class UpgradeData implements UpgradeDataInterface
{
    protected $eavSetupFactory;

    /**
     * UpgradeData constructor.
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        EavSetupFactory $eavSetupFactory
    ) {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * @param ModuleDataSetupInterface $setup
     * @param ModuleContextInterface $context
     */
    public function upgrade(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

        if (version_compare($context->getVersion(), '2.1.1', '<')) {
            try {
                $entityType = $eavSetup->getEntityTypeId('catalog_product');
                $label = 'Created At';
                $eavSetup->updateAttribute($entityType, 'created_at', 'frontend_label', $label, null);
                $eavSetup->updateAttribute($entityType, 'created_at', 'used_for_sort_by', 1, null);
            } catch (LocalizedException $e) {
            }
        }
    }
}

This code from Setup/UpgradeData.php, but will be better to use InstallData.php instead.

3
  • Where is this code added in the file system? Commented Nov 29, 2018 at 9:41
  • 1
    Why create a custom module to change a db field? i don't think is the best way.
    – LucScu
    Commented Jan 23, 2019 at 8:41
  • Its not working for me with same files can you please check magento.stackexchange.com/questions/354786/…
    – Prits
    Commented Apr 12, 2022 at 13:20
2

The way doesn't need write codes

  1. Find the created_at product attribute in DB table eav_attribute, set its column frontend_label to Created At(default is null).

  2. Find the created_at product attribute in DB table catalog_eav_attribute, set its column used_for_sort_by to 1(default is 0).

  3. Clean site cache and it is working.

Example: change table by mysql

# Get the attribute_id of 'created_at'
select attribute_id from eav_attribute where attribute_code = 'created_at' and entity_type_id=4;

# Set frontend_label
update eav_attribute set frontend_label = 'Created At' where attribute_id=112;

# Set used_for_sort_by
update catalog_eav_attribute set used_for_sort_by = 1 where attribute_id=112;
7
  • I wouldn't directly change the db values, especially if it's core data.
    – LucScu
    Commented Dec 5, 2019 at 11:42
  • @LucScu It is just a another easier way. It changed two DB field that don't matter. You could use codes to override function too, but the covered function will be changed in version upgrade, and you have to update your custom codes. Both methods have advantages and disadvantages. Use custom codes for a simple function is a bit of overkill.
    – Key Shang
    Commented Dec 6, 2019 at 7:40
  • @SagarParikhSGR I used it and it is working. Pay attention to using the right attribute_id.
    – Key Shang
    Commented Dec 18, 2019 at 6:41
  • @KeyShang my bad, its working perfectly, upvoted :) Commented Dec 18, 2019 at 7:06
  • This didn't work for me. I can see sorting option "created at" but when I sort by I get no products shown. "We can't find products matching the selection." Using Elasticsearch Magento 2.4.1. I think the issue is caused by using ElasticSearch default search engine ( there is no other options as of Magento 2.4x) Commented Oct 29, 2020 at 13:31
2

I can confirm that if you are using Magento 2.4.x with Elastic Search - that the direct database change previously recommended (by Key Shang) will cause pages that include the toolbar to break and or work inconsistently. To fix, disable the used_to_sort_by option for the created_at attribute, and find another way to achieve this.

2
  • This is working fine for me in Magento 2.4.x - no problems using created_at as sort option with ES.
    – paj
    Commented Apr 8, 2021 at 11:05
  • Its not working for me with same files can you please check magento.stackexchange.com/questions/354786/…
    – Prits
    Commented Apr 12, 2022 at 13:21
0
\vendor\magento\module-catalog\Block\Product\ProductList\Toolbar.php \vendor\magento\module-catalog\Model\Config.php

https://prnt.sc/w6nehd

$this->_availableOrder = $this->_catalogConfig->getAttributeUsedForSortByArray();

`

class Toolbar
{
    public function aroundSetCollection(\Magento\Catalog\Block\Product\ProductList\Toolbar $subject,
                                        \Closure $proceed, $collection)
    {
        $currentOrder = $subject->getCurrentOrder();
        $result = $proceed($collection);
        if($currentOrder)
        {
            if($currentOrder == 'high_to_low')
            {
                $subject->getCollection()->setOrder('price', 'desc');
            }
            elseif ($currentOrder == 'low_to_high')
            {
                $subject->getCollection()->setOrder('price', 'asc');
            }
        }
        else
        {
            $subject->getCollection()->getSelect()->reset('order');
            $subject->getCollection()->setOrder('price', 'asc');
        }
        return $result;
    }
}
`

`class Config
{
    public function afterGetAttributeUsedForSortByArray(\Magento\Catalog\Model\Config $catalogConfig, $options)
    {
        $options['low_to_high'] = __('Price - Low To High');
        $options['high_to_low'] = __('Price - High To Low');
        return $options;
    }
}`
1

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.