PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/Cart.php

https://github.com/cokarmando/PrestaShop
PHP | 3674 lines | 2701 code | 492 blank | 481 comment | 518 complexity | ccb827a3975b3fcc40b4b742e18d3f0d MD5 | raw file

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

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

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