PageRenderTime 78ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/Category.php

https://github.com/netplayer/PrestaShop
PHP | 1610 lines | 1132 code | 173 blank | 305 comment | 205 complexity | 976ea8b8fe2f23abdb0989ff5c86c7a8 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * 2007-2014 PrestaShop
  4. *
  5. * NOTICE OF LICENSE
  6. *
  7. * This source file is subject to the Open Software License (OSL 3.0)
  8. * that is bundled with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://opensource.org/licenses/osl-3.0.php
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@prestashop.com so we can send you a copy immediately.
  14. *
  15. * DISCLAIMER
  16. *
  17. * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
  18. * versions in the future. If you wish to customize PrestaShop for your
  19. * needs please refer to http://www.prestashop.com for more information.
  20. *
  21. * @author PrestaShop SA <contact@prestashop.com>
  22. * @copyright 2007-2014 PrestaShop SA
  23. * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
  24. * International Registered Trademark & Property of PrestaShop SA
  25. */
  26. class CategoryCore extends ObjectModel
  27. {
  28. public $id;
  29. /** @var integer category ID */
  30. public $id_category;
  31. /** @var string Name */
  32. public $name;
  33. /** @var boolean Status for display */
  34. public $active = 1;
  35. /** @var integer category position */
  36. public $position;
  37. /** @var string Description */
  38. public $description;
  39. /** @var integer Parent category ID */
  40. public $id_parent;
  41. /** @var integer default Category id */
  42. public $id_category_default;
  43. /** @var integer Parents number */
  44. public $level_depth;
  45. /** @var integer Nested tree model "left" value */
  46. public $nleft;
  47. /** @var integer Nested tree model "right" value */
  48. public $nright;
  49. /** @var string string used in rewrited URL */
  50. public $link_rewrite;
  51. /** @var string Meta title */
  52. public $meta_title;
  53. /** @var string Meta keywords */
  54. public $meta_keywords;
  55. /** @var string Meta description */
  56. public $meta_description;
  57. /** @var string Object creation date */
  58. public $date_add;
  59. /** @var string Object last modification date */
  60. public $date_upd;
  61. /** @var boolean is Category Root */
  62. public $is_root_category;
  63. /** @var integer */
  64. public $id_shop_default;
  65. public $groupBox;
  66. protected static $_links = array();
  67. /**
  68. * @see ObjectModel::$definition
  69. */
  70. public static $definition = array(
  71. 'table' => 'category',
  72. 'primary' => 'id_category',
  73. 'multilang' => true,
  74. 'multilang_shop' => true,
  75. 'fields' => array(
  76. 'nleft' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
  77. 'nright' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
  78. 'level_depth' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
  79. 'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true),
  80. 'id_parent' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
  81. 'id_shop_default' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
  82. 'is_root_category' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
  83. 'position' => array('type' => self::TYPE_INT),
  84. 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
  85. 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
  86. // Lang fields
  87. 'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCatalogName', 'required' => true, 'size' => 128),
  88. 'link_rewrite' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 128),
  89. 'description' => array('type' => self::TYPE_HTML, 'lang' => true, 'validate' => 'isCleanHtml'),
  90. 'meta_title' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 128),
  91. 'meta_description' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
  92. 'meta_keywords' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255),
  93. ),
  94. );
  95. /** @var string id_image is the category ID when an image exists and 'default' otherwise */
  96. public $id_image = 'default';
  97. protected $webserviceParameters = array(
  98. 'objectsNodeName' => 'categories',
  99. 'hidden_fields' => array('nleft', 'nright', 'groupBox'),
  100. 'fields' => array(
  101. 'id_parent' => array('xlink_resource'=> 'categories'),
  102. 'level_depth' => array('setter' => false),
  103. 'nb_products_recursive' => array('getter' => 'getWsNbProductsRecursive', 'setter' => false),
  104. ),
  105. 'associations' => array(
  106. 'categories' => array('getter' => 'getChildrenWs', 'resource' => 'category', ),
  107. 'products' => array('getter' => 'getProductsWs', 'resource' => 'product', ),
  108. ),
  109. );
  110. public function __construct($id_category = null, $id_lang = null, $id_shop = null)
  111. {
  112. parent::__construct($id_category, $id_lang, $id_shop);
  113. $this->id_image = ($this->id && file_exists(_PS_CAT_IMG_DIR_.(int)$this->id.'.jpg')) ? (int)$this->id : false;
  114. $this->image_dir = _PS_CAT_IMG_DIR_;
  115. }
  116. public static function getDescriptionClean($description)
  117. {
  118. return Tools::getDescriptionClean($description);
  119. }
  120. public function add($autodate = true, $null_values = false)
  121. {
  122. if (!isset($this->level_depth))
  123. $this->level_depth = $this->calcLevelDepth();
  124. if ($this->is_root_category && ($id_root_category = (int)Configuration::get('PS_ROOT_CATEGORY')))
  125. $this->id_parent = $id_root_category;
  126. $ret = parent::add($autodate, $null_values);
  127. if (Tools::isSubmit('checkBoxShopAsso_category'))
  128. foreach (Tools::getValue('checkBoxShopAsso_category') as $id_shop => $value)
  129. {
  130. $position = Category::getLastPosition((int)$this->id_parent, $id_shop);
  131. $this->addPosition($position, $id_shop);
  132. }
  133. else
  134. foreach (Shop::getShops(true) as $shop)
  135. {
  136. $position = Category::getLastPosition((int)$this->id_parent, $shop['id_shop']);
  137. if (!$position)
  138. $position = 1;
  139. $this->addPosition($position, $shop['id_shop']);
  140. }
  141. if (!isset($this->doNotRegenerateNTree) || !$this->doNotRegenerateNTree)
  142. Category::regenerateEntireNtree();
  143. $this->updateGroup($this->groupBox);
  144. Hook::exec('actionCategoryAdd', array('category' => $this));
  145. return $ret;
  146. }
  147. /**
  148. * update category positions in parent
  149. *
  150. * @param mixed $null_values
  151. * @return boolean
  152. */
  153. public function update($null_values = false)
  154. {
  155. if ($this->id_parent == $this->id)
  156. throw new PrestaShopException('a category cannot be it\'s own parent');
  157. if ($this->is_root_category)
  158. $this->id_parent = (int)Configuration::get('PS_ROOT_CATEGORY');
  159. // Update group selection
  160. $this->updateGroup($this->groupBox);
  161. $this->level_depth = $this->calcLevelDepth();
  162. // If the parent category was changed, we don't want to have 2 categories with the same position
  163. if ($this->getDuplicatePosition())
  164. {
  165. if (Tools::isSubmit('checkBoxShopAsso_category'))
  166. foreach (Tools::getValue('checkBoxShopAsso_category') as $id_asso_object => $row)
  167. foreach ($row as $id_shop => $value)
  168. $this->addPosition(Category::getLastPosition((int)$this->id_parent, (int)$id_shop), (int)$id_shop);
  169. else
  170. foreach (Shop::getShops(true) as $shop)
  171. $this->addPosition(max(1, Category::getLastPosition((int)$this->id_parent, $shop['id_shop'])), $shop['id_shop']);
  172. }
  173. $this->cleanPositions((int)$this->id_parent);
  174. $ret = parent::update($null_values);
  175. if (!isset($this->doNotRegenerateNTree) || !$this->doNotRegenerateNTree)
  176. {
  177. Category::regenerateEntireNtree();
  178. $this->recalculateLevelDepth($this->id);
  179. }
  180. Hook::exec('actionCategoryUpdate', array('category' => $this));
  181. return $ret;
  182. }
  183. /**
  184. * @see ObjectModel::toggleStatus()
  185. */
  186. public function toggleStatus()
  187. {
  188. $result = parent::toggleStatus();
  189. Hook::exec('actionCategoryUpdate');
  190. return $result;
  191. }
  192. /**
  193. * Recursive scan of subcategories
  194. *
  195. * @param integer $max_depth Maximum depth of the tree (i.e. 2 => 3 levels depth)
  196. * @param integer $current_depth specify the current depth in the tree (don't use it, only for rucursivity!)
  197. * @param integer $id_lang Specify the id of the language used
  198. * @param array $excluded_ids_array specify a list of ids to exclude of results
  199. *
  200. * @return array Subcategories lite tree
  201. */
  202. public function recurseLiteCategTree($max_depth = 3, $current_depth = 0, $id_lang = null, $excluded_ids_array = null)
  203. {
  204. $id_lang = is_null($id_lang) ? Context::getContext()->language->id : (int)$id_lang;
  205. $children = array();
  206. $subcats = $this->getSubCategories($id_lang, true);
  207. if (($max_depth == 0 || $current_depth < $max_depth) && $subcats && count($subcats))
  208. foreach ($subcats as &$subcat)
  209. {
  210. if (!$subcat['id_category'])
  211. break;
  212. else if (!is_array($excluded_ids_array) || !in_array($subcat['id_category'], $excluded_ids_array))
  213. {
  214. $categ = new Category($subcat['id_category'], $id_lang);
  215. $children[] = $categ->recurseLiteCategTree($max_depth, $current_depth + 1, $id_lang, $excluded_ids_array);
  216. }
  217. }
  218. if (is_array($this->description))
  219. foreach ($this->description as $lang => $description)
  220. $this->description[$lang] = Category::getDescriptionClean($description);
  221. else
  222. $this->description = Category::getDescriptionClean($this->description);
  223. return array(
  224. 'id' => (int)$this->id,
  225. 'link' => Context::getContext()->link->getCategoryLink($this->id, $this->link_rewrite),
  226. 'name' => $this->name,
  227. 'desc'=> $this->description,
  228. 'children' => $children
  229. );
  230. }
  231. public static function recurseCategory($categories, $current, $id_category = 1, $id_selected = 1)
  232. {
  233. echo '<option value="'.$id_category.'"'.(($id_selected == $id_category) ? ' selected="selected"' : '').'>'.
  234. str_repeat('&nbsp;', $current['infos']['level_depth'] * 5).stripslashes($current['infos']['name']).'</option>';
  235. if (isset($categories[$id_category]))
  236. foreach (array_keys($categories[$id_category]) as $key)
  237. Category::recurseCategory($categories, $categories[$id_category][$key], $key, $id_selected);
  238. }
  239. /**
  240. * Recursively add specified category childs to $to_delete array
  241. *
  242. * @param array &$to_delete Array reference where categories ID will be saved
  243. * @param integer $id_category Parent category ID
  244. */
  245. protected function recursiveDelete(&$to_delete, $id_category)
  246. {
  247. if (!is_array($to_delete) || !$id_category)
  248. die(Tools::displayError());
  249. $result = Db::getInstance()->executeS('
  250. SELECT `id_category`
  251. FROM `'._DB_PREFIX_.'category`
  252. WHERE `id_parent` = '.(int)$id_category);
  253. foreach ($result as $row)
  254. {
  255. $to_delete[] = (int)$row['id_category'];
  256. $this->recursiveDelete($to_delete, (int)$row['id_category']);
  257. }
  258. }
  259. public function deleteLite()
  260. {
  261. // Directly call the parent of delete, in order to avoid recursion
  262. return parent::delete();
  263. }
  264. public function delete()
  265. {
  266. if ((int)$this->id === 0 || (int)$this->id === 1)
  267. return false;
  268. $this->clearCache();
  269. $all_cat = $this->getAllChildren();
  270. $all_cat[] = $this;
  271. foreach ($all_cat as $cat)
  272. {
  273. $cat->deleteLite();
  274. if (!$this->hasMultishopEntries())
  275. {
  276. $cat->deleteImage();
  277. $cat->cleanGroups();
  278. $cat->cleanAssoProducts();
  279. // Delete associated restrictions on cart rules
  280. CartRule::cleanProductRuleIntegrity('categories', array($cat->id));
  281. Category::cleanPositions($cat->id_parent);
  282. /* Delete Categories in GroupReduction */
  283. if (GroupReduction::getGroupsReductionByCategoryId((int)$cat->id))
  284. GroupReduction::deleteCategory($cat->id);
  285. }
  286. }
  287. /* Rebuild the nested tree */
  288. if (!$this->hasMultishopEntries() && (!isset($this->doNotRegenerateNTree) || !$this->doNotRegenerateNTree))
  289. Category::regenerateEntireNtree();
  290. Hook::exec('actionCategoryDelete', array('category' => $this));
  291. return true;
  292. }
  293. /**
  294. * Delete several categories from database
  295. *
  296. * return boolean Deletion result
  297. */
  298. public function deleteSelection($categories)
  299. {
  300. $return = 1;
  301. foreach ($categories as $id_category)
  302. {
  303. $category = new Category($id_category);
  304. if ($category->isRootCategoryForAShop())
  305. return false;
  306. else
  307. $return &= $category->delete();
  308. }
  309. return $return;
  310. }
  311. /**
  312. * Get the depth level for the category
  313. *
  314. * @return integer Depth level
  315. */
  316. public function calcLevelDepth()
  317. {
  318. /* Root category */
  319. if (!$this->id_parent)
  320. return 0;
  321. $parent_category = new Category((int)$this->id_parent);
  322. if (!Validate::isLoadedObject($parent_category))
  323. throw new PrestaShopException('Parent category does not exist');
  324. return $parent_category->level_depth + 1;
  325. }
  326. /**
  327. * Re-calculate the values of all branches of the nested tree
  328. */
  329. public static function regenerateEntireNtree()
  330. {
  331. $id = Context::getContext()->shop->id;
  332. $id_shop = $id ? $id: Configuration::get('PS_SHOP_DEFAULT');
  333. $categories = Db::getInstance()->executeS('
  334. SELECT c.`id_category`, c.`id_parent`
  335. FROM `'._DB_PREFIX_.'category` c
  336. LEFT JOIN `'._DB_PREFIX_.'category_shop` cs
  337. ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = '.(int)$id_shop.')
  338. ORDER BY c.`id_parent`, cs.`position` ASC');
  339. $categories_array = array();
  340. foreach ($categories as $category)
  341. $categories_array[$category['id_parent']]['subcategories'][] = $category['id_category'];
  342. $n = 1;
  343. if (isset($categories_array[0]) && $categories_array[0]['subcategories'])
  344. Category::_subTree($categories_array, $categories_array[0]['subcategories'][0], $n);
  345. }
  346. protected static function _subTree(&$categories, $id_category, &$n)
  347. {
  348. $left = $n++;
  349. if (isset($categories[(int)$id_category]['subcategories']))
  350. foreach ($categories[(int)$id_category]['subcategories'] as $id_subcategory)
  351. Category::_subTree($categories, (int)$id_subcategory, $n);
  352. $right = (int)$n++;
  353. Db::getInstance()->execute('
  354. UPDATE '._DB_PREFIX_.'category
  355. SET nleft = '.(int)$left.', nright = '.(int)$right.'
  356. WHERE id_category = '.(int)$id_category.' LIMIT 1');
  357. }
  358. /**
  359. * Updates level_depth for all children of the given id_category
  360. *
  361. * @param integer $id_category parent category
  362. */
  363. public function recalculateLevelDepth($id_category)
  364. {
  365. if (!is_numeric($id_category))
  366. throw new PrestaShopException('id category is not numeric');
  367. /* Gets all children */
  368. $categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  369. SELECT id_category, id_parent, level_depth
  370. FROM '._DB_PREFIX_.'category
  371. WHERE id_parent = '.(int)$id_category);
  372. /* Gets level_depth */
  373. $level = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('
  374. SELECT level_depth
  375. FROM '._DB_PREFIX_.'category
  376. WHERE id_category = '.(int)$id_category);
  377. /* Updates level_depth for all children */
  378. foreach ($categories as $sub_category)
  379. {
  380. Db::getInstance()->execute('
  381. UPDATE '._DB_PREFIX_.'category
  382. SET level_depth = '.(int)($level['level_depth'] + 1).'
  383. WHERE id_category = '.(int)$sub_category['id_category']);
  384. /* Recursive call */
  385. $this->recalculateLevelDepth($sub_category['id_category']);
  386. }
  387. }
  388. /**
  389. * Return available categories
  390. *
  391. * @param integer $id_lang Language ID
  392. * @param boolean $active return only active categories
  393. * @return array Categories
  394. */
  395. public static function getCategories($id_lang = false, $active = true, $order = true, $sql_filter = '', $sql_sort = '', $sql_limit = '')
  396. {
  397. if (!Validate::isBool($active))
  398. die(Tools::displayError());
  399. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  400. SELECT *
  401. FROM `'._DB_PREFIX_.'category` c
  402. '.Shop::addSqlAssociation('category', 'c').'
  403. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON c.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').'
  404. WHERE 1 '.$sql_filter.' '.($id_lang ? 'AND `id_lang` = '.(int)$id_lang : '').'
  405. '.($active ? 'AND `active` = 1' : '').'
  406. '.(!$id_lang ? 'GROUP BY c.id_category' : '').'
  407. '.($sql_sort != '' ? $sql_sort : 'ORDER BY c.`level_depth` ASC, category_shop.`position` ASC').'
  408. '.($sql_limit != '' ? $sql_limit : '')
  409. );
  410. if (!$order)
  411. return $result;
  412. $categories = array();
  413. foreach ($result as $row)
  414. $categories[$row['id_parent']][$row['id_category']]['infos'] = $row;
  415. return $categories;
  416. }
  417. public static function getNestedCategories($root_category = null, $id_lang = false, $active = true, $groups = null,
  418. $use_shop_restriction = true, $sql_filter = '', $sql_sort = '', $sql_limit = '')
  419. {
  420. if (isset($root_category) && !Validate::isInt($root_category))
  421. die(Tools::displayError());
  422. if (!Validate::isBool($active))
  423. die(Tools::displayError());
  424. if (isset($groups) && Group::isFeatureActive() && !is_array($groups))
  425. $groups = (array)$groups;
  426. $cache_id = 'Category::getNestedCategories_'.md5((int)$root_category.(int)$id_lang.(int)$active.(int)$active
  427. .(isset($groups) && Group::isFeatureActive() ? implode('', $groups) : ''));
  428. if (!Cache::isStored($cache_id))
  429. {
  430. $result = Db::getInstance()->executeS('
  431. SELECT c.*, cl.*
  432. FROM `'._DB_PREFIX_.'category` c
  433. '.($use_shop_restriction ? Shop::addSqlAssociation('category', 'c') : '').'
  434. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON c.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').'
  435. '.(isset($groups) && Group::isFeatureActive() ? 'LEFT JOIN `'._DB_PREFIX_.'category_group` cg ON c.`id_category` = cg.`id_category`' : '').'
  436. '.(isset($root_category) ? 'RIGHT JOIN `'._DB_PREFIX_.'category` c2 ON c2.`id_category` = '.(int)$root_category.' AND c.`nleft` >= c2.`nleft` AND c.`nright` <= c2.`nright`' : '').'
  437. WHERE 1 '.$sql_filter.' '.($id_lang ? 'AND `id_lang` = '.(int)$id_lang : '').'
  438. '.($active ? ' AND c.`active` = 1' : '').'
  439. '.(isset($groups) && Group::isFeatureActive() ? ' AND cg.`id_group` IN ('.implode(',', $groups).')' : '').'
  440. '.(!$id_lang || (isset($groups) && Group::isFeatureActive()) ? ' GROUP BY c.`id_category`' : '').'
  441. '.($sql_sort != '' ? $sql_sort : ' ORDER BY c.`level_depth` ASC').'
  442. '.($sql_sort == '' && $use_shop_restriction ? ', category_shop.`position` ASC' : '').'
  443. '.($sql_limit != '' ? $sql_limit : '')
  444. );
  445. $categories = array();
  446. $buff = array();
  447. if (!isset($root_category))
  448. $root_category = 1;
  449. foreach ($result as $row)
  450. {
  451. $current = &$buff[$row['id_category']];
  452. $current = $row;
  453. if ($row['id_category'] == $root_category)
  454. $categories[$row['id_category']] = &$current;
  455. else
  456. $buff[$row['id_parent']]['children'][$row['id_category']] = &$current;
  457. }
  458. Cache::store($cache_id, $categories);
  459. }
  460. return Cache::retrieve($cache_id);
  461. }
  462. public static function getSimpleCategories($id_lang)
  463. {
  464. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  465. SELECT c.`id_category`, cl.`name`
  466. FROM `'._DB_PREFIX_.'category` c
  467. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').')
  468. '.Shop::addSqlAssociation('category', 'c').'
  469. WHERE cl.`id_lang` = '.(int)$id_lang.'
  470. AND c.`id_category` != '.Configuration::get('PS_ROOT_CATEGORY').'
  471. GROUP BY c.id_category
  472. ORDER BY c.`id_category`, category_shop.`position`');
  473. }
  474. public function getShopID()
  475. {
  476. return $this->id_shop;
  477. }
  478. /**
  479. * Return current category childs
  480. *
  481. * @param integer $id_lang Language ID
  482. * @param boolean $active return only active categories
  483. * @return array Categories
  484. */
  485. public function getSubCategories($id_lang, $active = true)
  486. {
  487. $sql_groups_where = '';
  488. $sql_groups_join = '';
  489. if (Group::isFeatureActive())
  490. {
  491. $sql_groups_join = 'LEFT JOIN `'._DB_PREFIX_.'category_group` cg ON (cg.`id_category` = c.`id_category`)';
  492. $groups = FrontController::getCurrentCustomerGroups();
  493. $sql_groups_where = 'AND cg.`id_group` '.(count($groups) ? 'IN ('.implode(',', $groups).')' : '='.(int)Group::getCurrent()->id);
  494. }
  495. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  496. SELECT c.*, cl.id_lang, cl.name, cl.description, cl.link_rewrite, cl.meta_title, cl.meta_keywords, cl.meta_description
  497. FROM `'._DB_PREFIX_.'category` c
  498. '.Shop::addSqlAssociation('category', 'c').'
  499. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category` AND `id_lang` = '.(int)$id_lang.' '.Shop::addSqlRestrictionOnLang('cl').')
  500. '.$sql_groups_join.'
  501. WHERE `id_parent` = '.(int)$this->id.'
  502. '.($active ? 'AND `active` = 1' : '').'
  503. '.$sql_groups_where.'
  504. GROUP BY c.`id_category`
  505. ORDER BY `level_depth` ASC, category_shop.`position` ASC');
  506. foreach ($result as &$row)
  507. {
  508. $row['id_image'] = Tools::file_exists_cache(_PS_CAT_IMG_DIR_.$row['id_category'].'.jpg') ? (int)$row['id_category'] : Language::getIsoById($id_lang).'-default';
  509. $row['legend'] = 'no picture';
  510. }
  511. return $result;
  512. }
  513. /**
  514. * Return current category products
  515. *
  516. * @param integer $id_lang Language ID
  517. * @param integer $p Page number
  518. * @param integer $n Number of products per page
  519. * @param boolean $get_total return the number of results instead of the results themself
  520. * @param boolean $active return only active products
  521. * @param boolean $random active a random filter for returned products
  522. * @param int $random_number_products number of products to return if random is activated
  523. * @param boolean $check_access set to false to return all products (even if customer hasn't access)
  524. * @return mixed Products or number of products
  525. */
  526. public function getProducts($id_lang, $p, $n, $order_by = null, $order_way = null, $get_total = false, $active = true, $random = false, $random_number_products = 1, $check_access = true, Context $context = null)
  527. {
  528. if (!$context)
  529. $context = Context::getContext();
  530. if ($check_access && !$this->checkAccess($context->customer->id))
  531. return false;
  532. $front = true;
  533. if (!in_array($context->controller->controller_type, array('front', 'modulefront')))
  534. $front = false;
  535. if ($p < 1) $p = 1;
  536. if (empty($order_by))
  537. $order_by = 'position';
  538. else
  539. /* Fix for all modules which are now using lowercase values for 'orderBy' parameter */
  540. $order_by = strtolower($order_by);
  541. if (empty($order_way))
  542. $order_way = 'ASC';
  543. $order_by_prefix = false;
  544. if ($order_by == 'id_product' || $order_by == 'date_add' || $order_by == 'date_upd')
  545. $order_by_prefix = 'p';
  546. elseif ($order_by == 'name')
  547. $order_by_prefix = 'pl';
  548. elseif ($order_by == 'manufacturer')
  549. {
  550. $order_by_prefix = 'm';
  551. $order_by = 'name';
  552. }
  553. elseif ($order_by == 'position')
  554. $order_by_prefix = 'cp';
  555. if ($order_by == 'price')
  556. $order_by = 'orderprice';
  557. if (!Validate::isBool($active) || !Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way))
  558. die (Tools::displayError());
  559. $id_supplier = (int)Tools::getValue('id_supplier');
  560. /* Return only the number of products */
  561. if ($get_total)
  562. {
  563. $sql = 'SELECT COUNT(cp.`id_product`) AS total
  564. FROM `'._DB_PREFIX_.'product` p
  565. '.Shop::addSqlAssociation('product', 'p').'
  566. LEFT JOIN `'._DB_PREFIX_.'category_product` cp ON p.`id_product` = cp.`id_product`
  567. WHERE cp.`id_category` = '.(int)$this->id.
  568. ($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '').
  569. ($active ? ' AND product_shop.`active` = 1' : '').
  570. ($id_supplier ? 'AND p.id_supplier = '.(int)$id_supplier : '');
  571. return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue($sql);
  572. }
  573. $sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, MAX(product_attribute_shop.id_product_attribute) id_product_attribute, product_attribute_shop.minimal_quantity AS product_attribute_minimal_quantity, pl.`description`, pl.`description_short`, pl.`available_now`,
  574. pl.`available_later`, pl.`link_rewrite`, pl.`meta_description`, pl.`meta_keywords`, pl.`meta_title`, pl.`name`, MAX(image_shop.`id_image`) id_image,
  575. il.`legend`, m.`name` AS manufacturer_name, cl.`name` AS category_default,
  576. DATEDIFF(product_shop.`date_add`, DATE_SUB(NOW(),
  577. INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).'
  578. DAY)) > 0 AS new, product_shop.price AS orderprice
  579. FROM `'._DB_PREFIX_.'category_product` cp
  580. LEFT JOIN `'._DB_PREFIX_.'product` p
  581. ON p.`id_product` = cp.`id_product`
  582. '.Shop::addSqlAssociation('product', 'p').'
  583. LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa
  584. ON (p.`id_product` = pa.`id_product`)
  585. '.Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.`default_on` = 1').'
  586. '.Product::sqlStock('p', 'product_attribute_shop', false, $context->shop).'
  587. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
  588. ON (product_shop.`id_category_default` = cl.`id_category`
  589. AND cl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').')
  590. LEFT JOIN `'._DB_PREFIX_.'product_lang` pl
  591. ON (p.`id_product` = pl.`id_product`
  592. AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').')
  593. LEFT JOIN `'._DB_PREFIX_.'image` i
  594. ON (i.`id_product` = p.`id_product`)'.
  595. Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').'
  596. LEFT JOIN `'._DB_PREFIX_.'image_lang` il
  597. ON (image_shop.`id_image` = il.`id_image`
  598. AND il.`id_lang` = '.(int)$id_lang.')
  599. LEFT JOIN `'._DB_PREFIX_.'manufacturer` m
  600. ON m.`id_manufacturer` = p.`id_manufacturer`
  601. WHERE product_shop.`id_shop` = '.(int)$context->shop->id.'
  602. AND cp.`id_category` = '.(int)$this->id
  603. .($active ? ' AND product_shop.`active` = 1' : '')
  604. .($front ? ' AND product_shop.`visibility` IN ("both", "catalog")' : '')
  605. .($id_supplier ? ' AND p.id_supplier = '.(int)$id_supplier : '')
  606. .' GROUP BY product_shop.id_product';
  607. if ($random === true)
  608. $sql .= ' ORDER BY RAND() LIMIT '.(int)$random_number_products;
  609. else
  610. $sql .= ' ORDER BY '.(!empty($order_by_prefix) ? $order_by_prefix.'.' : '').'`'.bqSQL($order_by).'` '.pSQL($order_way).'
  611. LIMIT '.(((int)$p - 1) * (int)$n).','.(int)$n;
  612. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  613. if ($order_by == 'orderprice')
  614. Tools::orderbyPrice($result, $order_way);
  615. if (!$result)
  616. return array();
  617. /* Modify SQL result */
  618. return Product::getProductsProperties($id_lang, $result);
  619. }
  620. /**
  621. * Return main categories
  622. *
  623. * @param integer $id_lang Language ID
  624. * @param boolean $active return only active categories
  625. * @return array categories
  626. */
  627. public static function getHomeCategories($id_lang, $active = true, $id_shop = false)
  628. {
  629. return self::getChildren(Configuration::get('PS_HOME_CATEGORY'), $id_lang, $active, $id_shop);
  630. }
  631. public static function getRootCategory($id_lang = null, Shop $shop = null)
  632. {
  633. $context = Context::getContext();
  634. if (is_null($id_lang))
  635. $id_lang = $context->language->id;
  636. if (!$shop)
  637. if (Shop::isFeatureActive() && Shop::getContext() != Shop::CONTEXT_SHOP)
  638. $shop = new Shop(Configuration::get('PS_SHOP_DEFAULT'));
  639. else
  640. $shop = $context->shop;
  641. else
  642. return new Category($shop->getCategory(), $id_lang);
  643. $is_more_than_one_root_category = count(Category::getCategoriesWithoutParent()) > 1;
  644. if (Shop::isFeatureActive() && $is_more_than_one_root_category && Shop::getContext() != Shop::CONTEXT_SHOP)
  645. $category = Category::getTopCategory($id_lang);
  646. else
  647. $category = new Category($shop->getCategory(), $id_lang);
  648. return $category;
  649. }
  650. /**
  651. *
  652. * @param int $id_parent
  653. * @param int $id_lang
  654. * @param bool $active
  655. * @return array
  656. */
  657. public static function getChildren($id_parent, $id_lang, $active = true, $id_shop = false)
  658. {
  659. if (!Validate::isBool($active))
  660. die(Tools::displayError());
  661. $cache_id = 'Category::getChildren_'.(int)$id_parent.'-'.(int)$id_lang.'-'.(bool)$active.'-'.(int)$id_shop;
  662. if (!Cache::isStored($cache_id))
  663. {
  664. $query = 'SELECT c.`id_category`, cl.`name`, cl.`link_rewrite`, category_shop.`id_shop`
  665. FROM `'._DB_PREFIX_.'category` c
  666. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').')
  667. '.Shop::addSqlAssociation('category', 'c').'
  668. WHERE `id_lang` = '.(int)$id_lang.'
  669. AND c.`id_parent` = '.(int)$id_parent.'
  670. '.($active ? 'AND `active` = 1' : '').'
  671. GROUP BY c.`id_category`
  672. ORDER BY category_shop.`position` ASC';
  673. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query);
  674. Cache::store($cache_id, $result);
  675. }
  676. return Cache::retrieve($cache_id);
  677. }
  678. /**
  679. * Return an array of all children of the current category
  680. *
  681. * @param int $id_lang
  682. * @return PrestaShopCollection Collection of Category
  683. */
  684. public function getAllChildren($id_lang = null)
  685. {
  686. if (is_null($id_lang))
  687. $id_lang = Context::getContext()->language->id;
  688. $categories = new PrestaShopCollection('Category', $id_lang);
  689. $categories->where('nleft', '>', $this->nleft);
  690. $categories->where('nright', '<', $this->nright);
  691. return $categories;
  692. }
  693. /**
  694. * This method allow to return children categories with the number of sub children selected for a product
  695. *
  696. * @param int $id_parent
  697. * @param int $id_product
  698. * @param int $id_lang
  699. * @return array
  700. */
  701. public static function getChildrenWithNbSelectedSubCat($id_parent, $selected_cat, $id_lang, Shop $shop = null, $use_shop_context = true)
  702. {
  703. if (!$shop)
  704. $shop = Context::getContext()->shop;
  705. $id_shop = $shop->id ? $shop->id : Configuration::get('PS_SHOP_DEFAULT');
  706. $selected_cat = explode(',', str_replace(' ', '', $selected_cat));
  707. $sql = '
  708. SELECT c.`id_category`, c.`level_depth`, cl.`name`,
  709. IF((
  710. SELECT COUNT(*)
  711. FROM `'._DB_PREFIX_.'category` c2
  712. WHERE c2.`id_parent` = c.`id_category`
  713. ) > 0, 1, 0) AS has_children,
  714. '.($selected_cat ? '(
  715. SELECT count(c3.`id_category`)
  716. FROM `'._DB_PREFIX_.'category` c3
  717. WHERE c3.`nleft` > c.`nleft`
  718. AND c3.`nright` < c.`nright`
  719. AND c3.`id_category` IN ('.implode(',', array_map('intval', $selected_cat)).')
  720. )' : '0').' AS nbSelectedSubCat
  721. FROM `'._DB_PREFIX_.'category` c
  722. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category` '.Shop::addSqlRestrictionOnLang('cl', $id_shop).')
  723. LEFT JOIN `'._DB_PREFIX_.'category_shop` cs ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = '.(int)$id_shop.')
  724. WHERE `id_lang` = '.(int)$id_lang.'
  725. AND c.`id_parent` = '.(int)$id_parent;
  726. if (Shop::getContext() == Shop::CONTEXT_SHOP && $use_shop_context)
  727. $sql .= ' AND cs.`id_shop` = '.(int)$shop->id;
  728. if (!Shop::isFeatureActive() || Shop::getContext() == Shop::CONTEXT_SHOP && $use_shop_context)
  729. $sql .= ' ORDER BY cs.`position` ASC';
  730. return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($sql);
  731. }
  732. /**
  733. * Copy products from a category to another
  734. *
  735. * @param integer $id_old Source category ID
  736. * @param boolean $id_new Destination category ID
  737. * @return boolean Duplication result
  738. */
  739. public static function duplicateProductCategories($id_old, $id_new)
  740. {
  741. $sql = 'SELECT `id_category`
  742. FROM `'._DB_PREFIX_.'category_product`
  743. WHERE `id_product` = '.(int)$id_old;
  744. $result = Db::getInstance()->executeS($sql);
  745. $row = array();
  746. if ($result)
  747. foreach ($result as $i)
  748. $row[] = '('.implode(', ', array((int)$id_new, $i['id_category'], '(SELECT tmp.max + 1 FROM (
  749. SELECT MAX(cp.`position`) AS max
  750. FROM `'._DB_PREFIX_.'category_product` cp
  751. WHERE cp.`id_category`='.(int)$i['id_category'].') AS tmp)'
  752. )).')';
  753. $flag = Db::getInstance()->execute('
  754. INSERT IGNORE INTO `'._DB_PREFIX_.'category_product` (`id_product`, `id_category`, `position`)
  755. VALUES '.implode(',', $row)
  756. );
  757. return $flag;
  758. }
  759. /**
  760. * Check if category can be moved in another one.
  761. * The category cannot be moved in a child category.
  762. *
  763. * @param integer $id_category current category
  764. * @param integer $id_parent Parent candidate
  765. * @return boolean Parent validity
  766. */
  767. public static function checkBeforeMove($id_category, $id_parent)
  768. {
  769. if ($id_category == $id_parent) return false;
  770. if ($id_parent == Configuration::get('PS_HOME_CATEGORY')) return true;
  771. $i = (int)$id_parent;
  772. while (42)
  773. {
  774. $result = Db::getInstance()->getRow('SELECT `id_parent` FROM `'._DB_PREFIX_.'category` WHERE `id_category` = '.(int)$i);
  775. if (!isset($result['id_parent'])) return false;
  776. if ($result['id_parent'] == $id_category) return false;
  777. if ($result['id_parent'] == Configuration::get('PS_HOME_CATEGORY')) return true;
  778. $i = $result['id_parent'];
  779. }
  780. }
  781. public static function getLinkRewrite($id_category, $id_lang)
  782. {
  783. if (!Validate::isUnsignedId($id_category) || !Validate::isUnsignedId($id_lang))
  784. return false;
  785. if (!isset(self::$_links[$id_category.'-'.$id_lang]))
  786. self::$_links[$id_category.'-'.$id_lang] = Db::getInstance()->getValue('
  787. SELECT cl.`link_rewrite`
  788. FROM `'._DB_PREFIX_.'category_lang` cl
  789. WHERE `id_lang` = '.(int)$id_lang.'
  790. '.Shop::addSqlRestrictionOnLang('cl').'
  791. AND cl.`id_category` = '.(int)$id_category);
  792. return self::$_links[$id_category.'-'.$id_lang];
  793. }
  794. public function getLink(Link $link = null, $id_lang = null)
  795. {
  796. if (!$link)
  797. $link = Context::getContext()->link;
  798. if (!$id_lang && is_array($this->link_rewrite))
  799. $id_lang = Context::getContext()->language->id;
  800. return $link->getCategoryLink($this,
  801. is_array($this->link_rewrite) ? $this->link_rewrite[$id_lang] : $this->link_rewrite, $id_lang);
  802. }
  803. public function getName($id_lang = null)
  804. {
  805. if (!$id_lang)
  806. {
  807. if (isset($this->name[Context::getContext()->language->id]))
  808. $id_lang = Context::getContext()->language->id;
  809. else
  810. $id_lang = (int)Configuration::get('PS_LANG_DEFAULT');
  811. }
  812. return isset($this->name[$id_lang]) ? $this->name[$id_lang] : '';
  813. }
  814. /**
  815. * Light back office search for categories
  816. *
  817. * @param integer $id_lang Language ID
  818. * @param string $query Searched string
  819. * @param boolean $unrestricted allows search without lang and includes first category and exact match
  820. * @return array Corresponding categories
  821. */
  822. public static function searchByName($id_lang, $query, $unrestricted = false)
  823. {
  824. if ($unrestricted === true)
  825. return Db::getInstance()->getRow('
  826. SELECT c.*, cl.*
  827. FROM `'._DB_PREFIX_.'category` c
  828. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category` '.Shop::addSqlRestrictionOnLang('cl').')
  829. WHERE `name` LIKE \''.pSQL($query).'\'');
  830. else
  831. return Db::getInstance()->executeS('
  832. SELECT c.*, cl.*
  833. FROM `'._DB_PREFIX_.'category` c
  834. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category` AND `id_lang` = '.(int)$id_lang.' '.Shop::addSqlRestrictionOnLang('cl').')
  835. WHERE `name` LIKE \'%'.pSQL($query).'%\'
  836. AND c.`id_category` != '.(int)Configuration::get('PS_HOME_CATEGORY'));
  837. }
  838. /**
  839. * Retrieve category by name and parent category id
  840. *
  841. * @param integer $id_lang Language ID
  842. * @param string $category_name Searched category name
  843. * @param integer $id_parent_category parent category ID
  844. * @return array Corresponding category
  845. */
  846. public static function searchByNameAndParentCategoryId($id_lang, $category_name, $id_parent_category)
  847. {
  848. return Db::getInstance()->getRow('
  849. SELECT c.*, cl.*
  850. FROM `'._DB_PREFIX_.'category` c
  851. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
  852. ON (c.`id_category` = cl.`id_category`
  853. AND `id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').')
  854. WHERE `name` LIKE \''.pSQL($category_name).'\'
  855. AND c.`id_category` != '.(int)Configuration::get('PS_HOME_CATEGORY').'
  856. AND c.`id_parent` = '.(int)$id_parent_category);
  857. }
  858. /**
  859. * Search with Pathes for categories
  860. *
  861. * @param integer $id_lang Language ID
  862. * @param string $path of category
  863. * @param boolean $object_to_create a category
  864. * * @param boolean $method_to_create a category
  865. * @return array Corresponding categories
  866. */
  867. public static function searchByPath($id_lang, $path, $object_to_create = false, $method_to_create = false)
  868. {
  869. $categories = explode('/', trim($path));
  870. $category = $id_parent_category = false;
  871. if (is_array($categories) && count($categories))
  872. foreach($categories as $category_name)
  873. {
  874. if ($id_parent_category)
  875. $category = Category::searchByNameAndParentCategoryId($id_lang, $category_name, $id_parent_category);
  876. else
  877. $category = Category::searchByName($id_lang,$category_name,true);
  878. if (!$category && $object_to_create && $method_to_create)
  879. {
  880. call_user_func_array(array($object_to_create, $method_to_create), array($id_lang, $category_name , $id_parent_category));
  881. $category = Category::searchByPath($id_lang, $category_name);
  882. }
  883. if (isset($category['id_category']) && $category['id_category'])
  884. $id_parent_category = (int)$category['id_category'];
  885. }
  886. return $category;
  887. }
  888. /**
  889. * Get Each parent category of this category until the root category
  890. *
  891. * @param integer $id_lang Language ID
  892. * @return array Corresponding categories
  893. */
  894. public function getParentsCategories($id_lang = null)
  895. {
  896. $context = Context::getContext()->cloneContext();
  897. $context->shop = clone($context->shop);
  898. if (is_null($id_lang))
  899. $id_lang = $context->language->id;
  900. $categories = null;
  901. $id_current = $this->id;
  902. if (count(Category::getCategoriesWithoutParent()) > 1 && Configuration::get('PS_MULTISHOP_FEATURE_ACTIVE') && count(Shop::getShops(true, null, true)) != 1)
  903. $context->shop->id_category = Category::getTopCategory()->id;
  904. elseif (!$context->shop->id)
  905. $context->shop = new Shop(Configuration::get('PS_SHOP_DEFAULT'));
  906. $id_shop = $context->shop->id;
  907. while (true)
  908. {
  909. $sql = '
  910. SELECT c.*, cl.*
  911. FROM `'._DB_PREFIX_.'category` c
  912. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
  913. ON (c.`id_category` = cl.`id_category`
  914. AND `id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('cl').')';
  915. if (Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP)
  916. $sql .= ' LEFT JOIN `'._DB_PREFIX_.'category_shop` cs ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = '.(int)$id_shop.')';
  917. $sql .= ' WHERE c.`id_category` = '.(int)$id_current;
  918. if (Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP)
  919. $sql .= ' AND cs.`id_shop` = '.(int)$context->shop->id;
  920. $root_category = Category::getRootCategory();
  921. if (Shop::isFeatureActive() && Shop::getContext() == Shop::CONTEXT_SHOP
  922. && (!Tools::isSubmit('id_category') || (int)Tools::getValue('id_category') == (int)$root_category->id || (int)$root_category->id == (int)$context->shop->id_category))
  923. $sql .= ' AND c.`id_parent` != 0';
  924. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($sql);
  925. if ($result)
  926. $categories[] = $result;
  927. elseif (!$categories)
  928. $categories = array();
  929. if (!$result || ($result['id_category'] == $context->shop->id_category))
  930. return $categories;
  931. $id_current = $result['id_parent'];
  932. }
  933. }
  934. /**
  935. * Specify if a category already in base
  936. *
  937. * @param int $id_category Category id
  938. * @return boolean
  939. */
  940. public static function categoryExists($id_category)
  941. {
  942. $row = Db::getInstance()->getRow('
  943. SELECT `id_category`
  944. FROM '._DB_PREFIX_.'category c
  945. WHERE c.`id_category` = '.(int)$id_category);
  946. return isset($row['id_category']);
  947. }
  948. public function cleanGroups()
  949. {
  950. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'category_group` WHERE `id_category` = '.(int)$this->id);
  951. }
  952. public function cleanAssoProducts()
  953. {
  954. Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'category_product` WHERE `id_category` = '.(int)$this->id);
  955. }
  956. public function addGroups($groups)
  957. {
  958. foreach ($groups as $group)
  959. Db::getInstance()->insert('category_group', array('id_category' => (int)$this->id, 'id_group' => (int)$group));
  960. }
  961. public function getGroups()
  962. {
  963. $cache_id = 'Category::getGroups_'.(int)$this->id;
  964. if (!Cache::isStored($cache_id))
  965. {
  966. $result = Db::getInstance()->executeS('
  967. SELECT cg.`id_group`
  968. FROM '._DB_PREFIX_.'category_group cg
  969. WHERE cg.`id_category` = '.(int)$this->id);
  970. $groups = array();
  971. foreach ($result as $group)
  972. $groups[] = $group['id_group'];
  973. Cache::store($cache_id, $groups);
  974. }
  975. return Cache::retrieve($cache_id);
  976. }
  977. public function addGroupsIfNoExist($id_group)
  978. {
  979. $groups = $this->getGroups();
  980. if (!in_array((int)$id_group, $groups))
  981. return $this->addGroups(array((int)$id_group));
  982. return false;
  983. }
  984. /**
  985. * checkAccess return true if id_customer is in a group allowed to see this category.
  986. *
  987. * @param mixed $id_customer
  988. * @access public
  989. * @return boolean true if access allowed for customer $id_customer
  990. */
  991. public function checkAccess($id_customer)
  992. {
  993. $cache_id = 'Category::checkAccess_'.(int)$this->id.'-'.$id_customer.(!$id_customer ? '-'.(int)Group::getCurrent()->id : '');
  994. if (!Cache::isStored($cache_id))
  995. {
  996. if (!$id_customer)
  997. $result = (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  998. SELECT ctg.`id_group`
  999. FROM '._DB_PREFIX_.'category_group ctg
  1000. WHERE ctg.`id_category` = '.(int)$this->id.' AND ctg.`id_group` = '.(int)Group::getCurrent()->id);
  1001. else
  1002. $result = (bool)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  1003. SELECT ctg.`id_group`
  1004. FROM '._DB_PREFIX_.'category_group ctg
  1005. INNER JOIN '._DB_PREFIX_.'customer_group cg on (cg.`id_group` = ctg.`id_group` AND cg.`id_customer` = '.(int)$id_customer.')
  1006. WHERE ctg.`id_category` = '.(int)$this->id);
  1007. Cache::store($cache_id, $result);
  1008. }
  1009. return Cache::retrieve($cache_id);
  1010. }
  1011. /**
  1012. * Update customer groups associated to the object
  1013. *
  1014. * @param array $list groups
  1015. */
  1016. public function updateGroup($list)
  1017. {
  1018. $this->cleanGroups();
  1019. if (empty($list))
  1020. $list = array(Configuration::get('PS_UNIDENTIFIED_GROUP'), Configuration::get('PS_GUEST_GROUP'), Configuration::get('PS_CUSTOMER_GROUP'));
  1021. $this->addGroups($list);
  1022. }
  1023. public static function setNewGroupForHome($id_group)
  1024. {
  1025. if (!(int)$id_group)
  1026. return false;
  1027. return Db::getInstance()->execute('
  1028. INSERT INTO `'._DB_PREFIX_.'category_group` (`id_category`, `id_group`)
  1029. VALUES ('.(int)Context::getContext()->shop->getCategory().', '.(int)$id_group.')');
  1030. }
  1031. public function updatePosition($way, $position)
  1032. {
  1033. if (!$res = Db::getInstance()->executeS('
  1034. SELECT cp.`id_category`, category_shop.`position`, cp.`id_parent`
  1035. FROM `'._DB_PREFIX_.'category` cp
  1036. '.Shop::addSqlAssociation('category', 'cp').'
  1037. WHERE cp.`id_parent` = '.(int)$this->id_parent.'
  1038. ORDER BY category_shop.`position` ASC'
  1039. ))
  1040. return false;
  1041. $moved_category = false;
  1042. foreach ($res as $category)
  1043. if ((int)$category['id_category'] == (int)$this->id)
  1044. $moved_category = $category;
  1045. if ($moved_category === false || !$position)
  1046. return false;
  1047. // < and > statements rather than BETWEEN operator
  1048. // since BETWEEN is treated differently according to databases
  1049. $result = (Db::getInstance()->execute('
  1050. UPDATE `'._DB_PREFIX_.'category` c '.Shop::addSqlAssociation('category', 'c').'
  1051. SET category_shop.`position`= category_shop.`position` '.($way ? '- 1' : '+ 1').'
  1052. WHERE category_shop.`position`
  1053. '.($way
  1054. ? '> '.(int)$moved_category['position'].' AND category_shop.`position` <= '.(int)$position
  1055. : '< '.(int)$moved_category['position'].' AND category_shop.`position` >= '.(int)$position).'
  1056. AND c.`id_parent`='.(int)$moved_category['id_parent'])
  1057. && Db::getInstance()->execute('
  1058. UPDATE `'._DB_PREFIX_.'category` c '.Shop::addSqlAssociation('category', 'c').'
  1059. SET category_shop.`position` = '.(int)$position.'
  1060. WHERE c.`id_parent` = '.(int)$moved_category['id_parent'].'
  1061. AND c.`id_category`='.(int)$moved_category['id_category']));
  1062. Hook::exec('actionCategoryUpdate');
  1063. return $result;
  1064. }
  1065. /**
  1066. * cleanPositions keep order of category in $id_category_parent,
  1067. * but remove duplicate position. Should not be used if positions
  1068. * are clean at the beginning !
  1069. *
  1070. * @param mixed $id_category_parent
  1071. * @return boolean true if succeed
  1072. */
  1073. public static function cleanPositions($id_category_parent = null)
  1074. {
  1075. if ($id_category_parent === null)
  1076. return;
  1077. $return = true;
  1078. $result = Db::getInstance()->executeS('
  1079. SELECT c.`id_category`
  1080. FROM `'._DB_PREFIX_.'category` c
  1081. '.Shop::addSqlAssociation('category', 'c').'
  1082. WHERE c.`id_parent` = '.(int)$id_category_parent.'
  1083. ORDER BY category_shop.`position`');
  1084. $count = count($result);
  1085. for ($i = 0; $i < $count; $i++)
  1086. {
  1087. $return &= Db::getInstance()->execute('
  1088. UPDATE `'._DB_PREFIX_.'category` c '.Shop::addSqlAssociation('category', 'c').'
  1089. SET category_shop.`position` = '.(int)($i + 1).'
  1090. WHERE c.`id_parent` = '.(int)$id_category_parent.' AND c.`id_category` = '.(int)$result[$i]['id_category']);
  1091. }
  1092. return $return;
  1093. }
  1094. /** this function return the number of category + 1 having $id_category_parent as parent.
  1095. *
  1096. * @todo rename that function to make it understandable (getNewLastPosition for example)
  1097. * @param int $id_category_parent the parent category
  1098. * @param int $id_shop
  1099. * @return int
  1100. */
  1101. public static function getLastPosition($id_category_parent, $id_shop)
  1102. {
  1103. return (1 + (int)Db::getInstance()->getValue('
  1104. SELECT MAX(cs.`position`)
  1105. FROM `'._DB_PREFIX_.'category` c
  1106. LEFT JOIN `'._DB_PREFIX_.'category_shop` cs ON (c.`id_category` = cs.`id_category` AND cs.`id_shop` = '.(int)$id_shop.')
  1107. WHERE c.`id_parent` = '.(int)$id_category_parent));
  1108. }
  1109. public static function getUrlRewriteInformations($id_category)
  1110. {
  1111. return Db::getInstance()->executeS('
  1112. SELECT l.`id_lang`, c.`link_rewrite`
  1113. FROM `'._DB_PREFIX_.'category_lang` AS c
  1114. LEFT JOIN `'._DB_PREFIX_.'lang` AS l ON c.`id_lang` = l.`id_lang`
  1115. WHERE c.`id_category` = '.(int)$id_category.'
  1116. AND l.`active` = 1'
  1117. );
  1118. }
  1119. /**
  1120. * Return nleft and nright fields for a given category
  1121. *
  1122. * @since 1.5.0
  1123. * @param int $id
  1124. * @return array
  1125. */
  1126. public static function getInterval($id)
  1127. {
  1128. $cache_id = 'Category::getInterval_'.(int)$id;
  1129. if (!Cache::isStored($cache_id))
  1130. {
  1131. $result = Db::getInstance()->getRow('
  1132. SELECT nleft, nright, level_depth
  1133. FROM '._DB_PREFIX_.'category
  1134. WHERE id_category = '.(int)$id);
  1135. Cache::store($cache_id, $result);
  1136. }
  1137. return Cache::retrieve($cache_id);
  1138. }
  1139. /**
  1140. * Check if current category is a child of shop root category
  1141. *
  1142. * @since 1.5.0
  1143. * @param Shop $shop
  1144. * @return bool
  1145. */
  1146. public function inShop(Shop $shop = null)
  1147. {
  1148. if (!$shop)
  1149. $shop = Context::getContext()->shop;
  1150. if (!$interval = Category::getInterval($shop->getCategory()))
  1151. return false;
  1152. return ($this->nleft >= $interval['nleft'] && $this->nright <= $interval['nright']);
  1153. }
  1154. public static function inShopStatic($id_category, Shop $shop = null)
  1155. {
  1156. if (!$shop || !is_object($shop))
  1157. $shop = Context::getContext()->shop;
  1158. if (!$interval = Category::getInterval($shop->getCategory()))
  1159. return false;
  1160. $row = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT nleft, nright FROM `'._DB_PREFIX_.'category` WHERE id_category = '.(int)$id_category);
  1161. return ($row['nleft'] >= $interval['nleft'] && $row['nright'] <= $interval['nright']);
  1162. }
  1163. public function getChildrenWs()
  1164. {
  1165. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  1166. SELECT c.`id_category` as id
  1167. FROM `'._DB_PREFIX_.'category` c
  1168. '.Shop::addSqlAssociation('category', 'c').'
  1169. WHERE c.`id_parent` = '.(int)$this->id.'
  1170. AND c.`active` = 1
  1171. ORDER BY category_shop.`position` ASC');
  1172. return $result;
  1173. }
  1174. public function getProductsWs()
  1175. {
  1176. $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  1177. SELECT cp.`id_product` as id
  1178. FROM `'._DB_PREFIX_.'category_product` cp
  1179. WHERE cp.`id_category` = '.(int)$this->id.'
  1180. ORDER BY `position` ASC');
  1181. return $result;
  1182. }
  1183. /**
  1184. * Search for another category with the same parent and the same position
  1185. *
  1186. * @return array first category found
  1187. */
  1188. public function getDuplicatePosition()
  1189. {
  1190. return Db::getInstance()->getValue('
  1191. SELECT c.`id_category`
  1192. FROM `'._DB_PREFIX_.'category` c
  1193. '.Shop::addSqlAssociation('category', 'c').'
  1194. WHERE c.`id_parent` = '.(int)$this->id_parent.'
  1195. AND category_shop.`position` = '.(int)$this->position.'
  1196. AND c.`id_category` != '.(int)$this->id);
  1197. }
  1198. public function getWsNbProductsRecursive()
  1199. {
  1200. $nb_product_recursive = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('
  1201. SELECT COUNT(distinct(id_product))
  1202. FROM `'._DB_PREFIX_.'category_product`
  1203. WHERE id_category IN (
  1204. SELECT c2.id_category
  1205. FROM `'._DB_PREFIX_.'category` c2
  1206. '.Shop::addSqlAssociation('category', 'c2').'
  1207. WHERE c2.nleft > '.(int)$this->nleft.'
  1208. AND c2.nright < '.(int)$this->nright.'
  1209. AND c2.active = 1
  1210. UNION SELECT '.(int)$this->id.'
  1211. )
  1212. ');
  1213. if (!$nb_product_recursive)
  1214. return -1;
  1215. return $nb_product_recursive;
  1216. }
  1217. /**
  1218. *
  1219. * @param Array $ids_category
  1220. * @param int $id_lang
  1221. * @return Array
  1222. */
  1223. public static function getCategoryInformations($ids_category, $id_lang = null)
  1224. {
  1225. if ($id_lang === null)
  1226. $id_lang = Context::getContext()->language->id;
  1227. if (!is_array($ids_category) || !count($ids_category))
  1228. return;
  1229. $categories = array();
  1230. $results = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS('
  1231. SELECT c.`id_category`, cl.`name`, cl.`link_rewrite`, cl.`id_lang`
  1232. FROM `'._DB_PREFIX_.'category` c
  1233. LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (c.`id_category` = cl.`id_category`'.Shop::addSqlRestrictionOnLang('cl').')
  1234. '.Shop::addSqlAssociation('category', 'c').'
  1235. WHERE cl.`id_lang` = '.(int)$id_lang.'
  1236. AND c.`id_category` IN ('.implode(',', array_map('intval', $ids_category)).')');
  1237. foreach ($results as $category)
  1238. $categories[$category['id_category']] = $category;
  1239. return $categories;
  1240. }
  1241. /**
  1242. * @param $id_shop
  1243. * @return bool
  1244. */
  1245. public function isParentCategoryAvailable($id_shop)
  1246. {
  1247. $id = Context::getContext()->shop->id;
  1248. $id_shop = $id ? $id : Configuration::get('PS_SHOP_DEFAULT');
  1249. return (bool)Db::getInstance()->getValue('
  1250. SELECT c.`id_category`
  1251. FROM `'._DB_PREFIX_.'category` c
  1252. '.Shop::addSqlAssociation('category', 'c').'
  1253. WHERE category_shop.`id_shop` = '.(int)$id_shop.'
  1254. AND c.`id_parent` = '.(int)$this->id_parent);
  1255. }
  1256. /**
  1257. * Add association between shop and catego…

Large files files are truncated, but you can click here to view the full file