Jump to content

Strict Standards: Declaration of ProductCore


CLance

Recommended Posts

I have this notification message:

 

Strict Standards: Declaration of ProductCore::validateField() should be compatible with that of ObjectModelCore::validateField() in /home/eviewtra/public_html/denmall/classes/Product.php on line 0

 

I have a blank product page, but I do not know how to solve this.

Please help me or guide me.

Thanks

Link to comment
Share on other sites

I did a lot of customization. But I am not sure when this happen.
Actually if I ignore the error, the webpage still look like usual, I just want to solve this to avoid any problem in future.

 

I am using Prestashop 1.5.6.1

This is the code in shop/product.php

<?php
/*
* 2007-2013 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
*  @author PrestaShop SA <[email protected]>
*  @copyright  2007-2013 PrestaShop SA
*  @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
*  International Registered Trademark & Property of PrestaShop SA
*/

/**
 * This file will be removed in 1.6
 * You have to use index.php?controller=page_name instead of this page
 *
 * @deprecated 1.5.0
 */

require(dirname(__FILE__).'/config/config.inc.php');
Tools::displayFileAsDeprecated();

Tools::redirect('index.php?controller=product'.((count($_GET) || count($_POST)) ? '&'.http_build_query(array_merge($_GET, $_POST), '', '&') : ''), __PS_BASE_URI__, null, 'HTTP/1.1 301 Moved Permanently');

You can have a look on my website:
http://eviewtrading.com/denmall/denmall/

Edited by Lance Chan (see edit history)
Link to comment
Share on other sites

Ops. Sorry.

Here you are the full version.

<?php
/*
* 2007-2013 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
*  @author PrestaShop SA <[email protected]>
*  @copyright  2007-2013 PrestaShop SA
*  @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
*  International Registered Trademark & Property of PrestaShop SA
*/

/**
 * @deprecated 1.5.0.1
 */
define('_CUSTOMIZE_FILE_', 0);
/**
 * @deprecated 1.5.0.1
 */
define('_CUSTOMIZE_TEXTFIELD_', 1);

class ProductCore extends ObjectModel
{
	/** @var string Tax name */
	public $tax_name;

	/** @var string Tax rate */
	public $tax_rate;

	/** @var integer Manufacturer id */
	public $id_manufacturer;

	/** @var integer Supplier id */
	public $id_supplier;

	/** @var integer default Category id */
	public $id_category_default;

	/** @var integer default Shop id */
	public $id_shop_default;

	/** @var string Manufacturer name */
	public $manufacturer_name;

	/** @var string Supplier name */
	public $supplier_name;

	/** @var string Name */
	public $name;

	/** @var string Long description */
	public $description;

	/** @var string Short description */
	public $description_short;

	/** @var integer Quantity available */
	public $quantity = 0;

	/** @var integer Minimal quantity for add to cart */
	public $minimal_quantity = 1;

	/** @var string available_now */
	public $available_now;

	/** @var string available_later */
	public $available_later;

	/** @var float Price in euros */
	public $price = 0;

	/** @var float Additional shipping cost */
	public $additional_shipping_cost = 0;

	/** @var float Wholesale Price in euros */
	public $wholesale_price = 0;

	/** @var boolean on_sale */
	public $on_sale = false;

	/** @var boolean online_only */
	public $online_only = false;

	/** @var string unity */
	public $unity = null;

		/** @var float price for product's unity */
	public $unit_price;

		/** @var float price for product's unity ratio */
	public $unit_price_ratio = 0;

	/** @var float Ecotax */
	public $ecotax = 0;

	/** @var string Reference */
	public $reference;

	/** @var string Supplier Reference */
	public $supplier_reference;

	/** @var string Location */
	public $location;

	/** @var string Width in default width unit */
	public $width = 0;

	/** @var string Height in default height unit */
	public $height = 0;

	/** @var string Depth in default depth unit */
	public $depth = 0;

	/** @var string Weight in default weight unit */
	public $weight = 0;

	/** @var string Ean-13 barcode */
	public $ean13;

	/** @var string Upc barcode */
	public $upc;

	/** @var string Friendly URL */
	public $link_rewrite;

	/** @var string Meta tag description */
	public $meta_description;

	/** @var string Meta tag keywords */
	public $meta_keywords;

	/** @var string Meta tag title */
	public $meta_title;

	/** @var boolean Product statuts */
	public $quantity_discount = 0;

	/** @var boolean Product customization */
	public $customizable;

	/** @var boolean Product is new */
	public $new = null;

	/** @var integer Number of uploadable files (concerning customizable products) */
	public $uploadable_files;

	/** @var int Number of text fields */
	public $text_fields;

	/** @var boolean Product statuts */
	public $active = true;
	
	/** @var boolean Product statuts */
	public $redirect_type = '';
	
	/** @var boolean Product statuts */
	public $id_product_redirected = 0;
		
	/** @var boolean Product available for order */
	public $available_for_order = true;

	/** @var string Object available order date */
	public $available_date = '0000-00-00';

	/** @var enum Product condition (new, used, refurbished) */
	public $condition;

	/** @var boolean Show price of Product */
	public $show_price = true;

	/** @var boolean is the product indexed in the search index? */
	public $indexed = 0;

	/** @var string ENUM('both', 'catalog', 'search', 'none') front office visibility */
	public $visibility;

	/** @var string Object creation date */
	public $date_add;

	/** @var string Object last modification date */
	public $date_upd;

	/*** @var array Tags */
	public $tags;

	public $id_tax_rules_group = 1;

	/**
	 * We keep this variable for retrocompatibility for themes
	 * @deprecated 1.5.0
	 */
	public $id_color_default = 0;

	/**
	 * @since 1.5.0
	 * @var boolean Tells if the product uses the advanced stock management
	 */
	public $advanced_stock_management = 0;
	public $out_of_stock;
	public $depends_on_stock;

	public $isFullyLoaded = false;

	public $cache_is_pack;
	public $cache_has_attachments;
	public $is_virtual;
	public $cache_default_attribute;

	/**
	 * @var string If product is populated, this property contain the rewrite link of the default category
	 */
	public $category;

	public static $_taxCalculationMethod = null;
	protected static $_prices = array();
	protected static $_pricesLevel2 = array();
	protected static $_incat = array();
	protected static $_cart_quantity = array();
	protected static $_tax_rules_group = array();
	protected static $_cacheFeatures = array();
	protected static $_frontFeaturesCache = array();
	protected static $producPropertiesCache = array();

	/** @var array cache stock data in getStock() method */
	protected static $cacheStock = array();

