Skip to content

Commit bdda86e

Browse files
Add ReplaceDocTypeGridEditorDataTypeArtifactMigrator and DocTypeGridEditorPropertyTypeMigrator
1 parent 93d72fb commit bdda86e

File tree

5 files changed

+297
-18
lines changed

5 files changed

+297
-18
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.preview.1.g294fdcd, 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: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
foreach (IGridEditorConfig gridEditor in GetGridEditors(gridLayouts).Where(IsDocTypeGridEditor))
63+
{
64+
foreach (Guid contentElementTypeKey in MigrateDocTypeGridEditor(gridEditor, allElementTypes))
65+
{
66+
yield return (gridEditor.Alias, new BlockGridBlockConfiguration()
67+
{
68+
ContentElementTypeKey = contentElementTypeKey,
69+
Label = gridEditor.Config.TryGetValue("nameTemplate", out var nameTemplateConfig) && nameTemplateConfig is string nameTemplate && !string.IsNullOrEmpty(nameTemplate)
70+
? nameTemplate
71+
: gridEditor.NameTemplate,
72+
EditorSize = gridEditor.Config.TryGetValue("overlaySize", out var overviewSizeConfig) && overviewSizeConfig is string overviewSize && !string.IsNullOrEmpty(overviewSize)
73+
? overviewSize
74+
: null,
75+
});
76+
}
77+
}
78+
}
79+
80+
/// <inheritdoc />
81+
protected override IEnumerable<IGridEditorConfig> GetGridEditors()
82+
{
83+
foreach (IGridEditorConfig gridEditor in base.GetGridEditors())
84+
{
85+
yield return gridEditor;
86+
}
87+
88+
if (AddDefaultDocTypeGridEditor)
89+
{
90+
yield return new GridEditor()
91+
{
92+
Name = "Doc Type",
93+
Alias = "docType",
94+
View = "/App_Plugins/DocTypeGridEditor/Views/doctypegrideditor.html",
95+
Render = "/App_Plugins/DocTypeGridEditor/Render/DocTypeGridEditor.cshtml",
96+
Icon = "icon-item-arrangement",
97+
};
98+
}
99+
}
100+
101+
/// <inheritdoc />
102+
protected override Guid? MigrateGridEditor(IGridEditorConfig gridEditor)
103+
// Skip migrating DocTypeGridEditor using base implementation (as that creates a new element type)
104+
=> IsDocTypeGridEditor(gridEditor) is false ? base.MigrateGridEditor(gridEditor) : null;
105+
106+
/// <summary>
107+
/// Migrates the DocTypeGridEditor.
108+
/// </summary>
109+
/// <param name="gridEditor">The grid editor.</param>
110+
/// <param name="allElementTypes">All element types.</param>
111+
/// <returns>
112+
/// The keys of the content element types of the migrated grid editor.
113+
/// </returns>
114+
protected virtual IEnumerable<Guid> MigrateDocTypeGridEditor(IGridEditorConfig gridEditor, IEnumerable<IContentType> allElementTypes)
115+
{
116+
if (gridEditor.Config.TryGetValue("allowedDocTypes", out var allowedDocTypesConfig) &&
117+
allowedDocTypesConfig is JArray allowedDocTypes &&
118+
allowedDocTypes.Values<string>().WhereNotNull().ToArray() is string[] docTypes &&
119+
docTypes.Length > 0)
120+
{
121+
// Use regex matching
122+
return allElementTypes.Where(x => docTypes.Any(y => Regex.IsMatch(x.Alias, y))).Select(x => x.Key);
123+
}
124+
125+
// Return all
126+
return allElementTypes.Select(x => x.Key);
127+
}
128+
129+
/// <summary>
130+
/// Gets all element types.
131+
/// </summary>
132+
/// <returns>
133+
/// Returns all element types.
134+
/// </returns>
135+
/// <remarks>
136+
/// The default implementation excludes element types with aliases that start with <c>gridLayout_</c>, <c>gridRow_</c>, <c>gridEditor_</c> or <c>gridSettings_</c>.
137+
/// </remarks>
138+
protected virtual IEnumerable<IContentType> GetAllElementTypes()
139+
{
140+
static bool IsAllowedElementType(string alias) => !alias.StartsWith("gridLayout_") && !alias.StartsWith("gridRow_") && !alias.StartsWith("gridEditor_") && !alias.StartsWith("gridSettings_");
141+
142+
return _contentTypeService.GetAllElementTypes().Where(x => IsAllowedElementType(x.Alias));
143+
}
144+
145+
/// <summary>
146+
/// Determines whether the grid editor is the DocTypeGridEditor.
147+
/// </summary>
148+
/// <param name="gridEditor">The grid editor.</param>
149+
/// <returns>
150+
/// <c>true</c> if the grid editor is the DocTypeGridEditor; otherwise, <c>false</c>.
151+
/// </returns>
152+
protected static bool IsDocTypeGridEditor(IGridEditorConfig gridEditor)
153+
=> gridEditor.View?.Contains("doctypegrideditor", StringComparison.OrdinalIgnoreCase) is true;
154+
}

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

