using Umbraco.Cms.Core.Cache.PropertyEditors; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Blocks; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; namespace Umbraco.Cms.Core.PropertyEditors; public abstract class BlockEditorValidatorBase : ComplexEditorValidator where TValue : BlockValue, new() where TLayout : class, IBlockLayoutItem, new() { private readonly IBlockEditorElementTypeCache _elementTypeCache; protected BlockEditorValidatorBase(IPropertyValidationService propertyValidationService, IBlockEditorElementTypeCache elementTypeCache) : base(propertyValidationService) => _elementTypeCache = elementTypeCache; protected IEnumerable GetBlockEditorDataValidation(BlockEditorData blockEditorData) { // There is no guarantee that the client will post data for every property defined in the Element Type but we still // need to validate that data for each property especially for things like 'required' data to work. // Lookup all element types for all content/settings and then we can populate any empty properties. var itemDataGroups = new[] { new { Path = nameof(BlockValue.ContentData).ToFirstLowerInvariant(), Items = blockEditorData.BlockValue.ContentData }, new { Path = nameof(BlockValue.SettingsData).ToFirstLowerInvariant(), Items = blockEditorData.BlockValue.SettingsData } }; foreach (var group in itemDataGroups) { var allElementTypes = _elementTypeCache.GetAll(group.Items.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key); for (var i = 0; i < group.Items.Count; i++) { BlockItemData item = group.Items[i]; if (!allElementTypes.TryGetValue(item.ContentTypeKey, out IContentType? elementType)) { throw new InvalidOperationException($"No element type found with key {item.ContentTypeKey}"); } // now ensure missing properties foreach (IPropertyType elementTypeProp in elementType.CompositionPropertyTypes) { if (!item.PropertyValues.ContainsKey(elementTypeProp.Alias)) { // set values to null item.PropertyValues[elementTypeProp.Alias] = new BlockItemData.BlockPropertyValue(null, elementTypeProp); item.RawPropertyValues[elementTypeProp.Alias] = null; } } var elementValidation = new ElementTypeValidationModel(item.ContentTypeAlias, item.Key); foreach (KeyValuePair prop in item.PropertyValues) { elementValidation.AddPropertyTypeValidation( new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value, $"{group.Path}[{i}].{prop.Value.PropertyType.Alias}")); } yield return elementValidation; } } } }