Développement

Ajouter du HTML dans les caratéristiques produit de Prestashop

Par Guillaume , le 29 juillet 2015 , mis à jour le 3 décembre 2015 — 1 minute de lecture

Bonjour à tous.

Dans ce tuto on va voir comment ajouter du HTML dans les caratéristiques produits de Prestashop.

Le test est fait sur la version 1.6.1.0

Pour commencer il faut overrider 2 classes:

– la Product.php

et la FeatureValue.php

 

Créer un fichier Product.php dans le dossier /override/classes/

et collez le code suivant:

<?php

/*
* 2007-2015 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 license@prestashop.com 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 <contact@prestashop.com>
* @copyright 2007-2015 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 int Manufacturer id */
public $id_manufacturer;

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

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

/** @var int 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 int Quantity available */
public $quantity = 0;

/** @var int 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;

public $specificPrice = 0;

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

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

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

/** @var bool 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 bool Product statuts */
public $quantity_discount = 0;

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

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

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

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

/** @var bool Product statuts */
public $active = true;

/** @var bool Product statuts */
public $redirect_type = \'\';

/** @var bool Product statuts */
public $id_product_redirected = 0;

/** @var bool Product available for order */
public $available_for_order = true;

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

/** @var string Enumerated (enum) product condition (new, used, refurbished) */
public $condition;

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

/** @var bool 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;

/**
* @var float Base price of the product
* @deprecated 1.6.0.13
*/
public $base_price;

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 bool 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 $id_pack_product_attribute;
public $cache_default_attribute;

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

/**
* @var int tell the type of stock management to apply on the pack
*/
public $pack_stock_type = 3;

public static $_taxCalculationMethod = null;
protected static $_prices = array();
protected static $_pricesLevel2 = array();
protected static $_incat = array();

/**
* @since 1.5.6.1
* @var array $_cart_quantity is deprecated since 1.5.6.1
*/
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\' => \'isDate\'),
\'date_upd\' => array(\'type\' => self::TYPE_DATE, \'shop\' => true, \'validate\' => \'isDate\'),
\'pack_stock_type\' => array(\'type\' => self::TYPE_INT, \'shop\' => true, \'validate\' => \'isUnsignedInt\'),

