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 AttributeGroupCore 34 * 35 * @since 1.0.0 36 */ 37class AttributeGroupCore extends ObjectModel 38{ 39 // @codingStandardsIgnoreStart 40 /** 41 * @see ObjectModel::$definition 42 */ 43 public static $definition = [ 44 'table' => 'attribute_group', 45 'primary' => 'id_attribute_group', 46 'multilang' => true, 47 'fields' => [ 48 'is_color_group' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool'], 49 'group_type' => ['type' => self::TYPE_STRING, 'required' => true], 50 'position' => ['type' => self::TYPE_INT, 'validate' => 'isInt'], 51 52 /* Lang fields */ 53 'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 128], 54 'public_name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 64], 55 ], 56 ]; 57 /** @var string Name */ 58 public $name; 59 /** @var bool $is_color_group */ 60 public $is_color_group; 61 /** @var int $position */ 62 public $position; 63 /** @var string $group_type */ 64 public $group_type; 65 /** @var string Public Name */ 66 public $public_name; 67 protected $webserviceParameters = [ 68 'objectsNodeName' => 'product_options', 69 'objectNodeName' => 'product_option', 70 'fields' => [], 71 'associations' => [ 72 'product_option_values' => [ 73 'resource' => 'product_option_value', 74 'fields' => [ 75 'id' => [], 76 ], 77 ], 78 ], 79 ]; 80 // @codingStandardsIgnoreEnd 81 82 /** 83 * Get all attributes for a given language / group 84 * 85 * @param int $idLang Language id 86 * @param bool $idAttributeGroup Attribute group id 87 * 88 * @return array Attributes 89 * @throws PrestaShopDatabaseException 90 * @throws PrestaShopException 91 */ 92 public static function getAttributes($idLang, $idAttributeGroup) 93 { 94 if (!Combination::isFeatureActive()) { 95 return []; 96 } 97 98 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 99 (new DbQuery()) 100 ->select('*') 101 ->from('attribute', 'a') 102 ->join(Shop::addSqlAssociation('attribute', 'a')) 103 ->leftJoin('attribute_lang', 'al', 'a.`id_attribute` = al.`id_attribute` AND al.`id_lang` = '.(int) $idLang) 104 ->where('a.`id_attribute_group` = '.(int) $idAttributeGroup) 105 ->orderBy('`position` ASC') 106 ); 107 } 108 109 /** 110 * Get all attributes groups for a given language 111 * 112 * @param int $idLang Language id 113 * 114 * @return array Attributes groups 115 * @throws PrestaShopDatabaseException 116 * @throws PrestaShopException 117 */ 118 public static function getAttributesGroups($idLang) 119 { 120 if (!Combination::isFeatureActive()) { 121 return []; 122 } 123 124 return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 125 (new DbQuery()) 126 ->select('DISTINCT agl.`name`, ag.*, agl.*') 127 ->from('attribute_group', 'ag') 128 ->join(Shop::addSqlAssociation('attribute_group', 'ag')) 129 ->leftJoin('attribute_group_lang', 'agl', 'ag.`id_attribute_group` = agl.`id_attribute_group` AND agl.`id_lang` = '.(int) $idLang) 130 ->orderBy('agl.`name` ASC') 131 ); 132 } 133 134 /** 135 * @param bool $autoDate 136 * @param bool $nullValues 137 * 138 * @return bool 139 * 140 * @since 1.0.0 141 * @version 1.0.0 Initial version 142 * @throws PrestaShopException 143 */ 144 public function add($autoDate = true, $nullValues = false) 145 { 146 if ($this->group_type == 'color') { 147 $this->is_color_group = 1; 148 } else { 149 $this->is_color_group = 0; 150 } 151 152 if ($this->position <= 0) { 153 $this->position = AttributeGroup::getHigherPosition() + 1; 154 } 155 156 $return = parent::add($autoDate, true); 157 Hook::exec('actionAttributeGroupSave', ['id_attribute_group' => $this->id]); 158 159 return $return; 160 } 161 162 /** 163 * getHigherPosition 164 * 165 * Get the higher group attribute position 166 * 167 * @return int $position 168 * @throws PrestaShopException 169 */ 170 public static function getHigherPosition() 171 { 172 $position = (int) Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue( 173 (new DbQuery()) 174 ->select('MAX(`position`)') 175 ->from('attribute_group') 176 ); 177 178 if (!$position) { 179 return -1; 180 } 181 182 return $position; 183 } 184 185 /** 186 * @param bool $nullValues 187 * 188 * @return bool 189 * 190 * @since 1.0.0 191 * @version 1.0.0 Initial version 192 */ 193 public function update($nullValues = false) 194 { 195 if ($this->group_type == 'color') { 196 $this->is_color_group = 1; 197 } else { 198 $this->is_color_group = 0; 199 } 200 201 $return = parent::update($nullValues); 202 Hook::exec('actionAttributeGroupSave', ['id_attribute_group' => $this->id]); 203 204 return $return; 205 } 206 207 /** 208 * Delete several objects from database 209 * 210 * return boolean Deletion result 211 * 212 * @since 1.0.0 213 * @version 1.0.0 Initial version 214 * 215 * @param array $selection 216 * 217 * @return bool 218 * @throws PrestaShopDatabaseException 219 */ 220 public function deleteSelection($selection) 221 { 222 /* Also delete Attributes */ 223 foreach ($selection as $value) { 224 $obj = new AttributeGroup($value); 225 if (!$obj->delete()) { 226 return false; 227 } 228 } 229 230 return true; 231 } 232 233 /** 234 * @return bool 235 * 236 * @since 1.0.0 237 * @version 1.0.0 Initial version 238 * @throws PrestaShopDatabaseException 239 * @throws PrestaShopException 240 */ 241 public function delete() 242 { 243 if (!$this->hasMultishopEntries() || Shop::getContext() == Shop::CONTEXT_ALL) { 244 /* Select children in order to find linked combinations */ 245 $attributeIds = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 246 (new DbQuery()) 247 ->select('`id_attribute`') 248 ->from('attribute') 249 ->where('`id_attribute_group` = '.(int) $this->id) 250 ); 251 if ($attributeIds === false) { 252 return false; 253 } 254 /* Removing attributes to the found combinations */ 255 $toRemove = []; 256 foreach ($attributeIds as $attribute) { 257 $toRemove[] = (int) $attribute['id_attribute']; 258 } 259 if (!empty($toRemove) 260 && Db::getInstance()->delete('product_attribute_combination', '`id_attribute` IN ('.implode(',', $toRemove).')') === false 261 ) { 262 return false; 263 } 264 /* Remove combinations if they do not possess attributes anymore */ 265 if (!AttributeGroup::cleanDeadCombinations()) { 266 return false; 267 } 268 /* Also delete related attributes */ 269 if (count($toRemove)) { 270 if (!Db::getInstance()->delete('attribute_lang', '`id_attribute` IN ('.implode(',', $toRemove).')') 271 || !Db::getInstance()->delete('attribute_shop', '`id_attribute` IN ('.implode(',', $toRemove).')') 272 || !Db::getInstance()->delete('attribute', '`id_attribute_group` = '.(int) $this->id) 273 ) { 274 return false; 275 } 276 } 277 $this->cleanPositions(); 278 } 279 $return = parent::delete(); 280 if ($return) { 281 Hook::exec('actionAttributeGroupDelete', ['id_attribute_group' => $this->id]); 282 } 283 284 return $return; 285 } 286 287 /** 288 * @return bool 289 * 290 * @throws PrestaShopDatabaseException 291 * @throws PrestaShopException 292 * @since 1.0.0 293 * @version 1.0.0 Initial version 294 */ 295 public static function cleanDeadCombinations() 296 { 297 $attributeCombinations = Db::getInstance()->executeS( 298 (new DbQuery()) 299 ->select('pac.`id_attribute`, pa.`id_product_attribute`') 300 ->from('product_attribute', 'pa') 301 ->leftJoin('product_attribute_combination', 'pac', 'pa.`id_product_attribute` = pac.`id_product_attribute`') 302 ); 303 $toRemove = []; 304 foreach ($attributeCombinations as $attributeCombination) { 305 if ((int) $attributeCombination['id_attribute'] == 0) { 306 $toRemove[] = (int) $attributeCombination['id_product_attribute']; 307 } 308 } 309 $return = true; 310 if (!empty($toRemove)) { 311 foreach ($toRemove as $remove) { 312 $combination = new Combination($remove); 313 $return &= $combination->delete(); 314 } 315 } 316 317 return $return; 318 } 319 320 /** 321 * Reorder group attribute position 322 * Call it after deleting a group attribute. 323 * 324 * @return bool $return 325 * 326 * @throws PrestaShopDatabaseException 327 * @throws PrestaShopException 328 * @since 1.0.0 329 * @version 1.0.0 Initial version 330 */ 331 public static function cleanPositions() 332 { 333 $return = true; 334 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 335 (new DbQuery()) 336 ->select('`id_attribute_group`') 337 ->from('attribute_group') 338 ->orderBy('`position`') 339 ); 340 341 $i = 0; 342 foreach ($result as $value) { 343 $return = Db::getInstance()->update( 344 'attribute_group', 345 [ 346 'position' => (int) $i++, 347 ], 348 '`id_attribute_group` = '.(int) $value['id_attribute_group'] 349 ); 350 } 351 352 return $return; 353 } 354 355 /** 356 * @param array $values 357 * 358 * @return bool 359 * 360 * @since 1.0.0 361 * @version 1.0.0 Initial version 362 * @throws PrestaShopDatabaseException 363 * @throws PrestaShopException 364 */ 365 public function setWsProductOptionValues($values) 366 { 367 $ids = []; 368 foreach ($values as $value) { 369 $ids[] = intval($value['id']); 370 } 371 Db::getInstance()->delete( 372 'attribute', 373 '`id_attribute_group` = '.(int) $this->id.' AND `id_attribute` NOT IN ('.implode(',', $ids).')' 374 ); 375 $ok = true; 376 foreach ($values as $value) { 377 $result = Db::getInstance()->update( 378 'attribute', 379 [ 380 'id_attribute_group' => (int) $this->id, 381 ], 382 '`id_attribute` = '.(int) $value['id'] 383 ); 384 if ($result === false) { 385 $ok = false; 386 } 387 } 388 389 return $ok; 390 } 391 392 /** 393 * @return array|false|mysqli_result|null|PDOStatement|resource 394 * 395 * @throws PrestaShopDatabaseException 396 * @throws PrestaShopException 397 * @since 1.0.0 398 * @version 1.0.0 Initial version 399 */ 400 public function getWsProductOptionValues() 401 { 402 $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 403 (new DbQuery()) 404 ->select('a.`id_attribute` AS `id`') 405 ->from('attribute', 'a') 406 ->join(Shop::addSqlAssociation('attribute', 'a')) 407 ->where('a.`id_attribute_group` = '.(int) $this->id) 408 ); 409 410 return $result; 411 } 412 413 /** 414 * Move a group attribute 415 * 416 * @param bool $way Up (1) or Down (0) 417 * @param int $position 418 * 419 * @return bool Update result 420 * 421 * @throws PrestaShopDatabaseException 422 * @throws PrestaShopException 423 * @since 1.0.0 424 * @version 1.0.0 Initial version 425 */ 426 public function updatePosition($way, $position) 427 { 428 if (!$res = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS( 429 (new DbQuery()) 430 ->select('ag.`position`, ag.`id_attribute_group`') 431 ->from('attribute_group', 'ag') 432 ->where('ag.`id_attribute_group` = '.(int) Tools::getValue('id_attribute_group', 1)) 433 ->orderBy('ag.`position` ASC') 434 ) 435 ) { 436 return false; 437 } 438 439 foreach ($res as $groupAttribute) { 440 if ((int) $groupAttribute['id_attribute_group'] == (int) $this->id) { 441 $movedGroupAttribute = $groupAttribute; 442 } 443 } 444 445 if (!isset($movedGroupAttribute) || !isset($position)) { 446 return false; 447 } 448 449 // < and > statements rather than BETWEEN operator 450 // since BETWEEN is treated differently according to databases 451 return Db::getInstance()->update( 452 'attribute_group', 453 [ 454 'position' => ['type' => 'sql', 'value' => '`position` '.($way ? '- 1' : '+ 1')], 455 ], 456 '`position` '.($way ? '> '.(int) $movedGroupAttribute['position'].' AND `position` <= '.(int) $position : '< '.(int) $movedGroupAttribute['position'].' AND `position` >= '.(int) $position) 457 ) && Db::getInstance()->update( 458 'attribute_group', 459 [ 460 'position' => (int) $position, 461 ], 462 '`id_attribute_group` = '.(int) $movedGroupAttribute['id_attribute_group'] 463 ); 464 } 465} 466