Skip to content

Commit

Permalink
Add selections feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Hlavtox committed Sep 27, 2023
1 parent 08d5a52 commit 946ca0e
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 54 deletions.
2 changes: 1 addition & 1 deletion config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<module>
<name>ps_facetedsearch</name>
<displayName><![CDATA[Faceted search]]></displayName>
<version><![CDATA[3.13.2]]></version>
<version><![CDATA[3.14.0]]></version>
<description><![CDATA[Displays a block allowing multiple filters.]]></description>
<author><![CDATA[PrestaShop]]></author>
<tab><![CDATA[front_office_features]]></tab>
Expand Down
104 changes: 66 additions & 38 deletions ps_facetedsearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function __construct()
{
$this->name = 'ps_facetedsearch';
$this->tab = 'front_office_features';
$this->version = '3.13.2';
$this->version = '3.14.0';
$this->author = 'PrestaShop';
$this->need_instance = 0;
$this->bootstrap = true;
Expand Down Expand Up @@ -172,6 +172,9 @@ protected function getDefaultFilters()
'label' => 'Product price filter (slider)',
'slider' => true,
],
'layered_selection_extras' => [
'label' => 'Product extras filter',
],
];
}

Expand Down Expand Up @@ -216,7 +219,7 @@ public function install()
$productsCount = $this->getDatabase()->getValue('SELECT COUNT(*) FROM `' . _DB_PREFIX_ . 'product`');

if ($productsCount < static::LOCK_TEMPLATE_CREATION) {
$this->rebuildLayeredCache();
$this->createDefaultTemplate();
}

$this->rebuildPriceIndexTable();
Expand Down Expand Up @@ -591,7 +594,6 @@ public function indexProductPrices($idProduct, $smart = true)
*/
public function getContent()
{
global $cookie;
$message = '';

if (Tools::isSubmit('SubmitFilter')) {
Expand Down Expand Up @@ -990,7 +992,7 @@ public function rebuildLayeredStructure()
`controller` VARCHAR(64) NOT NULL,
`id_category` INT(10) UNSIGNED NOT NULL,
`id_value` INT(10) UNSIGNED NULL DEFAULT \'0\',
`type` ENUM(\'category\',\'id_feature\',\'id_attribute_group\',\'availability\',\'condition\',\'manufacturer\',\'weight\',\'price\') NOT NULL,
`type` ENUM(\'category\',\'id_feature\',\'id_attribute_group\',\'availability\',\'condition\',\'manufacturer\',\'weight\',\'price\',\'extras\') NOT NULL,
`position` INT(10) UNSIGNED NOT NULL,
`filter_type` int(10) UNSIGNED NOT NULL DEFAULT 0,
`filter_show_limit` int(10) UNSIGNED NOT NULL DEFAULT 0,
Expand Down Expand Up @@ -1027,17 +1029,25 @@ public function rebuildLayeredStructure()
}

/**
* Build layered cache
*
* @param array $productsIds
* @param array $categoriesIds
* @param bool $rebuildLayeredCategories
* This method creates the first initial filter after installing the module,
* from all available features and attributes.
*/
public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $rebuildLayeredCategories = true)
public function createDefaultTemplate()
{
@set_time_limit(0);

$filterData = ['categories' => [], 'controllers' => ['category']];
// Default filter data
$filterData = [
'categories' => [],
'controllers' => []
];

// Add all stable controllers (except search)
foreach ($this->getSupportedControllers() as $controller_name => $data) {
if ($controller_name != 'search') {
$filterData['controllers'][] = $controller_name;
}
}

/* Set memory limit to 128M only if current is lower */
$memoryLimit = Tools::getMemoryLimit();
Expand All @@ -1053,6 +1063,7 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
$joinProduct = Shop::addSqlAssociation('product', 'p');
$joinProductAttribute = Shop::addSqlAssociation('product_attribute', 'pa');

// Fetch all available attributes and their values
$attributeGroups = $this->query(
'SELECT a.id_attribute, a.id_attribute_group
FROM ' . _DB_PREFIX_ . 'attribute a
Expand All @@ -1062,17 +1073,16 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
' . $joinProduct . $joinProductAttribute . '
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(',', array_map('intval', $categoriesIds)) . ')' : '') . '
AND ' . $alias . '.active = 1 AND ' . $alias . '.`visibility` IN ("both", "catalog")
' . (count($productsIds) ? 'AND p.id_product IN (' . implode(',', array_map('intval', $productsIds)) . ')' : '')
WHERE c.active = 1
AND ' . $alias . '.active = 1 AND ' . $alias . '.`visibility` IN ("both", "catalog")'
);

$attributeGroupsById = [];
while ($row = $db->nextRow($attributeGroups)) {
$attributeGroupsById[(int) $row['id_attribute']] = (int) $row['id_attribute_group'];
}

// Fetch all available features and their values
$features = $this->query(
'SELECT fv.id_feature_value, fv.id_feature
FROM ' . _DB_PREFIX_ . 'feature_value fv
Expand All @@ -1081,9 +1091,8 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
' . $joinProduct . '
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(',', array_map('intval', $categoriesIds)) . ')' : '') . '
AND ' . $alias . '.active = 1 AND ' . $alias . '.`visibility` IN ("both", "catalog") ' .
(count($productsIds) ? 'AND p.id_product IN (' . implode(',', array_map('intval', $productsIds)) . ')' : '')
WHERE (fv.custom IS NULL OR fv.custom = 0) AND c.active = 1
AND ' . $alias . '.active = 1 AND ' . $alias . '.`visibility` IN ("both", "catalog") '
);

$featuresById = [];
Expand All @@ -1104,10 +1113,9 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
LEFT JOIN ' . _DB_PREFIX_ . 'product_attribute pa ON (pa.id_product = p.id_product)
' . $joinProduct . $joinProductAttribute . '
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(',', array_map('intval', $categoriesIds)) . ')' : '') . '
WHERE c.active = 1
AND ' . $alias . '.active = 1 AND ' . $alias . '.`visibility` IN ("both", "catalog")
' . (count($productsIds) ? 'AND p.id_product IN (' . implode(',', array_map('intval', $productsIds)) . ')' : '') .
' AND (fv.custom IS NULL OR fv.custom = 0)
AND (fv.custom IS NULL OR fv.custom = 0)
GROUP BY p.id_product'
);

