// 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 }