Friday, October 30, 2015

PrestaShop 1.6 Shopping Cart Sorting

By default, PrestaShop orders the items in the shopping cart by the datetime they were added (order by date_add, id_product, id_product_attributes, from \classes\Cart.php). However, it drove me nuts that changing the quantity of the items in the cart changes their ps_cart_products::date_add, thereby causing the sorting order in the cart to change! Since the normal functioning of the code updates the basket with Ajax, it's usually not noticed by the visitor, but my code (for various reasons) actually performs a full update, which causes the items to jump visibly in the basket.

Although the default PrestaShop sorting is perfectly fine for the majority of users, I have seen people asking about how to change the sort order, and see the questions either unanswered or with answers that change the core code (something I try to avoid).

To "correct" this, I first created the following table:

CREATE TABLE `ps_cart_product_sorting` (
  `id_cart` int(10) unsigned NOT NULL,
  `id_product` int(10) unsigned NOT NULL,
  `original_date_add` datetime NOT NULL,
  UNIQUE KEY `ix_cart_product` (`id_cart`,`id_product`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

I then overrode \classes\Cart.php (by creating \overrides\classes\Cart.php), function getSummaryDetails. I first call the base class to return the $summary_details array. I then add the original date add to each product, and finally sort the array by the original date add.

<?php

function summary_compare (array $one, array $two)
{
    return strcmp ($one['original_date_add'], $two['original_date_add']);
}

class Cart extends CartCore
{
    //----------------------------------------------------------------------------
    public function getSummaryDetails($id_lang = null, $refresh = false)
    {
        $summary_details = parent::getSummaryDetails($id_lang, $refresh);
        if (isset ($summary_details['products'])) {
            for ($jj = 0; $jj < count($summary_details['products']); $jj++) {
                $summary_details['products'][$jj]['original_date_add'] =
                    $this->getOriginalDateAdd(
                        $summary_details['products'][$jj]['id_product'],
                        $summary_details['products'][$jj]['date_add']);
            }

            //Rather than letting prestashop sort by last modified (which moves things
            //around in the cart when changing qty there), sort by the original add_date.
            //Note that prestashop modifies cart_products::date_add every time the qty
            // changes, setting it to currente time.
            uasort($summary_details['products'], 'summary_compare');
        }

        return $summary_details;
    }

    //----------------------------------------------------------------------------
    protected function getOriginalDateAdd($id_product, $date_add)
    {
        $original_date_add = Db::getInstance()->getValue("
            select
                original_date_add
            from
                ps_cart_product_sorting
            where
                id_cart = {$this->id}
                and id_product = $id_product");

        if (!$original_date_add) {
            $original_date_add = $date_add;
            Db::getInstance()->insert('ps_cart_product_sorting', array (
                'id_cart' => $this->id,
                'id_product' => $id_product,
                'original_date_add' => $original_date_add
            ));
        }
        return $original_date_add;
    }
}

Finally, I added the sorting function

<?php
function summary_compare (array $one, array $two)
{
    return strcmp ($one['original_date_add'], $two['original_date_add']);
}