2017-06-27 11:26:08 +02:00

1059 lines
48 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
class Cart extends CartCore
{
/**
* Update product quantity
*
* @param int $quantity Quantity to add (or substract)
* @param int $id_product Product ID
* @param int $id_product_attribute Attribute ID if needed
* @param string $operator Indicate if quantity must be increased or decreased
*/
public function updateQty($quantity, $id_product, $id_product_attribute = null, $id_customization = false,
$operator = 'up', $id_address_delivery = 0, Shop $shop = null, $auto_add_cart_rule = true, $id_configurator = null)
{
if (!$shop) {
$shop = Context::getContext()->shop;
}
if (Context::getContext()->customer->id) {
if ($id_address_delivery == 0 && (int)$this->id_address_delivery) { // The $id_address_delivery is null, use the cart delivery address
$id_address_delivery = $this->id_address_delivery;
} elseif ($id_address_delivery == 0) { // The $id_address_delivery is null, get the default customer address
$id_address_delivery = (int)Address::getFirstCustomerAddressId((int)Context::getContext()->customer->id);
} elseif (!Customer::customerHasAddress(Context::getContext()->customer->id, $id_address_delivery)) { // The $id_address_delivery must be linked with customer
$id_address_delivery = 0;
}
}
$quantity = (int)$quantity;
$id_product = (int)$id_product;
$id_product_attribute = (int)$id_product_attribute;
$product = new Product($id_product, false, Configuration::get('PS_LANG_DEFAULT'), $shop->id);
if ($id_product_attribute) {
$combination = new Combination((int)$id_product_attribute);
if ($combination->id_product != $id_product) {
return false;
}
}
/* If we have a product combination, the minimal quantity is set with the one of this combination */
if (!empty($id_product_attribute)) {
$minimal_quantity = (int)Attribute::getAttributeMinimalQty($id_product_attribute);
} else {
$minimal_quantity = (int)$product->minimal_quantity;
}
if (!Validate::isLoadedObject($product)) {
die(Tools::displayError());
}
if (isset(self::$_nbProducts[$this->id])) {
unset(self::$_nbProducts[$this->id]);
}
if (isset(self::$_totalWeight[$this->id])) {
unset(self::$_totalWeight[$this->id]);
}
Hook::exec('actionBeforeCartUpdateQty', array(
'cart' => $this,
'product' => $product,
'id_product_attribute' => $id_product_attribute,
'id_customization' => $id_customization,
'quantity' => $quantity,
'operator' => $operator,
'id_address_delivery' => $id_address_delivery,
'shop' => $shop,
'auto_add_cart_rule' => $auto_add_cart_rule,
'id_configurator' => $id_configurator
));
if ((int)$quantity <= 0) {
return $this->deleteProduct($id_product, $id_product_attribute, (int)$id_customization, 0, $auto_add_cart_rule, $id_configurator);
} elseif (!$product->available_for_order || (Configuration::get('PS_CATALOG_MODE') && !defined('_PS_ADMIN_DIR_'))) {
return false;
} else {
/* Check if the product is already in the cart */
$result = $this->containsProduct($id_product, $id_product_attribute, (int)$id_customization, (int)$id_address_delivery, $id_configurator);
/* Update quantity if product already exist */
if ($result) {
if ($operator == 'up') {
$sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
FROM '._DB_PREFIX_.'product p
'.Product::sqlStock('p', $id_product_attribute, true, $shop).'
WHERE p.id_product = '.$id_product;
$result2 = Db::getInstance()->getRow($sql);
$product_qty = (int)$result2['quantity'];
// Quantity for product pack
if (Pack::isPack($id_product)) {
$product_qty = Pack::getQuantity($id_product, $id_product_attribute);
}
$new_qty = (int)$result['quantity'] + (int)$quantity;
$qty = '+ '.(int)$quantity;
if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock'])) {
if ($new_qty > $product_qty) {
return false;
}
}
} elseif ($operator == 'down') {
$qty = '- '.(int)$quantity;
$new_qty = (int)$result['quantity'] - (int)$quantity;
if ($new_qty < $minimal_quantity && $minimal_quantity > 1) {
return -1;
}
} else {
return false;
}
/* Delete product from cart */
if ($new_qty <= 0) {
return $this->deleteProduct((int)$id_product, (int)$id_product_attribute, (int)$id_customization, 0, $auto_add_cart_rule, $id_configurator);
} elseif ($new_qty < $minimal_quantity) {
return -1;
} else {
Db::getInstance()->execute('
UPDATE `'._DB_PREFIX_.'cart_product`
SET `quantity` = `quantity` '.$qty.', `date_add` = NOW()
WHERE `id_product` = '.(int)$id_product.
(!empty($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
AND `id_cart` = '.(int)$this->id.(Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery() ? ' AND `id_address_delivery` = '.(int)$id_address_delivery : '').'
AND `id_configurator` = '.(int)$id_configurator.'
LIMIT 1'
);
}
}
/* Add product to the cart */
elseif ($operator == 'up') {
$sql = 'SELECT stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity
FROM '._DB_PREFIX_.'product p
'.Product::sqlStock('p', $id_product_attribute, true, $shop).'
WHERE p.id_product = '.$id_product;
$result2 = Db::getInstance()->getRow($sql);
// Quantity for product pack
if (Pack::isPack($id_product)) {
$result2['quantity'] = Pack::getQuantity($id_product, $id_product_attribute);
}
if (!Product::isAvailableWhenOutOfStock((int)$result2['out_of_stock'])) {
if ((int)$quantity > $result2['quantity']) {
return false;
}
}
if ((int)$quantity < $minimal_quantity) {
return -1;
}
$result_add = Db::getInstance()->insert('cart_product', array(
'id_product' => (int)$id_product,
'id_product_attribute' => (int)$id_product_attribute,
'id_cart' => (int)$this->id,
'id_address_delivery' => (int)$id_address_delivery,
'id_shop' => $shop->id,
'quantity' => (int)$quantity,
'date_add' => date('Y-m-d H:i:s'),
'id_configurator' => (int)$id_configurator
));
if (!$result_add) {
return false;
}
}
}
// refresh cache of self::_products
$this->_products = $this->getProducts(true);
$this->update();
$context = Context::getContext()->cloneContext();
$context->cart = $this;
Cache::clean('getContextualValue_*');
if ($auto_add_cart_rule) {
CartRule::autoAddToCart($context);
}
if ($product->customizable) {
return $this->_updateCustomizationQuantity((int)$quantity, (int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery, $operator);
} else {
return true;
}
}
/**
* Return cart products
*
* @result array Products
*/
public function getProducts($refresh = false, $id_product = false, $id_country = null)
{
if (!$this->id) {
return array();
}
// Product cache must be strictly compared to NULL, or else an empty cart will add dozens of queries
if ($this->_products !== null && !$refresh) {
// Return product row with specified ID if it exists
if (is_int($id_product)) {
foreach ($this->_products as $product) {
if ($product['id_product'] == $id_product) {
return array($product);
}
}
return array();
}
return $this->_products;
}
// Build query
$sql = new DbQuery();
// Build SELECT
$sql->select('cp.`id_product_attribute`, cp.`id_product`, cp.`quantity` AS cart_quantity, cp.id_shop, cp.id_configurator, pl.`name`, p.`is_virtual`,
pl.`description_short`, pl.`available_now`, pl.`available_later`, product_shop.`id_category_default`, p.`id_supplier`,
p.`id_manufacturer`, product_shop.`on_sale`, product_shop.`ecotax`, product_shop.`additional_shipping_cost`,
product_shop.`available_for_order`, product_shop.`price`, product_shop.`active`, product_shop.`unity`, product_shop.`unit_price_ratio`,
stock.`quantity` AS quantity_available, p.`width`, p.`height`, p.`depth`, stock.`out_of_stock`, p.`weight`,
p.`date_add`, p.`date_upd`, IFNULL(stock.quantity, 0) as quantity, pl.`link_rewrite`, cl.`link_rewrite` AS category,
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,
product_shop.advanced_stock_management, ps.product_supplier_reference supplier_reference');
// Build FROM
$sql->from('cart_product', 'cp');
// Build JOIN
$sql->leftJoin('product', 'p', 'p.`id_product` = cp.`id_product`');
$sql->innerJoin('product_shop', 'product_shop', '(product_shop.`id_shop` = cp.`id_shop` AND product_shop.`id_product` = p.`id_product`)');
$sql->leftJoin('product_lang', 'pl', '
p.`id_product` = pl.`id_product`
AND pl.`id_lang` = '.(int)$this->id_lang.Shop::addSqlRestrictionOnLang('pl', 'cp.id_shop')
);
$sql->leftJoin('category_lang', 'cl', '
product_shop.`id_category_default` = cl.`id_category`
AND cl.`id_lang` = '.(int)$this->id_lang.Shop::addSqlRestrictionOnLang('cl', 'cp.id_shop')
);
$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`');
// @todo test if everything is ok, then refactorise call of this method
$sql->join(Product::sqlStock('cp', 'cp'));
// Build WHERE clauses
$sql->where('cp.`id_cart` = '.(int)$this->id);
if ($id_product) {
$sql->where('cp.`id_product` = '.(int)$id_product);
}
$sql->where('p.`id_product` IS NOT NULL');
// Build ORDER BY
$sql->orderBy('cp.`date_add`, cp.`id_product`, cp.`id_product_attribute` ASC');
if (Customization::isFeatureActive()) {
$sql->select('cu.`id_customization`, cu.`quantity` AS customization_quantity');
$sql->leftJoin('customization', 'cu',
'p.`id_product` = cu.`id_product` AND cp.`id_product_attribute` = cu.`id_product_attribute` AND cu.`id_cart` = '.(int)$this->id);
$sql->groupBy('cp.`id_product_attribute`, cp.`id_product`, cp.`id_shop`');
} else {
$sql->select('NULL AS customization_quantity, NULL AS id_customization');
}
if (Combination::isFeatureActive()) {
$sql->select('
product_attribute_shop.`price` AS price_attribute, product_attribute_shop.`ecotax` AS ecotax_attr,
IF (IFNULL(pa.`reference`, \'\') = \'\', p.`reference`, pa.`reference`) AS reference,
(p.`weight`+ pa.`weight`) weight_attribute,
IF (IFNULL(pa.`ean13`, \'\') = \'\', p.`ean13`, pa.`ean13`) AS ean13,
IF (IFNULL(pa.`upc`, \'\') = \'\', p.`upc`, pa.`upc`) AS upc,
IFNULL(product_attribute_shop.`minimal_quantity`, product_shop.`minimal_quantity`) as minimal_quantity,
IF(product_attribute_shop.wholesale_price > 0, product_attribute_shop.wholesale_price, product_shop.`wholesale_price`) wholesale_price
');
$sql->leftJoin('product_attribute', 'pa', 'pa.`id_product_attribute` = cp.`id_product_attribute`');
$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`)');
} else {
$sql->select(
'p.`reference` AS reference, p.`ean13`,
p.`upc` AS upc, product_shop.`minimal_quantity` AS minimal_quantity, product_shop.`wholesale_price` wholesale_price'
);
}
$sql->select('image_shop.`id_image` id_image, il.`legend`');
$sql->leftJoin('image_shop', 'image_shop', 'image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int)$this->id_shop);
$sql->leftJoin('image_lang', 'il', 'il.`id_image` = image_shop.`id_image` AND il.`id_lang` = '.(int)$this->id_lang);
$result = Db::getInstance()->executeS($sql);
// Reset the cache before the following return, or else an empty cart will add dozens of queries
$products_ids = array();
$pa_ids = array();
if ($result) {
foreach ($result as $key => $row) {
$products_ids[] = $row['id_product'];
$pa_ids[] = $row['id_product_attribute'];
$specific_price = SpecificPrice::getSpecificPrice($row['id_product'], $this->id_shop, $this->id_currency, $id_country, $this->id_shop_group, $row['cart_quantity'], $row['id_product_attribute'], $this->id_customer, $this->id);
if ($specific_price) {
$reduction_type_row = array('reduction_type' => $specific_price['reduction_type']);
} else {
$reduction_type_row = array('reduction_type' => 0);
}
$result[$key] = array_merge($row, $reduction_type_row);
}
}
// Thus you can avoid one query per product, because there will be only one query for all the products of the cart
Product::cacheProductsFeatures($products_ids);
Cart::cacheSomeAttributesLists($pa_ids, $this->id_lang);
$this->_products = array();
if (empty($result)) {
return array();
}
$ecotax_rate = (float)Tax::getProductEcotaxRate($this->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
$apply_eco_tax = Product::$_taxCalculationMethod == PS_TAX_INC && (int)Configuration::get('PS_TAX');
$cart_shop_context = Context::getContext()->cloneContext();
foreach ($result as &$row) {
// Configurator
if (isset($row['id_configurator']) && $row['id_configurator'] > 0) {
$row['configurator_opt'] = ConfiguratorStorage::getOptProductFlatten($row['id_configurator']);
}
if (isset($row['ecotax_attr']) && $row['ecotax_attr'] > 0) {
$row['ecotax'] = (float)$row['ecotax_attr'];
}
$row['stock_quantity'] = (int)$row['quantity'];
// for compatibility with 1.2 themes
$row['quantity'] = (int)$row['cart_quantity'];
if (isset($row['id_product_attribute']) && (int)$row['id_product_attribute'] && isset($row['weight_attribute'])) {
$row['weight'] = (float)$row['weight_attribute'];
}
if (Configuration::get('PS_TAX_ADDRESS_TYPE') == 'id_address_invoice') {
$address_id = (int)$this->id_address_invoice;
} else {
$address_id = (int)$row['id_address_delivery'];
}
if (!Address::addressExists($address_id)) {
$address_id = null;
}
if ($cart_shop_context->shop->id != $row['id_shop']) {
$cart_shop_context->shop = new Shop((int)$row['id_shop']);
}
$address = Address::initialize($address_id, true);
$id_tax_rules_group = Product::getIdTaxRulesGroupByIdProduct((int)$row['id_product'], $cart_shop_context);
$tax_calculator = TaxManagerFactory::getManager($address, $id_tax_rules_group)->getTaxCalculator();
$row['price_without_reduction'] = Product::getPriceStatic(
(int)$row['id_product'],
true,
isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
6,
null,
false,
false,
$row['cart_quantity'],
false,
(int)$this->id_customer ? (int)$this->id_customer : null,
(int)$this->id,
$address_id,
$specific_price_output,
true,
true,
$cart_shop_context,
true,
$row['id_configurator']
);
$row['price_with_reduction'] = Product::getPriceStatic(
(int)$row['id_product'],
true,
isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
6,
null,
false,
true,
$row['cart_quantity'],
false,
(int)$this->id_customer ? (int)$this->id_customer : null,
(int)$this->id,
$address_id,
$specific_price_output,
true,
true,
$cart_shop_context,
true,
$row['id_configurator']
);
$row['price'] = $row['price_with_reduction_without_tax'] = Product::getPriceStatic(
(int)$row['id_product'],
false,
isset($row['id_product_attribute']) ? (int)$row['id_product_attribute'] : null,
6,
null,
false,
true,
$row['cart_quantity'],
false,
(int)$this->id_customer ? (int)$this->id_customer : null,
(int)$this->id,
$address_id,
$specific_price_output,
true,
true,
$cart_shop_context,
true,
$row['id_configurator']
);
switch (Configuration::get('PS_ROUND_TYPE')) {
case Order::ROUND_TOTAL:
$row['total'] = $row['price_with_reduction_without_tax'] * (int)$row['cart_quantity'];
$row['total_wt'] = $row['price_with_reduction'] * (int)$row['cart_quantity'];
break;
case Order::ROUND_LINE:
$row['total'] = Tools::ps_round($row['price_with_reduction_without_tax'] * (int)$row['cart_quantity'], _PS_PRICE_COMPUTE_PRECISION_);
$row['total_wt'] = Tools::ps_round($row['price_with_reduction'] * (int)$row['cart_quantity'], _PS_PRICE_COMPUTE_PRECISION_);
break;
case Order::ROUND_ITEM:
default:
$row['total'] = Tools::ps_round($row['price_with_reduction_without_tax'], _PS_PRICE_COMPUTE_PRECISION_) * (int)$row['cart_quantity'];
$row['total_wt'] = Tools::ps_round($row['price_with_reduction'], _PS_PRICE_COMPUTE_PRECISION_) * (int)$row['cart_quantity'];
break;
}
$row['price_wt'] = $row['price_with_reduction'];
$row['description_short'] = Tools::nl2br($row['description_short']);
// check if a image associated with the attribute exists
if ($row['id_product_attribute']) {
$row2 = Image::getBestImageAttribute($row['id_shop'], $this->id_lang, $row['id_product'], $row['id_product_attribute']);
if ($row2) {
$row = array_merge($row, $row2);
}
}
$row['reduction_applies'] = ($specific_price_output && (float)$specific_price_output['reduction']);
$row['quantity_discount_applies'] = ($specific_price_output && $row['cart_quantity'] >= (int)$specific_price_output['from_quantity']);
$row['id_image'] = Product::defineProductImage($row, $this->id_lang);
$row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']);
$row['features'] = Product::getFeaturesStatic((int)$row['id_product']);
if (array_key_exists($row['id_product_attribute'].'-'.$this->id_lang, self::$_attributesLists)) {
$row = array_merge($row, self::$_attributesLists[$row['id_product_attribute'].'-'.$this->id_lang]);
}
$row = Product::getTaxesInformations($row, $cart_shop_context);
$this->_products[] = $row;
}
return $this->_products;
}
/**
* Delete a product from the cart
*
* @param int $id_product Product ID
* @param int $id_product_attribute Attribute ID if needed
* @param int $id_customization Customization id
* @return bool result
*/
public function deleteProduct($id_product, $id_product_attribute = null, $id_customization = null, $id_address_delivery = 0, $auto_add_cart_rule = true, $id_configurator = null)
{
if (isset(self::$_nbProducts[$this->id])) {
unset(self::$_nbProducts[$this->id]);
}
if (isset(self::$_totalWeight[$this->id])) {
unset(self::$_totalWeight[$this->id]);
}
if ((int)$id_customization) {
$product_total_quantity = (int)Db::getInstance()->getValue(
'SELECT `quantity`
FROM `'._DB_PREFIX_.'cart_product`
WHERE `id_product` = '.(int)$id_product.'
AND `id_cart` = '.(int)$this->id.'
AND `id_product_attribute` = '.(int)$id_product_attribute);
$customization_quantity = (int)Db::getInstance()->getValue('
SELECT `quantity`
FROM `'._DB_PREFIX_.'customization`
WHERE `id_cart` = '.(int)$this->id.'
AND `id_product` = '.(int)$id_product.'
AND `id_product_attribute` = '.(int)$id_product_attribute.'
'.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));
if (!$this->_deleteCustomization((int)$id_customization, (int)$id_product, (int)$id_product_attribute, (int)$id_address_delivery)) {
return false;
}
// refresh cache of self::_products
$this->_products = $this->getProducts(true);
return ($customization_quantity == $product_total_quantity
&& $this->deleteProduct((int)$id_product, (int)$id_product_attribute, null, (int)$id_address_delivery, true, $id_configurator));
}
/* Get customization quantity */
$result = Db::getInstance()->getRow('
SELECT SUM(`quantity`) AS \'quantity\'
FROM `'._DB_PREFIX_.'customization`
WHERE `id_cart` = '.(int)$this->id.'
AND `id_product` = '.(int)$id_product.'
AND `id_product_attribute` = '.(int)$id_product_attribute);
if ($result === false) {
return false;
}
/* If the product still possesses customization it does not have to be deleted */
if (Db::getInstance()->NumRows() && (int)$result['quantity']) {
return Db::getInstance()->execute('
UPDATE `'._DB_PREFIX_.'cart_product`
SET `quantity` = '.(int)$result['quantity'].'
WHERE `id_cart` = '.(int)$this->id.'
AND `id_product` = '.(int)$id_product.
($id_configurator != null ? ' AND `id_configurator` = '.(int)$id_configurator : '').
($id_product_attribute != null ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '')
);
}
/* Product deletion */
$result = Db::getInstance()->execute('
DELETE FROM `'._DB_PREFIX_.'cart_product`
WHERE `id_product` = '.(int)$id_product.
($id_configurator != null ? ' AND `id_configurator` = '.(int)$id_configurator : '').'
'.(!is_null($id_product_attribute) ? ' AND `id_product_attribute` = '.(int)$id_product_attribute : '').'
AND `id_cart` = '.(int)$this->id.'
'.((int)$id_address_delivery ? 'AND `id_address_delivery` = '.(int)$id_address_delivery : ''));
if ($result) {
$return = $this->update();
// refresh cache of self::_products
$this->_products = $this->getProducts(true);
CartRule::autoRemoveFromCart();
if ($auto_add_cart_rule) {
CartRule::autoAddToCart();
}
return $return;
}
return false;
}
public function containsProduct($id_product, $id_product_attribute = 0, $id_customization = 0, $id_address_delivery = 0, $id_configurator = null)
{
$sql = 'SELECT cp.`quantity` FROM `'._DB_PREFIX_.'cart_product` cp';
if ($id_customization) {
$sql .= '
LEFT JOIN `'._DB_PREFIX_.'customization` c ON (
c.`id_product` = cp.`id_product`
AND c.`id_product_attribute` = cp.`id_product_attribute`
)';
}
$sql .= '
WHERE cp.`id_product` = '.(int)$id_product.'
AND cp.`id_product_attribute` = '.(int)$id_product_attribute.'
AND cp.`id_cart` = '.(int)$this->id;
if (Configuration::get('PS_ALLOW_MULTISHIPPING') && $this->isMultiAddressDelivery()) {
$sql .= ' AND cp.`id_address_delivery` = '.(int)$id_address_delivery;
}
if ($id_customization) {
$sql .= ' AND c.`id_customization` = '.(int)$id_customization;
}
if ($id_configurator !== null) {
$sql .= ' AND cp.`id_configurator` = '.(int)$id_configurator;
}
return Db::getInstance()->getRow($sql);
}
/**
* Return useful informations for cart
*
* @return array Cart details
*/
public function getSummaryDetails($id_lang = null, $refresh = false)
{
$context = Context::getContext();
if (!$id_lang) {
$id_lang = $context->language->id;
}
$delivery = new Address((int)$this->id_address_delivery);
$invoice = new Address((int)$this->id_address_invoice);
// New layout system with personalization fields
$formatted_addresses = array(
'delivery' => AddressFormat::getFormattedLayoutData($delivery),
'invoice' => AddressFormat::getFormattedLayoutData($invoice)
);
$base_total_tax_inc = $this->getOrderTotal(true);
$base_total_tax_exc = $this->getOrderTotal(false);
$total_tax = $base_total_tax_inc - $base_total_tax_exc;
if ($total_tax < 0) {
$total_tax = 0;
}
$currency = new Currency($this->id_currency);
$products = $this->getProducts($refresh);
foreach ($products as $key => &$product) {
// @todo : Aller chercher les details de la configuration
// Flatten : Name + value(s) : configurator_opt_group, configurator_opt
// Créer un table de clé + liste valeur soit optgroupName [] = list (opt name)
$null = null;
// Change price with configurator option
$product['price_without_quantity_discount'] = Product::getPriceStatic(
$product['id_product'],
!Product::getTaxCalculationMethod(),
$product['id_product_attribute'],
6,
null,
false,
false,
1, //quantity
false, //force_associated_tax
null, //id_customer
$this->id,//id_cart
null, //id_address
$null, //specific_price_output
true, //with_ecotax
true, //use_group_reduction
null, //context
true, //use_customer_price
$product['id_configurator'] //configurator id
);
if ($product['reduction_type'] == 'amount') {
$reduction = (!Product::getTaxCalculationMethod() ? (float)$product['price_wt'] : (float)$product['price']) - (float)$product['price_without_quantity_discount'];
$product['reduction_formatted'] = Tools::displayPrice($reduction);
}
}
$gift_products = array();
$cart_rules = $this->getCartRules();
$total_shipping = $this->getTotalShippingCost();
$total_shipping_tax_exc = $this->getTotalShippingCost(null, false);
$total_products_wt = $this->getOrderTotal(true, Cart::ONLY_PRODUCTS);
$total_products = $this->getOrderTotal(false, Cart::ONLY_PRODUCTS);
$total_discounts = $this->getOrderTotal(true, Cart::ONLY_DISCOUNTS);
$total_discounts_tax_exc = $this->getOrderTotal(false, Cart::ONLY_DISCOUNTS);
// The cart content is altered for display
foreach ($cart_rules as &$cart_rule) {
// 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
if ($cart_rule['free_shipping'] && (empty($cart_rule['code']) || preg_match('/^'.CartRule::BO_ORDER_CODE_PREFIX.'[0-9]+/', $cart_rule['code']))) {
$cart_rule['value_real'] -= $total_shipping;
$cart_rule['value_tax_exc'] -= $total_shipping_tax_exc;
$cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
$cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
if ($total_discounts > $cart_rule['value_real']) {
$total_discounts -= $total_shipping;
}
if ($total_discounts_tax_exc > $cart_rule['value_tax_exc']) {
$total_discounts_tax_exc -= $total_shipping_tax_exc;
}
// Update total shipping
$total_shipping = 0;
$total_shipping_tax_exc = 0;
}
if ($cart_rule['gift_product']) {
foreach ($products as $key => &$product) {
if (empty($product['gift']) && $product['id_product'] == $cart_rule['gift_product'] && $product['id_product_attribute'] == $cart_rule['gift_product_attribute']) {
// Update total products
$total_products_wt = Tools::ps_round($total_products_wt - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
$total_products = Tools::ps_round($total_products - $product['price'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
// Update total discounts
$total_discounts = Tools::ps_round($total_discounts - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
$total_discounts_tax_exc = Tools::ps_round($total_discounts_tax_exc - $product['price'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
// Update cart rule value
$cart_rule['value_real'] = Tools::ps_round($cart_rule['value_real'] - $product['price_wt'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
$cart_rule['value_tax_exc'] = Tools::ps_round($cart_rule['value_tax_exc'] - $product['price'], (int)$context->currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
// Update product quantity
$product['total_wt'] = Tools::ps_round($product['total_wt'] - $product['price_wt'], (int)$currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
$product['total'] = Tools::ps_round($product['total'] - $product['price'], (int)$currency->decimals * _PS_PRICE_COMPUTE_PRECISION_);
$product['cart_quantity']--;
if (!$product['cart_quantity']) {
unset($products[$key]);
}
// Add a new product line
$gift_product = $product;
$gift_product['cart_quantity'] = 1;
$gift_product['price'] = 0;
$gift_product['price_wt'] = 0;
$gift_product['total_wt'] = 0;
$gift_product['total'] = 0;
$gift_product['gift'] = true;
$gift_products[] = $gift_product;
break; // One gift product per cart rule
}
}
}
}
foreach ($cart_rules as $key => &$cart_rule) {
if (((float)$cart_rule['value_real'] == 0 && (int)$cart_rule['free_shipping'] == 0)) {
unset($cart_rules[$key]);
}
}
$summary = array(
'delivery' => $delivery,
'delivery_state' => State::getNameById($delivery->id_state),
'invoice' => $invoice,
'invoice_state' => State::getNameById($invoice->id_state),
'formattedAddresses' => $formatted_addresses,
'products' => array_values($products),
'gift_products' => $gift_products,
'discounts' => array_values($cart_rules),
'is_virtual_cart' => (int)$this->isVirtualCart(),
'total_discounts' => $total_discounts,
'total_discounts_tax_exc' => $total_discounts_tax_exc,
'total_wrapping' => $this->getOrderTotal(true, Cart::ONLY_WRAPPING),
'total_wrapping_tax_exc' => $this->getOrderTotal(false, Cart::ONLY_WRAPPING),
'total_shipping' => $total_shipping,
'total_shipping_tax_exc' => $total_shipping_tax_exc,
'total_products_wt' => $total_products_wt,
'total_products' => $total_products,
'total_price' => $base_total_tax_inc,
'total_tax' => $total_tax,
'total_price_without_tax' => $base_total_tax_exc,
'is_multi_address_delivery' => $this->isMultiAddressDelivery() || ((int)Tools::getValue('multi-shipping') == 1),
'free_ship' =>!$total_shipping && !count($this->getDeliveryAddressesWithoutCarriers(true, $errors)),
'carrier' => new Carrier($this->id_carrier, $id_lang),
);
$hook = Hook::exec('actionCartSummary', $summary, null, true);
if (is_array($hook)) {
$summary = array_merge($summary, array_shift($hook));
}
return $summary;
}
/**
* This function returns the total cart amount
*
* Possible values for $type:
* Cart::ONLY_PRODUCTS
* Cart::ONLY_DISCOUNTS
* Cart::BOTH
* Cart::BOTH_WITHOUT_SHIPPING
* Cart::ONLY_SHIPPING
* Cart::ONLY_WRAPPING
* Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING
* Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING
*
* @param bool $withTaxes With or without taxes
* @param int $type Total type
* @param bool $use_cache Allow using cache of the method CartRule::getContextualValue
* @return float Order total
*/
public function getOrderTotal($with_taxes = true, $type = Cart::BOTH, $products = null, $id_carrier = null, $use_cache = true)
{
// Dependencies
$address_factory = Adapter_ServiceLocator::get('Adapter_AddressFactory');
$price_calculator = Adapter_ServiceLocator::get('Adapter_ProductPriceCalculator');
$configuration = Adapter_ServiceLocator::get('Core_Business_ConfigurationInterface');
$ps_tax_address_type = $configuration->get('PS_TAX_ADDRESS_TYPE');
$ps_use_ecotax = $configuration->get('PS_USE_ECOTAX');
$ps_round_type = $configuration->get('PS_ROUND_TYPE');
$ps_ecotax_tax_rules_group_id = $configuration->get('PS_ECOTAX_TAX_RULES_GROUP_ID');
$compute_precision = $configuration->get('_PS_PRICE_COMPUTE_PRECISION_');
if (!$this->id) {
return 0;
}
$type = (int)$type;
$array_type = array(
Cart::ONLY_PRODUCTS,
Cart::ONLY_DISCOUNTS,
Cart::BOTH,
Cart::BOTH_WITHOUT_SHIPPING,
Cart::ONLY_SHIPPING,
Cart::ONLY_WRAPPING,
Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING,
Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING,
);
// Define virtual context to prevent case where the cart is not the in the global context
$virtual_context = Context::getContext()->cloneContext();
$virtual_context->cart = $this;
if (!in_array($type, $array_type)) {
die(Tools::displayError());
}
$with_shipping = in_array($type, array(Cart::BOTH, Cart::ONLY_SHIPPING));
// if cart rules are not used
if ($type == Cart::ONLY_DISCOUNTS && !CartRule::isFeatureActive()) {
return 0;
}
// no shipping cost if is a cart with only virtuals products
$virtual = $this->isVirtualCart();
if ($virtual && $type == Cart::ONLY_SHIPPING) {
return 0;
}
if ($virtual && $type == Cart::BOTH) {
$type = Cart::BOTH_WITHOUT_SHIPPING;
}
if ($with_shipping || $type == Cart::ONLY_DISCOUNTS) {
if (is_null($products) && is_null($id_carrier)) {
$shipping_fees = $this->getTotalShippingCost(null, (bool)$with_taxes);
} else {
$shipping_fees = $this->getPackageShippingCost((int)$id_carrier, (bool)$with_taxes, null, $products);
}
} else {
$shipping_fees = 0;
}
if ($type == Cart::ONLY_SHIPPING) {
return $shipping_fees;
}
if ($type == Cart::ONLY_PRODUCTS_WITHOUT_SHIPPING) {
$type = Cart::ONLY_PRODUCTS;
}
$param_product = true;
if (is_null($products)) {
$param_product = false;
$products = $this->getProducts();
}
if ($type == Cart::ONLY_PHYSICAL_PRODUCTS_WITHOUT_SHIPPING) {
foreach ($products as $key => $product) {
if ($product['is_virtual']) {
unset($products[$key]);
}
}
$type = Cart::ONLY_PRODUCTS;
}
$order_total = 0;
if (Tax::excludeTaxeOption()) {
$with_taxes = false;
}
$products_total = array();
$ecotax_total = 0;
foreach ($products as $product) {
// products refer to the cart details
if ($virtual_context->shop->id != $product['id_shop']) {
$virtual_context->shop = new Shop((int)$product['id_shop']);
}
if ($ps_tax_address_type == 'id_address_invoice') {
$id_address = (int)$this->id_address_invoice;
} else {
$id_address = (int)$product['id_address_delivery'];
} // Get delivery address of the product from the cart
if (!$address_factory->addressExists($id_address)) {
$id_address = null;
}
// The $null variable below is not used,
// but it is necessary to pass it to getProductPrice because
// it expects a reference.
$null = null;
$price = $price_calculator->getProductPrice(
(int)$product['id_product'],
$with_taxes,
(int)$product['id_product_attribute'],
6,
null,
false,
true,
$product['cart_quantity'],
false,
(int)$this->id_customer ? (int)$this->id_customer : null,
(int)$this->id,
$id_address,
$null,
$ps_use_ecotax,
true,
$virtual_context,
true,
$product['id_configurator']
);
$address = $address_factory->findOrCreate($id_address, true);
if ($with_taxes) {
$id_tax_rules_group = Product::getIdTaxRulesGroupByIdProduct((int)$product['id_product'], $virtual_context);
$tax_calculator = TaxManagerFactory::getManager($address, $id_tax_rules_group)->getTaxCalculator();
} else {
$id_tax_rules_group = 0;
}
if (in_array($ps_round_type, array(Order::ROUND_ITEM, Order::ROUND_LINE))) {
if (!isset($products_total[$id_tax_rules_group])) {
$products_total[$id_tax_rules_group] = 0;
}
} elseif (!isset($products_total[$id_tax_rules_group.'_'.$id_address])) {
$products_total[$id_tax_rules_group.'_'.$id_address] = 0;
}
switch ($ps_round_type) {
case Order::ROUND_TOTAL:
$products_total[$id_tax_rules_group.'_'.$id_address] += $price * (int)$product['cart_quantity'];
break;
case Order::ROUND_LINE:
$product_price = $price * $product['cart_quantity'];
$products_total[$id_tax_rules_group] += Tools::ps_round($product_price, $compute_precision);
break;
case Order::ROUND_ITEM:
default:
$product_price = /*$with_taxes ? $tax_calculator->addTaxes($price) : */$price;
$products_total[$id_tax_rules_group] += Tools::ps_round($product_price, $compute_precision) * (int)$product['cart_quantity'];
break;
}
}
foreach ($products_total as $key => $price) {
$order_total += $price;
}
$order_total_products = $order_total;
if ($type == Cart::ONLY_DISCOUNTS) {
$order_total = 0;
}
// Wrapping Fees
$wrapping_fees = 0;
// With PS_ATCP_SHIPWRAP on the gift wrapping cost computation calls getOrderTotal with $type === Cart::ONLY_PRODUCTS, so the flag below prevents an infinite recursion.
$include_gift_wrapping = (!$configuration->get('PS_ATCP_SHIPWRAP') || $type !== Cart::ONLY_PRODUCTS);
if ($this->gift && $include_gift_wrapping) {
$wrapping_fees = Tools::convertPrice(Tools::ps_round($this->getGiftWrappingPrice($with_taxes), $compute_precision), Currency::getCurrencyInstance((int)$this->id_currency));
}
if ($type == Cart::ONLY_WRAPPING) {
return $wrapping_fees;
}
$order_total_discount = 0;
$order_shipping_discount = 0;
if (!in_array($type, array(Cart::ONLY_SHIPPING, Cart::ONLY_PRODUCTS)) && CartRule::isFeatureActive()) {
// First, retrieve the cart rules associated to this "getOrderTotal"
if ($with_shipping || $type == Cart::ONLY_DISCOUNTS) {
$cart_rules = $this->getCartRules(CartRule::FILTER_ACTION_ALL);
} else {
$cart_rules = $this->getCartRules(CartRule::FILTER_ACTION_REDUCTION);
// Cart Rules array are merged manually in order to avoid doubles
foreach ($this->getCartRules(CartRule::FILTER_ACTION_GIFT) as $tmp_cart_rule) {
$flag = false;
foreach ($cart_rules as $cart_rule) {
if ($tmp_cart_rule['id_cart_rule'] == $cart_rule['id_cart_rule']) {
$flag = true;
}
}
if (!$flag) {
$cart_rules[] = $tmp_cart_rule;
}
}
}
$id_address_delivery = 0;
if (isset($products[0])) {
$id_address_delivery = (is_null($products) ? $this->id_address_delivery : $products[0]['id_address_delivery']);
}
$package = array('id_carrier' => $id_carrier, 'id_address' => $id_address_delivery, 'products' => $products);
// Then, calculate the contextual value for each one
$flag = false;
foreach ($cart_rules as $cart_rule) {
// If the cart rule offers free shipping, add the shipping cost
if (($with_shipping || $type == Cart::ONLY_DISCOUNTS) && $cart_rule['obj']->free_shipping && !$flag) {
$order_shipping_discount = (float)Tools::ps_round($cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_SHIPPING, ($param_product ? $package : null), $use_cache), $compute_precision);
$flag = true;
}
// If the cart rule is a free gift, then add the free gift value only if the gift is in this package
if ((int)$cart_rule['obj']->gift_product) {
$in_order = false;
if (is_null($products)) {
$in_order = true;
} else {
foreach ($products as $product) {
if ($cart_rule['obj']->gift_product == $product['id_product'] && $cart_rule['obj']->gift_product_attribute == $product['id_product_attribute']) {
$in_order = true;
}
}
}
if ($in_order) {
$order_total_discount += $cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_GIFT, $package, $use_cache);
}
}
// If the cart rule offers a reduction, the amount is prorated (with the products in the package)
if ($cart_rule['obj']->reduction_percent > 0 || $cart_rule['obj']->reduction_amount > 0) {
$order_total_discount += Tools::ps_round($cart_rule['obj']->getContextualValue($with_taxes, $virtual_context, CartRule::FILTER_ACTION_REDUCTION, $package, $use_cache), $compute_precision);
}
}
$order_total_discount = min(Tools::ps_round($order_total_discount, 2), (float)$order_total_products) + (float)$order_shipping_discount;
$order_total -= $order_total_discount;
}
if ($type == Cart::BOTH) {
$order_total += $shipping_fees + $wrapping_fees;
}
if ($order_total < 0 && $type != Cart::ONLY_DISCOUNTS) {
return 0;
}
if ($type == Cart::ONLY_DISCOUNTS) {
return $order_total_discount;
}
return Tools::ps_round((float)$order_total, $compute_precision);
}
}