PageRenderTime 65ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 1ms

/classes/Cart.php

https://github.com/LucaTerzaghi/PrestaShop
PHP | 3733 lines | 2758 code | 495 blank | 480 comment | 543 complexity | 9635aa388fa21639e831544f0b18d24c MD5 | raw file
Possible License(s): LGPL-3.0, CC-BY-SA-3.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class CartCore extends ObjectModel
  27. {
  28. public $id;
  29. public $id_shop_group;
  30. public $id_shop;
  31. /** @var integer Customer delivery address ID */
  32. public $id_address_delivery;
  33. /** @var integer Customer invoicing address ID */
  34. public $id_address_invoice;
  35. /** @var integer Customer currency ID */
  36. public $id_currency;
  37. /** @var integer Customer ID */
  38. public $id_customer;
  39. /** @var integer Guest ID */
  40. public $id_guest;
  41. /** @var integer Language ID */
  42. public $id_lang;
  43. /** @var boolean True if the customer wants a recycled package */
  44. public $recyclable = 0;
  45. /** @var boolean True if the customer wants a gift wrapping */
  46. public $gift = 0;
  47. /** @var string Gift message if specified */
  48. public $gift_message;
  49. /** @var boolean Mobile Theme */
  50. public $mobile_theme;
  51. /** @var string Object creation date */
  52. public $date_add;
  53. /** @var string secure_key */
  54. public $secure_key;
  55. /** @var integer Carrier ID */
  56. public $id_carrier = 0;
  57. /** @var string Object last modification date */
  58. public $date_upd;
  59. public $checkedTos = false;
  60. public $pictures;
  61. public $textFields;
  62. public $delivery_option;
  63. /** @var boolean Allow to seperate order in multiple package in order to recieve as soon as possible the available products */
  64. public $allow_seperated_package = false;
  65. protected static $_nbProducts = array();
  66. protected static $_isVirtualCart = array();
  67. protected $_products = null;
  68. protected static $_totalWeight = array();
  69. protected $_taxCalculationMethod = PS_TAX_EXC;
  70. protected static $_carriers = null;
  71. protected static $_taxes_rate = null;
  72. protected static $_attributesLists = array();
  73. /**
  74. * @see ObjectModel::$definition
  75. */
  76. public static $definition = array(
  77. 'table' => 'cart',
  78. 'primary' => 'id_cart',
  79. 'fields' => array(
  80. 'id_shop_group' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  81. 'id_shop' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  82. 'id_address_delivery' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  83. 'id_address_invoice' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  84. 'id_carrier' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  85. 'id_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
  86. 'id_customer' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  87. 'id_guest' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  88. 'id_lang' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
  89. 'recyclable' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  90. 'gift' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  91. 'gift_message' => array('type' => self::TYPE_STRING, 'validate' => 'isMessage'),
  92. 'mobile_theme' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  93. 'delivery_option' => array('type' => self::TYPE_STRING),
  94. 'secure_key' => array('type' => self::TYPE_STRING, 'size' => 32),
  95. 'allow_seperated_package' =>array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  96. 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDateFormat'),
  97. 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDateFormat'),
  98. ),
  99. );
  100. protected $webserviceParameters = array(
  101. 'fields' => array(
  102. 'id_address_delivery' => array('xlink_resource' => 'addresses'),
  103. 'id_address_invoice' => array('xlink_resource' => 'addresses'),
  104. 'id_currency' => array('xlink_resource' => 'currencies'),
  105. 'id_customer' => array('xlink_resource' => 'customers'),
  106. 'id_guest' => array('xlink_resource' => 'guests'),
  107. 'id_lang' => array('xlink_resource' => 'languages'),
  108. ),
  109. 'associations' => array(
  110. 'cart_rows' => array('resource' => 'cart_row', 'virtual_entity' => true, 'fields' => array(
  111. 'id_product' => array('required' => true, 'xlink_resource' => 'products'),
  112. 'id_product_attribute' => array('required' => true, 'xlink_resource' => 'combinations'),
  113. 'id_address_delivery' => array('required' => true, 'xlink_resource' => 'addresses'),
  114. 'quantity' => array('required' => true),
  115. )
  116. ),
  117. ),
  118. );
  119. const ONLY_PRODUCTS = 1;
  120. const ONLY_DISCOUNTS = 2;
  121. const BOTH = 3;
  122. const BOTH_WITHOUT_SHIPPING = 4;
  123. const ONLY_SHIPPING = 5;
  124. const ONLY_WRAPPING = 6;
  125. const ONLY_PRODUCTS_WITHOUT_SHIPPING = 7;
  126. const ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING = 8;
  127. public function __construct($id = null, $id_lang = null)
  128. {
  129. parent::__construct($id);
  130. if (!is_null($id_lang))
  131. $this->id_lang = (int)(Language::getLanguage($id_lang) !== false) ? $id_lang : Configuration::get('PS_LANG_DEFAULT');
  132. if ($this->id_customer)
  133. {
  134. if (isset(Context::getContext()->customer) && Context::getContext()->customer->id == $this->id_customer)
  135. $customer = Context::getContext()->customer;
  136. else
  137. $customer = new Customer((int)$this->id_customer);
  138. if ((!$this->secure_key || $this->secure_key == '-1') && $customer->secure_key)
  139. {
  140. $this->secure_key = $customer->secure_key;
  141. $this->save();
  142. }
  143. }
  144. $this->_taxCalculationMethod = Group::getPriceDisplayMethod(Group::getCurrent()->id);
  145. }
  146. public function add($autodate = true, $null_values = false)
  147. {
  148. if (!$this->id_lang)
  149. $this->id_lang = Configuration::get('PS_LANG_DEFAULT');
  150. if (!$this->id_shop)
  151. $this->id_shop = Context::getContext()->shop->id;
  152. $return = parent::add($autodate);
  153. Hook::exec('actionCartSave');
  154. return $return;
  155. }
  156. public function update($null_values = false)
  157. {
  158. if (isset(self::$_nbProducts[$this->id]))
  159. unset(self::$_nbProducts[$this->id]);
  160. if (isset(self::$_totalWeight[$this->id]))
  161. unset(self::$_totalWeight[$this->id]);
  162. $this->_products = null;
  163. $return = parent::update();
  164. Hook::exec('actionCartSave');
  165. return $return;
  166. }
  167. /**
  168. * Update the address id of the cart
  169. *
  170. * @param int $id_address Current address id to change
  171. * @param int $id_address_new New address id
  172. */
  173. public function updateAddressId($id_address, $id_address_new)
  174. {
  175. $to_update = false;
  176. if (!isset($this->id_address_invoice) || $this->id_address_invoice == $id_address)
  177. {
  178. $to_update = true;
  179. $this->id_address_invoice = $id_address_new;
  180. }
  181. if (!isset($this->id_address_delivery) || $this->id_address_delivery == $id_address)
  182. {
  183. $to_update = true;
  184. $this->id_address_delivery = $id_address_new;
  185. }
  186. if ($to_update)
  187. $this->update();
  188. $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
  189. SET `id_address_delivery` = '.(int)$id_address_new.'
  190. WHERE `id_cart` = '.(int)$this->id.'
  191. AND `id_address_delivery` = '.(int)$id_address;
  192. Db::getInstance()->execute($sql);
  193. $sql = 'UPDATE `'._DB_PREFIX_.'customization`
  194. SET `id_address_delivery` = '.(int)$id_address_new.'
  195. WHERE `id_cart` = '.(int)$this->id.'
  196. AND `id_address_delivery` = '.(int)$id_address;
  197. Db::getInstance()->execute($sql);
  198. }
  199. public function delete()
  200. {
  201. if ($this->OrderExists()) //NOT delete a cart which is associated with an order
  202. return false;
  203. $uploaded_files = Db::getInstance()->executeS('
  204. SELECT cd.`value`
  205. FROM `'._DB_PREFIX_.'customized_data` cd
  206. INNER JOIN `'._DB_PREFIX_.'customization` c ON (cd.`id_customization`= c.`id_customization`)
  207. WHERE cd.`type`= 0 AND c.`id_cart`='.(int)$this->id
  208. );
  209. foreach ($uploaded_files as $must_unlink)
  210. {
  211. unlink(_PS_UPLOAD_DIR_.$must_unlink['value'].'_small');
  212. unlink(_PS_UPLOAD_DIR_.$must_unlink['value']);
  213. }
  214. Db::getInstance()->execute('
  215. DELETE FROM `'._DB_PREFIX_.'customized_data`
  216. WHERE `id_customization` IN (
  217. SELECT `id_customization`
  218. FROM `'._DB_PREFIX_.'customization`
  219. WHERE `id_cart`='.(int)$this->id.'
  220. )'
  221. );
  222. Db::getInstance()->execute('
  223. DELETE FROM `'._DB_PREFIX_.'customization`
  224. WHERE `id_cart` = '.(int)$this->id
  225. );
  226. if (!Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_cart_rule` WHERE `id_cart` = '.(int)$this->id)
  227. || !Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)$this->id))
  228. return false;
  229. return parent::delete();
  230. }
  231. public static function getTaxesAverageUsed($id_cart)
  232. {
  233. $cart = new Cart((int)$id_cart);
  234. if (!Validate::isLoadedObject($cart))
  235. die(Tools::displayError());
  236. if (!Configuration::get('PS_TAX'))
  237. return 0;
  238. $products = $cart->getProducts();
  239. $total_products_moy = 0;
  240. $ratio_tax = 0;
  241. if (!count($products))
  242. return 0;
  243. foreach ($products as $product) // products refer to the cart details
  244. {
  245. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice')
  246. $address_id = (int)$cart->id_address_invoice;
  247. else
  248. $address_id = (int)$product['id_address_delivery']; // Get delivery address of the product from the cart
  249. if (!Address::addressExists($address_id))
  250. $address_id = null;
  251. $total_products_moy += $product['total_wt'];
  252. $ratio_tax += $product['total_wt'] * Tax::getProductTaxRate(
  253. (int)$product['id_product'],
  254. (int)$address_id
  255. );
  256. }
  257. if ($total_products_moy > 0)
  258. return $ratio_tax / $total_products_moy;
  259. return 0;
  260. }
  261. /**
  262. * @deprecated 1.5.0, use Cart->getCartRules()
  263. */
  264. public function getDiscounts($lite = false, $refresh = false)
  265. {
  266. Tools::displayAsDeprecated();
  267. return $this->getCartRules();
  268. }
  269. public function getCartRules($filter = CartRule::FILTER_ACTION_ALL)
  270. {
  271. // If the cart has not been saved, then there can't be any cart rule applied
  272. if (!CartRule::isFeatureActive() || !$this->id)
  273. return array();
  274. $cache_key = 'Cart::getCartRules_'.$this->id.'-'.$filter;
  275. if (!Cache::isStored($cache_key))
  276. {
  277. $result = Db::getInstance()->executeS('
  278. SELECT *
  279. FROM `'._DB_PREFIX_.'cart_cart_rule` cd
  280. LEFT JOIN `'._DB_PREFIX_.'cart_rule` cr ON cd.`id_cart_rule` = cr.`id_cart_rule`
  281. LEFT JOIN `'._DB_PREFIX_.'cart_rule_lang` crl ON (
  282. cd.`id_cart_rule` = crl.`id_cart_rule`
  283. AND crl.id_lang = '.(int)$this->id_lang.'
  284. )
  285. WHERE `id_cart` = '.(int)$this->id.'
  286. '.($filter == CartRule::FILTER_ACTION_SHIPPING ? 'AND free_shipping = 1' : '').'
  287. '.($filter == CartRule::FILTER_ACTION_GIFT ? 'AND gift_product != 0' : '').'
  288. '.($filter == CartRule::FILTER_ACTION_REDUCTION ? 'AND (reduction_percent != 0 OR reduction_amount != 0)' : '')
  289. .' ORDER by cr.priority ASC'
  290. );
  291. Cache::store($cache_key, $result);
  292. }
  293. $result = Cache::retrieve($cache_key);
  294. // Define virtual context to prevent case where the cart is not the in the global context
  295. $virtual_context = Context::getContext()->cloneContext();
  296. $virtual_context->cart = $this;
  297. foreach ($result as &$row)
  298. {
  299. $row['obj'] = new CartRule($row['id_cart_rule'], (int)$this->id_lang);
  300. $row['value_real'] = $row['obj']->getContextualValue(true, $virtual_context, $filter);
  301. $row['value_tax_exc'] = $row['obj']->getContextualValue(false, $virtual_context, $filter);
  302. // Retro compatibility < 1.5.0.2
  303. $row['id_discount'] = $row['id_cart_rule'];
  304. $row['description'] = $row['name'];
  305. }
  306. return $result;
  307. }
  308. public function getDiscountsCustomer($id_cart_rule)
  309. {
  310. if (!CartRule::isFeatureActive())
  311. return 0;
  312. $cache_id = 'Cart::getDiscountsCustomer_'.(int)$this->id.'-'.(int)$id_cart_rule;
  313. if (!Cache::isStored($cache_id))
  314. {
  315. $result = (int)Db::getInstance()->getValue('
  316. SELECT COUNT(*)
  317. FROM `'._DB_PREFIX_.'cart_cart_rule`
  318. WHERE `id_cart_rule` = '.(int)$id_cart_rule.' AND `id_cart` = '.(int)$this->id);
  319. Cache::store($cache_id, $result);
  320. }
  321. return Cache::retrieve($cache_id);
  322. }
  323. public function getLastProduct()
  324. {
  325. $sql = '
  326. SELECT `id_product`, `id_product_attribute`, id_shop
  327. FROM `'._DB_PREFIX_.'cart_product`
  328. WHERE `id_cart` = '.(int)$this->id.'
  329. ORDER BY `date_add` DESC';
  330. $result = Db::getInstance()->getRow($sql);
  331. if ($result && isset($result['id_product']) && $result['id_product'])
  332. foreach ($this->getProducts() as $product)
  333. if ($result['id_product'] == $product['id_product']
  334. && (
  335. !$result['id_product_attribute']
  336. || $result['id_product_attribute'] == $product['id_product_attribute']
  337. ))
  338. return $product;
  339. return false;
  340. }
  341. /**
  342. * Return cart products
  343. *
  344. * @result array Products
  345. */
  346. public function getProducts($refresh = false, $id_product = false, $id_country = null)
  347. {
  348. if (!$this->id)
  349. return array();
  350. // Product cache must be strictly compared to NULL, or else an empty cart will add dozens of queries
  351. if ($this->_products !== null && !$refresh)
  352. {
  353. // Return product row with specified ID if it exists
  354. if (is_int($id_product))
  355. {
  356. foreach ($this->_products as $product)
  357. if ($product['id_product'] == $id_product)
  358. return array($product);
  359. return array();
  360. }
  361. return $this->_products;
  362. }
  363. // Build query
  364. $sql = new DbQuery();
  365. // Build SELECT
  366. $sql->select('cp.`id_product_attribute`, cp.`id_product`, cp.`quantity` AS cart_quantity, cp.id_shop, pl.`name`, p.`is_virtual`,
  367. pl.`description_short`, pl.`available_now`, pl.`available_later`, product_shop.`id_category_default`, p.`id_supplier`,
  368. p.`id_manufacturer`, product_shop.`on_sale`, product_shop.`ecotax`, product_shop.`additional_shipping_cost`,
  369. product_shop.`available_for_order`, product_shop.`price`, product_shop.`active`, product_shop.`unity`, product_shop.`unit_price_ratio`,
  370. stock.`quantity` AS quantity_available, p.`width`, p.`height`, p.`depth`, stock.`out_of_stock`, p.`weight`,
  371. p.`date_add`, p.`date_upd`, IFNULL(stock.quantity, 0) as quantity, pl.`link_rewrite`, cl.`link_rewrite` AS category,
  372. CONCAT(LPAD(cp.`id_product`, 10, 0), LPAD(IFNULL(cp.`id_product_attribute`, 0), 10, 0), IFNULL(cp.`id_address_delivery`, 0)) AS unique_id, cp.id_address_delivery,
  373. product_shop.`wholesale_price`, product_shop.advanced_stock_management, ps.product_supplier_reference supplier_reference');
  374. // Build FROM
  375. $sql->from('cart_product', 'cp');
  376. // Build JOIN
  377. $sql->leftJoin('product', 'p', 'p.`id_product` = cp.`id_product`');
  378. $sql->innerJoin('product_shop', 'product_shop', '(product_shop.`id_shop` = cp.`id_shop` AND product_shop.`id_product` = p.`id_product`)');
  379. $sql->leftJoin('product_lang', 'pl', '
  380. p.`id_product` = pl.`id_product`
  381. AND pl.`id_lang` = '.(int)$this->id_lang.Shop::addSqlRestrictionOnLang('pl', 'cp.id_shop')
  382. );
  383. $sql->leftJoin('category_lang', 'cl', '
  384. product_shop.`id_category_default` = cl.`id_category`
  385. AND cl.`id_lang` = '.(int)$this->id_lang.Shop::addSqlRestrictionOnLang('cl', 'cp.id_shop')
  386. );
  387. $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`');
  388. // @todo test if everything is ok, then refactorise call of this method
  389. $sql->join(Product::sqlStock('cp', 'cp'));
  390. // Build WHERE clauses
  391. $sql->where('cp.`id_cart` = '.(int)$this->id);
  392. if ($id_product)
  393. $sql->where('cp.`id_product` = '.(int)$id_product);
  394. $sql->where('p.`id_product` IS NOT NULL');
  395. // Build GROUP BY
  396. $sql->groupBy('unique_id');
  397. // Build ORDER BY
  398. $sql->orderBy('p.`id_product`, cp.`id_product_attribute`, cp.`date_add` ASC');
  399. if (Customization::isFeatureActive())
  400. {
  401. $sql->select('cu.`id_customization`, cu.`quantity` AS customization_quantity');
  402. $sql->leftJoin('customization', 'cu',
  403. 'p.`id_product` = cu.`id_product` AND cp.`id_product_attribute` = cu.`id_product_attribute` AND cu.`id_cart` = '.(int)$this->id);
  404. }
  405. else
  406. $sql->select('NULL AS customization_quantity, NULL AS id_customization');
  407. if (Combination::isFeatureActive())
  408. {
  409. $sql->select('
  410. product_attribute_shop.`price` AS price_attribute, product_attribute_shop.`ecotax` AS ecotax_attr,
  411. IF (IFNULL(pa.`reference`, \'\') = \'\', p.`reference`, pa.`reference`) AS reference,
  412. (p.`weight`+ pa.`weight`) weight_attribute,
  413. IF (IFNULL(pa.`ean13`, \'\') = \'\', p.`ean13`, pa.`ean13`) AS ean13,
  414. IF (IFNULL(pa.`upc`, \'\') = \'\', p.`upc`, pa.`upc`) AS upc,
  415. pai.`id_image` as pai_id_image, il.`legend` as pai_legend,
  416. IFNULL(product_attribute_shop.`minimal_quantity`, product_shop.`minimal_quantity`) as minimal_quantity
  417. ');
  418. $sql->leftJoin('product_attribute', 'pa', 'pa.`id_product_attribute` = cp.`id_product_attribute`');
  419. $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`)');
  420. $sql->leftJoin('product_attribute_image', 'pai', 'pai.`id_product_attribute` = pa.`id_product_attribute`');
  421. $sql->leftJoin('image_lang', 'il', 'il.`id_image` = pai.`id_image` AND il.`id_lang` = '.(int)$this->id_lang);
  422. }
  423. else
  424. $sql->select(
  425. 'p.`reference` AS reference, p.`ean13`,
  426. p.`upc` AS upc, product_shop.`minimal_quantity` AS minimal_quantity'
  427. );
  428. $result = Db::getInstance()->executeS($sql);
  429. // Reset the cache before the following return, or else an empty cart will add dozens of queries
  430. $products_ids = array();
  431. $pa_ids = array();
  432. if ($result)
  433. foreach ($result as $row)
  434. {
  435. $products_ids[] = $row['id_product'];
  436. $pa_ids[] = $row['id_product_attribute'];
  437. }
  438. // Thus you can avoid one query per product, because there will be only one query for all the products of the cart
  439. Product::cacheProductsFeatures($products_ids);
  440. Cart::cacheSomeAttributesLists($pa_ids, $this->id_lang);
  441. $this->_products = array();
  442. if (empty($result))
  443. return array();
  444. $cart_shop_context = Context::getContext()->cloneContext();
  445. foreach ($result as &$row)
  446. {
  447. if (isset($row['ecotax_attr']) && $row['ecotax_attr'] > 0)
  448. $row['ecotax'] = (float)$row['ecotax_attr'];
  449. $row['stock_quantity'] = (int)$row['quantity'];
  450. // for compatibility with 1.2 themes
  451. $row['quantity'] = (int)$row['cart_quantity'];
  452. if (isset($row['id_product_attribute']) && (int)$row['id_product_attribute'] && isset($row['weight_attribute']))
  453. $row['weight'] = (float)$row['weight_attribute'];
  454. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice')
  455. $address_id = (int)$this->id_address_invoice;
  456. else
  457. $address_id = (int)$row['id_address_delivery'];
  458. if (!Address::addressExists($address_id))
  459. $address_id = null;
  460. if ($cart_shop_context->shop->id != $row['id_shop'])
  461. $cart_shop_context->shop = new Shop((int)$row['id_shop']);
  462. if ($this->_taxCalculationMethod == PS_TAX_EXC)
  463. {
  464. $row['price'] = Product::getPriceStatic(
  465. (int)$row['id_product'],
  466. false,
  467. isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
  468. 2,
  469. null,
  470. false,
  471. true,
  472. (int)$row['cart_quantity'],
  473. false,
  474. ((int)$this->id_customer ? (int)$this->id_customer : null),
  475. (int)$this->id,
  476. ((int)$address_id ? (int)$address_id : null),
  477. $specific_price_output,
  478. true,
  479. true,
  480. $cart_shop_context
  481. ); // Here taxes are computed only once the quantity has been applied to the product price
  482. $row['price_wt'] = Product::getPriceStatic(
  483. (int)$row['id_product'],
  484. true,
  485. isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
  486. 2,
  487. null,
  488. false,
  489. true,
  490. (int)$row['cart_quantity'],
  491. false,
  492. ((int)$this->id_customer ? (int)$this->id_customer : null),
  493. (int)$this->id,
  494. ((int)$address_id ? (int)$address_id : null),
  495. $null,
  496. true,
  497. true,
  498. $cart_shop_context
  499. );
  500. $tax_rate = Tax::getProductTaxRate((int)$row['id_product'], (int)$address_id);
  501. $row['total_wt'] = Tools::ps_round($row['price'] * (float)$row['cart_quantity'] * (1 + (float)$tax_rate / 100), 2);
  502. $row['total'] = $row['price'] * (int)$row['cart_quantity'];
  503. }
  504. else
  505. {
  506. $row['price'] = Product::getPriceStatic(
  507. (int)$row['id_product'],
  508. false,
  509. (int)$row['id_product_attribute'],
  510. 2,
  511. null,
  512. false,
  513. true,
  514. $row['cart_quantity'],
  515. false,
  516. ((int)$this->id_customer ? (int)$this->id_customer : null),
  517. (int)$this->id,
  518. ((int)$address_id ? (int)$address_id : null),
  519. $specific_price_output,
  520. true,
  521. true,
  522. $cart_shop_context
  523. );
  524. $row['price_wt'] = Product::getPriceStatic(
  525. (int)$row['id_product'],
  526. true,
  527. (int)$row['id_product_attribute'],
  528. 2,
  529. null,
  530. false,
  531. true,
  532. $row['cart_quantity'],
  533. false,
  534. ((int)$this->id_customer ? (int)$this->id_customer : null),
  535. (int)$this->id,
  536. ((int)$address_id ? (int)$address_id : null),
  537. $null,
  538. true,
  539. true,
  540. $cart_shop_context
  541. );
  542. // In case when you use QuantityDiscount, getPriceStatic() can be return more of 2 decimals
  543. $row['price_wt'] = Tools::ps_round($row['price_wt'], 2);
  544. $row['total_wt'] = $row['price_wt'] * (int)$row['cart_quantity'];
  545. $row['total'] = Tools::ps_round($row['price'] * (int)$row['cart_quantity'], 2);
  546. $row['description_short'] = Tools::nl2br($row['description_short']);
  547. }
  548. if (!isset($row['pai_id_image']) || $row['pai_id_image'] == 0)
  549. {
  550. $cache_id = 'Cart::getProducts_'.'-pai_id_image-'.(int)$row['id_product'].'-'.(int)$this->id_lang.'-'.(int)$row['id_shop'];
  551. if (!Cache::isStored($cache_id))
  552. {
  553. $row2 = Db::getInstance()->getRow('
  554. SELECT image_shop.`id_image` id_image, il.`legend`
  555. FROM `'._DB_PREFIX_.'image` i
  556. JOIN `'._DB_PREFIX_.'image_shop` image_shop ON (i.id_image = image_shop.id_image AND image_shop.cover=1 AND image_shop.id_shop='.(int)$row['id_shop'].')
  557. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$this->id_lang.')
  558. WHERE i.`id_product` = '.(int)$row['id_product'].' AND image_shop.`cover` = 1'
  559. );
  560. Cache::store($cache_id, $row2);
  561. }
  562. $row2 = Cache::retrieve($cache_id);
  563. if (!$row2)
  564. $row2 = array('id_image' => false, 'legend' => false);
  565. else
  566. $row = array_merge($row, $row2);
  567. }
  568. else
  569. {
  570. $row['id_image'] = $row['pai_id_image'];
  571. $row['legend'] = $row['pai_legend'];
  572. }
  573. $row['reduction_applies'] = ($specific_price_output && (float)$specific_price_output['reduction']);
  574. $row['quantity_discount_applies'] = ($specific_price_output && $row['cart_quantity'] >= (int)$specific_price_output['from_quantity']);
  575. $row['id_image'] = Product::defineProductImage($row, $this->id_lang);
  576. $row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']);
  577. $row['features'] = Product::getFeaturesStatic((int)$row['id_product']);
  578. if (array_key_exists($row['id_product_attribute'].'-'.$this->id_lang, self::$_attributesLists))
  579. $row = array_merge($row, self::$_attributesLists[$row['id_product_attribute'].'-'.$this->id_lang]);
  580. $row = Product::getTaxesInformations($row, $cart_shop_context);
  581. $this->_products[] = $row;
  582. }
  583. return $this->_products;
  584. }
  585. public static function cacheSomeAttributesLists($ipa_list, $id_lang)
  586. {
  587. if (!Combination::isFeatureActive())
  588. return;
  589. $pa_implode = array();
  590. foreach ($ipa_list as $id_product_attribute)
  591. if ((int)$id_product_attribute && !array_key_exists($id_product_attribute.'-'.$id_lang, self::$_attributesLists))
  592. {
  593. $pa_implode[] = (int)$id_product_attribute;
  594. self::$_attributesLists[(int)$id_product_attribute.'-'.$id_lang] = array('attributes' => '', 'attributes_small' => '');
  595. }
  596. if (!count($pa_implode))
  597. return;
  598. $result = Db::getInstance()->executeS('
  599. SELECT pac.`id_product_attribute`, agl.`public_name` AS public_group_name, al.`name` AS attribute_name
  600. FROM `'._DB_PREFIX_.'product_attribute_combination` pac
  601. LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
  602. LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
  603. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (
  604. a.`id_attribute` = al.`id_attribute`
  605. AND al.`id_lang` = '.(int)$id_lang.'
  606. )
  607. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (
  608. ag.`id_attribute_group` = agl.`id_attribute_group`
  609. AND agl.`id_lang` = '.(int)$id_lang.'
  610. )
  611. WHERE pac.`id_product_attribute` IN ('.implode(',', $pa_implode).')
  612. ORDER BY agl.`public_name` ASC'
  613. );
  614. foreach ($result as $row)
  615. {
  616. self::$_attributesLists[$row['id_product_attribute'].'-'.$id_lang]['attributes'] .= $row['public_group_name'].' : '.$row['attribute_name'].', ';
  617. self::$_attributesLists[$row['id_product_attribute'].'-'.$id_lang]['attributes_small'] .= $row['attribute_name'].', ';
  618. }
  619. foreach ($pa_implode as $id_product_attribute)
  620. {
  621. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes'] = rtrim(
  622. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes'],
  623. ', '
  624. );
  625. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes_small'] = rtrim(
  626. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes_small'],
  627. ', '
  628. );
  629. }
  630. }
  631. /**
  632. * Return cart products quantity
  633. *
  634. * @result integer Products quantity
  635. */
  636. public function nbProducts()
  637. {
  638. if (!$this->id)
  639. return 0;
  640. return Cart::getNbProducts($this->id);
  641. }
  642. public static function getNbProducts($id)
  643. {
  644. // Must be strictly compared to NULL, or else an empty cart will bypass the cache and add dozens of queries
  645. if (isset(self::$_nbProducts[$id]) && self::$_nbProducts[$id] !== null)
  646. return self::$_nbProducts[$id];
  647. self::$_nbProducts[$id] = (int)Db::getInstance()->getValue('
  648. SELECT SUM(`quantity`)
  649. FROM `'._DB_PREFIX_.'cart_product`
  650. WHERE `id_cart` = '.(int)$id
  651. );
  652. return self::$_nbProducts[$id];
  653. }
  654. /**
  655. * @deprecated 1.5.0, use Cart->addCartRule()
  656. */
  657. public function addDiscount($id_cart_rule)
  658. {
  659. Tools::displayAsDeprecated();
  660. return $this->addCartRule($id_cart_rule);
  661. }
  662. public function addCartRule($id_cart_rule)
  663. {
  664. // You can't add a cart rule that does not exist
  665. $cartRule = new CartRule($id_cart_rule, Context::getContext()->language->id);
  666. if (!Validate::isLoadedObject($cartRule))
  667. return false;
  668. 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))
  669. return false;
  670. // Add the cart rule to the cart
  671. if (!Db::getInstance()->insert('cart_cart_rule', array(
  672. 'id_cart_rule' => (int)$id_cart_rule,
  673. 'id_cart' => (int)$this->id
  674. )))
  675. return false;
  676. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL);
  677. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING);
  678. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION);
  679. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT);
  680. if ((int)$cartRule->gift_product)
  681. $this->updateQty(1, $cartRule->gift_product, $cartRule->gift_product_attribute, false, 'up', 0, null, false);
  682. return true;
  683. }
  684. public function containsProduct($id_product, $id_product_attribute = 0, $id_customization = 0, $id_address_delivery = 0)
  685. {
  686. $sql = 'SELECT cp.`quantity` FROM `'._DB_PREFIX_.'cart_product` cp';
  687. if ($id_customization)
  688. $sql .= '
  689. LEFT JOIN `'._DB_PREFIX_.'customization` c ON (
  690. c.`id_product` = cp.`id_product`
  691. AND c.`id_product_attribute` = cp.`id_product_attribute`
  692. )';
  693. $sql .= '
  694. WHERE cp.`id_product` = '.(int)$id_product.'
  695. AND cp.`id_product_attribute` = '.(int)$id_product_attribute.'
  696. AND cp.`id_cart` = '.(int)$this->id;
  697. if (Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery())
  698. $sql .= ' AND cp.`id_address_delivery` = '.(int)$id_address_delivery;
  699. if ($id_customization)
  700. $sql .= ' AND c.`id_customization` = '.(int)$id_customization;
  701. return Db::getInstance()->getRow($sql);
  702. }
  703. /**
  704. * Update product quantity
  705. *
  706. * @param integer $quantity Quantity to add (or substract)
  707. * @param integer $id_product Product ID
  708. * @param integer $id_product_attribute Attribute ID if needed
  709. * @param string $operator Indicate if quantity must be increased or decreased
  710. */
  711. public function updateQty($quantity, $id_product, $id_product_attribute = null, $id_customization = false,
  712. $operator = 'up', $id_address_delivery = 0, Shop $shop = null, $auto_add_cart_rule = true)
  713. {
  714. if (!$shop)
  715. $shop = Context::getContext()->shop;
  716. if (Context::getContext()->customer->id)
  717. {
  718. if ($id_address_delivery == 0 && (int)$this->id_address_delivery) // The $id_address_delivery is null, use the cart delivery address
  719. $id_address_delivery = $this->id_address_delivery;
  720. elseif ($id_address_delivery == 0) // The $id_address_delivery is null, get the default customer address
  721. $id_address_delivery = (int)Address::getFirstCustomerAddressId((int)Context::getContext()->customer->id);
  722. elseif (!Customer::customerHasAddress(Context::getContext()->customer->id, $id_address_delivery)) // The $id_address_delivery must be linked with customer
  723. $id_address_delivery = 0;
  724. }
  725. $quantity = (int)$quantity;
  726. $id_product = (int)$id_product;
  727. $id_product_attribute = (int)$id_product_attribute;
  728. $product = new Product($id_product, false, Configuration::get('PS_LANG_DEFAULT'), $shop->id);
  729. if ($id_product_attribute)
  730. {
  731. $combination = new Combination((int)$id_product_attribute);
  732. if ($combination->id_product != $id_product)
  733. return false;
  734. }
  735. /* If we have a product combination, the minimal quantity is set with the one of this combination */
  736. if (!empty($id_product_attribute))
  737. $minimal_quantity = (int)Attribute::getAttributeMinimalQty($id_product_attribute);
  738. else
  739. $minimal_quantity = (int)$product->minimal_quantity;
  740. if (!Validate::isLoadedObject($product))
  741. die(Tools::displayError());
  742. if (isset(self::$_nbProducts[$this->id]))
  743. unset(self::$_nbProducts[$this->id]);
  744. if (isset(self::$_totalWeight[$this->id]))
  745. unset(self::$_totalWeight[$this->id]);
  746. if ((int)$quantity <= 0)
  747. return $this->deleteProduct($id_product, $id_product_attribute, (int)$id_customization);
  748. elseif (!$product->available_for_order || Configuration::get('PS_CATALOG_MODE'))
  749. return false;
  750. else
  751. {
  752. /* Check if the product is already in the cart */
  753. $result = $this->containsProduct($id_product, $id_product_attribute, (int)$id_customization, (int)$id_address_delivery);
  754. /* Update quantity if product already exist */
  755. if ($result)
  756. {
  757. if ($operator == 'up')
  758. {
  759. $sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
  760. FROM '._DB_PREFIX_.'product p
  761. '.Product::sqlStock('p', $id_product_attribute, true, $shop).'
  762. WHERE p.id_product = '.$id_product;
  763. $result2 = Db::getInstance()->getRow($sql);
  764. $product_qty = (int)$result2['quantity'];
  765. // Quantity for product pack
  766. if (Pack::isPack($id_product))
  767. $product_qty = Pack::getQuantity($id_product, $id_product_attribute);
  768. $new_qty = (int)$result['quantity'] + (int)$quantity;
  769. $qty = '+ '.(int)$quantity;
  770. if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock']))
  771. if ($new_qty > $product_qty)
  772. return false;
  773. }
  774. else if ($operator == 'down')
  775. {
  776. $qty = '- '.(int)$quantity;
  777. $new_qty = (int)$result['quantity'] - (int)$quantity;
  778. if ($new_qty < $minimal_quantity && $minimal_quantity > 1)
  779. return -1;
  780. }
  781. else
  782. return false;
  783. /* Delete product from cart */
  784. if ($new_qty <= 0)
  785. return $this->deleteProduct((int)$id_product, (int)$id_product_attribute, (int)$id_customization);
  786. else if ($new_qty < $minimal_quantity)
  787. return -1;
  788. else
  789. Db::getInstance()->execute('
  790. UPDATE `'._DB_PREFIX_.'cart_product`
  791. SET `quantity` = `quantity` '.$qty.', `date_add` = NOW()
  792. WHERE `id_product` = '.(int)$id_product.
  793. (!empty($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
  794. AND `id_cart` = '.(int)$this->id.(Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery() ? ' AND `id_address_delivery` = '.(int)$id_address_delivery : '').'
  795. LIMIT 1'
  796. );
  797. }
  798. /* Add product to the cart */
  799. elseif ($operator == 'up')
  800. {
  801. $sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
  802. FROM '._DB_PREFIX_.'product p
  803. '.Product::sqlStock('p', $id_product_attribute, true, $shop).'
  804. WHERE p.id_product = '.$id_product;
  805. $result2 = Db::getInstance()->getRow($sql);
  806. // Quantity for product pack
  807. if (Pack::isPack($id_product))
  808. $result2['quantity'] = Pack::getQuantity($id_product, $id_product_attribute);
  809. if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock']))
  810. if ((int)$quantity > $result2['quantity'])
  811. return false;
  812. if ((int)$quantity < $minimal_quantity)
  813. return -1;
  814. $result_add = Db::getInstance()->insert('cart_product', array(
  815. 'id_product' => (int)$id_product,
  816. 'id_product_attribute' => (int)$id_product_attribute,
  817. 'id_cart' => (int)$this->id,
  818. 'id_address_delivery' => (int)$id_address_delivery,
  819. 'id_shop' => $shop->id,
  820. 'quantity' => (int)$quantity,
  821. 'date_add' => date('Y-m-d H:i:s')
  822. ));
  823. if (!$result_add)
  824. return false;
  825. }
  826. }
  827. // refresh cache of self::_products
  828. $this->_products = $this->getProducts(true);
  829. $this->update(true);
  830. $context = Context::getContext()->cloneContext();
  831. $context->cart = $this;
  832. Cache::clean('getContextualValue_*');
  833. if ($auto_add_cart_rule)
  834. CartRule::autoAddToCart($context);
  835. if ($product->customizable)
  836. return $this->_updateCustomizationQuantity((int)$quantity, (int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery, $operator);
  837. else
  838. return true;
  839. }
  840. /*
  841. ** Customization management
  842. */
  843. protected function _updateCustomizationQuantity($quantity, $id_customization, $id_product, $id_product_attribute, $id_address_delivery, $operator = 'up')
  844. {
  845. // Link customization to product combination when it is first added to cart
  846. if (empty($id_customization))
  847. {
  848. $customization = $this->getProductCustomization($id_product, null, true);
  849. foreach ($customization as $field)
  850. {
  851. if ($field['quantity'] == 0)
  852. {
  853. Db::getInstance()->execute('
  854. UPDATE `'._DB_PREFIX_.'customization`
  855. SET `quantity` = '.(int)$quantity.',
  856. `id_product_attribute` = '.(int)$id_product_attribute.',
  857. `id_address_delivery` = '.(int)$id_address_delivery.',
  858. `in_cart` = 1
  859. WHERE `id_customization` = '.(int)$field['id_customization']);
  860. }
  861. }
  862. }
  863. /* Deletion */
  864. if (!empty($id_customization) && (int)$quantity < 1)
  865. return $this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute);
  866. /* Quantity update */
  867. if (!empty($id_customization))
  868. {
  869. $result = Db::getInstance()->getRow('SELECT `quantity` FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization);
  870. if ($result && Db::getInstance()->NumRows())
  871. {
  872. if ($operator == 'down' && (int)$result['quantity'] - (int)$quantity < 1)
  873. return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization);
  874. return Db::getInstance()->execute('
  875. UPDATE `'._DB_PREFIX_.'customization`
  876. SET
  877. `quantity` = `quantity` '.($operator == 'up' ? '+ ' : '- ').(int)$quantity.',
  878. `id_address_delivery` = '.(int)$id_address_delivery.'
  879. WHERE `id_customization` = '.(int)$id_customization);
  880. }
  881. else
  882. Db::getInstance()->execute('
  883. UPDATE `'._DB_PREFIX_.'customization`
  884. SET `id_address_delivery` = '.(int)$id_address_delivery.'
  885. WHERE `id_customization` = '.(int)$id_customization);
  886. }
  887. // refresh cache of self::_products
  888. $this->_products = $this->getProducts(true);
  889. $this->update(true);
  890. return true;
  891. }
  892. /**
  893. * Add customization item to database
  894. *
  895. * @param int $id_product
  896. * @param int $id_product_attribute
  897. * @param int $index
  898. * @param int $type
  899. * @param string $field
  900. * @param int $quantity
  901. * @return boolean success
  902. */
  903. public function _addCustomization($id_product, $id_product_attribute, $index, $type, $field, $quantity)
  904. {
  905. $exising_customization = Db::getInstance()->executeS('
  906. SELECT cu.`id_customization`, cd.`index`, cd.`value`, cd.`type` FROM `'._DB_PREFIX_.'customization` cu
  907. LEFT JOIN `'._DB_PREFIX_.'customized_data` cd
  908. ON cu.`id_customization` = cd.`id_customization`
  909. WHERE cu.id_cart = '.(int)$this->id.'
  910. AND cu.id_product = '.(int)$id_product.'
  911. AND in_cart = 0'
  912. );
  913. if ($exising_customization)
  914. {
  915. // If the customization field is alreay filled, delete it
  916. foreach ($exising_customization as $customization)
  917. {
  918. if ($customization['type'] == $type && $customization['index'] == $index)
  919. {
  920. Db::getInstance()->execute('
  921. DELETE FROM `'._DB_PREFIX_.'customized_data`
  922. WHERE id_customization = '.(int)$customization['id_customization'].'
  923. AND type = '.(int)$customization['type'].'
  924. AND `index` = '.(int)$customization['index']);
  925. if ($type == Product::CUSTOMIZE_FILE)
  926. {
  927. @unlink(_PS_UPLOAD_DIR_.$customization['value']);
  928. @unlink(_PS_UPLOAD_DIR_.$customization['value'].'_small');
  929. }
  930. break;
  931. }
  932. }
  933. $id_customization = $exising_customization[0]['id_customization'];
  934. }
  935. else
  936. {
  937. Db::getInstance()->execute(
  938. 'INSERT INTO `'._DB_PREFIX_.'customization` (`id_cart`, `id_product`, `id_product_attribute`, `quantity`)
  939. VALUES ('.(int)$this->id.', '.(int)$id_product.', '.(int)$id_product_attribute.', '.(int)$quantity.')'
  940. );
  941. $id_customization = Db::getInstance()->Insert_ID();
  942. }
  943. $query = 'INSERT INTO `'._DB_PREFIX_.'customized_data` (`id_customization`, `type`, `index`, `value`)
  944. VALUES ('.(int)$id_customization.', '.(int)$type.', '.(int)$index.', \''.pSQL($field).'\')';
  945. if (!Db::getInstance()->execute($query))
  946. return false;
  947. return true;
  948. }
  949. /**
  950. * Check if order has already been placed
  951. *
  952. * @return boolean result
  953. */
  954. public function orderExists()
  955. {
  956. $cache_id = 'Cart::orderExists_'.(int)$this->id;
  957. if (!Cache::isStored($cache_id))
  958. {
  959. $result = (bool)Db::getInstance()->getValue('SELECT count(*) FROM `'._DB_PREFIX_.'orders` WHERE `id_cart` = '.(int)$this->id);
  960. Cache::store($cache_id, $result);
  961. }
  962. return Cache::retrieve($cache_id);
  963. }
  964. /**
  965. * @deprecated 1.5.0, use Cart->removeCartRule()
  966. */
  967. public function deleteDiscount($id_cart_rule)
  968. {
  969. Tools::displayAsDeprecated();
  970. return $this->removeCartRule($id_cart_rule);
  971. }
  972. public function removeCartRule($id_cart_rule)
  973. {
  974. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL);
  975. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING);
  976. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION);
  977. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT);
  978. $result = Db::getInstance()->execute('
  979. DELETE FROM `'._DB_PREFIX_.'cart_cart_rule`
  980. WHERE `id_cart_rule` = '.(int)$id_cart_rule.'
  981. AND `id_cart` = '.(int)$this->id.'
  982. LIMIT 1');
  983. $cart_rule = new CartRule($id_cart_rule, Configuration::get('PS_LANG_DEFAULT'));
  984. if ((int)$cart_rule->gift_product)
  985. $this->updateQty(1, $cart_rule->gift_product, $cart_rule->gift_product_attribute, null, 'down', 0, null, false);
  986. return $result;
  987. }
  988. /**
  989. * Delete a product from the cart
  990. *
  991. * @param integer $id_product Product ID
  992. * @param integer $id_product_attribute Attribute ID if needed
  993. * @param integer $id_customization Customization id
  994. * @return boolean result
  995. */
  996. public function deleteProduct($id_product, $id_product_attribute = null, $id_customization = null, $id_address_delivery = 0)
  997. {
  998. if (isset(self::$_nbProducts[$this->id]))
  999. unset(self::$_nbProducts[$this->id]);
  1000. if (isset(self::$_totalWeight[$this->id]))
  1001. unset(self::$_totalWeight[$this->id]);
  1002. if ((int)$id_customization)
  1003. {
  1004. $product_total_quantity = (int)Db::getInstance()->getValue(
  1005. 'SELECT `quantity`
  1006. FROM `'._DB_PREFIX_.'cart_product`
  1007. WHERE `id_product` = '.(int)$id_product.'
  1008. AND `id_cart` = '.(int)$this->id.'
  1009. AND `id_product_attribute` = '.(int)$id_product_attribute);
  1010. $customization_quantity = (int)Db::getInstance()->getValue('
  1011. SELECT `quantity`
  1012. FROM `'._DB_PREFIX_.'customization`
  1013. WHERE `id_cart` = '.(int)$this->id.'
  1014. AND `id_product` = '.(int)$id_product.'
  1015. AND `id_product_attribute` = '.(int)$id_product_attribute.'
  1016. '.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));
  1017. if (!$this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery))
  1018. return false;
  1019. // refresh cache of self::_products
  1020. $this->_products = $this->getProducts(true);
  1021. return ($customization_quantity == $product_total_quantity && $this->deleteProduct((int)$id_product, (int)$id_product_attribute, null, (int)$id_address_delivery));
  1022. }
  1023. /* Get customization quantity */
  1024. $result = Db::getInstance()->getRow('
  1025. SELECT SUM(`quantity`) AS \'quantity\'
  1026. FROM `'._DB_PREFIX_.'customization`
  1027. WHERE `id_cart` = '.(int)$this->id.'
  1028. AND `id_product` = '.(int)$id_product.'
  1029. AND `id_product_attribute` = '.(int)$id_product_attribute);
  1030. if ($result === false)
  1031. return false;
  1032. /* If the product still possesses customization it does not have to be deleted */
  1033. if (Db::getInstance()->NumRows() && (int)$result['quantity'])
  1034. return Db::getInstance()->execute('
  1035. UPDATE `'._DB_PREFIX_.'cart_product`
  1036. SET `quantity` = '.(int)$result['quantity'].'
  1037. WHERE `id_cart` = '.(int)$this->id.'
  1038. AND `id_product` = '.(int)$id_product.
  1039. ($id_product_attribute != null ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '')
  1040. );
  1041. /* Product deletion */
  1042. $result = Db::getInstance()->execute('
  1043. DELETE FROM `'._DB_PREFIX_.'cart_product`
  1044. WHERE `id_product` = '.(int)$id_product.'
  1045. '.(!is_null($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
  1046. AND `id_cart` = '.(int)$this->id.'
  1047. '.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));
  1048. if ($result)
  1049. {
  1050. $return = $this->update(true);
  1051. // refresh cache of self::_products
  1052. $this->_products = $this->getProducts(true);
  1053. CartRule::autoRemoveFromCart();
  1054. CartRule::autoAddToCart();
  1055. return $return;
  1056. }
  1057. return false;
  1058. }
  1059. /**
  1060. * Delete a customization from the cart. If customization is a Picture,
  1061. * then the image is also deleted
  1062. *
  1063. * @param integer $id_customization
  1064. * @return boolean result
  1065. */
  1066. protected function _deleteCustomization($id_customization, $id_product, $id_product_attribute, $id_address_delivery = 0)
  1067. {
  1068. $result = true;
  1069. $customization = Db::getInstance()->getRow('SELECT *
  1070. FROM `'._DB_PREFIX_.'customization`
  1071. WHERE `id_customization` = '.(int)$id_customization);
  1072. if ($customization)
  1073. {
  1074. $cust_data = Db::getInstance()->getRow('SELECT *
  1075. FROM `'._DB_PREFIX_.'customized_data`
  1076. WHERE `id_customization` = '.(int)$id_customization);
  1077. // Delete customization picture if necessary
  1078. if (isset($cust_data['type']) && $cust_data['type'] == 0)
  1079. $result &= (@unlink(_PS_UPLOAD_DIR_.$cust_data['value']) && @unlink(_PS_UPLOAD_DIR_.$cust_data['value'].'_small'));
  1080. $result &= Db::getInstance()->execute(
  1081. 'DELETE FROM `'._DB_PREFIX_.'customized_data`
  1082. WHERE `id_customization` = '.(int)$id_customization
  1083. );
  1084. if ($result)
  1085. $result &= Db::getInstance()->execute(
  1086. 'UPDATE `'._DB_PREFIX_.'cart_product`
  1087. SET `quantity` = `quantity` - '.(int)$customization['quantity'].'
  1088. WHERE `id_cart` = '.(int)$this->id.'
  1089. AND `id_product` = '.(int)$id_product.
  1090. ((int)$id_product_attribute ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
  1091. AND `id_address_delivery` = '.(int)$id_address_delivery
  1092. );
  1093. if (!$result)
  1094. return false;
  1095. return Db::getInstance()->execute(
  1096. 'DELETE FROM `'._DB_PREFIX_.'customization`
  1097. WHERE `id_customization` = '.(int)$id_customization
  1098. );
  1099. }
  1100. return true;
  1101. }
  1102. public static function getTotalCart($id_cart, $use_tax_display = false, $type = Cart::BOTH)
  1103. {
  1104. $cart = new Cart($id_cart);
  1105. if (!Validate::isLoadedObject($cart))
  1106. die(Tools::displayError());
  1107. $with_taxes = $use_tax_display ? $cart->_taxCalculationMethod != PS_TAX_EXC : true;
  1108. return Tools::displayPrice($cart->getOrderTotal($with_taxes, $type), Currency::getCurrencyInstance((int)$cart->id_currency), false);
  1109. }
  1110. public static function getOrderTotalUsingTaxCalculationMethod($id_cart)
  1111. {
  1112. return Cart::getTotalCart($id_cart, true);
  1113. }
  1114. /**
  1115. * This function returns the total cart amount
  1116. *
  1117. * Possible values for $type:
  1118. * Cart::ONLY_PRODUCTS
  1119. * Cart::ONLY_DISCOUNTS
  1120. * Cart::BOTH
  1121. * Cart::BOTH_WITHOUT_SHIPPING
  1122. * Cart::ONLY_SHIPPING
  1123. * Cart::ONLY_WRAPPING
  1124. * Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING
  1125. * Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING
  1126. *
  1127. * @param boolean $withTaxes With or without taxes
  1128. * @param integer $type Total type
  1129. * @param boolean $use_cache Allow using cache of the method CartRule::getContextualValue
  1130. * @return float Order total
  1131. */
  1132. public function getOrderTotal($with_taxes = true, $type = Cart::BOTH, $products = null, $id_carrier = null, $use_cache = true)
  1133. {
  1134. if (!$this->id)
  1135. return 0;
  1136. $type = (int)$type;
  1137. $array_type = array(
  1138. Cart::ONLY_PRODUCTS,
  1139. Cart::ONLY_DISCOUNTS,
  1140. Cart::BOTH,
  1141. Cart::BOTH_WITHOUT_SHIPPING,
  1142. Cart::ONLY_SHIPPING,
  1143. Cart::ONLY_WRAPPING,
  1144. Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING,
  1145. Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING,
  1146. );
  1147. // Define virtual context to prevent case where the cart is not the in the global context
  1148. $virtual_context = Context::getContext()->cloneContext();
  1149. $virtual_context->cart = $this;
  1150. if (!in_array($type, $array_type))
  1151. die(Tools::displayError());
  1152. $with_shipping = in_array($type, array(Cart::BOTH, Cart::ONLY_SHIPPING));
  1153. // if cart rules are not used
  1154. if ($type == Cart::ONLY_DISCOUNTS && !CartRule::isFeatureActive())
  1155. return 0;
  1156. // no shipping cost if is a cart with only virtuals products
  1157. $virtual = $this->isVirtualCart();
  1158. if ($virtual && $type == Cart::ONLY_SHIPPING)
  1159. return 0;
  1160. if ($virtual && $type == Cart::BOTH)
  1161. $type = Cart::BOTH_WITHOUT_SHIPPING;
  1162. if ($with_shipping || $type == Cart::ONLY_DISCOUNTS)
  1163. {
  1164. if (is_null($products) && is_null($id_carrier))
  1165. $shipping_fees = $this->getTotalShippingCost(null, (boolean)$with_taxes);
  1166. else
  1167. $shipping_fees = $this->getPackageShippingCost($id_carrier, (bool)$with_taxes, null, $products);
  1168. }
  1169. else
  1170. $shipping_fees = 0;
  1171. if ($type == Cart::ONLY_SHIPPING)
  1172. return $shipping_fees;
  1173. if ($type == Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING)
  1174. $type = Cart::ONLY_PRODUCTS;
  1175. $param_product = true;
  1176. if (is_null($products))
  1177. {
  1178. $param_product = false;
  1179. $products = $this->getProducts();
  1180. }
  1181. if ($type == Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING)
  1182. {
  1183. foreach ($products as $key => $product)
  1184. if ($product['is_virtual'])
  1185. unset($products[$key]);
  1186. $type = Cart::ONLY_PRODUCTS;
  1187. }
  1188. $order_total = 0;
  1189. if (Tax::excludeTaxeOption())
  1190. $with_taxes = false;
  1191. foreach ($products as $product) // products refer to the cart details
  1192. {
  1193. if ($virtual_context->shop->id != $product['id_shop'])
  1194. $virtual_context->shop = new Shop((int)$product['id_shop']);
  1195. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice')
  1196. $address_id = (int)$this->id_address_invoice;
  1197. else
  1198. $address_id = (int)$product['id_address_delivery']; // Get delivery address of the product from the cart
  1199. if (!Address::addressExists($address_id))
  1200. $address_id = null;
  1201. if ($this->_taxCalculationMethod == PS_TAX_EXC)
  1202. {
  1203. // Here taxes are computed only once the quantity has been applied to the product price
  1204. $price = Product::getPriceStatic(
  1205. (int)$product['id_product'],
  1206. false,
  1207. (int)$product['id_product_attribute'],
  1208. 2,
  1209. null,
  1210. false,
  1211. true,
  1212. $product['cart_quantity'],
  1213. false,
  1214. (int)$this->id_customer ? (int)$this->id_customer : null,
  1215. (int)$this->id,
  1216. $address_id,
  1217. $null,
  1218. true,
  1219. true,
  1220. $virtual_context
  1221. );
  1222. $total_ecotax = $product['ecotax'] * (int)$product['cart_quantity'];
  1223. $total_price = $price * (int)$product['cart_quantity'];
  1224. if ($with_taxes)
  1225. {
  1226. $product_tax_rate = (float)Tax::getProductTaxRate((int)$product['id_product'], (int)$address_id, $virtual_context);
  1227. $product_eco_tax_rate = Tax::getProductEcotaxRate((int)$address_id);
  1228. $total_price = ($total_price - $total_ecotax) * (1 + $product_tax_rate / 100);
  1229. $total_ecotax = $total_ecotax * (1 + $product_eco_tax_rate / 100);
  1230. $total_price = Tools::ps_round($total_price + $total_ecotax, 2);
  1231. }
  1232. }
  1233. else
  1234. {
  1235. if ($with

Large files files are truncated, but you can click here to view the full file