Zmiana kolejności produktów w koszyku – ostatnio dodany produkt jako pierwszy, na górze listy w koszyku w Prestashop 1.7

Dzisiaj podzielimy się bardzo prostą ale dość ciekawą dodatkową modyfikacją systemu Prestashop 1.7.

Celem naszej modyfikacji jest sortowanie produktów w koszyku według kolejności wrzucenia ich do koszyka malejąco.

To znaczy, że produkt wrzucony (dodany) jako ostatni do koszyka – ma się pojawiać jako pierwszy na liście produktów w koszyku.

Ale o co właściwie chodzi?

Gdy dodamy do koszyka w sklepie na silniku Prestashop 1.7 dwa różne produkty, to zostaną one posortowane w koszyku według instrukcji ORDER BY w sql:

$sql->orderBy('cp.`date_add`, cp.`id_product`, cp.`id_product_attribute` ASC');

Powyższa instrukcja tworzy klientowi dość niewygodne lustro zakupów – ostatnio dodany produkt ląduje na końcu listy. Według nas klient, oczekuje raczej aby to ostatnio dodany produkt – był pierwszy na liście produktów w koszyku. Jest to z pewnością wygodniejsze rozwiązanie dla klienta robiącego większe zakupy, na przykład jeśli doda do bardzo długiej listy zakupów przypadkowy produkt to nie musi go szukać na końcu listy – będzie on na jej początku i w łatwy sposób, bez przewijania listy będzie go mógł usunąć.

Do dzieła!

Zrealizowanie tej funkcjonalności wymaga utworzenia nadpisania pliku klasy koszyka tzn. /classes/Cart.php, oczywiście nie nadpisujemy całego pliku – interesuje nas tylko jego część a dokładnie funkcja getProducts. Kopiujemy tą funkcję w całości z powyższego pliku.

Zajmijmy się samym plikiem nadpisania, tworzymy pusty plik Cart.php – otwieramy go i tworzymy nową klasę nadpisującą Cart, która rozszerza nam CartCore i wklejamy do niej wcześniej skopiowaną całą funkcję getProducts.

W powyższej funkcji interesuje nas wyłącznie poniższy fragment kodu, o którym mówiliśmy wcześniej tzn.:

$sql->orderBy('cp.`date_add`, cp.`id_product`, cp.`id_product_attribute` ASC');

Aby uzyskać efekt posortowania produktów w koszyku według kolejności dodawania ich do koszyka malejąco (ostatnio dodany jako pierwszy na liście) musimy dokonać odpowiedniej modyfikacji powyższej linijki kodu:

$sql->orderBy('cp.`date_add` DESC');

Całość nadpisania będzie wyglądała mniej więcej jak poniżej (w zależności od dokładnej wersji Prestashop 1.7):

<?php

