1

I'd like to add custom sort option "Sort by newest" using created_at system attribute and I'd like to set it as default, from the backend Stores->Catalog->Storefront->Product Listing Sort by.

I overrode \module-catalog\Block\Product\ProductList\Toolbar.php in my module with something like this:

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

Then I overrode module-catalog\Model\Config.php with:

/**
 * 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;        
}

and it works but I'm not able to set it as default option and I don't know how I can add the option in the backend.

Any ideas?

2
  • You want as default right? Sep 15, 2020 at 17:02
  • I'm also looking for a way to add newest, most popular, etc to sorting. I've tried various plugins but since update to Magento 2.4 and ElasticSearch default I can't get many to work. Did you compile your solution into a module? Oct 23, 2020 at 11:15

2 Answers 2

7
+50

I made a similar change, hooking into admin configurations in addition to Magento\Catalog\Model\Config:

  1. Default sort configuration in Stores > Configuration > Catalog > Storefront > Product Listing Sort by. It uses Magento\Catalog\Model\Config\Source\ListSort model for sorting.
  2. Category level Available Product Listing Sort By, Default Product Listing Sort By. Source model here - Magento\Catalog\Model\Category\Attribute\Source\Sortby.

This way, admin has full control over default options, and can change settings globally of per category.

As a convenience, we slightly normalized above source models to always use results from getAttributeUsedForSortByArray, that we already made a plugin over.

<!-- Use getAttributeUsedForSortByArray to include all plugins from there -->
<preference for="Magento\Catalog\Model\Config\Source\ListSort" type="Vendor\Module\Model\Config\Source\ListSource"/>
<preference for="Magento\Catalog\Model\Category\Attribute\Source\Sortby" type="Vendor\Module\Model\Category\Attribute\Source\Sortby"/>

<type name="Magento\Catalog\Model\Config">
    <!-- Append custom sort options to $options -->
    <plugin name="vendor_module" type="Vendor\Module\Plugin\Catalog\Model\Config" />
</type>

\Vendor\Module\Model\Config\Source\ListSource:

class ListSource extends \Magento\Catalog\Model\Config\Source\ListSort
{
    /**
     * @inheritDoc
     */
    public function toOptionArray()
    {
        $options = [];
        foreach ($this->_getCatalogConfig()->getAttributeUsedForSortByArray() as $code => $label) {
            $options[] = ['label' => __($label), 'value' => $code];
        }
        return $options;
    }
}

Vendor\Module\Model\Category\Attribute\Source\Sortby:

class Sortby extends \Magento\Catalog\Model\Category\Attribute\Source\Sortby
{
    /**
     * @inheritDoc
     */
    public function getAllOptions()
    {
        $options = [];
        foreach ($this->_getCatalogConfig()->getAttributeUsedForSortByArray() as $code => $label) {
            $options[] = ['label' => __($label), 'value' => $code];
        }
        return $options;
    }
}

UPDATE

After some debugging, I found proper solution for Magento 2.4!

  1. To get created_at EAV attribute to Elasticsearch index, this attribute needs to be marked as indexable.

See:

  • \Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider::getSearchableAttributes
  • \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection::addToIndexFilter

This attribute is visible=0, so it's not possible to do from admin panel, so we need to make a migration script:

app/code/Vendor/Module/Setup/Patch/Data/ChangeCreatedAtSearchable.php

<?php

namespace Vendor\Module\Setup\Patch\Data;

use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;

class ChangeCreatedAtSearchable implements DataPatchInterface
{
    /**
     * @var ModuleDataSetupInterface
     */
    private $moduleDataSetup;

    /**
     * @var \Magento\Eav\Setup\EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * ChangePriceAttributeDefaultScope constructor.
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function apply()
    {
        /** @var \Magento\Eav\Setup\EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->updateAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'created_at',
            'is_searchable',
            true
        );
    }

    public static function getDependencies()
    {
        return [];
    }

    public function getAliases()
    {
        return [];
    }
}

  1. Lastly, by default all attributes are declared as text in Elasticsearch, and sorting doesn't work for them.

We need to change created_at field declaration to "keyword".

di.xml:

<type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper">
    <!-- Sets `created_at` type keyword to enable sorting -->
    <plugin name="vendor_module" type="Vendor\Module\Plugin\Elasticsearch\Model\Adapter\AdditionalFieldMapper" sortOrder="10"/>
</type>
<type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy">
    <!-- Sets `created_at` type keyword to enable sorting -->
    <plugin name="vendor_module" type="Vendor\Module\Plugin\Elasticsearch\Model\Adapter\AdditionalFieldMapper" sortOrder="10"/>
</type>

Same plugin declared for multiple adapters, this is because in Magento 2.3 some of them used for different Elasticsearch adapters. Keeping just in case.

use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface;

class AdditionalFieldMapper
{
    /**
     * Sets `created_at` type keyword to enable sorting
     *
     * @param \Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy $subject
     * @param array $allAttributes
     * @return array
     * @see \Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper::getAllAttributesTypes
     */
    public function afterGetAllAttributesTypes($subject, array $allAttributes)
    {
        $allAttributes['created_at']['type'] = ConverterInterface::INTERNAL_DATA_TYPE_KEYWORD;

        return $allAttributes;
    }
}

Works for me after catalogsearch reindex :)

14
  • Thanks but I get this error Exception #0 (ReflectionException): Class Vendor\Module\Model\Config\Source\ListSource does not exist but it exists in app/code/Vendor/Module/Model/Config/Source/ListSource.php
    – KaMZaTa
    Sep 15, 2020 at 22:28
  • Ok, I forgot to indicate the namespace over the class declaration and now the Newest option is shown on the backend but if I set it as the default one I cannot see anymore a product on every category and subcategory page. They are empty.
    – KaMZaTa
    Sep 15, 2020 at 22:42
  • Even if I don't set it as the default one but I choose it from the frontend category pages, then category pages don't show any products.
    – KaMZaTa
    Sep 16, 2020 at 0:09
  • @KaMZaTa, my code handles admin options, and I presumed that your existing toolbar customization would handle the selection, my bad. Sep 16, 2020 at 5:52
  • 1
    IN Magento 2.4 Elasticsearch is the only search adapter, MySQL was dropped. So I believe this change is fairly robust. I'll add namespace classname to migration example. Actually, if you choose to sort by entity_id I don't see why it's inconsistent. It's a unique identifier, unlike created_at in my example. Sep 17, 2020 at 5:45
1

I would suggest to go with reference links:

Magento 2 Sort by New Products and Most View Product

https://www.sparsh-technologies.com/magento-extensions/magento-2-advanced-sorting-extension

https://github.com/nitishd/magento-moresort

https://github.com/zekinah/magento2-AdvancedSortby

https://github.com/vvasiloud/SortByDate-Magento2

Please let me know if the above links will not work for you.

Thanks.

2
  • Thanks but the 1°, 3°, and the 5° didn't work for me while the 2° and the 4° seem to be a little bit invasive (I didn't try them) because they add a new entire section in the backend instead of adding that simple Newest sort option.
    – KaMZaTa
    Sep 17, 2020 at 0:24
  • Yes, correct, My suggestion is just take a require logic or code from module and use it in your module, hope this will work.
    – Nits
    Sep 17, 2020 at 5:09

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.