Lines changed: 8 additions & 8 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.preview.1.g294fdcd, 14.0.0)",
53+
"resolved": "13.2.0--rc1.preview.1.g294fdcd",
54+
"contentHash": "7KpMTPrF7XjSq+xfbvxbcEzvkHxGbR/K5tW/fNcenljEJpjZW2/8k+vsuYxvLxHr48o6efLv2RSbUb4MtAz+mw==",
5555
"dependencies": {
56-
"Umbraco.Cms.Web.BackOffice": "[13.0.0, 14.0.0)",
57-
"Umbraco.Deploy.Core": "[13.1.0, 14.0.0)"
56+
"Umbraco.Cms.Web.BackOffice": "[13.0.0, 14.0.0-0)",
57+
"Umbraco.Deploy.Core": "[13.2.0--rc1.preview.1.g294fdcd, 14.0.0-0)"
5858
}
5959
},
6060
"Umbraco.GitVersioning.Extensions": {
@@ -2243,10 +2243,10 @@
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.preview.1.g294fdcd",
2247+
"contentHash": "yXF5WWHRcDg+TiTupStw6yrzSJMbsgPE/TGT1CtPLGFEI/hkQS1JRXVyeqEiRTVKXrxbGU0hUJeOuUEeXkxsEA==",
22482248
"dependencies": {
2249-
"Umbraco.Cms.Core": "[13.0.0, 14.0.0)"
2249+
"Umbraco.Cms.Core": "[13.0.0, 14.0.0-0)"
22502250
}
22512251
}
22522252
}

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

Lines changed: 9 additions & 9 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.preview.1.g294fdcd, 14.0.0)",
61+
"resolved": "13.2.0--rc1.preview.1.g294fdcd",
62+
"contentHash": "7KpMTPrF7XjSq+xfbvxbcEzvkHxGbR/K5tW/fNcenljEJpjZW2/8k+vsuYxvLxHr48o6efLv2RSbUb4MtAz+mw==",
6363
"dependencies": {
64-
"Umbraco.Cms.Web.BackOffice": "[13.0.0, 14.0.0)",
65-
"Umbraco.Deploy.Core": "[13.1.0, 14.0.0)"
64+
"Umbraco.Cms.Web.BackOffice": "[13.0.0, 14.0.0-0)",
65+
"Umbraco.Deploy.Core": "[13.2.0--rc1.preview.1.g294fdcd, 14.0.0-0)"
6666
}
6767
},
6868
"Umbraco.GitVersioning.Extensions": {
@@ -2749,17 +2749,17 @@
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.preview.1.g294fdcd",
2753+
"contentHash": "yXF5WWHRcDg+TiTupStw6yrzSJMbsgPE/TGT1CtPLGFEI/hkQS1JRXVyeqEiRTVKXrxbGU0hUJeOuUEeXkxsEA==",
27542754
"dependencies": {
2755-
"Umbraco.Cms.Core": "[13.0.0, 14.0.0)"
2755+
"Umbraco.Cms.Core": "[13.0.0, 14.0.0-0)"
27562756
}
27572757
},
27582758
"umbraco.deploy.contrib": {
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.preview.1.g294fdcd, 14.0.0)"
27632763
}
27642764
},
27652765
"Moq": {

0 commit comments

Comments
 (0)