* @copyright 2007-2011 PrestaShop SA
* @version Release: $Revision: 1.4 $
* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0)
* International Registred Trademark & Property of PrestaShop SA
*/
if (!defined('_CAN_LOAD_FILES_'))
exit;
class BlockLayered extends Module
{
public function __construct()
{
$this->name = 'blocklayered';
$this->tab = 'front_office_features';
$this->version = 1.3;
$this->author = 'PrestaShop';
parent::__construct();
$this->displayName = $this->l('Layered navigation block');
$this->description = $this->l('Displays a block with layered navigation filters.');
}
public function install()
{
if ($result = parent::install() AND $this->registerHook('leftColumn') AND $this->registerHook('header')
AND $this->registerHook('addProduct') AND $this->registerHook('updateProduct') AND $this->registerHook('deleteProduct')
AND $this->registerHook('categoryAddition') AND $this->registerHook('categoryUpdate') AND $this->registerHook('categoryDeletion'))
{
Configuration::updateValue('PS_LAYERED_NAVIGATION_CHECKBOXES', 1);
$this->rebuildLayeredStructure();
}
return $result;
}
public function uninstall()
{
/* Delete all configurations */
Configuration::deleteByName('PS_LAYERED_NAVIGATION_CHECKBOXES');
return parent::uninstall();
}
public function hookLeftColumn($params)
{
return $this->generateFilters();
}
public function hookRightColumn($params)
{
return $this->hookLeftColumn($params);
}
public function hookHeader($params)
{
Tools::addJS(($this->_path).'blocklayered.js');
Tools::addCSS(($this->_path).'blocklayered.css', 'all');
}
public function hookAddProduct($params)
{
$this->rebuildLayeredCache(array((int)$params['product']->id));
}
public function hookUpdateProduct($params)
{
Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_cache WHERE id_product = '.(int)$params['product']->id.' LIMIT 1');
$this->rebuildLayeredCache(array((int)$params['product']->id));
}
public function hookDeleteProduct($params)
{
Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_cache WHERE id_product = '.(int)$params['product']->id.' LIMIT 1');
}
public function hookCategoryAddition($params)
{
Db::getInstance()->Execute('ALTER TABLE `'._DB_PREFIX_.'layered_cache` ADD `c'.(int)$params['category']->id.'` TINYINT UNSIGNED NOT NULL DEFAULT \'0\'');
Configuration::updateValue('PS_LAYERED_COLUMNS', Configuration::get('PS_LAYERED_COLUMNS').',c'.(int)$params['category']->id);
$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);
else
{
$oneRow = Db::getInstance()->getRow('SELECT c'.(int)$params['category']->id.' FROM `'._DB_PREFIX_.'layered_cache`');
if (!isset($oneRow['c'.(int)$params['category']->id]))
{
Db::getInstance()->Execute('ALTER TABLE `'._DB_PREFIX_.'layered_cache` ADD `c'.(int)$params['category']->id.'` TINYINT UNSIGNED NOT NULL DEFAULT \'0\'');
Configuration::updateValue('PS_LAYERED_COLUMNS', Configuration::get('PS_LAYERED_COLUMNS').',c'.(int)$params['category']->id);
}
if (!Db::getInstance()->getRow('SELECT id_layered_category FROM `'._DB_PREFIX_.'layered_category` WHERE id_category = '.(int)$params['category']->id))
$this->rebuildLayeredCache(array(), array((int)$params['category']->id));
}
}
public function hookCategoryDeletion($params)
{
Db::getInstance()->Execute('DELETE FROM '._DB_PREFIX_.'layered_category WHERE id_category = '.(int)$params['category']->id);
$oneRow = Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'layered_cache`');
if (isset($oneRow['c'.(int)$params['category']->id]))
{
Db::getInstance()->Execute('ALTER TABLE `'._DB_PREFIX_.'layered_cache` DROP `c'.(int)$params['category']->id.'`');
Configuration::updateValue('PS_LAYERED_COLUMNS', str_replace(',c'.(int)$params['category']->id, '', Configuration::get('PS_LAYERED_COLUMNS')));
}
}
public function getContent()
{
if (Tools::isSubmit('submitLayeredCache'))
{
$this->rebuildLayeredStructure();
$this->rebuildLayeredCache();
echo '
'.$this->l('Layered navigation database was initialized successfully').'
';
}
public function rebuildLayeredStructure()
{
@set_time_limit(0);
@ini_set('memory_limit', '64M');
/* Delete and re-create the products cache table */
Db::getInstance()->Execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'layered_cache');
$createTable = 'CREATE TABLE `'._DB_PREFIX_.'layered_cache` (`id_product` INT UNSIGNED NOT NULL,';
$confValue = 'id_product,';
/* Add the missing feature values columns */
$featureValues = Db::getInstance()->ExecuteS('
SELECT fv.id_feature_value
FROM '._DB_PREFIX_.'feature_product fp
LEFT JOIN '._DB_PREFIX_.'feature_value fv ON (fv.id_feature_value = fp.id_feature_value)
LEFT JOIN '._DB_PREFIX_.'product p ON (p.id_product = fp.id_product)
WHERE (fv.custom IS NULL OR fv.custom = 0) AND p.active = 1
GROUP BY fv.id_feature_value');
/* Add the missing attribute values columns */
$attributeValues = Db::getInstance()->ExecuteS('
SELECT pac.id_attribute
FROM '._DB_PREFIX_.'product_attribute_combination pac
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)
WHERE p.active = 1
GROUP BY pac.id_attribute');
/* Add the missing categories columns */
$categories = Db::getInstance()->ExecuteS('
SELECT cp.id_category
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)
WHERE p.active = 1 AND c.active = 1
GROUP BY cp.id_category');
foreach ($featureValues AS $featureValue)
{
$createTable .= '`f'.(int)$featureValue['id_feature_value'].'` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',';
$confValue .= 'f'.(int)$featureValue['id_feature_value'].',';
}
foreach ($attributeValues AS $attributeValue)
{
$createTable .= '`a'.(int)$attributeValue['id_attribute'].'` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',';
$confValue .= 'a'.(int)$attributeValue['id_attribute'].',';
}
foreach ($categories AS $category)
{
$createTable .= '`c'.(int)$category['id_category'].'` TINYINT(1) UNSIGNED NOT NULL DEFAULT \'0\',';
$confValue .= 'c'.(int)$category['id_category'].',';
}
Configuration::updateValue('PS_LAYERED_COLUMNS', rtrim($confValue, ','));
Db::getInstance()->Execute($createTable.' PRIMARY KEY (`id_product`)) ENGINE=MyISAM CHARSET=latin1');
/* 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 NOT NULL DEFAULT \'0\',
`type` ENUM(\'category\',\'id_feature\',\'id_attribute_group\',\'quantity\',\'condition\',\'manufacturer\') 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;');
}
public function rebuildLayeredCache($productsIds = array(), $categoriesIds = array())
{
@set_time_limit(0);
@ini_set('memory_limit', '64M');
$db = Db::getInstance();
$nCategories = array();
$doneCategories = array();
$attributeGroups = Db::getInstance()->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'.(sizeof($categoriesIds) ? ' AND cp.id_category IN ('.implode(',', $categoriesIds).')' : '').' AND p.active = 1'.(sizeof($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()->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'.(sizeof($categoriesIds) ? ' AND cp.id_category IN ('.implode(',', $categoriesIds).')' : '').' AND p.active = 1'.(sizeof($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'.(sizeof($categoriesIds) ? ' AND cp.id_category IN ('.implode(',', $categoriesIds).')' : '').' AND p.active = 1'.(sizeof($productsIds) ? ' AND p.id_product IN ('.implode(',', $productsIds).')' : '').' AND (fv.custom IS NULL OR fv.custom = 0)
GROUP BY p.id_product', false);
/* Get all the columns to fill */
$columns = explode(',', Configuration::get('PS_LAYERED_COLUMNS'));
/* We do not need to build the query for products when we are just updating the layered_category table */
if (!sizeof($categoriesIds))
{
$query = 'INSERT INTO `'._DB_PREFIX_.'layered_cache` VALUES ';
$values = '';
}
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']));
/* We do not need to build the query for products when we are just updating the layered_category table */
if (!sizeof($categoriesIds))
{
$values .= '(';
$n = 0;
foreach ($columns AS $column)
{
if (!$n)
$values .= (int)$product['id_product'].',';
else
{
if (isset(${$column{0}}[ltrim($column, $column{0})]))
$values .= '1,';
else
$values .= '0,';
}
$n++;
}
$values = rtrim($values, ',').'),';
}
$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 ($toInsert)
Db::getInstance()->Execute(rtrim($queryCategory, ','));
}
/* We do not need to build the query for products when we are just updating the layered_category table */
if (!sizeof($categoriesIds))
$db->Execute($query.rtrim($values, ','));
}
function filterProducts($products, $selectedFilters, $excludeType = false)
{
$productsToKeep = array();
$filterByLetter = array('id_attribute_group' => 'a', 'id_feature' => 'f', 'category' => 'c', 'manufacturer' => 'id_manufacturer', 'quantity' => 'quantity', 'condition' => 'condition');
foreach ($selectedFilters AS $type => $filters)
{
if ($type == $excludeType OR !sizeof($filters))
continue;
else
{
$type = preg_match('/^(.*[^_0-9])/', $type, $res);
$type = $res[1];
switch ($type)
{
case 'id_attribute_group':
case 'id_feature':
case 'category':
foreach ($products AS $k => $product)
foreach ($filters AS $filter)
if (isset($product[$filterByLetter[$type].(int)$filter]))
$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;
}
foreach ($products AS $k => $product)
if (!in_array($k, $productsToKeep))
unset($products[(int)$k]);
$productsToKeep = array();
}
}
return $products;
}
}