post merge

This commit is contained in:
Bjarke Berg
2020-08-17 11:41:50 +02:00
99 changed files with 1140 additions and 550 deletions

View File

@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// The base class for any strongly typed model for a Block editor implementation
/// </summary>
public abstract class BlockEditorModel
{
protected BlockEditorModel(IEnumerable<IPublishedElement> contentData, IEnumerable<IPublishedElement> settingsData)
{
ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData));
SettingsData = settingsData ?? new List<IPublishedContent>();
}
public BlockEditorModel()
{
}
/// <summary>
/// The content data items of the Block List editor
/// </summary>
[DataMember(Name = "contentData")]
public IEnumerable<IPublishedElement> ContentData { get; set; } = new List<IPublishedContent>();
/// <summary>
/// The settings data items of the Block List editor
/// </summary>
[DataMember(Name = "settingsData")]
public IEnumerable<IPublishedElement> SettingsData { get; set; } = new List<IPublishedContent>();
}
}

View File

@@ -49,8 +49,14 @@ namespace Umbraco.Core.Models.Blocks
/// </summary>
public class BlockPropertyValue
{
public object Value { get; set; }
public IPropertyType PropertyType { get; set; }
public BlockPropertyValue(object value, IPropertyType propertyType)
{
Value = value;
PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType));
}
public object Value { get; }
public IPropertyType PropertyType { get; }
}
}
}

View File

@@ -1,51 +0,0 @@
using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Represents a layout item for the Block List editor
/// </summary>
[DataContract(Name = "blockListLayout", Namespace = "")]
public class BlockListLayoutReference : IBlockReference<IPublishedElement>
{
public BlockListLayoutReference(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings)
{
ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
Content = content ?? throw new ArgumentNullException(nameof(content));
Settings = settings; // can be null
SettingsUdi = settingsUdi; // can be null
}
/// <summary>
/// The Id of the content data item
/// </summary>
[DataMember(Name = "contentUdi")]
public Udi ContentUdi { get; }
/// <summary>
/// The Id of the settings data item
/// </summary>
[DataMember(Name = "settingsUdi")]
public Udi SettingsUdi { get; }
/// <summary>
/// The content data item referenced
/// </summary>
/// <remarks>
/// This is ignored from serialization since it is just a reference to the actual data element
/// </remarks>
[IgnoreDataMember]
public IPublishedElement Content { get; }
/// <summary>
/// The settings data item referenced
/// </summary>
/// <remarks>
/// This is ignored from serialization since it is just a reference to the actual data element
/// </remarks>
[IgnoreDataMember]
public IPublishedElement Settings { get; }
}
}

View File

@@ -1,4 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Umbraco.Core.Models.PublishedContent;
@@ -8,26 +11,54 @@ namespace Umbraco.Core.Models.Blocks
/// The strongly typed model for the Block List editor
/// </summary>
[DataContract(Name = "blockList", Namespace = "")]
public class BlockListModel : BlockEditorModel
public class BlockListModel : IReadOnlyList<BlockListItem>
{
private readonly IReadOnlyList<BlockListItem> _layout = new List<BlockListItem>();
public static BlockListModel Empty { get; } = new BlockListModel();
private BlockListModel()
{
}
public BlockListModel(IEnumerable<IPublishedElement> contentData, IEnumerable<IPublishedElement> settingsData, IEnumerable<BlockListLayoutReference> layout)
: base(contentData, settingsData)
public BlockListModel(IEnumerable<BlockListItem> layout)
{
Layout = layout;
_layout = layout.ToList();
}
/// <summary>
/// The layout items of the Block List editor
/// </summary>
[DataMember(Name = "layout")]
public IEnumerable<BlockListLayoutReference> Layout { get; } = new List<BlockListLayoutReference>();
public int Count => _layout.Count;
/// <summary>
/// Get the block by index
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public BlockListItem this[int index] => _layout[index];
/// <summary>
/// Get the block by content Guid
/// </summary>
/// <param name="contentKey"></param>
/// <returns></returns>
public BlockListItem this[Guid contentKey] => _layout.FirstOrDefault(x => x.Content.Key == contentKey);
/// <summary>
/// Get the block by content element Udi
/// </summary>
/// <param name="contentUdi"></param>
/// <returns></returns>
public BlockListItem this[Udi contentUdi]
{
get
{
if (!(contentUdi is GuidUdi guidUdi)) return null;
return _layout.FirstOrDefault(x => x.Content.Key == guidUdi.Guid);
}
}
public IEnumerator<BlockListItem> GetEnumerator() => _layout.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@@ -1,26 +0,0 @@
namespace Umbraco.Core.Models.Blocks
{
/// <summary>
/// Represents a data item reference for a Block editor implementation
/// </summary>
/// <typeparam name="TSettings"></typeparam>
/// <remarks>
/// see: https://github.com/umbraco/rfcs/blob/907f3758cf59a7b6781296a60d57d537b3b60b8c/cms/0011-block-data-structure.md#strongly-typed
/// </remarks>
public interface IBlockReference<TSettings> : IBlockReference
{
TSettings Settings { get; }
}
/// <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 ContentUdi { get; }
}
}