	public static $definition = array(
		'table' => 'product',
		'primary' => 'id_product',
		'multilang' => true,
		'multilang_shop' => true,
		'fields' => array(
			// Classic fields
			'id_shop_default' => 			array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'id_manufacturer' => 			array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'id_supplier' => 				array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
			'reference' => 					array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 32),
			'supplier_reference' => 		array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 32),
			'location' => 					array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 64),
			'width' => 						array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
			'height' => 					array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
			'depth' => 						array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
			'weight' => 					array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
			'quantity_discount' => 			array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
			'ean13' => 						array('type' => self::TYPE_STRING, 'validate' => 'isEan13', 'size' => 13),
			'upc' => 						array('type' => self::TYPE_STRING, 'validate' => 'isUpc', 'size' => 12),
			'cache_is_pack' => 				array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
			'cache_has_attachments' => 		array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
			'is_virtual' => 				array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),

			/* Shop fields */
			'id_category_default' => 		array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedId'),
			'id_tax_rules_group' => 		array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedId'),
			'on_sale' => 					array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'online_only' => 				array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'ecotax' => 					array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice'),
			'minimal_quantity' => 			array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
			'price' => 						array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice', 'required' => true),
			'wholesale_price' => 			array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice'),
			'unity' => 						array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isString'),
			'unit_price_ratio' => 			array('type' => self::TYPE_FLOAT, 'shop' => true),
			'additional_shipping_cost' => 	array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice'),
			'customizable' => 				array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
			'text_fields' => 				array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
			'uploadable_files' => 			array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
			'active' => 					array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'redirect_type' => 				array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isString'),
			'id_product_redirected' => 		array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedId'),
			'available_for_order' => 		array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'available_date' => 			array('type' => self::TYPE_DATE, 'shop' => true, 'validate' => 'isDateFormat'),
			'condition' => 					array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isGenericName', 'values' => array('new', 'used', 'refurbished'), 'default' => 'new'),
			'show_price' => 				array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'indexed' => 					array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'visibility' => 				array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isProductVisibility', 'values' => array('both', 'catalog', 'search', 'none'), 'default' => 'both'),
			'cache_default_attribute' => 	array('type' => self::TYPE_INT, 'shop' => true),
			'advanced_stock_management' => 	array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
			'date_add' => 					array('type' => self::TYPE_DATE, 'shop' => true, 'validate' => 'isDateFormat'),
			'date_upd' => 					array('type' => self::TYPE_DATE, 'shop' => true, 'validate' => 'isDateFormat'),

			/* Lang fields */
			'meta_description' => 			array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
			'meta_keywords' => 				array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
			'meta_title' => 				array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 128),
			'link_rewrite' => 				array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 128),
			'name' => 						array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCatalogName', 'required' => true, 'size' => 128),
			'description' => 				array('type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isString'),
			'description_short' => 			array('type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isString'),
			'available_now' => 				array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
			'available_later' => 			array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'IsGenericName', 'size' => 255),
		),
		'associations' => array(
			'manufacturer' => 				array('type' => self::HAS_ONE),
			'supplier' => 					array('type' => self::HAS_ONE),
			'default_category' => 			array('type' => self::HAS_ONE, 'field' => 'id_category_default', 'object' => 'Category'),
			'tax_rules_group' => 			array('type' => self::HAS_ONE),
			'categories' =>					array('type' => self::HAS_MANY, 'field' => 'id_category', 'object' => 'Category', 'association' => 'category_product'),
			'stock_availables' =>			array('type' => self::HAS_MANY, 'field' => 'id_stock_available', 'object' => 'StockAvailable', 'association' => 'stock_availables'),
		),
	);

	protected $webserviceParameters = array(
		'objectMethods' => array(
			'add' => 'addWs',
			'update' => 'updateWs'
		),
		'objectNodeNames' => 'products',
		'fields' => array(
			'id_manufacturer' => array(
				'xlink_resource' => 'manufacturers'
			),
			'id_supplier' => array(
				'xlink_resource' => 'suppliers'
			),
			'id_category_default' => array(
				'xlink_resource' => 'categories'
			),
			'new' => array(),
			'cache_default_attribute' => array(),
			'id_default_image' => array(
				'getter' => 'getCoverWs',
				'setter' => 'setCoverWs',
				'xlink_resource' => array(
					'resourceName' => 'images',
					'subResourceName' => 'products'
				)
			),
			'id_default_combination' => array(
				'getter' => 'getWsDefaultCombination',
				'setter' => 'setWsDefaultCombination',
				'xlink_resource' => array(
					'resourceName' => 'combinations'
				)
			),
			'id_tax_rules_group' => array(
				'xlink_resource' => array(
					'resourceName' => 'tax_rules_group'
				)
			),
			'position_in_category' => array(
				'getter' => 'getWsPositionInCategory',
				'setter' => false
			),
			'manufacturer_name' => array(
				'getter' => 'getWsManufacturerName',
				'setter' => false
			),
			'quantity' => array(
				'getter' => false,
				'setter' => false
			),
			'type' => array(
				'getter' => 'getWsType',
				'setter' => false,
			),
		),
		'associations' => array(
			'categories' => array(
				'resource' => 'category',
				'fields' => array(
					'id' => array('required' => true),
				)
			),
			'images' => array(
				'resource' => 'image',
				'fields' => array('id' => array())
			),
			'combinations' => array(
				'resource' => 'combinations',
				'fields' => array(
					'id' => array('required' => true),
				)
			),
			'product_option_values' => array(
				'resource' => 'product_options_values',
				'fields' => array(
					'id' => array('required' => true),
				)
			),
			'product_features' => array(
				'resource' => 'product_feature',
				'fields' => array(
					'id' => array('required' => true),
					'id_feature_value' => array(
						'required' => true,
						'xlink_resource' => 'product_feature_values'
					),
				)
			),
			'tags' => array('resource' => 'tag',
				'fields' => array(
					'id' => array('required' => true),
			)),
			'stock_availables' => array('resource' => 'stock_available',
				'fields' => array(
					'id' => array('required' => true),
					'id_product_attribute' => array('required' => true),
				),
				'setter' => false
			),
			'accessories' => array(
				'resource' => 'product',
				'fields' => array(
					'id' => array(
						'required' => true,
						'xlink_resource' => 'product'),
				)
			),
			'product_bundle' => array(
				'resource' => 'products',
				'fields' => array(
					'id' => array('required' => true),
					'quantity' => array(),
				),
			),
		),
	);

	const CUSTOMIZE_FILE = 0;
	const CUSTOMIZE_TEXTFIELD = 1;

	/**
	 * Note:  prefix is "PTYPE" because TYPE_ is used in ObjectModel (definition)
	 */
	const PTYPE_SIMPLE = 0;
	const PTYPE_PACK = 1;
	const PTYPE_VIRTUAL = 2;

	public function __construct($id_product = null, $full = false, $id_lang = null, $id_shop = null, Context $context = null)
	{
		parent::__construct($id_product, $id_lang, $id_shop);
		if (!$context)
			$context = Context::getContext();

		if ($full && $this->id)
		{
			$this->isFullyLoaded = $full;
			$this->tax_name = 'deprecated'; // The applicable tax may be BOTH the product one AND the state one (moreover this variable is some deadcode)
			$this->manufacturer_name = Manufacturer::getNameById((int)$this->id_manufacturer);
			$this->supplier_name = Supplier::getNameById((int)$this->id_supplier);
			$address = null;
			if (is_object($context->cart) && $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')} != null)
				$address = $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};

			$this->tax_rate = $this->getTaxesRate(new Address($address));

			$this->new = $this->isNew();

			// keep base price
			$this->base_price = $this->price;

			$this->price = Product::getPriceStatic((int)$this->id, false, null, 6, null, false, true, 1, false, null, null, null, $this->specificPrice);
			$this->unit_price = ($this->unit_price_ratio != 0  ? $this->price / $this->unit_price_ratio : 0);
			if ($this->id)
				$this->tags = Tag::getProductTags((int)$this->id);

			$this->loadStockData();
		}

		if ($this->id_category_default)
			$this->category = Category::getLinkRewrite((int)$this->id_category_default, (int)$id_lang);
	}

	/**
	 * @see ObjectModel::getFieldsShop()
	 * @return array
	 */
	public function getFieldsShop()
	{
		$fields = parent::getFieldsShop();
		if (is_null($this->update_fields) || (!empty($this->update_fields['price']) && !empty($this->update_fields['unit_price'])))
		$fields['unit_price_ratio'] = (float)$this->unit_price > 0 ? $this->price / $this->unit_price : 0;

		return $fields;
	}

	public function add($autodate = true, $null_values = false)
	{
		if (!parent::add($autodate, $null_values))
			return false;
			
		if ($this->getType() == Product::PTYPE_VIRTUAL)
			StockAvailable::setProductOutOfStock((int)$this->id, 1);
		else
			StockAvailable::setProductOutOfStock((int)$this->id, 2);

		$this->setGroupReduction();
		Hook::exec('actionProductSave', array('id_product' => $this->id));
		return true;
	}

	public function update($null_values = false)
	{
		$return = parent::update($null_values);
		$this->setGroupReduction();
		Hook::exec('actionProductSave', array('id_product' => $this->id));
		return $return;
	}

	public static function initPricesComputation($id_customer = null)
	{
		if ($id_customer)
		{
			$customer = new Customer((int)$id_customer);
			if (!Validate::isLoadedObject($customer))
				die(Tools::displayError());
			self::$_taxCalculationMethod = Group::getPriceDisplayMethod((int)$customer->id_default_group);
		}
		else
			self::$_taxCalculationMethod = Group::getPriceDisplayMethod(Group::getCurrent()->id);
	}

	public static function getTaxCalculationMethod($id_customer = null)
	{
		if (self::$_taxCalculationMethod === null || $id_customer !== null)
			Product::initPricesComputation($id_customer);

		return (int)self::$_taxCalculationMethod;
	}

	/**
	 * Move a product inside its category
	 * @param boolean $way Up (1)  or Down (0)
	 * @param integer $position
	 * return boolean Update result
	 */
	public function updatePosition($way, $position)
	{
		if (!$res = Db::getInstance()->executeS('
			SELECT cp.`id_product`, cp.`position`, cp.`id_category`
			FROM `'._DB_PREFIX_.'category_product` cp
			WHERE cp.`id_category` = '.(int)Tools::getValue('id_category', 1).'
			ORDER BY cp.`position` ASC'
		))
			return false;

		foreach ($res as $product)
			if ((int)$product['id_product'] == (int)$this->id)
				$moved_product = $product;

		if (!isset($moved_product) || !isset($position))
			return false;

		// < and > statements rather than BETWEEN operator
		// since BETWEEN is treated differently according to databases
		return (Db::getInstance()->execute('
			UPDATE `'._DB_PREFIX_.'category_product`
			SET `position`= `position` '.($way ? '- 1' : '+ 1').'
			WHERE `position`
			'.($way
				? '> '.(int)$moved_product['position'].' AND `position` <= '.(int)$position
				: '< '.(int)$moved_product['position'].' AND `position` >= '.(int)$position).'
			AND `id_category`='.(int)$moved_product['id_category'])
		&& Db::getInstance()->execute('
			UPDATE `'._DB_PREFIX_.'category_product`
			SET `position` = '.(int)$position.'
			WHERE `id_product` = '.(int)$moved_product['id_product'].'
			AND `id_category`='.(int)$moved_product['id_category']));
	}

	/*
	 * Reorder product position in category $id_category.
	 * Call it after deleting a product from a category.
	 *
	 * @param int $id_category
	 */
	public static function cleanPositions($id_category)
	{
		$return = true;

		$result = Db::getInstance()->executeS('
			SELECT `id_product`
			FROM `'._DB_PREFIX_.'category_product`
			WHERE `id_category` = '.(int)$id_category.'
			ORDER BY `position`
		');
		$total = count($result);

		for ($i = 0; $i < $total; $i++)
			$return &= Db::getInstance()->update('category_product', array(
				'position' => $i,
			), '`id_category` = '.(int)$id_category.' AND `id_product` = '.(int)$result[$i]['id_product']);

		return $return;
	}

	/**
	* Get the default attribute for a product
	*
	* @return int Attributes list
	*/
	public static function getDefaultAttribute($id_product, $minimum_quantity = 0)
	{
		static $combinations = array();

		if (!Combination::isFeatureActive())
			return 0;

		if (!isset($combinations[$id_product]))
			$combinations[$id_product] = array();
		if (isset($combinations[$id_product][$minimum_quantity]))
			return $combinations[$id_product][$minimum_quantity];

		$sql = 'SELECT pa.id_product_attribute
				FROM '._DB_PREFIX_.'product_attribute pa
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				'.($minimum_quantity > 0 ? Product::sqlStock('pa', 'pa') : '').
				' WHERE product_attribute_shop.default_on = 1 '
				.($minimum_quantity > 0 ? ' AND IFNULL(stock.quantity, 0) >= '.(int)$minimum_quantity : '').
				' AND pa.id_product = '.(int)$id_product;
		$result = Db::getInstance()->getValue($sql);

		if (!$result)
		{
			$sql = 'SELECT pa.id_product_attribute
					FROM '._DB_PREFIX_.'product_attribute pa
					'.Shop::addSqlAssociation('product_attribute', 'pa').'
					'.($minimum_quantity > 0 ? Product::sqlStock('pa', 'pa') : '').
					' WHERE pa.id_product = '.(int)$id_product
					.($minimum_quantity > 0 ? ' AND IFNULL(stock.quantity, 0) >= '.(int)$minimum_quantity : '');
			$result = Db::getInstance()->getValue($sql);
		}

		if (!$result)
		{
			$sql = 'SELECT pa.id_product_attribute
					FROM '._DB_PREFIX_.'product_attribute pa
					'.Shop::addSqlAssociation('product_attribute', 'pa').'
					WHERE product_attribute_shop.`default_on` = 1
					AND pa.id_product = '.(int)$id_product;
			$result = Db::getInstance()->getValue($sql);
		}

		if (!$result)
		{
			$sql = 'SELECT pa.id_product_attribute
					FROM '._DB_PREFIX_.'product_attribute pa
					'.Shop::addSqlAssociation('product_attribute', 'pa').'
					WHERE pa.id_product = '.(int)$id_product;
			$result = Db::getInstance()->getValue($sql);
		}
		
		$combinations[$id_product][$minimum_quantity] = $result;
		return $result;
	}

	public function setAvailableDate($available_date = '0000-00-00')
	{
		if (Validate::isDateFormat($available_date) && $this->available_date != $available_date)
		{
			$this->available_date = $available_date;
			return $this->update();
		}
		return false;
	}

	public static function updateIsVirtual($id_product)
	{
		Db::getInstance()->update('product', array(
			'is_virtual' => (int)$id_product,
		), 'id_product = '.(int)$id_product);
	}

	/**
	 * @see ObjectModel::validateFieldsLang()
	 */
	public function validateFieldsLang($die = true, $error_return = false)
	{
		$limit = (int)Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT');
		if ($limit <= 0)
			$limit = 800;
		$this->def['fields']['description_short']['size'] = $limit;

		return parent::validateFieldsLang($die, $error_return);
	}
	
	/**
	 * @see ObjectModel::validateField()
	 */
	public function validateField($field, $value, $id_lang = null)
	{
		$value = ($field == 'description_short' ? strip_tags($value) : $value);
		return parent::validateField($field, $value, $id_lang);
	}

	public function toggleStatus()
	{
		//test if the product is active and if redirect_type is empty string and set default value to id_product_redirected & redirect_type
		//  /!\ after parent::toggleStatus() active will be false, that why we set 404 by default 
		if ($this->active)
		{
			//case where active will be false after parent::toggleStatus()
			$this->id_product_redirected = 0;
			$this->redirect_type = '404';
		}
		else
		{	
			//case where active will be true after parent::toggleStatus()
			$this->id_product_redirected = 0;
			$this->redirect_type = '';
		}
		return parent::toggleStatus();
	}
	
	public function delete()
	{
		/*
		 * @since 1.5.0
		 * It is NOT possible to delete a product if there are currently:
		 * - physical stock for this product
		 * - supply order(s) for this product
		 */
		if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $this->advanced_stock_management)
		{
			$stock_manager = StockManagerFactory::getManager();
			$physical_quantity = $stock_manager->getProductPhysicalQuantities($this->id, 0);
			$real_quantity = $stock_manager->getProductRealQuantities($this->id, 0);
			if ($physical_quantity > 0)
				return false;
			if ($real_quantity > $physical_quantity)
				return false;
		}
		$result = parent::delete();

		// Removes the product from StockAvailable, for the current shop
		StockAvailable::removeProductFromStockAvailable($this->id);
		$result &= ($this->deleteProductAttributes() && $this->deleteImages() && $this->deleteSceneProducts());
		// If there are still entries in product_shop, don't remove completly the product
		if ($this->hasMultishopEntries())
			return true;
		
		Product::cleanPositions($this->id);
		
		Hook::exec('actionProductDelete', array('product' => $this));
		if (!$result ||
			!GroupReduction::deleteProductReduction($this->id) ||
			!$this->deleteCategories(true) ||
			!$this->deleteProductFeatures() ||
			!$this->deleteTags() ||
			!$this->deleteCartProducts() ||
			!$this->deleteAttributesImpacts() ||
			!$this->deleteAttachments() ||
			!$this->deleteCustomization() ||
			!SpecificPrice::deleteByProductId((int)$this->id) ||
			!$this->deletePack() ||
			!$this->deleteProductSale() ||
			!$this->deleteSearchIndexes() ||
			!$this->deleteAccessories() ||
			!$this->deleteFromAccessories() ||
			!$this->deleteFromSupplier() ||
			!$this->deleteDownload() ||
			!$this->deleteFromCartRules())
		return false;

		return true;
	}

	public function deleteSelection($products)
	{
		$return = 1;
		if (is_array($products) && ($count = count($products)))
		{
			// Deleting products can be quite long on a cheap server. Let's say 1.5 seconds by product (I've seen it!).
			if (intval(ini_get('max_execution_time')) < round($count * 1.5))
				ini_set('max_execution_time', round($count * 1.5));
			
			foreach ($products as $id_product)
			{
				$product = new Product((int)$id_product);
				$return &= $product->delete();
			}
		}
		return $return;
	}

	public function deleteFromCartRules()
	{
		CartRule::cleanProductRuleIntegrity('products', $this->id);
		return true;
	}
	
	public function deleteFromSupplier()
	{
		return Db::getInstance()->delete('product_supplier', 'id_product = '.(int)$this->id);
	}

	/**
	 * addToCategories add this product to the category/ies if not exists.
	 *
	 * @param mixed $categories id_category or array of id_category
	 * @return boolean true if succeed
	 */
	public function addToCategories($categories = array())
	{
		if (empty($categories))
			return false;

		if (!is_array($categories))
			$categories = array($categories);

		if (!count($categories))
			return false;

		$categories = array_map('intval', $categories);

		$current_categories = $this->getCategories();
		$current_categories = array_map('intval', $current_categories);

		// for new categ, put product at last position
		$res_categ_new_pos = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
			SELECT id_category, MAX(position)+1 newPos
			FROM `'._DB_PREFIX_.'category_product`
			WHERE `id_category` IN('.implode(',', $categories).')
			GROUP BY id_category');
		foreach ($res_categ_new_pos as $array)
			$new_categories[(int)$array['id_category']] = (int)$array['newPos'];

		$new_categ_pos = array();
		foreach ($categories as $id_category)
			$new_categ_pos[$id_category] = isset($new_categories[$id_category]) ? $new_categories[$id_category] : 0;

		$product_cats = array();

		foreach ($categories as $new_id_categ)
			if (!in_array($new_id_categ, $current_categories))
				$product_cats[] = array(
					'id_category' => (int)$new_id_categ,
					'id_product' => (int)$this->id,
					'position' => (int)$new_categ_pos[$new_id_categ],
				);

		Db::getInstance()->insert('category_product', $product_cats);
		return true;
	}

	/**
	* Update categories to index product into
	*
	* @param string $productCategories Categories list to index product into
	* @param boolean $keeping_current_pos (deprecated, no more used)
	* @return array Update/insertion result
	*/
	public function updateCategories($categories, $keeping_current_pos = false)
	{
		if (empty($categories))
			return false;

		$result = Db::getInstance()->executeS('
			SELECT c.`id_category`
			FROM `'._DB_PREFIX_.'category_product` cp
			LEFT JOIN `'._DB_PREFIX_.'category` c ON (c.`id_category` = cp.`id_category`)
			'.Shop::addSqlAssociation('category', 'c', true, null, true).'
			WHERE cp.`id_category` NOT IN ('.implode(',', array_map('intval', $categories)).')
			AND cp.id_product = '.$this->id
		);

		foreach ($result as $categ_to_delete)
			$this->deleteCategory($categ_to_delete['id_category']);
		// if none are found, it's an error
		if (!is_array($result))
			return false;

		if (!$this->addToCategories($categories))
			return false;

		SpecificPriceRule::applyAllRules(array((int)$this->id));
		return true;
	}

	/**
	 * deleteCategory delete this product from the category $id_category
	 *
	 * @param mixed $id_category
	 * @param mixed $clean_positions
	 * @return boolean
	 */
	public function deleteCategory($id_category, $clean_positions = true)
	{
		$result = Db::getInstance()->executeS(
			'SELECT `id_category`
			FROM `'._DB_PREFIX_.'category_product`
			WHERE `id_product` = '.(int)$this->id.'
			AND id_category = '.(int)$id_category.''
		);

		$return = Db::getInstance()->delete('category_product', 'id_product = '.(int)$this->id.' AND id_category = '.(int)$id_category);
		if ($clean_positions === true)
			foreach ($result as $row)
				$this->cleanPositions((int)$row['id_category']);
		SpecificPriceRule::applyAllRules(array((int)$this->id));
		return $return;
	}

	/**
	* Delete all association to category where product is indexed
	*
	* @param boolean $clean_positions clean category positions after deletion
	* @return array Deletion result
	*/
	public function deleteCategories($clean_positions = false)
	{
		if ($clean_positions === true)
			$result = Db::getInstance()->executeS(
				'SELECT `id_category`
				FROM `'._DB_PREFIX_.'category_product`
				WHERE `id_product` = '.(int)$this->id
			);

		$return = Db::getInstance()->delete('category_product', 'id_product = '.(int)$this->id);
		if ($clean_positions === true && is_array($result))
			foreach ($result as $row)
					$return &= $this->cleanPositions((int)$row['id_category']);
		
		return $return;
	}

	/**
	* Delete products tags entries
	*
	* @return array Deletion result
	*/
	public function deleteTags()
	{
		return Db::getInstance()->delete('product_tag', 'id_product = '.(int)$this->id)
			&& Db::getInstance()->delete('tag', 'id_tag NOT IN (SELECT id_tag FROM '._DB_PREFIX_.'product_tag)');
	}

	/**
	* Delete product from cart
	*
	* @return array Deletion result
	*/
	public function deleteCartProducts()
	{
		return Db::getInstance()->delete('cart_product', 'id_product = '.(int)$this->id);
	}

	/**
	* Delete product images from database
	*
	* @return bool success
	*/
	public function deleteImages()
	{
		$result = Db::getInstance()->executeS('
			SELECT `id_image`
			FROM `'._DB_PREFIX_.'image`
			WHERE `id_product` = '.(int)$this->id
		);

		$status = true;
		if ($result)
			foreach ($result as $row)
			{
				$image = new Image($row['id_image']);
				$status &= $image->delete();
			}
		return $status;
	}

	/**
	 * @deprecated 1.5.0 Use Combination::getPrice()
	 */
	public static function getProductAttributePrice($id_product_attribute)
	{
		return Combination::getPrice($id_product_attribute);
	}

	/**
	* Get all available products
	*
	* @param integer $id_lang Language id
	* @param integer $start Start number
	* @param integer $limit Number of products to return
	* @param string $order_by Field for ordering
	* @param string $order_way Way for ordering (ASC or DESC)
	* @return array Products details
	*/
	public static function getProducts($id_lang, $start, $limit, $order_by, $order_way, $id_category = false,
		$only_active = false, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$front = true;
		if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
			$front = false;

		if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
			die (Tools::displayError());
		if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add' || $order_by == 'date_upd')
			$order_by_prefix = 'p';
		else if ($order_by == 'name')
			$order_by_prefix = 'pl';
		else if ($order_by == 'position')
			$order_by_prefix = 'c';

		if (strpos($order_by, '.') > 0)
		{
			$order_by = explode('.', $order_by);
			$order_by_prefix = $order_by[0];
			$order_by = $order_by[1];
		}
		$sql = 'SELECT p.*, product_shop.*, pl.* , m.`name` AS manufacturer_name, s.`name` AS supplier_name
				FROM `'._DB_PREFIX_.'product` p
				'.Shop::addSqlAssociation('product', 'p').'
				LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` '.Shop::addSqlRestrictionOnLang('pl').')
				LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
				LEFT JOIN `'._DB_PREFIX_.'supplier` s ON (s.`id_supplier` = p.`id_supplier`)'.
				($id_category ? 'LEFT JOIN `'._DB_PREFIX_.'category_product` c ON (c.`id_product` = p.`id_product`)' : '').'
				WHERE pl.`id_lang` = '.(int)$id_lang.
					($id_category ? ' AND c.`id_category` = '.(int)$id_category : '').
					($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').
					($only_active ? ' AND product_shop.`active` = 1' : '').'
				ORDER BY '.(isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way).
				($limit > 0 ? ' LIMIT '.(int)$start.','.(int)$limit : '');
		$rq = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
		if ($order_by == 'price')
			Tools::orderbyPrice($rq, $order_way);

		foreach ($rq as &$row)
			$row = Product::getTaxesInformations($row);

		return ($rq);
	}

	public static function getSimpleProducts($id_lang, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$front = true;
		if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
			$front = false;

		$sql = 'SELECT p.`id_product`, pl.`name`
				FROM `'._DB_PREFIX_.'product` p
				'.Shop::addSqlAssociation('product', 'p').'
				LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` '.Shop::addSqlRestrictionOnLang('pl').')
				WHERE pl.`id_lang` = '.(int)$id_lang.'
				'.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
				ORDER BY pl.`name`';
		return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
	}

	public function isNew()
	{
		$result = Db::getInstance()->executeS('
			SELECT p.id_product
			FROM `'._DB_PREFIX_.'product` p
			'.Shop::addSqlAssociation('product', 'p').'
			WHERE p.id_product = '.(int)$this->id.'
			AND DATEDIFF(
				product_shop.`date_add`,
				DATE_SUB(
					NOW(),
					INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
				)
			) > 0
		');
		return count($result) > 0;
	}

	public function productAttributeExists($attributes_list, $current_product_attribute = false, Context $context = null, $all_shops = false, $return_id = false)
	{
		if (!Combination::isFeatureActive())
			return false;
		if ($context === null)
			$context = Context::getContext();
		$result = Db::getInstance()->executeS(
			'SELECT pac.`id_attribute`, pac.`id_product_attribute`
			FROM `'._DB_PREFIX_.'product_attribute` pa
			JOIN `'._DB_PREFIX_.'product_attribute_shop` pas ON (pas.id_product_attribute = pa.id_product_attribute)
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
			WHERE 1 '.(!$all_shops ? ' AND pas.id_shop ='.(int)$context->shop->id : '').' AND pa.`id_product` = '.(int)$this->id.
			($all_shops ? ' GROUP BY pac.id_attribute, pac.id_product_attribute ' : '')
		);

		/* If something's wrong */
		if (!$result || empty($result))
			return false;
		/* Product attributes simulation */
		$product_attributes = array();
		foreach ($result as $product_attribute)
			$product_attributes[$product_attribute['id_product_attribute']][] = $product_attribute['id_attribute'];
		/* Checking product's attribute existence */
		foreach ($product_attributes as $key => $product_attribute)
			if (count($product_attribute) == count($attributes_list))
			{
				$diff = false;
				for ($i = 0; $diff == false && isset($product_attribute[$i]); $i++)
					if (!in_array($product_attribute[$i], $attributes_list) || $key == $current_product_attribute)
						$diff = true;
				if (!$diff)
				{
					if ($return_id)
						return $key;
					return true;
				}
			}

		return false;
	}

	/**
	 * addProductAttribute is deprecated
	 *
	 * The quantity params now set StockAvailable for the current shop with the specified quantity
	 * The supplier_reference params now set the supplier reference of the default supplier of the product if possible
	 *
	 * @see StockManager if you want to manage real stock
	 * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
	 * @see ProductSupplier for manage supplier reference(s)
	 *
	 * @deprecated since 1.5.0
	 */
	public function addProductAttribute($price, $weight, $unit_impact, $ecotax, $quantity, $id_images, $reference,
		$id_supplier = null, $ean13, $default, $location = null, $upc = null, $minimal_quantity = 1)
	{
		Tools::displayAsDeprecated();

		$id_product_attribute = $this->addAttribute(
			$price, $weight, $unit_impact, $ecotax, $id_images,
			$reference, $ean13, $default, $location, $upc, $minimal_quantity
		);

		if (!$id_product_attribute)
			return false;

		StockAvailable::setQuantity($this->id, $id_product_attribute, $quantity);
		//Try to set the default supplier reference
		$this->addSupplierReference($id_supplier, $id_product_attribute);
		return $id_product_attribute;
	}
	
	public function generateMultipleCombinations($combinations, $attributes)
	{
		$attributes_list = array();
		$res = true;	
		$default_on = 1;
		foreach ($combinations as $key => $combination)
		{
			$id_combination = (int)$this->productAttributeExists($attributes[$key], false, null, true, true);
			$obj = new Combination($id_combination);
			
			if ($id_combination)
			{
				$obj->minimal_quantity = 1;
				$obj->available_date = '0000-00-00';
			}

			foreach ($combination as $field => $value)
				$obj->$field = $value;

			$obj->default_on = $default_on;
			$default_on = 0;

			$obj->save();
		
			if (!$id_combination)
			{
				$attribute_list = array();
				foreach ($attributes[$key] as $id_attribute)
					$attribute_list[] = array(
						'id_product_attribute' => (int)$obj->id,
						'id_attribute' => (int)$id_attribute
					);
				$res &= Db::getInstance()->insert('product_attribute_combination', $attribute_list);
			}
		}

		return $res;
	}
	
	/**
	* @param integer $quantity DEPRECATED
	* @param string $supplier_reference DEPRECATED
	*/
	public function addCombinationEntity($wholesale_price, $price, $weight, $unit_impact, $ecotax, $quantity,
		$id_images, $reference, $id_supplier, $ean13, $default, $location = null, $upc = null, $minimal_quantity = 1,  array $id_shop_list = array())
	{
		$id_product_attribute = $this->addAttribute(
			$price, $weight, $unit_impact, $ecotax, $id_images,
			$reference, $ean13, $default, $location, $upc, $minimal_quantity, $id_shop_list);

		$this->addSupplierReference($id_supplier, $id_product_attribute);
		$result = ObjectModel::updateMultishopTable('Combination', array(
			'wholesale_price' => (float)$wholesale_price,
		), 'a.id_product_attribute = '.(int)$id_product_attribute);

		if (!$id_product_attribute || !$result)
			return false;

		return $id_product_attribute;
	}

	public function addProductAttributeMultiple($attributes, $set_default = true)
	{
		Tools::displayAsDeprecated();
		$return = array();
		$default_value = 1;
		foreach ($attributes as &$attribute)
		{
			$obj = new Combination();
			foreach ($attribute as $key => $value)
				$obj->$key = $value;

			if ($set_default)
			{
				$obj->default_on = $default_value;
				$default_value = 0;
				// if we add a combination for this shop and this product does not use the combination feature in other shop,
				// we clone the default combination in every shop linked to this product
				if (!$this->hasAttributesInOtherShops())
				{
					$id_shop_list_array = Product::getShopsByProduct($this->id);
					$id_shop_list = array();
					foreach ($id_shop_list_array as $array_shop)
						$id_shop_list[] = $array_shop['id_shop'];
					$obj->id_shop_list = $id_shop_list;
				}
			}
			$obj->add();
			$return[] = $obj->id;
		}

		return $return;
	}

	/**
	* Del all default attributes for product
	*/
	public function deleteDefaultAttributes()
	{
		return ObjectModel::updateMultishopTable('Combination', array(
			'default_on' => 0,
		), 'id_product = '.(int)$this->id);
	}

	public function setDefaultAttribute($id_product_attribute)
	{
		$result = ObjectModel::updateMultishopTable('Combination', array(
			'default_on' => 1
		), '`id_product` = '.(int)$this->id.' AND a.`id_product_attribute` = '.(int)$id_product_attribute);

		$result &= ObjectModel::updateMultishopTable('product', array(
			'cache_default_attribute' => (int)$id_product_attribute,
		), 'a.`id_product` = '.(int)$this->id);
		$this->cache_default_attribute = (int)$id_product_attribute;
		return $result;
	}

	public static function updateDefaultAttribute($id_product)
	{
		$id_default_attribute = (int)Product::getDefaultAttribute($id_product);
		$result =  Db::getInstance()->update('product_shop', array(
			'cache_default_attribute' => $id_default_attribute,
		), 'id_product = '.(int)$id_product);
		$result &=  Db::getInstance()->update('product', array(
			'cache_default_attribute' => $id_default_attribute,
		), 'id_product = '.(int)$id_product);
		return $result;
	}

	/**
	* Update a product attribute
	*
	* @deprecated since 1.5
	* @see updateAttribute() to use instead
	* @see ProductSupplier for manage supplier reference(s)
	*
	*/
	public function updateProductAttribute($id_product_attribute, $wholesale_price, $price, $weight, $unit, $ecotax,
		$id_images, $reference, $id_supplier = null, $ean13, $default, $location = null, $upc = null, $minimal_quantity, $available_date)
	{
		Tools::displayAsDeprecated();

		$return = $this->updateAttribute(
			$id_product_attribute, $wholesale_price, $price, $weight, $unit, $ecotax,
			$id_images, $reference, $ean13, $default, $location = null, $upc = null, $minimal_quantity, $available_date
		);
		$this->addSupplierReference($id_supplier, $id_product_attribute);

		return $return;
	}

	/**
	 * Sets Supplier Reference
	 *
	 * @param int $id_supplier
	 * @param int $id_product_attribute
	 * @param string $supplier_reference
	 * @param float $price
	 * @param int $id_currency
	 */
	public function addSupplierReference($id_supplier, $id_product_attribute, $supplier_reference = null, $price = null, $id_currency = null)
	{
		//in some case we need to add price without supplier reference
		if ($supplier_reference === null)
			$supplier_reference = '';
		
		//Try to set the default supplier reference
		if ($id_supplier > 0)
		{
			$id_product_supplier = (int)ProductSupplier::getIdByProductAndSupplier($this->id, $id_product_attribute, $id_supplier);

			if (!$id_product_supplier)
			{
				//create new record
				$product_supplier_entity = new ProductSupplier();
				$product_supplier_entity->id_product = (int)$this->id;
				$product_supplier_entity->id_product_attribute = (int)$id_product_attribute;
				$product_supplier_entity->id_supplier = (int)$id_supplier;
				$product_supplier_entity->product_supplier_reference = pSQL($supplier_reference);
				$product_supplier_entity->product_supplier_price_te = (int)$price;
				$product_supplier_entity->id_currency = (int)$id_currency;
				$product_supplier_entity->save();
			}
			else
			{
				$product_supplier = new ProductSupplier((int)$id_product_supplier);
				$product_supplier->product_supplier_reference = pSQL($supplier_reference);
				$product_supplier->update();
			}
		}
	}

	/**
	* Update a product attribute
	*
	* @param integer $id_product_attribute Product attribute id
	* @param float $wholesale_price Wholesale price
	* @param float $price Additional price
	* @param float $weight Additional weight
	* @param float $unit
	* @param float $ecotax Additional ecotax
	* @param integer $id_image Image id
	* @param string $reference Reference
	* @param string $ean13 Ean-13 barcode
	* @param int $default Default On
	* @param string $upc Upc barcode
	* @param string $minimal_quantity Minimal quantity
	* @return array Update result
	*/
	public function updateAttribute($id_product_attribute, $wholesale_price, $price, $weight, $unit, $ecotax,
		$id_images, $reference, $ean13, $default, $location = null, $upc = null, $minimal_quantity = null, $available_date = null, $update_all_fields = true, array $id_shop_list = array())
	{
		$combination = new Combination($id_product_attribute);

		if (!$update_all_fields)
			$combination->setFieldsToUpdate(array(
				'price' => !is_null($price),
				'wholesale_price' => !is_null($wholesale_price),
				'ecotax' => !is_null($ecotax),
				'weight' => !is_null($weight),
				'unit_price_impact' => !is_null($unit),
				'default_on' => !is_null($default),
				'minimal_quantity' => !is_null($minimal_quantity),
				'available_date' => !is_null($available_date),
			));

		$price = str_replace(',', '.', $price);
		$weight = str_replace(',', '.', $weight);

		$combination->price = (float)$price;
		$combination->wholesale_price = (float)$wholesale_price;
		$combination->ecotax = (float)$ecotax;
		$combination->weight = (float)$weight;
		$combination->unit_price_impact = (float)$unit;
		$combination->reference = pSQL($reference);
		$combination->location = pSQL($location);
		$combination->ean13 = pSQL($ean13);
		$combination->upc = pSQL($upc);
		$combination->default_on = (int)$default;
		$combination->minimal_quantity = (int)$minimal_quantity;
		$combination->available_date = $available_date ? pSQL($available_date) : '0000-00-00';

		if (count($id_shop_list))
			$combination->id_shop_list = $id_shop_list;

		$combination->save();

		if (!empty($id_images))
			$combination->setImages($id_images);

		Product::updateDefaultAttribute($this->id);

		Hook::exec('actionProductAttributeUpdate', array('id_product_attribute' => $id_product_attribute));

		return true;
	}

	/**
	 * Add a product attribute
	 * @since 1.5.0.1
	 *
	 * @param float $price Additional price
	 * @param float $weight Additional weight
	 * @param float $ecotax Additional ecotax
	 * @param integer $id_images Image ids
	 * @param string $reference Reference
	 * @param string $location Location
	 * @param string $ean13 Ean-13 barcode
	 * @param boolean $default Is default attribute for product
	 * @param integer $minimal_quantity Minimal quantity to add to cart
	 * @return mixed $id_product_attribute or false
	 */
	public function addAttribute($price, $weight, $unit_impact, $ecotax, $id_images, $reference, $ean13,
								 $default, $location = null, $upc = null, $minimal_quantity = 1, array $id_shop_list = array())
	{
		if (!$this->id)
			return;

		$price = str_replace(',', '.', $price);
		$weight = str_replace(',', '.', $weight);

		$combination = new Combination();
		$combination->id_product = (int)$this->id;
		$combination->price = (float)$price;
		$combination->ecotax = (float)$ecotax;
		$combination->quantity = 0;
		$combination->weight = (float)$weight;
		$combination->unit_price_impact = (float)$unit_impact;
		$combination->reference = pSQL($reference);
		$combination->location = pSQL($location);
		$combination->ean13 = pSQL($ean13);
		$combination->upc = pSQL($upc);
		$combination->default_on = (int)$default;
		$combination->minimal_quantity = (int)$minimal_quantity;

		// if we add a combination for this shop and this product does not use the combination feature in other shop,
		// we clone the default combination in every shop linked to this product
		if ($default && !$this->hasAttributesInOtherShops())
		{
			$id_shop_list_array = Product::getShopsByProduct($this->id);
			foreach ($id_shop_list_array as $array_shop)
				$id_shop_list[] = $array_shop['id_shop'];
		}

		if (count($id_shop_list))
			$combination->id_shop_list = $id_shop_list;

		$combination->add();

		if (!$combination->id)
			return false;

		Product::updateDefaultAttribute($this->id);

		if (!empty($id_images))
			$combination->setImages($id_images);

		return (int)$combination->id;
	}


	/**
	 * @deprecated since 1.5.0
	 */
	public function updateQuantityProductWithAttributeQuantity()
	{
		Tools::displayAsDeprecated();

		return Db::getInstance()->execute('
		UPDATE `'._DB_PREFIX_.'product`
		SET `quantity` = IFNULL(
		(
			SELECT SUM(`quantity`)
			FROM `'._DB_PREFIX_.'product_attribute`
			WHERE `id_product` = '.(int)$this->id.'
		), \'0\')
		WHERE `id_product` = '.(int)$this->id);
	}
	/**
	* Delete product attributes
	*
	* @return array Deletion result
	*/
	public function deleteProductAttributes()
	{
		Hook::exec('actionProductAttributeDelete', array('id_product_attribute' => 0, 'id_product' => $this->id, 'deleteAllAttributes' => true));

		$result = true;
		$combinations = new Collection('Combination');
		$combinations->where('id_product', '=', $this->id);
		foreach ($combinations as $combination)
			$result &= $combination->delete();
		SpecificPriceRule::applyAllRules(array((int)$this->id));
		return $result;
	}

	/**
	* Delete product attributes impacts
	*
	* @return Deletion result
	*/
	public function deleteAttributesImpacts()
	{
		return Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'attribute_impact`
			WHERE `id_product` = '.(int)$this->id
		);
	}

	/**
	* Delete product features
	*
	* @return array Deletion result
	*/
	public function deleteProductFeatures()
	{
		SpecificPriceRule::applyAllRules(array((int)$this->id));
		return $this->deleteFeatures();
	}


	public static function updateCacheAttachment($id_product)
	{
		$value = (bool)Db::getInstance()->getValue('
								SELECT id_attachment
								FROM '._DB_PREFIX_.'product_attachment
								WHERE id_product='.(int)$id_product);
		return Db::getInstance()->update(
						'product', 
						array('cache_has_attachments' => (int)$value), 
						'id_product = '.(int)$id_product
					);
	}	
	
	/**
	* Delete product attachments
	*
	* @return array Deletion result
	*/
	public function deleteAttachments()
	{
		$res = Db::getInstance()->execute('
			DELETE FROM `'._DB_PREFIX_.'product_attachment`
			WHERE `id_product` = '.(int)$this->id
		);
		
		Product::updateCacheAttachment((int)$this->id);

		return $res;
	}

	/**
	* Delete product customizations
	*
	* @return array Deletion result
	*/
	public function deleteCustomization()
	{
		return (
			Db::getInstance()->execute(
				'DELETE FROM `'._DB_PREFIX_.'customization_field`
				WHERE `id_product` = '.(int)$this->id
			)
			&&
			Db::getInstance()->execute(
				'DELETE FROM `'._DB_PREFIX_.'customization_field_lang`
				WHERE `id_customization_field` NOT IN (SELECT id_customization_field
				FROM `'._DB_PREFIX_.'customization_field`)'
			)
		);
	}

	/**
	* Delete product pack details
	*
	* @return array Deletion result
	*/
	public function deletePack()
	{
		return Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'pack`
			WHERE `id_product_pack` = '.(int)$this->id.'
			OR `id_product_item` = '.(int)$this->id
		);
	}

	/**
	* Delete product sales
	*
	* @return array Deletion result
	*/
	public function deleteProductSale()
	{
		return Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'product_sale`
			WHERE `id_product` = '.(int)$this->id
		);
	}

	/**
	* Delete product in its scenes
	*
	* @return array Deletion result
	*/
	public function deleteSceneProducts()
	{
		return Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'scene_products`
			WHERE `id_product` = '.(int)$this->id
		);
	}

	/**
	* Delete product indexed words
	*
	* @return array Deletion result
	*/
	public function deleteSearchIndexes()
	{
		return (
			Db::getInstance()->execute(
				'DELETE FROM `'._DB_PREFIX_.'search_index`
				WHERE `id_product` = '.(int)$this->id
			)
			&&
			Db::getInstance()->execute(
				'DELETE FROM `'._DB_PREFIX_.'search_word`
				WHERE `id_word` NOT IN (
					SELECT id_word
					FROM `'._DB_PREFIX_.'search_index`
				)'
			)
		);
	}

	/**
	* Add a product attributes combinaison
	*
	* @param integer $id_product_attribute Product attribute id
	* @param array $attributes Attributes to forge combinaison
	* @return array Insertion result
	* @deprecated since 1.5.0.7
	*/
	public function addAttributeCombinaison($id_product_attribute, $attributes)
	{
		Tools::displayAsDeprecated();
		if (!is_array($attributes))
			die(Tools::displayError());
		if (!count($attributes))
			return false;

		$combination = new Combination((int)$id_product_attribute);
		return $combination->setAttributes($attributes);
	}

	public function addAttributeCombinationMultiple($id_attributes, $combinations)
	{
		Tools::displayAsDeprecated();
		$attributes_list = array();
		foreach ($id_attributes as $nb => $id_product_attribute)
			if (isset($combinations[$nb]))
				foreach ($combinations[$nb] as $id_attribute)
					$attributes_list[] = array(
						'id_product_attribute' => (int)$id_product_attribute,
						'id_attribute' => (int)$id_attribute,
					);

		return Db::getInstance()->insert('product_attribute_combination', $attributes_list);
	}


	/**
	* Delete a product attributes combination
	*
	* @param integer $id_product_attribute Product attribute id
	* @return array Deletion result
	*/
	public function deleteAttributeCombination($id_product_attribute)
	{
		if (!$this->id || !$id_product_attribute || !is_numeric($id_product_attribute))
			return false;

		Hook::exec(
			'deleteProductAttribute',
			array(
				'id_product_attribute' => $id_product_attribute,
				'id_product' => $this->id,
				'deleteAllAttributes' => false
			)
		);

		$combination = new Combination($id_product_attribute);
		$res = $combination->delete();
		SpecificPriceRule::applyAllRules(array((int)$this->id));
		return $res;
	}

	/**
	* Delete features
	*
	*/
	public function deleteFeatures()
	{
		// List products features
		$features = Db::getInstance()->executeS('
		SELECT p.*, f.*
		FROM `'._DB_PREFIX_.'feature_product` as p
		LEFT JOIN `'._DB_PREFIX_.'feature_value` as f ON (f.`id_feature_value` = p.`id_feature_value`)
		WHERE `id_product` = '.(int)$this->id);
		foreach ($features as $tab)
			// Delete product custom features
			if ($tab['custom'])
			{
				Db::getInstance()->execute('
				DELETE FROM `'._DB_PREFIX_.'feature_value`
				WHERE `id_feature_value` = '.(int)$tab['id_feature_value']);
				Db::getInstance()->execute('
				DELETE FROM `'._DB_PREFIX_.'feature_value_lang`
				WHERE `id_feature_value` = '.(int)$tab['id_feature_value']);
			}
		// Delete product features
		$result = Db::getInstance()->execute('
		DELETE FROM `'._DB_PREFIX_.'feature_product`
		WHERE `id_product` = '.(int)$this->id);

		SpecificPriceRule::applyAllRules(array((int)$this->id));
		return ($result);
	}

	/**
	* Get all available product attributes resume
	*
	* @param integer $id_lang Language id
	* @return array Product attributes combinations
	*/
	public function getAttributesResume($id_lang, $attribute_value_separator = ' - ', $attribute_separator = ', ')
	{
		if (!Combination::isFeatureActive())
			return array();
		$add_shop = '';

		$combinations = Db::getInstance()->executeS('SELECT pa.*, product_attribute_shop.*
				FROM `'._DB_PREFIX_.'product_attribute` pa
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				WHERE pa.`id_product` = '.(int)$this->id.'
				GROUP BY pa.`id_product_attribute`');

		if (!$combinations)
			return false;

		$product_attributes = array();
		foreach ($combinations as $combination)
			$product_attributes[] = (int)$combination['id_product_attribute'];
		
		$lang = Db::getInstance()->executeS('SELECT pac.id_product_attribute, GROUP_CONCAT(agl.`name`, \''.pSQL($attribute_value_separator).'\',al.`name` ORDER BY agl.`id_attribute_group` SEPARATOR \''.pSQL($attribute_separator).'\') as attribute_designation
				FROM `'._DB_PREFIX_.'product_attribute_combination` pac 
				LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)$id_lang.')
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)$id_lang.')
				WHERE pac.id_product_attribute IN ('.implode(',', $product_attributes).')
				GROUP BY pac.id_product_attribute');
		
		foreach ($lang as $k => $row)
			$combinations[$k]['attribute_designation'] = $row['attribute_designation'];
			
		
		//Get quantity of each variations
		foreach ($combinations as $key => $row)
		{
			$cache_key = $row['id_product'].'_'.$row['id_product_attribute'].'_quantity';

			if (!Cache::isStored($cache_key))
				Cache::store(
					$cache_key,
					StockAvailable::getQuantityAvailableByProduct($row['id_product'], $row['id_product_attribute'])
				);

			$combinations[$key]['quantity'] = Cache::retrieve($cache_key);
		}

		return $combinations;
	}

	/**
	* Get all available product attributes combinations
	*
	* @param integer $id_lang Language id
	* @return array Product attributes combinations
	*/
	public function getAttributeCombinations($id_lang)
	{
		if (!Combination::isFeatureActive())
			return array();

		$sql = 'SELECT pa.*, product_attribute_shop.*, ag.`id_attribute_group`, ag.`is_color_group`, agl.`name` AS group_name, al.`name` AS attribute_name,
					a.`id_attribute`, pa.`unit_price_impact`
				FROM `'._DB_PREFIX_.'product_attribute` pa
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)$id_lang.')
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)$id_lang.')
				WHERE pa.`id_product` = '.(int)$this->id.'
				GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group`
				ORDER BY pa.`id_product_attribute`';

		$res = Db::getInstance()->executeS($sql);

		//Get quantity of each variations
		foreach ($res as $key => $row)
		{
			$cache_key = $row['id_product'].'_'.$row['id_product_attribute'].'_quantity';

			if (!Cache::isStored($cache_key))
				Cache::store(
					$cache_key,
					StockAvailable::getQuantityAvailableByProduct($row['id_product'], $row['id_product_attribute'])
				);

			$res[$key]['quantity'] = Cache::retrieve($cache_key);
		}

		return $res;
	}

	/**
	* Get product attribute combination by id_product_attribute
	*
	* @param integer $id_product_attribute
	* @param integer $id_lang Language id
	* @return array Product attribute combination by id_product_attribute
	*/
	public function getAttributeCombinationsById($id_product_attribute, $id_lang)
	{
		if (!Combination::isFeatureActive())
			return array();
		$sql = 'SELECT pa.*, product_attribute_shop.*, ag.`id_attribute_group`, ag.`is_color_group`, agl.`name` AS group_name, al.`name` AS attribute_name,
					a.`id_attribute`, pa.`unit_price_impact`
				FROM `'._DB_PREFIX_.'product_attribute` pa
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)$id_lang.')
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)$id_lang.')
				WHERE pa.`id_product` = '.(int)$this->id.'
				AND pa.`id_product_attribute` = '.(int)$id_product_attribute.'
				GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group`
				ORDER BY pa.`id_product_attribute`';

		$res = Db::getInstance()->executeS($sql);

		//Get quantity of each variations
		foreach ($res as $key => $row)
		{
			$cache_key = $row['id_product'].'_'.$row['id_product_attribute'].'_quantity';

			if (!Cache::isStored($cache_key))
				Cache::store(
					$cache_key,
					StockAvailable::getQuantityAvailableByProduct($row['id_product'], $row['id_product_attribute'])
				);

			$res[$key]['quantity'] = Cache::retrieve($cache_key);
		}

		return $res;
	}

	public function getCombinationImages($id_lang)
	{
		if (!Combination::isFeatureActive())
			return false;

		$product_attributes = Db::getInstance()->executeS(
			'SELECT `id_product_attribute`
			FROM `'._DB_PREFIX_.'product_attribute`
			WHERE `id_product` = '.(int)$this->id
		);

		if (!$product_attributes)
			return false;

		$ids = array();

		foreach ($product_attributes as $product_attribute)
			$ids[] = (int)$product_attribute['id_product_attribute'];

		$result = Db::getInstance()->executeS('
			SELECT pai.`id_image`, pai.`id_product_attribute`, il.`legend`
			FROM `'._DB_PREFIX_.'product_attribute_image` pai
			LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (il.`id_image` = pai.`id_image`)
			LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_image` = pai.`id_image`)
			WHERE pai.`id_product_attribute` IN ('.implode(', ', $ids).') AND il.`id_lang` = '.(int)$id_lang.' ORDER by i.`position`'
		);

		if (!$result)
			return false;

		$images = array();

		foreach ($result as $row)
			$images[$row['id_product_attribute']][] = $row;

		return $images;
	}

	/**
	* Check if product has attributes combinations
	*
	* @return integer Attributes combinations number
	*/
	public function hasAttributes()
	{
		if (!Combination::isFeatureActive())
			return 0;
		return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
			SELECT COUNT(*)
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE pa.`id_product` = '.(int)$this->id
		);
	}

	/**
	* Get new products
	*
	* @param integer $id_lang Language id
	* @param integer $pageNumber Start from (optional)
	* @param integer $nbProducts Number of products to return (optional)
	* @return array New products
	*/
	
	public static function getAllProducts($id_lang, $page_number = 0, $nb_products = 10, $count = false, $order_by = null, $order_way = null, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$front = true;
		if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
			$front = false;

		if ($page_number < 0) $page_number = 0;
		if ($nb_products < 1) $nb_products = 10;
		if (empty($order_by) || $order_by == 'position') $order_by = 'date_add';
		if (empty($order_way)) $order_way = 'DESC';
		if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add'  || $order_by == 'date_upd')
			$order_by_prefix = 'p';
		else if ($order_by == 'name')
			$order_by_prefix = 'pl';
		if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
			die(Tools::displayError());

		$groups = FrontController::getCurrentCustomerGroups();
		$sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');
		if (strpos($order_by, '.') > 0)
		{
			$order_by = explode('.', $order_by);
			$order_by_prefix = $order_by[0];
			$order_by = $order_by[1];
		}
		if ($count)
		{
			$sql = 'SELECT COUNT(p.`id_product`) AS nb
					FROM `'._DB_PREFIX_.'product` p
					'.Shop::addSqlAssociation('product', 'p').'
					WHERE product_shop.`active` = 1
					'.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
					AND p.`id_product` IN (
						SELECT cp.`id_product`
						FROM `'._DB_PREFIX_.'category_group` cg
						LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
						WHERE cg.`id_group` '.$sql_groups.'
					)';
			return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
		}

		$sql = new DbQuery();
		$sql->select(
			'p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, pl.`link_rewrite`, pl.`meta_description`,
			pl.`meta_keywords`, pl.`meta_title`, pl.`name`, MAX(image_shop.`id_image`) id_image, il.`legend`, m.`name` AS manufacturer_name
			 '
		);

		$sql->from('product', 'p');
		$sql->join(Shop::addSqlAssociation('product', 'p'));
		$sql->leftJoin('product_lang', 'pl', '
			p.`id_product` = pl.`id_product`
			AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl')
		);
		$sql->leftJoin('image', 'i', 'i.`id_product` = p.`id_product`');
		$sql->join(Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1'));
		$sql->leftJoin('image_lang', 'il', 'i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang);
		$sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');

		$sql->where('product_shop.`active` = 1');

		if ($front)
			$sql->where('product_shop.`visibility` IN ("both", "catalog")');
		

		$sql->where('p.`id_product` IN (
			SELECT cp.`id_product`
			FROM `'._DB_PREFIX_.'category_group` cg
			LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
			WHERE cg.`id_group` '.$sql_groups.')'
		);
		$sql->groupBy('product_shop.id_product');

		$sql->orderBy((isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way));
		$sql->limit($nb_products, $page_number * $nb_products);

		if (Combination::isFeatureActive())
		{
			$sql->select('MAX(product_attribute_shop.id_product_attribute) id_product_attribute');
			$sql->leftOuterJoin('product_attribute', 'pa', 'p.`id_product` = pa.`id_product`');
			$sql->join(Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on = 1'));
		}
		$sql->join(Product::sqlStock('p', Combination::isFeatureActive() ? 'product_attribute_shop' : 0));

		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

		if ($order_by == 'price')
			Tools::orderbyPrice($result, $order_way);
		if (!$result)
			return false;

		$products_ids = array();
		foreach ($result as $row)
			$products_ids[] = $row['id_product'];
		// Thus you can avoid one query per product, because there will be only one query for all the products of the cart
		Product::cacheFrontFeatures($products_ids, $id_lang);

		return Product::getProductsProperties((int)$id_lang, $result);
	}
	
	public static function getNewProducts($id_lang, $page_number = 0, $nb_products = 10,
		$count = false, $order_by = null, $order_way = null, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$front = true;
		if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
			$front = false;

		if ($page_number < 0) $page_number = 0;
		if ($nb_products < 1) $nb_products = 10;
		if (empty($order_by) || $order_by == 'position') $order_by = 'date_add';
		if (empty($order_way)) $order_way = 'DESC';
		if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add'  || $order_by == 'date_upd')
			$order_by_prefix = 'p';
		else if ($order_by == 'name')
			$order_by_prefix = 'pl';
		if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
			die(Tools::displayError());

		$groups = FrontController::getCurrentCustomerGroups();
		$sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');
		if (strpos($order_by, '.') > 0)
		{
			$order_by = explode('.', $order_by);
			$order_by_prefix = $order_by[0];
			$order_by = $order_by[1];
		}
		if ($count)
		{
			$sql = 'SELECT COUNT(p.`id_product`) AS nb
					FROM `'._DB_PREFIX_.'product` p
					'.Shop::addSqlAssociation('product', 'p').'
					WHERE product_shop.`active` = 1
					AND DATEDIFF(
						product_shop.`date_add`,
						DATE_SUB(
							NOW(),
							INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
						)
					) > 0
					'.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
					AND p.`id_product` IN (
						SELECT cp.`id_product`
						FROM `'._DB_PREFIX_.'category_group` cg
						LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
						WHERE cg.`id_group` '.$sql_groups.'
					)';
			return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
		}

		$sql = new DbQuery();
		$sql->select(
			'p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, pl.`link_rewrite`, pl.`meta_description`,
			pl.`meta_keywords`, pl.`meta_title`, pl.`name`, MAX(image_shop.`id_image`) id_image, il.`legend`, m.`name` AS manufacturer_name,
			DATEDIFF(
				product_shop.`date_add`,
				DATE_SUB(
					NOW(),
					INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
				)
			) > 0 AS new'
		);

		$sql->from('product', 'p');
		$sql->join(Shop::addSqlAssociation('product', 'p'));
		$sql->leftJoin('product_lang', 'pl', '
			p.`id_product` = pl.`id_product`
			AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl')
		);
		$sql->leftJoin('image', 'i', 'i.`id_product` = p.`id_product`');
		$sql->join(Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1'));
		$sql->leftJoin('image_lang', 'il', 'i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang);
		$sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');

		$sql->where('product_shop.`active` = 1');

		if ($front)
			$sql->where('product_shop.`visibility` IN ("both", "catalog")');
		$sql->where('
			DATEDIFF(
				product_shop.`date_add`,
				DATE_SUB(
					NOW(),
					INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
				)
			) > 0'
		);

		$sql->where('p.`id_product` IN (
			SELECT cp.`id_product`
			FROM `'._DB_PREFIX_.'category_group` cg
			LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
			WHERE cg.`id_group` '.$sql_groups.')'
		);
		$sql->groupBy('product_shop.id_product');

		$sql->orderBy((isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way));
		$sql->limit($nb_products, $page_number * $nb_products);

		if (Combination::isFeatureActive())
		{
			$sql->select('MAX(product_attribute_shop.id_product_attribute) id_product_attribute');
			$sql->leftOuterJoin('product_attribute', 'pa', 'p.`id_product` = pa.`id_product`');
			$sql->join(Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on = 1'));
		}
		$sql->join(Product::sqlStock('p', Combination::isFeatureActive() ? 'product_attribute_shop' : 0));

		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

		if ($order_by == 'price')
			Tools::orderbyPrice($result, $order_way);
		if (!$result)
			return false;

		$products_ids = array();
		foreach ($result as $row)
			$products_ids[] = $row['id_product'];
		// Thus you can avoid one query per product, because there will be only one query for all the products of the cart
		Product::cacheFrontFeatures($products_ids, $id_lang);

		return Product::getProductsProperties((int)$id_lang, $result);
	}

	protected static function _getProductIdByDate($beginning, $ending, Context $context = null, $with_combination = false)
	{
		if (!$context)
			$context = Context::getContext();

		$id_address = $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
		$ids = Address::getCountryAndState($id_address);
		$id_country = (int)($ids['id_country'] ? $ids['id_country'] : Configuration::get('PS_COUNTRY_DEFAULT'));

		return SpecificPrice::getProductIdByDate(
			$context->shop->id,
			$context->currency->id,
			$id_country,
			$context->customer->id_default_group,
			$beginning,
			$ending,
			0,
			$with_combination
		);
	}

	/**
	* Get a random special
	*
	* @param integer $id_lang Language id
	* @return array Special
	*/
	public static function getRandomSpecial($id_lang, $beginning = false, $ending = false, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$front = true;
		if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
			$front = false;

		$current_date = date('Y-m-d H:i:s');
		$product_reductions = Product::_getProductIdByDate((!$beginning ? $current_date : $beginning), (!$ending ? $current_date : $ending), $context, true);

		if ($product_reductions)
		{
			$ids_product = ' AND (';
			foreach ($product_reductions as $product_reduction)
				$ids_product .= '( product_shop.`id_product` = '.(int)$product_reduction['id_product'].($product_reduction['id_product_attribute'] ? ' AND product_attribute_shop.`id_product_attribute`='.(int)$product_reduction['id_product_attribute'] :'').') OR';
			$ids_product = rtrim($ids_product, 'OR').')';

			$groups = FrontController::getCurrentCustomerGroups();
			$sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');

			// Please keep 2 distinct queries because RAND() is an awful way to achieve this result
			$sql = 'SELECT product_shop.id_product, MAX(product_attribute_shop.id_product_attribute) id_product_attribute
					FROM `'._DB_PREFIX_.'product` p
					'.Shop::addSqlAssociation('product', 'p').'
					LEFT JOIN  `'._DB_PREFIX_.'product_attribute` pa ON (product_shop.id_product = pa.id_product)
					'.Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on = 1').'
					WHERE product_shop.`active` = 1
						'.(($ids_product) ? $ids_product : '').'
						AND p.`id_product` IN (
							SELECT cp.`id_product`
							FROM `'._DB_PREFIX_.'category_group` cg
							LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
							WHERE cg.`id_group` '.$sql_groups.'
						)
					'.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
					GROUP BY product_shop.id_product
					ORDER BY RAND()';

			$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);

			if (!$id_product = $result['id_product'])
				return false;

			$sql = 'SELECT p.*, product_shop.*, stock.`out_of_stock` out_of_stock, pl.`description`, pl.`description_short`,
						pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`,
						p.`ean13`, p.`upc`, MAX(image_shop.`id_image`) id_image, il.`legend`,
						DATEDIFF(product_shop.`date_add`, DATE_SUB(NOW(),
						INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).'
							DAY)) > 0 AS new
					FROM `'._DB_PREFIX_.'product` p
					LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
						p.`id_product` = pl.`id_product`
						AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
					)
					'.Shop::addSqlAssociation('product', 'p').'
					LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'.
					Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
					LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
					'.Product::sqlStock('p', 0).'
					WHERE p.id_product = '.(int)$id_product.'
					GROUP BY product_shop.id_product';

			$row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
			if (!$row)
				return false;

			if ($result['id_product_attribute'])
				$row['id_product_attribute'] = $result['id_product_attribute'];
			return Product::getProductProperties($id_lang, $row);
		}
		else
			return false;
	}

	/**
	* Get prices drop
	*
	* @param integer $id_lang Language id
	* @param integer $pageNumber Start from (optional)
	* @param integer $nbProducts Number of products to return (optional)
	* @param boolean $count Only in order to get total number (optional)
	* @return array Prices drop
	*/
	public static function getPricesDrop($id_lang, $page_number = 0, $nb_products = 10, $count = false,
		$order_by = null, $order_way = null, $beginning = false, $ending = false, Context $context = null)
	{
		if (!Validate::isBool($count))
			die(Tools::displayError());

		if (!$context) $context = Context::getContext();
		if ($page_number < 0) $page_number = 0;
		if ($nb_products < 1) $nb_products = 10;
		if (empty($order_by) || $order_by == 'position') $order_by = 'price';
		if (empty($order_way)) $order_way = 'DESC';
		if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add'  || $order_by == 'date_upd')
			$order_by_prefix = 'p';
		else if ($order_by == 'name')
			$order_by_prefix = 'pl';
		if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
			die (Tools::displayError());
		$current_date = date('Y-m-d H:i:s');
		$ids_product = Product::_getProductIdByDate((!$beginning ? $current_date : $beginning), (!$ending ? $current_date : $ending), $context);

		$tab_id_product = array();
		foreach ($ids_product as $product)
			if (is_array($product))
				$tab_id_product[] = (int)$product['id_product'];
			else
				$tab_id_product[] = (int)$product;

		$front = true;
		if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
			$front = false;

		$groups = FrontController::getCurrentCustomerGroups();
		$sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');

		if ($count)
		{
			$sql = 'SELECT COUNT(DISTINCT p.`id_product`) AS nb
					FROM `'._DB_PREFIX_.'product` p
					'.Shop::addSqlAssociation('product', 'p').'
					WHERE product_shop.`active` = 1
						AND product_shop.`show_price` = 1
						'.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
						'.((!$beginning && !$ending) ? 'AND p.`id_product` IN('.((is_array($tab_id_product) && count($tab_id_product)) ? implode(', ', $tab_id_product) : 0).')' : '').'
						AND p.`id_product` IN (
							SELECT cp.`id_product`
							FROM `'._DB_PREFIX_.'category_group` cg
							LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
							WHERE cg.`id_group` '.$sql_groups.'
						)';
			$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
			return (int)$result['nb'];
		}
		if (strpos($order_by, '.') > 0)
		{
			$order_by = explode('.', $order_by);
			$order_by = pSQL($order_by[0]).'.`'.pSQL($order_by[1]).'`';
		}
		$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, MAX(product_attribute_shop.id_product_attribute) id_product_attribute,
					pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`,
					pl.`name`, MAX(image_shop.`id_image`) id_image, il.`legend`, m.`name` AS manufacturer_name,
					DATEDIFF(
						p.`date_add`,
						DATE_SUB(
							NOW(),
							INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
						)
					) > 0 AS new
				FROM `'._DB_PREFIX_.'product` p
				'.Shop::addSqlAssociation('product', 'p').'
				LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (pa.id_product = p.id_product)
				'.Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on=1').'
				'.Product::sqlStock('p', 0, false, $context->shop).'
				LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
					p.`id_product` = pl.`id_product`
					AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
				)
				LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'.
				Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
				LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
				LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
				WHERE product_shop.`active` = 1
				AND product_shop.`show_price` = 1
				'.($front ? ' AND p.`visibility` IN ("both", "catalog")' : '').'
				'.((!$beginning && !$ending) ? ' AND p.`id_product` IN ('.((is_array($tab_id_product) && count($tab_id_product)) ? implode(', ', $tab_id_product) : 0).')' : '').'
				AND p.`id_product` IN (
					SELECT cp.`id_product`
					FROM `'._DB_PREFIX_.'category_group` cg
					LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
					WHERE cg.`id_group` '.$sql_groups.'
				)
				GROUP BY product_shop.id_product
				ORDER BY '.(isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').pSQL($order_by).' '.pSQL($order_way).'
				LIMIT '.(int)($page_number * $nb_products).', '.(int)$nb_products;

		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);

		if ($order_by == 'price')
			Tools::orderbyPrice($result, $order_way);

		if (!$result)
			return false;

		return Product::getProductsProperties($id_lang, $result);
	}


	/**
	 * getProductCategories return an array of categories which this product belongs to
	 *
	 * @return array of categories
	 */
	public static function getProductCategories($id_product = '')
	{
		$ret = array();

		$row = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
			SELECT `id_category` FROM `'._DB_PREFIX_.'category_product`
			WHERE `id_product` = '.(int)$id_product
		);

		if ($row)
			foreach ($row as $val)
				$ret[] = $val['id_category'];

		return $ret;
	}

	public static function getProductCategoriesFull($id_product = '', $id_lang = null)
	{
		if (!$id_lang)
			$id_lang = Context::getContext()->language->id;

		$ret = array();
		$row = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
			SELECT cp.`id_category`, cl.`name`, cl.`link_rewrite` FROM `'._DB_PREFIX_.'category_product` cp
			LEFT JOIN `'._DB_PREFIX_.'category` c ON (c.id_category = cp.id_category)
			LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (cp.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').')
			'.Shop::addSqlAssociation('category', 'c').'
			WHERE cp.`id_product` = '.(int)$id_product.'
				AND cl.`id_lang` = '.(int)$id_lang
		);

		foreach ($row as $val)
			$ret[$val['id_category']] = $val;

		return $ret;
	}

	/**
	 * getCategories return an array of categories which this product belongs to
	 *
	 * @return array of categories
	 */
	public function getCategories()
	{
		return Product::getProductCategories($this->id);
	}

	/**
	 * Gets carriers assigned to the product
	 */
	public function getCarriers()
	{
		return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
			SELECT c.*
			FROM `'._DB_PREFIX_.'product_carrier` pc
			INNER JOIN `'._DB_PREFIX_.'carrier` c
				ON (c.`id_reference` = pc.`id_carrier_reference` AND c.`deleted` = 0)
			WHERE pc.`id_product` = '.(int)$this->id.'
				AND pc.`id_shop` = '.(int)$this->id_shop);
	}

	/**
	 * Sets carriers assigned to the product
	 */
	public function setCarriers($carrier_list)
	{
		$data = array();

		foreach ($carrier_list as $carrier)
		{
			$data[] = array(
				'id_product' => (int)$this->id,
				'id_carrier_reference' => (int)$carrier,
				'id_shop' => (int)$this->id_shop
			);
		}
		Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'product_carrier`
			WHERE id_product = '.(int)$this->id.'
			AND id_shop = '.(int)$this->id_shop
		);
		if (count($data))
			Db::getInstance()->insert('product_carrier', $data);
	}

	/**
	* Get product images and legends
	*
	* @param integer $id_lang Language id for multilingual legends
	* @return array Product images and legends
	*/
	public function	getImages($id_lang, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$sql = 'SELECT image_shop.`cover`, i.`id_image`, il.`legend`, i.`position`
				FROM `'._DB_PREFIX_.'image` i
				'.Shop::addSqlAssociation('image', 'i').'
				LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
				WHERE i.`id_product` = '.(int)$this->id.'
				ORDER BY `position`';
		return Db::getInstance()->executeS($sql);
	}

	/**
	* Get product cover image
	*
	* @return array Product cover image
	*/
	public static function getCover($id_product, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$sql = 'SELECT image_shop.`id_image`
				FROM `'._DB_PREFIX_.'image` i
				'.Shop::addSqlAssociation('image', 'i').'
				WHERE i.`id_product` = '.(int)$id_product.'
				AND image_shop.`cover` = 1';
		return Db::getInstance()->getRow($sql);
	}

	/**
	* Get product price
	*
	* @param integer $id_product Product id
	* @param boolean $usetax With taxes or not (optional)
	* @param integer $id_product_attribute Product attribute id (optional).
	* 	If set to false, do not apply the combination price impact. NULL does apply the default combination price impact.
	* @param integer $decimals Number of decimals (optional)
	* @param integer $divisor Useful when paying many time without fees (optional)
	* @param boolean $only_reduc Returns only the reduction amount
	* @param boolean $usereduc Set if the returned amount will include reduction
	* @param integer $quantity Required for quantity discount application (default value: 1)
	* @param boolean $forceAssociatedTax DEPRECATED - NOT USED Force to apply the associated tax. Only works when the parameter $usetax is true
	* @param integer $id_customer Customer ID (for customer group reduction)
	* @param integer $id_cart Cart ID. Required when the cookie is not accessible (e.g., inside a payment module, a cron task...)
	* @param integer $id_address Customer address ID. Required for price (tax included) calculation regarding the guest localization
	* @param variable_reference $specificPriceOutput.
	* 	If a specific price applies regarding the previous parameters, this variable is filled with the corresponding SpecificPrice object
	* @param boolean $with_ecotax insert ecotax in price output.
	* @return float Product price
	*/
	public static function getPriceStatic($id_product, $usetax = true, $id_product_attribute = null, $decimals = 6, $divisor = null,
		$only_reduc = false, $usereduc = true, $quantity = 1, $force_associated_tax = false, $id_customer = null, $id_cart = null,
		$id_address = null, &$specific_price_output = null, $with_ecotax = true, $use_group_reduction = true, Context $context = null,
		$use_customer_price = true)
	{
		if (!$context)
			$context = Context::getContext();

		$cur_cart = $context->cart;

		if ($divisor !== null)
			Tools::displayParameterAsDeprecated('divisor');

		if (!Validate::isBool($usetax) || !Validate::isUnsignedId($id_product))
			die(Tools::displayError());
		// Initializations
		$id_group = (isset($context->customer) ? $context->customer->id_default_group : (int)Configuration::get('PS_CUSTOMER_GROUP'));

		// If there is cart in context or if the specified id_cart is different from the context cart id
		if (!is_object($cur_cart) || (Validate::isUnsignedInt($id_cart) && $id_cart && $cur_cart->id != $id_cart))
		{
			/*
			* When a user (e.g., guest, customer, Google...) is on PrestaShop, he has already its cart as the global (see /init.php)
			* When a non-user calls directly this method (e.g., payment module...) is on PrestaShop, he does not have already it BUT knows the cart ID
			* When called from the back office, cart ID can be inexistant
			*/
			if (!$id_cart && !isset($context->employee))
				die(Tools::displayError());
			$cur_cart = new Cart($id_cart);
			// Store cart in context to avoid multiple instantiations in BO
			if (!Validate::isLoadedObject($context->cart))
				$context->cart = $cur_cart;
		}

		$cart_quantity = 0;
		if ((int)$id_cart)
		{
			$condition = '';
			$cache_name = (int)$id_cart.'_'.(int)$id_product.'_'.(int)$id_product_attribute;
			if (!isset(self::$_cart_quantity[$cache_name]) || self::$_cart_quantity[$cache_name] != (int)$quantity)
				self::$_cart_quantity[$cache_name] = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
				SELECT SUM(`quantity`)
				FROM `'._DB_PREFIX_.'cart_product`
				WHERE `id_product` = '.(int)$id_product.'
				AND `id_product_attribute` = '.(int)$id_product_attribute.'
				AND `id_cart` = '.(int)$id_cart);
			$cart_quantity = self::$_cart_quantity[$cache_name];
		}

		$id_currency = (int)Validate::isLoadedObject($context->currency) ? $context->currency->id : Configuration::get('PS_CURRENCY_DEFAULT');

		// retrieve address informations
		$id_country = (int)$context->country->id;
		$id_state = 0;
		$zipcode = 0;

		if (!$id_address)
			$id_address = $cur_cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};

		if ($id_address)
		{
			$address_infos = Address::getCountryAndState($id_address);
			if ($address_infos['id_country'])
			{
				$id_country = (int)$address_infos['id_country'];
				$id_state = (int)$address_infos['id_state'];
				$zipcode = $address_infos['postcode'];
			}
		}
		else if (isset($context->customer->geoloc_id_country))
		{
			$id_country = (int)$context->customer->geoloc_id_country;
			$id_state = (int)$context->customer->id_state;
			$zipcode = (int)$context->customer->postcode;
		}

		if (Tax::excludeTaxeOption())
			$usetax = false;

		if ($usetax != false
			&& !empty($address_infos['vat_number'])
			&& $address_infos['id_country'] != Configuration::get('VATNUMBER_COUNTRY')
			&& Configuration::get('VATNUMBER_MANAGEMENT'))
			$usetax = false;

		if (is_null($id_customer) && Validate::isLoadedObject($context->customer))
			$id_customer = $context->customer->id;

		return Product::priceCalculation(
			$context->shop->id,
			$id_product,
			$id_product_attribute,
			$id_country,
			$id_state,
			$zipcode,
			$id_currency,
			$id_group,
			$cart_quantity,
			$usetax,
			$decimals,
			$only_reduc,
			$usereduc,
			$with_ecotax,
			$specific_price_output,
			$use_group_reduction,
			$id_customer,
			$use_customer_price,
			$id_cart, 
			$quantity
		);
	}

	/**
	* Price calculation / Get product price
	*
	* @param integer $id_shop Shop id
	* @param integer $id_product Product id
	* @param integer $id_product_attribute Product attribute id
	* @param integer $id_country Country id
	* @param integer $id_state State id
	* @param integer $id_currency Currency id
	* @param integer $id_group Group id
	* @param integer $quantity Quantity Required for Specific prices : quantity discount application
	* @param boolean $use_tax with (1) or without (0) tax
	* @param integer $decimals Number of decimals returned
	* @param boolean $only_reduc Returns only the reduction amount
	* @param boolean $use_reduc Set if the returned amount will include reduction
	* @param boolean $with_ecotax insert ecotax in price output.
	* @param variable_reference $specific_price_output
	* 	If a specific price applies regarding the previous parameters, this variable is filled with the corresponding SpecificPrice object
	* @return float Product price
	**/
	public static function priceCalculation($id_shop, $id_product, $id_product_attribute, $id_country, $id_state, $zipcode, $id_currency,
		$id_group, $quantity, $use_tax, $decimals, $only_reduc, $use_reduc, $with_ecotax, &$specific_price, $use_group_reduction,
		$id_customer = 0, $use_customer_price = true, $id_cart = 0, $real_quantity = 0)
	{
		static $address = null;
		static $context = null;

		if ($address === null)
			$address = new Address();
		
		if ($context == null)
			$context = Context::getContext()->cloneContext();

		if ($id_shop !== null && $context->shop->id != (int)$id_shop)
			$context->shop = new Shop((int)$id_shop);
		
		if (!$use_customer_price)
			$id_customer = 0;

		if ($id_product_attribute === null)
			$id_product_attribute = Product::getDefaultAttribute($id_product);

		$cache_id = $id_product.'-'.$id_shop.'-'.$id_currency.'-'.$id_country.'-'.$id_state.'-'.$zipcode.'-'.$id_group.
			'-'.$quantity.'-'.$id_product_attribute.'-'.($use_tax?'1':'0').'-'.$decimals.'-'.($only_reduc?'1':'0').
			'-'.($use_reduc?'1':'0').'-'.$with_ecotax.'-'.$id_customer.'-'.(int)$use_group_reduction.'-'.(int)$id_cart.'-'.(int)$real_quantity;

		// reference parameter is filled before any returns
		$specific_price = SpecificPrice::getSpecificPrice(
			(int)$id_product,
			$id_shop,
			$id_currency,
			$id_country,
			$id_group,
			$quantity,
			$id_product_attribute,
			$id_customer,
			$id_cart,
			$real_quantity
		);

		if (isset(self::$_prices[$cache_id]))
			return self::$_prices[$cache_id];

		// fetch price & attribute price
		$cache_id_2 = $id_product.'-'.$id_shop;
		if (!isset(self::$_pricesLevel2[$cache_id_2]))
		{
			$sql = new DbQuery();
			$sql->select('product_shop.`price`, product_shop.`ecotax`');
			$sql->from('product', 'p');
			$sql->innerJoin('product_shop', 'product_shop', '(product_shop.id_product=p.id_product AND product_shop.id_shop = '.(int)$id_shop.')');
			$sql->where('p.`id_product` = '.(int)$id_product);
			if (Combination::isFeatureActive())
			{
				$sql->select('product_attribute_shop.id_product_attribute, product_attribute_shop.`price` AS attribute_price, product_attribute_shop.default_on');
				$sql->leftJoin('product_attribute', 'pa', 'pa.`id_product` = p.`id_product`');
				$sql->leftJoin('product_attribute_shop', 'product_attribute_shop', '(product_attribute_shop.id_product_attribute = pa.id_product_attribute AND product_attribute_shop.id_shop = '.(int)$id_shop.')');
			}
			else
				$sql->select('0 as id_product_attribute');

			$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
			
			foreach ($res as $row)
			{
				$array_tmp = array(
					'price' => $row['price'], 
					'ecotax' => $row['ecotax'],
					'attribute_price' => (isset($row['attribute_price']) ? $row['attribute_price'] : null)
				);
				self::$_pricesLevel2[$cache_id_2][(int)$row['id_product_attribute']] = $array_tmp;
				
				if (isset($row['default_on']) && $row['default_on'] == 1)
					self::$_pricesLevel2[$cache_id_2][0] = $array_tmp;
			}
		}
		if (!isset(self::$_pricesLevel2[$cache_id_2][(int)$id_product_attribute]))
			return;

		$result = self::$_pricesLevel2[$cache_id_2][(int)$id_product_attribute];

		if (!$specific_price || $specific_price['price'] < 0)
			$price = (float)$result['price'];
		else
			$price = (float)$specific_price['price'];
		// convert only if the specific price is in the default currency (id_currency = 0)
		if (!$specific_price || !($specific_price['price'] >= 0 && $specific_price['id_currency']))
			$price = Tools::convertPrice($price, $id_currency);

		// Attribute price
		if (is_array($result) && (!$specific_price || !$specific_price['id_product_attribute'] || $specific_price['price'] < 0))
		{
			$attribute_price = Tools::convertPrice($result['attribute_price'] !== null ? (float)$result['attribute_price'] : 0, $id_currency);
			// If you want the default combination, please use NULL value instead
			if ($id_product_attribute !== false)
				$price += $attribute_price;
		}

		// Tax
		$address->id_country = $id_country;
		$address->id_state = $id_state;
		$address->postcode = $zipcode;

		$tax_manager = TaxManagerFactory::getManager($address, Product::getIdTaxRulesGroupByIdProduct((int)$id_product, $context));
		$product_tax_calculator = $tax_manager->getTaxCalculator();

		// Add Tax
		if ($use_tax)
			$price = $product_tax_calculator->addTaxes($price);

		// Reduction
		$reduc = 0;
		if (($only_reduc || $use_reduc) && $specific_price)
		{
			if ($specific_price['reduction_type'] == 'amount')
			{
				$reduction_amount = $specific_price['reduction'];

				if (!$specific_price['id_currency'])
					$reduction_amount = Tools::convertPrice($reduction_amount, $id_currency);
				$reduc = !$use_tax ? $product_tax_calculator->removeTaxes($reduction_amount) : $reduction_amount;
			}
			else
				$reduc = $price * $specific_price['reduction'];
		}

		if ($only_reduc)
			return Tools::ps_round($reduc, $decimals);
		if ($use_reduc)
			$price -= $reduc;

		// Group reduction
		if ($use_group_reduction)
		{
			if ($reduction_from_category = (float)GroupReduction::getValueForProduct($id_product, $id_group))
				$price -= $price * $reduction_from_category;
			else // apply group reduction if there is no group reduction for this category
				$price *= ((100 - Group::getReductionByIdGroup($id_group)) / 100);
		}

		// Eco Tax
		if (($result['ecotax'] || isset($result['attribute_ecotax'])) && $with_ecotax)
		{
			$ecotax = $result['ecotax'];
			if (isset($result['attribute_ecotax']) && $result['attribute_ecotax'] > 0)
				$ecotax = $result['attribute_ecotax'];

			if ($id_currency)
				$ecotax = Tools::convertPrice($ecotax, $id_currency);
			if ($use_tax)
			{
				// reinit the tax manager for ecotax handling
				$tax_manager = TaxManagerFactory::getManager(
					$address,
					(int)Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID')
				);
				$ecotax_tax_calculator = $tax_manager->getTaxCalculator();
				$price += $ecotax_tax_calculator->addTaxes($ecotax);
			}
			else
				$price += $ecotax;
		}
		$price = Tools::ps_round($price, $decimals);
		if ($price < 0)
			$price = 0;

		self::$_prices[$cache_id] = $price;
		return self::$_prices[$cache_id];
	}

	public static function convertAndFormatPrice($price, $currency = false, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();
		if (!$currency)
			$currency = $context->currency;
		return Tools::displayPrice(Tools::convertPrice($price, $currency), $currency);
	}

	public static function isDiscounted($id_product, $quantity = 1, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$id_group = $context->customer->id_default_group;
		$cart_quantity = !$context->cart ? 0 : Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
			SELECT SUM(`quantity`)
			FROM `'._DB_PREFIX_.'cart_product`
			WHERE `id_product` = '.(int)$id_product.' AND `id_cart` = '.(int)$context->cart->id
		);
		$quantity = $cart_quantity ? $cart_quantity : $quantity;

		$id_currency = (int)$context->currency->id;
		$ids = Address::getCountryAndState((int)$context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
		$id_country = (int)($ids['id_country'] ? $ids['id_country'] : Configuration::get('PS_COUNTRY_DEFAULT'));
		return (bool)SpecificPrice::getSpecificPrice((int)$id_product, $context->shop->id, $id_currency, $id_country, $id_group, $quantity, null, 0, 0, $quantity);
	}

	/**
	* Get product price
	* Same as static function getPriceStatic, no need to specify product id
	*
	* @param boolean $tax With taxes or not (optional)
	* @param integer $id_product_attribute Product attribute id (optional)
	* @param integer $decimals Number of decimals (optional)
	* @param integer $divisor Util when paying many time without fees (optional)
	* @return float Product price in euros
	*/
	public function getPrice($tax = true, $id_product_attribute = null, $decimals = 6,
		$divisor = null, $only_reduc = false, $usereduc = true, $quantity = 1)
	{
		return Product::getPriceStatic((int)$this->id, $tax, $id_product_attribute, $decimals, $divisor, $only_reduc, $usereduc, $quantity);
	}

	public function getPublicPrice($tax = true, $id_product_attribute = null, $decimals = 6,
			$divisor = null, $only_reduc = false, $usereduc = true, $quantity = 1)
	{
		$specific_price_output = null;
		return Product::getPriceStatic((int)$this->id, $tax, $id_product_attribute, $decimals, $divisor, $only_reduc, $usereduc, $quantity,
			false, null, null, null, $specific_price_output, true, true, null, false);
	}

	public function getIdProductAttributeMostExpensive()
	{
		if (!Combination::isFeatureActive())
			return 0;

		$row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('
		SELECT pa.`id_product_attribute`
		FROM `'._DB_PREFIX_.'product_attribute` pa
		'.Shop::addSqlAssociation('product_attribute', 'pa').'
		WHERE pa.`id_product` = '.(int)$this->id.'
		ORDER BY product_attribute_shop.`price` DESC');

		return (isset($row['id_product_attribute']) && $row['id_product_attribute']) ? (int)$row['id_product_attribute'] : 0;
	}

	public function getDefaultIdProductAttribute()
	{
		if (!Combination::isFeatureActive())
			return 0;

		$row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('
			SELECT pa.`id_product_attribute`
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE pa.`id_product` = '.(int)$this->id.'
			AND product_attribute_shop.default_on = 1'
		);

		return (isset($row['id_product_attribute']) && $row['id_product_attribute']) ? (int)$row['id_product_attribute'] : 0;
	}

	public function getPriceWithoutReduct($notax = false, $id_product_attribute = false)
	{
		return Product::getPriceStatic((int)$this->id, !$notax, $id_product_attribute, 6, null, false, false);
	}

	/**
	* Display price with right format and currency
	*
	* @param array $params Params
	* @param $smarty Smarty object
	* @return string Price with right format and currency
	*/
	public static function convertPrice($params, &$smarty)
	{
		return Tools::displayPrice($params['price'], Context::getContext()->currency);
	}

	/**
	 * Convert price with currency
	 *
	 * @param array $params
	 * @param object $smarty DEPRECATED
	 * @return Ambigous <string, mixed, Ambigous <number, string>>
	 */
	public static function convertPriceWithCurrency($params, &$smarty)
	{
		return Tools::displayPrice($params['price'], $params['currency'], false);
	}

	public static function displayWtPrice($params, &$smarty)
	{
		return Tools::displayPrice($params['p'], Context::getContext()->currency);
	}

	/**
	 * Display WT price with currency
	 *
	 * @param array $params
	 * @param object DEPRECATED $smarty
	 * @return Ambigous <string, mixed, Ambigous <number, string>>
	 */
	public static function displayWtPriceWithCurrency($params, &$smarty)
	{
		return Tools::displayPrice($params['price'], $params['currency'], false);
	}

	/**
	* Get available product quantities
	*
	* @param integer $id_product Product id
	* @param integer $id_product_attribute Product attribute id (optional)
	* @return integer Available quantities
	*/
	public static function getQuantity($id_product, $id_product_attribute = null, $cache_is_pack = null)
	{
		$lang = Configuration::get('PS_LANG_DEFAULT');
		if ((int)$cache_is_pack || ($cache_is_pack === null && Pack::isPack((int)$id_product)))
		{
			if (!Pack::isInStock((int)$id_product))
				return 0;
		}

		// @since 1.5.0
		return (StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute));
	}

	/**
	 * Create JOIN query with 'stock_available' table
	 *
	 * @param string $productAlias Alias of product table
	 * @param string|int $productAttribute If string : alias of PA table ; if int : value of PA ; if null : nothing about PA
	 * @param bool $innerJoin LEFT JOIN or INNER JOIN
	 * @param Shop $shop
	 * @return string
	 */
	public static function sqlStock($product_alias, $product_attribute = 0, $inner_join = false, Shop $shop = null)
	{
		$id_shop = ($shop !== null ? (int)$shop->id : null);
		$sql = (($inner_join) ? ' INNER ' : ' LEFT ').'
			JOIN '._DB_PREFIX_.'stock_available stock
			ON (stock.id_product = '.pSQL($product_alias).'.id_product';

		if (!is_null($product_attribute))
		{
			if (!Combination::isFeatureActive())
				$sql .= ' AND stock.id_product_attribute = 0';
			elseif (is_numeric($product_attribute))
				$sql .= ' AND stock.id_product_attribute = '.$product_attribute;
			elseif (is_string($product_attribute))
				$sql .= ' AND stock.id_product_attribute = IFNULL(`'.bqSQL($product_attribute).'`.id_product_attribute, 0)';
		}

		$sql .= StockAvailable::addSqlShopRestriction(null, $id_shop, 'stock').' )';

		return $sql;
	}

	/**
	 * @deprecated since 1.5.0
	 *
	 * It's not possible to use this method with new stockManager and stockAvailable features
	 * Now this method do nothing
	 *
	 * @see StockManager if you want to manage real stock
	 * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
	 *
	 * @param array $product Array with ordered product (quantity, id_product_attribute if applicable)
	 * @return mixed Query result
	 */
	public static function updateQuantity()
	{
		Tools::displayAsDeprecated();

		return false;
	}

	/**
	 * @deprecated since 1.5.0
	 *
	 * It's not possible to use this method with new stockManager and stockAvailable features
	 * Now this method do nothing
	 *
	 * @see StockManager if you want to manage real stock
	 * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
	 *
	 */
	public static function reinjectQuantities()
	{
		Tools::displayAsDeprecated();

		return false;
	}

	public static function isAvailableWhenOutOfStock($out_of_stock)
	{
		// @TODO 1.5.0 Update of STOCK_MANAGEMENT & ORDER_OUT_OF_STOCK
		$return = (int)$out_of_stock == 2 ? (int)Configuration::get('PS_ORDER_OUT_OF_STOCK') : (int)$out_of_stock;
		return !Configuration::get('PS_STOCK_MANAGEMENT') ? true : $return;
	}

	/**
	 * Check product availability
	 *
	 * @param integer $qty Quantity desired
	 * @return boolean True if product is available with this quantity
	 */
	public function checkQty($qty)
	{
		if (Pack::isPack((int)$this->id) && !Pack::isInStock((int)$this->id))
			return false;

		if ($this->isAvailableWhenOutOfStock(StockAvailable::outOfStock($this->id)))
			return true;

		if (isset($this->id_product_attribute))
			$id_product_attribute = $this->id_product_attribute;
		else
			$id_product_attribute = 0;

		return ($qty <= StockAvailable::getQuantityAvailableByProduct($this->id, $id_product_attribute));
	}

	/**
	 * Check if there is no default attribute and create it if not
	 */
	public function checkDefaultAttributes()
	{
		if (!$this->id)
			return false;

		if (Db::getInstance()->getValue('SELECT COUNT(*)
				FROM `'._DB_PREFIX_.'product_attribute` pa
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				WHERE product_attribute_shop.`default_on` = 1
				AND pa.`id_product` = '.(int)$this->id) > 1)
				Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'product_attribute_shop product_attribute_shop, '._DB_PREFIX_.'product_attribute pa
					SET product_attribute_shop.default_on=0, pa.default_on = 0
					WHERE product_attribute_shop.id_product_attribute=pa.id_product_attribute AND pa.id_product='.(int)$this->id
					.Shop::addSqlRestriction(false, 'product_attribute_shop'));
			
			
		$row = Db::getInstance()->getRow('
			SELECT pa.id_product
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE product_attribute_shop.`default_on` = 1
				AND pa.`id_product` = '.(int)$this->id
		);
		if ($row)
			return true;

		$mini = Db::getInstance()->getRow('
		SELECT MIN(pa.id_product_attribute) as `id_attr`
		FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE `id_product` = '.(int)$this->id
		);
		if (!$mini)
			return false;

		if (!ObjectModel::updateMultishopTable('Combination', array('default_on' => 1), 'a.id_product_attribute = '.(int)$mini['id_attr']))
			return false;
		return true;
	}

	/**
	 * Get all available attribute groups
	 *
	 * @param integer $id_lang Language id
	 * @return array Attribute groups
	 */
	public function getAttributesGroups($id_lang)
	{
		if (!Combination::isFeatureActive())
			return array();
		$sql = 'SELECT ag.`id_attribute_group`, ag.`is_color_group`, agl.`name` AS group_name, agl.`public_name` AS public_group_name,
					a.`id_attribute`, al.`name` AS attribute_name, a.`color` AS attribute_color, pa.`id_product_attribute`,
					IFNULL(stock.quantity, 0) as quantity, product_attribute_shop.`price`, product_attribute_shop.`ecotax`, pa.`weight`,
					product_attribute_shop.`default_on`, pa.`reference`, product_attribute_shop.`unit_price_impact`,
					pa.`minimal_quantity`, pa.`available_date`, ag.`group_type`
				FROM `'._DB_PREFIX_.'product_attribute` pa
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				'.Product::sqlStock('pa', 'pa').'
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON a.`id_attribute` = al.`id_attribute`
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON ag.`id_attribute_group` = agl.`id_attribute_group`
				'.Shop::addSqlAssociation('attribute', 'a').'
				WHERE pa.`id_product` = '.(int)$this->id.'
					AND al.`id_lang` = '.(int)$id_lang.'
					AND agl.`id_lang` = '.(int)$id_lang.'
				GROUP BY id_attribute_group, id_product_attribute
				ORDER BY ag.`position` ASC, a.`position` ASC, agl.`name` ASC';
		return Db::getInstance()->executeS($sql);
	}

	/**
	 * Delete product accessories
	 *
	 * @return mixed Deletion result
	 */
	public function deleteAccessories()
	{
		return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'accessory` WHERE `id_product_1` = '.(int)$this->id);
	}

	/**
	 * Delete product from other products accessories
	 *
	 * @return mixed Deletion result
	 */
	public function deleteFromAccessories()
	{
		return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'accessory` WHERE `id_product_2` = '.(int)$this->id);
	}

	/**
	 * Get product accessories (only names)
	 *
	 * @param integer $id_lang Language id
	 * @param integer $id_product Product id
	 * @return array Product accessories
	 */
	public static function getAccessoriesLight($id_lang, $id_product, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$sql = 'SELECT p.`id_product`, p.`reference`, pl.`name`
				FROM `'._DB_PREFIX_.'accessory`
				LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product`= `id_product_2`)
				'.Shop::addSqlAssociation('product', 'p').'
				LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
					p.`id_product` = pl.`id_product`
					AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
				)
				WHERE `id_product_1` = '.(int)$id_product;

		return Db::getInstance()->executeS($sql);
	}

	/**
	 * Get product accessories
	 *
	 * @param integer $id_lang Language id
	 * @return array Product accessories
	 */
	public function getAccessories($id_lang, $active = true, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, pl.`link_rewrite`,
					pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`,
					MAX(image_shop.`id_image`) id_image, il.`legend`, m.`name` as manufacturer_name, cl.`name` AS category_default,
					DATEDIFF(
						p.`date_add`,
						DATE_SUB(
							NOW(),
							INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
						)
					) > 0 AS new
				FROM `'._DB_PREFIX_.'accessory`
				LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = `id_product_2`
				'.Shop::addSqlAssociation('product', 'p').'
				LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
					p.`id_product` = pl.`id_product`
					AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
				)
				LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (
					product_shop.`id_category_default` = cl.`id_category`
					AND cl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').'
				)
				LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'.
				Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
				LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
				LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (p.`id_manufacturer`= m.`id_manufacturer`)
				'.Product::sqlStock('p', 0).'
				WHERE `id_product_1` = '.(int)$this->id.
				($active ? ' AND product_shop.`active` = 1' : '').'
				GROUP BY product_shop.id_product';
		if (!$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql))
			return false;
		foreach ($result as &$row)
			$row['id_product_attribute'] = Product::getDefaultAttribute((int)$row['id_product']);
		return $this->getProductsProperties($id_lang, $result);
	}

	public static function getAccessoryById($accessory_id)
	{
		return Db::getInstance()->getRow('SELECT `id_product`, `name` FROM `'._DB_PREFIX_.'product_lang` WHERE `id_product` = '.(int)$accessory_id);
	}

	/**
	 * Link accessories with product
	 *
	 * @param array $accessories_id Accessories ids
	 */
	public function changeAccessories($accessories_id)
	{
		foreach ($accessories_id as $id_product_2)
			Db::getInstance()->insert('accessory', array(
				'id_product_1' => (int)$this->id,
				'id_product_2' => (int)$id_product_2
			));
	}

	/**
	 * Add new feature to product
	 */
	public function addFeaturesCustomToDB($id_value, $lang, $cust)
	{
		$row = array('id_feature_value' => (int)$id_value, 'id_lang' => (int)$lang, 'value' => pSQL($cust));
		return Db::getInstance()->insert('feature_value_lang', $row);
	}

	public function addFeaturesToDB($id_feature, $id_value, $cust = 0)
	{
		if ($cust)
		{
			$row = array('id_feature' => (int)$id_feature, 'custom' => 1);
			Db::getInstance()->insert('feature_value', $row);
			$id_value = Db::getInstance()->Insert_ID();
		}
		$row = array('id_feature' => (int)$id_feature, 'id_product' => (int)$this->id, 'id_feature_value' => (int)$id_value);
		Db::getInstance()->insert('feature_product', $row);
		SpecificPriceRule::applyAllRules(array((int)$this->id));
		if ($id_value)
			return ($id_value);
	}

	public static function addFeatureProductImport($id_product, $id_feature, $id_feature_value)
	{
		return Db::getInstance()->execute('
			INSERT INTO `'._DB_PREFIX_.'feature_product` (`id_feature`, `id_product`, `id_feature_value`)
			VALUES ('.(int)$id_feature.', '.(int)$id_product.', '.(int)$id_feature_value.')
			ON DUPLICATE KEY UPDATE `id_feature_value` = '.(int)$id_feature_value
		);
	}

	/**
	* Select all features for the object
	*
	* @return array Array with feature product's data
	*/
	public function getFeatures()
	{
		return Product::getFeaturesStatic((int)$this->id);
	}

	public static function getFeaturesStatic($id_product)
	{
		if (!Feature::isFeatureActive())
			return array();
		if (!array_key_exists($id_product, self::$_cacheFeatures))
			self::$_cacheFeatures[$id_product] = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
				SELECT id_feature, id_product, id_feature_value
				FROM `'._DB_PREFIX_.'feature_product`
				WHERE `id_product` = '.(int)$id_product
			);
		return self::$_cacheFeatures[$id_product];
	}

	public static function cacheProductsFeatures($product_ids)
	{
		if (!Feature::isFeatureActive())
			return;

		$product_implode = array();
		foreach ($product_ids as $id_product)
			if ((int)$id_product && !array_key_exists($id_product, self::$_cacheFeatures))
				$product_implode[] = (int)$id_product;
		if (!count($product_implode))
			return;

		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
		SELECT id_feature, id_product, id_feature_value
		FROM `'._DB_PREFIX_.'feature_product`
		WHERE `id_product` IN ('.implode($product_implode, ',').')');
		foreach ($result as $row)
		{
			if (!array_key_exists($row['id_product'], self::$_cacheFeatures))
				self::$_cacheFeatures[$row['id_product']] = array();
			self::$_cacheFeatures[$row['id_product']][] = $row;
		}
	}

	public static function cacheFrontFeatures($product_ids, $id_lang)
	{
		if (!Feature::isFeatureActive())
			return;

		$product_implode = array();
		foreach ($product_ids as $id_product)
			if ((int)$id_product && !array_key_exists($id_product.'-'.$id_lang, self::$_cacheFeatures))
				$product_implode[] = (int)$id_product;
		if (!count($product_implode))
			return;

		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
		SELECT id_product, name, value, pf.id_feature
		FROM '._DB_PREFIX_.'feature_product pf
		LEFT JOIN '._DB_PREFIX_.'feature_lang fl ON (fl.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')
		LEFT JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = pf.id_feature_value AND fvl.id_lang = '.(int)$id_lang.')
		LEFT JOIN '._DB_PREFIX_.'feature f ON (f.id_feature = pf.id_feature)
		WHERE `id_product` IN ('.implode($product_implode, ',').')
		ORDER BY f.position ASC');

		foreach ($result as $row)
		{
			if (!array_key_exists($row['id_product'].'-'.$id_lang, self::$_frontFeaturesCache))
				self::$_frontFeaturesCache[$row['id_product'].'-'.$id_lang] = array();
			if (!isset(self::$_frontFeaturesCache[$row['id_product'].'-'.$id_lang][$row['id_feature']]))
				self::$_frontFeaturesCache[$row['id_product'].'-'.$id_lang][$row['id_feature']] = $row;
		}
	}

	/**
	* Admin panel product search
	*
	* @param integer $id_lang Language id
	* @param string $query Search query
	* @return array Matching products
	*/
	public static function searchByName($id_lang, $query, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();

		$sql = new DbQuery();
		$sql->select('p.`id_product`, pl.`name`, p.`active`, p.`reference`, m.`name` AS manufacturer_name, stock.`quantity`, product_shop.advanced_stock_management, p.`customizable`');
		$sql->from('category_product', 'cp');
		$sql->leftJoin('product', 'p', 'p.`id_product` = cp.`id_product`');
		$sql->join(Shop::addSqlAssociation('product', 'p'));
		$sql->leftJoin('product_lang', 'pl', '
			p.`id_product` = pl.`id_product`
			AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl')
		);
		$sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');

		$where = 'pl.`name` LIKE \'%'.pSQL($query).'%\'
		OR p.`reference` LIKE \'%'.pSQL($query).'%\'
		OR p.`supplier_reference` LIKE \'%'.pSQL($query).'%\'
		OR  p.`id_product` IN (SELECT id_product FROM '._DB_PREFIX_.'product_supplier sp WHERE `product_supplier_reference` LIKE \'%'.pSQL($query).'%\')';
		$sql->groupBy('`id_product`');
		$sql->orderBy('pl.`name` ASC');

		if (Combination::isFeatureActive())
		{
			$sql->leftJoin('product_attribute', 'pa', 'pa.`id_product` = p.`id_product`');
			$sql->join(Shop::addSqlAssociation('product_attribute', 'pa', false));
			$where .= ' OR pa.`reference` LIKE \'%'.pSQL($query).'%\'';
		}
		$sql->where($where);
		$sql->join(Product::sqlStock('p', 'pa', false, $context->shop));

		$result = Db::getInstance()->executeS($sql);

		if (!$result)
			return false;

		$results_array = array();
		foreach ($result as $row)
		{
			$row['price_tax_incl'] = Product::getPriceStatic($row['id_product'], true, null, 2);
			$row['price_tax_excl'] = Product::getPriceStatic($row['id_product'], false, null, 2);
			$results_array[] = $row;
		}
		return $results_array;
	}

	/**
	* Duplicate attributes when duplicating a product
	*
	* @param integer $id_product_old Old product id
	* @param integer $id_product_new New product id
	*/
	public static function duplicateAttributes($id_product_old, $id_product_new)
	{
		$return = true;
		$combination_images = array();

		$result = Db::getInstance()->executeS('
		SELECT pa.*, product_attribute_shop.*
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE pa.`id_product` = '.(int)$id_product_old
		);

		foreach ($result as $row)
		{
			$id_product_attribute_old = (int)$row['id_product_attribute'];
			$result2 = Db::getInstance()->executeS('
			SELECT *
			FROM `'._DB_PREFIX_.'product_attribute_combination`
				WHERE `id_product_attribute` = '.$id_product_attribute_old
			);

			$row['id_product'] = $id_product_new;
			unset($row['id_product_attribute']);
			$combination = new Combination();
			foreach ($row as $k => $v)
				$combination->$k = $v;
			$return &= $combination->add();

			$id_product_attribute_new = (int)$combination->id;
			if ($result_images = Product::_getAttributeImageAssociations($id_product_attribute_old))
			{
				$combination_images['old'][$id_product_attribute_old] = $result_images;
				$combination_images['new'][$id_product_attribute_new] = $result_images;
			}
			foreach ($result2 as $row2)
			{
				$row2['id_product_attribute'] = $id_product_attribute_new;
				$return &= Db::getInstance()->insert('product_attribute_combination', $row2);
			}
		}
		return !$return ? false : $combination_images;
	}

	/**
	* Get product attribute image associations
	* @param integer $id_product_attribute
	* @return array
	*/
	public static function _getAttributeImageAssociations($id_product_attribute)
	{
		$combination_images = array();
		$data = Db::getInstance()->executeS('
			SELECT `id_image`
			FROM `'._DB_PREFIX_.'product_attribute_image`
			WHERE `id_product_attribute` = '.(int)$id_product_attribute);
		foreach ($data as $row)
			$combination_images[] = (int)$row['id_image'];
		return $combination_images;
	}

	public static function duplicateAccessories($id_product_old, $id_product_new)
	{
		$return = true;

		$result = Db::getInstance()->executeS('
		SELECT *
		FROM `'._DB_PREFIX_.'accessory`
		WHERE `id_product_1` = '.(int)$id_product_old);
		foreach ($result as $row)
		{
			$data = array(
				'id_product_1' => (int)$id_product_new,
				'id_product_2' => (int)$row['id_product_2']);
			$return &= Db::getInstance()->insert('accessory', $data);
		}
		return $return;
	}

	public static function duplicateTags($id_product_old, $id_product_new)
	{
		$tags = Db::getInstance()->executeS('SELECT `id_tag` FROM `'._DB_PREFIX_.'product_tag` WHERE `id_product` = '.(int)$id_product_old);
		if (!Db::getInstance()->NumRows())
			return true;

		$data = array();
		foreach ($tags as $tag)
			$data[] = array(
				'id_product' => (int)$id_product_new,
				'id_tag' => (int)$tag['id_tag'],
			);

		return Db::getInstance()->insert('product_tag', $data);
	}

	public static function duplicateDownload($id_product_old, $id_product_new)
	{
		$sql = 'SELECT `display_filename`, `filename`, `date_add`, `date_expiration`, `nb_days_accessible`, `nb_downloadable`, `active`, `is_shareable`
				FROM `'._DB_PREFIX_.'product_download`
				WHERE `id_product` = '.(int)$id_product_old;
		$results = Db::getInstance()->executeS($sql);
		if (!$results)
			return true;

		$data = array();
		$res = true;
		foreach ($results as $row)
		{
			$new_filename = ProductDownload::getNewFilename();
			copy(_PS_DOWNLOAD_DIR_.$row['filename'], _PS_DOWNLOAD_DIR_.$new_filename);

			$data[] = array(
				'id_product' => (int)$id_product_new,
				'display_filename' => pSQL($row['display_filename']),
				'filename' => pSQL($new_filename),
				'date_expiration' => pSQL($row['date_expiration']),
				'nb_days_accessible' => (int)$row['nb_days_accessible'],
				'nb_downloadable' => (int)$row['nb_downloadable'],
				'active' => (int)$row['active'],
				'is_shareable' => (int)$row['is_shareable'],
				'date_add' => date('Y-m-d H:i:s')
			);
			$res &= Db::getInstance()->insert('product_download', $data);
		}

		return $res;
	}

	public static function duplicateAttachments($id_product_old, $id_product_new)
	{
		// Get all ids attachments of the old product
		$sql = 'SELECT `id_attachment` FROM `'._DB_PREFIX_.'product_attachment` WHERE `id_product` = '.(int)$id_product_old;
		$results = Db::getInstance()->executeS($sql);

		if (!$results)
			return true;

		$data = array();

		// Prepare data of table product_attachment
		foreach ($results as $row)
			$data[] = array(
				'id_product' => (int)$id_product_new,
				'id_attachment' => (int)$row['id_attachment']
			);

		// Duplicate product attachement
		$res = Db::getInstance()->insert('product_attachment', $data);
		Product::updateCacheAttachment((int)$id_product_new);
		return $res;
	}

	/**
	* Duplicate features when duplicating a product
	*
	* @param integer $id_product_old Old product id
	* @param integer $id_product_old New product id
	*/
	public static function duplicateFeatures($id_product_old, $id_product_new)
	{
		$return = true;

		$result = Db::getInstance()->executeS('
		SELECT *
		FROM `'._DB_PREFIX_.'feature_product`
		WHERE `id_product` = '.(int)$id_product_old);
		foreach ($result as $row)
		{
			$result2 = Db::getInstance()->getRow('
			SELECT *
			FROM `'._DB_PREFIX_.'feature_value`
			WHERE `id_feature_value` = '.(int)$row['id_feature_value']);
			// Custom feature value, need to duplicate it
			if ($result2['custom'])
			{
				$old_id_feature_value = $result2['id_feature_value'];
				unset($result2['id_feature_value']);
				$return &= Db::getInstance()->insert('feature_value', $result2);
				$max_fv = Db::getInstance()->getRow('
					SELECT MAX(`id_feature_value`) AS nb
					FROM `'._DB_PREFIX_.'feature_value`');
				$new_id_feature_value = $max_fv['nb'];
				$languages = Language::getLanguages();
				foreach ($languages as $language)
				{
					$result3 = Db::getInstance()->getRow('
					SELECT *
					FROM `'._DB_PREFIX_.'feature_value_lang`
					WHERE `id_feature_value` = '.(int)$old_id_feature_value.'
					AND `id_lang` = '.(int)$language['id_lang']);

					if ($result3)
					{
						$result3['id_feature_value'] = $new_id_feature_value;
						$return &= Db::getInstance()->insert('feature_value_lang', $result3);
					}
				}
				$row['id_feature_value'] = $new_id_feature_value;
			}
			$row['id_product'] = $id_product_new;
			$return &= Db::getInstance()->insert('feature_product', $row);
		}
		return $return;
	}

	protected static function _getCustomizationFieldsNLabels($product_id)
	{
		if (!Customization::isFeatureActive())
			return false;

		$customizations = array();
		if (($customizations['fields'] = Db::getInstance()->executeS('
			SELECT `id_customization_field`, `type`, `required`
			FROM `'._DB_PREFIX_.'customization_field`
			WHERE `id_product` = '.(int)$product_id.'
			ORDER BY `id_customization_field`')) === false)
			return false;

		if (empty($customizations['fields']))
			return array();

		$customization_field_ids = array();
		foreach ($customizations['fields'] as $customization_field)
			$customization_field_ids[] = (int)$customization_field['id_customization_field'];

		if (($customization_labels = Db::getInstance()->executeS('
			SELECT `id_customization_field`, `id_lang`, `name`
			FROM `'._DB_PREFIX_.'customization_field_lang`
			WHERE `id_customization_field` IN ('.implode(', ', $customization_field_ids).')
			ORDER BY `id_customization_field`')) === false)
			return false;

		foreach ($customization_labels as $customization_label)
			$customizations['labels'][$customization_label['id_customization_field']][] = $customization_label;

		return $customizations;
	}

	public static function duplicateSpecificPrices($old_product_id, $product_id)
	{
		foreach (SpecificPrice::getIdsByProductId((int)$old_product_id) as $data)
		{
			$specific_price = new SpecificPrice((int)$data['id_specific_price']);
			if (!$specific_price->duplicate((int)$product_id))
				return false;
		}
		return true;
	}

	public static function duplicateCustomizationFields($old_product_id, $product_id)
	{
		// If customization is not activated, return success
		if (!Customization::isFeatureActive())
			return true;
		if (($customizations = Product::_getCustomizationFieldsNLabels($old_product_id)) === false)
			return false;
		if (empty($customizations))
			return true;
		foreach ($customizations['fields'] as $customization_field)
		{
			/* The new datas concern the new product */
			$customization_field['id_product'] = (int)$product_id;
			$old_customization_field_id = (int)$customization_field['id_customization_field'];

			unset($customization_field['id_customization_field']);

			if (!Db::getInstance()->insert('customization_field', $customization_field)
				|| !$customization_field_id = Db::getInstance()->Insert_ID())
				return false;

			if (isset($customizations['labels']))
			{
				$query = 'INSERT INTO `'._DB_PREFIX_.'customization_field_lang` (`id_customization_field`, `id_lang`, `name`) VALUES ';
				$data = array();
				foreach ($customizations['labels'][$old_customization_field_id] as $customization_label)
				{
					$data = array(
						'id_customization_field' => (int)$customization_field_id,
						'id_lang' => (int)$customization_label['id_lang'],
						'name' => pSQL($customization_label['name']),
					);

					if (!Db::getInstance()->insert('customization_field_lang', $data))
						return false;
				}
			}
		}
		return true;
	}

	/**
	* Get the link of the product page of this product
	*/
	public function getLink(Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();
		return $context->link->getProductLink($this);
	}

	public function getTags($id_lang)
	{
		if (!$this->isFullyLoaded && is_null($this->tags))
			$this->tags = Tag::getProductTags($this->id);

		if (!($this->tags && key_exists($id_lang, $this->tags)))
			return '';

		$result = '';
		foreach ($this->tags[$id_lang] as $tag_name)
			$result .= $tag_name.', ';

		return rtrim($result, ', ');
	}

	public static function defineProductImage($row, $id_lang)
	{
		if (isset($row['id_image']))
		if ($row['id_image'])
			return $row['id_product'].'-'.$row['id_image'];

		return Language::getIsoById((int)$id_lang).'-default';
	}

	public static function getProductProperties($id_lang, $row, Context $context = null)
	{
		if (!$row['id_product'])
			return false;

		if ($context == null)
			$context = Context::getContext();

		// Product::getDefaultAttribute is only called if id_product_attribute is missing from the SQL query at the origin of it:
		// consider adding it in order to avoid unnecessary queries
		$row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']);
		if (Combination::isFeatureActive() && (!isset($row['id_product_attribute']) || !$row['id_product_attribute'])
			&& ((isset($row['cache_default_attribute']) && ($ipa_default = $row['cache_default_attribute']) !== null)
				|| ($ipa_default = Product::getDefaultAttribute($row['id_product'], !$row['allow_oosp']))))
			$row['id_product_attribute'] = $ipa_default;
		if (!Combination::isFeatureActive() || !isset($row['id_product_attribute']))
			$row['id_product_attribute'] = 0;

		// Tax
		$usetax = Tax::excludeTaxeOption();

		$cache_key = $row['id_product'].'-'.$row['id_product_attribute'].'-'.$id_lang.'-'.(int)$usetax;
		if (isset($row['id_product_pack']))
			$cache_key .= '-pack'.$row['id_product_pack'];

		if (isset(self::$producPropertiesCache[$cache_key]))
			return array_merge($row, self::$producPropertiesCache[$cache_key]);

		// Datas
		$row['category'] = Category::getLinkRewrite((int)$row['id_category_default'], (int)$id_lang);
		$row['link'] = $context->link->getProductLink((int)$row['id_product'], $row['link_rewrite'], $row['category'], $row['ean13']);

		$row['attribute_price'] = 0;
		if (isset($row['id_product_attribute']) && $row['id_product_attribute'])
			$row['attribute_price'] = (float)Product::getProductAttributePrice($row['id_product_attribute']);

		$row['price_tax_exc'] = Product::getPriceStatic(
			(int)$row['id_product'],
			false,
			((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
			(self::$_taxCalculationMethod == PS_TAX_EXC ? 2 : 6)
		);

		if (self::$_taxCalculationMethod == PS_TAX_EXC)
		{
			$row['price_tax_exc'] = Tools::ps_round($row['price_tax_exc'], 2);
			$row['price'] = Product::getPriceStatic(
				(int)$row['id_product'],
				true,
				((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
				6
			);
			$row['price_without_reduction'] = Product::getPriceStatic(
				(int)$row['id_product'],
				false,
				((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
				2,
				null,
				false,
				false
			);
		}
		else
		{
			$row['price'] = Tools::ps_round(
				Product::getPriceStatic(
					(int)$row['id_product'],
					true,
					((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
					2
				),
				2
			);

			$row['price_without_reduction'] = Product::getPriceStatic(
				(int)$row['id_product'],
				true,
				((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
				6,
				null,
				false,
				false
			);
		}

		$row['reduction'] = Product::getPriceStatic(
			(int)$row['id_product'],
			(bool)$usetax,
			(int)$row['id_product_attribute'],
			6,
			null,
			true,
			true,
			1,
			true,
			null,
			null,
			null,
			$specific_prices
		);

		$row['specific_prices'] = $specific_prices;

		$row['quantity'] = Product::getQuantity(
			(int)$row['id_product'],
			0,
			isset($row['cache_is_pack']) ? $row['cache_is_pack'] : null
		);

		if ($row['id_product_attribute'])
		{
			$row['quantity_all_versions'] = $row['quantity'];
			$row['quantity'] = Product::getQuantity(
				(int)$row['id_product'],
    			$row['id_product_attribute'],
			   isset($row['cache_is_pack']) ? $row['cache_is_pack'] : null
			);
		}	

		$row['id_image'] = Product::defineProductImage($row, $id_lang);
		$row['features'] = Product::getFrontFeaturesStatic((int)$id_lang, $row['id_product']);

		$row['attachments'] = array();
		if (!isset($row['cache_has_attachments']) || $row['cache_has_attachments'])
			$row['attachments'] = Product::getAttachmentsStatic((int)$id_lang, $row['id_product']);

		$row['virtual'] = ((!isset($row['is_virtual']) || $row['is_virtual']) ? 1 : 0);

		// Pack management
		$row['pack'] = (!isset($row['cache_is_pack']) ? Pack::isPack($row['id_product']) : (int)$row['cache_is_pack']);
		$row['packItems'] = $row['pack'] ? Pack::getItemTable($row['id_product'], $id_lang) : array();
		$row['nopackprice'] = $row['pack'] ? Pack::noPackPrice($row['id_product']) : 0;
		if ($row['pack'] && !Pack::isInStock($row['id_product']))
			$row['quantity'] = 0;

		$row = Product::getTaxesInformations($row, $context);

		self::$producPropertiesCache[$cache_key] = $row;
		return self::$producPropertiesCache[$cache_key];
	}
	
	public static function getTaxesInformations($row, Context $context = null)
	{
		static $address = null;

		if ($context === null)
			$context = Context::getContext();
		if ($address === null)
			$address = new Address();

		$address->id_country = (int)$context->country->id;
		$address->id_state = 0;
		$address->postcode = 0;
		
		$tax_manager = TaxManagerFactory::getManager($address, Product::getIdTaxRulesGroupByIdProduct((int)$row['id_product'], $context));
		$row['rate'] = $tax_manager->getTaxCalculator()->getTotalRate();
		$row['tax_name'] = $tax_manager->getTaxCalculator()->getTaxesName();

		return $row;
	}

	public static function getProductsProperties($id_lang, $query_result)
	{
		$results_array = array();

		if (is_array($query_result))
			foreach ($query_result as $row)
				if ($row2 = Product::getProductProperties($id_lang, $row))
					$results_array[] = $row2;

		return $results_array;
	}

	/*
	* Select all features for a given language
	*
	* @param $id_lang Language id
	* @return array Array with feature's data
	*/
	public static function getFrontFeaturesStatic($id_lang, $id_product)
	{
		if (!Feature::isFeatureActive())
			return array();
		if (!array_key_exists($id_product.'-'.$id_lang, self::$_frontFeaturesCache))
		{
			self::$_frontFeaturesCache[$id_product.'-'.$id_lang] = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
				SELECT name, value, pf.id_feature
				FROM '._DB_PREFIX_.'feature_product pf
				LEFT JOIN '._DB_PREFIX_.'feature_lang fl ON (fl.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')
				LEFT JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = pf.id_feature_value AND fvl.id_lang = '.(int)$id_lang.')
				LEFT JOIN '._DB_PREFIX_.'feature f ON (f.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')
				WHERE pf.id_product = '.(int)$id_product.'
				ORDER BY f.position ASC'
			);
		}
		return self::$_frontFeaturesCache[$id_product.'-'.$id_lang];
	}

	public function getFrontFeatures($id_lang)
	{
		return Product::getFrontFeaturesStatic($id_lang, $this->id);
	}

	public static function getAttachmentsStatic($id_lang, $id_product)
	{
		return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
		SELECT *
		FROM '._DB_PREFIX_.'product_attachment pa
		LEFT JOIN '._DB_PREFIX_.'attachment a ON a.id_attachment = pa.id_attachment
		LEFT JOIN '._DB_PREFIX_.'attachment_lang al ON (a.id_attachment = al.id_attachment AND al.id_lang = '.(int)$id_lang.')
		WHERE pa.id_product = '.(int)$id_product);
	}

	public function getAttachments($id_lang)
	{
		return Product::getAttachmentsStatic($id_lang, $this->id);
	}

	/*
	** Customization management
	*/

	public static function getAllCustomizedDatas($id_cart, $id_lang = null, $only_in_cart = true)
	{
		if (!Customization::isFeatureActive())
			return false;

		// No need to query if there isn't any real cart!
		if (!$id_cart)
			return false;
		if (!$id_lang)
			$id_lang = Context::getContext()->language->id;

		if (!$result = Db::getInstance()->executeS('
			SELECT cd.`id_customization`, c.`id_address_delivery`, c.`id_product`, cfl.`id_customization_field`, c.`id_product_attribute`,
				cd.`type`, cd.`index`, cd.`value`, cfl.`name`
			FROM `'._DB_PREFIX_.'customized_data` cd
			NATURAL JOIN `'._DB_PREFIX_.'customization` c
			LEFT JOIN `'._DB_PREFIX_.'customization_field_lang` cfl ON (cfl.id_customization_field = cd.`index` AND id_lang = '.(int)$id_lang.')
			WHERE c.`id_cart` = '.(int)$id_cart.
			($only_in_cart ? ' AND c.`in_cart` = 1' : '').'
			ORDER BY `id_product`, `id_product_attribute`, `type`, `index`'))
			return false;

		$customized_datas = array();

		foreach ($result as $row)
			$customized_datas[(int)$row['id_product']][(int)$row['id_product_attribute']][(int)$row['id_address_delivery']][(int)$row['id_customization']]['datas'][(int)$row['type']][] = $row;

		if (!$result = Db::getInstance()->executeS(
			'SELECT `id_product`, `id_product_attribute`, `id_customization`, `id_address_delivery`, `quantity`, `quantity_refunded`, `quantity_returned`
			FROM `'._DB_PREFIX_.'customization`
			WHERE `id_cart` = '.(int)($id_cart).($only_in_cart ? '
			AND `in_cart` = 1' : '')))
			return false;

		foreach ($result as $row)
		{
			$customized_datas[(int)$row['id_product']][(int)$row['id_product_attribute']][(int)$row['id_address_delivery']][(int)$row['id_customization']]['quantity'] = (int)$row['quantity'];
			$customized_datas[(int)$row['id_product']][(int)$row['id_product_attribute']][(int)$row['id_address_delivery']][(int)$row['id_customization']]['quantity_refunded'] = (int)$row['quantity_refunded'];
			$customized_datas[(int)$row['id_product']][(int)$row['id_product_attribute']][(int)$row['id_address_delivery']][(int)$row['id_customization']]['quantity_returned'] = (int)$row['quantity_returned'];
		}

		return $customized_datas;
	}

	public static function addCustomizationPrice(&$products, &$customized_datas)
	{
		if (!$customized_datas)
			return;

		foreach ($products as &$product_update)
		{
			if (!Customization::isFeatureActive())
			{
				$product_update['customizationQuantityTotal'] = 0;
				$product_update['customizationQuantityRefunded'] = 0;
				$product_update['customizationQuantityReturned'] = 0;
			}
			else
			{
				$customization_quantity = 0;
				$customization_quantity_refunded = 0;
				$customization_quantity_returned = 0;

				/* Compatibility */
				$product_id = (int)(isset($product_update['id_product']) ? $product_update['id_product'] : $product_update['product_id']);
				$product_attribute_id = (int)(isset($product_update['id_product_attribute']) ? $product_update['id_product_attribute'] : $product_update['product_attribute_id']);
				$id_address_delivery = (int)$product_update['id_address_delivery'];
				$product_quantity = (int)(isset($product_update['cart_quantity']) ? $product_update['cart_quantity'] : $product_update['product_quantity']);
				$price = isset($product_update['price']) ? $product_update['price'] : $product_update['product_price'];
				if (isset($product_update['price_wt']) && $product_update['price_wt'])
					$price_wt = $product_update['price_wt'];
				else
					$price_wt = $price * (1 + ((isset($product_update['tax_rate']) ? $product_update['tax_rate'] : $product_update['rate']) * 0.01));

				if (isset($customized_datas[$product_id][$product_attribute_id]))
					foreach ($customized_datas[$product_id][$product_attribute_id][$id_address_delivery] as $customization)
					{
						$customization_quantity += (int)$customization['quantity'];
						$customization_quantity_refunded += (int)$customization['quantity_refunded'];
						$customization_quantity_returned += (int)$customization['quantity_returned'];
					}

				$product_update['customizationQuantityTotal'] = $customization_quantity;
				$product_update['customizationQuantityRefunded'] = $customization_quantity_refunded;
				$product_update['customizationQuantityReturned'] = $customization_quantity_returned;

				if ($customization_quantity)
				{
					$product_update['total_wt'] = $price_wt * ($product_quantity - $customization_quantity);
					$product_update['total_customization_wt'] = $price_wt * $customization_quantity;
					$product_update['total'] = $price * ($product_quantity - $customization_quantity);
					$product_update['total_customization'] = $price * $customization_quantity;
				}
			}
		}
	}

	/*
	** Customization fields' label management
	*/

	protected function _checkLabelField($field, $value)
	{
		if (!Validate::isLabel($value))
			return false;
		$tmp = explode('_', $field);
		if (count($tmp) < 4)
			return false;
		return $tmp;
	}

	protected function _deleteOldLabels()
	{
		$max = array(
			Product::CUSTOMIZE_FILE => (int)Tools::getValue('uploadable_files'),
			Product::CUSTOMIZE_TEXTFIELD => (int)Tools::getValue('text_fields')
		);

		/* Get customization field ids */
		if (($result = Db::getInstance()->executeS(
			'SELECT `id_customization_field`, `type`
			FROM `'._DB_PREFIX_.'customization_field`
			WHERE `id_product` = '.(int)$this->id.'
			ORDER BY `id_customization_field`')
		) === false)
			return false;

		if (empty($result))
			return true;

		$customization_fields = array(
			Product::CUSTOMIZE_FILE => array(),
			Product::CUSTOMIZE_TEXTFIELD => array()
		);

		foreach ($result as $row)
			$customization_fields[(int)$row['type']][] = (int)$row['id_customization_field'];

		$extra_file = count($customization_fields[Product::CUSTOMIZE_FILE]) - $max[Product::CUSTOMIZE_FILE];
		$extra_text = count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $max[Product::CUSTOMIZE_TEXTFIELD];

		/* If too much inside the database, deletion */
		if ($extra_file > 0 && count($customization_fields[Product::CUSTOMIZE_FILE]) - $extra_file >= 0 &&
		(!Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'customization_field`
			WHERE `id_product` = '.(int)$this->id.'
			AND `type` = '.Product::CUSTOMIZE_FILE.'
			AND `id_customization_field` >= '.(int)$customization_fields[Product::CUSTOMIZE_FILE][count($customization_fields[Product::CUSTOMIZE_FILE]) - $extra_file]
		)
		|| !Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'customization_field_lang`
			WHERE `id_customization_field`
			NOT IN (
				SELECT `id_customization_field`
				FROM `'._DB_PREFIX_.'customization_field`
			)'
		)))
			return false;

		if ($extra_text > 0 && count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $extra_text >= 0 &&
		(!Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'customization_field`
			WHERE `id_product` = '.(int)$this->id.'
			AND `type` = '.Product::CUSTOMIZE_TEXTFIELD.'
			AND `id_customization_field` >= '.(int)$customization_fields[Product::CUSTOMIZE_TEXTFIELD][count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $extra_text]
		)
		|| !Db::getInstance()->execute(
			'DELETE FROM `'._DB_PREFIX_.'customization_field_lang`
			WHERE `id_customization_field`
			NOT IN (
				SELECT `id_customization_field`
				FROM `'._DB_PREFIX_.'customization_field`
			)'
		)))
			return false;

		// Refresh cache of feature detachable
		Configuration::updateGlobalValue('PS_CUSTOMIZATION_FEATURE_ACTIVE', Customization::isCurrentlyUsed());

		return true;
	}

	protected function _createLabel(&$languages, $type)
	{
		// Label insertion
		if (!Db::getInstance()->execute('
			INSERT INTO `'._DB_PREFIX_.'customization_field` (`id_product`, `type`, `required`)
			VALUES ('.(int)$this->id.', '.(int)$type.', 0)') ||
			!$id_customization_field = (int)Db::getInstance()->Insert_ID())
			return false;

		// Multilingual label name creation
		$values = '';
		foreach ($languages as $language)
			$values .= '('.(int)$id_customization_field.', '.(int)$language['id_lang'].', \'\'), ';

		$values = rtrim($values, ', ');
		if (!Db::getInstance()->execute('
			INSERT INTO `'._DB_PREFIX_.'customization_field_lang` (`id_customization_field`, `id_lang`, `name`)
			VALUES '.$values))
			return false;

		// Set cache of feature detachable to true
		Configuration::updateGlobalValue('PS_CUSTOMIZATION_FEATURE_ACTIVE', '1');

		return true;
	}

	public function createLabels($uploadable_files, $text_fields)
	{
		$languages = Language::getLanguages();
		if ((int)$uploadable_files > 0)
			for ($i = 0; $i < (int)$uploadable_files; $i++)
				if (!$this->_createLabel($languages, Product::CUSTOMIZE_FILE))
					return false;

		if ((int)$text_fields > 0)
			for ($i = 0; $i < (int)$text_fields; $i++)
				if (!$this->_createLabel($languages, Product::CUSTOMIZE_TEXTFIELD))
					return false;

		return true;
	}

	public function updateLabels()
	{
		$has_required_fields = 0;
		foreach ($_POST as $field => $value)
			/* Label update */
			if (strncmp($field, 'label_', 6) == 0)
			{
				if (!$tmp = $this->_checkLabelField($field, $value))
					return false;
				/* Multilingual label name update */
				if (!Db::getInstance()->execute('
					INSERT INTO `'._DB_PREFIX_.'customization_field_lang`
					(`id_customization_field`, `id_lang`, `name`) VALUES ('.(int)$tmp[2].', '.(int)$tmp[3].', \''.pSQL($value).'\')
					ON DUPLICATE KEY UPDATE `name` = \''.pSQL($value).'\''))
					return false;
				$is_required = isset($_POST['require_'.(int)$tmp[1].'_'.(int)$tmp[2]]) ? 1 : 0;
				$has_required_fields |= $is_required;
				/* Require option update */
				if (!Db::getInstance()->execute(
					'UPDATE `'._DB_PREFIX_.'customization_field`
					SET `required` = '.(int)$is_required.'
					WHERE `id_customization_field` = '.(int)$tmp[2]))
					return false;
			}

		if ($has_required_fields && !ObjectModel::updateMultishopTable('product', array('customizable' => 2), 'a.id_product = '.(int)$this->id))
			return false;

		if (!$this->_deleteOldLabels())
			return false;

		return true;
	}

	public function getCustomizationFields($id_lang = false)
	{
		if (!Customization::isFeatureActive())
			return false;

		if (!$result = Db::getInstance()->executeS('
			SELECT cf.`id_customization_field`, cf.`type`, cf.`required`, cfl.`name`, cfl.`id_lang`
			FROM `'._DB_PREFIX_.'customization_field` cf
			NATURAL JOIN `'._DB_PREFIX_.'customization_field_lang` cfl
			WHERE cf.`id_product` = '.(int)$this->id.($id_lang ? ' AND cfl.`id_lang` = '.(int)$id_lang : '').'
			ORDER BY cf.`id_customization_field`'))
			return false;

		if ($id_lang)
			return $result;

		$customization_fields = array();
		foreach ($result as $row)
			$customization_fields[(int)$row['type']][(int)$row['id_customization_field']][(int)$row['id_lang']] = $row;

		return $customization_fields;
	}

	public function getCustomizationFieldIds()
	{
		if (!Customization::isFeatureActive())
			return array();
		return Db::getInstance()->executeS('
			SELECT `id_customization_field`, `type`, `required`
			FROM `'._DB_PREFIX_.'customization_field`
			WHERE `id_product` = '.(int)$this->id);
	}

	public function getRequiredCustomizableFields()
	{
		if (!Customization::isFeatureActive())
			return array();
		return Db::getInstance()->executeS('
			SELECT `id_customization_field`, `type`
			FROM `'._DB_PREFIX_.'customization_field`
			WHERE `id_product` = '.(int)$this->id.'
			AND `required` = 1'
		);
	}

	public function hasAllRequiredCustomizableFields(Context $context = null)
	{
		if (!Customization::isFeatureActive())
			return true;
		if (!$context)
			$context = Context::getContext();

		$fields = $context->cart->getProductCustomization($this->id, null, true);
		if (($required_fields = $this->getRequiredCustomizableFields()) === false)
			return false;

		$fields_present = array();
		foreach ($fields as $field)
			$fields_present[] = array('id_customization_field' => $field['index'], 'type' => $field['type']);
		foreach ($required_fields as $required_field)
			if (!in_array($required_field, $fields_present))
				return false;
		return true;
	}


	/**
	 * Checks if the product is in at least one of the submited categories
	 *
	 * @param int $id_product
	 * @param array $categories array of category arrays
	 * @return boolean is the product in at least one category
	 */
	public static function idIsOnCategoryId($id_product, $categories)
	{
		if (!((int)$id_product > 0) || !is_array($categories) || empty($categories))
			return false;
		$sql = 'SELECT id_product FROM `'._DB_PREFIX_.'category_product` WHERE `id_product` = '.(int)$id_product.' AND `id_category` IN (';
		foreach ($categories as $category)
			$sql .= (int)$category['id_category'].',';
		$sql = rtrim($sql, ',').')';

		$hash = md5($sql);
		if (!isset(self::$_incat[$hash]))
		{
			if (!Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql))
				return false;
			self::$_incat[$hash] = (Db::getInstance(_PS_USE_SQL_SLAVE_)->NumRows() > 0 ? true : false);
		}
		return self::$_incat[$hash];
	}

	public function getNoPackPrice()
	{
		return Pack::noPackPrice($this->id);
	}

	public function checkAccess($id_customer)
	{
		if (!$id_customer)
			return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
			SELECT ctg.`id_group`
			FROM `'._DB_PREFIX_.'category_product` cp
			INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
			WHERE cp.`id_product` = '.(int)$this->id.' AND ctg.`id_group` ='.(int)Group::getCurrent()->id);
		else
			return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
			SELECT cg.`id_group`
			FROM `'._DB_PREFIX_.'category_product` cp
			INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
			INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)
			WHERE cp.`id_product` = '.(int)$this->id.' AND cg.`id_customer` = '.(int)$id_customer);
	}


	/**
	 * Add a stock movement for current product
	 *
	 * Since 1.5, this method only permit to add/remove available quantities of the current product in the current shop
	 *
	 * @see StockManager if you want to manage real stock
	 * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
	 *
	 * @deprecated since 1.5.0
	 *
	 * @param int $quantity
	 * @param int $id_reason - useless
	 * @param int $id_product_attribute
	 * @param int $id_order - useless
	 * @param int $id_employee - useless
	 * @return bool
	 */
	public function addStockMvt($quantity, $id_reason, $id_product_attribute = null, $id_order = null, $id_employee = null)
	{
		if (!$this->id || !$id_reason)
			return false;

		if ($id_product_attribute == null)
			$id_product_attribute = 0;

		$reason = new StockMvtReason((int)$id_reason);
		if (!Validate::isLoadedObject($reason))
			return false;

		$quantity = abs((int)$quantity) * $reason->sign;

		return StockAvailable::updateQuantity($this->id, $id_product_attribute, $quantity);
	}

	/**
	 * @deprecated since 1.5.0
	 */
	public function getStockMvts($id_lang)
	{
		Tools::displayAsDeprecated();

		return Db::getInstance()->executeS('
			SELECT sm.id_stock_mvt, sm.date_add, sm.quantity, sm.id_order,
			CONCAT(pl.name, \' \', GROUP_CONCAT(IFNULL(al.name, \'\'), \'\')) product_name, CONCAT(e.lastname, \' \', e.firstname) employee, mrl.name reason
			FROM `'._DB_PREFIX_.'stock_mvt` sm
			LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
				sm.id_product = pl.id_product
				AND pl.id_lang = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
			)
			LEFT JOIN `'._DB_PREFIX_.'stock_mvt_reason_lang` mrl ON (
				sm.id_stock_mvt_reason = mrl.id_stock_mvt_reason
				AND mrl.id_lang = '.(int)$id_lang.'
			)
			LEFT JOIN `'._DB_PREFIX_.'employee` e ON (
				e.id_employee = sm.id_employee
			)
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON (
				pac.id_product_attribute = sm.id_product_attribute
			)
			LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (
				al.id_attribute = pac.id_attribute
				AND al.id_lang = '.(int)$id_lang.'
			)
			WHERE sm.id_product='.(int)$this->id.'
			GROUP BY sm.id_stock_mvt
		');
	}

	public static function getUrlRewriteInformations($id_product)
	{
		return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
			SELECT pl.`id_lang`, pl.`link_rewrite`, p.`ean13`, cl.`link_rewrite` AS category_rewrite
			FROM `'._DB_PREFIX_.'product` p
			LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product`'.Shop::addSqlRestrictionOnLang('pl').')
			'.Shop::addSqlAssociation('product', 'p').'
			LEFT JOIN `'._DB_PREFIX_.'lang` l ON (pl.`id_lang` = l.`id_lang`)
			LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (cl.`id_category` = product_shop.`id_category_default`  AND cl.`id_lang` = pl.`id_lang`'.Shop::addSqlRestrictionOnLang('cl').')
			WHERE p.`id_product` = '.(int)$id_product.'
			AND l.`active` = 1
		');
	}

	public function getIdTaxRulesGroup()
	{
		return $this->id_tax_rules_group;
	}

	public static function getIdTaxRulesGroupByIdProduct($id_product, Context $context = null)
	{
		if (!$context)
			$context = Context::getContext();
		$key = 'product_id_tax_rules_group_'.(int)$id_product.'_'.(int)$context->shop->id;
		if (!Cache::isStored($key))
			Cache::store($key,
			Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
				SELECT `id_tax_rules_group`
				FROM `'._DB_PREFIX_.'product_shop`
				WHERE `id_product` = '.(int)$id_product.' AND id_shop='.(int)$context->shop->id));

		return Cache::retrieve($key);
	}

	/**
	* @return the total taxes rate applied to the product
	*/
	public function getTaxesRate(Address $address = null)
	{
		if (!$address || !$address->id_country)
			$address = Address::initialize();

		$tax_manager = TaxManagerFactory::getManager($address, $this->id_tax_rules_group);
		$tax_calculator = $tax_manager->getTaxCalculator();

		return $tax_calculator->getTotalRate();
	}

	/**
	* Webservice getter : get product features association
	*
	* @return array
	*/
	public function getWsProductFeatures()
	{
		$rows = $this->getFeatures();
		foreach ($rows as $keyrow => $row)
		{
			foreach ($row as $keyfeature => $feature)
			{
				if ($keyfeature == 'id_feature')
				{
					$rows[$keyrow]['id'] = $feature;
					unset($rows[$keyrow]['id_feature']);
				}
				unset($rows[$keyrow]['id_product']);
			}
			asort($rows[$keyrow]);
		}
		return $rows;
	}

	/**
	* Webservice setter : set product features association
	*
	* @param $productFeatures Product Feature ids
	* @return boolean
	*/
	public function setWsProductFeatures($product_features)
	{
		$this->deleteProductFeatures();
		foreach ($product_features as $product_feature)
			$this->addFeaturesToDB($product_feature['id'], $product_feature['id_feature_value']);
		return true;
	}

	/**
	* Webservice getter : get virtual field default combination
	*
	* @return int
	*/
	public function getWsDefaultCombination()
	{
		return Product::getDefaultAttribute($this->id);
	}

	/**
	* Webservice setter : set virtual field default combination
	*
	* @param $id_combination id default combination
	*/
	public function setWsDefaultCombination($id_combination)
	{
		$this->deleteDefaultAttributes();
		return $this->setDefaultAttribute((int)$id_combination);
	}

	/**
	* Webservice getter : get category ids of current product for association
	*
	* @return array
	*/
	public function getWsCategories()
	{
		$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
			'SELECT cp.`id_category` AS id
			FROM `'._DB_PREFIX_.'category_product` cp
			LEFT JOIN `'._DB_PREFIX_.'category` c ON (c.id_category = cp.id_category)
			'.Shop::addSqlAssociation('category', 'c').'
			WHERE cp.`id_product` = '.(int)$this->id
		);
		return $result;
	}

	/**
	* Webservice setter : set category ids of current product for association
	*
	* @param $category_ids category ids
	*/
	public function setWsCategories($category_ids)
	{
		$ids = array();
		foreach ($category_ids as $value)
			$ids[] = $value['id'];
		if ($this->deleteCategories())
		{
			if ($ids)
			{
				$sql_values = '';
				$ids = array_map('intval', $ids);
				foreach ($ids as $position => $id)
					$sql_values[] = '('.(int)$id.', '.(int)$this->id.', '.(int)$position.')';
				$result = Db::getInstance()->execute('
					INSERT INTO `'._DB_PREFIX_.'category_product` (`id_category`, `id_product`, `position`)
					VALUES '.implode(',', $sql_values)
				);
				return $result;
			}
		}
		return true;
	}

 	/**
	* Webservice getter : get product accessories ids of current product for association
	*
	* @return array
	*/
	public function getWsAccessories()
	{
		$result = Db::getInstance()->executeS(
			'SELECT p.`id_product` AS id
			FROM `'._DB_PREFIX_.'accessory` a
			LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.id_product = a.id_product_2)
			'.Shop::addSqlAssociation('product', 'p').'
			WHERE a.`id_product_1` = '.(int)$this->id
		);

		return $result;
	}
	
	/**
	* Webservice setter : set product accessories ids of current product for association
	*
	* @param $accessories product ids
	*/
	public function setWsAccessories($accessories)
	{
		foreach ($accessories as $accessory) 
			Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'accessory` (`id_product_1`, `id_product_2`) VALUES ('.(int)$this->id.', '.(int)$accessory['id'].')');
		
		return true;
	}

       /**

	/**
	* Webservice getter : get combination ids of current product for association
	*
	* @return array
	*/
	public function getWsCombinations()
	{
		$result = Db::getInstance()->executeS(
			'SELECT pa.`id_product_attribute` as id
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE `id_product` = '.(int)$this->id
		);

		return $result;
	}

	/**
	* Webservice setter : set combination ids of current product for association
	*
	* @param $combinations combination ids
	*/
	public function setWsCombinations($combinations)
	{
		// No hook exec
		$ids_new = array();
		foreach ($combinations as $combination)
			$ids_new[] = (int)$combination['id'];

		$ids_orig = array();
		$original = Db::getInstance()->executeS(
			'SELECT pa.`id_product_attribute` as id
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			WHERE `id_product` = '.(int)$this->id
		);

		if (is_array($original))
			foreach ($original as $id)
				$ids_orig[] = $id['id'];

		$all_ids = array();
		$all = Db::getInstance()->executeS('SELECT pa.`id_product_attribute` as id FROM `'._DB_PREFIX_.'product_attribute` pa '.Shop::addSqlAssociation('product_attribute', 'pa'));
		if (is_array($all))
			foreach ($all as $id)
				$all_ids[] = $id['id'];

		$to_add = array();
		foreach ($ids_new as $id)
			if (!in_array($id, $ids_orig))
				$to_add[] = $id;

		$to_delete = array();
		foreach ($ids_orig as $id)
			if (!in_array($id, $ids_new))
				$to_delete[] = $id;

		// Delete rows
		if (count($to_delete) > 0)
			foreach ($to_delete as $id)
			{
				$combination = new Combination($id);
				$combination->delete();
			}

		foreach ($to_add as $id)
		{
			// Update id_product if exists else create
			if (in_array($id, $all_ids))
				Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'product_attribute` SET id_product = '.(int)$this->id.' WHERE id_product_attribute='.$id);
			else
				Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'product_attribute` (`id_product`) VALUES ('.$this->id.')');
		}
		return true;
	}

	/**
	* Webservice getter : get product option ids of current product for association
	*
	* @return array
	*/
	public function getWsProductOptionValues()
	{
		$result = Db::getInstance()->executeS('SELECT DISTINCT pac.id_attribute as id
			FROM `'._DB_PREFIX_.'product_attribute` pa
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON (pac.id_product_attribute = pa.id_product_attribute)
			WHERE pa.id_product = '.(int)$this->id);
		return $result;
	}

	/**
	* Webservice getter : get virtual field position in category
	*
	* @return int
	*/
	public function getWsPositionInCategory()
	{
		$result = Db::getInstance()->executeS('SELECT position
			FROM `'._DB_PREFIX_.'category_product`
			WHERE id_category = '.(int)$this->id_category_default.'
			AND id_product = '.(int)$this->id);
		if (count($result) > 0)
			return $result[0]['position'];
		return '';
	}

	/**
	* Webservice getter : get virtual field id_default_image in category
	*
	* @return int
	*/
	public function getCoverWs()
	{
		$result = $this->getCover($this->id);
		return $result['id_image'];
	}

	/**
	* Webservice setter : set virtual field id_default_image in category
	*
	* @return bool
	*/
	public function setCoverWs($id_image)
	{
		Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'image_shop` image_shop, `'._DB_PREFIX_.'image` i
			SET image_shop.`cover` = 0 
			WHERE i.`id_product` = '.(int)$this->id.' AND i.id_image = image_shop.id_image
			AND image_shop.id_shop='.(int)Context::getContext()->shop->id);
		Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'image_shop`
			SET `cover` = 1 WHERE `id_image` = '.(int)$id_image);

		return true;
	}

	/**
	* Webservice getter : get image ids of current product for association
	*
	* @return array
	*/
	public function	getWsImages()
	{
		return Db::getInstance()->executeS('
		SELECT i.`id_image` as id
		FROM `'._DB_PREFIX_.'image` i
		'.Shop::addSqlAssociation('image', 'i').'
		WHERE i.`id_product` = '.(int)$this->id.'
		ORDER BY i.`position`');
	}

	public function getWsStockAvailables()
	{
		return Db::getInstance()->executeS('SELECT `id_stock_available` id, `id_product_attribute`
														FROM `'._DB_PREFIX_.'stock_available`
														WHERE `id_product`='.($this->id).StockAvailable::addSqlShopRestriction());
	}
	
	public function getWsTags()
	{
		return Db::getInstance()->executeS('
		SELECT `id_tag` as id
		FROM `'._DB_PREFIX_.'product_tag`
		WHERE `id_product` = '.(int)$this->id);
	}


	public function getWsManufacturerName()
	{
		return Manufacturer::getNameById((int)$this->id_manufacturer);
	}

	public static function resetEcoTax()
	{
		return ObjectModel::updateMultishopTable('product', array(
			'ecotax' => 0,
		), '');
	}

	/**
	 * Set Group reduction if needed
	 */
	public function setGroupReduction()
	{
		return GroupReduction::setProductReduction($this->id, null, $this->id_category_default);
	}

	/**
	 * Checks if reference exists
	 * @return boolean
	 */
	public function existsRefInDatabase($reference)
	{
		$row = Db::getInstance()->getRow('
		SELECT `reference`
		FROM `'._DB_PREFIX_.'product` p
		WHERE p.reference = "'.pSQL($reference).'"');

		return isset($row['reference']);
	}

	/**
	 * Get all product attributes ids
	 *
	 * @since 1.5.0
	 * @param int $id_product the id of the product
	 * @return array product attribute id list
	 */
	public static function getProductAttributesIds($id_product, $shop_only = false)
	{
		return Db::getInstance()->executeS('
		SELECT pa.id_product_attribute
		FROM `'._DB_PREFIX_.'product_attribute` pa'.
		($shop_only ? Shop::addSqlAssociation('product_attribute', 'pa') : '').'
		WHERE pa.`id_product` = '.(int)$id_product);
	}

	/**
	 * Get label by lang and value by lang too
	 * @todo Remove existing module condition
	 * @param int $id_product
	 * @param int $product_attribute_id
	 * @return array
	 */
	public static function getAttributesParams($id_product, $id_product_attribute)
	{
		// if blocklayered module is installed we check if user has set custom attribute name
		if (Module::isInstalled('blocklayered'))
		{
			$nb_custom_values = Db::getInstance()->executeS('
			SELECT DISTINCT la.`id_attribute`, la.`url_name` as `name`
			FROM `'._DB_PREFIX_.'attribute` a
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
				ON (a.`id_attribute` = pac.`id_attribute`)
			LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
				ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			LEFT JOIN `'._DB_PREFIX_.'layered_indexable_attribute_lang_value` la
				ON (la.`id_attribute` = a.`id_attribute` AND la.`id_lang` = '.(int)Context::getContext()->language->id.')
			WHERE la.`url_name` IS NOT NULL AND la.`url_name` != \'\'
			AND pa.`id_product` = '.(int)$id_product.'
			AND pac.`id_product_attribute` = '.(int)$id_product_attribute);

			if (!empty($nb_custom_values))
			{
				$tab_id_attribute = array();
				foreach ($nb_custom_values as $attribute)
				{
					$tab_id_attribute[] = $attribute['id_attribute'];

					$group = Db::getInstance()->executeS('
					SELECT g.`id_attribute_group`, g.`url_name` as `group`
					FROM `'._DB_PREFIX_.'layered_indexable_attribute_group_lang_value` g
					LEFT JOIN `'._DB_PREFIX_.'attribute` a
						ON (a.`id_attribute_group` = g.`id_attribute_group`)
					WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
					AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
					AND g.`url_name` IS NOT NULL AND g.`url_name` != \'\'');
					if (empty($group))
					{
						$group = Db::getInstance()->executeS('
						SELECT g.`id_attribute_group`, g.`name` as `group`
						FROM `'._DB_PREFIX_.'attribute_group_lang` g
						LEFT JOIN `'._DB_PREFIX_.'attribute` a
							ON (a.`id_attribute_group` = g.`id_attribute_group`)
						WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
						AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
						AND g.`name` IS NOT NULL');
					}
					$result[] = array_merge($attribute, $group[0]);
				}
				$values_not_custom = Db::getInstance()->executeS('
				SELECT DISTINCT a.`id_attribute_group`, al.`name`, agl.`name` as `group`
				FROM `'._DB_PREFIX_.'attribute` a
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
					ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
					ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
					ON (a.`id_attribute` = pac.`id_attribute`)
				LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
					ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				WHERE pa.`id_product` = '.(int)$id_product.'
				AND pac.id_product_attribute = '.(int)$id_product_attribute.'
				AND a.`id_attribute` NOT IN('.implode(', ', $tab_id_attribute).')');
				$result = array_merge($values_not_custom, $result);
			}
			else
			{
				$result = Db::getInstance()->executeS('
				SELECT al.`name`, agl.`name` as `group`
				FROM `'._DB_PREFIX_.'attribute` a
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
					ON (al.`id_attribute` = a.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
					ON (pac.`id_attribute` = a.`id_attribute`)
				LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
					ON (pa.`id_product_attribute` = pac.`id_product_attribute`)
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
					ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
				WHERE pa.`id_product` = '.(int)$id_product.'
					AND pac.`id_product_attribute` = '.(int)$id_product_attribute.'
					AND agl.`id_lang` = '.(int)Context::getContext()->language->id);
			}
		}
		else
		{
			$result = Db::getInstance()->executeS('
			SELECT al.`name`, agl.`name` as `group`
			FROM `'._DB_PREFIX_.'attribute` a
			LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
				ON (al.`id_attribute` = a.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
				ON (pac.`id_attribute` = a.`id_attribute`)
			LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
				ON (pa.`id_product_attribute` = pac.`id_product_attribute`)
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
				ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
			WHERE pa.`id_product` = '.(int)$id_product.'
				AND pac.`id_product_attribute` = '.(int)$id_product_attribute.'
				AND agl.`id_lang` = '.(int)Context::getContext()->language->id);
		}
		return $result;
	}

	/**
	 * @todo Remove existing module condition
	 * @param int $id_product
	 */
	public static function getAttributesInformationsByProduct($id_product)
	{
		// if blocklayered module is installed we check if user has set custom attribute name
		if (Module::isInstalled('blocklayered'))
		{
			$nb_custom_values = Db::getInstance()->executeS('
			SELECT DISTINCT la.`id_attribute`, la.`url_name` as `attribute`
			FROM `'._DB_PREFIX_.'attribute` a
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
				ON (a.`id_attribute` = pac.`id_attribute`)
			LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
				ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			LEFT JOIN `'._DB_PREFIX_.'layered_indexable_attribute_lang_value` la
				ON (la.`id_attribute` = a.`id_attribute` AND la.`id_lang` = '.(int)Context::getContext()->language->id.')
			WHERE la.`url_name` IS NOT NULL AND la.`url_name` != \'\'
			AND pa.`id_product` = '.(int)$id_product);

			if (!empty($nb_custom_values))
			{
				$tab_id_attribute = array();
				foreach ($nb_custom_values as $attribute)
				{
					$tab_id_attribute[] = $attribute['id_attribute'];

					$group = Db::getInstance()->executeS('
					SELECT g.`id_attribute_group`, g.`url_name` as `group`
					FROM `'._DB_PREFIX_.'layered_indexable_attribute_group_lang_value` g
					LEFT JOIN `'._DB_PREFIX_.'attribute` a
						ON (a.`id_attribute_group` = g.`id_attribute_group`)
					WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
					AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
					AND g.`url_name` IS NOT NULL AND g.`url_name` != \'\'');
					if (empty($group))
					{
						$group = Db::getInstance()->executeS('
						SELECT g.`id_attribute_group`, g.`name` as `group`
						FROM `'._DB_PREFIX_.'attribute_group_lang` g
						LEFT JOIN `'._DB_PREFIX_.'attribute` a
							ON (a.`id_attribute_group` = g.`id_attribute_group`)
						WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
						AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
						AND g.`name` IS NOT NULL');
					}
					$result[] = array_merge($attribute, $group[0]);
				}
				$values_not_custom = Db::getInstance()->executeS('
				SELECT DISTINCT a.`id_attribute`, a.`id_attribute_group`, al.`name` as `attribute`, agl.`name` as `group`
				FROM `'._DB_PREFIX_.'attribute` a
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
					ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
					ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
					ON (a.`id_attribute` = pac.`id_attribute`)
				LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
					ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				'.Shop::addSqlAssociation('attribute', 'pac').'
				WHERE pa.`id_product` = '.(int)$id_product.'
				AND a.`id_attribute` NOT IN('.implode(', ', $tab_id_attribute).')');
				$result = array_merge($values_not_custom, $result);
			}
			else
			{
				$result = Db::getInstance()->executeS('
				SELECT DISTINCT a.`id_attribute`, a.`id_attribute_group`, al.`name` as `attribute`, agl.`name` as `group`
				FROM `'._DB_PREFIX_.'attribute` a
				LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
					ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
					ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
				LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
					ON (a.`id_attribute` = pac.`id_attribute`)
				LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
					ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
				'.Shop::addSqlAssociation('product_attribute', 'pa').'
				'.Shop::addSqlAssociation('attribute', 'pac').'
				WHERE pa.`id_product` = '.(int)$id_product);
			}
		}
		else
		{
			$result = Db::getInstance()->executeS('
			SELECT DISTINCT a.`id_attribute`, a.`id_attribute_group`, al.`name` as `attribute`, agl.`name` as `group`
			FROM `'._DB_PREFIX_.'attribute` a
			LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
				ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
			LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
				ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
				ON (a.`id_attribute` = pac.`id_attribute`)
			LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
				ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
			'.Shop::addSqlAssociation('product_attribute', 'pa').'
			'.Shop::addSqlAssociation('attribute', 'pac').'
			WHERE pa.`id_product` = '.(int)$id_product);
		}
		return $result;
	}

	/**
	 * Get the combination url anchor of the product
	 *
	 * @param integer $id_product_attribute
	 * @return string
	 */
	public function getAnchor($id_product_attribute)
	{
		$attributes = Product::getAttributesParams($this->id, $id_product_attribute);
		$anchor = '#';
		foreach ($attributes as &$a)
		{
			foreach ($a as &$
				$b = str_replace('-', '_', Tools::link_rewrite($);
			$anchor .= '/'.$a['group'].'-'.$a['name'];
		}
		return $anchor;
	}

	/**
	 * Gets the name of a given product, in the given lang
	 *
	 * @since 1.5.0
	 * @param int $id_product
	 * @param int $id_product_attribute Optional
	 * @param int $id_lang Optional
	 * @return string
	 */
	public static function getProductName($id_product, $id_product_attribute = null, $id_lang = null)
	{
		// use the lang in the context if $id_lang is not defined
		if (!$id_lang)
			$id_lang = (int)Context::getContext()->language->id;

		// creates the query object
		$query = new DbQuery();

		// selects different names, if it is a combination
		if ($id_product_attribute)
			$query->select('IFNULL(CONCAT(pl.name, \' : \', GROUP_CONCAT(DISTINCT agl.`name`, \' - \', al.name SEPARATOR \', \')),pl.name) as name');
		else
			$query->select('DISTINCT pl.name as name');

		// adds joins & where clauses for combinations
		if ($id_product_attribute)
		{
			$query->from('product_attribute', 'pa');
			$query->join(Shop::addSqlAssociation('product_attribute', 'pa'));
			$query->innerJoin('product_lang', 'pl', 'pl.id_product = pa.id_product AND pl.id_lang = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl'));
			$query->leftJoin('product_attribute_combination', 'pac', 'pac.id_product_attribute = pa.id_product_attribute');
			$query->leftJoin('attribute', 'atr', 'atr.id_attribute = pac.id_attribute');
			$query->leftJoin('attribute_lang', 'al', 'al.id_attribute = atr.id_attribute AND al.id_lang = '.(int)$id_lang);
			$query->leftJoin('attribute_group_lang', 'agl', 'agl.id_attribute_group = atr.id_attribute_group AND agl.id_lang = '.(int)$id_lang);
			$query->where('pa.id_product = '.(int)$id_product.' AND pa.id_product_attribute = '.(int)$id_product_attribute);
		}
		else // or just adds a 'where' clause for a simple product
		{
			$query->from('product_lang', 'pl');
			$query->where('pl.id_product = '.(int)$id_product);
			$query->where('pl.id_lang = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl'));
		}

		return Db::getInstance()->getValue($query);
	}

	public function addWs($autodate = true, $null_values = false)
	{
		$success = $this->add($autodate, $null_values);
		if ($success && Configuration::get('PS_SEARCH_INDEXATION'))
			Search::indexation(false, $this->id);
		return $success;
	}

	public function updateWs($null_values = false)
	{
		$success = parent::update($null_values);
		if ($success && Configuration::get('PS_SEARCH_INDEXATION'))
			Search::indexation(false, $this->id);
		return $success;
	}

	/**
	 * For a given product, returns its real quantity
	 *
	 * @since 1.5.0
	 * @param int $id_product
	 * @param int $id_product_attribute
	 * @param int $id_warehouse
	 * @param int $id_shop
	 * @return int real_quantity
	 */
	public static function getRealQuantity($id_product, $id_product_attribute = 0, $id_warehouse = 0, $id_shop = null)
	{
		static $manager = null;

		if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && is_null($manager))
			$manager = StockManagerFactory::getManager();

		if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && Product::usesAdvancedStockManagement($id_product) &&
			StockAvailable::dependsOnStock($id_product, $id_shop))
			return $manager->getProductRealQuantities($id_product, $id_product_attribute, $id_warehouse, true);
		else
			return StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute, $id_shop);
	}

	/**
	 * For a given product, tells if it uses the advanced stock management
	 *
	 * @since 1.5.0
	 * @param int $id_product
	 * @return bool
	 */
	public static function usesAdvancedStockManagement($id_product)
	{
		$query = new DbQuery;
		$query->select('product_shop.advanced_stock_management');
		$query->from('product', 'p');
		$query->join(Shop::addSqlAssociation('product', 'p'));
		$query->where('p.id_product = '.(int)$id_product);

		return (bool)Db::getInstance()->getValue($query);
	}

	/**
	 * This method allows to flush price cache
	 * @static
	 * @since 1.5.0
	 */
	public static function flushPriceCache()
	{
		self::$_prices = array();
		self::$_pricesLevel2 = array();
	}

	/**
	 * Get list of parent categories
	 *
	 * @since 1.5.0
	 * @param int $id_lang
	 * @return array
	 */
	public function getParentCategories($id_lang = null)
	{
		if (!$id_lang)
			$id_lang = Context::getContext()->language->id;

		$interval = Category::getInterval($this->id_category_default);
		$sql = new DbQuery();
		$sql->from('category', 'c');
		$sql->leftJoin('category_lang', 'cl', 'c.id_category = cl.id_category AND id_lang = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl'));
		$sql->where('c.nleft <= '.(int)$interval['nleft'].' AND c.nright >= '.(int)$interval['nright']);
		$sql->orderBy('c.nleft');

		return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
	}

	/**
	 * Fill the variables used for stock management
	 */
	public function loadStockData()
	{
		if (Validate::isLoadedObject($this))
		{
			// By default, the product quantity correspond to the available quantity to sell in the current shop
			$this->quantity = StockAvailable::getQuantityAvailableByProduct($this->id, 0);
			$this->out_of_stock = StockAvailable::outOfStock($this->id);
			$this->depends_on_stock = StockAvailable::dependsOnStock($this->id);
			if (Context::getContext()->shop->getContext() == Shop::CONTEXT_GROUP && Context::getContext()->shop->getContextShopGroup()->share_stock == 1)
				$this->advanced_stock_management = $this->useAdvancedStockManagement();
		}
	}
	
	public function useAdvancedStockManagement()
	{
		return Db::getInstance()->getValue('
					SELECT `advanced_stock_management`
					FROM '._DB_PREFIX_.'product_shop
					WHERE id_product='.(int)$this->id.Shop::addSqlRestriction()
				);
	}
	
	public function setAdvancedStockManagement($value)
	{
		$this->advanced_stock_management = (int)$value;
		if (Context::getContext()->shop->getContext() == Shop::CONTEXT_GROUP && Context::getContext()->shop->getContextShopGroup()->share_stock == 1)
			Db::getInstance()->execute('
				UPDATE `'._DB_PREFIX_.'product_shop`
				SET `advanced_stock_management`='.(int)$value.'
				WHERE id_product='.(int)$this->id.Shop::addSqlRestriction()
			);
		else
			$this->save();
	}

	/**
	 * get the default category according to the shop
	 */
	public function getDefaultCategory()
	{
		$default_category = Db::getInstance()->getValue('
			SELECT product_shop.`id_category_default`
			FROM `'._DB_PREFIX_.'product` p
			'.Shop::addSqlAssociation('product', 'p').'
			WHERE p.`id_product` = '.(int)$this->id);

		if (!$default_category)
			return array('id_category_default' => Context::getContext()->shop->id_category);
		else
			return $default_category;

	}

	public static function getShopsByProduct($id_product)
	{
		return Db::getInstance()->executeS('
			SELECT `id_shop`
			FROM `'._DB_PREFIX_.'product_shop`
			WHERE `id_product` = '.(int)$id_product);
	}

	/**
	 * Remove all downloadable files for product and its attributes
	 *
	 * @return bool
	 */
	public function deleteDownload()
	{
		$result = true;
		$collection_download = new Collection('ProductDownload');
		$collection_download->where('id_product', '=', $this->id);
		foreach ($collection_download as $product_download)
			$result &= $product_download->delete(true);
		return $result;
	}

	/**
	 * @deprecated 1.5.0.10
	 * @see Product::getAttributeCombinations()
	 * @param int $id_lang
	 */
	public function getAttributeCombinaisons($id_lang)
	{
		Tools::displayAsDeprecated('Use Product::getAttributeCombinations($id_lang)');
		return $this->getAttributeCombinations($id_lang);
	}

	/**
	 * @deprecated 1.5.0.10
	 * @see Product::deleteAttributeCombination()
	 * @param int $id_product_attribute
	 */
	public function deleteAttributeCombinaison($id_product_attribute)
	{
		Tools::displayAsDeprecated('Use Product::deleteAttributeCombination($id_product_attribute)');
		return $this->deleteAttributeCombination($id_product_attribute);
	}

	/**
	 * Get the product type (simple, virtual, pack)
	 * @since in 1.5.0
	 *
	 * @return int
	 */
	public function getType()
	{
		if (!$this->id)
			return Product::PTYPE_SIMPLE;
		if (Pack::isPack($this->id))
			return Product::PTYPE_PACK;
		if ($this->is_virtual)
			return Product::PTYPE_VIRTUAL;

		return Product::PTYPE_SIMPLE;
	}

	public function hasAttributesInOtherShops()
	{
		return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
			SELECT pa.id_product_attribute
			FROM `'._DB_PREFIX_.'product_attribute` pa
			LEFT JOIN `'._DB_PREFIX_.'product_attribute_shop` pas ON (pa.`id_product_attribute` = pas.`id_product_attribute`)
			WHERE pa.`id_product` = '.(int)$this->id
		);
	}
	
	public static function getIdTaxRulesGroupMostUsed()
	{
		return Db::getInstance()->getValue('
					SELECT id_tax_rules_group
					FROM (
						SELECT COUNT(*) n, product_shop.id_tax_rules_group
						FROM '._DB_PREFIX_.'product p
						'.Shop::addSqlAssociation('product', 'p').'
						JOIN '._DB_PREFIX_.'tax_rules_group trg ON (product_shop.id_tax_rules_group = trg.id_tax_rules_group)
						WHERE trg.active = 1
						GROUP BY product_shop.id_tax_rules_group
						ORDER BY n DESC
						LIMIT 1
					) most_used'
				);
	}

	/**
	 * For a given ean13 reference, returns the corresponding id
	 *
	 * @param string $ean13
	 * @return int id
	 */
	public static function getIdByEan13($ean13)
	{
		if (empty($ean13))
			return 0;
		
		if(!Validate::isEan13($ean13))
			return 0;

		$query = new DbQuery();
		$query->select('p.id_product');
		$query->from('product', 'p');
		$query->where('p.ean13 = \''.pSQL($ean13).'\'');

		return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($query);
	}
	
	public function getWsType()
	{
		$type_information = array(
			Product::PTYPE_SIMPLE => 'simple',
			Product::PTYPE_PACK => 'pack',
			Product::PTYPE_VIRTUAL => 'virtual',
		);
		return $type_information[$this->getType()];
	}

	public function getWsProductBundle()
	{
		return Db::getInstance()->executeS('SELECT id_product_item as id, quantity FROM '._DB_PREFIX_.'pack where id_product_pack = '.(int)$this->id);
	}
}

Link to comment
Share on other sites

Hello

 

Do you have declaration differences between the method of ObjectModel->validateField() and Product->validateField()  ?

In class product you have

 

public function validateField($field, $value, $id_lang = null)

 

does it look exactly the same in classes/ObjectModel.php ?

 

Regards

Link to comment
Share on other sites

Hello

 

Do you have declaration differences between the method of ObjectModel->validateField() and Product->validateField()  ?

In class product you have

public function validateField($field, $value, $id_lang = null)

does it look exactly the same in classes/ObjectModel.php ?

 

Regards

I didnt do alteration in classes.

I did some customisation in tpl and css.

 

Here is the code for classes/ObjectModel.php

<?php
/*
* 2007-2013 PrestaShop
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to [email protected] so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to http://www.prestashop.com for more information.
*
*  @author PrestaShop SA <[email protected]>
*  @copyright  2007-2013 PrestaShop SA
*  @license    http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
*  International Registered Trademark & Property of PrestaShop SA
*/

abstract class ObjectModelCore
{
	/**
	 * List of field types
	 */
	const TYPE_INT = 1;
	const TYPE_BOOL = 2;
	const TYPE_STRING = 3;
	const TYPE_FLOAT = 4;
	const TYPE_DATE = 5;
	const TYPE_HTML = 6;
	const TYPE_NOTHING = 7;

	/**
	 * List of data to format
	 */
	const FORMAT_COMMON = 1;
	const FORMAT_LANG = 2;
	const FORMAT_SHOP = 3;

	/**
	 * List of association types
	 */
	const HAS_ONE = 1;
	const HAS_MANY = 2;

	/** @var integer Object id */
	public $id;

	/** @var integer lang id */
	protected $id_lang = null;

	protected $id_shop = null;

	public $id_shop_list = null;

	protected $get_shop_from_context = true;

	protected static $fieldsRequiredDatabase = null;

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['table'] property instead
	 */
	protected $table;

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['primary'] property instead
	 */
	protected $identifier;

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
	 */
 	protected $fieldsRequired = array();

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
	 */
 	protected $fieldsSize = array();

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
	 */
 	protected $fieldsValidate = array();

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
	 */
 	protected $fieldsRequiredLang = array();

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
	 */
 	protected $fieldsSizeLang = array();

	/**
	 * @deprecated 1.5.0 This property shouldn't be overloaded anymore in class, use static $definition['fields'] property instead
	 */
 	protected $fieldsValidateLang = array();

	/**
	 * @deprecated 1.5.0
	 */
 	protected $tables = array();

 	/** @var array tables */
 	protected $webserviceParameters = array();

	/** @var  string path to image directory. Used for image deletion. */
	protected $image_dir = null;

	/** @var string file type of image files. Used for image deletion. */
	protected $image_format = 'jpg';

	/**
	 * @var array Contain object definition
	 * @since 1.5.0
	 */
	public static $definition = array();

	/**
	 * @var array Contain current object definition
	 */
	protected $def;

	/**
	 * @var array List of specific fields to update (all fields if null)
	 */
	protected $update_fields = null;
	
	/**
	 * @var Db An instance of the db in order to avoid calling Db::getInstance() thousands of time
	 */
	protected static $db = false;

	/**
	 * Returns object validation rules (fields validity)
	 *
	 * @param string $class Child class name for static use (optional)
	 * @return array Validation rules (fields validity)
	 */
	public static function getValidationRules($class = __CLASS__)
	{
		$object = new $class();
		return array(
			'required' => $object->fieldsRequired,
			'size' => $object->fieldsSize,
			'validate' => $object->fieldsValidate,
			'requiredLang' => $object->fieldsRequiredLang,
			'sizeLang' => $object->fieldsSizeLang,
			'validateLang' => $object->fieldsValidateLang,
		);
	}

	/**
	 * Build object
	 *
	 * @param int $id Existing object id in order to load object (optional)
	 * @param int $id_lang Required if object is multilingual (optional)
	 * @param int $id_shop ID shop for objects with multishop on langs
	 */
	public function __construct($id = null, $id_lang = null, $id_shop = null)
	{
		if (!ObjectModel::$db)
			ObjectModel::$db = Db::getInstance();

		$this->def = ObjectModel::getDefinition($this);
		$this->setDefinitionRetrocompatibility();

		if ($id_lang !== null)
			$this->id_lang = (Language::getLanguage($id_lang) !== false) ? $id_lang : Configuration::get('PS_LANG_DEFAULT');

		if ($id_shop && $this->isMultishop())
		{
			$this->id_shop = (int)$id_shop;
			$this->get_shop_from_context = false;
		}

		if ($this->isMultishop() && !$this->id_shop)
			$this->id_shop = Context::getContext()->shop->id;

	 	if (!Validate::isTableOrIdentifier($this->def['primary']) || !Validate::isTableOrIdentifier($this->def['table']))
			throw new PrestaShopException('Identifier or table format not valid for class '.get_class($this));

		if ($id)
		{
			// Load object from database if object id is present
			$cache_id = 'objectmodel_'.$this->def['classname'].'_'.(int)$id.'_'.(int)$this->id_shop.'_'.(int)$id_lang;
			if (!Cache::isStored($cache_id))
			{
				$sql = new DbQuery();
				$sql->from($this->def['table'], 'a');
				$sql->where('a.'.$this->def['primary'].' = '.(int)$id);

				// Get lang informations
				if ($id_lang)
				{
					$sql->leftJoin($this->def['table'].'_lang', 'b', 'a.'.$this->def['primary'].' = b.'.$this->def['primary'].' AND b.id_lang = '.(int)$id_lang);
					if ($this->id_shop && !empty($this->def['multilang_shop']))
						$sql->where('b.id_shop = '.$this->id_shop);
				}

				// Get shop informations
				if (Shop::isTableAssociated($this->def['table']))
					$sql->leftJoin($this->def['table'].'_shop', 'c', 'a.'.$this->def['primary'].' = c.'.$this->def['primary'].' AND c.id_shop = '.(int)$this->id_shop);
				if ($object_datas = ObjectModel::$db->getRow($sql))
				{
					if (!$id_lang && isset($this->def['multilang']) && $this->def['multilang'])
					{
						$sql = 'SELECT * FROM `'.pSQL(_DB_PREFIX_.$this->def['table']).'_lang`
								WHERE `'.$this->def['primary'].'` = '.(int)$id
								.(($this->id_shop && $this->isLangMultishop()) ? ' AND `id_shop` = '.$this->id_shop : '');
						if ($object_datas_lang = ObjectModel::$db->executeS($sql))
							foreach ($object_datas_lang as $row)
								foreach ($row as $key => $value)
								{
									if (array_key_exists($key, $this) && $key != $this->def['primary'])
									{
										if (!isset($object_datas[$key]) || !is_array($object_datas[$key]))
											$object_datas[$key] = array();
										$object_datas[$key][$row['id_lang']] = $value;
									}
								}
					}
					Cache::store($cache_id, $object_datas);
				}
			}
			else
				$object_datas = Cache::retrieve($cache_id);

			if ($object_datas)
			{
				$this->id = (int)$id;
				foreach ($object_datas as $key => $value)
					if (array_key_exists($key, $this))
						$this->{$key} = $value;	
			}
		}
	}

	/**
	 * Prepare fields for ObjectModel class (add, update)
	 * All fields are verified (pSQL, intval...)
	 *
	 * @return array All object fields
	 */
	public function getFields()
	{
		$this->validateFields();
		$fields = $this->formatFields(self::FORMAT_COMMON);

		// For retro compatibility
		if (Shop::isTableAssociated($this->def['table']))
			$fields = array_merge($fields, $this->getFieldsShop());

		// Ensure that we get something to insert
		if (!$fields && isset($this->id) && Validate::isUnsignedId($this->id))
			$fields[$this->def['primary']] = $this->id;
		return $fields;
	}

	/**
	 * Prepare fields for multishop
	 * Fields are not validated here, we considere they are already validated in getFields() method, this
	 * not the best solution but this is the only one possible for retro compatibility.
	 *
	 * @since 1.5.0
	 * @return array All object fields
	 */
	public function getFieldsShop()
	{
		$fields = $this->formatFields(self::FORMAT_SHOP);
		if (!$fields && isset($this->id) && Validate::isUnsignedId($this->id))
			$fields[$this->def['primary']] = $this->id;
		return $fields;
	}

	/**
	 * Prepare multilang fields
	 *
	 * @since 1.5.0
	 * @return array
	 */
	public function getFieldsLang()
	{
		// Retrocompatibility
		if (method_exists($this, 'getTranslationsFieldsChild'))
			return $this->getTranslationsFieldsChild();

		$this->validateFieldsLang();
		$is_lang_multishop = $this->isLangMultishop();

		$fields = array();
		if ($this->id_lang === null)
			foreach (Language::getLanguages(false) as $language)
			{
				$fields[$language['id_lang']] = $this->formatFields(self::FORMAT_LANG, $language['id_lang']);
				$fields[$language['id_lang']]['id_lang'] = $language['id_lang'];
				if ($this->id_shop && $is_lang_multishop)
					$fields[$language['id_lang']]['id_shop'] = (int)$this->id_shop;
			}
		else
		{
			$fields = array($this->id_lang => $this->formatFields(self::FORMAT_LANG, $this->id_lang));
			$fields[$this->id_lang]['id_lang'] = $this->id_lang;
			if ($this->id_shop && $is_lang_multishop)
				$fields[$this->id_lang]['id_shop'] = (int)$this->id_shop;
		}

		return $fields;
	}

	/**
	 * @since 1.5.0
	 * @param int $type FORMAT_COMMON or FORMAT_LANG or FORMAT_SHOP
	 * @param int $id_lang If this parameter is given, only take lang fields
	 * @return array
	 */
	protected function formatFields($type, $id_lang = null)
	{
		$fields = array();

		// Set primary key in fields
		if (isset($this->id))
			$fields[$this->def['primary']] = $this->id;

		foreach ($this->def['fields'] as $field => $data)
		{
			// Only get fields we need for the type
			// E.g. if only lang fields are filtered, ignore fields without lang => true
			if (($type == self::FORMAT_LANG && empty($data['lang']))
				|| ($type == self::FORMAT_SHOP && empty($data['shop']))
				|| ($type == self::FORMAT_COMMON && (!empty($data['shop']) || !empty($data['lang']))))
				continue;

			if (is_array($this->update_fields))
				if ((!empty($data['lang']) || !empty($data['shop'])) && (empty($this->update_fields[$field]) || ($type == self::FORMAT_LANG && empty($this->update_fields[$field][$id_lang]))))
					continue;

			// Get field value, if value is multilang and field is empty, use value from default lang
			$value = $this->$field;
			if ($type == self::FORMAT_LANG && $id_lang && is_array($value))
			{
				if (!empty($value[$id_lang]))
					$value = $value[$id_lang];
				else if (!empty($data['required']))
					$value = $value[Configuration::get('PS_LANG_DEFAULT')];
				else
					$value = '';
			}

			// Format field value
			$fields[$field] = ObjectModel::formatValue($value, $data['type']);
		}

		return $fields;
	}

	/**
	 * Format a data
	 *
	 * @param mixed $value
	 * @param int $type
	 */
	public static function formatValue($value, $type, $with_quotes = false)
	{
		switch ($type)
		{
			case self::TYPE_INT :
				return (int)$value;

			case self::TYPE_BOOL :
				return (int)$value;

			case self::TYPE_FLOAT :
				return (float)str_replace(',', '.', $value);

			case self::TYPE_DATE :
				if (!$value)
					return '0000-00-00';

				if ($with_quotes)
					return '\''.pSQL($value).'\'';
				return pSQL($value);

			case self::TYPE_HTML :
				if ($with_quotes)
					return '\''.pSQL($value, true).'\'';
				return pSQL($value, true);

			case self::TYPE_NOTHING :
				return $value;

			case self::TYPE_STRING :
			default :
				if ($with_quotes)
					return '\''.pSQL($value).'\'';
				return pSQL($value);
		}
	}

	/**
	 * Save current object to database (add or update)
	 *
	 * @param bool $null_values
	 * @param bool $autodate
	 * @return boolean Insertion result
	 */
	public function save($null_values = false, $autodate = true)
	{
		return (int)$this->id > 0 ? $this->update($null_values) : $this->add($autodate, $null_values);
	}

	/**
	 * Add current object to database
	 *
	 * @param bool $null_values
	 * @param bool $autodate
	 * @return boolean Insertion result
	 */
	public function add($autodate = true, $null_values = false)
	{
		if (!ObjectModel::$db)
			ObjectModel::$db = Db::getInstance();

		// @hook actionObject*AddBefore
		Hook::exec('actionObjectAddBefore', array('object' => $this));
		Hook::exec('actionObject'.get_class($this).'AddBefore', array('object' => $this));

		// Automatically fill dates
		if ($autodate && property_exists($this, 'date_add'))
			$this->date_add = date('Y-m-d H:i:s');
		if ($autodate && property_exists($this, 'date_upd'))
			$this->date_upd = date('Y-m-d H:i:s');

			
		if (Shop::isTableAssociated($this->def['table']))
		{
			$id_shop_list = Shop::getContextListShopID();
			if (count($this->id_shop_list) > 0)
				$id_shop_list = $this->id_shop_list;
		}
		
		// Database insertion
		if (isset($this->id) && !Tools::getValue('forceIDs'))
			unset($this->id);
		if (Shop::checkIdShopDefault($this->def['table']))
			$this->id_shop_default = min($id_shop_list);
		if (!$result = ObjectModel::$db->insert($this->def['table'], $this->getFields(), $null_values))
			return false;

		// Get object id in database
		$this->id = ObjectModel::$db->Insert_ID();

		// Database insertion for multishop fields related to the object
		if (Shop::isTableAssociated($this->def['table']))
		{
			$fields = $this->getFieldsShop();
			$fields[$this->def['primary']] = (int)$this->id;

			foreach ($id_shop_list as $id_shop)
			{
				$fields['id_shop'] = (int)$id_shop;
				$result &= ObjectModel::$db->insert($this->def['table'].'_shop', $fields, $null_values);
			}
		}

		if (!$result)
			return false;

		// Database insertion for multilingual fields related to the object
		if (!empty($this->def['multilang']))
		{
			$fields = $this->getFieldsLang();
			if ($fields && is_array($fields))
			{
				$shops = Shop::getCompleteListOfShopsID();
				$asso = Shop::getAssoTable($this->def['table'].'_lang');
				foreach ($fields as $field)
				{
					foreach (array_keys($field) as $key)
						if (!Validate::isTableOrIdentifier($key))
							throw new PrestaShopException('key '.$key.' is not table or identifier, ');
					$field[$this->def['primary']] = (int)$this->id;

					if ($asso !== false && $asso['type'] == 'fk_shop')
					{
						foreach ($shops as $id_shop)
						{
							$field['id_shop'] = (int)$id_shop;
							$result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field);
						}
					}
					else
						$result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field);
				}
			}
		}

		// @hook actionObject*AddAfter
		Hook::exec('actionObjectAddAfter', array('object' => $this));
		Hook::exec('actionObject'.get_class($this).'AddAfter', array('object' => $this));

		return $result;
	}
	
	/**
	 * Duplicate current object to database
	 *
	 * @return new object
	 */
	public function duplicateObject()
	{
		$definition = ObjectModel::getDefinition($this);

		$res = Db::getInstance()->getRow('
					SELECT * 
					FROM `'._DB_PREFIX_.bqSQL($definition['table']).'`
					WHERE `'.bqSQL($definition['primary']).'` = '.(int)$this->id
				);
		if (!$res)
			return false;
		unset($res[$definition['primary']]);
		foreach ($res as $field => &$value)
			if (isset($definition['fields'][$field]))
				$value = ObjectModel::formatValue($value, $definition['fields'][$field]['type']);

		if (!Db::getInstance()->insert($definition['table'], $res))
			return false;
		
		$object_id = Db::getInstance()->Insert_ID();

		if (isset($definition['multilang']) && $definition['multilang'])
		{
			$result = Db::getInstance()->executeS('
			SELECT * 
			FROM `'._DB_PREFIX_.bqSQL($definition['table']).'_lang`
			WHERE `'.bqSQL($definition['primary']).'` = '.(int)$this->id);
			if (!$result)
				return false;
	
			foreach ($result as &$row)
				foreach ($row as $field => &$value)
					if (isset($definition['fields'][$field]))
						$value = ObjectModel::formatValue($value, $definition['fields'][$field]['type']);
			
			// Keep $row2, you cannot use $row because there is an unexplicated conflict with the previous usage of this variable
			foreach ($result as $row2)
			{
				$row2[$definition['primary']] = (int)$object_id;
				if (!Db::getInstance()->insert($definition['table'].'_lang', $row2))
					return false;
			}
		}
	
		$object_duplicated = new $definition['classname']((int)$object_id);
		$object_duplicated->duplicateShops((int)$this->id);
		
		return $object_duplicated;
	}

	/**
	 * Update current object to database
	 *
	 * @param bool $null_values
	 * @return boolean Update result
	 */
	public function update($null_values = false)
	{
		if (!ObjectModel::$db)
			ObjectModel::$db = Db::getInstance();

		// @hook actionObject*UpdateBefore
		Hook::exec('actionObjectUpdateBefore', array('object' => $this));
		Hook::exec('actionObject'.get_class($this).'UpdateBefore', array('object' => $this));

		$this->clearCache();

		// Automatically fill dates
		if (array_key_exists('date_upd', $this))
			$this->date_upd = date('Y-m-d H:i:s');
			
		$id_shop_list = Shop::getContextListShopID();
		if (count($this->id_shop_list) > 0)
			$id_shop_list = $this->id_shop_list;

		if (Shop::checkIdShopDefault($this->def['table']) && !$this->id_shop_default)
			$this->id_shop_default = min($id_shop_list);
		// Database update
		if (!$result = ObjectModel::$db->update($this->def['table'], $this->getFields(), '`'.pSQL($this->def['primary']).'` = '.(int)$this->id, 0, $null_values))
			return false;

		// Database insertion for multishop fields related to the object
		if (Shop::isTableAssociated($this->def['table']))
		{
			$fields = $this->getFieldsShop();
			$fields[$this->def['primary']] = (int)$this->id;
			if (is_array($this->update_fields))
			{
				$update_fields = $this->update_fields;
				$this->update_fields = null;
				$all_fields = $this->getFieldsShop();
				$all_fields[$this->def['primary']] = (int)$this->id;
				$this->update_fields = $update_fields;
			}
			else
				$all_fields = $fields;

			foreach ($id_shop_list as $id_shop)
			{
				$fields['id_shop'] = (int)$id_shop;
				$all_fields['id_shop'] = (int)$id_shop;
				$where = $this->def['primary'].' = '.(int)$this->id.' AND id_shop = '.(int)$id_shop;

				// A little explanation of what we do here : we want to create multishop entry when update is called, but
				// only if we are in a shop context (if we are in all context, we just want to update entries that alread exists)
				$shop_exists = ObjectModel::$db->getValue('SELECT '.$this->def['primary'].' FROM '._DB_PREFIX_.$this->def['table'].'_shop WHERE '.$where);
				if ($shop_exists)
					$result &= ObjectModel::$db->update($this->def['table'].'_shop', $fields, $where, 0, $null_values);
				elseif (Shop::getContext() == Shop::CONTEXT_SHOP)
					$result &= ObjectModel::$db->insert($this->def['table'].'_shop', $all_fields, $null_values);
			}
		}

		// Database update for multilingual fields related to the object
		if (isset($this->def['multilang']) && $this->def['multilang'])
		{
			$fields = $this->getFieldsLang();
			if (is_array($fields))
			{
				foreach ($fields as $field)
				{
					foreach (array_keys($field) as $key)
						if (!Validate::isTableOrIdentifier($key))
							throw new PrestaShopException('key '.$key.' is not a valid table or identifier');

					// If this table is linked to multishop system, update / insert for all shops from context
					if ($this->isLangMultishop())
					{
						$id_shop_list = Shop::getContextListShopID();
						if (count($this->id_shop_list) > 0)
							$id_shop_list = $this->id_shop_list;
						foreach ($id_shop_list as $id_shop)
						{
							$field['id_shop'] = (int)$id_shop;
							$where = pSQL($this->def['primary']).' = '.(int)$this->id
										.' AND id_lang = '.(int)$field['id_lang']
										.' AND id_shop = '.(int)$id_shop;

							if (ObjectModel::$db->getValue('SELECT COUNT(*) FROM '.pSQL(_DB_PREFIX_.$this->def['table']).'_lang WHERE '.$where))
								$result &= ObjectModel::$db->update($this->def['table'].'_lang', $field, $where);
							else
								$result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field);
						}
					}
					// If this table is not linked to multishop system ...
					else
					{
						$where = pSQL($this->def['primary']).' = '.(int)$this->id
									.' AND id_lang = '.(int)$field['id_lang'];
						if (Db::getInstance()->getValue('SELECT COUNT(*) FROM '.pSQL(_DB_PREFIX_.$this->def['table']).'_lang WHERE '.$where))
							$result &= ObjectModel::$db->update($this->def['table'].'_lang', $field, $where);
						else
							$result &= ObjectModel::$db->insert($this->def['table'].'_lang', $field, $null_values);
					}
				}
			}
		}

		// @hook actionObject*UpdateAfter
		Hook::exec('actionObjectUpdateAfter', array('object' => $this));
		Hook::exec('actionObject'.get_class($this).'UpdateAfter', array('object' => $this));

		return $result;
	}

	/**
	 * Delete current object from database
	 *
	 * @return boolean Deletion result
	 */
	public function delete()
	{
		if (!ObjectModel::$db)
			ObjectModel::$db = Db::getInstance();

		// @hook actionObject*DeleteBefore
		Hook::exec('actionObjectDeleteBefore', array('object' => $this));
		Hook::exec('actionObject'.get_class($this).'DeleteBefore', array('object' => $this));

		$this->clearCache();
		$result = true;
		// Remove association to multishop table
		if (Shop::isTableAssociated($this->def['table']))
		{
			$id_shop_list = Shop::getContextListShopID();
			if (count($this->id_shop_list))
				$id_shop_list = $this->id_shop_list;

			$result &= ObjectModel::$db->delete($this->def['table'].'_shop', '`'.$this->def['primary'].'`='.(int)$this->id.' AND id_shop IN ('.implode(', ', $id_shop_list).')');
		}

		// Database deletion
		$has_multishop_entries = $this->hasMultishopEntries();
		if ($result && !$has_multishop_entries)
			$result &= ObjectModel::$db->delete($this->def['table'], '`'.pSQL($this->def['primary']).'` = '.(int)$this->id);

		if (!$result)
			return false;

		// Database deletion for multilingual fields related to the object
		if (!empty($this->def['multilang']) && !$has_multishop_entries)
			$result &= ObjectModel::$db->delete($this->def['table'].'_lang', '`'.pSQL($this->def['primary']).'` = '.(int)$this->id);

		// @hook actionObject*DeleteAfter
		Hook::exec('actionObjectDeleteAfter', array('object' => $this));
		Hook::exec('actionObject'.get_class($this).'DeleteAfter', array('object' => $this));

		return $result;
	}

	/**
	 * Delete several objects from database
	 *
	 * @param array $selection
	 * @return bool Deletion result
	 */
	public function deleteSelection($selection)
	{
		$result = true;
		foreach ($selection as $id)
		{
			$this->id = (int)$id;
			$result = $result && $this->delete();
		}
		return $result;
	}

	/**
	 * Toggle object status in database
	 *
	 * @return boolean Update result
	 */
	public function toggleStatus()
	{
	 	// Object must have a variable called 'active'
	 	if (!array_key_exists('active', $this))
			throw new PrestaShopException('property "active" is missing in object '.get_class($this));

		// Update only active field
		$this->setFieldsToUpdate(array('active' => true));

	 	// Update active status on object
	 	$this->active = !(int)$this->active;

		// Change status to active/inactive
		return $this->update(false);
	}

	/**
	 * @deprecated 1.5.0 (use getFieldsLang())
	 */
	protected function getTranslationsFields($fields_array)
	{
		$fields = array();

		if ($this->id_lang == null)
			foreach (Language::getLanguages(false) as $language)
				$this->makeTranslationFields($fields, $fields_array, $language['id_lang']);
		else
			$this->makeTranslationFields($fields, $fields_array, $this->id_lang);

		return $fields;
	}

	/**
	 * @deprecated 1.5.0
	 */
	protected function makeTranslationFields(&$fields, &$fields_array, $id_language)
	{
		$fields[$id_language]['id_lang'] = $id_language;
		$fields[$id_language][$this->def['primary']] = (int)$this->id;
		if ($this->id_shop && $this->isLangMultishop())
			$fields[$id_language]['id_shop'] = (int)$this->id_shop;
		foreach ($fields_array as $k => $field)
		{
			$html = false;
			$field_name = $field;
			if (is_array($field))
			{
				$field_name = $k;
				$html = (isset($field['html'])) ? $field['html'] : false;
			}

			/* Check fields validity */
			if (!Validate::isTableOrIdentifier($field_name))
				throw new PrestaShopException('identifier is not table or identifier : '.$field_name);

			// Copy the field, or the default language field if it's both required and empty
			if ((!$this->id_lang && isset($this->{$field_name}[$id_language]) && !empty($this->{$field_name}[$id_language]))
			|| ($this->id_lang && isset($this->$field_name) && !empty($this->$field_name)))
				$fields[$id_language][$field_name] = $this->id_lang ? pSQL($this->$field_name, $html) : pSQL($this->{$field_name}[$id_language], $html);
			else if (in_array($field_name, $this->fieldsRequiredLang))
				$fields[$id_language][$field_name] = pSQL($this->id_lang ? $this->$field_name : $this->{$field_name}[Configuration::get('PS_LANG_DEFAULT')], $html);
			else
				$fields[$id_language][$field_name] = '';
		}
	}

	/**
	 * Check for fields validity before database interaction
	 *
	 * @param bool $die
	 * @param bool $error_return
	 * @return bool|string
	 */
	public function validateFields($die = true, $error_return = false)
	{
		foreach ($this->def['fields'] as $field => $data)
		{
			if (!empty($data['lang']))
				continue;

			if (is_array($this->update_fields) && empty($this->update_fields[$field]))
				continue;

			$message = $this->validateField($field, $this->$field);
			if ($message !== true)
			{
				if ($die)
					throw new PrestaShopException($message);
				return $error_return ? $message : false;
			}
		}

		return true;
	}

	/**
	 * Check for multilingual fields validity before database interaction
	 *
	 * @param bool $die
	 * @param bool $error_return
	 * @return bool|string
	 */
	public function validateFieldsLang($die = true, $error_return = false)
	{
		foreach ($this->def['fields'] as $field => $data)
		{
			if (empty($data['lang']))
				continue;

			$values = $this->$field;
			
			// If the object has not been loaded in multilanguage, then the value is the one for the current language of the object
			if (!is_array($values))
				$values = array($this->id_lang => $values);

			// The value for the default must always be set, so we put an empty string if it does not exists
			if (!isset($values[Configuration::get('PS_LANG_DEFAULT')]))
				$values[Configuration::get('PS_LANG_DEFAULT')] = '';

			foreach ($values as $id_lang => $value)
			{
				if (is_array($this->update_fields) && empty($this->update_fields[$field][$id_lang]))
					continue;

				$message = $this->validateField($field, $value, $id_lang);
				if ($message !== true)
				{
					if ($die)
						throw new PrestaShopException($message);
					return $error_return ? $message : false;
				}
			}
		}

		return true;
	}

	/**
	 * Validate a single field
	 *
	 * @since 1.5.0
	 * @param string $field Field name
	 * @param mixed $value Field value
	 * @param int $id_lang
	 * @return bool|string
	 */
	public function validateField($field, $value, $id_lang = null, $skip = array(), $human_errors = false)
	{
		$this->cacheFieldsRequiredDatabase();
		$data = $this->def['fields'][$field];

		// Check if field is required
		$required_fields = (isset(self::$fieldsRequiredDatabase[get_class($this)])) ? self::$fieldsRequiredDatabase[get_class($this)] : array();
		if (!$id_lang || $id_lang == Configuration::get('PS_LANG_DEFAULT'))
			if (!in_array('required', $skip) && (!empty($data['required']) || in_array($field, $required_fields)))
				if (Tools::isEmpty($value))
					if ($human_errors)
						return sprintf(Tools::displayError('The %s field is required.'), $this->displayFieldName($field, get_class($this)));
					else
						return 'Property '.get_class($this).'->'.$field.' is empty';

		// Default value
		if (!$value && !empty($data['default']))
		{
			$value = $data['default'];
			$this->$field = $value;
		}

		// Check field values
		if (!in_array('values', $skip) && !empty($data['values']) && is_array($data['values']) && !in_array($value, $data['values']))
				return 'Property '.get_class($this).'->'.$field.' has bad value (allowed values are: '.implode(', ', $data['values']).')';

		// Check field size
		if (!in_array('size', $skip) && !empty($data['size']))
		{
			$size = $data['size'];
			if (!is_array($data['size']))
				$size = array('min' => 0, 'max' => $data['size']);

			$length = Tools::strlen($value);
			if ($length < $size['min'] || $length > $size['max'])
			{
				if ($human_errors)
				{
					if (isset($data['lang']) && $data['lang'])
					{
						$language = new Language((int)$id_lang);
						return sprintf(Tools::displayError('The field %1$s (%2$s) is too long (%3$d chars max, html chars including).'), $this->displayFieldName($field, get_class($this)), $language->name, $size['max']);
					}
					else
					 	return sprintf(Tools::displayError('The %1$s field is too long (%2$d chars max).'), $this->displayFieldName($field, get_class($this)), $size['max']);
				}
				else
					return 'Property '.get_class($this).'->'.$field.' length ('.$length.') must be between '.$size['min'].' and '.$size['max'];
			}
		}

		// Check field validator
		if (!in_array('validate', $skip) && !empty($data['validate']))
		{
			if (!method_exists('Validate', $data['validate']))
				throw new PrestaShopException('Validation function not found. '.$data['validate']);

			if (!empty($value))
			{
				$res = true;
				if (Tools::strtolower($data['validate']) == 'iscleanhtml')
				{
					if (!call_user_func(array('Validate', $data['validate']), $value, (int)Configuration::get('PS_ALLOW_HTML_IFRAME')))
						$res = false;
				}
				else
				{
					if (!call_user_func(array('Validate', $data['validate']), $value))
						$res = false;
				}
				if (!$res)
				{
					if ($human_errors)
							return sprintf(Tools::displayError('The %s field is invalid.'), $this->displayFieldName($field, get_class($this)));
					else
						return 'Property '.get_class($this).'->'.$field.' is not valid';
				}
			}
		}

		return true;
	}

	public static function displayFieldName($field, $class = __CLASS__, $htmlentities = true, Context $context = null)
	{
		global $_FIELDS;

		if ($_FIELDS === null && file_exists(_PS_TRANSLATIONS_DIR_.Context::getContext()->language->iso_code.'/fields.php'))
			include_once(_PS_TRANSLATIONS_DIR_.Context::getContext()->language->iso_code.'/fields.php');

		$key = $class.'_'.md5($field);
		return ((is_array($_FIELDS) && array_key_exists($key, $_FIELDS)) ? ($htmlentities ? htmlentities($_FIELDS[$key], ENT_QUOTES, 'utf-8') : $_FIELDS[$key]) : $field);
	}

	/**
	* TODO: refactor rename all calls to this to validateController
	* @deprecated since 1.5 use validateController instead
	*/
	public function validateControler($htmlentities = true)
	{
		Tools::displayAsDeprecated();
		return $this->validateController($htmlentities);
	}

	public function validateController($htmlentities = true)
	{
		$this->cacheFieldsRequiredDatabase();
		$errors = array();
		$required_fields_database = (isset(self::$fieldsRequiredDatabase[get_class($this)])) ? self::$fieldsRequiredDatabase[get_class($this)] : array();
		foreach ($this->def['fields'] as $field => $data)
		{
			$value = Tools::getValue($field, $this->{$field});		
			// Check if field is required by user
			if (in_array($field, $required_fields_database))
				$data['required'] = true;
			
			// Checking for required fields
			if (isset($data['required']) && $data['required'] && empty($value) && $value !== '0')
				if (!$this->id || $field != 'passwd')
					$errors[$field] = '<b>'.self::displayFieldName($field, get_class($this), $htmlentities).'</b> '.Tools::displayError('is required.');

			// Checking for maximum fields sizes
			if (isset($data['size']) && !empty($value) && Tools::strlen($value) > $data['size'])
				$errors[$field] = sprintf(
					Tools::displayError('%1$s is too long. Maximum length: %2$d'),
					self::displayFieldName($field, get_class($this), $htmlentities),
					$data['size']
				);

			// Checking for fields validity
			// Hack for postcode required for country which does not have postcodes
			if (!empty($value) || $value === '0' || ($field == 'postcode' && $value == '0'))
			{
				if (isset($data['validate']) && !Validate::$data['validate']($value) && (!empty($value) || $data['required']))
					$errors[$field] = '<b>'.self::displayFieldName($field, get_class($this), $htmlentities).'</b> '.Tools::displayError('is invalid.');
				else
				{
					if (isset($data['copy_post']) && !$data['copy_post'])
						continue;
					if ($field == 'passwd')
					{
						if ($value = Tools::getValue($field))
							$this->{$field} = Tools::encrypt($value);
					}
					else
						$this->{$field} = $value;
				}
			}
		}
		return $errors;
	}

	public function getWebserviceParameters($ws_params_attribute_name = null)
	{
		$this->cacheFieldsRequiredDatabase();
		$default_resource_parameters = array(
			'objectSqlId' => $this->def['primary'],
			'retrieveData' => array(
				'className' => get_class($this),
				'retrieveMethod' => 'getWebserviceObjectList',
				'params' => array(),
				'table' => $this->def['table'],
			),
			'fields' => array(
				'id' => array('sqlId' => $this->def['primary'], 'i18n' => false),
			),
		);

		if ($ws_params_attribute_name === null)
			$ws_params_attribute_name = 'webserviceParameters';

		if (!isset($this->{$ws_params_attribute_name}['objectNodeName']))
			$default_resource_parameters['objectNodeName'] = $this->def['table'];
		if (!isset($this->{$ws_params_attribute_name}['objectsNodeName']))
			$default_resource_parameters['objectsNodeName'] = $this->def['table'].'s';

		if (isset($this->{$ws_params_attribute_name}['associations']))
			foreach ($this->{$ws_params_attribute_name}['associations'] as $assoc_name => &$association)
			{
				if (!array_key_exists('setter', $association) || (isset($association['setter']) && !$association['setter']))
					$association['setter'] = Tools::toCamelCase('set_ws_'.$assoc_name);
				if (!array_key_exists('getter', $association))
					$association['getter'] = Tools::toCamelCase('get_ws_'.$assoc_name);
			}

		if (isset($this->{$ws_params_attribute_name}['retrieveData']) && isset($this->{$ws_params_attribute_name}['retrieveData']['retrieveMethod']))
			unset($default_resource_parameters['retrieveData']['retrieveMethod']);

		$resource_parameters = array_merge_recursive($default_resource_parameters, $this->{$ws_params_attribute_name});

		$required_fields = (isset(self::$fieldsRequiredDatabase[get_class($this)]) ? self::$fieldsRequiredDatabase[get_class($this)] : array());
		foreach ($this->def['fields'] as $field_name => $details)
		{
			if (!isset($resource_parameters['fields'][$field_name]))
				$resource_parameters['fields'][$field_name] = array();
			$current_field = array();
			$current_field['sqlId'] = $field_name;
			if (isset($details['size']))
				$current_field['maxSize'] = $details['size'];
			if (isset($details['lang']))
				$current_field['i18n'] = $details['lang'];
			else
				$current_field['i18n'] = false;
			if ((isset($details['required']) && $details['required'] === true) || in_array($field_name, $required_fields))
				$current_field['required'] = true;
			else
				$current_field['required'] = false;
			if (isset($details['validate']))
			{
				$current_field['validateMethod'] = (
 								array_key_exists('validateMethod', $resource_parameters['fields'][$field_name]) ?
 								array_merge($resource_parameters['fields'][$field_name]['validateMethod'], array($details['validate'])) :
 								array($details['validate'])
 							);
			}
			$resource_parameters['fields'][$field_name] = array_merge($resource_parameters['fields'][$field_name], $current_field);
		}
		if (isset($this->date_add))
			$resource_parameters['fields']['date_add']['setter'] = false;
		if (isset($this->date_upd))
			$resource_parameters['fields']['date_upd']['setter'] = false;
		foreach ($resource_parameters['fields'] as $key => $resource_parameters_field)
			if (!isset($resource_parameters_field['sqlId']))
				$resource_parameters['fields'][$key]['sqlId'] = $key;
		return $resource_parameters;
	}

	public function getWebserviceObjectList($sql_join, $sql_filter, $sql_sort, $sql_limit)
	{
		$assoc = Shop::getAssoTable($this->def['table']);
		$class_name = WebserviceRequest::$ws_current_classname;
		$vars = get_class_vars($class_name);
		if ($assoc !== false)
		{
			if ($assoc['type'] !== 'fk_shop')
			{
				$multi_shop_join = ' LEFT JOIN `'._DB_PREFIX_.bqSQL($this->def['table']).'_'.bqSQL($assoc['type']).'`
										AS `multi_shop_'.bqSQL($this->def['table']).'`
										ON (main.`'.bqSQL($this->def['primary']).'` = `multi_shop_'.bqSQL($this->def['table']).'`.`'.bqSQL($this->def['primary']).'`)';
				$sql_filter = 'AND `multi_shop_'.bqSQL($this->def['table']).'`.id_shop = '.Context::getContext()->shop->id.' '.$sql_filter;
				$sql_join = $multi_shop_join.' '.$sql_join;
			}
			else
			{
				$vars = get_class_vars($class_name);
				foreach ($vars['shopIDs'] as $id_shop)
					$or[] = '(main.id_shop = '.(int)$id_shop.(isset($this->def['fields']['id_shop_group']) ? ' OR (id_shop = 0 AND id_shop_group='.(int)Shop::getGroupFromShop((int)$id_shop).')' : '').')';
				
				$prepend = '';
				if (count($or))
					$prepend = 'AND ('.implode('OR', $or).')';
				$sql_filter = $prepend.' '.$sql_filter;
			}
		}
		$query = '
		SELECT DISTINCT main.`'.bqSQL($this->def['primary']).'` FROM `'._DB_PREFIX_.bqSQL($this->def['table']).'` AS main
		'.$sql_join.'
		WHERE 1 '.$sql_filter.'
		'.($sql_sort != '' ? $sql_sort : '').'
		'.($sql_limit != '' ? $sql_limit : '');
		return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
	}

	public function validateFieldsRequiredDatabase($htmlentities = true)
	{
		$this->cacheFieldsRequiredDatabase();
		$errors = array();
		$required_fields = (isset(self::$fieldsRequiredDatabase[get_class($this)])) ? self::$fieldsRequiredDatabase[get_class($this)] : array();

		foreach ($this->def['fields'] as $field => $data)
		{
			if (!in_array($field, $required_fields))
				continue;

			if (!method_exists('Validate', $data['validate']))
				throw new PrestaShopException('Validation function not found. '.$data['validate']);

			$value = Tools::getValue($field);

			if (empty($value))
				$errors[$field] = sprintf(Tools::displayError('The field %s is required.'), self::displayFieldName($field, get_class($this), $htmlentities));
		}

		return $errors;
	}

	public function getFieldsRequiredDatabase($all = false)
	{
		return Db::getInstance()->executeS('
		SELECT id_required_field, object_name, field_name
		FROM '._DB_PREFIX_.'required_field
		'.(!$all ? 'WHERE object_name = \''.pSQL(get_class($this)).'\'' : ''));
	}
	
	public function cacheFieldsRequiredDatabase()
	{
		if (!is_array(self::$fieldsRequiredDatabase))
		{
			$fields = $this->getfieldsRequiredDatabase(true);
			if ($fields)
				foreach ($fields as $row)
					self::$fieldsRequiredDatabase[$row['object_name']][(int)$row['id_required_field']] = pSQL($row['field_name']);
			else
				self::$fieldsRequiredDatabase = array();
		}
	}

	public function addFieldsRequiredDatabase($fields)
	{
		if (!is_array($fields))
			return false;

		if (!Db::getInstance()->execute('DELETE FROM '._DB_PREFIX_.'required_field WHERE object_name = \''.get_class($this).'\''))
			return false;

		foreach ($fields as $field)
			if (!Db::getInstance()->insert('required_field', array('object_name' => get_class($this), 'field_name' => pSQL($field))))
				return false;
		return true;
	}

	public function clearCache($all = false)
	{
		if ($all)
			Cache::clean('objectmodel_'.$this->def['classname'].'_*');
		elseif ($this->id)
			Cache::clean('objectmodel_'.$this->def['classname'].'_'.(int)$this->id.'_*');
	}

	/**
	 * Check if current object is associated to a shop
	 *
	 * @since 1.5.0
	 * @param int $id_shop
	 * @return bool
	 */
	public function isAssociatedToShop($id_shop = null)
	{
		if ($id_shop === null)
			$id_shop = Context::getContext()->shop->id;

		$cache_id = 'objectmodel_shop_'.$this->def['classname'].'_'.(int)$this->id.'-'.(int)$id_shop;
		if (!Cache::isStored($cache_id))
		{
			$sql = 'SELECT id_shop
					FROM `'.pSQL(_DB_PREFIX_.$this->def['table']).'_shop`
					WHERE `'.$this->def['primary'].'` = '.(int)$this->id.'
						AND id_shop = '.(int)$id_shop;
			Cache::store($cache_id, (bool)Db::getInstance()->getValue($sql));
		}
		return Cache::retrieve($cache_id);
	}

	/**
	 * This function associate an item to its context
	 *
	 * @param int|array $id_shops
	 * @return boolean
	 */
	public function associateTo($id_shops)
	{
		if (!$this->id)
			return;

		if (!is_array($id_shops))
			$id_shops = array($id_shops);

		$data = array();
		foreach ($id_shops as $id_shop)
		{
			if (!$this->isAssociatedToShop($id_shop))
				$data[] = array(
					$this->def['primary'] => (int)$this->id,
					'id_shop' => (int)$id_shop,
				);
		}

		if ($data)
			return Db::getInstance()->insert($this->def['table'].'_shop', $data);
		return true;
	}

	/**
	 * Get the list of associated id_shop
	 *
	 * @since 1.5.0
	 * @return array
	 */
	public function getAssociatedShops()
	{
		if (!Shop::isTableAssociated($this->def['table']))
			return array();

		$list = array();
		$sql = 'SELECT id_shop FROM `'._DB_PREFIX_.$this->def['table'].'_shop` WHERE `'.$this->def['primary'].'` = '.(int)$this->id;
		foreach (Db::getInstance()->executeS($sql) as $row)
			$list[] = $row['id_shop'];
		return $list;
	}

	/**
	 * @since 1.5.0
	 */
	public function duplicateShops($id)
	{
		if (!Shop::isTableAssociated($this->def['table']))
			return false;

		$sql = 'SELECT id_shop
				FROM '._DB_PREFIX_.$this->def['table'].'_shop
				WHERE '.$this->def['primary'].' = '.(int)$id;
		if ($results = Db::getInstance()->executeS($sql))
		{
			$ids = array();
			foreach ($results as $row)
				$ids[] = $row['id_shop'];
			return $this->associateTo($ids);
		}

		return false;
	}

	/**
	 * Check if there is more than one entries in associated shop table for current entity
	 *
	 * @since 1.5.0
	 * @return bool
	 */
	public function hasMultishopEntries()
	{
		if (!Shop::isTableAssociated($this->def['table']) || !Shop::isFeatureActive())
			return false; 
		return (bool)Db::getInstance()->getValue('SELECT COUNT(*) FROM `'._DB_PREFIX_.$this->def['table'].'_shop` WHERE `'.$this->def['primary'].'` = '.(int)$this->id);
	}

	public function isMultishop()
	{
		return Shop::isTableAssociated($this->def['table']) || !empty($this->def['multilang_shop']);
	}
	
	public function isMultiShopField($field)
	{
		return (isset($this->def['fields'][$field]) && isset($this->def['fields'][$field]['shop']) && $this->def['fields'][$field]['shop']);
	}

	public function isLangMultishop()
	{
		return !empty($this->def['multilang']) && !empty($this->def['multilang_shop']);
	}

	/**
	 * Update a table and splits the common datas and the shop datas
	 *
	 * @since 1.5.0
	 * @param string $classname
	 * @param array $data
	 * @param string $where
	 * @param string $specific_where Only executed for common table
	 * @return bool
	 */
	public static function updateMultishopTable($classname, $data, $where = '', $specific_where = '')
	{
		$def = ObjectModel::getDefinition($classname);
		$update_data = array();
		foreach ($data as $field => $value)
		{
			if (!isset($def['fields'][$field]))
				continue;

			if (!empty($def['fields'][$field]['shop']))
			{
				$update_data[] = "a.$field = '$value'";
				$update_data[] = "{$def['table']}_shop.$field = '$value'";
			}
			else
				$update_data[] = "a.$field = '$value'";
		}

		$sql = 'UPDATE '._DB_PREFIX_.$def['table'].' a
				'.Shop::addSqlAssociation($def['table'], 'a', true, null, true).'
				SET '.implode(', ', $update_data).
				(!empty($where) ? ' WHERE '.$where : '');
		return Db::getInstance()->execute($sql);
	}

	/**
	 * Delete images associated with the object
	 *
	 * @return bool success
	 */
	public function deleteImage($force_delete = false)
	{
		if (!$this->id)
			return false;
		
		if ($force_delete || !$this->hasMultishopEntries())
		{
			/* Deleting object images and thumbnails (cache) */
			if ($this->image_dir)
			{
				if (file_exists($this->image_dir.$this->id.'.'.$this->image_format)
					&& !unlink($this->image_dir.$this->id.'.'.$this->image_format))
					return false;
			}
			if (file_exists(_PS_TMP_IMG_DIR_.$this->def['table'].'_'.$this->id.'.'.$this->image_format)
				&& !unlink(_PS_TMP_IMG_DIR_.$this->def['table'].'_'.$this->id.'.'.$this->image_format))
				return false;
			if (file_exists(_PS_TMP_IMG_DIR_.$this->def['table'].'_mini_'.$this->id.'.'.$this->image_format)
				&& !unlink(_PS_TMP_IMG_DIR_.$this->def['table'].'_mini_'.$this->id.'.'.$this->image_format))
				return false;
	
			$types = ImageType::getImagesTypes();
			foreach ($types as $image_type)
				if (file_exists($this->image_dir.$this->id.'-'.stripslashes($image_type['name']).'.'.$this->image_format)
				&& !unlink($this->image_dir.$this->id.'-'.stripslashes($image_type['name']).'.'.$this->image_format))
					return false;
		}
		return true;
	}

	/**
	 * Specify if an ObjectModel is already in database
	 *
	 * @param int $id_entity
	 * @param string $table
	 * @return boolean
	 */
	public static function existsInDatabase($id_entity, $table)
	{
		$row = Db::getInstance()->getRow('
			SELECT `id_'.$table.'` as id
			FROM `'._DB_PREFIX_.$table.'` e
			WHERE e.`id_'.$table.'` = '.(int)$id_entity
		);

		return isset($row['id']);
	}

	/**
	 * This method is allow to know if a entity is currently used
	 * @since 1.5.0.1
	 * @param string $table name of table linked to entity
	 * @param bool $has_active_column true if the table has an active column
	 * @return bool
	 */
	public static function isCurrentlyUsed($table = null, $has_active_column = false)
	{
		if ($table === null)
			$table = self::$definition['table'];

		$query = new DbQuery();
		$query->select('`id_'.pSQL($table).'`');
		$query->from($table);
		if ($has_active_column)
			$query->where('`active` = 1');
		return (bool)Db::getInstance()->getValue($query);
	}

	/**
	 * Fill an object with given data. Data must be an array with this syntax: array(objProperty => value, objProperty2 => value, etc.)
	 *
	 * @since 1.5.0
	 * @param array $data
	 * @param int $id_lang
	 */
	public function hydrate(array $data, $id_lang = null)
	{
		$this->id_lang = $id_lang;
		if (isset($data[$this->def['primary']]))
			$this->id = $data[$this->def['primary']];
		foreach ($data as $key => $value)
			if (array_key_exists($key, $this))
				$this->$key = $value;
	}

	/**
	 * Fill (hydrate) a list of objects in order to get a collection of these objects
	 *
	 * @since 1.5.0
	 * @param string $class Class of objects to hydrate
	 * @param array $datas List of data (multi-dimensional array)
	 * @param int $id_lang
	 * @return array
	 */
	public static function hydrateCollection($class, array $datas, $id_lang = null)
	{
		if (!class_exists($class))
			throw new PrestaShopException("Class '$class' not found");

		$collection = array();
		$rows = array();
		if ($datas)
		{
			$definition = ObjectModel::getDefinition($class);
			if (!array_key_exists($definition['primary'], $datas[0]))
				throw new PrestaShopException("Identifier '{$definition['primary']}' not found for class '$class'");

			foreach ($datas as $row)
			{
				// Get object common properties
				$id = $row[$definition['primary']];
				if (!isset($rows[$id]))
					$rows[$id] = $row;

				// Get object lang properties
				if (isset($row['id_lang']) && !$id_lang)
					foreach ($definition['fields'] as $field => $data)
						if (!empty($data['lang']))
						{
							if (!is_array($rows[$id][$field]))
								$rows[$id][$field] = array();
							$rows[$id][$field][$row['id_lang']] = $row[$field];
						}
			}
		}

		// Hydrate objects
		foreach ($rows as $row)
		{
			$obj = new $class;
			$obj->hydrate($row, $id_lang);
			$collection[] = $obj;
		}
		return $collection;
	}

	/**
	 * Get object definition
	 *
	 * @param string $class Name of object
	 * @param string $field Name of field if we want the definition of one field only
	 * @return array
	 */
	public static function getDefinition($class, $field = null)
	{
		if (is_object($class))
			$class = get_class($class);

		if ($field === null)
			$cache_id = 'objectmodel_def_'.$class;

		if ($field !== null || !Cache::isStored($cache_id))
		{
			$reflection = new ReflectionClass($class);
			$definition = $reflection->getStaticPropertyValue('definition');

			$definition['classname'] = $class;

			if (!empty($definition['multilang']))
				$definition['associations'][Collection::LANG_ALIAS] = array(
					'type' => self::HAS_MANY,
					'field' => $definition['primary'],
					'foreign_field' => $definition['primary'],
				);
		
			if ($field)
				return isset($definition['fields'][$field]) ? $definition['fields'][$field] : null;

			Cache::store($cache_id, $definition);
			return $definition;
		}

		return Cache::retrieve($cache_id);
	}

	/**
	 * Retrocompatibility for classes without $definition static
	 * Remove this in 1.6 !
	 *
	 * @since 1.5.0
	 */
	protected function setDefinitionRetrocompatibility()
	{
		// Retrocompatibility with $table property ($definition['table'])
		if (isset($this->def['table']))
			$this->table = $this->def['table'];
		else
			$this->def['table'] = $this->table;

		// Retrocompatibility with $identifier property ($definition['primary'])
		if (isset($this->def['primary']))
			$this->identifier = $this->def['primary'];
		else
			$this->def['primary'] = $this->identifier;

		// Check multilang retrocompatibility
		if (method_exists($this, 'getTranslationsFieldsChild'))
			$this->def['multilang'] = true;

		// Retrocompatibility with $fieldsValidate, $fieldsRequired and $fieldsSize properties ($definition['fields'])
		if (isset($this->def['fields']))
		{
			foreach ($this->def['fields'] as $field => $data)
			{
				$suffix = (isset($data['lang']) && $data['lang']) ? 'Lang' : '';
				if (isset($data['validate']))
					$this->{'fieldsValidate'.$suffix}[$field] = $data['validate'];
				if (isset($data['required']) && $data['required'])
					$this->{'fieldsRequired'.$suffix}[] = $field;
				if (isset($data['size']))
					$this->{'fieldsSize'.$suffix}[$field] = $data['size'];
			}
		}
		else
		{
			$this->def['fields'] = array();
			$suffixs = array('', 'Lang');
			foreach($suffixs as $suffix)
			{
				foreach ($this->{'fieldsValidate'.$suffix} as $field => $validate)
				{
					$this->def['fields'][$field]['validate'] = $validate;
					if ($suffix == 'Lang')
						$this->def['fields'][$field]['lang'] = true;
				}
				foreach ($this->{'fieldsRequired'.$suffix} as $field)
				{
					$this->def['fields'][$field]['required'] = true;
					if ($suffix == 'Lang')
						$this->def['fields'][$field]['lang'] = true;
				}
				foreach ($this->{'fieldsSize'.$suffix} as $field => $size)
				{
					$this->def['fields'][$field]['size'] = $size;
					if ($suffix == 'Lang')
						$this->def['fields'][$field]['lang'] = true;
				}
			}
		}
	}

	/**
	 * Return the field value for the specified language if the field is multilang, else the field value.
	 *
	 * @param $field_name
	 * @param null $id_lang
	 * @return mixed
	 * @throws PrestaShopException
	 * @since 1.5
	 */
	public function getFieldByLang($field_name, $id_lang = null)
	{
		$definition = ObjectModel::getDefinition($this);
		// Is field in definition?
		if ($definition && isset($definition['fields'][$field_name]))
		{
			$field = $definition['fields'][$field_name];
			// Is field multilang?
			if (isset($field['lang']) && $field['lang'])
			{
				if (is_array($this->{$field_name}))
					return $this->{$field_name}[$id_lang ? $id_lang : Context::getContext()->language->id];
			}
			return $this->{$field_name};
		}
		else
			throw new PrestaShopException('Could not load field from definition.');
	}

	/**
	 * Set a list of specific fields to update
	 * array(field1 => true, field2 => false, langfield1 => array(1 => true, 2 => false))
	 *
	 * @since 1.5.0
	 * @param array $fields
	 */
	public function setFieldsToUpdate(array $fields)
	{
		$this->update_fields = $fields;
	}
}

Is it same?

Link to comment
Share on other sites

No, can you point the differences :)  ?

 

Product :

public function validateField($field, $value, $id_lang = null)

 

ObjectModel :

public function validateField($field, $value, $id_lang = null, $skip = array(), $human_errors = false)

 

Now you can modify product to be the same as objectModel but I think It will add trouble or show up other trouble.

You should go for a neat new clean install of prestashop.

 

Regards

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...