Gets copying logic working for the block editor, needs more tests though
This commit is contained in:
@@ -119,6 +119,15 @@ namespace Umbraco.Core
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all properties based on the editorAlias
|
||||
/// </summary>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="editorAlias"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Property> GetPropertiesByEditor(this IContentBase content, string editorAlias)
|
||||
=> content.Properties.Where(x => x.PropertyType.PropertyEditorAlias == editorAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Returns properties that do not belong to a group
|
||||
/// </summary>
|
||||
|
||||
@@ -18,10 +18,35 @@ namespace Umbraco.Core.Models.Blocks
|
||||
_propertyEditorAlias = propertyEditorAlias;
|
||||
}
|
||||
|
||||
public BlockEditorData ConvertFrom(JToken json)
|
||||
{
|
||||
var value = json.ToObject<BlockValue>();
|
||||
return Convert(value);
|
||||
}
|
||||
|
||||
public bool TryDeserialize(string json, out BlockEditorData blockEditorData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = JsonConvert.DeserializeObject<BlockValue>(json);
|
||||
blockEditorData = Convert(value);
|
||||
return true;
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
blockEditorData = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public BlockEditorData Deserialize(string json)
|
||||
{
|
||||
var value = JsonConvert.DeserializeObject<BlockValue>(json);
|
||||
return Convert(value);
|
||||
}
|
||||
|
||||
private BlockEditorData Convert(BlockValue value)
|
||||
{
|
||||
if (value.Layout == null)
|
||||
return BlockEditorData.Empty;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of property values.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for dealing with <see cref="ContentService"/> Copying/Saving events for complex editors
|
||||
/// </summary>
|
||||
internal class ComplexPropertyEditorContentEventHandler : IDisposable
|
||||
{
|
||||
private readonly string _editorAlias;
|
||||
private readonly Func<string, bool, string> _formatPropertyValue;
|
||||
private bool _disposedValue;
|
||||
|
||||
public ComplexPropertyEditorContentEventHandler(string editorAlias,
|
||||
Func<string, bool, string> formatPropertyValue)
|
||||
{
|
||||
_editorAlias = editorAlias;
|
||||
_formatPropertyValue = formatPropertyValue;
|
||||
ContentService.Copying += ContentService_Copying;
|
||||
ContentService.Saving += ContentService_Saving;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ContentService"/> Copying event handler
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ContentService_Copying(IContentService sender, CopyEventArgs<IContent> e)
|
||||
{
|
||||
var props = e.Copy.GetPropertiesByEditor(_editorAlias);
|
||||
UpdatePropertyValues(props, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ContentService"/> Saving event handler
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
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<Property> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unbinds from events
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ContentService.Copying -= ContentService_Copying;
|
||||
ContentService.Saving -= ContentService_Saving;
|
||||
}
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unbinds from events
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,7 @@
|
||||
<Compile Include="Models\RelationTypeExtensions.cs" />
|
||||
<Compile Include="Persistence\Repositories\IInstallationRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Implement\InstallationRepository.cs" />
|
||||
<Compile Include="PropertyEditors\ComplexPropertyEditorContentEventHandler.cs" />
|
||||
<Compile Include="Services\Implement\InstallationService.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_6_0\AddMainDomLock.cs" />
|
||||
<Compile Include="Models\Blocks\BlockListItem.cs" />
|
||||
|
||||
158
src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs
Normal file
158
src/Umbraco.Tests/PropertyEditors/BlockEditorComponentTests.cs
Normal file
@@ -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<Guid> 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<Guid> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using Umbraco.Web.Compose;
|
||||
|
||||
namespace Umbraco.Tests.PropertyEditors
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class NestedContentPropertyComponentTests
|
||||
{
|
||||
|
||||
@@ -147,6 +147,7 @@
|
||||
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
|
||||
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
|
||||
<Compile Include="Persistence\Repositories\EntityRepositoryTest.cs" />
|
||||
<Compile Include="PropertyEditors\BlockEditorComponentTests.cs" />
|
||||
<Compile Include="PropertyEditors\BlockListPropertyValueConverterTests.cs" />
|
||||
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
|
||||
<Compile Include="PropertyEditors\NestedContentPropertyComponentTests.cs" />
|
||||
|
||||
187
src/Umbraco.Web/Compose/BlockEditorComponent.cs
Normal file
187
src/Umbraco.Web/Compose/BlockEditorComponent.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A component for Block editors used to bind to events
|
||||
/// </summary>
|
||||
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<Guid> 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<Guid> createGuid, JsonSerializerSettings serializerSettings)
|
||||
{
|
||||
var oldToNew = new Dictionary<Udi, Udi>();
|
||||
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<BlockItemData> blockData, bool onlyMissingKeys, Func<Guid> 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<string, object>(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<Udi, Udi> oldToNew, IEnumerable<BlockItemData> blockData, bool onlyMissingKeys, Func<Guid> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/Umbraco.Web/Compose/BlockEditorComposer.cs
Normal file
12
src/Umbraco.Web/Compose/BlockEditorComposer.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
/// <summary>
|
||||
/// A composer for Block editors to run a component
|
||||
/// </summary>
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class BlockEditorComposer : ComponentComposer<NestedContentPropertyComponent>, ICoreComposer
|
||||
{ }
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// A component for NestedContent used to bind to events
|
||||
/// </summary>
|
||||
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<IContent> 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<Property> 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<Guid> 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
|
||||
|
||||
@@ -3,6 +3,9 @@ using Umbraco.Core.Composing;
|
||||
|
||||
namespace Umbraco.Web.Compose
|
||||
{
|
||||
/// <summary>
|
||||
/// A composer for nested content to run a component
|
||||
/// </summary>
|
||||
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
|
||||
public class NestedContentPropertyComposer : ComponentComposer<NestedContentPropertyComponent>, ICoreComposer
|
||||
{ }
|
||||
|
||||
@@ -130,6 +130,8 @@
|
||||
<Compile Include="Cache\UserGroupCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UserGroupPermissionsCacheRefresher.cs" />
|
||||
<Compile Include="Compose\BackOfficeUserAuditEventsComposer.cs" />
|
||||
<Compile Include="Compose\BlockEditorComponent.cs" />
|
||||
<Compile Include="Compose\BlockEditorComposer.cs" />
|
||||
<Compile Include="Compose\NestedContentPropertyComponent.cs" />
|
||||
<Compile Include="Compose\NotificationsComposer.cs" />
|
||||
<Compile Include="Compose\PublicAccessComposer.cs" />
|
||||
|
||||
Reference in New Issue
Block a user