Skip to content

Commit 0005ef8

Browse files
Copy BlockEditorValueConnector from Umbraco.Deploy to work around v13 compilation issue
1 parent 9f475b9 commit 0005ef8

File tree

2 files changed

+305
-1
lines changed

2 files changed

+305
-1
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System;
4+
using Microsoft.Extensions.Logging;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
using Umbraco.Cms.Core.Deploy;
8+
using Umbraco.Cms.Core.Models;
9+
using Umbraco.Cms.Core.Services;
10+
using Umbraco.Deploy.Core;
11+
using Umbraco.Deploy.Core.Connectors.ValueConnectors;
12+
using Umbraco.Deploy.Core.Connectors.ValueConnectors.Services;
13+
using Umbraco.Deploy.Infrastructure.Extensions;
14+
using Umbraco.Extensions;
15+
16+
namespace Umbraco.Deploy.Infrastructure.Connectors.ValueConnectors
17+
{
18+
// TODO: Delete in v13-RC2
19+
20+
/// <summary>
21+
/// A Deploy connector for BlockEditor based property editors (ie. BlockList)
22+
/// </summary>
23+
public abstract class BlockEditorValueConnectorTmp : ValueConnectorBase
24+
{
25+
private readonly IContentTypeService _contentTypeService;
26+
private readonly Lazy<ValueConnectorCollection> _valueConnectorsLazy;
27+
private readonly ILogger<BlockEditorValueConnectorTmp> _logger;
28+
29+
/// <inheritdoc />
30+
public override IEnumerable<string> PropertyEditorAliases { get; } = Enumerable.Empty<string>();
31+
32+
// cannot inject ValueConnectorCollection directly as it would cause a circular (recursive) dependency,
33+
// so we have to inject it lazily and use the lazy value when actually needing it
34+
private ValueConnectorCollection ValueConnectors => _valueConnectorsLazy.Value;
35+
36+
public BlockEditorValueConnectorTmp(IContentTypeService contentTypeService, Lazy<ValueConnectorCollection> valueConnectors, ILogger<BlockEditorValueConnectorTmp> logger)
37+
{
38+
_contentTypeService = contentTypeService ?? throw new ArgumentNullException(nameof(contentTypeService));
39+
_valueConnectorsLazy = valueConnectors ?? throw new ArgumentNullException(nameof(valueConnectors));
40+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
41+
}
42+
43+
public override string? ToArtifact(object? value, IPropertyType propertyType, ICollection<ArtifactDependency> dependencies, IContextCache contextCache)
44+
{
45+
_logger.LogDebug("Converting {PropertyType} to artifact.", propertyType.Alias);
46+
var valueAsString = value as string;
47+
48+
// nested values will arrive here as JObject - convert to string to enable reuse of same code as when non-nested.
49+
if (value is JObject)
50+
{
51+
_logger.LogDebug("Value is a JObject - converting to string.");
52+
valueAsString = value.ToString();
53+
}
54+
55+
if (string.IsNullOrWhiteSpace(valueAsString))
56+
{
57+
_logger.LogDebug($"Value is null or whitespace. Skipping conversion to artifact.");
58+
return null;
59+
}
60+
61+
if (!valueAsString.TryParseJson(out BlockEditorValue? blockEditorValue))
62+
{
63+
_logger.LogWarning("Value '{Value}' is not a JSON string. Skipping conversion to artifact.", valueAsString);
64+
return null;
65+
}
66+
67+
if (blockEditorValue?.Content == null)
68+
{
69+
_logger.LogWarning("Deserialized value is null. Skipping conversion to artifact.");
70+
return null;
71+
}
72+
73+
var allBlocks = blockEditorValue.Content.Concat(blockEditorValue.Settings ?? Enumerable.Empty<Block>()).ToList();
74+
75+
// get all the content types used in block editor items
76+
var allContentTypes = allBlocks.Select(x => x.ContentTypeKey)
77+
.Distinct()
78+
.ToDictionary(a => a, a =>
79+
{
80+
if (!Guid.TryParse(a, out var keyAsGuid))
81+
{
82+
throw new InvalidOperationException($"Could not parse ContentTypeKey as GUID {keyAsGuid}.");
83+
}
84+
85+
return contextCache.GetContentTypeByKey(_contentTypeService, keyAsGuid);
86+
});
87+
88+
//Ensure all of these content types are found
89+
if (allContentTypes.Values.Any(contentType => contentType == null))
90+
{
91+
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))}");
92+
}
93+
94+
//Ensure that these content types have dependencies added
95+
foreach (var contentType in allContentTypes.Values)
96+
{
97+
_logger.LogDebug("Adding dependency for content type {ContentType}.", contentType!.Alias);
98+
dependencies.Add(new ArtifactDependency(contentType.GetUdi(), false, ArtifactDependencyMode.Match));
99+
}
100+
101+
foreach (var block in allBlocks)
102+
{
103+
var contentType = allContentTypes[block.ContentTypeKey];
104+
105+
if (block.PropertyValues != null)
106+
{
107+
foreach (var key in block.PropertyValues.Keys.ToArray())
108+
{
109+
var innerPropertyType = contentType!.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key);
110+
111+
if (innerPropertyType == null)
112+
{
113+
_logger.LogWarning("No property type found with alias {PropertyType} on content type {ContentType}.", key, contentType.Alias);
114+
continue;
115+
}
116+
117+
// fetch the right value connector from the collection of connectors, intended for use with this property type.
118+
// throws if not found - no need for a null check
119+
var propertyValueConnector = ValueConnectors.Get(innerPropertyType);
120+
121+
// pass the value, property type and the dependencies collection to the connector to get a "artifact" value
122+
var innerValue = block.PropertyValues[key];
123+
object? parsedValue = propertyValueConnector.ToArtifact(innerValue, innerPropertyType, dependencies, contextCache);
124+
125+
_logger.LogDebug("Mapped {Key} value '{PropertyValue}' to '{ParsedValue}' using {PropertyValueConnectorType} for {PropertyType}.", key, block.PropertyValues[key], parsedValue, propertyValueConnector.GetType(), innerPropertyType.Alias);
126+
127+
parsedValue = parsedValue?.ToString();
128+
129+
block.PropertyValues[key] = parsedValue;
130+
}
131+
}
132+
}
133+
134+
value = JsonConvert.SerializeObject(blockEditorValue);
135+
_logger.LogDebug("Finished converting {PropertyType} to artifact.", propertyType.Alias);
136+
return (string)value;
137+
}
138+
139+
public override object? FromArtifact(string? value, IPropertyType propertyType, object? currentValue, IContextCache contextCache)
140+
{
141+
_logger.LogDebug("Converting {PropertyType} from artifact.", propertyType.Alias);
142+
if (string.IsNullOrWhiteSpace(value))
143+
{
144+
return value;
145+
}
146+
147+
if (!value.TryParseJson(out BlockEditorValue? blockEditorValue))
148+
{
149+
return value;
150+
}
151+
152+
if (blockEditorValue == null)
153+
{
154+
return value;
155+
}
156+
157+
var allBlocks = blockEditorValue.Content.Concat(blockEditorValue.Settings ?? Enumerable.Empty<Block>()).ToList();
158+
159+
var allContentTypes = allBlocks.Select(x => x.ContentTypeKey)
160+
.Distinct()
161+
.ToDictionary(a => a, a =>
162+
{
163+
if (!Guid.TryParse(a, out var keyAsGuid))
164+
throw new InvalidOperationException($"Could not parse ContentTypeKey as GUID {keyAsGuid}.");
165+
166+
return contextCache.GetContentTypeByKey(_contentTypeService, keyAsGuid);
167+
});
168+
169+
//Ensure all of these content types are found
170+
if (allContentTypes.Values.Any(contentType => contentType == null))
171+
{
172+
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))}");
173+
}
174+
175+
foreach (var block in allBlocks)
176+
{
177+
var contentType = allContentTypes[block.ContentTypeKey];
178+
179+
if (block.PropertyValues != null)
180+
{
181+
foreach (var key in block.PropertyValues.Keys.ToArray())
182+
{
183+
var innerPropertyType = contentType!.CompositionPropertyTypes.FirstOrDefault(x => x.Alias == key);
184+
185+
if (innerPropertyType == null)
186+
{
187+
_logger.LogWarning("No property type found with alias {Key} on content type {ContentType}.", key, contentType.Alias);
188+
continue;
189+
}
190+
191+
// fetch the right value connector from the collection of connectors, intended for use with this property type.
192+
// throws if not found - no need for a null check
193+
var propertyValueConnector = ValueConnectors.Get(innerPropertyType);
194+
195+
var innerValue = block.PropertyValues[key];
196+
197+
if (innerValue != null)
198+
{
199+
// pass the artifact value and property type to the connector to get a real value from the artifact
200+
var convertedValue = propertyValueConnector.FromArtifact(innerValue.ToString(), innerPropertyType, null, contextCache);
201+
202+
if (convertedValue == null)
203+
{
204+
block.PropertyValues[key] = null;
205+
}
206+
else
207+
{
208+
block.PropertyValues[key] = convertedValue;
209+
}
210+
_logger.LogDebug("Mapped {Key} value '{PropertyValue}' to '{ConvertedValue}' using {PropertyValueConnectorType} for {PropertyType}.", key, innerValue, convertedValue, propertyValueConnector.GetType(), innerPropertyType.Alias);
211+
}
212+
else
213+
{
214+
block.PropertyValues[key] = innerValue;
215+
_logger.LogDebug("{Key} value was null. Setting value as null without conversion.", key);
216+
}
217+
}
218+
}
219+
}
220+
221+
_logger.LogDebug("Finished converting {PropertyType} from artifact.", propertyType.Alias);
222+
223+
return JObject.FromObject(blockEditorValue);
224+
}
225+
226+
/// <summary>
227+
/// Strongly typed representation of the stored value for a block editor value
228+
/// </summary>
229+
/// <example>
230+
/// Example JSON:
231+
/// <![CDATA[
232+
/// {
233+
/// "layout": {
234+
/// "Umbraco.BlockList": [
235+
/// {
236+
/// "contentUdi": "umb://element/b401bb800a4a48f79786d5079bc47718"
237+
/// }
238+
/// ]
239+
/// },
240+
/// "contentData": [
241+
/// {
242+
/// "contentTypeKey": "5fe26fff-7163-4805-9eca-960b1f106bb9",
243+
/// "udi": "umb://element/b401bb800a4a48f79786d5079bc47718",
244+
/// "image": "umb://media/e28a0070890848079d5781774c3c5ffb",
245+
/// "text": "hero text",
246+
/// "contentpicker": "umb://document/87478d1efa66413698063f8d00fda1d1"
247+
/// }
248+
/// ],
249+
/// "settingsData": [
250+
/// {
251+
/// "contentTypeKey": "2e6094ea-7bca-4b7c-a223-375254a194f4",
252+
/// "udi": "umb://element/499cf69f00c84227a59ca10fb4ae4c9a",
253+
/// "textColor": "",
254+
/// "containerWidth": "standard",
255+
/// "textWidth": [],
256+
/// "height": [],
257+
/// "overlayStrength": [],
258+
/// "textAlignment": "left",
259+
/// "verticalTextAlignment": "top",
260+
/// "animate": "0"
261+
/// }
262+
/// ]
263+
/// }
264+
/// ]]>
265+
/// </example>
266+
public class BlockEditorValue
267+
{
268+
/// <summary>
269+
/// We do not have to actually handle anything in the layout since it should only contain references to items existing as data.
270+
/// JObject is fine for transferring this over.
271+
/// </summary>
272+
[JsonProperty("layout")]
273+
public JObject? Layout { get; set; }
274+
275+
/// <summary>
276+
/// This contains all the blocks created in the block editor.
277+
/// </summary>
278+
[JsonProperty("contentData")]
279+
public IEnumerable<Block> Content { get; set; } = Enumerable.Empty<Block>();
280+
281+
/// <summary>
282+
/// This contains the settings associated with the block editor.
283+
/// </summary>
284+
[JsonProperty("settingsData")]
285+
public IEnumerable<Block> Settings { get; set; } = Enumerable.Empty<Block>();
286+
}
287+
288+
public class Block
289+
{
290+
[JsonProperty("contentTypeKey")]
291+
public string ContentTypeKey { get; set; } = string.Empty;
292+
293+
[JsonProperty("udi")]
294+
public string Udi { get; set; } = string.Empty;
295+
296+
/// <summary>
297+
/// This is the property values defined on the block.
298+
/// 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.
299+
/// </summary>
300+
[JsonExtensionData]
301+
public IDictionary<string, object?> PropertyValues { get; set; } = new Dictionary<string, object?>();
302+
}
303+
}
304+
}

src/Umbraco.Commerce.Deploy/Connectors/ValueConnectors/UmbracoCommerceVariantsEditorValueConnector.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Umbraco.Commerce.Deploy.Connectors.ValueConnectors
1919
/// <summary>
2020
/// A Deploy connector for the Umbraco Commerce Variants Editor property editor
2121
/// </summary>
22-
public class UmbracoCommerceVariantsEditorValueConnector : BlockEditorValueConnector, IValueConnector2
22+
public class UmbracoCommerceVariantsEditorValueConnector : BlockEditorValueConnectorTmp, IValueConnector2
2323
{
2424
private readonly IUmbracoCommerceApi _umbracoCommerceApi;
2525

0 commit comments

Comments
 (0)