Skip to content

Commit 5750db5

Browse files
Merge branch 'v13/dev' into v14/dev
2 parents efec16f + 9a3ad52 commit 5750db5

File tree

5 files changed

+385
-3
lines changed

5 files changed

+385
-3
lines changed

Directory.Build.props

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
<!-- Package Validation -->
2828
<PropertyGroup>
2929
<GenerateCompatibilitySuppressionFile>false</GenerateCompatibilitySuppressionFile>
30-
<!-- TODO (V14): Re-enable when 14 is released. -->
31-
<EnablePackageValidation>false</EnablePackageValidation>
30+
<EnablePackageValidation>true</EnablePackageValidation>
3231
<PackageValidationBaselineVersion>14.0.0</PackageValidationBaselineVersion>
3332
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
3433
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>

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

0 commit comments

Comments
 (0)