From 0277ddc584c6efd3767d71ce4b567714f8e68e47 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 5 Mar 2018 14:59:23 +0100 Subject: [PATCH] DataType troubleshooting --- src/Umbraco.Core/Constants-PropertyEditors.cs | 2 +- .../Migrations/Upgrade/UmbracoPlan.cs | 4 +- ...factorDataType.cs => DataTypeMigration.cs} | 21 ++++++---- .../Migrations/Upgrade/V_8_0_0/SuperZero.cs | 10 +++-- .../Persistence/Factories/DataTypeFactory.cs | 4 +- .../PropertyEditors/ConfigurationEditor.cs | 33 +++++++++++++++ .../ConfigurationEditorOfTConfiguration.cs | 2 +- .../Serialization/FuzzyBooleanConverter.cs | 42 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 3 +- ...taTypeConfigurationFieldDisplayResolver.cs | 2 +- .../NestedContentConfiguration.cs | 3 -- 11 files changed, 105 insertions(+), 21 deletions(-) rename src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/{RefactorDataType.cs => DataTypeMigration.cs} (80%) create mode 100644 src/Umbraco.Core/Serialization/FuzzyBooleanConverter.cs diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index b2912eea16..56c174b68c 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -27,7 +27,7 @@ namespace Umbraco.Core /// /// Color Picker. /// - public const string ColorPicker = "Umbraco.ColorPickerAlias"; + public const string ColorPicker = "Umbraco.ColorPicker"; /// /// Content Picker. diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 21ce08de20..5e9e09e63b 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -84,7 +84,7 @@ namespace Umbraco.Core.Migrations.Upgrade .Chain("{3D18920C-E84D-405C-A06A-B7CEE52FE5DD}") .Chain("{FB0A5429-587E-4BD0-8A67-20F0E7E62FF7}") .Chain("{F0C42457-6A3B-4912-A7EA-F27ED85A2092}") - .Chain("{8640C9E4-A1C0-4C59-99BB-609B4E604981}") + .Chain("{8640C9E4-A1C0-4C59-99BB-609B4E604981}") .Chain("{DD1B99AF-8106-4E00-BAC7-A43003EA07F8}") .Chain("{CC1B1201-1328-443C-954A-E0BBB8CCC1B5}"); @@ -154,7 +154,7 @@ namespace Umbraco.Core.Migrations.Upgrade // 8.0.0 Chain("{6550C7E8-77B7-4DE3-9B58-E31C81CB9504}"); Chain("{E3388F73-89FA-45FE-A539-C7FACC8D63DD}"); - Chain("{82C4BA1D-7720-46B1-BBD7-07F3F73800E6}"); + Chain("{82C4BA1D-7720-46B1-BBD7-07F3F73800E6}"); Chain("{139F26D7-7E08-48E3-81D9-E50A21A72F67}"); Chain("{CC1B1201-1328-443C-954A-E0BBB8CCC1B5}"); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorDataType.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs similarity index 80% rename from src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorDataType.cs rename to src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs index b93b68b27d..202a4f0c0a 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/RefactorDataType.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/DataTypeMigration.cs @@ -1,20 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; using Newtonsoft.Json; using NPoco; using Umbraco.Core.Migrations.Install; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { - public class RefactorDataType : MigrationBase + public class DataTypeMigration : MigrationBase { - public RefactorDataType(IMigrationContext context) + public DataTypeMigration(IMigrationContext context) : base(context) { } @@ -38,6 +37,12 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 foreach (var x in DatabaseSchemaCreator.OrderedTables) Create.KeysAndIndexes(x.Value).Do(); + // renames + Database.Execute(Sql() + .Update(u => u.Set(x => x.EditorAlias, "Umbraco.ColorPicker")) + .Where(x => x.EditorAlias == "Umbraco.ColorPickerAlias")); + + // from preValues to configuration... var sql = Sql() .Select() .AndSelect(x => x.Alias, x => x.SortOrder, x => x.Value) @@ -64,9 +69,9 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 } else { - // fixme deal with null or empty aliases - // fixme deal with duplicate aliases - // in these cases, fallback to array? + // assuming we don't want to fall back to array + if (aliases.Length != group.Count() || aliases.Any(string.IsNullOrWhiteSpace)) + throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataType.NodeId} preValues: duplicate or null/empty alias."); // dictionary-base prevalues var values = group.ToDictionary(x => x.Alias, x => x.Value); diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs index fc85a70c6a..f9b1a959f8 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs @@ -11,8 +11,9 @@ var exists = Database.Fetch("select id from umbracoUser where id=-1;").Count > 0; if (exists) return; - Database.Execute("set identity_insert umbracoUser on;"); Database.Execute("update umbracoUser set userLogin = userLogin + '__' where userId=0"); + + Database.Execute("set identity_insert umbracoUser on;"); Database.Execute(@" insert into umbracoUser select -1, userDisabled, userNoConsole, userName, substring(userLogin, 1, len(userLogin) - 2), userPassword, passwordConfig, @@ -20,9 +21,12 @@ lastPasswordChangeDate, lastLoginDate, emailConfirmedDate, invitedDate, createDate, updateDate, avatar from umbracoUser where id=0;"); - Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;"); - Database.Execute("delete from umbracoUser where id=0;"); Database.Execute("set identity_insert umbracoUser off;"); + + Database.Execute("update umbracoUser2UserGroup set userId=-1 where userId=0;"); + Database.Execute("update umbracoNode set nodeUser=-1 where nodeUser=0;"); + Database.Execute("update uContentVersion set userId=-1 where userId=0;"); + Database.Execute("delete from umbracoUser where id=0;"); } } } diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index 66cbcb354f..226aa07483 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -1,5 +1,7 @@ using System; +using System.Reflection; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; @@ -51,7 +53,7 @@ namespace Umbraco.Core.Persistence.Factories EditorAlias = entity.EditorAlias, NodeId = entity.Id, DbType = entity.DatabaseType.ToString(), - Configuration = entity.Configuration == null ? null : JsonConvert.SerializeObject(entity.Configuration), + Configuration = entity.Configuration == null ? null : JsonConvert.SerializeObject(entity.Configuration, ConfigurationEditor.ConfigurationJsonSettings), NodeDto = BuildNodeDto(entity) }; diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index 159dc5ec7d..a4b6e52b3d 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Umbraco.Core.Serialization; namespace Umbraco.Core.PropertyEditors { @@ -99,5 +102,35 @@ namespace Umbraco.Core.PropertyEditors /// public virtual Dictionary ToValueEditor(object configuration) => ToConfigurationEditor(configuration); + + /// + /// Gets the custom json serializer settings for configurations. + /// + public static JsonSerializerSettings ConfigurationJsonSettings { get; } = new JsonSerializerSettings + { + ContractResolver = new ConfigurationCustomContractResolver(), + Converters = new List(new[]{new FuzzyBooleanConverter()}) + }; + + private class ConfigurationCustomContractResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + // base.CreateProperty deals with [JsonProperty("name")] + var property = base.CreateProperty(member, memberSerialization); + + // override with our custom attribute, if any + var attribute = member.GetCustomAttribute(); + if (attribute != null) property.PropertyName = attribute.Key; + + // for value types, + // don't try to deserialize nulls (in legacy json) + // no impact on serialization (value cannot be null) + if (member is PropertyInfo propertyInfo && propertyInfo.PropertyType.IsValueType) + property.NullValueHandling = NullValueHandling.Ignore; + + return property; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 96965f731a..bfa32bd06e 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -104,7 +104,7 @@ namespace Umbraco.Core.PropertyEditors try { if (string.IsNullOrWhiteSpace(configuration)) return new TConfiguration(); - return JsonConvert.DeserializeObject(configuration); + return JsonConvert.DeserializeObject(configuration, ConfigurationJsonSettings); } catch (Exception e) { diff --git a/src/Umbraco.Core/Serialization/FuzzyBooleanConverter.cs b/src/Umbraco.Core/Serialization/FuzzyBooleanConverter.cs new file mode 100644 index 0000000000..db94f41845 --- /dev/null +++ b/src/Umbraco.Core/Serialization/FuzzyBooleanConverter.cs @@ -0,0 +1,42 @@ +using System; +using Newtonsoft.Json; + +namespace Umbraco.Core.Serialization +{ + public class FuzzyBooleanConverter : JsonConverter + { + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotSupportedException(); + } + + public override bool CanRead => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var value = reader.Value; + if (value is bool) return value; + + switch (value.ToString().ToLower().Trim()) + { + case "true": + case "yes": + case "y": + case "1": + return true; + + case "false": + case "no": + case "n": + case "0": + return false; + } + + return new JsonSerializer().Deserialize(reader, objectType); + } + + public override bool CanConvert(Type objectType) => objectType == typeof(bool); + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e1f4a31be3..154c0a273a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -314,7 +314,7 @@ - + @@ -1294,6 +1294,7 @@ + diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs index ba76b96778..3e70f36839 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeConfigurationFieldDisplayResolver.cs @@ -36,7 +36,7 @@ namespace Umbraco.Web.Models.Mapping /// public IEnumerable Resolve(IDataType dataType) { - if (!string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var e) || !(e is DataEditor editor)) + if (string.IsNullOrWhiteSpace(dataType.EditorAlias) || !Current.PropertyEditors.TryGet(dataType.EditorAlias, out var e) || !(e is DataEditor editor)) throw new InvalidOperationException($"Could not find a property editor with alias \"{dataType.EditorAlias}\"."); var configuration = dataType.Configuration; diff --git a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs index b1ea7c633a..44648ee859 100644 --- a/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs +++ b/src/Umbraco.Web/PropertyEditors/NestedContentConfiguration.cs @@ -18,15 +18,12 @@ namespace Umbraco.Web.PropertyEditors public int? MaxItems { get; set; } [ConfigurationField("confirmDeletes", "Confirm Deletes", "boolean", Description = "Set whether item deletions should require confirming.")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // do not try to map a null value to a boolean public bool ConfirmDeletes { get; set; } = true; [ConfigurationField("showIcons", "Show Icons", "boolean", Description = "Set whether to show the items doc type icon in the list.")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // do not try to map a null value to a boolean public bool ShowIcons { get; set; } = true; [ConfigurationField("hideLabel", "Hide Label", "boolean", Description = "Set whether to hide the editor label and have the list take up the full width of the editor window.")] - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] // do not try to map a null value to a boolean public bool HideLabel { get; set; } public class ContentType