/* 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,
\'ws_modifier\' => array(
\'http_method\' => WebserviceRequest::HTTP_POST,
\'modifier\' => \'modifierWsLinkRewrite\'
)
),
\'name\' => array(\'type\' => self::TYPE_STRING, \'lang\' => true, \'validate\' => \'isCatalogName\', \'required\' => true, \'size\' => 128),
\'description\' => array(\'type\' => self::TYPE_HTML, \'lang\' => true, \'validate\' => \'isCleanHtml\'),
\'description_short\' => array(\'type\' => self::TYPE_HTML, \'lang\' => true, \'validate\' => \'isCleanHtml\'),
\'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_rule_groups\'
)
),
\'position_in_category\' => array(
\'getter\' => \'getWsPositionInCategory\',
\'setter\' => \'setWsPositionInCategory\'
),
\'manufacturer_name\' => array(
\'getter\' => \'getWsManufacturerName\',
\'setter\' => false
),
\'quantity\' => array(
\'getter\' => false,
\'setter\' => false
),
\'type\' => array(
\'getter\' => \'getWsType\',
\'setter\' => \'setWsType\',
),
),
\'associations\' => array(
\'categories\' => array(
\'resource\' => \'category\',
\'fields\' => array(
\'id\' => array(\'required\' => true),
)
),
\'images\' => array(
\'resource\' => \'image\',
\'fields\' => array(\'id\' => array())
),
\'combinations\' => array(
\'resource\' => \'combination\',
\'fields\' => array(
\'id\' => array(\'required\' => true),
)
),
\'product_option_values\' => array(
\'resource\' => \'product_option_value\',
\'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\',
\'api\' => \'products\',
\'fields\' => array(
\'id\' => array(
\'required\' => true,
\'xlink_resource\' => \'product\'),
)
),
\'product_bundle\' => array(
\'resource\' => \'product\',
\'api\' => \'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 ($full && $this->id)
{
if (!$context)
$context = Context::getContext();

$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;
$fields[\'unity\'] = pSQL($this->unity);

return $fields;
}

public function add($autodate = true, $null_values = false)
{
if (!parent::add($autodate, $null_values))
return false;

$id_shop_list = Shop::getContextListShopID();
if ($this->getType() == Product::PTYPE_VIRTUAL)
{
foreach ($id_shop_list as $value)
StockAvailable::setProductOutOfStock((int)$this->id, 1, $value);

if ($this->active && !Configuration::get(\'PS_VIRTUAL_PROD_FEATURE_ACTIVE\'))
Configuration::updateGlobalValue(\'PS_VIRTUAL_PROD_FEATURE_ACTIVE\', \'1\');
}
else
foreach ($id_shop_list as $value)
StockAvailable::setProductOutOfStock((int)$this->id, 2, $value);

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

public function update($null_values = false)
{
$return = parent::update($null_values);
$this->setGroupReduction();

// Sync stock Reference, EAN13 and UPC
if (Configuration::get(\'PS_ADVANCED_STOCK_MANAGEMENT\') && StockAvailable::dependsOnStock($this->id, Context::getContext()->shop->id))
Db::getInstance()->update(\'stock\', array(
\'reference\' => pSQL($this->reference),
\'ean13\' => pSQL($this->ean13),
\'upc\' => pSQL($this->upc),
), \'id_product = \'.(int)$this->id.\' AND id_product_attribute = 0\');

Hook::exec(\'actionProductSave\', array(\'id_product\' => (int)$this->id, \'product\' => $this));
Hook::exec(\'actionProductUpdate\', array(\'id_product\' => (int)$this->id, \'product\' => $this));
if ($this->getType() == Product::PTYPE_VIRTUAL && $this->active && !Configuration::get(\'PS_VIRTUAL_PROD_FEATURE_ACTIVE\'))
Configuration::updateGlobalValue(\'PS_VIRTUAL_PROD_FEATURE_ACTIVE\', \'1\');

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);
$cur_cart = Context::getContext()->cart;
$id_address = 0;
if (Validate::isLoadedObject($cur_cart))
$id_address = (int)$cur_cart->{Configuration::get(\'PS_TAX_ADDRESS_TYPE\')};
$address_infos = Address::getCountryAndState($id_address);

if (self::$_taxCalculationMethod != PS_TAX_EXC
&& !empty($address_infos[\'vat_number\'])
&& $address_infos[\'id_country\'] != Configuration::get(\'VATNUMBER_COUNTRY\')
&& Configuration::get(\'VATNUMBER_MANAGEMENT\'))
self::$_taxCalculationMethod = PS_TAX_EXC;
}
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 bool $way Up (1) or Down (0)
* @param int $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, $position = 0)
{
$return = true;

if (!(int)$position)
{

$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\']);
}
else
$return &= Db::getInstance()->update(\'category_product\',
array(\'position\' => array(\'type\' => \'sql\', \'value\' => \'`position`-1\')),
\'`id_category` = \'.(int)$id_category.\' AND `position` > \'.(int)$position);

return $return;
}

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

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

if ($reset && isset($combinations[$id_product]))
unset($combinations[$id_product]);

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


$sql = \'SELECT product_attribute_shop.id_product_attribute
FROM \'._DB_PREFIX_.\'product_attribute pa
\'.Shop::addSqlAssociation(\'product_attribute\', \'pa\').\'
WHERE pa.id_product = \'.(int)$id_product;

$result_no_filter = Db::getInstance()->getValue($sql);
if (!$result_no_filter)
{
$combinations[$id_product][$minimum_quantity] = 0;
return 0;
}

$sql = \'SELECT product_attribute_shop.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 product_attribute_shop.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 product_attribute_shop.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)
$result = $result_no_filter;

$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;
}

/**
* For a given id_product and id_product_attribute, return available date
*
* @param int $id_product
* @param int $id_product_attribute Optional
* @return string/null
*/
public static function getAvailableDate($id_product, $id_product_attribute = null)
{
$sql = \'SELECT\';

if ($id_product_attribute === null)
$sql .= \' p.`available_date`\';
else
$sql .= \' IF(pa.`available_date` = "0000-00-00", p.`available_date`, pa.`available_date`) AS available_date\';

$sql .= \' FROM `\'._DB_PREFIX_.\'product` p\';

if ($id_product_attribute !== null)
$sql .= \' LEFT JOIN `\'._DB_PREFIX_.\'product_attribute` pa ON (pa.`id_product` = p.`id_product`)\';

$sql .= Shop::addSqlAssociation(\'product\', \'p\');

if ($id_product_attribute !== null)
$sql .= Shop::addSqlAssociation(\'product_attribute\', \'pa\');

$sql .= \' WHERE p.`id_product` = \'.(int)$id_product;

if ($id_product_attribute !== null)
$sql .= \' AND pa.`id_product` = \'.(int)$id_product.\' AND pa.`id_product_attribute` = \'.(int)$id_product_attribute;

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

if ($result == \'0000-00-00\')
$result = null;

return $result;
}

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

/**
* @see ObjectModel::validateField()
*/
public function validateField($field, $value, $id_lang = null, $skip = array(), $human_errors = false)
{
if ($field == \'description_short\')
{
$limit = (int)Configuration::get(\'PS_PRODUCT_SHORT_DESC_LIMIT\');
if ($limit <= 0) $limit = 800; $size_without_html = Tools::strlen(strip_tags($value)); $size_with_html = Tools::strlen($value); $this->def[\'fields\'][\'description_short\'][\'size\'] = $limit + $size_with_html - $size_without_html;
}
return parent::validateField($field, $value, $id_lang, $skip, $human_errors);
}

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 :p
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;

Hook::exec(\'actionProductDelete\', array(\'id_product\' => (int)$this->id, \'product\' => $this));
if (!$result ||
!GroupReduction::deleteProductReduction($this->id) ||
!$this->deleteCategories(true) ||
!$this->deleteProductFeatures() ||
!$this->deleteTags() ||
!$this->deleteCartProducts() ||
!$this->deleteAttributesImpacts() ||
!$this->deleteAttachments(false) ||
!$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 bool 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 bool $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
);

// if none are found, it\'s an error
if (!is_array($result))
return false;

foreach ($result as $categ_to_delete)
$this->deleteCategory($categ_to_delete[\'id_category\']);

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 bool
*/
public function deleteCategory($id_category, $clean_positions = true)
{
$result = Db::getInstance()->executeS(
\'SELECT `id_category`, `position`
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\'], (int)$row[\'position\']);
SpecificPriceRule::applyAllRules(array((int)$this->id));
return $return;
}

/**
* Delete all association to category where product is indexed
*
* @param bool $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`, `position`
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\'], (int)$row[\'position\']);

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\', \'NOT EXISTS (SELECT 1 FROM \'._DB_PREFIX_.\'product_tag
WHERE \'._DB_PREFIX_.\'product_tag.id_tag = \'._DB_PREFIX_.\'tag.id_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 int $id_lang Language id
* @param int $start Start number
* @param int $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\';
elseif ($order_by == \'name\')
$order_by_prefix = \'pl\';
elseif ($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(
"\'.date(\'Y-m-d\').\' 00:00:00",
INTERVAL \'.(Validate::isUnsignedInt(Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\')) ? Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') : 20).\' DAY
)
) > 0
\', true, false);
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)
{
$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;
$this->setAvailableDate();

$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 int $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(), $available_date = null)
{
$id_product_attribute = $this->addAttribute(
$price, $weight, $unit_impact, $ecotax, $id_images,
$reference, $ean13, $default, $location, $upc, $minimal_quantity, $id_shop_list, $available_date);
$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;
}

/**
* @deprecated 1.5.5.0
* @param $attributes
* @param bool $set_default
* @return array
*/
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\' => null,
), \'a.`id_product` = \'.(int)$this->id);
}

public function setDefaultAttribute($id_product_attribute)
{
$result = ObjectModel::updateMultishopTable(\'Combination\', array(
\'default_on\' => 1
), \'a.`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.Shop::addSqlRestriction());

$result &= Db::getInstance()->update(\'product\', array(
\'cache_default_attribute\' => $id_default_attribute,
), \'id_product = \'.(int)$id_product);

if ($result && $id_default_attribute)
return $id_default_attribute;
else
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 or updates 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) && ($this->id > 0))
{
$id_product_supplier = (int)ProductSupplier::getIdByProductAndSupplier($this->id, $id_product_attribute, $id_supplier);

$product_supplier = new ProductSupplier($id_product_supplier);

if (!$id_product_supplier)
{
$product_supplier->id_product = (int)$this->id;
$product_supplier->id_product_attribute = (int)$id_product_attribute;
$product_supplier->id_supplier = (int)$id_supplier;
}

$product_supplier->product_supplier_reference = pSQL($supplier_reference);
$product_supplier->product_supplier_price_te = !is_null($price) ? (float)$price : (float)$product_supplier->product_supplier_price_te;
$product_supplier->id_currency = !is_null($id_currency) ? (int)$id_currency : (int)$product_supplier->id_currency;
$product_supplier->save();
}
}

/**
* Update a product attribute
*
* @param int $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 int $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 (is_array($id_images) && count($id_images))
$combination->setImages($id_images);

$id_default_attribute = (int)Product::updateDefaultAttribute($this->id);
if ($id_default_attribute)
$this->cache_default_attribute = $id_default_attribute;

// Sync stock Reference, EAN13 and UPC for this attribute
if (Configuration::get(\'PS_ADVANCED_STOCK_MANAGEMENT\') && StockAvailable::dependsOnStock($this->id, Context::getContext()->shop->id))
Db::getInstance()->update(\'stock\', array(
\'reference\' => pSQL($reference),
\'ean13\' => pSQL($ean13),
\'upc\' => pSQL($upc),
), \'id_product = \'.$this->id.\' AND id_product_attribute = \'.(int)$id_product_attribute);

Hook::exec(\'actionProductAttributeUpdate\', array(\'id_product_attribute\' => (int)$id_product_attribute));
Tools::clearColorListCache($this->id);

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 int $id_images Image ids
* @param string $reference Reference
* @param string $location Location
* @param string $ean13 Ean-13 barcode
* @param bool $default Is default attribute for product
* @param int $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(), $available_date = null)
{
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;
$combination->available_date = $available_date;

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

$combination->add();

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

$total_quantity = (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(\'
SELECT SUM(quantity) as quantity
FROM \'._DB_PREFIX_.\'stock_available
WHERE id_product = \'.(int)$this->id.\'
AND id_product_attribute <> 0 \'
);

if (!$total_quantity)
Db::getInstance()->update(\'stock_available\', array(\'quantity\' => 0), \'`id_product` = \'.$this->id);

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

if ($id_default_attribute)
{
$this->cache_default_attribute = $id_default_attribute;
if (!$combination->available_date)
$this->setAvailableDate();
}

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

Tools::clearColorListCache($this->id);

if (Configuration::get(\'PS_DEFAULT_WAREHOUSE_NEW_PRODUCT\') != 0 && Configuration::get(\'PS_ADVANCED_STOCK_MANAGEMENT\'))
{
$warehouse_location_entity = new WarehouseProductLocation();
$warehouse_location_entity->id_product = $this->id;
$warehouse_location_entity->id_product_attribute = (int)$combination->id;
$warehouse_location_entity->id_warehouse = Configuration::get(\'PS_DEFAULT_WAREHOUSE_NEW_PRODUCT\');
$warehouse_location_entity->location = pSQL(\'\');
$warehouse_location_entity->save();
}

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\' => (int)$this->id, \'deleteAllAttributes\' => true));

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

/**
* Delete product attributes impacts
*
* @return bool
*/
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
*
* @param bool $update_cache If set to true attachment cache will be updated
* @return array Deletion result
*/
public function deleteAttachments($update_attachment_cache = true)
{
$res = Db::getInstance()->execute(\'
DELETE FROM `\'._DB_PREFIX_.\'product_attachment`
WHERE `id_product` = \'.(int)$this->id
);

if (isset($update_attachment_cache) && (bool)$update_attachment_cache === true)
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 `\'._DB_PREFIX_.\'customization_field_lang` FROM `\'._DB_PREFIX_.\'customization_field_lang` LEFT JOIN `\'._DB_PREFIX_.\'customization_field`
ON (\'._DB_PREFIX_.\'customization_field.id_customization_field = \'._DB_PREFIX_.\'customization_field_lang.id_customization_field)
WHERE \'._DB_PREFIX_.\'customization_field.id_customization_field IS NULL\'
)
);
}

/**
* 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 `\'._DB_PREFIX_.\'search_index`, `\'._DB_PREFIX_.\'search_word`
FROM `\'._DB_PREFIX_.\'search_index` JOIN `\'._DB_PREFIX_.\'search_word`
WHERE `\'._DB_PREFIX_.\'search_index`.`id_product` = \'.(int)$this->id.\'
AND `\'._DB_PREFIX_.\'search_word`.`id_word` = `\'._DB_PREFIX_.\'search_index`.id_word\'
)
);
}

/**
* Add a product attributes combinaison
*
* @param int $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);
}

/**
* @deprecated 1.5.5.0
* @param $id_attributes
* @param $combinations
* @return bool
* @throws PrestaShopDatabaseException
*/
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 int $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 int $id_lang Language id
* @return array Product attributes combinations
*/
public function getAttributesResume($id_lang, $attribute_value_separator = \' - \', $attribute_separator = \', \')
{
if (!Combination::isFeatureActive())
return array();

$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))
{
$result = StockAvailable::getQuantityAvailableByProduct($row[\'id_product\'], $row[\'id_product_attribute\']);
Cache::store(
$cache_key,
$result
);
$combinations[$key][\'quantity\'] = $result;
}
else
$combinations[$key][\'quantity\'] = Cache::retrieve($cache_key);
}

return $combinations;
}

/**
* Get all available product attributes combinations
*
* @param int $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`
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 int $id_product_attribute
* @param int $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`
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))
{
$result = StockAvailable::getQuantityAvailableByProduct($row[\'id_product\'], $row[\'id_product_attribute\']);
Cache::store(
$cache_key,
$result
);
$res[$key][\'quantity\'] = $result;
}
else
$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;
}

public static function getCombinationImageById($id_product_attribute, $id_lang)
{
if (!Combination::isFeatureActive() || !$id_product_attribute)
return false;

$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` = \'.(int)$id_product_attribute.\' AND il.`id_lang` = \'.(int)$id_lang.\' ORDER by i.`position` LIMIT 1\'
);

if (!$result)
return false;

return $result[0];
}

/**
* Check if product has attributes combinations
*
* @return int 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 int $id_lang Language id
* @param int $pageNumber Start from (optional)
* @param int $nbProducts Number of products to return (optional)
* @return array New products
*/
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\'; elseif ($order_by == \'name\') $order_by_prefix = \'pl\'; if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way)) die(Tools::displayError()); $sql_groups = \'\'; if (Group::isFeatureActive()) { $groups = FrontController::getCurrentCustomerGroups(); $sql_groups = \' AND EXISTS(SELECT 1 FROM `\'._DB_PREFIX_.\'category_product` cp JOIN `\'._DB_PREFIX_.\'category_group` cg ON (cp.id_category = cg.id_category AND cg.`id_group` \'.(count($groups) ? \'IN (\'.implode(\',\', $groups).\')\' : \'= 1\').\') WHERE cp.`id_product` = p.`id_product`)\'; } 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 product_shop.`date_add` > "\'.date(\'Y-m-d\', strtotime(\'-\'.(Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') ? (int)Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') : 20).\' DAY\')).\'"
\'.($front ? \' AND product_shop.`visibility` IN ("both", "catalog")\' : \'\').\'
\'.$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`, pl.`available_now`, pl.`available_later`, image_shop.`id_image` id_image, il.`legend`, m.`name` AS manufacturer_name,
product_shop.`date_add` > "\'.date(\'Y-m-d\', strtotime(\'-\'.(Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') ? (int)Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') : 20).\' DAY\')).\'" 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_shop\', \'image_shop\', \'image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=\'.(int)$context->shop->id);
$sql->leftJoin(\'image_lang\', \'il\', \'image_shop.`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(\'product_shop.`date_add` > "\'.date(\'Y-m-d\', strtotime(\'-\'.(Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') ? (int)Configuration::get(\'PS_NB_DAYS_NEW_PRODUCT\') : 20).\' DAY\')).\'"\');
if (Group::isFeatureActive())
{
$sql->join(\'JOIN \'._DB_PREFIX_.\'category_product cp ON (cp.id_product = p.id_product)\');
$sql->join(\'JOIN \'._DB_PREFIX_.\'category_group cg ON (cg.id_category = cp.id_category)\');
$sql->where(\'cg.`id_group` \'.(count($groups) ? \'IN (\'.implode(\',\', $groups).\')\' : \'= 1\'));
}
$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(\'product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity, IFNULL(product_attribute_shop.id_product_attribute,0) id_product_attribute\');
$sql->leftJoin(\'product_attribute_shop\', \'product_attribute_shop\', \'p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=\'.(int)$context->shop->id);
}
$sql->join(Product::sqlStock(\'p\', 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 = $ids[\'id_country\'] ? (int)$ids[\'id_country\'] : (int)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 int $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_products = \'\';
foreach ($product_reductions as $product_reduction)
$ids_products .= \'(\'.(int)$product_reduction[\'id_product\'].\',\'.($product_reduction[\'id_product_attribute\'] ? (int)$product_reduction[\'id_product_attribute\'] :\'0\').\'),\';

$ids_products = rtrim($ids_products, \',\');
Db::getInstance(_PS_USE_SQL_SLAVE_)->execute(\'CREATE TEMPORARY TABLE `\'._DB_PREFIX_.\'product_reductions` (id_product INT UNSIGNED NOT NULL DEFAULT 0, id_product_attribute INT UNSIGNED NOT NULL DEFAULT 0) ENGINE=MEMORY\', false);
if ($ids_products)
Db::getInstance(_PS_USE_SQL_SLAVE_)->execute(\'INSERT INTO `\'._DB_PREFIX_.\'product_reductions` VALUES \'.$ids_products, false);

$groups = FrontController::getCurrentCustomerGroups();
$sql_groups = \' AND EXISTS(SELECT 1 FROM `\'._DB_PREFIX_.\'category_product` cp
JOIN `\'._DB_PREFIX_.\'category_group` cg ON (cp.id_category = cg.id_category AND cg.`id_group` \'.(count($groups) ? \'IN (\'.implode(\',\', $groups).\')\' : \'= 1\').\')
WHERE cp.`id_product` = p.`id_product`)\';

// Please keep 2 distinct queries because RAND() is an awful way to achieve this result
$sql = \'SELECT product_shop.id_product, IFNULL(product_attribute_shop.id_product_attribute,0) id_product_attribute
FROM
`\'._DB_PREFIX_.\'product_reductions` pr,
`\'._DB_PREFIX_.\'product` p
\'.Shop::addSqlAssociation(\'product\', \'p\').\'
LEFT JOIN `\'._DB_PREFIX_.\'product_attribute_shop` product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=\'.(int)$context->shop->id.\')
WHERE p.id_product=pr.id_product AND (pr.id_product_attribute = 0 OR product_attribute_shop.id_product_attribute = pr.id_product_attribute) AND product_shop.`active` = 1
\'.$sql_groups.\'
\'.($front ? \' AND product_shop.`visibility` IN ("both", "catalog")\' : \'\').\'
ORDER BY RAND()\';

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

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

// no group by needed : there\'s only one attribute with cover=1 for a given id_product + shop
$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`, pl.`available_now`, pl.`available_later`,
p.`ean13`, p.`upc`, image_shop.`id_image` id_image, il.`legend`,
DATEDIFF(product_shop.`date_add`, DATE_SUB("\'.date(\'Y-m-d\').\' 00:00:00",
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_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=\'.(int)$context->shop->id.\')
LEFT JOIN `\'._DB_PREFIX_.\'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = \'.(int)$id_lang.\')
\'.Product::sqlStock(\'p\', 0).\'
WHERE p.id_product = \'.(int)$id_product;

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

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

/**
* Get prices drop
*
* @param int $id_lang Language id
* @param int $pageNumber Start from (optional)
* @param int $nbProducts Number of products to return (optional)
* @param bool $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\'; elseif ($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;

$sql_groups = \'\';
if (Group::isFeatureActive())
{
$groups = FrontController::getCurrentCustomerGroups();
$sql_groups = \' AND EXISTS(SELECT 1 FROM `\'._DB_PREFIX_.\'category_product` cp
JOIN `\'._DB_PREFIX_.\'category_group` cg ON (cp.id_category = cg.id_category AND cg.`id_group` \'.(count($groups) ? \'IN (\'.implode(\',\', $groups).\')\' : \'= 1\').\')
WHERE cp.`id_product` = p.`id_product`)\';
}

if ($count)
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(\'
SELECT COUNT(DISTINCT p.`id_product`)
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).\')\' : \'\').\'
\'.$sql_groups);
}

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`,
IFNULL(product_attribute_shop.id_product_attribute, 0) id_product_attribute,
pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`,
pl.`name`, image_shop.`id_image` id_image, il.`legend`, m.`name` AS manufacturer_name,
DATEDIFF(
p.`date_add`,
DATE_SUB(
"\'.date(\'Y-m-d\').\' 00:00:00",
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_shop` product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=\'.(int)$context->shop->id.\')
\'.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_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=\'.(int)$context->shop->id.\')
LEFT JOIN `\'._DB_PREFIX_.\'image_lang` il ON (image_shop.`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).\')\' : \'\').\'
\'.$sql_groups.\'
GROUP BY p.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 (!$result)
return false;

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

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 = \'\')
{
$cache_id = \'Product::getProductCategories_\'.(int)$id_product;
if (!Cache::isStored($cache_id))
{
$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\'];
Cache::store($cache_id, $ret);
return $ret;
}
return Cache::retrieve($cache_id);
}

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
);

$unique_array = array();
foreach ($data as $sub_array)
if (!in_array($sub_array, $unique_array))
$unique_array[] = $sub_array;

if (count($unique_array))
Db::getInstance()->insert(\'product_carrier\', $unique_array, false, true, Db::INSERT_IGNORE);
}

/**
* Get product images and legends
*
* @param int $id_lang Language id for multilingual legends
* @return array Product images and legends
*/
public function getImages($id_lang, Context $context = null)
{
return Db::getInstance()->executeS(\'
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`\'
);
}

/**
* Get product cover image
*
* @return array Product cover image
*/
public static function getCover($id_product, Context $context = null)
{
if (!$context)
$context = Context::getContext();
$cache_id = \'Product::getCover_\'.(int)$id_product.\'-\'.(int)$context->shop->id;
if (!Cache::isStored($cache_id))
{
$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\';
$result = Db::getInstance()->getRow($sql);
Cache::store($cache_id, $result);
return $result;
}
return Cache::retrieve($cache_id);
}

/**
* Returns product price
*
* @param int $id_product Product id
* @param bool $usetax With taxes or not (optional)
* @param int|null $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 int $decimals Number of decimals (optional)
* @param int|null $divisor Useful when paying many time without fees (optional)
* @param bool $only_reduc Returns only the reduction amount
* @param bool $usereduc Set if the returned amount will include reduction
* @param int $quantity Required for quantity discount application (default value: 1)
* @param bool $force_associated_tax DEPRECATED - NOT USED Force to apply the associated tax.
* Only works when the parameter $usetax is true
* @param int|null $id_customer Customer ID (for customer group reduction)
* @param int|null $id_cart Cart ID. Required when the cookie is not accessible
* (e.g., inside a payment module, a cron task...)
* @param int|null $id_address Customer address ID. Required for price (tax included)
* calculation regarding the guest localization
* @param null $specific_price_output If a specific price applies regarding the previous parameters,
* this variable is filled with the corresponding SpecificPrice object
* @param bool $with_ecotax Insert ecotax in price output.
* @param bool $use_group_reduction
* @param Context $context
* @param bool $use_customer_price
* @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 = null;
if ($id_customer)
$id_group = Customer::getDefaultGroupId((int)$id_customer);
if (!$id_group)
$id_group = (int)Group::getCurrent()->id;

// 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)
{
$cache_id = \'Product::getPriceStatic_\'.(int)$id_product.\'-\'.(int)$id_cart;
if (!Cache::isStored($cache_id) || ($cart_quantity = Cache::retrieve($cache_id) != (int)$quantity))
{
$sql = \'SELECT SUM(`quantity`)
FROM `\'._DB_PREFIX_.\'cart_product`
WHERE `id_product` = \'.(int)$id_product.\'
AND `id_cart` = \'.(int)$id_cart;
$cart_quantity = (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
Cache::store($cache_id, $cart_quantity);
}
else
$cart_quantity = Cache::retrieve($cache_id);
}

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

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

if (!$id_address && Validate::isLoadedObject($cur_cart))
$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\'];
}
}
elseif (isset($context->customer->geoloc_id_country))
{
$id_country = (int)$context->customer->geoloc_id_country;
$id_state = (int)$context->customer->id_state;
$zipcode = $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,
$quantity,
$usetax,
$decimals,
$only_reduc,
$usereduc,
$with_ecotax,
$specific_price_output,
$use_group_reduction,
$id_customer,
$use_customer_price,
$id_cart,
$cart_quantity
);

return $return;
}

/**
* Price calculation / Get product price
*
* @param int $id_shop Shop id
* @param int $id_product Product id
* @param int $id_product_attribute Product attribute id
* @param int $id_country Country id
* @param int $id_state State id
* @param string $zipcode
* @param int $id_currency Currency id
* @param int $id_group Group id
* @param int $quantity Quantity Required for Specific prices : quantity discount application
* @param bool $use_tax with (1) or without (0) tax
* @param int $decimals Number of decimals returned
* @param bool $only_reduc Returns only the reduction amount
* @param bool $use_reduc Set if the returned amount will include reduction
* @param bool $with_ecotax insert ecotax in price output.
* @param null $specific_price If a specific price applies regarding the previous parameters,
* this variable is filled with the corresponding SpecificPrice object
* @param bool $use_group_reduction
* @param int $id_customer
* @param bool $use_customer_price
* @param int $id_cart
* @param int $real_quantity
* @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 = (int)$id_product.\'-\'.(int)$id_shop.\'-\'.(int)$id_currency.\'-\'.(int)$id_country.\'-\'.$id_state.\'-\'.$zipcode.\'-\'.(int)$id_group.
\'-\'.(int)$quantity.\'-\'.(int)$id_product_attribute.
\'-\'.(int)$with_ecotax.\'-\'.(int)$id_customer.\'-\'.(int)$use_group_reduction.\'-\'.(int)$id_cart.\'-\'.(int)$real_quantity.
\'-\'.($only_reduc?\'1\':\'0\').\'-\'.($use_reduc?\'1\':\'0\').\'-\'.($use_tax?\'1\':\'0\').\'-\'.(int)$decimals;

// 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(\'IFNULL(product_attribute_shop.id_product_attribute,0) id_product_attribute, product_attribute_shop.`price` AS attribute_price, product_attribute_shop.default_on\');
$sql->leftJoin(\'product_attribute_shop\', \'product_attribute_shop\', \'(product_attribute_shop.id_product = p.id_product 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);

if (is_array($res) && count($res))
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);

// 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;
}

// Reduction
$specific_price_reduction = 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);

$specific_price_reduction = $reduction_amount;

// Adjust taxes if required

if (!$use_tax && $specific_price[\'reduction_tax\'])
$specific_price_reduction = $product_tax_calculator->removeTaxes($specific_price_reduction);
if ($use_tax && !$specific_price[\'reduction_tax\'])
$specific_price_reduction = $product_tax_calculator->addTaxes($specific_price_reduction);
}
else
$specific_price_reduction = $price * $specific_price[\'reduction\'];
}

if ($use_reduc)
$price -= $specific_price_reduction;

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

$price -= $group_reduction;
}

if ($only_reduc)
return Tools::ps_round($specific_price_reduction, $decimals);

$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 = $ids[\'id_country\'] ? (int)$ids[\'id_country\'] : (int)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 bool $tax With taxes or not (optional)
* @param int $id_product_attribute Product attribute id (optional)
* @param int $decimals Number of decimals (optional)
* @param int $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, $decimals = 6)
{
return Product::getPriceStatic((int)$this->id, !$notax, $id_product_attribute, $decimals, 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 string 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 Smarty $smarty DEPRECATED
* @return string 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 int $id_product Product id
* @param int $id_product_attribute Product attribute id (optional)
* @return int Available quantities
*/
public static function getQuantity($id_product, $id_product_attribute = null, $cache_is_pack = null)
{
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 = null, $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)
*
* @deprecated 1.5.3.0
* @return false
*/
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
*
* @deprecated 1.5.3.0
* @see StockManager if you want to manage real stock
* @see StockAvailable if you want to manage available quantities for sale on your shop(s)
* @return false
*/
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
static $ps_stock_management = null;
if ($ps_stock_management === null)
$ps_stock_management = Configuration::get(\'PS_STOCK_MANAGEMENT\');

if (!$ps_stock_management)
return true;
else
{
static $ps_order_out_of_stock = null;
if ($ps_order_out_of_stock === null)
$ps_order_out_of_stock = Configuration::get(\'PS_ORDER_OUT_OF_STOCK\');

return (int)$out_of_stock == 2 ? (int)$ps_order_out_of_stock : (int)$out_of_stock;
}
}

/**
* Check product availability
*
* @param int $qty Quantity desired
* @return bool 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) > Shop::getTotalShops(true))
Db::getInstance()->execute(\'UPDATE \'._DB_PREFIX_.\'product_attribute_shop product_attribute_shop, \'._DB_PREFIX_.\'product_attribute pa
SET product_attribute_shop.default_on=NULL, pa.default_on = NULL
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 pa.`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;
}

public static function getAttributesColorList(Array $products, $have_stock = true)
{
if (!count($products))
return array();

$id_lang = Context::getContext()->language->id;

$check_stock = !Configuration::get(\'PS_DISP_UNAVAILABLE_ATTR\');
if (!$res = Db::getInstance()->executeS(\'
SELECT pa.`id_product`, a.`color`, pac.`id_product_attribute`, \'.($check_stock ? \'SUM(IF(stock.`quantity` > 0, 1, 0))\' : \'0\').\' qty, a.`id_attribute`, al.`name`, IF(color = "", a.id_attribute, color) group_by
FROM `\'._DB_PREFIX_.\'product_attribute` pa
\'.Shop::addSqlAssociation(\'product_attribute\', \'pa\').
($check_stock ? Product::sqlStock(\'pa\', \'pa\') : \'\').\'
JOIN `\'._DB_PREFIX_.\'product_attribute_combination` pac ON (pac.`id_product_attribute` = product_attribute_shop.`id_product_attribute`)
JOIN `\'._DB_PREFIX_.\'attribute` a ON (a.`id_attribute` = pac.`id_attribute`)
JOIN `\'._DB_PREFIX_.\'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = \'.(int)$id_lang.\')
JOIN `\'._DB_PREFIX_.\'attribute_group` ag ON (a.id_attribute_group = ag.`id_attribute_group`)
WHERE pa.`id_product` IN (\'.implode(array_map(\'intval\', $products), \',\').\') AND ag.`is_color_group` = 1
GROUP BY pa.`id_product`, a.`id_attribute`, `group_by`
\'.($check_stock ? \'HAVING qty > 0\' : \'\').\'
ORDER BY a.`position` ASC;\'
)
)
return false;

$colors = array();
foreach ($res as $row)
{
if (Tools::isEmpty($row[\'color\']) && !@filemtime(_PS_COL_IMG_DIR_.$row[\'id_attribute\'].\'.jpg\'))
continue;

$colors[(int)$row[\'id_product\']][] = array(\'id_product_attribute\' => (int)$row[\'id_product_attribute\'], \'color\' => $row[\'color\'], \'id_product\' => $row[\'id_product\'], \'name\' => $row[\'name\'], \'id_attribute\' => $row[\'id_attribute\']);
}

return $colors;
}

/**
* Get all available attribute groups
*
* @param int $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, product_attribute_shop.`id_product_attribute`,
IFNULL(stock.quantity, 0) as quantity, product_attribute_shop.`price`, product_attribute_shop.`ecotax`, product_attribute_shop.`weight`,
product_attribute_shop.`default_on`, pa.`reference`, product_attribute_shop.`unit_price_impact`,
product_attribute_shop.`minimal_quantity`, product_attribute_shop.`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 int $id_lang Language id
* @param int $id_product Product id
* @return array Product accessories
*/
public static function getAccessoriesLight($id_lang, $id_product)
{
return Db::getInstance()->executeS(\'
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
);
}

/**
* Get product accessories
*
* @param int $id_lang Language id
* @return array Product accessories
*/
public function getAccessories($id_lang, $active = true)
{
$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`, pl.`available_now`, pl.`available_later`,
image_shop.`id_image` id_image, il.`legend`, m.`name` as manufacturer_name, cl.`name` AS category_default, IFNULL(product_attribute_shop.id_product_attribute, 0) id_product_attribute,
DATEDIFF(
p.`date_add`,
DATE_SUB(
"\'.date(\'Y-m-d\').\' 00:00:00",
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_attribute_shop` product_attribute_shop
ON (p.`id_product` = product_attribute_shop.`id_product` AND product_attribute_shop.`default_on` = 1 AND product_attribute_shop.id_shop=\'.(int)$this->id_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_.\'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_shop` image_shop
ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop=\'.(int)$this->id_shop.\')
LEFT JOIN `\'._DB_PREFIX_.\'image_lang` il ON (image_shop.`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 AND product_shop.`visibility` != \'none\'\' : \'\').\'
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\' => $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 fp.id_feature, fp.id_product, fp.id_feature_value, custom
FROM `\'._DB_PREFIX_.\'feature_product` fp
LEFT JOIN `\'._DB_PREFIX_.\'feature_value` fv ON (fp.id_feature_value = fv.id_feature_value)
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)
\'.Shop::addSqlAssociation(\'feature\', \'f\').\'
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 int $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.`ean13`, p.`upc`, p.`active`, p.`reference`, m.`name` AS manufacturer_name, stock.`quantity`, product_shop.advanced_stock_management, p.`customizable`\');
$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(\'manufacturer\', \'m\', \'m.`id_manufacturer` = p.`id_manufacturer`\');

$where = \'pl.`name` LIKE \'%\'.pSQL($query).\'%\'
OR p.`ean13` LIKE \'%\'.pSQL($query).\'%\'
OR p.`upc` LIKE \'%\'.pSQL($query).\'%\'
OR p.`reference` LIKE \'%\'.pSQL($query).\'%\'
OR p.`supplier_reference` LIKE \'%\'.pSQL($query).\'%\'
OR EXISTS(SELECT * FROM `\'._DB_PREFIX_.\'product_supplier` sp WHERE sp.`id_product` = p.`id_product` AND `product_supplier_reference` LIKE \'%\'.pSQL($query).\'%\')\';

$sql->orderBy(\'pl.`name` ASC\');

if (Combination::isFeatureActive())
{
$where .= \' OR EXISTS(SELECT * FROM `\'._DB_PREFIX_.\'product_attribute` `pa` WHERE pa.`id_product` = p.`id_product` AND (pa.`reference` LIKE \'%\'.pSQL($query).\'%\'
OR pa.`supplier_reference` LIKE \'%\'.pSQL($query).\'%\'
OR pa.`ean13` LIKE \'%\'.pSQL($query).\'%\'
OR pa.`upc` LIKE \'%\'.pSQL($query).\'%\'))\';
}
$sql->where($where);
$sql->join(Product::sqlStock(\'p\', 0));

$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 int $id_product_old Old product id
* @param int $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
);
$combinations = array();

foreach ($result as $row)
{
$id_product_attribute_old = (int)$row[\'id_product_attribute\'];
if (!isset($combinations[$id_product_attribute_old]))
{
$id_combination = null;
$id_shop = null;
$result2 = Db::getInstance()->executeS(\'
SELECT *
FROM `\'._DB_PREFIX_.\'product_attribute_combination`
WHERE `id_product_attribute` = \'.$id_product_attribute_old
);
}
else
{
$id_combination = (int)$combinations[$id_product_attribute_old];
$id_shop = (int)$row[\'id_shop\'];
$context_old = Shop::getContext();
$context_shop_id_old = Shop::getContextShopID();
Shop::setContext(Shop::CONTEXT_SHOP, $id_shop);

}

$row[\'id_product\'] = $id_product_new;
unset($row[\'id_product_attribute\']);

$combination = new Combination($id_combination, null, $id_shop);
foreach ($row as $k => $v)
$combination->$k = $v;
$return &= $combination->save();

$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;
}

if (!isset($combinations[$id_product_attribute_old]))
{
$combinations[$id_product_attribute_old] = (int)$id_product_attribute_new;
foreach ($result2 as $row2)
{
$row2[\'id_product_attribute\'] = $id_product_attribute_new;
$return &= Db::getInstance()->insert(\'product_attribute_combination\', $row2);
}
}
else
Shop::setContext($context_old, $context_shop_id_old);

//Copy suppliers
$result3 = Db::getInstance()->executeS(\'
SELECT *
FROM `\'._DB_PREFIX_.\'product_supplier`
WHERE `id_product_attribute` = \'.(int)$id_product_attribute_old.\'
AND `id_product` = \'.(int)$id_product_old);

foreach ($result3 as $row3)
{
unset($row3[\'id_product_supplier\']);
$row3[\'id_product\'] = $id_product_new;
$row3[\'id_product_attribute\'] = $id_product_attribute_new;
$return &= Db::getInstance()->insert(\'product_supplier\', $row3);
}
}

$impacts = self::getAttributesImpacts($id_product_old);

if (is_array($impacts) && count($impacts))
{
$impact_sql = \'INSERT INTO `\'._DB_PREFIX_.\'attribute_impact` (`id_product`, `id_attribute`, `weight`, `price`) VALUES \';

foreach ($impacts as $id_attribute => $impact)
$impact_sql .= \'(\'.(int)$id_product_new.\', \'.(int)$id_attribute.\', \'.(float)$impacts[$id_attribute][\'weight\'].\', \'
.(float)$impacts[$id_attribute][\'price\'].\'),\';

$impact_sql = substr_replace($impact_sql, \'\', -1);
$impact_sql .= \' ON DUPLICATE KEY UPDATE `price` = VALUES(price), `weight` = VALUES(weight)\';

Db::getInstance()->execute($impact_sql);
}

return !$return ? false : $combination_images;
}

public static function getAttributesImpacts($id_product)
{
$return = array();
$result = Db::getInstance()->executeS(
\'SELECT ai.`id_attribute`, ai.`price`, ai.`weight`
FROM `\'._DB_PREFIX_.\'attribute_impact` ai
WHERE ai.`id_product` = \'.(int)$id_product);

if (!$result)
return array();
foreach ($result as $impact)
{
$return[$impact[\'id_attribute\']][\'price\'] = (float)$impact[\'price\'];
$return[$impact[\'id_attribute\']][\'weight\'] = (float)$impact[\'weight\'];
}
return $return;
}

/**
* Get product attribute image associations
* @param int $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`, `id_lang` 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\'],
\'id_lang\' => (int)$tag[\'id_lang\'],
);

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();
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\')
);
}
return Db::getInstance()->insert(\'product_download\', $data);
}

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 int $id_product_old Old product id
* @param int $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\'];

foreach (Language::getIDs(false) as $id_lang)
{
$result3 = Db::getInstance()->getRow(\'
SELECT *
FROM `\'._DB_PREFIX_.\'feature_value_lang`
WHERE `id_feature_value` = \'.(int)$old_id_feature_value.\'
AND `id_lang` = \'.(int)$id_lang);

if ($result3)
{
$result3[\'id_feature_value\'] = (int)$new_id_feature_value;
$result3[\'value\'] = pSQL($result3[\'value\']);
$return &= Db::getInstance()->insert(\'feature_value_lang\', $result3);
}
}
$row[\'id_feature_value\'] = $new_id_feature_value;
}

$row[\'id_product\'] = (int)$id_product_new;
$return &= Db::getInstance()->insert(\'feature_product\', $row);
}
return $return;
}

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

if (Shop::isFeatureActive() && !$id_shop)
$id_shop = (int)Context::getContext()->shop->id;

$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).\')\'.($id_shop ? \' AND cfl.`id_shop` = \'.$id_shop : \'\').\'
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\']))
{
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;
}

/**
* Adds suppliers from old product onto a newly duplicated product
*
* @param int $id_product_old
* @param int $id_product_new
*/
public static function duplicateSuppliers($id_product_old, $id_product_new)
{
$result = Db::getInstance()->executeS(\'
SELECT *
FROM `\'._DB_PREFIX_.\'product_supplier`
WHERE `id_product` = \'.(int)$id_product_old.\' AND `id_product_attribute` = 0\');

foreach ($result as $row)
{
unset($row[\'id_product_supplier\']);
$row[\'id_product\'] = $id_product_new;
if (!Db::getInstance()->insert(\'product_supplier\', $row))
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 && array_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\']) && $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();

$id_product_attribute = $row[\'id_product_attribute\'] = (!empty($row[\'id_product_attribute\']) ? (int)$row[\'id_product_attribute\'] : null);

// 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() && $id_product_attribute === null
&& ((isset($row[\'cache_default_attribute\']) && ($ipa_default = $row[\'cache_default_attribute\']) !== null)
|| ($ipa_default = Product::getDefaultAttribute($row[\'id_product\'], !$row[\'allow_oosp\']))))
$id_product_attribute = $row[\'id_product_attribute\'] = $ipa_default;
if (!Combination::isFeatureActive() || !isset($row[\'id_product_attribute\']))
$id_product_attribute = $row[\'id_product_attribute\'] = 0;

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

$cache_key = $row[\'id_product\'].\'-\'.$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 ($id_product_attribute)
$row[\'attribute_price\'] = (float)Product::getProductAttributePrice($id_product_attribute);

$row[\'price_tax_exc\'] = Product::getPriceStatic(
(int)$row[\'id_product\'],
false,
$id_product_attribute,
(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,
$id_product_attribute,
6
);
$row[\'price_without_reduction\'] = Product::getPriceStatic(
(int)$row[\'id_product\'],
false,
$id_product_attribute,
2,
null,
false,
false
);
}
else
{
$row[\'price\'] = Tools::ps_round(
Product::getPriceStatic(
(int)$row[\'id_product\'],
true,
$id_product_attribute,
6
),
(int)Configuration::get(\'PS_PRICE_DISPLAY_PRECISION\')
);
$row[\'price_without_reduction\'] = Product::getPriceStatic(
(int)$row[\'id_product\'],
true,
$id_product_attribute,
6,
null,
false,
false
);
}

$row[\'reduction\'] = Product::getPriceStatic(
(int)$row[\'id_product\'],
(bool)$usetax,
$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
);

$row[\'quantity_all_versions\'] = $row[\'quantity\'];

if ($row[\'id_product_attribute\'])
$row[\'quantity\'] = Product::getQuantity(
(int)$row[\'id_product\'],
$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[\'customization_required\'] = false;
if (isset($row[\'customizable\']) && $row[\'customizable\'] && Customization::isFeatureActive())
if (count(Product::getRequiredCustomizableFieldsStatic((int)$row[\'id_product\'])))
$row[\'customization_required\'] = true;

$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.\')
\'.Shop::addSqlAssociation(\'feature\', \'f\').\'
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, $id_shop = null)
{
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 (Shop::isFeatureActive() && !$id_shop)
$id_shop = (int)Context::getContext()->shop->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.
($id_shop ? \' AND cfl.`id_shop` = \'.$id_shop : \'\').\')
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 = isset($product_update[\'id_product\']) ? (int)$product_update[\'id_product\'] : (int)$product_update[\'product_id\'];
$product_attribute_id = isset($product_update[\'id_product_attribute\']) ? (int)$product_update[\'id_product_attribute\'] : (int)$product_update[\'product_attribute_id\'];
$id_address_delivery = (int)$product_update[\'id_address_delivery\'];
$product_quantity = isset($product_update[\'cart_quantity\']) ? (int)$product_update[\'cart_quantity\'] : (int)$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][$id_address_delivery]))
$id_address_delivery = 0;
if (isset($customized_datas[$product_id][$product_attribute_id][$id_address_delivery]))
{
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)$this->uploadable_files,
Product::CUSTOMIZE_TEXTFIELD => (int)$this->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 `\'._DB_PREFIX_.\'customization_field`,`\'._DB_PREFIX_.\'customization_field_lang`
FROM `\'._DB_PREFIX_.\'customization_field` JOIN `\'._DB_PREFIX_.\'customization_field_lang`
WHERE `\'._DB_PREFIX_.\'customization_field`.`id_product` = \'.(int)$this->id.\'
AND `\'._DB_PREFIX_.\'customization_field`.`type` = \'.Product::CUSTOMIZE_FILE.\'
AND `\'._DB_PREFIX_.\'customization_field_lang`.`id_customization_field` = `\'._DB_PREFIX_.\'customization_field`.`id_customization_field`
AND `\'._DB_PREFIX_.\'customization_field`.`id_customization_field` >= \'.(int)$customization_fields[Product::CUSTOMIZE_FILE][count($customization_fields[Product::CUSTOMIZE_FILE]) - $extra_file]
)))
return false;

if ($extra_text > 0 && count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $extra_text >= 0 &&
(!Db::getInstance()->execute(
\'DELETE `\'._DB_PREFIX_.\'customization_field`,`\'._DB_PREFIX_.\'customization_field_lang`
FROM `\'._DB_PREFIX_.\'customization_field` JOIN `\'._DB_PREFIX_.\'customization_field_lang`
WHERE `\'._DB_PREFIX_.\'customization_field`.`id_product` = \'.(int)$this->id.\'
AND `\'._DB_PREFIX_.\'customization_field`.`type` = \'.Product::CUSTOMIZE_TEXTFIELD.\'
AND `\'._DB_PREFIX_.\'customization_field_lang`.`id_customization_field` = `\'._DB_PREFIX_.\'customization_field`.`id_customization_field`
AND `\'._DB_PREFIX_.\'customization_field`.`id_customization_field` >= \'.(int)$customization_fields[Product::CUSTOMIZE_TEXTFIELD][count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $extra_text]
)))
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)
foreach (Shop::getContextListShopID() as $id_shop)
$values .= \'(\'.(int)$id_customization_field.\', \'.(int)$language[\'id_lang\'].\', \'.$id_shop .\',\'\'), \';

$values = rtrim($values, \', \');
if (!Db::getInstance()->execute(\'
INSERT INTO `\'._DB_PREFIX_.\'customization_field_lang` (`id_customization_field`, `id_lang`, `id_shop`, `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 (Shop::isFeatureActive())
{
foreach (Shop::getContextListShopID() as $id_shop)
if (!Db::getInstance()->execute(\'INSERT INTO `\'._DB_PREFIX_.\'customization_field_lang`
(`id_customization_field`, `id_lang`, `id_shop`, `name`) VALUES (\'.(int)$tmp[2].\', \'.(int)$tmp[3].\', \'.$id_shop.\', \'\'.pSQL($value).\'\')
ON DUPLICATE KEY UPDATE `name` = \'\'.pSQL($value).\'\'\'))
return false;
}
elseif (!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, $id_shop = null)
{
if (!Customization::isFeatureActive())
return false;

if (Shop::isFeatureActive() && !$id_shop)
$id_shop = (int)Context::getContext()->shop->id;

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 : \'\').
($id_shop ? \' AND cfl.`id_shop` = \'.$id_shop : \'\').\'
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 Product::getRequiredCustomizableFieldsStatic($this->id);
}

public static function getRequiredCustomizableFieldsStatic($id)
{
if (!$id || !Customization::isFeatureActive())
return array();
return Db::getInstance()->executeS(\'
SELECT `id_customization_field`, `type`
FROM `\'._DB_PREFIX_.\'customization_field`
WHERE `id_product` = \'.(int)$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\']);

if (is_array($required_fields) && count($required_fields))
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 bool 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((int)$this->id);
}

public function checkAccess($id_customer)
{
return Product::checkAccessStatic((int)$this->id, (int)$id_customer);
}

public static function checkAccessStatic($id_product, $id_customer)
{
if (!Group::isFeatureActive())
return true;

$cache_id = \'Product::checkAccess_\'.(int)$id_product.\'-\'.(int)$id_customer.(!$id_customer ? \'-\'.(int)Group::getCurrent()->id : \'\');
if (!Cache::isStored($cache_id))
{
if (!$id_customer)
$result = (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)$id_product.\' AND ctg.`id_group` = \'.(int)Group::getCurrent()->id);
else
$result = (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)$id_product.\' AND cg.`id_customer` = \'.(int)$id_customer);

Cache::store($cache_id, $result);
return $result;
}
return Cache::retrieve($cache_id);
}

/**
* 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 - DEPRECATED
* @param int $id_employee - DEPRECATED
* @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))
{
$result = 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);
Cache::store($key, (int)$result);
return (int)$result;
}
return Cache::retrieve($key);
}

/**
* Returns tax rate.
*
* @param Address|null $address
* @return float 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\']);
unset($rows[$keyrow][\'custom\']);
}
asort($rows[$keyrow]);
}
return $rows;
}

/**
* Webservice setter : set product features association
*
* @param $product_features Product Feature ids
* @return bool
*/
public function setWsProductFeatures($product_features)
{
Db::getInstance()->execute(\'
DELETE FROM `\'._DB_PREFIX_.\'feature_product`
WHERE `id_product` = \'.(int)$this->id
);
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 int $id_combination id default combination
* @return bool
*/
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 array $category_ids category ids
* @return bool
*/
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)
);
Hook::exec(\'updateProduct\', array(\'id_product\' => (int)$this->id));
return $result;
}
}
Hook::exec(\'updateProduct\', array(\'id_product\' => (int)$this->id));
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)
{
$this->deleteAccessories();
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 pa.`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 pa.`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 setter : set virtual field position in category
*
* @return bool
*/
public function setWsPositionInCategory($position)
{
if ($position < 0) WebserviceRequest::getInstance()->setError(500, Tools::displayError(\'You cannot set a negative position, the minimum for a position is 0.\'), 134);
$result = Db::getInstance()->executeS(\'
SELECT `id_product`
FROM `\'._DB_PREFIX_.\'category_product`
WHERE `id_category` = \'.(int)$this->id_category_default.\'
ORDER BY `position`
\');
if (($position > 0) && ($position + 1 > count($result)))
WebserviceRequest::getInstance()->setError(500, Tools::displayError(\'You cannot set a position greater than the total number of products in the category, minus 1 (position numbering starts at 0).\'), 135);

foreach ($result as &$value)
$value = $value[\'id_product\'];
$current_position = $this->getWsPositionInCategory();

if ($current_position && isset($result[$current_position]))
{
$save = $result[$current_position];
unset($result[$current_position]);
array_splice($result, (int)$position, 0, $save);
}

foreach ($result as $position => $id_product)
{
Db::getInstance()->update(\'category_product\', array(
\'position\' => $position,
), \'`id_category` = \'.(int)$this->id_category_default.\' AND `id_product` = \'.(int)$id_product);
}
return true;
}

/**
* 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);
}

/**
* Webservice setter : set tag ids of current product for association
*
* @param $tag_ids tag ids
*/
public function setWsTags($tag_ids)
{
$ids = array();
foreach ($tag_ids as $value)
$ids[] = $value[\'id\'];
if ($this->deleteWsTags())
{
if ($ids)
{
$sql_values = \'\';
$ids = array_map(\'intval\', $ids);
foreach ($ids as $position => $id)
{
$id_lang = Db::getInstance()->getValue(\'SELECT `id_lang` FROM `\'._DB_PREFIX_.\'tag` WHERE `id_tag`=\'.(int)$id);
$sql_values[] = \'(\'.(int)$this->id.\', \'.(int)$id.\', \'.(int)$id_lang.\')\';
}
$result = Db::getInstance()->execute(\'
INSERT INTO `\'._DB_PREFIX_.\'product_tag` (`id_product`, `id_tag`, `id_lang`)
VALUES \'.implode(\',\', $sql_values)
);
return $result;
}
}
return true;
}

/**
* Delete products tags entries without delete tags for webservice usage
*
* @return array Deletion result
*/
public function deleteWsTags()
{
return Db::getInstance()->delete(\'product_tag\', \'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);
}

/**
* Checks if reference exists
* @return bool
*/
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)
{
$id_lang = (int)Context::getContext()->language->id;
$id_shop = (int)Context::getContext()->shop->id;
$cache_id = \'Product::getAttributesParams_\'.(int)$id_product.\'-\'.(int)$id_product_attribute.\'-\'.(int)$id_lang.\'-\'.(int)$id_shop;

// if blocklayered module is installed we check if user has set custom attribute name
if (Module::isInstalled(\'blocklayered\') && Module::isEnabled(\'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)$id_lang.\')
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 a.`id_attribute`, 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)$id_lang.\'
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)$id_lang.\'
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`, 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)$id_lang.\')
LEFT JOIN `\'._DB_PREFIX_.\'attribute_group_lang` agl
ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = \'.(int)$id_lang.\')
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).\')\');
return array_merge($values_not_custom, $result);
}
}

if (!Cache::isStored($cache_id))
{
$result = Db::getInstance()->executeS(\'
SELECT a.`id_attribute`, a.`id_attribute_group`, 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)$id_lang.\')
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)$id_lang.\')
WHERE pa.`id_product` = \'.(int)$id_product.\'
AND pac.`id_product_attribute` = \'.(int)$id_product_attribute.\'
AND agl.`id_lang` = \'.(int)$id_lang);
Cache::store($cache_id, $result);
}
else
$result = Cache::retrieve($cache_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\') && Module::isEnabled(\'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 int $id_product_attribute
* @return string
*/
public function getAnchor($id_product_attribute, $with_id = false)
{
$attributes = Product::getAttributesParams($this->id, $id_product_attribute);
$anchor = \'#\';
$sep = Configuration::get(\'PS_ATTRIBUTE_ANCHOR_SEPARATOR\');
foreach ($attributes as &$a)
{
foreach ($a as &$b)
$b = str_replace($sep, \'_\', Tools::link_rewrite($b));
$anchor .= \'/\'.($with_id && isset($a[\'id_attribute\']) && $a[\'id_attribute\']? (int)$a[\'id_attribute\'].$sep : \'\').$a[\'group\'].$sep.$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);
Hook::exec(\'updateProduct\', array(\'id_product\' => (int)$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
*
* @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->setFieldsToUpdate(array(\'advanced_stock_management\' => true));
$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 PrestaShopCollection(\'ProductDownload\');
$collection_download->where(\'id_product\', \'=\', $this->id);
foreach ($collection_download as $product_download)
{
/** @var ProductDownload $product_download */
$result &= $product_download->delete($product_download->checkFile());
}
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 AND trg.deleted = 0
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()];
}

/*
Create the link rewrite if not exists or invalid on product creation
*/
public function modifierWsLinkRewrite()
{
foreach ($this->name as $id_lang => $name)
{
if (empty($this->link_rewrite[$id_lang]))
$this->link_rewrite[$id_lang] = Tools::link_rewrite($name);
elseif (!Validate::isLinkRewrite($this->link_rewrite[$id_lang]))
$this->link_rewrite[$id_lang] = Tools::link_rewrite($this->link_rewrite[$id_lang]);
}

return true;
}

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);
}

public function setWsType($type_str)
{
$reverse_type_information = array(
\'simple\' => Product::PTYPE_SIMPLE,
\'pack\' => Product::PTYPE_PACK,
\'virtual\' => Product::PTYPE_VIRTUAL,
);

if (!isset($reverse_type_information[$type_str]))
return false;

$type = $reverse_type_information[$type_str];

if (Pack::isPack((int)$this->id) && $type != Product::PTYPE_PACK)
Pack::deleteItems($this->id);

$this->cache_is_pack = ($type == Product::PTYPE_PACK);
$this->is_virtual = ($type == Product::PTYPE_VIRTUAL);

return true;
}

public function setWsProductBundle($items)
{
if ($this->is_virtual)
return false;

Pack::deleteItems($this->id);

foreach ($items as $item)
if ((int)$item[\'id\'] > 0)
Pack::addItem($this->id, (int)$item[\'id\'], (int)$item[\'quantity\']);
return true;
}

public function isColorUnavailable($id_attribute, $id_shop)
{
return Db::getInstance()->getValue(\'
SELECT sa.id_product_attribute
FROM \'._DB_PREFIX_.\'stock_available sa
WHERE id_product=\'.(int)$this->id.\' AND quantity <= 0 \'.StockAvailable::addSqlShopRestriction(null, $id_shop, \'sa\').\' AND EXISTS ( SELECT 1 FROM \'._DB_PREFIX_.\'product_attribute pa JOIN \'._DB_PREFIX_.\'product_attribute_shop product_attribute_shop ON (product_attribute_shop.id_product_attribute = pa.id_product_attribute AND product_attribute_shop.id_shop=\'.(int)$id_shop.\') JOIN \'._DB_PREFIX_.\'product_attribute_combination pac ON (pac.id_product_attribute AND product_attribute_shop.id_product_attribute) WHERE sa.id_product_attribute = pa.id_product_attribute AND pa.id_product=\'.(int)$this->id.\' AND pac.id_attribute=\'.(int)$id_attribute.\'
)\'
);
}

public static function getColorsListCacheId($id_product)
{
return \'productlist_colors|\'.(int)$id_product.\'|\'.(int)Context::getContext()->shop->id.\'|\'.(int)Context::getContext()->cookie->id_lang;
}

public static function setPackStockType($id_product, $pack_stock_type)
{
return Db::getInstance()->execute(\'UPDATE \'._DB_PREFIX_.\'product p
\'.Shop::addSqlAssociation(\'product\', \'p\').\' SET product_shop.pack_stock_type = \'.(int)$pack_stock_type.\' WHERE p.`id_product` = \'.(int)$id_product);
}
}

Ensuite faire la même manipulation pour le FeatureValue.php
collez le code suivant:

<?php

/*
* 2007-2015 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 license@prestashop.com 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 <contact@prestashop.com>
* @copyright 2007-2015 PrestaShop SA
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* International Registered Trademark & Property of PrestaShop SA
*/

class FeatureValueCore extends ObjectModel
{
/** @var int Group id which attribute belongs */
public $id_feature;

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

/** @var bool Custom */
public $custom = 0;

/**
* @see ObjectModel::$definition
*/
public static $definition = array(
\'table\' => \'feature_value\',
\'primary\' => \'id_feature_value\',
\'multilang\' => true,
\'fields\' => array(
\'id_feature\' => array(\'type\' => self::TYPE_INT, \'validate\' => \'isUnsignedId\', \'required\' => true),
\'custom\' => array(\'type\' => self::TYPE_BOOL, \'validate\' => \'isBool\'),

/* Lang fields */
\'value\' => array(\'type\' => self::TYPE_STRING, \'lang\' => true, \'validate\' => \'isCleanHtml\', \'required\' => true, \'size\' => 4500000000000),
),
);

protected $webserviceParameters = array(
\'objectsNodeName\' => \'product_feature_values\',
\'objectNodeName\' => \'product_feature_value\',
\'fields\' => array(
\'id_feature\' => array(\'xlink_resource\'=> \'product_features\'),
),
);

/**
* Get all values for a given feature
*
* @param bool $id_feature Feature id
* @return array Array with feature\'s values
*/
public static function getFeatureValues($id_feature)
{
return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(\'
SELECT *
FROM `\'._DB_PREFIX_.\'feature_value`
WHERE `id_feature` = \'.(int)$id_feature
);
}

/**
* Get all values for a given feature and language
*
* @param int $id_lang Language id
* @param bool $id_feature Feature id
* @return array Array with feature\'s values
*/
public static function getFeatureValuesWithLang($id_lang, $id_feature, $custom = false)
{
return Db::getInstance()->executeS(\'
SELECT *
FROM `\'._DB_PREFIX_.\'feature_value` v
LEFT JOIN `\'._DB_PREFIX_.\'feature_value_lang` vl
ON (v.`id_feature_value` = vl.`id_feature_value` AND vl.`id_lang` = \'.(int)$id_lang.\')
WHERE v.`id_feature` = \'.(int)$id_feature.\'
\'.(!$custom ? \'AND (v.`custom` IS NULL OR v.`custom` = 0)\' : \'\').\'
ORDER BY vl.`value` ASC
\');
}

/**
* Get all language for a given value
*
* @param bool $id_feature_value Feature value id
* @return array Array with value\'s languages
*/
public static function getFeatureValueLang($id_feature_value)
{
return Db::getInstance()->executeS(\'
SELECT *
FROM `\'._DB_PREFIX_.\'feature_value_lang`
WHERE `id_feature_value` = \'.(int)$id_feature_value.\'
ORDER BY `id_lang`
\');
}

/**
* Select the good lang in tab
*
* @param array $lang Array with all language
* @param int $id_lang Language id
* @return string String value name selected
*/
public static function selectLang($lang, $id_lang)
{
foreach ($lang as $tab)
if ($tab[\'id_lang\'] == $id_lang)
return $tab[\'value\'];
}

public static function addFeatureValueImport($id_feature, $value, $id_product = null, $id_lang = null, $custom = false)
{
$id_feature_value = false;
if (!is_null($id_product) && $id_product)
{
$id_feature_value = Db::getInstance()->getValue(\'
SELECT fp.`id_feature_value`
FROM \'._DB_PREFIX_.\'feature_product fp
INNER JOIN \'._DB_PREFIX_.\'feature_value fv USING (`id_feature_value`)
WHERE fp.`id_feature` = \'.(int)$id_feature.\'
AND fv.`custom` = \'.(int)$custom.\'
AND fp.`id_product` = \'.(int)$id_product);

if ($custom && $id_feature_value && !is_null($id_lang) && $id_lang)
Db::getInstance()->execute(\'
UPDATE \'._DB_PREFIX_.\'feature_value_lang
SET `value` = \'\'.pSQL($value).\'\'
WHERE `id_feature_value` = \'.(int)$id_feature_value.\'
AND `value` != \'\'.pSQL($value).\'\'
AND `id_lang` = \'.(int)$id_lang);
}

if (!$custom)
$id_feature_value = Db::getInstance()->getValue(\'
SELECT fv.`id_feature_value`
FROM \'._DB_PREFIX_.\'feature_value fv
LEFT JOIN \'._DB_PREFIX_.\'feature_value_lang fvl ON (fvl.`id_feature_value` = fv.`id_feature_value` AND fvl.`id_lang` = \'.(int)$id_lang.\')
WHERE `value` = \'\'.pSQL($value).\'\'
AND fv.`id_feature` = \'.(int)$id_feature.\'
AND fv.`custom` = 0
GROUP BY fv.`id_feature_value`\');

if ($id_feature_value)
return (int)$id_feature_value;

// Feature doesn\'t exist, create it
$feature_value = new FeatureValue();
$feature_value->id_feature = (int)$id_feature;
$feature_value->custom = (bool)$custom;
$feature_value->value = array_fill_keys(Language::getIDs(false), $value);
$feature_value->add();

return (int)$feature_value->id;
}

public function add($autodate = true, $nullValues = false)
{
$return = parent::add($autodate, $nullValues);
if ($return)
Hook::exec(\'actionFeatureValueSave\', array(\'id_feature_value\' => $this->id));
return $return;
}

public function delete()
{
/* Also delete related products */
Db::getInstance()->execute(\'
DELETE FROM `\'._DB_PREFIX_.\'feature_product`
WHERE `id_feature_value` = \'.(int)$this->id
);
$return = parent::delete();

if ($return)
Hook::exec(\'actionFeatureValueDelete\', array(\'id_feature_value\' => $this->id));
return $return;
}

public function update($nullValues = false)
{
$return = parent::update($nullValues);
if ($return)
Hook::exec(\'actionFeatureValueSave\', array(\'id_feature_value\' => $this->id));
return $return;
}
}

Pour terminer, supprimez le fichier class_index.php qui de situe dans le dossier /cache.

Faites un test
2015-07-29_130649
et voila vous pouvez désormais ajouter du HTML dans les caratéristiques produits !

Pour le détail:
Dans la classe FeatureValue on a modifié le type de dans isCleanHtml puis agrandi la taille maximum du champ.
Pour la classe Product on a modifier la méthode addFeaturesCustomToDB et on a supprimé la fonction pSQL() avant l\’ajout en base de donnée.

Enjoy 😉

Guillaume

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.