Skip to content

Commit 6578f97

Browse files
committed
adding connector for blocklist property editor.
1 parent 9ff2829 commit 6578f97

File tree

3 files changed

+265
-3
lines changed

3 files changed

+265
-3
lines changed

src/Umbraco.Deploy.Contrib.Connectors/Umbraco.Deploy.Contrib.Connectors.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,15 @@
161161
<ItemGroup>
162162
<Compile Include="Properties\AssemblyInfo.cs" />
163163
<Compile Include="Properties\VersionInfo.cs" />
164+
<Compile Include="ValueConnectors\BlockEditorValueConnector.cs" />
164165
<Compile Include="ValueConnectors\MultiUrlPickerValueConnector.cs" />
166+
<Compile Include="ValueConnectors\BlockListValueConnector.cs" />
165167
<Compile Include="ValueConnectors\NestedContentValueConnector.cs" />
166168
</ItemGroup>
167169
<ItemGroup>
168170
<Content Include="GridCellValueConnectors\dummy.txt" />
169171
</ItemGroup>
170-
<ItemGroup>
171-
<Folder Include="ValueConnectors\" />
172-
</ItemGroup>
172+
<ItemGroup />
173173
<ItemGroup />
174174
<ItemGroup>
175175
<None Include="packages.config" />
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Newtonsoft.Json;
5+
using Newtonsoft.Json.Linq;
6+
using Umbraco.Core;
7+
using Umbraco.Core.Deploy;
8+
using Umbraco.Core.Logging;
9+
using Umbraco.Core.Models;
10+
using Umbraco.Core.Services;
11+
using Umbraco.Deploy.Connectors.ValueConnectors.Services;
12+
13+
namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors
14+
{
15+
/// <summary>
16+
/// A Deploy connector for BlockEditor based property editors (ie. BlockList)
17+
/// </summary>
18+
public abstract class BlockEditorValueConnector : IValueConnector
19+
{
20+
private readonly IContentTypeService _contentTypeService;
21+
private readonly Lazy<ValueConnectorCollection> _valueConnectorsLazy;
22+
private readonly ILogger _logger;
23+
24+
public virtual IEnumerable<string> PropertyEditorAliases => new[] { "Umbraco.BlockEditor" };
25+
26+
// cannot inject ValueConnectorCollection directly as it would cause a circular (recursive) dependency,
27+
// so we have to inject it lazily and use the lazy value when actually needing it
28+
private ValueConnectorCollection ValueConnectors => _valueConnectorsLazy.Value;
29+
30+
public BlockEditorValueConnector(IContentTypeService contentTypeService, Lazy<ValueConnectorCollection> valueConnectors, ILogger logger)
31+
{
32+
if (contentTypeService == null) throw new ArgumentNullException(nameof(contentTypeService));
33+
if (valueConnectors == null) throw new ArgumentNullException(nameof(valueConnectors));
34+
if (logger == null) throw new ArgumentNullException(nameof(logger));
35+
_contentTypeService = contentTypeService;
36+
_valueConnectorsLazy = valueConnectors;
37+
_logger = logger;
38+
}
39+
40+
public string ToArtifact(object value, PropertyType propertyType, ICollection<ArtifactDependency> dependencies)
41+
{
42+
var svalue = value as string;
43+
if (string.IsNullOrWhiteSpace(svalue))
44+
return null;
45+
46+
if (svalue.DetectIsJson() == false)
47+
return null;
48+
49+
var blockEditorValue = JsonConvert.DeserializeObject<BlockEditorValue>(svalue);
50+
51+
if (blockEditorValue == null)
52+
return null;
53+
54+
// get all the content types used in block editor items
55+
var allContentTypes = blockEditorValue.Data.Select(x => x.ContentTypeKey)
56+
.Distinct()
57+
.ToDictionary(a => a, a =>
58+
{
59+
if (!Guid.TryParse(a, out var keyAsGuid))
60+
throw new InvalidOperationException($"Could not parse ContentTypeKey as GUID {keyAsGuid}.");
61+
return _contentTypeService.Get(keyAsGuid);
62+
});
63+
64+
//Ensure all of these content types are found
65+
if (allContentTypes.Values.Any(contentType => contentType == null))
66+
{
67+
throw new InvalidOperationException($"Could not resolve these content types for the Block Editor property: {string.Join(",", allContentTypes.Where(x => x.Value == null).Select(x => x.Key))}");
68+
}
69+
70+
//Ensure that these content types have dependencies added
71+
foreach (var contentType in allContentTypes.Values)
72+
{
73+
dependencies.Add(new ArtifactDependency(contentType.GetUdi(), false, ArtifactDependencyMode.Match));
74+
}
75+
76+
foreach (var block in blockEditorValue.Data)
77+
{
78+
var contentType = allContentTypes[block.ContentTypeKey];
79+
80+
foreach (var key in block.PropertyValues.Keys.ToArray())
81+
{
82+
var propType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key);
83+
84+
if (propType == null)
85+
{
86+
_logger.Debug<NestedContentValueConnector>($"No property type found with alias {key} on content type {contentType.Alias}");
87+
continue;
88+
}
89+
90+
// fetch the right value connector from the collection of connectors, intended for use with this property type.
91+
// throws if not found - no need for a null check
92+
var propValueConnector = ValueConnectors.Get(propType);
93+
94+
// pass the value, property type and the dependencies collection to the connector to get a "artifact" value
95+
var val = block.PropertyValues[key];
96+
object parsedValue = propValueConnector.ToArtifact(val, propType, dependencies);
97+
98+
// getting Map image value umb://media/43e7401fb3cd48ceaa421df511ec703c to (nothing) - why?!
99+
_logger.Debug<BlockEditorValueConnector>("Map " + key + " value '" + block.PropertyValues[key] + "' to '" + parsedValue + "' using " + propValueConnector.GetType() + " for " + propType);
100+
101+
parsedValue = parsedValue?.ToString();
102+
103+
block.PropertyValues[key] = parsedValue;
104+
}
105+
}
106+
107+
value = JsonConvert.SerializeObject(blockEditorValue);
108+
return (string)value;
109+
}
110+
111+
public object FromArtifact(string value, PropertyType propertyType, object currentValue)
112+
{
113+
if (string.IsNullOrWhiteSpace(value))
114+
{
115+
return value;
116+
}
117+
118+
if (value.DetectIsJson() == false)
119+
return value;
120+
121+
var blockEditorValue = JsonConvert.DeserializeObject<BlockEditorValue>(value);
122+
123+
if (blockEditorValue == null)
124+
return value;
125+
126+
var allContentTypes = blockEditorValue.Data.Select(x => x.ContentTypeKey)
127+
.Distinct()
128+
.ToDictionary(a => a, a =>
129+
{
130+
if (!Guid.TryParse(a, out var keyAsGuid))
131+
throw new InvalidOperationException($"Could not parse ContentTypeKey as GUID {keyAsGuid}.");
132+
return _contentTypeService.Get(keyAsGuid);
133+
});
134+
135+
//Ensure all of these content types are found
136+
if (allContentTypes.Values.Any(contentType => contentType == null))
137+
{
138+
throw new InvalidOperationException($"Could not resolve these content types for the Block Editor property: {string.Join(",", allContentTypes.Where(x => x.Value == null).Select(x => x.Key))}");
139+
}
140+
141+
foreach (var block in blockEditorValue.Data)
142+
{
143+
var contentType = allContentTypes[block.ContentTypeKey];
144+
145+
foreach (var key in block.PropertyValues.Keys.ToArray())
146+
{
147+
var innerPropertyType = contentType.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key);
148+
149+
if (innerPropertyType == null)
150+
{
151+
_logger.Debug<BlockEditorValueConnector>($"No property type found with alias {key} on content type {contentType.Alias}");
152+
continue;
153+
}
154+
155+
// fetch the right value connector from the collection of connectors, intended for use with this property type.
156+
// throws if not found - no need for a null check
157+
var propValueConnector = ValueConnectors.Get(innerPropertyType);
158+
159+
var propertyValue = block.PropertyValues[key];
160+
161+
if (propertyValue != null)
162+
{
163+
// pass the artifact value and property type to the connector to get a real value from the artifact
164+
var convertedValue = propValueConnector.FromArtifact(propertyValue.ToString(), innerPropertyType, null);
165+
if (convertedValue == null)
166+
{
167+
block.PropertyValues[key] = null;
168+
}
169+
else
170+
{
171+
block.PropertyValues[key] = convertedValue;
172+
}
173+
}
174+
else
175+
{
176+
block.PropertyValues[key] = propertyValue;
177+
}
178+
}
179+
}
180+
181+
value = JsonConvert.SerializeObject(blockEditorValue);
182+
return value;
183+
}
184+
185+
/// <summary>
186+
/// Strongly typed representation of the stored value for a block editor value
187+
/// </summary>
188+
/// <example>
189+
/// Example JSON:
190+
/// <![CDATA[
191+
/// {
192+
/// "layout": {
193+
/// "Umbraco.BlockList": [
194+
/// {
195+
/// "udi": "umb://element/b401bb800a4a48f79786d5079bc47718"
196+
/// }
197+
/// ]
198+
/// },
199+
/// "data": [
200+
/// {
201+
/// "contentTypeKey": "5fe26fff-7163-4805-9eca-960b1f106bb9",
202+
/// "udi": "umb://element/b401bb800a4a48f79786d5079bc47718",
203+
/// "image": "umb://media/e28a0070890848079d5781774c3c5ffb",
204+
/// "text": "hero text",
205+
/// "contentpicker": "umb://document/87478d1efa66413698063f8d00fda1d1"
206+
/// }
207+
/// ]
208+
/// }
209+
/// ]]>
210+
/// </example>
211+
public class BlockEditorValue
212+
{
213+
/// <summary>
214+
/// We do not have to actually handle anything in the layout since it should only contain references to items existing as data.
215+
/// JObject is fine for transferring this over.
216+
/// </summary>
217+
[JsonProperty("layout")]
218+
public JObject Layout { get; set; }
219+
220+
/// <summary>
221+
/// This contains all the blocks created in the block editor.
222+
/// </summary>
223+
[JsonProperty("data")]
224+
public IEnumerable<Block> Data { get; set; }
225+
}
226+
227+
public class Block
228+
{
229+
[JsonProperty("contentTypeKey")]
230+
public string ContentTypeKey { get; set; }
231+
[JsonProperty("udi")]
232+
public string Udi { get; set; }
233+
234+
/// <summary>
235+
/// This is the property values defined on the block.
236+
/// These can be anything so we have to use a dictionary to represent them and JsonExtensionData attribute ensures all otherwise unmapped properties are stored here.
237+
/// </summary>
238+
[JsonExtensionData]
239+
public IDictionary<string, object> PropertyValues { get; set; }
240+
}
241+
}
242+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Umbraco.Core.Logging;
4+
using Umbraco.Core.Services;
5+
using Umbraco.Deploy.Connectors.ValueConnectors.Services;
6+
7+
namespace Umbraco.Deploy.Contrib.Connectors.ValueConnectors
8+
{
9+
/// <summary>
10+
/// A Deploy connector for the BlockList property editor
11+
/// </summary>
12+
public class BlockListValueConnector : BlockEditorValueConnector
13+
{
14+
public override IEnumerable<string> PropertyEditorAliases => new[] { "Umbraco.BlockList" };
15+
16+
public BlockListValueConnector(IContentTypeService contentTypeService, Lazy<ValueConnectorCollection> valueConnectors, ILogger logger)
17+
: base(contentTypeService, valueConnectors, logger)
18+
{ }
19+
}
20+
}

0 commit comments

Comments
 (0)