Server side validation for property editors (colour picker) (#18557)
* Added server-side validation for colour picker. * Minor refactor. * Add danish translation --------- Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -122,6 +122,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="unexpectedRange">Værdien %0% forventes ikke at indeholde et spænd.</key>
|
||||
<key alias="invalidRange">Værdien %0% forventes at have en værdi der er større end fra værdien.</key>
|
||||
<key alias="notOneOfOptions">"Værdien '%0%' er ikke en af de tilgængelige valgmuligheder.</key>
|
||||
<key alias="invalidColor">"Den valgte farve '%0%' er ikke en af de tilgængelige valgmuligheder.</key>
|
||||
</area>
|
||||
<area alias="recycleBin">
|
||||
<key alias="contentTrashed">Slettet indhold med Id: {0} Relateret til original "parent" med id: {1}</key>
|
||||
|
||||
@@ -395,7 +395,8 @@
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
|
||||
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
|
||||
<key alias="notOneOfOptions">"The value '%0%' is not one of the available options.</key>
|
||||
<key alias="notOneOfOptions">The value '%0%' is not one of the available options.</key>
|
||||
<key alias="invalidColor">"The selected colour '%0%' is not one of the available options.</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -396,7 +396,8 @@
|
||||
<key alias="invalidMediaType">The chosen media type is invalid.</key>
|
||||
<key alias="multipleMediaNotAllowed">Multiple selected media is not allowed.</key>
|
||||
<key alias="invalidStartNode">The selected media is from the wrong folder.</key>
|
||||
<key alias="notOneOfOptions">"The value '%0%' is not one of the available options.</key>
|
||||
<key alias="notOneOfOptions">The value '%0%' is not one of the available options.</key>
|
||||
<key alias="invalidColor">The selected color '%0%' is not one of the available options.</key>
|
||||
</area>
|
||||
<area alias="healthcheck">
|
||||
<!-- The following keys get these tokens passed in:
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Nodes;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a color picker property editor.
|
||||
/// </summary>
|
||||
[DataEditor(
|
||||
Constants.PropertyEditors.Aliases.ColorPicker,
|
||||
ValueEditorIsReusable = true)]
|
||||
@@ -14,6 +24,9 @@ public class ColorPickerPropertyEditor : DataEditor
|
||||
private readonly IIOHelper _ioHelper;
|
||||
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ColorPickerPropertyEditor"/> class.
|
||||
/// </summary>
|
||||
public ColorPickerPropertyEditor(IDataValueEditorFactory dataValueEditorFactory, IIOHelper ioHelper, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
|
||||
: base(dataValueEditorFactory)
|
||||
{
|
||||
@@ -22,10 +35,78 @@ public class ColorPickerPropertyEditor : DataEditor
|
||||
SupportsReadOnly = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IPropertyIndexValueFactory PropertyIndexValueFactory { get; } = new NoopPropertyIndexValueFactory();
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IDataValueEditor CreateValueEditor()
|
||||
=> DataValueEditorFactory.Create<ColorPickerPropertyValueEditor>(Attribute!);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IConfigurationEditor CreateConfigurationEditor() =>
|
||||
new ColorPickerConfigurationEditor(_ioHelper, _configurationEditorJsonSerializer);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the value editor for the color picker property editor.
|
||||
/// </summary>
|
||||
internal class ColorPickerPropertyValueEditor : DataValueEditor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ColorPickerPropertyValueEditor"/> class.
|
||||
/// </summary>
|
||||
public ColorPickerPropertyValueEditor(
|
||||
IShortStringHelper shortStringHelper,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IIOHelper ioHelper,
|
||||
DataEditorAttribute attribute,
|
||||
ILocalizedTextService localizedTextService)
|
||||
: base(shortStringHelper, jsonSerializer, ioHelper, attribute)
|
||||
{
|
||||
Validators.AddRange(new ConfiguredColorValidator(localizedTextService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the color selection for the color picker property editor.
|
||||
/// </summary>
|
||||
internal class ConfiguredColorValidator : IValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _localizedTextService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConfiguredColorValidator"/> class.
|
||||
/// </summary>
|
||||
public ConfiguredColorValidator(ILocalizedTextService localizedTextService) => _localizedTextService = localizedTextService;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
|
||||
{
|
||||
if (value is null || value is not JsonObject valueAsJsonObject)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (dataTypeConfiguration is not ColorPickerConfiguration colorPickerConfiguration)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
string? selectedColor = valueAsJsonObject["value"]?.GetValue<string>();
|
||||
if (selectedColor.IsNullOrWhiteSpace())
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
IEnumerable<string> validColors = colorPickerConfiguration.Items.Select(x => EnsureConsistentColorRepresentation(x.Value));
|
||||
if (validColors.Contains(EnsureConsistentColorRepresentation(selectedColor)) is false)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_localizedTextService.Localize("validation", "invalidColor", [selectedColor]),
|
||||
["value"]);
|
||||
}
|
||||
}
|
||||
|
||||
private static string EnsureConsistentColorRepresentation(string color)
|
||||
=> (color.StartsWith('#') ? color : $"#{color}").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Globalization;
|
||||
using Humanizer;
|
||||
using System.Text.Json.Nodes;
|
||||
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.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
|
||||
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
|
||||
|
||||
[TestFixture]
|
||||
public class ColorPickerValueEditorTests
|
||||
{
|
||||
[TestCase("#ffffff", true)]
|
||||
[TestCase("#f0f0f0", false)]
|
||||
public void Validates_Is_Configured_Color(string color, bool expectedSuccess)
|
||||
{
|
||||
var value = JsonNode.Parse($"{{\"label\": \"\", \"value\": \"{color}\"}}");
|
||||
var editor = CreateValueEditor();
|
||||
var result = editor.Validate(value, false, null, PropertyValidationContext.Empty());
|
||||
if (expectedSuccess)
|
||||
{
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreEqual(1, result.Count());
|
||||
|
||||
var validationResult = result.First();
|
||||
Assert.AreEqual(validationResult.ErrorMessage, "validation_invalidColor");
|
||||
}
|
||||
}
|
||||
|
||||
private static ColorPickerPropertyEditor.ColorPickerPropertyValueEditor CreateValueEditor()
|
||||
{
|
||||
var localizedTextServiceMock = new Mock<ILocalizedTextService>();
|
||||
localizedTextServiceMock.Setup(x => x.Localize(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<CultureInfo>(),
|
||||
It.IsAny<IDictionary<string, string>>()))
|
||||
.Returns((string key, string alias, CultureInfo culture, IDictionary<string, string> args) => $"{key}_{alias}");
|
||||
return new ColorPickerPropertyEditor.ColorPickerPropertyValueEditor(
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
Mock.Of<IJsonSerializer>(),
|
||||
Mock.Of<IIOHelper>(),
|
||||
new DataEditorAttribute("alias"),
|
||||
localizedTextServiceMock.Object)
|
||||
{
|
||||
ConfigurationObject = new ColorPickerConfiguration
|
||||
{
|
||||
Items = [
|
||||
new ColorPickerConfiguration.ColorPickerItem { Value = "ffffff", Label = "White" },
|
||||
new ColorPickerConfiguration.ColorPickerItem { Value = "000000", Label = "Black" }
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user