Skip to content

Commit

Permalink
Add highlights feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Hlavtox committed Sep 16, 2023
1 parent 08d5a52 commit 14190cb
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 19 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
16 changes: 13 additions & 3 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_highlights' => [
'label' => 'Product highlights filter',
],
];
}

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\',\'highlights\') 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 @@ -1191,6 +1193,12 @@ public function rebuildLayeredCache($productsIds = [], $categoriesIds = [], $reb
$doneCategories[(int) $idCategory]['p'] = true;
$toInsert = true;
}

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

Expand Down Expand Up @@ -1295,6 +1303,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_highlights') {
$sqlInsert .= '(' . (int) $idCategory . ', \'' . $controller . '\', ' . (int) $idShop . ', NULL,\'highlights\',' . (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
96 changes: 96 additions & 0 deletions src/Filters/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ public function getFilterBlock(
case 'availability':
$filterBlocks[] = $this->getAvailabilitiesBlock($filter, $selectedFilters);
break;
case 'highlights':
$filterBlocks[] = $this->getHighlightsBlock($filter, $selectedFilters);
break;
case 'manufacturer':
$filterBlocks[] = $this->getManufacturersBlock($filter, $selectedFilters, $idLang);
break;
Expand Down Expand Up @@ -532,6 +535,99 @@ private function getAvailabilitiesBlock($filter, $selectedFilters)
return $quantityBlock;
}

/**
* Gets block for extra product properties like "new", "on sale" and "discounted"
*
* @param array $filter
* @param array $selectedFilters
*
* @return array
*/
private function getHighlightsBlock($filter, $selectedFilters)
{
// Prepare array with options
$highlightsOptions = [];

// Products on sale - available everywhere
$highlightsOptions['sale'] = [
'name' => $this->context->getTranslator()->trans(
'On sale',
[],
'Modules.Facetedsearch.Shop'
),
'nbr' => 0,
];
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(Search::HIGHLIGHTS_FILTER);
$filteredSearchAdapter->addOperationsFilter(
Search::HIGHLIGHTS_FILTER,
[[['on_sale', [1], '=']]]
);
$highlightsOptions['sale']['nbr'] = $filteredSearchAdapter->count();

// New products - available everywhere except that page
if ($this->query->getQueryType() != 'new-products') {
$highlightsOptions['new'] = [
'name' => $this->context->getTranslator()->trans(
'New product',
[],
'Modules.Facetedsearch.Shop'
),
'nbr' => 0,
];
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('date_add');
$timeCondition = date(
'Y-m-d 00:00:00',
strtotime(
((int) Configuration::get('PS_NB_DAYS_NEW_PRODUCT') > 0 ?
'-' . ((int) Configuration::get('PS_NB_DAYS_NEW_PRODUCT') - 1) . ' days' :
'+ 1 days')
)
);
$filteredSearchAdapter->addFilter('date_add', ["'" . $timeCondition . "'"], '>');
$highlightsOptions['new']['nbr'] = $filteredSearchAdapter->count();
}

// Discounted products - available everywhere except that page
if ($this->query->getQueryType() != 'prices-drop') {
$highlightsOptions['discount'] = [
'name' => $this->context->getTranslator()->trans(
'Discounted',
[],
'Modules.Facetedsearch.Shop'
),
'nbr' => 0,
];
$filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(Search::HIGHLIGHTS_FILTER);
$filteredSearchAdapter->addOperationsFilter(
Search::HIGHLIGHTS_FILTER,
[[['reduction', [0], '>']]]
);
$highlightsOptions['discount']['nbr'] = $filteredSearchAdapter->count();
}

// If some filters are selected, we mark them as such
if (isset($selectedFilters['highlights'])) {
// We loop through selected filters and assign it to our options and remove the rest
foreach ($highlightsOptions as $key => $values) {
if (in_array($key, $selectedFilters['highlights'], true)) {
$highlightsOptions[$key]['checked'] = true;
}
}
}

$conditionBlock = [
'type_lite' => 'highlights',
'type' => 'highlights',
'id_key' => 0,
'name' => $this->context->getTranslator()->trans('Highlights', [], 'Modules.Facetedsearch.Shop'),
'values' => $highlightsOptions,
'filter_show_limit' => (int) $filter['filter_show_limit'],
'filter_type' => $filter['filter_type'],
];

return $conditionBlock;
}

/**
* Get the manufacturers filter block
*
Expand Down
35 changes: 35 additions & 0 deletions src/Filters/Converter.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Converter
const TYPE_MANUFACTURER = 'manufacturer';
const TYPE_PRICE = 'price';
const TYPE_WEIGHT = 'weight';
const TYPE_HIGHLIGHTS = 'highlights';

const PROPERTY_URL_NAME = 'url_name';
const PROPERTY_COLOR = 'color';
Expand Down Expand Up @@ -115,6 +116,7 @@ public function getFacetsFromFilterBlocks(array $filterBlocks)
switch ($filterBlock['type']) {
case self::TYPE_CATEGORY:
case self::TYPE_CONDITION:
case self::TYPE_HIGHLIGHTS:
case self::TYPE_MANUFACTURER:
case self::TYPE_AVAILABILITY:
case self::TYPE_ATTRIBUTE_GROUP:
Expand Down Expand Up @@ -351,6 +353,37 @@ public function createFacetedSearchFiltersFromQuery(ProductSearchQuery $query)
}
}
break;
case self::TYPE_HIGHLIGHTS:
if (!isset($receivedFilters[$filterLabel])) {
// No need to filter if no information
continue 2;
}

$highlightsOptions = [
$this->context->getTranslator()->trans(
'New product',
[],
'Modules.Facetedsearch.Shop'
) => 'new',
$this->context->getTranslator()->trans(
'On sale',
[],
'Modules.Facetedsearch.Shop'
) => 'sale',
$this->context->getTranslator()->trans(
'Discounted',
[],
'Modules.Facetedsearch.Shop'
) => 'discount',
];

$searchFilters[$filter['type']] = [];
foreach ($highlightsOptions as $highlightsOption => $optionId) {
if (isset($receivedFilters[$filterLabel]) && in_array($highlightsOption, $receivedFilters[$filterLabel])) {
$searchFilters[$filter['type']][] = $optionId;
}
}
break;
case self::TYPE_FEATURE:
$features = $this->dataAccessor->getFeatures($idLang);
foreach ($features as $feature) {
Expand Down Expand Up @@ -471,6 +504,8 @@ private function convertFilterTypeToLabel($filterType)
return $this->context->getTranslator()->trans('Weight', [], 'Modules.Facetedsearch.Shop');
case self::TYPE_CONDITION:
return $this->context->getTranslator()->trans('Condition', [], 'Modules.Facetedsearch.Shop');
case self::TYPE_HIGHLIGHTS:
return $this->context->getTranslator()->trans('Highlights', [], 'Modules.Facetedsearch.Shop');
case self::TYPE_AVAILABILITY:
return $this->context->getTranslator()->trans('Availability', [], 'Modules.Facetedsearch.Shop');
case self::TYPE_MANUFACTURER:
Expand Down
46 changes: 46 additions & 0 deletions src/Product/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
class Search
{
const STOCK_MANAGEMENT_FILTER = 'with_stock_management';
const HIGHLIGHTS_FILTER = 'highlights';

/**
* @var bool
Expand Down Expand Up @@ -173,6 +174,41 @@ private function addSearchFilters($selectedFilters)
$this->addFilter('id_category', $filterValues);
break;

case 'highlights':
// Filter for new products
if (in_array('new', $filterValues)) {
$timeCondition = date(
'Y-m-d 00:00:00',
strtotime(
((int) Configuration::get('PS_NB_DAYS_NEW_PRODUCT') > 0 ?
'-' . ((int) Configuration::get('PS_NB_DAYS_NEW_PRODUCT') - 1) . ' days' :
'+ 1 days')
)
);
// Reset filter to prevent two same filters if we are on new products page
$this->getSearchAdapter()->addFilter('date_add', ["'" . $timeCondition . "'"], '>');
}

// Filter for discounts - they must work as OR
$operationsFilter = [];
if (in_array('discount', $filterValues)) {
$operationsFilter[] = [
['reduction', [0], '>'],
];
}
if (in_array('sale', $filterValues)) {
$operationsFilter[] = [
['on_sale', [1], '='],
];
}
if (!empty($operationsFilter)) {
$this->getSearchAdapter()->addOperationsFilter(
self::HIGHLIGHTS_FILTER,
$operationsFilter
);
}
break;

case 'availability':
/*
* $filterValues options can have following values:
Expand Down Expand Up @@ -360,6 +396,11 @@ private function addControllerSpecificFilters()
* If there is a zero set to disable this feature, it creates unreachable condition.
*/
if ($this->query->getQueryType() == 'new-products') {
// We check if some specific filter of this type wasn't added before
if (!empty($this->getSearchAdapter()->getFilter('date_add'))) {
return;
}

$timeCondition = date(
'Y-m-d 00:00:00',
strtotime(
Expand All @@ -386,6 +427,11 @@ private function addControllerSpecificFilters()
* We are selecting products that have a specific price created meeting certain conditions.
*/
if ($this->query->getQueryType() == 'prices-drop') {
// We check if some specific filter of this type wasn't added before
if (!empty($this->getSearchAdapter()->getFilter('reduction'))) {
return;
}

$this->getSearchAdapter()->addFilter('reduction', [0], '>');
}

Expand Down
6 changes: 3 additions & 3 deletions src/Product/SearchProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ private function addEncodedFacetsToFilters(array $facets)
/**
* Remove the facet when there's only 1 result.
* Keep facet status when it's a slider.
* Keep facet status if it's a availability facet.
* Keep facet status if it's a availability or highlights facet.
*
* @param array $facets
* @param int $totalProducts
Expand Down Expand Up @@ -545,8 +545,8 @@ private function hideUselessFacets(array $facets, $totalProducts)
&& $usefulFiltersCount > 0
)
||
// If there is only one filter, but it's availability filter - we want this one to be displayed all the time
($usefulFiltersCount === 1 && $facet->getType() == 'availability')
// If there is only one filter, but it's availability or highlights filter - we want this one to be displayed all the time
($usefulFiltersCount === 1 && ($facet->getType() == 'availability' || $facet->getType() == 'highlights'))
);
// Other cases - hidden by default
}
Expand Down
Loading

0 comments on commit 14190cb

Please sign in to comment.