manually merge in benj PR
This commit is contained in:
@@ -7,7 +7,6 @@ using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_0_1;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_1_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_6_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_7_0;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade
|
||||
{
|
||||
@@ -194,10 +193,7 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
// to 8.7.0...
|
||||
To<MissingDictionaryIndex>("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}");
|
||||
|
||||
// to 8.7.0...
|
||||
To<StackedContentToBlockList>("{DFA35FA2-BFBB-433F-84E5-BD75940CDDF6}");
|
||||
To<ConvertToElements>("{DA434576-3DEF-46D7-942A-CE34D7F7FB8A}");
|
||||
|
||||
//FINAL
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0
|
||||
{
|
||||
public class ConvertToElements : MigrationBase
|
||||
{
|
||||
public ConvertToElements(IMigrationContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// Get all document type IDs by alias
|
||||
var docTypes = Database.Fetch<ContentTypeDto>();
|
||||
var docTypeMap = new Dictionary<string, int>(docTypes.Count);
|
||||
docTypes.ForEach(d => docTypeMap[d.Alias] = d.NodeId);
|
||||
|
||||
// Find all Nested Content or Block List data types
|
||||
var dataTypes = GetDataTypes(Constants.PropertyEditors.Aliases.BlockList);
|
||||
|
||||
// Find all document types listed in each
|
||||
var elementTypeIds = dataTypes.SelectMany(d => GetDocTypeIds(d.Configuration, docTypeMap)).ToList();
|
||||
|
||||
// Find all compositions those document types use
|
||||
var parentElementTypeIds = Database.Fetch<ContentType2ContentTypeDto>(Sql()
|
||||
.Select<ContentType2ContentTypeDto>()
|
||||
.From<ContentType2ContentTypeDto>()
|
||||
.WhereIn<ContentType2ContentTypeDto>(c => c.ChildId, elementTypeIds)
|
||||
).Select(c => c.ParentId);
|
||||
|
||||
elementTypeIds = elementTypeIds.Union(parentElementTypeIds).ToList();
|
||||
|
||||
// Convert all those document types to element type
|
||||
// TODO: We need to wait on an update from @benjaminc to make this 'safe'
|
||||
// see https://github.com/umbraco/Umbraco-CMS/pull/7910#discussion_r409927495
|
||||
foreach (var docType in docTypes)
|
||||
{
|
||||
if (!elementTypeIds.Contains(docType.NodeId)) continue;
|
||||
|
||||
docType.IsElement = true;
|
||||
Database.Update(docType);
|
||||
}
|
||||
}
|
||||
|
||||
private List<DataTypeDto> GetDataTypes(params string[] aliases)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.WhereIn<DataTypeDto>(d => d.EditorAlias, aliases);
|
||||
|
||||
return Database.Fetch<DataTypeDto>(sql);
|
||||
}
|
||||
|
||||
private IEnumerable<int> GetDocTypeIds(string configuration, Dictionary<string, int> idMap)
|
||||
{
|
||||
if (configuration.IsNullOrWhiteSpace() || configuration[0] != '{') return Enumerable.Empty<int>();
|
||||
|
||||
var obj = JObject.Parse(configuration);
|
||||
if (obj["blocks"] is JArray blArr)
|
||||
{
|
||||
var arr = blArr.ToObject<BlockConfiguration[]>();
|
||||
return arr.Select(i => idMap.TryGetValue(i.Alias, out var id) ? id : 0).Where(i => i != 0);
|
||||
}
|
||||
|
||||
return Enumerable.Empty<int>();
|
||||
}
|
||||
|
||||
public class BlockConfiguration
|
||||
{
|
||||
[JsonProperty("contentTypeAlias")]
|
||||
public string Alias { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations.PostMigrations;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0
|
||||
{
|
||||
public class StackedContentToBlockList : MigrationBase
|
||||
{
|
||||
public StackedContentToBlockList(IMigrationContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// Convert all Stacked Content properties to Block List properties, both in the data types and in the property data
|
||||
var refreshCache = Migrate(GetDataTypes("Our.Umbraco.StackedContent"), GetKnownDocumentTypes());
|
||||
|
||||
// if some data types have been updated directly in the database (editing DataTypeDto and/or PropertyDataDto),
|
||||
// bypassing the services, then we need to rebuild the cache entirely, including the umbracoContentNu table
|
||||
if (refreshCache)
|
||||
Context.AddPostMigration<RebuildPublishedSnapshot>();
|
||||
}
|
||||
|
||||
private List<DataTypeDto> GetDataTypes(string alias)
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select<DataTypeDto>()
|
||||
.From<DataTypeDto>()
|
||||
.Where<DataTypeDto>(d => d.EditorAlias == alias);
|
||||
|
||||
return Database.Fetch<DataTypeDto>(sql);
|
||||
}
|
||||
|
||||
private Dictionary<Guid, KnownContentType> GetKnownDocumentTypes()
|
||||
{
|
||||
var sql = Sql()
|
||||
.Select<ContentTypeDto>(r => r.Select(x => x.NodeDto))
|
||||
.From<ContentTypeDto>()
|
||||
.InnerJoin<NodeDto>()
|
||||
.On<ContentTypeDto, NodeDto>(c => c.NodeId, n => n.NodeId);
|
||||
|
||||
var types = Database.Fetch<ContentTypeDto>(sql);
|
||||
var typeMap = new Dictionary<int, ContentTypeDto>(types.Count);
|
||||
types.ForEach(t => typeMap[t.NodeId] = t);
|
||||
|
||||
sql = Sql()
|
||||
.Select<ContentType2ContentTypeDto>()
|
||||
.From<ContentType2ContentTypeDto>();
|
||||
var joins = Database.Fetch<ContentType2ContentTypeDto>(sql);
|
||||
// Find all relationships between types, either inherited or composited
|
||||
var joinLk = joins
|
||||
.Union(types
|
||||
.Where(t => typeMap.ContainsKey(t.NodeDto.ParentId))
|
||||
.Select(t => new ContentType2ContentTypeDto { ChildId = t.NodeId, ParentId = t.NodeDto.ParentId }))
|
||||
.ToLookup(j => j.ChildId, j => j.ParentId);
|
||||
|
||||
sql = Sql()
|
||||
.Select<PropertyTypeDto>(r => r.Select(x => x.DataTypeDto))
|
||||
.From<PropertyTypeDto>()
|
||||
.InnerJoin<DataTypeDto>()
|
||||
.On<PropertyTypeDto, DataTypeDto>(c => c.DataTypeId, n => n.NodeId)
|
||||
.WhereIn<DataTypeDto>(d => d.EditorAlias, new[] { Constants.PropertyEditors.Aliases.NestedContent, Constants.PropertyEditors.Aliases.ColorPicker });
|
||||
var props = Database.Fetch<PropertyTypeDto>(sql);
|
||||
// Get all nested content and color picker property aliases by content type ID
|
||||
var propLk = props.ToLookup(p => p.ContentTypeId, p => p.Alias);
|
||||
|
||||
var knownMap = new Dictionary<Guid, KnownContentType>(types.Count);
|
||||
types.ForEach(t => knownMap[t.NodeDto.UniqueId] = new KnownContentType(t.Alias, t.NodeDto.UniqueId, propLk[t.NodeId].Union(joinLk[t.NodeId].SelectMany(r => propLk[r])).ToArray()));
|
||||
return knownMap;
|
||||
}
|
||||
|
||||
private bool Migrate(IEnumerable<DataTypeDto> dataTypesToMigrate, Dictionary<Guid, KnownContentType> knownDocumentTypes)
|
||||
{
|
||||
var refreshCache = false;
|
||||
|
||||
foreach (var dataType in dataTypesToMigrate)
|
||||
{
|
||||
if (!dataType.Configuration.IsNullOrWhiteSpace())
|
||||
{
|
||||
var config = UpdateConfiguration(dataType, knownDocumentTypes);
|
||||
|
||||
if (config.Blocks.Length > 0) UpdatePropertyData(dataType, config, knownDocumentTypes);
|
||||
}
|
||||
|
||||
UpdateDataType(dataType);
|
||||
|
||||
refreshCache = true;
|
||||
}
|
||||
|
||||
return refreshCache;
|
||||
}
|
||||
|
||||
private BlockListConfiguration UpdateConfiguration(DataTypeDto dataType, Dictionary<Guid, KnownContentType> knownDocumentTypes)
|
||||
{
|
||||
var old = JsonConvert.DeserializeObject<StackedContentConfiguration>(dataType.Configuration);
|
||||
var config = new BlockListConfiguration
|
||||
{
|
||||
Blocks = old.ContentTypes?.Select(t => new BlockListConfiguration.BlockConfiguration
|
||||
{
|
||||
Key = knownDocumentTypes.TryGetValue(t.IcContentTypeGuid, out var ct) ? ct.Key : Guid.Empty,
|
||||
Label = t.NameTemplate
|
||||
}).Where(c => c.Key != null).ToArray(),
|
||||
UseInlineEditingAsDefault = old.SingleItemMode == "1" || old.SingleItemMode == bool.TrueString
|
||||
};
|
||||
|
||||
if (int.TryParse(old.MaxItems, out var max) && max > 0)
|
||||
{
|
||||
config.ValidationLimit = new BlockListConfiguration.NumberRange { Max = max };
|
||||
}
|
||||
|
||||
dataType.Configuration = ConfigurationEditor.ToDatabase(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
private void UpdatePropertyData(DataTypeDto dataType, BlockListConfiguration config, Dictionary<Guid, KnownContentType> knownDocumentTypes)
|
||||
{
|
||||
// get property data dtos
|
||||
var propertyDataDtos = Database.Fetch<PropertyDataDto>(Sql()
|
||||
.Select<PropertyDataDto>()
|
||||
.From<PropertyDataDto>()
|
||||
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((pt, pd) => pt.Id == pd.PropertyTypeId)
|
||||
.InnerJoin<DataTypeDto>().On<DataTypeDto, PropertyTypeDto>((dt, pt) => dt.NodeId == pt.DataTypeId)
|
||||
.Where<PropertyTypeDto>(x => x.DataTypeId == dataType.NodeId));
|
||||
|
||||
// update dtos
|
||||
var updatedDtos = propertyDataDtos.Where(x => UpdatePropertyDataDto(x, config, knownDocumentTypes));
|
||||
|
||||
// persist changes
|
||||
foreach (var propertyDataDto in updatedDtos)
|
||||
Database.Update(propertyDataDto);
|
||||
}
|
||||
|
||||
|
||||
private bool UpdatePropertyDataDto(PropertyDataDto dto, BlockListConfiguration config, Dictionary<Guid, KnownContentType> knownDocumentTypes)
|
||||
{
|
||||
var model = new SimpleModel();
|
||||
|
||||
if (dto != null && !dto.TextValue.IsNullOrWhiteSpace() && dto.TextValue[0] == '[')
|
||||
{
|
||||
var scObjs = JsonConvert.DeserializeObject<JObject[]>(dto.TextValue);
|
||||
foreach (var obj in scObjs) model.AddDataItem(obj, knownDocumentTypes);
|
||||
}
|
||||
|
||||
dto.TextValue = JsonConvert.SerializeObject(model);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateDataType(DataTypeDto dataType)
|
||||
{
|
||||
dataType.DbType = ValueStorageType.Ntext.ToString();
|
||||
dataType.EditorAlias = Constants.PropertyEditors.Aliases.BlockList;
|
||||
|
||||
Database.Update(dataType);
|
||||
}
|
||||
|
||||
private class BlockListConfiguration
|
||||
{
|
||||
|
||||
[JsonProperty("blocks")]
|
||||
public BlockConfiguration[] Blocks { get; set; }
|
||||
|
||||
|
||||
[JsonProperty("validationLimit")]
|
||||
public NumberRange ValidationLimit { get; set; } = new NumberRange();
|
||||
|
||||
public class NumberRange
|
||||
{
|
||||
[JsonProperty("min")]
|
||||
public int? Min { get; set; }
|
||||
|
||||
[JsonProperty("max")]
|
||||
public int? Max { get; set; }
|
||||
}
|
||||
|
||||
public class BlockConfiguration
|
||||
{
|
||||
[JsonProperty("backgroundColor")]
|
||||
public string BackgroundColor { get; set; }
|
||||
|
||||
[JsonProperty("iconColor")]
|
||||
public string IconColor { get; set; }
|
||||
|
||||
[JsonProperty("thumbnail")]
|
||||
public string Thumbnail { get; set; }
|
||||
|
||||
[JsonProperty("contentTypeKey")]
|
||||
public Guid Key { get; set; }
|
||||
|
||||
[JsonProperty("settingsElementTypeKey")]
|
||||
public string settingsElementTypeKey { get; set; }
|
||||
|
||||
[JsonProperty("view")]
|
||||
public string View { get; set; }
|
||||
|
||||
[JsonProperty("label")]
|
||||
public string Label { get; set; }
|
||||
|
||||
[JsonProperty("editorSize")]
|
||||
public string EditorSize { get; set; }
|
||||
}
|
||||
|
||||
[JsonProperty("useInlineEditingAsDefault")]
|
||||
public bool UseInlineEditingAsDefault { get; set; }
|
||||
|
||||
[JsonProperty("maxPropertyWidth")]
|
||||
public string MaxPropertyWidth { get; set; }
|
||||
}
|
||||
|
||||
private class StackedContentConfiguration
|
||||
{
|
||||
|
||||
public class StackedContentType
|
||||
{
|
||||
public Guid IcContentTypeGuid { get; set; }
|
||||
public string NameTemplate { get; set; }
|
||||
}
|
||||
|
||||
public StackedContentType[] ContentTypes { get; set; }
|
||||
public string EnableCopy { get; set; }
|
||||
public string EnableFilter { get; set; }
|
||||
public string EnablePreview { get; set; }
|
||||
public string HideLabel { get; set; }
|
||||
public string MaxItems { get; set; }
|
||||
public string SingleItemMode { get; set; }
|
||||
}
|
||||
|
||||
private class SimpleModel
|
||||
{
|
||||
[JsonProperty("layout")]
|
||||
public SimpleLayout Layout { get; } = new SimpleLayout();
|
||||
[JsonProperty("data")]
|
||||
public List<JObject> Data { get; } = new List<JObject>();
|
||||
|
||||
public void AddDataItem(JObject obj, Dictionary<Guid, KnownContentType> knownDocumentTypes)
|
||||
{
|
||||
if (!Guid.TryParse(obj["key"].ToString(), out var key)) key = Guid.NewGuid();
|
||||
if (!Guid.TryParse(obj["icContentTypeGuid"].ToString(), out var ctGuid)) ctGuid = Guid.Empty;
|
||||
if (!knownDocumentTypes.TryGetValue(ctGuid, out var ct)) ct = new KnownContentType(null, ctGuid, null);
|
||||
|
||||
obj.Remove("key");
|
||||
obj.Remove("icContentTypeGuid");
|
||||
|
||||
var udi = new GuidUdi(Constants.UdiEntityType.Element, key).ToString();
|
||||
obj["udi"] = udi;
|
||||
obj["contentTypeKey"] = ct.Key;
|
||||
|
||||
if (ct.StringToRawProperties != null && ct.StringToRawProperties.Length > 0)
|
||||
{
|
||||
// Nested content inside a stacked content item used to be stored as a deserialized string of the JSON array
|
||||
// Now we store the content as the raw JSON array, so we need to convert from the string form to the array
|
||||
foreach (var prop in ct.StringToRawProperties)
|
||||
{
|
||||
var val = obj[prop];
|
||||
var value = val?.ToString();
|
||||
if (val != null && val.Type == JTokenType.String && !value.IsNullOrWhiteSpace())
|
||||
obj[prop] = JsonConvert.DeserializeObject<JToken>(value);
|
||||
}
|
||||
}
|
||||
|
||||
Data.Add(obj);
|
||||
Layout.Refs.Add(new SimpleLayout.SimpleLayoutRef { Udi = udi });
|
||||
}
|
||||
|
||||
public class SimpleLayout
|
||||
{
|
||||
[JsonProperty(Constants.PropertyEditors.Aliases.BlockList)]
|
||||
public List<SimpleLayoutRef> Refs { get; } = new List<SimpleLayoutRef>();
|
||||
|
||||
public class SimpleLayoutRef
|
||||
{
|
||||
[JsonProperty("udi")]
|
||||
public string Udi { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class KnownContentType
|
||||
{
|
||||
public KnownContentType(string alias, Guid key, string[] stringToRawProperties)
|
||||
{
|
||||
Alias = alias ?? throw new ArgumentNullException(nameof(alias));
|
||||
Key = key;
|
||||
StringToRawProperties = stringToRawProperties ?? throw new ArgumentNullException(nameof(stringToRawProperties));
|
||||
}
|
||||
|
||||
public string Alias { get; }
|
||||
public Guid Key { get; }
|
||||
public string[] StringToRawProperties { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,8 +132,6 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\ContentTypeDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyDataDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyTypeDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_7_0\ConvertToElements.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_7_0\StackedContentToBlockList.cs" />
|
||||
<Compile Include="Models\Blocks\BlockEditorDataConverter.cs" />
|
||||
<Compile Include="Models\Blocks\BlockEditorData.cs" />
|
||||
<Compile Include="Models\Blocks\BlockListLayoutItem.cs" />
|
||||
|
||||
Reference in New Issue
Block a user