2022-01-26 12:22:05 +01:00
// Copyright (c) Umbraco.
2021-02-15 10:42:35 +01:00
// See LICENSE for more details.
2022-04-29 11:52:58 +02:00
using Microsoft.Extensions.DependencyInjection ;
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 ;
2022-11-29 11:22:57 +00:00
using Umbraco.Cms.Core.DependencyInjection ;
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 ;
using Umbraco.Cms.Core.Strings ;
2021-02-09 11:26:22 +01:00
using Umbraco.Extensions ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
namespace Umbraco.Cms.Core.PropertyEditors ;
/// <summary>
/// Represents a nested content property editor.
/// </summary>
[ DataEditor (
Constants . PropertyEditors . Aliases . NestedContent ,
2022-11-14 12:40:06 +01:00
"Nested Content (legacy)" ,
2022-06-02 08:18:31 +02:00
"nestedcontent" ,
ValueType = ValueTypes . Json ,
Group = Constants . PropertyEditors . Groups . Lists ,
2022-08-31 11:03:34 +02:00
Icon = "icon-thumbnail-list" ,
2022-11-14 12:40:06 +01:00
ValueEditorIsReusable = false ,
IsDeprecated = true ) ]
2023-03-13 11:33:27 +01:00
[Obsolete("Nested content is obsolete, will be removed in V13")]
2022-06-02 08:18:31 +02:00
public class NestedContentPropertyEditor : DataEditor
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias" ;
private readonly IEditorConfigurationParser _editorConfigurationParser ;
private readonly IIOHelper _ioHelper ;
2023-02-13 14:47:26 +01:00
private readonly INestedContentPropertyIndexValueFactory _nestedContentPropertyIndexValueFactory ;
2022-06-02 08:18:31 +02:00
2023-02-13 14:47:26 +01:00
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 12.")]
2022-06-02 08:18:31 +02:00
public NestedContentPropertyEditor (
IDataValueEditorFactory dataValueEditorFactory ,
IIOHelper ioHelper )
: this ( dataValueEditorFactory , ioHelper , StaticServiceProvider . Instance . GetRequiredService < IEditorConfigurationParser > ( ) )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
}
2017-09-12 16:22:16 +02:00
2023-02-13 14:47:26 +01:00
[Obsolete("Use non-obsoleted ctor. This will be removed in Umbraco 13.")]
2022-06-02 08:18:31 +02:00
public NestedContentPropertyEditor (
IDataValueEditorFactory dataValueEditorFactory ,
IIOHelper ioHelper ,
IEditorConfigurationParser editorConfigurationParser )
2023-02-13 14:47:26 +01:00
: this (
dataValueEditorFactory ,
ioHelper ,
editorConfigurationParser ,
StaticServiceProvider . Instance . GetRequiredService < INestedContentPropertyIndexValueFactory > ( ) )
{
}
public NestedContentPropertyEditor (
IDataValueEditorFactory dataValueEditorFactory ,
IIOHelper ioHelper ,
IEditorConfigurationParser editorConfigurationParser ,
INestedContentPropertyIndexValueFactory nestedContentPropertyIndexValueFactory )
2022-06-02 08:18:31 +02:00
: base ( dataValueEditorFactory )
{
_ioHelper = ioHelper ;
_editorConfigurationParser = editorConfigurationParser ;
2023-02-13 14:47:26 +01:00
_nestedContentPropertyIndexValueFactory = nestedContentPropertyIndexValueFactory ;
2022-06-22 15:45:13 +02:00
SupportsReadOnly = true ;
2022-06-02 08:18:31 +02:00
}
2022-04-29 11:52:58 +02:00
2023-02-13 14:47:26 +01:00
public override IPropertyIndexValueFactory PropertyIndexValueFactory = > _nestedContentPropertyIndexValueFactory ;
2022-06-02 08:18:31 +02:00
#region Pre Value Editor
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
protected override IConfigurationEditor CreateConfigurationEditor ( ) = >
new NestedContentConfigurationEditor ( _ioHelper , _editorConfigurationParser ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
#endregion
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
private static bool IsSystemPropertyKey ( string propKey ) = >
propKey = = "name" | | propKey = = "key" | | propKey = = ContentTypeAliasPropertyKey ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
#region Value Editor
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
protected override IDataValueEditor CreateValueEditor ( )
= > DataValueEditorFactory . Create < NestedContentPropertyValueEditor > ( Attribute ! ) ;
2017-09-12 16:22:16 +02:00
2022-11-28 11:20:46 +01:00
internal class NestedContentPropertyValueEditor : DataValueEditor , IDataValueReference , IDataValueTags
2022-06-02 08:18:31 +02:00
{
private readonly IDataTypeService _dataTypeService ;
2024-01-19 20:02:57 +01:00
private readonly PropertyEditorCollection _propertyEditors ;
private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories ;
2022-06-02 08:18:31 +02:00
private readonly ILogger < NestedContentPropertyEditor > _logger ;
private readonly NestedContentValues _nestedContentValues ;
public NestedContentPropertyValueEditor (
IDataTypeService dataTypeService ,
ILocalizedTextService localizedTextService ,
IContentTypeService contentTypeService ,
IShortStringHelper shortStringHelper ,
DataEditorAttribute attribute ,
PropertyEditorCollection propertyEditors ,
2024-01-19 20:02:57 +01:00
DataValueReferenceFactoryCollection dataValueReferenceFactories ,
2022-06-02 08:18:31 +02:00
ILogger < NestedContentPropertyEditor > logger ,
IJsonSerializer jsonSerializer ,
IIOHelper ioHelper ,
IPropertyValidationService propertyValidationService )
: base ( localizedTextService , shortStringHelper , jsonSerializer , ioHelper , attribute )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
_dataTypeService = dataTypeService ;
2024-01-19 20:02:57 +01:00
_propertyEditors = propertyEditors ;
_dataValueReferenceFactories = dataValueReferenceFactories ;
2022-06-02 08:18:31 +02:00
_logger = logger ;
_nestedContentValues = new NestedContentValues ( contentTypeService ) ;
2024-01-19 20:02:57 +01:00
2022-06-02 08:18:31 +02:00
Validators . Add ( new NestedContentValidator ( propertyValidationService , _nestedContentValues , contentTypeService ) ) ;
}
2019-10-24 12:34:48 +02:00
2022-06-02 08:18:31 +02:00
/// <inheritdoc />
public override object? Configuration
{
get = > base . Configuration ;
set
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
if ( value = = null )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
throw new ArgumentNullException ( nameof ( value ) ) ;
}
2018-03-16 09:06:44 +01:00
2022-06-02 08:18:31 +02:00
if ( ! ( value is NestedContentConfiguration configuration ) )
{
throw new ArgumentException (
$"Expected a {typeof(NestedContentConfiguration).Name} instance, but got {value.GetType().Name}." ,
nameof ( value ) ) ;
2017-09-12 16:22:16 +02:00
}
2022-06-02 08:18:31 +02:00
base . Configuration = value ;
HideLabel = configuration . HideLabel . TryConvertTo < bool > ( ) . Result ;
2017-09-12 16:22:16 +02:00
}
2022-06-02 08:18:31 +02:00
}
2017-09-12 16:22:16 +02:00
2024-01-19 20:02:57 +01:00
/// <inheritdoc />
2022-06-02 08:18:31 +02:00
public IEnumerable < UmbracoEntityReference > GetReferences ( object? value )
{
2024-01-19 20:02:57 +01:00
foreach ( NestedContentValues . NestedContentPropertyValue propertyValue in GetAllPropertyValues ( value ) )
2022-06-02 08:18:31 +02:00
{
2024-01-19 20:02:57 +01:00
if ( ! _propertyEditors . TryGet ( propertyValue . PropertyType . PropertyEditorAlias , out IDataEditor ? dataEditor ) )
2022-01-26 12:22:05 +01:00
{
2024-01-19 20:02:57 +01:00
continue ;
}
2017-09-12 16:22:16 +02:00
2024-01-19 20:02:57 +01:00
foreach ( UmbracoEntityReference reference in _dataValueReferenceFactories . GetReferences ( dataEditor , propertyValue . Value ) )
{
yield return reference ;
2022-06-02 08:18:31 +02:00
}
2017-09-12 16:22:16 +02:00
}
2022-06-02 08:18:31 +02:00
}
2017-09-12 16:22:16 +02:00
2022-11-28 11:20:46 +01:00
/// <inheritdoc />
public IEnumerable < ITag > GetTags ( object? value , object? dataTypeConfiguration , int? languageId )
{
2024-01-19 20:02:57 +01:00
foreach ( NestedContentValues . NestedContentPropertyValue propertyValue in GetAllPropertyValues ( value ) )
2022-11-28 11:20:46 +01:00
{
2024-01-19 20:02:57 +01:00
if ( ! _propertyEditors . TryGet ( propertyValue . PropertyType . PropertyEditorAlias , out IDataEditor ? dataEditor ) | |
dataEditor . GetValueEditor ( ) is not IDataValueTags dataValueTags )
2022-11-28 11:20:46 +01:00
{
2024-01-19 20:02:57 +01:00
continue ;
}
2022-11-28 11:20:46 +01:00
2024-01-19 20:02:57 +01:00
object? configuration = _dataTypeService . GetDataType ( propertyValue . PropertyType . DataTypeKey ) ? . Configuration ;
foreach ( ITag tag in dataValueTags . GetTags ( propertyValue . Value , configuration , languageId ) )
{
yield return tag ;
2022-11-28 11:20:46 +01:00
}
}
}
2024-01-19 20:02:57 +01:00
private IEnumerable < NestedContentValues . NestedContentPropertyValue > GetAllPropertyValues ( object? value )
= > _nestedContentValues . GetPropertyValues ( value ) . SelectMany ( x = > x . PropertyValues . Values ) ;
2022-06-02 08:18:31 +02:00
#region DB to String
2017-11-15 08:53:20 +01:00
2022-06-02 08:18:31 +02:00
public override string ConvertDbToString ( IPropertyType propertyType , object? propertyValue )
{
IReadOnlyList < NestedContentValues . NestedContentRowValue > rows =
_nestedContentValues . GetPropertyValues ( propertyValue ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
if ( rows . Count = = 0 )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
return string . Empty ;
}
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
foreach ( NestedContentValues . NestedContentRowValue row in rows . ToList ( ) )
{
foreach ( KeyValuePair < string , NestedContentValues . NestedContentPropertyValue > prop in row . PropertyValues
. ToList ( ) )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
try
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
// convert the value, and store the converted value
IDataEditor ? propEditor = _propertyEditors [ prop . Value . PropertyType . PropertyEditorAlias ] ;
if ( propEditor = = null )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
continue ;
}
2019-10-30 17:14:04 +11:00
2022-06-02 08:18:31 +02:00
var tempConfig = _dataTypeService . GetDataType ( prop . Value . PropertyType . DataTypeId )
? . Configuration ;
IDataValueEditor valEditor = propEditor . GetValueEditor ( tempConfig ) ;
var convValue = valEditor . ConvertDbToString ( prop . Value . PropertyType , prop . Value . Value ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
// update the raw value since this is what will get serialized out
row . RawPropertyValues [ prop . Key ] = convValue ;
}
catch ( InvalidOperationException ex )
{
// deal with weird situations by ignoring them (no comment)
row . RawPropertyValues . Remove ( prop . Key ) ;
_logger . LogWarning ( ex , "ConvertDbToString removed property value {PropertyKey} in row {RowId} for property type {PropertyTypeAlias}" , prop . Key , row . Id , propertyType . Alias ) ;
}
}
}
2021-06-29 09:55:02 +02:00
2022-06-02 08:18:31 +02:00
return JsonConvert . SerializeObject ( rows , Formatting . None ) . ToXmlString < string > ( ) ;
}
2021-06-29 09:55:02 +02:00
2022-06-02 08:18:31 +02:00
#endregion
2020-06-23 00:27:01 +10:00
2022-06-02 08:18:31 +02:00
#region Convert database // editor
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
// note: there is NO variant support here
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
/// <summary>
/// Ensure that sub-editor values are translated through their ToEditor methods
/// </summary>
/// <param name="property"></param>
/// <param name="culture"></param>
/// <param name="segment"></param>
/// <returns></returns>
public override object ToEditor ( IProperty property , string? culture = null , string? segment = null )
{
var val = property . GetValue ( culture , segment ) ;
var valEditors = new Dictionary < int , IDataValueEditor > ( ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
IReadOnlyList < NestedContentValues . NestedContentRowValue > rows = _nestedContentValues . GetPropertyValues ( val ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
if ( rows . Count = = 0 )
{
return string . Empty ;
}
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
foreach ( NestedContentValues . NestedContentRowValue row in rows . ToList ( ) )
{
foreach ( KeyValuePair < string , NestedContentValues . NestedContentPropertyValue > prop in row . PropertyValues
. ToList ( ) )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
try
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
// create a temp property with the value
// - force it to be culture invariant as NC can't handle culture variant element properties
prop . Value . PropertyType . Variations = ContentVariation . Nothing ;
var tempProp = new Property ( prop . Value . PropertyType ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
tempProp . SetValue ( prop . Value . Value ) ;
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
// convert that temp property, and store the converted value
IDataEditor ? propEditor = _propertyEditors [ prop . Value . PropertyType . PropertyEditorAlias ] ;
if ( propEditor = = null )
{
// update the raw value since this is what will get serialized out
row . RawPropertyValues [ prop . Key ] = tempProp . GetValue ( ) ? . ToString ( ) ;
continue ;
}
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
var dataTypeId = prop . Value . PropertyType . DataTypeId ;
if ( ! valEditors . TryGetValue ( dataTypeId , out IDataValueEditor ? valEditor ) )
{
var tempConfig = _dataTypeService . GetDataType ( dataTypeId ) ? . Configuration ;
valEditor = propEditor . GetValueEditor ( tempConfig ) ;
valEditors . Add ( dataTypeId , valEditor ) ;
}
var convValue = valEditor . ToEditor ( tempProp ) ;
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
2022-06-02 08:18:31 +02:00
row . RawPropertyValues [ prop . Key ] = convValue = = null ? null : JToken . FromObject ( convValue ) ;
}
catch ( InvalidOperationException ex )
{
// deal with weird situations by ignoring them (no comment)
row . RawPropertyValues . Remove ( prop . Key ) ;
_logger . LogWarning ( 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
}
}
}
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
// return the object, there's a native json converter for this so it will serialize correctly
return rows ;
}
/// <summary>
/// Ensure that sub-editor values are translated through their FromEditor methods
/// </summary>
/// <param name="editorValue"></param>
/// <param name="currentValue"></param>
/// <returns></returns>
public override object? FromEditor ( ContentPropertyData editorValue , object? currentValue )
{
if ( editorValue . Value = = null | | string . IsNullOrWhiteSpace ( editorValue . Value . ToString ( ) ) )
2019-12-02 15:00:56 +00:00
{
2022-06-02 08:18:31 +02:00
return null ;
}
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
IReadOnlyList < NestedContentValues . NestedContentRowValue > rows =
_nestedContentValues . GetPropertyValues ( editorValue . Value ) ;
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
if ( rows . Count = = 0 )
{
return null ;
}
foreach ( NestedContentValues . NestedContentRowValue row in rows . ToList ( ) )
{
foreach ( KeyValuePair < string , NestedContentValues . NestedContentPropertyValue > prop in row . PropertyValues
. ToList ( ) )
2019-12-02 15:00:56 +00:00
{
2022-06-02 08:18:31 +02:00
// Fetch the property types prevalue
var propConfiguration =
_dataTypeService . GetDataType ( prop . Value . PropertyType . DataTypeId ) ? . Configuration ;
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
// Lookup the property editor
IDataEditor ? propEditor = _propertyEditors [ prop . Value . PropertyType . PropertyEditorAlias ] ;
if ( propEditor = = null )
{
continue ;
}
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
// Create a fake content property data object
var contentPropData = new ContentPropertyData ( prop . Value . Value , propConfiguration ) ;
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
// Get the property editor to do it's conversion
var newValue = propEditor . GetValueEditor ( ) . FromEditor ( contentPropData , prop . Value . Value ) ;
2019-12-02 15:00:56 +00:00
2022-06-02 08:18:31 +02:00
// update the raw value since this is what will get serialized out
row . RawPropertyValues [ prop . Key ] = newValue = = null ? null : JToken . FromObject ( newValue ) ;
2019-12-02 15:00:56 +00:00
}
}
2022-06-02 08:18:31 +02:00
// return json
return JsonConvert . SerializeObject ( rows , Formatting . None ) ;
2017-09-12 16:22:16 +02:00
}
2022-06-02 08:18:31 +02:00
#endregion
}
/// <summary>
/// Validator for nested content to ensure that all nesting of editors is validated
/// </summary>
internal class NestedContentValidator : ComplexEditorValidator
{
private readonly IContentTypeService _contentTypeService ;
private readonly NestedContentValues _nestedContentValues ;
public NestedContentValidator ( IPropertyValidationService propertyValidationService , NestedContentValues nestedContentValues , IContentTypeService contentTypeService )
: base ( propertyValidationService )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
_nestedContentValues = nestedContentValues ;
_contentTypeService = contentTypeService ;
}
2017-09-14 11:41:46 +02:00
2022-06-02 08:18:31 +02:00
protected override IEnumerable < ElementTypeValidationModel > GetElementTypeValidation ( object? value )
{
IReadOnlyList < NestedContentValues . NestedContentRowValue > rows =
_nestedContentValues . GetPropertyValues ( value ) ;
if ( rows . Count = = 0 )
2017-09-14 11:41:46 +02:00
{
2022-06-02 08:18:31 +02:00
yield break ;
2017-09-14 11:41:46 +02:00
}
2022-06-02 08:18:31 +02:00
// 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 ( NestedContentValues . NestedContentRowValue row in rows )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
if ( ! allElementTypes . TryGetValue ( row . ContentTypeAlias , out IContentType ? elementType ) )
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
throw new InvalidOperationException ( $"No element type found with alias {row.ContentTypeAlias}" ) ;
}
2020-08-05 19:03:02 +10:00
2022-06-02 08:18:31 +02:00
// now ensure missing properties
foreach ( IPropertyType elementTypeProp in elementType . CompositionPropertyTypes )
{
if ( ! row . PropertyValues . ContainsKey ( elementTypeProp . Alias ) )
2020-08-05 19:03:02 +10:00
{
2022-06-02 08:18:31 +02:00
// set values to null
row . PropertyValues [ elementTypeProp . Alias ] = new NestedContentValues . NestedContentPropertyValue
2020-08-05 19:03:02 +10:00
{
2024-01-19 20:02:57 +01:00
PropertyType = elementTypeProp ,
Value = null ,
2022-06-02 08:18:31 +02:00
} ;
row . RawPropertyValues [ elementTypeProp . Alias ] = null ;
2020-08-05 19:03:02 +10:00
}
2022-06-02 08:18:31 +02:00
}
2020-08-05 19:03:02 +10:00
2022-06-02 08:18:31 +02:00
var elementValidation = new ElementTypeValidationModel ( row . ContentTypeAlias , row . Id ) ;
foreach ( KeyValuePair < string , NestedContentValues . NestedContentPropertyValue > prop in
row . PropertyValues )
{
elementValidation . AddPropertyTypeValidation (
new PropertyTypeValidationModel ( prop . Value . PropertyType , prop . Value . Value ) ) ;
2019-11-01 14:22:49 +11:00
}
2022-06-02 08:18:31 +02:00
yield return elementValidation ;
2017-09-12 16:22:16 +02:00
}
}
2022-06-02 08:18:31 +02:00
}
/// <summary>
/// Used to deserialize the nested content serialized value
/// </summary>
internal class NestedContentValues
{
private readonly Lazy < Dictionary < string , IContentType > > _contentTypes ;
public NestedContentValues ( IContentTypeService contentTypeService ) = > _contentTypes =
new Lazy < Dictionary < string , IContentType > > ( ( ) = > contentTypeService . GetAll ( ) . ToDictionary ( c = > c . Alias ) ) ;
private IContentType ? GetElementType ( NestedContentRowValue item )
{
_contentTypes . Value . TryGetValue ( item . ContentTypeAlias , out IContentType ? contentType ) ;
return contentType ;
}
2019-10-24 21:25:15 +02:00
2020-06-26 15:02:02 +10:00
/// <summary>
2022-06-02 08:18:31 +02:00
/// Deserialize the raw json property value
2020-06-26 15:02:02 +10:00
/// </summary>
2022-06-02 08:18:31 +02:00
/// <param name="propertyValue"></param>
/// <returns></returns>
public IReadOnlyList < NestedContentRowValue > GetPropertyValues ( object? propertyValue )
2019-11-01 14:22:49 +11:00
{
2022-06-02 08:18:31 +02:00
if ( propertyValue = = null | | string . IsNullOrWhiteSpace ( propertyValue . ToString ( ) ) )
2019-11-01 14:22:49 +11:00
{
2022-06-02 08:18:31 +02:00
return new List < NestedContentRowValue > ( ) ;
2019-11-01 14:22:49 +11:00
}
2017-09-12 16:22:16 +02:00
2022-06-02 08:18:31 +02:00
if ( ! propertyValue . ToString ( ) ! . DetectIsJson ( ) )
2019-11-01 14:22:49 +11:00
{
2022-06-02 08:18:31 +02:00
return new List < NestedContentRowValue > ( ) ;
2019-11-01 14:22:49 +11:00
}
2022-06-02 08:18:31 +02:00
List < NestedContentRowValue > ? rowValues =
JsonConvert . DeserializeObject < List < NestedContentRowValue > > ( propertyValue . ToString ( ) ! ) ;
2020-11-30 11:24:24 +01:00
2022-06-02 08:18:31 +02: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.
if ( rowValues = = null | | rowValues . Count = = 0 )
{
return new List < NestedContentRowValue > ( ) ;
}
2019-11-01 14:22:49 +11:00
2022-06-02 08:18:31 +02:00
var contentTypePropertyTypes = new Dictionary < string , Dictionary < string , IPropertyType > > ( ) ;
2019-11-01 14:22:49 +11:00
2022-06-02 08:18:31 +02:00
foreach ( NestedContentRowValue row in rowValues )
{
IContentType ? contentType = GetElementType ( row ) ;
if ( contentType = = null )
2019-11-01 14:22:49 +11:00
{
2022-06-02 08:18:31 +02:00
continue ;
}
2019-11-01 14:22:49 +11:00
2022-06-02 08:18:31 +02: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 Dictionary < string , IPropertyType > ? propertyTypes ) )
{
propertyTypes = contentTypePropertyTypes [ contentType . Alias ] =
contentType . CompositionPropertyTypes . ToDictionary ( x = > x . Alias , x = > x ) ;
}
2020-06-23 00:27:01 +10:00
2022-06-02 08:18:31 +02:00
// find any keys that are not real property types and remove them
if ( row . RawPropertyValues ! = null )
{
foreach ( KeyValuePair < string , object? > prop in row . RawPropertyValues . ToList ( ) )
2019-11-01 14:22:49 +11:00
{
2022-06-02 08:18:31 +02:00
if ( IsSystemPropertyKey ( prop . Key ) )
2020-06-23 00:27:01 +10:00
{
2022-06-02 08:18:31 +02:00
continue ;
}
2020-10-29 02:34:51 +01:00
2022-06-02 08:18:31 +02:00
// doesn't exist so remove it
if ( ! propertyTypes . TryGetValue ( prop . Key , out IPropertyType ? propType ) )
{
row . RawPropertyValues . Remove ( prop . Key ) ;
}
else
{
// set the value to include the resolved property type
row . PropertyValues [ prop . Key ] = new NestedContentPropertyValue
2020-10-29 02:34:51 +01:00
{
2022-06-02 08:18:31 +02:00
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
}
}
2022-06-02 08:18:31 +02:00
return rowValues ;
}
2022-02-22 13:35:32 +01:00
2022-06-02 08:18:31 +02:00
/// <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 ; }
2019-11-01 14:22:49 +11:00
2022-06-02 08:18:31 +02:00
public IPropertyType PropertyType { get ; set ; } = null ! ;
2017-09-12 16:22:16 +02:00
}
2022-06-02 08:18:31 +02:00
/// <summary>
/// Used to deserialize a nested content row
/// </summary>
internal class NestedContentRowValue
2017-09-12 16:22:16 +02:00
{
2022-06-02 08:18:31 +02:00
[JsonProperty("key")]
public Guid Id { get ; set ; }
[JsonProperty("name")]
public string? Name { get ; set ; }
[JsonProperty("ncContentTypeAlias")]
public string ContentTypeAlias { get ; set ; } = null ! ;
public IPropertyType ? PropType { get ; }
/// <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 IDictionary < string , object? > RawPropertyValues { get ; set ; } = null ! ;
/// <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 > ( ) ;
2017-09-12 16:22:16 +02:00
}
}
2022-06-02 08:18:31 +02:00
#endregion
2017-09-12 16:22:16 +02:00
}