Jump to content

Add product sorting method


Recommended Posts

Hi, I would like to know how to add a product sorting method by brand. I tried doing several overrides on the ps_facetedsearch module without any success.

Could you explain to me how to do it?
The Prestashop version used is 8.1.3 and the ps_facetedsearch module is 3.15.1

Link to comment
Share on other sites

On 3/20/2024 at 10:59 AM, adrianofina said:

Hi, I would like to know how to add a product sorting method by brand. I tried doing several overrides on the ps_facetedsearch module without any success.

Could you explain to me how to do it?
The Prestashop version used is 8.1.3 and the ps_facetedsearch module is 3.15.1

You need to look at the core class 'ProductListingFrontController' (not the faceted search module) as that is where those sort orders are managed. You'll find a whole bunch of hooks you can use to insert your own behaviour. You should be able to add a module to hook into that and set your own custom sort order.

  • Thanks 1
Link to comment
Share on other sites

20 hours ago, Paul C said:

You need to look at the core class 'ProductListingFrontController' (not the faceted search module) as that is where those sort orders are managed. You'll find a whole bunch of hooks you can use to insert your own behaviour. You should be able to add a module to hook into that and set your own custom sort order.

Hi, thanks for the reply.

I took a look inside the file and can't find anything regarding product sorting methods. Could you explain me better how to do it?

Link to comment
Share on other sites

