Fixes all the block editor deserialization processes

This commit is contained in:
Shannon
2020-06-26 15:02:02 +10:00
parent b8893968e5
commit 8acc6ed5b4
16 changed files with 305 additions and 246 deletions
@@ -0,0 +1,22 @@
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Data converter for the block list property editor
/// </summary>
public class BlocListEditorDataConverter : BlockEditorDataConverter
{
public BlocListEditorDataConverter() : base(Constants.PropertyEditors.Aliases.BlockList)
{
}
protected override IReadOnlyList<Udi> GetBlockReferences(JToken jsonLayout)
{
var blockListLayout = jsonLayout.ToObject<IEnumerable<BlockListLayoutItem>>();
return blockListLayout.Select(x => x.Udi).ToList();
}
}
}
@@ -0,0 +1,66 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using Umbraco.Core.Serialization;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Converted block data from json
/// </summary>
public class BlockEditorData
{
public static BlockEditorData Empty { get; } = new BlockEditorData();
private BlockEditorData()
{
}
public BlockEditorData(JToken layout, IReadOnlyList<Udi> layoutBlockReferences, IReadOnlyList<BlockItemData> blocks)
{
Layout = layout ?? throw new ArgumentNullException(nameof(layout));
LayoutBlockReferences = layoutBlockReferences ?? throw new ArgumentNullException(nameof(layoutBlockReferences));
Blocks = blocks ?? throw new ArgumentNullException(nameof(blocks));
}
public JToken Layout { get; }
public IReadOnlyList<Udi> LayoutBlockReferences { get; } = new List<Udi>();
public IReadOnlyList<BlockItemData> Blocks { get; } = new List<BlockItemData>();
internal class BlockValue
{
[JsonProperty("layout")]
public IDictionary<string, JToken> Layout { get; set; }
[JsonProperty("data")]
public IEnumerable<BlockItemData> Data { get; set; }
}
/// <summary>
/// Represents a single block's data in raw form
/// </summary>
public class BlockItemData
{
[JsonProperty("contentTypeKey")]
public Guid ContentTypeKey { get; set; }
[JsonProperty("udi")]
[JsonConverter(typeof(UdiJsonConverter))]
public Udi Udi { get; set; }
/// <summary>
/// The remaining properties will be serialized to a dictionary
/// </summary>
/// <remarks>
/// The JsonExtensionDataAttribute is used to put the non-typed properties into a bucket
/// http://www.newtonsoft.com/json/help/html/DeserializeExtensionData.htm
/// NestedContent serializes to string, int, whatever eg
/// "stringValue":"Some String","numericValue":125,"otherNumeric":null
/// </remarks>
[JsonExtensionData]
public Dictionary<string, object> RawPropertyValues { get; set; } = new Dictionary<string, object>();
}
}
}
@@ -0,0 +1,46 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using System;
using System.Collections.Generic;
using static Umbraco.Core.Models.Blocks.BlockEditorData;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Converts the block json data into objects
/// </summary>
public abstract class BlockEditorDataConverter
{
private readonly string _propertyEditorAlias;
protected BlockEditorDataConverter(string propertyEditorAlias)
{
_propertyEditorAlias = propertyEditorAlias;
}
public BlockEditorData Convert(string json)
{
var value = JsonConvert.DeserializeObject<BlockValue>(json);
if (value.Layout == null)
return BlockEditorData.Empty;
if (!value.Layout.TryGetValue(_propertyEditorAlias, out var layout))
return BlockEditorData.Empty;
var references = GetBlockReferences(layout);
var blocks = value.Data.ToList();
return new BlockEditorData(layout, references, blocks);
}
/// <summary>
/// Return the collection of <see cref="IBlockReference"/> from the block editor's Layout (which could be an array or an object depending on the editor)
/// </summary>
/// <param name="jsonLayout"></param>
/// <returns></returns>
protected abstract IReadOnlyList<Udi> GetBlockReferences(JToken jsonLayout);
}
}
@@ -0,0 +1,19 @@
using Newtonsoft.Json;
using Umbraco.Core.Serialization;
using static Umbraco.Core.Models.Blocks.BlockEditorData;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Used for deserializing the block list layout
/// </summary>
public class BlockListLayoutItem
{
[JsonProperty("settings")]
public BlockItemData Settings { get; set; }
[JsonProperty("udi")]
[JsonConverter(typeof(UdiJsonConverter))]
public Udi Udi { get; set; }
}
}
@@ -1,16 +0,0 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
namespace Umbraco.Core.Models.Blocks
{
// TODO: Rename this, we don't want to use the name "Helper"
// TODO: What is this? This requires code docs
// TODO: This is not used publicly at all - therefore it probably shouldn't be public
// TODO: These could easily be abstract methods on the underlying BlockEditorPropertyEditor instead
public interface IBlockEditorDataHelper
{
// TODO: Does this abstraction need a reference to JObject? Maybe it does but ideally it doesn't
IEnumerable<IBlockReference> GetBlockReferences(JObject layout);
bool IsEditorSpecificPropertyKey(string propertyKey);
}
}
@@ -1,15 +1,5 @@
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Represents a data item reference for a Block Editor implementation
/// </summary>
/// <remarks>
/// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed
/// </remarks>
public interface IBlockReference
{
Udi Udi { get; }
}
// TODO: IBlockElement doesn't make sense, this is a reference to an actual element with some settings
// and always has to do with the "Layout", should possibly be called IBlockReference or IBlockLayout or IBlockLayoutReference
@@ -0,0 +1,13 @@
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Represents a data item reference for a Block Editor implementation
/// </summary>
/// <remarks>
/// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed
/// </remarks>
public interface IBlockReference
{
Udi Udi { get; }
}
}
@@ -37,7 +37,7 @@ namespace Umbraco.Core.Services
if (dataType == null) throw new InvalidOperationException("No data type found by id " + propertyType.DataTypeId);
var editor = _propertyEditors[propertyType.PropertyEditorAlias];
if (editor == null) throw new InvalidOperationException("No property editor found by alias " + propertyType.PropertyEditorAlias); ;
if (editor == null) throw new InvalidOperationException("No property editor found by alias " + propertyType.PropertyEditorAlias);
return ValidatePropertyValue(_textService, editor, dataType, postedValue, propertyType.Mandatory, propertyType.ValidationRegExp, propertyType.MandatoryMessage, propertyType.ValidationRegExpMessage);
}
+5 -1
View File
@@ -134,7 +134,11 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyTypeDto80.cs" />
<Compile Include="Migrations\Upgrade\V_8_7_0\ConvertToElements.cs" />
<Compile Include="Migrations\Upgrade\V_8_7_0\StackedContentToBlockList.cs" />
<Compile Include="Models\Blocks\IBlockEditorDataHelper.cs" />
<Compile Include="Models\Blocks\BlockEditorDataConverter.cs" />
<Compile Include="Models\Blocks\BlockEditorData.cs" />
<Compile Include="Models\Blocks\BlockListLayoutItem.cs" />
<Compile Include="Models\Blocks\BlocListEditorDataConverter.cs" />
<Compile Include="Models\Blocks\IBlockReference.cs" />
<Compile Include="Models\ContentDataIntegrityReport.cs" />
<Compile Include="Models\ContentDataIntegrityReportEntry.cs" />
<Compile Include="Models\ContentDataIntegrityReportOptions.cs" />
@@ -165,7 +165,7 @@ namespace Umbraco.Tests.PropertyEditors
Assert.AreEqual(0, converted.Layout.Count());
json = @"{
layout: [],
layout: {},
data: []}";
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
@@ -2,9 +2,7 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -12,9 +10,11 @@ using Umbraco.Core.Models.Blocks;
using Umbraco.Core.Models.Editors;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using static Umbraco.Core.Models.Blocks.BlockEditorData;
namespace Umbraco.Web.PropertyEditors
{
/// <summary>
/// Abstract class for block editor based editors
/// </summary>
@@ -22,16 +22,14 @@ namespace Umbraco.Web.PropertyEditors
{
public const string ContentTypeKeyPropertyKey = "contentTypeKey";
public const string UdiPropertyKey = "udi";
private readonly IBlockEditorDataHelper _dataHelper;
private readonly ILocalizedTextService _localizedTextService;
private readonly Lazy<PropertyEditorCollection> _propertyEditors;
private readonly IDataTypeService _dataTypeService;
private readonly IContentTypeService _contentTypeService;
public BlockEditorPropertyEditor(ILogger logger, Lazy<PropertyEditorCollection> propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, IBlockEditorDataHelper dataHelper, ILocalizedTextService localizedTextService)
public BlockEditorPropertyEditor(ILogger logger, Lazy<PropertyEditorCollection> propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService localizedTextService)
: base(logger)
{
_dataHelper = dataHelper;
_localizedTextService = localizedTextService;
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
@@ -43,22 +41,20 @@ namespace Umbraco.Web.PropertyEditors
#region Value Editor
protected override IDataValueEditor CreateValueEditor() => new BlockEditorPropertyValueEditor(Attribute, _dataHelper, PropertyEditors, _dataTypeService, _contentTypeService, _localizedTextService);
protected override IDataValueEditor CreateValueEditor() => new BlockEditorPropertyValueEditor(Attribute, PropertyEditors, _dataTypeService, _contentTypeService, _localizedTextService);
internal class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly IBlockEditorDataHelper _dataHelper;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
private readonly IDataTypeService _dataTypeService; // TODO: Not used yet but we'll need it to fill in the FromEditor/ToEditor
private readonly BlockEditorValues _blockEditorValues;
public BlockEditorPropertyValueEditor(DataEditorAttribute attribute, IBlockEditorDataHelper dataHelper, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService textService)
public BlockEditorPropertyValueEditor(DataEditorAttribute attribute, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService textService)
: base(attribute)
{
_dataHelper = dataHelper;
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
_blockEditorValues = new BlockEditorValues(dataHelper, contentTypeService);
_blockEditorValues = new BlockEditorValues(new BlocListEditorDataConverter(), contentTypeService);
Validators.Add(new BlockEditorValidator(_blockEditorValues, propertyEditors, dataTypeService, textService));
}
@@ -68,20 +64,21 @@ namespace Umbraco.Web.PropertyEditors
var result = new List<UmbracoEntityReference>();
foreach (var row in _blockEditorValues.GetPropertyValues(rawJson, out _))
foreach (var row in _blockEditorValues.GetPropertyValues(rawJson))
{
if (row.PropType == null) continue;
foreach (var prop in row.PropertyValues)
{
var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
var propEditor = _propertyEditors[row.PropType.PropertyEditorAlias];
var valueEditor = propEditor?.GetValueEditor();
if (!(valueEditor is IDataValueReference reference)) continue;
var valueEditor = propEditor?.GetValueEditor();
if (!(valueEditor is IDataValueReference reference)) continue;
var val = prop.Value.Value?.ToString();
var val = row.JsonRowValue[row.PropKey]?.ToString();
var refs = reference.GetReferences(val);
var refs = reference.GetReferences(val);
result.AddRange(refs);
result.AddRange(refs);
}
}
return result;
@@ -99,134 +96,112 @@ namespace Umbraco.Web.PropertyEditors
protected override IEnumerable<ElementTypeValidationModel> GetElementTypeValidation(object value)
{
// TODO: Fix all of this
return Enumerable.Empty<ElementTypeValidationModel>();
//foreach (var row in _blockEditorValues.GetPropertyValues(value, out _))
//{
// if (row.PropType == null) continue;
// var val = row.JsonRowValue[row.PropKey];
// yield return new ElementTypeValidationModel(val, row.PropType);
//}
foreach (var row in _blockEditorValues.GetPropertyValues(value))
{
var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias);
foreach (var prop in row.PropertyValues)
{
elementValidation.AddPropertyTypeValidation(
new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value));
}
yield return elementValidation;
}
}
}
internal class BlockEditorValues
{
private readonly IBlockEditorDataHelper _dataHelper;
private readonly Lazy<Dictionary<Guid, IContentType>> _contentTypes;
private readonly BlockEditorDataConverter _dataConverter;
public BlockEditorValues(IBlockEditorDataHelper dataHelper, IContentTypeService contentTypeService)
public BlockEditorValues(BlockEditorDataConverter dataConverter, IContentTypeService contentTypeService)
{
_dataHelper = dataHelper;
_contentTypes = new Lazy<Dictionary<Guid, IContentType>>(() => contentTypeService.GetAll().ToDictionary(c => c.Key));
_dataConverter = dataConverter;
}
private IContentType GetElementType(JObject item)
private IContentType GetElementType(BlockItemData item)
{
Guid contentTypeKey = item[ContentTypeKeyPropertyKey]?.ToObject<Guid>() ?? Guid.Empty;
_contentTypes.Value.TryGetValue(contentTypeKey, out var contentType);
_contentTypes.Value.TryGetValue(item.ContentTypeKey, out var contentType);
return contentType;
}
public IEnumerable<RowValue> GetPropertyValues(object propertyValue, out List<JObject> deserialized)
public IReadOnlyList<BlockValue> GetPropertyValues(object propertyValue)
{
var rowValues = new List<RowValue>();
deserialized = null;
if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString()))
return Enumerable.Empty<RowValue>();
return new List<BlockValue>();
var data = JsonConvert.DeserializeObject<BlockEditorData>(propertyValue.ToString());
if (data?.Layout == null || data.Data == null || data.Data.Count == 0)
return Enumerable.Empty<RowValue>();
var converted = _dataConverter.Convert(propertyValue.ToString());
var blockRefs = _dataHelper.GetBlockReferences(data.Layout);
if (blockRefs == null)
return Enumerable.Empty<RowValue>();
if (converted.Blocks.Count == 0)
return new List<BlockValue>();
var dataMap = new Dictionary<Udi, JObject>(data.Data.Count);
data.Data.ForEach(d =>
var contentTypePropertyTypes = new Dictionary<string, Dictionary<string, PropertyType>>();
var result = new List<BlockValue>();
foreach(var block in converted.Blocks)
{
var udiObj = d?[UdiPropertyKey];
if (Udi.TryParse(udiObj == null || udiObj.Type != JTokenType.String ? null : udiObj.ToString(), out var udi))
dataMap[udi] = d;
});
deserialized = blockRefs.Select(r => dataMap.TryGetValue(r.Udi, out var block) ? block : null).Where(r => r != null).ToList();
if (deserialized == null || deserialized.Count == 0)
return Enumerable.Empty<RowValue>();
var index = 0;
foreach (var o in deserialized)
{
var propValues = o;
var contentType = GetElementType(propValues);
var contentType = GetElementType(block);
if (contentType == null)
continue;
var propertyTypes = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x);
var propAliases = propValues.Properties().Select(x => x.Name);
foreach (var propAlias in propAliases)
// get the prop types for this content type but keep a dictionary of found ones so we don't have to keep re-looking and re-creating
// objects on each iteration.
if (!contentTypePropertyTypes.TryGetValue(contentType.Alias, out var propertyTypes))
propertyTypes = contentTypePropertyTypes[contentType.Alias] = contentType.CompositionPropertyTypes.ToDictionary(x => x.Alias, x => x);
var propValues = new Dictionary<string, BlockPropertyValue>();
// find any keys that are not real property types and remove them
foreach (var prop in block.RawPropertyValues.ToList())
{
propertyTypes.TryGetValue(propAlias, out var propType);
rowValues.Add(new RowValue(propAlias, propType, propValues, index));
// doesn't exist so remove it
if (!propertyTypes.TryGetValue(prop.Key, out var propType))
{
block.RawPropertyValues.Remove(prop.Key);
}
else
{
// set the value to include the resolved property type
propValues[prop.Key] = new BlockPropertyValue
{
PropertyType = propType,
Value = prop.Value
};
}
}
index++;
result.Add(new BlockValue
{
ContentTypeAlias = contentType.Alias,
PropertyValues = propValues
});
}
return rowValues;
return result;
}
// TODO: See notes in NestedContent property editor, this all needs to die and whatever is used should be shared between the editors
internal class RowValue
/// <summary>
/// Used during deserialization to populate the property value/property type of a nested content row property
/// </summary>
internal class BlockPropertyValue
{
public RowValue(string propKey, PropertyType propType, JObject propValues, int index)
{
PropKey = propKey ?? throw new ArgumentNullException(nameof(propKey));
PropType = propType;
JsonRowValue = propValues ?? throw new ArgumentNullException(nameof(propValues));
RowIndex = index;
}
/// <summary>
/// The current property key being iterated for the row value
/// </summary>
public string PropKey { get; }
/// <summary>
/// The <see cref="PropertyType"/> of the value (if any), this may be null
/// </summary>
public PropertyType PropType { get; }
/// <summary>
/// The json values for the current row
/// </summary>
public JObject JsonRowValue { get; }
/// <summary>
/// The Nested Content row index
/// </summary>
public int RowIndex { get; }
public object Value { get; set; }
public PropertyType PropertyType { get; set; }
}
private class BlockEditorData
/// <summary>
/// Used during deserialization to populate the content type alias and property values of a block
/// </summary>
internal class BlockValue
{
[JsonProperty("layout")]
public JObject Layout { get; set; }
[JsonProperty("data")]
public List<JObject> Data { get; set; }
public string ContentTypeAlias { get; set; }
public IDictionary<string, BlockPropertyValue> PropertyValues { get; set; } = new Dictionary<string, BlockPropertyValue>();
}
}
#endregion
// TODO: This isn't even used :/
private static bool IsSystemPropertyKey(string propertyKey) => ContentTypeKeyPropertyKey == propertyKey || UdiPropertyKey == propertyKey;
}
}
@@ -23,7 +23,7 @@ namespace Umbraco.Web.PropertyEditors
public class BlockListPropertyEditor : BlockEditorPropertyEditor
{
public BlockListPropertyEditor(ILogger logger, Lazy<PropertyEditorCollection> propertyEditors, IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILocalizedTextService localizedTextService)
: base(logger, propertyEditors, dataTypeService, contentTypeService, new DataHelper(), localizedTextService)
: base(logger, propertyEditors, dataTypeService, contentTypeService, localizedTextService)
{ }
#region Pre Value Editor
@@ -31,37 +31,5 @@ namespace Umbraco.Web.PropertyEditors
protected override IConfigurationEditor CreateConfigurationEditor() => new BlockListConfigurationEditor();
#endregion
#region IBlockEditorDataHelper
// TODO: Rename this we don't want to use the name "Helper"
private class DataHelper : IBlockEditorDataHelper
{
public IEnumerable<IBlockReference> GetBlockReferences(JObject layout)
{
if (!(layout?[Constants.PropertyEditors.Aliases.BlockList] is JArray blLayouts))
yield break;
foreach (var blLayout in blLayouts)
{
if (!(blLayout is JObject blockRef) || !(blockRef[UdiPropertyKey] is JValue udiRef) || udiRef.Type != JTokenType.String || !Udi.TryParse(udiRef.ToString(), out var udi)) continue;
yield return new SimpleRef(udi);
}
}
public bool IsEditorSpecificPropertyKey(string propertyKey) => false;
private class SimpleRef : IBlockReference
{
public SimpleRef(Udi udi)
{
Udi = udi;
}
public Udi Udi { get; }
}
}
#endregion
}
}
@@ -72,7 +72,8 @@ namespace Umbraco.Web.PropertyEditors
}
// add the property results to the element type results
elementTypeValidationResult.ValidationResults.Add(propValidationResult);
if (propValidationResult.ValidationResults.Count > 0)
elementTypeValidationResult.ValidationResults.Add(propValidationResult);
}
if (elementTypeValidationResult.ValidationResults.Count > 0)
@@ -86,7 +87,7 @@ namespace Umbraco.Web.PropertyEditors
{
public PropertyTypeValidationModel(PropertyType propertyType, object postedValue)
{
PostedValue = postedValue ?? throw new ArgumentNullException(nameof(postedValue));
PostedValue = postedValue;
PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType));
}
@@ -255,6 +255,9 @@ namespace Umbraco.Web.PropertyEditors
}
}
/// <summary>
/// Validator for nested content to ensure that all nesting of editors is validated
/// </summary>
internal class NestedContentValidator : ComplexEditorValidator
{
private readonly NestedContentValues _nestedContentValues;
@@ -280,6 +283,9 @@ namespace Umbraco.Web.PropertyEditors
}
}
/// <summary>
/// Used to deserialize the nested content serialized value
/// </summary>
internal class NestedContentValues
{
private readonly Lazy<Dictionary<string, IContentType>> _contentTypes;
@@ -295,7 +301,11 @@ namespace Umbraco.Web.PropertyEditors
return contentType;
}
// TODO: See note for "RowValue", luckily this is all internal as I'm pretty sure we should totally overhaul this
/// <summary>
/// Deserialize the raw json property value
/// </summary>
/// <param name="propertyValue"></param>
/// <returns></returns>
public IReadOnlyList<NestedContentRowValue> GetPropertyValues(object propertyValue)
{
if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString()))
@@ -349,12 +359,18 @@ namespace Umbraco.Web.PropertyEditors
return rowValues;
}
/// <summary>
/// Used during deserialization to populate the property value/property type of a nested content row property
/// </summary>
internal class NestedContentPropertyValue
{
public object Value { get; set; }
public PropertyType PropertyType { get; set; }
}
/// <summary>
/// Used to deserialize a nested content row
/// </summary>
internal class NestedContentRowValue
{
[JsonProperty("name")]
@@ -375,42 +391,12 @@ namespace Umbraco.Web.PropertyEditors
[JsonExtensionData]
public IDictionary<string, object> RawPropertyValues { get; set; }
/// <summary>
/// Used during deserialization to convert the raw property data into data with a property type context
/// </summary>
[JsonIgnore]
public IDictionary<string, NestedContentPropertyValue> PropertyValues { get; set; } = new Dictionary<string, NestedContentPropertyValue>();
}
//// TODO: This is a very odd class, it represents one 'row' of json per value of an NC item. It is a total
//// waste of data and the JsonRowValue string value is copied (i think for each 'row'), need to revisit this and make sure it makes sense.
//internal class RowValue
//{
// public RowValue(string propKey, PropertyType propType, JObject propValues, int index)
// {
// PropKey = propKey ?? throw new ArgumentNullException(nameof(propKey));
// PropType = propType;
// JsonRowValue = propValues ?? throw new ArgumentNullException(nameof(propValues));
// RowIndex = index;
// }
// /// <summary>
// /// The current property key being iterated for the row value
// /// </summary>
// public string PropKey { get; }
// /// <summary>
// /// The <see cref="PropertyType"/> of the value (if any), this may be null
// /// </summary>
// public PropertyType PropType { get; }
// /// <summary>
// /// The json values for the current row
// /// </summary>
// public JObject JsonRowValue { get; }
// /// <summary>
// /// The Nested Content row index
// /// </summary>
// public int RowIndex { get; }
//}
}
#endregion
@@ -2,12 +2,16 @@
using System;
using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Models.Blocks;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.PublishedCache;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
/// Converts json block objects into <see cref="IPublishedElement"/>
/// </summary>
public sealed class BlockEditorConverter
{
private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor;
@@ -20,36 +24,25 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
}
public IPublishedElement ConvertToElement(
JObject sourceObject, string contentTypeKeyPropertyKey,
BlockEditorData.BlockItemData data,
PropertyCacheLevel referenceCacheLevel, bool preview)
{
var elementTypeKey = sourceObject[contentTypeKeyPropertyKey]?.ToObject<Guid>();
if (!elementTypeKey.HasValue)
return null;
// hack! we need to cast, we have n ochoice beacuse we cannot make breaking changes.
// hack! we need to cast, we have no choice beacuse we cannot make breaking changes.
var publishedContentCache = _publishedSnapshotAccessor.PublishedSnapshot.Content as IPublishedContentCache2;
if (publishedContentCache == null)
throw new InvalidOperationException("The published content cache is not " + typeof(IPublishedContentCache2));
// only convert element types - content types will cause an exception when PublishedModelFactory creates the model
var publishedContentType = publishedContentCache.GetContentType(elementTypeKey.Value);
var publishedContentType = publishedContentCache.GetContentType(data.ContentTypeKey);
if (publishedContentType == null || publishedContentType.IsElement == false)
return null;
var propertyValues = sourceObject.ToObject<Dictionary<string, object>>();
var propertyValues = data.RawPropertyValues;
if (!propertyValues.TryGetValue("key", out var keyo) || !Guid.TryParse(keyo.ToString(), out var key))
{
if (propertyValues.TryGetValue("udi", out var udio) && udio is string udis && GuidUdi.TryParse(udis, out var udi))
{
key = udi.Guid;
}
else
{
key = Guid.Empty;
}
}
// Get the udi from the deserialized object. If this is empty we can fallback to checking the 'key' if there is one
var key = (data.Udi is GuidUdi gudi) ? gudi.Guid : Guid.Empty;
if (propertyValues.TryGetValue("key", out var keyo))
Guid.TryParse(keyo.ToString(), out key);
IPublishedElement element = new PublishedElement(publishedContentType, key, propertyValues, preview, referenceCacheLevel, _publishedSnapshotAccessor);
element = _publishedModelFactory.CreateModel(element);
@@ -61,22 +61,16 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var value = (string)inter;
if (string.IsNullOrWhiteSpace(value)) return model;
var objects = JsonConvert.DeserializeObject<JObject>(value);
if (objects.Count == 0) return model;
var converter = new BlocListEditorDataConverter();
var converted = converter.Convert(value);
if (converted.Blocks.Count == 0) return model;
var jsonLayout = objects["layout"] as JObject;
if (jsonLayout == null) return model;
var jsonData = objects["data"] as JArray;
if (jsonData == null) return model;
var blockListLayouts = jsonLayout[Constants.PropertyEditors.Aliases.BlockList] as JArray;
if (blockListLayouts == null) return model;
var blockListLayout = converted.Layout.ToObject<IEnumerable<BlockListLayoutItem>>();
// parse the data elements
foreach (var data in jsonData.Cast<JObject>())
foreach (var data in converted.Blocks)
{
var element = _blockConverter.ConvertToElement(data, BlockEditorPropertyEditor.ContentTypeKeyPropertyKey, referenceCacheLevel, preview);
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
elements[element.Key] = element;
}
@@ -84,14 +78,12 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
// if there's no elements just return since if there's no data it doesn't matter what is stored in layout
if (elements.Count == 0) return model;
foreach (var blockListLayout in blockListLayouts)
foreach (var layoutItem in blockListLayout)
{
var settingsJson = blockListLayout["settings"] as JObject;
// the result of this can be null, that's ok
var element = settingsJson != null ? _blockConverter.ConvertToElement(settingsJson, BlockEditorPropertyEditor.ContentTypeKeyPropertyKey, referenceCacheLevel, preview) : null;
var element = layoutItem.Settings != null ? _blockConverter.ConvertToElement(layoutItem.Settings, referenceCacheLevel, preview) : null;
if (!Udi.TryParse(blockListLayout.Value<string>("udi"), out var udi) || !(udi is GuidUdi guidUdi))
continue;
var guidUdi = (GuidUdi)layoutItem.Udi;
// get the data reference
if (!elements.TryGetValue(guidUdi.Guid, out var data))
@@ -107,7 +99,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
if (element != null && string.IsNullOrWhiteSpace(blockConfig.SettingsElementTypeKey))
element = null;
var layoutRef = new BlockListLayoutReference(udi, data, element);
var layoutRef = new BlockListLayoutReference(layoutItem.Udi, data, element);
layout.Add(layoutRef);
}