Fixes all the block editor deserialization processes
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+10
-18
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user