Merge pull request #8854 from ronaldbarendse/v8/bugfix/generic-blocklistitem

8.7RC Get generic BlockListItem settings model type from configuration
This commit is contained in:
Mole
2021-09-02 14:45:22 +02:00
committed by GitHub
2 changed files with 79 additions and 52 deletions

View File

@@ -1,6 +1,4 @@
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System;
using Umbraco.Core;
using Umbraco.Core.Models.Blocks;
using Umbraco.Core.Models.PublishedContent;
@@ -10,7 +8,7 @@ using Umbraco.Web.PublishedCache;
namespace Umbraco.Web.PropertyEditors.ValueConverters
{
/// <summary>
/// Converts json block objects into <see cref="IPublishedElement"/>
/// Converts JSON block objects into <see cref="IPublishedElement" />.
/// </summary>
public sealed class BlockEditorConverter
{
@@ -23,30 +21,52 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
_publishedModelFactory = publishedModelFactory;
}
public IPublishedElement ConvertToElement(
BlockItemData data,
PropertyCacheLevel referenceCacheLevel, bool preview)
public IPublishedElement ConvertToElement(BlockItemData data, PropertyCacheLevel referenceCacheLevel, bool preview)
{
// hack! we need to cast, we have no choice beacuse we cannot make breaking changes.
var publishedContentType = GetContentType(data.ContentTypeKey);
// Only convert element types
if (publishedContentType == null || publishedContentType.IsElement == false)
{
return null;
}
var propertyValues = data.RawPropertyValues;
// 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 (key == Guid.Empty && 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);
return element;
}
public Type GetModelType(Guid contentTypeKey)
{
var publishedContentType = GetContentType(contentTypeKey);
if (publishedContentType != null)
{
var modelType = ModelType.For(publishedContentType.Alias);
return _publishedModelFactory.MapModelType(modelType);
}
return typeof(IPublishedElement);
}
private IPublishedContentType GetContentType(Guid contentTypeKey)
{
// HACK! We need to cast, we have no choice because we can't make breaking changes (and we need the GUID overload)
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(data.ContentTypeKey);
if (publishedContentType == null || publishedContentType.IsElement == false)
return null;
var propertyValues = data.RawPropertyValues;
// 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);
return element;
return publishedContentCache.GetContentType(contentTypeKey);
}
}
}

View File

@@ -1,6 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
@@ -51,16 +49,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
using (_proflog.DebugDuration<BlockListPropertyValueConverter>($"ConvertPropertyToBlockList ({propertyType.DataType.Id})"))
{
var configuration = propertyType.DataType.ConfigurationAs<BlockListConfiguration>();
var blockConfigMap = configuration.Blocks.ToDictionary(x => x.ContentElementTypeKey);
var validSettingElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey).Where(x => x.HasValue).Distinct().ToList();
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
var layout = new List<BlockListItem>();
var value = (string)inter;
// Short-circuit on empty values
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
var converted = _blockListEditorDataConverter.Deserialize(value);
@@ -68,49 +59,60 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var blockListLayout = converted.Layout.ToObject<IEnumerable<BlockListLayoutItem>>();
// convert the content data
// Get configuration
var configuration = propertyType.DataType.ConfigurationAs<BlockListConfiguration>();
var blockConfigMap = configuration.Blocks.ToDictionary(x => x.ContentElementTypeKey);
var validSettingsElementTypes = blockConfigMap.Values.Select(x => x.SettingsElementTypeKey).Where(x => x.HasValue).Distinct().ToList();
// Convert the content data
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
foreach (var data in converted.BlockValue.ContentData)
{
if (!blockConfigMap.ContainsKey(data.ContentTypeKey)) continue;
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
contentPublishedElements[element.Key] = element;
}
// convert the settings data
// If there are no content elements, it doesn't matter what is stored in layout
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
// Convert the settings data
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
foreach (var data in converted.BlockValue.SettingsData)
{
if (!validSettingElementTypes.Contains(data.ContentTypeKey)) continue;
if (!validSettingsElementTypes.Contains(data.ContentTypeKey)) continue;
var element = _blockConverter.ConvertToElement(data, referenceCacheLevel, preview);
if (element == null) continue;
settingsPublishedElements[element.Key] = element;
}
// if there's no elements just return since if there's no data it doesn't matter what is stored in layout
if (contentPublishedElements.Count == 0) return BlockListModel.Empty;
var layout = new List<BlockListItem>();
foreach (var layoutItem in blockListLayout)
{
// get the content reference
var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi;
// Get the content reference
var contentGuidUdi = (GuidUdi)layoutItem.ContentUdi;
if (!contentPublishedElements.TryGetValue(contentGuidUdi.Guid, out var contentData))
continue;
// get the setting reference
IPublishedElement settingsData = null;
var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null;
if (settingGuidUdi != null)
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
if (!contentData.ContentType.TryGetKey(out var contentTypeKey))
throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2));
if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig))
continue;
// this can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
// we also ensure that the content type's match since maybe the settings type has been changed after this has been persisted.
// Get the setting reference
IPublishedElement settingsData = null;
var settingGuidUdi = layoutItem.SettingsUdi != null ? (GuidUdi)layoutItem.SettingsUdi : null;
if (settingGuidUdi != null)
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
// This can happen if they have a settings type, save content, remove the settings type, and display the front-end page before saving the content again
// We also ensure that the content types match, since maybe the settings type has been changed after this has been persisted
if (settingsData != null)
{
if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey))
@@ -120,8 +122,13 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
settingsData = null;
}
// Get settings type from configuration
var settingsType = blockConfig.SettingsElementTypeKey.HasValue
? _blockConverter.GetModelType(blockConfig.SettingsElementTypeKey.Value)
: typeof(IPublishedElement);
// TODO: This should be optimized/cached, as calling Activator.CreateInstance is slow
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsData?.GetType() ?? typeof(IPublishedElement));
var layoutType = typeof(BlockListItem<,>).MakeGenericType(contentData.GetType(), settingsType);
var layoutRef = (BlockListItem)Activator.CreateInstance(layoutType, contentGuidUdi, contentData, settingGuidUdi, settingsData);
layout.Add(layoutRef);