Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to filter by discounts, on sale and new products #880

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Hlavtox marked this conversation as resolved.
Show resolved Hide resolved
$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,
Hlavtox marked this conversation as resolved.
Show resolved Hide resolved
],
];

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

Expand Down
Loading