* @copyright 2007-2011 PrestaShop SA * @version Release: $Revision: 9298 $ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) * International Registred Trademark & Property of PrestaShop SA */ if (!defined('_PS_VERSION_')) exit; class BlockLayered extends Module { private $products; private $nbr_products; public function __construct() { $this->name = 'blocklayered'; $this->tab = 'front_office_features'; $this->version = 1.4; $this->author = 'PrestaShop'; $this->need_instance = 0; parent::__construct(); $this->displayName = $this->l('Layered navigation block'); $this->description = $this->l('Displays a block with layered navigation filters.'); } public function install() { if (parent::install() && $this->registerHook('leftColumn') && $this->registerHook('header') && $this->registerHook('footer') && $this->registerHook('categoryAddition') && $this->registerHook('categoryUpdate') && $this->registerHook('attributeGroupForm') && $this->registerHook('afterSaveAttributeGroup') && $this->registerHook('afterDeleteAttributeGroup') && $this->registerHook('featureForm') && $this->registerHook('afterDeleteFeature') && $this->registerHook('afterSaveFeature') && $this->registerHook('categoryDeletion') && $this->registerHook('afterSaveProduct') && $this->registerHook('productListAssign') && $this->registerHook('postProcessAttributeGroup') && $this->registerHook('postProcessFeature') && $this->registerHook('featureValueForm') && $this->registerHook('postProcessFeatureValue') && $this->registerHook('afterDeleteFeatureValue') && $this->registerHook('afterSaveFeatureValue') && $this->registerHook('attributeForm') && $this->registerHook('postProcessAttribute') && $this->registerHook('afterDeleteAttribute') && $this->registerHook('afterSaveAttribute')) { Configuration::updateValue('PS_LAYERED_HIDE_0_VALUES', 0); Configuration::updateValue('PS_LAYERED_SHOW_QTIES', 1); $this->rebuildLayeredStructure(); $this->rebuildLayeredCache(); self::installPriceIndexTable(); $this->installFriendlyUrlTable(); $this->installIndexableAttributeTable(); $this->installProductAttributeTable(); $this->indexUrl(); $this->indexAttribute(); if (Db::getInstance()->getValue('SELECT count(*) FROM `'._DB_PREFIX_.'product`') < 10000) // Lock price indexation if too many products self::fullPricesIndexProcess(); return true; } else { // Installation failed (or hook registration) => uninstall the module $this->uninstall(); return false; } } public function uninstall() { /* Delete all configurations */ Configuration::deleteByName('PS_LAYERED_HIDE_0_VALUES'); Configuration::deleteByName('PS_LAYERED_SHOW_QTIES'); Configuration::deleteByName('PS_LAYERED_INDEXED'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_price_index'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_friendly_url'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_indexable_attribute_group'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_indexable_feature'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_indexable_feature_lang_value'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_category'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_filter'); Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_product_attribute'); return parent::uninstall(); } private static function installPriceIndexTable() { Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_price_index`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_price_index` ( `id_product` INT NOT NULL, `id_currency` INT NOT NULL, `price_min` INT NOT NULL, `price_max` INT NOT NULL, PRIMARY KEY (`id_product`, `id_currency`), INDEX `id_currency` (`id_currency`), INDEX `price_min` (`price_min`), INDEX `price_max` (`price_max`)) ENGINE = '._MYSQL_ENGINE_); } private function installFriendlyUrlTable() { Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_friendly_url`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_friendly_url` ( `id_layered_friendly_url` INT NOT NULL AUTO_INCREMENT, `url_key` varchar(32) NOT NULL, `data` varchar(200) NOT NULL, `id_lang` INT NOT NULL, PRIMARY KEY (`id_layered_friendly_url`), INDEX `id_lang` (`id_lang`)) ENGINE = '._MYSQL_ENGINE_); Db::getInstance()->Execute('CREATE INDEX `url_key` ON `'._DB_PREFIX_.'layered_friendly_url`(url_key(5))'); } private function installIndexableAttributeTable() { // Attributes Groups Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_indexable_attribute_group`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_indexable_attribute_group` ( `id_attribute_group` INT NOT NULL, `indexable` BOOL NOT NULL DEFAULT 0, PRIMARY KEY (`id_attribute_group`)) ENGINE = '._MYSQL_ENGINE_); Db::getInstance()->Execute(' INSERT INTO `'._DB_PREFIX_.'layered_indexable_attribute_group` SELECT id_attribute_group, 1 FROM `'._DB_PREFIX_.'attribute_group`'); Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_indexable_attribute_group_lang_value`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_indexable_attribute_group_lang_value` ( `id_attribute_group` INT NOT NULL, `id_lang` INT NOT NULL, `url_name` VARCHAR(20), `meta_title` VARCHAR(20), PRIMARY KEY (`id_attribute_group`, `id_lang`)) ENGINE = '._MYSQL_ENGINE_); // Attributes Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_indexable_attribute_lang_value`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_indexable_attribute_lang_value` ( `id_attribute` INT NOT NULL, `id_lang` INT NOT NULL, `url_name` VARCHAR(20), `meta_title` VARCHAR(20), PRIMARY KEY (`id_attribute`, `id_lang`)) ENGINE = '._MYSQL_ENGINE_); // Features Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_indexable_feature`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_indexable_feature` ( `id_feature` INT NOT NULL, `indexable` BOOL NOT NULL DEFAULT 0, PRIMARY KEY (`id_feature`)) ENGINE = '._MYSQL_ENGINE_); Db::getInstance()->Execute(' INSERT INTO `'._DB_PREFIX_.'layered_indexable_feature` SELECT id_feature, 1 FROM `'._DB_PREFIX_.'feature`'); Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_indexable_feature_lang_value`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_indexable_feature_lang_value` ( `id_feature` INT NOT NULL, `id_lang` INT NOT NULL, `url_name` VARCHAR(20) NOT NULL, `meta_title` VARCHAR(20), PRIMARY KEY (`id_feature`, `id_lang`)) ENGINE = '._MYSQL_ENGINE_); // Features values Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_indexable_feature_value_lang_value`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_indexable_feature_value_lang_value` ( `id_feature_value` INT NOT NULL, `id_lang` INT NOT NULL, `url_name` VARCHAR(20), `meta_title` VARCHAR(20), PRIMARY KEY (`id_feature_value`, `id_lang`)) ENGINE = '._MYSQL_ENGINE_); } /** * * create table product attribute */ public function installProductAttributeTable() { Db::getInstance()->Execute('DROP TABLE IF EXISTS `'._DB_PREFIX_.'layered_product_attribute`'); Db::getInstance()->Execute(' CREATE TABLE `'._DB_PREFIX_.'layered_product_attribute` ( `id_attribute` int(10) unsigned NOT NULL, `id_product` int(10) unsigned NOT NULL, `id_attribute_group` int(10) unsigned NOT NULL DEFAULT "0", KEY `id_attribute` (`id_attribute`) ) ENGINE= '._MYSQL_ENGINE_); } /** * * Generate data product attribute */ public function indexAttribute($id_product = null) { if (is_null($id_product)) Db::getInstance()->execute('TRUNCATE '._DB_PREFIX_.'layered_product_attribute'); else Db::getInstance()->execute('DELETE FROM '._DB_PREFIX_.'layered_product_attribute WHERE id_product = '.(int)$id_product); Db::getInstance()->Execute('INSERT INTO `'._DB_PREFIX_.'layered_product_attribute` (`id_attribute`, `id_product`, `id_attribute_group`) SELECT pac.id_attribute, pa.id_product, ag.id_attribute_group FROM '._DB_PREFIX_.'product_attribute pa INNER JOIN '._DB_PREFIX_.'product_attribute_combination pac ON pac.id_product_attribute = pa.id_product_attribute INNER JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute = pac.id_attribute) INNER JOIN '._DB_PREFIX_.'attribute_group ag ON ag.id_attribute_group = a.id_attribute_group '.(is_null($id_product) ? '' : 'AND pa.id_product = '.(int)$id_product).' GROUP BY a.id_attribute, pa.id_product'); return 1; } /* * Url indexation */ public function indexUrl($ajax = false, $truncate = true) { if ($truncate) Db::getInstance()->execute('TRUNCATE '._DB_PREFIX_.'layered_friendly_url'); $attributeValuesByLang = array(); $filters = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT lc.*, id_lang, name, link_rewrite, cl.id_category FROM '._DB_PREFIX_.'layered_category lc INNER JOIN '._DB_PREFIX_.'category_lang cl ON (cl.id_category = lc.id_category AND lc.id_category <> 1 ) GROUP BY type, id_value, id_lang'); if (!$filters) return; foreach ($filters as $filter) switch ($filter['type']) { case 'id_attribute_group': $attributes = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT agl.public_name name, a.id_attribute_group id_name, al.name value, a.id_attribute id_value, al.id_lang, liagl.url_name name_url_name, lial.url_name value_url_name FROM '._DB_PREFIX_.'attribute_group ag INNER JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (agl.id_attribute_group = ag.id_attribute_group) INNER JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute_group = ag.id_attribute_group) INNER JOIN '._DB_PREFIX_.'attribute_lang al ON (al.id_attribute = a.id_attribute) LEFT JOIN '._DB_PREFIX_.'layered_indexable_attribute_group liag ON (liag.id_attribute_group = a.id_attribute_group) LEFT JOIN '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value liagl ON (liagl.id_attribute_group = ag.id_attribute_group AND liagl.id_lang = '.(int)$filter['id_lang'].') LEFT JOIN '._DB_PREFIX_.'layered_indexable_attribute_lang_value lial ON (lial.id_attribute = a.id_attribute AND lial.id_lang = '.(int)$filter['id_lang'].') WHERE a.id_attribute_group = '.(int)$filter['id_value'].' AND agl.id_lang = al.id_lang AND agl.id_lang = '.(int)$filter['id_lang']); foreach ($attributes as $attribute) { if (!isset($attributeValuesByLang[$attribute['id_lang']])) $attributeValuesByLang[$attribute['id_lang']] = array(); if (!isset($attributeValuesByLang[$attribute['id_lang']]['c'.$attribute['id_name']])) $attributeValuesByLang[$attribute['id_lang']]['c'.$attribute['id_name']] = array(); $attributeValuesByLang[$attribute['id_lang']]['c'.$attribute['id_name']][] = array( 'name' => (!empty($attribute['name_url_name']) ? $attribute['name_url_name'] : $attribute['name']), 'id_name' => 'c'.$attribute['id_name'], 'value' => (!empty($attribute['value_url_name']) ? $attribute['value_url_name'] : $attribute['value']), 'id_value' => $attribute['id_name'].'_'.$attribute['id_value'], 'id_id_value' => $attribute['id_value'], 'category_name' => $filter['link_rewrite'], 'type' => $filter['type']); } break; case 'id_feature': $features = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT fl.name name, fl.id_feature id_name, fvl.id_feature_value id_value, fvl.value value, fl.id_lang, fl.id_lang, lifl.url_name name_url_name, lifvl.url_name value_url_name FROM '._DB_PREFIX_.'feature_lang fl LEFT JOIN '._DB_PREFIX_.'layered_indexable_feature lif ON (lif.id_feature = fl.id_feature) INNER JOIN '._DB_PREFIX_.'feature_value fv ON (fv.id_feature = fl.id_feature) INNER JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = fv.id_feature_value) LEFT JOIN '._DB_PREFIX_.'layered_indexable_feature_lang_value lifl ON (lifl.id_feature = fl.id_feature AND lifl.id_lang = '.(int)$filter['id_lang'].') LEFT JOIN '._DB_PREFIX_.'layered_indexable_feature_value_lang_value lifvl ON (lifvl.id_feature_value = fvl.id_feature_value AND lifvl.id_lang = '.(int)$filter['id_lang'].') WHERE fl.id_feature = '.(int)$filter['id_value'].' AND fvl.id_lang = fl.id_lang AND fvl.id_lang = '.(int)$filter['id_lang']); foreach ($features as $feature) { if (!isset($attributeValuesByLang[$feature['id_lang']])) $attributeValuesByLang[$feature['id_lang']] = array(); if (!isset($attributeValuesByLang[$feature['id_lang']]['f'.$feature['id_name']])) $attributeValuesByLang[$feature['id_lang']]['f'.$feature['id_name']] = array(); $attributeValuesByLang[$feature['id_lang']]['f'.$feature['id_name']][] = array( 'name' => (!empty($feature['name_url_name']) ? $feature['name_url_name'] : $feature['name']), 'id_name' => 'f'.$feature['id_name'], 'value' => (!empty($feature['value_url_name']) ? $feature['value_url_name'] : $feature['value']), 'id_value' => $feature['id_name'].'_'.$feature['id_value'], 'id_id_value' => $feature['id_value'], 'category_name' => $filter['link_rewrite'], 'type' => $filter['type']); } break; case 'category': $categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT cl.name, cl.id_lang, c.id_category FROM '._DB_PREFIX_.'category c INNER JOIN '._DB_PREFIX_.'category_lang cl ON (c.id_category = cl.id_category) WHERE cl.id_lang = '.(int)$filter['id_lang']); foreach ($categories as $category) { if (!isset($attributeValuesByLang[$category['id_lang']])) $attributeValuesByLang[$category['id_lang']] = array(); if (!isset($attributeValuesByLang[$category['id_lang']]['category'])) $attributeValuesByLang[$category['id_lang']]['category'] = array(); $attributeValuesByLang[$category['id_lang']]['category'][] = array('name' => $this->l('Categories'), 'id_name' => null, 'value' => $category['name'], 'id_value' => $category['id_category'], 'category_name' => $filter['link_rewrite'], 'type' => $filter['type']); } break; case 'manufacturer': $manufacturers = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT m.name as name,l.id_lang as id_lang, id_manufacturer FROM '._DB_PREFIX_.'manufacturer m , '._DB_PREFIX_.'lang l WHERE l.id_lang = '.(int)$filter['id_lang'].' '); foreach ($manufacturers as $manufacturer) { if (!isset($attributeValuesByLang[$manufacturer['id_lang']])) $attributeValuesByLang[$manufacturer['id_lang']] = array(); if (!isset($attributeValuesByLang[$manufacturer['id_lang']]['manufacturer'])) $attributeValuesByLang[$manufacturer['id_lang']]['manufacturer'] = array(); $attributeValuesByLang[$manufacturer['id_lang']]['manufacturer'][] = array('name' => $this->translateWord('Manufacturer', $manufacturer['id_lang']), 'id_name' => null, 'value' => $manufacturer['name'], 'id_value' => $manufacturer['id_manufacturer'], 'category_name' => $filter['link_rewrite'], 'type' => $filter['type']); } break; case 'quantity': $avaibility_list = array( $this->translateWord('Not available', (int)$filter['id_lang']), $this->translateWord('In stock', (int)$filter['id_lang']) ); foreach ($avaibility_list as $key => $quantity) $attributeValuesByLang[$filter['id_lang']]['quantity'][] = array('name' => $this->translateWord('Availability', (int)$filter['id_lang']), 'id_name' => null, 'value' => $quantity, 'id_value' => $key, 'id_id_value' => 0, 'category_name' => $filter['link_rewrite'], 'type' => $filter['type']); break; case 'condition': $condition_list = array( 'new' => $this->translateWord('New', (int)$filter['id_lang']), 'used' => $this->translateWord('Used', (int)$filter['id_lang']), 'refurbished' => $this->translateWord('Refurbished', (int)$filter['id_lang']) ); foreach ($condition_list as $key => $condition) $attributeValuesByLang[$filter['id_lang']]['condition'][] = array('name' => $this->translateWord('Condition', (int)$filter['id_lang']), 'id_name' => null, 'value' => $condition, 'id_value' => $key, 'category_name' => $filter['link_rewrite'], 'type' => $filter['type']); break; } // Foreach langs foreach ($attributeValuesByLang as $id_lang => $attributeValues) { // Foreach attributes generate a couple "/_". For example: color_blue foreach ($attributeValues as $attribute) foreach ($attribute as $param) { $selectedFilters = array(); $link = '/'.str_replace('-', '_', Tools::link_rewrite($param['name'])).'-'.str_replace('-', '_', Tools::link_rewrite($param['value'])); $selectedFilters[$param['type']] = array(); if (!isset($param['id_id_value'])) $param['id_id_value'] = $param['id_value']; $selectedFilters[$param['type']][$param['id_id_value']] = $param['id_value']; $urlKey = md5($link); $idLayeredFriendlyUrl = Db::getInstance()->getValue('SELECT id_layered_friendly_url FROM `'._DB_PREFIX_.'layered_friendly_url` WHERE `id_lang` = '.$id_lang.' AND `url_key` = \''.$urlKey.'\''); if ($idLayeredFriendlyUrl == false) { Db::getInstance()->AutoExecute(_DB_PREFIX_.'layered_friendly_url', array('url_key' => $urlKey, 'data' => serialize($selectedFilters), 'id_lang' => $id_lang), 'INSERT'); $idLayeredFriendlyUrl = Db::getInstance()->Insert_ID(); } } } if ($ajax) return '{"result": 1}'; else return 1; } public function translateWord($string, $id_lang ) { static $_MODULES = array(); global $_MODULE; $file = _PS_MODULE_DIR_.$this->name.'/'.Language::getIsoById($id_lang).'.php'; if (!array_key_exists($id_lang, $_MODULES)) { if (!file_exists($file)) return $string; include($file); $_MODULES[$id_lang] = $_MODULE; } $string = str_replace('\'', '\\\'', $string); // set array key to lowercase for 1.3 compatibility $_MODULES[$id_lang] = array_change_key_case($_MODULES[$id_lang]); $currentKey = '<{'.strtolower( $this->name).'}'.strtolower(_THEME_NAME_).'>'.strtolower($this->name).'_'.md5($string); $defaultKey = '<{'.strtolower( $this->name).'}prestashop>'.strtolower($this->name).'_'.md5($string); if (isset($_MODULES[$id_lang][$currentKey])) $ret = stripslashes($_MODULES[$id_lang][$currentKey]); else if (isset($_MODULES[$id_lang][Tools::strtolower($currentKey)])) $ret = stripslashes($_MODULES[$id_lang][Tools::strtolower($currentKey)]); else if (isset($_MODULES[$id_lang][$defaultKey])) $ret = stripslashes($_MODULES[$id_lang][$defaultKey]); else if (isset($_MODULES[$id_lang][Tools::strtolower($defaultKey)])) $ret = stripslashes($_MODULES[$id_lang][Tools::strtolower($defaultKey)]); else $ret = stripslashes($string); return str_replace('"', '"', $ret); } public function hookProductListAssign($params) { global $smarty; if (!Configuration::get('PS_LAYERED_INDEXED')) return; // Inform the hook was executed $params['hookExecuted'] = true; // List of product to overrride categoryController $params['catProducts'] = array(); $selectedFilters = $this->getSelectedFilters(); $filterBlock = self::getFilterBlock($selectedFilters); $title = ''; if (is_array($filterBlock['title_values'])) foreach ($filterBlock['title_values'] as $key => $val) $title .= ' – '.$key.' '.implode('/', $val); $smarty->assign('categoryNameComplement', $title); $this->getProducts($selectedFilters, $params['catProducts'], $params['nbProducts'], $p, $n, $pages_nb, $start, $stop, $range); // Need a nofollow on the pagination links? $smarty->assign('no_follow', $filterBlock['nofollow']); } public function hookAfterSaveProduct($params) { if (!$params['id_product']) return; self::indexProductPrices((int)$params['id_product']); $this->indexAttribute((int)$params['id_product']); } public function hookAfterSaveFeature($params) { if (!$params['id_feature'] || Tools::getValue('layered_indexable') === false) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_feature WHERE id_feature = '.(int)$params['id_feature']); Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'layered_indexable_feature VALUES ('.(int)$params['id_feature'].', '.(int)Tools::getValue('layered_indexable').')'); Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_feature_lang_value WHERE id_feature = '.(int)$params['id_feature']); // don't care about the id_lang foreach (Language::getLanguages(false) as $language) { // Data are validated by method "hookPostProcessFeature" $id_lang = (int)$language['id_lang']; Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'layered_indexable_feature_lang_value VALUES ('.(int)$params['id_feature'].', '.$id_lang.', \''.pSQL(Tools::link_rewrite(Tools::getValue('url_name_'.$id_lang))).'\', \''.pSQL(Tools::safeOutput(Tools::getValue('meta_title_'.$id_lang), true)).'\')'); } } public function hookAfterSaveFeatureValue($params) { if (!$params['id_feature_value']) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_feature_value_lang_value WHERE id_feature_value = '.(int)$params['id_feature_value']); // don't care about the id_lang foreach (Language::getLanguages(false) as $language) { // Data are validated by method "hookPostProcessFeatureValue" $id_lang = (int)$language['id_lang']; Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'layered_indexable_feature_value_lang_value VALUES ('.(int)$params['id_feature_value'].', '.$id_lang.', \''.pSQL(Tools::link_rewrite(Tools::getValue('url_name_'.$id_lang))).'\', \''.pSQL(Tools::safeOutput(Tools::getValue('meta_title_'.$id_lang), true)).'\')'); } } public function hookAfterDeleteFeatureValue($params) { if (!$params['id_feature_value']) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_feature_value_lang_value WHERE id_feature_value = '.(int)$params['id_feature_value']); } public function hookPostProcessFeatureValue($params) { $this->hookPostProcessAttributeGroup($params); } public function hookFeatureValueForm($params) { $languages = Language::getLanguages(false); $default_form_language = (int)(Configuration::get('PS_LANG_DEFAULT')); $langValue = array(); $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS( 'SELECT url_name, meta_title, id_lang FROM '._DB_PREFIX_.'layered_indexable_feature_value_lang_value WHERE id_feature_value = '.(int)$params['id_feature_value']); if ($result) foreach ($result as $data) $langValue[$data['id_lang']] = array('url_name' => $data['url_name'], 'meta_title' => $data['meta_title']); $return = '
'; foreach ($languages as $language) $return .= '
'.$this->l('Invalid characters:').' <>;=#{}_ 

'.$this->l('Specific format in url block layered generation').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'url_name', true, true); $return .= '
'; foreach ($languages as $language) $return .= '

'.$this->l('Specific format for meta title').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'meta_title', true, true); $return .= '
'; return $return; } public function hookAfterSaveAttribute($params) { if (!$params['id_attribute']) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_attribute_lang_value WHERE id_attribute = '.(int)$params['id_attribute']); // don't care about the id_lang foreach (Language::getLanguages(false) as $language) { // Data are validated by method "hookPostProcessAttribute" $id_lang = (int)$language['id_lang']; Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'layered_indexable_attribute_lang_value VALUES ('.(int)$params['id_attribute'].', '.$id_lang.', \''.pSQL(Tools::link_rewrite(Tools::getValue('url_name_'.$id_lang))).'\', \''.pSQL(Tools::safeOutput(Tools::getValue('meta_title_'.$id_lang), true)).'\')'); } } public function hookAfterDeleteAttribute($params) { if (!$params['id_attribute']) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_attribute_lang_value WHERE id_attribute = '.(int)$params['id_attribute']); } public function hookPostProcessAttribute($params) { $this->hookPostProcessAttributeGroup($params); } public function hookAttributeForm($params) { $languages = Language::getLanguages(false); $default_form_language = (int)(Configuration::get('PS_LANG_DEFAULT')); $langValue = array(); $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS( 'SELECT url_name, meta_title, id_lang FROM '._DB_PREFIX_.'layered_indexable_attribute_lang_value WHERE id_attribute = '.(int)$params['id_attribute']); if ($result) foreach ($result as $data) $langValue[$data['id_lang']] = array('url_name' => $data['url_name'], 'meta_title' => $data['meta_title']); $return = '
'; foreach ($languages as $language) $return .= '
'.$this->l('Invalid characters:').' <>;=#{}_ 

'.$this->l('Specific format in url block layered generation').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'url_name', true, true); $return .= '
'; foreach ($languages as $language) $return .= '

'.$this->l('Specific format for meta title').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'meta_title', true, true); $return .= '
'; return $return; } public function hookPostProcessFeature($params) { $this->hookPostProcessAttributeGroup($params); } public function hookAfterDeleteFeature($params) { if (!$params['id_feature']) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_feature WHERE id_feature = '.(int)$params['id_feature']); } public function hookAfterSaveAttributeGroup($params) { if (!$params['id_attribute_group'] || Tools::getValue('layered_indexable') === false) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_attribute_group WHERE id_attribute_group = '.(int)$params['id_attribute_group']); Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'layered_indexable_attribute_group VALUES ('.(int)$params['id_attribute_group'].', '.(int)Tools::getValue('layered_indexable').')'); Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value WHERE id_attribute_group = '.(int)$params['id_attribute_group']); // don't care about the id_lang foreach (Language::getLanguages(false) as $language) { // Data are validated by method "hookPostProcessAttributeGroup" $id_lang = (int)$language['id_lang']; Db::getInstance()->Execute('INSERT INTO '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value VALUES ('.(int)$params['id_attribute_group'].', '.$id_lang.', \''.pSQL(Tools::link_rewrite(Tools::getValue('url_name_'.$id_lang))).'\', \''.pSQL(Tools::safeOutput(Tools::getValue('meta_title_'.$id_lang), true)).'\')'); } } public function hookPostProcessAttributeGroup($params) { // Limit to one call static $once = false; if ($once) return; $once = true; $errors = array(); foreach (Language::getLanguages(false) as $language) { $id_lang = $language['id_lang']; if (Tools::getValue('url_name_'.$id_lang)) if (Tools::link_rewrite(Tools::getValue('url_name_'.$id_lang)) != strtolower( Tools::getValue('url_name_'.$id_lang))) { // Here use the reference "errors" to stop saving process $params['errors'][] = Tools::displayError(sprintf($this->l('"%s" is not a valid url'), Tools::getValue('url_name_'.$id_lang))); } } } public function hookAfterDeleteAttributeGroup($params) { if (!$params['id_attribute_group']) return; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_attribute_group WHERE id_attribute_group = '.(int)$params['id_attribute_group']); Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value WHERE id_attribute_group = '.(int)$params['id_attribute_group']); } public function hookAttributeGroupForm($params) { $languages = Language::getLanguages(false); $default_form_language = (int)(Configuration::get('PS_LANG_DEFAULT')); $indexable = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT indexable FROM '._DB_PREFIX_.'layered_indexable_attribute_group WHERE id_attribute_group = '.(int)$params['id_attribute_group']); $langValue = array(); $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS( 'SELECT url_name, meta_title, id_lang FROM '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value WHERE id_attribute_group = '.(int)$params['id_attribute_group']); if ($result) foreach ($result as $data) $langValue[$data['id_lang']] = array('url_name' => $data['url_name'], 'meta_title' => $data['meta_title']); if ($indexable === false) $on = true; else $on = (bool)$indexable; $return = '
'; foreach ($languages as $language) $return .= '
'.$this->l('Invalid characters:').' <>;=#{}_ 

