PageRenderTime 16ms CodeModel.GetById 229ms app.highlight 78ms RepoModel.GetById 1ms app.codeStats 3ms

/classes/Cart.php

https://github.com/LucaTerzaghi/PrestaShop
PHP | 3733 lines | 2758 code | 495 blank | 480 comment | 543 complexity | 9635aa388fa21639e831544f0b18d24c MD5 | raw file

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

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

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