* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ namespace PrestaShop\Module\FacetedSearch\Filters; use Category; use Configuration; use Context; use Db; use Feature; use Group; use Manufacturer; use PrestaShop\Module\FacetedSearch\Adapter\InterfaceAdapter; use PrestaShop\Module\FacetedSearch\Definition\Availability; use PrestaShop\Module\FacetedSearch\Product\Search; use PrestaShop\PrestaShop\Core\Localization\Locale; use PrestaShop\PrestaShop\Core\Localization\Specification\NumberSymbolList; use PrestaShop\PrestaShop\Core\Product\Search\ProductSearchQuery; use PrestaShopDatabaseException; /** * Display filters block on navigation */ class Block { /** * @var InterfaceAdapter */ private $searchAdapter; /** * @var bool */ private $psStockManagement; /** * @var bool */ private $psOrderOutOfStock; /** * @var Context */ private $context; /** * @var Db */ private $database; /** * @var array */ private $attributesGroup; /** * @var DataAccessor */ private $dataAccessor; /** * @var Provider */ private $provider; /** * @var ProductSearchQuery */ private $query; public function __construct( InterfaceAdapter $searchAdapter, Context $context, Db $database, DataAccessor $dataAccessor, ProductSearchQuery $query, Provider $provider ) { $this->searchAdapter = $searchAdapter; $this->context = $context; $this->database = $database; $this->dataAccessor = $dataAccessor; $this->query = $query; $this->provider = $provider; } /** * @param int $nbProducts * @param array $selectedFilters * * @return array */ public function getFilterBlock( $nbProducts, $selectedFilters ) { $idLang = (int) $this->context->language->id; $idShop = (int) $this->context->shop->id; // Get category ID from the query or home category as a fallback $idCategory = (int) $this->query->getIdCategory(); if (empty($idCategory)) { $idCategory = (int) Configuration::get('PS_HOME_CATEGORY'); } // Get filters configured for the current query $filters = $this->provider->getFiltersForQuery($this->query, $idShop); $filterBlocks = []; // iterate through each filter, and the get corresponding filter block foreach ($filters as $filter) { switch ($filter['type']) { case 'price': $filterBlocks[] = $this->getPriceRangeBlock($filter, $selectedFilters, $nbProducts); break; case 'weight': $filterBlocks[] = $this->getWeightRangeBlock($filter, $selectedFilters, $nbProducts); break; case 'condition': $filterBlocks[] = $this->getConditionsBlock($filter, $selectedFilters); break; case 'availability': $filterBlocks[] = $this->getAvailabilitiesBlock($filter, $selectedFilters); break; case 'extras': $filterBlocks[] = $this->getHighlightsBlock($filter, $selectedFilters); break; case 'manufacturer': $filterBlocks[] = $this->getManufacturersBlock($filter, $selectedFilters, $idLang); break; case 'id_attribute_group': $filterBlocks = array_merge($filterBlocks, $this->getAttributesBlock($filter, $selectedFilters, $idLang)); break; case 'id_feature': $filterBlocks = array_merge($filterBlocks, $this->getFeaturesBlock($filter, $selectedFilters, $idLang)); break; case 'category': $parent = new Category($idCategory, $idLang); $filterBlocks[] = $this->getCategoriesBlock($filter, $selectedFilters, $idLang, $parent); } } return [ 'filters' => $filterBlocks, ]; } protected function showPriceFilter() { return Group::getCurrent()->show_prices; } /** * Get the filter block from the cache table * * @param string $filterHash * * @return array|null */ public function getFromCache($filterHash) { if (!Configuration::get('PS_LAYERED_CACHE_ENABLED')) { return null; } $row = $this->database->getRow( 'SELECT data FROM ' . _DB_PREFIX_ . 'layered_filter_block WHERE hash="' . pSQL($filterHash) . '"' ); if (!empty($row)) { return unserialize(current($row)); } return null; } /** * Insert the filter block into the cache table * * @param string $filterHash * @param array $data */ public function insertIntoCache($filterHash, $data) { if (!Configuration::get('PS_LAYERED_CACHE_ENABLED')) { return; } try { $this->database->execute( 'REPLACE INTO ' . _DB_PREFIX_ . 'layered_filter_block (hash, data) ' . 'VALUES ("' . $filterHash . '", "' . pSQL(serialize($data)) . '")' ); } catch (PrestaShopDatabaseException $e) { // Don't worry if the cache have invalid or duplicate hash } } /** * @param array $filter * @param array $selectedFilters * @param int $nbProducts * * @return array */ private function getPriceRangeBlock($filter, $selectedFilters, $nbProducts) { if (!$this->showPriceFilter()) { return []; } $priceSpecifications = $this->preparePriceSpecifications(); $priceBlock = [ 'type_lite' => 'price', 'type' => 'price', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Price', [], 'Modules.Facetedsearch.Shop'), 'max' => '0', 'min' => null, 'unit' => $this->context->currency->sign, 'specifications' => $priceSpecifications, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => Converter::WIDGET_TYPE_SLIDER, 'nbr' => $nbProducts, ]; list($priceMinFilter, $priceMaxFilter, $weightFilter) = $this->ignorePriceAndWeightFilters( $this->searchAdapter->getInitialPopulation() ); list($priceBlock['min'], $priceBlock['max']) = $this->searchAdapter->getInitialPopulation()->getMinMaxPriceValue(); $priceBlock['value'] = !empty($selectedFilters['price']) ? $selectedFilters['price'] : null; $this->restorePriceAndWeightFilters( $this->searchAdapter->getInitialPopulation(), $priceMinFilter, $priceMaxFilter, $weightFilter ); return $priceBlock; } /** * Price / weight filter block should not apply their own filters * otherwise they will always disappear if we filter on price / weight * because only one choice will remain * * @param InterfaceAdapter $filteredSearchAdapter * * @return array */ private function ignorePriceAndWeightFilters(InterfaceAdapter $filteredSearchAdapter) { // disable the current price and weight filters to compute ranges $priceMinFilter = $filteredSearchAdapter->getFilter('price_min'); $priceMaxFilter = $filteredSearchAdapter->getFilter('price_max'); $weightFilter = $filteredSearchAdapter->getFilter('weight'); $filteredSearchAdapter->resetFilter('price_min'); $filteredSearchAdapter->resetFilter('price_max'); $filteredSearchAdapter->resetFilter('weight'); return [ $priceMinFilter, $priceMaxFilter, $weightFilter, ]; } /** * Restore price and weight filters * * @param InterfaceAdapter $filteredSearchAdapter * @param int $priceMinFilter * @param int $priceMaxFilter * @param int $weightFilter */ private function restorePriceAndWeightFilters( $filteredSearchAdapter, $priceMinFilter, $priceMaxFilter, $weightFilter ) { // put back the price and weight filters $filteredSearchAdapter->setFilter('price_min', $priceMinFilter); $filteredSearchAdapter->setFilter('price_max', $priceMaxFilter); $filteredSearchAdapter->setFilter('weight', $weightFilter); } /** * Get the weight filter block * * @param array $filter * @param array $selectedFilters * @param int $nbProducts * * @return array */ private function getWeightRangeBlock($filter, $selectedFilters, $nbProducts) { $weightBlock = [ 'type_lite' => 'weight', 'type' => 'weight', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Weight', [], 'Modules.Facetedsearch.Shop'), 'max' => '0', 'min' => null, 'unit' => Configuration::get('PS_WEIGHT_UNIT'), 'specifications' => null, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => Converter::WIDGET_TYPE_SLIDER, 'value' => null, 'nbr' => $nbProducts, ]; list($priceMinFilter, $priceMaxFilter, $weightFilter) = $this->ignorePriceAndWeightFilters( $this->searchAdapter->getInitialPopulation() ); list($weightBlock['min'], $weightBlock['max']) = $this->searchAdapter->getInitialPopulation()->getMinMaxValue('p.weight'); if (empty($weightBlock['min']) && empty($weightBlock['max'])) { // We don't need to continue, no filter available return []; } $weightBlock['value'] = !empty($selectedFilters['weight']) ? $selectedFilters['weight'] : null; $this->restorePriceAndWeightFilters( $this->searchAdapter->getInitialPopulation(), $priceMinFilter, $priceMaxFilter, $weightFilter ); return $weightBlock; } /** * Get the condition filter block * * @param array $filter * @param array $selectedFilters * * @return array */ private function getConditionsBlock($filter, $selectedFilters) { $conditionArray = [ 'new' => [ 'name' => $this->context->getTranslator()->trans( 'New', [], 'Modules.Facetedsearch.Shop' ), 'nbr' => 0, ], 'used' => [ 'name' => $this->context->getTranslator()->trans( 'Used', [], 'Modules.Facetedsearch.Shop' ), 'nbr' => 0, ], 'refurbished' => [ 'name' => $this->context->getTranslator()->trans( 'Refurbished', [], 'Modules.Facetedsearch.Shop' ), 'nbr' => 0, ], ]; $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('condition'); $results = $filteredSearchAdapter->valueCount('condition'); foreach ($results as $key => $values) { $condition = $values['condition']; $count = $values['c']; $conditionArray[$condition]['nbr'] = $count; if (isset($selectedFilters['condition']) && in_array($condition, $selectedFilters['condition']) ) { $conditionArray[$condition]['checked'] = true; } } $conditionBlock = [ 'type_lite' => 'condition', 'type' => 'condition', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Condition', [], 'Modules.Facetedsearch.Shop'), 'values' => $conditionArray, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; return $conditionBlock; } /** * Get the quantities filter block * * @param array $filter * @param array $selectedFilters * * @return array */ private function getAvailabilitiesBlock($filter, $selectedFilters) { if ($this->psStockManagement === null) { $this->psStockManagement = (bool) Configuration::get('PS_STOCK_MANAGEMENT'); } if ($this->psOrderOutOfStock === null) { $this->psOrderOutOfStock = (bool) Configuration::get('PS_ORDER_OUT_OF_STOCK'); } // We only initialize the options if stock management is activated $availabilityOptions = []; if ($this->psStockManagement) { $availabilityOptions = [ Availability::IN_STOCK => [ 'name' => $this->context->getTranslator()->trans( 'In stock', [], 'Modules.Facetedsearch.Shop' ), 'nbr' => 0, ], Availability::AVAILABLE => [ 'name' => $this->context->getTranslator()->trans( 'Available', [], 'Modules.Facetedsearch.Shop' ), 'nbr' => 0, ], Availability::NOT_AVAILABLE => [ 'name' => $this->context->getTranslator()->trans( 'Not available', [], 'Modules.Facetedsearch.Shop' ), 'nbr' => 0, ], ]; $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(Search::STOCK_MANAGEMENT_FILTER); // Products without quantity in stock, with out-of-stock ordering disabled $filteredSearchAdapter->addOperationsFilter( Search::STOCK_MANAGEMENT_FILTER, [ [ ['quantity', [0], '<='], ['out_of_stock', !$this->psOrderOutOfStock ? [0, 2] : [0], '='], ], ] ); $availabilityOptions[Availability::NOT_AVAILABLE]['nbr'] = $filteredSearchAdapter->count(); // Products in stock, or with out-of-stock ordering enabled $filteredSearchAdapter->addOperationsFilter( Search::STOCK_MANAGEMENT_FILTER, [ [ ['out_of_stock', $this->psOrderOutOfStock ? [1, 2] : [1], '='], ], [ ['quantity', [0], '>'], ], ] ); $availabilityOptions[Availability::AVAILABLE]['nbr'] = $filteredSearchAdapter->count(); // Products in stock $filteredSearchAdapter->addOperationsFilter( Search::STOCK_MANAGEMENT_FILTER, [ [ ['quantity', [0], '>'], ], ] ); $availabilityOptions[Availability::IN_STOCK]['nbr'] = $filteredSearchAdapter->count(); // If some filter was selected, we want to show only this single filter, it does not make sense to show others if (isset($selectedFilters['availability'])) { // We loop through selected filters and assign it to our options and remove the rest foreach ($availabilityOptions as $key => $values) { if (in_array($key, $selectedFilters['availability'], true)) { $availabilityOptions[$key]['checked'] = true; } } } // Hide Available option if the count is the same as In stock, it doesn't make no sense // Product count is a reliable indicator here, because there can never be product IN STOCK that is not AVAILABLE // So if the counts match, it MUST BE the same products if ($availabilityOptions[Availability::AVAILABLE]['nbr'] == $availabilityOptions[Availability::IN_STOCK]['nbr']) { unset($availabilityOptions[Availability::AVAILABLE]); } } $quantityBlock = [ 'type_lite' => 'availability', 'type' => 'availability', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Availability', [], 'Modules.Facetedsearch.Shop'), 'values' => $availabilityOptions, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; 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 $extrasOptions = []; // Products on sale - available everywhere $extrasOptions['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], '=']]] ); $extrasOptions['sale']['nbr'] = $filteredSearchAdapter->count(); // New products - available everywhere except that page if ($this->query->getQueryType() != 'new-products') { $extrasOptions['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 . "'"], '>'); $extrasOptions['new']['nbr'] = $filteredSearchAdapter->count(); } // Discounted products - available everywhere except that page if ($this->query->getQueryType() != 'prices-drop') { $extrasOptions['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], '>']]] ); $extrasOptions['discount']['nbr'] = $filteredSearchAdapter->count(); } // If some filters are selected, we mark them as such if (isset($selectedFilters['extras'])) { // We loop through selected filters and assign it to our options and remove the rest foreach ($extrasOptions as $key => $values) { if (in_array($key, $selectedFilters['extras'], true)) { $extrasOptions[$key]['checked'] = true; } } } $conditionBlock = [ 'type_lite' => 'extras', 'type' => 'extras', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Selections', [], 'Modules.Facetedsearch.Shop'), 'values' => $extrasOptions, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; return $conditionBlock; } /** * Get the manufacturers filter block * * @param array $filter * @param array $selectedFilters * @param int $idLang * * @return array */ private function getManufacturersBlock($filter, $selectedFilters, $idLang) { $manufacturersArray = $manufacturers = []; // TODO - Needed to make manufacturer filter work (=disappear) on manufacturer page, not sure how it works. // (Manufacturer's page is the only page having id_manufacturer as the initial filter, that's why.) if ($this->query->getQueryType() == 'manufacturer') { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(); } else { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('id_manufacturer'); } $tempManufacturers = Manufacturer::getManufacturers(false, $idLang); if (empty($tempManufacturers)) { return $manufacturersArray; } foreach ($tempManufacturers as $key => $manufacturer) { $manufacturers[$manufacturer['id_manufacturer']] = $manufacturer; } $results = $filteredSearchAdapter->valueCount('id_manufacturer'); foreach ($results as $key => $values) { if (!isset($values['id_manufacturer'])) { continue; } $id_manufacturer = $values['id_manufacturer']; if (empty($manufacturers[$id_manufacturer]['name'])) { continue; } $count = $values['c']; $manufacturersArray[$id_manufacturer] = [ 'name' => $manufacturers[$id_manufacturer]['name'], 'nbr' => $count, ]; if (isset($selectedFilters['manufacturer']) && in_array($id_manufacturer, $selectedFilters['manufacturer']) ) { $manufacturersArray[$id_manufacturer]['checked'] = true; } } $manufacturerBlock = [ 'type_lite' => 'manufacturer', 'type' => 'manufacturer', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Brand', [], 'Modules.Facetedsearch.Shop'), 'values' => $manufacturersArray, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; return $manufacturerBlock; } /** * Get the attributes filter block * * @param array $filter * @param array $selectedFilters * @param int $idLang * * @return array */ private function getAttributesBlock($filter, $selectedFilters, $idLang) { $attributesBlock = []; $filteredSearchAdapter = null; $idAttributeGroup = $filter['id_value']; if (!empty($selectedFilters['id_attribute_group'])) { foreach ($selectedFilters['id_attribute_group'] as $key => $selectedFilter) { if ($key == $idAttributeGroup) { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('with_attributes_' . $idAttributeGroup); break; } } } if (!$filteredSearchAdapter) { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(); } $attributesGroup = $this->dataAccessor->getAttributesGroups($idLang); if ($attributesGroup === []) { return $attributesBlock; } $attributes = $this->dataAccessor->getAttributes($idLang, $idAttributeGroup); $filteredSearchAdapter->addOperationsFilter( 'id_attribute_group_' . $idAttributeGroup, [[['id_attribute_group', [(int) $idAttributeGroup]]]] ); $results = $filteredSearchAdapter->valueCount('id_attribute'); foreach ($results as $key => $values) { $idAttribute = $values['id_attribute']; if (!isset($attributes[$idAttribute])) { continue; } $count = $values['c']; $attribute = $attributes[$idAttribute]; $idAttributeGroup = $attribute['id_attribute_group']; if (!isset($attributesBlock[$idAttributeGroup])) { $attributeGroup = $attributesGroup[$idAttributeGroup]; $attributesBlock[$idAttributeGroup] = [ 'type_lite' => 'id_attribute_group', 'type' => 'id_attribute_group', 'id_key' => $idAttributeGroup, 'name' => $attributeGroup['attribute_group_name'], 'is_color_group' => (bool) $attributeGroup['is_color_group'], 'values' => [], 'url_name' => $attributeGroup['url_name'], 'meta_title' => $attributeGroup['meta_title'], 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; } $attributesBlock[$idAttributeGroup]['values'][$idAttribute] = [ 'name' => $attribute['name'], 'nbr' => $count, 'url_name' => $attribute['url_name'], 'meta_title' => $attribute['meta_title'], ]; if ($attributesBlock[$idAttributeGroup]['is_color_group'] !== false) { $attributesBlock[$idAttributeGroup]['values'][$idAttribute]['color'] = $attribute['color']; } if (array_key_exists('id_attribute_group', $selectedFilters)) { foreach ($selectedFilters['id_attribute_group'] as $selectedAttribute) { if (in_array($idAttribute, $selectedAttribute)) { $attributesBlock[$idAttributeGroup]['values'][$idAttribute]['checked'] = true; } } } } foreach ($attributesBlock as $idAttributeGroup => $value) { $attributesBlock[$idAttributeGroup]['values'] = $this->sortByKey($attributes, $value['values']); } $attributesBlock = $this->sortByKey($attributesGroup, $attributesBlock); return $attributesBlock; } /** * Sort an array using the same key order than the sortedReferenceArray * * @param array $sortedReferenceArray * @param array $array * * @return array */ private function sortByKey(array $sortedReferenceArray, $array) { $sortedArray = []; // iterate in the original order foreach ($sortedReferenceArray as $key => $value) { if (array_key_exists($key, $array)) { $sortedArray[$key] = $array[$key]; } } return $sortedArray; } /** * Get the features filter block * * @param array $filter * @param array $selectedFilters * @param int $idLang * * @return array */ private function getFeaturesBlock($filter, $selectedFilters, $idLang) { $featureBlock = []; $idFeature = $filter['id_value']; $filteredSearchAdapter = null; if (!empty($selectedFilters['id_feature'])) { foreach ($selectedFilters['id_feature'] as $key => $selectedFilter) { if ($key == $idFeature) { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('with_features_' . $idFeature); break; } } } if (!$filteredSearchAdapter) { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter(); } $features = $this->dataAccessor->getFeatures($idLang); if (empty($features)) { return []; } $filteredSearchAdapter->addOperationsFilter( 'id_feature_' . $idFeature, [[['id_feature', [(int) $idFeature]]]] ); $filteredSearchAdapter->addSelectField('id_feature'); $results = $filteredSearchAdapter->valueCount('id_feature_value'); foreach ($results as $key => $values) { $idFeatureValue = $values['id_feature_value']; $idFeature = $values['id_feature']; $count = $values['c']; $feature = $features[$idFeature]; if (!isset($featureBlock[$idFeature])) { $features[$idFeature]['featureValues'] = $this->dataAccessor->getFeatureValues($idFeature, $idLang); $featureBlock[$idFeature] = [ 'type_lite' => 'id_feature', 'type' => 'id_feature', 'id_key' => $idFeature, 'values' => [], 'name' => $feature['name'], 'url_name' => $feature['url_name'], 'meta_title' => $feature['meta_title'], 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; } $featureValues = $features[$idFeature]['featureValues']; if (!isset($featureValues[$idFeatureValue]['value'])) { continue; } $featureBlock[$idFeature]['values'][$idFeatureValue] = [ 'nbr' => $count, 'name' => $featureValues[$idFeatureValue]['value'], 'url_name' => $featureValues[$idFeatureValue]['url_name'], 'meta_title' => $featureValues[$idFeatureValue]['meta_title'], ]; if (array_key_exists('id_feature', $selectedFilters)) { foreach ($selectedFilters['id_feature'] as $selectedFeature) { if (in_array($idFeatureValue, $selectedFeature)) { $featureBlock[$feature['id_feature']]['values'][$idFeatureValue]['checked'] = true; } } } } $featureBlock = $this->sortFeatureBlock($featureBlock); return $featureBlock; } /** * Natural sort multi-dimensional feature array * * @param array $featureBlock * * @return array */ private function sortFeatureBlock($featureBlock) { //Natural sort foreach ($featureBlock as $key => $value) { $temp = []; foreach ($featureBlock[$key]['values'] as $idFeatureValue => $featureValueInfos) { $temp[$idFeatureValue] = $featureValueInfos['name']; } natcasesort($temp); $temp2 = []; foreach ($temp as $keytemp => $valuetemp) { $temp2[$keytemp] = $featureBlock[$key]['values'][$keytemp]; } $featureBlock[$key]['values'] = $temp2; } return $featureBlock; } /** * Add the categories filter condition based on the parent and config variables * * @param InterfaceAdapter $filteredSearchAdapter * @param Category $parent */ private function addCategoriesBlockFilters(InterfaceAdapter $filteredSearchAdapter, $parent) { if (Group::isFeatureActive()) { $userGroups = ($this->context->customer->isLogged() ? $this->context->customer->getGroups() : [ Configuration::get( 'PS_UNIDENTIFIED_GROUP' ), ]); $filteredSearchAdapter->addFilter('id_group', $userGroups); } $depth = (int) Configuration::get('PS_LAYERED_FILTER_CATEGORY_DEPTH', null, null, null, 1); if ($depth) { $levelDepth = $parent->level_depth; $filteredSearchAdapter->addFilter('level_depth', [$depth + $levelDepth], '<='); } $filteredSearchAdapter->addFilter('nleft', [$parent->nleft], '>'); $filteredSearchAdapter->addFilter('nright', [$parent->nright], '<'); } /** * Get the categories filter block * * @param array $filter * @param array $selectedFilters * @param int $idLang * @param Category $parent * * @return array */ private function getCategoriesBlock($filter, $selectedFilters, $idLang, $parent) { $filteredSearchAdapter = $this->searchAdapter->getFilteredSearchAdapter('id_category'); $this->addCategoriesBlockFilters($filteredSearchAdapter, $parent); $categoryArray = []; $categories = Category::getAllCategoriesName( null, $idLang, true, null, true, '', 'ORDER BY c.nleft, c.position' ); foreach ($categories as $key => $value) { $categories[$value['id_category']] = $value; } $results = $filteredSearchAdapter->valueCount('id_category'); foreach ($results as $key => $values) { $idCategory = $values['id_category']; if (!isset($categories[$idCategory])) { // Category can sometimes not be found in case of multistore // plus waiting for indexation continue; } $count = $values['c']; $categoryArray[$idCategory] = [ 'name' => $categories[$idCategory]['name'], 'nbr' => $count, ]; if (isset($selectedFilters['category']) && in_array($idCategory, $selectedFilters['category'])) { $categoryArray[$idCategory]['checked'] = true; } } $categoryBlock = [ 'type_lite' => 'category', 'type' => 'category', 'id_key' => 0, 'name' => $this->context->getTranslator()->trans('Categories', [], 'Modules.Facetedsearch.Shop'), 'values' => $categoryArray, 'filter_show_limit' => (int) $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], ]; return $categoryBlock; } /** * Prepare price specifications to display cldr prices. * * @return array */ private function preparePriceSpecifications() { /* @var Currency */ $currency = $this->context->currency; // New method since PS 1.7.6 if (isset($this->context->currentLocale) && method_exists($this->context->currentLocale, 'getPriceSpecification')) { /* @var PriceSpecification */ $priceSpecification = $this->context->currentLocale->getPriceSpecification($currency->iso_code); /* @var NumberSymbolList */ $symbolList = $priceSpecification->getSymbolsByNumberingSystem(Locale::NUMBERING_SYSTEM_LATIN); $symbol = [ $symbolList->getDecimal(), $symbolList->getGroup(), $symbolList->getList(), $symbolList->getPercentSign(), $symbolList->getMinusSign(), $symbolList->getPlusSign(), $symbolList->getExponential(), $symbolList->getSuperscriptingExponent(), $symbolList->getPerMille(), $symbolList->getInfinity(), $symbolList->getNaN(), ]; return array_merge( ['symbol' => $symbol], $priceSpecification->toArray() ); } // Default symbol configuration $symbol = [ '.', ',', ';', '%', '-', '+', 'E', '×', '‰', '∞', 'NaN', ]; // The property `$precision` exists only from PS 1.7.6. On previous versions, all prices have 2 decimals $precision = isset($currency->precision) ? $currency->precision : 2; $formats = explode(';', $currency->format); if (count($formats) > 1) { $positivePattern = $formats[0]; $negativePattern = $formats[1]; } else { $positivePattern = $currency->format; $negativePattern = $currency->format; } return [ 'positivePattern' => $positivePattern, 'negativePattern' => $negativePattern, 'symbol' => $symbol, 'maxFractionDigits' => $precision, 'minFractionDigits' => $precision, 'groupingUsed' => true, 'primaryGroupSize' => 3, 'secondaryGroupSize' => 3, 'currencyCode' => $currency->iso_code, 'currencySymbol' => $currency->sign, ]; } }