'.$this->l('Specific format in url block layered generation').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'url_name', true, true); $return .= '
'; foreach ($languages as $language) $return .= '

'.$this->l('Specific format for meta title').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'meta_title', true, true); $return .= '

'.$this->l('Use this attribute in url generated by the module block layered navigation').'

'; return $return; } public function hookFeatureForm($params) { $languages = Language::getLanguages(false); $default_form_language = (int)(Configuration::get('PS_LANG_DEFAULT')); $indexable = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue('SELECT indexable FROM '._DB_PREFIX_.'layered_indexable_feature WHERE id_feature = '.(int)$params['id_feature']); $langValue = array(); $result = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS( 'SELECT url_name, meta_title, id_lang FROM '._DB_PREFIX_.'layered_indexable_feature_lang_value WHERE id_feature = '.(int)$params['id_feature']); if ($result) foreach ($result as $data) $langValue[$data['id_lang']] = array('url_name' => $data['url_name'], 'meta_title' => $data['meta_title']); if ($indexable === false) $on = true; else $on = (bool)$indexable; $return = '
'; foreach ($languages as $language) $return .= '
'.$this->l('Invalid characters:').' <>;=#{}_ 

'.$this->l('Specific format in url block layered generation').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'url_name', true, true); $return .= '
'; foreach ($languages as $language) $return .= '