View File

@@ -47,17 +47,9 @@ namespace Umbraco.Web.Models.Mapping
public ContentTypeBasic GetContentType(IContentBase source, MapperContext context)
{
var user = _umbracoContextAccessor.UmbracoContext?.Security?.CurrentUser;
if (user?.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)) ?? false)
{
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
var contentTypeBasic = context.Map<IContentTypeComposition, ContentTypeBasic>(contentType);
return contentTypeBasic;
}
//no access
return null;
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
var contentTypeBasic = context.Map<IContentTypeComposition, ContentTypeBasic>(contentType);
return contentTypeBasic;
}
public IEnumerable<ContentApp> GetContentApps(IUmbracoEntity source)

View File

@@ -54,7 +54,7 @@ namespace Umbraco.Web.PropertyEditors
internal class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference
{
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService; // TODO: Not used yet but we'll need it to fill in the FromEditor/ToEditor
private readonly IDataTypeService _dataTypeService;
private readonly ILogger _logger;
private readonly BlockEditorValues _blockEditorValues;
@@ -65,7 +65,7 @@ namespace Umbraco.Web.PropertyEditors
_dataTypeService = dataTypeService;
_logger = logger;
_blockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, _logger);
Validators.Add(new BlockEditorValidator(_blockEditorValues, propertyEditors, dataTypeService, textService));
Validators.Add(new BlockEditorValidator(_blockEditorValues, propertyEditors, dataTypeService, textService, contentTypeService));
Validators.Add(new MinMaxValidator(_blockEditorValues, textService));
}
@@ -246,19 +246,33 @@ namespace Umbraco.Web.PropertyEditors
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
{
var blockConfig = (BlockListConfiguration)dataTypeConfiguration;
if (blockConfig == null) yield break;
var validationLimit = blockConfig.ValidationLimit;
if (validationLimit == null) yield break;
var blockEditorData = _blockEditorValues.DeserializeAndClean(value);
if ((blockEditorData == null && blockConfig?.ValidationLimit?.Min > 0)
|| (blockEditorData != null && blockEditorData.Layout.Count() < blockConfig?.ValidationLimit?.Min))
if ((blockEditorData == null && validationLimit.Min.HasValue && validationLimit.Min > 0)
|| (blockEditorData != null && validationLimit.Min.HasValue && blockEditorData.Layout.Count() < validationLimit.Min))
{
yield return new ValidationResult(
_textService.Localize("validation/entriesShort", new[] { blockConfig.ValidationLimit.Min.ToString(), (blockConfig.ValidationLimit.Min - blockEditorData.Layout.Count()).ToString() }),
_textService.Localize("validation/entriesShort", new[]
{
validationLimit.Min.ToString(),
(validationLimit.Min - blockEditorData.Layout.Count()).ToString()
}),
new[] { "minCount" });
}
if (blockEditorData != null && blockEditorData.Layout.Count() > blockConfig?.ValidationLimit?.Max)
if (blockEditorData != null && validationLimit.Max.HasValue && blockEditorData.Layout.Count() > validationLimit.Max)
{
yield return new ValidationResult(
_textService.Localize("validation/entriesExceed", new[] { blockConfig.ValidationLimit.Max.ToString(), (blockEditorData.Layout.Count() - blockConfig.ValidationLimit.Max).ToString() }),
_textService.Localize("validation/entriesExceed", new[]
{
validationLimit.Max.ToString(),
(blockEditorData.Layout.Count() - validationLimit.Max).ToString()
}),
new[] { "maxCount" });
}
}
@@ -267,10 +281,13 @@ namespace Umbraco.Web.PropertyEditors
internal class BlockEditorValidator : ComplexEditorValidator
{
private readonly BlockEditorValues _blockEditorValues;
private readonly IContentTypeService _contentTypeService;
public BlockEditorValidator(BlockEditorValues blockEditorValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) : base(propertyEditors, dataTypeService, textService)
public BlockEditorValidator(BlockEditorValues blockEditorValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService, IContentTypeService contentTypeService)
: base(propertyEditors, dataTypeService, textService)
{
_blockEditorValues = blockEditorValues;
_contentTypeService = contentTypeService;
}
protected override IEnumerable<ElementTypeValidationModel> GetElementTypeValidation(object value)
@@ -278,8 +295,28 @@ namespace Umbraco.Web.PropertyEditors
var blockEditorData = _blockEditorValues.DeserializeAndClean(value);
if (blockEditorData != null)
{
foreach (var row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
// 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 allElements = blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData).ToList();
var allElementTypes = _contentTypeService.GetAll(allElements.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key);
foreach (var row in allElements)
{
if (!allElementTypes.TryGetValue(row.ContentTypeKey, out var elementType))
throw new InvalidOperationException($"No element type found with key {row.ContentTypeKey}");
// now ensure missing properties
foreach (var elementTypeProp in elementType.CompositionPropertyTypes)
{
if (!row.PropertyValues.ContainsKey(elementTypeProp.Alias))
{
// set values to null
row.PropertyValues[elementTypeProp.Alias] = new BlockPropertyValue(null, elementTypeProp);
row.RawPropertyValues[elementTypeProp.Alias] = null;
}
}
var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Key);
foreach (var prop in row.PropertyValues)
{
@@ -331,7 +368,7 @@ namespace Umbraco.Web.PropertyEditors
var contentTypePropertyTypes = new Dictionary<string, Dictionary<string, IPropertyType>>();
// filter out any content that isn't referenced in the layout references
foreach(var block in blockEditorData.BlockValue.ContentData.Where(x => blockEditorData.References.Any(r => r.ContentUdi == x.Udi)))
foreach (var block in blockEditorData.BlockValue.ContentData.Where(x => blockEditorData.References.Any(r => r.ContentUdi == x.Udi)))
{
ResolveBlockItemData(block, contentTypePropertyTypes);
}
@@ -374,11 +411,7 @@ namespace Umbraco.Web.PropertyEditors
else
{
// set the value to include the resolved property type
propValues[prop.Key] = new BlockPropertyValue
{
PropertyType = propType,
Value = prop.Value
};
propValues[prop.Key] = new BlockPropertyValue(prop.Value, propType);
}
}

View File

@@ -27,8 +27,7 @@ namespace Umbraco.Web.PropertyEditors
[JsonProperty("thumbnail")]
public string Thumbnail { get; set; }
// TODO: This is named inconsistently in JS but renaming it needs to be done in quite a lot of places, this should be contentElementTypeKey
[JsonProperty("contentTypeKey")]
[JsonProperty("contentElementTypeKey")]
public Guid ContentElementTypeKey { get; set; }
[JsonProperty("settingsElementTypeKey")]

View File

@@ -90,11 +90,12 @@ namespace Umbraco.Web.PropertyEditors
_dataTypeService = dataTypeService;
_logger = logger;
_nestedContentValues = new NestedContentValues(contentTypeService);
Validators.Add(new NestedContentValidator(_nestedContentValues, propertyEditors, dataTypeService, localizedTextService));
Validators.Add(new NestedContentValidator(_nestedContentValues, propertyEditors, dataTypeService, localizedTextService, contentTypeService));
_contentTypes = new Lazy<Dictionary<string, IContentType>>(() =>
_contentTypeService.GetAll().ToDictionary(c => c.Alias)
);
}
/// <inheritdoc />
@@ -300,16 +301,47 @@ namespace Umbraco.Web.PropertyEditors
internal class NestedContentValidator : ComplexEditorValidator
{
private readonly NestedContentValues _nestedContentValues;
private readonly IContentTypeService _contentTypeService;
public NestedContentValidator(NestedContentValues nestedContentValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService)
public NestedContentValidator(NestedContentValues nestedContentValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService, IContentTypeService contentTypeService)
: base(propertyEditors, dataTypeService, textService)
{
_nestedContentValues = nestedContentValues;
_contentTypeService = contentTypeService;
}
protected override IEnumerable<ElementTypeValidationModel> GetElementTypeValidation(object value)
{
foreach (var row in _nestedContentValues.GetPropertyValues(value))
var rows = _nestedContentValues.GetPropertyValues(value);
if (rows.Count == 0) yield break;
// 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 allElementAliases = rows.Select(x => x.ContentTypeAlias).ToList();
// unfortunately we need to get all content types and post filter - but they are cached so its ok, there's
// no overload to lookup by many aliases.
var allElementTypes = _contentTypeService.GetAll().Where(x => allElementAliases.Contains(x.Alias)).ToDictionary(x => x.Alias);
foreach (var row in rows)
{
if (!allElementTypes.TryGetValue(row.ContentTypeAlias, out var elementType))
throw new InvalidOperationException($"No element type found with alias {row.ContentTypeAlias}");
// now ensure missing properties
foreach (var elementTypeProp in elementType.CompositionPropertyTypes)
{
if (!row.PropertyValues.ContainsKey(elementTypeProp.Alias))
{
// set values to null
row.PropertyValues[elementTypeProp.Alias] = new NestedContentValues.NestedContentPropertyValue
{
PropertyType = elementTypeProp,
Value = null
};
row.RawPropertyValues[elementTypeProp.Alias] = null;
}
}
var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Id);
foreach (var prop in row.PropertyValues)
{

View File

@@ -58,7 +58,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
var contentPublishedElements = new Dictionary<Guid, IPublishedElement>();
var settingsPublishedElements = new Dictionary<Guid, IPublishedElement>();
var layout = new List<BlockListLayoutReference>();
var layout = new List<BlockListItem>();
var value = (string)inter;
if (string.IsNullOrWhiteSpace(value)) return BlockListModel.Empty;
@@ -120,11 +120,11 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
settingsData = null;
}
var layoutRef = new BlockListLayoutReference(contentGuidUdi, contentData, settingGuidUdi, settingsData);
var layoutRef = new BlockListItem(contentGuidUdi, contentData, settingGuidUdi, settingsData);
layout.Add(layoutRef);
}
var model = new BlockListModel(contentPublishedElements.Values, settingsPublishedElements.Values, layout);
var model = new BlockListModel(layout);
return model;
}
}