Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,29 @@

namespace Umbraco.Cms.Core.PropertyEditors;

internal sealed class ColorPickerConfigurationEditor : ConfigurationEditor<ColorPickerConfiguration>
internal sealed partial class ColorPickerConfigurationEditor : ConfigurationEditor<ColorPickerConfiguration>
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorPickerConfigurationEditor"/> class.
/// </summary>
public ColorPickerConfigurationEditor(IIOHelper ioHelper, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
: base(ioHelper)
{
ConfigurationField items = Fields.First(x => x.Key == "items");
items.Validators.Add(new ColorListValidator(configurationEditorJsonSerializer));
}

internal sealed class ColorListValidator : IValueValidator
internal sealed partial class ColorListValidator : IValueValidator
{
private readonly IConfigurationEditorJsonSerializer _configurationEditorJsonSerializer;

/// <summary>
/// Initializes a new instance of the <see cref="ColorListValidator"/> class.
/// </summary>
public ColorListValidator(IConfigurationEditorJsonSerializer configurationEditorJsonSerializer)
=> _configurationEditorJsonSerializer = configurationEditorJsonSerializer;

/// <inheritdoc/>
public IEnumerable<ValidationResult> Validate(object? value, string? valueType, object? dataTypeConfiguration, PropertyValidationContext validationContext)
{
var stringValue = value?.ToString();
Expand All @@ -46,17 +53,53 @@ public IEnumerable<ValidationResult> Validate(object? value, string? valueType,

if (items is null)
{
yield return new ValidationResult($"The configuration value {stringValue} is not a valid color picker configuration", new[] { "items" });
yield return new ValidationResult($"The configuration value {stringValue} is not a valid color picker configuration", ["items"]);
yield break;
}

var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var duplicates = new List<string>();
foreach (ColorPickerConfiguration.ColorPickerItem item in items)
{
if (Regex.IsMatch(item.Value, "^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase) == false)
if (ColorPattern().IsMatch(item.Value) == false)
{
yield return new ValidationResult($"The value {item.Value} is not a valid hex color", new[] { "items" });
yield return new ValidationResult($"The value {item.Value} is not a valid hex color", ["items"]);
continue;
}

var normalized = Normalize(item.Value);
if (seen.Add(normalized) is false)
{
duplicates.Add(normalized);
}
}

if (duplicates.Count > 0)
{
yield return new ValidationResult(
$"Duplicate color values are not allowed: {string.Join(", ", duplicates)}",
["items"]);
}
}

private static string Normalize(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}

var normalizedValue = value.Trim().ToLowerInvariant();

if (normalizedValue.Length == 3)
{
normalizedValue = $"{normalizedValue[0]}{normalizedValue[0]}{normalizedValue[1]}{normalizedValue[1]}{normalizedValue[2]}{normalizedValue[2]}";
}

return normalizedValue;
}

[GeneratedRegex("^([0-9a-f]{3}|[0-9a-f]{6})$", RegexOptions.IgnoreCase, "en-GB")]
private static partial Regex ColorPattern();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,24 @@ public void Validates_Color_Vals()
PropertyValidationContext.Empty());
Assert.AreEqual(2, result.Count());
}

[Test]
public void Validates_Color_Vals_Are_Unique()
{
var validator = new ColorPickerConfigurationEditor.ColorListValidator(ConfigurationEditorJsonSerializer());
var result =
validator.Validate(
new JsonArray(
JsonNode.Parse("""{"value": "FFFFFF", "label": "One"}"""),
JsonNode.Parse("""{"value": "000000", "label": "Two"}"""),
JsonNode.Parse("""{"value": "FF00AA", "label": "Three"}"""),
JsonNode.Parse("""{"value": "fff", "label": "Four"}"""),
JsonNode.Parse("""{"value": "000000", "label": "Five"}"""),
JsonNode.Parse("""{"value": "F0A", "label": "Six"}""")),
null,
null,
PropertyValidationContext.Empty());
Assert.AreEqual(1, result.Count());
Assert.IsTrue(result.First().ErrorMessage.Contains("ffffff, 000000, ff00aa"));
}
}