'.$this->l('Specific format for meta title').'

'; $return .= $this->displayFlags($languages, $default_form_language, 'flag_fields', 'meta_title', true, true); $return .= '

'.$this->l('Use this attribute in url generated by the module block layered navigation').'

'; return $return; } /* * $cursor $cursor in order to restart indexing from the last state */ public static function fullPricesIndexProcess($cursor = 0, $ajax = false, $smart = false) { if ($cursor == 0 && !$smart) self::installPriceIndexTable(); return self::indexPrices($cursor, true, $ajax, $smart); } /* * $cursor $cursor in order to restart indexing from the last state */ public static function pricesIndexProcess($cursor = 0, $ajax = false) { return self::indexPrices($cursor, false, $ajax); } private static function indexPrices($cursor = null, $full = false, $ajax = false, $smart = false) { if ($full) $nbProducts = (int)Db::getInstance()->getValue('SELECT count(*) FROM '._DB_PREFIX_.'product WHERE `active` = 1'); else $nbProducts = (int)Db::getInstance()->getValue( 'SELECT COUNT(*) FROM `'._DB_PREFIX_.'product` p LEFT JOIN `'._DB_PREFIX_.'layered_price_index` psi ON (psi.id_product = p.id_product) WHERE `active` = 1 AND psi.id_product IS NULL'); $maxExecutionTime = @ini_get('max_execution_time'); if ($maxExecutionTime > 5 || $maxExecutionTime <= 0) $maxExecutionTime = 5; $startTime = microtime(true); do { $cursor = (int)self::indexPricesUnbreakable((int)$cursor, $full, $smart); $timeElapsed = microtime(true) - $startTime; } while ($cursor < $nbProducts && (Tools::getMemoryLimit()) > memory_get_peak_usage() && $timeElapsed < $maxExecutionTime); if (($nbProducts > 0 && !$full || $cursor < $nbProducts && $full) && !$ajax) { $token = substr(Tools::encrypt('blocklayered/index'), 0, 10); if (!Tools::file_get_contents(Tools::getProtocol().Tools::getHttpHost().__PS_BASE_URI__.'modules/blocklayered/blocklayered-price-indexer.php?token='.$token.'&cursor='.(int)$cursor.'&full='.(int)$full)) self::indexPrices((int)$cursor, (int)$full); return $cursor; } if ($ajax && $nbProducts > 0 && $cursor < $nbProducts && $full) return '{"cursor": '.$cursor.', "count": '.($nbProducts - $cursor).'}'; else if ($ajax && $nbProducts > 0 && !$full) return '{"cursor": '.$cursor.', "count": '.($nbProducts).'}'; else { Configuration::updateValue('PS_LAYERED_INDEXED', 1); if ($ajax) return '{"result": "ok"}'; else return -1; } } /* * $cursor $cursor in order to restart indexing from the last state */ private static function indexPricesUnbreakable($cursor, $full = false, $smart = false) { static $length = 100; // Nb of products to index if (is_null($cursor)) $cursor = 0; if ($full) $query = ' SELECT id_product FROM `'._DB_PREFIX_.'product` WHERE `active` = 1 ORDER by id_product LIMIT '.(int)$cursor.','.(int)$length; else $query = ' SELECT p.id_product FROM `'._DB_PREFIX_.'product` p LEFT JOIN `'._DB_PREFIX_.'layered_price_index` psi ON (psi.id_product = p.id_product) WHERE `active` = 1 AND psi.id_product is null ORDER by id_product LIMIT 0,'.(int)$length; foreach (Db::getInstance()->ExecuteS($query) as $product) self::indexProductPrices((int)$product['id_product'], ($smart && $full)); return (int)($cursor + $length); } public static function indexProductPrices($idProduct, $smart = true) { static $groups = null; if (is_null($groups)) { $groups = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT id_group FROM `'._DB_PREFIX_.'group_reduction`'); if (!$groups) $groups = array(); } static $currencyList = null; if (is_null($currencyList)) $currencyList = Currency::getCurrencies(); $minPrice = array(); $maxPrice = array(); if ($smart) Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'layered_price_index` WHERE `id_product` = '.(int)$idProduct); $maxTaxRate = Db::getInstance()->getValue(' SELECT max(t.rate) max_rate FROM `'._DB_PREFIX_.'product` p LEFT JOIN `'._DB_PREFIX_.'tax_rules_group` trg ON (trg.id_tax_rules_group = p.id_tax_rules_group) LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (tr.id_tax_rules_group = trg.id_tax_rules_group) LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.id_tax = tr.id_tax AND t.active = 1) WHERE id_product = '.(int)$idProduct.' GROUP BY id_product'); $productMinPrices = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT id_shop, id_currency, id_country, id_group, from_quantity FROM `'._DB_PREFIX_.'specific_price` WHERE id_product = '.(int)$idProduct); // Get min price foreach ($currencyList as $currency) { $price = Product::priceCalculation(null, (int)$idProduct, null, null, null, null, $currency['id_currency'], null, null, false, true, false, true, true, $specificPriceOutput, true); if (!isset($maxPrice[$currency['id_currency']])) $maxPrice[$currency['id_currency']] = 0; if (!isset($minPrice[$currency['id_currency']])) $minPrice[$currency['id_currency']] = null; if ($price > $maxPrice[$currency['id_currency']]) $maxPrice[$currency['id_currency']] = $price; if ($price == 0) continue; if (is_null($minPrice[$currency['id_currency']]) || $price < $minPrice[$currency['id_currency']]) $minPrice[$currency['id_currency']] = $price; } foreach ($productMinPrices as $specificPrice) foreach ($currencyList as $currency) { if ($specificPrice['id_currency'] && $specificPrice['id_currency'] != $currency['id_currency']) continue; $price = Product::priceCalculation((($specificPrice['id_shop'] == 0) ? null : (int)$specificPrice['id_shop']), (int)$idProduct, null, (($specificPrice['id_country'] == 0) ? null : $specificPrice['id_country']), null, null, $currency['id_currency'], (($specificPrice['id_group'] == 0) ? null : $specificPrice['id_group']), $specificPrice['from_quantity'], false, true, false, true, true, $specificPriceOutput, true); if (!isset($maxPrice[$currency['id_currency']])) $maxPrice[$currency['id_currency']] = 0; if (!isset($minPrice[$currency['id_currency']])) $minPrice[$currency['id_currency']] = null; if ($price > $maxPrice[$currency['id_currency']]) $maxPrice[$currency['id_currency']] = $price; if ($price == 0) continue; if (is_null($minPrice[$currency['id_currency']]) || $price < $minPrice[$currency['id_currency']]) $minPrice[$currency['id_currency']] = $price; } foreach ($groups as $group) foreach ($currencyList as $currency) { $price = Product::priceCalculation(null, (int)$idProduct, null, null, null, null, (int)$currency['id_currency'], (int)$group['id_group'], null, false, true, false, true, true, $specificPriceOutput, true); if (!isset($maxPrice[$currency['id_currency']])) $maxPrice[$currency['id_currency']] = 0; if (!isset($minPrice[$currency['id_currency']])) $minPrice[$currency['id_currency']] = null; if ($price > $maxPrice[$currency['id_currency']]) $maxPrice[$currency['id_currency']] = $price; if ($price == 0) continue; if (is_null($minPrice[$currency['id_currency']]) || $price < $minPrice[$currency['id_currency']]) $minPrice[$currency['id_currency']] = $price; } $values = array(); foreach ($currencyList as $currency) $values[] = '('.(int)$idProduct.', '.(int)$currency['id_currency'].', '.(int)$minPrice[$currency['id_currency']].', '.(int)($maxPrice[$currency['id_currency']] * (100 + $maxTaxRate) / 100).')'; Db::getInstance()->Execute(' INSERT INTO `'._DB_PREFIX_.'layered_price_index` (id_product, id_currency, price_min, price_max) VALUES '.implode(',', $values).' ON DUPLICATE KEY UPDATE id_product = id_product # avoid duplicate keys'); } public function hookLeftColumn($params) { return $this->generateFiltersBlock($this->getSelectedFilters()); } public function hookRightColumn($params) { return $this->hookLeftColumn($params); } public function hookHeader($params) { global $smarty, $cookie; // No filters => module disable if ($filterBlock = $this->getFilterBlock($this->getSelectedFilters())) if ($filterBlock['nbr_filterBlocks'] == 0) return false; if (Tools::getValue('id_category', Tools::getValue('id_category_layered', 1)) == 1) return; $idLang = (int)$cookie->id_lang; $category = new Category((int)Tools::getValue('id_category')); $categoryMetas = Tools::getMetaTags($idLang, ''); $categoryTitle = (empty($category->meta_title[$idLang]) ? $category->name[$idLang] : $category->meta_title[$idLang]); // Generate meta title and meta description $title = ''; if (is_array($filterBlock['title_values'])) foreach ($filterBlock['title_values'] as $key => $val) $title .= $key.' '.implode('/', $val).' – '; $title = rtrim($title, ' – '); if (!empty($title)) { $smarty->assign('meta_title', ucfirst(strtolower(preg_replace('/^'.$categoryTitle.'/', $categoryTitle.' – '.$title, $categoryMetas['meta_title'])))); $smarty->assign('meta_description', rtrim($categoryTitle.' – '.$title.' – '.$categoryMetas['meta_description'], ' – ')); } else $smarty->assign('meta_title', ucfirst(strtolower($categoryMetas['meta_title']))); $metaKeyWordsComplement = substr(str_replace(' – ', ', ', strtolower($title)), 1000); if (!empty($metaKeyWordsComplement)) $smarty->assign('meta_keywords', rtrim($categoryTitle.', '.$metaKeyWordsComplement.', '.$categoryMetas['meta_keywords'], ', ')); Tools::addJS(($this->_path).'blocklayered.js'); Tools::addJS(_PS_JS_DIR_.'jquery/jquery-ui-1.8.10.custom.min.js'); Tools::addCSS(_PS_CSS_DIR_.'jquery-ui-1.8.10.custom.css', 'all'); Tools::addCSS(($this->_path).'blocklayered.css', 'all'); Tools::addJS(_PS_JS_DIR_.'jquery/jquery.scrollTo-1.4.2-min.js'); } public function hookFooter($params) { if (basename($_SERVER['PHP_SELF']) == 'category.php') return ' '; } public function hookCategoryAddition($params) { $this->rebuildLayeredCache(array(), array((int)$params['category']->id)); } public function hookCategoryUpdate($params) { /* The category status might (active, inactive) have changed, we have to update the layered cache table structure */ if (!$params['category']->active) $this->hookCategoryDeletion($params); } public function hookCategoryDeletion($params) { Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_category WHERE id_category = '.(int)$params['category']->id); } public function getContent() { global $cookie; $html = ''; if (Tools::isSubmit('SubmitFilter')) { if (!Tools::getValue('layered_tpl_name')) $html .= '
X '.$this->l('Filter template name required (cannot be empty)').'
'; else { if (isset($_POST['id_layered_filter']) && $_POST['id_layered_filter']) Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_filter WHERE id_layered_filter = '.(int)Tools::getValue('id_layered_filter')); if (Tools::getValue('scope') == 1) { Db::getInstance()->Execute('TRUNCATE TABLE '._DB_PREFIX_.'layered_filter'); $categories = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT id_category FROM '._DB_PREFIX_.'category'); foreach ($categories as $category) $_POST['categoryBox'][] = (int)$category['id_category']; } if (count($_POST['categoryBox'])) { /* Clean categoryBox before use */ if (isset($_POST['categoryBox']) && is_array($_POST['categoryBox'])) foreach ($_POST['categoryBox'] as &$categoryBoxTmp) $categoryBoxTmp = (int)$categoryBoxTmp; Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_category WHERE id_category IN ('.implode(',', array_map('intval', $_POST['categoryBox'])).')'); $filterValues = array(); foreach ($_POST['categoryBox'] as $idc) $filterValues['categories'][] = (int)$idc; $sqlToInsert = 'INSERT INTO '._DB_PREFIX_.'layered_category (id_category, id_value, type, position) VALUES '; foreach ($_POST['categoryBox'] as $id_category_layered) { $n = 0; foreach ($_POST as $key => $value) if (substr($key, 0, 17) == 'layered_selection' && $value == 'on') { $filterValues[$key] = $value; $n++; if ($key == 'layered_selection_stock') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'quantity\','.(int)$n.'),'; else if ($key == 'layered_selection_subcategories') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'category\','.(int)$n.'),'; else if ($key == 'layered_selection_condition') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'condition\','.(int)$n.'),'; else if ($key == 'layered_selection_weight_slider') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'weight\','.(int)$n.'),'; else if ($key == 'layered_selection_price_slider') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'price\','.(int)$n.'),'; else if ($key == 'layered_selection_manufacturer') $sqlToInsert .= '('.(int)$id_category_layered.',NULL,\'manufacturer\','.(int)$n.'),'; else if (substr($key, 0, 21) == 'layered_selection_ag_') $sqlToInsert .= '('.(int)$id_category_layered.','.(int)str_replace('layered_selection_ag_', '', $key).',\'id_attribute_group\','.(int)$n.'),'; else if (substr($key, 0, 23) == 'layered_selection_feat_') $sqlToInsert .= '('.(int)$id_category_layered.','.(int)str_replace('layered_selection_feat_', '', $key).',\'id_feature\','.(int)$n.'),'; } } Db::getInstance()->Execute(rtrim($sqlToInsert, ',')); $valuesToInsert = array( 'name' => pSQL(Tools::getValue('layered_tpl_name')), 'filters' => pSQL(serialize($filterValues)), 'n_categories' => (int)count($filterValues['categories']), 'date_add' => date('Y-m-d H:i:s')); if (isset($_POST['id_layered_filter']) && $_POST['id_layered_filter']) $valuesToInsert['id_layered_filter'] = (int)Tools::getValue('id_layered_filter'); Db::getInstance()->AutoExecute(_DB_PREFIX_.'layered_filter', $valuesToInsert, 'INSERT'); echo '
'.$this->l('Your filter').' "'.Tools::safeOutput(Tools::getValue('layered_tpl_name')).'" '. ((isset($_POST['id_layered_filter']) && $_POST['id_layered_filter']) ? $this->l('was updated successfully.') : $this->l('was added successfully.')).'
'; } } } else if (Tools::isSubmit('submitLayeredSettings')) { Configuration::updateValue('PS_LAYERED_HIDE_0_VALUES', Tools::getValue('ps_layered_hide_0_values')); Configuration::updateValue('PS_LAYERED_SHOW_QTIES', Tools::getValue('ps_layered_show_qties')); $html .= '
'.$this->l('Settings saved successfully').'
'; } else if (isset($_GET['deleteFilterTemplate'])) { $layeredValues = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' SELECT filters FROM '._DB_PREFIX_.'layered_filter WHERE id_layered_filter = '.(int)$_GET['id_layered_filter']); if ($layeredValues) { Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_filter WHERE id_layered_filter = '.(int)$_GET['id_layered_filter'].' LIMIT 1'); $html .= '
'.$this->l('Filters template deleted, categories updated (reverted to default Filters template).').'
'; } else { $html .= '
'.$this->l('Filters template not found').'
'; } } $html .= '

