public function test_ranges_are_merged_and_max_boudary_is_rounded() { $ranges = [['min' => 10, 'max' => 30, 'count' => 4], ['min' => 30, 'max' => 97, 'count' => 12], ['min' => 100, 'max' => 140, 'count' => 20]]; $aggregator = new Ps_FacetedsearchRangeAggregator(); $actual = $aggregator->mergeRanges($ranges, 3); $this->assertEquals([['min' => 10, 'max' => 30, 'count' => 4], ['min' => 30, 'max' => 100, 'count' => 12], ['min' => 100, 'max' => 140, 'count' => 20]], $actual); }
public function getFilterBlock($selected_filters = array(), $compute_range_filters = true) { global $cookie; // Remove all empty selected filters foreach ($selected_filters as $key => $value) { switch ($key) { case 'price': case 'weight': if ($value[0] === '' && $value[1] === '') { unset($selected_filters[$key]); } break; default: if ($value == '' || $value == array()) { unset($selected_filters[$key]); } break; } } static $latest_selected_filters = null; static $latest_cat_restriction = null; static $productCache = array(); $context = Context::getContext(); $id_lang = $context->language->id; $currency = $context->currency; $id_shop = (int) $context->shop->id; $alias = 'product_shop'; $id_parent = (int) Tools::getValue('id_category', Tools::getValue('id_category_layered', Configuration::get('PS_HOME_CATEGORY'))); $parent = new Category((int) $id_parent, $id_lang); /* Get the filters for the current category */ $filters = Db::getInstance(_PS_USE_SQL_SLAVE_)->executeS(' SELECT type, id_value, filter_show_limit, filter_type FROM ' . _DB_PREFIX_ . 'layered_category WHERE id_category = ' . (int) $id_parent . ' AND id_shop = ' . $id_shop . ' GROUP BY `type`, id_value ORDER BY position ASC'); /* Create the table which contains all the id_product in a cat or a tree */ $current_cat_restriction = 'parent_' . ($this->ps_layered_full_tree ? (int) $parent->nleft . '_' . (int) $parent->nright : (int) $id_parent . '_context_' . (int) $context->shop->id); if ($current_cat_restriction != $latest_cat_restriction) { Db::getInstance()->execute('DROP TEMPORARY TABLE IF EXISTS ' . _DB_PREFIX_ . 'cat_restriction', false); Db::getInstance()->execute('CREATE TEMPORARY TABLE ' . _DB_PREFIX_ . 'cat_restriction ENGINE=MEMORY SELECT DISTINCT cp.id_product, p.id_manufacturer, product_shop.condition, p.weight FROM ' . _DB_PREFIX_ . 'category c STRAIGHT_JOIN ' . _DB_PREFIX_ . 'category_product cp ON (c.id_category = cp.id_category AND ' . ($this->ps_layered_full_tree ? 'c.nleft >= ' . (int) $parent->nleft . ' AND c.nright <= ' . (int) $parent->nright : 'c.id_category = ' . (int) $id_parent) . ' AND c.active = 1) STRAIGHT_JOIN ' . _DB_PREFIX_ . 'product_shop product_shop ON (product_shop.id_product = cp.id_product AND product_shop.id_shop = ' . (int) $context->shop->id . ') STRAIGHT_JOIN ' . _DB_PREFIX_ . 'product p ON (p.id_product=cp.id_product) WHERE product_shop.`active` = 1 AND product_shop.`visibility` IN ("both", "catalog")', false); Db::getInstance()->execute('ALTER TABLE ' . _DB_PREFIX_ . 'cat_restriction ADD PRIMARY KEY (id_product), ADD KEY `id_manufacturer` (`id_manufacturer`,`id_product`) USING BTREE, ADD KEY `condition` (`condition`,`id_product`) USING BTREE, ADD KEY `weight` (`weight`,`id_product`) USING BTREE', false); $latest_cat_restriction = $current_cat_restriction; } $filter_blocks = array(); foreach ($filters as $filter) { $cacheKey = $filter['type'] . '-' . $filter['id_value']; if ($current_cat_restriction == $latest_cat_restriction && $latest_selected_filters == $selected_filters && isset($productCache[$cacheKey])) { $products = $productCache[$cacheKey]; } else { $sql_query = array('select' => '', 'from' => '', 'join' => '', 'where' => '', 'group' => ''); switch ($filter['type']) { case 'price': $sql_query['select'] = 'SELECT p.`id_product`, psi.price_min, psi.price_max '; // price slider is not filter dependent $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'cat_restriction p'; $sql_query['join'] = 'INNER JOIN `' . _DB_PREFIX_ . 'layered_price_index` psi ON (psi.id_product = p.id_product AND psi.id_currency = ' . (int) $context->currency->id . ' AND psi.id_shop=' . (int) $context->shop->id . ')'; $sql_query['where'] = 'WHERE 1'; break; case 'weight': $sql_query['select'] = 'SELECT p.`id_product`, p.`weight` '; // price slider is not filter dependent $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'cat_restriction p'; $sql_query['where'] = 'WHERE 1'; break; case 'condition': $sql_query['select'] = 'SELECT DISTINCT p.`id_product`, product_shop.`condition` '; $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'cat_restriction p'; $sql_query['where'] = 'WHERE 1'; $sql_query['from'] .= Shop::addSqlAssociation('product', 'p'); break; case 'quantity': $sql_query['select'] = 'SELECT DISTINCT p.`id_product`, sa.`quantity`, sa.`out_of_stock` '; $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'cat_restriction p'; $sql_query['join'] .= 'LEFT JOIN `' . _DB_PREFIX_ . 'stock_available` sa ON (sa.id_product = p.id_product AND sa.id_product_attribute=0 ' . StockAvailable::addSqlShopRestriction(null, null, 'sa') . ') '; $sql_query['where'] = 'WHERE 1'; break; case 'manufacturer': $sql_query['select'] = 'SELECT COUNT(DISTINCT p.id_product) nbr, m.id_manufacturer, m.name '; $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'cat_restriction p INNER JOIN ' . _DB_PREFIX_ . 'manufacturer m ON (m.id_manufacturer = p.id_manufacturer) '; $sql_query['where'] = 'WHERE 1'; $sql_query['group'] = ' GROUP BY p.id_manufacturer ORDER BY m.name'; break; case 'id_attribute_group': // attribute group $sql_query['select'] = ' SELECT COUNT(DISTINCT lpa.id_product) nbr, lpa.id_attribute_group, a.color, al.name attribute_name, agl.public_name attribute_group_name , lpa.id_attribute, ag.is_color_group, liagl.url_name name_url_name, liagl.meta_title name_meta_title, lial.url_name value_url_name, lial.meta_title value_meta_title'; $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'layered_product_attribute lpa INNER JOIN ' . _DB_PREFIX_ . 'attribute a ON a.id_attribute = lpa.id_attribute INNER JOIN ' . _DB_PREFIX_ . 'attribute_lang al ON al.id_attribute = a.id_attribute AND al.id_lang = ' . (int) $id_lang . ' INNER JOIN ' . _DB_PREFIX_ . 'cat_restriction p ON p.id_product = lpa.id_product INNER JOIN ' . _DB_PREFIX_ . 'attribute_group ag ON ag.id_attribute_group = lpa.id_attribute_group INNER JOIN ' . _DB_PREFIX_ . 'attribute_group_lang agl ON agl.id_attribute_group = lpa.id_attribute_group AND agl.id_lang = ' . (int) $id_lang . ' LEFT JOIN ' . _DB_PREFIX_ . 'layered_indexable_attribute_group_lang_value liagl ON (liagl.id_attribute_group = lpa.id_attribute_group AND liagl.id_lang = ' . (int) $id_lang . ') LEFT JOIN ' . _DB_PREFIX_ . 'layered_indexable_attribute_lang_value lial ON (lial.id_attribute = lpa.id_attribute AND lial.id_lang = ' . (int) $id_lang . ') '; $sql_query['where'] = 'WHERE lpa.id_attribute_group = ' . (int) $filter['id_value']; $sql_query['where'] .= ' AND lpa.`id_shop` = ' . (int) $context->shop->id; $sql_query['group'] = ' GROUP BY lpa.id_attribute ORDER BY ag.`position` ASC, a.`position` ASC'; break; case 'id_feature': $id_lang = (int) $id_lang; $sql_query['select'] = 'SELECT fl.name feature_name, fp.id_feature, fv.id_feature_value, fvl.value, COUNT(DISTINCT p.id_product) nbr, lifl.url_name name_url_name, lifl.meta_title name_meta_title, lifvl.url_name value_url_name, lifvl.meta_title value_meta_title '; $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'feature_product fp INNER JOIN ' . _DB_PREFIX_ . 'cat_restriction p ON p.id_product = fp.id_product LEFT JOIN ' . _DB_PREFIX_ . 'feature_lang fl ON (fl.id_feature = fp.id_feature AND fl.id_lang = ' . $id_lang . ') INNER JOIN ' . _DB_PREFIX_ . 'feature_value fv ON (fv.id_feature_value = fp.id_feature_value AND (fv.custom IS NULL OR fv.custom = 0)) LEFT JOIN ' . _DB_PREFIX_ . 'feature_value_lang fvl ON (fvl.id_feature_value = fp.id_feature_value AND fvl.id_lang = ' . $id_lang . ') LEFT JOIN ' . _DB_PREFIX_ . 'layered_indexable_feature_lang_value lifl ON (lifl.id_feature = fp.id_feature AND lifl.id_lang = ' . $id_lang . ') LEFT JOIN ' . _DB_PREFIX_ . 'layered_indexable_feature_value_lang_value lifvl ON (lifvl.id_feature_value = fp.id_feature_value AND lifvl.id_lang = ' . $id_lang . ') '; $sql_query['where'] = 'WHERE fp.id_feature = ' . (int) $filter['id_value']; $sql_query['group'] = 'GROUP BY fv.id_feature_value '; break; case 'category': if (Group::isFeatureActive()) { $this->user_groups = $this->context->customer->isLogged() ? $this->context->customer->getGroups() : array(Configuration::get('PS_UNIDENTIFIED_GROUP')); } $depth = Configuration::get('PS_LAYERED_FILTER_CATEGORY_DEPTH'); if ($depth === false) { $depth = 1; } $sql_query['select'] = ' SELECT c.id_category, c.id_parent, cl.name, (SELECT count(DISTINCT p.id_product) # '; $sql_query['from'] = ' FROM ' . _DB_PREFIX_ . 'category_product cp LEFT JOIN ' . _DB_PREFIX_ . 'product p ON (p.id_product = cp.id_product) '; $sql_query['where'] = ' WHERE cp.id_category = c.id_category AND ' . $alias . '.active = 1 AND ' . $alias . '.`visibility` IN ("both", "catalog")'; $sql_query['group'] = ') count_products FROM ' . _DB_PREFIX_ . 'category c LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON (cl.id_category = c.id_category AND cl.`id_shop` = ' . (int) Context::getContext()->shop->id . ' and cl.id_lang = ' . (int) $id_lang . ') '; if (Group::isFeatureActive()) { $sql_query['group'] .= 'RIGHT JOIN ' . _DB_PREFIX_ . 'category_group cg ON (cg.id_category = c.id_category AND cg.`id_group` IN (' . implode(', ', $this->user_groups) . ')) '; } $sql_query['group'] .= 'WHERE c.nleft > ' . (int) $parent->nleft . ' AND c.nright < ' . (int) $parent->nright . ' ' . ($depth ? 'AND c.level_depth <= ' . ($parent->level_depth + (int) $depth) : '') . ' AND c.active = 1 GROUP BY c.id_category ORDER BY c.nleft, c.position'; $sql_query['from'] .= Shop::addSqlAssociation('product', 'p'); } /* * Loop over the filters again to add their restricting clauses to the sql * query being built. */ foreach ($filters as $filter_tmp) { $method_name = 'get' . ucfirst($filter_tmp['type']) . 'FilterSubQuery'; if (method_exists('Ps_Facetedsearch', $method_name)) { $no_subquery_necessary = $filter['type'] == $filter_tmp['type'] && $filter['id_value'] == $filter_tmp['id_value'] && ($filter['id_value'] || $filter['type'] === 'category' || $filter['type'] === 'condition' || $filter['type'] === 'quantity'); if ($no_subquery_necessary) { // Do not apply the same filter twice, i.e. when the primary filter // and the sub filter have the same type and same id_value. $sub_query_filter = array(); } else { // The next part is hard to follow, but here's what I think this // bit of code does: // It checks whether some filters in the current facet // (our current iterator, $filter_tmp), which // is part of the "template" for this category, were selected by the // user. // If so, it formats the current facet // in yet another strange way that is appropriate // for calling get***FilterSubQuery. // For instance, if inside $selected_filters I have: // [id_attribute_group] => Array // ( // [8] => 3_8 // [11] => 3_11 // ) // And $filter_tmp is: // Array // ( // [type] => id_attribute_group // [id_value] => 3 // [filter_show_limit] => 0 // [filter_type] => 0 // ) // Then $selected_filters_cleaned will be: // Array // ( // [0] => 8 // [1] => 11 // ) // The strategy employed is different whether we're dealing with // a facet with an "id_value" (this is the most complex case involving // the usual underscore-encoded values deserialization witchcraft) // such as "id_attribute_group" or with a facet without id_value. // In the latter case we're in luck because we can just use the // facet in $selected_filters directly. if (!is_null($filter_tmp['id_value'])) { $selected_filters_cleaned = $this->cleanFilterByIdValue(@$selected_filters[$filter_tmp['type']], $filter_tmp['id_value']); } else { $selected_filters_cleaned = @$selected_filters[$filter_tmp['type']]; } $ignore_join = $filter['type'] == $filter_tmp['type']; // Prepare the new bits of SQL query. // $ignore_join is set to true when the sub-facet // is of the same "type" as the main facet. This way // the method ($method_name) knows that the tables it needs are already // there and don't need to be joined again. $sub_query_filter = self::$method_name($selected_filters_cleaned, $ignore_join); } // Now we "merge" the query from the subfilter with the main query foreach ($sub_query_filter as $key => $value) { $sql_query[$key] .= $value; } } } $products = false; if (!empty($sql_query['from'])) { $assembled_sql_query = implode("\n", array($sql_query['select'], $sql_query['from'], $sql_query['join'], $sql_query['where'], $sql_query['group'])); $products = Db::getInstance()->executeS($assembled_sql_query, true, false); } // price & weight have slidebar, so it's ok to not complete recompute the product list if (!empty($selected_filters['price']) && $filter['type'] != 'price' && $filter['type'] != 'weight') { $products = self::filterProductsByPrice(@$selected_filters['price'], $products); } $productCache[$cacheKey] = $products; } switch ($filter['type']) { case 'price': if ($this->showPriceFilter()) { $price_array = array('type_lite' => 'price', 'type' => 'price', 'id_key' => 0, 'name' => $this->trans('Price', array(), 'Modules.FacetedSearch.Shop'), 'slider' => true, 'max' => '0', 'min' => null, 'unit' => $currency->sign, 'format' => $currency->format, 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], 'list_of_values' => array()); if ($compute_range_filters && isset($products) && $products) { $rangeAggregator = new Ps_FacetedsearchRangeAggregator(); $aggregatedRanges = $rangeAggregator->aggregateRanges($products, 'price_min', 'price_max'); $price_array['min'] = $aggregatedRanges['min']; $price_array['max'] = $aggregatedRanges['max']; $mergedRanges = $rangeAggregator->mergeRanges($aggregatedRanges['ranges'], 10); $price_array['list_of_values'] = array_map(function (array $range) { return array(0 => $range['min'], 1 => $range['max'], 'nbr' => $range['count']); }, $mergedRanges); $price_array['values'] = array($price_array['min'], $price_array['max']); } $filter_blocks[] = $price_array; } break; case 'weight': $weight_array = array('type_lite' => 'weight', 'type' => 'weight', 'id_key' => 0, 'name' => $this->trans('Weight', array(), 'Modules.FacetedSearch.Shop'), 'slider' => true, 'max' => '0', 'min' => null, 'unit' => Configuration::get('PS_WEIGHT_UNIT'), 'format' => 5, 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type'], 'list_of_values' => array()); if ($compute_range_filters && isset($products) && $products) { $rangeAggregator = new Ps_FacetedsearchRangeAggregator(); $aggregatedRanges = $rangeAggregator->getRangesFromList($products, 'weight'); $weight_array['min'] = $aggregatedRanges['min']; $weight_array['max'] = $aggregatedRanges['max']; $mergedRanges = $rangeAggregator->mergeRanges($aggregatedRanges['ranges'], 10); $weight_array['list_of_values'] = array_map(function (array $range) { return array(0 => $range['min'], 1 => $range['max'], 'nbr' => $range['count']); }, $mergedRanges); if (empty($weight_array['list_of_values']) && isset($selected_filters['weight'])) { // in case we don't have a list of values, // add the original one. // This may happen when e.g. all products // weigh 0. $weight_array['list_of_values'] = array(array(0 => $selected_filters['weight'][0], 1 => $selected_filters['weight'][1], 'nbr' => count($products))); } $weight_array['values'] = array($weight_array['min'], $weight_array['max']); } $filter_blocks[] = $weight_array; break; case 'condition': $condition_array = array('new' => array('name' => $this->trans('New', array(), 'Modules.FacetedSearch.Shop'), 'nbr' => 0), 'used' => array('name' => $this->trans('Used', array(), 'Modules.FacetedSearch.Shop'), 'nbr' => 0), 'refurbished' => array('name' => $this->trans('Refurbished', array(), 'Modules.FacetedSearch.Shop'), 'nbr' => 0)); if (isset($products) && $products) { foreach ($products as $product) { if (isset($selected_filters['condition']) && in_array($product['condition'], $selected_filters['condition'])) { $condition_array[$product['condition']]['checked'] = true; } } } foreach ($condition_array as $key => $condition) { if (isset($selected_filters['condition']) && in_array($key, $selected_filters['condition'])) { $condition_array[$key]['checked'] = true; } } if (isset($products) && $products) { foreach ($products as $product) { if (isset($condition_array[$product['condition']])) { ++$condition_array[$product['condition']]['nbr']; } } } $filter_blocks[] = array('type_lite' => 'condition', 'type' => 'condition', 'id_key' => 0, 'name' => $this->trans('Condition', array(), 'Modules.FacetedSearch.Shop'), 'values' => $condition_array, 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type']); break; case 'quantity': $quantity_array = array(0 => array('name' => $this->trans('Not available', array(), 'Modules.FacetedSearch.Shop'), 'nbr' => 0), 1 => array('name' => $this->trans('In stock', array(), 'Modules.FacetedSearch.Shop'), 'nbr' => 0)); foreach ($quantity_array as $key => $quantity) { if (isset($selected_filters['quantity']) && in_array($key, $selected_filters['quantity'])) { $quantity_array[$key]['checked'] = true; } } if (isset($products) && $products) { foreach ($products as $product) { //If oosp move all not available quantity to available quantity if ((int) $product['quantity'] > 0 || Product::isAvailableWhenOutOfStock($product['out_of_stock'])) { ++$quantity_array[1]['nbr']; } else { ++$quantity_array[0]['nbr']; } } } $filter_blocks[] = array('type_lite' => 'quantity', 'type' => 'quantity', 'id_key' => 0, 'name' => $this->trans('Availability', array(), 'Modules.FacetedSearch.Shop'), 'values' => $quantity_array, 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type']); break; case 'manufacturer': if (isset($products) && $products) { $manufaturers_array = array(); foreach ($products as $manufacturer) { if (!isset($manufaturers_array[$manufacturer['id_manufacturer']])) { $manufaturers_array[$manufacturer['id_manufacturer']] = array('name' => $manufacturer['name'], 'nbr' => $manufacturer['nbr']); } if (isset($selected_filters['manufacturer']) && in_array((int) $manufacturer['id_manufacturer'], $selected_filters['manufacturer'])) { $manufaturers_array[$manufacturer['id_manufacturer']]['checked'] = true; } } $filter_blocks[] = array('type_lite' => 'manufacturer', 'type' => 'manufacturer', 'id_key' => 0, 'name' => $this->trans('Brand', array(), 'Modules.FacetedSearch.Shop'), 'values' => $manufaturers_array, 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type']); } break; case 'id_attribute_group': $attributes_array = array(); if (isset($products) && $products) { foreach ($products as $attributes) { if (!isset($attributes_array[$attributes['id_attribute_group']])) { $attributes_array[$attributes['id_attribute_group']] = array('type_lite' => 'id_attribute_group', 'type' => 'id_attribute_group', 'id_key' => (int) $attributes['id_attribute_group'], 'name' => $attributes['attribute_group_name'], 'is_color_group' => (bool) $attributes['is_color_group'], 'values' => array(), 'url_name' => $attributes['name_url_name'], 'meta_title' => $attributes['name_meta_title'], 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type']); } if (!isset($attributes_array[$attributes['id_attribute_group']]['values'][$attributes['id_attribute']])) { $attributes_array[$attributes['id_attribute_group']]['values'][$attributes['id_attribute']] = array('color' => $attributes['color'], 'name' => $attributes['attribute_name'], 'nbr' => (int) $attributes['nbr'], 'url_name' => $attributes['value_url_name'], 'meta_title' => $attributes['value_meta_title']); } if (isset($selected_filters['id_attribute_group'][$attributes['id_attribute']])) { $attributes_array[$attributes['id_attribute_group']]['values'][$attributes['id_attribute']]['checked'] = true; } } $filter_blocks = array_merge($filter_blocks, $attributes_array); } break; case 'id_feature': $feature_array = array(); if (isset($products) && $products) { foreach ($products as $feature) { if (!isset($feature_array[$feature['id_feature']])) { $feature_array[$feature['id_feature']] = array('type_lite' => 'id_feature', 'type' => 'id_feature', 'id_key' => (int) $feature['id_feature'], 'values' => array(), 'name' => $feature['feature_name'], 'url_name' => $feature['name_url_name'], 'meta_title' => $feature['name_meta_title'], 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type']); } if (!isset($feature_array[$feature['id_feature']]['values'][$feature['id_feature_value']])) { $feature_array[$feature['id_feature']]['values'][$feature['id_feature_value']] = array('nbr' => (int) $feature['nbr'], 'name' => $feature['value'], 'url_name' => $feature['value_url_name'], 'meta_title' => $feature['value_meta_title']); } if (isset($selected_filters['id_feature'][$feature['id_feature_value']])) { $feature_array[$feature['id_feature']]['values'][$feature['id_feature_value']]['checked'] = true; } } //Natural sort foreach ($feature_array as $key => $value) { $temp = array(); foreach ($feature_array[$key]['values'] as $keyint => $valueint) { $temp[$keyint] = $valueint['name']; } natcasesort($temp); $temp2 = array(); foreach ($temp as $keytemp => $valuetemp) { $temp2[$keytemp] = $feature_array[$key]['values'][$keytemp]; } $feature_array[$key]['values'] = $temp2; } $filter_blocks = array_merge($filter_blocks, $feature_array); } break; case 'category': $tmp_array = array(); if (isset($products) && $products) { $categories_with_products_count = 0; foreach ($products as $category) { $tmp_array[$category['id_category']] = array('name' => $category['name'], 'nbr' => (int) $category['count_products']); if ((int) $category['count_products']) { ++$categories_with_products_count; } if (isset($selected_filters['category']) && in_array($category['id_category'], $selected_filters['category'])) { $tmp_array[$category['id_category']]['checked'] = true; } } if ($categories_with_products_count) { $filter_blocks[] = array('type_lite' => 'category', 'type' => 'category', 'id_key' => 0, 'name' => $this->trans('Categories', array(), 'Modules.FacetedSearch.Shop'), 'values' => $tmp_array, 'filter_show_limit' => $filter['filter_show_limit'], 'filter_type' => $filter['filter_type']); } } break; } } $latest_selected_filters = $selected_filters; return array('filters' => $filter_blocks); }