class Cart extends CartCore {

public function getProducts($refresh = false, $id_product = false, $id_country = null, $fullInfos = true)
    {
        if (!$this--->id) {
            return array();
        }
        // Product cache must be strictly compared to NULL, or else an empty cart will add dozens of queries
        if ($this->_products !== null && !$refresh) {
            // Return product row with specified ID if it exists
            if (is_int($id_product)) {
                foreach ($this->_products as $product) {
                    if ($product['id_product'] == $id_product) {
                        return array($product);
                    }
                }

                return array();
            }

            return $this->_products;
        }

        // Build query
        $sql = new DbQuery();

        // Build SELECT
        $sql->select('cp.`id_product_attribute`, cp.`id_product`, cp.`quantity` AS cart_quantity, cp.id_shop, cp.`id_customization`, pl.`name`, p.`is_virtual`,
                        pl.`description_short`, pl.`available_now`, pl.`available_later`, product_shop.`id_category_default`, p.`id_supplier`,
                        p.`id_manufacturer`, m.`name` AS manufacturer_name, product_shop.`on_sale`, product_shop.`ecotax`, product_shop.`additional_shipping_cost`,
                        product_shop.`available_for_order`, product_shop.`show_price`, product_shop.`price`, product_shop.`active`, product_shop.`unity`, product_shop.`unit_price_ratio`,
                        stock.`quantity` AS quantity_available, p.`width`, p.`height`, p.`depth`, stock.`out_of_stock`, p.`weight`,
                        p.`available_date`, p.`date_add`, p.`date_upd`, IFNULL(stock.quantity, 0) as quantity, pl.`link_rewrite`, cl.`link_rewrite` AS category,
                        CONCAT(LPAD(cp.`id_product`, 10, 0), LPAD(IFNULL(cp.`id_product_attribute`, 0), 10, 0), IFNULL(cp.`id_address_delivery`, 0), IFNULL(cp.`id_customization`, 0)) AS unique_id, cp.id_address_delivery,
                        product_shop.advanced_stock_management, ps.product_supplier_reference supplier_reference');

        // Build FROM
        $sql->from('cart_product', 'cp');

        // Build JOIN
        $sql->leftJoin('product', 'p', 'p.`id_product` = cp.`id_product`');
        $sql->innerJoin('product_shop', 'product_shop', '(product_shop.`id_shop` = cp.`id_shop` AND product_shop.`id_product` = p.`id_product`)');
        $sql->leftJoin(
            'product_lang',
            'pl',
            'p.`id_product` = pl.`id_product`
            AND pl.`id_lang` = ' . (int) $this->id_lang . Shop::addSqlRestrictionOnLang('pl', 'cp.id_shop')
        );

        $sql->leftJoin(
            'category_lang',
            'cl',
            'product_shop.`id_category_default` = cl.`id_category`
            AND cl.`id_lang` = ' . (int) $this->id_lang . Shop::addSqlRestrictionOnLang('cl', 'cp.id_shop')
        );

        $sql->leftJoin('product_supplier', 'ps', 'ps.`id_product` = cp.`id_product` AND ps.`id_product_attribute` = cp.`id_product_attribute` AND ps.`id_supplier` = p.`id_supplier`');
        $sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');

        // @todo test if everything is ok, then refactorise call of this method
        $sql->join(Product::sqlStock('cp', 'cp'));

        // Build WHERE clauses
        $sql->where('cp.`id_cart` = ' . (int) $this->id);
        if ($id_product) {
            $sql->where('cp.`id_product` = ' . (int) $id_product);
        }
        $sql->where('p.`id_product` IS NOT NULL');

        // Build ORDER BY
        $sql->orderBy('cp.`date_add` DESC');

        if (Customization::isFeatureActive()) {
            $sql->select('cu.`id_customization`, cu.`quantity` AS customization_quantity');
            $sql->leftJoin(
                'customization',
                'cu',
                'p.`id_product` = cu.`id_product` AND cp.`id_product_attribute` = cu.`id_product_attribute` AND cp.`id_customization` = cu.`id_customization` AND cu.`id_cart` = ' . (int) $this->id
            );
            $sql->groupBy('cp.`id_product_attribute`, cp.`id_product`, cp.`id_shop`, cp.`id_customization`');
        } else {
            $sql->select('NULL AS customization_quantity, NULL AS id_customization');
        }

        if (Combination::isFeatureActive()) {
            $sql->select('
                product_attribute_shop.`price` AS price_attribute, product_attribute_shop.`ecotax` AS ecotax_attr,
                IF (IFNULL(pa.`reference`, \'\') = \'\', p.`reference`, pa.`reference`) AS reference,
                (p.`weight`+ pa.`weight`) weight_attribute,
                IF (IFNULL(pa.`ean13`, \'\') = \'\', p.`ean13`, pa.`ean13`) AS ean13,
                IF (IFNULL(pa.`isbn`, \'\') = \'\', p.`isbn`, pa.`isbn`) AS isbn,
                IF (IFNULL(pa.`upc`, \'\') = \'\', p.`upc`, pa.`upc`) AS upc,
                IFNULL(product_attribute_shop.`minimal_quantity`, product_shop.`minimal_quantity`) as minimal_quantity,
                IF(product_attribute_shop.wholesale_price > 0,  product_attribute_shop.wholesale_price, product_shop.`wholesale_price`) wholesale_price
            ');

            $sql->leftJoin('product_attribute', 'pa', 'pa.`id_product_attribute` = cp.`id_product_attribute`');
            $sql->leftJoin('product_attribute_shop', 'product_attribute_shop', '(product_attribute_shop.`id_shop` = cp.`id_shop` AND product_attribute_shop.`id_product_attribute` = pa.`id_product_attribute`)');
        } else {
            $sql->select(
                'p.`reference` AS reference, p.`ean13`, p.`isbn`,
                p.`upc` AS upc, product_shop.`minimal_quantity` AS minimal_quantity, product_shop.`wholesale_price` wholesale_price'
            );
        }

        $sql->select('image_shop.`id_image` id_image, il.`legend`');
        $sql->leftJoin('image_shop', 'image_shop', 'image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=' . (int) $this->id_shop);
        $sql->leftJoin('image_lang', 'il', 'il.`id_image` = image_shop.`id_image` AND il.`id_lang` = ' . (int) $this->id_lang);

        $result = Db::getInstance()->executeS($sql);

        // Reset the cache before the following return, or else an empty cart will add dozens of queries
        $products_ids = array();
        $pa_ids = array();
        if ($result) {
            foreach ($result as $key => $row) {
                $products_ids[] = $row['id_product'];
                $pa_ids[] = $row['id_product_attribute'];
                $specific_price = SpecificPrice::getSpecificPrice($row['id_product'], $this->id_shop, $this->id_currency, $id_country, $this->id_shop_group, $row['cart_quantity'], $row['id_product_attribute'], $this->id_customer, $this->id);
                if ($specific_price) {
                    $reduction_type_row = array('reduction_type' => $specific_price['reduction_type']);
                } else {
                    $reduction_type_row = array('reduction_type' => 0);
                }

                $result[$key] = array_merge($row, $reduction_type_row);
            }
        }
        // Thus you can avoid one query per product, because there will be only one query for all the products of the cart
        Product::cacheProductsFeatures($products_ids);
        Cart::cacheSomeAttributesLists($pa_ids, $this->id_lang);

        $this->_products = array();
        if (empty($result)) {
            return array();
        }

        if ($fullInfos) {
            $ecotax_rate = (float) Tax::getProductEcotaxRate($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
            $apply_eco_tax = Product::$_taxCalculationMethod == PS_TAX_INC && (int) Configuration::get('PS_TAX');
            $cart_shop_context = Context::getContext()->cloneContext();

            $gifts = $this->getCartRules(CartRule::FILTER_ACTION_GIFT);
            $givenAwayProductsIds = array();

            if ($this->shouldSplitGiftProductsQuantity && count($gifts) > 0) {
                foreach ($gifts as $gift) {
                    foreach ($result as $rowIndex => $row) {
                        if (!array_key_exists('is_gift', $result[$rowIndex])) {
                            $result[$rowIndex]['is_gift'] = false;
                        }

                        if (
                            $row['id_product'] == $gift['gift_product'] &&
                            $row['id_product_attribute'] == $gift['gift_product_attribute']
                        ) {
                            $row['is_gift'] = true;
                            $result[$rowIndex] = $row;
                        }
                    }

                    $index = $gift['gift_product'] . '-' . $gift['gift_product_attribute'];
                    if (!array_key_exists($index, $givenAwayProductsIds)) {
                        $givenAwayProductsIds[$index] = 1;
                    } else {
                        ++$givenAwayProductsIds[$index];
                    }
                }
            }

            foreach ($result as &$row) {
                if (!array_key_exists('is_gift', $row)) {
                    $row['is_gift'] = false;
                }

                $additionalRow = Product::getProductProperties((int) $this->id_lang, $row);
                $row['reduction'] = $additionalRow['reduction'];
                $row['reduction_without_tax'] = $additionalRow['reduction_without_tax'];
                $row['price_without_reduction'] = $additionalRow['price_without_reduction'];
                $row['specific_prices'] = $additionalRow['specific_prices'];
                unset($additionalRow);

                $givenAwayQuantity = 0;
                $giftIndex = $row['id_product'] . '-' . $row['id_product_attribute'];
                if ($row['is_gift'] && array_key_exists($giftIndex, $givenAwayProductsIds)) {
                    $givenAwayQuantity = $givenAwayProductsIds[$giftIndex];
                }

                if (!$row['is_gift'] || (int) $row['cart_quantity'] === $givenAwayQuantity) {
                    $row = $this->applyProductCalculations($row, $cart_shop_context);
                } else {
                    // Separate products given away from those manually added to cart
                    $this->_products[] = $this->applyProductCalculations($row, $cart_shop_context, $givenAwayQuantity);
                    unset($row['is_gift']);
                    $row = $this->applyProductCalculations(
                        $row,
                        $cart_shop_context,
                        $row['cart_quantity'] - $givenAwayQuantity
                    );
                }

                $this->_products[] = $row;
            }
        } else {
            $this->_products = $result;
        }

        return $this->_products;
    }
}

Zapisujemy nasz plik.

Oczywiście tradycyjnie nadpisanie lądują w folderze /override, ostatecznie ścieżka do naszego nadpisania będzie wyglądała dokładnie tak:

/override/classes/Cart.php

Gotowe! Odświeżamy, jeśli zmiany nie są widoczne – czyścimy Pamięć podręczną (Zaawansowane – Wydajność – przycisk Wyczyść pamięć podręczną). Jeśli coś funkcjonuje nieprawidłowo – skorzystaj z trybu debugowania w celu ustalenia źródła problemu ze stworzonym nadpisaniem. Uważaj – szczególnie jeżeli już tworzyłeś, lub któryś z modułów stworzył wcześniej nadpisanie pliku /classes/Cart.php – wówczas musisz przeanalizować ten plik i nadpisane wcześniej funkcje oraz ewentualnie nanieść odpowiednie zmiany względem tego artykułu.

Dziękujemy za Twoją obecność i zapraszamy na kolejną wspólną potyczkę z systemem Prestashop 1.7.

 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Przewiń do góry