// Copyright (c) Umbraco.
// See LICENSE for more details.
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Web.Common.DependencyInjection;
using Umbraco.Extensions;
using BlockGridAreaConfiguration = Umbraco.Cms.Core.PropertyEditors.BlockGridConfiguration.BlockGridAreaConfiguration;
namespace Umbraco.Cms.Core.PropertyEditors;
///
/// Abstract base class for block grid based editors.
///
public abstract class BlockGridPropertyEditorBase : DataEditor
{
private readonly IBlockValuePropertyIndexValueFactory _blockValuePropertyIndexValueFactory;
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
protected BlockGridPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory)
: this(dataValueEditorFactory, StaticServiceProvider.Instance.GetRequiredService())
{
}
protected BlockGridPropertyEditorBase(IDataValueEditorFactory dataValueEditorFactory, IBlockValuePropertyIndexValueFactory blockValuePropertyIndexValueFactory)
: base(dataValueEditorFactory)
{
_blockValuePropertyIndexValueFactory = blockValuePropertyIndexValueFactory;
SupportsReadOnly = true;
}
public override IPropertyIndexValueFactory PropertyIndexValueFactory => _blockValuePropertyIndexValueFactory;
#region Value Editor
protected override IDataValueEditor CreateValueEditor() =>
DataValueEditorFactory.Create(Attribute!);
private class BlockGridEditorPropertyValueEditor : BlockEditorPropertyValueEditor
{
public BlockGridEditorPropertyValueEditor(
DataEditorAttribute attribute,
PropertyEditorCollection propertyEditors,
DataValueReferenceFactoryCollection dataValueReferenceFactories,
IDataTypeService dataTypeService,
ILocalizedTextService textService,
ILogger logger,
IShortStringHelper shortStringHelper,
IJsonSerializer jsonSerializer,
IIOHelper ioHelper,
IContentTypeService contentTypeService,
IPropertyValidationService propertyValidationService)
: base(attribute, propertyEditors, dataValueReferenceFactories, dataTypeService, textService, logger, shortStringHelper, jsonSerializer, ioHelper)
{
BlockEditorValues = new BlockEditorValues(new BlockGridEditorDataConverter(jsonSerializer), contentTypeService, logger);
Validators.Add(new BlockEditorValidator(propertyValidationService, BlockEditorValues, contentTypeService));
Validators.Add(new MinMaxValidator(BlockEditorValues, textService));
}
private class MinMaxValidator : BlockEditorMinMaxValidatorBase
{
private readonly BlockEditorValues _blockEditorValues;
public MinMaxValidator(BlockEditorValues blockEditorValues, ILocalizedTextService textService)
: base(textService) =>
_blockEditorValues = blockEditorValues;
public override IEnumerable Validate(object? value, string? valueType, object? dataTypeConfiguration)
{
if (dataTypeConfiguration is not BlockGridConfiguration blockConfig)
{
return Array.Empty();
}
BlockEditorData? blockEditorData = _blockEditorValues.DeserializeAndClean(value);
var validationResults = new List();
validationResults.AddRange(ValidateNumberOfBlocks(blockEditorData, blockConfig.ValidationLimit.Min, blockConfig.ValidationLimit.Max));
var areasConfigsByKey = blockConfig.Blocks.SelectMany(b => b.Areas).ToDictionary(a => a.Key);
IList ExtractLayoutAreaItems(BlockGridLayoutItem item)
{
var areas = item.Areas.ToList();
areas.AddRange(item.Areas.SelectMany(a => a.Items).SelectMany(ExtractLayoutAreaItems));
return areas;
}
BlockGridLayoutAreaItem[]? areas = blockEditorData?.Layout?.ToObject>()?.SelectMany(ExtractLayoutAreaItems).ToArray();
if (areas?.Any() != true)
{
return validationResults;
}
foreach (BlockGridLayoutAreaItem area in areas)
{
if (!areasConfigsByKey.TryGetValue(area.Key, out BlockGridAreaConfiguration? areaConfig))
{
continue;
}
if ((areaConfig.MinAllowed.HasValue && area.Items.Length < areaConfig.MinAllowed) || (areaConfig.MaxAllowed.HasValue && area.Items.Length > areaConfig.MaxAllowed))
{
validationResults.Add(new ValidationResult(TextService.Localize("validation", "entriesAreasMismatch")));
}
}
return validationResults;
}
}
}
#endregion
}