From a90f0c407027e7a93aa3923cdbfc45fbad368c5a Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Wed, 26 Jun 2019 14:37:02 +0200 Subject: [PATCH] Added custom validation messages to property types: model, dto and migration, update via property type editing, display when content editing. --- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + ...AddPropertyTypeValidationMessageColumns.cs | 20 +++++++ src/Umbraco.Core/Models/PropertyType.cs | 24 ++++++++- .../Packaging/PackageDataInstallation.cs | 2 + .../Persistence/Dtos/PropertyTypeDto.cs | 10 ++++ .../Dtos/PropertyTypeReadOnlyDto.cs | 6 +++ .../Factories/PropertyGroupFactory.cs | 4 ++ .../Persistence/Mappers/PropertyTypeMapper.cs | 2 + .../Implement/ContentTypeCommonRepository.cs | 2 + .../PropertyEditors/DataValueEditor.cs | 6 +-- .../PropertyEditors/IDataValueEditor.cs | 6 ++- .../PropertyEditors/IValueFormatValidator.cs | 5 +- .../IValueRequiredValidator.cs | 5 +- .../Validators/RegexValidator.cs | 19 +++++-- .../Validators/RequiredValidator.cs | 22 ++++++-- .../Services/Implement/EntityXmlSerializer.cs | 4 ++ .../Services/PropertyValidationService.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Mapping/ContentTypeModelMappingTests.cs | 32 ++++++----- .../components/umbgroupsbuilder.directive.js | 6 ++- .../less/components/umb-group-builder.less | 16 ++++-- src/Umbraco.Web.UI.Client/src/less/forms.less | 3 +- .../propertysettings/propertysettings.html | 53 ++++++++++++------- .../textbox/textbox.controller.js | 11 +++- .../propertyeditors/textbox/textbox.html | 4 +- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 + .../Umbraco/config/lang/en_us.xml | 2 + .../Editors/Filters/ContentModelValidator.cs | 2 +- .../ContentEditing/ContentPropertyDto.cs | 7 +++ .../ContentEditing/PropertyTypeValidation.cs | 6 +++ .../Mapping/ContentPropertyDisplayMapper.cs | 2 + .../Mapping/ContentPropertyDtoMapper.cs | 2 + .../Mapping/ContentTypeMapDefinition.cs | 2 + .../Models/Mapping/PropertyTypeGroupMapper.cs | 8 ++- .../NestedContentPropertyEditor.cs | 19 +++++-- .../PropertyEditors/TagsPropertyEditor.cs | 2 +- 36 files changed, 254 insertions(+), 66 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/AddPropertyTypeValidationMessageColumns.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 834eade986..3a3c9cffc5 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -176,6 +176,7 @@ namespace Umbraco.Core.Migrations.Upgrade To("{B69B6E8C-A769-4044-A27E-4A4E18D1645A}"); To("{0372A42B-DECF-498D-B4D1-6379E907EB94}"); To("{5B1E0D93-F5A3-449B-84BA-65366B84E2D4}"); + To("{3D67D2C8-5E65-47D0-A9E1-DC2EE0779D6B}"); //FINAL } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/AddPropertyTypeValidationMessageColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/AddPropertyTypeValidationMessageColumns.cs new file mode 100644 index 0000000000..07514faf75 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_1_0/AddPropertyTypeValidationMessageColumns.cs @@ -0,0 +1,20 @@ +using System.Linq; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_1_0 +{ + public class AddPropertyTypeValidationMessageColumns : MigrationBase + { + public AddPropertyTypeValidationMessageColumns(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToList(); + + AddColumnIfNotExists(columns, "mandatoryMessage"); + AddColumnIfNotExists(columns, "validationRegExpMessage"); + } + } +} diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 1e950a876c..1ae5133f8d 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -25,8 +25,10 @@ namespace Umbraco.Core.Models private string _propertyEditorAlias; private ValueStorageType _valueStorageType; private bool _mandatory; + private string _mandatoryMessage; private int _sortOrder; private string _validationRegExp; + private string _validationRegExpMessage; private ContentVariation _variations; /// @@ -175,7 +177,7 @@ namespace Umbraco.Core.Models } /// - /// Gets of sets a value indicating whether a value for this property type is required. + /// Gets or sets a value indicating whether a value for this property type is required. /// [DataMember] public bool Mandatory @@ -184,6 +186,16 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _mandatory, nameof(Mandatory)); } + /// + /// Gets or sets the custom validation message used when a value for this PropertyType is required + /// + [DataMember] + public string MandatoryMessage + { + get => _mandatoryMessage; + set => SetPropertyValueAndDetectChanges(value, ref _mandatoryMessage, nameof(MandatoryMessage)); + } + /// /// Gets of sets the sort order of the property type. /// @@ -204,6 +216,16 @@ namespace Umbraco.Core.Models set => SetPropertyValueAndDetectChanges(value, ref _validationRegExp, nameof(ValidationRegExp)); } + /// + /// Gets or sets the custom validation message used when a pattern for this PropertyType must be matched + /// + [DataMember] + public string ValidationRegExpMessage + { + get => _validationRegExpMessage; + set => SetPropertyValueAndDetectChanges(value, ref _validationRegExpMessage, nameof(ValidationRegExpMessage)); + } + /// /// Gets or sets the content variation of the property type. /// diff --git a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs index c811f484bc..d5738b5e3f 100644 --- a/src/Umbraco.Core/Packaging/PackageDataInstallation.cs +++ b/src/Umbraco.Core/Packaging/PackageDataInstallation.cs @@ -749,7 +749,9 @@ namespace Umbraco.Core.Packaging Name = property.Element("Name").Value, Description = (string)property.Element("Description"), Mandatory = property.Element("Mandatory") != null ? property.Element("Mandatory").Value.ToLowerInvariant().Equals("true") : false, + MandatoryMessage = property.Element("MandatoryMessage") != null ? (string)property.Element("MandatoryMessage") : string.Empty, ValidationRegExp = (string)property.Element("Validation"), + ValidationRegExpMessage = property.Element("ValidationRegExpMessage") != null ? (string)property.Element("ValidationRegExpMessage") : string.Empty, SortOrder = sortOrder }; diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs index 8c52aa1e15..3e8d6e7496 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeDto.cs @@ -43,10 +43,20 @@ namespace Umbraco.Core.Persistence.Dtos [Constraint(Default = "0")] public bool Mandatory { get; set; } + [Column("mandatoryMessage")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string MandatoryMessage { get; set; } + [Column("validationRegExp")] [NullSetting(NullSetting = NullSettings.Null)] public string ValidationRegExp { get; set; } + [Column("validationRegExpMessage")] + [NullSetting(NullSetting = NullSettings.Null)] + [Length(500)] + public string ValidationRegExpMessage { get; set; } + [Column("Description")] [NullSetting(NullSetting = NullSettings.Null)] [Length(2000)] diff --git a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs index c68dee42b5..4c352a0134 100644 --- a/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/PropertyTypeReadOnlyDto.cs @@ -32,9 +32,15 @@ namespace Umbraco.Core.Persistence.Dtos [Column("mandatory")] public bool Mandatory { get; set; } + [Column("mandatoryMessage")] + public string MandatoryMessage { get; set; } + [Column("validationRegExp")] public string ValidationRegExp { get; set; } + [Column("validationRegExpMessage")] + public string ValidationRegExpMessage { get; set; } + [Column("Description")] public string Description { get; set; } diff --git a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs index c134748047..0846d6d1f6 100644 --- a/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/PropertyGroupFactory.cs @@ -59,8 +59,10 @@ namespace Umbraco.Core.Persistence.Factories propertyType.Key = typeDto.UniqueId; propertyType.Name = typeDto.Name; propertyType.Mandatory = typeDto.Mandatory; + propertyType.MandatoryMessage = typeDto.MandatoryMessage; propertyType.SortOrder = typeDto.SortOrder; propertyType.ValidationRegExp = typeDto.ValidationRegExp; + propertyType.ValidationRegExpMessage = typeDto.ValidationRegExpMessage; propertyType.PropertyGroupId = new Lazy(() => tempGroupDto.Id); propertyType.CreateDate = createDate; propertyType.UpdateDate = updateDate; @@ -123,9 +125,11 @@ namespace Umbraco.Core.Persistence.Factories DataTypeId = propertyType.DataTypeId, Description = propertyType.Description, Mandatory = propertyType.Mandatory, + MandatoryMessage = propertyType.MandatoryMessage, Name = propertyType.Name, SortOrder = propertyType.SortOrder, ValidationRegExp = propertyType.ValidationRegExp, + ValidationRegExpMessage = propertyType.ValidationRegExpMessage, UniqueId = propertyType.Key, Variations = (byte)propertyType.Variations }; diff --git a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs index ab1869a7f5..6f22b61f9a 100644 --- a/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs +++ b/src/Umbraco.Core/Persistence/Mappers/PropertyTypeMapper.cs @@ -24,9 +24,11 @@ namespace Umbraco.Core.Persistence.Mappers DefineMap(nameof(PropertyType.DataTypeId), nameof(PropertyTypeDto.DataTypeId)); DefineMap(nameof(PropertyType.Description), nameof(PropertyTypeDto.Description)); DefineMap(nameof(PropertyType.Mandatory), nameof(PropertyTypeDto.Mandatory)); + DefineMap(nameof(PropertyType.MandatoryMessage), nameof(PropertyTypeDto.MandatoryMessage)); DefineMap(nameof(PropertyType.Name), nameof(PropertyTypeDto.Name)); DefineMap(nameof(PropertyType.SortOrder), nameof(PropertyTypeDto.SortOrder)); DefineMap(nameof(PropertyType.ValidationRegExp), nameof(PropertyTypeDto.ValidationRegExp)); + DefineMap(nameof(PropertyType.ValidationRegExpMessage), nameof(PropertyTypeDto.ValidationRegExpMessage)); DefineMap(nameof(PropertyType.PropertyEditorAlias), nameof(DataTypeDto.EditorAlias)); DefineMap(nameof(PropertyType.ValueStorageType), nameof(DataTypeDto.DbType)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs index ccafb9f771..8cbcf4a523 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeCommonRepository.cs @@ -293,10 +293,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement Id = dto.Id, Key = dto.UniqueId, Mandatory = dto.Mandatory, + MandatoryMessage = dto.MandatoryMessage, Name = dto.Name, PropertyGroupId = groupId.HasValue ? new Lazy(() => groupId.Value) : null, SortOrder = dto.SortOrder, ValidationRegExp = dto.ValidationRegExp, + ValidationRegExpMessage = dto.ValidationRegExpMessage, Variations = (ContentVariation)dto.Variations }; } diff --git a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs index c4380f032c..b88c49583f 100644 --- a/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/DataValueEditor.cs @@ -85,7 +85,7 @@ namespace Umbraco.Core.PropertyEditors public string ValueType { get; set; } /// - public IEnumerable Validate(object value, bool required, string format) + public IEnumerable Validate(object value, bool required, string requiredMessage, string format, string formatMessage) { List results = null; var r = Validators.SelectMany(v => v.Validate(value, ValueType, Configuration)).ToList(); @@ -97,14 +97,14 @@ namespace Umbraco.Core.PropertyEditors if (required) { - r = RequiredValidator.ValidateRequired(value, ValueType).ToList(); + r = RequiredValidator.ValidateRequired(value, ValueType, requiredMessage).ToList(); if (r.Any()) { if (results == null) results = r; else results.AddRange(r); } } var stringValue = value?.ToString(); if (!string.IsNullOrWhiteSpace(format) && !string.IsNullOrWhiteSpace(stringValue)) { - r = FormatValidator.ValidateFormat(value, ValueType, format).ToList(); + r = FormatValidator.ValidateFormat(value, ValueType, format, formatMessage).ToList(); if (r.Any()) { if (results == null) results = r; else results.AddRange(r); } } diff --git a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs index cb68531cc7..0db4881da4 100644 --- a/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/IDataValueEditor.cs @@ -35,12 +35,14 @@ namespace Umbraco.Core.PropertyEditors bool HideLabel { get; } /// - /// Validates a property value. + /// Validates a property value using custom messages. /// /// The property value. /// A value indicating whether the property value is required. + /// A custom validation message to use when the property value is required. /// A specific format (regex) that the property value must respect. - IEnumerable Validate(object value, bool required, string format); + /// A custom validation message to use when the property value is does not match the specific format (regex). + IEnumerable Validate(object value, bool required, string requiredMessage, string format, string formatMessage); /// /// Gets the validators to use to validate the edited value. diff --git a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs index 3e4aea4385..a361f387e3 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueFormatValidator.cs @@ -14,11 +14,12 @@ namespace Umbraco.Core.PropertyEditors /// The value to validate. /// The value type. /// A format definition. + /// A custom validation message to use when the property value is does not match the specific format (regex). /// Validation results. /// /// The is expected to be a valid regular expression. /// This is used to validate values against the property type validation regular expression. /// - IEnumerable ValidateFormat(object value, string valueType, string format); + IEnumerable ValidateFormat(object value, string valueType, string format, string formatMessage); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs index f8e62788c8..7055e1287b 100644 --- a/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/IValueRequiredValidator.cs @@ -13,10 +13,11 @@ namespace Umbraco.Core.PropertyEditors /// /// The value to validate. /// The value type. + /// A custom validation message to use when the property value is required. /// Validation results. /// /// This is used to validate values when the property type specifies that a value is required. /// - IEnumerable ValidateRequired(object value, string valueType); + IEnumerable ValidateRequired(object value, string valueType, string requiredMessage); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs index e405fa3a3e..53686d745c 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RegexValidator.cs @@ -58,17 +58,28 @@ namespace Umbraco.Core.PropertyEditors.Validators public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { if (_regex == null) + { throw new InvalidOperationException("The validator has not been configured."); + } - return ValidateFormat(value, valueType, _regex); + return ValidateFormat(value, valueType, _regex, string.Empty); } /// - public IEnumerable ValidateFormat(object value, string valueType, string format) + public IEnumerable ValidateFormat(object value, string valueType, string format, string formatMessage) { - if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullOrEmptyException(nameof(format)); + if (string.IsNullOrWhiteSpace(format)) + { + throw new ArgumentNullOrEmptyException(nameof(format)); + } + if (value == null || !new Regex(format).IsMatch(value.ToString())) - yield return new ValidationResult(_textService.Localize("validation", "invalidPattern"), new[] { "value" }); + { + var message = string.IsNullOrWhiteSpace(formatMessage) + ? _textService.Localize("validation", "invalidPattern") + : formatMessage; + yield return new ValidationResult(message, new[] { "value" }); + } } } } diff --git a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs index c51f572817..d752c4ca47 100644 --- a/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs +++ b/src/Umbraco.Core/PropertyEditors/Validators/RequiredValidator.cs @@ -27,28 +27,40 @@ namespace Umbraco.Core.PropertyEditors.Validators /// public IEnumerable Validate(object value, string valueType, object dataTypeConfiguration) { - return ValidateRequired(value, valueType); + return ValidateRequired(value, valueType, string.Empty); } /// - public IEnumerable ValidateRequired(object value, string valueType) + public IEnumerable ValidateRequired(object value, string valueType, string requiredMessage) { if (value == null) { - yield return new ValidationResult(_textService.Localize("validation", "invalidNull"), new[] {"value"}); + var message = string.IsNullOrWhiteSpace(requiredMessage) + ? _textService.Localize("validation", "invalidNull") + : requiredMessage; + yield return new ValidationResult(message, new[] {"value"}); yield break; } if (valueType.InvariantEquals(ValueTypes.Json)) { + var message = string.IsNullOrWhiteSpace(requiredMessage) + ? _textService.Localize("validation", "invalidEmpty") + : requiredMessage; if (value.ToString().DetectIsEmptyJson()) - yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" }); + { + yield return new ValidationResult(message, new[] { "value" }); + } + yield break; } if (value.ToString().IsNullOrWhiteSpace()) { - yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" }); + var message = string.IsNullOrWhiteSpace(requiredMessage) + ? _textService.Localize("validation", "invalidEmpty") + : requiredMessage; + yield return new ValidationResult(message, new[] { "value" }); } } } diff --git a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs index 863ecc0b1b..d9be4b01b7 100644 --- a/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs +++ b/src/Umbraco.Core/Services/Implement/EntityXmlSerializer.cs @@ -360,7 +360,9 @@ namespace Umbraco.Core.Services.Implement new XElement("Definition", definition.Key), new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("Mandatory", propertyType.Mandatory.ToString()), + new XElement("MandatoryMessage", propertyType.MandatoryMessage), new XElement("Validation", propertyType.ValidationRegExp), + new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage), new XElement("Description", new XCData(propertyType.Description))); genericProperties.Add(genericProperty); } @@ -486,7 +488,9 @@ namespace Umbraco.Core.Services.Implement new XElement("Tab", propertyGroup == null ? "" : propertyGroup.Name), new XElement("SortOrder", propertyType.SortOrder), new XElement("Mandatory", propertyType.Mandatory.ToString()), + propertyType.MandatoryMessage != null ? new XElement("MandatoryMessage", propertyType.MandatoryMessage) : null, propertyType.ValidationRegExp != null ? new XElement("Validation", propertyType.ValidationRegExp) : null, + propertyType.ValidationRegExpMessage != null ? new XElement("ValidationRegExpMessage", propertyType.ValidationRegExpMessage) : null, propertyType.Description != null ? new XElement("Description", new XCData(propertyType.Description)) : null); genericProperties.Add(genericProperty); diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index b846095bd1..2e1ba0b8eb 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -133,7 +133,7 @@ namespace Umbraco.Core.Services var editor = _propertyEditors[propertyType.PropertyEditorAlias]; var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId).Configuration; var valueEditor = editor.GetValueEditor(configuration); - return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any(); + return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.MandatoryMessage, propertyType.ValidationRegExp, propertyType.ValidationRegExpMessage).Any(); } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 7c0e41348b..cbd372cfb6 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -240,6 +240,7 @@ + diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 21180ce51b..eee7446823 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -621,7 +621,9 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation() { Mandatory = true, - Pattern = "xyz" + MandatoryMessage = "Please enter a value", + Pattern = "xyz", + PatternMessage = "Please match the pattern", } }; @@ -634,7 +636,9 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.DataTypeId, result.DataTypeId); Assert.AreEqual(basic.Label, result.Name); Assert.AreEqual(basic.Validation.Mandatory, result.Mandatory); + Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); + Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); } [Test] @@ -655,7 +659,9 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation() { Mandatory = true, - Pattern = "xyz" + MandatoryMessage = "Please enter a value", + Pattern = "xyz", + PatternMessage = "Please match the pattern", } }; @@ -668,7 +674,9 @@ namespace Umbraco.Tests.Models.Mapping Assert.AreEqual(basic.DataTypeId, result.DataTypeId); Assert.AreEqual(basic.Label, result.Name); Assert.AreEqual(basic.Validation.Mandatory, result.Mandatory); + Assert.AreEqual(basic.Validation.MandatoryMessage, result.MandatoryMessage); Assert.AreEqual(basic.Validation.Pattern, result.ValidationRegExp); + Assert.AreEqual(basic.Validation.PatternMessage, result.ValidationRegExpMessage); } [Test] @@ -951,7 +959,7 @@ namespace Umbraco.Tests.Models.Mapping Name = "Tab 1", SortOrder = 0, Inherited = false, - Properties = new [] + Properties = new[] { new MemberPropertyTypeBasic { @@ -965,7 +973,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1000,7 +1008,7 @@ namespace Umbraco.Tests.Models.Mapping Name = "Tab 1", SortOrder = 0, Inherited = false, - Properties = new [] + Properties = new[] { new PropertyTypeBasic { @@ -1011,7 +1019,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1052,7 +1060,7 @@ namespace Umbraco.Tests.Models.Mapping Name = "Tab 1", SortOrder = 0, Inherited = false, - Properties = new [] + Properties = new[] { new PropertyTypeBasic { @@ -1063,7 +1071,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1109,7 +1117,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1133,7 +1141,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1187,7 +1195,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 @@ -1211,7 +1219,7 @@ namespace Umbraco.Tests.Models.Mapping Validation = new PropertyTypeValidation { Mandatory = false, - Pattern = "" + Pattern = string.Empty }, SortOrder = 0, DataTypeId = 555 diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js index 396699866c..1d04418060 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbgroupsbuilder.directive.js @@ -501,7 +501,9 @@ property.dataTypeIcon = propertyModel.dataTypeIcon; property.dataTypeName = propertyModel.dataTypeName; property.validation.mandatory = propertyModel.validation.mandatory; + property.validation.mandatoryMessage = propertyModel.validation.mandatoryMessage; property.validation.pattern = propertyModel.validation.pattern; + property.validation.patternMessage = propertyModel.validation.patternMessage; property.showOnMemberProfile = propertyModel.showOnMemberProfile; property.memberCanEdit = propertyModel.memberCanEdit; property.isSensitiveValue = propertyModel.isSensitiveValue; @@ -592,7 +594,9 @@ propertyState: "init", validation: { mandatory: false, - pattern: null + mandatoryMessage: null, + pattern: null, + patternMessage: null } }; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index 4c23aef5f0..36bd00c8cb 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -494,11 +494,11 @@ input.umb-group-builder__group-sort-value { font-weight: bold; font-size: 14px; color: @ui-action-type; - - &:hover{ + + &:hover { text-decoration: none; - color:@ui-action-type-hover; - border-color:@ui-action-border-hover; + color: @ui-action-type-hover; + border-color: @ui-action-border-hover; } } @@ -555,7 +555,13 @@ input.umb-group-builder__group-sort-value { overflow: hidden; } - .editor-validation-pattern{ + .editor-validation-message { + min-width: 100%; + min-height: 25px; + margin-top: 4px; + } + + .editor-validation-pattern { border: 1px solid @gray-7; margin: 10px 0 0; padding: 6px; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index cfbb8b78ab..72abb3ba00 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -528,7 +528,8 @@ input[type="checkbox"][readonly] { .help-inline { display: inline-block; vertical-align: middle; - padding-left: 5px; + padding-top: 4px; + padding-left: 2px; } div.help { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 93d7936326..56c2b94bd7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -81,38 +81,55 @@
- +
- -
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js index 32e9891eb4..2bbd7404f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.controller.js @@ -1,4 +1,4 @@ -function textboxController($scope) { +function textboxController($scope, localizationService) { // macro parameter editor doesn't contains a config object, // so we create a new one to hold any properties if (!$scope.model.config) { @@ -18,6 +18,15 @@ function textboxController($scope) { } } $scope.model.change(); + + $scope.mandatoryMessage = ""; + if ($scope.model.validation.mandatoryMessage) { + $scope.mandatoryMessage = $scope.model.validation.mandatoryMessage; + } else { + localizationService.localize("general_required").then(function (value) { + $scope.mandatoryMessage = value; + }); + } } angular.module('umbraco').controller("Umbraco.PropertyEditors.textboxController", textboxController); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html index 92f02b9f5b..238d70e5e3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/textbox/textbox.html @@ -7,9 +7,9 @@ ng-trim="false" ng-keyup="model.change()" /> - + {{textboxFieldForm.textbox.errorMsg}} - Required + {{mandatoryMessage}}
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index 175146aafd..7541297815 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1951,7 +1951,9 @@ To manage your website, simply open the Umbraco back office and start adding con Validate as a URL ...or enter a custom validation Field is mandatory + Enter a custom validation error message (optional) Enter a regular expression + Enter a custom validation error message (optional) You need to add at least You can only have items diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 6aa6054992..9357d4720a 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1961,7 +1961,9 @@ To manage your website, simply open the Umbraco back office and start adding con Validate as a URL ...or enter a custom validation Field is mandatory + Enter a custom validation error message (optional) Enter a regular expression + Enter a custom validation error message (optional) You need to add at least You can only have items diff --git a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs index 4acf0c948e..67f2e2d090 100644 --- a/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs +++ b/src/Umbraco.Web/Editors/Filters/ContentModelValidator.cs @@ -167,7 +167,7 @@ namespace Umbraco.Web.Editors.Filters { // validate var valueEditor = editor.GetValueEditor(property.DataType.Configuration); - foreach (var r in valueEditor.Validate(postedValue, property.IsRequired, property.ValidationRegExp)) + foreach (var r in valueEditor.Validate(postedValue, property.IsRequired, property.IsRequiredMessage, property.ValidationRegExp, property.ValidationRegExpMessage)) { modelState.AddPropertyError(r, property.Alias, property.Culture); } diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs index 1f9d0e9b77..83e5e2a9b2 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDto.cs @@ -11,10 +11,17 @@ namespace Umbraco.Web.Models.ContentEditing internal class ContentPropertyDto : ContentPropertyBasic { public IDataType DataType { get; set; } + public string Label { get; set; } + public string Description { get; set; } + public bool IsRequired { get; set; } + + public string IsRequiredMessage { get; set; } + public string ValidationRegExp { get; set; } + public string ValidationRegExpMessage { get; set; } } } diff --git a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs index 929c7ce324..a45af12341 100644 --- a/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs +++ b/src/Umbraco.Web/Models/ContentEditing/PropertyTypeValidation.cs @@ -11,7 +11,13 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "mandatory")] public bool Mandatory { get; set; } + [DataMember(Name = "mandatoryMessage")] + public string MandatoryMessage { get; set; } + [DataMember(Name = "pattern")] public string Pattern { get; set; } + + [DataMember(Name = "patternMessage")] + public string PatternMessage { get; set; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 8a45548e9c..535c67bee1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -42,7 +42,9 @@ namespace Umbraco.Web.Models.Mapping //add the validation information dest.Validation.Mandatory = originalProp.PropertyType.Mandatory; + dest.Validation.MandatoryMessage = originalProp.PropertyType.MandatoryMessage; dest.Validation.Pattern = originalProp.PropertyType.ValidationRegExp; + dest.Validation.PatternMessage = originalProp.PropertyType.ValidationRegExpMessage; if (dest.PropertyEditor == null) { diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index f192cd32ce..472a32f0b1 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -21,7 +21,9 @@ namespace Umbraco.Web.Models.Mapping base.Map(property, dest, context); dest.IsRequired = property.PropertyType.Mandatory; + dest.IsRequiredMessage = property.PropertyType.MandatoryMessage; dest.ValidationRegExp = property.PropertyType.ValidationRegExp; + dest.ValidationRegExpMessage = property.PropertyType.ValidationRegExpMessage; dest.Description = property.PropertyType.Description; dest.Label = property.PropertyType.Name; dest.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs index a438f04781..f89e0604b4 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapDefinition.cs @@ -220,7 +220,9 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Label; target.DataTypeId = source.DataTypeId; target.Mandatory = source.Validation.Mandatory; + target.MandatoryMessage = source.Validation.MandatoryMessage; target.ValidationRegExp = source.Validation.Pattern; + target.ValidationRegExpMessage = source.Validation.PatternMessage; target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing; if (source.Id > 0) diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index 8c5f347799..70074d22a9 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -223,7 +223,13 @@ namespace Umbraco.Web.Models.Mapping Alias = p.Alias, Description = p.Description, Editor = p.PropertyEditorAlias, - Validation = new PropertyTypeValidation {Mandatory = p.Mandatory, Pattern = p.ValidationRegExp}, + Validation = new PropertyTypeValidation + { + Mandatory = p.Mandatory, + MandatoryMessage = p.MandatoryMessage, + Pattern = p.ValidationRegExp, + PatternMessage = p.ValidationRegExpMessage, + }, Label = p.Name, View = propertyEditor.GetValueEditor().View, Config = config, diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs index 6dee2f78b5..f85171304b 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentPropertyEditor.cs @@ -313,9 +313,19 @@ namespace Umbraco.Web.PropertyEditors if (propType.Mandatory) { if (propValues[propKey] == null) - yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be null", new[] { propKey }); + { + var message = string.IsNullOrWhiteSpace(propType.MandatoryMessage) + ? $"'{propType.Name}' cannot be null" + : propType.MandatoryMessage; + yield return new ValidationResult($"Item {(i + 1)}: {message}", new[] { propKey }); + } else if (propValues[propKey].ToString().IsNullOrWhiteSpace() || (propValues[propKey].Type == JTokenType.Array && !propValues[propKey].HasValues)) - yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' cannot be empty", new[] { propKey }); + { + var message = string.IsNullOrWhiteSpace(propType.MandatoryMessage) + ? $"'{propType.Name}' cannot be empty" + : propType.MandatoryMessage; + yield return new ValidationResult($"Item{(i + 1)}: {message}", new[] { propKey }); + } } // Check regex @@ -325,7 +335,10 @@ namespace Umbraco.Web.PropertyEditors var regex = new Regex(propType.ValidationRegExp); if (!regex.IsMatch(propValues[propKey].ToString())) { - yield return new ValidationResult("Item " + (i + 1) + " '" + propType.Name + "' is invalid, it does not match the correct pattern", new[] { propKey }); + var message = string.IsNullOrWhiteSpace(propType.ValidationRegExpMessage) + ? $"'{propType.Name}' is invalid, it does not match the correct pattern" + : propType.ValidationRegExpMessage; + yield return new ValidationResult($"Item {(i + 1)}: {message}", new[] { propKey }); } } } diff --git a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs index 90527a8b8d..508f4d03e8 100644 --- a/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/TagsPropertyEditor.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web.PropertyEditors private class RequiredJsonValueValidator : IValueRequiredValidator { /// - public IEnumerable ValidateRequired(object value, string valueType) + public IEnumerable ValidateRequired(object value, string valueType, string requiredMessage) { if (value == null) {