'cart', 'primary' => 'id_cart', 'fields' => [ 'id_shop_group' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_shop' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_address_delivery' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_address_invoice' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_carrier' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_currency' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'id_customer' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_guest' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId'], 'id_lang' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true], 'recyclable' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'gift' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'gift_message' => ['type' => self::TYPE_STRING, 'validate' => 'isCleanHtml'], 'mobile_theme' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'delivery_option' => ['type' => self::TYPE_STRING], 'secure_key' => ['type' => self::TYPE_STRING, 'size' => 32], 'allow_seperated_package' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], ], ]; /** * Reset static cache */ public static function resetStaticCache() { self::$_nbProducts = []; self::$_isVirtualCart = []; self::$_totalWeight = []; self::$_carriers = null; self::$_taxes_rate = null; self::$_attributesLists = []; self::$_customer = null; self::$cacheDeliveryOption = []; self::$cacheNbPackages = []; self::$cachePackageList = []; self::$cacheDeliveryOptionList = []; } /** * Get number of products in cart * * @return int */ public function nbProducts() { if (!$this->id) { return 0; } return self::getNbProducts($this->id); } /** * Get number of products in cart by ID * * @param int $id * @return int */ public static function getNbProducts($id) { if (isset(self::$_nbProducts[$id]) && self::$_nbProducts[$id] !== null) { return self::$_nbProducts[$id]; } self::$_nbProducts[$id] = (int) Db::getInstance()->getValue( 'SELECT SUM(`quantity`) FROM `' . _DB_PREFIX_ . 'cart_product` WHERE `id_cart` = ' . (int) $id ); return self::$_nbProducts[$id]; } /** * Add a CartRule to the Cart * * @param int $id_cart_rule * @param bool $useOrderPrices * @return bool */ public function addCartRule($id_cart_rule, bool $useOrderPrices = false) { $cartRule = new CartRule($id_cart_rule, Context::getContext()->language->id); if (!Validate::isLoadedObject($cartRule)) { return false; } if (Db::getInstance()->getValue('SELECT id_cart_rule FROM ' . _DB_PREFIX_ . 'cart_cart_rule WHERE id_cart_rule = ' . (int) $id_cart_rule . ' AND id_cart = ' . (int) $this->id)) { return false; } if (!Db::getInstance()->insert('cart_cart_rule', [ 'id_cart_rule' => (int) $id_cart_rule, 'id_cart' => (int) $this->id, ])) { return false; } Cache::clean('Cart::getCartRules_' . $this->id . '-' . CartRule::FILTER_ACTION_ALL); Cache::clean('Cart::getCartRules_' . $this->id . '-' . CartRule::FILTER_ACTION_SHIPPING); Cache::clean('Cart::getCartRules_' . $this->id . '-' . CartRule::FILTER_ACTION_REDUCTION); Cache::clean('Cart::getCartRules_' . $this->id . '-' . CartRule::FILTER_ACTION_GIFT); if ((int) $cartRule->gift_product) { $this->updateQty( 1, $cartRule->gift_product, $cartRule->gift_product_attribute, false, 'up', 0, null, false, false, true, $useOrderPrices ); } return true; } /** * Get product quantity in cart * * @param int $idProduct * @param int $idProductAttribute * @param int $idCustomization * @param int $idAddressDelivery * @return array */ public function getProductQuantity($idProduct, $idProductAttribute = 0, $idCustomization = 0, $idAddressDelivery = 0) { $sql = 'SELECT SUM(cp.`quantity`) as quantity FROM `' . _DB_PREFIX_ . 'cart_product` cp WHERE cp.`id_product` = ' . (int) $idProduct . ' AND cp.`id_product_attribute` = ' . (int) $idProductAttribute . ' AND cp.`id_customization` = ' . (int) $idCustomization . ' AND cp.`id_cart` = ' . (int) $this->id; if ($idAddressDelivery) { $sql .= ' AND cp.`id_address_delivery` = ' . (int) $idAddressDelivery; } $result = Db::getInstance()->getRow($sql); return [ 'quantity' => (int) $result['quantity'], 'deep_quantity' => (int) $result['quantity'] ]; } /** * Update product quantity * * @param int $quantity * @param int $id_product * @param int|null $id_product_attribute * @param int|false $id_customization * @param string $operator * @param int $id_address_delivery * @param Shop|null $shop * @param bool $auto_add_cart_rule * @param bool $skipAvailabilityCheckOutOfStock * @param bool $preserveGiftRemoval * @param bool $useOrderPrices * @return bool|int */ public function updateQty( $quantity, $id_product, $id_product_attribute = null, $id_customization = false, $operator = 'up', $id_address_delivery = 0, ?Shop $shop = null, $auto_add_cart_rule = true, $skipAvailabilityCheckOutOfStock = false, bool $preserveGiftRemoval = true, bool $useOrderPrices = false ) { if (!$shop) { $shop = Context::getContext()->shop; } $quantity = (int) $quantity; $id_product = (int) $id_product; $id_product_attribute = (int) $id_product_attribute; $id_customization = (int) $id_customization; $product = new Product($id_product, false, (int) Configuration::get('PS_LANG_DEFAULT'), $shop->id); if (!Validate::isLoadedObject($product)) { return false; } if ($id_product_attribute) { $combination = new Combination((int) $id_product_attribute); if ($combination->id_product != $id_product) { return false; } } $currentQuantity = $this->getProductQuantity($id_product, $id_product_attribute, $id_customization, $id_address_delivery); $currentQuantity = (int) $currentQuantity['quantity']; if ($operator == 'up') { $newQuantity = $currentQuantity + $quantity; } elseif ($operator == 'down') { $newQuantity = $currentQuantity - $quantity; } else { $newQuantity = $quantity; } if ($newQuantity <= 0) { return $this->deleteProduct($id_product, $id_product_attribute, $id_customization, $id_address_delivery, $preserveGiftRemoval, $useOrderPrices); } // Check if product exists in cart $sql = 'SELECT `id_cart_product` FROM `' . _DB_PREFIX_ . 'cart_product` WHERE `id_cart` = ' . (int) $this->id . ' AND `id_product` = ' . (int) $id_product . ' AND `id_product_attribute` = ' . (int) $id_product_attribute . ' AND `id_customization` = ' . (int) $id_customization; if ($id_address_delivery) { $sql .= ' AND `id_address_delivery` = ' . (int) $id_address_delivery; } $result = Db::getInstance()->getRow($sql); if ($result) { // Update existing product $sql = 'UPDATE `' . _DB_PREFIX_ . 'cart_product` SET `quantity` = ' . (int) $newQuantity . ' WHERE `id_cart_product` = ' . (int) $result['id_cart_product']; } else { // Add new product $sql = 'INSERT INTO `' . _DB_PREFIX_ . 'cart_product` (`id_cart`, `id_product`, `id_product_attribute`, `id_customization`, `id_address_delivery`, `quantity`) VALUES (' . (int) $this->id . ', ' . (int) $id_product . ', ' . (int) $id_product_attribute . ', ' . (int) $id_customization . ', ' . (int) $id_address_delivery . ', ' . (int) $newQuantity . ')'; } if (!Db::getInstance()->execute($sql)) { return false; } // Clear cache self::$_nbProducts[$this->id] = null; self::$_totalWeight[$this->id] = null; return $newQuantity; } /** * Delete product from cart * * @param int $id_product * @param int $id_product_attribute * @param int $id_customization * @param int $id_address_delivery * @param bool $preserveGiftsRemoval * @param bool $useOrderPrices * @return bool */ public function deleteProduct( $id_product, $id_product_attribute = 0, $id_customization = 0, $id_address_delivery = 0, bool $preserveGiftsRemoval = true, bool $useOrderPrices = false ) { $sql = 'DELETE FROM `' . _DB_PREFIX_ . 'cart_product` WHERE `id_cart` = ' . (int) $this->id . ' AND `id_product` = ' . (int) $id_product . ' AND `id_product_attribute` = ' . (int) $id_product_attribute . ' AND `id_customization` = ' . (int) $id_customization; if ($id_address_delivery) { $sql .= ' AND `id_address_delivery` = ' . (int) $id_address_delivery; } if (!Db::getInstance()->execute($sql)) { return false; } // Clear cache self::$_nbProducts[$this->id] = null; self::$_totalWeight[$this->id] = null; return true; } /** * Get order total * * @param bool $withTaxes * @param int $type * @param array|null $products * @param int|null $id_carrier * @param bool $use_cache * @param bool $keepOrderPrices * @return float */ public function getOrderTotal( $withTaxes = true, $type = self::BOTH, $products = null, $id_carrier = null, $use_cache = false, bool $keepOrderPrices = false ) { $total = 0; if ($type == self::ONLY_PRODUCTS || $type == self::BOTH || $type == self::BOTH_WITHOUT_SHIPPING) { $products = $this->getProducts($use_cache, false, null, true, $keepOrderPrices); foreach ($products as $product) { $price = $product['price_with_reduction']; if (!$withTaxes) { $price = $product['price_with_reduction_without_tax']; } $total += $price * $product['cart_quantity']; } } if ($type == self::ONLY_DISCOUNTS || $type == self::BOTH) { $discounts = $this->getDiscounts(); foreach ($discounts as $discount) { $total -= $discount['value_real']; } } if ($type == self::ONLY_SHIPPING || $type == self::BOTH) { if ($id_carrier) { $total += $this->getCarrierCost($id_carrier, $withTaxes); } } if ($type == self::ONLY_WRAPPING || $type == self::BOTH) { if ($this->gift) { $total += $this->getGiftWrappingPrice($withTaxes); } } return Tools::ps_round($total, 2); } /** * Get total weight * * @param array|null $products * @return float */ public function getTotalWeight($products = null) { if (!$this->id) { return 0; } if (isset(self::$_totalWeight[$this->id])) { return self::$_totalWeight[$this->id]; } if ($products === null) { $products = $this->getProducts(); } $weight = 0; foreach ($products as $product) { $weight += $product['weight'] * $product['cart_quantity']; } self::$_totalWeight[$this->id] = $weight; return $weight; } /** * Check if cart is virtual (only virtual products) * * @return bool */ public function isVirtualCart() { if (!$this->id) { return true; } if (isset(self::$_isVirtualCart[$this->id])) { return self::$_isVirtualCart[$this->id]; } $sql = 'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'cart_product` cp LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON cp.`id_product` = p.`id_product` WHERE cp.`id_cart` = ' . (int) $this->id . ' AND p.`is_virtual` = 0'; $result = Db::getInstance()->getValue($sql); $isVirtual = ($result == 0); self::$_isVirtualCart[$this->id] = $isVirtual; return $isVirtual; } /** * Check if cart has products * * @return bool */ public function hasProducts() { if (!$this->id) { return false; } return (bool) Db::getInstance()->getValue( 'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'cart_product` WHERE `id_cart` = ' . (int) $this->id ); } /** * Check if cart has real products * * @return bool */ public function hasRealProducts() { if (!$this->id) { return false; } $sql = 'SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'cart_product` cp LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON cp.`id_product` = p.`id_product` WHERE cp.`id_cart` = ' . (int) $this->id . ' AND p.`is_virtual` = 0'; return (bool) Db::getInstance()->getValue($sql); } /** * Get carrier cost * * @param int $id_carrier * @param bool $useTax * @param Country|null $default_country * @param string|null $delivery_option * @return float */ public function getCarrierCost($id_carrier, $useTax = true, ?Country $default_country = null, $delivery_option = null) { // Simplified carrier cost calculation $carrier = new Carrier($id_carrier); if (!Validate::isLoadedObject($carrier)) { return 0; } $cost = $carrier->getDeliveryPriceByWeight($this->getTotalWeight()); if ($useTax) { $tax = new Tax($carrier->getIdTaxRulesGroup()); if (Validate::isLoadedObject($tax)) { $cost += $cost * ($tax->rate / 100); } } return Tools::ps_round($cost, 2); } /** * Get gift wrapping price * * @param bool $with_taxes * @param int|null $id_address * @return float */ public function getGiftWrappingPrice($with_taxes = true, $id_address = null) { $wrapping_fees = (float) Configuration::get('PS_GIFT_WRAPPING_PRICE'); if ($with_taxes) { $tax = new Tax(Configuration::get('PS_GIFT_WRAPPING_TAX_RULES_GROUP')); if (Validate::isLoadedObject($tax)) { $wrapping_fees += $wrapping_fees * ($tax->rate / 100); } } return Tools::ps_round($wrapping_fees, 2); } /** * Get last none ordered cart * * @param int $id_customer * @return int|false */ public static function lastNoneOrderedCart($id_customer) { $sql = 'SELECT c.`id_cart` FROM `' . _DB_PREFIX_ . 'cart` c LEFT JOIN `' . _DB_PREFIX_ . 'orders` o ON c.`id_cart` = o.`id_cart` WHERE c.`id_customer` = ' . (int) $id_customer . ' AND o.`id_cart` IS NULL ORDER BY c.`date_add` DESC'; return Db::getInstance()->getValue($sql); } /** * Get customer carts * * @param int $id_customer * @param bool $with_order * @return array */ public static function getCustomerCarts($id_customer, $with_order = true) { $sql = 'SELECT c.* FROM `' . _DB_PREFIX_ . 'cart` c'; if ($with_order) { $sql .= ' LEFT JOIN `' . _DB_PREFIX_ . 'orders` o ON c.`id_cart` = o.`id_cart` WHERE c.`id_customer` = ' . (int) $id_customer . ' AND o.`id_cart` IS NULL'; } else { $sql .= ' WHERE c.`id_customer` = ' . (int) $id_customer; } $sql .= ' ORDER BY c.`date_add` DESC'; return Db::getInstance()->executeS($sql); } /** * Check quantities * * @param bool $returnProductOnFailure * @return bool|array */ public function checkQuantities($returnProductOnFailure = false) { $products = $this->getProducts(); $errors = []; foreach ($products as $product) { if (!$product['active'] || !$product['available_for_order']) { $errors[] = $product; continue; } if ($product['quantity'] < $product['cart_quantity']) { $errors[] = $product; } } if ($returnProductOnFailure) { return $errors; } return empty($errors); } /** * Get products * * @param bool $refresh * @param bool $id_product * @param int|null $id_country * @param bool $fullInfos * @param bool $keepOrderPrices * @return array */ public function getProducts($refresh = false, $id_product = false, $id_country = null, $fullInfos = true, bool $keepOrderPrices = false) { if (!$this->id) { return []; } $sql = 'SELECT cp.*, p.*, pl.*, i.`id_image`, cl.`name` as category_default FROM `' . _DB_PREFIX_ . 'cart_product` cp LEFT JOIN `' . _DB_PREFIX_ . 'product` p ON cp.`id_product` = p.`id_product` LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON p.`id_product` = pl.`id_product` AND pl.`id_lang` = ' . (int) $this->id_lang . ' LEFT JOIN `' . _DB_PREFIX_ . 'image` i ON p.`id_product` = i.`id_product` AND i.`cover` = 1 LEFT JOIN `' . _DB_PREFIX_ . 'category_lang` cl ON p.`id_category_default` = cl.`id_category` AND cl.`id_lang` = ' . (int) $this->id_lang . ' WHERE cp.`id_cart` = ' . (int) $this->id; if ($id_product) { $sql .= ' AND cp.`id_product` = ' . (int) $id_product; } $sql .= ' ORDER BY cp.`date_add` ASC'; $products = Db::getInstance()->executeS($sql); if (!$products) { return []; } foreach ($products as &$product) { $product['price_with_reduction'] = $product['price']; $product['price_with_reduction_without_tax'] = $product['price']; $product['total'] = $product['price'] * $product['cart_quantity']; $product['total_wt'] = $product['price'] * $product['cart_quantity']; } return $products; } /** * Get discounts * * @return array */ public function getDiscounts() { $sql = 'SELECT cr.*, crl.`name` FROM `' . _DB_PREFIX_ . 'cart_cart_rule` ccr LEFT JOIN `' . _DB_PREFIX_ . 'cart_rule` cr ON ccr.`id_cart_rule` = cr.`id_cart_rule` LEFT JOIN `' . _DB_PREFIX_ . 'cart_rule_lang` crl ON cr.`id_cart_rule` = crl.`id_cart_rule` AND crl.`id_lang` = ' . (int) $this->id_lang . ' WHERE ccr.`id_cart` = ' . (int) $this->id . ' AND cr.`active` = 1'; return Db::getInstance()->executeS($sql); } }