diff --git a/src/Umbraco.Core/Models/DataType.cs b/src/Umbraco.Core/Models/DataType.cs index d26901ae94..0c32483fd3 100644 --- a/src/Umbraco.Core/Models/DataType.cs +++ b/src/Umbraco.Core/Models/DataType.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; +using Newtonsoft.Json; using Umbraco.Core.Models.Entities; using Umbraco.Core.PropertyEditors; @@ -16,49 +17,54 @@ namespace Umbraco.Core.Models { private static PropertySelectors _selectors; - private string _editorAlias; + private PropertyEditor _editor; private ValueStorageType _databaseType; private object _configuration; private bool _hasConfiguration; private string _configurationJson; - private PropertyEditor _editor; /// /// Initializes a new instance of the class. /// - public DataType(int parentId, string propertyEditorAlias) + public DataType(PropertyEditor editor, int parentId = -1) { + _editor = editor ?? throw new ArgumentNullException(nameof(editor)); ParentId = parentId; - _editorAlias = propertyEditorAlias; - } - - /// - /// Initializes a new instance of the class. - /// - public DataType(string propertyEditorAlias) - { - ParentId = -1; - _editorAlias = propertyEditorAlias; } private static PropertySelectors Selectors => _selectors ?? (_selectors = new PropertySelectors()); - // ReSharper disable once ClassNeverInstantiated.Local private class PropertySelectors { - public readonly PropertyInfo EditorAlias = ExpressionHelper.GetPropertyInfo(x => x.EditorAlias); + public readonly PropertyInfo Editor = ExpressionHelper.GetPropertyInfo(x => x.Editor); public readonly PropertyInfo DatabaseType = ExpressionHelper.GetPropertyInfo(x => x.DatabaseType); public readonly PropertyInfo Configuration = ExpressionHelper.GetPropertyInfo(x => x.Configuration); } /// - [DataMember] - public string EditorAlias + [IgnoreDataMember] + public PropertyEditor Editor { - get => _editorAlias; - set => SetPropertyValueAndDetectChanges(value, ref _editorAlias, Selectors.EditorAlias); + get => _editor; + set + { + // ignore if no change + if (_editor.Alias == value.Alias) return; + OnPropertyChanged(Selectors.Editor); + + // try to map the existing configuration to the new configuration + // simulate saving to db and reloading (ie go via json) + var configuration = Configuration; + var json = JsonConvert.SerializeObject(configuration); + _editor = value; + Configuration = _editor.ConfigurationEditor.FromDatabase(json); + } } + /// + [DataMember] + public string EditorAlias => _editor.Alias; + /// [DataMember] public ValueStorageType DatabaseType @@ -78,47 +84,68 @@ namespace Umbraco.Core.Models // else, use the editor to get the configuration object if (_hasConfiguration) return _configuration; - if (_editor == null) return null; _configuration = _editor.ConfigurationEditor.FromDatabase(_configurationJson); _hasConfiguration = true; _configurationJson = null; - _editor = null; + return _configuration; } set { - if (value != null) - { - // fixme - do it HERE - // fixme - BUT then we have a problem, what if it's changed? - // can't we treat configurations as plain immutable objects?! - // also it means that we just cannot work with dictionaries? - if (value is IConfigureValueType valueTypeConfiguration) - DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType); - if (value is IDictionary dictionaryConfiguration - && dictionaryConfiguration.TryGetValue("", out var valueTypeObject) - && valueTypeObject is string valueTypeString) - DatabaseType = ValueTypes.ToStorageType(valueTypeString); - } + if (value == null) + throw new ArgumentNullException(nameof(value)); - // fixme detect changes? if it's the same object? need a special comparer! - SetPropertyValueAndDetectChanges(value, ref _configuration, Selectors.Configuration); + // we don't support re-assigning the same object + // configurations are kinda non-mutable, mainly because detecting changes would be a pain + if (_configuration == value) // reference comparison + throw new ArgumentException("Configurations are kinda non-mutable. Do not reassign the same object.", nameof(value)); + + // validate configuration type + if (!_editor.ConfigurationEditor.IsConfiguration(value)) + throw new ArgumentException($"Value of type {value.GetType().Name} cannot be a configuration for editor {_editor.Alias}, expecting.", nameof(value)); + + // extract database type from configuration object, if appropriate + if (value is IConfigureValueType valueTypeConfiguration) + DatabaseType = ValueTypes.ToStorageType(valueTypeConfiguration.ValueType); + + // extract database type from dictionary, if appropriate + if (value is IDictionary dictionaryConfiguration + && dictionaryConfiguration.TryGetValue(Constants.PropertyEditors.ConfigurationKeys.DataValueType, out var valueTypeObject) + && valueTypeObject is string valueTypeString + && ValueTypes.IsValue(valueTypeString)) + DatabaseType = ValueTypes.ToStorageType(valueTypeString); + + _configuration = value; _hasConfiguration = true; _configurationJson = null; - _editor = null; + + // it's always a change + OnPropertyChanged(Selectors.Configuration); } } - /// // fixme on interface!? - public void SetConfiguration(string configurationJson, PropertyEditor editor) + public abstract class EditorConfiguration + { + public abstract bool Equals(EditorConfiguration other); + } + + /// + /// Lazily set the configuration as a serialized json string. + /// + /// + /// Will be de-serialized on-demand. + /// This method is meant to be used when building entities from database, exclusively. + /// It does NOT register a property change to dirty. It ignores the fact that the configuration + /// may contain the database type, because the datatype DTO should also contain that database + /// type, and they should be the same. + /// Think before using! + /// + internal void SetConfiguration(string configurationJson) { - // fixme this is lazy, BUT then WHEN are we figuring out the valueType? - _editor = editor ?? throw new ArgumentNullException(nameof(editor)); _hasConfiguration = false; _configuration = null; _configurationJson = configurationJson; - OnPropertyChanged(Selectors.Configuration); } } } diff --git a/src/Umbraco.Core/Models/IDataType.cs b/src/Umbraco.Core/Models/IDataType.cs index 37a54c36b4..c44629a8c0 100644 --- a/src/Umbraco.Core/Models/IDataType.cs +++ b/src/Umbraco.Core/Models/IDataType.cs @@ -1,4 +1,5 @@ using Umbraco.Core.Models.Entities; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Core.Models { @@ -8,9 +9,14 @@ namespace Umbraco.Core.Models public interface IDataType : IUmbracoEntity { /// - /// Gets or sets the property editor alias. + /// Gets or sets the property editor. /// - string EditorAlias { get; set; } + PropertyEditor Editor { get; set; } + + /// + /// Gets the property editor alias. + /// + string EditorAlias { get; } /// /// Gets or sets the database type for the data type values. diff --git a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs index 8c560f094f..e8ddadd79a 100644 --- a/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/DataTypeFactory.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Persistence.Factories if (!editors.TryGet(dto.EditorAlias, out var editor)) throw new InvalidOperationException($"Could not find an editor with alias \"{dto.EditorAlias}\"."); - var dataType = new DataType(dto.EditorAlias); + var dataType = new DataType(editor); try { @@ -32,7 +32,7 @@ namespace Umbraco.Core.Persistence.Factories dataType.Trashed = dto.NodeDto.Trashed; dataType.CreatorId = dto.NodeDto.UserId ?? 0; - dataType.SetConfiguration(dto.Configuration, editor); + dataType.SetConfiguration(dto.Configuration); // reset dirty initial properties (U4-1946) dataType.ResetDirtyProperties(false); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs index 24756aefd0..86444a4cc3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -9,7 +9,6 @@ using Umbraco.Core.Events; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs index a94506967d..0b82322513 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditor.cs @@ -56,6 +56,12 @@ namespace Umbraco.Core.PropertyEditors /// The default configuration is used to initialize new datatypes. public virtual IDictionary DefaultConfiguration => new Dictionary(); + /// + /// Determines whether a configuration object is of the type expected by the configuration editor. + /// + public virtual bool IsConfiguration(object obj) + => obj is IDictionary; + // notes // ToConfigurationEditor returns a dictionary, and FromConfigurationEditor accepts a dictionary. // this is due to the way our front-end editors work, see DataTypeController.PostSave diff --git a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs index 945d8e8b5a..96965f731a 100644 --- a/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/ConfigurationEditorOfTConfiguration.cs @@ -94,6 +94,10 @@ namespace Umbraco.Core.PropertyEditors /// public override IDictionary DefaultConfiguration => ToConfigurationEditor(new TConfiguration()); + /// + public override bool IsConfiguration(object obj) + => obj is TConfiguration; + /// public override object FromDatabase(string configuration) { diff --git a/src/Umbraco.Core/PropertyEditors/VoidEditor.cs b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs new file mode 100644 index 0000000000..429db431fe --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/VoidEditor.cs @@ -0,0 +1,21 @@ +using Umbraco.Core.Composing; +using Umbraco.Core.Logging; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Represents a void editor. + /// + /// Can be used in some places where an editor is needed but no actual + /// editor is available. Not to be used otherwise. Not discovered, and therefore + /// not part of the editors collection. + [HideFromTypeFinder] + public class VoidEditor : PropertyEditor + { + public VoidEditor(ILogger logger) + : base(logger) + { + Alias = "Umbraco.Void"; + } + } +} diff --git a/src/Umbraco.Core/Services/Implement/PackagingService.cs b/src/Umbraco.Core/Services/Implement/PackagingService.cs index 579de1adac..ddc72a285c 100644 --- a/src/Umbraco.Core/Services/Implement/PackagingService.cs +++ b/src/Umbraco.Core/Services/Implement/PackagingService.cs @@ -872,7 +872,7 @@ namespace Umbraco.Core.Services.Implement throw new ArgumentException("The passed in XElement is not valid! It does not contain a root element called 'DataTypes' for multiple imports or 'DataType' for a single import."); } - var dataTypes = new Dictionary(); + var dataTypes = new List(); var dataTypeElements = name.Equals("DataTypes") ? (from doc in element.Elements("DataType") select doc).ToList() : new List { element }; @@ -898,16 +898,31 @@ namespace Umbraco.Core.Services.Implement ? databaseTypeAttribute.Value.EnumParse(true) : ValueStorageType.Ntext; - //the Id field is actually the string property editor Alias - var dataTypeDefinition = new DataType(dataTypeElement.Attribute("Id").Value.Trim()) + // the Id field is actually the string property editor Alias + // however, the actual editor with this alias could be installed with the package, and + // therefore not yet part of the _propertyEditors collection, so we cannot try and get + // the actual editor - going with a void editor + + var editorAlias = dataTypeElement.Attribute("Id")?.Value?.Trim(); + if (!_propertyEditors.TryGet(editorAlias, out var editor)) + editor = new VoidEditor(_logger) { Alias = editorAlias }; + + var dataTypeDefinition = new DataType(editor) { Key = dataTypeDefinitionId, Name = dataTypeDefinitionName, DatabaseType = databaseType, ParentId = parentId }; - dataTypes.Add(dataTypeDefinitionName, dataTypeDefinition); + // fixme - so what? + var configurationAttribute = dataTypeElement.Attribute("Configuration"); + if (!string.IsNullOrWhiteSpace(configurationAttribute?.Value)) + { + dataTypeDefinition.Configuration = editor.ConfigurationEditor.FromDatabase(configurationAttribute.Value); + } + + dataTypes.Add(dataTypeDefinition); } else { @@ -916,24 +931,15 @@ namespace Umbraco.Core.Services.Implement } } - var list = dataTypes.Select(x => x.Value).ToList(); - if (list.Any()) + if (dataTypes.Count > 0) { - //NOTE: As long as we have to deal with the two types of PreValue lists (with/without Keys) - //this is a bit of a pain to handle while ensuring that the imported DataTypes has PreValues - //place when triggering the save event. - - _dataTypeService.Save(list, userId, false);//Save without raising events - - SavePrevaluesFromXml(list, dataTypeElements);//Save the PreValues for the current list of DataTypes - - _dataTypeService.Save(list, userId, true);//Re-save and raise events + _dataTypeService.Save(dataTypes, userId, true); } if (raiseEvents) - ImportedDataType.RaiseEvent(new ImportEventArgs(list, element, false), this); + ImportedDataType.RaiseEvent(new ImportEventArgs(dataTypes, element, false), this); - return list; + return dataTypes; } private Dictionary CreateDataTypeFolderStructure(IEnumerable datatypeElements) @@ -994,39 +1000,6 @@ namespace Umbraco.Core.Services.Implement return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } - private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) - { - foreach (var dataTypeElement in dataTypeElements) - { - var configurationAttribute = dataTypeElement.Attribute("Configuration"); - if (string.IsNullOrWhiteSpace(configurationAttribute?.Value)) continue; - - var dataTypeName = dataTypeElement.Attribute("Name")?.Value; - var dataType = dataTypes.FirstOrDefault(x => x.Name == dataTypeName); - - if (dataType == null) - { - _logger.Warn($"No data type found with name \"{dataTypeName}\", configuration will not be saved."); - continue; - } - - if (!_propertyEditors.TryGet(dataType.EditorAlias, out var editor)) - { - _logger.Warn($"Failed to find an editor with alias \"{dataType.EditorAlias}\", configuration will not be saved."); - continue; - } - - try - { - dataType.Configuration = editor.ConfigurationEditor.FromDatabase(configurationAttribute.Value); - } - catch (Exception ex) - { - _logger.Warn($"Failed to deserialize string \"{configurationAttribute.Value}\" as configuration for editor of type \"{editor.GetType().Name}\".", ex); - } - } - } - #endregion #region Dictionary Items diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 8e10804c41..3f219bae7a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -343,6 +343,7 @@ + diff --git a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs index bd289dcdf7..f79953e13e 100644 --- a/src/Umbraco.Tests/Manifest/ManifestParserTests.cs +++ b/src/Umbraco.Tests/Manifest/ManifestParserTests.cs @@ -194,7 +194,7 @@ javascript: ['~/test.js',/*** some note about stuff asd09823-4**09234*/ '~/test2 // this is not part of the manifest var preValues = editor.DefaultConfiguration; - Assert.IsNull(preValues); + Assert.IsEmpty(preValues); var preValueEditor = editor.ConfigurationEditor; Assert.IsNotNull(preValueEditor); diff --git a/src/Umbraco.Tests/Models/DataTypeTests.cs b/src/Umbraco.Tests/Models/DataTypeTests.cs index 35b4476fd7..6a943c9695 100644 --- a/src/Umbraco.Tests/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests/Models/DataTypeTests.cs @@ -1,8 +1,10 @@ using System; -using System.Collections.Generic; using System.Diagnostics; +using Moq; using NUnit.Framework; +using Umbraco.Core.Logging; using Umbraco.Core.Models; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models @@ -13,7 +15,7 @@ namespace Umbraco.Tests.Models [Test] public void Can_Deep_Clone() { - var dtd = new DataType(9, Guid.NewGuid().ToString()) + var dtd = new DataType(new VoidEditor(Mock.Of()), 9) { CreateDate = DateTime.Now, CreatorId = 5, @@ -58,7 +60,7 @@ namespace Umbraco.Tests.Models { var ss = new SerializationService(new JsonNetSerializer()); - var dtd = new DataType(9, Guid.NewGuid().ToString()) + var dtd = new DataType(new VoidEditor(Mock.Of()), 9) { CreateDate = DateTime.Now, CreatorId = 5, diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs index 149fe9f6ab..b9eac41784 100644 --- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs +++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs @@ -502,7 +502,7 @@ namespace Umbraco.Tests.Models.Mapping public void MemberPropertyGroupBasic_To_MemberPropertyGroup() { _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new DataType("test")); + .Returns(new DataType(new VoidEditor(Mock.Of()))); var basic = new PropertyGroupBasic { @@ -571,7 +571,7 @@ namespace Umbraco.Tests.Models.Mapping public void PropertyGroupBasic_To_PropertyGroup() { _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new DataType("test")); + .Returns(new DataType(new VoidEditor(Mock.Of()))); var basic = new PropertyGroupBasic { @@ -637,7 +637,7 @@ namespace Umbraco.Tests.Models.Mapping public void MemberPropertyTypeBasic_To_PropertyType() { _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new DataType("test")); + .Returns(new DataType(new VoidEditor(Mock.Of()))); var basic = new MemberPropertyTypeBasic() { @@ -671,7 +671,7 @@ namespace Umbraco.Tests.Models.Mapping public void PropertyTypeBasic_To_PropertyType() { _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new DataType("test")); + .Returns(new DataType(new VoidEditor(Mock.Of()))); var basic = new PropertyTypeBasic() { @@ -883,7 +883,7 @@ namespace Umbraco.Tests.Models.Mapping public void MemberPropertyTypeBasic_To_MemberPropertyTypeDisplay() { _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new DataType("test")); + .Returns(new DataType(new VoidEditor(Mock.Of()))); var basic = new MemberPropertyTypeBasic() { @@ -921,7 +921,7 @@ namespace Umbraco.Tests.Models.Mapping public void PropertyTypeBasic_To_PropertyTypeDisplay() { _dataTypeService.Setup(x => x.GetDataType(It.IsAny())) - .Returns(new DataType("test")); + .Returns(new DataType(new VoidEditor(Mock.Of()))); var basic = new PropertyTypeBasic() { diff --git a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs index 59d718e63f..edb8674dd8 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ExpressionTests.cs @@ -13,6 +13,7 @@ using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Tests.TestHelpers; using System.Linq; using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.PropertyEditors; namespace Umbraco.Tests.Persistence.Querying { @@ -22,7 +23,7 @@ namespace Umbraco.Tests.Persistence.Querying [Test] public void Equals_Claus_With_Two_Entity_Values() { - var dataType = new DataType(-1, "Test") + var dataType = new DataType(new VoidEditor(Mock.Of())) { Id = 12345 }; diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 1b028de6a0..1c5736c698 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -18,6 +18,7 @@ using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Persistence.Repositories { @@ -347,7 +348,8 @@ namespace Umbraco.Tests.Persistence.Repositories { var repository = CreateRepository((IScopeAccessor) provider, out var contentTypeRepository, out DataTypeRepository dataTypeDefinitionRepository); - var dtd = new DataType(-1, Constants.PropertyEditors.Aliases.Decimal) { Name = "test", DatabaseType = ValueStorageType.Decimal }; + var editor = new DecimalPropertyEditor(Logger); + var dtd = new DataType(editor) { Name = "test", DatabaseType = ValueStorageType.Decimal }; dataTypeDefinitionRepository.Save(dtd); const string decimalPropertyAlias = "decimalProperty"; diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 20d066adb8..cc515c4347 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -8,7 +8,9 @@ using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; using LightInject; using Umbraco.Core.Persistence.Repositories.Implement; +using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Persistence.Repositories { @@ -42,14 +44,14 @@ namespace Umbraco.Tests.Persistence.Repositories var container2 = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "blah2", ParentId = container1.Id }; containerRepository.Save(container2); - var dataType = (IDataType)new DataType(container2.Id, Constants.PropertyEditors.Aliases.RadioButtonList) + var dataType = (IDataType) new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService), container2.Id) { Name = "dt1" }; repository.Save(dataType); //create a - var dataType2 = (IDataType)new DataType(dataType.Id, Constants.PropertyEditors.Aliases.RadioButtonList) + var dataType2 = (IDataType)new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService), dataType.Id) { Name = "dt2" }; @@ -121,7 +123,7 @@ namespace Umbraco.Tests.Persistence.Repositories var container = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "blah" }; containerRepository.Save(container); - var dataTypeDefinition = new DataType(container.Id, Constants.PropertyEditors.Aliases.RadioButtonList) { Name = "test" }; + var dataTypeDefinition = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService), container.Id) { Name = "test" }; repository.Save(dataTypeDefinition); Assert.AreEqual(container.Id, dataTypeDefinition.ParentId); @@ -141,7 +143,7 @@ namespace Umbraco.Tests.Persistence.Repositories var container = new EntityContainer(Constants.ObjectTypes.DataType) { Name = "blah" }; containerRepository.Save(container); - IDataType dataType = new DataType(container.Id, Constants.PropertyEditors.Aliases.RadioButtonList) { Name = "test" }; + IDataType dataType = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService), container.Id) { Name = "test" }; repository.Save(dataType); // Act @@ -164,7 +166,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (provider.CreateScope()) { var repository = CreateRepository(); - IDataType dataType = new DataType(-1, Constants.PropertyEditors.Aliases.RadioButtonList) {Name = "test"}; + IDataType dataType = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) {Name = "test"}; repository.Save(dataType); @@ -285,11 +287,12 @@ namespace Umbraco.Tests.Persistence.Repositories using (provider.CreateScope()) { var repository = CreateRepository(); - var dataTypeDefinition = new DataType(Constants.PropertyEditors.Aliases.NoEdit) + var dataTypeDefinition = new DataType(new LabelPropertyEditor(Logger)) { DatabaseType = ValueStorageType.Integer, Name = "AgeDataType", - CreatorId = 0 + CreatorId = 0, + Configuration = new LabelConfiguration { ValueType = ValueTypes.Xml } }; // Act @@ -302,7 +305,15 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(dataTypeDefinition.HasIdentity, Is.True); Assert.That(exists, Is.True); - TestHelper.AssertPropertyValuesAreEqual(dataTypeDefinition, fetched, "yyyy-MM-dd HH:mm:ss"); + // cannot compare 'configuration' as it's two different objects + TestHelper.AssertPropertyValuesAreEqual(dataTypeDefinition, fetched, "yyyy-MM-dd HH:mm:ss", ignoreProperties: new [] { "Configuration" }); + + // still, can compare explicitely + Assert.IsNotNull(dataTypeDefinition.Configuration); + Assert.IsInstanceOf(dataTypeDefinition.Configuration); + Assert.IsNotNull(fetched.Configuration); + Assert.IsInstanceOf(fetched.Configuration); + Assert.AreEqual(ConfigurationEditor.ConfigurationAs(dataTypeDefinition.Configuration).ValueType, ConfigurationEditor.ConfigurationAs(fetched.Configuration).ValueType); } } @@ -314,7 +325,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (provider.CreateScope()) { var repository = CreateRepository(); - var dataTypeDefinition = new DataType(Constants.PropertyEditors.Aliases.Integer) + var dataTypeDefinition = new DataType(new IntegerPropertyEditor(Logger)) { DatabaseType = ValueStorageType.Integer, Name = "AgeDataType", @@ -325,7 +336,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act var definition = repository.Get(dataTypeDefinition.Id); definition.Name = "AgeDataType Updated"; - definition.EditorAlias = Constants.PropertyEditors.Aliases.NoEdit; //change + definition.Editor = new LabelPropertyEditor(Logger); //change repository.Save(definition); var definitionUpdated = repository.Get(dataTypeDefinition.Id); @@ -345,7 +356,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (provider.CreateScope()) { var repository = CreateRepository(); - var dataTypeDefinition = new DataType(Constants.PropertyEditors.Aliases.NoEdit) + var dataTypeDefinition = new DataType(new LabelPropertyEditor(Logger)) { DatabaseType = ValueStorageType.Integer, Name = "AgeDataType", diff --git a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs index 86f859db43..5f51d8d5e8 100644 --- a/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs +++ b/src/Umbraco.Tests/PropertyEditors/MultiValuePropertyEditorTests.cs @@ -32,7 +32,8 @@ namespace Umbraco.Tests.PropertyEditors var dataTypeServiceMock = new Mock(); var editor = new PublishValuesMultipleValueEditor(true, Mock.Of(), new ValueEditorAttribute("key", "nam", "view")); - var prop = new Property(1, new PropertyType(new DataType(1, "Test.TestEditor"))); + var dataType = new DataType(new CheckBoxListPropertyEditor(Mock.Of(), Mock.Of())); + var prop = new Property(1, new PropertyType(dataType)); prop.SetValue("1234,4567,8910"); var result = editor.ConvertDbToString(prop.PropertyType, prop.GetValue(), new Mock().Object); @@ -43,7 +44,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void DropDownMultipleValueEditor_No_Keys_Format_Data_For_Cache() { - var dataType = new DataType("editorAlias") + var dataType = new DataType(new CheckBoxListPropertyEditor(Mock.Of(), Mock.Of())) { Configuration = new ValueListConfiguration { @@ -53,7 +54,8 @@ namespace Umbraco.Tests.PropertyEditors new ValueListConfiguration.ValueListItem { Id = 1234, Value = "Value 2" }, new ValueListConfiguration.ValueListItem { Id = 8910, Value = "Value 3" } } - } + }, + Id = 1 }; var dataTypeService = Mock.Of(); @@ -62,12 +64,10 @@ namespace Umbraco.Tests.PropertyEditors .Setup(x => x.GetDataType(It.IsAny())) .Returns(x => x == 1 ? dataType : null); - var editor = new PublishValuesMultipleValueEditor(false, Mock.Of(), new ValueEditorAttribute("alias", "name", "view")); - - var prop = new Property(1, new PropertyType(new DataType(1, "Test.TestEditor") { Id = 1 })); + var prop = new Property(1, new PropertyType(dataType)); prop.SetValue("1234,4567,8910"); - var result = editor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); + var result = dataType.Editor.ValueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); Assert.AreEqual("Value 1,Value 2,Value 3", result); } @@ -75,7 +75,7 @@ namespace Umbraco.Tests.PropertyEditors [Test] public void DropDownValueEditor_Format_Data_For_Cache() { - var dataType = new DataType("editorAlias") + var dataType = new DataType(new CheckBoxListPropertyEditor(Mock.Of(), Mock.Of())) { Configuration = new ValueListConfiguration { @@ -85,7 +85,8 @@ namespace Umbraco.Tests.PropertyEditors new ValueListConfiguration.ValueListItem { Id = 1234, Value = "Value 2" }, new ValueListConfiguration.ValueListItem { Id = 11, Value = "Value 3" } } - } + }, + Id = 1 }; var dataTypeService = Mock.Of(); @@ -94,12 +95,10 @@ namespace Umbraco.Tests.PropertyEditors .Setup(x => x.GetDataType(It.IsAny())) .Returns(x => x == 1 ? dataType : null); - var editor = new PublishValueValueEditor(new ValueEditorAttribute("alias", "name", "view"), Mock.Of()); - - var prop = new Property(1, new PropertyType(new DataType(1, "Test.TestEditor") { Id = 1 })); + var prop = new Property(1, new PropertyType(dataType)); prop.SetValue("1234"); - var result = editor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); + var result = dataType.Editor.ValueEditor.ConvertDbToString(prop.PropertyType, prop.GetValue(), dataTypeService); Assert.AreEqual("Value 2", result); } diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index c333032599..769f16a25c 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -31,8 +31,13 @@ namespace Umbraco.Tests.Published var profiler = Mock.Of(); var proflog = new ProfilingLogger(logger, profiler); - var dataType1 = new DataType("editorAlias1") + PropertyEditorCollection editors = null; + var editor = new NestedContentPropertyEditor(logger, new Lazy(() => editors)); + editors = new PropertyEditorCollection(new PropertyEditor[] { editor }); + + var dataType1 = new DataType(editor) { + Id = 1, Configuration = new NestedContentConfiguration { MinItems = 1, @@ -44,8 +49,9 @@ namespace Umbraco.Tests.Published } }; - var dataType2 = new DataType("editorAlias2") + var dataType2 = new DataType(editor) { + Id = 2, Configuration = new NestedContentConfiguration { MinItems = 1, @@ -124,12 +130,6 @@ namespace Umbraco.Tests.Published new NestedContentManyValueConverter(publishedSnapshotAccessor.Object, publishedModelFactory.Object, proflog), }); - PropertyEditorCollection editors = null; - editors = new PropertyEditorCollection(new PropertyEditor[] - { - new NestedContentPropertyEditor(Mock.Of(), new Lazy(() => editors)) - }); - var source = new DataTypeConfigurationSource(dataTypeService, editors); var factory = new PublishedContentTypeFactory(publishedModelFactory.Object, converters, source); diff --git a/src/Umbraco.Tests/Services/CachedDataTypeServiceTests.cs b/src/Umbraco.Tests/Services/CachedDataTypeServiceTests.cs index 937117f952..2bce0ecf6c 100644 --- a/src/Umbraco.Tests/Services/CachedDataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/CachedDataTypeServiceTests.cs @@ -2,6 +2,7 @@ using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Services { @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Services { var dataTypeService = ServiceContext.DataTypeService; - IDataType dataType = new DataType(-1, Constants.PropertyEditors.Aliases.NoEdit) { Name = "Testing Textfield", DatabaseType = ValueStorageType.Ntext }; + IDataType dataType = new DataType(new LabelPropertyEditor(Logger)) { Name = "Testing Textfield", DatabaseType = ValueStorageType.Ntext }; dataTypeService.Save(dataType); //Get all the first time (no cache) diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index 5491017dae..e36ab4cac8 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -7,6 +7,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.Testing; +using Umbraco.Web.PropertyEditors; namespace Umbraco.Tests.Services { @@ -24,7 +25,7 @@ namespace Umbraco.Tests.Services var dataTypeService = ServiceContext.DataTypeService; // Act - IDataType dataType = new DataType(-1, Constants.PropertyEditors.Aliases.NoEdit) { Name = "Testing Textfield", DatabaseType = ValueStorageType.Ntext }; + IDataType dataType = new DataType(new LabelPropertyEditor(Logger)) { Name = "Testing Textfield", DatabaseType = ValueStorageType.Ntext }; dataTypeService.Save(dataType); // Assert @@ -66,7 +67,7 @@ namespace Umbraco.Tests.Services var dataTypeService = ServiceContext.DataTypeService; // Act - var dataTypeDefinition = new DataType(-1, "Test.TestEditor") { Name = string.Empty, DatabaseType = ValueStorageType.Ntext }; + var dataTypeDefinition = new DataType(new LabelPropertyEditor(Logger)) { Name = string.Empty, DatabaseType = ValueStorageType.Ntext }; // Act & Assert Assert.Throws(() => dataTypeService.Save(dataTypeDefinition)); diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index bcd9f8fb61..75a5a3544c 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -152,7 +152,8 @@ namespace Umbraco.Tests.TestHelpers else { // directly compare values - Assert.AreEqual(expected, actual, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expected, actual); + Assert.AreEqual(expected, actual, "Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, + expected?.ToString() ?? "", actual?.ToString() ?? ""); } } diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 134cda2690..7d5f5dd79d 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -33,6 +33,8 @@ namespace Umbraco.Web.Editors [EnableOverrideAuthorization] public class DataTypeController : BackOfficeNotificationsController { + private PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // fixme inject + /// /// Gets data type by name /// @@ -81,7 +83,9 @@ namespace Umbraco.Web.Editors public DataTypeDisplay GetEmpty(int parentId) { - var dt = new DataType(parentId, ""); + // cannot create an "empty" data type, so use something by default. + var editor = PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit]; + var dt = new DataType(editor, parentId); return Mapper.Map(dt); } @@ -113,7 +117,8 @@ namespace Umbraco.Web.Editors //if it doesnt exist yet, we will create it. if (dt == null) { - dt = new DataType(Constants.PropertyEditors.Aliases.ListView); + var editor = PropertyEditors[Constants.PropertyEditors.Aliases.ListView]; + dt = new DataType(editor); dt.Name = Constants.Conventions.DataTypes.ListViewPrefix + contentTypeAlias; Services.DataTypeService.Save(dt); } diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs index a35183521d..0a91553bde 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs @@ -16,6 +16,8 @@ namespace Umbraco.Web.Models.Mapping /// internal class DataTypeMapperProfile : Profile { + private PropertyEditorCollection PropertyEditors => Current.PropertyEditors; // fixme inject + public DataTypeMapperProfile() { // create, capture, cache @@ -91,7 +93,7 @@ namespace Umbraco.Web.Models.Mapping .ConvertUsing(src => configurationDisplayResolver.Resolve(src)); CreateMap() - .ConstructUsing(src => new DataType(src.EditorAlias) {CreateDate = DateTime.Now}) + .ConstructUsing(src => new DataType(PropertyEditors[src.EditorAlias]) {CreateDate = DateTime.Now}) .IgnoreEntityCommonProperties() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) .ForMember(dest => dest.Key, opt => opt.Ignore()) // ignore key, else resets UniqueId - U4-3911 @@ -101,7 +103,8 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) .ForMember(dest => dest.Level, opt => opt.Ignore()) .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) - .ForMember(dest => dest.Configuration, opt => opt.Ignore()); + .ForMember(dest => dest.Configuration, opt => opt.Ignore()) + .ForMember(dest => dest.Editor, opt => opt.MapFrom(src => PropertyEditors[src.EditorAlias])); //Converts a property editor to a new list of pre-value fields - used when creating a new data type or changing a data type with new pre-vals CreateMap>()