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