diff --git a/src/Umbraco.Infrastructure/Models/DataType.cs b/src/Umbraco.Infrastructure/Models/DataType.cs index c237f6381c..299795f865 100644 --- a/src/Umbraco.Infrastructure/Models/DataType.cs +++ b/src/Umbraco.Infrastructure/Models/DataType.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Runtime.Serialization; using Newtonsoft.Json; using Umbraco.Core.Models.Entities; diff --git a/src/Umbraco.Infrastructure/Persistence/Factories/DataTypeFactory.cs b/src/Umbraco.Infrastructure/Persistence/Factories/DataTypeFactory.cs index 04609b2821..fa991d6679 100644 --- a/src/Umbraco.Infrastructure/Persistence/Factories/DataTypeFactory.cs +++ b/src/Umbraco.Infrastructure/Persistence/Factories/DataTypeFactory.cs @@ -1,25 +1,24 @@ using System; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Core.Strings; - namespace Umbraco.Core.Persistence.Factories { internal static class DataTypeFactory { - public static IDataType BuildEntity(DataTypeDto dto, PropertyEditorCollection editors, ILogger logger, IIOHelper ioHelper, IDataTypeService dataTypeService, ILocalizedTextService localizedTextService, ILocalizationService localizationService, IShortStringHelper shortStringHelper) + public static IDataType BuildEntity(DataTypeDto dto, PropertyEditorCollection editors, ILogger logger) { + // Check we have an editor for the data type. if (!editors.TryGet(dto.EditorAlias, out var editor)) { - logger.Warn(typeof(DataType), "Could not find an editor with alias {EditorAlias}, treating as Label." - +" The site may fail to boot and / or load data types and run.", dto.EditorAlias); - //convert to label - editor = new LabelPropertyEditor(logger, ioHelper,dataTypeService , localizedTextService, localizationService, shortStringHelper); + logger.Warn(typeof(DataType), "Could not find an editor with alias {EditorAlias}, treating as Label. " + + "The site may fail to boot and/or load data types and run.", dto.EditorAlias); + + // Create as special type, which downstream can be handled by converting to a LabelPropertyEditor to make clear + // the situation to the user. + editor = new MissingPropertyEditor(); } var dataType = new DataType(editor); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs index 1f0d944c7e..42a89384d7 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -7,7 +7,6 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; @@ -17,7 +16,6 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services; -using Umbraco.Core.Strings; using static Umbraco.Core.Persistence.SqlExtensionsStatics; namespace Umbraco.Core.Persistence.Repositories.Implement @@ -28,22 +26,11 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal class DataTypeRepository : NPocoRepositoryBase, IDataTypeRepository { private readonly Lazy _editors; - private readonly IIOHelper _ioHelper; - private readonly Lazy _dataTypeService; - private readonly ILocalizedTextService _localizedTextService; - private readonly ILocalizationService _localizationService; - private readonly IShortStringHelper _shortStringHelper; - // TODO: https://github.com/umbraco/Umbraco-CMS/issues/4237 - get rid of Lazy injection and fix circular dependencies - public DataTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, Lazy editors, ILogger logger, IIOHelper ioHelper, Lazy dataTypeService, ILocalizedTextService localizedTextService, ILocalizationService localizationService, IShortStringHelper shortStringHelper) + public DataTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, Lazy editors, ILogger logger) : base(scopeAccessor, cache, logger) { _editors = editors; - _ioHelper = ioHelper; - _dataTypeService = dataTypeService; - _localizedTextService = localizedTextService; - _localizationService = localizationService; - _shortStringHelper = shortStringHelper; } #region Overrides of RepositoryBase @@ -67,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement } var dtos = Database.Fetch(dataTypeSql); - return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value, Logger,_ioHelper, _dataTypeService.Value, _localizedTextService, _localizationService, _shortStringHelper)).ToArray(); + return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value, Logger)).ToArray(); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -78,7 +65,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var dtos = Database.Fetch(sql); - return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value, Logger, _ioHelper, _dataTypeService.Value, _localizedTextService, _localizationService, _shortStringHelper)).ToArray(); + return dtos.Select(x => DataTypeFactory.BuildEntity(x, _editors.Value, Logger)).ToArray(); } #endregion diff --git a/src/Umbraco.Infrastructure/PropertyEditors/MissingPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/MissingPropertyEditor.cs new file mode 100644 index 0000000000..902aab7cc7 --- /dev/null +++ b/src/Umbraco.Infrastructure/PropertyEditors/MissingPropertyEditor.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Core.PropertyEditors +{ + /// + /// Represents a temporary representation of an editor for cases where a data type is created but not editor is available. + /// + public class MissingPropertyEditor : IDataEditor + { + public string Alias => "Umbraco.Missing"; + + public EditorType Type => EditorType.Nothing; + + public string Name => "Missing property editor"; + + public string Icon => string.Empty; + + public string Group => string.Empty; + + public bool IsDeprecated => false; + + public IDictionary DefaultConfiguration => throw new NotImplementedException(); + + public IPropertyIndexValueFactory PropertyIndexValueFactory => throw new NotImplementedException(); + + public IConfigurationEditor GetConfigurationEditor() + { + return new ConfigurationEditor(); + } + + public IDataValueEditor GetValueEditor() + { + throw new NotImplementedException(); + } + + public IDataValueEditor GetValueEditor(object configuration) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs index f9d0256568..08bed264ae 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/DataTypeService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Events; using Umbraco.Core.Exceptions; +using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Dtos; @@ -10,6 +11,7 @@ using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; +using Umbraco.Core.Strings; namespace Umbraco.Core.Services.Implement { @@ -23,10 +25,15 @@ namespace Umbraco.Core.Services.Implement private readonly IContentTypeRepository _contentTypeRepository; private readonly IAuditRepository _auditRepository; private readonly IEntityRepository _entityRepository; + private readonly IIOHelper _ioHelper; + private readonly ILocalizedTextService _localizedTextService; + private readonly ILocalizationService _localizationService; + private readonly IShortStringHelper _shortStringHelper; public DataTypeService(IScopeProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeRepository dataTypeRepository, IDataTypeContainerRepository dataTypeContainerRepository, - IAuditRepository auditRepository, IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository) + IAuditRepository auditRepository, IEntityRepository entityRepository, IContentTypeRepository contentTypeRepository, + IIOHelper ioHelper, ILocalizedTextService localizedTextService, ILocalizationService localizationService, IShortStringHelper shortStringHelper) : base(provider, logger, eventMessagesFactory) { _dataTypeRepository = dataTypeRepository; @@ -34,6 +41,10 @@ namespace Umbraco.Core.Services.Implement _auditRepository = auditRepository; _entityRepository = entityRepository; _contentTypeRepository = contentTypeRepository; + _ioHelper = ioHelper; + _localizedTextService = localizedTextService; + _localizationService = localizationService; + _shortStringHelper = shortStringHelper; } #region Containers @@ -227,7 +238,9 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _dataTypeRepository.Get(Query().Where(x => x.Name == name)).FirstOrDefault(); + var dataType = _dataTypeRepository.Get(Query().Where(x => x.Name == name)).FirstOrDefault(); + ConvertMissingEditorOfDataTypeToLabel(dataType); + return dataType; } } @@ -240,7 +253,9 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _dataTypeRepository.Get(id); + var dataType = _dataTypeRepository.Get(id); + ConvertMissingEditorOfDataTypeToLabel(dataType); + return dataType; } } @@ -254,7 +269,9 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { var query = Query().Where(x => x.Key == id); - return _dataTypeRepository.Get(query).FirstOrDefault(); + var dataType = _dataTypeRepository.Get(query).FirstOrDefault(); + ConvertMissingEditorOfDataTypeToLabel(dataType); + return dataType; } } @@ -268,7 +285,9 @@ namespace Umbraco.Core.Services.Implement using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { var query = Query().Where(x => x.EditorAlias == propertyEditorAlias); - return _dataTypeRepository.Get(query); + var dataType = _dataTypeRepository.Get(query); + ConvertMissingEditorsOfDataTypesToLabels(dataType); + return dataType; } } @@ -281,7 +300,31 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - return _dataTypeRepository.GetMany(ids); + var dataTypes = _dataTypeRepository.GetMany(ids); + ConvertMissingEditorsOfDataTypesToLabels(dataTypes); + return dataTypes; + } + } + + private void ConvertMissingEditorOfDataTypeToLabel(IDataType dataType) + { + if (dataType == null) + { + return; + } + + ConvertMissingEditorsOfDataTypesToLabels(new[] { dataType }); + } + + private void ConvertMissingEditorsOfDataTypesToLabels(IEnumerable dataTypes) + { + // Any data types that don't have an associated editor are created of a specific type. + // We convert them to labels to make clear to the user why the data type cannot be used. + var dataTypesWithMissingEditors = dataTypes + .Where(x => x.Editor is MissingPropertyEditor); + foreach (var dataType in dataTypesWithMissingEditors) + { + dataType.Editor = new LabelPropertyEditor(Logger, _ioHelper, this, _localizedTextService, _localizationService, _shortStringHelper); } } diff --git a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs index a49fdf3abe..6658c689e1 100644 --- a/src/Umbraco.Tests/Composing/TypeLoaderTests.cs +++ b/src/Umbraco.Tests/Composing/TypeLoaderTests.cs @@ -275,7 +275,7 @@ AnotherContentFinder public void GetDataEditors() { var types = _typeLoader.GetDataEditors(); - Assert.AreEqual(38, types.Count()); + Assert.AreEqual(39, types.Count()); } /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index fc45f0ba3f..30bf5be17b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -50,7 +50,7 @@ namespace Umbraco.Tests.Persistence.Repositories TemplateRepository tr; var ctRepository = CreateRepository(scopeAccessor, out contentTypeRepository, out tr); var editors = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty())); - dtdRepository = new DataTypeRepository(scopeAccessor, appCaches, new Lazy(() => editors), Logger, IOHelper, new Lazy(() => DataTypeService), LocalizedTextService, LocalizationService, ShortStringHelper); + dtdRepository = new DataTypeRepository(scopeAccessor, appCaches, new Lazy(() => editors), Logger); return ctRepository; } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index 92b9dd0ad2..f237fb0fc7 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -158,7 +158,7 @@ namespace Umbraco.Tests.TestHelpers var localizationService = GetLazyService(factory, c => new LocalizationService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c))); var userService = GetLazyService(factory, c => new UserService(scopeProvider, logger, eventMessagesFactory, runtimeState, GetRepo(c), GetRepo(c),globalSettings)); - var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c))); + var dataTypeService = GetLazyService(factory, c => new DataTypeService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), ioHelper, localizedTextService.Value, localizationService.Value, TestHelper.ShortStringHelper)); var propertyValidationService = new Lazy(() => new PropertyValidationService(propertyEditorCollection, dataTypeService.Value)); var contentService = GetLazyService(factory, c => new ContentService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), GetRepo(c), propertyValidationService)); var notificationService = GetLazyService(factory, c => new NotificationService(scopeProvider, userService.Value, contentService.Value, localizationService.Value, logger, ioHelper, GetRepo(c), globalSettings, umbracoSettings.Content));