Skip to content

Commit 6131add

Browse files
Merge pull request #66 from umbraco/v13/feature/doctypegrideditor-migrators
Add migrators to support DocTypeGridEditor to Block Grid migration
2 parents 76cd920 + 6662fb5 commit 6131add

File tree

5 files changed

+298
-14
lines changed

5 files changed

+298
-14
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
<!-- Umbraco packages -->
1414
<ItemGroup>
1515
<PackageVersion Include="Umbraco.Cms.Web.Common" Version="[13.0.0, 14)" />
16-
<PackageVersion Include="Umbraco.Deploy.Infrastructure" Version="[13.1.0, 14)" />
16+
<PackageVersion Include="Umbraco.Deploy.Infrastructure" Version="[13.2.0-rc1, 14)" />
1717
</ItemGroup>
1818
</Project>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using Microsoft.Extensions.Logging;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
using Umbraco.Cms.Core;
8+
using Umbraco.Cms.Core.Deploy;
9+
using Umbraco.Cms.Core.Models;
10+
using Umbraco.Cms.Core.Models.Blocks;
11+
using Umbraco.Cms.Core.PropertyEditors;
12+
using Umbraco.Cms.Core.Serialization;
13+
using Umbraco.Cms.Core.Services;
14+
using Umbraco.Cms.Core.Strings;
15+
using Umbraco.Deploy.Infrastructure.Migrators;
16+
using Umbraco.Extensions;
17+
18+
namespace Umbraco.Deploy.Contrib.Migrators;
19+
20+
/// <summary>
21+
/// Migrates the property value when the editor of a property type changed from <see cref="Constants.PropertyEditors.Aliases.Grid" /> to <see cref="Constants.PropertyEditors.Aliases.BlockGrid" /> and supports DocTypeGridEditor.
22+
/// </summary>
23+
public class DocTypeGridEditorPropertyTypeMigrator : GridPropertyTypeMigrator
24+
{
25+
private readonly IJsonSerializer _jsonSerializer;
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="DocTypeGridEditorPropertyTypeMigrator" /> class.
29+
/// </summary>
30+
/// <param name="logger">The logger.</param>
31+
/// <param name="jsonSerializer">The JSON serializer.</param>
32+
/// <param name="dataTypeService">The data type service.</param>
33+
/// <param name="shortStringHelper">The short string helper.</param>
34+
/// <param name="contentTypeService">The content type service.</param>
35+
/// <param name="mediaService">The media service.</param>
36+
public DocTypeGridEditorPropertyTypeMigrator(ILogger<GridPropertyTypeMigrator> logger, IJsonSerializer jsonSerializer, IDataTypeService dataTypeService, IShortStringHelper shortStringHelper, IContentTypeService contentTypeService, IMediaService mediaService)
37+
: base(logger, jsonSerializer, dataTypeService, shortStringHelper, contentTypeService, mediaService)
38+
=> _jsonSerializer = jsonSerializer;
39+
40+
/// <inheritdoc />
41+
protected override BlockItemData? MigrateGridControl(GridValue.GridControl gridControl, BlockGridConfiguration configuration, IContextCache contextCache)
42+
{
43+
if (TryDeserialize(gridControl.Value, out DocTypeGridEditorValue? value))
44+
{
45+
return MigrateGridControl(value, configuration, contextCache);
46+
}
47+
48+
return base.MigrateGridControl(gridControl, configuration, contextCache);
49+
}
50+
51+
/// <summary>
52+
/// Migrates the grid control.
53+
/// </summary>
54+
/// <param name="value">The DTGE value.</param>
55+
/// <param name="configuration">The configuration.</param>
56+
/// <param name="contextCache">The context cache.</param>
57+
/// <returns>
58+
/// The block item data, or <c>null</c> if migration should be skipped.
59+
/// </returns>
60+
protected virtual BlockItemData? MigrateGridControl(DocTypeGridEditorValue value, BlockGridConfiguration configuration, IContextCache contextCache)
61+
{
62+
IContentType contentType = GetContentType(value.ContentTypeAlias, configuration, contextCache)
63+
?? throw new InvalidOperationException($"Migrating legacy grid failed, because content type with alias '{value.ContentTypeAlias}' could not be found (in the Block Grid configuration).");
64+
65+
return new BlockItemData()
66+
{
67+
Udi = Udi.Create(Constants.UdiEntityType.Element, value.Id),
68+
ContentTypeKey = contentType.Key,
69+
RawPropertyValues = value.Value
70+
};
71+
}
72+
73+
private bool TryDeserialize(JToken? value, [NotNullWhen(true)] out DocTypeGridEditorValue? docTypeGridEditorValue)
74+
{
75+
try
76+
{
77+
docTypeGridEditorValue = value switch
78+
{
79+
JObject jsonObject => jsonObject.ToObject<DocTypeGridEditorValue>(),
80+
JToken jsonToken when jsonToken.Value<string>() is string json && json.DetectIsJson() => _jsonSerializer.Deserialize<DocTypeGridEditorValue>(json),
81+
_ => null
82+
};
83+
84+
return !string.IsNullOrEmpty(docTypeGridEditorValue?.ContentTypeAlias);
85+
}
86+
catch (JsonSerializationException)
87+
{
88+
docTypeGridEditorValue = null;
89+
return false;
90+
}
91+
}
92+
93+
/// <summary>
94+
/// The DTGE grid editor value.
95+
/// </summary>
96+
protected sealed class DocTypeGridEditorValue
97+
{
98+
/// <summary>
99+
/// Gets or sets the value.
100+
/// </summary>
101+
/// <value>
102+
/// The value.
103+
/// </value>
104+
[JsonProperty("value")]
105+
public Dictionary<string, object?> Value { get; set; } = new();
106+
107+
/// <summary>
108+
/// Gets or sets the content type alias.
109+
/// </summary>
110+
/// <value>
111+
/// The content type alias.
112+
/// </value>
113+
[JsonProperty("dtgeContentTypeAlias")]
114+
public string ContentTypeAlias { get; set; } = string.Empty;
115+
116+
/// <summary>
117+
/// Gets or sets the identifier.
118+
/// </summary>
119+
/// <value>
120+
/// The identifier.
121+
/// </value>
122+
[JsonProperty("id")]
123+
public Guid Id { get; set; } = Guid.NewGuid();
124+
}
125+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.RegularExpressions;
5+
using Newtonsoft.Json.Linq;
6+
using Umbraco.Cms.Core;
7+
using Umbraco.Cms.Core.Configuration.Grid;
8+
using Umbraco.Cms.Core.Models;
9+
using Umbraco.Cms.Core.PropertyEditors;
10+
using Umbraco.Cms.Core.Serialization;
11+
using Umbraco.Cms.Core.Services;
12+
using Umbraco.Cms.Core.Strings;
13+
using Umbraco.Deploy.Infrastructure.Artifacts;
14+
using Umbraco.Deploy.Infrastructure.Migrators;
15+
using Umbraco.Extensions;
16+
using static Umbraco.Cms.Core.PropertyEditors.BlockGridConfiguration;
17+
18+
namespace Umbraco.Deploy.Contrib.Migrators;
19+
20+
/// <summary>
21+
/// Migrates the <see cref="DataTypeArtifact" /> to replace the legacy/obsoleted <see cref="Constants.PropertyEditors.Aliases.Grid" /> editor with <see cref="Constants.PropertyEditors.Aliases.BlockGrid" /> and support DTGE grid editors.
22+
/// </summary>
23+
public class ReplaceDocTypeGridEditorDataTypeArtifactMigrator : ReplaceGridDataTypeArtifactMigrator
24+
{
25+
private readonly IContentTypeService _contentTypeService;
26+
27+
/// <summary>
28+
/// Gets or sets a value indicating whether to add the default DTGE grid editor (if not configured in the grid.editors.config.js files).
29+
/// </summary>
30+
/// <value>
31+
/// <c>true</c> if the default DTGE grid editor is added; otherwise, <c>false</c>.
32+
/// </value>
33+
/// <remarks>
34+
/// Defaults to <c>true</c>.
35+
/// </remarks>
36+
protected bool AddDefaultDocTypeGridEditor { get; init; } = true;
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="ReplaceDocTypeGridEditorDataTypeArtifactMigrator" /> class.
40+
/// </summary>
41+
/// <param name="propertyEditors">The property editors.</param>
42+
/// <param name="configurationEditorJsonSerializer">The configuration editor JSON serializer.</param>
43+
/// <param name="contentTypeService">The content type service.</param>
44+
/// <param name="dataTypeService">The data type service.</param>
45+
/// <param name="shortStringHelper">The short string helper.</param>
46+
/// <param name="gridConfig">The grid configuration.</param>
47+
public ReplaceDocTypeGridEditorDataTypeArtifactMigrator(PropertyEditorCollection propertyEditors, IConfigurationEditorJsonSerializer configurationEditorJsonSerializer, IContentTypeService contentTypeService, IDataTypeService dataTypeService, IShortStringHelper shortStringHelper, IGridConfig gridConfig)
48+
: base(propertyEditors, configurationEditorJsonSerializer, contentTypeService, dataTypeService, shortStringHelper, gridConfig)
49+
=> _contentTypeService = contentTypeService;
50+
51+
/// <inheritdoc />
52+
protected override IEnumerable<(string, BlockGridBlockConfiguration)> MigrateGridEditors(IEnumerable<GridConfigurationLayout> gridLayouts)
53+
{
54+
// Migrate regular grid editors (DTGE editors are skipped)
55+
foreach ((string alias, BlockGridBlockConfiguration blockGridBlockConfiguration) in base.MigrateGridEditors(gridLayouts))
56+
{
57+
yield return (alias, blockGridBlockConfiguration);
58+
}
59+
60+
// Migrate DTGE editors
61+
var allElementTypes = GetAllElementTypes().ToList();
62+
var migratedContentTypeKeys = new HashSet<Guid>();
63+
foreach (IGridEditorConfig gridEditor in GetGridEditors(gridLayouts).Where(IsDocTypeGridEditor))
64+
{
65+
foreach (Guid contentElementTypeKey in MigrateDocTypeGridEditor(gridEditor, allElementTypes))
66+
{
67+
// Avoid DocTypeGridEditors returning duplicate block configurations for the same content element type
68+
if (migratedContentTypeKeys.Add(contentElementTypeKey))
69+
{
70+
yield return (gridEditor.Alias, new BlockGridBlockConfiguration()
71+
{
72+
ContentElementTypeKey = contentElementTypeKey,
73+
Label = gridEditor.Config.TryGetValue("nameTemplate", out var nameTemplateConfig) && nameTemplateConfig is string nameTemplate && !string.IsNullOrEmpty(nameTemplate)
74+
? nameTemplate
75+
: gridEditor.NameTemplate,
76+
EditorSize = gridEditor.Config.TryGetValue("overlaySize", out var overviewSizeConfig) && overviewSizeConfig is string overviewSize && !string.IsNullOrEmpty(overviewSize)
77+
? overviewSize
78+
: null,
79+
});
80+
}
81+
}
82+
}
83+
}
84+
85+
/// <inheritdoc />
86+
protected override IEnumerable<IGridEditorConfig> GetGridEditors()
87+
{
88+
foreach (IGridEditorConfig gridEditor in base.GetGridEditors())
89+
{
90+
yield return gridEditor;
91+
}
92+
93+
if (AddDefaultDocTypeGridEditor)
94+
{
95+
yield return new GridEditor()
96+
{
97+
Name = "Doc Type",
98+
Alias = "docType",
99+
View = "/App_Plugins/DocTypeGridEditor/Views/doctypegrideditor.html",
100+
Render = "/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml",
101+
Icon = "icon-item-arrangement",
102+
};
103+
}
104+
}
105+
106+
/// <inheritdoc />
107+
protected override Guid? MigrateGridEditor(IGridEditorConfig gridEditor)
108+
// Skip migrating DocTypeGridEditor using base implementation (as that creates a new element type)
109+
=> IsDocTypeGridEditor(gridEditor) is false ? base.MigrateGridEditor(gridEditor) : null;
110+
111+
/// <summary>
112+
/// Migrates the DocTypeGridEditor.
113+
/// </summary>
114+
/// <param name="gridEditor">The grid editor.</param>
115+
/// <param name="allElementTypes">All element types.</param>
116+
/// <returns>
117+
/// The keys of the content element types of the migrated grid editor.
118+
/// </returns>
119+
protected virtual IEnumerable<Guid> MigrateDocTypeGridEditor(IGridEditorConfig gridEditor, IEnumerable<IContentType> allElementTypes)
120+
{
121+
if (gridEditor.Config.TryGetValue("allowedDocTypes", out var allowedDocTypesConfig) &&
122+
allowedDocTypesConfig is JArray allowedDocTypes &&
123+
allowedDocTypes.Values<string>().WhereNotNull().ToArray() is string[] docTypes &&
124+
docTypes.Length > 0)
125+
{
126+
// Use regex matching
127+
return allElementTypes.Where(x => docTypes.Any(y => Regex.IsMatch(x.Alias, y))).Select(x => x.Key);
128+
}
129+
130+
// Return all
131+
return allElementTypes.Select(x => x.Key);
132+
}
133+
134+
/// <summary>
135+
/// Gets all element types.
136+
/// </summary>
137+
/// <returns>
138+
/// Returns all element types.
139+
/// </returns>
140+
/// <remarks>
141+
/// The default implementation excludes element types with aliases that start with <c>gridLayout_</c>, <c>gridRow_</c>, <c>gridEditor_</c> or <c>gridSettings_</c>.
142+
/// </remarks>
143+
protected virtual IEnumerable<IContentType> GetAllElementTypes()
144+
{
145+
static bool IsAllowedElementType(string alias) => !alias.StartsWith("gridLayout_") && !alias.StartsWith("gridRow_") && !alias.StartsWith("gridEditor_") && !alias.StartsWith("gridSettings_");
146+
147+
return _contentTypeService.GetAllElementTypes().Where(x => IsAllowedElementType(x.Alias));
148+
}
149+
150+
/// <summary>
151+
/// Determines whether the grid editor is the DocTypeGridEditor.
152+
/// </summary>
153+
/// <param name="gridEditor">The grid editor.</param>
154+
/// <returns>
155+
/// <c>true</c> if the grid editor is the DocTypeGridEditor; otherwise, <c>false</c>.
156+
/// </returns>
157+
protected static bool IsDocTypeGridEditor(IGridEditorConfig gridEditor)
158+
=> gridEditor.View?.Contains("doctypegrideditor", StringComparison.OrdinalIgnoreCase) is true;
159+
}

src/Umbraco.Deploy.Contrib/packages.lock.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@
4949
},
5050
"Umbraco.Deploy.Infrastructure": {
5151
"type": "Direct",
52-
"requested": "[13.1.0, 14.0.0)",
53-
"resolved": "13.1.0",
54-
"contentHash": "LS2RJC4znoS4FvZEuDD0Fw8tdOhP0nAf4HYoCRirPNfaP4GOt5/jjTxr6TyF6nJr2JPXr9qEE40DoOC5+uXMLg==",
52+
"requested": "[13.2.0-rc1, 14.0.0)",
53+
"resolved": "13.2.0-rc1",
54+
"contentHash": "VhVsKrkpxreisA4JWVD9HnPsjaYckHZbA4ED+OSVlwa9AmwNRXAsoRtaZqaEDuxMY3fzZxZRpihBFzIIDxuosg==",
5555
"dependencies": {
5656
"Umbraco.Cms.Web.BackOffice": "[13.0.0, 14.0.0)",
57-
"Umbraco.Deploy.Core": "[13.1.0, 14.0.0)"
57+
"Umbraco.Deploy.Core": "[13.2.0-rc1, 14.0.0)"
5858
}
5959
},
6060
"Umbraco.GitVersioning.Extensions": {
@@ -2243,8 +2243,8 @@
22432243
},
22442244
"Umbraco.Deploy.Core": {
22452245
"type": "Transitive",
2246-
"resolved": "13.1.0",
2247-
"contentHash": "UnXxci5sanYHLTABTjUdYLkKuRaoQPYS9lp/nZ1r8h+OcDo/h4gES6VnS1OFE8XAZRanae9IqyT1TtMTReNUoQ==",
2246+
"resolved": "13.2.0-rc1",
2247+
"contentHash": "y9dkLU297uLGhzRB1A32DjFbMZoySM4TtcC/Jq2gvJw4uds3S5T0pfXgI+lBGaEbg/oRPtAr+0OHXugVYXM1Ag==",
22482248
"dependencies": {
22492249
"Umbraco.Cms.Core": "[13.0.0, 14.0.0)"
22502250
}

tests/Umbraco.Deploy.Contrib.Tests/packages.lock.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@
5757
},
5858
"Umbraco.Deploy.Infrastructure": {
5959
"type": "Direct",
60-
"requested": "[13.1.0, 14.0.0)",
61-
"resolved": "13.1.0",
62-
"contentHash": "LS2RJC4znoS4FvZEuDD0Fw8tdOhP0nAf4HYoCRirPNfaP4GOt5/jjTxr6TyF6nJr2JPXr9qEE40DoOC5+uXMLg==",
60+
"requested": "[13.2.0-rc1, 14.0.0)",
61+
"resolved": "13.2.0-rc1",
62+
"contentHash": "VhVsKrkpxreisA4JWVD9HnPsjaYckHZbA4ED+OSVlwa9AmwNRXAsoRtaZqaEDuxMY3fzZxZRpihBFzIIDxuosg==",
6363
"dependencies": {
6464
"Umbraco.Cms.Web.BackOffice": "[13.0.0, 14.0.0)",
65-
"Umbraco.Deploy.Core": "[13.1.0, 14.0.0)"
65+
"Umbraco.Deploy.Core": "[13.2.0-rc1, 14.0.0)"
6666
}
6767
},
6868
"Umbraco.GitVersioning.Extensions": {
@@ -2749,8 +2749,8 @@
27492749
},
27502750
"Umbraco.Deploy.Core": {
27512751
"type": "Transitive",
2752-
"resolved": "13.1.0",
2753-
"contentHash": "UnXxci5sanYHLTABTjUdYLkKuRaoQPYS9lp/nZ1r8h+OcDo/h4gES6VnS1OFE8XAZRanae9IqyT1TtMTReNUoQ==",
2752+
"resolved": "13.2.0-rc1",
2753+
"contentHash": "y9dkLU297uLGhzRB1A32DjFbMZoySM4TtcC/Jq2gvJw4uds3S5T0pfXgI+lBGaEbg/oRPtAr+0OHXugVYXM1Ag==",
27542754
"dependencies": {
27552755
"Umbraco.Cms.Core": "[13.0.0, 14.0.0)"
27562756
}
@@ -2759,7 +2759,7 @@
27592759
"type": "Project",
27602760
"dependencies": {
27612761
"Umbraco.Cms.Web.Common": "[13.0.0, 14.0.0)",
2762-
"Umbraco.Deploy.Infrastructure": "[13.1.0, 14.0.0)"
2762+
"Umbraco.Deploy.Infrastructure": "[13.2.0-rc1, 14.0.0)"
27632763
}
27642764
},
27652765
"Moq": {

0 commit comments

Comments
 (0)