From f78e4fcdf67992baf7628c2053bb9347e73ca2be Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Tue, 7 Apr 2020 11:55:45 -0700 Subject: [PATCH] Add migrations for new block editor --- .../Migrations/Upgrade/UmbracoPlan.cs | 5 + .../Upgrade/V_8_7_0/ColorPickerPreValues.cs | 106 ++++++++++++++++++ .../Upgrade/V_8_7_0/ConvertToElements.cs | 92 +++++++++++++++ .../V_8_7_0/StackedContentToBlockList.cs | 74 ++++++++++-- src/Umbraco.Core/Umbraco.Core.csproj | 3 + 5 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ColorPickerPreValues.cs create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ConvertToElements.cs diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index f65a60197f..1d30efa573 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -194,6 +194,11 @@ namespace Umbraco.Core.Migrations.Upgrade To("{a78e3369-8ea3-40ec-ad3f-5f76929d2b20}"); + + // to 8.7.0... + To("{DFA35FA2-BFBB-433F-84E5-BD75940CDDF6}"); + To("{711AC937-B11C-47AC-8D4A-5B8868A3C2C6}"); + To("{DA434576-3DEF-46D7-942A-CE34D7F7FB8A}"); //FINAL } } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ColorPickerPreValues.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ColorPickerPreValues.cs new file mode 100644 index 0000000000..6e959e86d7 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ColorPickerPreValues.cs @@ -0,0 +1,106 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; +using System.Text.RegularExpressions; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 +{ + public class ColorPickerPreValues : MigrationBase + { + private static readonly Regex OldPreValuesPattern1 = new Regex("\\s*{(\\s*\"[0-9]+\"\\s*:\\s*\"[0-9a-fA-F]+\"\\s*,)*\\s*\"useLabel\"\\s*:\\s*\"[01]\"\\s*}\\s*", RegexOptions.Compiled); + private static readonly Regex OldPreValuesPattern2 = new Regex("\\s*{(\\s*\"[0-9]+\"\\s*:\\s*{\\s*\"value\"\\s*:\\s*\"[0-9a-fA-F]+\"\\s*(,\\s*\"label\"\\s*:\\s*\"[^\"]*\"\\s*)?(,\\s*\"sortOrder\"\\s*:\\s*[0-9]+\\s*)?}\\s*,)*\\s*\"useLabel\"\\s*:\\s*\"[01]\"\\s*}\\s*", RegexOptions.Compiled); + + public ColorPickerPreValues(IMigrationContext context) : base(context) + { + } + + public override void Migrate() + { + var sql = Sql() + .Select() + .From() + .Where(d => d.EditorAlias == Constants.PropertyEditors.Aliases.ColorPicker); + + var dtos = Database.Fetch(sql); + + foreach (var dto in dtos) + { + if (dto.Configuration.IsNullOrWhiteSpace()) continue; + + if (OldPreValuesPattern1.IsMatch(dto.Configuration)) ConvertPreValues(dto, ConvertStyle1); + else if (OldPreValuesPattern2.IsMatch(dto.Configuration)) ConvertPreValues(dto, ConvertStyle2); + else continue; + + Database.Update(dto); + } + } + + private void ConvertPreValues(DataTypeDto dto, Func converter) + { + var obj = JObject.Parse(dto.Configuration); + var config = new ColorPickerConfiguration(); + var id = 0; + + foreach (var prop in obj.Properties()) + { + if (prop.Name.ToLowerInvariant() == "uselabel") + { + config.UseLabel = prop.Value.ToString() == "1"; + } + else + { + id++; + config.Items.Add(new ValueListConfiguration.ValueListItem + { + Id = id, + Value = JsonConvert.SerializeObject(converter(id, prop.Value)) + }); + } + } + + dto.Configuration = JsonConvert.SerializeObject(config); + } + + private ItemValue ConvertStyle1(int index, JToken token) + { + var value = token.ToString(); + return new ItemValue + { + Color = value, + Label = value, + SortOrder = index + }; + } + + private ItemValue ConvertStyle2(int index, JToken token) + { + var obj = (JObject)token; + var value = obj["value"].ToString(); + var label = obj["label"]?.ToString(); + var order = obj["sortOrder"]?.ToString(); + + return new ItemValue + { + Color = value, + Label = label.IsNullOrWhiteSpace() ? value : label, + SortOrder = int.TryParse(order, out var o) ? o : index + }; + } + + private class ItemValue + { + [JsonProperty("value")] + public string Color { get; set; } + + [JsonProperty("label")] + public string Label { get; set; } + + [JsonProperty("sortOrder")] + public int SortOrder { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ConvertToElements.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ConvertToElements.cs new file mode 100644 index 0000000000..e42453a3fe --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/ConvertToElements.cs @@ -0,0 +1,92 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +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(); + var docTypeMap = new Dictionary(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.NestedContent, 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(Sql() + .Select() + .From() + .WhereIn(c => c.ChildId, elementTypeIds) + ).Select(c => c.ParentId); + + elementTypeIds = elementTypeIds.Union(parentElementTypeIds).ToList(); + + // Convert all those document types to element type + foreach (var docType in docTypes) + { + if (!elementTypeIds.Contains(docType.NodeId)) continue; + + docType.IsElement = true; + Database.Update(docType); + } + } + + private List GetDataTypes(params string[] aliases) + { + var sql = Sql() + .Select() + .From() + .WhereIn(d => d.EditorAlias, aliases); + + return Database.Fetch(sql); + } + + private IEnumerable GetDocTypeIds(string configuration, Dictionary idMap) + { + if (configuration.IsNullOrWhiteSpace() || configuration[0] != '{') return Enumerable.Empty(); + + var obj = JObject.Parse(configuration); + if (obj["contentTypes"] is JArray ncArr) + { + var arr = ncArr.ToObject(); + return arr.Select(i => idMap.TryGetValue(i.Alias, out var id) ? id : 0).Where(i => i != 0); + } + else if (obj["blocks"] is JArray blArr) + { + var arr = blArr.ToObject(); + return arr.Select(i => idMap.TryGetValue(i.Alias, out var id) ? id : 0).Where(i => i != 0); + } + + return Enumerable.Empty(); + } + + public class ContentType + { + [JsonProperty("ncAlias")] + public string Alias { get; set; } + } + + public class BlockConfiguration + { + [JsonProperty("contentTypeAlias")] + public string Alias { get; set; } + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/StackedContentToBlockList.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/StackedContentToBlockList.cs index 076b4f205c..43ee85c2ea 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/StackedContentToBlockList.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_7_0/StackedContentToBlockList.cs @@ -20,6 +20,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 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), @@ -38,7 +39,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 return Database.Fetch(sql); } - private Dictionary GetKnownDocumentTypes() + private Dictionary GetKnownDocumentTypes() { var sql = Sql() .Select(r => r.Select(x => x.NodeDto)) @@ -47,12 +48,40 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 .On(c => c.NodeId, n => n.NodeId); var types = Database.Fetch(sql); - var map = new Dictionary(types.Count); - types.ForEach(t => map[t.NodeDto.UniqueId] = t.Alias); - return map; + var typeMap = new Dictionary(types.Count); + types.ForEach(t => typeMap[t.NodeId] = t); + + sql = Sql() + .Select() + .From(); + var joins = Database.Fetch(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(r => r.Select(x => x.DataTypeDto)) + .From() + .InnerJoin() + .On(c => c.DataTypeId, n => n.NodeId) + .Where(d => d.EditorAlias == Constants.PropertyEditors.Aliases.NestedContent); + var props = Database.Fetch(sql); + // Get all nested content property aliases by content type ID + var propLk = props.ToLookup(p => p.ContentTypeId, p => p.Alias); + + var knownMap = new Dictionary(types.Count); + types.ForEach(t => knownMap[t.NodeDto.UniqueId] = new KnownContentType + { + Alias = t.Alias, + NestedContentProperties = propLk[t.NodeId].Union(joinLk[t.NodeId].SelectMany(r => propLk[r])).ToArray() + }); + return knownMap; } - private bool Migrate(IEnumerable dataTypesToMigrate, Dictionary knownDocumentTypes) + private bool Migrate(IEnumerable dataTypesToMigrate, Dictionary knownDocumentTypes) { var refreshCache = false; @@ -73,14 +102,14 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 return refreshCache; } - private BlockListConfiguration UpdateConfiguration(DataTypeDto dataType, Dictionary knownDocumentTypes) + private BlockListConfiguration UpdateConfiguration(DataTypeDto dataType, Dictionary knownDocumentTypes) { var old = JsonConvert.DeserializeObject(dataType.Configuration); var config = new BlockListConfiguration { Blocks = old.ContentTypes?.Select(t => new BlockListConfiguration.BlockConfiguration { - Alias = knownDocumentTypes[t.IcContentTypeGuid], + Alias = knownDocumentTypes[t.IcContentTypeGuid].Alias, Label = t.NameTemplate }).ToArray(), UseInlineEditingAsDefault = old.SingleItemMode == "1" || old.SingleItemMode == bool.TrueString @@ -96,7 +125,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 return config; } - private void UpdatePropertyData(DataTypeDto dataType, BlockListConfiguration config, Dictionary knownDocumentTypes) + private void UpdatePropertyData(DataTypeDto dataType, BlockListConfiguration config, Dictionary knownDocumentTypes) { // get property data dtos var propertyDataDtos = Database.Fetch(Sql() @@ -115,7 +144,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 } - private bool UpdatePropertyDataDto(PropertyDataDto dto, BlockListConfiguration config, Dictionary knownDocumentTypes) + private bool UpdatePropertyDataDto(PropertyDataDto dto, BlockListConfiguration config, Dictionary knownDocumentTypes) { var model = new SimpleModel(); @@ -202,18 +231,33 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 [JsonProperty("data")] public List Data { get; } = new List(); - public void AddDataItem(JObject obj, Dictionary knownDocumentTypes) + public void AddDataItem(JObject obj, Dictionary knownDocumentTypes) { if (!Guid.TryParse(obj["key"].ToString(), out var key)) throw new ArgumentException("Could not find a valid key in the data item"); if (!Guid.TryParse(obj["icContentTypeGuid"].ToString(), out var ctGuid)) throw new ArgumentException("Could not find a valid content type GUID in the data item"); - if (!knownDocumentTypes.TryGetValue(ctGuid, out var ctAlias)) throw new ArgumentException($"Unknown content type GUID '{ctGuid}'"); + if (!knownDocumentTypes.TryGetValue(ctGuid, out var ct)) throw new ArgumentException($"Unknown content type GUID '{ctGuid}'"); obj.Remove("key"); obj.Remove("icContentTypeGuid"); var udi = new GuidUdi(Constants.UdiEntityType.Element, key).ToString(); obj["udi"] = udi; - obj["contentTypeAlias"] = ctAlias; + obj["contentTypeAlias"] = ct.Alias; + + if (ct.NestedContentProperties != null && ct.NestedContentProperties.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.NestedContentProperties) + { + var val = obj[prop]; + var value = val?.ToString(); + if (val != null && val.Type == JTokenType.String && !value.IsNullOrWhiteSpace() && value[0] == '[') + obj[prop] = JArray.Parse(value); + else if (val.Type != JTokenType.Array) + obj[prop] = new JArray(); + } + } Data.Add(obj); Layout.Refs.Add(new SimpleLayout.SimpleLayoutRef { Udi = udi }); @@ -231,5 +275,11 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_7_0 } } } + + private class KnownContentType + { + public string Alias { get; set; } + public string[] NestedContentProperties { get; set; } + } } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 18c51fd905..1ae5267b6a 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -131,6 +131,9 @@ + + +