'.$this->l('Layered navigation').'

'.$this->l('Indexes and caches').' '; if (!Configuration::get('PS_LAYERED_INDEXED')) $html .= ' '; $categoryList = array(); foreach (Db::getInstance()->ExecuteS('SELECT id_category FROM `'._DB_PREFIX_.'category`') as $category) if ($category['id_category'] != 1) $categoryList[] = $category['id_category']; $html .= ' '. $this->l('Index all missing prices').'
'. $this->l('Re-build entire price index').'
'. $this->l('Build attribute index').'
'. $this->l('Build url index').'

'.$this->l('You can set a cron job that will re-build price index using the following URL:').'
'. Tools::getProtocol().Tools::getHttpHost().__PS_BASE_URI__.'modules/blocklayered/blocklayered-price-indexer.php'.'?token='.substr(Tools::encrypt('blocklayered/index'), 0, 10).'&full=1
'.$this->l('You can set a cron job that will re-build url index using the following URL:').'
'. Tools::getProtocol().Tools::getHttpHost().__PS_BASE_URI__.'modules/blocklayered/blocklayered-url-indexer.php'.'?token='.substr(Tools::encrypt('blocklayered/index'), 0, 10).'&truncate=1
'.$this->l('You can set a cron job that will re-build attribute index using the following URL:').'
'. Tools::getProtocol().Tools::getHttpHost().__PS_BASE_URI__.'modules/blocklayered/blocklayered-attribute-indexer.php'.'?token='.substr(Tools::encrypt('blocklayered/index'), 0, 10).'

'.$this->l('A nightly rebuild is recommended.').'

