PageRenderTime 85ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/Cart.php

https://github.com/netplayer/PrestaShop
PHP | 3736 lines | 2759 code | 497 blank | 480 comment | 543 complexity | 79c02b324d41b14f46787f59fae1d1b1 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0
  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, IFNULL(sp.`reduction_type`, 0) AS reduction_type');
  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. $sql->leftJoin('specific_price', 'sp', 'sp.`id_product` = cp.`id_product`'); // AND 'sp.`id_shop` = cp.`id_shop`
  389. // @todo test if everything is ok, then refactorise call of this method
  390. $sql->join(Product::sqlStock('cp', 'cp'));
  391. // Build WHERE clauses
  392. $sql->where('cp.`id_cart` = '.(int)$this->id);
  393. if ($id_product)
  394. $sql->where('cp.`id_product` = '.(int)$id_product);
  395. $sql->where('p.`id_product` IS NOT NULL');
  396. // Build GROUP BY
  397. $sql->groupBy('unique_id');
  398. // Build ORDER BY
  399. $sql->orderBy('cp.`date_add`, p.`id_product`, cp.`id_product_attribute` ASC');
  400. if (Customization::isFeatureActive())
  401. {
  402. $sql->select('cu.`id_customization`, cu.`quantity` AS customization_quantity');
  403. $sql->leftJoin('customization', 'cu',
  404. 'p.`id_product` = cu.`id_product` AND cp.`id_product_attribute` = cu.`id_product_attribute` AND cu.`id_cart` = '.(int)$this->id);
  405. }
  406. else
  407. $sql->select('NULL AS customization_quantity, NULL AS id_customization');
  408. if (Combination::isFeatureActive())
  409. {
  410. $sql->select('
  411. product_attribute_shop.`price` AS price_attribute, product_attribute_shop.`ecotax` AS ecotax_attr,
  412. IF (IFNULL(pa.`reference`, \'\') = \'\', p.`reference`, pa.`reference`) AS reference,
  413. (p.`weight`+ pa.`weight`) weight_attribute,
  414. IF (IFNULL(pa.`ean13`, \'\') = \'\', p.`ean13`, pa.`ean13`) AS ean13,
  415. IF (IFNULL(pa.`upc`, \'\') = \'\', p.`upc`, pa.`upc`) AS upc,
  416. pai.`id_image` as pai_id_image, il.`legend` as pai_legend,
  417. IFNULL(product_attribute_shop.`minimal_quantity`, product_shop.`minimal_quantity`) as minimal_quantity
  418. ');
  419. $sql->leftJoin('product_attribute', 'pa', 'pa.`id_product_attribute` = cp.`id_product_attribute`');
  420. $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`)');
  421. $sql->leftJoin('product_attribute_image', 'pai', 'pai.`id_product_attribute` = pa.`id_product_attribute`');
  422. $sql->leftJoin('image_lang', 'il', 'il.`id_image` = pai.`id_image` AND il.`id_lang` = '.(int)$this->id_lang);
  423. }
  424. else
  425. $sql->select(
  426. 'p.`reference` AS reference, p.`ean13`,
  427. p.`upc` AS upc, product_shop.`minimal_quantity` AS minimal_quantity'
  428. );
  429. $result = Db::getInstance()->executeS($sql);
  430. // Reset the cache before the following return, or else an empty cart will add dozens of queries
  431. $products_ids = array();
  432. $pa_ids = array();
  433. if ($result)
  434. foreach ($result as $row)
  435. {
  436. $products_ids[] = $row['id_product'];
  437. $pa_ids[] = $row['id_product_attribute'];
  438. }
  439. // Thus you can avoid one query per product, because there will be only one query for all the products of the cart
  440. Product::cacheProductsFeatures($products_ids);
  441. Cart::cacheSomeAttributesLists($pa_ids, $this->id_lang);
  442. $this->_products = array();
  443. if (empty($result))
  444. return array();
  445. $cart_shop_context = Context::getContext()->cloneContext();
  446. foreach ($result as &$row)
  447. {
  448. if (isset($row['ecotax_attr']) && $row['ecotax_attr'] > 0)
  449. $row['ecotax'] = (float)$row['ecotax_attr'];
  450. $row['stock_quantity'] = (int)$row['quantity'];
  451. // for compatibility with 1.2 themes
  452. $row['quantity'] = (int)$row['cart_quantity'];
  453. if (isset($row['id_product_attribute']) && (int)$row['id_product_attribute'] && isset($row['weight_attribute']))
  454. $row['weight'] = (float)$row['weight_attribute'];
  455. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice')
  456. $address_id = (int)$this->id_address_invoice;
  457. else
  458. $address_id = (int)$row['id_address_delivery'];
  459. if (!Address::addressExists($address_id))
  460. $address_id = null;
  461. if ($cart_shop_context->shop->id != $row['id_shop'])
  462. $cart_shop_context->shop = new Shop((int)$row['id_shop']);
  463. if ($this->_taxCalculationMethod == PS_TAX_EXC)
  464. {
  465. $row['price'] = Product::getPriceStatic(
  466. (int)$row['id_product'],
  467. false,
  468. isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
  469. 2,
  470. null,
  471. false,
  472. true,
  473. (int)$row['cart_quantity'],
  474. false,
  475. ((int)$this->id_customer ? (int)$this->id_customer : null),
  476. (int)$this->id,
  477. ((int)$address_id ? (int)$address_id : null),
  478. $specific_price_output,
  479. true,
  480. true,
  481. $cart_shop_context
  482. ); // Here taxes are computed only once the quantity has been applied to the product price
  483. $row['price_wt'] = Product::getPriceStatic(
  484. (int)$row['id_product'],
  485. true,
  486. isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
  487. 2,
  488. null,
  489. false,
  490. true,
  491. (int)$row['cart_quantity'],
  492. false,
  493. ((int)$this->id_customer ? (int)$this->id_customer : null),
  494. (int)$this->id,
  495. ((int)$address_id ? (int)$address_id : null),
  496. $null,
  497. true,
  498. true,
  499. $cart_shop_context
  500. );
  501. $tax_rate = Tax::getProductTaxRate((int)$row['id_product'], (int)$address_id);
  502. $row['total_wt'] = Tools::ps_round($row['price'] * (float)$row['cart_quantity'] * (1 + (float)$tax_rate / 100), 2);
  503. $row['total'] = $row['price'] * (int)$row['cart_quantity'];
  504. }
  505. else
  506. {
  507. $row['price'] = Product::getPriceStatic(
  508. (int)$row['id_product'],
  509. false,
  510. (int)$row['id_product_attribute'],
  511. 2,
  512. null,
  513. false,
  514. true,
  515. $row['cart_quantity'],
  516. false,
  517. ((int)$this->id_customer ? (int)$this->id_customer : null),
  518. (int)$this->id,
  519. ((int)$address_id ? (int)$address_id : null),
  520. $specific_price_output,
  521. true,
  522. true,
  523. $cart_shop_context
  524. );
  525. $row['price_wt'] = Product::getPriceStatic(
  526. (int)$row['id_product'],
  527. true,
  528. (int)$row['id_product_attribute'],
  529. 2,
  530. null,
  531. false,
  532. true,
  533. $row['cart_quantity'],
  534. false,
  535. ((int)$this->id_customer ? (int)$this->id_customer : null),
  536. (int)$this->id,
  537. ((int)$address_id ? (int)$address_id : null),
  538. $null,
  539. true,
  540. true,
  541. $cart_shop_context
  542. );
  543. // In case when you use QuantityDiscount, getPriceStatic() can be return more of 2 decimals
  544. $row['price_wt'] = Tools::ps_round($row['price_wt'], 2);
  545. $row['total_wt'] = $row['price_wt'] * (int)$row['cart_quantity'];
  546. $row['total'] = Tools::ps_round($row['price'] * (int)$row['cart_quantity'], 2);
  547. $row['description_short'] = Tools::nl2br($row['description_short']);
  548. }
  549. if (!isset($row['pai_id_image']) || $row['pai_id_image'] == 0)
  550. {
  551. $cache_id = 'Cart::getProducts_'.'-pai_id_image-'.(int)$row['id_product'].'-'.(int)$this->id_lang.'-'.(int)$row['id_shop'];
  552. if (!Cache::isStored($cache_id))
  553. {
  554. $row2 = Db::getInstance()->getRow('
  555. SELECT image_shop.`id_image` id_image, il.`legend`
  556. FROM `'._DB_PREFIX_.'image` i
  557. 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'].')
  558. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$this->id_lang.')
  559. WHERE i.`id_product` = '.(int)$row['id_product'].' AND image_shop.`cover` = 1'
  560. );
  561. Cache::store($cache_id, $row2);
  562. }
  563. $row2 = Cache::retrieve($cache_id);
  564. if (!$row2)
  565. $row2 = array('id_image' => false, 'legend' => false);
  566. else
  567. $row = array_merge($row, $row2);
  568. }
  569. else
  570. {
  571. $row['id_image'] = $row['pai_id_image'];
  572. $row['legend'] = $row['pai_legend'];
  573. }
  574. $row['reduction_applies'] = ($specific_price_output && (float)$specific_price_output['reduction']);
  575. $row['quantity_discount_applies'] = ($specific_price_output && $row['cart_quantity'] >= (int)$specific_price_output['from_quantity']);
  576. $row['id_image'] = Product::defineProductImage($row, $this->id_lang);
  577. $row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']);
  578. $row['features'] = Product::getFeaturesStatic((int)$row['id_product']);
  579. if (array_key_exists($row['id_product_attribute'].'-'.$this->id_lang, self::$_attributesLists))
  580. $row = array_merge($row, self::$_attributesLists[$row['id_product_attribute'].'-'.$this->id_lang]);
  581. $row = Product::getTaxesInformations($row, $cart_shop_context);
  582. $this->_products[] = $row;
  583. }
  584. return $this->_products;
  585. }
  586. public static function cacheSomeAttributesLists($ipa_list, $id_lang)
  587. {
  588. if (!Combination::isFeatureActive())
  589. return;
  590. $pa_implode = array();
  591. foreach ($ipa_list as $id_product_attribute)
  592. if ((int)$id_product_attribute && !array_key_exists($id_product_attribute.'-'.$id_lang, self::$_attributesLists))
  593. {
  594. $pa_implode[] = (int)$id_product_attribute;
  595. self::$_attributesLists[(int)$id_product_attribute.'-'.$id_lang] = array('attributes' => '', 'attributes_small' => '');
  596. }
  597. if (!count($pa_implode))
  598. return;
  599. $result = Db::getInstance()->executeS('
  600. SELECT pac.`id_product_attribute`, agl.`public_name` AS public_group_name, al.`name` AS attribute_name
  601. FROM `'._DB_PREFIX_.'product_attribute_combination` pac
  602. LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
  603. LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
  604. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (
  605. a.`id_attribute` = al.`id_attribute`
  606. AND al.`id_lang` = '.(int)$id_lang.'
  607. )
  608. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (
  609. ag.`id_attribute_group` = agl.`id_attribute_group`
  610. AND agl.`id_lang` = '.(int)$id_lang.'
  611. )
  612. WHERE pac.`id_product_attribute` IN ('.implode(',', $pa_implode).')
  613. ORDER BY agl.`public_name` ASC'
  614. );
  615. foreach ($result as $row)
  616. {
  617. self::$_attributesLists[$row['id_product_attribute'].'-'.$id_lang]['attributes'] .= $row['public_group_name'].' : '.$row['attribute_name'].', ';
  618. self::$_attributesLists[$row['id_product_attribute'].'-'.$id_lang]['attributes_small'] .= $row['attribute_name'].', ';
  619. }
  620. foreach ($pa_implode as $id_product_attribute)
  621. {
  622. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes'] = rtrim(
  623. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes'],
  624. ', '
  625. );
  626. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes_small'] = rtrim(
  627. self::$_attributesLists[$id_product_attribute.'-'.$id_lang]['attributes_small'],
  628. ', '
  629. );
  630. }
  631. }
  632. /**
  633. * Return cart products quantity
  634. *
  635. * @result integer Products quantity
  636. */
  637. public function nbProducts()
  638. {
  639. if (!$this->id)
  640. return 0;
  641. return Cart::getNbProducts($this->id);
  642. }
  643. public static function getNbProducts($id)
  644. {
  645. // Must be strictly compared to NULL, or else an empty cart will bypass the cache and add dozens of queries
  646. if (isset(self::$_nbProducts[$id]) && self::$_nbProducts[$id] !== null)
  647. return self::$_nbProducts[$id];
  648. self::$_nbProducts[$id] = (int)Db::getInstance()->getValue('
  649. SELECT SUM(`quantity`)
  650. FROM `'._DB_PREFIX_.'cart_product`
  651. WHERE `id_cart` = '.(int)$id
  652. );
  653. return self::$_nbProducts[$id];
  654. }
  655. /**
  656. * @deprecated 1.5.0, use Cart->addCartRule()
  657. */
  658. public function addDiscount($id_cart_rule)
  659. {
  660. Tools::displayAsDeprecated();
  661. return $this->addCartRule($id_cart_rule);
  662. }
  663. public function addCartRule($id_cart_rule)
  664. {
  665. // You can't add a cart rule that does not exist
  666. $cartRule = new CartRule($id_cart_rule, Context::getContext()->language->id);
  667. if (!Validate::isLoadedObject($cartRule))
  668. return false;
  669. 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))
  670. return false;
  671. // Add the cart rule to the cart
  672. if (!Db::getInstance()->insert('cart_cart_rule', array(
  673. 'id_cart_rule' => (int)$id_cart_rule,
  674. 'id_cart' => (int)$this->id
  675. )))
  676. return false;
  677. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL);
  678. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING);
  679. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION);
  680. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT);
  681. if ((int)$cartRule->gift_product)
  682. $this->updateQty(1, $cartRule->gift_product, $cartRule->gift_product_attribute, false, 'up', 0, null, false);
  683. return true;
  684. }
  685. public function containsProduct($id_product, $id_product_attribute = 0, $id_customization = 0, $id_address_delivery = 0)
  686. {
  687. $sql = 'SELECT cp.`quantity` FROM `'._DB_PREFIX_.'cart_product` cp';
  688. if ($id_customization)
  689. $sql .= '
  690. LEFT JOIN `'._DB_PREFIX_.'customization` c ON (
  691. c.`id_product` = cp.`id_product`
  692. AND c.`id_product_attribute` = cp.`id_product_attribute`
  693. )';
  694. $sql .= '
  695. WHERE cp.`id_product` = '.(int)$id_product.'
  696. AND cp.`id_product_attribute` = '.(int)$id_product_attribute.'
  697. AND cp.`id_cart` = '.(int)$this->id;
  698. if (Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery())
  699. $sql .= ' AND cp.`id_address_delivery` = '.(int)$id_address_delivery;
  700. if ($id_customization)
  701. $sql .= ' AND c.`id_customization` = '.(int)$id_customization;
  702. return Db::getInstance()->getRow($sql);
  703. }
  704. /**
  705. * Update product quantity
  706. *
  707. * @param integer $quantity Quantity to add (or substract)
  708. * @param integer $id_product Product ID
  709. * @param integer $id_product_attribute Attribute ID if needed
  710. * @param string $operator Indicate if quantity must be increased or decreased
  711. */
  712. public function updateQty($quantity, $id_product, $id_product_attribute = null, $id_customization = false,
  713. $operator = 'up', $id_address_delivery = 0, Shop $shop = null, $auto_add_cart_rule = true)
  714. {
  715. if (!$shop)
  716. $shop = Context::getContext()->shop;
  717. if (Context::getContext()->customer->id)
  718. {
  719. if ($id_address_delivery == 0 && (int)$this->id_address_delivery) // The $id_address_delivery is null, use the cart delivery address
  720. $id_address_delivery = $this->id_address_delivery;
  721. elseif ($id_address_delivery == 0) // The $id_address_delivery is null, get the default customer address
  722. $id_address_delivery = (int)Address::getFirstCustomerAddressId((int)Context::getContext()->customer->id);
  723. elseif (!Customer::customerHasAddress(Context::getContext()->customer->id, $id_address_delivery)) // The $id_address_delivery must be linked with customer
  724. $id_address_delivery = 0;
  725. }
  726. $quantity = (int)$quantity;
  727. $id_product = (int)$id_product;
  728. $id_product_attribute = (int)$id_product_attribute;
  729. $product = new Product($id_product, false, Configuration::get('PS_LANG_DEFAULT'), $shop->id);
  730. if ($id_product_attribute)
  731. {
  732. $combination = new Combination((int)$id_product_attribute);
  733. if ($combination->id_product != $id_product)
  734. return false;
  735. }
  736. /* If we have a product combination, the minimal quantity is set with the one of this combination */
  737. if (!empty($id_product_attribute))
  738. $minimal_quantity = (int)Attribute::getAttributeMinimalQty($id_product_attribute);
  739. else
  740. $minimal_quantity = (int)$product->minimal_quantity;
  741. if (!Validate::isLoadedObject($product))
  742. die(Tools::displayError());
  743. if (isset(self::$_nbProducts[$this->id]))
  744. unset(self::$_nbProducts[$this->id]);
  745. if (isset(self::$_totalWeight[$this->id]))
  746. unset(self::$_totalWeight[$this->id]);
  747. if ((int)$quantity <= 0)
  748. return $this->deleteProduct($id_product, $id_product_attribute, (int)$id_customization);
  749. elseif (!$product->available_for_order || Configuration::get('PS_CATALOG_MODE'))
  750. return false;
  751. else
  752. {
  753. /* Check if the product is already in the cart */
  754. $result = $this->containsProduct($id_product, $id_product_attribute, (int)$id_customization, (int)$id_address_delivery);
  755. /* Update quantity if product already exist */
  756. if ($result)
  757. {
  758. if ($operator == 'up')
  759. {
  760. $sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
  761. FROM '._DB_PREFIX_.'product p
  762. '.Product::sqlStock('p', $id_product_attribute, true, $shop).'
  763. WHERE p.id_product = '.$id_product;
  764. $result2 = Db::getInstance()->getRow($sql);
  765. $product_qty = (int)$result2['quantity'];
  766. // Quantity for product pack
  767. if (Pack::isPack($id_product))
  768. $product_qty = Pack::getQuantity($id_product, $id_product_attribute);
  769. $new_qty = (int)$result['quantity'] + (int)$quantity;
  770. $qty = '+ '.(int)$quantity;
  771. if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock']))
  772. if ($new_qty > $product_qty)
  773. return false;
  774. }
  775. else if ($operator == 'down')
  776. {
  777. $qty = '- '.(int)$quantity;
  778. $new_qty = (int)$result['quantity'] - (int)$quantity;
  779. if ($new_qty < $minimal_quantity && $minimal_quantity > 1)
  780. return -1;
  781. }
  782. else
  783. return false;
  784. /* Delete product from cart */
  785. if ($new_qty <= 0)
  786. return $this->deleteProduct((int)$id_product, (int)$id_product_attribute, (int)$id_customization);
  787. else if ($new_qty < $minimal_quantity)
  788. return -1;
  789. else
  790. Db::getInstance()->execute('
  791. UPDATE `'._DB_PREFIX_.'cart_product`
  792. SET `quantity` = `quantity` '.$qty.', `date_add` = NOW()
  793. WHERE `id_product` = '.(int)$id_product.
  794. (!empty($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
  795. AND `id_cart` = '.(int)$this->id.(Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery() ? ' AND `id_address_delivery` = '.(int)$id_address_delivery : '').'
  796. LIMIT 1'
  797. );
  798. }
  799. /* Add product to the cart */
  800. elseif ($operator == 'up')
  801. {
  802. $sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
  803. FROM '._DB_PREFIX_.'product p
  804. '.Product::sqlStock('p', $id_product_attribute, true, $shop).'
  805. WHERE p.id_product = '.$id_product;
  806. $result2 = Db::getInstance()->getRow($sql);
  807. // Quantity for product pack
  808. if (Pack::isPack($id_product))
  809. $result2['quantity'] = Pack::getQuantity($id_product, $id_product_attribute);
  810. if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock']))
  811. if ((int)$quantity > $result2['quantity'])
  812. return false;
  813. if ((int)$quantity < $minimal_quantity)
  814. return -1;
  815. $result_add = Db::getInstance()->insert('cart_product', array(
  816. 'id_product' => (int)$id_product,
  817. 'id_product_attribute' => (int)$id_product_attribute,
  818. 'id_cart' => (int)$this->id,
  819. 'id_address_delivery' => (int)$id_address_delivery,
  820. 'id_shop' => $shop->id,
  821. 'quantity' => (int)$quantity,
  822. 'date_add' => date('Y-m-d H:i:s')
  823. ));
  824. if (!$result_add)
  825. return false;
  826. }
  827. }
  828. // refresh cache of self::_products
  829. $this->_products = $this->getProducts(true);
  830. $this->update(true);
  831. $context = Context::getContext()->cloneContext();
  832. $context->cart = $this;
  833. Cache::clean('getContextualValue_*');
  834. if ($auto_add_cart_rule)
  835. CartRule::autoAddToCart($context);
  836. if ($product->customizable)
  837. return $this->_updateCustomizationQuantity((int)$quantity, (int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery, $operator);
  838. else
  839. return true;
  840. }
  841. /*
  842. ** Customization management
  843. */
  844. protected function _updateCustomizationQuantity($quantity, $id_customization, $id_product, $id_product_attribute, $id_address_delivery, $operator = 'up')
  845. {
  846. // Link customization to product combination when it is first added to cart
  847. if (empty($id_customization))
  848. {
  849. $customization = $this->getProductCustomization($id_product, null, true);
  850. foreach ($customization as $field)
  851. {
  852. if ($field['quantity'] == 0)
  853. {
  854. Db::getInstance()->execute('
  855. UPDATE `'._DB_PREFIX_.'customization`
  856. SET `quantity` = '.(int)$quantity.',
  857. `id_product_attribute` = '.(int)$id_product_attribute.',
  858. `id_address_delivery` = '.(int)$id_address_delivery.',
  859. `in_cart` = 1
  860. WHERE `id_customization` = '.(int)$field['id_customization']);
  861. }
  862. }
  863. }
  864. /* Deletion */
  865. if (!empty($id_customization) && (int)$quantity < 1)
  866. return $this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute);
  867. /* Quantity update */
  868. if (!empty($id_customization))
  869. {
  870. $result = Db::getInstance()->getRow('SELECT `quantity` FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization);
  871. if ($result && Db::getInstance()->NumRows())
  872. {
  873. if ($operator == 'down' && (int)$result['quantity'] - (int)$quantity < 1)
  874. return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'customization` WHERE `id_customization` = '.(int)$id_customization);
  875. return Db::getInstance()->execute('
  876. UPDATE `'._DB_PREFIX_.'customization`
  877. SET
  878. `quantity` = `quantity` '.($operator == 'up' ? '+ ' : '- ').(int)$quantity.',
  879. `id_address_delivery` = '.(int)$id_address_delivery.'
  880. WHERE `id_customization` = '.(int)$id_customization);
  881. }
  882. else
  883. Db::getInstance()->execute('
  884. UPDATE `'._DB_PREFIX_.'customization`
  885. SET `id_address_delivery` = '.(int)$id_address_delivery.'
  886. WHERE `id_customization` = '.(int)$id_customization);
  887. }
  888. // refresh cache of self::_products
  889. $this->_products = $this->getProducts(true);
  890. $this->update(true);
  891. return true;
  892. }
  893. /**
  894. * Add customization item to database
  895. *
  896. * @param int $id_product
  897. * @param int $id_product_attribute
  898. * @param int $index
  899. * @param int $type
  900. * @param string $field
  901. * @param int $quantity
  902. * @return boolean success
  903. */
  904. public function _addCustomization($id_product, $id_product_attribute, $index, $type, $field, $quantity)
  905. {
  906. $exising_customization = Db::getInstance()->executeS('
  907. SELECT cu.`id_customization`, cd.`index`, cd.`value`, cd.`type` FROM `'._DB_PREFIX_.'customization` cu
  908. LEFT JOIN `'._DB_PREFIX_.'customized_data` cd
  909. ON cu.`id_customization` = cd.`id_customization`
  910. WHERE cu.id_cart = '.(int)$this->id.'
  911. AND cu.id_product = '.(int)$id_product.'
  912. AND in_cart = 0'
  913. );
  914. if ($exising_customization)
  915. {
  916. // If the customization field is alreay filled, delete it
  917. foreach ($exising_customization as $customization)
  918. {
  919. if ($customization['type'] == $type && $customization['index'] == $index)
  920. {
  921. Db::getInstance()->execute('
  922. DELETE FROM `'._DB_PREFIX_.'customized_data`
  923. WHERE id_customization = '.(int)$customization['id_customization'].'
  924. AND type = '.(int)$customization['type'].'
  925. AND `index` = '.(int)$customization['index']);
  926. if ($type == Product::CUSTOMIZE_FILE)
  927. {
  928. @unlink(_PS_UPLOAD_DIR_.$customization['value']);
  929. @unlink(_PS_UPLOAD_DIR_.$customization['value'].'_small');
  930. }
  931. break;
  932. }
  933. }
  934. $id_customization = $exising_customization[0]['id_customization'];
  935. }
  936. else
  937. {
  938. Db::getInstance()->execute(
  939. 'INSERT INTO `'._DB_PREFIX_.'customization` (`id_cart`, `id_product`, `id_product_attribute`, `quantity`)
  940. VALUES ('.(int)$this->id.', '.(int)$id_product.', '.(int)$id_product_attribute.', '.(int)$quantity.')'
  941. );
  942. $id_customization = Db::getInstance()->Insert_ID();
  943. }
  944. $query = 'INSERT INTO `'._DB_PREFIX_.'customized_data` (`id_customization`, `type`, `index`, `value`)
  945. VALUES ('.(int)$id_customization.', '.(int)$type.', '.(int)$index.', \''.pSQL($field).'\')';
  946. if (!Db::getInstance()->execute($query))
  947. return false;
  948. return true;
  949. }
  950. /**
  951. * Check if order has already been placed
  952. *
  953. * @return boolean result
  954. */
  955. public function orderExists()
  956. {
  957. $cache_id = 'Cart::orderExists_'.(int)$this->id;
  958. if (!Cache::isStored($cache_id))
  959. {
  960. $result = (bool)Db::getInstance()->getValue('SELECT count(*) FROM `'._DB_PREFIX_.'orders` WHERE `id_cart` = '.(int)$this->id);
  961. Cache::store($cache_id, $result);
  962. }
  963. return Cache::retrieve($cache_id);
  964. }
  965. /**
  966. * @deprecated 1.5.0, use Cart->removeCartRule()
  967. */
  968. public function deleteDiscount($id_cart_rule)
  969. {
  970. Tools::displayAsDeprecated();
  971. return $this->removeCartRule($id_cart_rule);
  972. }
  973. public function removeCartRule($id_cart_rule)
  974. {
  975. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_ALL);
  976. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_SHIPPING);
  977. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_REDUCTION);
  978. Cache::clean('Cart::getCartRules_'.$this->id.'-'.CartRule::FILTER_ACTION_GIFT);
  979. $result = Db::getInstance()->execute('
  980. DELETE FROM `'._DB_PREFIX_.'cart_cart_rule`
  981. WHERE `id_cart_rule` = '.(int)$id_cart_rule.'
  982. AND `id_cart` = '.(int)$this->id.'
  983. LIMIT 1');
  984. $cart_rule = new CartRule($id_cart_rule, Configuration::get('PS_LANG_DEFAULT'));
  985. if ((int)$cart_rule->gift_product)
  986. $this->updateQty(1, $cart_rule->gift_product, $cart_rule->gift_product_attribute, null, 'down', 0, null, false);
  987. return $result;
  988. }
  989. /**
  990. * Delete a product from the cart
  991. *
  992. * @param integer $id_product Product ID
  993. * @param integer $id_product_attribute Attribute ID if needed
  994. * @param integer $id_customization Customization id
  995. * @return boolean result
  996. */
  997. public function deleteProduct($id_product, $id_product_attribute = null, $id_customization = null, $id_address_delivery = 0)
  998. {
  999. if (isset(self::$_nbProducts[$this->id]))
  1000. unset(self::$_nbProducts[$this->id]);
  1001. if (isset(self::$_totalWeight[$this->id]))
  1002. unset(self::$_totalWeight[$this->id]);
  1003. if ((int)$id_customization)
  1004. {
  1005. $product_total_quantity = (int)Db::getInstance()->getValue(
  1006. 'SELECT `quantity`
  1007. FROM `'._DB_PREFIX_.'cart_product`
  1008. WHERE `id_product` = '.(int)$id_product.'
  1009. AND `id_cart` = '.(int)$this->id.'
  1010. AND `id_product_attribute` = '.(int)$id_product_attribute);
  1011. $customization_quantity = (int)Db::getInstance()->getValue('
  1012. SELECT `quantity`
  1013. FROM `'._DB_PREFIX_.'customization`
  1014. WHERE `id_cart` = '.(int)$this->id.'
  1015. AND `id_product` = '.(int)$id_product.'
  1016. AND `id_product_attribute` = '.(int)$id_product_attribute.'
  1017. '.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));
  1018. if (!$this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery))
  1019. return false;
  1020. // refresh cache of self::_products
  1021. $this->_products = $this->getProducts(true);
  1022. return ($customization_quantity == $product_total_quantity && $this->deleteProduct((int)$id_product, (int)$id_product_attribute, null, (int)$id_address_delivery));
  1023. }
  1024. /* Get customization quantity */
  1025. $result = Db::getInstance()->getRow('
  1026. SELECT SUM(`quantity`) AS \'quantity\'
  1027. FROM `'._DB_PREFIX_.'customization`
  1028. WHERE `id_cart` = '.(int)$this->id.'
  1029. AND `id_product` = '.(int)$id_product.'
  1030. AND `id_product_attribute` = '.(int)$id_product_attribute);
  1031. if ($result === false)
  1032. return false;
  1033. /* If the product still possesses customization it does not have to be deleted */
  1034. if (Db::getInstance()->NumRows() && (int)$result['quantity'])
  1035. return Db::getInstance()->execute('
  1036. UPDATE `'._DB_PREFIX_.'cart_product`
  1037. SET `quantity` = '.(int)$result['quantity'].'
  1038. WHERE `id_cart` = '.(int)$this->id.'
  1039. AND `id_product` = '.(int)$id_product.
  1040. ($id_product_attribute != null ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '')
  1041. );
  1042. /* Product deletion */
  1043. $result = Db::getInstance()->execute('
  1044. DELETE FROM `'._DB_PREFIX_.'cart_product`
  1045. WHERE `id_product` = '.(int)$id_product.'
  1046. '.(!is_null($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
  1047. AND `id_cart` = '.(int)$this->id.'
  1048. '.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));
  1049. if ($result)
  1050. {
  1051. $return = $this->update(true);
  1052. // refresh cache of self::_products
  1053. $this->_products = $this->getProducts(true);
  1054. CartRule::autoRemoveFromCart();
  1055. CartRule::autoAddToCart();
  1056. return $return;
  1057. }
  1058. return false;
  1059. }
  1060. /**
  1061. * Delete a customization from the cart. If customization is a Picture,
  1062. * then the image is also deleted
  1063. *
  1064. * @param integer $id_customization
  1065. * @return boolean result
  1066. */
  1067. protected function _deleteCustomization($id_customization, $id_product, $id_product_attribute, $id_address_delivery = 0)
  1068. {
  1069. $result = true;
  1070. $customization = Db::getInstance()->getRow('SELECT *
  1071. FROM `'._DB_PREFIX_.'customization`
  1072. WHERE `id_customization` = '.(int)$id_customization);
  1073. if ($customization)
  1074. {
  1075. $cust_data = Db::getInstance()->getRow('SELECT *
  1076. FROM `'._DB_PREFIX_.'customized_data`
  1077. WHERE `id_customization` = '.(int)$id_customization);
  1078. // Delete customization picture if necessary
  1079. if (isset($cust_data['type']) && $cust_data['type'] == 0)
  1080. $result &= (@unlink(_PS_UPLOAD_DIR_.$cust_data['value']) && @unlink(_PS_UPLOAD_DIR_.$cust_data['value'].'_small'));
  1081. $result &= Db::getInstance()->execute(
  1082. 'DELETE FROM `'._DB_PREFIX_.'customized_data`
  1083. WHERE `id_customization` = '.(int)$id_customization
  1084. );
  1085. if ($result)
  1086. $result &= Db::getInstance()->execute(
  1087. 'UPDATE `'._DB_PREFIX_.'cart_product`
  1088. SET `quantity` = `quantity` - '.(int)$customization['quantity'].'
  1089. WHERE `id_cart` = '.(int)$this->id.'
  1090. AND `id_product` = '.(int)$id_product.
  1091. ((int)$id_product_attribute ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
  1092. AND `id_address_delivery` = '.(int)$id_address_delivery
  1093. );
  1094. if (!$result)
  1095. return false;
  1096. return Db::getInstance()->execute(
  1097. 'DELETE FROM `'._DB_PREFIX_.'customization`
  1098. WHERE `id_customization` = '.(int)$id_customization
  1099. );
  1100. }
  1101. return true;
  1102. }
  1103. public static function getTotalCart($id_cart, $use_tax_display = false, $type = Cart::BOTH)
  1104. {
  1105. $cart = new Cart($id_cart);
  1106. if (!Validate::isLoadedObject($cart))
  1107. die(Tools::displayError());
  1108. $with_taxes = $use_tax_display ? $cart->_taxCalculationMethod != PS_TAX_EXC : true;
  1109. return Tools::displayPrice($cart->getOrderTotal($with_taxes, $type), Currency::getCurrencyInstance((int)$cart->id_currency), false);
  1110. }
  1111. public static function getOrderTotalUsingTaxCalculationMethod($id_cart)
  1112. {
  1113. return Cart::getTotalCart($id_cart, true);
  1114. }
  1115. /**
  1116. * This function returns the total cart amount
  1117. *
  1118. * Possible values for $type:
  1119. * Cart::ONLY_PRODUCTS
  1120. * Cart::ONLY_DISCOUNTS
  1121. * Cart::BOTH
  1122. * Cart::BOTH_WITHOUT_SHIPPING
  1123. * Cart::ONLY_SHIPPING
  1124. * Cart::ONLY_WRAPPING
  1125. * Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING
  1126. * Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING
  1127. *
  1128. * @param boolean $withTaxes With or without taxes
  1129. * @param integer $type Total type
  1130. * @param boolean $use_cache Allow using cache of the method CartRule::getContextualValue
  1131. * @return float Order total
  1132. */
  1133. public function getOrderTotal($with_taxes = true, $type = Cart::BOTH, $products = null, $id_carrier = null, $use_cache = true)
  1134. {
  1135. if (!$this->id)
  1136. return 0;
  1137. $type = (int)$type;
  1138. $array_type = array(
  1139. Cart::ONLY_PRODUCTS,
  1140. Cart::ONLY_DISCOUNTS,
  1141. Cart::BOTH,
  1142. Cart::BOTH_WITHOUT_SHIPPING,
  1143. Cart::ONLY_SHIPPING,
  1144. Cart::ONLY_WRAPPING,
  1145. Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING,
  1146. Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING,
  1147. );
  1148. // Define virtual context to prevent case where the cart is not the in the global context
  1149. $virtual_context = Context::getContext()->cloneContext();
  1150. $virtual_context->cart = $this;
  1151. if (!in_array($type, $array_type))
  1152. die(Tools::displayError());
  1153. $with_shipping = in_array($type, array(Cart::BOTH, Cart::ONLY_SHIPPING));
  1154. // if cart rules are not used
  1155. if ($type == Cart::ONLY_DISCOUNTS && !CartRule::isFeatureActive())
  1156. return 0;
  1157. // no shipping cost if is a cart with only virtuals products
  1158. $virtual = $this->isVirtualCart();
  1159. if ($virtual && $type == Cart::ONLY_SHIPPING)
  1160. return 0;
  1161. if ($virtual && $type == Cart::BOTH)
  1162. $type = Cart::BOTH_WITHOUT_SHIPPING;
  1163. if ($with_shipping || $type == Cart::ONLY_DISCOUNTS)
  1164. {
  1165. if (is_null($products) && is_null($id_carrier))
  1166. $shipping_fees = $this->getTotalShippingCost(null, (boolean)$with_taxes);
  1167. else
  1168. $shipping_fees = $this->getPackageShippingCost($id_carrier, (bool)$with_taxes, null, $products);
  1169. }
  1170. else
  1171. $shipping_fees = 0;
  1172. if ($type == Cart::ONLY_SHIPPING)
  1173. return $shipping_fees;
  1174. if ($type == Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING)
  1175. $type = Cart::ONLY_PRODUCTS;
  1176. $param_product = true;
  1177. if (is_null($products))
  1178. {
  1179. $param_product = false;
  1180. $products = $this->getProducts();
  1181. }
  1182. if ($type == Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING)
  1183. {
  1184. foreach ($products as $key => $product)
  1185. if ($product['is_virtual'])
  1186. unset($products[$key]);
  1187. $type = Cart::ONLY_PRODUCTS;
  1188. }
  1189. $order_total = 0;
  1190. if (Tax::excludeTaxeOption())
  1191. $with_taxes = false;
  1192. foreach ($products as $product) // products refer to the cart details
  1193. {
  1194. if ($virtual_context->shop->id != $product['id_shop'])
  1195. $virtual_context->shop = new Shop((int)$product['id_shop']);
  1196. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice')
  1197. $address_id = (int)$this->id_address_invoice;
  1198. else
  1199. $address_id = (int)$product['id_address_delivery']; // Get delivery address of the product from the cart
  1200. if (!Address::addressExists($address_id))
  1201. $address_id = null;
  1202. if ($this->_taxCalculationMethod == PS_TAX_EXC)
  1203. {
  1204. // Here taxes are computed only once the quantity has been applied to the product price
  1205. $price = Product::getPriceStatic(
  1206. (int)$product['id_product'],
  1207. false,
  1208. (int)$product['id_product_attribute'],
  1209. 2,
  1210. null,
  1211. false,
  1212. true,
  1213. $product['cart_quantity'],
  1214. false,
  1215. (int)$this->id_customer ? (int)$this->id_customer : null,
  1216. (int)$this->id,
  1217. $address_id,
  1218. $null,
  1219. true,
  1220. true,
  1221. $virtual_context
  1222. );
  1223. $total_ecotax = $product['ecotax'] * (int)$product['cart_quantity'];
  1224. $total_price = $price * (int)$product['cart_quantity'];
  1225. if ($with_taxes)
  1226. {
  1227. $product_tax_rate = (float)Tax::getProductTaxRate((int)$product['id_product'], (int)$address_id, $virtual_context);
  1228. $product_eco_tax_rate = Tax::getProductEcotaxRate((int)$address_id);
  1229. $total_price = ($total_price - $total_ecotax) * (1 + $product_tax_rate / 100);
  1230. $total_ecotax = $total_ecotax * (1 + $product_eco_tax_rate / 100);
  1231. $total_price = Tools::ps_round($total_price + $total_ecotax, 2);
  1232. }
  1233. }
  1234. else
  1235. {
  1236. if ($with_taxes)
  1237. $price = Product::getPriceStatic(
  1238. (int)$product['id_product'],
  1239. true,
  1240. (int)$product['id_product_attribute'],
  1241. 2,
  1242. null,
  1243. false,
  1244. true,
  1245. $product['cart_quantity'],
  1246. false,
  1247. ((int)$this->id_customer ? (int)$this->id_customer : null),
  1248. (int)$this->id,
  1249. ((int)$address_id ? (int)$address_id : null),
  1250. $null,
  1251. true,
  1252. true,
  1253. $virtual_context
  1254. );
  1255. else
  1256. $price = Product::getPriceStatic(
  1257. (int)$product['id_product'],
  1258. false,
  1259. (int)$product['id_product_attribute'],
  1260. 2,
  1261. null,
  1262. false,
  1263. true,
  1264. $product['cart_quantity'],
  1265. false,
  1266. ((int)$this->id_customer ? (int)$this->id_customer : null),
  1267. (int)$this->id,
  1268. ((int)$address_id ? (int)$address_id : null),
  1269. $null,
  1270. true,
  1271. true,
  1272. $virtual_context
  1273. );
  1274. $total_price = Tools::ps_round($price * (int)$product['cart_quantity'], 2);
  1275. }
  1276. $order_total += $total_price;
  1277. }
  1278. $order_total_products = $order_total;
  1279. if ($type == Cart::ONLY_DISCOUNTS)
  1280. $order_total = 0;
  1281. // Wrapping Fees
  1282. $wrapping_fees = 0;
  1283. if ($this->gift)
  1284. $wrapping_fees = Tools::convertPrice(Tools::ps_round($this->getGiftWrappingPrice($with_taxes), 2), Currency::getCurrencyInstance((int)$this->id_currency));
  1285. if ($type == Cart::ONLY_WRAPPING)
  1286. return $wrapping_fees;
  1287. $order_total_discount = 0;
  1288. if (!in_array($type, array(Cart::ONLY_SHIPPING, Cart::ONLY_PRODUCTS)) && CartRule::isFeatureActive())
  1289. {
  1290. // First, retrieve the cart rules associated to this "getOrderTotal"
  1291. if ($with_shipping || $type == Cart::ONLY_DISCOUNTS)
  1292. $cart_rules = $this->getCartRules(CartRule::FILTER_ACTION_ALL);
  1293. else
  1294. {
  1295. $cart_rules = $this->getCartRules(CartRule::FILTER_ACTION_REDUCTION);
  1296. // Cart Rules array are merged manually in order to avoid doubles
  1297. foreach ($this->getCartRules(CartRule::FILTER_ACTION_GIFT) as $tmp_cart_rule)
  1298. {
  1299. $flag = false;
  1300. foreach ($cart_rules as $cart_rule)
  1301. if ($tmp_cart_rule['id_cart_rule'] == $cart_rule['id_cart_rule'])
  1302. $flag = true;
  1303. if (!$flag)
  1304. $cart_rules[] = $tmp_cart_rule;
  1305. }
  1306. }
  1307. $id_address_delivery = 0;
  1308. if (isset($products[0]))
  1309. $id_address_delivery = (is_null($products) ? $this->id_address_delivery : $products[0]['id_address_delivery']);
  1310. $package = array('id_carrier' => $id_carrier, 'id_address' => $id_address_delivery, 'products' => $products);
  1311. // Then, calculate the contextual value for each one
  1312. foreach ($cart_rules as $cart_rule)
  1313. {
  1314. // If the cart rule offers free shipping, add the shipping cost
  1315. if (($with_shipping || $type == Cart::ONLY_DISCOUNTS) && $cart_rule['obj']->free_shipping)
  1316. $order_total_discount += Tools::ps_round($cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_SHIPPING, ($param_product ? $package : null), $use_cache), 2);
  1317. // If the cart rule is a free gift, then add the free gift value only if the gift is in this package
  1318. if ((int)$cart_rule['obj']->gift_product)
  1319. {
  1320. $in_order = false;
  1321. if (is_null($products))
  1322. $in_order = true;
  1323. else
  1324. foreach ($products as $product)
  1325. if ($cart_rule['obj']->gift_product == $product['id_product'] && $cart_rule['obj']->gift_product_attribute == $product['id_product_attribute'])
  1326. $in_order = true;
  1327. if ($in_order)
  1328. $order_total_discount += $cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_GIFT, $package, $use_cache);
  1329. }
  1330. // If the cart rule offers a reduction, the amount is prorated (with the products in the package)
  1331. if ($cart_rule['obj']->reduction_percent > 0 || $cart_rule['obj']->reduction_amount > 0)
  1332. $order_total_discount += Tools::ps_round($cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_REDUCTION, $package, $use_cache), 2);
  1333. }
  1334. $order_total_discount = min(Tools::ps_round($order_total_discount, 2), $wrapping_fees + $order_total_products + $shipping_fees);
  1335. $order_total -= $order_total_discount;
  1336. }
  1337. if ($type == Cart::BOTH)
  1338. $order_total += $shipping_fees + $wrapping_fees;
  1339. if ($order_total < 0 && $type != Cart::ONLY_DISCOUNTS)
  1340. return 0;
  1341. if ($type == Cart::ONLY_DISCOUNTS)
  1342. return $order_total_discount;
  1343. return Tools::ps_round((float)$order_total, 2);
  1344. }
  1345. /**
  1346. * Get the gift wrapping price
  1347. * @param boolean $with_taxes With or without taxes
  1348. * @return float wrapping price
  1349. */
  1350. public function getGiftWrappingPrice($with_taxes = true, $id_address = null)
  1351. {
  1352. static $address = null;
  1353. if ($id_address === null)
  1354. $id_address = (int)$this->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
  1355. if ($address === null)
  1356. $address = Address::initialize($id_address);
  1357. $wrapping_fees = (float)Configuration::get('PS_GIFT_WRAPPING_PRICE');
  1358. if ($with_taxes && $wrapping_fees > 0)
  1359. {
  1360. $tax_manager = TaxManagerFactory::getManager($address, (int)Configuration::get('PS_GIFT_WRAPPING_TAX_RULES_GROUP'));
  1361. $tax_calculator = $tax_manager->getTaxCalculator();
  1362. $wrapping_fees = $tax_calculator->addTaxes($wrapping_fees);
  1363. }
  1364. return $wrapping_fees;
  1365. }
  1366. /**
  1367. * Get the number of packages
  1368. *
  1369. * @return int number of packages
  1370. */
  1371. public function getNbOfPackages()
  1372. {
  1373. static $nb_packages = 0;
  1374. if (!$nb_packages)
  1375. foreach ($this->getPackageList() as $by_address)
  1376. $nb_packages += count($by_address);
  1377. return $nb_packages;
  1378. }
  1379. /**
  1380. * Get products grouped by package and by addresses to be sent individualy (one package = one shipping cost).
  1381. *
  1382. * @return array array(
  1383. * 0 => array( // First address
  1384. * 0 => array( // First package
  1385. * 'product_list' => array(...),
  1386. * 'carrier_list' => array(...),
  1387. * 'id_warehouse' => array(...),
  1388. * ),
  1389. * ),
  1390. * );
  1391. * @todo Add avaibility check
  1392. */
  1393. public function getPackageList($flush = false)
  1394. {
  1395. static $cache = array();
  1396. if (isset($cache[(int)$this->id.'_'.(int)$this->id_address_delivery]) && $cache[(int)$this->id.'_'.(int)$this->id_address_delivery] !== false && !$flush)
  1397. return $cache[(int)$this->id.'_'.(int)$this->id_address_delivery];
  1398. $product_list = $this->getProducts();
  1399. // Step 1 : Get product informations (warehouse_list and carrier_list), count warehouse
  1400. // Determine the best warehouse to determine the packages
  1401. // For that we count the number of time we can use a warehouse for a specific delivery address
  1402. $warehouse_count_by_address = array();
  1403. $warehouse_carrier_list = array();
  1404. $stock_management_active = Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT');
  1405. foreach ($product_list as &$product)
  1406. {
  1407. if ((int)$product['id_address_delivery'] == 0)
  1408. $product['id_address_delivery'] = (int)$this->id_address_delivery;
  1409. if (!isset($warehouse_count_by_address[$product['id_address_delivery']]))
  1410. $warehouse_count_by_address[$product['id_address_delivery']] = array();
  1411. $product['warehouse_list'] = array();
  1412. if ($stock_management_active &&
  1413. ((int)$product['advanced_stock_management'] == 1 || Pack::usesAdvancedStockManagement((int)$product['id_product'])))
  1414. {
  1415. $warehouse_list = Warehouse::getProductWarehouseList($product['id_product'], $product['id_product_attribute'], $this->id_shop);
  1416. if (count($warehouse_list) == 0)
  1417. $warehouse_list = Warehouse::getProductWarehouseList($product['id_product'], $product['id_product_attribute']);
  1418. // Does the product is in stock ?
  1419. // If yes, get only warehouse where the product is in stock
  1420. $warehouse_in_stock = array();
  1421. $manager = StockManagerFactory::getManager();
  1422. foreach ($warehouse_list as $key => $warehouse)
  1423. {
  1424. $product_real_quantities = $manager->getProductRealQuantities(
  1425. $product['id_product'],
  1426. $product['id_product_attribute'],
  1427. array($warehouse['id_warehouse']),
  1428. true
  1429. );
  1430. if ($product_real_quantities > 0 || Pack::isPack((int)$product['id_product']))
  1431. $warehouse_in_stock[] = $warehouse;
  1432. }
  1433. if (!empty($warehouse_in_stock))
  1434. {
  1435. $warehouse_list = $warehouse_in_stock;
  1436. $product['in_stock'] = true;
  1437. }
  1438. else
  1439. $product['in_stock'] = false;
  1440. }
  1441. else
  1442. {
  1443. //simulate default warehouse
  1444. $warehouse_list = array(0);
  1445. $product['in_stock'] = StockAvailable::getQuantityAvailableByProduct($product['id_product'], $product['id_product_attribute']) > 0;
  1446. }
  1447. foreach ($warehouse_list as $warehouse)
  1448. {
  1449. if (!isset($warehouse_carrier_list[$warehouse['id_warehouse']]))
  1450. {
  1451. $warehouse_object = new Warehouse($warehouse['id_warehouse']);
  1452. $warehouse_carrier_list[$warehouse['id_warehouse']] = $warehouse_object->getCarriers();
  1453. }
  1454. $product['warehouse_list'][] = $warehouse['id_warehouse'];
  1455. if (!isset($warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']]))
  1456. $warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']] = 0;
  1457. $warehouse_count_by_address[$product['id_address_delivery']][$warehouse['id_warehouse']]++;
  1458. }
  1459. }
  1460. unset($product);
  1461. arsort($warehouse_count_by_address);
  1462. // Step 2 : Group product by warehouse
  1463. $grouped_by_warehouse = array();
  1464. foreach ($product_list as &$product)
  1465. {
  1466. if (!isset($grouped_by_warehouse[$product['id_address_delivery']]))
  1467. $grouped_by_warehouse[$product['id_address_delivery']] = array(
  1468. 'in_stock' => array(),
  1469. 'out_of_stock' => array(),
  1470. );
  1471. $product['carrier_list'] = array();
  1472. $id_warehouse = 0;
  1473. foreach ($warehouse_count_by_address[$product['id_address_delivery']] as $id_war => $val)
  1474. {
  1475. if (in_array((int)$id_war, $product['warehouse_list']))
  1476. {
  1477. $product['carrier_list'] = array_merge($product['carrier_list'], Carrier::getAvailableCarrierList(new Product($product['id_product']), $id_war, $product['id_address_delivery'], null, $this));
  1478. if (!$id_warehouse)
  1479. $id_warehouse = (int)$id_war;
  1480. }
  1481. }
  1482. if (!isset($grouped_by_warehouse[$product['id_address_delivery']]['in_stock'][$id_warehouse]))
  1483. {
  1484. $grouped_by_warehouse[$product['id_address_delivery']]['in_stock'][$id_warehouse] = array();
  1485. $grouped_by_warehouse[$product['id_address_delivery']]['out_of_stock'][$id_warehouse] = array();
  1486. }
  1487. if (!$this->allow_seperated_package)
  1488. $key = 'in_stock';
  1489. else
  1490. $key = $product['in_stock'] ? 'in_stock' : 'out_of_stock';
  1491. if (empty($product['carrier_list']))
  1492. $product['carrier_list'] = array(0);
  1493. $grouped_by_warehouse[$product['id_address_delivery']][$key][$id_warehouse][] = $product;
  1494. }
  1495. unset($product);
  1496. // Step 3 : grouped product from grouped_by_warehouse by available carriers
  1497. $grouped_by_carriers = array();
  1498. foreach ($grouped_by_warehouse as $id_address_delivery => $products_in_stock_list)
  1499. {
  1500. if (!isset($grouped_by_carriers[$id_address_delivery]))
  1501. $grouped_by_carriers[$id_address_delivery] = array(
  1502. 'in_stock' => array(),
  1503. 'out_of_stock' => array(),
  1504. );
  1505. foreach ($products_in_stock_list as $key => $warehouse_list)
  1506. {
  1507. if (!isset($grouped_by_carriers[$id_address_delivery][$key]))
  1508. $grouped_by_carriers[$id_address_delivery][$key] = array();
  1509. foreach ($warehouse_list as $id_warehouse => $product_list)
  1510. {
  1511. if (!isset($grouped_by_carriers[$id_address_delivery][$key][$id_warehouse]))
  1512. $grouped_by_carriers[$id_address_delivery][$key][$id_warehouse] = array();
  1513. foreach ($product_list as $product)
  1514. {
  1515. $package_carriers_key = implode(',', $product['carrier_list']);
  1516. if (!isset($grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key]))
  1517. $grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key] = array(
  1518. 'product_list' => array(),
  1519. 'carrier_list' => $product['carrier_list'],
  1520. 'warehouse_list' => $product['warehouse_list']
  1521. );
  1522. $grouped_by_carriers[$id_address_delivery][$key][$id_warehouse][$package_carriers_key]['product_list'][] = $product;
  1523. }
  1524. }
  1525. }
  1526. }
  1527. $package_list = array();
  1528. // Step 4 : merge product from grouped_by_carriers into $package to minimize the number of package
  1529. foreach ($grouped_by_carriers as $id_address_delivery => $products_in_stock_list)
  1530. {
  1531. if (!isset($package_list[$id_address_delivery]))
  1532. $package_list[$id_address_delivery] = array(
  1533. 'in_stock' => array(),
  1534. 'out_of_stock' => array(),
  1535. );
  1536. foreach ($products_in_stock_list as $key => $warehouse_list)
  1537. {
  1538. if (!isset($package_list[$id_address_delivery][$key]))
  1539. $package_list[$id_address_delivery][$key] = array();
  1540. // Count occurance of each carriers to minimize the number of packages
  1541. $carrier_count = array();
  1542. foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers)
  1543. {
  1544. foreach ($products_grouped_by_carriers as $data)
  1545. {
  1546. foreach ($data['carrier_list'] as $id_carrier)
  1547. {
  1548. if (!isset($carrier_count[$id_carrier]))
  1549. $carrier_count[$id_carrier] = 0;
  1550. $carrier_count[$id_carrier]++;
  1551. }
  1552. }
  1553. }
  1554. arsort($carrier_count);
  1555. foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers)
  1556. {
  1557. if (!isset($package_list[$id_address_delivery][$key][$id_warehouse]))
  1558. $package_list[$id_address_delivery][$key][$id_warehouse] = array();
  1559. foreach ($products_grouped_by_carriers as $data)
  1560. {
  1561. foreach ($carrier_count as $id_carrier => $rate)
  1562. {
  1563. if (in_array($id_carrier, $data['carrier_list']))
  1564. {
  1565. if (!isset($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]))
  1566. $package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier] = array(
  1567. 'carrier_list' => $data['carrier_list'],
  1568. 'warehouse_list' => $data['warehouse_list'],
  1569. 'product_list' => array(),
  1570. );
  1571. $package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['carrier_list'] =
  1572. array_intersect($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['carrier_list'], $data['carrier_list']);
  1573. $package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['product_list'] =
  1574. array_merge($package_list[$id_address_delivery][$key][$id_warehouse][$id_carrier]['product_list'], $data['product_list']);
  1575. break;
  1576. }
  1577. }
  1578. }
  1579. }
  1580. }
  1581. }
  1582. // Step 5 : Reduce depth of $package_list
  1583. $final_package_list = array();
  1584. foreach ($package_list as $id_address_delivery => $products_in_stock_list)
  1585. {
  1586. if (!isset($final_package_list[$id_address_delivery]))
  1587. $final_package_list[$id_address_delivery] = array();
  1588. foreach ($products_in_stock_list as $key => $warehouse_list)
  1589. foreach ($warehouse_list as $id_warehouse => $products_grouped_by_carriers)
  1590. foreach ($products_grouped_by_carriers as $data)
  1591. {
  1592. $final_package_list[$id_address_delivery][] = array(
  1593. 'product_list' => $data['product_list'],
  1594. 'carrier_list' => $data['carrier_list'],
  1595. 'warehouse_list' => $data['warehouse_list'],
  1596. 'id_warehouse' => $id_warehouse,
  1597. );
  1598. }
  1599. }
  1600. $cache[(int)$this->id] = $final_package_list;
  1601. return $final_package_list;
  1602. }
  1603. public function getPackageIdWarehouse($package, $id_carrier = null)
  1604. {
  1605. if ($id_carrier === null)
  1606. if (isset($package['id_carrier']))
  1607. $id_carrier = (int)$package['id_carrier'];
  1608. if ($id_carrier == null)
  1609. return $package['id_warehouse'];
  1610. foreach ($package['warehouse_list'] as $id_warehouse)
  1611. {
  1612. $warehouse = new Warehouse((int)$id_warehouse);
  1613. $available_warehouse_carriers = $warehouse->getCarriers();
  1614. if (in_array($id_carrier, $available_warehouse_carriers))
  1615. return (int)$id_warehouse;
  1616. }
  1617. return 0;
  1618. }
  1619. /**
  1620. * Get all deliveries options available for the current cart
  1621. * @param Country $default_country
  1622. * @param boolean $flush Force flushing cache
  1623. *
  1624. * @return array array(
  1625. * 0 => array( // First address
  1626. * '12,' => array( // First delivery option available for this address
  1627. * carrier_list => array(
  1628. * 12 => array( // First carrier for this option
  1629. * 'instance' => Carrier Object,
  1630. * 'logo' => <url to the carriers logo>,
  1631. * 'price_with_tax' => 12.4,
  1632. * 'price_without_tax' => 12.4,
  1633. * 'package_list' => array(
  1634. * 1,
  1635. * 3,
  1636. * ),
  1637. * ),
  1638. * ),
  1639. * is_best_grade => true, // Does this option have the biggest grade (quick shipping) for this shipping address
  1640. * is_best_price => true, // Does this option have the lower price for this shipping address
  1641. * unique_carrier => true, // Does this option use a unique carrier
  1642. * total_price_with_tax => 12.5,
  1643. * total_price_without_tax => 12.5,
  1644. * position => 5, // Average of the carrier position
  1645. * ),
  1646. * ),
  1647. * );
  1648. * If there are no carriers available for an address, return an empty array
  1649. */
  1650. public function getDeliveryOptionList(Country $default_country = null, $flush = false)
  1651. {
  1652. static $cache = null;
  1653. if ($cache !== null && !$flush)
  1654. return $cache;
  1655. $delivery_option_list = array();
  1656. $carriers_price = array();
  1657. $carrier_collection = array();
  1658. $package_list = $this->getPackageList();
  1659. // Foreach addresses
  1660. foreach ($package_list as $id_address => $packages)
  1661. {
  1662. // Initialize vars
  1663. $delivery_option_list[$id_address] = array();
  1664. $carriers_price[$id_address] = array();
  1665. $common_carriers = null;
  1666. $best_price_carriers = array();
  1667. $best_grade_carriers = array();
  1668. $carriers_instance = array();
  1669. // Get country
  1670. if ($id_address)
  1671. {
  1672. $address = new Address($id_address);
  1673. $country = new Country($address->id_country);
  1674. }
  1675. else
  1676. $country = $default_country;
  1677. // Foreach packages, get the carriers with best price, best position and best grade
  1678. foreach ($packages as $id_package => $package)
  1679. {
  1680. // No carriers available
  1681. if (count($package['carrier_list']) == 1 && current($package['carrier_list']) == 0)
  1682. {
  1683. $cache = array();
  1684. return $cache;
  1685. }
  1686. $carriers_price[$id_address][$id_package] = array();
  1687. // Get all common carriers for each packages to the same address
  1688. if (is_null($common_carriers))
  1689. $common_carriers = $package['carrier_list'];
  1690. else
  1691. $common_carriers = array_intersect($common_carriers, $package['carrier_list']);
  1692. $best_price = null;
  1693. $best_price_carrier = null;
  1694. $best_grade = null;
  1695. $best_grade_carrier = null;
  1696. // Foreach carriers of the package, calculate his price, check if it the best price, position and grade
  1697. foreach ($package['carrier_list'] as $id_carrier)
  1698. {
  1699. if (!isset($carriers_instance[$id_carrier]))
  1700. $carriers_instance[$id_carrier] = new Carrier($id_carrier);
  1701. $price_with_tax = $this->getPackageShippingCost($id_carrier, true, $country, $package['product_list']);
  1702. $price_without_tax = $this->getPackageShippingCost($id_carrier, false, $country, $package['product_list']);
  1703. if (is_null($best_price) || $price_with_tax < $best_price)
  1704. {
  1705. $best_price = $price_with_tax;
  1706. $best_price_carrier = $id_carrier;
  1707. }
  1708. $carriers_price[$id_address][$id_package][$id_carrier] = array(
  1709. 'without_tax' => $price_without_tax,
  1710. 'with_tax' => $price_with_tax);
  1711. $grade = $carriers_instance[$id_carrier]->grade;
  1712. if (is_null($best_grade) || $grade > $best_grade)
  1713. {
  1714. $best_grade = $grade;
  1715. $best_grade_carrier = $id_carrier;
  1716. }
  1717. }
  1718. $best_price_carriers[$id_package] = $best_price_carrier;
  1719. $best_grade_carriers[$id_package] = $best_grade_carrier;
  1720. }
  1721. // Reset $best_price_carrier, it's now an array
  1722. $best_price_carrier = array();
  1723. $key = '';
  1724. // Get the delivery option with the lower price
  1725. foreach ($best_price_carriers as $id_package => $id_carrier)
  1726. {
  1727. $key .= $id_carrier.',';
  1728. if (!isset($best_price_carrier[$id_carrier]))
  1729. $best_price_carrier[$id_carrier] = array(
  1730. 'price_with_tax' => 0,
  1731. 'price_without_tax' => 0,
  1732. 'package_list' => array(),
  1733. 'product_list' => array(),
  1734. );
  1735. $best_price_carrier[$id_carrier]['price_with_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'];
  1736. $best_price_carrier[$id_carrier]['price_without_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'];
  1737. $best_price_carrier[$id_carrier]['package_list'][] = $id_package;
  1738. $best_price_carrier[$id_carrier]['product_list'] = array_merge($best_price_carrier[$id_carrier]['product_list'], $packages[$id_package]['product_list']);
  1739. $best_price_carrier[$id_carrier]['instance'] = $carriers_instance[$id_carrier];
  1740. }
  1741. // Add the delivery option with best price as best price
  1742. $delivery_option_list[$id_address][$key] = array(
  1743. 'carrier_list' => $best_price_carrier,
  1744. 'is_best_price' => true,
  1745. 'is_best_grade' => false,
  1746. 'unique_carrier' => (count($best_price_carrier) <= 1)
  1747. );
  1748. // Reset $best_grade_carrier, it's now an array
  1749. $best_grade_carrier = array();
  1750. $key = '';
  1751. // Get the delivery option with the best grade
  1752. foreach ($best_grade_carriers as $id_package => $id_carrier)
  1753. {
  1754. $key .= $id_carrier.',';
  1755. if (!isset($best_grade_carrier[$id_carrier]))
  1756. $best_grade_carrier[$id_carrier] = array(
  1757. 'price_with_tax' => 0,
  1758. 'price_without_tax' => 0,
  1759. 'package_list' => array(),
  1760. 'product_list' => array(),
  1761. );
  1762. $best_grade_carrier[$id_carrier]['price_with_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'];
  1763. $best_grade_carrier[$id_carrier]['price_without_tax'] += $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'];
  1764. $best_grade_carrier[$id_carrier]['package_list'][] = $id_package;
  1765. $best_grade_carrier[$id_carrier]['product_list'] = array_merge($best_grade_carrier[$id_carrier]['product_list'], $packages[$id_package]['product_list']);
  1766. $best_grade_carrier[$id_carrier]['instance'] = $carriers_instance[$id_carrier];
  1767. }
  1768. // Add the delivery option with best grade as best grade
  1769. if (!isset($delivery_option_list[$id_address][$key]))
  1770. $delivery_option_list[$id_address][$key] = array(
  1771. 'carrier_list' => $best_grade_carrier,
  1772. 'is_best_price' => false,
  1773. 'unique_carrier' => (count($best_grade_carrier) <= 1)
  1774. );
  1775. $delivery_option_list[$id_address][$key]['is_best_grade'] = true;
  1776. // Get all delivery options with a unique carrier
  1777. foreach ($common_carriers as $id_carrier)
  1778. {
  1779. $key = '';
  1780. $package_list = array();
  1781. $product_list = array();
  1782. $price_with_tax = 0;
  1783. $price_without_tax = 0;
  1784. foreach ($packages as $id_package => $package)
  1785. {
  1786. $key .= $id_carrier.',';
  1787. $price_with_tax += $carriers_price[$id_address][$id_package][$id_carrier]['with_tax'];
  1788. $price_without_tax += $carriers_price[$id_address][$id_package][$id_carrier]['without_tax'];
  1789. $package_list[] = $id_package;
  1790. $product_list = array_merge($product_list, $package['product_list']);
  1791. }
  1792. if (!isset($delivery_option_list[$id_address][$key]))
  1793. $delivery_option_list[$id_address][$key] = array(
  1794. 'is_best_price' => false,
  1795. 'is_best_grade' => false,
  1796. 'unique_carrier' => true,
  1797. 'carrier_list' => array(
  1798. $id_carrier => array(
  1799. 'price_with_tax' => $price_with_tax,
  1800. 'price_without_tax' => $price_without_tax,
  1801. 'instance' => $carriers_instance[$id_carrier],
  1802. 'package_list' => $package_list,
  1803. 'product_list' => $product_list,
  1804. )
  1805. )
  1806. );
  1807. else
  1808. $delivery_option_list[$id_address][$key]['unique_carrier'] = (count($delivery_option_list[$id_address][$key]['carrier_list']) <= 1);
  1809. }
  1810. }
  1811. $cart_rules = CartRule::getCustomerCartRules(Context::getContext()->cookie->id_lang, Context::getContext()->cookie->id_customer, true, true, false, $this);
  1812. $free_carriers_rules = array();
  1813. foreach ($cart_rules as $cart_rule)
  1814. {
  1815. if ($cart_rule['free_shipping'] && $cart_rule['carrier_restriction'])
  1816. {
  1817. $cr = new CartRule((int)$cart_rule['id_cart_rule']);
  1818. if (Validate::isLoadedObject($cr))
  1819. {
  1820. $carriers = $cr->getAssociatedRestrictions('carrier', true, false);
  1821. if (is_array($carriers) && count($carriers) && isset($carriers['selected']))
  1822. foreach($carriers['selected'] as $carrier)
  1823. if (isset($carrier['id_carrier']) && $carrier['id_carrier'])
  1824. $free_carriers_rules[] = (int)$carrier['id_carrier'];
  1825. }
  1826. }
  1827. }
  1828. // For each delivery options :
  1829. // - Set the carrier list
  1830. // - Calculate the price
  1831. // - Calculate the average position
  1832. foreach ($delivery_option_list as $id_address => $delivery_option)
  1833. foreach ($delivery_option as $key => $value)
  1834. {
  1835. $total_price_with_tax = 0;
  1836. $total_price_without_tax = 0;
  1837. $position = 0;
  1838. foreach ($value['carrier_list'] as $id_carrier => $data)
  1839. {
  1840. $total_price_with_tax += $data['price_with_tax'];
  1841. $total_price_without_tax += $data['price_without_tax'];
  1842. $total_price_without_tax_with_rules = (in_array($id_carrier, $free_carriers_rules)) ? 0 : $total_price_without_tax ;
  1843. if (!isset($carrier_collection[$id_carrier]))
  1844. $carrier_collection[$id_carrier] = new Carrier($id_carrier);
  1845. $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['instance'] = $carrier_collection[$id_carrier];
  1846. if (file_exists(_PS_SHIP_IMG_DIR_.$id_carrier.'.jpg'))
  1847. $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['logo'] = _THEME_SHIP_DIR_.$id_carrier.'.jpg';
  1848. else
  1849. $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['logo'] = false;
  1850. $position += $carrier_collection[$id_carrier]->position;
  1851. }
  1852. $delivery_option_list[$id_address][$key]['total_price_with_tax'] = $total_price_with_tax;
  1853. $delivery_option_list[$id_address][$key]['total_price_without_tax'] = $total_price_without_tax;
  1854. $delivery_option_list[$id_address][$key]['is_free'] = !$total_price_without_tax_with_rules ? true : false;
  1855. $delivery_option_list[$id_address][$key]['position'] = $position / count($value['carrier_list']);
  1856. }
  1857. // Sort delivery option list
  1858. foreach ($delivery_option_list as &$array)
  1859. uasort ($array, array('Cart', 'sortDeliveryOptionList'));
  1860. $cache = $delivery_option_list;
  1861. return $delivery_option_list;
  1862. }
  1863. /**
  1864. *
  1865. * Sort list of option delivery by parameters define in the BO
  1866. * @param $option1
  1867. * @param $option2
  1868. * @return int -1 if $option 1 must be placed before and 1 if the $option1 must be placed after the $option2
  1869. */
  1870. public static function sortDeliveryOptionList($option1, $option2)
  1871. {
  1872. static $order_by_price = null;
  1873. static $order_way = null;
  1874. if (is_null($order_by_price))
  1875. $order_by_price = !Configuration::get('PS_CARRIER_DEFAULT_SORT');
  1876. if (is_null($order_way))
  1877. $order_way = Configuration::get('PS_CARRIER_DEFAULT_ORDER');
  1878. if ($order_by_price)
  1879. if ($order_way)
  1880. return ($option1['total_price_with_tax'] < $option2['total_price_with_tax']) * 2 - 1; // return -1 or 1
  1881. else
  1882. return ($option1['total_price_with_tax'] >= $option2['total_price_with_tax']) * 2 - 1; // return -1 or 1
  1883. else
  1884. if ($order_way)
  1885. return ($option1['position'] < $option2['position']) * 2 - 1; // return -1 or 1
  1886. else
  1887. return ($option1['position'] >= $option2['position']) * 2 - 1; // return -1 or 1
  1888. }
  1889. public function carrierIsSelected($id_carrier, $id_address)
  1890. {
  1891. $delivery_option = $this->getDeliveryOption();
  1892. $delivery_option_list = $this->getDeliveryOptionList();
  1893. if (!isset($delivery_option[$id_address]))
  1894. return false;
  1895. if (!isset($delivery_option_list[$id_address][$delivery_option[$id_address]]))
  1896. return false;
  1897. if (!in_array($id_carrier, array_keys($delivery_option_list[$id_address][$delivery_option[$id_address]]['carrier_list'])))
  1898. return false;
  1899. return true;
  1900. }
  1901. /**
  1902. * Get all deliveries options available for the current cart formated like Carriers::getCarriersForOrder
  1903. * This method was wrote for retrocompatibility with 1.4 theme
  1904. * New theme need to use Cart::getDeliveryOptionList() to generate carriers option in the checkout process
  1905. *
  1906. * @since 1.5.0
  1907. *
  1908. * @param Country $default_country
  1909. * @param boolean $flush Force flushing cache
  1910. *
  1911. */
  1912. public function simulateCarriersOutput(Country $default_country = null, $flush = false)
  1913. {
  1914. static $cache = false;
  1915. if ($cache !== false && !$flush)
  1916. return $cache;
  1917. $delivery_option_list = $this->getDeliveryOptionList($default_country, $flush);
  1918. // This method cannot work if there is multiple address delivery
  1919. if (count($delivery_option_list) > 1 || empty($delivery_option_list))
  1920. return array();
  1921. $carriers = array();
  1922. foreach (reset($delivery_option_list) as $key => $option)
  1923. {
  1924. $price = $option['total_price_with_tax'];
  1925. $price_tax_exc = $option['total_price_without_tax'];
  1926. if ($option['unique_carrier'])
  1927. {
  1928. $carrier = reset($option['carrier_list']);
  1929. $name = $carrier['instance']->name;
  1930. $img = $carrier['logo'];
  1931. $delay = $carrier['instance']->delay;
  1932. $delay = isset($delay[Context::getContext()->language->id]) ? $delay[Context::getContext()->language->id] : $delay[(int)Configuration::get('PS_LANG_DEFAULT')];
  1933. }
  1934. else
  1935. {
  1936. $nameList = array();
  1937. foreach ($option['carrier_list'] as $carrier)
  1938. $nameList[] = $carrier['instance']->name;
  1939. $name = join(' -', $nameList);
  1940. $img = ''; // No images if multiple carriers
  1941. $delay = '';
  1942. }
  1943. $carriers[] = array(
  1944. 'name' => $name,
  1945. 'img' => $img,
  1946. 'delay' => $delay,
  1947. 'price' => $price,
  1948. 'price_tax_exc' => $price_tax_exc,
  1949. 'id_carrier' => Cart::intifier($key), // Need to translate to an integer for retrocompatibility reason, in 1.4 template we used intval
  1950. 'is_module' => false,
  1951. );
  1952. }
  1953. return $carriers;
  1954. }
  1955. public function simulateCarrierSelectedOutput($use_cache = true)
  1956. {
  1957. $delivery_option = $this->getDeliveryOption(null, false, $use_cache);
  1958. if (count($delivery_option) > 1 || empty($delivery_option))
  1959. return 0;
  1960. return Cart::intifier(reset($delivery_option));
  1961. }
  1962. /**
  1963. * Translate a string option_delivery identifier ('24,3,') in a int (3240002000)
  1964. *
  1965. * The option_delivery identifier is a list of integers separated by a ','.
  1966. * This method replace the delimiter by a sequence of '0'.
  1967. * The size of this sequence is fixed by the first digit of the return
  1968. *
  1969. * @return int
  1970. */
  1971. public static function intifier($string, $delimiter = ',')
  1972. {
  1973. $elm = explode($delimiter, $string);
  1974. $max = max($elm);
  1975. return strlen($max).implode(str_repeat('0', strlen($max) + 1), $elm);
  1976. }
  1977. /**
  1978. * Translate a int option_delivery identifier (3240002000) in a string ('24,3,')
  1979. */
  1980. public static function desintifier($int, $delimiter = ',')
  1981. {
  1982. $delimiter_len = $int[0];
  1983. $int = strrev(substr($int, 1));
  1984. $elm = explode(str_repeat('0', $delimiter_len + 1), $int);
  1985. return strrev(implode($delimiter, $elm));
  1986. }
  1987. /**
  1988. * Does the cart use multiple address
  1989. * @return boolean
  1990. */
  1991. public function isMultiAddressDelivery()
  1992. {
  1993. static $cache = null;
  1994. if (is_null($cache))
  1995. {
  1996. $sql = new DbQuery();
  1997. $sql->select('count(distinct id_address_delivery)');
  1998. $sql->from('cart_product', 'cp');
  1999. $sql->where('id_cart = '.(int)$this->id);
  2000. $cache = Db::getInstance()->getValue($sql) > 1;
  2001. }
  2002. return $cache;
  2003. }
  2004. /**
  2005. * Get all delivery addresses object for the current cart
  2006. */
  2007. public function getAddressCollection()
  2008. {
  2009. $collection = array();
  2010. $cache_id = 'Cart::getAddressCollection'.(int)$this->id;
  2011. if (!Cache::isStored($cache_id))
  2012. {
  2013. $result = Db::getInstance()->executeS(
  2014. 'SELECT DISTINCT `id_address_delivery`
  2015. FROM `'._DB_PREFIX_.'cart_product`
  2016. WHERE id_cart = '.(int)$this->id
  2017. );
  2018. Cache::store($cache_id, $result);
  2019. }
  2020. $result = Cache::retrieve($cache_id);
  2021. $result[] = array('id_address_delivery' => (int)$this->id_address_delivery);
  2022. foreach ($result as $row)
  2023. if ((int)$row['id_address_delivery'] != 0)
  2024. $collection[(int)$row['id_address_delivery']] = new Address((int)$row['id_address_delivery']);
  2025. return $collection;
  2026. }
  2027. /**
  2028. * Set the delivery option and id_carrier, if there is only one carrier
  2029. */
  2030. public function setDeliveryOption($delivery_option = null)
  2031. {
  2032. if (empty($delivery_option) || count($delivery_option) == 0)
  2033. {
  2034. $this->delivery_option = '';
  2035. $this->id_carrier = 0;
  2036. return;
  2037. }
  2038. Cache::clean('getContextualValue_*');
  2039. $delivery_option_list = $this->getDeliveryOptionList(null, true);
  2040. foreach ($delivery_option_list as $id_address => $options)
  2041. if (!isset($delivery_option[$id_address]))
  2042. foreach ($options as $key => $option)
  2043. if ($option['is_best_price'])
  2044. {
  2045. $delivery_option[$id_address] = $key;
  2046. break;
  2047. }
  2048. if (count($delivery_option) == 1)
  2049. $this->id_carrier = $this->getIdCarrierFromDeliveryOption($delivery_option);
  2050. $this->delivery_option = serialize($delivery_option);
  2051. }
  2052. protected function getIdCarrierFromDeliveryOption($delivery_option)
  2053. {
  2054. $delivery_option_list = $this->getDeliveryOptionList();
  2055. foreach ($delivery_option as $key => $value)
  2056. if (isset($delivery_option_list[$key]) && isset($delivery_option_list[$key][$value]))
  2057. if (count($delivery_option_list[$key][$value]['carrier_list']) == 1)
  2058. return current(array_keys($delivery_option_list[$key][$value]['carrier_list']));
  2059. return 0;
  2060. }
  2061. /**
  2062. * Get the delivery option seleted, or if no delivery option was selected, the cheapest option for each address
  2063. * @return array delivery option
  2064. */
  2065. public function getDeliveryOption($default_country = null, $dontAutoSelectOptions = false, $use_cache = true)
  2066. {
  2067. static $cache = array();
  2068. $cache_id = (int)(is_object($default_country) ? $default_country->id : 0).'-'.(int)$dontAutoSelectOptions;
  2069. if (isset($cache[$cache_id]) && $use_cache)
  2070. return $cache[$cache_id];
  2071. $delivery_option_list = $this->getDeliveryOptionList($default_country);
  2072. // The delivery option was selected
  2073. if (isset($this->delivery_option) && $this->delivery_option != '')
  2074. {
  2075. $delivery_option = Tools::unSerialize($this->delivery_option);
  2076. $validated = true;
  2077. foreach ($delivery_option as $id_address => $key)
  2078. if (!isset($delivery_option_list[$id_address][$key]))
  2079. {
  2080. $validated = false;
  2081. break;
  2082. }
  2083. if ($validated)
  2084. {
  2085. $cache[$cache_id] = $delivery_option;
  2086. return $delivery_option;
  2087. }
  2088. }
  2089. if ($dontAutoSelectOptions)
  2090. return false;
  2091. // No delivery option selected or delivery option selected is not valid, get the better for all options
  2092. $delivery_option = array();
  2093. foreach ($delivery_option_list as $id_address => $options)
  2094. {
  2095. foreach ($options as $key => $option)
  2096. if (Configuration::get('PS_CARRIER_DEFAULT') == -1 && $option['is_best_price'])
  2097. {
  2098. $delivery_option[$id_address] = $key;
  2099. break;
  2100. }
  2101. elseif (Configuration::get('PS_CARRIER_DEFAULT') == -2 && $option['is_best_grade'])
  2102. {
  2103. $delivery_option[$id_address] = $key;
  2104. break;
  2105. }
  2106. elseif ($option['unique_carrier'] && in_array(Configuration::get('PS_CARRIER_DEFAULT'), array_keys($option['carrier_list'])))
  2107. {
  2108. $delivery_option[$id_address] = $key;
  2109. break;
  2110. }
  2111. reset($options);
  2112. if (!isset($delivery_option[$id_address]))
  2113. $delivery_option[$id_address] = key($options);
  2114. }
  2115. $cache[$cache_id] = $delivery_option;
  2116. return $delivery_option;
  2117. }
  2118. /**
  2119. * Return shipping total for the cart
  2120. *
  2121. * @param array $delivery_option Array of the delivery option for each address
  2122. * @param booleal $use_tax
  2123. * @param Country $default_country
  2124. * @return float Shipping total
  2125. */
  2126. public function getTotalShippingCost($delivery_option = null, $use_tax = true, Country $default_country = null)
  2127. {
  2128. if(isset(Context::getContext()->cookie->id_country))
  2129. $default_country = new Country(Context::getContext()->cookie->id_country);
  2130. if (is_null($delivery_option))
  2131. $delivery_option = $this->getDeliveryOption($default_country, false, false);
  2132. $total_shipping = 0;
  2133. $delivery_option_list = $this->getDeliveryOptionList($default_country);
  2134. foreach ($delivery_option as $id_address => $key)
  2135. {
  2136. if (!isset($delivery_option_list[$id_address]) || !isset($delivery_option_list[$id_address][$key]))
  2137. continue;
  2138. if ($use_tax)
  2139. $total_shipping += $delivery_option_list[$id_address][$key]['total_price_with_tax'];
  2140. else
  2141. $total_shipping += $delivery_option_list[$id_address][$key]['total_price_without_tax'];
  2142. }
  2143. return $total_shipping;
  2144. }
  2145. /**
  2146. * Return shipping total of a specific carriers for the cart
  2147. *
  2148. * @param int $id_carrier
  2149. * @param array $delivery_option Array of the delivery option for each address
  2150. * @param booleal $useTax
  2151. * @param Country $default_country
  2152. * @return float Shipping total
  2153. */
  2154. public function getCarrierCost($id_carrier, $useTax = true, Country $default_country = null, $delivery_option = null)
  2155. {
  2156. if (is_null($delivery_option))
  2157. $delivery_option = $this->getDeliveryOption($default_country);
  2158. $total_shipping = 0;
  2159. $delivery_option_list = $this->getDeliveryOptionList();
  2160. foreach ($delivery_option as $id_address => $key)
  2161. {
  2162. if (!isset($delivery_option_list[$id_address]) || !isset($delivery_option_list[$id_address][$key]))
  2163. continue;
  2164. if (isset($delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]))
  2165. {
  2166. if ($useTax)
  2167. $total_shipping += $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['price_with_tax'];
  2168. else
  2169. $total_shipping += $delivery_option_list[$id_address][$key]['carrier_list'][$id_carrier]['price_without_tax'];
  2170. }
  2171. }
  2172. return $total_shipping;
  2173. }
  2174. /**
  2175. * @deprecated 1.5.0, use Cart->getPackageShippingCost()
  2176. */
  2177. public function getOrderShippingCost($id_carrier = null, $use_tax = true, Country $default_country = null, $product_list = null)
  2178. {
  2179. Tools::displayAsDeprecated();
  2180. return $this->getPackageShippingCost($id_carrier, $use_tax, $default_country, $product_list);
  2181. }
  2182. /**
  2183. * Return package shipping cost
  2184. *
  2185. * @param integer $id_carrier Carrier ID (default : current carrier)
  2186. * @param booleal $use_tax
  2187. * @param Country $default_country
  2188. * @param Array $product_list
  2189. * @param array $product_list List of product concerned by the shipping. If null, all the product of the cart are used to calculate the shipping cost
  2190. *
  2191. * @return float Shipping total
  2192. */
  2193. public function getPackageShippingCost($id_carrier = null, $use_tax = true, Country $default_country = null, $product_list = null, $id_zone = null)
  2194. {
  2195. if ($this->isVirtualCart())
  2196. return 0;
  2197. if (!$default_country)
  2198. $default_country = Context::getContext()->country;
  2199. $complete_product_list = $this->getProducts();
  2200. if (is_null($product_list))
  2201. $products = $complete_product_list;
  2202. else
  2203. $products = $product_list;
  2204. if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice')
  2205. $address_id = (int)$this->id_address_invoice;
  2206. elseif (count($product_list))
  2207. {
  2208. $prod = current($product_list);
  2209. $address_id = (int)$prod['id_address_delivery'];
  2210. }
  2211. else
  2212. $address_id = null;
  2213. if (!Address::addressExists($address_id))
  2214. $address_id = null;
  2215. $cache_id = 'getPackageShippingCost_'.(int)$this->id.'_'.(int)$address_id.'_'.(int)$id_carrier.'_'.(int)$use_tax.'_'.(int)$default_country->id;
  2216. if ($products)
  2217. foreach ($products as $product)
  2218. $cache_id .= '_'.(int)$product['id_product'].'_'.(int)$product['id_product_attribute'];
  2219. if (Cache::isStored($cache_id))
  2220. return Cache::retrieve($cache_id);
  2221. // Order total in default currency without fees
  2222. $order_total = $this->getOrderTotal(true, Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING, $product_list);
  2223. // Start with shipping cost at 0
  2224. $shipping_cost = 0;
  2225. // If no product added, return 0
  2226. if (!count($products))
  2227. {
  2228. Cache::store($cache_id, $shipping_cost);
  2229. return $shipping_cost;
  2230. }
  2231. if (!isset($id_zone))
  2232. {
  2233. // Get id zone
  2234. if (!$this->isMultiAddressDelivery()
  2235. && isset($this->id_address_delivery) // Be carefull, id_address_delivery is not usefull one 1.5
  2236. && $this->id_address_delivery
  2237. && Customer::customerHasAddress($this->id_customer, $this->id_address_delivery
  2238. ))
  2239. $id_zone = Address::getZoneById((int)$this->id_address_delivery);
  2240. else
  2241. {
  2242. if (!Validate::isLoadedObject($default_country))
  2243. $default_country = new Country(Configuration::get('PS_COUNTRY_DEFAULT'), Configuration::get('PS_LANG_DEFAULT'));
  2244. $id_zone = (int)$default_country->id_zone;
  2245. }
  2246. }
  2247. if ($id_carrier && !$this->isCarrierInRange((int)$id_carrier, (int)$id_zone))
  2248. $id_carrier = '';
  2249. if (empty($id_carrier) && $this->isCarrierInRange((int)Configuration::get('PS_CARRIER_DEFAULT'), (int)$id_zone))
  2250. $id_carrier = (int)Configuration::get('PS_CARRIER_DEFAULT');
  2251. $total_package_without_shipping_tax_inc = $this->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING, $product_list);
  2252. if (empty($id_carrier))
  2253. {
  2254. if ((int)$this->id_customer)
  2255. {
  2256. $customer = new Customer((int)$this->id_customer);
  2257. $result = Carrier::getCarriers((int)Configuration::get('PS_LANG_DEFAULT'), true, false, (int)$id_zone, $customer->getGroups());
  2258. unset($customer);
  2259. }
  2260. else
  2261. $result = Carrier::getCarriers((int)Configuration::get('PS_LANG_DEFAULT'), true, false, (int)$id_zone);
  2262. foreach ($result as $k => $row)
  2263. {
  2264. if ($row['id_carrier'] == Configuration::get('PS_CARRIER_DEFAULT'))
  2265. continue;
  2266. if (!isset(self::$_carriers[$row['id_carrier']]))
  2267. self::$_carriers[$row['id_carrier']] = new Carrier((int)$row['id_carrier']);
  2268. $carrier = self::$_carriers[$row['id_carrier']];
  2269. // Get only carriers that are compliant with shipping method
  2270. if (($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_WEIGHT && $carrier->getMaxDeliveryPriceByWeight((int)$id_zone) === false)
  2271. || ($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_PRICE && $carrier->getMaxDeliveryPriceByPrice((int)$id_zone) === false))
  2272. {
  2273. unset($result[$k]);
  2274. continue;
  2275. }
  2276. // If out-of-range behavior carrier is set on "Desactivate carrier"
  2277. if ($row['range_behavior'])
  2278. {
  2279. $check_delivery_price_by_weight = Carrier::checkDeliveryPriceByWeight($row['id_carrier'], $this->getTotalWeight(), (int)$id_zone);
  2280. $total_order = $total_package_without_shipping_tax_inc;
  2281. $check_delivery_price_by_price = Carrier::checkDeliveryPriceByPrice($row['id_carrier'], $total_order, (int)$id_zone, (int)$this->id_currency);
  2282. // Get only carriers that have a range compatible with cart
  2283. if (($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_WEIGHT && !$check_delivery_price_by_weight)
  2284. || ($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_PRICE && !$check_delivery_price_by_price))
  2285. {
  2286. unset($result[$k]);
  2287. continue;
  2288. }
  2289. }
  2290. if ($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_WEIGHT)
  2291. $shipping = $carrier->getDeliveryPriceByWeight($this->getTotalWeight($product_list), (int)$id_zone);
  2292. else
  2293. $shipping = $carrier->getDeliveryPriceByPrice($order_total, (int)$id_zone, (int)$this->id_currency);
  2294. if (!isset($min_shipping_price))
  2295. $min_shipping_price = $shipping;
  2296. if ($shipping <= $min_shipping_price)
  2297. {
  2298. $id_carrier = (int)$row['id_carrier'];
  2299. $min_shipping_price = $shipping;
  2300. }
  2301. }
  2302. }
  2303. if (empty($id_carrier))
  2304. $id_carrier = Configuration::get('PS_CARRIER_DEFAULT');
  2305. if (!isset(self::$_carriers[$id_carrier]))
  2306. self::$_carriers[$id_carrier] = new Carrier((int)$id_carrier, Configuration::get('PS_LANG_DEFAULT'));
  2307. $carrier = self::$_carriers[$id_carrier];
  2308. // No valid Carrier or $id_carrier <= 0 ?
  2309. if (!Validate::isLoadedObject($carrier))
  2310. {
  2311. Cache::store($cache_id, 0);
  2312. return 0;
  2313. }
  2314. if (!$carrier->active)
  2315. {
  2316. Cache::store($cache_id, $shipping_cost);
  2317. return $shipping_cost;
  2318. }
  2319. // Free fees if free carrier
  2320. if ($carrier->is_free == 1)
  2321. {
  2322. Cache::store($cache_id, 0);
  2323. return 0;
  2324. }
  2325. // Select carrier tax
  2326. if ($use_tax && !Tax::excludeTaxeOption())
  2327. {
  2328. $address = Address::initialize((int)$address_id);
  2329. $carrier_tax = $carrier->getTaxesRate($address);
  2330. }
  2331. $configuration = Configuration::getMultiple(array(
  2332. 'PS_SHIPPING_FREE_PRICE',
  2333. 'PS_SHIPPING_HANDLING',
  2334. 'PS_SHIPPING_METHOD',
  2335. 'PS_SHIPPING_FREE_WEIGHT'
  2336. ));
  2337. // Free fees
  2338. $free_fees_price = 0;
  2339. if (isset($configuration['PS_SHIPPING_FREE_PRICE']))
  2340. $free_fees_price = Tools::convertPrice((float)$configuration['PS_SHIPPING_FREE_PRICE'], Currency::getCurrencyInstance((int)$this->id_currency));
  2341. $orderTotalwithDiscounts = $this->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING, null, null, false);
  2342. if ($orderTotalwithDiscounts >= (float)($free_fees_price) && (float)($free_fees_price) > 0)
  2343. {
  2344. Cache::store($cache_id, $shipping_cost);
  2345. return $shipping_cost;
  2346. }
  2347. if (isset($configuration['PS_SHIPPING_FREE_WEIGHT'])
  2348. && $this->getTotalWeight() >= (float)$configuration['PS_SHIPPING_FREE_WEIGHT']
  2349. && (float)$configuration['PS_SHIPPING_FREE_WEIGHT'] > 0)
  2350. {
  2351. Cache::store($cache_id, $shipping_cost);
  2352. return $shipping_cost;
  2353. }
  2354. // Get shipping cost using correct method
  2355. if ($carrier->range_behavior)
  2356. {
  2357. if(!isset($id_zone))
  2358. {
  2359. // Get id zone
  2360. if (isset($this->id_address_delivery)
  2361. && $this->id_address_delivery
  2362. && Customer::customerHasAddress($this->id_customer, $this->id_address_delivery))
  2363. $id_zone = Address::getZoneById((int)$this->id_address_delivery);
  2364. else
  2365. $id_zone = (int)$default_country->id_zone;
  2366. }
  2367. if (($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_WEIGHT && !Carrier::checkDeliveryPriceByWeight($carrier->id, $this->getTotalWeight(), (int)$id_zone))
  2368. || ($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_PRICE && !Carrier::checkDeliveryPriceByPrice($carrier->id, $total_package_without_shipping_tax_inc, $id_zone, (int)$this->id_currency)
  2369. ))
  2370. $shipping_cost += 0;
  2371. else
  2372. {
  2373. if ($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_WEIGHT)
  2374. $shipping_cost += $carrier->getDeliveryPriceByWeight($this->getTotalWeight($product_list), $id_zone);
  2375. else // by price
  2376. $shipping_cost += $carrier->getDeliveryPriceByPrice($order_total, $id_zone, (int)$this->id_currency);
  2377. }
  2378. }
  2379. else
  2380. {
  2381. if ($carrier->getShippingMethod() == Carrier::SHIPPING_METHOD_WEIGHT)
  2382. $shipping_cost += $carrier->getDeliveryPriceByWeight($this->getTotalWeight($product_list), $id_zone);
  2383. else
  2384. $shipping_cost += $carrier->getDeliveryPriceByPrice($order_total, $id_zone, (int)$this->id_currency);
  2385. }
  2386. // Adding handling charges
  2387. if (isset($configuration['PS_SHIPPING_HANDLING']) && $carrier->shipping_handling)
  2388. $shipping_cost += (float)$configuration['PS_SHIPPING_HANDLING'];
  2389. // Additional Shipping Cost per product
  2390. foreach ($products as $product)
  2391. if (!$product['is_virtual'])
  2392. $shipping_cost += $product['additional_shipping_cost'] * $product['cart_quantity'];
  2393. $shipping_cost = Tools::convertPrice($shipping_cost, Currency::getCurrencyInstance((int)$this->id_currency));
  2394. //get external shipping cost from module
  2395. if ($carrier->shipping_external)
  2396. {
  2397. $module_name = $carrier->external_module_name;
  2398. $module = Module::getInstanceByName($module_name);
  2399. if (Validate::isLoadedObject($module))
  2400. {
  2401. if (array_key_exists('id_carrier', $module))
  2402. $module->id_carrier = $carrier->id;
  2403. if ($carrier->need_range)
  2404. if (method_exists($module, 'getPackageShippingCost'))
  2405. $shipping_cost = $module->getPackageShippingCost($this, $shipping_cost, $products);
  2406. else
  2407. $shipping_cost = $module->getOrderShippingCost($this, $shipping_cost);
  2408. else
  2409. $shipping_cost = $module->getOrderShippingCostExternal($this);
  2410. // Check if carrier is available
  2411. if ($shipping_cost === false)
  2412. {
  2413. Cache::store($cache_id, false);
  2414. return false;
  2415. }
  2416. }
  2417. else
  2418. {
  2419. Cache::store($cache_id, false);
  2420. return false;
  2421. }
  2422. }
  2423. // Apply tax
  2424. if ($use_tax && isset($carrier_tax))
  2425. $shipping_cost *= 1 + ($carrier_tax / 100);
  2426. $shipping_cost = (float)Tools::ps_round((float)$shipping_cost, 2);
  2427. Cache::store($cache_id, $shipping_cost);
  2428. return $shipping_cost;
  2429. }
  2430. /**
  2431. * Return cart weight
  2432. * @return float Cart weight
  2433. */
  2434. public function getTotalWeight($products = null)
  2435. {
  2436. if (!is_null($products))
  2437. {
  2438. $total_weight = 0;
  2439. foreach ($products as $product)
  2440. {
  2441. if (!isset($product['weight_attribute']) || is_null($product['weight_attribute']))
  2442. $total_weight += $product['weight'] * $product['cart_quantity'];
  2443. else
  2444. $total_weight += $product['weight_attribute'] * $product['cart_quantity'];
  2445. }
  2446. return $total_weight;
  2447. }
  2448. if (!isset(self::$_totalWeight[$this->id]))
  2449. {
  2450. if (Combination::isFeatureActive())
  2451. $weight_product_with_attribute = Db::getInstance()->getValue('
  2452. SELECT SUM((p.`weight` + pa.`weight`) * cp.`quantity`) as nb
  2453. FROM `'._DB_PREFIX_.'cart_product` cp
  2454. LEFT JOIN `'._DB_PREFIX_.'product` p ON (cp.`id_product` = p.`id_product`)
  2455. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (cp.`id_product_attribute` = pa.`id_product_attribute`)
  2456. WHERE (cp.`id_product_attribute` IS NOT NULL AND cp.`id_product_attribute` != 0)
  2457. AND cp.`id_cart` = '.(int)$this->id);
  2458. else
  2459. $weight_product_with_attribute = 0;
  2460. $weight_product_without_attribute = Db::getInstance()->getValue('
  2461. SELECT SUM(p.`weight` * cp.`quantity`) as nb
  2462. FROM `'._DB_PREFIX_.'cart_product` cp
  2463. LEFT JOIN `'._DB_PREFIX_.'product` p ON (cp.`id_product` = p.`id_product`)
  2464. WHERE (cp.`id_product_attribute` IS NULL OR cp.`id_product_attribute` = 0)
  2465. AND cp.`id_cart` = '.(int)$this->id);
  2466. self::$_totalWeight[$this->id] = round((float)$weight_product_with_attribute + (float)$weight_product_without_attribute, 3);
  2467. }
  2468. return self::$_totalWeight[$this->id];
  2469. }
  2470. /**
  2471. * @deprecated 1.5.0
  2472. */
  2473. public function checkDiscountValidity($obj, $discounts, $order_total, $products, $check_cart_discount = false)
  2474. {
  2475. Tools::displayAsDeprecated();
  2476. $context = Context::getContext()->cloneContext();
  2477. $context->cart = $this;
  2478. return $obj->checkValidity($context);
  2479. }
  2480. /**
  2481. * Return useful informations for cart
  2482. *
  2483. * @return array Cart details
  2484. */
  2485. public function getSummaryDetails($id_lang = null, $refresh = false)
  2486. {
  2487. $context = Context::getContext();
  2488. if (!$id_lang)
  2489. $id_lang = $context->language->id;
  2490. $delivery = new Address((int)$this->id_address_delivery);
  2491. $invoice = new Address((int)$this->id_address_invoice);
  2492. // New layout system with personalization fields
  2493. $formatted_addresses = array(
  2494. 'delivery' => AddressFormat::getFormattedLayoutData($delivery),
  2495. 'invoice' => AddressFormat::getFormattedLayoutData($invoice)
  2496. );
  2497. $base_total_tax_inc = $this->getOrderTotal(true);
  2498. $base_total_tax_exc = $this->getOrderTotal(false);
  2499. $total_tax = $base_total_tax_inc - $base_total_tax_exc;
  2500. if ($total_tax < 0)
  2501. $total_tax = 0;
  2502. $currency = new Currency($this->id_currency);
  2503. $products = $this->getProducts($refresh);
  2504. $gift_products = array();
  2505. $cart_rules = $this->getCartRules();
  2506. $total_shipping = $this->getTotalShippingCost();
  2507. $total_shipping_tax_exc = $this->getTotalShippingCost(null, false);
  2508. $total_products_wt = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS);
  2509. $total_products = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS);
  2510. $total_discounts = $this->getOrderTotal(true, Cart::ONLY_DISCOUNTS);
  2511. $total_discounts_tax_exc = $this->getOrderTotal(false, Cart::ONLY_DISCOUNTS);
  2512. // The cart content is altered for display
  2513. foreach ($cart_rules as &$cart_rule)
  2514. {
  2515. // If the cart rule is automatic (wihtout any code) and include free shipping, it should not be displayed as a cart rule but only set the shipping cost to 0
  2516. if ($cart_rule['free_shipping'] && (empty($cart_rule['code']) || preg_match('/^'.CartRule::BO_ORDER_CODE_PREFIX.'[0-9]+/', $cart_rule['code'])))
  2517. {
  2518. $cart_rule['value_real'] -= $total_shipping;
  2519. $cart_rule['value_tax_exc'] -= $total_shipping_tax_exc;
  2520. $cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2521. $cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2522. if ($total_discounts > $cart_rule['value_real'])
  2523. $total_discounts -= $total_shipping;
  2524. if ($total_discounts_tax_exc > $cart_rule['value_tax_exc'])
  2525. $total_discounts_tax_exc -= $total_shipping_tax_exc;
  2526. // Update total shipping
  2527. $total_shipping = 0;
  2528. $total_shipping_tax_exc = 0;
  2529. }
  2530. if ($cart_rule['gift_product'])
  2531. {
  2532. foreach ($products as $key => &$product)
  2533. if (empty($product['gift']) && $product['id_product'] == $cart_rule['gift_product'] && $product['id_product_attribute'] == $cart_rule['gift_product_attribute'])
  2534. {
  2535. // Update total products
  2536. $total_products_wt = Tools::ps_round($total_products_wt - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2537. $total_products = Tools::ps_round($total_products - $product['price'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2538. // Update total discounts
  2539. $total_discounts = Tools::ps_round($total_discounts - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2540. $total_discounts_tax_exc = Tools::ps_round($total_discounts_tax_exc - $product['price'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2541. // Update cart rule value
  2542. $cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'] - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2543. $cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'] - $product['price'], (int)$context->currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2544. // Update product quantity
  2545. $product['total_wt'] = Tools::ps_round($product['total_wt'] - $product['price_wt'], (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2546. $product['total'] = Tools::ps_round($product['total'] - $product['price'], (int)$currency->decimals * _PS_PRICE_DISPLAY_PRECISION_);
  2547. $product['cart_quantity']--;
  2548. if (!$product['cart_quantity'])
  2549. unset($products[$key]);
  2550. // Add a new product line
  2551. $gift_product = $product;
  2552. $gift_product['cart_quantity'] = 1;
  2553. $gift_product['price'] = 0;
  2554. $gift_product['price_wt'] = 0;
  2555. $gift_product['total_wt'] = 0;
  2556. $gift_product['total'] = 0;
  2557. $gift_product['gift'] = true;
  2558. $gift_products[] = $gift_product;
  2559. break; // One gift product per cart rule
  2560. }
  2561. }
  2562. }
  2563. foreach ($cart_rules as $key => &$cart_rule)
  2564. if ($cart_rule['value_real'] == 0)
  2565. unset($cart_rules[$key]);
  2566. return array(
  2567. 'delivery' => $delivery,
  2568. 'delivery_state' => State::getNameById($delivery->id_state),
  2569. 'invoice' => $invoice,
  2570. 'invoice_state' => State::getNameById($invoice->id_state),
  2571. 'formattedAddresses' => $formatted_addresses,
  2572. 'products' => array_values($products),
  2573. 'gift_products' => $gift_products,
  2574. 'discounts' => array_values($cart_rules),
  2575. 'is_virtual_cart' => (int)$this->isVirtualCart(),
  2576. 'total_discounts' => $total_discounts,
  2577. 'total_discounts_tax_exc' => $total_discounts_tax_exc,
  2578. 'total_wrapping' => $this->getOrderTotal(true, Cart::ONLY_WRAPPING),
  2579. 'total_wrapping_tax_exc' => $this->getOrderTotal(false, Cart::ONLY_WRAPPING),
  2580. 'total_shipping' => $total_shipping,
  2581. 'total_shipping_tax_exc' => $total_shipping_tax_exc,
  2582. 'total_products_wt' => $total_products_wt,
  2583. 'total_products' => $total_products,
  2584. 'total_price' => $base_total_tax_inc,
  2585. 'total_tax' => $total_tax,
  2586. 'total_price_without_tax' => $base_total_tax_exc,
  2587. 'is_multi_address_delivery' => $this->isMultiAddressDelivery() || ((int)Tools::getValue('multi-shipping') == 1),
  2588. 'free_ship' => $total_shipping ? 0 : 1,
  2589. 'carrier' => new Carrier($this->id_carrier, $id_lang),
  2590. );
  2591. }
  2592. public function checkQuantities()
  2593. {
  2594. if (Configuration::get('PS_CATALOG_MODE'))
  2595. return false;
  2596. foreach ($this->getProducts() as $product)
  2597. if (!$product['active'] || !$product['available_for_order']
  2598. || (!$product['allow_oosp'] && $product['stock_quantity'] < $product['cart_quantity']))
  2599. return false;
  2600. return true;
  2601. }
  2602. public static function lastNoneOrderedCart($id_customer)
  2603. {
  2604. $sql = 'SELECT c.`id_cart`
  2605. FROM '._DB_PREFIX_.'cart c
  2606. WHERE c.`id_cart` NOT IN (SELECT o.`id_cart` FROM '._DB_PREFIX_.'orders o WHERE o.`id_customer` = '.(int)$id_customer.')
  2607. AND c.`id_customer` = '.(int)$id_customer.'
  2608. '.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'c').'
  2609. ORDER BY c.`date_upd` DESC';
  2610. if (!$id_cart = Db::getInstance()->getValue($sql))
  2611. return false;
  2612. return (int)$id_cart;
  2613. }
  2614. /**
  2615. * Check if cart contains only virtual products
  2616. *
  2617. * @return boolean true if is a virtual cart or false
  2618. */
  2619. public function isVirtualCart($strict = false)
  2620. {
  2621. if (!ProductDownload::isFeatureActive())
  2622. return false;
  2623. if (!isset(self::$_isVirtualCart[$this->id]))
  2624. {
  2625. $products = $this->getProducts();
  2626. if (!count($products))
  2627. return false;
  2628. $is_virtual = 1;
  2629. foreach ($products as $product)
  2630. {
  2631. if (empty($product['is_virtual']))
  2632. $is_virtual = 0;
  2633. }
  2634. self::$_isVirtualCart[$this->id] = (int)$is_virtual;
  2635. }
  2636. return self::$_isVirtualCart[$this->id];
  2637. }
  2638. /**
  2639. * Build cart object from provided id_order
  2640. *
  2641. * @param int $id_order
  2642. * @return Cart|bool
  2643. */
  2644. public static function getCartByOrderId($id_order)
  2645. {
  2646. if ($id_cart = Cart::getCartIdByOrderId($id_order))
  2647. return new Cart((int)$id_cart);
  2648. return false;
  2649. }
  2650. public static function getCartIdByOrderId($id_order)
  2651. {
  2652. $result = Db::getInstance()->getRow('SELECT `id_cart` FROM '._DB_PREFIX_.'orders WHERE `id_order` = '.(int)$id_order);
  2653. if (!$result || empty($result) || !array_key_exists('id_cart', $result))
  2654. return false;
  2655. return $result['id_cart'];
  2656. }
  2657. /**
  2658. * Add customer's text
  2659. *
  2660. * @params int $id_product
  2661. * @params int $index
  2662. * @params int $type
  2663. * @params string $textValue
  2664. *
  2665. * @return bool Always true
  2666. */
  2667. public function addTextFieldToProduct($id_product, $index, $type, $text_value)
  2668. {
  2669. return $this->_addCustomization($id_product, 0, $index, $type, $text_value, 0);
  2670. }
  2671. /**
  2672. * Add customer's pictures
  2673. *
  2674. * @return bool Always true
  2675. */
  2676. public function addPictureToProduct($id_product, $index, $type, $file)
  2677. {
  2678. return $this->_addCustomization($id_product, 0, $index, $type, $file, 0);
  2679. }
  2680. public function deletePictureToProduct($id_product, $index)
  2681. {
  2682. Tools::displayAsDeprecated();
  2683. return $this->deleteCustomizationToProduct($id_product, 0);
  2684. }
  2685. /**
  2686. * Remove a customer's customization
  2687. *
  2688. * @param int $id_product
  2689. * @param int $index
  2690. * @return bool
  2691. */
  2692. public function deleteCustomizationToProduct($id_product, $index)
  2693. {
  2694. $result = true;
  2695. $cust_data = Db::getInstance()->getRow('
  2696. SELECT cu.`id_customization`, cd.`index`, cd.`value`, cd.`type` FROM `'._DB_PREFIX_.'customization` cu
  2697. LEFT JOIN `'._DB_PREFIX_.'customized_data` cd
  2698. ON cu.`id_customization` = cd.`id_customization`
  2699. WHERE cu.`id_cart` = '.(int)$this->id.'
  2700. AND cu.`id_product` = '.(int)$id_product.'
  2701. AND `index` = '.(int)$index.'
  2702. AND `in_cart` = 0'
  2703. );
  2704. // Delete customization picture if necessary
  2705. if ($cust_data['type'] == 0)
  2706. $result &= (@unlink(_PS_UPLOAD_DIR_.$cust_data['value']) && @unlink(_PS_UPLOAD_DIR_.$cust_data['value'].'_small'));
  2707. $result &= Db::getInstance()->execute('DELETE
  2708. FROM `'._DB_PREFIX_.'customized_data`
  2709. WHERE `id_customization` = '.(int)$cust_data['id_customization'].'
  2710. AND `index` = '.(int)$index
  2711. );
  2712. return $result;
  2713. }
  2714. /**
  2715. * Return custom pictures in this cart for a specified product
  2716. *
  2717. * @param int $id_product
  2718. * @param int $type only return customization of this type
  2719. * @param bool $not_in_cart only return customizations that are not in cart already
  2720. * @return array result rows
  2721. */
  2722. public function getProductCustomization($id_product, $type = null, $not_in_cart = false)
  2723. {
  2724. if (!Customization::isFeatureActive())
  2725. return array();
  2726. $result = Db::getInstance()->executeS('
  2727. SELECT cu.id_customization, cd.index, cd.value, cd.type, cu.in_cart, cu.quantity
  2728. FROM `'._DB_PREFIX_.'customization` cu
  2729. LEFT JOIN `'._DB_PREFIX_.'customized_data` cd ON (cu.`id_customization` = cd.`id_customization`)
  2730. WHERE cu.id_cart = '.(int)$this->id.'
  2731. AND cu.id_product = '.(int)$id_product.
  2732. ($type === Product::CUSTOMIZE_FILE ? ' AND type = '.(int)Product::CUSTOMIZE_FILE : '').
  2733. ($type === Product::CUSTOMIZE_TEXTFIELD ? ' AND type = '.(int)Product::CUSTOMIZE_TEXTFIELD : '').
  2734. ($not_in_cart ? ' AND in_cart = 0' : '')
  2735. );
  2736. return $result;
  2737. }
  2738. public static function getCustomerCarts($id_customer, $with_order = true)
  2739. {
  2740. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  2741. SELECT *
  2742. FROM '._DB_PREFIX_.'cart c
  2743. WHERE c.`id_customer` = '.(int)$id_customer.'
  2744. '.(!$with_order ? 'AND id_cart NOT IN (SELECT id_cart FROM '._DB_PREFIX_.'orders o)' : '').'
  2745. ORDER BY c.`date_add` DESC');
  2746. }
  2747. public static function replaceZeroByShopName($echo, $tr)
  2748. {
  2749. return ($echo == '0' ? Configuration::get('PS_SHOP_NAME') : $echo);
  2750. }
  2751. public function duplicate()
  2752. {
  2753. if (!Validate::isLoadedObject($this))
  2754. return false;
  2755. $cart = new Cart($this->id);
  2756. $cart->id = null;
  2757. $cart->id_shop = $this->id_shop;
  2758. $cart->id_shop_group = $this->id_shop_group;
  2759. if (!Customer::customerHasAddress((int)$cart->id_customer, (int)$cart->id_address_delivery))
  2760. $cart->id_address_delivery = (int)Address::getFirstCustomerAddressId((int)$cart->id_customer);
  2761. if (!Customer::customerHasAddress((int)$cart->id_customer, (int)$cart->id_address_invoice))
  2762. $cart->id_address_invoice = (int)Address::getFirstCustomerAddressId((int)$cart->id_customer);
  2763. $cart->add();
  2764. if (!Validate::isLoadedObject($cart))
  2765. return false;
  2766. $success = true;
  2767. $products = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('SELECT * FROM `'._DB_PREFIX_.'cart_product` WHERE `id_cart` = '.(int)$this->id);
  2768. $id_address_delivery = Configuration::get('PS_ALLOW_MULTISHIPPING') ? $cart->id_address_delivery : 0;
  2769. foreach ($products as $product)
  2770. {
  2771. if ($id_address_delivery)
  2772. {
  2773. if (Customer::customerHasAddress((int)$cart->id_customer, $product['id_address_delivery']))
  2774. $id_address_delivery = $product['id_address_delivery'];
  2775. }
  2776. $success &= $cart->updateQty(
  2777. $product['quantity'],
  2778. (int)$product['id_product'],
  2779. (int)$product['id_product_attribute'],
  2780. null,
  2781. 'up',
  2782. (int)$id_address_delivery,
  2783. new Shop($cart->id_shop)
  2784. );
  2785. }
  2786. // Customized products
  2787. $customs = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  2788. SELECT *
  2789. FROM '._DB_PREFIX_.'customization c
  2790. LEFT JOIN '._DB_PREFIX_.'customized_data cd ON cd.id_customization = c.id_customization
  2791. WHERE c.id_cart = '.(int)$this->id
  2792. );
  2793. // Get datas from customization table
  2794. $customs_by_id = array();
  2795. foreach ($customs as $custom)
  2796. {
  2797. if (!isset($customs_by_id[$custom['id_customization']]))
  2798. $customs_by_id[$custom['id_customization']] = array(
  2799. 'id_product_attribute' => $custom['id_product_attribute'],
  2800. 'id_product' => $custom['id_product'],
  2801. 'quantity' => $custom['quantity']
  2802. );
  2803. }
  2804. // Insert new customizations
  2805. $custom_ids = array();
  2806. foreach ($customs_by_id as $customization_id => $val)
  2807. {
  2808. Db::getInstance()->execute('
  2809. INSERT INTO `'._DB_PREFIX_.'customization` (id_cart, id_product_attribute, id_product, `id_address_delivery`, quantity, `quantity_refunded`, `quantity_returned`, `in_cart`)
  2810. VALUES('.(int)$cart->id.', '.(int)$val['id_product_attribute'].', '.(int)$val['id_product'].', '.(int)$id_address_delivery.', '.(int)$val['quantity'].', 0, 0, 1)'
  2811. );
  2812. $custom_ids[$customization_id] = Db::getInstance(_PS_USE_SQL_SLAVE_)->Insert_ID();
  2813. }
  2814. // Insert customized_data
  2815. if (count($customs))
  2816. {
  2817. $first = true;
  2818. $sql_custom_data = 'INSERT INTO '._DB_PREFIX_.'customized_data (`id_customization`, `type`, `index`, `value`) VALUES ';
  2819. foreach ($customs as $custom)
  2820. {
  2821. if (!$first)
  2822. $sql_custom_data .= ',';
  2823. else
  2824. $first = false;
  2825. $sql_custom_data .= '('.(int)$custom_ids[$custom['id_customization']].', '.(int)$custom['type'].', '.
  2826. (int)$custom['index'].', \''.pSQL($custom['value']).'\')';
  2827. }
  2828. Db::getInstance()->execute($sql_custom_data);
  2829. }
  2830. return array('cart' => $cart, 'success' => $success);
  2831. }
  2832. public function getWsCartRows()
  2833. {
  2834. return Db::getInstance()->executeS('
  2835. SELECT id_product, id_product_attribute, quantity, id_address_delivery
  2836. FROM `'._DB_PREFIX_.'cart_product`
  2837. WHERE id_cart = '.(int)$this->id.' AND id_shop = '.(int)Context::getContext()->shop->id
  2838. );
  2839. }
  2840. public function setWsCartRows($values)
  2841. {
  2842. if ($this->deleteAssociations())
  2843. {
  2844. $query = 'INSERT INTO `'._DB_PREFIX_.'cart_product`(`id_cart`, `id_product`, `id_product_attribute`, `id_address_delivery`, `quantity`, `date_add`, `id_shop`) VALUES ';
  2845. foreach ($values as $value)
  2846. $query .= '('.(int)$this->id.', '.(int)$value['id_product'].', '.
  2847. (isset($value['id_product_attribute']) ? (int)$value['id_product_attribute'] : 'NULL').', '.
  2848. (isset($value['id_address_delivery']) ? (int)$value['id_address_delivery'] : 0).', '.
  2849. (int)$value['quantity'].', NOW(), '.(int)Context::getContext()->shop->id.'),';
  2850. Db::getInstance()->execute(rtrim($query, ','));
  2851. }
  2852. return true;
  2853. }
  2854. public function setProductAddressDelivery($id_product, $id_product_attribute, $old_id_address_delivery, $new_id_address_delivery)
  2855. {
  2856. // Check address is linked with the customer
  2857. if (!Customer::customerHasAddress(Context::getContext()->customer->id, $new_id_address_delivery))
  2858. return false;
  2859. if ($new_id_address_delivery == $old_id_address_delivery)
  2860. return false;
  2861. // Checking if the product with the old address delivery exists
  2862. $sql = new DbQuery();
  2863. $sql->select('count(*)');
  2864. $sql->from('cart_product', 'cp');
  2865. $sql->where('id_product = '.(int)$id_product);
  2866. $sql->where('id_product_attribute = '.(int)$id_product_attribute);
  2867. $sql->where('id_address_delivery = '.(int)$old_id_address_delivery);
  2868. $sql->where('id_cart = '.(int)$this->id);
  2869. $result = Db::getInstance()->getValue($sql);
  2870. if ($result == 0)
  2871. return false;
  2872. // Checking if there is no others similar products with this new address delivery
  2873. $sql = new DbQuery();
  2874. $sql->select('sum(quantity) as qty');
  2875. $sql->from('cart_product', 'cp');
  2876. $sql->where('id_product = '.(int)$id_product);
  2877. $sql->where('id_product_attribute = '.(int)$id_product_attribute);
  2878. $sql->where('id_address_delivery = '.(int)$new_id_address_delivery);
  2879. $sql->where('id_cart = '.(int)$this->id);
  2880. $result = Db::getInstance()->getValue($sql);
  2881. // Removing similar products with this new address delivery
  2882. $sql = 'DELETE FROM '._DB_PREFIX_.'cart_product
  2883. WHERE id_product = '.(int)$id_product.'
  2884. AND id_product_attribute = '.(int)$id_product_attribute.'
  2885. AND id_address_delivery = '.(int)$new_id_address_delivery.'
  2886. AND id_cart = '.(int)$this->id.'
  2887. LIMIT 1';
  2888. Db::getInstance()->execute($sql);
  2889. // Changing the address
  2890. $sql = 'UPDATE '._DB_PREFIX_.'cart_product
  2891. SET `id_address_delivery` = '.(int)$new_id_address_delivery.',
  2892. `quantity` = `quantity` + '.(int)$result['sum'].'
  2893. WHERE id_product = '.(int)$id_product.'
  2894. AND id_product_attribute = '.(int)$id_product_attribute.'
  2895. AND id_address_delivery = '.(int)$old_id_address_delivery.'
  2896. AND id_cart = '.(int)$this->id.'
  2897. LIMIT 1';
  2898. Db::getInstance()->execute($sql);
  2899. // Changing the address of the customizations
  2900. $sql = 'UPDATE '._DB_PREFIX_.'customization
  2901. SET `id_address_delivery` = '.(int)$new_id_address_delivery.'
  2902. WHERE id_product = '.(int)$id_product.'
  2903. AND id_product_attribute = '.(int)$id_product_attribute.'
  2904. AND id_address_delivery = '.(int)$old_id_address_delivery.'
  2905. AND id_cart = '.(int)$this->id;
  2906. Db::getInstance()->execute($sql);
  2907. return true;
  2908. }
  2909. public function duplicateProduct($id_product, $id_product_attribute, $id_address_delivery,
  2910. $new_id_address_delivery, $quantity = 1, $keep_quantity = false)
  2911. {
  2912. // Check address is linked with the customer
  2913. if (!Customer::customerHasAddress(Context::getContext()->customer->id, $new_id_address_delivery))
  2914. return false;
  2915. // Checking the product do not exist with the new address
  2916. $sql = new DbQuery();
  2917. $sql->select('count(*)');
  2918. $sql->from('cart_product', 'c');
  2919. $sql->where('id_product = '.(int)$id_product);
  2920. $sql->where('id_product_attribute = '.(int)$id_product_attribute);
  2921. $sql->where('id_address_delivery = '.(int)$new_id_address_delivery);
  2922. $sql->where('id_cart = '.(int)$this->id);
  2923. $result = Db::getInstance()->getValue($sql);
  2924. if ($result > 0)
  2925. return false;
  2926. // Duplicating cart_product line
  2927. $sql = 'INSERT INTO '._DB_PREFIX_.'cart_product
  2928. (`id_cart`, `id_product`, `id_shop`, `id_product_attribute`, `quantity`, `date_add`, `id_address_delivery`)
  2929. values(
  2930. '.(int)$this->id.',
  2931. '.(int)$id_product.',
  2932. '.(int)$this->id_shop.',
  2933. '.(int)$id_product_attribute.',
  2934. '.(int)$quantity.',
  2935. NOW(),
  2936. '.(int)$new_id_address_delivery.')';
  2937. Db::getInstance()->execute($sql);
  2938. if (!$keep_quantity)
  2939. {
  2940. $sql = new DbQuery();
  2941. $sql->select('quantity');
  2942. $sql->from('cart_product', 'c');
  2943. $sql->where('id_product = '.(int)$id_product);
  2944. $sql->where('id_product_attribute = '.(int)$id_product_attribute);
  2945. $sql->where('id_address_delivery = '.(int)$id_address_delivery);
  2946. $sql->where('id_cart = '.(int)$this->id);
  2947. $duplicatedQuantity = Db::getInstance()->getValue($sql);
  2948. if ($duplicatedQuantity > $quantity)
  2949. {
  2950. $sql = 'UPDATE '._DB_PREFIX_.'cart_product
  2951. SET `quantity` = `quantity` - '.(int)$quantity.'
  2952. WHERE id_cart = '.(int)$this->id.'
  2953. AND id_product = '.(int)$id_product.'
  2954. AND id_shop = '.(int)$this->id_shop.'
  2955. AND id_product_attribute = '.(int)$id_product_attribute.'
  2956. AND id_address_delivery = '.(int)$id_address_delivery;
  2957. Db::getInstance()->execute($sql);
  2958. }
  2959. }
  2960. // Checking if there is customizations
  2961. $sql = new DbQuery();
  2962. $sql->select('*');
  2963. $sql->from('customization', 'c');
  2964. $sql->where('id_product = '.(int)$id_product);
  2965. $sql->where('id_product_attribute = '.(int)$id_product_attribute);
  2966. $sql->where('id_address_delivery = '.(int)$id_address_delivery);
  2967. $sql->where('id_cart = '.(int)$this->id);
  2968. $results = Db::getInstance()->executeS($sql);
  2969. foreach ($results as $customization)
  2970. {
  2971. // Duplicate customization
  2972. $sql = 'INSERT INTO '._DB_PREFIX_.'customization
  2973. (`id_product_attribute`, `id_address_delivery`, `id_cart`, `id_product`, `quantity`, `in_cart`)
  2974. VALUES (
  2975. '.$customization['id_product_attribute'].',
  2976. '.$new_id_address_delivery.',
  2977. '.$customization['id_cart'].',
  2978. '.$customization['id_product'].',
  2979. '.$quantity.',
  2980. '.$customization['in_cart'].')';
  2981. Db::getInstance()->execute($sql);
  2982. $sql = 'INSERT INTO '._DB_PREFIX_.'customized_data(`id_customization`, `type`, `index`, `value`)
  2983. (
  2984. SELECT '.(int)Db::getInstance()->Insert_ID().' `id_customization`, `type`, `index`, `value`
  2985. FROM customized_data
  2986. WHERE id_customization = '.$customization['id_customization'].'
  2987. )';
  2988. Db::getInstance()->execute($sql);
  2989. }
  2990. $customization_count = count($results);
  2991. if ($customization_count > 0)
  2992. {
  2993. $sql = 'UPDATE '._DB_PREFIX_.'cart_product
  2994. SET `quantity` = `quantity` = '.(int)$customization_count * $quantity.'
  2995. WHERE id_cart = '.(int)$this->id.'
  2996. AND id_product = '.(int)$id_product.'
  2997. AND id_shop = '.(int)$this->id_shop.'
  2998. AND id_product_attribute = '.(int)$id_product_attribute.'
  2999. AND id_address_delivery = '.(int)$new_id_address_delivery;
  3000. Db::getInstance()->execute($sql);
  3001. }
  3002. return true;
  3003. }
  3004. /**
  3005. * Update products cart address delivery with the address delivery of the cart
  3006. */
  3007. public function setNoMultishipping()
  3008. {
  3009. $emptyCache = false;
  3010. if (Configuration::get('PS_ALLOW_MULTISHIPPING'))
  3011. {
  3012. // Upgrading quantities
  3013. $sql = 'SELECT sum(`quantity`) as quantity, id_product, id_product_attribute, count(*) as count
  3014. FROM `'._DB_PREFIX_.'cart_product`
  3015. WHERE `id_cart` = '.(int)$this->id.'
  3016. AND `id_shop` = '.(int)$this->id_shop.'
  3017. GROUP BY id_product, id_product_attribute
  3018. HAVING count > 1';
  3019. foreach (Db::getInstance()->executeS($sql) as $product)
  3020. {
  3021. $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
  3022. SET `quantity` = '.$product['quantity'].'
  3023. WHERE `id_cart` = '.(int)$this->id.'
  3024. AND `id_shop` = '.(int)$this->id_shop.'
  3025. AND id_product = '.$product['id_product'].'
  3026. AND id_product_attribute = '.$product['id_product_attribute'];
  3027. if (Db::getInstance()->execute($sql))
  3028. $emptyCache = true;
  3029. }
  3030. // Merging multiple lines
  3031. $sql = 'DELETE cp1
  3032. FROM `'._DB_PREFIX_.'cart_product` cp1
  3033. INNER JOIN `'._DB_PREFIX_.'cart_product` cp2
  3034. ON (
  3035. (cp1.id_cart = cp2.id_cart)
  3036. AND (cp1.id_product = cp2.id_product)
  3037. AND (cp1.id_product_attribute = cp2.id_product_attribute)
  3038. AND (cp1.id_address_delivery <> cp2.id_address_delivery)
  3039. AND (cp1.date_add > cp2.date_add)
  3040. )';
  3041. Db::getInstance()->execute($sql);
  3042. }
  3043. // Update delivery address for each product line
  3044. $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
  3045. SET `id_address_delivery` = (
  3046. SELECT `id_address_delivery` FROM `'._DB_PREFIX_.'cart`
  3047. WHERE `id_cart` = '.(int)$this->id.' AND `id_shop` = '.(int)$this->id_shop.'
  3048. )
  3049. WHERE `id_cart` = '.(int)$this->id.'
  3050. '.(Configuration::get('PS_ALLOW_MULTISHIPPING') ? ' AND `id_shop` = '.(int)$this->id_shop : '');
  3051. $cache_id = 'Cart::setNoMultishipping'.(int)$this->id.'-'.(int)$this->id_shop.(isset($this->id_address_delivery)? '-'.(int)$this->id_address_delivery : '');
  3052. if (!Cache::isStored($cache_id))
  3053. {
  3054. if ($result = (bool)Db::getInstance()->execute($sql))
  3055. $emptyCache = true;
  3056. Cache::store($cache_id, $result);
  3057. }
  3058. if (Customization::isFeatureActive())
  3059. Db::getInstance()->execute('
  3060. UPDATE `'._DB_PREFIX_.'customization`
  3061. SET `id_address_delivery` = (
  3062. SELECT `id_address_delivery` FROM `'._DB_PREFIX_.'cart`
  3063. WHERE `id_cart` = '.(int)$this->id.'
  3064. )
  3065. WHERE `id_cart` = '.(int)$this->id);
  3066. if ($emptyCache)
  3067. $this->_products = null;
  3068. }
  3069. /**
  3070. * Set an address to all products on the cart without address delivery
  3071. */
  3072. public function autosetProductAddress()
  3073. {
  3074. $id_address_delivery = 0;
  3075. // Get the main address of the customer
  3076. if ((int)$this->id_address_delivery > 0)
  3077. $id_address_delivery = (int)$this->id_address_delivery;
  3078. else
  3079. $id_address_delivery = (int)Address::getFirstCustomerAddressId(Context::getContext()->customer->id);
  3080. if (!$id_address_delivery)
  3081. return;
  3082. // Update
  3083. $sql = 'UPDATE `'._DB_PREFIX_.'cart_product`
  3084. SET `id_address_delivery` = '.(int)$id_address_delivery.'
  3085. WHERE `id_cart` = '.(int)$this->id.'
  3086. AND (`id_address_delivery` = 0 OR `id_address_delivery` IS NULL)
  3087. AND `id_shop` = '.(int)$this->id_shop;
  3088. Db::getInstance()->execute($sql);
  3089. $sql = 'UPDATE `'._DB_PREFIX_.'customization`
  3090. SET `id_address_delivery` = '.(int)$id_address_delivery.'
  3091. WHERE `id_cart` = '.(int)$this->id.'
  3092. AND (`id_address_delivery` = 0 OR `id_address_delivery` IS NULL)';
  3093. Db::getInstance()->execute($sql);
  3094. }
  3095. public function deleteAssociations()
  3096. {
  3097. return (Db::getInstance()->execute('
  3098. DELETE FROM `'._DB_PREFIX_.'cart_product`
  3099. WHERE `id_cart` = '.(int)$this->id) !== false);
  3100. }
  3101. /**
  3102. * isGuestCartByCartId
  3103. *
  3104. * @param int $id_cart
  3105. * @return bool true if cart has been made by a guest customer
  3106. */
  3107. public static function isGuestCartByCartId($id_cart)
  3108. {
  3109. if (!(int)$id_cart)
  3110. return false;
  3111. return (bool)Db::getInstance()->getValue('
  3112. SELECT `is_guest`
  3113. FROM `'._DB_PREFIX_.'customer` cu
  3114. LEFT JOIN `'._DB_PREFIX_.'cart` ca ON (ca.`id_customer` = cu.`id_customer`)
  3115. WHERE ca.`id_cart` = '.(int)$id_cart);
  3116. }
  3117. /**
  3118. * isCarrierInRange
  3119. *
  3120. * Check if the specified carrier is in range
  3121. *
  3122. * @id_carrier int
  3123. * @id_zone int
  3124. */
  3125. public function isCarrierInRange($id_carrier, $id_zone)
  3126. {
  3127. $carrier = new Carrier((int)$id_carrier, Configuration::get('PS_LANG_DEFAULT'));
  3128. $shipping_method = $carrier->getShippingMethod();
  3129. if (!$carrier->range_behavior)
  3130. return true;
  3131. if ($shipping_method == Carrier::SHIPPING_METHOD_FREE)
  3132. return true;
  3133. $check_delivery_price_by_weight = Carrier::checkDeliveryPriceByWeight(
  3134. (int)$id_carrier,
  3135. $this->getTotalWeight(),
  3136. $id_zone
  3137. );
  3138. if ($shipping_method == Carrier::SHIPPING_METHOD_WEIGHT && $check_delivery_price_by_weight)
  3139. return true;
  3140. $check_delivery_price_by_price = Carrier::checkDeliveryPriceByPrice(
  3141. (int)$id_carrier,
  3142. $this->getOrderTotal(
  3143. true,
  3144. Cart::BOTH_WITHOUT_SHIPPING
  3145. ),
  3146. $id_zone,
  3147. (int)$this->id_currency
  3148. );
  3149. if ($shipping_method == Carrier::SHIPPING_METHOD_PRICE && $check_delivery_price_by_price)
  3150. return true;
  3151. return false;
  3152. }
  3153. /**
  3154. * @param bool $ignore_virtual Ignore virtual product
  3155. * @param bool $exclusive If true, the validation is exclusive : it must be present product in stock and out of stock
  3156. * @since 1.5.0
  3157. *
  3158. * @return bool false is some products from the cart are out of stock
  3159. */
  3160. public function isAllProductsInStock($ignore_virtual = false, $exclusive = false)
  3161. {
  3162. $product_out_of_stock = 0;
  3163. $product_in_stock = 0;
  3164. foreach ($this->getProducts() as $product)
  3165. {
  3166. if (!$exclusive)
  3167. {
  3168. if ((int)$product['quantity_available'] <= 0
  3169. && (!$ignore_virtual || !$product['is_virtual']))
  3170. return false;
  3171. }
  3172. else
  3173. {
  3174. if ((int)$product['quantity_available'] <= 0
  3175. && (!$ignore_virtual || !$product['is_virtual']))
  3176. $product_out_of_stock++;
  3177. if ((int)$product['quantity_available'] > 0
  3178. && (!$ignore_virtual || !$product['is_virtual']))
  3179. $product_in_stock++;
  3180. if ($product_in_stock > 0 && $product_out_of_stock > 0)
  3181. return false;
  3182. }
  3183. }
  3184. return true;
  3185. }
  3186. /**
  3187. *
  3188. * Execute hook displayCarrierList (extraCarrier) and merge theme to the $array
  3189. * @param array $array
  3190. */
  3191. public static function addExtraCarriers(&$array)
  3192. {
  3193. $first = true;
  3194. $hook_extracarrier_addr = array();
  3195. foreach (Context::getContext()->cart->getAddressCollection() as $address)
  3196. {
  3197. $hook = Hook::exec('displayCarrierList', array('address' => $address));
  3198. $hook_extracarrier_addr[$address->id] = $hook;
  3199. if ($first)
  3200. {
  3201. $array = array_merge(
  3202. $array,
  3203. array('HOOK_EXTRACARRIER' => $hook)
  3204. );
  3205. $first = false;
  3206. }
  3207. $array = array_merge(
  3208. $array,
  3209. array('HOOK_EXTRACARRIER_ADDR' => $hook_extracarrier_addr)
  3210. );
  3211. }
  3212. }
  3213. /**
  3214. * Get all the ids of the delivery addresses without carriers
  3215. *
  3216. * @param bool $return_collection Return a collection
  3217. *
  3218. * @return array Array of address id or of address object
  3219. */
  3220. public function getDeliveryAddressesWithoutCarriers($return_collection = false)
  3221. {
  3222. $addresses_without_carriers = array();
  3223. foreach ($this->getProducts() as $product)
  3224. {
  3225. if (!in_array($product['id_address_delivery'], $addresses_without_carriers)
  3226. && !count(Carrier::getAvailableCarrierList(new Product($product['id_product']), null, $product['id_address_delivery'])))
  3227. $addresses_without_carriers[] = $product['id_address_delivery'];
  3228. }
  3229. if (!$return_collection)
  3230. return $addresses_without_carriers;
  3231. else
  3232. {
  3233. $addresses_instance_without_carriers = array();
  3234. foreach ($addresses_without_carriers as $id_address)
  3235. $addresses_instance_without_carriers[] = new Address($id_address);
  3236. return $addresses_instance_without_carriers;
  3237. }
  3238. }
  3239. }