Hi, to be honest I haven't had the need to do this myself so can't offer any substantial code examples (there isn't an example in the git module examples repository either sadly).

The following in the ProductListingFrontController class appeared to be the key to the solution:

 

    /**
     * This method is the heart of the search provider delegation
     * mechanism.
     *
     * It executes the `productSearchProvider` hook (array style),
     * and returns the first one encountered.
     *
     * This provides a well specified way for modules to execute
     * the search query instead of the core.
     *
     * The hook is called with the $query argument, which allows
     * modules to decide if they can manage the query.
     *
     * For instance, if two search modules are installed and
     * one module knows how to search by category but not by manufacturer,
     * then "ManufacturerController" will use one module to do the query while
     * "CategoryController" will use another module to do the query.
     *
     * If no module can perform the query then null is returned.
     *
     * @param ProductSearchQuery $query
     *
     * @return ProductSearchProviderInterface|null
     */
    private function getProductSearchProviderFromModules($query)
    {
        // An array [module_name => module_output] will be returned
        $providers = Hook::exec(
            'productSearchProvider',
            ['query' => $query],
            null,
            true
        );

        if (!is_array($providers)) {
            $providers = [];
        }

        foreach ($providers as $provider) {
            if ($provider instanceof ProductSearchProviderInterface) {
                return $provider;
            }
        }

        return null;
    }

So your module will need to hook into productSearchProvider and respond if it can handle the query passed to it. It would appear that your module would return an array containing an object of class ProductSearchProviderInterface. 

However, you should also see within this function in the ProductListingFrontController  class (lines cut for readability):

 

/**
     * This returns all template variables needed for rendering
     * the product list, the facets, the pagination and the sort orders.
     *
     * @return array variables ready for templating
     */
    protected function getProductSearchVariables()
    {
        ....
        // prepare the sort orders
        // note that, again, the product controller is sort-orders
        // agnostic
        // a module can easily add specific sort orders that it needs
        // to support (e.g. sort by "energy efficiency")
        $sort_orders = $this->getTemplateVarSortOrders(
            $result->getAvailableSortOrders(),
            $query->getSortOrder()->toString()
        );
        ....
        $searchVariables = [
            'result' => $result,
            'label' => $this->getListingLabel(),
            'products' => $products,
            'sort_orders' => $sort_orders,
            'sort_selected' => $sort_selected,
            'pagination' => $pagination,
            'rendered_facets' => $rendered_facets,
            'rendered_active_filters' => $rendered_active_filters,
            'js_enabled' => $this->ajax,
            'current_url' => $this->updateQueryString([
                'q' => $result->getEncodedFacets(),
            ]),
        ];

        Hook::exec('filterProductSearch', ['searchVariables' => &$searchVariables]);
        Hook::exec('actionProductSearchAfter', $searchVariables);

        return $searchVariables;
    }

The second parameter to the getTemplateVarSortOrders leads to the function ProductSearchResult::getAvailableSortOrders(). This leads you back into the ps_faceted search module where you were attempting to inject your own sort order.  A bit of a dead end - however as you can see there are two hooks at the end of this function that would allow you the opportunity of modifying the sort orders, so I assume you could implement the hook filterProductSearch in your module. I'm guessing you need to insert your own sort order based on the content of the getTemplateVarSortOrders function called above, i.e.

 

/**
     * Prepares the sort-order links.
     *
     * Sort order links contain the current encoded facets if any,
     * but not the page number because normally when you change the sort order
     * you want to go back to page one.
     *
     * @param array $sortOrders the available sort orders
     * @param string $currentSortOrderURLParameter used to know which of the sort orders (if any) is active
     *
     * @return array
     */
    protected function getTemplateVarSortOrders(array $sortOrders, $currentSortOrderURLParameter)
    {
        return array_map(function ($sortOrder) use ($currentSortOrderURLParameter) {
            $order = $sortOrder->toArray();
            $order['current'] = $order['urlParameter'] === $currentSortOrderURLParameter;
            $order['url'] = $this->updateQueryString([
                'order' => $order['urlParameter'],
                'page' => null,
            ]);

            return $order;
        }, $sortOrders);
    }

It appears that this sets the parameters for the SQL that sorts the query, and you may just have to focus on this hook and perhaps don't need to worry about the full-blown ProductSearchProviderInterface route.....

Not sure if any of this rambling will help you though! It's a shame that very little of this is documented but it's getting there slowly.

EDIT: I just realised that I only discussed the function ProductSearchResult::getAvailableSortOrders() in passing, but the contents of this will give vital clues for the correct form of the custom sort order you're looking to create.

Edited by Paul C
Additional thought (see edit history)
  • Like 1
Link to comment
Share on other sites

  • 1 month later...

Thank you @Paul C. I was trying to remove the reference sorting options and this did the job without overriding ps_facetedsearch module.

I created a new module that registered the actionProductSearchProviderRunQueryAfter hook. This gives you as parameters the ProductSearchQuery sent to the search provider and the ProductSearchResult returned by the search provider. ProductSearchResult has a method to set the available sort orders which I used for the custom sort orders.
 

/**
     * Overwrite the available sorting options after the search provider (ps_facetedsearch) returned a result
     */
    public function hookActionProductSearchProviderRunQueryAfter(array $params): void
    {
        /** @var ProductSearchQuery*/
        $query = $params["query"];

        /** @var ProductSearchResult */
        $result = $params["result"];

        $result->setAvailableSortOrders($this->getCustomSorting($query));
    }

    /**
     * Create a custom sorting order
     * 
     * @param ProductSearchQuery $query
     *
     * @return SortOrder[]
     */
    private function getCustomSorting(ProductSearchQuery $query): array
    {
        $sortSalesDesc = new SortOrder('product', 'sales', 'desc');
        $sortPosAsc = new SortOrder('product', 'position', 'asc');
        $sortNameAsc = new SortOrder('product', 'name', 'asc');
        $sortNameDesc = new SortOrder('product', 'name', 'desc');
        $sortPriceAsc = new SortOrder('product', 'price', 'asc');
        $sortPriceDesc = new SortOrder('product', 'price', 'desc');
        $sortDateAsc = new SortOrder('product', 'date_add', 'asc');
        $sortDateDesc = new SortOrder('product', 'date_add', 'desc');
		// optinally add your own sorting option
        $sortEanAsc = new SortOrder('product', 'ean13', 'desc');
        // $sortRefAsc = new SortOrder('product', 'reference', 'asc');
        // $sortRefDesc = new SortOrder('product', 'reference', 'desc');

        $sortOrders = [
            $sortSalesDesc->setLabel(
                $this->trans('Sales, highest to lowest', [], 'Shop.Theme.Catalog')
            ),
            $sortPosAsc->setLabel(
                $this->trans('Relevance', [], 'Shop.Theme.Catalog')
            ),
            $sortNameAsc->setLabel(
                $this->trans('Name, A to Z', [], 'Shop.Theme.Catalog')
            ),
            $sortNameDesc->setLabel(
                $this->trans('Name, Z to A', [], 'Shop.Theme.Catalog')
            ),
            $sortPriceAsc->setLabel(
                $this->trans('Price, low to high', [], 'Shop.Theme.Catalog')
            ),
            $sortPriceDesc->setLabel(
                $this->trans('Price, high to low', [], 'Shop.Theme.Catalog')
            ),
			// add the label for your own sorting option
            $sortEanAsc->setLabel(
                $this->trans('Ean13, low to high', [], 'Shop.Theme.Catalog')
            ),
            // $sortRefAsc->setLabel(
            //     $translator->trans('Reference, A to Z', [], 'Shop.Theme.Catalog')
            // ),
            // $sortRefDesc->setLabel(
            //     $translator->trans('Reference, Z to A', [], 'Shop.Theme.Catalog')
            // ),
        ];

        if ($query->getQueryType() == 'new-products') {
            $sortOrders[] = $sortDateAsc->setLabel(
                $this->trans('Date added, oldest to newest', [], 'Shop.Theme.Catalog')
            );
            $sortOrders[] = $sortDateDesc->setLabel(
                $this->trans('Date added, newest to oldest', [], 'Shop.Theme.Catalog')
            );
        }

        return $sortOrders;
    }

 

  • Like 1
Link to comment
Share on other sites

Hi @buzzvicky by chance I actually had to do something similar and this is the hook I used (although I was just removing them, not adding my own):

 

    public function hookfilterProductSearch($arguments)
    {
        // Get the source data
        $searchVariables = $arguments['searchVariables'];

        // We need to build a new list of our included sort orders
        $new_sort_orders = [];
        if (!empty($searchVariables['sort_orders'])) {
            foreach ($searchVariables['sort_orders'] as $order) {
                // Filter out the ones we don't want
                if (
                    $order['label'] != $this->trans('Sales, highest to lowest', [], 'Shop.Theme.Catalog') &&
                    $order['label'] != $this->trans('Name, A to Z', [], 'Shop.Theme.Catalog') &&
                    $order['label'] != $this->trans('Name, Z to A', [], 'Shop.Theme.Catalog') &&
                    $order['label'] != $this->trans('Reference, A to Z', [], 'Shop.Theme.Catalog') &&
                    $order['label'] != $this->trans('Reference, Z to A', [], 'Shop.Theme.Catalog')
                )
                    $new_sort_orders[] = $order;
            }
            // Set our new sort orders
            $searchVariables['sort_orders'] = $new_sort_orders;
            $arguments['searchVariables'] = $searchVariables;
        }
    }

Paul

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...