'.$this->l('Existing filters templates').''; $filtersTemplates = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT * FROM '._DB_PREFIX_.'layered_filter ORDER BY date_add DESC'); if (count($filtersTemplates)) { $html .= '

'.count($filtersTemplates).' '.$this->l('filters templates are configured:').'

'; foreach ($filtersTemplates as $filtersTemplate) { /* Clean request URI first */ $_SERVER['REQUEST_URI'] = preg_replace('/&deleteFilterTemplate=[0-9]*&id_layered_filter=[0-9]*/', '', $_SERVER['REQUEST_URI']); $html .= ' '; } $html .= '
'.$this->l('ID').' '.$this->l('Name').' '.$this->l('Categories').' '.$this->l('Created on').' '.$this->l('Actions').'
'.(int)$filtersTemplate['id_layered_filter'].' '.$filtersTemplate['name'].' '.(int)$filtersTemplate['n_categories'].' '.Tools::displayDate($filtersTemplate['date_add'], (int)$cookie->id_lang, true).'
'; } else $html .= $this->l('No filter template found.'); $html .= '

'.$this->l('Build your own filters template').'
'; $html .= '

'.$this->l('Step 1/3 - Select categories').'

'.$this->l('Use this template for:').'

'.$this->l('Categories using this template').'

  1. '.$this->l('Select one ore more category using this filter template').'
  2. '.$this->l('Press "Save this selection" or close the window to save').'
'; $trads = array(); $selectedCat = array(); foreach (Helper::$translationsKeysForAdminCategorieTree as $key) $trads[$key] = $this->l($key); $html .= Helper::renderAdminCategorieTree($trads, $selectedCat, 'categoryBox'); $html .= '

'.$this->l('Step 2/3 - Select filters').'

'.$this->l('Selected filters').' (0)

'.$this->l('No filters selected yet.').'

    '.$this->ajaxCallBackOffice().'

    '.$this->l('Errors:').'
    • '.$this->l('Filter template name required (cannot be empty)').'

    '.$this->l('Step 3/3 - Name your template').'

    '.$this->l('Template name:').' ('.$this->l('only as a reminder').')




    '.$this->l('Configuration').'
    '.$this->l('Option').' '.$this->l('Value').'
    '.$this->l('Hide filter values with no product is matching').' '.$this->l('Yes').' '.$this->l('Yes').' '.$this->l('No').' '.$this->l('No').'
    '.$this->l('Show the number of matching products').' '.$this->l('Yes').' '.$this->l('Yes').' '.$this->l('No').' '.$this->l('No').'

    '; return $html; } private function getSelectedFilters() { $id_parent = (int)Tools::getValue('id_category', Tools::getValue('id_category_layered', 1)); if ($id_parent == 1) return; // Force attributes selection (by url '.../2-mycategory/color-blue' or by get parameter 'selected_filters') if (strpos($_SERVER['SCRIPT_FILENAME'], 'blocklayered-ajax.php') === false || Tools::getValue('selected_filters') !== false) { if (Tools::getValue('selected_filters')) $url = Tools::getValue('selected_filters'); else $url = preg_replace('/\/(?:\w*)\/(?:[0-9]+[-\w]*)([^\?]*)\??.*/', '$1', Tools::safeOutput($_SERVER['REQUEST_URI'], true)); $urlAttributes = explode('/', $url); array_shift($urlAttributes); $selectedFilters = array('category' => array()); if (!empty($urlAttributes)) { foreach ($urlAttributes as $urlAttribute) { $urlParameters = explode('-', $urlAttribute); $attributeName = array_shift($urlParameters); if (in_array($attributeName, array('price', 'weight'))) $selectedFilters[$attributeName] = array($urlParameters[0], $urlParameters[1]); else { foreach ($urlParameters as $urlParameter) { $data = Db::getInstance()->getValue('SELECT data FROM `'._DB_PREFIX_.'layered_friendly_url` WHERE `url_key` = \''.md5('/'.$attributeName.'-'.$urlParameter).'\''); if ($data) foreach (unserialize($data) as $keyParams => $params) { if (!isset($selectedFilters[$keyParams])) $selectedFilters[$keyParams] = array(); foreach ($params as $keyParam => $param) { if (!isset($selectedFilters[$keyParams][$keyParam])) $selectedFilters[$keyParams][$keyParam] = array(); $selectedFilters[$keyParams][$keyParam] = $param; } } } } } return $selectedFilters; } } /* Analyze all the filters selected by the user and store them into a tab */ $selectedFilters = array('category' => array(), 'manufacturer' => array(), 'quantity' => array(), 'condition' => array()); foreach ($_GET as $key => $value) if (substr($key, 0, 8) == 'layered_') { preg_match('/^(.*)_[0-9|new|used|refurbished|slider]+$/', substr($key, 8, strlen($key) - 8), $res); if (isset($res[1])) { $tmpTab = explode('_', $value); $value = $tmpTab[0]; $id_key = false; if (isset($tmpTab[1])) $id_key = $tmpTab[1]; if ($res[1] == 'condition' && in_array($value, array('new', 'used', 'refurbished'))) $selectedFilters['condition'][] = $value; else if ($res[1] == 'quantity' && (!$value || $value == 1)) $selectedFilters['quantity'][] = $value; else if (in_array($res[1], array('category', 'manufacturer'))) { if (!isset($selectedFilters[$res[1].($id_key ? '_'.$id_key : '')])) $selectedFilters[$res[1].($id_key ? '_'.$id_key : '')] = array(); $selectedFilters[$res[1].($id_key ? '_'.$id_key : '')][] = (int)$value; } else if (in_array($res[1], array('id_attribute_group', 'id_feature'))) { if (!isset($selectedFilters[$res[1]])) $selectedFilters[$res[1]] = array(); $selectedFilters[$res[1]][(int)$value] = $id_key.'_'.(int)$value; } else if ($res[1] == 'weight') $selectedFilters[$res[1]] = $tmpTab; else if ($res[1] == 'price') $selectedFilters[$res[1]] = $tmpTab; } } return $selectedFilters; } public function getProductByFilters($selectedFilters = array()) { global $cookie; if (!empty($this->products)) return $this->products; /* If the current category isn't defined or if it's homepage, we have nothing to display */ $id_parent = (int)Tools::getValue('id_category', Tools::getValue('id_category_layered', 1)); if ($id_parent == 1) return false; $queryFiltersWhere = ' AND p.active = 1'; $queryFiltersFrom = ''; $parent = new Category((int)$id_parent); if (!count($selectedFilters['category'])) $queryFiltersFrom .= ' INNER JOIN '._DB_PREFIX_.'category_product cp ON p.id_product = cp.id_product INNER JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category AND c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright.')'; foreach ($selectedFilters as $key => $filterValues) { if (!count($filterValues)) continue; preg_match('/^(.*[^_0-9])/', $key, $res); $key = $res[1]; switch ($key) { case 'id_feature': $subQueries = array(); foreach ($filterValues as $filterValue) { $filterValueArray = explode('_', $filterValue); if (!isset($subQueries[$filterValueArray[0]])) $subQueries[$filterValueArray[0]] = array(); $subQueries[$filterValueArray[0]][] = 'fp.`id_feature_value` = '.(int)$filterValueArray[1]; } foreach ($subQueries as $subQuery) { $queryFiltersWhere .= ' AND p.id_product IN (SELECT `id_product` FROM `'._DB_PREFIX_.'feature_product` fp WHERE '; $queryFiltersWhere .= implode(' OR ', $subQuery).') '; } break; case 'id_attribute_group': $subQueries = array(); foreach ($filterValues as $filterValue) { $filterValueArray = explode('_', $filterValue); if (!isset($subQueries[$filterValueArray[0]])) $subQueries[$filterValueArray[0]] = array(); $subQueries[$filterValueArray[0]][] = 'pac.`id_attribute` = '.(int)$filterValueArray[1]; } foreach ($subQueries as $subQuery) { $queryFiltersWhere .= ' AND p.id_product IN (SELECT pa.`id_product` FROM `'._DB_PREFIX_.'product_attribute_combination` pac LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (pa.`id_product_attribute` = pac.`id_product_attribute`) WHERE '; $queryFiltersWhere .= implode(' OR ', $subQuery).') '; } break; case 'category': $queryFiltersWhere .= ' AND p.id_product IN (SELECT id_product FROM '._DB_PREFIX_.'category_product cp WHERE '; foreach ($selectedFilters['category'] as $id_category) $queryFiltersWhere .= 'cp.`id_category` = '.(int)$id_category.' OR '; $queryFiltersWhere = rtrim($queryFiltersWhere, 'OR ').')'; break; case 'quantity': if (count($selectedFilters['quantity']) == 2) break; $queryFiltersWhere .= ' AND p.quantity '.(!$selectedFilters['quantity'][0] ? '=' : '>').' 0'; break; case 'manufacturer': $queryFiltersWhere .= ' AND p.id_manufacturer IN ('.implode($selectedFilters['manufacturer'], ',').')'; break; case 'condition': if (count($selectedFilters['condition']) == 3) break; $queryFiltersWhere .= ' AND p.condition IN ('; foreach ($selectedFilters['condition'] as $cond) $queryFiltersWhere .= '\''.$cond.'\','; $queryFiltersWhere = rtrim($queryFiltersWhere, ',').')'; break; case 'weight': if ($selectedFilters['weight'][0] != 0 || $selectedFilters['weight'][1] != 0) $queryFiltersWhere .= ' AND p.`weight` BETWEEN '.(float)($selectedFilters['weight'][0] - 0.001).' AND '.(float)($selectedFilters['weight'][1] + 0.001); case 'price': if (isset($selectedFilters['price'])) { if ($selectedFilters['price'][0] != 0 || $selectedFilters['price'][1] != 0) { $priceFilter = array(); $priceFilter['min'] = (float)($selectedFilters['price'][0]); $priceFilter['max'] = (float)($selectedFilters['price'][1]); } } else $priceFilter = false; break; } } $idCurrency = Currency::getCurrent()->id; $priceFilterQueryIn = ''; // All products with price range between price filters limits $priceFilterQueryOut = ''; // All products with a price filters limit on it price range if (isset($priceFilter) && $priceFilter) { $priceFilterQueryIn = 'INNER JOIN `'._DB_PREFIX_.'layered_price_index` psi ON psi.price_min >= '.(int)$priceFilter['min'].' AND psi.price_max <= '.(int)$priceFilter['max'].' AND psi.`id_product` = p.`id_product` AND psi.`id_currency` = '.(int)$idCurrency; $priceFilterQueryOut = 'INNER JOIN `'._DB_PREFIX_.'layered_price_index` psi ON ((psi.price_min <= '.(int)$priceFilter['min'].' AND psi.price_max >= '.(int)$priceFilter['min'].') OR (psi.price_max >= '.(int)$priceFilter['max'].' AND psi.price_min <= '.(int)$priceFilter['max'].')) AND psi.`id_product` = p.`id_product` AND psi.`id_currency` = '.(int)$idCurrency; } $allProductsOut = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT p.`id_product` id_product FROM `'._DB_PREFIX_.'product` p '.$priceFilterQueryOut.' '.$queryFiltersFrom.' WHERE 1 '.$queryFiltersWhere.' GROUP BY id_product', false); $allProductsIn = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT p.`id_product` id_product FROM `'._DB_PREFIX_.'product` p '.$priceFilterQueryIn.' '.$queryFiltersFrom.' WHERE 1 '.$queryFiltersWhere.' GROUP BY id_product', false); $productIdList = array(); while ($product = DB::getInstance()->nextRow($allProductsIn)) $productIdList[] = (int)$product['id_product']; while ($product = DB::getInstance()->nextRow($allProductsOut)) if (isset($priceFilter) && $priceFilter) { $price = (int)Product::getPriceStatic($product['id_product']); // Cast to int because we don't care about cents if ($price < $priceFilter['min'] || $price > $priceFilter['max']) continue; $productIdList[] = (int)$product['id_product']; } $this->nbr_products = count($productIdList); if ($this->nbr_products == 0) $this->products = array(); else { $n = (int)Tools::getValue('n', Configuration::get('PS_PRODUCTS_PER_PAGE')); $this->products = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT p.id_product, p.on_sale, p.out_of_stock, p.available_for_order, p.quantity, p.minimal_quantity, p.id_category_default, p.customizable, p.show_price, p.`weight`, p.ean13, pl.available_later, pl.description_short, pl.link_rewrite, pl.name, i.id_image, il.legend, m.name manufacturer_name, p.condition, p.id_manufacturer, DATEDIFF(p.`date_add`, DATE_SUB(NOW(), INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY)) > 0 AS new FROM `'._DB_PREFIX_.'category_product` cp LEFT JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category) LEFT JOIN `'._DB_PREFIX_.'product` p ON p.`id_product` = cp.`id_product` LEFT JOIN '._DB_PREFIX_.'product_lang pl ON (pl.id_product = p.id_product) LEFT JOIN '._DB_PREFIX_.'image i ON (i.id_product = p.id_product AND i.cover = 1) LEFT JOIN '._DB_PREFIX_.'image_lang il ON (i.id_image = il.id_image AND il.id_lang = '.(int)($cookie->id_lang).') LEFT JOIN '._DB_PREFIX_.'manufacturer m ON (m.id_manufacturer = p.id_manufacturer) WHERE p.`active` = 1 AND c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright.' AND pl.id_lang = '.(int)$cookie->id_lang.' AND p.id_product IN ('.implode(',', $productIdList).')' .' GROUP BY p.id_product ORDER BY '.Tools::getProductsOrder('by', Tools::getValue('orderby'), true).' '.Tools::getProductsOrder('way', Tools::getValue('orderway')). ' LIMIT '.(((int)Tools::getValue('p', 1) - 1) * $n.','.$n)); } return $this->products; } public function getFilterBlock($selectedFilters = array()) { global $cookie; static $cache = null; if (is_array($cache)) return $cache; $id_parent = (int)Tools::getValue('id_category', Tools::getValue('id_category_layered', 1)); if ($id_parent == 1) return; $parent = new Category((int)$id_parent); /* Get the filters for the current category */ $filters = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS('SELECT * FROM '._DB_PREFIX_.'layered_category WHERE id_category = '.(int)$id_parent.' GROUP BY `type`, id_value ORDER BY position ASC'); // Remove all empty selected filters foreach ($selectedFilters as $key => $value) switch ($key) { case 'price': case 'weight': if ($value[0] == '' && $value[1] == '' || $value[0] == 0 && $value[1] == 0) unset($selectedFilters[$key]); break; default: if ($value == '') unset($selectedFilters[$key]); break; } $filterBlocks = array(); foreach ($filters as $filter) { $sqlQuery = array('select' => '', 'from' => '', 'join' => '', 'where' => '', 'group' => ''); switch ($filter['type']) { // conditions + quantities + weight + price case 'price': case 'weight': case 'condition': case 'quantity': $sqlQuery['select'] = ' SELECT p.`id_product`, p.`condition`, p.`id_manufacturer`, p.`quantity`, p.`weight` '; $sqlQuery['from'] = ' FROM '._DB_PREFIX_.'product p '; $sqlQuery['join'] = ' INNER JOIN '._DB_PREFIX_.'category_product cp ON (cp.id_product = p.id_product) INNER JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category AND c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright.') '; $sqlQuery['where'] = 'WHERE p.`active` = 1 '; break; case 'manufacturer': $sqlQuery['select'] = 'SELECT m.name, count(p.id_product) nbr, m.id_manufacturer '; $sqlQuery['from'] = ' FROM `'._DB_PREFIX_.'category_product` cp INNER JOIN `'._DB_PREFIX_.'category` c ON (c.id_category = cp.id_category) INNER JOIN '._DB_PREFIX_.'product p ON (p.id_product = cp.id_product AND p.active = 1) INNER JOIN '._DB_PREFIX_.'manufacturer m ON (m.id_manufacturer = p.id_manufacturer) '; $sqlQuery['where'] = ' WHERE c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright.' '; $sqlQuery['group'] = ' GROUP BY p.id_manufacturer '; break; case 'id_attribute_group':// attribute group $sqlQuery['select'] = ' SELECT count(lpa.id_attribute) nbr, lpa.id_attribute_group, a.color, al.name attribute_name, agl.public_name attribute_group_name , lpa.id_attribute, ag.is_color_group, liagl.url_name name_url_name, liagl.meta_title name_meta_title, lial.url_name value_url_name, lial.meta_title value_meta_title'; $sqlQuery['from'] = ' FROM '._DB_PREFIX_.'layered_product_attribute lpa INNER JOIN '._DB_PREFIX_.'attribute a ON a.id_attribute = lpa.id_attribute INNER JOIN '._DB_PREFIX_.'attribute_lang al ON al.id_attribute = a.id_attribute AND al.id_lang = '.(int)$cookie->id_lang.' INNER JOIN '._DB_PREFIX_.'product as p ON p.id_product = lpa.id_product AND p.active = 1 INNER JOIN '._DB_PREFIX_.'attribute_group ag ON ag.id_attribute_group = lpa.id_attribute_group INNER JOIN '._DB_PREFIX_.'attribute_group_lang agl ON agl.id_attribute_group = lpa.id_attribute_group AND agl.id_lang = '.(int)$cookie->id_lang.' LEFT JOIN '._DB_PREFIX_.'layered_indexable_attribute_group_lang_value liagl ON (liagl.id_attribute_group = lpa.id_attribute_group AND liagl.id_lang = '.(int)$cookie->id_lang.') LEFT JOIN '._DB_PREFIX_.'layered_indexable_attribute_lang_value lial ON (lial.id_attribute = lpa.id_attribute AND lial.id_lang = '.(int)$cookie->id_lang.') '; $sqlQuery['where'] = 'WHERE a.id_attribute_group = '.(int)$filter['id_value'].' AND p.id_product IN ( SELECT id_product FROM '._DB_PREFIX_.'category_product cp INNER JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category AND c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright.')) '; $sqlQuery['group'] = ' GROUP BY lpa.id_attribute ORDER BY id_attribute_group, id_attribute '; break; case 'id_feature': $sqlQuery['select'] = 'SELECT fl.name feature_name, fp.id_feature, fv.id_feature_value, fvl.value, count(fv.id_feature_value) nbr, lifl.url_name name_url_name, lifl.meta_title name_meta_title, lifvl.url_name value_url_name, lifvl.meta_title value_meta_title '; $sqlQuery['from'] = ' FROM '._DB_PREFIX_.'feature_product fp INNER JOIN '._DB_PREFIX_.'product p ON (p.id_product = fp.id_product AND p.active = 1) LEFT JOIN '._DB_PREFIX_.'feature_lang fl ON (fl.id_feature = fp.id_feature AND fl.id_lang = '.(int)$cookie->id_lang.') INNER JOIN '._DB_PREFIX_.'feature_value fv ON (fv.id_feature_value = fp.id_feature_value AND (fv.custom IS NULL OR fv.custom = 0)) LEFT JOIN '._DB_PREFIX_.'feature_value_lang fvl ON (fvl.id_feature_value = fp.id_feature_value AND fvl.id_lang = '.(int)$cookie->id_lang.') LEFT JOIN '._DB_PREFIX_.'layered_indexable_feature_lang_value lifl ON (lifl.id_feature = fp.id_feature AND lifl.id_lang = '.(int)$cookie->id_lang.') LEFT JOIN '._DB_PREFIX_.'layered_indexable_feature_value_lang_value lifvl ON (lifvl.id_feature_value = fp.id_feature_value AND lifvl.id_lang = '.(int)$cookie->id_lang.') '; $sqlQuery['where'] = 'WHERE p.`active` = 1 AND fp.id_feature = '.(int)$filter['id_value'].' AND p.id_product IN ( SELECT id_product FROM '._DB_PREFIX_.'category_product cp INNER JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category AND c.nleft >= '.(int)$parent->nleft.' AND c.nright <= '.(int)$parent->nright.')) '; $sqlQuery['group'] = 'GROUP BY fv.id_feature_value '; break; case 'category': $sqlQuery['select'] = ' SELECT c.id_category, c.id_parent, cl.name, (SELECT count(*) # '; $sqlQuery['from'] = ' FROM '._DB_PREFIX_.'category_product cp LEFT JOIN '._DB_PREFIX_.'product p ON (p.id_product = cp.id_product) '; $sqlQuery['where'] = ' WHERE cp.id_category = c.id_category '; $sqlQuery['group'] = ') count_products FROM '._DB_PREFIX_.'category c LEFT JOIN '._DB_PREFIX_.'category_lang cl ON (cl.id_category = c.id_category AND cl.id_lang = '.(int)$cookie->id_lang.') WHERE c.id_parent = '.(int)$id_parent.' GROUP BY c.id_category ORDER BY level_depth'; } foreach ($filters as $filterTmp) { $methodName = 'get'.ucfirst($filterTmp['type']).'FilterSubQuery'; if (method_exists('BlockLayered', $methodName) && (!in_array($filter['type'], array('price', 'weight')) && $filter['type'] != $filterTmp['type'] || $filter['type'] == $filterTmp['type'])) { if ($filter['type'] == $filterTmp['type'] && $filter['id_value'] == $filterTmp['id_value']) $subQueryFilter = self::$methodName(array(), true); else { if (!is_null($filterTmp['id_value'])) $selected_filters_cleaned = $this->cleanFilterByIdValue(@$selectedFilters[$filterTmp['type']], $filterTmp['id_value']); else $selected_filters_cleaned = @$selectedFilters[$filterTmp['type']]; $subQueryFilter = self::$methodName($selected_filters_cleaned, $filter['type'] == $filterTmp['type']); } foreach ($subQueryFilter as $key => $value) $sqlQuery[$key] .= $value; } } $products = false; if (!empty($sqlQuery['from'])) $products = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS($sqlQuery['select']."\n".$sqlQuery['from']."\n".$sqlQuery['join']."\n".$sqlQuery['where']."\n".$sqlQuery['group']); foreach ($filters as $filterTmp) { $methodName = 'filterProductsBy'.ucfirst($filterTmp['type']); if (method_exists('BlockLayered', $methodName) && (!in_array($filter['type'], array('price', 'weight')) && $filter['type'] != $filterTmp['type'] || $filter['type'] == $filterTmp['type'])) if ($filter['type'] == $filterTmp['type']) $products = self::$methodName(array(), $products); else $products = self::$methodName(@$selectedFilters[$filterTmp['type']], $products); } switch ($filter['type']) { case 'price': $priceArray = array('type_lite' => 'price', 'type' => 'price', 'id_key' => 0, 'name' => $this->l('Price'), 'slider' => true, 'max' => '0', 'min' => null, 'values' => array ('1' => 0), 'unit' => Currency::getCurrent()->sign); if (isset($products) && $products) foreach ($products as $product) { if (is_null($priceArray['min'])) { $priceArray['min'] = $product['price_min']; $priceArray['values'][0] = $product['price_min']; } else if ($priceArray['min'] > $product['price_min']) { $priceArray['min'] = $product['price_min']; $priceArray['values'][0] = $product['price_min']; } if ($priceArray['max'] < $product['price_max']) { $priceArray['max'] = $product['price_max']; $priceArray['values'][1] = $product['price_max']; } } if ($priceArray['max'] != $priceArray['min'] && $priceArray['min'] != null) { if (isset($selectedFilters['price']) && isset($selectedFilters['price'][0]) && isset($selectedFilters['price'][1])) { $priceArray['values'][0] = $selectedFilters['price'][0]; $priceArray['values'][1] = $selectedFilters['price'][1]; } $filterBlocks[] = $priceArray; } break; case 'weight': $weightArray = array('type_lite' => 'weight', 'type' => 'weight', 'id_key' => 0, 'name' => $this->l('Weight'), 'slider' => true, 'max' => '0', 'min' => null, 'values' => array ('1' => 0), 'unit' => Configuration::get('PS_WEIGHT_UNIT')); if (isset($products) && $products) foreach ($products as $product) { if (is_null($weightArray['min'])) { $weightArray['min'] = $product['weight']; $weightArray['values'][0] = $product['weight']; } else if ($weightArray['min'] > $product['weight']) { $weightArray['min'] = $product['weight']; $weightArray['values'][0] = $product['weight']; } if ($weightArray['max'] < $product['weight']) { $weightArray['max'] = $product['weight']; $weightArray['values'][1] = $product['weight']; } } if ($weightArray['max'] != $weightArray['min'] && $weightArray['min'] != null) { if (isset($selectedFilters['weight']) && isset($selectedFilters['weight'][0]) && isset($selectedFilters['weight'][1])) { $weightArray['values'][0] = $selectedFilters['weight'][0]; $weightArray['values'][1] = $selectedFilters['weight'][1]; } $filterBlocks[] = $weightArray; } break; case 'condition': $conditionArray = array('new' => array('name' => $this->l('New'), 'nbr' => 0), 'used' => array('name' => $this->l('Used'), 'nbr' => 0), 'refurbished' => array('name' => $this->l('Refurbished'), 'nbr' => 0)); if (isset($products) && $products) foreach ($products as $product) if (isset($selectedFilters['condition']) && in_array($product['condition'], $selectedFilters['condition'])) $conditionArray[$product['condition']]['checked'] = true; foreach ($conditionArray as $key => $condition) if (isset($selectedFilters['condition']) && in_array($key, $selectedFilters['condition'])) $conditionArray[$key]['checked'] = true; if (isset($products) && $products) foreach ($products as $product) if (isset($conditionArray[$product['condition']])) $conditionArray[$product['condition']]['nbr']++; $filterBlocks[] = array('type_lite' => 'condition', 'type' => 'condition', 'id_key' => 0, 'name' => $this->l('Condition'), 'values' => $conditionArray); break; case 'quantity': $quantityArray = array (0 => array('name' => $this->l('Not available'), 'nbr' => 0), 1 => array('name' => $this->l('In stock'), 'nbr' => 0)); foreach ($quantityArray as $key => $quantity) if (isset($selectedFilters['quantity']) && in_array($key, $selectedFilters['quantity'])) $quantityArray[$key]['checked'] = true; if (isset($products) && $products) foreach ($products as $product) $quantityArray[(int)($product['quantity'] > 0)]['nbr']++; $filterBlocks[] = array('type_lite' => 'quantity', 'type' => 'quantity', 'id_key' => 0, 'name' => $this->l('Availability'), 'values' => $quantityArray); break; case 'manufacturer': if (isset($products) && $products) { $manufaturersArray = array(); foreach ($products as $manufacturer) { $manufaturersArray[$manufacturer['id_manufacturer']] = array('name' => $manufacturer['name'], 'nbr' => $manufacturer['nbr']); if (isset($selectedFilters['manufacturer']) && in_array((int)$manufacturer['id_manufacturer'], $selectedFilters['manufacturer'])) $manufaturersArray[$manufacturer['id_manufacturer']]['checked'] = true; } $filterBlocks[] = array('type_lite' => 'manufacturer', 'type' => 'manufacturer', 'id_key' => 0, 'name' => $this->l('Manufacturer'), 'values' => $manufaturersArray); } break; case 'id_attribute_group': $attributesArray = array(); if (isset($products) && $products) { foreach ($products as $attributes) { if (!isset($attributesArray[$attributes['id_attribute_group']])) $attributesArray[$attributes['id_attribute_group']] = array ('type_lite' => 'id_attribute_group', 'type' => 'id_attribute_group', 'id_key' => (int)$attributes['id_attribute_group'], 'name' => $attributes['attribute_group_name'], 'is_color_group' => (bool)$attributes['is_color_group'], 'values' => array(), 'url_name' => $attributes['name_url_name'], 'meta_title' => $attributes['name_meta_title']); $attributesArray[$attributes['id_attribute_group']]['values'][$attributes['id_attribute']] = array( 'color' => $attributes['color'], 'name' => $attributes['attribute_name'], 'nbr' => (int)$attributes['nbr'], 'url_name' => $attributes['value_url_name'], 'meta_title' => $attributes['value_meta_title']); if (isset($selectedFilters['id_attribute_group'][$attributes['id_attribute']])) $attributesArray[$attributes['id_attribute_group']]['values'][$attributes['id_attribute']]['checked'] = true; } $filterBlocks = array_merge($filterBlocks, $attributesArray); } break; case 'id_feature': $featureArray = array(); if (isset($products) && $products) { foreach ($products as $feature) { if (!isset($featureArray[$feature['id_feature']])) $featureArray[$feature['id_feature']] = array('type_lite' => 'id_feature', 'type' => 'id_feature', 'id_key' => (int)$feature['id_feature'], 'values' => array(), 'name' => $feature['feature_name'], 'url_name' => $feature['name_url_name'], 'meta_title' => $feature['name_meta_title']); $featureArray[$feature['id_feature']]['values'][$feature['id_feature_value']] = array('nbr' => (int)$feature['nbr'], 'name' => $feature['value'], 'url_name' => $feature['value_url_name'], 'meta_title' => $feature['value_meta_title']); if (isset($selectedFilters['id_feature'][$feature['id_feature_value']])) $featureArray[$feature['id_feature']]['values'][$feature['id_feature_value']]['checked'] = true; } $filterBlocks = array_merge($filterBlocks, $featureArray); } break; case 'category': $tmpArray = array(); if (isset($products) && $products) { foreach ($products as $category) { $tmpArray[$category['id_category']] = array('name' => $category['name'], 'nbr' => (int)$category['count_products']); if (isset($selectedFilters['category']) && in_array($category['id_category'], $selectedFilters['category'])) $tmpArray[$category['id_category']]['checked'] = true; } $filterBlocks[] = array ('type_lite' => 'category', 'type' => 'category', 'id_key' => 0, 'name' => $this->l('Categories'), 'values' => $tmpArray); } break; } } // All non indexable attribute and feature $nonIndexable = array(); // Get all non indexable attribute groups foreach (Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT public_name FROM `'._DB_PREFIX_.'attribute_group_lang` agl LEFT JOIN `'._DB_PREFIX_.'layered_indexable_attribute_group` liag ON liag.id_attribute_group = agl.id_attribute_group WHERE indexable IS NULL OR indexable = 0 AND id_lang = '.(int)$cookie->id_lang) as $attribute) $nonIndexable[] = Tools::link_rewrite($attribute['public_name']); // Get all non indexable features foreach (Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT name FROM `'._DB_PREFIX_.'feature_lang` fl LEFT JOIN `'._DB_PREFIX_.'layered_indexable_feature` lif ON lif.id_feature = fl.id_feature WHERE indexable IS NULL OR indexable = 0 AND id_lang = '.(int)$cookie->id_lang) as $attribute) $nonIndexable[] = Tools::link_rewrite($attribute['name']); //generate SEO link $paramSelected = ''; $optionCheckedArray = array(); $paramGroupSelectedArray = array(); $titleValues = array(); $link = new Link(); $linkBase = $link->getCategoryLink($id_parent, Category::getLinkRewrite($id_parent, (int)($cookie->id_lang)), (int)($cookie->id_lang)); $filterBlockList = array(); //get filters checked by group foreach ($filterBlocks as $typeFilter) { $filterName = (!empty($typeFilter['url_name']) ? $typeFilter['url_name'] : $typeFilter['name']); $paramGroupSelected = ''; foreach ($typeFilter['values'] as $key => $value) { if (is_array($value) && array_key_exists('checked', $value )) { $valueName = !empty($value['url_name']) ? $value['url_name'] : $value['name']; $paramGroupSelected .= '-'.str_replace('-', '_', Tools::link_rewrite($valueName)); $paramGroupSelectedArray[Tools::link_rewrite($filterName)][] = Tools::link_rewrite($valueName); if (!isset($titleValues[$filterName])) $titleValues[$filterName] = array(); $titleValues[$filterName][] = $valueName; } else $paramGroupSelectedArray[Tools::link_rewrite($filterName)][] = array(); } if (!empty($paramGroupSelected)) { $paramSelected .= '/'.str_replace('-', '_', Tools::link_rewrite($filterName)).$paramGroupSelected; $optionCheckedArray[Tools::link_rewrite($filterName)] = $paramGroupSelected; } } $blackList = array('weight','price'); $nofollow = false; foreach ($filterBlocks as &$typeFilter) { $filterName = (!empty($typeFilter['url_name']) ? $typeFilter['url_name'] : $typeFilter['name']); if (count($typeFilter) > 0 && !in_array($typeFilter['type'], $blackList)) { foreach ($typeFilter['values'] as $key => $values) { $nofollow = false; $optionCheckedCloneArray = $optionCheckedArray; //if not filters checked, add parameter $valueName = !empty($values['url_name']) ? $values['url_name'] : $values['name']; if (!in_array(Tools::link_rewrite($valueName), $paramGroupSelectedArray[Tools::link_rewrite($filterName)])) { //update parameter filter checked before if (array_key_exists(Tools::link_rewrite($filterName), $optionCheckedArray)) { $optionCheckedCloneArray[Tools::link_rewrite($filterName)] = $optionCheckedCloneArray[Tools::link_rewrite($filterName)].'-'.str_replace('-', '_', Tools::link_rewrite($valueName)); $nofollow = true; } else $optionCheckedCloneArray[Tools::link_rewrite($filterName)] = '-'.str_replace('-', '_', Tools::link_rewrite($valueName)); } else { // Remove selected parameters $optionCheckedCloneArray[Tools::link_rewrite($filterName)] = str_replace('-'.str_replace('-', '_', Tools::link_rewrite($valueName)), '', $optionCheckedCloneArray[Tools::link_rewrite($filterName)]); if (empty($optionCheckedCloneArray[Tools::link_rewrite($filterName)])) unset($optionCheckedCloneArray[Tools::link_rewrite($filterName)]); } $parameters = ''; foreach ($optionCheckedCloneArray as $keyGroup => $valueGroup) $parameters .= '/'.str_replace('-', '_', $keyGroup).$valueGroup; // Check if there is an non indexable attribute or feature in the url foreach ($nonIndexable as $value) if (strpos($parameters, '/'.$value) !== false) $nofollow = true; //write link by mode rewriting if (!Configuration::get('PS_REWRITING_SETTINGS')) $typeFilter['values'][$key]['link'] = $linkBase.'&selected_filters='.$parameters; else $typeFilter['values'][$key]['link'] = $linkBase.$parameters; $typeFilter['values'][$key]['rel'] = ($nofollow) ? 'nofollow' : ''; } } } $nFilters = 0; if (isset($selectedFilters['price'])) if ($priceArray['min'] == $selectedFilters['price'][0] && $priceArray['max'] == $selectedFilters['price'][1]) unset($selectedFilters['price']); if (isset($selectedFilters['weight'])) if ($weightArray['min'] == $selectedFilters['weight'][0] && $weightArray['max'] == $selectedFilters['weight'][1]) unset($selectedFilters['weight']); foreach ($selectedFilters as $filters) $nFilters += count($filters); $cache = array('layered_show_qties' => (int)Configuration::get('PS_LAYERED_SHOW_QTIES'), 'id_category_layered' => (int)$id_parent, 'selected_filters' => $selectedFilters, 'n_filters' => (int)$nFilters, 'nbr_filterBlocks' => count($filterBlocks), 'filters' => $filterBlocks, 'title_values' => $titleValues, 'current_friendly_url' => htmlentities($paramSelected), 'nofollow' => !empty($paramSelected) || $nofollow); return $cache; } public function cleanFilterByIdValue($attributes, $id_value) { $selected_filters = array(); if (is_array($attributes)) foreach ($attributes as $attribute) { $attribute_data = explode('_', $attribute); if ($attribute_data[0] == $id_value) $selected_filters[] = $attribute_data[1]; } return $selected_filters; } public function generateFiltersBlock($selectedFilters) { global $smarty; if ($filterBlock = $this->getFilterBlock($selectedFilters)) { if ($filterBlock['nbr_filterBlocks'] == 0) return false; $smarty->assign($filterBlock); return $this->display(__FILE__, 'blocklayered.tpl'); } else return false; } private static function getPriceFilterSubQuery($filterValue) { $idCurrency = (int)Currency::getCurrent()->id; $priceFilterQuery = ''; if (isset($filterValue) && $filterValue) { $idCurrency = Currency::getCurrent()->id; $priceFilterQuery = ' INNER JOIN `'._DB_PREFIX_.'layered_price_index` psi ON (psi.id_product = p.id_product AND psi.id_currency = '.(int)$idCurrency.' AND psi.price_min <= '.(int)$filterValue[1].' AND psi.price_max >= '.(int)$filterValue[0].') '; } else { $idCurrency = Currency::getCurrent()->id; $priceFilterQuery = ' INNER JOIN `'._DB_PREFIX_.'layered_price_index` psi ON (psi.id_product = p.id_product AND psi.id_currency = '.(int)$idCurrency.') '; } return array('join' => $priceFilterQuery, 'select' => ', psi.price_min, psi.price_max'); } private static function filterProductsByPrice($filterValue, $productCollection) { if (empty($filterValue)) return $productCollection; foreach ($productCollection as $key => $product) { if (isset($filterValue) && $filterValue && isset($product['price_min']) && isset($product['id_product']) && ((int)$filterValue[0] > $product['price_min'] || (int)$filterValue[1] < $product['price_max'])) { $price = Product::getPriceStatic($product['id_product']); if ($price < $filterValue[0] || $price > $filterValue[1]) continue; unset($productCollection[$key]); } } return $productCollection; } private static function getWeightFilterSubQuery($filterValue, $ignoreJoin) { if (isset($filterValue) && $filterValue) if ($filterValue[0] != 0 || $filterValue[1] != 0) return array('where' => ' AND p.`weight` BETWEEN '.(float)($filterValue[0] - 0.001).' AND '.(float)($filterValue[1] + 0.001).' '); return array(); } private static function getFeatureFilterSubQuery($filterValue, $ignoreJoin) { if (empty($filterValue)) return array(); $queryFilters = ' AND p.id_product IN (SELECT id_product FROM '._DB_PREFIX_.'feature_product fp WHERE '; foreach ($filterValue as $filterVal) $queryFilters .= 'fp.`id_feature_value` = '.(int)$filterVal.' OR '; $queryFilters = rtrim($queryFilters, 'OR ').') '; return array('where' => $queryFilters); } private static function getId_attribute_groupFilterSubQuery($filterValue, $ignoreJoin) { if (empty($filterValue)) return array(); $queryFilters = ' AND p.id_product IN (SELECT pa.`id_product` FROM `'._DB_PREFIX_.'product_attribute_combination` pac LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (pa.`id_product_attribute` = pac.`id_product_attribute`) WHERE '; foreach ($filterValue as $filterVal) $queryFilters .= 'pac.`id_attribute` = '.(int)$filterVal.' OR '; $queryFilters = rtrim($queryFilters, 'OR ').') '; return array('where' => $queryFilters); } private static function getCategoryFilterSubQuery($filterValue, $ignoreJoin) { if (empty($filterValue)) return array(); $queryFiltersJoin = ''; $queryFiltersWhere = ' AND p.id_product IN (SELECT id_product FROM '._DB_PREFIX_.'category_product cp WHERE '; foreach ($filterValue as $id_category) $queryFiltersWhere .= 'cp.`id_category` = '.(int)$id_category.' OR '; $queryFiltersWhere = rtrim($queryFiltersWhere, 'OR ').') '; return array('where' => $queryFiltersWhere, 'join' => $queryFiltersJoin); } private static function getQuantityFilterSubQuery($filterValue, $ignoreJoin) { if (count($filterValue) == 2 || empty($filterValue)) return array(); $queryFilters = ' AND p.quantity '.(!$filterValue[0] ? '=' : '>').' 0 '; return array('where' => $queryFilters); } private static function getManufacturerFilterSubQuery($filterValue, $ignoreJoin) { if (empty($filterValue)) $queryFilters = ''; else { array_walk($filterValue, create_function('&$id_manufacturer', '$id_manufacturer = (int)$id_manufacturer;')); $queryFilters = ' AND p.id_manufacturer IN ('.implode($filterValue, ',').')'; } if ($ignoreJoin) return array('where' => $queryFilters, 'select' => ', m.name'); else return array('where' => $queryFilters, 'select' => ', m.name', 'join' => 'LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON (m.id_manufacturer = p.id_manufacturer) '); } private static function getConditionFilterSubQuery($filterValue, $ignoreJoin) { if (count($filterValue) == 3 || empty($filterValue)) return array(); $queryFilters = ' AND p.condition IN ('; foreach ($filterValue as $cond) $queryFilters .= '\''.$cond.'\','; $queryFilters = rtrim($queryFilters, ',').') '; return array('where' => $queryFilters); } public function ajaxCallBackOffice($categoryBox = array(), $id_layered_filter = null) { global $cookie; if (!empty($id_layered_filter)) { $layeredFilter = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow('SELECT * FROM '._DB_PREFIX_.'layered_filter WHERE id_layered_filter = '.(int)$id_layered_filter); if ($layeredFilter && isset($layeredFilter['filters']) && !empty($layeredFilter['filters'])) $layeredValues = unserialize($layeredFilter['filters']); if (isset($layeredValues['categories']) && count($layeredValues['categories'])) foreach ($layeredValues['categories'] as $id_category) $categoryBox[] = (int)$id_category; } /* Clean categoryBox before use */ if (isset($categoryBox) && is_array($categoryBox)) foreach ($categoryBox as &$value) $value = (int)$value; $attributeGroups = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT ag.id_attribute_group, ag.is_color_group, agl.name, COUNT(DISTINCT(a.id_attribute)) n FROM '._DB_PREFIX_.'attribute_group ag LEFT JOIN '._DB_PREFIX_.'attribute_group_lang agl ON (agl.id_attribute_group = ag.id_attribute_group) LEFT JOIN '._DB_PREFIX_.'attribute a ON (a.id_attribute_group = ag.id_attribute_group) '.(count($categoryBox) ? ' LEFT JOIN '._DB_PREFIX_.'product_attribute_combination pac ON (pac.id_attribute = a.id_attribute) LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN '._DB_PREFIX_.'category_product cp ON (cp.id_product = pa.id_product)' : '').' WHERE agl.id_lang = '.(int)$cookie->id_lang. (count($categoryBox) ? ' AND cp.id_category IN ('.implode(',', $categoryBox).')' : '').' GROUP BY ag.id_attribute_group'); $features = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT fl.id_feature, fl.name, COUNT(DISTINCT(fv.id_feature_value)) n FROM '._DB_PREFIX_.'feature_lang fl LEFT JOIN '._DB_PREFIX_.'feature_value fv ON (fv.id_feature = fl.id_feature) '.(count($categoryBox) ? ' LEFT JOIN '._DB_PREFIX_.'feature_product fp ON (fp.id_feature = fv.id_feature) LEFT JOIN '._DB_PREFIX_.'category_product cp ON (cp.id_product = fp.id_product)' : '').' WHERE (fv.custom IS NULL OR fv.custom = 0) AND fl.id_lang = '.(int)$cookie->id_lang. (count($categoryBox) ? ' AND cp.id_category IN ('.implode(',', $categoryBox).')' : '').' GROUP BY fl.id_feature'); $nElements = count($attributeGroups) + count($features) + 4; if ($nElements > 20) $nElements = 20; $html = '

    '.$this->l('Available filters').' (0)

      • '.$this->l('Sub-categories filter').'
      • '.$this->l('Product stock filter').'
      • '.$this->l('Product condition filter').'
      • '.$this->l('Product manufacturer filter').'
      • '.$this->l('Product weight filter (slider)').'
      • '.$this->l('Product price filter (slider)').'
      '; if (count($attributeGroups)) { $html .= '
        '; foreach ($attributeGroups as $attributeGroup) $html .= '
      • '.$this->l('Attribute group:').' '.$attributeGroup['name'].' ('.(int)$attributeGroup['n'].' '.($attributeGroup['n'] > 1 ? $this->l('attributes') : $this->l('attribute')).')'. ($attributeGroup['is_color_group'] ? ' ' : '').'
      • '; $html .= '
      '; } if (count($features)) { $html .= '
        '; foreach ($features as $feature) $html .= '
      • '.$this->l('Feature:').' '.$feature['name'].' ('.(int)$feature['n'].' '.($feature['n'] > 1 ? $this->l('values') : $this->l('value')).')
      • '; $html .= '
      '; } $html .= '
      '; if (isset($layeredValues)) { $html .= ' '; } return $html; } public function ajaxCall() { global $smarty; $selectedFilters = $this->getSelectedFilters(); $this->getProducts($selectedFilters, $products, $nbProducts, $p, $n, $pages_nb, $start, $stop, $range); $smarty->assign('nb_products', $nbProducts); $smarty->assign('category', (object)array('id' => Tools::getValue('id_category_layered', 1))); $pagination_infos = array('pages_nb' => (int)($pages_nb), 'p' => (int)$p, 'n' => (int)$n, 'range' => (int)$range, 'start' => (int)$start, 'stop' => (int)$stop, 'nArray' => $nArray = (int)Configuration::get('PS_PRODUCTS_PER_PAGE') != 10 ? array((int)Configuration::get('PS_PRODUCTS_PER_PAGE'), 10, 20, 50) : array(10, 20, 50)); $smarty->assign($pagination_infos); $smarty->assign('comparator_max_item', (int)(Configuration::get('PS_COMPARATOR_MAX_ITEM'))); $smarty->assign('products', $products); // Prevent bug with old template where category.tpl contain the title of the category and category-count.tpl do not exists if (file_exists(_PS_THEME_DIR_.'category-count.tpl')) $categoryCount = $smarty->fetch(_PS_THEME_DIR_.'category-count.tpl'); else $categoryCount = ''; if ($nbProducts == 0) $product_list_tpl = 'blocklayered-no-products.tpl'; else $product_list_tpl = _PS_THEME_DIR_.'product-list.tpl'; /* We are sending an array in jSon to the .js controller, it will update both the filters and the products zones */ return Tools::jsonEncode(array( 'filtersBlock' => $this->generateFiltersBlock($selectedFilters), 'productList' => $smarty->fetch($product_list_tpl), 'pagination' => $smarty->fetch(_PS_THEME_DIR_.'pagination.tpl'), 'categoryCount' => $categoryCount)); } public function getProducts($selectedFilters, &$products, &$nbProducts, &$p, &$n, &$pages_nb, &$start, &$stop, &$range) { global $cookie; $products = $this->getProductByFilters($selectedFilters); $products = Product::getProductsProperties((int)$cookie->id_lang, $products); $nbProducts = $this->nbr_products; $range = 2; /* how many pages around page selected */ $n = (int)Tools::getValue('n', Configuration::get('PS_PRODUCTS_PER_PAGE')); $p = Tools::getValue('p', 1); if ($p < 0) $p = 0; if ($p > ($nbProducts / $n)) $p = ceil($nbProducts / $n); $pages_nb = ceil($nbProducts / (int)($n)); $start = (int)($p - $range); if ($start < 1) $start = 1; $stop = (int)($p + $range); if ($stop > $pages_nb) $stop = (int)($pages_nb); } public function rebuildLayeredStructure() { @set_time_limit(0); /* Set memory limit to 128M only if current is lower */ $memory_limit = @ini_get('memory_limit'); if (substr($memory_limit, -1) != 'G' && ((substr($memory_limit, -1) == 'M' && substr($memory_limit, 0, -1) < 128) || is_numeric($memory_limit) && (intval($memory_limit) < 131072))) @ini_set('memory_limit', '128M'); /* Delete and re-create the layered categories table */ Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_category'); Db::getInstance()->Execute(' CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'layered_category` ( `id_layered_category` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `id_category` INT(10) UNSIGNED NOT NULL, `id_value` INT(10) UNSIGNED NULL DEFAULT \'0\', `type` ENUM(\'category\',\'id_feature\',\'id_attribute_group\',\'quantity\',\'condition\',\'manufacturer\',\'weight\',\'price\') NOT NULL, `position` INT(10) UNSIGNED NOT NULL, PRIMARY KEY (`id_layered_category`), KEY `id_category` (`id_category`,`type`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;'); /* MyISAM + latin1 = Smaller/faster */ Db::getInstance()->Execute(' CREATE TABLE IF NOT EXISTS `'._DB_PREFIX_.'layered_filter` ( `id_layered_filter` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(64) NOT NULL, `filters` TEXT NULL, `n_categories` INT(10) UNSIGNED NOT NULL, `date_add` DATETIME NOT NULL)'); } public function rebuildLayeredCache($productsIds = array(), $categoriesIds = array()) { @set_time_limit(0); /* Set memory limit to 128M only if current is lower */ $memory_limit = @ini_get('memory_limit'); if (substr($memory_limit, -1) != 'G' && ((substr($memory_limit, -1) == 'M' && substr($memory_limit, 0, -1) < 128) || is_numeric($memory_limit) && (intval($memory_limit) < 131072))) @ini_set('memory_limit', '128M'); $db = Db::getInstance(_PS_USE_SQL_SLAVE_); $nCategories = array(); $doneCategories = array(); $attributeGroups = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT a.id_attribute, a.id_attribute_group FROM '._DB_PREFIX_.'attribute a LEFT JOIN '._DB_PREFIX_.'product_attribute_combination pac ON (pac.id_attribute = a.id_attribute) LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (pa.id_product_attribute = pac.id_product_attribute) LEFT JOIN '._DB_PREFIX_.'product p ON (p.id_product = pa.id_product) LEFT JOIN '._DB_PREFIX_.'category_product cp ON (cp.id_product = p.id_product) LEFT JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category) WHERE c.active = 1'.(count($categoriesIds) ? ' AND cp.id_category IN ('.implode(',', $categoriesIds).')' : '').' AND p.active = 1'.(count($productsIds) ? ' AND p.id_product IN ('.implode(',', $productsIds).')' : ''), false); $attributeGroupsById = array(); while ($row = $db->nextRow($attributeGroups)) $attributeGroupsById[(int)$row['id_attribute']] = (int)$row['id_attribute_group']; $features = Db::getInstance(_PS_USE_SQL_SLAVE_)->ExecuteS(' SELECT fv.id_feature_value, fv.id_feature FROM '._DB_PREFIX_.'feature_value fv LEFT JOIN '._DB_PREFIX_.'feature_product fp ON (fp.id_feature_value = fv.id_feature_value) LEFT JOIN '._DB_PREFIX_.'product p ON (p.id_product = fp.id_product) LEFT JOIN '._DB_PREFIX_.'category_product cp ON (cp.id_product = p.id_product) LEFT JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category) WHERE (fv.custom IS NULL OR fv.custom = 0) AND c.active = 1'.(count($categoriesIds) ? ' AND cp.id_category IN ('.implode(',', $categoriesIds).')' : '').' AND p.active = 1'.(count($productsIds) ? ' AND p.id_product IN ('.implode(',', $productsIds).')' : ''), false); $featuresById = array(); while ($row = $db->nextRow($features)) $featuresById[(int)$row['id_feature_value']] = (int)$row['id_feature']; $result = $db->ExecuteS(' SELECT p.id_product, GROUP_CONCAT(DISTINCT fv.id_feature_value) features, GROUP_CONCAT(DISTINCT cp.id_category) categories, GROUP_CONCAT(DISTINCT pac.id_attribute) attributes FROM '._DB_PREFIX_.'product p LEFT JOIN '._DB_PREFIX_.'category_product cp ON (cp.id_product = p.id_product) LEFT JOIN '._DB_PREFIX_.'category c ON (c.id_category = cp.id_category) LEFT JOIN '._DB_PREFIX_.'feature_product fp ON (fp.id_product = p.id_product) LEFT JOIN '._DB_PREFIX_.'feature_value fv ON (fv.id_feature_value = fp.id_feature_value) LEFT JOIN '._DB_PREFIX_.'product_attribute pa ON (pa.id_product = p.id_product) LEFT JOIN '._DB_PREFIX_.'product_attribute_combination pac ON (pac.id_product_attribute = pa.id_product_attribute) WHERE c.active = 1'.(count($categoriesIds) ? ' AND cp.id_category IN ('.implode(',', $categoriesIds).')' : '').' AND p.active = 1'.(count($productsIds) ? ' AND p.id_product IN ('.implode(',', $productsIds).')' : '').' AND (fv.custom IS NULL OR fv.custom = 0) GROUP BY p.id_product', false); while ($product = $db->nextRow($result)) { $a = $c = $f = array(); if (!empty($product['attributes'])) $a = array_flip(explode(',', $product['attributes'])); if (!empty($product['categories'])) $c = array_flip(explode(',', $product['categories'])); if (!empty($product['features'])) $f = array_flip(explode(',', $product['features'])); $queryCategory = 'INSERT INTO '._DB_PREFIX_.'layered_category (id_category, id_value, type, position) VALUES '; $toInsert = false; foreach ($c as $id_category => $category) { if (!isset($nCategories[(int)$id_category])) $nCategories[(int)$id_category] = 1; if (!isset($doneCategories[(int)$id_category]['cat'])) { $doneCategories[(int)$id_category]['cat'] = true; $queryCategory .= '('.(int)$id_category.',NULL,\'category\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } foreach ($a as $kAttribute => $attribute) if (!isset($doneCategories[(int)$id_category]['a'.(int)$attributeGroupsById[(int)$kAttribute]])) { $doneCategories[(int)$id_category]['a'.(int)$attributeGroupsById[(int)$kAttribute]] = true; $queryCategory .= '('.(int)$id_category.','.(int)$attributeGroupsById[(int)$kAttribute].',\'id_attribute_group\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } foreach ($f as $kFeature => $feature) if (!isset($doneCategories[(int)$id_category]['f'.(int)$featuresById[(int)$kFeature]])) { $doneCategories[(int)$id_category]['f'.(int)$featuresById[(int)$kFeature]] = true; $queryCategory .= '('.(int)$id_category.','.(int)$featuresById[(int)$kFeature].',\'id_feature\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } if (!isset($doneCategories[(int)$id_category]['q'])) { $doneCategories[(int)$id_category]['q'] = true; $queryCategory .= '('.(int)$id_category.',NULL,\'quantity\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } if (!isset($doneCategories[(int)$id_category]['m'])) { $doneCategories[(int)$id_category]['m'] = true; $queryCategory .= '('.(int)$id_category.',NULL,\'manufacturer\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } if (!isset($doneCategories[(int)$id_category]['c'])) { $doneCategories[(int)$id_category]['c'] = true; $queryCategory .= '('.(int)$id_category.',NULL,\'condition\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } if (!isset($doneCategories[(int)$id_category]['w'])) { $doneCategories[(int)$id_category]['w'] = true; $queryCategory .= '('.(int)$id_category.',NULL,\'weight\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } if (!isset($doneCategories[(int)$id_category]['p'])) { $doneCategories[(int)$id_category]['p'] = true; $queryCategory .= '('.(int)$id_category.',NULL,\'price\','.(int)$nCategories[(int)$id_category]++.'),'; $toInsert = true; } } if ($toInsert) Db::getInstance()->Execute(rtrim($queryCategory, ',')); } } public function filterProducts($products, $selectedFilters, $excludeType = false) { static $priceStatics = array(); $productsToKeep = array(); $filterByLetter = array('id_attribute_group' => 'a', 'id_feature' => 'f', 'category' => 'c', 'manufacturer' => 'id_manufacturer', 'quantity' => 'quantity', 'condition' => 'condition', 'weight' => 'weight', 'price' => 'price'); foreach ($selectedFilters as $type => $filters) { if ($type == $excludeType || !count($filters)) continue; else { $type = preg_match('/^(.*[^_0-9])/', $type, $res); $type = $res[1]; switch ($type) { case 'category': foreach ($products as $k => $product) if ($filter = Tools::getValue('id_category_layered')) $productsToKeep[] = (int)$k; //don't break me case 'id_attribute_group': case 'id_feature': foreach ($products as $k => $product) foreach ($filters as $filter) if (in_array($filter, $product[$filterByLetter[$type]])) $productsToKeep[] = (int)$k; break; case 'manufacturer': case 'condition': case 'quantity': foreach ($products as $k => $product) foreach ($filters as $filter) if ($product[$filterByLetter[$type]] == $filter) $productsToKeep[] = (int)$k; break; case 'weight': $min = $filters[0]; $max = $filters[1]; foreach ($products as $k => $product) if ((float)$min <= (float)$product[$filterByLetter[$type]] && (float)$product[$filterByLetter[$type]] <= (float)$max) $productsToKeep[] = (int)$k; break; case 'price': $min = $filters[0]; $max = $filters[1]; foreach ($products as $k => $product) if ((float)$min <= (float)$product['price_min'] && (float)$product['price_max'] <= (float)$max) $productsToKeep[] = (int)$k; else if ((float)$product['price_min'] < (float)$max && (float)$product['price_max'] > (float)$max || (float)$product['price_min'] < (float)$min && (float)$product['price_max'] > (float)$min) { if (!isset($priceStatics[(int)$k])) $priceStatics[(int)$k] = Product::getPriceStatic((int)$k); $price = $priceStatics[(int)$k]; if ((float)$min <= $price && $price <= (float)$max) $productsToKeep[] = (int)$k; } break; } foreach ($products as $k => $product) if (!in_array($k, $productsToKeep)) unset($products[(int)$k]); $productsToKeep = array(); } } return $products; } }