2021-02-15 10:42:35 +01:00
|
|
|
|
// Copyright (c) Umbraco.
|
|
|
|
|
|
// See LICENSE for more details.
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
2020-09-18 14:37:19 +02:00
|
|
|
|
using Microsoft.Extensions.Logging;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
2021-05-18 12:40:24 +02:00
|
|
|
|
using Umbraco.Cms.Core.Hosting;
|
2021-02-09 10:22:42 +01:00
|
|
|
|
using Umbraco.Cms.Core.IO;
|
|
|
|
|
|
using Umbraco.Cms.Core.Models;
|
|
|
|
|
|
using Umbraco.Cms.Core.Models.Editors;
|
|
|
|
|
|
using Umbraco.Cms.Core.Serialization;
|
|
|
|
|
|
using Umbraco.Cms.Core.Services;
|
2021-05-18 12:40:24 +02:00
|
|
|
|
using Umbraco.Cms.Core.Services.Implement;
|
2021-02-09 10:22:42 +01:00
|
|
|
|
using Umbraco.Cms.Core.Strings;
|
2021-02-09 11:26:22 +01:00
|
|
|
|
using Umbraco.Extensions;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2021-02-15 10:42:35 +01:00
|
|
|
|
namespace Umbraco.Cms.Core.PropertyEditors
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2018-02-15 14:49:32 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Represents a nested content property editor.
|
|
|
|
|
|
/// </summary>
|
2019-06-07 17:59:38 +01:00
|
|
|
|
[DataEditor(
|
|
|
|
|
|
Constants.PropertyEditors.Aliases.NestedContent,
|
|
|
|
|
|
"Nested Content",
|
|
|
|
|
|
"nestedcontent",
|
|
|
|
|
|
ValueType = ValueTypes.Json,
|
|
|
|
|
|
Group = Constants.PropertyEditors.Groups.Lists,
|
2019-10-02 10:41:00 +02:00
|
|
|
|
Icon = "icon-thumbnail-list")]
|
2018-02-25 10:43:16 +01:00
|
|
|
|
public class NestedContentPropertyEditor : DataEditor
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-12-04 14:03:39 +01:00
|
|
|
|
private readonly IIOHelper _ioHelper;
|
2021-05-18 12:40:24 +02:00
|
|
|
|
|
2017-09-14 11:41:46 +02:00
|
|
|
|
|
2020-06-05 07:54:14 +02:00
|
|
|
|
public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2019-12-11 08:13:51 +01:00
|
|
|
|
public NestedContentPropertyEditor(
|
2021-05-18 12:40:24 +02:00
|
|
|
|
IDataValueEditorFactory dataValueEditorFactory,
|
|
|
|
|
|
IIOHelper ioHelper)
|
|
|
|
|
|
: base (dataValueEditorFactory)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-12-04 14:03:39 +01:00
|
|
|
|
_ioHelper = ioHelper;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region Pre Value Editor
|
|
|
|
|
|
|
2019-12-04 14:03:39 +01:00
|
|
|
|
protected override IConfigurationEditor CreateConfigurationEditor() => new NestedContentConfigurationEditor(_ioHelper);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Value Editor
|
|
|
|
|
|
|
2021-05-18 12:40:24 +02:00
|
|
|
|
protected override IDataValueEditor CreateValueEditor()
|
|
|
|
|
|
=> DataValueEditorFactory.Create<NestedContentPropertyValueEditor>(Attribute);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2019-12-02 15:00:56 +00:00
|
|
|
|
internal class NestedContentPropertyValueEditor : DataValueEditor, IDataValueReference
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2017-09-14 11:41:46 +02:00
|
|
|
|
private readonly PropertyEditorCollection _propertyEditors;
|
2019-10-30 17:14:04 +11:00
|
|
|
|
private readonly IDataTypeService _dataTypeService;
|
2020-09-18 14:37:19 +02:00
|
|
|
|
private readonly ILogger<NestedContentPropertyEditor> _logger;
|
2019-11-01 14:22:49 +11:00
|
|
|
|
private readonly NestedContentValues _nestedContentValues;
|
2017-09-14 11:41:46 +02:00
|
|
|
|
|
2020-08-06 12:59:21 +02:00
|
|
|
|
public NestedContentPropertyValueEditor(
|
|
|
|
|
|
IDataTypeService dataTypeService,
|
|
|
|
|
|
ILocalizedTextService localizedTextService,
|
|
|
|
|
|
IContentTypeService contentTypeService,
|
|
|
|
|
|
IShortStringHelper shortStringHelper,
|
|
|
|
|
|
DataEditorAttribute attribute,
|
|
|
|
|
|
PropertyEditorCollection propertyEditors,
|
2020-12-04 19:01:52 +01:00
|
|
|
|
ILogger<NestedContentPropertyEditor> logger,
|
2021-05-18 12:40:24 +02:00
|
|
|
|
IJsonSerializer jsonSerializer,
|
|
|
|
|
|
IIOHelper ioHelper,
|
|
|
|
|
|
IPropertyValidationService propertyValidationService)
|
|
|
|
|
|
: base(localizedTextService, shortStringHelper, jsonSerializer, ioHelper, attribute)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2017-09-14 11:41:46 +02:00
|
|
|
|
_propertyEditors = propertyEditors;
|
2019-10-30 17:14:04 +11:00
|
|
|
|
_dataTypeService = dataTypeService;
|
2020-07-23 23:04:11 +10:00
|
|
|
|
_logger = logger;
|
2019-11-01 14:22:49 +11:00
|
|
|
|
_nestedContentValues = new NestedContentValues(contentTypeService);
|
2021-05-18 12:40:24 +02:00
|
|
|
|
Validators.Add(new NestedContentValidator(propertyValidationService, _nestedContentValues, contentTypeService));
|
2019-10-24 12:34:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-01-26 17:55:20 +01:00
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
|
public override object Configuration
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2018-01-26 17:55:20 +01:00
|
|
|
|
get => base.Configuration;
|
|
|
|
|
|
set
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2018-01-26 17:55:20 +01:00
|
|
|
|
if (value == null)
|
|
|
|
|
|
throw new ArgumentNullException(nameof(value));
|
|
|
|
|
|
if (!(value is NestedContentConfiguration configuration))
|
2018-03-16 09:06:44 +01:00
|
|
|
|
throw new ArgumentException($"Expected a {typeof(NestedContentConfiguration).Name} instance, but got {value.GetType().Name}.", nameof(value));
|
2018-01-26 17:55:20 +01:00
|
|
|
|
base.Configuration = value;
|
2018-03-16 09:06:44 +01:00
|
|
|
|
|
|
|
|
|
|
HideLabel = configuration.HideLabel.TryConvertTo<bool>().Result;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region DB to String
|
|
|
|
|
|
|
2021-05-18 12:40:24 +02:00
|
|
|
|
public override string ConvertDbToString(IPropertyType propertyType, object propertyValue)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var rows = _nestedContentValues.GetPropertyValues(propertyValue);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
if (rows.Count == 0)
|
2022-01-10 09:32:29 +01:00
|
|
|
|
return null;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach (var row in rows.ToList())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach(var prop in row.PropertyValues.ToList())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
try
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// convert the value, and store the converted value
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
2019-11-01 14:22:49 +11:00
|
|
|
|
if (propEditor == null) continue;
|
|
|
|
|
|
|
2021-05-18 12:40:24 +02:00
|
|
|
|
var tempConfig = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration;
|
2019-10-30 17:14:04 +11:00
|
|
|
|
var valEditor = propEditor.GetValueEditor(tempConfig);
|
2021-05-18 12:40:24 +02:00
|
|
|
|
var convValue = valEditor.ConvertDbToString(prop.Value.PropertyType, prop.Value.Value);
|
2020-06-23 00:27:01 +10:00
|
|
|
|
|
|
|
|
|
|
// update the raw value since this is what will get serialized out
|
|
|
|
|
|
row.RawPropertyValues[prop.Key] = convValue;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
2020-07-23 23:04:11 +10:00
|
|
|
|
catch (InvalidOperationException ex)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// deal with weird situations by ignoring them (no comment)
|
2020-06-23 00:27:01 +10:00
|
|
|
|
row.RawPropertyValues.Remove(prop.Key);
|
2020-09-16 09:58:07 +02:00
|
|
|
|
_logger.LogWarning(
|
2020-07-23 23:04:11 +10:00
|
|
|
|
ex,
|
|
|
|
|
|
"ConvertDbToString removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}",
|
|
|
|
|
|
prop.Key, row.Id, propertyType.Alias);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-01-10 09:32:29 +01:00
|
|
|
|
return JsonConvert.SerializeObject(rows, Formatting.None).ToXmlString<string>();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2019-12-10 12:37:52 +01:00
|
|
|
|
|
2019-10-30 17:14:04 +11:00
|
|
|
|
|
2017-11-15 08:53:20 +01:00
|
|
|
|
#region Convert database // editor
|
|
|
|
|
|
|
|
|
|
|
|
// note: there is NO variant support here
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-07-23 16:03:35 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Ensure that sub-editor values are translated through their ToEditor methods
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="property"></param>
|
|
|
|
|
|
/// <param name="dataTypeService"></param>
|
|
|
|
|
|
/// <param name="culture"></param>
|
|
|
|
|
|
/// <param name="segment"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2019-11-12 10:42:39 +01:00
|
|
|
|
public override object ToEditor(IProperty property, string culture = null, string segment = null)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2018-04-21 09:57:28 +02:00
|
|
|
|
var val = property.GetValue(culture, segment);
|
2021-06-29 09:55:02 +02:00
|
|
|
|
var valEditors = new Dictionary<int, IDataValueEditor>();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var rows = _nestedContentValues.GetPropertyValues(val);
|
2019-11-01 14:22:49 +11:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
if (rows.Count == 0)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach (var row in rows.ToList())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach(var prop in row.PropertyValues.ToList())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
try
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// create a temp property with the value
|
|
|
|
|
|
// - force it to be culture invariant as NC can't handle culture variant element properties
|
2020-06-23 00:27:01 +10:00
|
|
|
|
prop.Value.PropertyType.Variations = ContentVariation.Nothing;
|
|
|
|
|
|
var tempProp = new Property(prop.Value.PropertyType);
|
|
|
|
|
|
|
|
|
|
|
|
tempProp.SetValue(prop.Value.Value);
|
2019-10-30 17:14:04 +11:00
|
|
|
|
|
|
|
|
|
|
// convert that temp property, and store the converted value
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
2019-11-01 14:22:49 +11:00
|
|
|
|
if (propEditor == null)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
// update the raw value since this is what will get serialized out
|
|
|
|
|
|
row.RawPropertyValues[prop.Key] = tempProp.GetValue()?.ToString();
|
2019-11-01 14:22:49 +11:00
|
|
|
|
continue;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-06-29 09:55:02 +02:00
|
|
|
|
var dataTypeId = prop.Value.PropertyType.DataTypeId;
|
|
|
|
|
|
if (!valEditors.TryGetValue(dataTypeId, out var valEditor))
|
|
|
|
|
|
{
|
2021-07-05 20:58:04 +02:00
|
|
|
|
var tempConfig = _dataTypeService.GetDataType(dataTypeId).Configuration;
|
2021-06-29 09:55:02 +02:00
|
|
|
|
valEditor = propEditor.GetValueEditor(tempConfig);
|
|
|
|
|
|
|
|
|
|
|
|
valEditors.Add(dataTypeId, valEditor);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-12-10 12:37:52 +01:00
|
|
|
|
var convValue = valEditor.ToEditor(tempProp);
|
2020-06-23 00:27:01 +10:00
|
|
|
|
|
|
|
|
|
|
// update the raw value since this is what will get serialized out
|
|
|
|
|
|
row.RawPropertyValues[prop.Key] = convValue == null ? null : JToken.FromObject(convValue);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
2020-07-23 23:04:11 +10:00
|
|
|
|
catch (InvalidOperationException ex)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// deal with weird situations by ignoring them (no comment)
|
2020-06-23 00:27:01 +10:00
|
|
|
|
row.RawPropertyValues.Remove(prop.Key);
|
2020-09-16 09:58:07 +02:00
|
|
|
|
_logger.LogWarning(
|
2020-07-23 23:04:11 +10:00
|
|
|
|
ex,
|
|
|
|
|
|
"ToEditor removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}",
|
|
|
|
|
|
prop.Key, row.Id, property.PropertyType.Alias);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-23 16:03:35 +10:00
|
|
|
|
// return the object, there's a native json converter for this so it will serialize correctly
|
2020-06-23 00:27:01 +10:00
|
|
|
|
return rows;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-07-23 16:03:35 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Ensure that sub-editor values are translated through their FromEditor methods
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="editorValue"></param>
|
|
|
|
|
|
/// <param name="currentValue"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2018-03-16 09:06:44 +01:00
|
|
|
|
public override object FromEditor(ContentPropertyData editorValue, object currentValue)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
|
|
|
|
|
if (editorValue.Value == null || string.IsNullOrWhiteSpace(editorValue.Value.ToString()))
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var rows = _nestedContentValues.GetPropertyValues(editorValue.Value);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
if (rows.Count == 0)
|
2022-01-10 09:32:29 +01:00
|
|
|
|
return null;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach (var row in rows.ToList())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach(var prop in row.PropertyValues.ToList())
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// Fetch the property types prevalue
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var propConfiguration = _dataTypeService.GetDataType(prop.Value.PropertyType.DataTypeId).Configuration;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// Lookup the property editor
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
2019-11-01 14:22:49 +11:00
|
|
|
|
if (propEditor == null) continue;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// Create a fake content property data object
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var contentPropData = new ContentPropertyData(prop.Value.Value, propConfiguration);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// Get the property editor to do it's conversion
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var newValue = propEditor.GetValueEditor().FromEditor(contentPropData, prop.Value.Value);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
// update the raw value since this is what will get serialized out
|
|
|
|
|
|
row.RawPropertyValues[prop.Key] = (newValue == null) ? null : JToken.FromObject(newValue);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-10-30 17:14:04 +11:00
|
|
|
|
// return json
|
2022-01-10 09:32:29 +01:00
|
|
|
|
return JsonConvert.SerializeObject(rows, Formatting.None);
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
2019-12-02 15:00:56 +00:00
|
|
|
|
|
|
|
|
|
|
public IEnumerable<UmbracoEntityReference> GetReferences(object value)
|
|
|
|
|
|
{
|
|
|
|
|
|
var rawJson = value == null ? string.Empty : value is string str ? str : value.ToString();
|
|
|
|
|
|
|
|
|
|
|
|
var result = new List<UmbracoEntityReference>();
|
|
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach (var row in _nestedContentValues.GetPropertyValues(rawJson))
|
2019-12-02 15:00:56 +00:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach(var prop in row.PropertyValues)
|
|
|
|
|
|
{
|
|
|
|
|
|
var propEditor = _propertyEditors[prop.Value.PropertyType.PropertyEditorAlias];
|
2019-12-02 15:00:56 +00:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var valueEditor = propEditor?.GetValueEditor();
|
|
|
|
|
|
if (!(valueEditor is IDataValueReference reference)) continue;
|
2019-12-02 15:00:56 +00:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var val = prop.Value.Value?.ToString();
|
2019-12-02 15:00:56 +00:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var refs = reference.GetReferences(val);
|
2019-12-02 15:00:56 +00:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
result.AddRange(refs);
|
|
|
|
|
|
}
|
2019-12-02 15:00:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-26 15:02:02 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Validator for nested content to ensure that all nesting of editors is validated
|
|
|
|
|
|
/// </summary>
|
2020-06-15 23:05:32 +10:00
|
|
|
|
internal class NestedContentValidator : ComplexEditorValidator
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2019-11-01 14:22:49 +11:00
|
|
|
|
private readonly NestedContentValues _nestedContentValues;
|
2020-08-05 19:03:02 +10:00
|
|
|
|
private readonly IContentTypeService _contentTypeService;
|
2017-09-14 11:41:46 +02:00
|
|
|
|
|
2021-05-18 12:40:24 +02:00
|
|
|
|
public NestedContentValidator(IPropertyValidationService propertyValidationService, NestedContentValues nestedContentValues, IContentTypeService contentTypeService)
|
|
|
|
|
|
: base(propertyValidationService)
|
2017-09-14 11:41:46 +02:00
|
|
|
|
{
|
2019-11-01 14:22:49 +11:00
|
|
|
|
_nestedContentValues = nestedContentValues;
|
2020-08-05 19:03:02 +10:00
|
|
|
|
_contentTypeService = contentTypeService;
|
2017-09-14 11:41:46 +02:00
|
|
|
|
}
|
2020-06-23 00:27:01 +10:00
|
|
|
|
protected override IEnumerable<ElementTypeValidationModel> GetElementTypeValidation(object value)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-08-05 19:03:02 +10:00
|
|
|
|
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)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-08-05 19:03:02 +10:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-29 21:55:54 +10:00
|
|
|
|
var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Id);
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach (var prop in row.PropertyValues)
|
2017-09-12 16:22:16 +02:00
|
|
|
|
{
|
2020-06-23 13:34:19 +10:00
|
|
|
|
elementValidation.AddPropertyTypeValidation(
|
2020-08-06 12:59:21 +02:00
|
|
|
|
new PropertyTypeValidationModel(prop.Value.PropertyType, prop.Value.Value));
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
2020-06-23 00:27:01 +10:00
|
|
|
|
yield return elementValidation;
|
2019-11-01 14:22:49 +11:00
|
|
|
|
}
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-10-24 21:25:15 +02:00
|
|
|
|
|
2020-06-26 15:02:02 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Used to deserialize the nested content serialized value
|
|
|
|
|
|
/// </summary>
|
2019-11-01 14:22:49 +11:00
|
|
|
|
internal class NestedContentValues
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly Lazy<Dictionary<string, IContentType>> _contentTypes;
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2019-11-01 14:22:49 +11:00
|
|
|
|
public NestedContentValues(IContentTypeService contentTypeService)
|
|
|
|
|
|
{
|
|
|
|
|
|
_contentTypes = new Lazy<Dictionary<string, IContentType>>(() => contentTypeService.GetAll().ToDictionary(c => c.Alias));
|
|
|
|
|
|
}
|
2017-09-12 16:22:16 +02:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
private IContentType GetElementType(NestedContentRowValue item)
|
2019-11-01 14:22:49 +11:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
_contentTypes.Value.TryGetValue(item.ContentTypeAlias, out var contentType);
|
2019-11-01 14:22:49 +11:00
|
|
|
|
return contentType;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-26 15:02:02 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Deserialize the raw json property value
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="propertyValue"></param>
|
|
|
|
|
|
/// <returns></returns>
|
2020-06-23 00:27:01 +10:00
|
|
|
|
public IReadOnlyList<NestedContentRowValue> GetPropertyValues(object propertyValue)
|
2019-11-01 14:22:49 +11:00
|
|
|
|
{
|
|
|
|
|
|
if (propertyValue == null || string.IsNullOrWhiteSpace(propertyValue.ToString()))
|
2020-06-23 00:27:01 +10:00
|
|
|
|
return new List<NestedContentRowValue>();
|
2019-11-01 14:22:49 +11:00
|
|
|
|
|
2020-10-29 02:34:51 +01:00
|
|
|
|
if (!propertyValue.ToString().DetectIsJson())
|
|
|
|
|
|
return new List<NestedContentRowValue>();
|
2020-11-30 11:24:24 +01:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var rowValues = JsonConvert.DeserializeObject<List<NestedContentRowValue>>(propertyValue.ToString());
|
2019-11-01 14:22:49 +11:00
|
|
|
|
|
|
|
|
|
|
// There was a note here about checking if the result had zero items and if so it would return null, so we'll continue to do that
|
|
|
|
|
|
// The original note was: "Issue #38 - Keep recursive property lookups working"
|
|
|
|
|
|
// Which is from the original NC tracker: https://github.com/umco/umbraco-nested-content/issues/38
|
|
|
|
|
|
// This check should be used everywhere when iterating NC prop values, instead of just the one previous place so that
|
|
|
|
|
|
// empty values don't get persisted when there is nothing, it should actually be null.
|
2020-06-23 00:27:01 +10:00
|
|
|
|
if (rowValues == null || rowValues.Count == 0)
|
|
|
|
|
|
return new List<NestedContentRowValue>();
|
2019-11-01 14:22:49 +11:00
|
|
|
|
|
2020-08-06 12:59:21 +02:00
|
|
|
|
var contentTypePropertyTypes = new Dictionary<string, Dictionary<string, IPropertyType>>();
|
2019-11-01 14:22:49 +11:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
foreach (var row in rowValues)
|
2019-11-01 14:22:49 +11:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
var contentType = GetElementType(row);
|
2019-11-01 14:22:49 +11:00
|
|
|
|
if (contentType == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
|
|
// find any keys that are not real property types and remove them
|
2020-10-29 02:34:51 +01:00
|
|
|
|
if (row.RawPropertyValues != null)
|
2019-11-01 14:22:49 +11:00
|
|
|
|
{
|
2020-10-29 02:34:51 +01:00
|
|
|
|
foreach (var prop in row.RawPropertyValues.ToList())
|
2020-06-23 00:27:01 +10:00
|
|
|
|
{
|
2020-10-29 02:34:51 +01:00
|
|
|
|
if (IsSystemPropertyKey(prop.Key)) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// doesn't exist so remove it
|
|
|
|
|
|
if (!propertyTypes.TryGetValue(prop.Key, out var propType))
|
2020-06-23 00:27:01 +10:00
|
|
|
|
{
|
2020-10-29 02:34:51 +01:00
|
|
|
|
row.RawPropertyValues.Remove(prop.Key);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// set the value to include the resolved property type
|
|
|
|
|
|
row.PropertyValues[prop.Key] = new NestedContentPropertyValue
|
|
|
|
|
|
{
|
|
|
|
|
|
PropertyType = propType,
|
|
|
|
|
|
Value = prop.Value
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2020-06-23 00:27:01 +10:00
|
|
|
|
}
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
2019-11-01 14:22:49 +11:00
|
|
|
|
}
|
2019-11-01 14:45:05 +11:00
|
|
|
|
|
|
|
|
|
|
return rowValues;
|
2019-11-01 14:22:49 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-26 15:02:02 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Used during deserialization to populate the property value/property type of a nested content row property
|
|
|
|
|
|
/// </summary>
|
2020-06-23 00:27:01 +10:00
|
|
|
|
internal class NestedContentPropertyValue
|
2019-11-01 14:22:49 +11:00
|
|
|
|
{
|
2020-06-23 00:27:01 +10:00
|
|
|
|
public object Value { get; set; }
|
2020-08-06 12:59:21 +02:00
|
|
|
|
public IPropertyType PropertyType { get; set; }
|
2020-06-23 00:27:01 +10:00
|
|
|
|
}
|
2019-11-01 14:22:49 +11:00
|
|
|
|
|
2020-06-26 15:02:02 +10:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Used to deserialize a nested content row
|
|
|
|
|
|
/// </summary>
|
2020-06-23 00:27:01 +10:00
|
|
|
|
internal class NestedContentRowValue
|
|
|
|
|
|
{
|
2020-06-29 21:55:54 +10:00
|
|
|
|
[JsonProperty("key")]
|
|
|
|
|
|
public Guid Id{ get; set; }
|
2019-11-01 14:45:05 +11:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
[JsonProperty("name")]
|
|
|
|
|
|
public string Name { get; set; }
|
2019-11-01 14:45:05 +11:00
|
|
|
|
|
2020-06-23 00:27:01 +10:00
|
|
|
|
[JsonProperty("ncContentTypeAlias")]
|
|
|
|
|
|
public string ContentTypeAlias { get; set; }
|
2019-12-10 12:37:52 +01:00
|
|
|
|
public IPropertyType PropType { get; }
|
2019-11-01 14:45:05 +11:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-06-23 00:27:01 +10:00
|
|
|
|
/// The remaining properties will be serialized to a dictionary
|
2019-11-01 14:45:05 +11:00
|
|
|
|
/// </summary>
|
2020-06-23 00:27:01 +10:00
|
|
|
|
/// <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 IDictionary<string, object> RawPropertyValues { get; set; }
|
2019-11-01 14:45:05 +11:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2020-06-26 15:02:02 +10:00
|
|
|
|
/// Used during deserialization to convert the raw property data into data with a property type context
|
2019-11-01 14:45:05 +11:00
|
|
|
|
/// </summary>
|
2020-06-23 00:27:01 +10:00
|
|
|
|
[JsonIgnore]
|
|
|
|
|
|
public IDictionary<string, NestedContentPropertyValue> PropertyValues { get; set; } = new Dictionary<string, NestedContentPropertyValue>();
|
2017-09-12 16:22:16 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
private static bool IsSystemPropertyKey(string propKey)
|
|
|
|
|
|
{
|
|
|
|
|
|
return propKey == "name" || propKey == "key" || propKey == ContentTypeAliasPropertyKey;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|