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
This commit is contained in:
Benedek Farkas 2024-04-17 11:52:51 +02:00 committed by GitHub
parent 04e9c73391
commit 3a6810ec67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 58 deletions

View File

@ -7,7 +7,6 @@ using Orchard.Forms.Services;
using Orchard.Localization; using Orchard.Localization;
namespace Orchard.Projections.FilterEditors.Forms { namespace Orchard.Projections.FilterEditors.Forms {
public class StringFilterForm : IFormProvider { public class StringFilterForm : IFormProvider {
public const string FormName = "StringFilter"; public const string FormName = "StringFilter";
@ -20,44 +19,44 @@ namespace Orchard.Projections.FilterEditors.Forms {
} }
public void Describe(DescribeContext context) { public void Describe(DescribeContext context) {
Func<IShapeFactory, object> form = object form(IShapeFactory shape) {
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( f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Equals), Text = T("Is equal to").Text });
Id: "StringFilter", f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotEquals), Text = T("Is not equal to").Text });
_Operator: Shape.SelectList( f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Contains), Text = T("Contains").Text });
Id: "operator", Name: "Operator", f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.ContainsAny), Text = T("Contains any word").Text });
Title: T("Operator"), f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.ContainsAll), Text = T("Contains all words").Text });
Size: 1, f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.Starts), Text = T("Starts with").Text });
Multiple: false 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 });
_Value: Shape.TextBox( f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotEnds), Text = T("Does not end with").Text });
Id: "value", Name: "Value", f._Operator.Add(new SelectListItem { Value = Convert.ToString(StringOperator.NotContains), Text = T("Does not contain").Text });
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 }); return f;
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; context.Form(FormName, (Func<IShapeFactory, object>)form);
};
context.Form(FormName, form);
} }
@ -65,6 +64,11 @@ namespace Orchard.Projections.FilterEditors.Forms {
var op = (StringOperator)Enum.Parse(typeof(StringOperator), Convert.ToString(formState.Operator)); var op = (StringOperator)Enum.Parse(typeof(StringOperator), Convert.ToString(formState.Operator));
object value = Convert.ToString(formState.Value); 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) { switch (op) {
case StringOperator.Equals: case StringOperator.Equals:
return x => x.Eq(property, value); 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)); return y => y.Not(x => x.Like(property, Convert.ToString(value), HqlMatchMode.End));
case StringOperator.NotContains: case StringOperator.NotContains:
return y => y.Not(x => x.Like(property, Convert.ToString(value), HqlMatchMode.Anywhere)); 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: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
@ -130,11 +126,6 @@ namespace Orchard.Projections.FilterEditors.Forms {
return T("{0} does not end with '{1}'", fieldName, value); return T("{0} does not end with '{1}'", fieldName, value);
case StringOperator.NotContains: case StringOperator.NotContains:
return T("{0} does not contain '{1}'", fieldName, value); 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: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
} }
@ -151,7 +142,6 @@ namespace Orchard.Projections.FilterEditors.Forms {
NotStarts, NotStarts,
Ends, Ends,
NotEnds, NotEnds,
NotContains, NotContains
ContainsAnyIfProvided
} }
} }

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData; using Orchard.ContentManagement.MetaData;
using Orchard.Core.Common.Models; using Orchard.Core.Common.Models;
using Orchard.Core.Contents.Extensions; using Orchard.Core.Contents.Extensions;
@ -15,13 +14,16 @@ namespace Orchard.Projections {
public class Migrations : DataMigrationImpl { public class Migrations : DataMigrationImpl {
private readonly IRepository<MemberBindingRecord> _memberBindingRepository; private readonly IRepository<MemberBindingRecord> _memberBindingRepository;
private readonly IRepository<LayoutRecord> _layoutRepository; private readonly IRepository<LayoutRecord> _layoutRepository;
private readonly IRepository<FilterRecord> _filterRepository;
public Migrations( public Migrations(
IRepository<MemberBindingRecord> memberBindingRepository, IRepository<MemberBindingRecord> memberBindingRepository,
IRepository<LayoutRecord> layoutRepository) { IRepository<LayoutRecord> layoutRepository,
IRepository<FilterRecord> filterRepository) {
_memberBindingRepository = memberBindingRepository; _memberBindingRepository = memberBindingRepository;
_layoutRepository = layoutRepository; _layoutRepository = layoutRepository;
_filterRepository = filterRepository;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
@ -359,15 +361,30 @@ namespace Orchard.Projections {
} }
public int UpdateFrom5() { public int UpdateFrom5() {
SchemaBuilder.AlterTable("LayoutRecord", t => t.AddColumn<string>("GUIdentifier", SchemaBuilder.AlterTable("LayoutRecord", t => t
column => column.WithLength(68))); .AddColumn<string>("GUIdentifier", column => column.WithLength(68)));
var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList(); var layoutRecords = _layoutRepository.Table.Where(l => l.GUIdentifier == null || l.GUIdentifier == "").ToList();
foreach (var layout in layoutRecords) { foreach (var layout in layoutRecords) {
layout.GUIdentifier = Guid.NewGuid().ToString(); layout.GUIdentifier = Guid.NewGuid().ToString();
} }
return 6; 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;
}
} }
} }