1<?php 2/** 3 * 2007-2016 PrestaShop 4 * 5 * thirty bees is an extension to the PrestaShop e-commerce software developed by PrestaShop SA 6 * Copyright (C) 2017-2018 thirty bees 7 * 8 * NOTICE OF LICENSE 9 * 10 * This source file is subject to the Open Software License (OSL 3.0) 11 * that is bundled with this package in the file LICENSE.txt. 12 * It is also available through the world-wide-web at this URL: 13 * http://opensource.org/licenses/osl-3.0.php 14 * If you did not receive a copy of the license and are unable to 15 * obtain it through the world-wide-web, please send an email 16 * to license@thirtybees.com so we can send you a copy immediately. 17 * 18 * DISCLAIMER 19 * 20 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer 21 * versions in the future. If you wish to customize PrestaShop for your 22 * needs please refer to https://www.thirtybees.com for more information. 23 * 24 * @author thirty bees <contact@thirtybees.com> 25 * @author PrestaShop SA <contact@prestashop.com> 26 * @copyright 2017-2018 thirty bees 27 * @copyright 2007-2016 PrestaShop SA 28 * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 29 * PrestaShop is an internationally registered trademark & property of PrestaShop SA 30 */ 31 32/** 33 * Class PackCore 34 */ 35class PackCore extends Product 36{ 37 protected static $cachePackItems = []; 38 protected static $cacheIsPack = []; 39 protected static $cacheIsPacked = []; 40 41 /** 42 * @param int $idProduct 43 * 44 * @return float|int 45 * 46 * @throws PrestaShopDatabaseException 47 * @throws PrestaShopException 48 * @since 1.0.0 49 */ 50 public static function noPackPrice($idProduct) 51 { 52 $sum = 0; 53 $priceDisplayMethod = !static::$_taxCalculationMethod; 54 $items = Pack::getItems($idProduct, Configuration::get('PS_LANG_DEFAULT')); 55 foreach ($items as $item) { 56 /** @var Product $item */ 57 $sum += $item->getPrice($priceDisplayMethod, ($item->id_pack_product_attribute ? $item->id_pack_product_attribute : null)) * $item->pack_quantity; 58 } 59 60 return $sum; 61 } 62 63 /** 64 * @param int $idProduct 65 * @param int $idLang 66 * 67 * @return array|mixed 68 * @throws PrestaShopDatabaseException 69 * @throws PrestaShopException 70 */ 71 public static function getItems($idProduct, $idLang) 72 { 73 if (!Pack::isFeatureActive()) { 74 return []; 75 } 76 77 if (array_key_exists($idProduct, static::$cachePackItems)) { 78 return static::$cachePackItems[$idProduct]; 79 } 80 $result = Db::getInstance()->executeS('SELECT id_product_item, id_product_attribute_item, quantity FROM `'._DB_PREFIX_.'pack` where id_product_pack = '.(int) $idProduct); 81 $arrayResult = []; 82 foreach ($result as $row) { 83 $p = new Product($row['id_product_item'], false, $idLang); 84 $p->loadStockData(); 85 $p->pack_quantity = $row['quantity']; 86 $p->id_pack_product_attribute = (isset($row['id_product_attribute_item']) && $row['id_product_attribute_item'] ? $row['id_product_attribute_item'] : 0); 87 if (isset($row['id_product_attribute_item']) && $row['id_product_attribute_item']) { 88 $sql = 'SELECT agl.`name` AS group_name, al.`name` AS attribute_name 89 FROM `'._DB_PREFIX_.'product_attribute` pa 90 '.Shop::addSqlAssociation('product_attribute', 'pa').' 91 LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = pa.`id_product_attribute` 92 LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute` 93 LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group` 94 LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int) Context::getContext()->language->id.') 95 LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int) Context::getContext()->language->id.') 96 WHERE pa.`id_product_attribute` = '.$row['id_product_attribute_item'].' 97 GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group` 98 ORDER BY pa.`id_product_attribute`'; 99 100 $combinations = Db::getInstance()->executeS($sql); 101 foreach ($combinations as $k => $combination) { 102 $p->name .= ' '.$combination['group_name'].'-'.$combination['attribute_name']; 103 } 104 } 105 $arrayResult[] = $p; 106 } 107 static::$cachePackItems[$idProduct] = $arrayResult; 108 109 return static::$cachePackItems[$idProduct]; 110 } 111 112 /** 113 * This method is allow to know if a feature is used or active 114 * 115 * @since 1.5.0.1 116 * @return bool 117 * @throws PrestaShopException 118 */ 119 public static function isFeatureActive() 120 { 121 return Configuration::get('PS_PACK_FEATURE_ACTIVE'); 122 } 123 124 /** 125 * @param int $idProduct 126 * 127 * @return int 128 * @throws PrestaShopDatabaseException 129 * @throws PrestaShopException 130 */ 131 public static function noPackWholesalePrice($idProduct) 132 { 133 $sum = 0; 134 $items = Pack::getItems($idProduct, Configuration::get('PS_LANG_DEFAULT')); 135 foreach ($items as $item) { 136 $sum += $item->wholesale_price * $item->pack_quantity; 137 } 138 139 return $sum; 140 } 141 142 /** 143 * @param int $idProduct 144 * 145 * @return bool 146 * @throws PrestaShopDatabaseException 147 * @throws PrestaShopException 148 */ 149 public static function isInStock($idProduct) 150 { 151 if (!Pack::isFeatureActive()) { 152 return true; 153 } 154 155 $items = Pack::getItems((int) $idProduct, Configuration::get('PS_LANG_DEFAULT')); 156 157 foreach ($items as $item) { 158 /** @var Product $item */ 159 // Updated for 1.5.0 160 if (Product::getQuantity($item->id) < $item->pack_quantity && !$item->isAvailableWhenOutOfStock((int) $item->out_of_stock)) { 161 return false; 162 } 163 } 164 165 return true; 166 } 167 168 /** 169 * @param int $idProduct 170 * @param int $idLang 171 * @param bool $full 172 * 173 * @return array|false|null|PDOStatement 174 * @throws PrestaShopDatabaseException 175 * @throws PrestaShopException 176 */ 177 public static function getItemTable($idProduct, $idLang, $full = false) 178 { 179 if (!Pack::isFeatureActive()) { 180 return []; 181 } 182 183 $context = Context::getContext(); 184 185 $sql = 'SELECT p.*, product_shop.*, pl.*, image_shop.`id_image` id_image, il.`legend`, cl.`name` AS category_default, a.quantity AS pack_quantity, product_shop.`id_category_default`, a.id_product_pack, a.id_product_attribute_item 186 FROM `'._DB_PREFIX_.'pack` a 187 LEFT JOIN `'._DB_PREFIX_.'product` p ON p.id_product = a.id_product_item 188 LEFT JOIN `'._DB_PREFIX_.'product_lang` pl 189 ON p.id_product = pl.id_product 190 AND pl.`id_lang` = '.(int) $idLang.Shop::addSqlRestrictionOnLang('pl').' 191 LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop 192 ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int) $context->shop->id.') 193 LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int) $idLang.') 194 '.Shop::addSqlAssociation('product', 'p').' 195 LEFT JOIN `'._DB_PREFIX_.'category_lang` cl 196 ON product_shop.`id_category_default` = cl.`id_category` 197 AND cl.`id_lang` = '.(int) $idLang.Shop::addSqlRestrictionOnLang('cl').' 198 WHERE product_shop.`id_shop` = '.(int) $context->shop->id.' 199 AND a.`id_product_pack` = '.(int) $idProduct.' 200 GROUP BY a.`id_product_item`, a.`id_product_attribute_item`'; 201 202 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); 203 204 foreach ($result as &$line) { 205 if (Combination::isFeatureActive() && isset($line['id_product_attribute_item']) && $line['id_product_attribute_item']) { 206 $line['cache_default_attribute'] = $line['id_product_attribute'] = $line['id_product_attribute_item']; 207 208 $sql = 'SELECT agl.`name` AS group_name, al.`name` AS attribute_name, pai.`id_image` AS id_product_attribute_image 209 FROM `'._DB_PREFIX_.'product_attribute` pa 210 '.Shop::addSqlAssociation('product_attribute', 'pa').' 211 LEFT JOIN `'._DB_PREFIX_.'product_attribute_combination` pac ON pac.`id_product_attribute` = '.$line['id_product_attribute_item'].' 212 LEFT JOIN `'._DB_PREFIX_.'attribute` a ON a.`id_attribute` = pac.`id_attribute` 213 LEFT JOIN `'._DB_PREFIX_.'attribute_group` ag ON ag.`id_attribute_group` = a.`id_attribute_group` 214 LEFT JOIN `'._DB_PREFIX_.'attribute_lang` al ON (a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int) Context::getContext()->language->id.') 215 LEFT JOIN `'._DB_PREFIX_.'attribute_group_lang` agl ON (ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int) Context::getContext()->language->id.') 216 LEFT JOIN `'._DB_PREFIX_.'product_attribute_image` pai ON ('.$line['id_product_attribute_item'].' = pai.`id_product_attribute`) 217 WHERE pa.`id_product` = '.(int) $line['id_product'].' AND pa.`id_product_attribute` = '.$line['id_product_attribute_item'].' 218 GROUP BY pa.`id_product_attribute`, ag.`id_attribute_group` 219 ORDER BY pa.`id_product_attribute`'; 220 221 $attrName = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); 222 223 if (isset($attrName[0]['id_product_attribute_image']) && $attrName[0]['id_product_attribute_image']) { 224 $line['id_image'] = $attrName[0]['id_product_attribute_image']; 225 } 226 $line['name'] .= "\n"; 227 foreach ($attrName as $value) { 228 $line['name'] .= ' '.$value['group_name'].'-'.$value['attribute_name']; 229 } 230 } 231 $line = Product::getTaxesInformations($line); 232 } 233 234 if (!$full) { 235 return $result; 236 } 237 238 $arrayResult = []; 239 foreach ($result as $prow) { 240 if (!Pack::isPack($prow['id_product'])) { 241 $prow['id_product_attribute'] = (int) $prow['id_product_attribute_item']; 242 $arrayResult[] = Product::getProductProperties($idLang, $prow); 243 } 244 } 245 246 return $arrayResult; 247 } 248 249 /** 250 * Is product a pack? 251 * 252 * @param int $idProduct 253 * 254 * @return bool 255 * @throws PrestaShopException 256 */ 257 public static function isPack($idProduct) 258 { 259 if (!Pack::isFeatureActive()) { 260 return false; 261 } 262 263 if (!$idProduct) { 264 return false; 265 } 266 267 if (!array_key_exists($idProduct, static::$cacheIsPack)) { 268 $result = Db::getInstance()->getValue('SELECT COUNT(*) FROM `'._DB_PREFIX_.'pack` WHERE id_product_pack = '.(int) $idProduct); 269 static::$cacheIsPack[$idProduct] = ($result > 0); 270 } 271 272 return static::$cacheIsPack[$idProduct]; 273 } 274 275 /** 276 * @param int $idProduct 277 * @param int $idLang 278 * @param bool $full 279 * @param null $limit 280 * 281 * @return array|false|null|PDOStatement 282 * @throws PrestaShopException 283 */ 284 public static function getPacksTable($idProduct, $idLang, $full = false, $limit = null) 285 { 286 if (!Pack::isFeatureActive()) { 287 return []; 288 } 289 290 $packs = Db::getInstance()->getValue( 291 ' 292 SELECT GROUP_CONCAT(a.`id_product_pack`) 293 FROM `'._DB_PREFIX_.'pack` a 294 WHERE a.`id_product_item` = '.(int) $idProduct 295 ); 296 297 if (!(int) $packs) { 298 return []; 299 } 300 301 $context = Context::getContext(); 302 303 $sql = ' 304 SELECT p.*, product_shop.*, pl.*, image_shop.`id_image` id_image, il.`legend`, IFNULL(product_attribute_shop.id_product_attribute, 0) id_product_attribute 305 FROM `'._DB_PREFIX_.'product` p 306 NATURAL LEFT JOIN `'._DB_PREFIX_.'product_lang` pl 307 '.Shop::addSqlAssociation('product', 'p').' 308 LEFT JOIN `'._DB_PREFIX_.'product_attribute_shop` product_attribute_shop 309 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.') 310 LEFT JOIN `'._DB_PREFIX_.'image_shop` image_shop 311 ON (image_shop.`id_product` = p.`id_product` AND image_shop.cover=1 AND image_shop.id_shop='.(int) $context->shop->id.') 312 LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (image_shop.`id_image` = il.`id_image` AND il.`id_lang` = '.(int) $idLang.') 313 WHERE pl.`id_lang` = '.(int) $idLang.' 314 '.Shop::addSqlRestrictionOnLang('pl').' 315 AND p.`id_product` IN ('.$packs.') 316 GROUP BY p.id_product'; 317 if ($limit) { 318 $sql .= ' LIMIT '.(int) $limit; 319 } 320 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql); 321 if (!$full) { 322 return $result; 323 } 324 325 $arrayResult = []; 326 foreach ($result as $row) { 327 if (!Pack::isPacked($row['id_product'])) { 328 $arrayResult[] = Product::getProductProperties($idLang, $row); 329 } 330 } 331 332 return $arrayResult; 333 } 334 335 /** 336 * Is product in a pack? 337 * If $id_product_attribute specified, then will restrict search on the given combination, 338 * else this method will match a product if at least one of all its combination is in a pack. 339 * 340 * @param int $idProduct 341 * @param bool|int $idProductAttribute Optional combination of the product 342 * 343 * @return bool 344 * @throws PrestaShopException 345 * @throws PrestaShopException 346 */ 347 public static function isPacked($idProduct, $idProductAttribute = false) 348 { 349 350 if (!Pack::isFeatureActive()) { 351 return false; 352 } 353 if ($idProductAttribute === false) { 354 $cacheKey = $idProduct.'-0'; 355 if (!array_key_exists($cacheKey, static::$cacheIsPacked)) { 356 $result = Db::getInstance()->getValue('SELECT COUNT(*) FROM `'._DB_PREFIX_.'pack` WHERE id_product_item = '.(int) $idProduct); 357 static::$cacheIsPacked[$cacheKey] = ($result > 0); 358 } 359 360 return static::$cacheIsPacked[$cacheKey]; 361 } else { 362 $cacheKey = $idProduct.'-'.$idProductAttribute; 363 if (!array_key_exists($cacheKey, static::$cacheIsPacked)) { 364 $result = Db::getInstance()->getValue( 365 'SELECT COUNT(*) FROM `'._DB_PREFIX_.'pack` WHERE id_product_item = '.((int) $idProduct).' AND 366 id_product_attribute_item = '.((int) $idProductAttribute) 367 ); 368 static::$cacheIsPacked[$cacheKey] = ($result > 0); 369 } 370 371 return static::$cacheIsPacked[$cacheKey]; 372 } 373 } 374 375 /** 376 * @param int $idProduct 377 * 378 * @return bool 379 * 380 * @since 1.0.0 381 * @throws PrestaShopException 382 */ 383 public static function deleteItems($idProduct) 384 { 385 return Db::getInstance()->update('product', ['cache_is_pack' => 0], 'id_product = '.(int) $idProduct) && 386 Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'pack` WHERE `id_product_pack` = '.(int) $idProduct) && 387 Configuration::updateGlobalValue('PS_PACK_FEATURE_ACTIVE', Pack::isCurrentlyUsed()); 388 } 389 390 /** 391 * This method is allow to know if a Pack entity is currently used 392 * 393 * @since 1.5.0 394 * 395 * @param string $table 396 * @param bool $hasActiveColumn 397 * 398 * @return bool 399 * @throws PrestaShopException 400 */ 401 public static function isCurrentlyUsed($table = null, $hasActiveColumn = false) 402 { 403 // We dont't use the parent method because the identifier isn't id_pack 404 return (bool) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 405 ' 406 SELECT `id_product_pack` 407 FROM `'._DB_PREFIX_.'pack` 408 ' 409 ); 410 } 411 412 /** 413 * Add an item to the pack 414 * 415 * @param int $idProduct 416 * @param int $idItem 417 * @param int $qty 418 * @param int $idAttributeItem 419 * 420 * @return bool true if everything was fine 421 * @throws PrestaShopDatabaseException 422 * @throws PrestaShopException 423 * @throws PrestaShopException 424 */ 425 public static function addItem($idProduct, $idItem, $qty, $idAttributeItem = 0) 426 { 427 $idAttributeItem = (int) $idAttributeItem ? (int) $idAttributeItem : Product::getDefaultAttribute((int) $idItem); 428 429 return Db::getInstance()->update('product', ['cache_is_pack' => 1], 'id_product = '.(int) $idProduct) && 430 Db::getInstance()->insert( 431 'pack', 432 [ 433 'id_product_pack' => (int) $idProduct, 434 'id_product_item' => (int) $idItem, 435 'id_product_attribute_item' => (int) $idAttributeItem, 436 'quantity' => (int) $qty, 437 ] 438 ) 439 && Configuration::updateGlobalValue('PS_PACK_FEATURE_ACTIVE', '1'); 440 } 441 442 /** 443 * @param int $idProductOld 444 * @param int $idProductNew 445 * 446 * @return bool 447 * 448 * @since 1.0.0 449 * @throws PrestaShopException 450 */ 451 public static function duplicate($idProductOld, $idProductNew) 452 { 453 Db::getInstance()->execute( 454 'INSERT INTO `'._DB_PREFIX_.'pack` (`id_product_pack`, `id_product_item`, `id_product_attribute_item`, `quantity`) 455 (SELECT '.(int) $idProductNew.', `id_product_item`, `id_product_attribute_item`, `quantity` FROM `'._DB_PREFIX_.'pack` WHERE `id_product_pack` = '.(int) $idProductOld.')' 456 ); 457 458 // If return query result, a non-pack product will return false 459 return true; 460 } 461 462 /** 463 * For a given pack, tells if it has at least one product using the advanced stock management 464 * 465 * @param int $idProduct id_pack 466 * 467 * @return bool 468 */ 469 public static function usesAdvancedStockManagement($idProduct) 470 { 471 if (!Pack::isPack($idProduct)) { 472 return false; 473 } 474 475 $products = Pack::getItems($idProduct, Configuration::get('PS_LANG_DEFAULT')); 476 foreach ($products as $product) { 477 // if one product uses the advanced stock management 478 if ($product->advanced_stock_management == 1) { 479 return true; 480 } 481 } 482 483 // not used 484 return false; 485 } 486 487 /** 488 * For a given pack, tells if all products using the advanced stock management 489 * 490 * @param int $idProduct id_pack 491 * 492 * @return bool 493 */ 494 public static function allUsesAdvancedStockManagement($idProduct) 495 { 496 if (!Pack::isPack($idProduct)) { 497 return false; 498 } 499 500 $products = Pack::getItems($idProduct, Configuration::get('PS_LANG_DEFAULT')); 501 foreach ($products as $product) { 502 // if one product uses the advanced stock management 503 if ($product->advanced_stock_management == 0) { 504 return false; 505 } 506 } 507 508 // not used 509 return true; 510 } 511 512 /** 513 * Returns Packs that conatins the given product in the right declinaison. 514 * 515 * @param integer $idItem Product item id that could be contained in a|many pack(s) 516 * @param integer $idAttributeItem The declinaison of the product 517 * @param integer $idLang 518 * 519 * @return array[Product] Packs that contains the given product 520 */ 521 public static function getPacksContainingItem($idItem, $idAttributeItem, $idLang) 522 { 523 if (!Pack::isFeatureActive() || !$idItem) { 524 return []; 525 } 526 527 $query = 'SELECT `id_product_pack`, `quantity` FROM `'._DB_PREFIX_.'pack` 528 WHERE `id_product_item` = '.((int) $idItem); 529 if (Combination::isFeatureActive()) { 530 $query .= ' AND `id_product_attribute_item` = '.((int) $idAttributeItem); 531 } 532 $result = Db::getInstance()->executeS($query); 533 $arrayResult = []; 534 foreach ($result as $row) { 535 $p = new Product($row['id_product_pack'], true, $idLang); 536 $p->loadStockData(); 537 $p->pack_item_quantity = $row['quantity']; // Specific need from StockAvailable::updateQuantity() 538 $arrayResult[] = $p; 539 } 540 541 return $arrayResult; 542 } 543} 544