From 3a6810ec6749f333580d12ca0a6223131dd46682 Mon Sep 17 00:00:00 2001 From: Benedek Farkas <benedek.farkas@lombiq.com> Date: Wed, 17 Apr 2024 11:52:51 +0200 Subject: [PATCH] 8225: Adding a checkbox to StringFilterForm to control whether an empty value should cause the filter to be skipped (#8781) * Adding a checkbox to StringFilterForm to control whether an empty value should cause the filter to be skipped * Removing StringOperator.ContainsAnyIfProvided as its now obsolete due to the IgnoreFilterIfValueIsEmpty checkbox setting * Code styling in StringFilterForm * Adding missing T-string * Adding migration step to upgrade from using the ContainsAnyIfProvided operator in StringFilterForm --- .../FilterEditors/Forms/StringFilterForm.cs | 94 +++++++++---------- .../Modules/Orchard.Projections/Migrations.cs | 29 ++++-- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/StringFilterForm.cs b/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/StringFilterForm.cs index 59656c4b6..4b88d245c 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/StringFilterForm.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/FilterEditors/Forms/StringFilterForm.cs @@ -7,7 +7,6 @@ using Orchard.Forms.Services; using Orchard.Localization; namespace Orchard.Projections.FilterEditors.Forms { - public class StringFilterForm : IFormProvider { public const string FormName = "StringFilter"; @@ -20,44 +19,44 @@ namespace Orchard.Projections.FilterEditors.Forms { } public void Describe(DescribeContext context) { - Func<IShapeFactory, object> form = - shape => { + object form(IShapeFactory shape) { + var f = Shape.Form( + Id: "StringFilter", + _Operator: Shape.SelectList( + Id: "operator", Name: "Operator", + Title: T("Operator"), + Size: 1, + Multiple: false + ), + _Value: Shape.TextBox( + Id: "value", Name: "Value", + Title: T("Value"), + Classes: new[] { "text medium", "tokenized" }, + Description: T("Enter the value the string should be.") + ), + _IgnoreIfEmptyValue: Shape.Checkbox( + Id: "IgnoreFilterIfValueIsEmpty", + Name: "IgnoreFilterIfValueIsEmpty", + Title: T("Ignore filter if value is empty"), + Description: T("When enabled, the filter will not be applied if the provided value is or evaluates to empty."), + Value: "true" + )); - var f = Shape.Form( - Id: "StringFilter", - _Operator: Shape.SelectList( - Id: "operator", Name: "Operator", - Title: T("Operator"), - Size: 1, - Multiple: false - ), - _Value: Shape.TextBox( - Id: "value", Name: "Value", - Title: T("Value"), - Classes: new[] { "text medium", "tokenized" }, - Description: T("Enter the value the string should be.") - ) - ); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Equals), Text = T("Is equal to").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotEquals), Text = T("Is not equal to").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Contains), Text = T("Contains").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.ContainsAny), Text = T("Contains any word").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.ContainsAll), Text = T("Contains all words").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Starts), Text = T("Starts with").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotStarts), Text = T("Does not start with").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Ends), Text = T("Ends with").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotEnds), Text = T("Does not end with").Text }); + f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotContains), Text = T("Does not contain").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Equals), Text = T("Is equal to").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotEquals), Text = T("Is not equal to").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Contains), Text = T("Contains").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.ContainsAny), Text = T("Contains any word").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.ContainsAll), Text = T("Contains all words").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Starts), Text = T("Starts with").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotStarts), Text = T("Does not start with").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Ends), Text = T("Ends with").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotEnds), Text = T("Does not end with").Text }); - f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotContains), Text = T("Does not contain").Text }); - f._Operator.Add(new SelectListItem { - Value = Convert.ToString(StringOperator.ContainsAnyIfProvided), - Text = T("Contains any word (if any is provided)").Text - }); + return f; + } - return f; - }; - - context.Form(FormName, form); + context.Form(FormName, (Func<IShapeFactory, object>)form); } @@ -65,6 +64,11 @@ namespace Orchard.Projections.FilterEditors.Forms { var op = (StringOperator)Enum.Parse(typeof(StringOperator), Convert.ToString(formState.Operator)); object value = Convert.ToString(formState.Value); + if (bool.TryParse(formState.IgnoreFilterIfValueIsEmpty?.ToString() ?? "", out bool ignoreIfEmpty) + && ignoreIfEmpty + && string.IsNullOrWhiteSpace(value as string)) + return (ex) => { }; + switch (op) { case StringOperator.Equals: return x => x.Eq(property, value); @@ -92,14 +96,6 @@ namespace Orchard.Projections.FilterEditors.Forms { return y => y.Not(x => x.Like(property, Convert.ToString(value), HqlMatchMode.End)); case StringOperator.NotContains: return y => y.Not(x => x.Like(property, Convert.ToString(value), HqlMatchMode.Anywhere)); - case StringOperator.ContainsAnyIfProvided: - if (string.IsNullOrWhiteSpace((string)value)) - return x => x.IsNotEmpty("Id"); // basically, return every possible ContentItem - var values3 = Convert.ToString(value) - .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - var predicates3 = values3.Skip(1) - .Select<string, Action<IHqlExpressionFactory>>(x => y => y.Like(property, x, HqlMatchMode.Anywhere)).ToArray(); - return x => x.Disjunction(y => y.Like(property, values3[0], HqlMatchMode.Anywhere), predicates3); default: throw new ArgumentOutOfRangeException(); } @@ -130,11 +126,6 @@ namespace Orchard.Projections.FilterEditors.Forms { return T("{0} does not end with '{1}'", fieldName, value); case StringOperator.NotContains: return T("{0} does not contain '{1}'", fieldName, value); - case StringOperator.ContainsAnyIfProvided: - return T("{0} contains any of '{1}' (or '{1}' is empty)", - fieldName, - new LocalizedString(string.Join("', '", - value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)))); default: throw new ArgumentOutOfRangeException(); } @@ -151,7 +142,6 @@ namespace Orchard.Projections.FilterEditors.Forms { NotStarts, Ends, NotEnds, - NotContains, - ContainsAnyIfProvided + NotContains } -} \ No newline at end of file +} diff --git a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs index 2c751846a..cf0b6f57e 100644 --- a/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs +++ b/src/Orchard.Web/Modules/Orchard.Projections/Migrations.cs @@ -1,7 +1,6 @@ using System; using System.Data; using System.Linq; -using Orchard.ContentManagement; using Orchard.ContentManagement.MetaData; using Orchard.Core.Common.Models; using Orchard.Core.Contents.Extensions; @@ -15,13 +14,16 @@ namespace Orchard.Projections { public class Migrations : DataMigrationImpl { private readonly IRepository<MemberBindingRecord> _memberBindingRepository; private readonly IRepository<LayoutRecord> _layoutRepository; - + private readonly IRepository<FilterRecord> _filterRepository; public Migrations( IRepository<MemberBindingRecord> memberBindingRepository, - IRepository<LayoutRecord> layoutRepository) { + IRepository<LayoutRecord> layoutRepository, + IRepository<FilterRecord> filterRepository) { _memberBindingRepository = memberBindingRepository; _layoutRepository = layoutRepository; + _filterRepository = filterRepository; + T = NullLocalizer.Instance; } @@ -359,15 +361,30 @@ namespace Orchard.Projections { } public int UpdateFrom5() { - SchemaBuilder.AlterTable("LayoutRecord", t => t.AddColumn<string>("GUIdentifier", - column => column.WithLength(68))); + SchemaBuilder.AlterTable("LayoutRecord", t => t + .AddColumn<string>("GUIdentifier", column => column.WithLength(68))); var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList(); foreach (var layout in layoutRecords) { - layout.GUIdentifier = Guid.NewGuid().ToString(); + layout.GUIdentifier = Guid.NewGuid().ToString(); } return 6; } + + public int UpdateFrom6() { + // This casts a somewhat wide net, but filters can't be queried by the form they are using and different + // types of filters can (and do) use StringFilterForm. However, the "Operator" parameter's value being + // "ContainsAnyIfProvided" is very specific. + var formStateToReplace = "<Operator>ContainsAnyIfProvided</Operator>"; + var filterRecordsToUpdate = _filterRepository.Table.Where(f => f.State.Contains(formStateToReplace)).ToList(); + foreach (var filter in filterRecordsToUpdate) { + filter.State = filter.State.Replace( + formStateToReplace, + "<Operator>ContainsAny</Operator><IgnoreFilterIfValueIsEmpty>true</IgnoreFilterIfValueIsEmpty>"); + } + + return 7; + } } } \ No newline at end of file