diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml index 090958bbd1..03a4ab185e 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml @@ -125,6 +125,7 @@ Mange hilsner fra Umbraco robotten De %0% enheder givet er større end det tilladte minimum af %1%. Værdien %0% passer ikke med den konfigureret trin værdi af %1% og mindste værdi af %2%. Værdien %0% forventes ikke at indeholde et spænd. + Tekststrengen er længere end den tilladte længde på %0% tegn. Værdien %0% forventes at have en værdi der er større end fra værdien. Det valgte indhold er af den forkerte type. "Værdien '%0%' er ikke en af de tilgængelige valgmuligheder. diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index bb6d39d3fc..730fa1a6e9 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -378,6 +378,7 @@ Value is invalid, it does not match the correct pattern %1% more.]]> %1% too many.]]> + The string length exceeds the maximum length of %0% characters. The content amount requirements are not met for one or more areas. Invalid member group name Invalid user group name diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index bb2ba79a3b..2327ac21d0 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -395,6 +395,7 @@ The value %0% is not expected to have a to value less than the from value %1% more.]]> %1% too many.]]> + The string length exceeds the maximum length of %0% characters. The content amount requirements are not met for one or more areas. The chosen media type is invalid. The chosen content is of invalid type. diff --git a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs index 0b0d34b1ef..8a2ff20da6 100644 --- a/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/TextOnlyValueEditor.cs @@ -1,8 +1,13 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Validation; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; @@ -12,23 +17,27 @@ namespace Umbraco.Cms.Core.PropertyEditors; /// public class TextOnlyValueEditor : DataValueEditor { - [Obsolete($"Use the constructor that does not accept {nameof(ILocalizedTextService)}. Will be removed in V15.")] public TextOnlyValueEditor( DataEditorAttribute attribute, ILocalizedTextService localizedTextService, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : this(attribute, shortStringHelper, jsonSerializer, ioHelper) - { - } + : base(shortStringHelper, jsonSerializer, ioHelper, attribute) => + Validators.Add(new LengthValidator(localizedTextService)); + [Obsolete($"Use the constructor that accepts {nameof(ILocalizedTextService)}. Will be removed in V16.")] public TextOnlyValueEditor( DataEditorAttribute attribute, IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : base(shortStringHelper, jsonSerializer, ioHelper, attribute) + : this( + attribute, + StaticServiceProvider.Instance.GetRequiredService(), + shortStringHelper, + jsonSerializer, + ioHelper) { } @@ -65,4 +74,48 @@ public class TextOnlyValueEditor : DataValueEditor " can only be used with string based property editors"); } } + + /// + /// A common length validator for both textbox and text area. + /// + internal class LengthValidator : IValueValidator + { + private readonly ILocalizedTextService _localizedTextService; + + public LengthValidator(ILocalizedTextService localizedTextService) + { + _localizedTextService = localizedTextService; + } + + public IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration, + PropertyValidationContext validationContext) + { + int? maxCharacters = dataTypeConfiguration switch + { + TextAreaConfiguration areaConfiguration => areaConfiguration.MaxChars, + TextboxConfiguration textboxConfiguration => textboxConfiguration.MaxChars, + _ => null, + }; + + if (maxCharacters is null) + { + return []; + } + + if (value is string typedValue && typedValue.Length > maxCharacters) + { + return + [ + new ValidationResult( + _localizedTextService.Localize( + "validation", + "stringLengthExceeded", + [maxCharacters.ToString()]), + ["value'"]) + ]; + } + + return []; + } + } } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs index a9ff943921..ef8b8733c7 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/VariationTests.cs @@ -632,6 +632,7 @@ public class VariationTests var dataValueEditorFactory = Mock.Of(x => x.Create(It.IsAny()) == new TextOnlyValueEditor( attribute, + Mock.Of(), Mock.Of(), new SystemTextJsonSerializer(), Mock.Of())); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs index 217b820c78..16ea5c11f9 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/DataValueEditorReuseTests.cs @@ -29,6 +29,7 @@ public class DataValueEditorReuseTests .Setup(m => m.Create(It.IsAny())) .Returns(() => new TextOnlyValueEditor( new DataEditorAttribute("a"), + Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of())); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TextOnlyValueEditorValidatorTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TextOnlyValueEditorValidatorTests.cs new file mode 100644 index 0000000000..14fa669b72 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/Validators/TextOnlyValueEditorValidatorTests.cs @@ -0,0 +1,66 @@ +using System.ComponentModel.DataAnnotations; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models.Validation; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Serialization; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors.Validators; + +[TestFixture] +internal class TextOnlyValueEditorValidatorTests +{ + internal enum ConfigurationType + { + TextAreaConfiguration, + TextboxConfiguration, + } + + [TestCase(true, ConfigurationType.TextboxConfiguration, null, "123")] + [TestCase(true, ConfigurationType.TextAreaConfiguration, null, "123")] + [TestCase(false, ConfigurationType.TextAreaConfiguration, 2, "123")] + [TestCase(false, ConfigurationType.TextboxConfiguration, 2, "123")] + [TestCase(true, ConfigurationType.TextboxConfiguration, 10, "123")] + [TestCase(true, ConfigurationType.TextAreaConfiguration, 10, "123")] + public void Validates_String_Length(bool shouldSucceed, ConfigurationType configurationType, int? maxChars, string value) + { + var editor = CreateValueEditor(); + + editor.ConfigurationObject = CreateConfiguration(configurationType, maxChars); + + var results = editor.Validate(value, false, null, PropertyValidationContext.Empty()); + + ValidateResult(shouldSucceed, results); + } + + private static object CreateConfiguration(ConfigurationType type, int? maxChars) => + type switch + { + ConfigurationType.TextboxConfiguration => new TextboxConfiguration { MaxChars = maxChars }, + ConfigurationType.TextAreaConfiguration => new TextAreaConfiguration { MaxChars = maxChars }, + _ => throw new InvalidOperationException(), + }; + + private static void ValidateResult(bool succeed, IEnumerable result) + { + if (succeed) + { + Assert.IsEmpty(result); + } + else + { + Assert.IsNotEmpty(result); + } + } + + private TextOnlyValueEditor CreateValueEditor() => + new( + new DataEditorAttribute("alias"), + Mock.Of(), + Mock.Of(), + new SystemTextJsonSerializer(), + Mock.Of()); +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs index 70c6c95ebe..097a0495e5 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Services/PropertyValidationServiceTests.cs @@ -279,7 +279,7 @@ public class PropertyValidationServiceTests IShortStringHelper shortStringHelper, IJsonSerializer jsonSerializer, IIOHelper ioHelper) - : base(attribute, shortStringHelper, jsonSerializer, ioHelper) + : base(attribute, Mock.Of(), shortStringHelper, jsonSerializer, ioHelper) { }