Expand Down Expand Up @@ -1138,11 +1146,36 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
if (!isset($nCategories[(int) $idCategory])) {
$nCategories[(int) $idCategory] = 1;
}

// Stock filter
if (!isset($doneCategories[(int) $idCategory]['q'])) {
$filterData['layered_selection_stock'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['q'] = true;
$toInsert = true;
}

// Add extras filter
if (!isset($doneCategories[(int) $idCategory]['e'])) {
$filterData['layered_selection_extras'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['e'] = true;
$toInsert = true;
}

// Price filter
if (!isset($doneCategories[(int) $idCategory]['p'])) {
$filterData['layered_selection_price_slider'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['p'] = true;
$toInsert = true;
}

// Category filter
if (!isset($doneCategories[(int) $idCategory]['cat'])) {
$filterData['layered_selection_subcategories'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['cat'] = true;
$toInsert = true;
}

// Attribute filter
if (is_array($attributeGroupsById) && count($attributeGroupsById) > 0) {
foreach ($a as $kAttribute => $attribute) {
if (!isset($doneCategories[(int) $idCategory]['a' . (int) $attributeGroupsById[(int) $kAttribute]])) {
Expand All @@ -1152,7 +1185,9 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
}
}
}
if (is_array($attributeGroupsById) && count($attributeGroupsById) > 0) {

// Features filter
if (is_array($featuresById) && count($featuresById) > 0) {
foreach ($f as $kFeature => $feature) {
if (!isset($doneCategories[(int) $idCategory]['f' . (int) $featuresById[(int) $kFeature]])) {
$filterData['layered_selection_feat_' . (int) $featuresById[(int) $kFeature]] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
Expand All @@ -1162,38 +1197,30 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
}
}

if (!isset($doneCategories[(int) $idCategory]['q'])) {
$filterData['layered_selection_stock'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['q'] = true;
$toInsert = true;
}

// Manufacturer filter
if (!isset($doneCategories[(int) $idCategory]['m'])) {
$filterData['layered_selection_manufacturer'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['m'] = true;
$toInsert = true;
}

// Condition filter
if (!isset($doneCategories[(int) $idCategory]['c'])) {
$filterData['layered_selection_condition'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['c'] = true;
$toInsert = true;
}

// Weight filter
if (!isset($doneCategories[(int) $idCategory]['w'])) {
$filterData['layered_selection_weight_slider'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['w'] = true;
$toInsert = true;
}

if (!isset($doneCategories[(int) $idCategory]['p'])) {
$filterData['layered_selection_price_slider'] = ['filter_type' => Converter::WIDGET_TYPE_CHECKBOX, 'filter_show_limit' => 0];
$doneCategories[(int) $idCategory]['p'] = true;
$toInsert = true;
}
}
}

// If there are any filters available to setup, we will create the filter template
if ($toInsert) {
$this->getDatabase()->execute('INSERT INTO ' . _DB_PREFIX_ . 'layered_filter(name, filters, n_categories, date_add)
VALUES (\'' . sprintf($this->trans('My template %s', [], 'Modules.Facetedsearch.Admin'), date('Y-m-d')) . '\', \'' . pSQL(serialize($filterData)) . '\', ' . count($filterData['categories']) . ', NOW())');
Expand All @@ -1204,11 +1231,10 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
$this->getDatabase()->execute('INSERT INTO ' . _DB_PREFIX_ . 'layered_filter_shop (`id_layered_filter`, `id_shop`)
VALUES(' . $last_id . ', ' . (int) $idShop . ')');
}

if ($rebuildLayeredCategories) {
$this->buildLayeredCategories();
}
}

// Now we need to build layered_category table from this template
$this->buildLayeredCategories();
}

/**
Expand Down Expand Up @@ -1295,6 +1321,8 @@ public function buildLayeredCategories()
} elseif (substr($key, 0, 23) == 'layered_selection_feat_') {
$sqlInsert .= '(' . (int) $idCategory . ', \'' . $controller . '\', ' . (int) $idShop . ', ' . (int) str_replace('layered_selection_feat_', '', $key) . ',
\'id_feature\',' . (int) $n . ', ' . (int) $limit . ', ' . (int) $type . '),';
} elseif ($key == 'layered_selection_extras') {
$sqlInsert .= '(' . (int) $idCategory . ', \'' . $controller . '\', ' . (int) $idShop . ', NULL,\'extras\',' . (int) $n . ', ' . (int) $limit . ', ' . (int) $type . '),';
}

++$nbSqlValuesToInsert;
Expand Down
14 changes: 8 additions & 6 deletions src/Adapter/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,20 @@ public function getQuery()

// Process and generate all fields for the SQL query below
$orderField = $this->computeOrderByField($filterToTableMapping);
$selectFields = $this->computeSelectFields($filterToTableMapping);
$whereConditions = $this->computeWhereConditions($filterToTableMapping);
$joinConditions = $this->computeJoinConditions($filterToTableMapping);
$groupFields = $this->computeGroupByFields($filterToTableMapping);

// Now, let's build the query...
// If this query IS the initial population (the base table), we are selecting from product table
if ($this->getInitialPopulation() === null) {
$referenceTable = _DB_PREFIX_ . 'product';
// If not, we will call this function again but for the initial population
} else {
$referenceTable = '(' . $this->getInitialPopulation()->getQuery() . ')';
}

$selectFields = $this->computeSelectFields($filterToTableMapping);
$whereConditions = $this->computeWhereConditions($filterToTableMapping);
$joinConditions = $this->computeJoinConditions($filterToTableMapping);
$groupFields = $this->computeGroupByFields($filterToTableMapping);

$query = 'SELECT ' . implode(', ', $selectFields) . ' FROM ' . $referenceTable . ' p';

foreach ($joinConditions as $joinAliasInfos) {
Expand Down Expand Up @@ -327,7 +327,7 @@ protected function getFieldMapping()
(sp.from = \'0000-00-00 00:00:00\' OR \'' . date('Y-m-d H:i:s') . '\' >= sp.from) AND
(sp.to = \'0000-00-00 00:00:00\' OR \'' . date('Y-m-d H:i:s') . '\' <= sp.to)
)',
'joinType' => self::INNER_JOIN,
'joinType' => self::LEFT_JOIN,
],
];

Expand Down Expand Up @@ -805,6 +805,8 @@ public function useFiltersAsInitialPopulation()
'weight',
'price',
'sales',
'on_sale',
'date_add',
]
);

Expand Down
Loading

0 comments on commit 946ca0e

Please sign in to comment.