protected virtual IDictionary <string, FacetGroup> GetFacets(CatalogSearchQuery searchQuery, int totalHits) { var result = new Dictionary <string, FacetGroup>(); var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; foreach (var key in searchQuery.FacetDescriptors.Keys) { var descriptor = searchQuery.FacetDescriptors[key]; var facets = new List <Facet>(); var kind = FacetGroup.GetKindByKey(key); if (kind == FacetGroupKind.Category) { #region Category // order by product count var categoryQuery = from c in _categoryRepository.TableUntracked where !c.Deleted && c.Published join pc in _productCategoryRepository.TableUntracked on c.Id equals pc.CategoryId into pcm from pc in pcm.DefaultIfEmpty() group c by c.Id into grp orderby grp.Count() descending select new { Id = grp.FirstOrDefault().Id, Name = grp.FirstOrDefault().Name, DisplayOrder = grp.FirstOrDefault().DisplayOrder }; if (descriptor.MaxChoicesCount > 0) { categoryQuery = categoryQuery.Take(descriptor.MaxChoicesCount); } var categories = categoryQuery.ToList(); var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "Category" && x.LocaleKey == "Name" && x.LanguageId == languageId); var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); foreach (var category in categories) { var selected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(category.Id)); if (totalHits == 0 && !selected) { continue; } string label = null; names.TryGetValue(category.Id, out label); facets.Add(new Facet(new FacetValue(category.Id, IndexTypeCode.Int32) { IsSelected = selected, Label = label.HasValue() ? label : category.Name, DisplayOrder = category.DisplayOrder })); } #endregion } else if (kind == FacetGroupKind.Brand) { #region Brand // order by product count var manufacturerQuery = from m in _manufacturerRepository.TableUntracked where !m.Deleted && m.Published join pm in _productManufacturerRepository.TableUntracked on m.Id equals pm.ManufacturerId into pmm from pm in pmm.DefaultIfEmpty() group m by m.Id into grp orderby grp.Count() descending select new { Id = grp.FirstOrDefault().Id, Name = grp.FirstOrDefault().Name, DisplayOrder = grp.FirstOrDefault().DisplayOrder }; if (descriptor.MaxChoicesCount > 0) { manufacturerQuery = manufacturerQuery.Take(descriptor.MaxChoicesCount); } var manufacturers = manufacturerQuery.ToList(); var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "Manufacturer" && x.LocaleKey == "Name" && x.LanguageId == languageId); var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); foreach (var manu in manufacturers) { var selected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(manu.Id)); if (totalHits == 0 && !selected) { continue; } string label = null; names.TryGetValue(manu.Id, out label); facets.Add(new Facet(new FacetValue(manu.Id, IndexTypeCode.Int32) { IsSelected = selected, Label = label.HasValue() ? label : manu.Name, DisplayOrder = manu.DisplayOrder })); } #endregion } else if (kind == FacetGroupKind.DeliveryTime) { #region Delivery time var deliveryTimes = _deliveryTimeService.GetAllDeliveryTimes(); var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "DeliveryTime" && x.LocaleKey == "Name" && x.LanguageId == languageId); var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); foreach (var deliveryTime in deliveryTimes) { if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) { break; } var selected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(deliveryTime.Id)); if (totalHits == 0 && !selected) { continue; } string label = null; names.TryGetValue(deliveryTime.Id, out label); facets.Add(new Facet(new FacetValue(deliveryTime.Id, IndexTypeCode.Int32) { IsSelected = selected, Label = label.HasValue() ? label : deliveryTime.Name, DisplayOrder = deliveryTime.DisplayOrder })); } #endregion } else if (kind == FacetGroupKind.Price) { #region Price var count = 0; var hasActivePredefinedFacet = false; var minPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published).Min(x => (double)x.Price); var maxPrice = _productRepository.Table.Where(x => !x.Deleted && x.Published).Max(x => (double)x.Price); minPrice = FacetUtility.MakePriceEven(minPrice); maxPrice = FacetUtility.MakePriceEven(maxPrice); for (var i = 0; i < _priceThresholds.Length; ++i) { if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) { break; } var price = _priceThresholds[i]; if (price < minPrice) { continue; } if (price >= maxPrice) { i = int.MaxValue - 1; } var selected = descriptor.Values.Any(x => x.IsSelected && x.Value == null && x.UpperValue != null && (double)x.UpperValue == price); if (totalHits == 0 && !selected) { continue; } if (selected) { hasActivePredefinedFacet = true; } facets.Add(new Facet(new FacetValue(null, price, IndexTypeCode.Double, false, true) { DisplayOrder = ++count, IsSelected = selected })); } // Add facet for custom price range. var priceDescriptorValue = descriptor.Values.FirstOrDefault(); var customPriceFacetValue = new FacetValue( priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.Value : null, priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.UpperValue : null, IndexTypeCode.Double, true, true); customPriceFacetValue.IsSelected = customPriceFacetValue.Value != null || customPriceFacetValue.UpperValue != null; if (!(totalHits == 0 && !customPriceFacetValue.IsSelected)) { facets.Insert(0, new Facet("custom", customPriceFacetValue)); } #endregion } else if (kind == FacetGroupKind.Rating) { if (totalHits == 0 && !descriptor.Values.Any(x => x.IsSelected)) { continue; } foreach (var rating in FacetUtility.GetRatings()) { var newFacet = new Facet(rating); newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(rating.Value)); facets.Add(newFacet); } } else if (kind == FacetGroupKind.Availability || kind == FacetGroupKind.NewArrivals) { var value = descriptor.Values.FirstOrDefault(); if (value != null && !(totalHits == 0 && !value.IsSelected)) { var newValue = value.Clone(); newValue.Value = true; newValue.TypeCode = IndexTypeCode.Boolean; newValue.IsRange = false; newValue.IsSelected = value.IsSelected; facets.Add(new Facet(newValue)); } } if (facets.Any(x => x.Published)) { //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); result.Add(key, new FacetGroup( key, descriptor.Label, descriptor.IsMultiSelect, descriptor.DisplayOrder, facets.OrderBy(descriptor))); } } return(result); }
protected virtual IDictionary <string, FacetGroup> GetFacets(CatalogSearchQuery searchQuery, int totalHits) { var result = new Dictionary <string, FacetGroup>(); var storeId = searchQuery.StoreId ?? _services.StoreContext.CurrentStore.Id; var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; foreach (var key in searchQuery.FacetDescriptors.Keys) { var descriptor = searchQuery.FacetDescriptors[key]; var facets = new List <Facet>(); var kind = FacetGroup.GetKindByKey("Catalog", key); switch (kind) { case FacetGroupKind.Category: case FacetGroupKind.Brand: case FacetGroupKind.DeliveryTime: case FacetGroupKind.Rating: case FacetGroupKind.Price: if (totalHits == 0 && !descriptor.Values.Any(x => x.IsSelected)) { continue; } break; } if (kind == FacetGroupKind.Category) { var categoryTree = _categoryService.GetCategoryTree(0, false, storeId); var categories = categoryTree.Flatten(false); if (descriptor.MaxChoicesCount > 0) { categories = categories.Take(descriptor.MaxChoicesCount); } var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "Category" && x.LocaleKey == "Name" && x.LanguageId == languageId); var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); foreach (var category in categories) { names.TryGetValue(category.Id, out var label); facets.Add(new Facet(new FacetValue(category.Id, IndexTypeCode.Int32) { IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(category.Id)), Label = label.HasValue() ? label : category.Name, DisplayOrder = category.DisplayOrder })); } } else if (kind == FacetGroupKind.Brand) { var manufacturers = _manufacturerService.GetAllManufacturers(null, storeId); if (descriptor.MaxChoicesCount > 0) { manufacturers = manufacturers.Take(descriptor.MaxChoicesCount).ToList(); } var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "Manufacturer" && x.LocaleKey == "Name" && x.LanguageId == languageId); var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); foreach (var manu in manufacturers) { names.TryGetValue(manu.Id, out var label); facets.Add(new Facet(new FacetValue(manu.Id, IndexTypeCode.Int32) { IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(manu.Id)), Label = label.HasValue() ? label : manu.Name, DisplayOrder = manu.DisplayOrder })); } } else if (kind == FacetGroupKind.DeliveryTime) { var deliveryTimes = _deliveryTimeService.GetAllDeliveryTimes(); var nameQuery = _localizedPropertyRepository.TableUntracked .Where(x => x.LocaleKeyGroup == "DeliveryTime" && x.LocaleKey == "Name" && x.LanguageId == languageId); var names = nameQuery.ToList().ToDictionarySafe(x => x.EntityId, x => x.LocaleValue); foreach (var deliveryTime in deliveryTimes) { if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) { break; } names.TryGetValue(deliveryTime.Id, out var label); facets.Add(new Facet(new FacetValue(deliveryTime.Id, IndexTypeCode.Int32) { IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(deliveryTime.Id)), Label = label.HasValue() ? label : deliveryTime.Name, DisplayOrder = deliveryTime.DisplayOrder })); } } else if (kind == FacetGroupKind.Price) { var count = 0; var hasActivePredefinedFacet = false; var minPrice = _productRepository.Table.Where(x => x.Published && !x.Deleted && !x.IsSystemProduct).Min(x => (double)x.Price); var maxPrice = _productRepository.Table.Where(x => x.Published && !x.Deleted && !x.IsSystemProduct).Max(x => (double)x.Price); minPrice = FacetUtility.MakePriceEven(minPrice); maxPrice = FacetUtility.MakePriceEven(maxPrice); for (var i = 0; i < _priceThresholds.Length; ++i) { if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) { break; } var price = _priceThresholds[i]; if (price < minPrice) { continue; } if (price >= maxPrice) { i = int.MaxValue - 1; } var selected = descriptor.Values.Any(x => x.IsSelected && x.Value == null && x.UpperValue != null && (double)x.UpperValue == price); if (selected) { hasActivePredefinedFacet = true; } facets.Add(new Facet(new FacetValue(null, price, IndexTypeCode.Double, false, true) { DisplayOrder = ++count, IsSelected = selected })); } // Add facet for custom price range. var priceDescriptorValue = descriptor.Values.FirstOrDefault(); var customPriceFacetValue = new FacetValue( priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.Value : null, priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.UpperValue : null, IndexTypeCode.Double, true, true); customPriceFacetValue.IsSelected = customPriceFacetValue.Value != null || customPriceFacetValue.UpperValue != null; if (!(totalHits == 0 && !customPriceFacetValue.IsSelected)) { facets.Insert(0, new Facet("custom", customPriceFacetValue)); } } else if (kind == FacetGroupKind.Rating) { foreach (var rating in FacetUtility.GetRatings()) { var newFacet = new Facet(rating); newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(rating.Value)); facets.Add(newFacet); } } else if (kind == FacetGroupKind.Availability || kind == FacetGroupKind.NewArrivals) { var value = descriptor.Values.FirstOrDefault(); if (value != null) { if (kind == FacetGroupKind.NewArrivals && totalHits == 0 && !value.IsSelected) { continue; } var newValue = value.Clone(); newValue.Value = true; newValue.TypeCode = IndexTypeCode.Boolean; newValue.IsRange = false; newValue.IsSelected = value.IsSelected; facets.Add(new Facet(newValue)); } } if (facets.Any(x => x.Published)) { //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); result.Add(key, new FacetGroup( "Catalog", key, descriptor.Label, descriptor.IsMultiSelect, false, descriptor.DisplayOrder, facets.OrderBy(descriptor))); } } return(result); }
protected override async Task <IEnumerable <RuleDescriptor> > LoadDescriptorsAsync() { var language = _services.WorkContext.WorkingLanguage; var currency = _services.WorkContext.WorkingCurrency; var oneStarStr = T("Search.Facet.1StarAndMore").Value; var xStarsStr = T("Search.Facet.XStarsAndMore").Value; var stores = _services.StoreContext.GetAllStores() .Select(x => new RuleValueSelectListOption { Value = x.Id.ToString(), Text = x.Name }) .ToArray(); var visibilities = await((ProductVisibility[])Enum.GetValues(typeof(ProductVisibility))) .SelectAsync(async x => new RuleValueSelectListOption { Value = ((int)x).ToString(), Text = await _localizationService.GetLocalizedEnumAsync(x) }) .ToArrayAsync(); var productTypes = await((ProductType[])Enum.GetValues(typeof(ProductType))) .SelectAsync(async x => new RuleValueSelectListOption { Value = ((int)x).ToString(), Text = await _localizationService.GetLocalizedEnumAsync(x) }) .ToArrayAsync(); var ratings = FacetUtility.GetRatings() .Reverse() .Skip(1) .Select(x => new RuleValueSelectListOption { Value = ((double)x.Value).ToString(CultureInfo.InvariantCulture), Text = (double)x.Value == 1 ? oneStarStr : xStarsStr.FormatInvariant(x.Value) }) .ToArray(); var categoryTree = _catalogSettings.ShowProductsFromSubcategories ? await _categoryService.GetCategoryTreeAsync(includeHidden : true) : null; #region Special filters CatalogSearchQuery categoryFilter(SearchFilterContext ctx, int[] x) { if (x?.Any() ?? false) { var ids = new HashSet <int>(x); if (_catalogSettings.ShowProductsFromSubcategories) { foreach (var id in x) { var node = categoryTree.SelectNodeById(id); if (node != null) { ids.AddRange(node.Flatten(false).Select(y => y.Id)); } } } return(ctx.Query.WithCategoryIds(_catalogSettings.IncludeFeaturedProductsInNormalLists ? (bool?)null : false, ids.ToArray())); } return(ctx.Query); }; CatalogSearchQuery stockQuantityFilter(SearchFilterContext ctx, int x) { if (ctx.Expression.Operator == RuleOperator.IsEqualTo || ctx.Expression.Operator == RuleOperator.IsNotEqualTo) { return(ctx.Query.WithStockQuantity(x, x, ctx.Expression.Operator == RuleOperator.IsEqualTo, ctx.Expression.Operator == RuleOperator.IsEqualTo)); } else if (ctx.Expression.Operator == RuleOperator.GreaterThanOrEqualTo || ctx.Expression.Operator == RuleOperator.GreaterThan) { return(ctx.Query.WithStockQuantity(x, null, ctx.Expression.Operator == RuleOperator.GreaterThanOrEqualTo, null)); } else if (ctx.Expression.Operator == RuleOperator.LessThanOrEqualTo || ctx.Expression.Operator == RuleOperator.LessThan) { return(ctx.Query.WithStockQuantity(null, x, null, ctx.Expression.Operator == RuleOperator.LessThanOrEqualTo)); } return(ctx.Query); }; CatalogSearchQuery priceFilter(SearchFilterContext ctx, decimal x) { var price = new Money(x, currency); if (ctx.Expression.Operator == RuleOperator.IsEqualTo || ctx.Expression.Operator == RuleOperator.IsNotEqualTo) { return(ctx.Query.PriceBetween(price, price, ctx.Expression.Operator == RuleOperator.IsEqualTo, ctx.Expression.Operator == RuleOperator.IsEqualTo)); } else if (ctx.Expression.Operator == RuleOperator.GreaterThanOrEqualTo || ctx.Expression.Operator == RuleOperator.GreaterThan) { return(ctx.Query.PriceBetween(price, null, ctx.Expression.Operator == RuleOperator.GreaterThanOrEqualTo, null)); } else if (ctx.Expression.Operator == RuleOperator.LessThanOrEqualTo || ctx.Expression.Operator == RuleOperator.LessThan) { return(ctx.Query.PriceBetween(null, price, null, ctx.Expression.Operator == RuleOperator.LessThanOrEqualTo)); } return(ctx.Query); }; CatalogSearchQuery createdFilter(SearchFilterContext ctx, DateTime x) { if (ctx.Expression.Operator == RuleOperator.IsEqualTo || ctx.Expression.Operator == RuleOperator.IsNotEqualTo) { return(ctx.Query.CreatedBetween(x, x, ctx.Expression.Operator == RuleOperator.IsEqualTo, ctx.Expression.Operator == RuleOperator.IsEqualTo)); } else if (ctx.Expression.Operator == RuleOperator.GreaterThanOrEqualTo || ctx.Expression.Operator == RuleOperator.GreaterThan) { return(ctx.Query.CreatedBetween(x, null, ctx.Expression.Operator == RuleOperator.GreaterThanOrEqualTo, null)); } else if (ctx.Expression.Operator == RuleOperator.LessThanOrEqualTo || ctx.Expression.Operator == RuleOperator.LessThan) { return(ctx.Query.CreatedBetween(null, x, null, ctx.Expression.Operator == RuleOperator.LessThanOrEqualTo)); } return(ctx.Query); }; #endregion var descriptors = new List <SearchFilterDescriptor> { new SearchFilterDescriptor <int>((ctx, x) => ctx.Query.HasStoreId(x)) { Name = "Store", DisplayName = T("Admin.Rules.FilterDescriptor.Store"), RuleType = RuleType.Int, SelectList = new LocalRuleValueSelectList(stores), Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.AllowedCustomerRoles(x)) { Name = "CustomerRole", DisplayName = T("Admin.Rules.FilterDescriptor.IsInCustomerRole"), RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("CustomerRole") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.PublishedOnly(x)) { Name = "Published", DisplayName = T("Admin.Catalog.Products.Fields.Published"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.AvailableOnly(x)) { Name = "AvailableByStock", DisplayName = T("Products.Availability.InStock"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.AvailableByDate(x)) { Name = "AvailableByDate", DisplayName = T("Admin.Rules.FilterDescriptor.AvailableByDate"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <int>((ctx, x) => ctx.Query.WithVisibility((ProductVisibility)x)) { Name = "Visibility", DisplayName = T("Admin.Catalog.Products.Fields.Visibility"), RuleType = RuleType.Int, SelectList = new LocalRuleValueSelectList(visibilities), Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.WithProductIds(x)) { Name = "Product", DisplayName = T("Common.Entity.Product"), RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("Product") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }, new SearchFilterDescriptor <int>((ctx, x) => ctx.Query.IsProductType((ProductType)x)) { Name = "ProductType", DisplayName = T("Admin.Catalog.Products.Fields.ProductType"), RuleType = RuleType.Int, SelectList = new LocalRuleValueSelectList(productTypes), Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <int[]>(categoryFilter) { Name = "Category", DisplayName = T("Common.Entity.Category"), RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("Category") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }, new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.WithManufacturerIds(null, x)) { Name = "Manufacturer", DisplayName = T("Common.Entity.Manufacturer"), RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("Manufacturer") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }, // Same logic as the filter above product list. new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.HasAnyCategory(!x)) { Name = "WithoutCategory", DisplayName = T("Admin.Catalog.Products.List.SearchWithoutCategories"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.HasAnyManufacturer(!x)) { Name = "WithoutManufacturer", DisplayName = T("Admin.Catalog.Products.List.SearchWithoutManufacturers"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.WithProductTagIds(x)) { Name = "ProductTag", DisplayName = T("Admin.Catalog.Products.Fields.ProductTags"), RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("ProductTag") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }, new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.WithDeliveryTimeIds(x)) { Name = "DeliveryTime", DisplayName = T("Admin.Catalog.Products.Fields.DeliveryTime"), RuleType = RuleType.IntArray, Operators = new RuleOperator[] { RuleOperator.In }, SelectList = new RemoteRuleValueSelectList("DeliveryTime") { Multiple = true } }, new SearchFilterDescriptor <int>(stockQuantityFilter) { Name = "StockQuantity", DisplayName = T("Admin.Catalog.Products.Fields.StockQuantity"), RuleType = RuleType.Int }, new SearchFilterDescriptor <decimal>(priceFilter) { Name = "Price", DisplayName = T("Admin.Catalog.Products.Fields.Price"), RuleType = RuleType.Money }, new SearchFilterDescriptor <DateTime>(createdFilter) { Name = "CreatedOn", DisplayName = T("Common.CreatedOn"), RuleType = RuleType.DateTime }, new SearchFilterDescriptor <double>((ctx, x) => ctx.Query.WithRating(x, null)) { Name = "Rating", DisplayName = T("Admin.Catalog.ProductReviews.Fields.Rating"), RuleType = RuleType.Float, Operators = new RuleOperator[] { RuleOperator.GreaterThanOrEqualTo }, SelectList = new LocalRuleValueSelectList(ratings) }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.HomePageProductsOnly(x)) { Name = "HomepageProduct", DisplayName = T("Admin.Catalog.Products.Fields.ShowOnHomePage"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.DownloadOnly(x)) { Name = "Download", DisplayName = T("Admin.Catalog.Products.Fields.IsDownload"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.RecurringOnly(x)) { Name = "Recurring", DisplayName = T("Admin.Catalog.Products.Fields.IsRecurring"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.ShipEnabledOnly(x)) { Name = "ShipEnabled", DisplayName = T("Admin.Catalog.Products.Fields.IsShipEnabled"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.FreeShippingOnly(x)) { Name = "FreeShipping", DisplayName = T("Admin.Catalog.Products.Fields.IsFreeShipping"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.TaxExemptOnly(x)) { Name = "TaxExempt", DisplayName = T("Admin.Catalog.Products.Fields.IsTaxExempt"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.EsdOnly(x)) { Name = "Esd", DisplayName = T("Admin.Catalog.Products.Fields.IsEsd"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } }, new SearchFilterDescriptor <bool>((ctx, x) => ctx.Query.HasDiscount(x)) { Name = "Discount", DisplayName = T("Admin.Catalog.Products.Fields.HasDiscountsApplied"), RuleType = RuleType.Boolean, Operators = new RuleOperator[] { RuleOperator.IsEqualTo } } }; if (_services.ApplicationContext.ModuleCatalog.GetModuleByName("SmartStore.MegaSearchPlus") != null) { ISearchFilter[] filters(string fieldName, int parentId, int[] valueIds) { return(valueIds.Select(id => SearchFilter.ByField(fieldName, id).ExactMatch().NotAnalyzed().HasParent(parentId)).ToArray()); } // Sort by display order! var pageIndex = -1; var variantsQuery = _services.DbContext.ProductAttributes .AsNoTracking() .Where(x => x.AllowFiltering) .OrderBy(x => x.DisplayOrder); while (true) { var variants = await variantsQuery.ToPagedList(++pageIndex, 1000).LoadAsync(); foreach (var variant in variants) { var descriptor = new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.WithFilter(SearchFilter.Combined(filters("variantvalueid", variant.Id, x)))) { Name = $"Variant{variant.Id}", DisplayName = variant.GetLocalized(x => x.Name, language, true, false), GroupKey = "Admin.Catalog.Attributes.ProductAttributes", RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("VariantValue") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }; descriptor.Metadata["ParentId"] = variant.Id; descriptors.Add(descriptor); } if (!variants.HasNextPage) { break; } } pageIndex = -1; var attributesQuery = _services.DbContext.SpecificationAttributes .AsNoTracking() .Where(x => x.AllowFiltering) .OrderBy(x => x.DisplayOrder); while (true) { var attributes = await attributesQuery.ToPagedList(++pageIndex, 1000).LoadAsync(); foreach (var attribute in attributes) { var descriptor = new SearchFilterDescriptor <int[]>((ctx, x) => ctx.Query.WithFilter(SearchFilter.Combined(filters("attrvalueid", attribute.Id, x)))) { Name = $"Attribute{attribute.Id}", DisplayName = attribute.GetLocalized(x => x.Name, language, true, false), GroupKey = "Admin.Catalog.Attributes.SpecificationAttributes", RuleType = RuleType.IntArray, SelectList = new RemoteRuleValueSelectList("AttributeOption") { Multiple = true }, Operators = new RuleOperator[] { RuleOperator.In } }; descriptor.Metadata["ParentId"] = attribute.Id; descriptors.Add(descriptor); } if (!attributes.HasNextPage) { break; } } } descriptors .Where(x => x.RuleType == RuleType.Money) .Each(x => x.Metadata["postfix"] = _services.CurrencyService.PrimaryCurrency.CurrencyCode); return(descriptors.Cast <RuleDescriptor>()); }