Provide custom required validator for block list and toggle (#18474)

* Provide custom required validator for block list.

* Adds a custom required validator for the toggle to ensure the value provided is true.

* Remove unnecessary usings

* Remove redundant interface and base constructor

* Remove unnecessary interface

---------

Co-authored-by: mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
Andy Butland
2025-03-04 16:25:10 +01:00
committed by GitHub
parent a99c581ab5
commit 867d405992
9 changed files with 172 additions and 9 deletions

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
@@ -88,7 +88,7 @@ public class PropertyValidationService : IPropertyValidationService
{
// Retrieve default messages used for required and regex validatation. We'll replace these
// if set with custom ones if they've been provided for a given property.
var requiredDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.Missing };
var requiredDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.Missing, Constants.Validation.ErrorMessages.Properties.Empty };
var formatDefaultMessages = new[] { Constants.Validation.ErrorMessages.Properties.PatternMismatch };
IDataValueEditor valueEditor = _valueEditorCache.GetValueEditor(editor, dataType);

View File

@@ -20,8 +20,6 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal
where TValue : BlockValue<TLayout>, new()
where TLayout : class, IBlockLayoutItem, new()
{
private readonly IJsonSerializer _jsonSerializer;
[Obsolete("Please use the non-obsolete constructor. Will be removed in V16.")]
protected BlockEditorPropertyValueEditor(
DataEditorAttribute attribute,
@@ -52,7 +50,12 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal
IIOHelper ioHelper,
DataEditorAttribute attribute)
: base(propertyEditors, dataTypeConfigurationCache, shortStringHelper, jsonSerializer, dataValueReferenceFactories, blockEditorVarianceHandler, languageService, ioHelper, attribute) =>
_jsonSerializer = jsonSerializer;
JsonSerializer = jsonSerializer;
/// <summary>
/// Gets the <see cref="IJsonSerializer"/>.
/// </summary>
protected IJsonSerializer JsonSerializer { get; }
/// <inheritdoc />
public override IEnumerable<UmbracoEntityReference> GetReferences(object? value)
@@ -143,6 +146,6 @@ public abstract class BlockEditorPropertyValueEditor<TValue, TLayout> : BlockVal
MapBlockValueFromEditor(blockEditorData.BlockValue);
// return json
return _jsonSerializer.Serialize(blockEditorData.BlockValue);
return JsonSerializer.Serialize(blockEditorData.BlockValue);
}
}

View File

@@ -11,6 +11,7 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
using StaticServiceProvider = Umbraco.Cms.Core.DependencyInjection.StaticServiceProvider;
namespace Umbraco.Cms.Core.PropertyEditors;
@@ -74,6 +75,9 @@ public abstract class BlockListPropertyEditorBase : DataEditor
Validators.Add(new MinMaxValidator(BlockEditorValues, textService));
}
/// <inheritdoc />
public override IValueRequiredValidator RequiredValidator => new BlockListValueRequiredValidator(JsonSerializer);
protected override BlockListValue CreateWithLayout(IEnumerable<BlockListLayoutItem> layout) => new(layout);
private class MinMaxValidator : BlockEditorMinMaxValidatorBase<BlockListValue, BlockListLayoutItem>

View File

@@ -10,7 +10,6 @@ using Umbraco.Cms.Core.PropertyEditors.ValueConverters;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Extensions;
namespace Umbraco.Cms.Core.PropertyEditors;

View File

