diff --git a/src/Umbraco.Core/ContentExtensions.cs b/src/Umbraco.Core/ContentExtensions.cs
index 7bce23e98e..eb6339741a 100644
--- a/src/Umbraco.Core/ContentExtensions.cs
+++ b/src/Umbraco.Core/ContentExtensions.cs
@@ -119,6 +119,15 @@ namespace Umbraco.Core
return false;
}
+ ///
+ /// Returns all properties based on the editorAlias
+ ///
+ ///
+ ///
+ ///
+ public static IEnumerable GetPropertiesByEditor(this IContentBase content, string editorAlias)
+ => content.Properties.Where(x => x.PropertyType.PropertyEditorAlias == editorAlias);
+
///
/// Returns properties that do not belong to a group
///
diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
index 22e364c0f8..802e8c2ee3 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs
@@ -18,10 +18,35 @@ namespace Umbraco.Core.Models.Blocks
_propertyEditorAlias = propertyEditorAlias;
}
+ public BlockEditorData ConvertFrom(JToken json)
+ {
+ var value = json.ToObject();
+ return Convert(value);
+ }
+
+ public bool TryDeserialize(string json, out BlockEditorData blockEditorData)
+ {
+ try
+ {
+ var value = JsonConvert.DeserializeObject(json);
+ blockEditorData = Convert(value);
+ return true;
+ }
+ catch (System.Exception)
+ {
+ blockEditorData = null;
+ return false;
+ }
+ }
+
public BlockEditorData Deserialize(string json)
{
var value = JsonConvert.DeserializeObject(json);
+ return Convert(value);
+ }
+ private BlockEditorData Convert(BlockValue value)
+ {
if (value.Layout == null)
return BlockEditorData.Empty;
diff --git a/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs
index 3453ff2a78..5de44e16c1 100644
--- a/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs
+++ b/src/Umbraco.Core/Models/Blocks/BlockListLayoutItem.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Models.Blocks
[JsonConverter(typeof(UdiJsonConverter))]
public Udi ContentUdi { get; set; }
- [JsonProperty("settingsUdi")]
+ [JsonProperty("settingsUdi", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(UdiJsonConverter))]
public Udi SettingsUdi { get; set; }
}
diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs
index c587a45424..13fbf949d6 100644
--- a/src/Umbraco.Core/Models/PropertyCollection.cs
+++ b/src/Umbraco.Core/Models/PropertyCollection.cs
@@ -7,6 +7,7 @@ using System.Runtime.Serialization;
namespace Umbraco.Core.Models
{
+
///
/// Represents a collection of property values.
///
diff --git a/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs b/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs
new file mode 100644
index 0000000000..2b819d4555
--- /dev/null
+++ b/src/Umbraco.Core/PropertyEditors/ComplexPropertyEditorContentEventHandler.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Events;
+using Umbraco.Core.Models;
+using Umbraco.Core.Services;
+using Umbraco.Core.Services.Implement;
+
+namespace Umbraco.Core.PropertyEditors
+{
+ ///
+ /// Utility class for dealing with Copying/Saving events for complex editors
+ ///
+ internal class ComplexPropertyEditorContentEventHandler : IDisposable
+ {
+ private readonly string _editorAlias;
+ private readonly Func _formatPropertyValue;
+ private bool _disposedValue;
+
+ public ComplexPropertyEditorContentEventHandler(string editorAlias,
+ Func formatPropertyValue)
+ {
+ _editorAlias = editorAlias;
+ _formatPropertyValue = formatPropertyValue;
+ ContentService.Copying += ContentService_Copying;
+ ContentService.Saving += ContentService_Saving;
+ }
+
+ ///
+ /// Copying event handler
+ ///
+ ///
+ ///
+ private void ContentService_Copying(IContentService sender, CopyEventArgs e)
+ {
+ var props = e.Copy.GetPropertiesByEditor(_editorAlias);
+ UpdatePropertyValues(props, false);
+ }
+
+ ///
+ /// Saving event handler
+ ///
+ ///
+ ///
+ private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e)
+ {
+ foreach (var entity in e.SavedEntities)
+ {
+ var props = entity.GetPropertiesByEditor(_editorAlias);
+ UpdatePropertyValues(props, true);
+ }
+ }
+
+ private void UpdatePropertyValues(IEnumerable props, bool onlyMissingKeys)
+ {
+ foreach (var prop in props)
+ {
+ // A Property may have one or more values due to cultures
+ var propVals = prop.Values;
+ foreach (var cultureVal in propVals)
+ {
+ // Remove keys from published value & any nested properties
+ var updatedPublishedVal = _formatPropertyValue(cultureVal.PublishedValue?.ToString(), onlyMissingKeys);
+ cultureVal.PublishedValue = updatedPublishedVal;
+
+ // Remove keys from edited/draft value & any nested properties
+ var updatedEditedVal = _formatPropertyValue(cultureVal.EditedValue?.ToString(), onlyMissingKeys);
+ cultureVal.EditedValue = updatedEditedVal;
+ }
+ }
+ }
+
+ ///
+ /// Unbinds from events
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ ContentService.Copying -= ContentService_Copying;
+ ContentService.Saving -= ContentService_Saving;
+ }
+ _disposedValue = true;
+ }
+ }
+
+ ///
+ /// Unbinds from events
+ ///
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 1b1ee4fb28..ba344eca2a 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -148,6 +148,7 @@
+
diff --git a/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs
new file mode 100644
index 0000000000..174ad256b3
--- /dev/null
+++ b/src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs
@@ -0,0 +1,158 @@
+using Newtonsoft.Json;
+using NUnit.Framework;
+using System;
+using Umbraco.Web.Compose;
+
+namespace Umbraco.Tests.PropertyEditors
+{
+ [TestFixture]
+ public class BlockEditorComponentTests
+ {
+ private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
+ {
+ Formatting = Formatting.None,
+ NullValueHandling = NullValueHandling.Ignore,
+
+ };
+
+ [Test]
+ public void No_Nesting()
+ {
+ var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
+ var guidCounter = 0;
+ Func guidFactory = () => guids[guidCounter++];
+
+ var json = @"{
+ ""layout"":
+ {
+ ""Umbraco.BlockList"": [
+ {
+ ""contentUdi"": ""umb://element/036ce82586a64dfba2d523a99ed80f58""
+ },
+ {
+ ""contentUdi"": ""umb://element/48288c21a38a40ef82deb3eda90a58f6""
+ }
+ ]
+ },
+ ""contentData"": [
+ {
+ ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"",
+ ""udi"": ""umb://element/036ce82586a64dfba2d523a99ed80f58"",
+ ""featureName"": ""Hello"",
+ ""featureDetails"": ""World""
+ },
+ {
+ ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"",
+ ""udi"": ""umb://element/48288c21a38a40ef82deb3eda90a58f6"",
+ ""featureName"": ""Another"",
+ ""featureDetails"": ""Feature""
+ }
+ ],
+ ""settingsData"": []
+}";
+
+
+ var expected = json
+ .Replace("036ce82586a64dfba2d523a99ed80f58", guids[0].ToString("N"))
+ .Replace("48288c21a38a40ef82deb3eda90a58f6", guids[1].ToString("N"));
+
+ var component = new BlockEditorComponent();
+ var result = component.CreateNestedContentKeys(json, false, guidFactory);
+
+ var expectedJson = JsonConvert.DeserializeObject(expected, _serializerSettings).ToString();
+ var resultJson = JsonConvert.DeserializeObject(result, _serializerSettings).ToString();
+ Console.WriteLine(expectedJson);
+ Console.WriteLine(resultJson);
+ Assert.AreEqual(expectedJson, resultJson);
+ }
+
+ [Test]
+ public void One_Level_Nesting_Escaped()
+ {
+ var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
+ var guidCounter = 0;
+ Func guidFactory = () => guids[guidCounter++];
+
+ // we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
+ // and this is how to do that, the result will also include quotes around it.
+ var innerJson = JsonConvert.DeserializeObject(@"{
+ ""layout"":
+ {
+ ""Umbraco.BlockList"": [
+ {
+ ""contentUdi"": ""umb://element/4C44CE6B3A5C4F5F8F15E3DC24819A9E""
+ },
+ {
+ ""contentUdi"": ""umb://element/A062C06D6B0B44AC892B35D90309C7F8""
+ }
+ ]
+ },
+ ""contentData"": [
+ {
+ ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"",
+ ""udi"": ""umb://element/4C44CE6B3A5C4F5F8F15E3DC24819A9E"",
+ ""featureName"": ""Hello"",
+ ""featureDetails"": ""World""
+ },
+ {
+ ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"",
+ ""udi"": ""umb://element/A062C06D6B0B44AC892B35D90309C7F8"",
+ ""featureName"": ""Another"",
+ ""featureDetails"": ""Feature""
+ }
+ ],
+ ""settingsData"": []
+}", _serializerSettings);
+
+ var serializedInnerJson = JsonConvert.SerializeObject(innerJson, _serializerSettings);
+
+ var subJsonEscaped = JsonConvert.ToString(serializedInnerJson);
+
+ var json = @"{
+ ""layout"":
+ {
+ ""Umbraco.BlockList"": [
+ {
+ ""contentUdi"": ""umb://element/036ce82586a64dfba2d523a99ed80f58""
+ },
+ {
+ ""contentUdi"": ""umb://element/48288c21a38a40ef82deb3eda90a58f6""
+ }
+ ]
+ },
+ ""contentData"": [
+ {
+ ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"",
+ ""udi"": ""umb://element/036ce82586a64dfba2d523a99ed80f58"",
+ ""featureName"": ""Hello"",
+ ""featureDetails"": ""World"",
+ ""subFeatures"": " + subJsonEscaped + @"
+ },
+ {
+ ""contentTypeKey"": ""d6ce4a86-91a2-45b3-a99c-8691fc1fb020"",
+ ""udi"": ""umb://element/48288c21a38a40ef82deb3eda90a58f6"",
+ ""featureName"": ""Another"",
+ ""featureDetails"": ""Feature""
+ }
+ ],
+ ""settingsData"": []
+}";
+
+ var expected = json
+ .Replace("036ce82586a64dfba2d523a99ed80f58", guids[0].ToString("N"))
+ .Replace("48288c21a38a40ef82deb3eda90a58f6", guids[1].ToString("N"))
+ .Replace("4C44CE6B3A5C4F5F8F15E3DC24819A9E", guids[2].ToString("N"))
+ .Replace("A062C06D6B0B44AC892B35D90309C7F8", guids[3].ToString("N"));
+
+ var component = new BlockEditorComponent();
+ var result = component.CreateNestedContentKeys(json, false, guidFactory);
+
+ var expectedJson = JsonConvert.DeserializeObject(expected, _serializerSettings).ToString();
+ var resultJson = JsonConvert.DeserializeObject(result, _serializerSettings).ToString();
+ Console.WriteLine(expectedJson);
+ Console.WriteLine(resultJson);
+ Assert.AreEqual(expectedJson, resultJson);
+ }
+
+ }
+}
diff --git a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs
index 1b83c048d2..5b7e220123 100644
--- a/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs
+++ b/src/Umbraco.Tests/PropertyEditors/NestedContentPropertyComponentTests.cs
@@ -9,6 +9,7 @@ using Umbraco.Web.Compose;
namespace Umbraco.Tests.PropertyEditors
{
+
[TestFixture]
public class NestedContentPropertyComponentTests
{
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index f803f1cd1d..004945bb46 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -147,6 +147,7 @@
+
diff --git a/src/Umbraco.Web/Compose/BlockEditorComponent.cs b/src/Umbraco.Web/Compose/BlockEditorComponent.cs
new file mode 100644
index 0000000000..43962533d6
--- /dev/null
+++ b/src/Umbraco.Web/Compose/BlockEditorComponent.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Events;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Blocks;
+using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Services;
+using Umbraco.Core.Services.Implement;
+using Umbraco.Web.PropertyEditors;
+
+namespace Umbraco.Web.Compose
+{
+
+
+ ///
+ /// A component for Block editors used to bind to events
+ ///
+ public class BlockEditorComponent : IComponent
+ {
+ private ComplexPropertyEditorContentEventHandler _handler;
+ private readonly BlockListEditorDataConverter _converter = new BlockListEditorDataConverter();
+
+ public void Initialize()
+ {
+ _handler = new ComplexPropertyEditorContentEventHandler(
+ Constants.PropertyEditors.Aliases.BlockList,
+ CreateNestedContentKeys);
+ }
+
+ public void Terminate() => _handler?.Dispose();
+
+ private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) => CreateNestedContentKeys(rawJson, onlyMissingKeys, null);
+
+ // internal for tests
+ internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null, JsonSerializerSettings serializerSettings = null)
+ {
+ // used so we can test nicely
+ if (createGuid == null)
+ createGuid = () => Guid.NewGuid();
+
+ if (string.IsNullOrWhiteSpace(rawJson) || !rawJson.DetectIsJson())
+ return rawJson;
+
+ // Parse JSON
+ var blockListValue = _converter.Deserialize(rawJson);
+
+ UpdateBlockListRecursively(blockListValue, onlyMissingKeys, createGuid, serializerSettings);
+
+ return JsonConvert.SerializeObject(blockListValue.BlockValue, serializerSettings);
+ }
+
+ private void UpdateBlockListRecursively(BlockEditorData blockListData, bool onlyMissingKeys, Func createGuid, JsonSerializerSettings serializerSettings)
+ {
+ var oldToNew = new Dictionary();
+ MapOldToNewUdis(oldToNew, blockListData.BlockValue.ContentData, onlyMissingKeys, createGuid);
+ MapOldToNewUdis(oldToNew, blockListData.BlockValue.SettingsData, onlyMissingKeys, createGuid);
+
+ for (var i = 0; i < blockListData.References.Count; i++)
+ {
+ var reference = blockListData.References[i];
+ var hasContentMap = oldToNew.TryGetValue(reference.ContentUdi, out var contentMap);
+ Udi settingsMap = null;
+ var hasSettingsMap = reference.SettingsUdi != null && oldToNew.TryGetValue(reference.SettingsUdi, out settingsMap);
+
+ if (hasContentMap)
+ {
+ // replace the reference
+ blockListData.References.RemoveAt(i);
+ blockListData.References.Insert(i, new ContentAndSettingsReference(contentMap, hasSettingsMap ? settingsMap : null));
+ }
+ }
+
+ // build the layout with the new UDIs
+ var layout = (JArray)blockListData.Layout;
+ layout.Clear();
+ foreach (var reference in blockListData.References)
+ {
+ layout.Add(JObject.FromObject(new BlockListLayoutItem
+ {
+ ContentUdi = reference.ContentUdi,
+ SettingsUdi = reference.SettingsUdi
+ }));
+ }
+
+
+ RecursePropertyValues(blockListData.BlockValue.ContentData, onlyMissingKeys, createGuid, serializerSettings);
+ RecursePropertyValues(blockListData.BlockValue.SettingsData, onlyMissingKeys, createGuid, serializerSettings);
+ }
+
+ private void RecursePropertyValues(IEnumerable blockData, bool onlyMissingKeys, Func createGuid, JsonSerializerSettings serializerSettings)
+ {
+ foreach (var data in blockData)
+ {
+ // check if we need to recurse (make a copy of the dictionary since it will be modified)
+ foreach (var propertyAliasToBlockItemData in new Dictionary(data.RawPropertyValues))
+ {
+ var asString = propertyAliasToBlockItemData.Value?.ToString();
+
+ //// if this is a nested block list
+ //if (propertyAliasToBlockItemData.Value.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.BlockList)
+ //{
+ // // recurse
+ // var blockListValue = _converter.Deserialize(asString);
+ // UpdateBlockListRecursively(blockListValue, onlyMissingKeys, createGuid);
+ // // set new value
+ // data.RawPropertyValues[propertyAliasToBlockItemData.Key] = JsonConvert.SerializeObject(blockListValue.BlockValue);
+ //}
+
+ if (asString != null && asString.DetectIsJson())
+ {
+ // this gets a little ugly because there could be some other complex editor that contains another block editor
+ // and since we would have no idea how to parse that, all we can do is try JSON Path to find another block editor
+ // of our type
+ var json = JToken.Parse(asString);
+
+ // select all tokens (flatten)
+ var allProperties = json.SelectTokens("$..*").Select(x => x.Parent as JProperty).WhereNotNull().ToList();
+ foreach (var prop in allProperties)
+ {
+ if (prop.Name == Constants.PropertyEditors.Aliases.BlockList)
+ {
+ // get it's parent 'layout' and it's parent's container
+ var layout = prop.Parent?.Parent as JProperty;
+ if (layout != null && layout.Parent is JObject layoutJson)
+ {
+ // recurse
+ var blockListValue = _converter.ConvertFrom(layoutJson);
+ UpdateBlockListRecursively(blockListValue, onlyMissingKeys, createGuid, serializerSettings);
+
+ // set new value
+ if (layoutJson.Parent != null)
+ {
+ // we can replace the sub string
+ layoutJson.Replace(JsonConvert.SerializeObject(blockListValue.BlockValue, serializerSettings));
+ }
+ else
+ {
+ // this was the root string
+ data.RawPropertyValues[propertyAliasToBlockItemData.Key] = JsonConvert.SerializeObject(blockListValue.BlockValue, serializerSettings);
+ }
+ }
+ }
+ else if (prop.Name != "layout" && prop.Name != "contentData" && prop.Name != "settingsData" && prop.Name != "contentTypeKey")
+ {
+ // this is an arbitrary property that could contain a nested complex editor
+ var propVal = prop.Value?.ToString();
+ // check if this might contain a nested Block Editor
+ if (!propVal.IsNullOrWhiteSpace() && propVal.DetectIsJson() && propVal.InvariantContains(Constants.PropertyEditors.Aliases.BlockList))
+ {
+ if (_converter.TryDeserialize(propVal, out var nestedBlockData))
+ {
+ // recurse
+ UpdateBlockListRecursively(nestedBlockData, onlyMissingKeys, createGuid, serializerSettings);
+ // set the value to the updated one
+ prop.Value = JsonConvert.SerializeObject(nestedBlockData.BlockValue, serializerSettings);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void MapOldToNewUdis(Dictionary oldToNew, IEnumerable blockData, bool onlyMissingKeys, Func createGuid)
+ {
+ foreach (var data in blockData)
+ {
+ if (data.Udi == null)
+ throw new InvalidOperationException("Block data cannot contain a null UDI");
+
+ // replace the UDIs
+ if (!onlyMissingKeys)
+ {
+ var newUdi = GuidUdi.Create(Constants.UdiEntityType.Element, createGuid());
+ oldToNew[data.Udi] = newUdi;
+ data.Udi = newUdi;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Web/Compose/BlockEditorComposer.cs b/src/Umbraco.Web/Compose/BlockEditorComposer.cs
new file mode 100644
index 0000000000..debda081da
--- /dev/null
+++ b/src/Umbraco.Web/Compose/BlockEditorComposer.cs
@@ -0,0 +1,12 @@
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Web.Compose
+{
+ ///
+ /// A composer for Block editors to run a component
+ ///
+ [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
+ public class BlockEditorComposer : ComponentComposer, ICoreComposer
+ { }
+}
diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
index 5794a2734e..9414a1a836 100644
--- a/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
+++ b/src/Umbraco.Web/Compose/NestedContentPropertyComponent.cs
@@ -6,67 +6,32 @@ using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
+using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
using Umbraco.Web.PropertyEditors;
namespace Umbraco.Web.Compose
{
+
+ ///
+ /// A component for NestedContent used to bind to events
+ ///
public class NestedContentPropertyComponent : IComponent
{
+ private ComplexPropertyEditorContentEventHandler _handler;
+
public void Initialize()
{
- ContentService.Copying += ContentService_Copying;
- ContentService.Saving += ContentService_Saving;
+ _handler = new ComplexPropertyEditorContentEventHandler(
+ Constants.PropertyEditors.Aliases.NestedContent,
+ CreateNestedContentKeys);
}
- private void ContentService_Copying(IContentService sender, CopyEventArgs e)
- {
- // When a content node contains nested content property
- // Check if the copied node contains a nested content
- var nestedContentProps = e.Copy.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent);
- UpdateNestedContentProperties(nestedContentProps, false);
- }
+ public void Terminate() => _handler?.Dispose();
- private void ContentService_Saving(IContentService sender, ContentSavingEventArgs e)
- {
- // One or more content nodes could be saved in a bulk publish
- foreach (var entity in e.SavedEntities)
- {
- // When a content node contains nested content property
- // Check if the copied node contains a nested content
- var nestedContentProps = entity.Properties.Where(x => x.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.NestedContent);
- UpdateNestedContentProperties(nestedContentProps, true);
- }
- }
+ private string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys) => CreateNestedContentKeys(rawJson, onlyMissingKeys, null);
- public void Terminate()
- {
- ContentService.Copying -= ContentService_Copying;
- ContentService.Saving -= ContentService_Saving;
- }
-
- private void UpdateNestedContentProperties(IEnumerable nestedContentProps, bool onlyMissingKeys)
- {
- // Each NC Property on a doctype
- foreach (var nestedContentProp in nestedContentProps)
- {
- // A NC Prop may have one or more values due to cultures
- var propVals = nestedContentProp.Values;
- foreach (var cultureVal in propVals)
- {
- // Remove keys from published value & any nested NC's
- var updatedPublishedVal = CreateNestedContentKeys(cultureVal.PublishedValue?.ToString(), onlyMissingKeys);
- cultureVal.PublishedValue = updatedPublishedVal;
-
- // Remove keys from edited/draft value & any nested NC's
- var updatedEditedVal = CreateNestedContentKeys(cultureVal.EditedValue?.ToString(), onlyMissingKeys);
- cultureVal.EditedValue = updatedEditedVal;
- }
- }
- }
-
-
// internal for tests
internal string CreateNestedContentKeys(string rawJson, bool onlyMissingKeys, Func createGuid = null)
{
@@ -98,7 +63,6 @@ namespace Umbraco.Web.Compose
{
// get it's sibling 'key' property
var ncKeyVal = prop.Parent["key"] as JValue;
- // TODO: This bool seems odd, if the key is null, shouldn't we fill it in regardless of onlyMissingKeys?
if ((onlyMissingKeys && ncKeyVal == null) || (!onlyMissingKeys && ncKeyVal != null))
{
// create or replace
diff --git a/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
index 4c9d9dee1c..8e4cfbfffc 100644
--- a/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
+++ b/src/Umbraco.Web/Compose/NestedContentPropertyComposer.cs
@@ -3,6 +3,9 @@ using Umbraco.Core.Composing;
namespace Umbraco.Web.Compose
{
+ ///
+ /// A composer for nested content to run a component
+ ///
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class NestedContentPropertyComposer : ComponentComposer, ICoreComposer
{ }
diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index a0ffacf8f3..5e1176cdc6 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -130,6 +130,8 @@
+
+