From 7c2f230feba6a6f025303b99a4632ff97d12d079 Mon Sep 17 00:00:00 2001 From: Mike Alhayek Date: Thu, 29 Dec 2022 10:15:17 -0800 Subject: [PATCH] Add field validation handlers to ensure field validation when contents are created outside the UI. Fix #12978 (#12979) --- .../Handlers/ContentPickerFieldHandler.cs | 36 ++++++++ .../Handlers/DateFieldHandler.cs | 30 ++++++ .../Handlers/DateTimeFieldHandler.cs | 30 ++++++ .../Handlers/LinkFieldHandler.cs | 92 +++++++++++++++++++ ...ocalizationSetContentPickerFieldHandler.cs | 35 +++++++ .../Handlers/MultiTextFieldHandler.cs | 30 ++++++ .../Handlers/NumericFieldHandler.cs | 57 ++++++++++++ .../Handlers/TextFieldHandler.cs | 2 +- .../Handlers/TimeFieldHandler.cs | 30 ++++++ .../Handlers/UserPickerFieldHandler.cs | 35 +++++++ .../Handlers/YoutubeFieldHandler.cs | 47 ++++++++++ .../OrchardCore.ContentFields/Startup.cs | 39 ++++++-- .../Handlers/GeoPointFieldHandler.cs | 38 ++++++++ .../OrchardCore.Spatial/Startup.cs | 5 +- .../Handlers/TaxonomyFieldHandler.cs | 30 ++++++ .../OrchardCore.Taxonomies/Startup.cs | 4 +- 16 files changed, 526 insertions(+), 14 deletions(-) create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/ContentPickerFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateTimeFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LinkFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LocalizationSetContentPickerFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/MultiTextFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/NumericFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TimeFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/UserPickerFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/YoutubeFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Spatial/Handlers/GeoPointFieldHandler.cs create mode 100644 src/OrchardCore.Modules/OrchardCore.Taxonomies/Handlers/TaxonomyFieldHandler.cs diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/ContentPickerFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/ContentPickerFieldHandler.cs new file mode 100644 index 00000000000..fa2e106e585 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/ContentPickerFieldHandler.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class ContentPickerFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public ContentPickerFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, ContentPickerField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && field.ContentItemIds.Length == 0) + { + context.Fail(S["The {0} field is required.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.ContentItemIds)); + } + + if (!settings.Multiple && field.ContentItemIds.Length > 1) + { + context.Fail(S["The {0} field cannot contain multiple items.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.ContentItemIds)); + } + + return Task.CompletedTask; + } +} + diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateFieldHandler.cs new file mode 100644 index 00000000000..982cfb1dfa6 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateFieldHandler.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class DateFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public DateFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, DateField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && !field.Value.HasValue) + { + context.Fail(S["A value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Value)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateTimeFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateTimeFieldHandler.cs new file mode 100644 index 00000000000..8aca43ae849 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/DateTimeFieldHandler.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class DateTimeFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public DateTimeFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, DateTimeField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && !field.Value.HasValue) + { + context.Fail(S["A value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Value)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LinkFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LinkFieldHandler.cs new file mode 100644 index 00000000000..0e48c35de3a --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LinkFieldHandler.cs @@ -0,0 +1,92 @@ +using System; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; +using OrchardCore.Infrastructure.Html; + +namespace OrchardCore.ContentFields.Handlers; + +public class LinkFieldHandler : ContentFieldHandler +{ + private readonly IUrlHelperFactory _urlHelperFactory; + private readonly IActionContextAccessor _actionContextAccessor; + private readonly IStringLocalizer S; + private readonly IHtmlSanitizerService _htmlSanitizerService; + private readonly HtmlEncoder _htmlencoder; + + public LinkFieldHandler( + IUrlHelperFactory urlHelperFactory, + IActionContextAccessor actionContextAccessor, + IStringLocalizer localizer, + IHtmlSanitizerService htmlSanitizerService, + HtmlEncoder htmlencoder) + { + _urlHelperFactory = urlHelperFactory; + _actionContextAccessor = actionContextAccessor; + S = localizer; + _htmlSanitizerService = htmlSanitizerService; + _htmlencoder = htmlencoder; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, LinkField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + var urlToValidate = field.Url; + if (!String.IsNullOrEmpty(urlToValidate)) + { + var indexAnchor = urlToValidate.IndexOf('#'); + if (indexAnchor > -1) + { + urlToValidate = urlToValidate.Substring(0, indexAnchor); + } + + if (urlToValidate.StartsWith("~/", StringComparison.Ordinal)) + { + var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext); + urlToValidate = urlHelper.Content(urlToValidate); + } + + urlToValidate = urlToValidate.ToUriComponents(); + } + + // Validate Url + if (settings.Required && String.IsNullOrWhiteSpace(field.Url)) + { + context.Fail(S["The url is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Url)); + } + else if (!String.IsNullOrWhiteSpace(field.Url)) + { + if (!Uri.IsWellFormedUriString(urlToValidate, UriKind.RelativeOrAbsolute)) + { + context.Fail(S["{0} is an invalid url.", field.Url], nameof(field.Url)); + } + else + { + var link = $""; + + if (!String.Equals(link, _htmlSanitizerService.Sanitize(link), StringComparison.OrdinalIgnoreCase)) + { + context.Fail(S["{0} is an invalid url.", field.Url], nameof(field.Url)); + } + } + } + + if (settings.LinkTextMode == LinkTextMode.Required && String.IsNullOrWhiteSpace(field.Text)) + { + context.Fail(S["The link text is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Text)); + } + else if (settings.LinkTextMode == LinkTextMode.Static && String.IsNullOrWhiteSpace(settings.DefaultText)) + { + context.Fail(S["The text default value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Text)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LocalizationSetContentPickerFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LocalizationSetContentPickerFieldHandler.cs new file mode 100644 index 00000000000..9af45f1f4da --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/LocalizationSetContentPickerFieldHandler.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class LocalizationSetContentPickerFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public LocalizationSetContentPickerFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, LocalizationSetContentPickerField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && field.LocalizationSets.Length == 0) + { + context.Fail(S["The {0} field is required.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.LocalizationSets)); + } + + if (!settings.Multiple && field.LocalizationSets.Length > 1) + { + context.Fail(S["The {0} field cannot contain multiple items.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.LocalizationSets)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/MultiTextFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/MultiTextFieldHandler.cs new file mode 100644 index 00000000000..b893ba6d582 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/MultiTextFieldHandler.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class MultiTextFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public MultiTextFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, MultiTextField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && field.Values.Length == 0) + { + context.Fail(S["A value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Values)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/NumericFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/NumericFieldHandler.cs new file mode 100644 index 00000000000..fd81fb47ad8 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/NumericFieldHandler.cs @@ -0,0 +1,57 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class NumericFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public NumericFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, NumericField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && !field.Value.HasValue) + { + context.Fail(S["A value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Value)); + } + + if (field.Value.HasValue) + { + if (settings.Minimum.HasValue && field.Value.Value < settings.Minimum.Value) + { + context.Fail(S["The value must be greater than {0}.", settings.Minimum.Value], nameof(field.Value)); + } + + if (settings.Maximum.HasValue && field.Value.Value > settings.Maximum.Value) + { + context.Fail(S["The value must be less than {0}.", settings.Maximum.Value], nameof(field.Value)); + } + + // Check the number of decimals. + if (Math.Round(field.Value.Value, settings.Scale) != field.Value.Value) + { + if (settings.Scale == 0) + { + context.Fail(S["The {0} field must be an integer.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Value)); + } + else + { + context.Fail(S["Invalid number of digits for {0}, max allowed: {1}.", context.ContentPartFieldDefinition.DisplayName(), settings.Scale], nameof(field.Value)); + } + } + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TextFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TextFieldHandler.cs index a02b891a549..b9d8aa45314 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TextFieldHandler.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TextFieldHandler.cs @@ -21,7 +21,7 @@ public override Task ValidatingAsync(ValidateContentFieldContext context, TextFi { var settings = context.ContentPartFieldDefinition.GetSettings(); - if (settings.Required && String.IsNullOrEmpty(field.Text)) + if (settings.Required && String.IsNullOrWhiteSpace(field.Text)) { context.Fail(S["A value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Text)); } diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TimeFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TimeFieldHandler.cs new file mode 100644 index 00000000000..d75e30fc08c --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/TimeFieldHandler.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class TimeFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public TimeFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, TimeField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && !field.Value.HasValue) + { + context.Fail(S["A value is required for {0}.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Value)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/UserPickerFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/UserPickerFieldHandler.cs new file mode 100644 index 00000000000..450a516fa52 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/UserPickerFieldHandler.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class UserPickerFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public UserPickerFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, UserPickerField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && field.UserIds.Length == 0) + { + context.Fail(S["The {0} field is required.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.UserIds)); + } + + if (!settings.Multiple && field.UserIds.Length > 1) + { + context.Fail(S["The {0} field cannot contain multiple items.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.UserIds)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/YoutubeFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/YoutubeFieldHandler.cs new file mode 100644 index 00000000000..526497a71f0 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Handlers/YoutubeFieldHandler.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentFields.Fields; +using OrchardCore.ContentFields.Settings; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; + +namespace OrchardCore.ContentFields.Handlers; + +public class YoutubeFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public YoutubeFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, YoutubeField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && String.IsNullOrWhiteSpace(field.RawAddress)) + { + context.Fail(S["A value is required for '{0}'.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.RawAddress)); + } + + if (field.RawAddress != null) + { + var uri = new Uri(field.RawAddress); + + // if it is a url with QueryString + if (!String.IsNullOrWhiteSpace(uri.Query)) + { + var query = QueryHelpers.ParseQuery(uri.Query); + if (!query.ContainsKey("v")) + { + context.Fail(S["The format of the url is invalid"], nameof(field.RawAddress)); + } + } + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.ContentFields/Startup.cs b/src/OrchardCore.Modules/OrchardCore.ContentFields/Startup.cs index f3417aa4db6..129a18e60db 100644 --- a/src/OrchardCore.Modules/OrchardCore.ContentFields/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.ContentFields/Startup.cs @@ -88,49 +88,65 @@ public override void ConfigureServices(IServiceCollection services) // Link Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // MultiText Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // Numeric Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // DateTime Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // Date Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // Time Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // Video field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); // Content picker field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -163,7 +179,9 @@ public LocalizationSetContentPickerStartup(IOptions adminOptions) public override void ConfigureServices(IServiceCollection services) { services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); } @@ -219,7 +237,8 @@ public override void ConfigureServices(IServiceCollection services) services.AddContentField() .UseDisplayDriver(d => !String.Equals(d, "UserNames", StringComparison.OrdinalIgnoreCase)) - .UseDisplayDriver(d => String.Equals(d, "UserNames", StringComparison.OrdinalIgnoreCase)); + .UseDisplayDriver(d => String.Equals(d, "UserNames", StringComparison.OrdinalIgnoreCase)) + .AddHandler(); services.AddScoped(); services.AddScoped(); diff --git a/src/OrchardCore.Modules/OrchardCore.Spatial/Handlers/GeoPointFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.Spatial/Handlers/GeoPointFieldHandler.cs new file mode 100644 index 00000000000..655a562d733 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Spatial/Handlers/GeoPointFieldHandler.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; +using OrchardCore.Spatial.Fields; +using OrchardCore.Spatial.Settings; + +namespace OrchardCore.Spatial.Handlers; + +public class GeoPointFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public GeoPointFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, GeoPointField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required) + { + if (!field.Latitude.HasValue) + { + context.Fail(S["The {0} field is required.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Latitude)); + } + + if (!field.Longitude.HasValue) + { + context.Fail(S["The {0} field is required.", context.ContentPartFieldDefinition.DisplayName()], nameof(field.Longitude)); + } + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Spatial/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Spatial/Startup.cs index 6cc5d63d4d8..ec64a35dec1 100644 --- a/src/OrchardCore.Modules/OrchardCore.Spatial/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Spatial/Startup.cs @@ -9,6 +9,7 @@ using OrchardCore.ResourceManagement; using OrchardCore.Spatial.Drivers; using OrchardCore.Spatial.Fields; +using OrchardCore.Spatial.Handlers; using OrchardCore.Spatial.Indexing; using OrchardCore.Spatial.ViewModels; @@ -22,7 +23,9 @@ public override void ConfigureServices(IServiceCollection services) // Coordinate Field services.AddContentField() - .UseDisplayDriver(); + .UseDisplayDriver() + .AddHandler(); + services.AddScoped(); services.AddScoped(); diff --git a/src/OrchardCore.Modules/OrchardCore.Taxonomies/Handlers/TaxonomyFieldHandler.cs b/src/OrchardCore.Modules/OrchardCore.Taxonomies/Handlers/TaxonomyFieldHandler.cs new file mode 100644 index 00000000000..5a3786434b5 --- /dev/null +++ b/src/OrchardCore.Modules/OrchardCore.Taxonomies/Handlers/TaxonomyFieldHandler.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Localization; +using OrchardCore.ContentManagement.Handlers; +using OrchardCore.ContentManagement.Metadata.Models; +using OrchardCore.Taxonomies.Fields; +using OrchardCore.Taxonomies.Settings; + +namespace OrchardCore.Taxonomies.Handlers; + +public class TaxonomyFieldHandler : ContentFieldHandler +{ + private readonly IStringLocalizer S; + + public TaxonomyFieldHandler(IStringLocalizer stringLocalizer) + { + S = stringLocalizer; + } + + public override Task ValidatingAsync(ValidateContentFieldContext context, TaxonomyField field) + { + var settings = context.ContentPartFieldDefinition.GetSettings(); + + if (settings.Required && field.TermContentItemIds.Length == 0) + { + context.Fail(S["A value is required for '{0}'", context.ContentPartFieldDefinition.DisplayName()], nameof(field.TermContentItemIds)); + } + + return Task.CompletedTask; + } +} diff --git a/src/OrchardCore.Modules/OrchardCore.Taxonomies/Startup.cs b/src/OrchardCore.Modules/OrchardCore.Taxonomies/Startup.cs index 982ff35d10a..2c313628242 100644 --- a/src/OrchardCore.Modules/OrchardCore.Taxonomies/Startup.cs +++ b/src/OrchardCore.Modules/OrchardCore.Taxonomies/Startup.cs @@ -70,7 +70,8 @@ public override void ConfigureServices(IServiceCollection services) // Taxonomy Field services.AddContentField() - .UseDisplayDriver(d => !String.Equals(d, "Tags", StringComparison.OrdinalIgnoreCase)); + .UseDisplayDriver(d => !String.Equals(d, "Tags", StringComparison.OrdinalIgnoreCase)) + .AddHandler(); services.AddScoped(); services.AddScoped(); @@ -87,7 +88,6 @@ public override void ConfigureServices(IServiceCollection services) services.AddContentPart(); services.AddScoped(); services.AddScoped(); - } public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder routes, IServiceProvider serviceProvider)