예제 #1
0
        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);
        }
예제 #3
0
        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>());
        }