PageRenderTime 94ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/classes/Product.php

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