PageRenderTime 104ms CodeModel.GetById 41ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/Product.php

https://bitbucket.org/yhjohn/ayanapure.com
PHP | 5036 lines | 3563 code | 609 blank | 864 comment | 473 complexity | 84d708637d2f5db0d20dceb03059be96 MD5 | raw file
Possible License(s): LGPL-2.1, LGPL-3.0
  1. <?php
  2. /*
  3. * 2007-2012 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2012 PrestaShop SA
  23. * @version Release: $Revision: 7506 $
  24. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  25. * International Registered Trademark & Property of PrestaShop SA
  26. */
  27. /**
  28. * @deprecated 1.5.0.1
  29. */
  30. define('_CUSTOMIZE_FILE_', 0);
  31. /**
  32. * @deprecated 1.5.0.1
  33. */
  34. define('_CUSTOMIZE_TEXTFIELD_', 1);
  35. class ProductCore extends ObjectModel
  36. {
  37. /** @var string Tax name */
  38. public $tax_name;
  39. /** @var string Tax rate */
  40. public $tax_rate;
  41. /** @var integer Manufacturer id */
  42. public $id_manufacturer;
  43. /** @var integer Supplier id */
  44. public $id_supplier;
  45. /** @var integer default Category id */
  46. public $id_category_default;
  47. /** @var integer default Shop id */
  48. public $id_shop_default;
  49. /** @var string Manufacturer name */
  50. public $manufacturer_name;
  51. /** @var string Supplier name */
  52. public $supplier_name;
  53. /** @var string Name */
  54. public $name;
  55. /** @var string Long description */
  56. public $description;
  57. /** @var string Short description */
  58. public $description_short;
  59. /** @var integer Quantity available */
  60. public $quantity = 0;
  61. /** @var integer Minimal quantity for add to cart */
  62. public $minimal_quantity = 1;
  63. /** @var string available_now */
  64. public $available_now;
  65. /** @var string available_later */
  66. public $available_later;
  67. /** @var float Price in euros */
  68. public $price = 0;
  69. /** @var float Additional shipping cost */
  70. public $additional_shipping_cost = 0;
  71. /** @var float Wholesale Price in euros */
  72. public $wholesale_price = 0;
  73. /** @var boolean on_sale */
  74. public $on_sale = false;
  75. /** @var boolean online_only */
  76. public $online_only = false;
  77. /** @var string unity */
  78. public $unity = null;
  79. /** @var float price for product's unity */
  80. public $unit_price;
  81. /** @var float price for product's unity ratio */
  82. public $unit_price_ratio = 0;
  83. /** @var float Ecotax */
  84. public $ecotax = 0;
  85. /** @var string Reference */
  86. public $reference;
  87. /** @var string Supplier Reference */
  88. public $supplier_reference;
  89. /** @var string Location */
  90. public $location;
  91. /** @var string Width in default width unit */
  92. public $width = 0;
  93. /** @var string Height in default height unit */
  94. public $height = 0;
  95. /** @var string Depth in default depth unit */
  96. public $depth = 0;
  97. /** @var string Weight in default weight unit */
  98. public $weight = 0;
  99. /** @var string Ean-13 barcode */
  100. public $ean13;
  101. /** @var string Upc barcode */
  102. public $upc;
  103. /** @var string Friendly URL */
  104. public $link_rewrite;
  105. /** @var string Meta tag description */
  106. public $meta_description;
  107. /** @var string Meta tag keywords */
  108. public $meta_keywords;
  109. /** @var string Meta tag title */
  110. public $meta_title;
  111. /** @var boolean Product statuts */
  112. public $quantity_discount = 0;
  113. /** @var boolean Product customization */
  114. public $customizable;
  115. /** @var boolean Product is new */
  116. public $new = null;
  117. /** @var integer Number of uploadable files (concerning customizable products) */
  118. public $uploadable_files;
  119. /** @var int Number of text fields */
  120. public $text_fields;
  121. /** @var boolean Product statuts */
  122. public $active = true;
  123. /** @var boolean Product available for order */
  124. public $available_for_order = true;
  125. /** @var string Object available order date */
  126. public $available_date = '0000-00-00';
  127. /** @var enum Product condition (new, used, refurbished) */
  128. public $condition;
  129. /** @var boolean Show price of Product */
  130. public $show_price = true;
  131. /** @var boolean is the product indexed in the search index? */
  132. public $indexed = 0;
  133. /** @var string ENUM('both', 'catalog', 'search', 'none') front office visibility */
  134. public $visibility;
  135. /** @var string Object creation date */
  136. public $date_add;
  137. /** @var string Object last modification date */
  138. public $date_upd;
  139. /*** @var array Tags */
  140. public $tags;
  141. public $id_tax_rules_group = 1;
  142. /**
  143. * We keep this variable for retrocompatibility for themes
  144. * @deprecated 1.5.0
  145. */
  146. public $id_color_default = 0;
  147. /**
  148. * @since 1.5.0
  149. * @var boolean Tells if the product uses the advanced stock management
  150. */
  151. public $advanced_stock_management = 0;
  152. public $out_of_stock;
  153. public $depends_on_stock;
  154. public $isFullyLoaded = false;
  155. public $cache_is_pack;
  156. public $cache_has_attachments;
  157. public $is_virtual;
  158. public $cache_default_attribute;
  159. /**
  160. * @var string If product is populated, this property contain the rewrite link of the default category
  161. */
  162. public $category;
  163. public static $_taxCalculationMethod = PS_TAX_EXC;
  164. protected static $_prices = array();
  165. protected static $_pricesLevel2 = array();
  166. protected static $_incat = array();
  167. protected static $_cart_quantity = array();
  168. protected static $_tax_rules_group = array();
  169. protected static $_cacheFeatures = array();
  170. protected static $_frontFeaturesCache = array();
  171. protected static $producPropertiesCache = array();
  172. /** @var array cache stock data in getStock() method */
  173. protected static $cacheStock = array();
  174. public static $definition = array(
  175. 'table' => 'product',
  176. 'primary' => 'id_product',
  177. 'multilang' => true,
  178. 'multilang_shop' => true,
  179. 'fields' => array(
  180. // Classic fields
  181. 'id_shop_default' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  182. 'id_manufacturer' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  183. 'id_supplier' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  184. 'reference' => array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 32),
  185. 'supplier_reference' => array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 32),
  186. 'location' => array('type' => self::TYPE_STRING, 'validate' => 'isReference', 'size' => 64),
  187. 'width' => array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
  188. 'height' => array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
  189. 'depth' => array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
  190. 'weight' => array('type' => self::TYPE_FLOAT, 'validate' => 'isUnsignedFloat'),
  191. 'quantity_discount' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  192. 'ean13' => array('type' => self::TYPE_STRING, 'validate' => 'isEan13', 'size' => 13),
  193. 'upc' => array('type' => self::TYPE_STRING, 'validate' => 'isUpc', 'size' => 12),
  194. 'cache_is_pack' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  195. 'cache_has_attachments' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  196. 'is_virtual' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  197. /* Shop fields */
  198. 'id_category_default' => array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedId'),
  199. 'id_tax_rules_group' => array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedId'),
  200. 'on_sale' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  201. 'online_only' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  202. 'ecotax' => array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice'),
  203. 'minimal_quantity' => array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
  204. 'price' => array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice', 'required' => true),
  205. 'wholesale_price' => array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice'),
  206. 'unity' => array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isString'),
  207. 'unit_price_ratio' => array('type' => self::TYPE_FLOAT, 'shop' => true),
  208. 'additional_shipping_cost' => array('type' => self::TYPE_FLOAT, 'shop' => true, 'validate' => 'isPrice'),
  209. 'customizable' => array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
  210. 'text_fields' => array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
  211. 'uploadable_files' => array('type' => self::TYPE_INT, 'shop' => true, 'validate' => 'isUnsignedInt'),
  212. 'active' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  213. 'available_for_order' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  214. 'available_date' => array('type' => self::TYPE_DATE, 'shop' => true, 'validate' => 'isDateFormat'),
  215. 'condition' => array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isGenericName', 'values' => array('new', 'used', 'refurbished'), 'default' => 'new'),
  216. 'show_price' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  217. 'indexed' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  218. 'visibility' => array('type' => self::TYPE_STRING, 'shop' => true, 'validate' => 'isProductVisibility', 'values' => array('both', 'catalog', 'search', 'none'), 'default' => 'both'),
  219. 'cache_default_attribute' => array('type' => self::TYPE_INT, 'shop' => true),
  220. 'advanced_stock_management' => array('type' => self::TYPE_BOOL, 'shop' => true, 'validate' => 'isBool'),
  221. 'date_add' => array('type' => self::TYPE_DATE, 'shop' => true, 'validate' => 'isDateFormat'),
  222. 'date_upd' => array('type' => self::TYPE_DATE, 'shop' => true, 'validate' => 'isDateFormat'),
  223. /* Lang fields */
  224. 'meta_description' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
  225. 'meta_keywords' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
  226. 'meta_title' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 128),
  227. 'link_rewrite' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 128),
  228. 'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCatalogName', 'required' => true, 'size' => 128),
  229. 'description' => array('type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isString'),
  230. 'description_short' => array('type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isString'),
  231. 'available_now' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
  232. 'available_later' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'IsGenericName', 'size' => 255),
  233. ),
  234. 'associations' => array(
  235. 'manufacturer' => array('type' => self::HAS_ONE),
  236. 'supplier' => array('type' => self::HAS_ONE),
  237. 'default_category' => array('type' => self::HAS_ONE, 'field' => 'id_category_default', 'object' => 'Category'),
  238. 'tax_rules_group' => array('type' => self::HAS_ONE),
  239. 'categories' => array('type' => self::HAS_MANY, 'field' => 'id_category', 'object' => 'Category', 'association' => 'category_product'),
  240. 'stock_availables' => array('type' => self::HAS_MANY, 'field' => 'id_stock_available', 'object' => 'StockAvailable', 'association' => 'stock_availables'),
  241. ),
  242. );
  243. protected $webserviceParameters = array(
  244. 'objectMethods' => array(
  245. 'add' => 'addWs',
  246. 'update' => 'updateWs'
  247. ),
  248. 'objectNodeNames' => 'products',
  249. 'fields' => array(
  250. 'id_manufacturer' => array(
  251. 'xlink_resource' => 'manufacturers'
  252. ),
  253. 'id_supplier' => array(
  254. 'xlink_resource' => 'suppliers'
  255. ),
  256. 'id_category_default' => array(
  257. 'xlink_resource' => 'categories'
  258. ),
  259. 'new' => array(),
  260. 'cache_default_attribute' => array(),
  261. 'id_default_image' => array(
  262. 'getter' => 'getCoverWs',
  263. 'setter' => 'setCoverWs',
  264. 'xlink_resource' => array(
  265. 'resourceName' => 'images',
  266. 'subResourceName' => 'products'
  267. )
  268. ),
  269. 'id_default_combination' => array(
  270. 'getter' => 'getWsDefaultCombination',
  271. 'setter' => 'setWsDefaultCombination',
  272. 'xlink_resource' => array(
  273. 'resourceName' => 'combinations'
  274. )
  275. ),
  276. 'id_tax_rules_group' => array(
  277. 'xlink_resource' => array(
  278. 'resourceName' => 'tax_rules_group'
  279. )
  280. ),
  281. 'position_in_category' => array(
  282. 'getter' => 'getWsPositionInCategory',
  283. 'setter' => false
  284. ),
  285. 'manufacturer_name' => array(
  286. 'getter' => 'getWsManufacturerName',
  287. 'setter' => false
  288. ),
  289. 'quantity' => array(
  290. 'getter' => false,
  291. 'setter' => false
  292. ),
  293. ),
  294. 'associations' => array(
  295. 'categories' => array(
  296. 'resource' => 'category',
  297. 'fields' => array(
  298. 'id' => array('required' => true),
  299. )
  300. ),
  301. 'images' => array(
  302. 'resource' => 'image',
  303. 'fields' => array('id' => array())
  304. ),
  305. 'combinations' => array(
  306. 'resource' => 'combinations',
  307. 'fields' => array(
  308. 'id' => array('required' => true),
  309. )
  310. ),
  311. 'product_option_values' => array(
  312. 'resource' => 'product_options_values',
  313. 'fields' => array(
  314. 'id' => array('required' => true),
  315. )
  316. ),
  317. 'product_features' => array(
  318. 'resource' => 'product_feature',
  319. 'fields' => array(
  320. 'id' => array('required' => true),
  321. 'id_feature_value' => array(
  322. 'required' => true,
  323. 'xlink_resource' => 'product_feature_values'
  324. ),
  325. )
  326. ),
  327. 'tags' => array('resource' => 'tag',
  328. 'fields' => array(
  329. 'id' => array('required' => true),
  330. )),
  331. 'stock_availables' => array('resource' => 'stock_available',
  332. 'fields' => array(
  333. 'id' => array('required' => true),
  334. 'id_product_attribute' => array('required' => true),
  335. ),
  336. 'setter' => false
  337. ),
  338. ),
  339. );
  340. const CUSTOMIZE_FILE = 0;
  341. const CUSTOMIZE_TEXTFIELD = 1;
  342. /**
  343. * Note: prefix is "PTYPE" because TYPE_ is used in ObjectModel (definition)
  344. */
  345. const PTYPE_SIMPLE = 0;
  346. const PTYPE_PACK = 1;
  347. const PTYPE_VIRTUAL = 2;
  348. public function __construct($id_product = null, $full = false, $id_lang = null, $id_shop = null, Context $context = null)
  349. {
  350. parent::__construct($id_product, $id_lang, $id_shop);
  351. if (!$context)
  352. $context = Context::getContext();
  353. if ($full && $this->id)
  354. {
  355. $this->isFullyLoaded = $full;
  356. $this->tax_name = 'deprecated'; // The applicable tax may be BOTH the product one AND the state one (moreover this variable is some deadcode)
  357. $this->manufacturer_name = Manufacturer::getNameById((int)$this->id_manufacturer);
  358. $this->supplier_name = Supplier::getNameById((int)$this->id_supplier);
  359. $address = null;
  360. if (is_object($context->cart) && $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')} != null)
  361. $address = $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
  362. $this->tax_rate = $this->getTaxesRate(new Address($address));
  363. $this->new = $this->isNew();
  364. $this->price = Product::getPriceStatic((int)$this->id, false, null, 6, null, false, true, 1, false, null, null, null, $this->specificPrice);
  365. $this->unit_price = ($this->unit_price_ratio != 0 ? $this->price / $this->unit_price_ratio : 0);
  366. if ($this->id)
  367. $this->tags = Tag::getProductTags((int)$this->id);
  368. $this->loadStockData();
  369. }
  370. if ($this->id_category_default)
  371. $this->category = Category::getLinkRewrite((int)$this->id_category_default, (int)$id_lang);
  372. }
  373. /**
  374. * @see ObjectModel::getFieldsShop()
  375. * @return array
  376. */
  377. public function getFieldsShop()
  378. {
  379. $fields = parent::getFieldsShop();
  380. if (is_null($this->update_fields) || (!empty($this->update_fields['price']) && !empty($this->update_fields['unit_price'])))
  381. $fields['unit_price_ratio'] = (float)$this->unit_price > 0 ? $this->price / $this->unit_price : 0;
  382. return $fields;
  383. }
  384. public function add($autodate = true, $null_values = false)
  385. {
  386. if (!parent::add($autodate, $null_values))
  387. return false;
  388. if ($this->getType() == Product::PTYPE_VIRTUAL)
  389. StockAvailable::setProductOutOfStock((int)$this->id, 1);
  390. else
  391. StockAvailable::setProductOutOfStock((int)$this->id, 2);
  392. $this->setGroupReduction();
  393. Hook::exec('actionProductSave', array('id_product' => $this->id));
  394. return true;
  395. }
  396. public function update($null_values = false)
  397. {
  398. $return = parent::update($null_values);
  399. $this->setGroupReduction();
  400. Hook::exec('actionProductSave', array('id_product' => $this->id));
  401. return $return;
  402. }
  403. public static function initPricesComputation($id_customer = null)
  404. {
  405. if ($id_customer)
  406. {
  407. $customer = new Customer((int)$id_customer);
  408. if (!Validate::isLoadedObject($customer))
  409. die(Tools::displayError());
  410. self::$_taxCalculationMethod = Group::getPriceDisplayMethod((int)$customer->id_default_group);
  411. }
  412. else
  413. self::$_taxCalculationMethod = Group::getPriceDisplayMethod(Group::getCurrent()->id);
  414. }
  415. public static function getTaxCalculationMethod($id_customer = null)
  416. {
  417. if ($id_customer)
  418. Product::initPricesComputation((int)$id_customer);
  419. return (int)self::$_taxCalculationMethod;
  420. }
  421. /**
  422. * Move a product inside its category
  423. * @param boolean $way Up (1) or Down (0)
  424. * @param integer $position
  425. * return boolean Update result
  426. */
  427. public function updatePosition($way, $position)
  428. {
  429. if (!$res = Db::getInstance()->executeS('
  430. SELECT cp.`id_product`, cp.`position`, cp.`id_category`
  431. FROM `'._DB_PREFIX_.'category_product` cp
  432. WHERE cp.`id_category` = '.(int)Tools::getValue('id_category', 1).'
  433. ORDER BY cp.`position` ASC'
  434. ))
  435. return false;
  436. foreach ($res as $product)
  437. if ((int)$product['id_product'] == (int)$this->id)
  438. $moved_product = $product;
  439. if (!isset($moved_product) || !isset($position))
  440. return false;
  441. // < and > statements rather than BETWEEN operator
  442. // since BETWEEN is treated differently according to databases
  443. return (Db::getInstance()->execute('
  444. UPDATE `'._DB_PREFIX_.'category_product`
  445. SET `position`= `position` '.($way ? '- 1' : '+ 1').'
  446. WHERE `position`
  447. '.($way
  448. ? '> '.(int)$moved_product['position'].' AND `position` <= '.(int)$position
  449. : '< '.(int)$moved_product['position'].' AND `position` >= '.(int)$position).'
  450. AND `id_category`='.(int)$moved_product['id_category'])
  451. && Db::getInstance()->execute('
  452. UPDATE `'._DB_PREFIX_.'category_product`
  453. SET `position` = '.(int)$position.'
  454. WHERE `id_product` = '.(int)$moved_product['id_product'].'
  455. AND `id_category`='.(int)$moved_product['id_category']));
  456. }
  457. /*
  458. * Reorder product position in category $id_category.
  459. * Call it after deleting a product from a category.
  460. *
  461. * @param int $id_category
  462. */
  463. public static function cleanPositions($id_category)
  464. {
  465. $return = true;
  466. $result = Db::getInstance()->executeS('
  467. SELECT `id_product`
  468. FROM `'._DB_PREFIX_.'category_product`
  469. WHERE `id_category` = '.(int)$id_category.'
  470. ORDER BY `position`
  471. ');
  472. $total = count($result);
  473. for ($i = 0; $i < $total; $i++)
  474. $return &= Db::getInstance()->update('category_product', array(
  475. 'position' => $i,
  476. ), '`id_category` = '.(int)$id_category.' AND `id_product` = '.(int)$result[$i]['id_product']);
  477. return $return;
  478. }
  479. /**
  480. * Get the default attribute for a product
  481. *
  482. * @return int Attributes list
  483. */
  484. public static function getDefaultAttribute($id_product, $minimum_quantity = 0)
  485. {
  486. if (!Combination::isFeatureActive())
  487. return 0;
  488. $sql = 'SELECT pa.id_product_attribute
  489. FROM '._DB_PREFIX_.'product_attribute pa
  490. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  491. '.($minimum_quantity > 0 ? Product::sqlStock('pa', 'pa') : '').
  492. ' WHERE product_attribute_shop.default_on = 1 '
  493. .($minimum_quantity > 0 ? ' AND IFNULL(stock.quantity, 0) >= '.(int)$minimum_quantity : '').
  494. ' AND pa.id_product = '.(int)$id_product;
  495. $result = Db::getInstance()->getValue($sql);
  496. if (!$result)
  497. {
  498. $sql = 'SELECT pa.id_product_attribute
  499. FROM '._DB_PREFIX_.'product_attribute pa
  500. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  501. '.($minimum_quantity > 0 ? Product::sqlStock('pa', 'pa') : '').
  502. ' WHERE pa.id_product = '.(int)$id_product
  503. .($minimum_quantity > 0 ? ' AND IFNULL(stock.quantity, 0) >= '.(int)$minimum_quantity : '');
  504. $result = Db::getInstance()->getValue($sql);
  505. }
  506. if (!$result)
  507. {
  508. $sql = 'SELECT pa.id_product_attribute
  509. FROM '._DB_PREFIX_.'product_attribute pa
  510. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  511. WHERE product_attribute_shop.`default_on` = 1
  512. AND pa.id_product = '.(int)$id_product;
  513. $result = Db::getInstance()->getValue($sql);
  514. }
  515. if (!$result)
  516. {
  517. $sql = 'SELECT pa.id_product_attribute
  518. FROM '._DB_PREFIX_.'product_attribute pa
  519. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  520. WHERE pa.id_product = '.(int)$id_product;
  521. $result = Db::getInstance()->getValue($sql);
  522. }
  523. return $result;
  524. }
  525. public static function updateDefaultAttribute($id_product)
  526. {
  527. Db::getInstance()->update('product_shop', array(
  528. 'cache_default_attribute' => (int)Product::getDefaultAttribute($id_product),
  529. ), 'id_product = '.(int)$id_product);
  530. }
  531. public static function updateIsVirtual($id_product)
  532. {
  533. Db::getInstance()->update('product', array(
  534. 'is_virtual' => (int)$id_product,
  535. ), 'id_product = '.(int)$id_product);
  536. }
  537. /**
  538. * @see ObjectModel::validateFieldsLang()
  539. */
  540. public function validateFieldsLang($die = true, $error_return = false)
  541. {
  542. $limit = (int)Configuration::get('PS_PRODUCT_SHORT_DESC_LIMIT');
  543. if ($limit <= 0)
  544. $limit = 800;
  545. $this->def['fields']['description_short']['size'] = $limit;
  546. return parent::validateFieldsLang($die, $error_return);
  547. }
  548. public function delete()
  549. {
  550. /*
  551. * @since 1.5.0
  552. * It is NOT possible to delete a product if there are currently:
  553. * - physical stock for this product
  554. * - supply order(s) for this product
  555. */
  556. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && $this->advanced_stock_management)
  557. {
  558. $stock_manager = StockManagerFactory::getManager();
  559. $physical_quantity = $stock_manager->getProductPhysicalQuantities($this->id, 0);
  560. $real_quantity = $stock_manager->getProductRealQuantities($this->id, 0);
  561. if ($physical_quantity > 0)
  562. return false;
  563. if ($real_quantity > $physical_quantity)
  564. return false;
  565. }
  566. $result = parent::delete();
  567. // Removes the product from StockAvailable, for the current shop
  568. StockAvailable::removeProductFromStockAvailable($this->id);
  569. $result &= ($this->deleteProductAttributes() && $this->deleteImages() && $this->deleteSceneProducts());
  570. // If there are still entries in product_shop, don't remove completly the product
  571. if ($this->hasMultishopEntries())
  572. return true;
  573. Product::cleanPositions($this->id);
  574. Hook::exec('actionProductDelete', array('product' => $this));
  575. if (!$result ||
  576. !GroupReduction::deleteProductReduction($this->id) ||
  577. !$this->deleteCategories(true) ||
  578. !$this->deleteProductFeatures() ||
  579. !$this->deleteTags() ||
  580. !$this->deleteCartProducts() ||
  581. !$this->deleteAttributesImpacts() ||
  582. !$this->deleteAttachments() ||
  583. !$this->deleteCustomization() ||
  584. !SpecificPrice::deleteByProductId((int)$this->id) ||
  585. !$this->deletePack() ||
  586. !$this->deleteProductSale() ||
  587. !$this->deleteSearchIndexes() ||
  588. !$this->deleteAccessories() ||
  589. !$this->deleteFromAccessories() ||
  590. !$this->deleteFromSupplier() ||
  591. !$this->deleteDownload() ||
  592. !$this->deleteFromCartRules())
  593. return false;
  594. return true;
  595. }
  596. public function deleteSelection($products)
  597. {
  598. $return = 1;
  599. if (is_array($products) && ($count = count($products)))
  600. {
  601. // Deleting products can be quite long on a cheap server. Let's say 1.5 seconds by product (I've seen it!).
  602. if (intval(ini_get('max_execution_time')) < round($count * 1.5))
  603. ini_set('max_execution_time', round($count * 1.5));
  604. foreach ($products as $id_product)
  605. {
  606. $product = new Product((int)$id_product);
  607. $return &= $product->delete();
  608. }
  609. }
  610. return $return;
  611. }
  612. public function deleteFromCartRules()
  613. {
  614. CartRule::cleanProductRuleIntegrity('products', $this->id);
  615. return true;
  616. }
  617. public function deleteFromSupplier()
  618. {
  619. return Db::getInstance()->delete('product_supplier', 'id_product = '.(int)$this->id);
  620. }
  621. /**
  622. * addToCategories add this product to the category/ies if not exists.
  623. *
  624. * @param mixed $categories id_category or array of id_category
  625. * @return boolean true if succeed
  626. */
  627. public function addToCategories($categories = array())
  628. {
  629. if (empty($categories))
  630. return false;
  631. if (!is_array($categories))
  632. $categories = array($categories);
  633. if (!count($categories))
  634. return false;
  635. $categories = array_map('intval', $categories);
  636. $current_categories = $this->getCategories();
  637. $current_categories = array_map('intval', $current_categories);
  638. // for new categ, put product at last position
  639. $res_categ_new_pos = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  640. SELECT id_category, MAX(position)+1 newPos
  641. FROM `'._DB_PREFIX_.'category_product`
  642. WHERE `id_category` IN('.implode(',', $categories).')
  643. GROUP BY id_category');
  644. foreach ($res_categ_new_pos as $array)
  645. $new_categories[(int)$array['id_category']] = (int)$array['newPos'];
  646. $new_categ_pos = array();
  647. foreach ($categories as $id_category)
  648. $new_categ_pos[$id_category] = isset($new_categories[$id_category]) ? $new_categories[$id_category] : 0;
  649. $product_cats = array();
  650. foreach ($categories as $new_id_categ)
  651. if (!in_array($new_id_categ, $current_categories))
  652. $product_cats[] = array(
  653. 'id_category' => (int)$new_id_categ,
  654. 'id_product' => (int)$this->id,
  655. 'position' => (int)$new_categ_pos[$new_id_categ],
  656. );
  657. Db::getInstance()->insert('category_product', $product_cats);
  658. return true;
  659. }
  660. /**
  661. * Update categories to index product into
  662. *
  663. * @param string $productCategories Categories list to index product into
  664. * @param boolean $keeping_current_pos (deprecated, no more used)
  665. * @return array Update/insertion result
  666. */
  667. public function updateCategories($categories, $keeping_current_pos = false)
  668. {
  669. if (empty($categories))
  670. return false;
  671. $result = Db::getInstance()->executeS('
  672. SELECT c.`id_category`
  673. FROM `'._DB_PREFIX_.'category_product` cp
  674. LEFT JOIN `'._DB_PREFIX_.'category` c ON (c.`id_category` = cp.`id_category`)
  675. '.Shop::addSqlAssociation('category', 'c', true).'
  676. WHERE cp.`id_category` NOT IN ('.implode(',', array_map('intval', $categories)).')
  677. AND cp.id_product = '.$this->id
  678. );
  679. foreach ($result as $categ_to_delete)
  680. $this->deleteCategory($categ_to_delete['id_category']);
  681. // if none are found, it's an error
  682. if (!is_array($result))
  683. return false;
  684. if (!$this->addToCategories($categories))
  685. return false;
  686. SpecificPriceRule::applyAllRules(array((int)$this->id));
  687. return true;
  688. }
  689. /**
  690. * deleteCategory delete this product from the category $id_category
  691. *
  692. * @param mixed $id_category
  693. * @param mixed $clean_positions
  694. * @return boolean
  695. */
  696. public function deleteCategory($id_category, $clean_positions = true)
  697. {
  698. $result = Db::getInstance()->executeS(
  699. 'SELECT `id_category`
  700. FROM `'._DB_PREFIX_.'category_product`
  701. WHERE `id_product` = '.(int)$this->id.'
  702. AND id_category = '.(int)$id_category.''
  703. );
  704. $return = Db::getInstance()->delete('category_product', 'id_product = '.(int)$this->id.' AND id_category = '.(int)$id_category);
  705. if ($clean_positions === true)
  706. foreach ($result as $row)
  707. $this->cleanPositions((int)$row['id_category']);
  708. SpecificPriceRule::applyAllRules(array((int)$this->id));
  709. return $return;
  710. }
  711. /**
  712. * Delete all association to category where product is indexed
  713. *
  714. * @param boolean $clean_positions clean category positions after deletion
  715. * @return array Deletion result
  716. */
  717. public function deleteCategories($clean_positions = false)
  718. {
  719. $return = Db::getInstance()->delete('category_product', 'id_product = '.(int)$this->id);
  720. if ($clean_positions === true)
  721. {
  722. $result = Db::getInstance()->executeS(
  723. 'SELECT `id_category`
  724. FROM `'._DB_PREFIX_.'category_product`
  725. WHERE `id_product` = '.(int)$this->id
  726. );
  727. foreach ($result as $row)
  728. $this->cleanPositions((int)$row['id_category']);
  729. }
  730. return $return;
  731. }
  732. /**
  733. * Delete products tags entries
  734. *
  735. * @return array Deletion result
  736. */
  737. public function deleteTags()
  738. {
  739. return Db::getInstance()->delete('product_tag', 'id_product = '.(int)$this->id)
  740. && Db::getInstance()->delete('tag', 'id_tag NOT IN (SELECT id_tag FROM '._DB_PREFIX_.'product_tag)');
  741. }
  742. /**
  743. * Delete product from cart
  744. *
  745. * @return array Deletion result
  746. */
  747. public function deleteCartProducts()
  748. {
  749. return Db::getInstance()->delete('cart_product', 'id_product = '.(int)$this->id);
  750. }
  751. /**
  752. * Delete product images from database
  753. *
  754. * @return bool success
  755. */
  756. public function deleteImages()
  757. {
  758. $result = Db::getInstance()->executeS('
  759. SELECT `id_image`
  760. FROM `'._DB_PREFIX_.'image`
  761. WHERE `id_product` = '.(int)$this->id
  762. );
  763. $status = true;
  764. if ($result)
  765. foreach ($result as $row)
  766. {
  767. $image = new Image($row['id_image']);
  768. $status &= $image->delete();
  769. }
  770. return $status;
  771. }
  772. /**
  773. * @deprecated 1.5.0 Use Combination::getPrice()
  774. */
  775. public static function getProductAttributePrice($id_product_attribute)
  776. {
  777. return Combination::getPrice($id_product_attribute);
  778. }
  779. /**
  780. * Get all available products
  781. *
  782. * @param integer $id_lang Language id
  783. * @param integer $start Start number
  784. * @param integer $limit Number of products to return
  785. * @param string $order_by Field for ordering
  786. * @param string $order_way Way for ordering (ASC or DESC)
  787. * @return array Products details
  788. */
  789. public static function getProducts($id_lang, $start, $limit, $order_by, $order_way, $id_category = false,
  790. $only_active = false, Context $context = null)
  791. {
  792. if (!$context)
  793. $context = Context::getContext();
  794. $front = true;
  795. if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
  796. $front = false;
  797. if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
  798. die (Tools::displayError());
  799. if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add' || $order_by == 'date_upd')
  800. $order_by_prefix = 'p';
  801. else if ($order_by == 'name')
  802. $order_by_prefix = 'pl';
  803. else if ($order_by == 'position')
  804. $order_by_prefix = 'c';
  805. if (strpos($order_by, '.') > 0)
  806. {
  807. $order_by = explode('.', $order_by);
  808. $order_by_prefix = $order_by[0];
  809. $order_by = $order_by[1];
  810. }
  811. $sql = 'SELECT p.*, product_shop.*, pl.* , t.`rate` AS tax_rate, m.`name` AS manufacturer_name, s.`name` AS supplier_name
  812. FROM `'._DB_PREFIX_.'product` p
  813. '.Shop::addSqlAssociation('product', 'p').'
  814. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` '.Shop::addSqlRestrictionOnLang('pl').')
  815. LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (product_shop.`id_tax_rules_group` = tr.`id_tax_rules_group`
  816. AND tr.`id_country` = '.(int)Context::getContext()->country->id.'
  817. AND tr.`id_state` = 0)
  818. LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
  819. LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
  820. LEFT JOIN `'._DB_PREFIX_.'supplier` s ON (s.`id_supplier` = p.`id_supplier`)'.
  821. ($id_category ? 'LEFT JOIN `'._DB_PREFIX_.'category_product` c ON (c.`id_product` = p.`id_product`)' : '').'
  822. WHERE pl.`id_lang` = '.(int)$id_lang.
  823. ($id_category ? ' AND c.`id_category` = '.(int)$id_category : '').
  824. ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').
  825. ($only_active ? ' AND product_shop.`active` = 1' : '').'
  826. ORDER BY '.(isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way).
  827. ($limit > 0 ? ' LIMIT '.(int)$start.','.(int)$limit : '');
  828. $rq = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  829. if ($order_by == 'price')
  830. Tools::orderbyPrice($rq, $order_way);
  831. return ($rq);
  832. }
  833. public static function getSimpleProducts($id_lang, Context $context = null)
  834. {
  835. if (!$context)
  836. $context = Context::getContext();
  837. $front = true;
  838. if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
  839. $front = false;
  840. $sql = 'SELECT p.`id_product`, pl.`name`
  841. FROM `'._DB_PREFIX_.'product` p
  842. '.Shop::addSqlAssociation('product', 'p').'
  843. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product` '.Shop::addSqlRestrictionOnLang('pl').')
  844. WHERE pl.`id_lang` = '.(int)$id_lang.'
  845. '.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
  846. ORDER BY pl.`name`';
  847. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  848. }
  849. public function isNew()
  850. {
  851. $result = Db::getInstance()->executeS('
  852. SELECT p.id_product
  853. FROM `'._DB_PREFIX_.'product` p
  854. '.Shop::addSqlAssociation('product', 'p').'
  855. WHERE p.id_product = '.(int)$this->id.'
  856. AND DATEDIFF(
  857. product_shop.`date_add`,
  858. DATE_SUB(
  859. NOW(),
  860. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
  861. )
  862. ) > 0
  863. ');
  864. return count($result) > 0;
  865. }
  866. public function productAttributeExists($attributes_list, $current_product_attribute = false, Context $context = null, $all_shops = false, $return_id = false)
  867. {
  868. if (!Combination::isFeatureActive())
  869. return false;
  870. if ($context === null)
  871. $context = Context::getContext();
  872. $result = Db::getInstance()->executeS(
  873. 'SELECT pac.`id_attribute`, pac.`id_product_attribute`
  874. FROM `'._DB_PREFIX_.'product_attribute` pa
  875. JOIN `'._DB_PREFIX_.'product_attribute_shop` pas ON (pas.id_product_attribute = pa.id_product_attribute)
  876. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  877. WHERE 1 '.(!$all_shops ? ' AND pas.id_shop ='.(int)$context->shop->id : '').' AND pa.`id_product` = '.(int)$this->id.
  878. ($all_shops ? ' GROUP BY pac.id_attribute, pac.id_product_attribute ' : '')
  879. );
  880. /* If something's wrong */
  881. if (!$result || empty($result))
  882. return false;
  883. /* Product attributes simulation */
  884. $product_attributes = array();
  885. foreach ($result as $product_attribute)
  886. $product_attributes[$product_attribute['id_product_attribute']][] = $product_attribute['id_attribute'];
  887. /* Checking product's attribute existence */
  888. foreach ($product_attributes as $key => $product_attribute)
  889. if (count($product_attribute) == count($attributes_list))
  890. {
  891. $diff = false;
  892. for ($i = 0; $diff == false && isset($product_attribute[$i]); $i++)
  893. if (!in_array($product_attribute[$i], $attributes_list) || $key == $current_product_attribute)
  894. $diff = true;
  895. if (!$diff)
  896. {
  897. if ($return_id)
  898. return $key;
  899. return true;
  900. }
  901. }
  902. return false;
  903. }
  904. /**
  905. * addProductAttribute is deprecated
  906. *
  907. * The quantity params now set StockAvailable for the current shop with the specified quantity
  908. * The supplier_reference params now set the supplier reference of the default supplier of the product if possible
  909. *
  910. * @see StockManager if you want to manage real stock
  911. * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
  912. * @see ProductSupplier for manage supplier reference(s)
  913. *
  914. * @deprecated since 1.5.0
  915. */
  916. public function addProductAttribute($price, $weight, $unit_impact, $ecotax, $quantity, $id_images, $reference,
  917. $id_supplier = null, $ean13, $default, $location = null, $upc = null, $minimal_quantity = 1)
  918. {
  919. Tools::displayAsDeprecated();
  920. $id_product_attribute = $this->addAttribute(
  921. $price, $weight, $unit_impact, $ecotax, $id_images,
  922. $reference, $ean13, $default, $location, $upc, $minimal_quantity
  923. );
  924. if (!$id_product_attribute)
  925. return false;
  926. StockAvailable::setQuantity($this->id, $id_product_attribute, $quantity);
  927. //Try to set the default supplier reference
  928. $this->addSupplierReference($id_supplier, $id_product_attribute);
  929. return $id_product_attribute;
  930. }
  931. public function generateMultipleCombinations($combinations, $attributes)
  932. {
  933. $attributes_list = array();
  934. $res = true;
  935. $default_on = 1;
  936. foreach ($combinations as $key => $combination)
  937. {
  938. $id_combination = (int)$this->productAttributeExists($attributes[$key], false, null, true, true);
  939. $obj = new Combination($id_combination);
  940. if ($id_combination)
  941. {
  942. $obj->minimal_quantity = 1;
  943. $obj->available_date = '0000-00-00';
  944. }
  945. foreach ($combination as $field => $value)
  946. $obj->$field = $value;
  947. $obj->default_on = $default_on;
  948. $default_on = 0;
  949. $obj->save();
  950. if (!$id_combination)
  951. {
  952. $attribute_list = array();
  953. foreach ($attributes[$key] as $id_attribute)
  954. $attribute_list[] = array(
  955. 'id_product_attribute' => (int)$obj->id,
  956. 'id_attribute' => (int)$id_attribute
  957. );
  958. $res &= Db::getInstance()->insert('product_attribute_combination', $attribute_list);
  959. }
  960. }
  961. return $res;
  962. }
  963. /**
  964. * @param integer $quantity DEPRECATED
  965. * @param string $supplier_reference DEPRECATED
  966. */
  967. public function addCombinationEntity($wholesale_price, $price, $weight, $unit_impact, $ecotax, $quantity,
  968. $id_images, $reference, $id_supplier, $ean13, $default, $location = null, $upc = null, $minimal_quantity = 1, array $id_shop_list = array())
  969. {
  970. $id_product_attribute = $this->addAttribute(
  971. $price, $weight, $unit_impact, $ecotax, $id_images,
  972. $reference, $ean13, $default, $location, $upc, $minimal_quantity, $id_shop_list);
  973. $this->addSupplierReference($id_supplier, $id_product_attribute);
  974. $result = ObjectModel::updateMultishopTable('Combination', array(
  975. 'wholesale_price' => (float)$wholesale_price,
  976. ), 'a.id_product_attribute = '.(int)$id_product_attribute);
  977. if (!$id_product_attribute || !$result)
  978. return false;
  979. return $id_product_attribute;
  980. }
  981. public function addProductAttributeMultiple($attributes, $set_default = true)
  982. {
  983. Tools::displayAsDeprecated();
  984. $return = array();
  985. $default_value = 1;
  986. foreach ($attributes as &$attribute)
  987. {
  988. $obj = new Combination();
  989. foreach ($attribute as $key => $value)
  990. $obj->$key = $value;
  991. if ($set_default)
  992. {
  993. $obj->default_on = $default_value;
  994. $default_value = 0;
  995. // if we add a combination for this shop and this product does not use the combination feature in other shop,
  996. // we clone the default combination in every shop linked to this product
  997. if (!$this->hasAttributesInOtherShops())
  998. {
  999. $id_shop_list_array = Product::getShopsByProduct($this->id);
  1000. $id_shop_list = array();
  1001. foreach ($id_shop_list_array as $array_shop)
  1002. $id_shop_list[] = $array_shop['id_shop'];
  1003. $obj->id_shop_list = $id_shop_list;
  1004. }
  1005. }
  1006. $obj->add();
  1007. $return[] = $obj->id;
  1008. }
  1009. return $return;
  1010. }
  1011. /**
  1012. * Del all default attributes for product
  1013. */
  1014. public function deleteDefaultAttributes()
  1015. {
  1016. return ObjectModel::updateMultishopTable('Combination', array(
  1017. 'default_on' => 0,
  1018. ), 'id_product = '.(int)$this->id);
  1019. }
  1020. public function setDefaultAttribute($id_product_attribute)
  1021. {
  1022. $result = ObjectModel::updateMultishopTable('Combination', array(
  1023. 'default_on' => 1
  1024. ), '`id_product` = '.(int)$this->id.' AND a.`id_product_attribute` = '.(int)$id_product_attribute);
  1025. $result &= ObjectModel::updateMultishopTable('product', array(
  1026. 'cache_default_attribute' => (int)$id_product_attribute,
  1027. ), 'a.`id_product` = '.(int)$this->id);
  1028. return $result;
  1029. }
  1030. /**
  1031. * Update a product attribute
  1032. *
  1033. * @deprecated since 1.5
  1034. * @see updateAttribute() to use instead
  1035. * @see ProductSupplier for manage supplier reference(s)
  1036. *
  1037. */
  1038. public function updateProductAttribute($id_product_attribute, $wholesale_price, $price, $weight, $unit, $ecotax,
  1039. $id_images, $reference, $id_supplier = null, $ean13, $default, $location = null, $upc = null, $minimal_quantity, $available_date)
  1040. {
  1041. Tools::displayAsDeprecated();
  1042. $return = $this->updateAttribute(
  1043. $id_product_attribute, $wholesale_price, $price, $weight, $unit, $ecotax,
  1044. $id_images, $reference, $ean13, $default, $location = null, $upc = null, $minimal_quantity, $available_date
  1045. );
  1046. $this->addSupplierReference($id_supplier, $id_product_attribute);
  1047. return $return;
  1048. }
  1049. /**
  1050. * Sets Supplier Reference
  1051. *
  1052. * @param int $id_supplier
  1053. * @param int $id_product_attribute
  1054. * @param string $supplier_reference
  1055. * @param float $price
  1056. * @param int $id_currency
  1057. */
  1058. public function addSupplierReference($id_supplier, $id_product_attribute, $supplier_reference = null, $price = null, $id_currency = null)
  1059. {
  1060. //in some case we need to add price without supplier reference
  1061. if ($supplier_reference === null)
  1062. $supplier_reference = '';
  1063. //Try to set the default supplier reference
  1064. if ($id_supplier > 0)
  1065. {
  1066. $id_product_supplier = (int)ProductSupplier::getIdByProductAndSupplier($this->id, $id_product_attribute, $id_supplier);
  1067. if (!$id_product_supplier)
  1068. {
  1069. //create new record
  1070. $product_supplier_entity = new ProductSupplier();
  1071. $product_supplier_entity->id_product = (int)$this->id;
  1072. $product_supplier_entity->id_product_attribute = (int)$id_product_attribute;
  1073. $product_supplier_entity->id_supplier = (int)$id_supplier;
  1074. $product_supplier_entity->product_supplier_reference = pSQL($supplier_reference);
  1075. $product_supplier_entity->product_supplier_price_te = (int)$price;
  1076. $product_supplier_entity->id_currency = (int)$id_currency;
  1077. $product_supplier_entity->save();
  1078. }
  1079. else
  1080. {
  1081. $product_supplier = new ProductSupplier((int)$id_product_supplier);
  1082. $product_supplier->product_supplier_reference = pSQL($supplier_reference);
  1083. $product_supplier->update();
  1084. }
  1085. }
  1086. }
  1087. /**
  1088. * Update a product attribute
  1089. *
  1090. * @param integer $id_product_attribute Product attribute id
  1091. * @param float $wholesale_price Wholesale price
  1092. * @param float $price Additional price
  1093. * @param float $weight Additional weight
  1094. * @param float $unit
  1095. * @param float $ecotax Additional ecotax
  1096. * @param integer $id_image Image id
  1097. * @param string $reference Reference
  1098. * @param string $ean13 Ean-13 barcode
  1099. * @param int $default Default On
  1100. * @param string $upc Upc barcode
  1101. * @param string $minimal_quantity Minimal quantity
  1102. * @return array Update result
  1103. */
  1104. public function updateAttribute($id_product_attribute, $wholesale_price, $price, $weight, $unit, $ecotax,
  1105. $id_images, $reference, $ean13, $default, $location = null, $upc = null, $minimal_quantity = null, $available_date = null, $update_all_fields = true, array $id_shop_list = array())
  1106. {
  1107. $combination = new Combination($id_product_attribute);
  1108. if (!$update_all_fields)
  1109. $combination->setFieldsToUpdate(array(
  1110. 'price' => !is_null($price),
  1111. 'wholesale_price' => !is_null($wholesale_price),
  1112. 'ecotax' => !is_null($ecotax),
  1113. 'weight' => !is_null($weight),
  1114. 'unit_price_impact' => !is_null($unit),
  1115. 'default_on' => !is_null($default),
  1116. 'minimal_quantity' => !is_null($minimal_quantity),
  1117. 'available_date' => !is_null($available_date),
  1118. ));
  1119. $price = str_replace(',', '.', $price);
  1120. $weight = str_replace(',', '.', $weight);
  1121. $combination->price = (float)$price;
  1122. $combination->wholesale_price = (float)$wholesale_price;
  1123. $combination->ecotax = (float)$ecotax;
  1124. $combination->weight = (float)$weight;
  1125. $combination->unit_price_impact = (float)$unit;
  1126. $combination->reference = pSQL($reference);
  1127. $combination->location = pSQL($location);
  1128. $combination->ean13 = pSQL($ean13);
  1129. $combination->upc = pSQL($upc);
  1130. $combination->default_on = (int)$default;
  1131. $combination->minimal_quantity = (int)$minimal_quantity;
  1132. $combination->available_date = $available_date ? pSQL($available_date) : '0000-00-00';
  1133. if (count($id_shop_list))
  1134. $combination->id_shop_list = $id_shop_list;
  1135. $combination->save();
  1136. if (!empty($id_images))
  1137. $combination->setImages($id_images);
  1138. Product::updateDefaultAttribute($this->id);
  1139. Hook::exec('actionProductAttributeUpdate', array('id_product_attribute' => $id_product_attribute));
  1140. return true;
  1141. }
  1142. /**
  1143. * Add a product attribute
  1144. * @since 1.5.0.1
  1145. *
  1146. * @param float $price Additional price
  1147. * @param float $weight Additional weight
  1148. * @param float $ecotax Additional ecotax
  1149. * @param integer $id_images Image ids
  1150. * @param string $reference Reference
  1151. * @param string $location Location
  1152. * @param string $ean13 Ean-13 barcode
  1153. * @param boolean $default Is default attribute for product
  1154. * @param integer $minimal_quantity Minimal quantity to add to cart
  1155. * @return mixed $id_product_attribute or false
  1156. */
  1157. public function addAttribute($price, $weight, $unit_impact, $ecotax, $id_images, $reference, $ean13,
  1158. $default, $location = null, $upc = null, $minimal_quantity = 1, array $id_shop_list = array())
  1159. {
  1160. if (!$this->id)
  1161. return;
  1162. $price = str_replace(',', '.', $price);
  1163. $weight = str_replace(',', '.', $weight);
  1164. $combination = new Combination();
  1165. $combination->id_product = (int)$this->id;
  1166. $combination->price = (float)$price;
  1167. $combination->ecotax = (float)$ecotax;
  1168. $combination->quantity = 0;
  1169. $combination->weight = (float)$weight;
  1170. $combination->unit_price_impact = (float)$unit_impact;
  1171. $combination->reference = pSQL($reference);
  1172. $combination->location = pSQL($location);
  1173. $combination->ean13 = pSQL($ean13);
  1174. $combination->upc = pSQL($upc);
  1175. $combination->default_on = (int)$default;
  1176. $combination->minimal_quantity = (int)$minimal_quantity;
  1177. // if we add a combination for this shop and this product does not use the combination feature in other shop,
  1178. // we clone the default combination in every shop linked to this product
  1179. if ($default && !$this->hasAttributesInOtherShops())
  1180. {
  1181. $id_shop_list_array = Product::getShopsByProduct($this->id);
  1182. foreach ($id_shop_list_array as $array_shop)
  1183. $id_shop_list[] = $array_shop['id_shop'];
  1184. }
  1185. if (count($id_shop_list))
  1186. $combination->id_shop_list = $id_shop_list;
  1187. $combination->add();
  1188. if (!$combination->id)
  1189. return false;
  1190. Product::updateDefaultAttribute($this->id);
  1191. if (!empty($id_images))
  1192. $combination->setImages($id_images);
  1193. return (int)$combination->id;
  1194. }
  1195. /**
  1196. * @deprecated since 1.5.0
  1197. */
  1198. public function updateQuantityProductWithAttributeQuantity()
  1199. {
  1200. Tools::displayAsDeprecated();
  1201. return Db::getInstance()->execute('
  1202. UPDATE `'._DB_PREFIX_.'product`
  1203. SET `quantity` = IFNULL(
  1204. (
  1205. SELECT SUM(`quantity`)
  1206. FROM `'._DB_PREFIX_.'product_attribute`
  1207. WHERE `id_product` = '.(int)$this->id.'
  1208. ), \'0\')
  1209. WHERE `id_product` = '.(int)$this->id);
  1210. }
  1211. /**
  1212. * Delete product attributes
  1213. *
  1214. * @return array Deletion result
  1215. */
  1216. public function deleteProductAttributes()
  1217. {
  1218. Hook::exec('actionProductAttributeDelete', array('id_product_attribute' => 0, 'id_product' => $this->id, 'deleteAllAttributes' => true));
  1219. $result = true;
  1220. $combinations = new Collection('Combination');
  1221. $combinations->where('id_product', '=', $this->id);
  1222. foreach ($combinations as $combination)
  1223. $result &= $combination->delete();
  1224. SpecificPriceRule::applyAllRules(array((int)$this->id));
  1225. return $result;
  1226. }
  1227. /**
  1228. * Delete product attributes impacts
  1229. *
  1230. * @return Deletion result
  1231. */
  1232. public function deleteAttributesImpacts()
  1233. {
  1234. return Db::getInstance()->execute(
  1235. 'DELETE FROM `'._DB_PREFIX_.'attribute_impact`
  1236. WHERE `id_product` = '.(int)$this->id
  1237. );
  1238. }
  1239. /**
  1240. * Delete product features
  1241. *
  1242. * @return array Deletion result
  1243. */
  1244. public function deleteProductFeatures()
  1245. {
  1246. SpecificPriceRule::applyAllRules(array((int)$this->id));
  1247. return $this->deleteFeatures();
  1248. }
  1249. /**
  1250. * Delete product attachments
  1251. *
  1252. * @return array Deletion result
  1253. */
  1254. public function deleteAttachments()
  1255. {
  1256. return Db::getInstance()->execute('
  1257. DELETE FROM `'._DB_PREFIX_.'product_attachment`
  1258. WHERE `id_product` = '.(int)$this->id
  1259. );
  1260. }
  1261. /**
  1262. * Delete product customizations
  1263. *
  1264. * @return array Deletion result
  1265. */
  1266. public function deleteCustomization()
  1267. {
  1268. return (
  1269. Db::getInstance()->execute(
  1270. 'DELETE FROM `'._DB_PREFIX_.'customization_field`
  1271. WHERE `id_product` = '.(int)$this->id
  1272. )
  1273. &&
  1274. Db::getInstance()->execute(
  1275. 'DELETE FROM `'._DB_PREFIX_.'customization_field_lang`
  1276. WHERE `id_customization_field` NOT IN (SELECT id_customization_field
  1277. FROM `'._DB_PREFIX_.'customization_field`)'
  1278. )
  1279. );
  1280. }
  1281. /**
  1282. * Delete product pack details
  1283. *
  1284. * @return array Deletion result
  1285. */
  1286. public function deletePack()
  1287. {
  1288. return Db::getInstance()->execute(
  1289. 'DELETE FROM `'._DB_PREFIX_.'pack`
  1290. WHERE `id_product_pack` = '.(int)$this->id.'
  1291. OR `id_product_item` = '.(int)$this->id
  1292. );
  1293. }
  1294. /**
  1295. * Delete product sales
  1296. *
  1297. * @return array Deletion result
  1298. */
  1299. public function deleteProductSale()
  1300. {
  1301. return Db::getInstance()->execute(
  1302. 'DELETE FROM `'._DB_PREFIX_.'product_sale`
  1303. WHERE `id_product` = '.(int)$this->id
  1304. );
  1305. }
  1306. /**
  1307. * Delete product in its scenes
  1308. *
  1309. * @return array Deletion result
  1310. */
  1311. public function deleteSceneProducts()
  1312. {
  1313. return Db::getInstance()->execute(
  1314. 'DELETE FROM `'._DB_PREFIX_.'scene_products`
  1315. WHERE `id_product` = '.(int)$this->id
  1316. );
  1317. }
  1318. /**
  1319. * Delete product indexed words
  1320. *
  1321. * @return array Deletion result
  1322. */
  1323. public function deleteSearchIndexes()
  1324. {
  1325. return (
  1326. Db::getInstance()->execute(
  1327. 'DELETE FROM `'._DB_PREFIX_.'search_index`
  1328. WHERE `id_product` = '.(int)$this->id
  1329. )
  1330. &&
  1331. Db::getInstance()->execute(
  1332. 'DELETE FROM `'._DB_PREFIX_.'search_word`
  1333. WHERE `id_word` NOT IN (
  1334. SELECT id_word
  1335. FROM `'._DB_PREFIX_.'search_index`
  1336. )'
  1337. )
  1338. );
  1339. }
  1340. /**
  1341. * Add a product attributes combinaison
  1342. *
  1343. * @param integer $id_product_attribute Product attribute id
  1344. * @param array $attributes Attributes to forge combinaison
  1345. * @return array Insertion result
  1346. * @deprecated since 1.5.0.7
  1347. */
  1348. public function addAttributeCombinaison($id_product_attribute, $attributes)
  1349. {
  1350. Tools::displayAsDeprecated();
  1351. if (!is_array($attributes))
  1352. die(Tools::displayError());
  1353. if (!count($attributes))
  1354. return false;
  1355. $combination = new Combination((int)$id_product_attribute);
  1356. return $combination->setAttributes($attributes);
  1357. }
  1358. public function addAttributeCombinationMultiple($id_attributes, $combinations)
  1359. {
  1360. Tools::displayAsDeprecated();
  1361. $attributes_list = array();
  1362. foreach ($id_attributes as $nb => $id_product_attribute)
  1363. if (isset($combinations[$nb]))
  1364. foreach ($combinations[$nb] as $id_attribute)
  1365. $attributes_list[] = array(
  1366. 'id_product_attribute' => (int)$id_product_attribute,
  1367. 'id_attribute' => (int)$id_attribute,
  1368. );
  1369. return Db::getInstance()->insert('product_attribute_combination', $attributes_list);
  1370. }
  1371. /**
  1372. * Delete a product attributes combination
  1373. *
  1374. * @param integer $id_product_attribute Product attribute id
  1375. * @return array Deletion result
  1376. */
  1377. public function deleteAttributeCombination($id_product_attribute)
  1378. {
  1379. if (!$this->id || !$id_product_attribute || !is_numeric($id_product_attribute))
  1380. return false;
  1381. Hook::exec(
  1382. 'deleteProductAttribute',
  1383. array(
  1384. 'id_product_attribute' => $id_product_attribute,
  1385. 'id_product' => $this->id,
  1386. 'deleteAllAttributes' => false
  1387. )
  1388. );
  1389. $combination = new Combination($id_product_attribute);
  1390. $res = $combination->delete();
  1391. SpecificPriceRule::applyAllRules(array((int)$this->id));
  1392. return $res;
  1393. }
  1394. /**
  1395. * Delete features
  1396. *
  1397. */
  1398. public function deleteFeatures()
  1399. {
  1400. // List products features
  1401. $features = Db::getInstance()->executeS('
  1402. SELECT p.*, f.*
  1403. FROM `'._DB_PREFIX_.'feature_product` as p
  1404. LEFT JOIN `'._DB_PREFIX_.'feature_value` as f ON (f.`id_feature_value` = p.`id_feature_value`)
  1405. WHERE `id_product` = '.(int)$this->id);
  1406. foreach ($features as $tab)
  1407. // Delete product custom features
  1408. if ($tab['custom'])
  1409. {
  1410. Db::getInstance()->execute('
  1411. DELETE FROM `'._DB_PREFIX_.'feature_value`
  1412. WHERE `id_feature_value` = '.(int)$tab['id_feature_value']);
  1413. Db::getInstance()->execute('
  1414. DELETE FROM `'._DB_PREFIX_.'feature_value_lang`
  1415. WHERE `id_feature_value` = '.(int)$tab['id_feature_value']);
  1416. }
  1417. // Delete product features
  1418. $result = Db::getInstance()->execute('
  1419. DELETE FROM `'._DB_PREFIX_.'feature_product`
  1420. WHERE `id_product` = '.(int)$this->id);
  1421. SpecificPriceRule::applyAllRules(array((int)$this->id));
  1422. return ($result);
  1423. }
  1424. /**
  1425. * Get all available product attributes resume
  1426. *
  1427. * @param integer $id_lang Language id
  1428. * @return array Product attributes combinations
  1429. */
  1430. public function getAttributesResume($id_lang, $attribute_value_separator = ' - ', $attribute_separator = ', ')
  1431. {
  1432. if (!Combination::isFeatureActive())
  1433. return array();
  1434. $add_shop = '';
  1435. $combinations = Db::getInstance()->executeS('SELECT pa.*, product_attribute_shop.*
  1436. FROM `'._DB_PREFIX_.'product_attribute` pa
  1437. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  1438. WHERE pa.`id_product` = '.(int)$this->id.'
  1439. GROUP BY pa.`id_product_attribute`');
  1440. if (!$combinations)
  1441. return false;
  1442. $product_attributes = array();
  1443. foreach ($combinations as $combination)
  1444. $product_attributes[] = (int)$combination['id_product_attribute'];
  1445. $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
  1446. FROM `'._DB_PREFIX_.'product_attribute_combination` pac
  1447. LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
  1448. LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
  1449. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)$id_lang.')
  1450. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)$id_lang.')
  1451. WHERE pac.id_product_attribute IN ('.implode(',', $product_attributes).')
  1452. GROUP BY pac.id_product_attribute');
  1453. foreach ($lang as $k => $row)
  1454. $combinations[$k]['attribute_designation'] = $row['attribute_designation'];
  1455. //Get quantity of each variations
  1456. foreach ($combinations as $key => $row)
  1457. {
  1458. $cache_key = $row['id_product'].'_'.$row['id_product_attribute'].'_quantity';
  1459. if (!Cache::isStored($cache_key))
  1460. Cache::store(
  1461. $cache_key,
  1462. StockAvailable::getQuantityAvailableByProduct($row['id_product'], $row['id_product_attribute'])
  1463. );
  1464. $combinations[$key]['quantity'] = Cache::retrieve($cache_key);
  1465. }
  1466. return $combinations;
  1467. }
  1468. /**
  1469. * Get all available product attributes combinations
  1470. *
  1471. * @param integer $id_lang Language id
  1472. * @return array Product attributes combinations
  1473. */
  1474. public function getAttributeCombinations($id_lang)
  1475. {
  1476. if (!Combination::isFeatureActive())
  1477. return array();
  1478. $sql = 'SELECT pa.*, product_attribute_shop.*, ag.`id_attribute_group`, ag.`is_color_group`, agl.`name` AS group_name, al.`name` AS attribute_name,
  1479. a.`id_attribute`, pa.`unit_price_impact`
  1480. FROM `'._DB_PREFIX_.'product_attribute` pa
  1481. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  1482. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
  1483. LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
  1484. LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
  1485. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)$id_lang.')
  1486. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)$id_lang.')
  1487. WHERE pa.`id_product` = '.(int)$this->id.'
  1488. GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group`
  1489. ORDER BY pa.`id_product_attribute`';
  1490. $res = Db::getInstance()->executeS($sql);
  1491. //Get quantity of each variations
  1492. foreach ($res as $key => $row)
  1493. {
  1494. $cache_key = $row['id_product'].'_'.$row['id_product_attribute'].'_quantity';
  1495. if (!Cache::isStored($cache_key))
  1496. Cache::store(
  1497. $cache_key,
  1498. StockAvailable::getQuantityAvailableByProduct($row['id_product'], $row['id_product_attribute'])
  1499. );
  1500. $res[$key]['quantity'] = Cache::retrieve($cache_key);
  1501. }
  1502. return $res;
  1503. }
  1504. /**
  1505. * Get product attribute combination by id_product_attribute
  1506. *
  1507. * @param integer $id_product_attribute
  1508. * @param integer $id_lang Language id
  1509. * @return array Product attribute combination by id_product_attribute
  1510. */
  1511. public function getAttributeCombinationsById($id_product_attribute, $id_lang)
  1512. {
  1513. if (!Combination::isFeatureActive())
  1514. return array();
  1515. $sql = 'SELECT pa.*, product_attribute_shop.*, ag.`id_attribute_group`, ag.`is_color_group`, agl.`name` AS group_name, al.`name` AS attribute_name,
  1516. a.`id_attribute`, pa.`unit_price_impact`
  1517. FROM `'._DB_PREFIX_.'product_attribute` pa
  1518. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  1519. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
  1520. LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
  1521. LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
  1522. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)$id_lang.')
  1523. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)$id_lang.')
  1524. WHERE pa.`id_product` = '.(int)$this->id.'
  1525. AND pa.`id_product_attribute` = '.(int)$id_product_attribute.'
  1526. GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group`
  1527. ORDER BY pa.`id_product_attribute`';
  1528. $res = Db::getInstance()->executeS($sql);
  1529. //Get quantity of each variations
  1530. foreach ($res as $key => $row)
  1531. {
  1532. $cache_key = $row['id_product'].'_'.$row['id_product_attribute'].'_quantity';
  1533. if (!Cache::isStored($cache_key))
  1534. Cache::store(
  1535. $cache_key,
  1536. StockAvailable::getQuantityAvailableByProduct($row['id_product'], $row['id_product_attribute'])
  1537. );
  1538. $res[$key]['quantity'] = Cache::retrieve($cache_key);
  1539. }
  1540. return $res;
  1541. }
  1542. public function getCombinationImages($id_lang)
  1543. {
  1544. if (!Combination::isFeatureActive())
  1545. return false;
  1546. $product_attributes = Db::getInstance()->executeS(
  1547. 'SELECT `id_product_attribute`
  1548. FROM `'._DB_PREFIX_.'product_attribute`
  1549. WHERE `id_product` = '.(int)$this->id
  1550. );
  1551. if (!$product_attributes)
  1552. return false;
  1553. $ids = array();
  1554. foreach ($product_attributes as $product_attribute)
  1555. $ids[] = (int)$product_attribute['id_product_attribute'];
  1556. $result = Db::getInstance()->executeS('
  1557. SELECT pai.`id_image`, pai.`id_product_attribute`, il.`legend`
  1558. FROM `'._DB_PREFIX_.'product_attribute_image` pai
  1559. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (il.`id_image` = pai.`id_image`)
  1560. LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_image` = pai.`id_image`)
  1561. WHERE pai.`id_product_attribute` IN ('.implode(', ', $ids).') AND il.`id_lang` = '.(int)$id_lang.' ORDER by i.`position`'
  1562. );
  1563. if (!$result)
  1564. return false;
  1565. $images = array();
  1566. foreach ($result as $row)
  1567. $images[$row['id_product_attribute']][] = $row;
  1568. return $images;
  1569. }
  1570. /**
  1571. * Check if product has attributes combinations
  1572. *
  1573. * @return integer Attributes combinations number
  1574. */
  1575. public function hasAttributes()
  1576. {
  1577. if (!Combination::isFeatureActive())
  1578. return 0;
  1579. return Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  1580. SELECT COUNT(*)
  1581. FROM `'._DB_PREFIX_.'product_attribute` pa
  1582. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  1583. WHERE pa.`id_product` = '.(int)$this->id
  1584. );
  1585. }
  1586. /**
  1587. * Get new products
  1588. *
  1589. * @param integer $id_lang Language id
  1590. * @param integer $pageNumber Start from (optional)
  1591. * @param integer $nbProducts Number of products to return (optional)
  1592. * @return array New products
  1593. */
  1594. public static function getNewProducts($id_lang, $page_number = 0, $nb_products = 10,
  1595. $count = false, $order_by = null, $order_way = null, Context $context = null)
  1596. {
  1597. if (!$context)
  1598. $context = Context::getContext();
  1599. $front = true;
  1600. if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
  1601. $front = false;
  1602. if ($page_number < 0) $page_number = 0;
  1603. if ($nb_products < 1) $nb_products = 10;
  1604. if (empty($order_by) || $order_by == 'position') $order_by = 'date_add';
  1605. if (empty($order_way)) $order_way = 'DESC';
  1606. if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add' || $order_by == 'date_upd')
  1607. $order_by_prefix = 'p';
  1608. else if ($order_by == 'name')
  1609. $order_by_prefix = 'pl';
  1610. if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
  1611. die(Tools::displayError());
  1612. $groups = FrontController::getCurrentCustomerGroups();
  1613. $sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');
  1614. if (strpos($order_by, '.') > 0)
  1615. {
  1616. $order_by = explode('.', $order_by);
  1617. $order_by_prefix = $order_by[0];
  1618. $order_by = $order_by[1];
  1619. }
  1620. if ($count)
  1621. {
  1622. $sql = 'SELECT COUNT(p.`id_product`) AS nb
  1623. FROM `'._DB_PREFIX_.'product` p
  1624. '.Shop::addSqlAssociation('product', 'p').'
  1625. WHERE product_shop.`active` = 1
  1626. AND DATEDIFF(
  1627. product_shop.`date_add`,
  1628. DATE_SUB(
  1629. NOW(),
  1630. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
  1631. )
  1632. ) > 0
  1633. '.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
  1634. AND p.`id_product` IN (
  1635. SELECT cp.`id_product`
  1636. FROM `'._DB_PREFIX_.'category_group` cg
  1637. LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
  1638. WHERE cg.`id_group` '.$sql_groups.'
  1639. )';
  1640. return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
  1641. }
  1642. $sql = new DbQuery();
  1643. $sql->select(
  1644. 'p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, pl.`link_rewrite`, pl.`meta_description`,
  1645. pl.`meta_keywords`, pl.`meta_title`, pl.`name`, image_shop.`id_image`, il.`legend`, t.`rate`, m.`name` AS manufacturer_name,
  1646. DATEDIFF(
  1647. product_shop.`date_add`,
  1648. DATE_SUB(
  1649. NOW(),
  1650. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
  1651. )
  1652. ) > 0 AS new,
  1653. (product_shop.`price` * ((100 + (t.`rate`))/100)) AS orderprice'
  1654. );
  1655. $sql->from('product', 'p');
  1656. $sql->join(Shop::addSqlAssociation('product', 'p'));
  1657. $sql->leftJoin('product_lang', 'pl', '
  1658. p.`id_product` = pl.`id_product`
  1659. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl')
  1660. );
  1661. $sql->leftJoin('image', 'i', 'i.`id_product` = p.`id_product`');
  1662. $sql->join(Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1'));
  1663. $sql->leftJoin('image_lang', 'il', 'i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang);
  1664. $sql->leftJoin('tax_rule', 'tr', '
  1665. product_shop.`id_tax_rules_group` = tr.`id_tax_rules_group`
  1666. AND tr.`id_country` = '.(int)$context->country->id.'
  1667. AND tr.`id_state` = 0'
  1668. );
  1669. $sql->leftJoin('tax', 't', 't.`id_tax` = tr.`id_tax`');
  1670. $sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');
  1671. $sql->where('product_shop.`active` = 1');
  1672. $sql->where('(image_shop.id_image IS NOT NULL OR i.id_image IS NULL) OR (image_shop.id_image IS NULL AND i.cover=1)');
  1673. if ($front)
  1674. $sql->where('product_shop.`visibility` IN ("both", "catalog")');
  1675. $sql->where('
  1676. DATEDIFF(
  1677. product_shop.`date_add`,
  1678. DATE_SUB(
  1679. NOW(),
  1680. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
  1681. )
  1682. ) > 0'
  1683. );
  1684. $sql->where('p.`id_product` IN (
  1685. SELECT cp.`id_product`
  1686. FROM `'._DB_PREFIX_.'category_group` cg
  1687. LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
  1688. WHERE cg.`id_group` '.$sql_groups.')'
  1689. );
  1690. $sql->orderBy((isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').'`'.pSQL($order_by).'` '.pSQL($order_way));
  1691. $sql->limit($nb_products, $page_number * $nb_products);
  1692. if (Combination::isFeatureActive())
  1693. {
  1694. $sql->select('pa.id_product_attribute');
  1695. $sql->leftOuterJoin('product_attribute', 'pa', 'p.`id_product` = pa.`id_product`');
  1696. $sql->join(Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on = 1'));
  1697. $sql->where('(pa.id_product_attribute IS NULL OR product_attribute_shop.id_shop='.(int)$context->shop->id.')');
  1698. }
  1699. $sql->join(Product::sqlStock('p', Combination::isFeatureActive() ? 'product_attribute_shop' : 0));
  1700. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  1701. if ($order_by == 'price')
  1702. Tools::orderbyPrice($result, $order_way);
  1703. if (!$result)
  1704. return false;
  1705. $products_ids = array();
  1706. foreach ($result as $row)
  1707. $products_ids[] = $row['id_product'];
  1708. // Thus you can avoid one query per product, because there will be only one query for all the products of the cart
  1709. Product::cacheFrontFeatures($products_ids, $id_lang);
  1710. return Product::getProductsProperties((int)$id_lang, $result);
  1711. }
  1712. protected static function _getProductIdByDate($beginning, $ending, Context $context = null, $with_combination = false)
  1713. {
  1714. if (!$context)
  1715. $context = Context::getContext();
  1716. $id_address = $context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
  1717. $ids = Address::getCountryAndState($id_address);
  1718. $id_country = (int)($ids['id_country'] ? $ids['id_country'] : Configuration::get('PS_COUNTRY_DEFAULT'));
  1719. return SpecificPrice::getProductIdByDate(
  1720. $context->shop->id,
  1721. $context->currency->id,
  1722. $id_country,
  1723. $context->customer->id_default_group,
  1724. $beginning,
  1725. $ending,
  1726. 0,
  1727. $with_combination
  1728. );
  1729. }
  1730. /**
  1731. * Get a random special
  1732. *
  1733. * @param integer $id_lang Language id
  1734. * @return array Special
  1735. */
  1736. public static function getRandomSpecial($id_lang, $beginning = false, $ending = false, Context $context = null)
  1737. {
  1738. if (!$context)
  1739. $context = Context::getContext();
  1740. $front = true;
  1741. if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
  1742. $front = false;
  1743. $current_date = date('Y-m-d H:i:s');
  1744. $product_reductions = Product::_getProductIdByDate((!$beginning ? $current_date : $beginning), (!$ending ? $current_date : $ending), $context, true);
  1745. if ($product_reductions)
  1746. {
  1747. $ids_product = ' AND (';
  1748. foreach ($product_reductions as $product_reduction)
  1749. $ids_product .= '( product_shop.`id_product` = '.(int)$product_reduction['id_product'].($product_reduction['id_product_attribute'] ? ' AND product_attribute_shop.`id_product_attribute`='.(int)$product_reduction['id_product_attribute'] :'').') OR';
  1750. $ids_product = rtrim($ids_product, 'OR').')';
  1751. $groups = FrontController::getCurrentCustomerGroups();
  1752. $sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');
  1753. // Please keep 2 distinct queries because RAND() is an awful way to achieve this result
  1754. $sql = 'SELECT product_shop.id_product, product_attribute_shop.id_product_attribute
  1755. FROM `'._DB_PREFIX_.'product` p
  1756. '.Shop::addSqlAssociation('product', 'p').'
  1757. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (product_shop.id_product = pa.id_product)
  1758. '.Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on = 1').'
  1759. WHERE product_shop.`active` = 1
  1760. '.(($ids_product) ? $ids_product : '').'
  1761. AND p.`id_product` IN (
  1762. SELECT cp.`id_product`
  1763. FROM `'._DB_PREFIX_.'category_group` cg
  1764. LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
  1765. WHERE cg.`id_group` '.$sql_groups.'
  1766. )
  1767. '.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
  1768. AND (pa.id_product_attribute IS NULL OR product_attribute_shop.default_on = 1)
  1769. GROUP BY product_shop.id_product
  1770. ORDER BY RAND()';
  1771. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
  1772. if (!$id_product = $result['id_product'])
  1773. return false;
  1774. $sql = 'SELECT p.*, product_shop.*, stock.`out_of_stock` out_of_stock, pl.`description`, pl.`description_short`,
  1775. pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`,
  1776. p.`ean13`, p.`upc`, image_shop.`id_image`, il.`legend`, t.`rate`
  1777. FROM `'._DB_PREFIX_.'product` p
  1778. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
  1779. p.`id_product` = pl.`id_product`
  1780. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
  1781. )
  1782. '.Shop::addSqlAssociation('product', 'p').'
  1783. LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'.
  1784. Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
  1785. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
  1786. LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (product_shop.`id_tax_rules_group` = tr.`id_tax_rules_group`
  1787. AND tr.`id_country` = '.(int)Context::getContext()->country->id.'
  1788. AND tr.`id_state` = 0)
  1789. LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
  1790. '.Product::sqlStock('p', 0).'
  1791. WHERE p.id_product = '.(int)$id_product.'
  1792. AND (i.id_image IS NULL OR image_shop.id_shop='.(int)$context->shop->id.')';
  1793. $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
  1794. if (!$row)
  1795. return false;
  1796. if ($result['id_product_attribute'])
  1797. $row['id_product_attribute'] = $result['id_product_attribute'];
  1798. return Product::getProductProperties($id_lang, $row);
  1799. }
  1800. else
  1801. return false;
  1802. }
  1803. /**
  1804. * Get prices drop
  1805. *
  1806. * @param integer $id_lang Language id
  1807. * @param integer $pageNumber Start from (optional)
  1808. * @param integer $nbProducts Number of products to return (optional)
  1809. * @param boolean $count Only in order to get total number (optional)
  1810. * @return array Prices drop
  1811. */
  1812. public static function getPricesDrop($id_lang, $page_number = 0, $nb_products = 10, $count = false,
  1813. $order_by = null, $order_way = null, $beginning = false, $ending = false, Context $context = null)
  1814. {
  1815. if (!Validate::isBool($count))
  1816. die(Tools::displayError());
  1817. if (!$context) $context = Context::getContext();
  1818. if ($page_number < 0) $page_number = 0;
  1819. if ($nb_products < 1) $nb_products = 10;
  1820. if (empty($order_by) || $order_by == 'position') $order_by = 'price';
  1821. if (empty($order_way)) $order_way = 'DESC';
  1822. if ($order_by == 'id_product' || $order_by == 'price' || $order_by == 'date_add' || $order_by == 'date_upd')
  1823. $order_by_prefix = 'p';
  1824. else if ($order_by == 'name')
  1825. $order_by_prefix = 'pl';
  1826. if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
  1827. die (Tools::displayError());
  1828. $current_date = date('Y-m-d H:i:s');
  1829. $ids_product = Product::_getProductIdByDate((!$beginning ? $current_date : $beginning), (!$ending ? $current_date : $ending), $context);
  1830. $tab_id_product = array();
  1831. foreach ($ids_product as $product)
  1832. if (is_array($product))
  1833. $tab_id_product[] = (int)$product['id_product'];
  1834. else
  1835. $tab_id_product[] = (int)$product;
  1836. $front = true;
  1837. if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
  1838. $front = false;
  1839. $groups = FrontController::getCurrentCustomerGroups();
  1840. $sql_groups = (count($groups) ? 'IN ('.implode(',', $groups).')' : '= 1');
  1841. if ($count)
  1842. {
  1843. $sql = 'SELECT COUNT(DISTINCT p.`id_product`) AS nb
  1844. FROM `'._DB_PREFIX_.'product` p
  1845. '.Shop::addSqlAssociation('product', 'p').'
  1846. WHERE product_shop.`active` = 1
  1847. AND product_shop.`show_price` = 1
  1848. '.($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').'
  1849. '.((!$beginning && !$ending) ? 'AND p.`id_product` IN('.((is_array($tab_id_product) && count($tab_id_product)) ? implode(', ', $tab_id_product) : 0).')' : '').'
  1850. AND p.`id_product` IN (
  1851. SELECT cp.`id_product`
  1852. FROM `'._DB_PREFIX_.'category_group` cg
  1853. LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
  1854. WHERE cg.`id_group` '.$sql_groups.'
  1855. )';
  1856. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
  1857. return (int)$result['nb'];
  1858. }
  1859. if (strpos($order_by, '.') > 0)
  1860. {
  1861. $order_by = explode('.', $order_by);
  1862. $order_by = pSQL($order_by[0]).'.`'.pSQL($order_by[1]).'`';
  1863. }
  1864. $sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, product_attribute_shop.id_product_attribute,
  1865. pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`,
  1866. pl.`name`, image_shop.`id_image`, il.`legend`, t.`rate`, m.`name` AS manufacturer_name,
  1867. DATEDIFF(
  1868. p.`date_add`,
  1869. DATE_SUB(
  1870. NOW(),
  1871. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
  1872. )
  1873. ) > 0 AS new
  1874. FROM `'._DB_PREFIX_.'product` p
  1875. '.Shop::addSqlAssociation('product', 'p').'
  1876. LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (pa.id_product = p.id_product)
  1877. '.Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.default_on=1').'
  1878. '.Product::sqlStock('p', 0, false, $context->shop).'
  1879. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
  1880. p.`id_product` = pl.`id_product`
  1881. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
  1882. )
  1883. LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'.
  1884. Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
  1885. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
  1886. LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (product_shop.`id_tax_rules_group` = tr.`id_tax_rules_group`
  1887. AND tr.`id_country` = '.(int)Context::getContext()->country->id.'
  1888. AND tr.`id_state` = 0)
  1889. LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
  1890. LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.`id_manufacturer` = p.`id_manufacturer`)
  1891. WHERE product_shop.`active` = 1
  1892. AND product_shop.`show_price` = 1
  1893. AND ((image_shop.id_image IS NOT NULL OR i.id_image IS NULL) OR (image_shop.id_image IS NULL AND i.cover=1))
  1894. '.($front ? ' AND p.`visibility` IN ("both", "catalog")' : '').'
  1895. '.((!$beginning && !$ending) ? ' AND p.`id_product` IN ('.((is_array($tab_id_product) && count($tab_id_product)) ? implode(', ', $tab_id_product) : 0).')' : '').'
  1896. AND p.`id_product` IN (
  1897. SELECT cp.`id_product`
  1898. FROM `'._DB_PREFIX_.'category_group` cg
  1899. LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON (cp.`id_category` = cg.`id_category`)
  1900. WHERE cg.`id_group` '.$sql_groups.'
  1901. )
  1902. AND (pa.id_product_attribute IS NULL OR product_attribute_shop.default_on = 1)
  1903. ORDER BY '.(isset($order_by_prefix) ? pSQL($order_by_prefix).'.' : '').pSQL($order_by).' '.pSQL($order_way).'
  1904. LIMIT '.(int)($page_number * $nb_products).', '.(int)$nb_products;
  1905. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  1906. if ($order_by == 'price')
  1907. Tools::orderbyPrice($result, $order_way);
  1908. if (!$result)
  1909. return false;
  1910. return Product::getProductsProperties($id_lang, $result);
  1911. }
  1912. /**
  1913. * getProductCategories return an array of categories which this product belongs to
  1914. *
  1915. * @return array of categories
  1916. */
  1917. public static function getProductCategories($id_product = '')
  1918. {
  1919. $ret = array();
  1920. $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  1921. SELECT `id_category` FROM `'._DB_PREFIX_.'category_product`
  1922. WHERE `id_product` = '.(int)$id_product
  1923. );
  1924. if ($row)
  1925. foreach ($row as $val)
  1926. $ret[] = $val['id_category'];
  1927. return $ret;
  1928. }
  1929. public static function getProductCategoriesFull($id_product = '', $id_lang = null)
  1930. {
  1931. if (!$id_lang)
  1932. $id_lang = Context::getContext()->language->id;
  1933. $ret = array();
  1934. $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  1935. SELECT cp.`id_category`, cl.`name`, cl.`link_rewrite` FROM `'._DB_PREFIX_.'category_product` cp
  1936. LEFT JOIN `'._DB_PREFIX_.'category` c ON (c.id_category = cp.id_category)
  1937. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (cp.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').')
  1938. '.Shop::addSqlAssociation('category', 'c').'
  1939. WHERE cp.`id_product` = '.(int)$id_product.'
  1940. AND cl.`id_lang` = '.(int)$id_lang
  1941. );
  1942. foreach ($row as $val)
  1943. $ret[$val['id_category']] = $val;
  1944. return $ret;
  1945. }
  1946. /**
  1947. * getCategories return an array of categories which this product belongs to
  1948. *
  1949. * @return array of categories
  1950. */
  1951. public function getCategories()
  1952. {
  1953. return Product::getProductCategories($this->id);
  1954. }
  1955. /**
  1956. * Gets carriers assigned to the product
  1957. */
  1958. public function getCarriers()
  1959. {
  1960. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  1961. SELECT c.*
  1962. FROM `'._DB_PREFIX_.'product_carrier` pc
  1963. INNER JOIN `'._DB_PREFIX_.'carrier` c
  1964. ON (c.`id_reference` = pc.`id_carrier_reference` AND c.`deleted` = 0)
  1965. WHERE pc.`id_product` = '.(int)$this->id.'
  1966. AND pc.`id_shop` = '.(int)$this->id_shop);
  1967. }
  1968. /**
  1969. * Sets carriers assigned to the product
  1970. */
  1971. public function setCarriers($carrier_list)
  1972. {
  1973. $data = array();
  1974. foreach ($carrier_list as $carrier)
  1975. {
  1976. $data[] = array(
  1977. 'id_product' => (int)$this->id,
  1978. 'id_carrier_reference' => (int)$carrier,
  1979. 'id_shop' => (int)$this->id_shop
  1980. );
  1981. }
  1982. Db::getInstance()->execute(
  1983. 'DELETE FROM `'._DB_PREFIX_.'product_carrier`
  1984. WHERE id_product = '.(int)$this->id.'
  1985. AND id_shop = '.(int)$this->id_shop
  1986. );
  1987. if (count($data))
  1988. Db::getInstance()->insert('product_carrier', $data);
  1989. }
  1990. /**
  1991. * Get product images and legends
  1992. *
  1993. * @param integer $id_lang Language id for multilingual legends
  1994. * @return array Product images and legends
  1995. */
  1996. public function getImages($id_lang, Context $context = null)
  1997. {
  1998. if (!$context)
  1999. $context = Context::getContext();
  2000. $sql = 'SELECT image_shop.`cover`, i.`id_image`, il.`legend`, i.`position`
  2001. FROM `'._DB_PREFIX_.'image` i
  2002. '.Shop::addSqlAssociation('image', 'i').'
  2003. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
  2004. WHERE i.`id_product` = '.(int)$this->id.'
  2005. ORDER BY `position`';
  2006. return Db::getInstance()->executeS($sql);
  2007. }
  2008. /**
  2009. * Get product cover image
  2010. *
  2011. * @return array Product cover image
  2012. */
  2013. public static function getCover($id_product, Context $context = null)
  2014. {
  2015. if (!$context)
  2016. $context = Context::getContext();
  2017. $sql = 'SELECT image_shop.`id_image`
  2018. FROM `'._DB_PREFIX_.'image` i
  2019. '.Shop::addSqlAssociation('image', 'i').'
  2020. WHERE i.`id_product` = '.(int)$id_product.'
  2021. AND image_shop.`cover` = 1';
  2022. return Db::getInstance()->getRow($sql);
  2023. }
  2024. /**
  2025. * Get product price
  2026. *
  2027. * @param integer $id_product Product id
  2028. * @param boolean $usetax With taxes or not (optional)
  2029. * @param integer $id_product_attribute Product attribute id (optional).
  2030. * If set to false, do not apply the combination price impact. NULL does apply the default combination price impact.
  2031. * @param integer $decimals Number of decimals (optional)
  2032. * @param integer $divisor Useful when paying many time without fees (optional)
  2033. * @param boolean $only_reduc Returns only the reduction amount
  2034. * @param boolean $usereduc Set if the returned amount will include reduction
  2035. * @param integer $quantity Required for quantity discount application (default value: 1)
  2036. * @param boolean $forceAssociatedTax DEPRECATED - NOT USED Force to apply the associated tax. Only works when the parameter $usetax is true
  2037. * @param integer $id_customer Customer ID (for customer group reduction)
  2038. * @param integer $id_cart Cart ID. Required when the cookie is not accessible (e.g., inside a payment module, a cron task...)
  2039. * @param integer $id_address Customer address ID. Required for price (tax included) calculation regarding the guest localization
  2040. * @param variable_reference $specificPriceOutput.
  2041. * If a specific price applies regarding the previous parameters, this variable is filled with the corresponding SpecificPrice object
  2042. * @param boolean $with_ecotax insert ecotax in price output.
  2043. * @return float Product price
  2044. */
  2045. public static function getPriceStatic($id_product, $usetax = true, $id_product_attribute = null, $decimals = 6, $divisor = null,
  2046. $only_reduc = false, $usereduc = true, $quantity = 1, $force_associated_tax = false, $id_customer = null, $id_cart = null,
  2047. $id_address = null, &$specific_price_output = null, $with_ecotax = true, $use_group_reduction = true, Context $context = null,
  2048. $use_customer_price = true)
  2049. {
  2050. if (!$context)
  2051. $context = Context::getContext();
  2052. $cur_cart = $context->cart;
  2053. if ($divisor !== null)
  2054. Tools::displayParameterAsDeprecated('divisor');
  2055. if (!Validate::isBool($usetax) || !Validate::isUnsignedId($id_product))
  2056. die(Tools::displayError());
  2057. // Initializations
  2058. $id_group = (isset($context->customer) ? $context->customer->id_default_group : _PS_DEFAULT_CUSTOMER_GROUP_);
  2059. // If there is cart in context or if the specified id_cart is different from the context cart id
  2060. if (!is_object($cur_cart) || (Validate::isUnsignedInt($id_cart) && $id_cart && $cur_cart->id != $id_cart))
  2061. {
  2062. /*
  2063. * When a user (e.g., guest, customer, Google...) is on PrestaShop, he has already its cart as the global (see /init.php)
  2064. * 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
  2065. * When called from the back office, cart ID can be inexistant
  2066. */
  2067. if (!$id_cart && !isset($context->employee))
  2068. die(Tools::displayError());
  2069. $cur_cart = new Cart($id_cart);
  2070. // Store cart in context to avoid multiple instantiations in BO
  2071. if (!Validate::isLoadedObject($context->cart))
  2072. $context->cart = $cur_cart;
  2073. }
  2074. $cart_quantity = 0;
  2075. if ((int)$id_cart)
  2076. {
  2077. $condition = '';
  2078. $cache_name = (int)$id_cart.'_'.(int)$id_product;
  2079. if (!isset(self::$_cart_quantity[$cache_name]) || self::$_cart_quantity[$cache_name] != (int)$quantity)
  2080. self::$_cart_quantity[$cache_name] = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  2081. SELECT SUM(`quantity`)
  2082. FROM `'._DB_PREFIX_.'cart_product`
  2083. WHERE `id_product` = '.(int)$id_product.'
  2084. AND `id_cart` = '.(int)$id_cart);
  2085. $cart_quantity = self::$_cart_quantity[$cache_name];
  2086. }
  2087. $id_currency = (int)Validate::isLoadedObject($context->currency) ? $context->currency->id : Configuration::get('PS_CURRENCY_DEFAULT');
  2088. // retrieve address informations
  2089. $id_country = (int)$context->country->id;
  2090. $id_state = 0;
  2091. $zipcode = 0;
  2092. if (!$id_address)
  2093. $id_address = $cur_cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')};
  2094. if ($id_address)
  2095. {
  2096. $address_infos = Address::getCountryAndState($id_address);
  2097. if ($address_infos['id_country'])
  2098. {
  2099. $id_country = (int)$address_infos['id_country'];
  2100. $id_state = (int)$address_infos['id_state'];
  2101. $zipcode = $address_infos['postcode'];
  2102. }
  2103. }
  2104. else if (isset($context->customer->geoloc_id_country))
  2105. {
  2106. $id_country = (int)$context->customer->geoloc_id_country;
  2107. $id_state = (int)$context->customer->id_state;
  2108. $zipcode = (int)$context->customer->postcode;
  2109. }
  2110. if (Tax::excludeTaxeOption())
  2111. $usetax = false;
  2112. if ($usetax != false
  2113. && !empty($address_infos['vat_number'])
  2114. && $address_infos['id_country'] != Configuration::get('VATNUMBER_COUNTRY')
  2115. && Configuration::get('VATNUMBER_MANAGEMENT'))
  2116. $usetax = false;
  2117. if (is_null($id_customer) && Validate::isLoadedObject($context->customer))
  2118. $id_customer = $context->customer->id;
  2119. return Product::priceCalculation(
  2120. $context->shop->id,
  2121. $id_product,
  2122. $id_product_attribute,
  2123. $id_country,
  2124. $id_state,
  2125. $zipcode,
  2126. $id_currency,
  2127. $id_group,
  2128. $cart_quantity,
  2129. $usetax,
  2130. $decimals,
  2131. $only_reduc,
  2132. $usereduc,
  2133. $with_ecotax,
  2134. $specific_price_output,
  2135. $use_group_reduction,
  2136. $id_customer,
  2137. $use_customer_price,
  2138. $id_cart,
  2139. $quantity
  2140. );
  2141. }
  2142. /**
  2143. * Price calculation / Get product price
  2144. *
  2145. * @param integer $id_shop Shop id
  2146. * @param integer $id_product Product id
  2147. * @param integer $id_product_attribute Product attribute id
  2148. * @param integer $id_country Country id
  2149. * @param integer $id_state State id
  2150. * @param integer $id_currency Currency id
  2151. * @param integer $id_group Group id
  2152. * @param integer $quantity Quantity Required for Specific prices : quantity discount application
  2153. * @param boolean $use_tax with (1) or without (0) tax
  2154. * @param integer $decimals Number of decimals returned
  2155. * @param boolean $only_reduc Returns only the reduction amount
  2156. * @param boolean $use_reduc Set if the returned amount will include reduction
  2157. * @param boolean $with_ecotax insert ecotax in price output.
  2158. * @param variable_reference $specific_price_output
  2159. * If a specific price applies regarding the previous parameters, this variable is filled with the corresponding SpecificPrice object
  2160. * @return float Product price
  2161. **/
  2162. public static function priceCalculation($id_shop, $id_product, $id_product_attribute, $id_country, $id_state, $zipcode, $id_currency,
  2163. $id_group, $quantity, $use_tax, $decimals, $only_reduc, $use_reduc, $with_ecotax, &$specific_price, $use_group_reduction,
  2164. $id_customer = 0, $use_customer_price = true, $id_cart = 0, $real_quantity = 0)
  2165. {
  2166. static $address = null;
  2167. static $context = null;
  2168. if ($address === null)
  2169. $address = new Address();
  2170. if ($context == null)
  2171. $context = Context::getContext()->cloneContext();
  2172. if ($id_shop !== null && $context->shop->id != (int)$id_shop)
  2173. $context->shop = new Shop((int)$id_shop);
  2174. if (!$use_customer_price)
  2175. $id_customer = 0;
  2176. $cache_id = $id_product.'-'.$id_shop.'-'.$id_currency.'-'.$id_country.'-'.$id_state.'-'.$zipcode.'-'.$id_group.
  2177. '-'.$quantity.'-'.(int)$id_product_attribute.'-'.($use_tax?'1':'0').'-'.$decimals.'-'.($only_reduc?'1':'0').
  2178. '-'.($use_reduc?'1':'0').'-'.$with_ecotax.'-'.$id_customer;
  2179. // reference parameter is filled before any returns
  2180. $specific_price = SpecificPrice::getSpecificPrice(
  2181. (int)$id_product,
  2182. $id_shop,
  2183. $id_currency,
  2184. $id_country,
  2185. $id_group,
  2186. $quantity,
  2187. $id_product_attribute,
  2188. $id_customer,
  2189. $id_cart,
  2190. $real_quantity
  2191. );
  2192. if (isset(self::$_prices[$cache_id]))
  2193. return self::$_prices[$cache_id];
  2194. // fetch price & attribute price
  2195. $cache_id_2 = $id_product;
  2196. if (!isset(self::$_pricesLevel2[$cache_id_2]))
  2197. {
  2198. $sql = new DbQuery();
  2199. $sql->select('product_shop.`price`, product_shop.`ecotax`');
  2200. $sql->from('product', 'p');
  2201. $sql->innerJoin('product_shop', 'product_shop', '(product_shop.id_product=p.id_product AND product_shop.id_shop='.(int)$id_shop.')');
  2202. $sql->where('p.`id_product` = '.(int)$id_product);
  2203. if (Combination::isFeatureActive())
  2204. {
  2205. $sql->select('product_attribute_shop.id_product_attribute, product_attribute_shop.`price` AS attribute_price, product_attribute_shop.default_on');
  2206. $sql->leftJoin('product_attribute', 'pa', 'pa.`id_product` = p.`id_product`');
  2207. $sql->leftJoin('product_attribute_shop', 'product_attribute_shop', '(product_attribute_shop.id_product_attribute=pa.id_product_attribute AND product_attribute_shop.id_shop='.(int)$id_shop.')');
  2208. }
  2209. else
  2210. $sql->select('0 as id_product_attribute');
  2211. $res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  2212. foreach ($res as $row)
  2213. {
  2214. $array_tmp = array(
  2215. 'price' => $row['price'],
  2216. 'ecotax' => $row['ecotax'],
  2217. 'attribute_price' => (isset($row['attribute_price']) ? $row['attribute_price'] : null)
  2218. );
  2219. self::$_pricesLevel2[$cache_id_2][(int)$row['id_product_attribute']] = $array_tmp;
  2220. if (isset($row['default_on']) && $row['default_on'] == 1)
  2221. self::$_pricesLevel2[$cache_id_2][0] = $array_tmp;
  2222. }
  2223. }
  2224. if (!isset(self::$_pricesLevel2[$cache_id_2][(int)$id_product_attribute]))
  2225. return;
  2226. $result = self::$_pricesLevel2[$cache_id_2][(int)$id_product_attribute];
  2227. if (!$specific_price || $specific_price['price'] < 0)
  2228. $price = (float)$result['price'];
  2229. else
  2230. $price = (float)$specific_price['price'];
  2231. // convert only if the specific price is in the default currency (id_currency = 0)
  2232. if (!$specific_price || !($specific_price['price'] >= 0 && $specific_price['id_currency']))
  2233. $price = Tools::convertPrice($price, $id_currency);
  2234. // Attribute price
  2235. if (is_array($result) && (!$specific_price || !$specific_price['id_product_attribute'] || $specific_price['price'] < 0))
  2236. {
  2237. $attribute_price = Tools::convertPrice($result['attribute_price'] !== null ? (float)$result['attribute_price'] : 0, $id_currency);
  2238. if ($id_product_attribute !== false && !is_null($id_product_attribute)) // If you want the default combination, please use NULL value instead
  2239. $price += $attribute_price;
  2240. }
  2241. // Tax
  2242. $address->id_country = $id_country;
  2243. $address->id_state = $id_state;
  2244. $address->postcode = $zipcode;
  2245. $tax_manager = TaxManagerFactory::getManager($address, Product::getIdTaxRulesGroupByIdProduct((int)$id_product, $context));
  2246. $product_tax_calculator = $tax_manager->getTaxCalculator();
  2247. // Add Tax
  2248. if ($use_tax)
  2249. $price = $product_tax_calculator->addTaxes($price);
  2250. $price = Tools::ps_round($price, $decimals);
  2251. // Reduction
  2252. $reduc = 0;
  2253. if (($only_reduc || $use_reduc) && $specific_price)
  2254. {
  2255. if ($specific_price['reduction_type'] == 'amount')
  2256. {
  2257. $reduction_amount = $specific_price['reduction'];
  2258. if (!$specific_price['id_currency'])
  2259. $reduction_amount = Tools::convertPrice($reduction_amount, $id_currency);
  2260. $reduc = Tools::ps_round(!$use_tax ? $product_tax_calculator->removeTaxes($reduction_amount) : $reduction_amount, $decimals);
  2261. }
  2262. else
  2263. $reduc = Tools::ps_round($price * $specific_price['reduction'], $decimals);
  2264. }
  2265. if ($only_reduc)
  2266. return $reduc;
  2267. if ($use_reduc)
  2268. $price -= $reduc;
  2269. // Group reduction
  2270. if ($use_group_reduction)
  2271. {
  2272. if ($reduction_from_category = (float)GroupReduction::getValueForProduct($id_product, $id_group))
  2273. $price -= $price * $reduction_from_category;
  2274. else // apply group reduction if there is no group reduction for this category
  2275. $price *= ((100 - Group::getReductionByIdGroup($id_group)) / 100);
  2276. }
  2277. $price = Tools::ps_round($price, $decimals);
  2278. // Eco Tax
  2279. if (($result['ecotax'] || isset($result['attribute_ecotax'])) && $with_ecotax)
  2280. {
  2281. $ecotax = $result['ecotax'];
  2282. if (isset($result['attribute_ecotax']) && $result['attribute_ecotax'] > 0)
  2283. $ecotax = $result['attribute_ecotax'];
  2284. if ($id_currency)
  2285. $ecotax = Tools::convertPrice($ecotax, $id_currency);
  2286. if ($use_tax)
  2287. {
  2288. // reinit the tax manager for ecotax handling
  2289. $tax_manager = TaxManagerFactory::getManager(
  2290. $address,
  2291. (int)Configuration::get('PS_ECOTAX_TAX_RULES_GROUP_ID')
  2292. );
  2293. $ecotax_tax_calculator = $tax_manager->getTaxCalculator();
  2294. $price += $ecotax_tax_calculator->addTaxes($ecotax);
  2295. }
  2296. else
  2297. $price += $ecotax;
  2298. }
  2299. $price = Tools::ps_round($price, $decimals);
  2300. if ($price < 0)
  2301. $price = 0;
  2302. self::$_prices[$cache_id] = $price;
  2303. return self::$_prices[$cache_id];
  2304. }
  2305. public static function convertAndFormatPrice($price, $currency = false, Context $context = null)
  2306. {
  2307. if (!$context)
  2308. $context = Context::getContext();
  2309. if (!$currency)
  2310. $currency = $context->currency;
  2311. return Tools::displayPrice(Tools::convertPrice($price, $currency), $currency);
  2312. }
  2313. public static function isDiscounted($id_product, $quantity = 1, Context $context = null)
  2314. {
  2315. if (!$context)
  2316. $context = Context::getContext();
  2317. $id_group = $context->customer->id_default_group;
  2318. $cart_quantity = !$context->cart ? 0 : Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  2319. SELECT SUM(`quantity`)
  2320. FROM `'._DB_PREFIX_.'cart_product`
  2321. WHERE `id_product` = '.(int)$id_product.' AND `id_cart` = '.(int)$context->cart->id
  2322. );
  2323. $quantity = $cart_quantity ? $cart_quantity : $quantity;
  2324. $id_currency = (int)$context->currency->id;
  2325. $ids = Address::getCountryAndState((int)$context->cart->{Configuration::get('PS_TAX_ADDRESS_TYPE')});
  2326. $id_country = (int)($ids['id_country'] ? $ids['id_country'] : Configuration::get('PS_COUNTRY_DEFAULT'));
  2327. return (bool)SpecificPrice::getSpecificPrice((int)$id_product, $context->shop->id, $id_currency, $id_country, $id_group, $quantity);
  2328. }
  2329. /**
  2330. * Get product price
  2331. * Same as static function getPriceStatic, no need to specify product id
  2332. *
  2333. * @param boolean $tax With taxes or not (optional)
  2334. * @param integer $id_product_attribute Product attribute id (optional)
  2335. * @param integer $decimals Number of decimals (optional)
  2336. * @param integer $divisor Util when paying many time without fees (optional)
  2337. * @return float Product price in euros
  2338. */
  2339. public function getPrice($tax = true, $id_product_attribute = null, $decimals = 6,
  2340. $divisor = null, $only_reduc = false, $usereduc = true, $quantity = 1)
  2341. {
  2342. return Product::getPriceStatic((int)$this->id, $tax, $id_product_attribute, $decimals, $divisor, $only_reduc, $usereduc, $quantity);
  2343. }
  2344. public function getPublicPrice($tax = true, $id_product_attribute = null, $decimals = 6,
  2345. $divisor = null, $only_reduc = false, $usereduc = true, $quantity = 1)
  2346. {
  2347. $specific_price_output = null;
  2348. return Product::getPriceStatic((int)$this->id, $tax, $id_product_attribute, $decimals, $divisor, $only_reduc, $usereduc, $quantity,
  2349. false, null, null, null, $specific_price_output, true, true, null, false);
  2350. }
  2351. public function getIdProductAttributeMostExpensive()
  2352. {
  2353. if (!Combination::isFeatureActive())
  2354. return 0;
  2355. $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('
  2356. SELECT pa.`id_product_attribute`
  2357. FROM `'._DB_PREFIX_.'product_attribute` pa
  2358. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  2359. WHERE pa.`id_product` = '.(int)$this->id.'
  2360. ORDER BY product_attribute_shop.`price` DESC');
  2361. return (isset($row['id_product_attribute']) && $row['id_product_attribute']) ? (int)$row['id_product_attribute'] : 0;
  2362. }
  2363. public function getDefaultIdProductAttribute()
  2364. {
  2365. if (!Combination::isFeatureActive())
  2366. return 0;
  2367. $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('
  2368. SELECT pa.`id_product_attribute`
  2369. FROM `'._DB_PREFIX_.'product_attribute` pa
  2370. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  2371. WHERE pa.`id_product` = '.(int)$this->id.'
  2372. AND product_attribute_shop.default_on = 1'
  2373. );
  2374. return (isset($row['id_product_attribute']) && $row['id_product_attribute']) ? (int)$row['id_product_attribute'] : 0;
  2375. }
  2376. public function getPriceWithoutReduct($notax = false, $id_product_attribute = false)
  2377. {
  2378. return Product::getPriceStatic((int)$this->id, !$notax, $id_product_attribute, 6, null, false, false);
  2379. }
  2380. /**
  2381. * Display price with right format and currency
  2382. *
  2383. * @param array $params Params
  2384. * @param $smarty Smarty object
  2385. * @return string Price with right format and currency
  2386. */
  2387. public static function convertPrice($params, &$smarty)
  2388. {
  2389. return Tools::displayPrice($params['price'], Context::getContext()->currency);
  2390. }
  2391. /**
  2392. * Convert price with currency
  2393. *
  2394. * @param array $params
  2395. * @param object $smarty DEPRECATED
  2396. * @return Ambigous <string, mixed, Ambigous <number, string>>
  2397. */
  2398. public static function convertPriceWithCurrency($params, &$smarty)
  2399. {
  2400. return Tools::displayPrice($params['price'], $params['currency'], false);
  2401. }
  2402. public static function displayWtPrice($params, &$smarty)
  2403. {
  2404. return Tools::displayPrice($params['p'], Context::getContext()->currency);
  2405. }
  2406. /**
  2407. * Display WT price with currency
  2408. *
  2409. * @param array $params
  2410. * @param object DEPRECATED $smarty
  2411. * @return Ambigous <string, mixed, Ambigous <number, string>>
  2412. */
  2413. public static function displayWtPriceWithCurrency($params, &$smarty)
  2414. {
  2415. return Tools::displayPrice($params['price'], $params['currency'], false);
  2416. }
  2417. /**
  2418. * Get available product quantities
  2419. *
  2420. * @param integer $id_product Product id
  2421. * @param integer $id_product_attribute Product attribute id (optional)
  2422. * @return integer Available quantities
  2423. */
  2424. public static function getQuantity($id_product, $id_product_attribute = null, $cache_is_pack = null)
  2425. {
  2426. $lang = Configuration::get('PS_LANG_DEFAULT');
  2427. if ((int)$cache_is_pack || ($cache_is_pack === null && Pack::isPack((int)$id_product)))
  2428. {
  2429. if (!Pack::isInStock((int)$id_product))
  2430. return 0;
  2431. }
  2432. // @since 1.5.0
  2433. return (StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute));
  2434. }
  2435. /**
  2436. * Create JOIN query with 'stock_available' table
  2437. *
  2438. * @param string $productAlias Alias of product table
  2439. * @param string|int $productAttribute If string : alias of PA table ; if int : value of PA ; if null : nothing about PA
  2440. * @param bool $innerJoin LEFT JOIN or INNER JOIN
  2441. * @param Shop $shop
  2442. * @return string
  2443. */
  2444. public static function sqlStock($product_alias, $product_attribute = 0, $inner_join = false, Shop $shop = null)
  2445. {
  2446. $id_shop = ($shop !== null ? (int)$shop->id : null);
  2447. $sql = (($inner_join) ? ' INNER ' : ' LEFT ').'
  2448. JOIN '._DB_PREFIX_.'stock_available stock
  2449. ON (stock.id_product = '.pSQL($product_alias).'.id_product';
  2450. if (!is_null($product_attribute))
  2451. {
  2452. if (!Combination::isFeatureActive())
  2453. $sql .= ' AND stock.id_product_attribute = 0';
  2454. elseif (is_numeric($product_attribute))
  2455. $sql .= ' AND stock.id_product_attribute = '.$product_attribute;
  2456. elseif (is_string($product_attribute))
  2457. $sql .= ' AND stock.id_product_attribute = IFNULL(`'.bqSQL($product_attribute).'`.id_product_attribute, 0)';
  2458. }
  2459. $sql .= StockAvailable::addSqlShopRestriction(null, $id_shop, 'stock').' )';
  2460. return $sql;
  2461. }
  2462. /**
  2463. * @deprecated since 1.5.0
  2464. *
  2465. * It's not possible to use this method with new stockManager and stockAvailable features
  2466. * Now this method do nothing
  2467. *
  2468. * @see StockManager if you want to manage real stock
  2469. * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
  2470. *
  2471. * @param array $product Array with ordered product (quantity, id_product_attribute if applicable)
  2472. * @return mixed Query result
  2473. */
  2474. public static function updateQuantity()
  2475. {
  2476. Tools::displayAsDeprecated();
  2477. return false;
  2478. }
  2479. /**
  2480. * @deprecated since 1.5.0
  2481. *
  2482. * It's not possible to use this method with new stockManager and stockAvailable features
  2483. * Now this method do nothing
  2484. *
  2485. * @see StockManager if you want to manage real stock
  2486. * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
  2487. *
  2488. */
  2489. public static function reinjectQuantities()
  2490. {
  2491. Tools::displayAsDeprecated();
  2492. return false;
  2493. }
  2494. public static function isAvailableWhenOutOfStock($out_of_stock)
  2495. {
  2496. // @TODO 1.5.0 Update of STOCK_MANAGEMENT & ORDER_OUT_OF_STOCK
  2497. $return = (int)$out_of_stock == 2 ? (int)Configuration::get('PS_ORDER_OUT_OF_STOCK') : (int)$out_of_stock;
  2498. return !Configuration::get('PS_STOCK_MANAGEMENT') ? true : $return;
  2499. }
  2500. /**
  2501. * Check product availability
  2502. *
  2503. * @param integer $qty Quantity desired
  2504. * @return boolean True if product is available with this quantity
  2505. */
  2506. public function checkQty($qty)
  2507. {
  2508. if (Pack::isPack((int)$this->id) && !Pack::isInStock((int)$this->id))
  2509. return false;
  2510. if ($this->isAvailableWhenOutOfStock(StockAvailable::outOfStock($this->id)))
  2511. return true;
  2512. if (isset($this->id_product_attribute))
  2513. $id_product_attribute = $this->id_product_attribute;
  2514. else
  2515. $id_product_attribute = 0;
  2516. return ($qty <= StockAvailable::getQuantityAvailableByProduct($this->id, $id_product_attribute));
  2517. }
  2518. /**
  2519. * Check if there is not a default attribute and create it not
  2520. */
  2521. public function checkDefaultAttributes()
  2522. {
  2523. if (!$this->id)
  2524. return false;
  2525. if (Db::getInstance()->getValue('SELECT COUNT(*)
  2526. FROM `'._DB_PREFIX_.'product_attribute` pa
  2527. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  2528. WHERE product_attribute_shop.`default_on` = 1
  2529. AND pa.`id_product` = '.(int)$this->id) > 1)
  2530. Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'product_attribute_shop product_attribute_shop, '._DB_PREFIX_.'product_attribute pa
  2531. SET product_attribute_shop.default_on=0, pa.default_on = 0
  2532. WHERE product_attribute_shop.id_product_attribute=pa.id_product_attribute AND pa.id_product='.(int)$this->id
  2533. .Shop::addSqlRestriction(false, 'product_attribute_shop'));
  2534. $row = Db::getInstance()->getRow('
  2535. SELECT pa.id_product
  2536. FROM `'._DB_PREFIX_.'product_attribute` pa
  2537. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  2538. WHERE product_attribute_shop.`default_on` = 1
  2539. AND pa.`id_product` = '.(int)$this->id
  2540. );
  2541. if ($row)
  2542. return true;
  2543. $mini = Db::getInstance()->getRow('
  2544. SELECT MIN(pa.id_product_attribute) as `id_attr`
  2545. FROM `'._DB_PREFIX_.'product_attribute` pa
  2546. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  2547. WHERE `id_product` = '.(int)$this->id
  2548. );
  2549. if (!$mini)
  2550. return false;
  2551. if (!ObjectModel::updateMultishopTable('Combination', array('default_on' => 1), 'a.id_product_attribute = '.(int)$mini['id_attr']))
  2552. return false;
  2553. return true;
  2554. }
  2555. /**
  2556. * Get all available attribute groups
  2557. *
  2558. * @param integer $id_lang Language id
  2559. * @return array Attribute groups
  2560. */
  2561. public function getAttributesGroups($id_lang)
  2562. {
  2563. if (!Combination::isFeatureActive())
  2564. return array();
  2565. $sql = 'SELECT ag.`id_attribute_group`, ag.`is_color_group`, agl.`name` AS group_name, agl.`public_name` AS public_group_name,
  2566. a.`id_attribute`, al.`name` AS attribute_name, a.`color` AS attribute_color, pa.`id_product_attribute`,
  2567. IFNULL(stock.quantity, 0) as quantity, product_attribute_shop.`price`, product_attribute_shop.`ecotax`, pa.`weight`,
  2568. product_attribute_shop.`default_on`, pa.`reference`, product_attribute_shop.`unit_price_impact`,
  2569. pa.`minimal_quantity`, pa.`available_date`, ag.`group_type`
  2570. FROM `'._DB_PREFIX_.'product_attribute` pa
  2571. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  2572. '.Product::sqlStock('pa', 'pa').'
  2573. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute`
  2574. LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute`
  2575. LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group`
  2576. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON a.`id_attribute` = al.`id_attribute`
  2577. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON ag.`id_attribute_group` = agl.`id_attribute_group`
  2578. '.Shop::addSqlAssociation('attribute', 'a').'
  2579. WHERE pa.`id_product` = '.(int)$this->id.'
  2580. AND al.`id_lang` = '.(int)$id_lang.'
  2581. AND agl.`id_lang` = '.(int)$id_lang.'
  2582. GROUP BY id_attribute_group, id_product_attribute
  2583. ORDER BY ag.`position` ASC, a.`position` ASC';
  2584. return Db::getInstance()->executeS($sql);
  2585. }
  2586. /**
  2587. * Delete product accessories
  2588. *
  2589. * @return mixed Deletion result
  2590. */
  2591. public function deleteAccessories()
  2592. {
  2593. return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'accessory` WHERE `id_product_1` = '.(int)$this->id);
  2594. }
  2595. /**
  2596. * Delete product from other products accessories
  2597. *
  2598. * @return mixed Deletion result
  2599. */
  2600. public function deleteFromAccessories()
  2601. {
  2602. return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'accessory` WHERE `id_product_2` = '.(int)$this->id);
  2603. }
  2604. /**
  2605. * Get product accessories (only names)
  2606. *
  2607. * @param integer $id_lang Language id
  2608. * @param integer $id_product Product id
  2609. * @return array Product accessories
  2610. */
  2611. public static function getAccessoriesLight($id_lang, $id_product, Context $context = null)
  2612. {
  2613. if (!$context)
  2614. $context = Context::getContext();
  2615. $sql = 'SELECT p.`id_product`, p.`reference`, pl.`name`
  2616. FROM `'._DB_PREFIX_.'accessory`
  2617. LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product`= `id_product_2`)
  2618. '.Shop::addSqlAssociation('product', 'p').'
  2619. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
  2620. p.`id_product` = pl.`id_product`
  2621. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
  2622. )
  2623. WHERE `id_product_1` = '.(int)$id_product;
  2624. return Db::getInstance()->executeS($sql);
  2625. }
  2626. /**
  2627. * Get product accessories
  2628. *
  2629. * @param integer $id_lang Language id
  2630. * @return array Product accessories
  2631. */
  2632. public function getAccessories($id_lang, $active = true, Context $context = null)
  2633. {
  2634. if (!$context)
  2635. $context = Context::getContext();
  2636. $sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description`, pl.`description_short`, pl.`link_rewrite`,
  2637. pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`,
  2638. i.`id_image`, il.`legend`, t.`rate`, m.`name` as manufacturer_name, cl.`name` AS category_default,
  2639. DATEDIFF(
  2640. p.`date_add`,
  2641. DATE_SUB(
  2642. NOW(),
  2643. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY
  2644. )
  2645. ) > 0 AS new
  2646. FROM `'._DB_PREFIX_.'accessory`
  2647. LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = `id_product_2`
  2648. '.Shop::addSqlAssociation('product', 'p').'
  2649. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
  2650. p.`id_product` = pl.`id_product`
  2651. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
  2652. )
  2653. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (
  2654. product_shop.`id_category_default` = cl.`id_category`
  2655. AND cl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').'
  2656. )
  2657. LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'.
  2658. Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
  2659. LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.')
  2660. LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (p.`id_manufacturer`= m.`id_manufacturer`)
  2661. LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (product_shop.`id_tax_rules_group` = tr.`id_tax_rules_group`
  2662. AND tr.`id_country` = '.(int)$context->country->id.'
  2663. AND tr.`id_state` = 0)
  2664. LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
  2665. '.Product::sqlStock('p', 0).'
  2666. WHERE `id_product_1` = '.(int)$this->id.'
  2667. AND ((image_shop.id_image IS NOT NULL OR i.id_image IS NULL) OR (image_shop.id_image IS NULL AND i.cover=1))'.
  2668. ($active ? ' AND product_shop.`active` = 1' : '');
  2669. if (!$result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql))
  2670. return false;
  2671. foreach ($result as &$row)
  2672. $row['id_product_attribute'] = Product::getDefaultAttribute((int)$row['id_product']);
  2673. return $this->getProductsProperties($id_lang, $result);
  2674. }
  2675. public static function getAccessoryById($accessory_id)
  2676. {
  2677. return Db::getInstance()->getRow('SELECT `id_product`, `name` FROM `'._DB_PREFIX_.'product_lang` WHERE `id_product` = '.(int)$accessory_id);
  2678. }
  2679. /**
  2680. * Link accessories with product
  2681. *
  2682. * @param array $accessories_id Accessories ids
  2683. */
  2684. public function changeAccessories($accessories_id)
  2685. {
  2686. foreach ($accessories_id as $id_product_2)
  2687. Db::getInstance()->insert('accessory', array(
  2688. 'id_product_1' => (int)$this->id,
  2689. 'id_product_2' => (int)$id_product_2
  2690. ));
  2691. }
  2692. /**
  2693. * Add new feature to product
  2694. */
  2695. public function addFeaturesCustomToDB($id_value, $lang, $cust)
  2696. {
  2697. $row = array('id_feature_value' => (int)$id_value, 'id_lang' => (int)$lang, 'value' => pSQL($cust));
  2698. return Db::getInstance()->insert('feature_value_lang', $row);
  2699. }
  2700. public function addFeaturesToDB($id_feature, $id_value, $cust = 0)
  2701. {
  2702. if ($cust)
  2703. {
  2704. $row = array('id_feature' => (int)$id_feature, 'custom' => 1);
  2705. Db::getInstance()->insert('feature_value', $row);
  2706. $id_value = Db::getInstance()->Insert_ID();
  2707. }
  2708. $row = array('id_feature' => (int)$id_feature, 'id_product' => (int)$this->id, 'id_feature_value' => (int)$id_value);
  2709. Db::getInstance()->insert('feature_product', $row);
  2710. SpecificPriceRule::applyAllRules(array((int)$this->id));
  2711. if ($id_value)
  2712. return ($id_value);
  2713. }
  2714. public static function addFeatureProductImport($id_product, $id_feature, $id_feature_value)
  2715. {
  2716. return Db::getInstance()->execute('
  2717. INSERT INTO `'._DB_PREFIX_.'feature_product` (`id_feature`, `id_product`, `id_feature_value`)
  2718. VALUES ('.(int)$id_feature.', '.(int)$id_product.', '.(int)$id_feature_value.')
  2719. ON DUPLICATE KEY UPDATE `id_feature_value` = '.(int)$id_feature_value
  2720. );
  2721. }
  2722. /**
  2723. * Select all features for the object
  2724. *
  2725. * @return array Array with feature product's data
  2726. */
  2727. public function getFeatures()
  2728. {
  2729. return Product::getFeaturesStatic((int)$this->id);
  2730. }
  2731. public static function getFeaturesStatic($id_product)
  2732. {
  2733. if (!Feature::isFeatureActive())
  2734. return array();
  2735. if (!array_key_exists($id_product, self::$_cacheFeatures))
  2736. self::$_cacheFeatures[$id_product] = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  2737. SELECT id_feature, id_product, id_feature_value
  2738. FROM `'._DB_PREFIX_.'feature_product`
  2739. WHERE `id_product` = '.(int)$id_product
  2740. );
  2741. return self::$_cacheFeatures[$id_product];
  2742. }
  2743. public static function cacheProductsFeatures($product_ids)
  2744. {
  2745. if (!Feature::isFeatureActive())
  2746. return;
  2747. $product_implode = array();
  2748. foreach ($product_ids as $id_product)
  2749. if ((int)$id_product && !array_key_exists($id_product, self::$_cacheFeatures))
  2750. $product_implode[] = (int)$id_product;
  2751. if (!count($product_implode))
  2752. return;
  2753. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  2754. SELECT id_feature, id_product, id_feature_value
  2755. FROM `'._DB_PREFIX_.'feature_product`
  2756. WHERE `id_product` IN ('.implode($product_implode, ',').')');
  2757. foreach ($result as $row)
  2758. {
  2759. if (!array_key_exists($row['id_product'], self::$_cacheFeatures))
  2760. self::$_cacheFeatures[$row['id_product']] = array();
  2761. self::$_cacheFeatures[$row['id_product']][] = $row;
  2762. }
  2763. }
  2764. public static function cacheFrontFeatures($product_ids, $id_lang)
  2765. {
  2766. if (!Feature::isFeatureActive())
  2767. return;
  2768. $product_implode = array();
  2769. foreach ($product_ids as $id_product)
  2770. if ((int)$id_product && !array_key_exists($id_product.'-'.$id_lang, self::$_cacheFeatures))
  2771. $product_implode[] = (int)$id_product;
  2772. if (!count($product_implode))
  2773. return;
  2774. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  2775. SELECT id_product, name, value, pf.id_feature
  2776. FROM '._DB_PREFIX_.'feature_product pf
  2777. LEFT JOIN '._DB_PREFIX_.'feature_lang fl ON (fl.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')
  2778. LEFT JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = pf.id_feature_value AND fvl.id_lang = '.(int)$id_lang.')
  2779. LEFT JOIN '._DB_PREFIX_.'feature f ON (f.id_feature = pf.id_feature)
  2780. WHERE `id_product` IN ('.implode($product_implode, ',').')
  2781. ORDER BY f.position ASC');
  2782. foreach ($result as $row)
  2783. {
  2784. if (!array_key_exists($row['id_product'].'-'.$id_lang, self::$_frontFeaturesCache))
  2785. self::$_frontFeaturesCache[$row['id_product'].'-'.$id_lang] = array();
  2786. self::$_frontFeaturesCache[$row['id_product'].'-'.$id_lang][] = $row;
  2787. }
  2788. }
  2789. /**
  2790. * Admin panel product search
  2791. *
  2792. * @param integer $id_lang Language id
  2793. * @param string $query Search query
  2794. * @return array Matching products
  2795. */
  2796. public static function searchByName($id_lang, $query, Context $context = null)
  2797. {
  2798. if (!$context)
  2799. $context = Context::getContext();
  2800. $sql = new DbQuery();
  2801. $sql->select('p.`id_product`, pl.`name`, p.`active`, p.`reference`, m.`name` AS manufacturer_name, stock.`quantity`, product_shop.advanced_stock_management, p.`customizable`');
  2802. $sql->from('category_product', 'cp');
  2803. $sql->leftJoin('product', 'p', 'p.`id_product` = cp.`id_product`');
  2804. $sql->join(Shop::addSqlAssociation('product', 'p'));
  2805. $sql->leftJoin('product_lang', 'pl', '
  2806. p.`id_product` = pl.`id_product`
  2807. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl')
  2808. );
  2809. $sql->leftJoin('manufacturer', 'm', 'm.`id_manufacturer` = p.`id_manufacturer`');
  2810. $where = 'pl.`name` LIKE \'%'.pSQL($query).'%\' OR p.`reference` LIKE \'%'.pSQL($query).'%\' OR p.`supplier_reference` LIKE \'%'.pSQL($query).'%\'';
  2811. $sql->groupBy('`id_product`');
  2812. $sql->orderBy('pl.`name` ASC');
  2813. if (Combination::isFeatureActive())
  2814. {
  2815. $sql->leftJoin('product_attribute', 'pa', 'pa.`id_product` = p.`id_product`');
  2816. $sql->join(Shop::addSqlAssociation('product_attribute', 'pa', false));
  2817. $where .= ' OR pa.`reference` LIKE \'%'.pSQL($query).'%\'';
  2818. }
  2819. $sql->where($where);
  2820. $sql->join(Product::sqlStock('p', 'pa', false, $context->shop));
  2821. $result = Db::getInstance()->executeS($sql);
  2822. if (!$result)
  2823. return false;
  2824. $results_array = array();
  2825. foreach ($result as $row)
  2826. {
  2827. $row['price_tax_incl'] = Product::getPriceStatic($row['id_product'], true, null, 2);
  2828. $row['price_tax_excl'] = Product::getPriceStatic($row['id_product'], false, null, 2);
  2829. $results_array[] = $row;
  2830. }
  2831. return $results_array;
  2832. }
  2833. /**
  2834. * Duplicate attributes when duplicating a product
  2835. *
  2836. * @param integer $id_product_old Old product id
  2837. * @param integer $id_product_new New product id
  2838. */
  2839. public static function duplicateAttributes($id_product_old, $id_product_new)
  2840. {
  2841. $return = true;
  2842. $combination_images = array();
  2843. $result = Db::getInstance()->executeS('
  2844. SELECT *
  2845. FROM `'._DB_PREFIX_.'product_attribute` pa
  2846. WHERE pa.`id_product` = '.(int)$id_product_old
  2847. );
  2848. foreach ($result as $row)
  2849. {
  2850. $id_product_attribute_old = (int)$row['id_product_attribute'];
  2851. $result2 = Db::getInstance()->executeS('
  2852. SELECT *
  2853. FROM `'._DB_PREFIX_.'product_attribute_combination`
  2854. WHERE `id_product_attribute` = '.$id_product_attribute_old
  2855. );
  2856. $row['id_product'] = $id_product_new;
  2857. unset($row['id_product_attribute']);
  2858. $combination = new Combination();
  2859. foreach ($row as $k => $v)
  2860. $combination->$k = $v;
  2861. $return &= $combination->add();
  2862. $id_product_attribute_new = (int)$combination->id;
  2863. if ($result_images = Product::_getAttributeImageAssociations($id_product_attribute_old))
  2864. {
  2865. $combination_images['old'][$id_product_attribute_old] = $result_images;
  2866. $combination_images['new'][$id_product_attribute_new] = $result_images;
  2867. }
  2868. foreach ($result2 as $row2)
  2869. {
  2870. $row2['id_product_attribute'] = $id_product_attribute_new;
  2871. $return &= Db::getInstance()->insert('product_attribute_combination', $row2);
  2872. }
  2873. }
  2874. return !$return ? false : $combination_images;
  2875. }
  2876. /**
  2877. * Get product attribute image associations
  2878. * @param integer $id_product_attribute
  2879. * @return array
  2880. */
  2881. public static function _getAttributeImageAssociations($id_product_attribute)
  2882. {
  2883. $combination_images = array();
  2884. $data = Db::getInstance()->executeS('
  2885. SELECT `id_image`
  2886. FROM `'._DB_PREFIX_.'product_attribute_image`
  2887. WHERE `id_product_attribute` = '.(int)$id_product_attribute);
  2888. foreach ($data as $row)
  2889. $combination_images[] = (int)$row['id_image'];
  2890. return $combination_images;
  2891. }
  2892. public static function duplicateAccessories($id_product_old, $id_product_new)
  2893. {
  2894. $return = true;
  2895. $result = Db::getInstance()->executeS('
  2896. SELECT *
  2897. FROM `'._DB_PREFIX_.'accessory`
  2898. WHERE `id_product_1` = '.(int)$id_product_old);
  2899. foreach ($result as $row)
  2900. {
  2901. $data = array(
  2902. 'id_product_1' => (int)$id_product_new,
  2903. 'id_product_2' => (int)$row['id_product_2']);
  2904. $return &= Db::getInstance()->insert('accessory', $data);
  2905. }
  2906. return $return;
  2907. }
  2908. public static function duplicateTags($id_product_old, $id_product_new)
  2909. {
  2910. $tags = Db::getInstance()->executeS('SELECT `id_tag` FROM `'._DB_PREFIX_.'product_tag` WHERE `id_product` = '.(int)$id_product_old);
  2911. if (!Db::getInstance()->NumRows())
  2912. return true;
  2913. $data = array();
  2914. foreach ($tags as $tag)
  2915. $data[] = array(
  2916. 'id_product' => (int)$id_product_new,
  2917. 'id_tag' => (int)$tag['id_tag'],
  2918. );
  2919. return Db::getInstance()->insert('product_tag', $data);
  2920. }
  2921. public static function duplicateDownload($id_product_old, $id_product_new)
  2922. {
  2923. $sql = 'SELECT `display_filename`, `filename`, `date_add`, `date_expiration`, `nb_days_accessible`, `nb_downloadable`, `active`, `is_shareable`
  2924. FROM `'._DB_PREFIX_.'product_download`
  2925. WHERE `id_product` = '.(int)$id_product_old;
  2926. $results = Db::getInstance()->executeS($sql);
  2927. if (!$results)
  2928. return true;
  2929. $data = array();
  2930. foreach ($results as $row)
  2931. $data[] = array(
  2932. 'id_product' => (int)$id_product_new,
  2933. 'display_filename' => pSQL($row['display_filename']),
  2934. 'filename' => pSQL($row['filename']),
  2935. 'date_expiration' => pSQL($row['date_expiration']),
  2936. 'nb_days_accessible' => (int)$row['nb_days_accessible'],
  2937. 'nb_downloadable' => (int)$row['nb_downloadable'],
  2938. 'active' => (int)$row['active'],
  2939. 'is_shareable' => (int)$row['is_shareable']
  2940. );
  2941. return Db::getInstance()->insert('product_download', $data);
  2942. }
  2943. public static function duplicateAttachments($id_product_old, $id_product_new)
  2944. {
  2945. // Get all ids attachments of the old product
  2946. $sql = 'SELECT `id_attachment` FROM `'._DB_PREFIX_.'product_attachment` WHERE `id_product` = '.(int)$id_product_old;
  2947. $results = Db::getInstance()->executeS($sql);
  2948. if (!$results)
  2949. return true;
  2950. $data = array();
  2951. // Prepare data of table product_attachment
  2952. foreach ($results as $row)
  2953. $data[] = array(
  2954. 'id_product' => (int)$id_product_new,
  2955. 'id_attachment' => (int)$row['id_attachment']
  2956. );
  2957. // Duplicate product attachement
  2958. return Db::getInstance()->insert('product_attachment', $data);
  2959. }
  2960. /**
  2961. * Duplicate features when duplicating a product
  2962. *
  2963. * @param integer $id_product_old Old product id
  2964. * @param integer $id_product_old New product id
  2965. */
  2966. public static function duplicateFeatures($id_product_old, $id_product_new)
  2967. {
  2968. $return = true;
  2969. $result = Db::getInstance()->executeS('
  2970. SELECT *
  2971. FROM `'._DB_PREFIX_.'feature_product`
  2972. WHERE `id_product` = '.(int)$id_product_old);
  2973. foreach ($result as $row)
  2974. {
  2975. $result2 = Db::getInstance()->getRow('
  2976. SELECT *
  2977. FROM `'._DB_PREFIX_.'feature_value`
  2978. WHERE `id_feature_value` = '.(int)$row['id_feature_value']);
  2979. // Custom feature value, need to duplicate it
  2980. if ($result2['custom'])
  2981. {
  2982. $old_id_feature_value = $result2['id_feature_value'];
  2983. unset($result2['id_feature_value']);
  2984. $return &= Db::getInstance()->insert('feature_value', $result2);
  2985. $max_fv = Db::getInstance()->getRow('
  2986. SELECT MAX(`id_feature_value`) AS nb
  2987. FROM `'._DB_PREFIX_.'feature_value`');
  2988. $new_id_feature_value = $max_fv['nb'];
  2989. $languages = Language::getLanguages();
  2990. foreach ($languages as $language)
  2991. {
  2992. $result3 = Db::getInstance()->getRow('
  2993. SELECT *
  2994. FROM `'._DB_PREFIX_.'feature_value_lang`
  2995. WHERE `id_feature_value` = '.(int)$old_id_feature_value.'
  2996. AND `id_lang` = '.(int)$language['id_lang']);
  2997. if ($result3)
  2998. {
  2999. $result3['id_feature_value'] = $new_id_feature_value;
  3000. $return &= Db::getInstance()->insert('feature_value_lang', $result3);
  3001. }
  3002. }
  3003. $row['id_feature_value'] = $new_id_feature_value;
  3004. }
  3005. $row['id_product'] = $id_product_new;
  3006. $return &= Db::getInstance()->insert('feature_product', $row);
  3007. }
  3008. return $return;
  3009. }
  3010. protected static function _getCustomizationFieldsNLabels($product_id)
  3011. {
  3012. if (!Customization::isFeatureActive())
  3013. return false;
  3014. $customizations = array();
  3015. if (($customizations['fields'] = Db::getInstance()->executeS('
  3016. SELECT `id_customization_field`, `type`, `required`
  3017. FROM `'._DB_PREFIX_.'customization_field`
  3018. WHERE `id_product` = '.(int)$product_id.'
  3019. ORDER BY `id_customization_field`')) === false)
  3020. return false;
  3021. if (empty($customizations['fields']))
  3022. return array();
  3023. $customization_field_ids = array();
  3024. foreach ($customizations['fields'] as $customization_field)
  3025. $customization_field_ids[] = (int)$customization_field['id_customization_field'];
  3026. if (($customization_labels = Db::getInstance()->executeS('
  3027. SELECT `id_customization_field`, `id_lang`, `name`
  3028. FROM `'._DB_PREFIX_.'customization_field_lang`
  3029. WHERE `id_customization_field` IN ('.implode(', ', $customization_field_ids).')
  3030. ORDER BY `id_customization_field`')) === false)
  3031. return false;
  3032. foreach ($customization_labels as $customization_label)
  3033. $customizations['labels'][$customization_label['id_customization_field']][] = $customization_label;
  3034. return $customizations;
  3035. }
  3036. public static function duplicateSpecificPrices($old_product_id, $product_id)
  3037. {
  3038. foreach (SpecificPrice::getIdsByProductId((int)$old_product_id) as $data)
  3039. {
  3040. $specific_price = new SpecificPrice((int)$data['id_specific_price']);
  3041. if (!$specific_price->duplicate((int)$product_id))
  3042. return false;
  3043. }
  3044. return true;
  3045. }
  3046. public static function duplicateCustomizationFields($old_product_id, $product_id)
  3047. {
  3048. // If customization is not activated, return success
  3049. if (!Customization::isFeatureActive())
  3050. return true;
  3051. if (($customizations = Product::_getCustomizationFieldsNLabels($old_product_id)) === false)
  3052. return false;
  3053. if (empty($customizations))
  3054. return true;
  3055. foreach ($customizations['fields'] as $customization_field)
  3056. {
  3057. /* The new datas concern the new product */
  3058. $customization_field['id_product'] = (int)$product_id;
  3059. $old_customization_field_id = (int)$customization_field['id_customization_field'];
  3060. unset($customization_field['id_customization_field']);
  3061. if (!Db::getInstance()->insert('customization_field', $customization_field)
  3062. || !$customization_field_id = Db::getInstance()->Insert_ID())
  3063. return false;
  3064. if (isset($customizations['labels']))
  3065. {
  3066. $query = 'INSERT INTO `'._DB_PREFIX_.'` (`id_customization_field`, `id_lang`, `name`) VALUES ';
  3067. $data = array();
  3068. foreach ($customizations['labels'][$old_customization_field_id] as $customization_label)
  3069. $data = array(
  3070. 'id_customization_field' => (int)$customization_field_id,
  3071. 'id_lang' => (int)$customization_label['id_lang'],
  3072. 'name' => pSQL($customization_label['name']),
  3073. );
  3074. if (!Db::getInstance()->insert('customization_field_lang', $data))
  3075. return false;
  3076. }
  3077. }
  3078. return true;
  3079. }
  3080. /**
  3081. * Get the link of the product page of this product
  3082. */
  3083. public function getLink(Context $context = null)
  3084. {
  3085. if (!$context)
  3086. $context = Context::getContext();
  3087. return $context->link->getProductLink($this);
  3088. }
  3089. public function getTags($id_lang)
  3090. {
  3091. if (!$this->isFullyLoaded && is_null($this->tags))
  3092. $this->tags = Tag::getProductTags($this->id);
  3093. if (!($this->tags && key_exists($id_lang, $this->tags)))
  3094. return '';
  3095. $result = '';
  3096. foreach ($this->tags[$id_lang] as $tag_name)
  3097. $result .= $tag_name.', ';
  3098. return rtrim($result, ', ');
  3099. }
  3100. public static function defineProductImage($row, $id_lang)
  3101. {
  3102. if (isset($row['id_image']))
  3103. if ($row['id_image'])
  3104. return $row['id_product'].'-'.$row['id_image'];
  3105. return Language::getIsoById((int)$id_lang).'-default';
  3106. }
  3107. public static function getProductProperties($id_lang, $row, Context $context = null)
  3108. {
  3109. if (!$row['id_product'])
  3110. return false;
  3111. if ($context == null)
  3112. $context = Context::getContext();
  3113. // Product::getDefaultAttribute is only called if id_product_attribute is missing from the SQL query at the origin of it:
  3114. // consider adding it in order to avoid unnecessary queries
  3115. $row['allow_oosp'] = Product::isAvailableWhenOutOfStock($row['out_of_stock']);
  3116. if (Combination::isFeatureActive() && (!isset($row['id_product_attribute']) || !$row['id_product_attribute'])
  3117. && ((isset($row['cache_default_attribute']) && ($ipa_default = $row['cache_default_attribute']) !== null)
  3118. || ($ipa_default = Product::getDefaultAttribute($row['id_product'], !$row['allow_oosp']))))
  3119. $row['id_product_attribute'] = $ipa_default;
  3120. if (!Combination::isFeatureActive() || !isset($row['id_product_attribute']))
  3121. $row['id_product_attribute'] = 0;
  3122. // Tax
  3123. $usetax = Tax::excludeTaxeOption();
  3124. $cache_key = $row['id_product'].'-'.$row['id_product_attribute'].'-'.$id_lang.'-'.(int)$usetax;
  3125. if (isset($row['id_product_pack']))
  3126. $cache_key .= '-pack'.$row['id_product_pack'];
  3127. if (isset(self::$producPropertiesCache[$cache_key]))
  3128. return self::$producPropertiesCache[$cache_key];
  3129. // Datas
  3130. $row['category'] = Category::getLinkRewrite((int)$row['id_category_default'], (int)$id_lang);
  3131. $row['link'] = $context->link->getProductLink((int)$row['id_product'], $row['link_rewrite'], $row['category'], $row['ean13']);
  3132. $row['attribute_price'] = 0;
  3133. if (isset($row['id_product_attribute']) && $row['id_product_attribute'])
  3134. $row['attribute_price'] = (float)Product::getProductAttributePrice($row['id_product_attribute']);
  3135. $row['price_tax_exc'] = Product::getPriceStatic(
  3136. (int)$row['id_product'],
  3137. false,
  3138. ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
  3139. (self::$_taxCalculationMethod == PS_TAX_EXC ? 2 : 6)
  3140. );
  3141. if (self::$_taxCalculationMethod == PS_TAX_EXC)
  3142. {
  3143. $row['price_tax_exc'] = Tools::ps_round($row['price_tax_exc'], 2);
  3144. $row['price'] = Product::getPriceStatic(
  3145. (int)$row['id_product'],
  3146. true,
  3147. ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
  3148. 6
  3149. );
  3150. $row['price_without_reduction'] = Product::getPriceStatic(
  3151. (int)$row['id_product'],
  3152. false,
  3153. ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
  3154. 2,
  3155. null,
  3156. false,
  3157. false
  3158. );
  3159. }
  3160. else
  3161. {
  3162. $row['price'] = Tools::ps_round(
  3163. Product::getPriceStatic(
  3164. (int)$row['id_product'],
  3165. true,
  3166. ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
  3167. 2
  3168. ),
  3169. 2
  3170. );
  3171. $row['price_without_reduction'] = Product::getPriceStatic(
  3172. (int)$row['id_product'],
  3173. true,
  3174. ((isset($row['id_product_attribute']) && !empty($row['id_product_attribute'])) ? (int)$row['id_product_attribute'] : null),
  3175. 6,
  3176. null,
  3177. false,
  3178. false
  3179. );
  3180. }
  3181. $row['reduction'] = Product::getPriceStatic(
  3182. (int)$row['id_product'],
  3183. (bool)$usetax,
  3184. (int)$row['id_product_attribute'],
  3185. 6,
  3186. null,
  3187. true,
  3188. true,
  3189. 1,
  3190. true,
  3191. null,
  3192. null,
  3193. null,
  3194. $specific_prices
  3195. );
  3196. $row['specific_prices'] = $specific_prices;
  3197. if ($row['id_product_attribute'])
  3198. {
  3199. $row['quantity_all_versions'] = $row['quantity'];
  3200. $row['quantity'] = Product::getQuantity(
  3201. (int)$row['id_product'],
  3202. $row['id_product_attribute'],
  3203. isset($row['cache_is_pack']) ? $row['cache_is_pack'] : null
  3204. );
  3205. }
  3206. else
  3207. $row['quantity'] = Product::getQuantity((int)$row['id_product']);
  3208. $row['id_image'] = Product::defineProductImage($row, $id_lang);
  3209. $row['features'] = Product::getFrontFeaturesStatic((int)$id_lang, $row['id_product']);
  3210. $row['attachments'] = array();
  3211. if (!isset($row['cache_has_attachments']) || $row['cache_has_attachments'])
  3212. $row['attachments'] = Product::getAttachmentsStatic((int)$id_lang, $row['id_product']);
  3213. $row['virtual'] = ((!isset($row['is_virtual']) || $row['is_virtual']) ? 1 : 0);
  3214. // Pack management
  3215. $row['pack'] = (!isset($row['cache_is_pack']) ? Pack::isPack($row['id_product']) : (int)$row['cache_is_pack']);
  3216. $row['packItems'] = $row['pack'] ? Pack::getItemTable($row['id_product'], $id_lang) : array();
  3217. $row['nopackprice'] = $row['pack'] ? Pack::noPackPrice($row['id_product']) : 0;
  3218. if ($row['pack'] && !Pack::isInStock($row['id_product']))
  3219. $row['quantity'] = 0;
  3220. self::$producPropertiesCache[$cache_key] = $row;
  3221. return self::$producPropertiesCache[$cache_key];
  3222. }
  3223. public static function getProductsProperties($id_lang, $query_result)
  3224. {
  3225. $results_array = array();
  3226. if (is_array($query_result))
  3227. foreach ($query_result as $row)
  3228. if ($row2 = Product::getProductProperties($id_lang, $row))
  3229. $results_array[] = $row2;
  3230. return $results_array;
  3231. }
  3232. /*
  3233. * Select all features for a given language
  3234. *
  3235. * @param $id_lang Language id
  3236. * @return array Array with feature's data
  3237. */
  3238. public static function getFrontFeaturesStatic($id_lang, $id_product)
  3239. {
  3240. if (!Feature::isFeatureActive())
  3241. return array();
  3242. if (!array_key_exists($id_product.'-'.$id_lang, self::$_frontFeaturesCache))
  3243. {
  3244. self::$_frontFeaturesCache[$id_product.'-'.$id_lang] = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  3245. SELECT name, value, pf.id_feature
  3246. FROM '._DB_PREFIX_.'feature_product pf
  3247. LEFT JOIN '._DB_PREFIX_.'feature_lang fl ON (fl.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')
  3248. LEFT JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = pf.id_feature_value AND fvl.id_lang = '.(int)$id_lang.')
  3249. LEFT JOIN '._DB_PREFIX_.'feature f ON (f.id_feature = pf.id_feature AND fl.id_lang = '.(int)$id_lang.')
  3250. WHERE pf.id_product = '.(int)$id_product.'
  3251. ORDER BY f.position ASC'
  3252. );
  3253. }
  3254. return self::$_frontFeaturesCache[$id_product.'-'.$id_lang];
  3255. }
  3256. public function getFrontFeatures($id_lang)
  3257. {
  3258. return Product::getFrontFeaturesStatic($id_lang, $this->id);
  3259. }
  3260. public static function getAttachmentsStatic($id_lang, $id_product)
  3261. {
  3262. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  3263. SELECT *
  3264. FROM '._DB_PREFIX_.'product_attachment pa
  3265. LEFT JOIN '._DB_PREFIX_.'attachment a ON a.id_attachment = pa.id_attachment
  3266. LEFT JOIN '._DB_PREFIX_.'attachment_lang al ON (a.id_attachment = al.id_attachment AND al.id_lang = '.(int)$id_lang.')
  3267. WHERE pa.id_product = '.(int)$id_product);
  3268. }
  3269. public function getAttachments($id_lang)
  3270. {
  3271. return Product::getAttachmentsStatic($id_lang, $this->id);
  3272. }
  3273. /*
  3274. ** Customization management
  3275. */
  3276. public static function getAllCustomizedDatas($id_cart, $id_lang = null, $only_in_cart = true)
  3277. {
  3278. if (!Customization::isFeatureActive())
  3279. return false;
  3280. // No need to query if there isn't any real cart!
  3281. if (!$id_cart)
  3282. return false;
  3283. if (!$id_lang)
  3284. $id_lang = Context::getContext()->language->id;
  3285. if (!$result = Db::getInstance()->executeS('
  3286. SELECT cd.`id_customization`, c.`id_address_delivery`, c.`id_product`, cfl.`id_customization_field`, c.`id_product_attribute`,
  3287. cd.`type`, cd.`index`, cd.`value`, cfl.`name`
  3288. FROM `'._DB_PREFIX_.'customized_data` cd
  3289. NATURAL JOIN `'._DB_PREFIX_.'customization` c
  3290. LEFT JOIN `'._DB_PREFIX_.'customization_field_lang` cfl ON (cfl.id_customization_field = cd.`index` AND id_lang = '.(int)$id_lang.')
  3291. WHERE c.`id_cart` = '.(int)$id_cart.
  3292. ($only_in_cart ? ' AND c.`in_cart` = 1' : '').'
  3293. ORDER BY `id_product`, `id_product_attribute`, `type`, `index`'))
  3294. return false;
  3295. $customized_datas = array();
  3296. foreach ($result as $row)
  3297. $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;
  3298. if (!$result = Db::getInstance()->executeS(
  3299. 'SELECT `id_product`, `id_product_attribute`, `id_customization`, `id_address_delivery`, `quantity`, `quantity_refunded`, `quantity_returned`
  3300. FROM `'._DB_PREFIX_.'customization`
  3301. WHERE `id_cart` = '.(int)($id_cart).($only_in_cart ? '
  3302. AND `in_cart` = 1' : '')))
  3303. return false;
  3304. foreach ($result as $row)
  3305. {
  3306. $customized_datas[(int)$row['id_product']][(int)$row['id_product_attribute']][(int)$row['id_address_delivery']][(int)$row['id_customization']]['quantity'] = (int)$row['quantity'];
  3307. $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'];
  3308. $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'];
  3309. }
  3310. return $customized_datas;
  3311. }
  3312. public static function addCustomizationPrice(&$products, &$customized_datas)
  3313. {
  3314. if (!$customized_datas)
  3315. return;
  3316. foreach ($products as &$product_update)
  3317. {
  3318. if (!Customization::isFeatureActive())
  3319. {
  3320. $product_update['customizationQuantityTotal'] = 0;
  3321. $product_update['customizationQuantityRefunded'] = 0;
  3322. $product_update['customizationQuantityReturned'] = 0;
  3323. }
  3324. else
  3325. {
  3326. $customization_quantity = 0;
  3327. $customization_quantity_refunded = 0;
  3328. $customization_quantity_returned = 0;
  3329. /* Compatibility */
  3330. $product_id = (int)(isset($product_update['id_product']) ? $product_update['id_product'] : $product_update['product_id']);
  3331. $product_attribute_id = (int)(isset($product_update['id_product_attribute']) ? $product_update['id_product_attribute'] : $product_update['product_attribute_id']);
  3332. $id_address_delivery = (int)$product_update['id_address_delivery'];
  3333. $product_quantity = (int)(isset($product_update['cart_quantity']) ? $product_update['cart_quantity'] : $product_update['product_quantity']);
  3334. $price = isset($product_update['price']) ? $product_update['price'] : $product_update['product_price'];
  3335. $price_wt = $price * (1 + ((isset($product_update['tax_rate']) ? $product_update['tax_rate'] : $product_update['rate']) * 0.01));
  3336. if (isset($customized_datas[$product_id][$product_attribute_id]))
  3337. foreach ($customized_datas[$product_id][$product_attribute_id][$id_address_delivery] as $customization)
  3338. {
  3339. $customization_quantity += (int)$customization['quantity'];
  3340. $customization_quantity_refunded += (int)$customization['quantity_refunded'];
  3341. $customization_quantity_returned += (int)$customization['quantity_returned'];
  3342. }
  3343. $product_update['customizationQuantityTotal'] = $customization_quantity;
  3344. $product_update['customizationQuantityRefunded'] = $customization_quantity_refunded;
  3345. $product_update['customizationQuantityReturned'] = $customization_quantity_returned;
  3346. if ($customization_quantity)
  3347. {
  3348. $product_update['total_wt'] = $price_wt * ($product_quantity - $customization_quantity);
  3349. $product_update['total_customization_wt'] = $price_wt * $customization_quantity;
  3350. $product_update['total'] = $price * ($product_quantity - $customization_quantity);
  3351. $product_update['total_customization'] = $price * $customization_quantity;
  3352. }
  3353. }
  3354. }
  3355. }
  3356. /*
  3357. ** Customization fields' label management
  3358. */
  3359. protected function _checkLabelField($field, $value)
  3360. {
  3361. if (!Validate::isLabel($value))
  3362. return false;
  3363. $tmp = explode('_', $field);
  3364. if (count($tmp) < 4)
  3365. return false;
  3366. return $tmp;
  3367. }
  3368. protected function _deleteOldLabels()
  3369. {
  3370. $max = array(
  3371. Product::CUSTOMIZE_FILE => (int)Tools::getValue('uploadable_files'),
  3372. Product::CUSTOMIZE_TEXTFIELD => (int)Tools::getValue('text_fields')
  3373. );
  3374. /* Get customization field ids */
  3375. if (($result = Db::getInstance()->executeS(
  3376. 'SELECT `id_customization_field`, `type`
  3377. FROM `'._DB_PREFIX_.'customization_field`
  3378. WHERE `id_product` = '.(int)$this->id.'
  3379. ORDER BY `id_customization_field`')
  3380. ) === false)
  3381. return false;
  3382. if (empty($result))
  3383. return true;
  3384. $customization_fields = array(
  3385. Product::CUSTOMIZE_FILE => array(),
  3386. Product::CUSTOMIZE_TEXTFIELD => array()
  3387. );
  3388. foreach ($result as $row)
  3389. $customization_fields[(int)$row['type']][] = (int)$row['id_customization_field'];
  3390. $extra_file = count($customization_fields[Product::CUSTOMIZE_FILE]) - $max[Product::CUSTOMIZE_FILE];
  3391. $extra_text = count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $max[Product::CUSTOMIZE_TEXTFIELD];
  3392. /* If too much inside the database, deletion */
  3393. if ($extra_file > 0 && count($customization_fields[Product::CUSTOMIZE_FILE]) - $extra_file >= 0 &&
  3394. (!Db::getInstance()->execute(
  3395. 'DELETE FROM `'._DB_PREFIX_.'customization_field`
  3396. WHERE `id_product` = '.(int)$this->id.'
  3397. AND `type` = '.Product::CUSTOMIZE_FILE.'
  3398. AND `id_customization_field` >= '.(int)$customization_fields[Product::CUSTOMIZE_FILE][count($customization_fields[Product::CUSTOMIZE_FILE]) - $extra_file]
  3399. )
  3400. || !Db::getInstance()->execute(
  3401. 'DELETE FROM `'._DB_PREFIX_.'customization_field_lang`
  3402. WHERE `id_customization_field`
  3403. NOT IN (
  3404. SELECT `id_customization_field`
  3405. FROM `'._DB_PREFIX_.'customization_field`
  3406. )'
  3407. )))
  3408. return false;
  3409. if ($extra_text > 0 && count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $extra_text >= 0 &&
  3410. (!Db::getInstance()->execute(
  3411. 'DELETE FROM `'._DB_PREFIX_.'customization_field`
  3412. WHERE `id_product` = '.(int)$this->id.'
  3413. AND `type` = '.Product::CUSTOMIZE_TEXTFIELD.'
  3414. AND `id_customization_field` >= '.(int)$customization_fields[Product::CUSTOMIZE_TEXTFIELD][count($customization_fields[Product::CUSTOMIZE_TEXTFIELD]) - $extra_text]
  3415. )
  3416. || !Db::getInstance()->execute(
  3417. 'DELETE FROM `'._DB_PREFIX_.'customization_field_lang`
  3418. WHERE `id_customization_field`
  3419. NOT IN (
  3420. SELECT `id_customization_field`
  3421. FROM `'._DB_PREFIX_.'customization_field`
  3422. )'
  3423. )))
  3424. return false;
  3425. // Refresh cache of feature detachable
  3426. Configuration::updateGlobalValue('PS_CUSTOMIZATION_FEATURE_ACTIVE', Customization::isCurrentlyUsed());
  3427. return true;
  3428. }
  3429. protected function _createLabel(&$languages, $type)
  3430. {
  3431. // Label insertion
  3432. if (!Db::getInstance()->execute('
  3433. INSERT INTO `'._DB_PREFIX_.'customization_field` (`id_product`, `type`, `required`)
  3434. VALUES ('.(int)$this->id.', '.(int)$type.', 0)') ||
  3435. !$id_customization_field = (int)Db::getInstance()->Insert_ID())
  3436. return false;
  3437. // Multilingual label name creation
  3438. $values = '';
  3439. foreach ($languages as $language)
  3440. $values .= '('.(int)$id_customization_field.', '.(int)$language['id_lang'].', \'\'), ';
  3441. $values = rtrim($values, ', ');
  3442. if (!Db::getInstance()->execute('
  3443. INSERT INTO `'._DB_PREFIX_.'customization_field_lang` (`id_customization_field`, `id_lang`, `name`)
  3444. VALUES '.$values))
  3445. return false;
  3446. // Set cache of feature detachable to true
  3447. Configuration::updateGlobalValue('PS_CUSTOMIZATION_FEATURE_ACTIVE', '1');
  3448. return true;
  3449. }
  3450. public function createLabels($uploadable_files, $text_fields)
  3451. {
  3452. $languages = Language::getLanguages();
  3453. if ((int)$uploadable_files > 0)
  3454. for ($i = 0; $i < (int)$uploadable_files; $i++)
  3455. if (!$this->_createLabel($languages, Product::CUSTOMIZE_FILE))
  3456. return false;
  3457. if ((int)$text_fields > 0)
  3458. for ($i = 0; $i < (int)$text_fields; $i++)
  3459. if (!$this->_createLabel($languages, Product::CUSTOMIZE_TEXTFIELD))
  3460. return false;
  3461. return true;
  3462. }
  3463. public function updateLabels()
  3464. {
  3465. $has_required_fields = 0;
  3466. foreach ($_POST as $field => $value)
  3467. /* Label update */
  3468. if (strncmp($field, 'label_', 6) == 0)
  3469. {
  3470. if (!$tmp = $this->_checkLabelField($field, $value))
  3471. return false;
  3472. /* Multilingual label name update */
  3473. if (!Db::getInstance()->execute('
  3474. INSERT INTO `'._DB_PREFIX_.'customization_field_lang`
  3475. (`id_customization_field`, `id_lang`, `name`) VALUES ('.(int)$tmp[2].', '.(int)$tmp[3].', \''.pSQL($value).'\')
  3476. ON DUPLICATE KEY UPDATE `name` = \''.pSQL($value).'\''))
  3477. return false;
  3478. $is_required = isset($_POST['require_'.(int)$tmp[1].'_'.(int)$tmp[2]]) ? 1 : 0;
  3479. $has_required_fields |= $is_required;
  3480. /* Require option update */
  3481. if (!Db::getInstance()->execute(
  3482. 'UPDATE `'._DB_PREFIX_.'customization_field`
  3483. SET `required` = '.(int)$is_required.'
  3484. WHERE `id_customization_field` = '.(int)$tmp[2]))
  3485. return false;
  3486. }
  3487. if ($has_required_fields && !ObjectModel::updateMultishopTable('product', array('customizable' => 2), 'a.id_product = '.(int)$this->id))
  3488. return false;
  3489. if (!$this->_deleteOldLabels())
  3490. return false;
  3491. return true;
  3492. }
  3493. public function getCustomizationFields($id_lang = false)
  3494. {
  3495. if (!Customization::isFeatureActive())
  3496. return false;
  3497. if (!$result = Db::getInstance()->executeS('
  3498. SELECT cf.`id_customization_field`, cf.`type`, cf.`required`, cfl.`name`, cfl.`id_lang`
  3499. FROM `'._DB_PREFIX_.'customization_field` cf
  3500. NATURAL JOIN `'._DB_PREFIX_.'customization_field_lang` cfl
  3501. WHERE cf.`id_product` = '.(int)$this->id.($id_lang ? ' AND cfl.`id_lang` = '.(int)$id_lang : '').'
  3502. ORDER BY cf.`id_customization_field`'))
  3503. return false;
  3504. if ($id_lang)
  3505. return $result;
  3506. $customization_fields = array();
  3507. foreach ($result as $row)
  3508. $customization_fields[(int)$row['type']][(int)$row['id_customization_field']][(int)$row['id_lang']] = $row;
  3509. return $customization_fields;
  3510. }
  3511. public function getCustomizationFieldIds()
  3512. {
  3513. if (!Customization::isFeatureActive())
  3514. return array();
  3515. return Db::getInstance()->executeS('
  3516. SELECT `id_customization_field`, `type`, `required`
  3517. FROM `'._DB_PREFIX_.'customization_field`
  3518. WHERE `id_product` = '.(int)$this->id);
  3519. }
  3520. public function getRequiredCustomizableFields()
  3521. {
  3522. if (!Customization::isFeatureActive())
  3523. return array();
  3524. return Db::getInstance()->executeS('
  3525. SELECT `id_customization_field`, `type`
  3526. FROM `'._DB_PREFIX_.'customization_field`
  3527. WHERE `id_product` = '.(int)$this->id.'
  3528. AND `required` = 1'
  3529. );
  3530. }
  3531. public function hasAllRequiredCustomizableFields(Context $context = null)
  3532. {
  3533. if (!Customization::isFeatureActive())
  3534. return true;
  3535. if (!$context)
  3536. $context = Context::getContext();
  3537. $fields = $context->cart->getProductCustomization($this->id, null, true);
  3538. if (($required_fields = $this->getRequiredCustomizableFields()) === false)
  3539. return false;
  3540. $fields_present = array();
  3541. foreach ($fields as $field)
  3542. $fields_present[] = array('id_customization_field' => $field['index'], 'type' => $field['type']);
  3543. foreach ($required_fields as $required_field)
  3544. if (!in_array($required_field, $fields_present))
  3545. return false;
  3546. return true;
  3547. }
  3548. /**
  3549. * Checks if the product is in at least one of the submited categories
  3550. *
  3551. * @param int $id_product
  3552. * @param array $categories array of category arrays
  3553. * @return boolean is the product in at least one category
  3554. */
  3555. public static function idIsOnCategoryId($id_product, $categories)
  3556. {
  3557. if (!((int)$id_product > 0) || !is_array($categories) || empty($categories))
  3558. return false;
  3559. $sql = 'SELECT id_product FROM `'._DB_PREFIX_.'category_product` WHERE `id_product` = '.(int)$id_product.' AND `id_category` IN (';
  3560. foreach ($categories as $category)
  3561. $sql .= (int)$category['id_category'].',';
  3562. $sql = rtrim($sql, ',').')';
  3563. $hash = md5($sql);
  3564. if (!isset(self::$_incat[$hash]))
  3565. {
  3566. if (!Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql))
  3567. return false;
  3568. self::$_incat[$hash] = (Db::getInstance(_PS_USE_SQL_SLAVE_)->NumRows() > 0 ? true : false);
  3569. }
  3570. return self::$_incat[$hash];
  3571. }
  3572. public function getNoPackPrice()
  3573. {
  3574. return Pack::noPackPrice($this->id);
  3575. }
  3576. public function checkAccess($id_customer)
  3577. {
  3578. if (!$id_customer)
  3579. return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  3580. SELECT ctg.`id_group`
  3581. FROM `'._DB_PREFIX_.'category_product` cp
  3582. INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
  3583. WHERE cp.`id_product` = '.(int)$this->id.' AND ctg.`id_group` = 1');
  3584. else
  3585. return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  3586. SELECT cg.`id_group`
  3587. FROM `'._DB_PREFIX_.'category_product` cp
  3588. INNER JOIN `'._DB_PREFIX_.'category_group` ctg ON (ctg.`id_category` = cp.`id_category`)
  3589. INNER JOIN `'._DB_PREFIX_.'customer_group` cg ON (cg.`id_group` = ctg.`id_group`)
  3590. WHERE cp.`id_product` = '.(int)$this->id.' AND cg.`id_customer` = '.(int)$id_customer);
  3591. }
  3592. /**
  3593. * Add a stock movement for current product
  3594. *
  3595. * Since 1.5, this method only permit to add/remove available quantities of the current product in the current shop
  3596. *
  3597. * @see StockManager if you want to manage real stock
  3598. * @see StockAvailable if you want to manage available quantities for sale on your shop(s)
  3599. *
  3600. * @deprecated since 1.5.0
  3601. *
  3602. * @param int $quantity
  3603. * @param int $id_reason - useless
  3604. * @param int $id_product_attribute
  3605. * @param int $id_order - useless
  3606. * @param int $id_employee - useless
  3607. * @return bool
  3608. */
  3609. public function addStockMvt($quantity, $id_reason, $id_product_attribute = null, $id_order = null, $id_employee = null)
  3610. {
  3611. if (!$this->id || !$id_reason)
  3612. return false;
  3613. if ($id_product_attribute == null)
  3614. $id_product_attribute = 0;
  3615. $reason = new StockMvtReason((int)$id_reason);
  3616. if (!Validate::isLoadedObject($reason))
  3617. return false;
  3618. $quantity = abs((int)$quantity) * $reason->sign;
  3619. return StockAvailable::updateQuantity($this->id, $id_product_attribute, $quantity);
  3620. }
  3621. /**
  3622. * @deprecated since 1.5.0
  3623. */
  3624. public function getStockMvts($id_lang)
  3625. {
  3626. Tools::displayAsDeprecated();
  3627. return Db::getInstance()->executeS('
  3628. SELECT sm.id_stock_mvt, sm.date_add, sm.quantity, sm.id_order,
  3629. CONCAT(pl.name, \' \', GROUP_CONCAT(IFNULL(al.name, \'\'), \'\')) product_name, CONCAT(e.lastname, \' \', e.firstname) employee, mrl.name reason
  3630. FROM `'._DB_PREFIX_.'stock_mvt` sm
  3631. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
  3632. sm.id_product = pl.id_product
  3633. AND pl.id_lang = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
  3634. )
  3635. LEFT JOIN `'._DB_PREFIX_.'stock_mvt_reason_lang` mrl ON (
  3636. sm.id_stock_mvt_reason = mrl.id_stock_mvt_reason
  3637. AND mrl.id_lang = '.(int)$id_lang.'
  3638. )
  3639. LEFT JOIN `'._DB_PREFIX_.'employee` e ON (
  3640. e.id_employee = sm.id_employee
  3641. )
  3642. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON (
  3643. pac.id_product_attribute = sm.id_product_attribute
  3644. )
  3645. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (
  3646. al.id_attribute = pac.id_attribute
  3647. AND al.id_lang = '.(int)$id_lang.'
  3648. )
  3649. WHERE sm.id_product='.(int)$this->id.'
  3650. GROUP BY sm.id_stock_mvt
  3651. ');
  3652. }
  3653. public static function getUrlRewriteInformations($id_product)
  3654. {
  3655. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  3656. SELECT pl.`id_lang`, pl.`link_rewrite`, p.`ean13`, cl.`link_rewrite` AS category_rewrite
  3657. FROM `'._DB_PREFIX_.'product` p
  3658. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (p.`id_product` = pl.`id_product`'.Shop::addSqlRestrictionOnLang('pl').')
  3659. '.Shop::addSqlAssociation('product', 'p').'
  3660. LEFT JOIN `'._DB_PREFIX_.'lang` l ON (pl.`id_lang` = l.`id_lang`)
  3661. 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').')
  3662. WHERE p.`id_product` = '.(int)$id_product.'
  3663. AND l.`active` = 1
  3664. ');
  3665. }
  3666. public function getIdTaxRulesGroup()
  3667. {
  3668. return $this->id_tax_rules_group;
  3669. }
  3670. public static function getIdTaxRulesGroupByIdProduct($id_product, Context $context = null)
  3671. {
  3672. if (!$context)
  3673. $context = Context::getContext();
  3674. $key = 'product_id_tax_rules_group_'.(int)$id_product.'_'.(int)$context->shop->id;
  3675. if (!Cache::isStored($key))
  3676. Cache::store($key,
  3677. Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  3678. SELECT `id_tax_rules_group`
  3679. FROM `'._DB_PREFIX_.'product_shop`
  3680. WHERE `id_product` = '.(int)$id_product.' AND id_shop='.(int)$context->shop->id));
  3681. return Cache::retrieve($key);
  3682. }
  3683. /**
  3684. * @return the total taxes rate applied to the product
  3685. */
  3686. public function getTaxesRate(Address $address = null)
  3687. {
  3688. if (!$address || !$address->id_country)
  3689. $address = Address::initialize();
  3690. $tax_manager = TaxManagerFactory::getManager($address, $this->id_tax_rules_group);
  3691. $tax_calculator = $tax_manager->getTaxCalculator();
  3692. return $tax_calculator->getTotalRate();
  3693. }
  3694. /**
  3695. * Webservice getter : get product features association
  3696. *
  3697. * @return array
  3698. */
  3699. public function getWsProductFeatures()
  3700. {
  3701. $rows = $this->getFeatures();
  3702. foreach ($rows as $keyrow => $row)
  3703. {
  3704. foreach ($row as $keyfeature => $feature)
  3705. {
  3706. if ($keyfeature == 'id_feature')
  3707. {
  3708. $rows[$keyrow]['id'] = $feature;
  3709. unset($rows[$keyrow]['id_feature']);
  3710. }
  3711. unset($rows[$keyrow]['id_product']);
  3712. }
  3713. asort($rows[$keyrow]);
  3714. }
  3715. return $rows;
  3716. }
  3717. /**
  3718. * Webservice setter : set product features association
  3719. *
  3720. * @param $productFeatures Product Feature ids
  3721. * @return boolean
  3722. */
  3723. public function setWsProductFeatures($product_features)
  3724. {
  3725. $this->deleteProductFeatures();
  3726. foreach ($product_features as $product_feature)
  3727. $this->addFeaturesToDB($product_feature['id'], $product_feature['id_feature_value']);
  3728. return true;
  3729. }
  3730. /**
  3731. * Webservice getter : get virtual field default combination
  3732. *
  3733. * @return int
  3734. */
  3735. public function getWsDefaultCombination()
  3736. {
  3737. return Product::getDefaultAttribute($this->id);
  3738. }
  3739. /**
  3740. * Webservice setter : set virtual field default combination
  3741. *
  3742. * @param $id_combination id default combination
  3743. */
  3744. public function setWsDefaultCombination($id_combination)
  3745. {
  3746. $this->deleteDefaultAttributes();
  3747. return $this->setDefaultAttribute((int)$id_combination);
  3748. }
  3749. /**
  3750. * Webservice getter : get category ids of current product for association
  3751. *
  3752. * @return array
  3753. */
  3754. public function getWsCategories()
  3755. {
  3756. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(
  3757. 'SELECT cp.`id_category` AS id
  3758. FROM `'._DB_PREFIX_.'category_product` cp
  3759. LEFT JOIN `'._DB_PREFIX_.'category` c ON (c.id_category = cp.id_category)
  3760. '.Shop::addSqlAssociation('category', 'c').'
  3761. WHERE cp.`id_product` = '.(int)$this->id
  3762. );
  3763. return $result;
  3764. }
  3765. /**
  3766. * Webservice setter : set category ids of current product for association
  3767. *
  3768. * @param $category_ids category ids
  3769. */
  3770. public function setWsCategories($category_ids)
  3771. {
  3772. $ids = array();
  3773. foreach ($category_ids as $value)
  3774. $ids[] = $value['id'];
  3775. if ($this->deleteCategories())
  3776. {
  3777. if ($ids)
  3778. {
  3779. $sql_values = '';
  3780. $ids = array_map('intval', $ids);
  3781. foreach ($ids as $position => $id)
  3782. $sql_values[] = '('.(int)$id.', '.(int)$this->id.', '.(int)$position.')';
  3783. $result = Db::getInstance()->execute('
  3784. INSERT INTO `'._DB_PREFIX_.'category_product` (`id_category`, `id_product`, `position`)
  3785. VALUES '.implode(',', $sql_values)
  3786. );
  3787. return $result;
  3788. }
  3789. }
  3790. return true;
  3791. }
  3792. /**
  3793. * Webservice getter : get combination ids of current product for association
  3794. *
  3795. * @return array
  3796. */
  3797. public function getWsCombinations()
  3798. {
  3799. $result = Db::getInstance()->executeS(
  3800. 'SELECT pa.`id_product_attribute` as id
  3801. FROM `'._DB_PREFIX_.'product_attribute` pa
  3802. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  3803. WHERE `id_product` = '.(int)$this->id
  3804. );
  3805. return $result;
  3806. }
  3807. /**
  3808. * Webservice setter : set combination ids of current product for association
  3809. *
  3810. * @param $combinations combination ids
  3811. */
  3812. public function setWsCombinations($combinations)
  3813. {
  3814. // No hook exec
  3815. $ids_new = array();
  3816. foreach ($combinations as $combination)
  3817. $ids_new[] = (int)$combination['id'];
  3818. $ids_orig = array();
  3819. $original = Db::getInstance()->executeS(
  3820. 'SELECT pa.`id_product_attribute` as id
  3821. FROM `'._DB_PREFIX_.'product_attribute` pa
  3822. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  3823. WHERE `id_product` = '.(int)$this->id
  3824. );
  3825. if (is_array($original))
  3826. foreach ($original as $id)
  3827. $ids_orig[] = $id['id'];
  3828. $all_ids = array();
  3829. $all = Db::getInstance()->executeS('SELECT pa.`id_product_attribute` as id FROM `'._DB_PREFIX_.'product_attribute` pa '.Shop::addSqlAssociation('product_attribute', 'pa'));
  3830. if (is_array($all))
  3831. foreach ($all as $id)
  3832. $all_ids[] = $id['id'];
  3833. $to_add = array();
  3834. foreach ($ids_new as $id)
  3835. if (!in_array($id, $ids_orig))
  3836. $to_add[] = $id;
  3837. $to_delete = array();
  3838. foreach ($ids_orig as $id)
  3839. if (!in_array($id, $ids_new))
  3840. $to_delete[] = $id;
  3841. // Delete rows
  3842. if (count($to_delete) > 0)
  3843. foreach ($to_delete as $id)
  3844. {
  3845. $combination = new Combination($id);
  3846. $combination->delete();
  3847. }
  3848. foreach ($to_add as $id)
  3849. {
  3850. // Update id_product if exists else create
  3851. if (in_array($id, $all_ids))
  3852. Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'product_attribute` SET id_product = '.(int)$this->id.' WHERE id_product_attribute='.$id);
  3853. else
  3854. Db::getInstance()->execute('INSERT INTO `'._DB_PREFIX_.'product_attribute` (`id_product`) VALUES ('.$this->id.')');
  3855. }
  3856. return true;
  3857. }
  3858. /**
  3859. * Webservice getter : get product option ids of current product for association
  3860. *
  3861. * @return array
  3862. */
  3863. public function getWsProductOptionValues()
  3864. {
  3865. $result = Db::getInstance()->executeS('SELECT DISTINCT pac.id_attribute as id
  3866. FROM `'._DB_PREFIX_.'product_attribute` pa
  3867. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  3868. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON (pac.id_product_attribute = pa.id_product_attribute)
  3869. WHERE pa.id_product = '.(int)$this->id);
  3870. return $result;
  3871. }
  3872. /**
  3873. * Webservice getter : get virtual field position in category
  3874. *
  3875. * @return int
  3876. */
  3877. public function getWsPositionInCategory()
  3878. {
  3879. $result = Db::getInstance()->executeS('SELECT position
  3880. FROM `'._DB_PREFIX_.'category_product`
  3881. WHERE id_category = '.(int)$this->id_category_default.'
  3882. AND id_product = '.(int)$this->id);
  3883. if (count($result) > 0)
  3884. return $result[0]['position'];
  3885. return '';
  3886. }
  3887. /**
  3888. * Webservice getter : get virtual field id_default_image in category
  3889. *
  3890. * @return int
  3891. */
  3892. public function getCoverWs()
  3893. {
  3894. $result = $this->getCover($this->id);
  3895. return $result['id_image'];
  3896. }
  3897. /**
  3898. * Webservice setter : set virtual field id_default_image in category
  3899. *
  3900. * @return bool
  3901. */
  3902. public function setCoverWs($id_image)
  3903. {
  3904. Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'image_shop` image_shop, `'._DB_PREFIX_.'image` i
  3905. SET image_shop.`cover` = 0
  3906. WHERE i.`id_product` = '.(int)$this->id.' AND i.id_image = image_shop.id_image
  3907. AND image_shop.id_shop='.(int)Context::getContext()->shop->id);
  3908. Db::getInstance()->execute('UPDATE `'._DB_PREFIX_.'image_shop`
  3909. SET `cover` = 1 WHERE `id_image` = '.(int)$id_image);
  3910. return true;
  3911. }
  3912. /**
  3913. * Webservice getter : get image ids of current product for association
  3914. *
  3915. * @return array
  3916. */
  3917. public function getWsImages()
  3918. {
  3919. return Db::getInstance()->executeS('
  3920. SELECT i.`id_image` as id
  3921. FROM `'._DB_PREFIX_.'image` i
  3922. '.Shop::addSqlAssociation('image', 'i').'
  3923. WHERE i.`id_product` = '.(int)$this->id.'
  3924. ORDER BY i.`position`');
  3925. }
  3926. public function getWsStockAvailables()
  3927. {
  3928. return Db::getInstance()->executeS('SELECT `id_stock_available` id, `id_product_attribute`
  3929. FROM `'._DB_PREFIX_.'stock_available`
  3930. WHERE `id_product`='.($this->id).StockAvailable::addSqlShopRestriction());
  3931. }
  3932. public function getWsTags()
  3933. {
  3934. return Db::getInstance()->executeS('
  3935. SELECT `id_tag` as id
  3936. FROM `'._DB_PREFIX_.'product_tag`
  3937. WHERE `id_product` = '.(int)$this->id);
  3938. }
  3939. public function getWsManufacturerName()
  3940. {
  3941. return Manufacturer::getNameById((int)$this->id_manufacturer);
  3942. }
  3943. public static function resetEcoTax()
  3944. {
  3945. return ObjectModel::updateMultishopTable('product', array(
  3946. 'ecotax' => 0,
  3947. ), '');
  3948. }
  3949. /**
  3950. * Set Group reduction if needed
  3951. */
  3952. public function setGroupReduction()
  3953. {
  3954. return GroupReduction::setProductReduction($this->id, null, $this->id_category_default);
  3955. }
  3956. /**
  3957. * Checks if reference exists
  3958. * @return boolean
  3959. */
  3960. public function existsRefInDatabase($reference)
  3961. {
  3962. $row = Db::getInstance()->getRow('
  3963. SELECT `reference`
  3964. FROM `'._DB_PREFIX_.'product` p
  3965. WHERE p.reference = "'.pSQL($reference).'"');
  3966. return isset($row['reference']);
  3967. }
  3968. /**
  3969. * Get all product attributes ids
  3970. *
  3971. * @since 1.5.0
  3972. * @param int $id_product the id of the product
  3973. * @return array product attribute id list
  3974. */
  3975. public static function getProductAttributesIds($id_product, $shop_only = false)
  3976. {
  3977. return Db::getInstance()->executeS('
  3978. SELECT pa.id_product_attribute
  3979. FROM `'._DB_PREFIX_.'product_attribute` pa'.
  3980. ($shop_only ? Shop::addSqlAssociation('product_attribute', 'pa') : '').'
  3981. WHERE pa.`id_product` = '.(int)$id_product);
  3982. }
  3983. /**
  3984. * Get label by lang and value by lang too
  3985. * @todo Remove existing module condition
  3986. * @param int $id_product
  3987. * @param int $product_attribute_id
  3988. * @return array
  3989. */
  3990. public static function getAttributesParams($id_product, $id_product_attribute)
  3991. {
  3992. // if blocklayered module is installed we check if user has set custom attribute name
  3993. if (Module::isInstalled('blocklayered'))
  3994. {
  3995. $nb_custom_values = Db::getInstance()->executeS('
  3996. SELECT DISTINCT la.`id_attribute`, la.`url_name` as `name`
  3997. FROM `'._DB_PREFIX_.'attribute` a
  3998. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  3999. ON (a.`id_attribute` = pac.`id_attribute`)
  4000. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4001. ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  4002. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4003. LEFT JOIN `'._DB_PREFIX_.'layered_indexable_attribute_lang_value` la
  4004. ON (la.`id_attribute` = a.`id_attribute` AND la.`id_lang` = '.(int)Context::getContext()->language->id.')
  4005. WHERE la.`url_name` IS NOT NULL
  4006. AND pa.`id_product` = '.(int)$id_product);
  4007. if (!empty($nb_custom_values))
  4008. {
  4009. $tab_id_attribute = array();
  4010. foreach ($nb_custom_values as $attribute)
  4011. {
  4012. $tab_id_attribute[] = $attribute['id_attribute'];
  4013. $group = Db::getInstance()->executeS('
  4014. SELECT g.`id_attribute_group`, g.`url_name` as `group`
  4015. FROM `'._DB_PREFIX_.'layered_indexable_attribute_group_lang_value` g
  4016. LEFT JOIN `'._DB_PREFIX_.'attribute` a
  4017. ON (a.`id_attribute_group` = g.`id_attribute_group`)
  4018. WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
  4019. AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
  4020. AND g.`url_name` IS NOT NULL');
  4021. if (empty($group))
  4022. {
  4023. $group = Db::getInstance()->executeS('
  4024. SELECT g.`id_attribute_group`, g.`name` as `group`
  4025. FROM `'._DB_PREFIX_.'attribute_group_lang` g
  4026. LEFT JOIN `'._DB_PREFIX_.'attribute` a
  4027. ON (a.`id_attribute_group` = g.`id_attribute_group`)
  4028. WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
  4029. AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
  4030. AND g.`name` IS NOT NULL');
  4031. }
  4032. $result[] = array_merge($attribute, $group[0]);
  4033. }
  4034. $values_not_custom = Db::getInstance()->executeS('
  4035. SELECT DISTINCT a.`id_attribute_group`, al.`name`, agl.`name` as `group`
  4036. FROM `'._DB_PREFIX_.'attribute` a
  4037. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
  4038. ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
  4039. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
  4040. ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
  4041. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4042. ON (a.`id_attribute` = pac.`id_attribute`)
  4043. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4044. ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  4045. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4046. WHERE pa.`id_product` = '.(int)$id_product.'
  4047. AND a.`id_attribute` NOT IN('.implode(', ', $tab_id_attribute).')');
  4048. $result = array_merge($values_not_custom, $result);
  4049. }
  4050. else
  4051. {
  4052. $result = Db::getInstance()->executeS('
  4053. SELECT al.`name`, agl.`name` as `group`
  4054. FROM `'._DB_PREFIX_.'attribute` a
  4055. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
  4056. ON (al.`id_attribute` = a.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
  4057. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4058. ON (pac.`id_attribute` = a.`id_attribute`)
  4059. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4060. ON (pa.`id_product_attribute` = pac.`id_product_attribute`)
  4061. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4062. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
  4063. ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
  4064. WHERE pa.`id_product` = '.(int)$id_product.'
  4065. AND pac.`id_product_attribute` = '.(int)$id_product_attribute.'
  4066. AND agl.`id_lang` = '.(int)Context::getContext()->language->id);
  4067. }
  4068. }
  4069. else
  4070. {
  4071. $result = Db::getInstance()->executeS('
  4072. SELECT al.`name`, agl.`name` as `group`
  4073. FROM `'._DB_PREFIX_.'attribute` a
  4074. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
  4075. ON (al.`id_attribute` = a.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
  4076. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4077. ON (pac.`id_attribute` = a.`id_attribute`)
  4078. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4079. ON (pa.`id_product_attribute` = pac.`id_product_attribute`)
  4080. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4081. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
  4082. ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
  4083. WHERE pa.`id_product` = '.(int)$id_product.'
  4084. AND pac.`id_product_attribute` = '.(int)$id_product_attribute.'
  4085. AND agl.`id_lang` = '.(int)Context::getContext()->language->id);
  4086. }
  4087. return $result;
  4088. }
  4089. /**
  4090. * @todo Remove existing module condition
  4091. * @param int $id_product
  4092. */
  4093. public static function getAttributesInformationsByProduct($id_product)
  4094. {
  4095. // if blocklayered module is installed we check if user has set custom attribute name
  4096. if (Module::isInstalled('blocklayered'))
  4097. {
  4098. $nb_custom_values = Db::getInstance()->executeS('
  4099. SELECT DISTINCT la.`id_attribute`, la.`url_name` as `attribute`
  4100. FROM `'._DB_PREFIX_.'attribute` a
  4101. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4102. ON (a.`id_attribute` = pac.`id_attribute`)
  4103. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4104. ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  4105. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4106. LEFT JOIN `'._DB_PREFIX_.'layered_indexable_attribute_lang_value` la
  4107. ON (la.`id_attribute` = a.`id_attribute` AND la.`id_lang` = '.(int)Context::getContext()->language->id.')
  4108. WHERE la.`url_name` IS NOT NULL
  4109. AND pa.`id_product` = '.(int)$id_product);
  4110. if (!empty($nb_custom_values))
  4111. {
  4112. $tab_id_attribute = array();
  4113. foreach ($nb_custom_values as $attribute)
  4114. {
  4115. $tab_id_attribute[] = $attribute['id_attribute'];
  4116. $group = Db::getInstance()->executeS('
  4117. SELECT g.`id_attribute_group`, g.`url_name` as `group`
  4118. FROM `'._DB_PREFIX_.'layered_indexable_attribute_group_lang_value` g
  4119. LEFT JOIN `'._DB_PREFIX_.'attribute` a
  4120. ON (a.`id_attribute_group` = g.`id_attribute_group`)
  4121. WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
  4122. AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
  4123. AND g.`url_name` IS NOT NULL');
  4124. if (empty($group))
  4125. {
  4126. $group = Db::getInstance()->executeS('
  4127. SELECT g.`id_attribute_group`, g.`name` as `group`
  4128. FROM `'._DB_PREFIX_.'attribute_group_lang` g
  4129. LEFT JOIN `'._DB_PREFIX_.'attribute` a
  4130. ON (a.`id_attribute_group` = g.`id_attribute_group`)
  4131. WHERE a.`id_attribute` = '.(int)$attribute['id_attribute'].'
  4132. AND g.`id_lang` = '.(int)Context::getContext()->language->id.'
  4133. AND g.`name` IS NOT NULL');
  4134. }
  4135. $result[] = array_merge($attribute, $group[0]);
  4136. }
  4137. $values_not_custom = Db::getInstance()->executeS('
  4138. SELECT DISTINCT a.`id_attribute`, a.`id_attribute_group`, a.`id_attribute_group`, al.`name` as `attribute`, agl.`name` as `group`
  4139. FROM `'._DB_PREFIX_.'attribute` a
  4140. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
  4141. ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
  4142. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
  4143. ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
  4144. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4145. ON (a.`id_attribute` = pac.`id_attribute`)
  4146. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4147. ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  4148. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4149. '.Shop::addSqlAssociation('attribute', 'pac').'
  4150. WHERE pa.`id_product` = '.(int)$id_product.'
  4151. AND a.`id_attribute` NOT IN('.implode(', ', $tab_id_attribute).')');
  4152. $result = array_merge($values_not_custom, $result);
  4153. }
  4154. else
  4155. {
  4156. $result = Db::getInstance()->executeS('
  4157. SELECT DISTINCT a.`id_attribute`, a.`id_attribute_group`, al.`name` as `attribute`, agl.`name` as `group`
  4158. FROM `'._DB_PREFIX_.'attribute` a
  4159. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
  4160. ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
  4161. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
  4162. ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
  4163. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4164. ON (a.`id_attribute` = pac.`id_attribute`)
  4165. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4166. ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  4167. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4168. '.Shop::addSqlAssociation('attribute', 'pac').'
  4169. WHERE pa.`id_product` = '.(int)$id_product);
  4170. }
  4171. }
  4172. else
  4173. {
  4174. $result = Db::getInstance()->executeS('
  4175. SELECT DISTINCT a.`id_attribute`, a.`id_attribute_group`, al.`name` as `attribute`, agl.`name` as `group`
  4176. FROM `'._DB_PREFIX_.'attribute` a
  4177. LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al
  4178. ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int)Context::getContext()->language->id.')
  4179. LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl
  4180. ON (a.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int)Context::getContext()->language->id.')
  4181. LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac
  4182. ON (a.`id_attribute` = pac.`id_attribute`)
  4183. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  4184. ON (pac.`id_product_attribute` = pa.`id_product_attribute`)
  4185. '.Shop::addSqlAssociation('product_attribute', 'pa').'
  4186. '.Shop::addSqlAssociation('attribute', 'pac').'
  4187. WHERE pa.`id_product` = '.(int)$id_product);
  4188. }
  4189. return $result;
  4190. }
  4191. /**
  4192. * Get the combination url anchor of the product
  4193. *
  4194. * @param integer $id_product_attribute
  4195. * @return string
  4196. */
  4197. public function getAnchor($id_product_attribute)
  4198. {
  4199. $attributes = Product::getAttributesParams($this->id, $id_product_attribute);
  4200. $anchor = '#';
  4201. foreach ($attributes as &$a)
  4202. {
  4203. foreach ($a as &$b)
  4204. $b = str_replace('-', '_', Tools::link_rewrite($b));
  4205. $anchor .= '/'.$a['group'].'-'.$a['name'];
  4206. }
  4207. return $anchor;
  4208. }
  4209. /**
  4210. * Gets the name of a given product, in the given lang
  4211. *
  4212. * @since 1.5.0
  4213. * @param int $id_product
  4214. * @param int $id_product_attribute Optional
  4215. * @param int $id_lang Optional
  4216. * @return string
  4217. */
  4218. public static function getProductName($id_product, $id_product_attribute = null, $id_lang = null)
  4219. {
  4220. // use the lang in the context if $id_lang is not defined
  4221. if (!$id_lang)
  4222. $id_lang = (int)Context::getContext()->language->id;
  4223. // creates the query object
  4224. $query = new DbQuery();
  4225. // selects different names, if it is a combination
  4226. if ($id_product_attribute)
  4227. $query->select('IFNULL(CONCAT(pl.name, \' : \', GROUP_CONCAT(DISTINCT agl.`name`, \' - \', al.name SEPARATOR \', \')),pl.name) as name');
  4228. else
  4229. $query->select('DISTINCT pl.name as name');
  4230. // adds joins & where clauses for combinations
  4231. if ($id_product_attribute)
  4232. {
  4233. $query->from('product_attribute', 'pa');
  4234. $query->join(Shop::addSqlAssociation('product_attribute', 'pa'));
  4235. $query->innerJoin('product_lang', 'pl', 'pl.id_product = pa.id_product AND pl.id_lang = '.(int)$id_lang);
  4236. $query->leftJoin('product_attribute_combination', 'pac', 'pac.id_product_attribute = pa.id_product_attribute');
  4237. $query->leftJoin('attribute', 'atr', 'atr.id_attribute = pac.id_attribute');
  4238. $query->leftJoin('attribute_lang', 'al', 'al.id_attribute = atr.id_attribute AND al.id_lang = '.(int)$id_lang);
  4239. $query->leftJoin('attribute_group_lang', 'agl', 'agl.id_attribute_group = atr.id_attribute_group AND agl.id_lang = '.(int)$id_lang);
  4240. $query->where('pa.id_product = '.(int)$id_product.' AND pa.id_product_attribute = '.(int)$id_product_attribute);
  4241. }
  4242. else // or just adds a 'where' clause for a simple product
  4243. {
  4244. $query->from('product_lang', 'pl');
  4245. $query->where('pl.id_product = '.(int)$id_product);
  4246. }
  4247. return Db::getInstance()->getValue($query);
  4248. }
  4249. public function addWs($autodate = true, $null_values = false)
  4250. {
  4251. $success = $this->add($autodate, $null_values);
  4252. if ($success && Configuration::get('PS_SEARCH_INDEXATION'))
  4253. Search::indexation(false, $this->id);
  4254. return $success;
  4255. }
  4256. public function updateWs($null_values = false)
  4257. {
  4258. $success = parent::update($null_values);
  4259. if ($success && Configuration::get('PS_SEARCH_INDEXATION'))
  4260. Search::indexation(false, $this->id);
  4261. return $success;
  4262. }
  4263. /**
  4264. * For a given product, returns its real quantity
  4265. *
  4266. * @since 1.5.0
  4267. * @param int $id_product
  4268. * @param int $id_product_attribute
  4269. * @param int $id_warehouse
  4270. * @param int $id_shop
  4271. * @return int real_quantity
  4272. */
  4273. public static function getRealQuantity($id_product, $id_product_attribute = 0, $id_warehouse = 0, $id_shop = null)
  4274. {
  4275. static $manager = null;
  4276. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && is_null($manager))
  4277. $manager = StockManagerFactory::getManager();
  4278. if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT') && Product::usesAdvancedStockManagement($id_product) &&
  4279. StockAvailable::dependsOnStock($id_product, $id_shop))
  4280. return $manager->getProductRealQuantities($id_product, $id_product_attribute, $id_warehouse, true);
  4281. else
  4282. return StockAvailable::getQuantityAvailableByProduct($id_product, $id_product_attribute, $id_shop);
  4283. }
  4284. /**
  4285. * For a given product, tells if it uses the advanced stock management
  4286. *
  4287. * @since 1.5.0
  4288. * @param int $id_product
  4289. * @return bool
  4290. */
  4291. public static function usesAdvancedStockManagement($id_product)
  4292. {
  4293. $query = new DbQuery;
  4294. $query->select('product_shop.advanced_stock_management');
  4295. $query->from('product', 'p');
  4296. $query->join(Shop::addSqlAssociation('product', 'p'));
  4297. $query->where('p.id_product = '.(int)$id_product);
  4298. return (bool)Db::getInstance()->getValue($query);
  4299. }
  4300. /**
  4301. * This method allows to flush price cache
  4302. * @static
  4303. * @since 1.5.0
  4304. */
  4305. public static function flushPriceCache()
  4306. {
  4307. self::$_prices = array();
  4308. self::$_pricesLevel2 = array();
  4309. }
  4310. /**
  4311. * Get list of parent categories
  4312. *
  4313. * @since 1.5.0
  4314. * @param int $id_lang
  4315. * @return array
  4316. */
  4317. public function getParentCategories($id_lang = null)
  4318. {
  4319. if (!$id_lang)
  4320. $id_lang = Context::getContext()->language->id;
  4321. $interval = Category::getInterval($this->id_category_default);
  4322. $sql = new DbQuery();
  4323. $sql->from('category', 'c');
  4324. $sql->leftJoin('category_lang', 'cl', 'c.id_category = cl.id_category AND id_lang = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl'));
  4325. $sql->where('c.nleft <= '.(int)$interval['nleft'].' AND c.nright >= '.(int)$interval['nright']);
  4326. $sql->orderBy('c.nleft');
  4327. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  4328. }
  4329. /**
  4330. * Fill the variables used for stock management
  4331. */
  4332. public function loadStockData()
  4333. {
  4334. if (Validate::isLoadedObject($this))
  4335. {
  4336. // By default, the product quantity correspond to the available quantity to sell in the current shop
  4337. $this->quantity = StockAvailable::getQuantityAvailableByProduct($this->id, 0);
  4338. $this->out_of_stock = StockAvailable::outOfStock($this->id);
  4339. $this->depends_on_stock = StockAvailable::dependsOnStock($this->id);
  4340. if (Context::getContext()->shop->getContext() == Shop::CONTEXT_GROUP && Context::getContext()->shop->getContextShopGroup()->share_stock == 1)
  4341. $this->advanced_stock_management = Db::getInstance()->getValue('SELECT `advanced_stock_management`
  4342. FROM '._DB_PREFIX_.'product_shop
  4343. WHERE id_product='.(int)$this->id.Shop::addSqlRestriction());
  4344. }
  4345. }
  4346. /**
  4347. * get the default category according to the shop
  4348. */
  4349. public function getDefaultCategory()
  4350. {
  4351. $default_category = Db::getInstance()->getValue('
  4352. SELECT product_shop.`id_category_default`
  4353. FROM `'._DB_PREFIX_.'product` p
  4354. '.Shop::addSqlAssociation('product', 'p').'
  4355. WHERE p.`id_product` = '.(int)$this->id);
  4356. if (!$default_category)
  4357. return array('id_category_default' => Context::getContext()->shop->id_category);
  4358. else
  4359. return $default_category;
  4360. }
  4361. public static function getShopsByProduct($id_product)
  4362. {
  4363. return Db::getInstance()->executeS('
  4364. SELECT `id_shop`
  4365. FROM `'._DB_PREFIX_.'product_shop`
  4366. WHERE `id_product` = '.(int)$id_product);
  4367. }
  4368. /**
  4369. * Remove all downloadable files for product and its attributes
  4370. *
  4371. * @return bool
  4372. */
  4373. public function deleteDownload()
  4374. {
  4375. $result = true;
  4376. $collection_download = new Collection('ProductDownload');
  4377. $collection_download->where('id_product', '=', $this->id);
  4378. foreach ($collection_download as $product_download)
  4379. $result &= $product_download->delete(true);
  4380. return $result;
  4381. }
  4382. /**
  4383. * @deprecated 1.5.0.10
  4384. * @see Product::getAttributeCombinations()
  4385. * @param int $id_lang
  4386. */
  4387. public function getAttributeCombinaisons($id_lang)
  4388. {
  4389. Tools::displayAsDeprecated('Use Product::getAttributeCombinations($id_lang)');
  4390. return $this->getAttributeCombinations($id_lang);
  4391. }
  4392. /**
  4393. * @deprecated 1.5.0.10
  4394. * @see Product::deleteAttributeCombination()
  4395. * @param int $id_product_attribute
  4396. */
  4397. public function deleteAttributeCombinaison($id_product_attribute)
  4398. {
  4399. Tools::displayAsDeprecated('Use Product::deleteAttributeCombination($id_product_attribute)');
  4400. return $this->deleteAttributeCombination($id_product_attribute);
  4401. }
  4402. /**
  4403. * Get the product type (simple, virtual, pack)
  4404. * @since in 1.5.0
  4405. *
  4406. * @return int
  4407. */
  4408. public function getType()
  4409. {
  4410. if (!$this->id)
  4411. return Product::PTYPE_SIMPLE;
  4412. if (Pack::isPack($this->id))
  4413. return Product::PTYPE_PACK;
  4414. if ($this->is_virtual)
  4415. return Product::PTYPE_VIRTUAL;
  4416. return Product::PTYPE_SIMPLE;
  4417. }
  4418. public function hasAttributesInOtherShops()
  4419. {
  4420. return (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  4421. SELECT pa.id_product_attribute
  4422. FROM `'._DB_PREFIX_.'product_attribute` pa
  4423. LEFT JOIN `'._DB_PREFIX_.'product_attribute_shop` pas ON (pa.`id_product_attribute` = pas.`id_product_attribute`)
  4424. WHERE pa.`id_product` = '.(int)$this->id
  4425. );
  4426. }
  4427. }