* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0) */ class CMSCategoryCore extends ObjectModel { public $id; /** @var int CMSCategory ID */ public $id_cms_category; /** @var string|array Name */ public $name; /** @var bool Status for display */ public $active = true; /** @var string|array Description */ public $description; /** @var int Parent CMSCategory ID */ public $id_parent; /** @var int category position */ public $position; /** @var int Parents number */ public $level_depth; /** @var string|array string used in rewrited URL */ public $link_rewrite; /** @var string|array Meta title */ public $meta_title; /** @var string|array Meta keywords */ public $meta_keywords; /** @var string|array Meta description */ public $meta_description; /** @var string Object creation date */ public $date_add; /** @var string Object last modification date */ public $date_upd; protected static $_links = []; /** * @see ObjectModel::$definition */ public static $definition = [ 'table' => 'cms_category', 'primary' => 'id_cms_category', 'multilang' => true, 'multilang_shop' => true, 'fields' => [ 'active' => ['type' => self::TYPE_BOOL, 'validate' => 'isBool', 'required' => true], 'id_parent' => ['type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true], 'position' => ['type' => self::TYPE_INT], 'level_depth' => ['type' => self::TYPE_INT], 'date_add' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], 'date_upd' => ['type' => self::TYPE_DATE, 'validate' => 'isDate'], /* Lang fields */ 'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCatalogName', 'required' => true, 'size' => 64], 'link_rewrite' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isLinkRewrite', 'required' => true, 'size' => 64], 'description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isCleanHtml', 'size' => 4194303], 'meta_title' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255], 'meta_description' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 512], 'meta_keywords' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'size' => 255], ], ]; public function add($autodate = true, $null_values = false) { $this->position = CMSCategory::getLastPosition((int) $this->id_parent); $this->level_depth = $this->calcLevelDepth(); foreach ($this->name as $k => $value) { if (preg_match('/^[1-9]\./', $value)) { $this->name[$k] = '0' . $value; } } $ret = parent::add($autodate, $null_values); $this->cleanPositions($this->id_parent); return $ret; } public function update($null_values = false) { $this->level_depth = $this->calcLevelDepth(); foreach ($this->name as $k => $value) { if (preg_match('/^[1-9]\./', $value)) { $this->name[$k] = '0' . $value; } } return parent::update($null_values); } /** * Recursive scan of subcategories. * * @param int $max_depth Maximum depth of the tree (i.e. 2 => 3 levels depth) * @param int $currentDepth specify the current depth in the tree (don't use it, only for rucursivity!) * @param int|null $id_lang Specify the id of the language used * @param array|null $excluded_ids_array specify a list of ids to exclude of results * @param Link|null $link * * @return array Subcategories lite tree */ public function recurseLiteCategTree($max_depth = 3, $currentDepth = 0, $id_lang = null, $excluded_ids_array = null, Link $link = null) { if (!$link) { $link = Context::getContext()->link; } if (null === $id_lang) { $id_lang = Context::getContext()->language->id; } // recursivity for subcategories $children = []; $subcats = $this->getSubCategories($id_lang, true); if (($max_depth == 0 || $currentDepth < $max_depth) && count($subcats)) { foreach ($subcats as $subcat) { if (!$subcat['id_cms_category']) { break; } elseif (!is_array($excluded_ids_array) || !in_array($subcat['id_cms_category'], $excluded_ids_array)) { $categ = new CMSCategory($subcat['id_cms_category'], $id_lang); $categ->name = CMSCategory::hideCMSCategoryPosition($categ->name); $children[] = $categ->recurseLiteCategTree($max_depth, $currentDepth + 1, $id_lang, $excluded_ids_array); } } } return [ 'id' => $this->id_cms_category, 'link' => $link->getCMSCategoryLink($this->id, $this->link_rewrite), 'name' => $this->name, 'desc' => $this->description, 'children' => $children, ]; } public static function getRecurseCategory($id_lang = null, $current = 1, $active = 1, $links = 0, Link $link = null) { if (!$link) { $link = Context::getContext()->link; } if (null === $id_lang) { $id_lang = Context::getContext()->language->id; } $sql = 'SELECT c.`id_cms_category`, c.`id_parent`, c.`level_depth`, cl.`name`, cl.`link_rewrite` FROM `' . _DB_PREFIX_ . 'cms_category` c JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON c.`id_cms_category` = cl.`id_cms_category` WHERE c.`id_cms_category` = ' . (int) $current . ' AND `id_lang` = ' . (int) $id_lang; /** @var array $category */ $category = Db::getInstance()->getRow($sql); $sql = 'SELECT c.`id_cms_category` FROM `' . _DB_PREFIX_ . 'cms_category` c ' . Shop::addSqlAssociation('cms_category', 'c') . ' WHERE c.`id_parent` = ' . (int) $current . ($active ? ' AND c.`active` = 1' : '') . ' ORDER BY c.`position`'; $result = Db::getInstance()->executeS($sql); foreach ($result as $row) { $category['children'][] = CMSCategory::getRecurseCategory($id_lang, $row['id_cms_category'], $active, $links); } $sql = 'SELECT c.`id_cms`, cl.`meta_title`, cl.`link_rewrite` FROM `' . _DB_PREFIX_ . 'cms` c ' . Shop::addSqlAssociation('cms', 'c') . ' JOIN `' . _DB_PREFIX_ . 'cms_lang` cl ON c.`id_cms` = cl.`id_cms` WHERE `id_cms_category` = ' . (int) $current . ($active ? ' AND c.`active` = 1' : '') . ' AND cl.`id_shop` = ' . (int) Context::getContext()->shop->id . ' AND cl.`id_lang` = ' . (int) $id_lang . ' GROUP BY c.id_cms ORDER BY c.`position`'; $category['cms'] = Db::getInstance()->executeS($sql); if ($links == 1) { $category['link'] = $link->getCMSCategoryLink($current, $category['link_rewrite']); foreach ($category['cms'] as $key => $cms) { $category['cms'][$key]['link'] = $link->getCMSLink($cms['id_cms'], $cms['link_rewrite']); } } return $category; } public static function recurseCMSCategory($categories, $current, $id_cms_category = 1, $id_selected = 1, $is_html = 0) { $html = ''; if ($is_html == 0) { echo $html; } if (isset($categories[$id_cms_category])) { foreach (array_keys($categories[$id_cms_category]) as $key) { $html .= CMSCategory::recurseCMSCategory($categories, $categories[$id_cms_category][$key], $key, $id_selected, $is_html); } } return $html; } /** * Recursively add specified CMSCategory childs to $toDelete array. * * @param array $to_delete Array reference where categories ID will be saved * @param array|int $id_cms_category Parent CMSCategory ID */ protected function recursiveDelete(&$to_delete, $id_cms_category) { if (!is_array($to_delete)) { die(Tools::displayError('Parameter "to_delete" is invalid.')); } if (!$id_cms_category) { die(Tools::displayError('Parameter "id_cms_category" is invalid.')); } $result = Db::getInstance()->executeS(' SELECT `id_cms_category` FROM `' . _DB_PREFIX_ . 'cms_category` WHERE `id_parent` = ' . (int) $id_cms_category); foreach ($result as $row) { $to_delete[] = (int) $row['id_cms_category']; $this->recursiveDelete($to_delete, (int) $row['id_cms_category']); } } /** * Directly call the parent of delete, in order to avoid recursion. * * @return bool Deletion result */ private function deleteLite() { return parent::delete(); } public function delete() { if ((int) $this->id === 1) { return false; } $this->clearCache(); /** @var array $cmsCategories */ $cmsCategories = $this->getAllChildren(); $cmsCategories[] = $this; foreach ($cmsCategories as $cmsCategory) { $cmsCategory->deleteCMS(); $cmsCategory->deleteLite(); CMSCategory::cleanPositions($cmsCategory->id_parent); } return true; } /** * Delete pages which are in CMSCategories to delete. * * @return bool Deletion result */ private function deleteCMS() { $result = true; $cms = new PrestaShopCollection('CMS'); $cms->where('id_cms_category', '=', $this->id); foreach ($cms as $c) { $result &= $c->delete(); } return $result; } /** * Delete several categories from database. * * return boolean Deletion result */ public function deleteSelection($categories) { $return = true; foreach ($categories as $id_category_cms) { $category_cms = new CMSCategory($id_category_cms); $return = $return && $category_cms->delete(); } return $return; } /** * Get the number of parent categories. * * @return int Level depth */ public function calcLevelDepth() { $parentCMSCategory = new CMSCategory($this->id_parent); return $parentCMSCategory->level_depth + 1; } /** * Return available categories. * * @param int $id_lang Language ID * @param bool $active return only active categories * * @return array Categories */ public static function getCategories($id_lang, $active = true, $order = true) { if (!Validate::isBool($active)) { die(Tools::displayError('Parameter "active" is invalid.')); } $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT * FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON c.`id_cms_category` = cl.`id_cms_category` WHERE `id_lang` = ' . (int) $id_lang . ' ' . ($active ? 'AND `active` = 1' : '') . ' ORDER BY `name` ASC'); if (!$order) { return $result; } $categories = []; foreach ($result as $row) { $categories[$row['id_parent']][$row['id_cms_category']]['infos'] = $row; } return $categories; } public static function getSimpleCategories($id_lang) { return Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT c.`id_cms_category`, cl.`name` FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON (c.`id_cms_category` = cl.`id_cms_category`) WHERE cl.`id_lang` = ' . (int) $id_lang . ' ORDER BY cl.`name`'); } /** * Return current CMSCategory childs. * * @param int $id_lang Language ID * @param bool $active return only active categories * * @return array Categories */ public function getSubCategories($id_lang, $active = true) { if (!Validate::isBool($active)) { die(Tools::displayError('Parameter "active" is invalid.')); } $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT c.*, cl.id_lang, cl.name, cl.description, cl.link_rewrite, cl.meta_title, cl.meta_keywords, cl.meta_description FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON (c.`id_cms_category` = cl.`id_cms_category` AND `id_lang` = ' . (int) $id_lang . ') WHERE `id_parent` = ' . (int) $this->id . ' ' . ($active ? 'AND `active` = 1' : '') . ' GROUP BY c.`id_cms_category` ORDER BY `position` ASC'); // Modify SQL result foreach ($result as &$row) { $row['name'] = CMSCategory::hideCMSCategoryPosition($row['name']); } return $result; } /** * Hide CMSCategory prefix used for position. * * @param string $name CMSCategory name * * @return string Name without position */ public static function hideCMSCategoryPosition($name) { return preg_replace('/^[0-9]+\./', '', $name); } /** * Return main categories. * * @param int $id_lang Language ID * @param bool $active return only active categories * * @return array categories */ public static function getHomeCategories($id_lang, $active = true) { return CMSCategory::getChildren(1, $id_lang, $active); } public static function getChildren($id_parent, $id_lang, $active = true) { if (!Validate::isBool($active)) { die(Tools::displayError('Parameter "active" is invalid.')); } $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT c.`id_cms_category`, cl.`name`, cl.`link_rewrite` FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON c.`id_cms_category` = cl.`id_cms_category` WHERE `id_lang` = ' . (int) $id_lang . ' AND c.`id_parent` = ' . (int) $id_parent . ' ' . ($active ? 'AND `active` = 1' : '') . ' ORDER BY `name` ASC'); // Modify SQL result $results_array = []; foreach ($result as $row) { $row['name'] = CMSCategory::hideCMSCategoryPosition($row['name']); $results_array[] = $row; } return $results_array; } /** * Return an array of all children of the current CMSCategory. * * @return PrestaShopCollection|array Collection of CMSCategory */ private function getAllChildren() { // Get children $toDelete = [(int) $this->id]; $this->recursiveDelete($toDelete, (int) $this->id); $toDelete = array_unique($toDelete); // remove id of current CMSCategory because we want only ids of children unset($toDelete[0]); if (count($toDelete)) { $children = new PrestaShopCollection('CMSCategory'); $children->where('id_cms_category', 'in', $toDelete); return $children; } return $toDelete; } /** * Check if CMSCategory can be moved in another one. * * @param int $id_parent Parent candidate * * @return bool Parent validity */ public static function checkBeforeMove($id_cms_category, $id_parent) { if ($id_cms_category == $id_parent) { return false; } if ($id_parent == 1) { return true; } $i = (int) $id_parent; while (42) { $result = Db::getInstance()->getRow('SELECT `id_parent` FROM `' . _DB_PREFIX_ . 'cms_category` WHERE `id_cms_category` = ' . (int) $i); if (!isset($result['id_parent'])) { return false; } if ($result['id_parent'] == $id_cms_category) { return false; } if ($result['id_parent'] == 1) { return true; } $i = $result['id_parent']; } } public static function getLinkRewrite($id_cms_category, $id_lang) { if (!Validate::isUnsignedId($id_cms_category) || !Validate::isUnsignedId($id_lang)) { return false; } if (isset(self::$_links[$id_cms_category . '-' . $id_lang])) { return self::$_links[$id_cms_category . '-' . $id_lang]; } $result = Db::getInstance()->getRow(' SELECT cl.`link_rewrite` FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON c.`id_cms_category` = cl.`id_cms_category` WHERE `id_lang` = ' . (int) $id_lang . ' AND c.`id_cms_category` = ' . (int) $id_cms_category); self::$_links[$id_cms_category . '-' . $id_lang] = $result['link_rewrite']; return $result['link_rewrite']; } public function getLink(Link $link = null) { if (!$link) { $link = Context::getContext()->link; } return $link->getCMSCategoryLink($this->id, $this->link_rewrite); } public function getName($id_lang = null) { $context = Context::getContext(); if (!$id_lang) { if (isset($this->name[$context->language->id])) { $id_lang = $context->language->id; } else { $id_lang = (int) Configuration::get('PS_LANG_DEFAULT'); } } return isset($this->name[$id_lang]) ? $this->name[$id_lang] : ''; } /** * Light back office search for categories. * * @param int $id_lang Language ID * @param string $query Searched string * @param bool $unrestricted allows search without lang and includes first CMSCategory and exact match * * @return array Corresponding categories */ public static function searchByName($id_lang, $query, $unrestricted = false) { if ($unrestricted === true) { return Db::getInstance()->getRow(' SELECT c.*, cl.* FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON (c.`id_cms_category` = cl.`id_cms_category`) WHERE `name` = \'' . pSQL($query) . '\''); } else { return Db::getInstance()->executeS(' SELECT c.*, cl.* FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON (c.`id_cms_category` = cl.`id_cms_category` AND `id_lang` = ' . (int) $id_lang . ') WHERE `name` LIKE \'%' . pSQL($query) . '%\' AND c.`id_cms_category` != 1'); } } /** * Get Each parent CMSCategory of this CMSCategory until the root CMSCategory. * * @param int $id_lang Language ID * * @return array Corresponding categories */ public function getParentsCategories($id_lang = null) { if (null === $id_lang) { $id_lang = Context::getContext()->language->id; } $categories = null; $id_current = $this->id; while (true) { $query = ' SELECT c.*, cl.* FROM `' . _DB_PREFIX_ . 'cms_category` c LEFT JOIN `' . _DB_PREFIX_ . 'cms_category_lang` cl ON (c.`id_cms_category` = cl.`id_cms_category` AND `id_lang` = ' . (int) $id_lang . ') WHERE c.`id_cms_category` = ' . (int) $id_current . ' AND c.`id_parent` != 0 '; $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS($query); $categories[] = $result[0]; if (!$result || $result[0]['id_parent'] == 1) { return $categories; } $id_current = $result[0]['id_parent']; } } public function updatePosition($way, $position) { if (!$res = Db::getInstance()->executeS( ' SELECT cp.`id_cms_category`, cp.`position`, cp.`id_parent` FROM `' . _DB_PREFIX_ . 'cms_category` cp WHERE cp.`id_parent` = ' . (int) $this->id_parent . ' ORDER BY cp.`position` ASC' )) { return false; } foreach ($res as $category) { if ((int) $category['id_cms_category'] == (int) $this->id) { $moved_category = $category; } } if (!isset($moved_category) || !isset($position)) { return false; } // < and > statements rather than BETWEEN operator // since BETWEEN is treated differently according to databases return Db::getInstance()->execute(' UPDATE `' . _DB_PREFIX_ . 'cms_category` SET `position`= `position` ' . ($way ? '- 1' : '+ 1') . ' WHERE `position` ' . ($way ? '> ' . (int) $moved_category['position'] . ' AND `position` <= ' . (int) $position : '< ' . (int) $moved_category['position'] . ' AND `position` >= ' . (int) $position) . ' AND `id_parent`=' . (int) $moved_category['id_parent']) && Db::getInstance()->execute(' UPDATE `' . _DB_PREFIX_ . 'cms_category` SET `position` = ' . (int) $position . ' WHERE `id_parent` = ' . (int) $moved_category['id_parent'] . ' AND `id_cms_category`=' . (int) $moved_category['id_cms_category']); } public static function cleanPositions($id_category_parent) { $result = Db::getInstance()->executeS(' SELECT `id_cms_category` FROM `' . _DB_PREFIX_ . 'cms_category` WHERE `id_parent` = ' . (int) $id_category_parent . ' ORDER BY `position`'); $sizeof = count($result); for ($i = 0; $i < $sizeof; ++$i) { $sql = ' UPDATE `' . _DB_PREFIX_ . 'cms_category` SET `position` = ' . (int) $i . ' WHERE `id_parent` = ' . (int) $id_category_parent . ' AND `id_cms_category` = ' . (int) $result[$i]['id_cms_category']; Db::getInstance()->execute($sql); } return true; } public static function getLastPosition($id_category_parent) { return Db::getInstance()->getValue('SELECT MAX(position)+1 FROM `' . _DB_PREFIX_ . 'cms_category` WHERE `id_parent` = ' . (int) $id_category_parent); } public static function getUrlRewriteInformations($id_category) { $sql = ' SELECT l.`id_lang`, c.`link_rewrite` FROM `' . _DB_PREFIX_ . 'cms_category_lang` AS c LEFT JOIN `' . _DB_PREFIX_ . 'lang` AS l ON c.`id_lang` = l.`id_lang` WHERE c.`id_cms_category` = ' . (int) $id_category . ' AND l.`active` = 1'; $arr_return = Db::getInstance()->executeS($sql); return $arr_return; } }