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)
{
}