@@ -6,11 +6,12 @@ using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Editors;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
namespace Umbraco.Cms.Core.PropertyEditors;
/// <summary>
/// Represents a checkbox property and parameter editor.
/// Represents a true/false (toggle) property editor.
/// </summary>
[DataEditor(
Constants.PropertyEditors.Aliases.Boolean,
@@ -29,8 +30,14 @@ public class TrueFalsePropertyEditor : DataEditor
protected override IDataValueEditor CreateValueEditor()
=> DataValueEditorFactory.Create<TrueFalsePropertyValueEditor>(Attribute!);
/// <summary>
/// Defines the value editor for the true/false (toggle) property editor.
/// </summary>
internal class TrueFalsePropertyValueEditor : DataValueEditor
{
/// <summary>
/// Initializes a new instance of the <see cref="TrueFalsePropertyValueEditor"/> class.
/// </summary>
public TrueFalsePropertyValueEditor(
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
@@ -40,10 +47,17 @@ public class TrueFalsePropertyEditor : DataEditor
{
}
/// <inheritdoc />
public override IValueRequiredValidator RequiredValidator => new TrueFalseValueRequiredValidator();
/// <inheritdoc/>
public override object? ToEditor(IProperty property, string? culture = null, string? segment = null)
=> ParsePropertyValue(property.GetValue(culture, segment));
// NOTE: property editor value type is Integer, which means we need to store the boolean representation as 0 or 1
/// <inheritdoc/>
/// <remarks>
/// NOTE: property editor value type is Integer, which means we need to store the boolean representation as 0 or 1.
/// </remarks>
public override object? FromEditor(ContentPropertyData editorValue, object? currentValue)
=> ParsePropertyValue(editorValue.Value) ? 1 : 0;

View File

@@ -0,0 +1,40 @@
using System.ComponentModel.DataAnnotations;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.PropertyEditors.Validators;
using Umbraco.Cms.Core.Serialization;
namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
/// <summary>
/// Custom validator for block value required validation.
/// </summary>
internal class BlockListValueRequiredValidator : RequiredValidator
{
private readonly IJsonSerializer _jsonSerializer;
/// <summary>
/// Initializes a new instance of the <see cref="BlockListValueRequiredValidator"/> class.
/// </summary>
public BlockListValueRequiredValidator(IJsonSerializer jsonSerializer) => _jsonSerializer = jsonSerializer;
/// <inheritdoc/>
public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
{
IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType);
if (value is null)
{
return validationResults;
}
if (_jsonSerializer.TryDeserialize(value, out BlockListValue? blockListValue) &&
blockListValue.ContentData.Count == 0 &&
blockListValue.Layout.Count == 0)
{
validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"]));
}
return validationResults;
}
}

View File

@@ -0,0 +1,29 @@
using System.ComponentModel.DataAnnotations;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.PropertyEditors.Validators;
namespace Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
/// <summary>
/// Custom validator for true/false (toggle) required validation.
/// </summary>
internal class TrueFalseValueRequiredValidator : RequiredValidator
{
/// <inheritdoc/>
public override IEnumerable<ValidationResult> ValidateRequired(object? value, string? valueType)
{
IEnumerable<ValidationResult> validationResults = base.ValidateRequired(value, valueType);
if (value is null)
{
return validationResults;
}
if (value is bool valueAsBool && valueAsBool is false)
{
validationResults = validationResults.Append(new ValidationResult(Constants.Validation.ErrorMessages.Properties.Empty, ["value"]));
}
return validationResults;
}
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.Text.Json.Nodes;
using NUnit.Framework;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
using Umbraco.Cms.Infrastructure.Serialization;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
[TestFixture]
public class BlockListValueRequiredValidatorTests
{
[Test]
public void Validates_Empty_Block_List_As_Not_Provided()
{
var validator = new BlockListValueRequiredValidator(new SystemTextJsonSerializer());
var value = JsonNode.Parse("{ \"contentData\": [], \"settingsData\": [] }");
var result = validator.ValidateRequired(value, ValueTypes.Json);
Assert.AreEqual(1, result.Count());
}
[Test]
public void Validates_Populated_Block_List_As_Provided()
{
var validator = new BlockListValueRequiredValidator(new SystemTextJsonSerializer());
var value = JsonNode.Parse("{ \"contentData\": [ {} ], \"settingsData\": [] }");
var result = validator.ValidateRequired(value, ValueTypes.Json);
Assert.IsEmpty(result);
}
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using NUnit.Framework;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Infrastructure.PropertyEditors.Validators;
namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.PropertyEditors;
[TestFixture]
public class TrueFalseValueRequiredValidatorTests
{
[Test]
public void Validates_Null_Value_As_Not_Provided()
{
var validator = new TrueFalseValueRequiredValidator();
var result = validator.ValidateRequired(null, ValueTypes.Integer);
Assert.AreEqual(1, result.Count());
}
[Test]
public void Validates_False_Value_As_Not_Provided()
{
var validator = new TrueFalseValueRequiredValidator();
var result = validator.ValidateRequired(false, ValueTypes.Integer);
Assert.AreEqual(1, result.Count());
}
[Test]
public void Validates_True_Value_As_Provided()
{
var validator = new TrueFalseValueRequiredValidator();
var result = validator.ValidateRequired(true, ValueTypes.Integer);
Assert.IsEmpty(result);
}
}