diff --git a/src/Umbraco.Core/Mapping/Mapper.cs b/src/Umbraco.Core/Mapping/Mapper.cs index f609c8c298..36c7462d8b 100644 --- a/src/Umbraco.Core/Mapping/Mapper.cs +++ b/src/Umbraco.Core/Mapping/Mapper.cs @@ -1,4 +1,5 @@ using System; +using System.CodeDom; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -16,8 +17,11 @@ namespace Umbraco.Core.Mapping public class Mapper { - private readonly Dictionary>> _ctors = new Dictionary>>(); - private readonly Dictionary>> _maps = new Dictionary>>(); + private readonly Dictionary>> _ctors + = new Dictionary>>(); + + private readonly Dictionary>> _maps + = new Dictionary>>(); public Mapper(MapperProfileCollection profiles) { @@ -25,32 +29,103 @@ namespace Umbraco.Core.Mapping profile.SetMaps(this); } + #region Define + + //public void Define() + // => Define((source, target) => { }); + public void Define() - => Define((source, target) => { }); + => Define((source, target, context) => { }); - public void Define(Action map) - => Define(source => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map); + //public void Define(Action map) + // => Define(source => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map); - public void Define(Func ctor) - => Define(ctor, (source, target) => { }); + public void Define(Action map) + => Define((source, context) => throw new NotSupportedException($"Don't know how to create {typeof(TTarget)} instances."), map); - public void Define(Func ctor, Action map) + //public void Define(Func ctor) + // => Define(ctor, (source, target) => { }); + + public void Define(Func ctor) + => Define(ctor, (source, target, context) => { }); + + private Dictionary> DefineCtors(Type sourceType) + { + if (!_ctors.TryGetValue(sourceType, out var sourceCtor)) + sourceCtor = _ctors[sourceType] = new Dictionary>(); + return sourceCtor; + } + + private Dictionary> DefineMaps(Type sourceType) + { + if (!_maps.TryGetValue(sourceType, out var sourceMap)) + sourceMap = _maps[sourceType] = new Dictionary>(); + return sourceMap; + } + + //public void Define(Func ctor, Action map) + //{ + // var sourceType = typeof(TSource); + // var targetType = typeof(TTarget); + + // var sourceCtors = DefineCtors(sourceType); + // sourceCtors[targetType] = (source, context) => ctor((TSource) source); + + // var sourceMaps = DefineMaps(sourceType); + // sourceMaps[targetType] = (source, target, context) => map((TSource) source, (TTarget) target); + //} + + //public void Define(Func ctor, Action map) + //{ + // var sourceType = typeof(TSource); + // var targetType = typeof(TTarget); + + // var sourceCtors = DefineCtors(sourceType); + // sourceCtors[targetType] = (source, context) => ctor((TSource)source); + + // var sourceMaps = DefineMaps(sourceType); + // sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context); + //} + + //public void Define(Func ctor, Action map) + //{ + // var sourceType = typeof(TSource); + // var targetType = typeof(TTarget); + + // var sourceCtors = DefineCtors(sourceType); + // sourceCtors[targetType] = (source, context) => ctor((TSource)source, context); + + // var sourceMaps = DefineMaps(sourceType); + // sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target); + //} + + public void Define(Func ctor, Action map) { var sourceType = typeof(TSource); var targetType = typeof(TTarget); - if (!_ctors.TryGetValue(sourceType, out var sourceCtor)) - sourceCtor = _ctors[sourceType] = new Dictionary>(); + var sourceCtors = DefineCtors(sourceType); + sourceCtors[targetType] = (source, context) => ctor((TSource)source, context); - sourceCtor[targetType] = source => ctor((TSource) source); - - if (!_maps.TryGetValue(sourceType, out var sourceMap)) - sourceMap = _maps[sourceType] = new Dictionary>(); - - sourceMap[targetType] = (source, target) => map((TSource) source, (TTarget) target); + var sourceMaps = DefineMaps(sourceType); + sourceMaps[targetType] = (source, target, context) => map((TSource)source, (TTarget)target, context); } + #endregion + + #region Map + public TTarget Map(object source) + => Map(source, new MapperContext(this)); + + public TTarget Map(object source, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, context); + } + + public TTarget Map(object source, MapperContext context) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -62,8 +137,8 @@ namespace Umbraco.Core.Mapping var map = GetMap(sourceType, typeof(TTarget)); if (ctor != null && map != null) { - var target = ctor(source); - map(source, target); + var target = ctor(source, context); + map(source, target, context); return (TTarget)target; } @@ -104,8 +179,8 @@ namespace Umbraco.Core.Mapping foreach (var sourceItem in sourceEnumerable) { - var targetItem = ctor(sourceItem); - map(sourceItem, targetItem); + var targetItem = ctor(sourceItem, context); + map(sourceItem, targetItem, context); targetEnumerable.Add(targetItem); } @@ -125,6 +200,16 @@ namespace Umbraco.Core.Mapping // TODO: when AutoMapper is completely gone these two methods can merge public TTarget Map(TSource source) + => Map(source, new MapperContext(this)); + + public TTarget Map(TSource source, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, context); + } + + public TTarget Map(TSource source, MapperContext context) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -136,8 +221,8 @@ namespace Umbraco.Core.Mapping var map = GetMap(sourceType, targetType); if (ctor != null && map != null) { - var target = ctor(source); - map(source, target); + var target = ctor(source, context); + map(source, target, context); return (TTarget) target; } @@ -162,8 +247,8 @@ namespace Umbraco.Core.Mapping foreach (var sourceItem in sourceEnumerable) { - var targetItem = ctor(sourceItem); - map(sourceItem, targetItem); + var targetItem = ctor(sourceItem, context); + map(sourceItem, targetItem, context); targetEnumerable.Add(targetItem); } @@ -181,6 +266,17 @@ namespace Umbraco.Core.Mapping } public TTarget Map(TSource source, TTarget target) + => Map(source, target, new MapperContext(this)); + + public TTarget Map(TSource source, TTarget target, Action f) + { + var context = new MapperContext(this); + f(context); + return Map(source, target, context); + } + + + public TTarget Map(TSource source, TTarget target, MapperContext context) { // fixme should we deal with enumerables? @@ -192,11 +288,11 @@ namespace Umbraco.Core.Mapping return AutoMapper.Mapper.Map(source, target); } - map(source, target); + map(source, target, context); return target; } - private Func GetCtor(Type sourceType, Type targetType) + private Func GetCtor(Type sourceType, Type targetType) { if (!_ctors.TryGetValue(sourceType, out var sourceCtor)) { @@ -209,7 +305,7 @@ namespace Umbraco.Core.Mapping return sourceCtor.TryGetValue(targetType, out var ctor) ? ctor : null; } - private Action GetMap(Type sourceType, Type targetType) + private Action GetMap(Type sourceType, Type targetType) { if (!_maps.TryGetValue(sourceType, out var sourceMap)) { @@ -221,5 +317,7 @@ namespace Umbraco.Core.Mapping return sourceMap.TryGetValue(targetType, out var map) ? map : null; } + + #endregion } } diff --git a/src/Umbraco.Core/Mapping/MapperContext.cs b/src/Umbraco.Core/Mapping/MapperContext.cs new file mode 100644 index 0000000000..5f78fc6893 --- /dev/null +++ b/src/Umbraco.Core/Mapping/MapperContext.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Mapping +{ + /// + /// Represents a mapper context. + /// + public class MapperContext + { + private IDictionary _items; + + /// + /// Initializes a new instance of the class. + /// + public MapperContext(Mapper mapper) + { + Mapper = mapper; + } + + /// + /// Gets the mapper. + /// + public Mapper Mapper { get;} + + /// + /// Gets a value indicating whether the context has items. + /// + public bool HasItems => _items != null; + + /// + /// Gets the context items. + /// + public IDictionary Items => _items ?? (_items = new Dictionary()); + } +} diff --git a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs index 8054c36908..dc4f6f313d 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityMapperProfile.cs @@ -22,13 +22,13 @@ namespace Umbraco.Core.Models.Identity public void SetMaps(Mapper mapper) { mapper.Define( - source => + (source, context) => { var target = new BackOfficeIdentityUser(source.Id, source.Groups); target.DisableChangeTracking(); return target; }, - (source, target) => + (source, target, context) => { Map(source, target); target.ResetDirtyProperties(true); diff --git a/src/Umbraco.Core/Security/BackOfficeUserStore.cs b/src/Umbraco.Core/Security/BackOfficeUserStore.cs index eb293df03d..cf50bcf033 100644 --- a/src/Umbraco.Core/Security/BackOfficeUserStore.cs +++ b/src/Umbraco.Core/Security/BackOfficeUserStore.cs @@ -1,16 +1,12 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Common; using System.Linq; using System.Threading.Tasks; using System.Web.Security; -using AutoMapper; using Microsoft.AspNet.Identity; -using Microsoft.Owin; using Umbraco.Core.Configuration; using Umbraco.Core.Exceptions; -using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Identity; using Umbraco.Core.Models.Membership; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 97ac224353..e7203b3be8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -212,6 +212,7 @@ + diff --git a/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs b/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs index 42f64938de..88fb595440 100644 --- a/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs +++ b/src/Umbraco.Web/Composing/CompositionExtensions/WebMappingProfiles.cs @@ -3,7 +3,6 @@ using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Mapping; using Umbraco.Core.Models; -using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Trees; @@ -17,9 +16,13 @@ namespace Umbraco.Web.Composing.CompositionExtensions composition.WithCollectionBuilder() .Append() .Append() + .Append() + .Append() .Append() + .Append() .Append() .Append() + .Append() .Append() .Append() .Append() @@ -32,13 +35,13 @@ namespace Umbraco.Web.Composing.CompositionExtensions //composition.Register(); //composition.Register(); composition.Register(); - composition.Register(); - composition.Register(); + //composition.Register(); + //composition.Register(); //composition.Register(); - composition.Register(); + //composition.Register(); //composition.Register(); //composition.Register(); - composition.Register(); + //composition.Register(); composition.Register(); //composition.Register(); //composition.Register(); @@ -51,10 +54,10 @@ namespace Umbraco.Web.Composing.CompositionExtensions //register any resolvers, etc.. that the profiles use composition.Register(); composition.Register>(); - composition.Register>(); - composition.Register>(); + composition.Register>(); + composition.Register>(); composition.Register>(); - composition.Register(); + composition.Register(); composition.Register(); composition.Register(); composition.Register(); diff --git a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs index fd8951993b..ca7cb2e4e2 100644 --- a/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs +++ b/src/Umbraco.Web/Editors/Binders/ContentItemBinder.cs @@ -53,14 +53,14 @@ namespace Umbraco.Web.Editors.Binders if (variant.Culture.IsNullOrWhiteSpace()) { //map the property dto collection (no culture is passed to the mapping context so it will be invariant) - variant.PropertyCollectionDto = Mapper.Map(model.PersistedContent); + variant.PropertyCollectionDto = Current.Mapper.Map(model.PersistedContent); } else { //map the property dto collection with the culture of the current variant - variant.PropertyCollectionDto = Mapper.Map( + variant.PropertyCollectionDto = Current.Mapper.Map( model.PersistedContent, - options => options.SetCulture(variant.Culture)); + context => context.SetCulture(variant.Culture)); } //now map all of the saved values to the dto diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index fc9ceb82cd..b7bac41eb2 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -503,15 +503,15 @@ namespace Umbraco.Web.Editors var pagedResult = new PagedResult>(totalChildren, pageNumber, pageSize); pagedResult.Items = children.Select(content => - AutoMapper.Mapper.Map>(content, - opts => + Mapper.Map>(content, + context => { - opts.SetCulture(cultureName); + context.SetCulture(cultureName); // if there's a list of property aliases to map - we will make sure to store this in the mapping context. if (!includeProperties.IsNullOrWhiteSpace()) - opts.SetIncludedProperties(includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries)); + context.SetIncludedProperties(includeProperties.Split(new[] { ", ", "," }, StringSplitOptions.RemoveEmptyEntries)); })) .ToList(); // evaluate now diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index fa01fd943d..3af5b0dd5d 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Net; using System.Web.Http; -using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models.Membership; using Umbraco.Web.Models.ContentEditing; @@ -507,13 +506,15 @@ namespace Umbraco.Web.Editors var culture = ClientCulture(); var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { - Items = entities.Select(entity => AutoMapper.Mapper.Map(entity, options => - { - options.SetCulture(culture); - options.AfterMap((src, dest) => { dest.AdditionalData["hasChildren"] = src.HasChildren; }); - } - ) - ) + Items = entities.Select(source => + { + var target = Mapper.Map(source, context => + { + context.SetCulture(culture); + }); + target.AdditionalData["hasChildren"] = source.HasChildren; + return target; + }) }; return pagedResult; @@ -1052,7 +1053,7 @@ namespace Umbraco.Web.Editors private EntityBasic MapEntity(object entity, string culture = null) { culture = culture ?? ClientCulture(); - return AutoMapper.Mapper.Map(entity, opts => { opts.SetCulture(culture); }); + return Mapper.Map(entity, context => { context.SetCulture(culture); }); } private string ClientCulture() => Request.ClientCulture(); diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs index e5e74c2749..96b540d3ba 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentTypeCompositionDisplay.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; -using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Models.ContentEditing { @@ -22,14 +16,6 @@ namespace Umbraco.Web.Models.ContentEditing //name, alias, icon, thumb, desc, inherited from basic - //List view - [DataMember(Name = "isContainer")] - public bool IsContainer { get; set; } - - //Element - [DataMember(Name = "isElement")] - public bool IsElement { get; set; } - [DataMember(Name = "listViewEditorName")] [ReadOnly(true)] public string ListViewEditorName { get; set; } @@ -84,6 +70,5 @@ namespace Umbraco.Web.Models.ContentEditing //Tabs [DataMember(Name = "groups")] public IEnumerable> Groups { get; set; } - } } diff --git a/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs index 6c3288afb5..56bd48ee17 100644 --- a/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/AuditMapperProfile.cs @@ -8,11 +8,11 @@ namespace Umbraco.Web.Models.Mapping { public void SetMaps(Mapper mapper) { - mapper.Define(source => new AuditLog(), (source, target) => Map(source, target)); + mapper.Define(source => new AuditLog(), Map); } // Umbraco.Code.MapAll -UserAvatars -UserName - private AuditLog Map(IAuditItem source, AuditLog target) + private void Map(IAuditItem source, AuditLog target) { target.UserId = source.UserId; target.NodeId = source.Id; @@ -21,7 +21,6 @@ namespace Umbraco.Web.Models.Mapping target.EntityType = source.EntityType; target.Comment = source.Comment; target.Parameters = source.Parameters; - return target; } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs index 5318552eef..0695dcf43d 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentMapperProfile.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Models.Mapping public ContentMapperProfile( ContentUrlResolver contentUrlResolver, ContentTreeNodeUrlResolver contentTreeNodeUrlResolver, - TabsAndPropertiesResolver tabsAndPropertiesResolver, + TabsAndPropertiesMapper tabsAndPropertiesMapper, ContentAppResolver contentAppResolver, IUserService userService, IContentService contentService, @@ -73,7 +73,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.Language, opt => opt.Ignore()) .ForMember(dest => dest.Notifications, opt => opt.Ignore()) .ForMember(dest => dest.State, opt => opt.MapFrom>()) - .ForMember(dest => dest.Tabs, opt => opt.MapFrom(tabsAndPropertiesResolver)); + .ForMember(dest => dest.Tabs, opt => opt.MapFrom(tabsAndPropertiesMapper)); //FROM IContent TO ContentItemBasic CreateMap>() diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs similarity index 81% rename from src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs rename to src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs index effa59fd63..fc20fd3c53 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs @@ -1,29 +1,26 @@ using System; -using System.Collections.Generic; using System.Linq; -using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; -using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; -using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Models.Mapping { /// /// Creates a base generic ContentPropertyBasic from a Property /// - internal class ContentPropertyBasicConverter : ITypeConverter + internal class ContentPropertyBasicMapper where TDestination : ContentPropertyBasic, new() { private readonly ILogger _logger; private readonly PropertyEditorCollection _propertyEditors; protected IDataTypeService DataTypeService { get; } - public ContentPropertyBasicConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyBasicMapper(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) { _logger = logger; _propertyEditors = propertyEditors; @@ -34,12 +31,12 @@ namespace Umbraco.Web.Models.Mapping /// Assigns the PropertyEditor, Id, Alias and Value to the property /// /// - public virtual TDestination Convert(Property property, TDestination dest, ResolutionContext context) + public virtual TDestination Map(Property property, TDestination dest, MapperContext context) { var editor = _propertyEditors[property.PropertyType.PropertyEditorAlias]; if (editor == null) { - _logger.Error>( + _logger.Error>( new NullReferenceException("The property editor with alias " + property.PropertyType.PropertyEditorAlias + " does not exist"), "No property editor '{PropertyEditorAlias}' found, converting to a Label", property.PropertyType.PropertyEditorAlias); @@ -57,12 +54,12 @@ namespace Umbraco.Web.Models.Mapping // if there's a set of property aliases specified, we will check if the current property's value should be mapped. // if it isn't one of the ones specified in 'includeProperties', we will just return the result without mapping the Value. - var includedProperties = context.Options.GetIncludedProperties(); + var includedProperties = context.GetIncludedProperties(); if (includedProperties != null && !includedProperties.Contains(property.Alias)) return result; //Get the culture from the context which will be set during the mapping operation for each property - var culture = context.Options.GetCulture(); + var culture = context.GetCulture(); //a culture needs to be in the context for a property type that can vary if (culture == null && property.PropertyType.VariesByCulture()) diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs similarity index 80% rename from src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs rename to src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs index 84eaabf52b..756fb44dba 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayMapper.cs @@ -1,9 +1,5 @@ -using System; -using System.Linq; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; +using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -14,18 +10,18 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates a ContentPropertyDisplay from a Property /// - internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter + internal class ContentPropertyDisplayMapper : ContentPropertyBasicMapper { private readonly ILocalizedTextService _textService; - public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyDisplayMapper(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) : base(dataTypeService, logger, propertyEditors) { _textService = textService; } - public override ContentPropertyDisplay Convert(Property originalProp, ContentPropertyDisplay dest, ResolutionContext context) + public override ContentPropertyDisplay Map(Property originalProp, ContentPropertyDisplay dest, MapperContext context) { - var display = base.Convert(originalProp, dest, context); + var display = base.Map(originalProp, dest, context); var config = DataTypeService.GetDataType(originalProp.PropertyType.DataTypeId).Configuration; diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs similarity index 60% rename from src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs rename to src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs index b8333b6229..bdb9c16c6a 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoMapper.cs @@ -1,7 +1,5 @@ -using System; -using AutoMapper; -using Umbraco.Core; -using Umbraco.Core.Logging; +using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -12,22 +10,22 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates a ContentPropertyDto from a Property /// - internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter + internal class ContentPropertyDtoMapper : ContentPropertyBasicMapper { - public ContentPropertyDtoConverter(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) + public ContentPropertyDtoMapper(IDataTypeService dataTypeService, ILogger logger, PropertyEditorCollection propertyEditors) : base(dataTypeService, logger, propertyEditors) { } - public override ContentPropertyDto Convert(Property property, ContentPropertyDto dest, ResolutionContext context) + public override ContentPropertyDto Map(Property property, ContentPropertyDto dest, MapperContext context) { - var propertyDto = base.Convert(property, dest, context); + var propertyDto = base.Map(property, dest, context); propertyDto.IsRequired = property.PropertyType.Mandatory; propertyDto.ValidationRegExp = property.PropertyType.ValidationRegExp; propertyDto.Description = property.PropertyType.Description; propertyDto.Label = property.PropertyType.Name; propertyDto.DataType = DataTypeService.GetDataType(property.PropertyType.DataTypeId); - + return propertyDto; } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs index f9ed72fc11..9b4afada34 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyMapperProfile.cs @@ -1,5 +1,5 @@ -using AutoMapper; -using Umbraco.Core.Logging; +using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; @@ -11,30 +11,51 @@ namespace Umbraco.Web.Models.Mapping /// A mapper which declares how to map content properties. These mappings are shared among media (and probably members) which is /// why they are in their own mapper /// - internal class ContentPropertyMapperProfile : Profile + internal class ContentPropertyMapperProfile : IMapperProfile { + private readonly ContentPropertyBasicMapper _contentPropertyBasicConverter; + private readonly ContentPropertyDtoMapper _contentPropertyDtoConverter; + private readonly ContentPropertyDisplayMapper _contentPropertyDisplayMapper; + public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService, ILogger logger, PropertyEditorCollection propertyEditors) { - var contentPropertyBasicConverter = new ContentPropertyBasicConverter(dataTypeService, logger, propertyEditors); - var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService, logger, propertyEditors); - var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService, logger, propertyEditors); + _contentPropertyBasicConverter = new ContentPropertyBasicMapper(dataTypeService, logger, propertyEditors); + _contentPropertyDtoConverter = new ContentPropertyDtoMapper(dataTypeService, logger, propertyEditors); + _contentPropertyDisplayMapper = new ContentPropertyDisplayMapper(dataTypeService, textService, logger, propertyEditors); + } - //FROM Property TO ContentPropertyBasic - CreateMap>() - .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) - .ForMember(tab => tab.IsActive, expression => expression.MapFrom(_ => true)) - .ForMember(tab => tab.Properties, expression => expression.Ignore()) - .ForMember(tab => tab.Alias, expression => expression.Ignore()) - .ForMember(tab => tab.Expanded, expression => expression.Ignore()); + public void SetMaps(Mapper mapper) + { + mapper.Define>((source, context) => new Tab(), Map); + mapper.Define((source, context) => new ContentPropertyBasic(), Map); + mapper.Define((source, context) => new ContentPropertyDto(), Map); + mapper.Define((source, context) => new ContentPropertyDisplay(), Map); + } - //FROM Property TO ContentPropertyBasic - CreateMap().ConvertUsing(contentPropertyBasicConverter); + // Umbraco.Code.MapAll -Properties -Alias -Expanded + private void Map(PropertyGroup source, Tab target, MapperContext mapper) + { + target.Id = source.Id; + target.IsActive = true; + target.Label = source.Name; +} - //FROM Property TO ContentPropertyDto - CreateMap().ConvertUsing(contentPropertyDtoConverter); + private void Map(Property source, ContentPropertyBasic target, MapperContext context) + { + // assume this is mapping everything and no MapAll is required + _contentPropertyBasicConverter.Map(source, target, context); + } - //FROM Property TO ContentPropertyDisplay - CreateMap().ConvertUsing(contentPropertyDisplayConverter); + private void Map(Property source, ContentPropertyDto target, MapperContext context) + { + // assume this is mapping everything and no MapAll is required + _contentPropertyDtoConverter.Map(source, target, context); + } + + private void Map(Property source, ContentPropertyDisplay target, MapperContext context) + { + // assume this is mapping everything and no MapAll is required + _contentPropertyDisplayMapper.Map(source, target, context); } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs index cc2c7b0cb9..48f35f83cb 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeMapperProfile.cs @@ -1,268 +1,667 @@ using System; +using System.Collections.Generic; using System.Linq; -using AutoMapper; using Umbraco.Core; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; using Umbraco.Web.Models.ContentEditing; using Umbraco.Core.Services; -using ContentVariation = Umbraco.Core.Models.ContentVariation; namespace Umbraco.Web.Models.Mapping { /// /// Defines mappings for content/media/members type mappings /// - internal class ContentTypeMapperProfile : Profile + internal class ContentTypeMapperProfile : IMapperProfile { - public ContentTypeMapperProfile(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, ILogger logger) + private readonly PropertyEditorCollection _propertyEditors; + private readonly IDataTypeService _dataTypeService; + private readonly IFileService _fileService; + private readonly IContentTypeService _contentTypeService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IMemberTypeService _memberTypeService; + private readonly ILogger _logger; + + public ContentTypeMapperProfile(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, + IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService, + ILogger logger) { - CreateMap() - //do the base mapping - .MapBaseContentTypeSaveToEntity() - .ConstructUsing((source) => new ContentType(source.ParentId)) - .ForMember(source => source.AllowedTemplates, opt => opt.Ignore()) - .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) - .AfterMap((source, dest) => + _propertyEditors = propertyEditors; + _dataTypeService = dataTypeService; + _fileService = fileService; + _contentTypeService = contentTypeService; + _mediaTypeService = mediaTypeService; + _memberTypeService = memberTypeService; + _logger = logger; + } + + public void SetMaps(Mapper mapper) + { + mapper.Define((source, context) => new ContentType(source.ParentId), Map); + mapper.Define((source, context) => new MediaType(source.ParentId), Map); + mapper.Define((source, context) => new MemberType(source.ParentId), Map); + + mapper.Define((source, context) => new DocumentTypeDisplay(), Map); + mapper.Define((source, context) => new MediaTypeDisplay(), Map); + mapper.Define((source, context) => new MemberTypeDisplay(), Map); + + mapper.Define( + (source, context) => { - dest.AllowedTemplates = source.AllowedTemplates - .Where(x => x != null) - .Select(fileService.GetTemplate) - .Where(x => x != null) - .ToArray(); + var dataType = _dataTypeService.GetDataType(source.DataTypeId); + if (dataType == null) throw new NullReferenceException("No data type found with id " + source.DataTypeId); + return new PropertyType(dataType, source.Alias); + }, Map); - if (source.DefaultTemplate != null) - dest.SetDefaultTemplate(fileService.GetTemplate(source.DefaultTemplate)); - else - dest.SetDefaultTemplate(null); + // TODO: isPublishing in ctor? + mapper.Define, PropertyGroup>((source, context) => new PropertyGroup(false), Map); + mapper.Define, PropertyGroup>((source, context) => new PropertyGroup(false), Map); - ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService); - }); + mapper.Define((source, context) => new ContentTypeBasic(), Map); + mapper.Define((source, context) => new ContentTypeBasic(), Map); + mapper.Define((source, context) => new ContentTypeBasic(), Map); + mapper.Define((source, context) => new ContentTypeBasic(), Map); - CreateMap() - //do the base mapping - .MapBaseContentTypeSaveToEntity() - .ConstructUsing((source) => new MediaType(source.ParentId)) - .AfterMap((source, dest) => + mapper.Define((source, context) => new DocumentTypeDisplay(), Map); + mapper.Define((source, context) => new MediaTypeDisplay(), Map); + mapper.Define((source, context) => new MemberTypeDisplay(), Map); + + mapper.Define, PropertyGroupDisplay>((source, context) => new PropertyGroupDisplay(), Map); + mapper.Define, PropertyGroupDisplay>((source, context) => new PropertyGroupDisplay(), Map); + + mapper.Define((source, context) => new PropertyTypeDisplay(), Map); + mapper.Define((source, context) => new MemberPropertyTypeDisplay(), Map); + } + + // no MapAll - take care + private void Map(DocumentTypeSave source, IContentType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context.Mapper); + MapComposition(source, target, alias => _contentTypeService.Get(alias)); + + target.AllowedTemplates = source.AllowedTemplates + .Where(x => x != null) + .Select(_fileService.GetTemplate) + .Where(x => x != null) + .ToArray(); + + target.SetDefaultTemplate(source.DefaultTemplate == null ? null : _fileService.GetTemplate(source.DefaultTemplate)); + } + + // no MapAll - take care + private void Map(MediaTypeSave source, IMediaType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context.Mapper); + MapComposition(source, target, alias => _mediaTypeService.Get(alias)); + } + + // no MapAll - take care + private void Map(MemberTypeSave source, IMemberType target, MapperContext context) + { + MapSaveToTypeBase(source, target, context.Mapper); + MapComposition(source, target, alias => _memberTypeService.Get(alias)); + + foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) + { + var localCopy = propertyType; + var destProp = target.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); + if (destProp == null) continue; + target.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); + target.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); + target.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); + } + } + + // no MapAll - take care + private void Map(IContentType source, DocumentTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target); + + target.AllowCultureVariant = source.VariesByCulture(); + + //sync templates + target.AllowedTemplates = source.AllowedTemplates.Select(context.Mapper.Map).ToArray(); + + if (source.DefaultTemplate != null) + target.DefaultTemplate = context.Mapper.Map(source.DefaultTemplate); + + //default listview + target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; + + if (string.IsNullOrEmpty(source.Alias)) return; + + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; + if (_dataTypeService.GetDataType(name) != null) + target.ListViewEditorName = name; + } + + // no MapAll - take care + private void Map(IMediaType source, MediaTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target); + + //default listview + target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; + + if (string.IsNullOrEmpty(source.Name)) return; + + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; + if (_dataTypeService.GetDataType(name) != null) + target.ListViewEditorName = name; + } + + // no MapAll - take care + private void Map(IMemberType source, MemberTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target); + + //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData + foreach (var propertyType in source.PropertyTypes) + { + var localCopy = propertyType; + var displayProp = target.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); + if (displayProp == null) continue; + displayProp.MemberCanEditProperty = source.MemberCanEditProperty(localCopy.Alias); + displayProp.MemberCanViewProperty = source.MemberCanViewProperty(localCopy.Alias); + displayProp.IsSensitiveData = source.IsSensitiveProperty(localCopy.Alias); + } + } + + // Umbraco.Code.MapAll -Blueprints + private static void Map(IContentTypeBase source, ContentTypeBasic target, string entityType) + { + target.Udi = Udi.Create(entityType, source.Key); + target.Alias = source.Alias; + target.CreateDate = source.CreateDate; + target.Description = source.Description; + target.Icon = source.Icon; + target.Id = source.Id; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + target.Trashed = source.Trashed; + target.UpdateDate = source.UpdateDate; + } + + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private static void Map(IContentTypeComposition source, ContentTypeBasic target, MapperContext context) + { + Map(source, target, Constants.UdiEntityType.MemberType); + } + + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private static void Map(IContentType source, ContentTypeBasic target, MapperContext context) + { + Map(source, target, Constants.UdiEntityType.DocumentType); + } + + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private static void Map(IMediaType source, ContentTypeBasic target, MapperContext context) + { + Map(source, target, Constants.UdiEntityType.MediaType); + } + + // no MapAll - uses the IContentTypeBase map method, which has MapAll + private static void Map(IMemberType source, ContentTypeBasic target, MapperContext context) + { + Map(source, target, Constants.UdiEntityType.MemberType); + } + + // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate + // Umbraco.Code.MapAll -SupportsPublishing -Key -PropertyEditorAlias -ValueStorageType + private static void Map(PropertyTypeBasic source, PropertyType target, MapperContext context) + { + target.Name = source.Label; + target.DataTypeId = source.DataTypeId; + target.Mandatory = source.Validation.Mandatory; + target.ValidationRegExp = source.Validation.Pattern; + target.Variations = source.AllowCultureVariant ? ContentVariation.Culture : ContentVariation.Nothing; + + if (source.Id > 0) + target.Id = source.Id; + + if (source.GroupId > 0) + target.PropertyGroupId = new Lazy(() => source.GroupId, false); + + target.Alias = source.Alias; + target.Description = source.Description; + target.SortOrder = source.SortOrder; + } + + // no MapAll - take care + private void Map(DocumentTypeSave source, DocumentTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target, context.Mapper); + + //sync templates + var destAllowedTemplateAliases = target.AllowedTemplates.Select(x => x.Alias); + //if the dest is set and it's the same as the source, then don't change + if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) + { + var templates = _fileService.GetTemplates(source.AllowedTemplates.ToArray()); + target.AllowedTemplates = source.AllowedTemplates + .Select(x => context.Mapper.Map(templates.SingleOrDefault(t => t.Alias == x))) + .WhereNotNull() + .ToArray(); + } + + if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) + { + //if the dest is set and it's the same as the source, then don't change + if (target.DefaultTemplate == null || source.DefaultTemplate != target.DefaultTemplate.Alias) { - ContentTypeProfileExtensions.AfterMapMediaTypeSaveToEntity(source, dest, mediaTypeService); - }); + var template = _fileService.GetTemplate(source.DefaultTemplate); + target.DefaultTemplate = template == null ? null : context.Mapper.Map(template); + } + } + else + { + target.DefaultTemplate = null; + } + } - CreateMap() - //do the base mapping - .MapBaseContentTypeSaveToEntity() - .ConstructUsing(source => new MemberType(source.ParentId)) - .AfterMap((source, dest) => + // no MapAll - take care + private void Map(MediaTypeSave source, MediaTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target, context.Mapper); + } + + // no MapAll - take care + private void Map(MemberTypeSave source, MemberTypeDisplay target, MapperContext context) + { + MapTypeToDisplayBase(source, target, context.Mapper); + } + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes + private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context) + { + if (source.Id > 0) + target.Id = source.Id; + target.Name = source.Name; + target.SortOrder = source.SortOrder; + } + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate -Key -PropertyTypes + private static void Map(PropertyGroupBasic source, PropertyGroup target, MapperContext context) + { + if (source.Id > 0) + target.Id = source.Id; + target.Name = source.Name; + target.SortOrder = source.SortOrder; + } + + // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames + private static void Map(PropertyGroupBasic source, PropertyGroupDisplay target, MapperContext context) + { + if (source.Id > 0) + target.Id = source.Id; + + target.Inherited = source.Inherited; + target.Name = source.Name; + target.SortOrder = source.SortOrder; + + target.Properties = source.Properties.Select(context.Mapper.Map); + } + + // Umbraco.Code.MapAll -ContentTypeId -ParentTabContentTypes -ParentTabContentTypeNames + private static void Map(PropertyGroupBasic source, PropertyGroupDisplay target, MapperContext context) + { + if (source.Id > 0) + target.Id = source.Id; + + target.Inherited = source.Inherited; + target.Name = source.Name; + target.SortOrder = source.SortOrder; + + target.Properties = source.Properties.Select(context.Mapper.Map); + } + + // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked + private static void Map(PropertyTypeBasic source, PropertyTypeDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.AllowCultureVariant = source.AllowCultureVariant; + target.DataTypeId = source.DataTypeId; + target.Description = source.Description; + target.GroupId = source.GroupId; + target.Id = source.Id; + target.Inherited = source.Inherited; + target.Label = source.Label; + target.SortOrder = source.SortOrder; + target.Validation = source.Validation; + } + + // Umbraco.Code.MapAll -Editor -View -Config -ContentTypeId -ContentTypeName -Locked + private static void Map(MemberPropertyTypeBasic source, MemberPropertyTypeDisplay target, MapperContext context) + { + target.Alias = source.Alias; + target.AllowCultureVariant = source.AllowCultureVariant; + target.DataTypeId = source.DataTypeId; + target.Description = source.Description; + target.GroupId = source.GroupId; + target.Id = source.Id; + target.Inherited = source.Inherited; + target.IsSensitiveData = source.IsSensitiveData; + target.Label = source.Label; + target.MemberCanEditProperty = source.MemberCanEditProperty; + target.MemberCanViewProperty = source.MemberCanViewProperty; + target.SortOrder = source.SortOrder; + target.Validation = source.Validation; + } + + // Umbraco.Code.MapAll -CreatorId -Level -SortOrder + // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate + // Umbraco.Code.MapAll -ContentTypeComposition (done by AfterMapSaveToType) + private static void MapSaveToTypeBase(TSource source, IContentTypeComposition target, Mapper mapper) + where TSource : ContentTypeSave + where TSourcePropertyType : PropertyTypeBasic + { + // TODO: not so clean really + var isPublishing = target is IContentType; + + var id = Convert.ToInt32(source.Id); + if (id > 0) + target.Id = id; + + target.Alias = source.Alias; + target.Description = source.Description; + target.Icon = source.Icon; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + + target.AllowedAsRoot = source.AllowAsRoot; + target.AllowedContentTypes = source.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)); + + target.Variations = ContentVariation.Nothing; + if (!(target is IMemberType) && source.AllowCultureVariant) + target.Variations |= ContentVariation.Culture; + + // handle property groups and property types + // note that ContentTypeSave has + // - all groups, inherited and local; only *one* occurrence per group *name* + // - potentially including the generic properties group + // - all properties, inherited and local + // + // also, see PropertyTypeGroupResolver.ResolveCore: + // - if a group is local *and* inherited, then Inherited is true + // and the identifier is the identifier of the *local* group + // + // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some + // unique-alias-checking, etc that is *not* compatible with re-mapping everything + // the way we do it here, so we should exclusively do it by + // - managing a property group's PropertyTypes collection + // - managing the content type's PropertyTypes collection (for generic properties) + + // handle actual groups (non-generic-properties) + var destOrigGroups = target.PropertyGroups.ToArray(); // local groups + var destOrigProperties = target.PropertyTypes.ToArray(); // all properties, in groups or not + var destGroups = new List(); + var sourceGroups = source.Groups.Where(x => x.IsGenericProperties == false).ToArray(); + foreach (var sourceGroup in sourceGroups) + { + // get the dest group + var destGroup = MapSaveGroup(sourceGroup, destOrigGroups, mapper); + + // handle local properties + var destProperties = sourceGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties, mapper)) + .ToArray(); + + // if the group has no local properties, skip it, ie sort-of garbage-collect + // local groups which would not have local properties anymore + if (destProperties.Length == 0) + continue; + + // ensure no duplicate alias, then assign the group properties collection + EnsureUniqueAliases(destProperties); + destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + destGroups.Add(destGroup); + } + + // ensure no duplicate name, then assign the groups collection + EnsureUniqueNames(destGroups); + target.PropertyGroups = new PropertyGroupCollection(destGroups); + + // because the property groups collection was rebuilt, there is no need to remove + // the old groups - they are just gone and will be cleared by the repository + + // handle non-grouped (ie generic) properties + var genericPropertiesGroup = source.Groups.FirstOrDefault(x => x.IsGenericProperties); + if (genericPropertiesGroup != null) + { + // handle local properties + var destProperties = genericPropertiesGroup.Properties + .Where(x => x.Inherited == false) + .Select(x => MapSaveProperty(x, destOrigProperties, mapper)) + .ToArray(); + + // ensure no duplicate alias, then assign the generic properties collection + EnsureUniqueAliases(destProperties); + target.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); + } + + // because all property collections were rebuilt, there is no need to remove + // some old properties, they are just gone and will be cleared by the repository + } + + // Umbraco.Code.MapAll -Blueprints -Errors -ListViewEditorName -Trashed + private void MapTypeToDisplayBase(IContentTypeComposition source, ContentTypeCompositionDisplay target) + { + target.Alias = source.Alias; + target.AllowAsRoot = source.AllowedAsRoot; + target.CreateDate = source.CreateDate; + target.Description = source.Description; + target.Icon = source.Icon; + target.Id = source.Id; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + target.Udi = MapContentTypeUdi(source); + target.UpdateDate = source.UpdateDate; + + target.AllowedContentTypes = source.AllowedContentTypes.Select(x => x.Id.Value); + target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias); + target.LockedCompositeContentTypes = MapLockedCompositions(source); + } + + // no MapAll - relies on the non-generic method + private void MapTypeToDisplayBase(IContentTypeComposition source, TTarget target) + where TTarget : ContentTypeCompositionDisplay + where TTargetPropertyType : PropertyTypeDisplay, new() + { + MapTypeToDisplayBase(source, target); + + var groupsMapper = new PropertyTypeGroupMapper(_propertyEditors, _dataTypeService, _logger); + target.Groups = groupsMapper.Map(source); + } + + // Umbraco.Code.MapAll -CreateDate -UpdateDate -ListViewEditorName -Errors -LockedCompositeContentTypes + private void MapTypeToDisplayBase(ContentTypeSave source, ContentTypeCompositionDisplay target) + { + target.Alias = source.Alias; + target.AllowAsRoot = source.AllowAsRoot; + target.AllowedContentTypes = source.AllowedContentTypes; + target.Blueprints = source.Blueprints; + target.CompositeContentTypes = source.CompositeContentTypes; + target.Description = source.Description; + target.Icon = source.Icon; + target.Id = source.Id; + target.IsContainer = source.IsContainer; + target.IsElement = source.IsElement; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Thumbnail = source.Thumbnail; + target.Trashed = source.Trashed; + target.Udi = source.Udi; + } + + // no MapAll - relies on the non-generic method + private void MapTypeToDisplayBase(TSource source, TTarget target, Mapper mapper) + where TSource : ContentTypeSave + where TSourcePropertyType : PropertyTypeBasic + where TTarget : ContentTypeCompositionDisplay + where TTargetPropertyType : PropertyTypeDisplay + { + MapTypeToDisplayBase(source, target); + + target.Groups = source.Groups.Select(mapper.Map>); + } + + private IEnumerable MapLockedCompositions(IContentTypeComposition source) + { + // get ancestor ids from path of parent if not root + if (source.ParentId == Constants.System.Root) + return Enumerable.Empty(); + + var parent = _contentTypeService.Get(source.ParentId); + if (parent == null) + return Enumerable.Empty(); + + var aliases = new List(); + var ancestorIds = parent.Path.Split(',').Select(int.Parse); + // loop through all content types and return ordered aliases of ancestors + var allContentTypes = _contentTypeService.GetAll().ToArray(); + foreach (var ancestorId in ancestorIds) + { + var ancestor = allContentTypes.FirstOrDefault(x => x.Id == ancestorId); + if (ancestor != null) + aliases.Add(ancestor.Alias); + } + return aliases.OrderBy(x => x); + } + + internal static Udi MapContentTypeUdi(IContentTypeComposition source) + { + if (source == null) return null; + + string udiType; + switch (source) + { + case IMemberType _: + udiType = Constants.UdiEntityType.MemberType; + break; + case IMediaType _: + udiType = Constants.UdiEntityType.MediaType; + break; + case IContentType _: + udiType = Constants.UdiEntityType.DocumentType; + break; + default: + throw new Exception("panic"); + } + + return Udi.Create(udiType, source.Key); + } + + private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups, Mapper mapper) + where TPropertyType : PropertyTypeBasic + { + PropertyGroup destGroup; + if (sourceGroup.Id > 0) + { + // update an existing group + // ensure it is still there, then map/update + destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); + if (destGroup != null) { - ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService); + mapper.Map(sourceGroup, destGroup); + return destGroup; + } - //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData - foreach (var propertyType in source.Groups.SelectMany(x => x.Properties)) - { - var localCopy = propertyType; - var destProp = dest.PropertyTypes.SingleOrDefault(x => x.Alias.InvariantEquals(localCopy.Alias)); - if (destProp != null) - { - dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty); - dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty); - dest.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData); - } - } - }); + // force-clear the ID as it does not match anything + sourceGroup.Id = 0; + } - CreateMap().ConvertUsing(dest => dest.Alias); + // insert a new group, or update an existing group that has + // been deleted in the meantime and we need to re-create + // map/create + destGroup = mapper.Map(sourceGroup); + return destGroup; + } - CreateMap() - //map base logic - .MapBaseContentTypeEntityToDisplay(propertyEditors, dataTypeService, contentTypeService, logger) - .AfterMap((memberType, display) => + private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable destOrigProperties, Mapper mapper) + { + PropertyType destProperty; + if (sourceProperty.Id > 0) + { + // updating an existing property + // ensure it is still there, then map/update + destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); + if (destProperty != null) { - //map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData - foreach (var propertyType in memberType.PropertyTypes) - { - var localCopy = propertyType; - var displayProp = display.Groups.SelectMany(dest => dest.Properties).SingleOrDefault(dest => dest.Alias.InvariantEquals(localCopy.Alias)); - if (displayProp != null) - { - displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias); - displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias); - displayProp.IsSensitiveData = memberType.IsSensitiveProperty(localCopy.Alias); - } - } - }); + mapper.Map(sourceProperty, destProperty); + return destProperty; + } - CreateMap() - //map base logic - .MapBaseContentTypeEntityToDisplay(propertyEditors, dataTypeService, contentTypeService, logger) - .AfterMap((source, dest) => - { - //default listview - dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media"; + // force-clear the ID as it does not match anything + sourceProperty.Id = 0; + } - if (string.IsNullOrEmpty(source.Name) == false) - { - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; - if (dataTypeService.GetDataType(name) != null) - dest.ListViewEditorName = name; - } - }); + // insert a new property, or update an existing property that has + // been deleted in the meantime and we need to re-create + // map/create + destProperty = mapper.Map(sourceProperty); + return destProperty; + } - CreateMap() - //map base logic - .MapBaseContentTypeEntityToDisplay(propertyEditors, dataTypeService, contentTypeService, logger) - .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) - .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) - .ForMember(display => display.Notifications, opt => opt.Ignore()) - .ForMember(display => display.AllowCultureVariant, opt => opt.MapFrom(type => type.VariesByCulture())) - .AfterMap((source, dest) => - { - //sync templates - dest.AllowedTemplates = source.AllowedTemplates.Select(Mapper.Map).ToArray(); + private static void EnsureUniqueAliases(IEnumerable properties) + { + var propertiesA = properties.ToArray(); + var distinctProperties = propertiesA + .Select(x => x.Alias.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != propertiesA.Length) + throw new InvalidOperationException("Cannot map properties due to alias conflict."); + } - if (source.DefaultTemplate != null) - dest.DefaultTemplate = Mapper.Map(source.DefaultTemplate); + private static void EnsureUniqueNames(IEnumerable groups) + { + var groupsA = groups.ToArray(); + var distinctProperties = groupsA + .Select(x => x.Name.ToUpperInvariant()) + .Distinct() + .Count(); + if (distinctProperties != groupsA.Length) + throw new InvalidOperationException("Cannot map groups due to name conflict."); + } - //default listview - dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; + private static void MapComposition(ContentTypeSave source, IContentTypeComposition target, Func getContentType) + { + var current = target.CompositionAliases().ToArray(); + var proposed = source.CompositeContentTypes; - if (string.IsNullOrEmpty(source.Alias) == false) - { - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; - if (dataTypeService.GetDataType(name) != null) - dest.ListViewEditorName = name; - } + var remove = current.Where(x => !proposed.Contains(x)); + var add = proposed.Where(x => !current.Contains(x)); - }); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MemberType, source.Key))) - .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MemberType, source.Key))) - .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.MediaType, source.Key))) - .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(source => Udi.Create(Constants.UdiEntityType.DocumentType, source.Key))) - .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - - CreateMap() - - .ConstructUsing((propertyTypeBasic, context) => - { - var dataType = dataTypeService.GetDataType(propertyTypeBasic.DataTypeId); - if (dataType == null) throw new NullReferenceException("No data type found with id " + propertyTypeBasic.DataTypeId); - return new PropertyType(dataType, propertyTypeBasic.Alias); - }) - - .IgnoreEntityCommonProperties() - - .ForMember(dest => dest.SupportsPublishing, opt => opt.Ignore()) - - // see note above - have to do this here? - .ForMember(dest => dest.PropertyEditorAlias, opt => opt.Ignore()) - .ForMember(dest => dest.DeleteDate, opt => opt.Ignore()) - - .ForMember(dto => dto.Variations, opt => opt.MapFrom()) - - //only map if it is actually set - .ForMember(dest => dest.Id, opt => opt.Condition(source => source.Id > 0)) - //only map if it is actually set, if it's not set, it needs to be handled differently and will be taken care of in the - // IContentType.AddPropertyType - .ForMember(dest => dest.PropertyGroupId, opt => opt.Condition(source => source.GroupId > 0)) - .ForMember(dest => dest.PropertyGroupId, opt => opt.MapFrom(display => new Lazy(() => display.GroupId, false))) - .ForMember(dest => dest.Key, opt => opt.Ignore()) - .ForMember(dest => dest.HasIdentity, opt => opt.Ignore()) - //ignore because this is set in the ctor NOT ON UPDATE, STUPID! - //.ForMember(type => type.Alias, opt => opt.Ignore()) - //ignore because this is obsolete and shouldn't be used - .ForMember(dest => dest.DataTypeId, opt => opt.Ignore()) - .ForMember(dest => dest.Mandatory, opt => opt.MapFrom(source => source.Validation.Mandatory)) - .ForMember(dest => dest.ValidationRegExp, opt => opt.MapFrom(source => source.Validation.Pattern)) - .ForMember(dest => dest.DataTypeId, opt => opt.MapFrom(source => source.DataTypeId)) - .ForMember(dest => dest.Name, opt => opt.MapFrom(source => source.Label)); - - #region *** Used for mapping on top of an existing display object from a save object *** - - CreateMap() - .MapBaseContentTypeSaveToDisplay(); - - CreateMap() - .MapBaseContentTypeSaveToDisplay(); - - CreateMap() - .MapBaseContentTypeSaveToDisplay() - .ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore()) - .ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore()) - .AfterMap((source, dest) => - { - //sync templates - var destAllowedTemplateAliases = dest.AllowedTemplates.Select(x => x.Alias); - //if the dest is set and it's the same as the source, then don't change - if (destAllowedTemplateAliases.SequenceEqual(source.AllowedTemplates) == false) - { - var templates = fileService.GetTemplates(source.AllowedTemplates.ToArray()); - dest.AllowedTemplates = source.AllowedTemplates - .Select(x => Mapper.Map(templates.SingleOrDefault(t => t.Alias == x))) - .WhereNotNull() - .ToArray(); - } - - if (source.DefaultTemplate.IsNullOrWhiteSpace() == false) - { - //if the dest is set and it's the same as the source, then don't change - if (dest.DefaultTemplate == null || source.DefaultTemplate != dest.DefaultTemplate.Alias) - { - var template = fileService.GetTemplate(source.DefaultTemplate); - dest.DefaultTemplate = template == null ? null : Mapper.Map(template); - } - } - else - { - dest.DefaultTemplate = null; - } - }); - - //for doc types, media types - CreateMap, PropertyGroup>() - .MapPropertyGroupBasicToPropertyGroupPersistence, PropertyTypeBasic>(); - - //for members - CreateMap, PropertyGroup>() - .MapPropertyGroupBasicToPropertyGroupPersistence, MemberPropertyTypeBasic>(); - - //for doc types, media types - CreateMap, PropertyGroupDisplay>() - .MapPropertyGroupBasicToPropertyGroupDisplay, PropertyTypeBasic, PropertyTypeDisplay>(); - - //for members - CreateMap, PropertyGroupDisplay>() - .MapPropertyGroupBasicToPropertyGroupDisplay, MemberPropertyTypeBasic, MemberPropertyTypeDisplay>(); - - CreateMap() - .ForMember(g => g.Editor, opt => opt.Ignore()) - .ForMember(g => g.View, opt => opt.Ignore()) - .ForMember(g => g.Config, opt => opt.Ignore()) - .ForMember(g => g.ContentTypeId, opt => opt.Ignore()) - .ForMember(g => g.ContentTypeName, opt => opt.Ignore()) - .ForMember(g => g.Locked, exp => exp.Ignore()); - - CreateMap() - .ForMember(g => g.Editor, opt => opt.Ignore()) - .ForMember(g => g.View, opt => opt.Ignore()) - .ForMember(g => g.Config, opt => opt.Ignore()) - .ForMember(g => g.ContentTypeId, opt => opt.Ignore()) - .ForMember(g => g.ContentTypeName, opt => opt.Ignore()) - .ForMember(g => g.Locked, exp => exp.Ignore()); - - #endregion + foreach (var alias in remove) + target.RemoveContentType(alias); + foreach (var alias in add) + { + // TODO: Remove N+1 lookup + var contentType = getContentType(alias); + if (contentType != null) + target.AddContentType(contentType); + } } } } diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs deleted file mode 100644 index 9ec8e572b3..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeProfileExtensions.cs +++ /dev/null @@ -1,342 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AutoMapper; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; -using Umbraco.Web.Models.ContentEditing; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Used as a shared way to do the underlying mapping for content types base classes - /// - /// - /// We used to use 'Include' Automapper inheritance functionality and although this works, the unit test - /// to assert mappings fails which is an Automapper bug. So instead we will use an extension method for the mappings - /// to re-use mappings. - /// - internal static class ContentTypeProfileExtensions - { - public static IMappingExpression MapPropertyGroupBasicToPropertyGroupPersistence( - this IMappingExpression mapping) - where TSource : PropertyGroupBasic - where TPropertyTypeBasic : PropertyTypeBasic - { - return mapping - .ConstructUsing(x => new PropertyGroup(false)) // TODO: we have NO idea of isPublishing here = so what? - .IgnoreEntityCommonProperties() - .ForMember(dest => dest.Id, map => map.Condition(src => src.Id > 0)) - .ForMember(dest => dest.Key, map => map.Ignore()) - .ForMember(dest => dest.HasIdentity, map => map.Ignore()) - .ForMember(dest => dest.DeleteDate, map => map.Ignore()) - .ForMember(dest => dest.PropertyTypes, map => map.Ignore()); - } - - public static IMappingExpression> MapPropertyGroupBasicToPropertyGroupDisplay( - this IMappingExpression> mapping) - where TSource : PropertyGroupBasic - where TPropertyTypeBasic : PropertyTypeBasic - where TPropertyTypeDisplay : PropertyTypeDisplay - { - return mapping - .ForMember(dest => dest.Id, opt => opt.Condition(src => src.Id > 0)) - .ForMember(dest => dest.ContentTypeId, opt => opt.Ignore()) - .ForMember(dest => dest.ParentTabContentTypes, opt => opt.Ignore()) - .ForMember(dest => dest.ParentTabContentTypeNames, opt => opt.Ignore()) - .ForMember(dest => dest.Properties, opt => opt.MapFrom(src => src.Properties.Select(Mapper.Map))); - } - - public static void AfterMapContentTypeSaveToEntity(TSource source, TDestination dest, IContentTypeService contentTypeService) - where TSource : ContentTypeSave - where TDestination : IContentTypeComposition - { - //sync compositions - var current = dest.CompositionAliases().ToArray(); - var proposed = source.CompositeContentTypes; - - var remove = current.Where(x => proposed.Contains(x) == false); - var add = proposed.Where(x => current.Contains(x) == false); - - foreach (var rem in remove) - { - dest.RemoveContentType(rem); - } - - foreach (var a in add) - { - // TODO: Remove N+1 lookup - var addCt = contentTypeService.Get(a); - if (addCt != null) - dest.AddContentType(addCt); - } - } - - public static void AfterMapMediaTypeSaveToEntity(TSource source, TDestination dest, IMediaTypeService mediaTypeService) - where TSource : MediaTypeSave - where TDestination : IContentTypeComposition - { - //sync compositions - var current = dest.CompositionAliases().ToArray(); - var proposed = source.CompositeContentTypes; - - var remove = current.Where(x => proposed.Contains(x) == false); - var add = proposed.Where(x => current.Contains(x) == false); - - foreach (var rem in remove) - { - dest.RemoveContentType(rem); - } - - foreach (var a in add) - { - // TODO: Remove N+1 lookup - var addCt = mediaTypeService.Get(a); - if (addCt != null) - dest.AddContentType(addCt); - } - } - - public static IMappingExpression MapBaseContentTypeSaveToDisplay( - this IMappingExpression mapping) - where TSource : ContentTypeSave - where TDestination : ContentTypeCompositionDisplay - where TPropertyTypeDestination : PropertyTypeDisplay - where TPropertyTypeSource : PropertyTypeBasic - { - var propertyGroupDisplayResolver = new PropertyGroupDisplayResolver(); - - return mapping - .ForMember(dest => dest.CreateDate, opt => opt.Ignore()) - .ForMember(dest => dest.UpdateDate, opt => opt.Ignore()) - .ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore()) - .ForMember(dest => dest.Notifications, opt => opt.Ignore()) - .ForMember(dest => dest.Errors, opt => opt.Ignore()) - .ForMember(dest => dest.LockedCompositeContentTypes, opt => opt.Ignore()) - .ForMember(dest => dest.Groups, opt => opt.MapFrom(src => propertyGroupDisplayResolver.Resolve(src))); - } - - public static IMappingExpression MapBaseContentTypeEntityToDisplay( - this IMappingExpression mapping, PropertyEditorCollection propertyEditors, - IDataTypeService dataTypeService, IContentTypeService contentTypeService, ILogger logger) - where TSource : IContentTypeComposition - where TDestination : ContentTypeCompositionDisplay - where TPropertyTypeDisplay : PropertyTypeDisplay, new() - { - var contentTypeUdiResolver = new ContentTypeUdiResolver(); - var lockedCompositionsResolver = new LockedCompositionsResolver(contentTypeService); - var propertyTypeGroupResolver = new PropertyTypeGroupResolver(propertyEditors, dataTypeService, logger); - - return mapping - .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => contentTypeUdiResolver.Resolve(src))) - .ForMember(dest => dest.Notifications, opt => opt.Ignore()) - .ForMember(dest => dest.Blueprints, opt => opt.Ignore()) - .ForMember(dest => dest.Errors, opt => opt.Ignore()) - .ForMember(dest => dest.AllowAsRoot, opt => opt.MapFrom(src => src.AllowedAsRoot)) - .ForMember(dest => dest.ListViewEditorName, opt => opt.Ignore()) - //Ignore because this is not actually used for content types - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - - .ForMember(dest => dest.AllowedContentTypes, opt => opt.MapFrom(src => src.AllowedContentTypes.Select(x => x.Id.Value))) - .ForMember(dest => dest.CompositeContentTypes, opt => opt.MapFrom(src => src.ContentTypeComposition)) - .ForMember(dest => dest.LockedCompositeContentTypes, opt => opt.MapFrom(src => lockedCompositionsResolver.Resolve(src))) - .ForMember(dest => dest.Groups, opt => opt.MapFrom(src => propertyTypeGroupResolver.Resolve(src))) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - } - - /// - /// Display -> Entity class base mapping logic - /// - /// - /// - /// - /// - /// - public static IMappingExpression MapBaseContentTypeSaveToEntity( - this IMappingExpression mapping) - //where TSource : ContentTypeCompositionDisplay - where TSource : ContentTypeSave - where TDestination : IContentTypeComposition - where TSourcePropertyType : PropertyTypeBasic - { - // TODO: not so clean really - var isPublishing = typeof(IContentType).IsAssignableFrom(typeof(TDestination)); - - mapping = mapping - //only map id if set to something higher then zero - .ForMember(dest => dest.Id, opt => opt.Condition(src => (Convert.ToInt32(src.Id) > 0))) - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => Convert.ToInt32(src.Id))) - - //These get persisted as part of the saving procedure, nothing to do with the display model - .IgnoreEntityCommonProperties() - - .ForMember(dest => dest.AllowedAsRoot, opt => opt.MapFrom(src => src.AllowAsRoot)) - .ForMember(dest => dest.CreatorId, opt => opt.Ignore()) - .ForMember(dest => dest.Level, opt => opt.Ignore()) - .ForMember(dest => dest.SortOrder, opt => opt.Ignore()) - //ignore, we'll do this in after map - .ForMember(dest => dest.PropertyGroups, opt => opt.Ignore()) - .ForMember(dest => dest.NoGroupPropertyTypes, opt => opt.Ignore()) - // ignore, composition is managed in AfterMapContentTypeSaveToEntity - .ForMember(dest => dest.ContentTypeComposition, opt => opt.Ignore()); - - // ignore for members - mapping = typeof(TDestination) == typeof(IMemberType) - ? mapping.ForMember(dto => dto.Variations, opt => opt.Ignore()) - : mapping.ForMember(dto => dto.Variations, opt => opt.MapFrom>()); - - mapping = mapping - .ForMember( - dest => dest.AllowedContentTypes, - opt => opt.MapFrom(src => src.AllowedContentTypes.Select((t, i) => new ContentTypeSort(t, i)))) - - .AfterMap((src, dest) => - { - // handle property groups and property types - // note that ContentTypeSave has - // - all groups, inherited and local; only *one* occurrence per group *name* - // - potentially including the generic properties group - // - all properties, inherited and local - // - // also, see PropertyTypeGroupResolver.ResolveCore: - // - if a group is local *and* inherited, then Inherited is true - // and the identifier is the identifier of the *local* group - // - // IContentTypeComposition AddPropertyGroup, AddPropertyType methods do some - // unique-alias-checking, etc that is *not* compatible with re-mapping everything - // the way we do it here, so we should exclusively do it by - // - managing a property group's PropertyTypes collection - // - managing the content type's PropertyTypes collection (for generic properties) - - // handle actual groups (non-generic-properties) - var destOrigGroups = dest.PropertyGroups.ToArray(); // local groups - var destOrigProperties = dest.PropertyTypes.ToArray(); // all properties, in groups or not - var destGroups = new List(); - var sourceGroups = src.Groups.Where(x => x.IsGenericProperties == false).ToArray(); - foreach (var sourceGroup in sourceGroups) - { - // get the dest group - var destGroup = MapSaveGroup(sourceGroup, destOrigGroups); - - // handle local properties - var destProperties = sourceGroup.Properties - .Where(x => x.Inherited == false) - .Select(x => MapSaveProperty(x, destOrigProperties)) - .ToArray(); - - // if the group has no local properties, skip it, ie sort-of garbage-collect - // local groups which would not have local properties anymore - if (destProperties.Length == 0) - continue; - - // ensure no duplicate alias, then assign the group properties collection - EnsureUniqueAliases(destProperties); - destGroup.PropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); - destGroups.Add(destGroup); - } - - // ensure no duplicate name, then assign the groups collection - EnsureUniqueNames(destGroups); - dest.PropertyGroups = new PropertyGroupCollection(destGroups); - - // because the property groups collection was rebuilt, there is no need to remove - // the old groups - they are just gone and will be cleared by the repository - - // handle non-grouped (ie generic) properties - var genericPropertiesGroup = src.Groups.FirstOrDefault(x => x.IsGenericProperties); - if (genericPropertiesGroup != null) - { - // handle local properties - var destProperties = genericPropertiesGroup.Properties - .Where(x => x.Inherited == false) - .Select(x => MapSaveProperty(x, destOrigProperties)) - .ToArray(); - - // ensure no duplicate alias, then assign the generic properties collection - EnsureUniqueAliases(destProperties); - dest.NoGroupPropertyTypes = new PropertyTypeCollection(isPublishing, destProperties); - } - - // because all property collections were rebuilt, there is no need to remove - // some old properties, they are just gone and will be cleared by the repository - }); - - return mapping; - } - - private static PropertyGroup MapSaveGroup(PropertyGroupBasic sourceGroup, IEnumerable destOrigGroups) - where TPropertyType : PropertyTypeBasic - { - PropertyGroup destGroup; - if (sourceGroup.Id > 0) - { - // update an existing group - // ensure it is still there, then map/update - destGroup = destOrigGroups.FirstOrDefault(x => x.Id == sourceGroup.Id); - if (destGroup != null) - { - Mapper.Map(sourceGroup, destGroup); - return destGroup; - } - - // force-clear the ID as it does not match anything - sourceGroup.Id = 0; - } - - // insert a new group, or update an existing group that has - // been deleted in the meantime and we need to re-create - // map/create - destGroup = Mapper.Map(sourceGroup); - return destGroup; - } - - private static PropertyType MapSaveProperty(PropertyTypeBasic sourceProperty, IEnumerable destOrigProperties) - { - PropertyType destProperty; - if (sourceProperty.Id > 0) - { - // updating an existing property - // ensure it is still there, then map/update - destProperty = destOrigProperties.FirstOrDefault(x => x.Id == sourceProperty.Id); - if (destProperty != null) - { - Mapper.Map(sourceProperty, destProperty); - return destProperty; - } - - // force-clear the ID as it does not match anything - sourceProperty.Id = 0; - } - - // insert a new property, or update an existing property that has - // been deleted in the meantime and we need to re-create - // map/create - destProperty = Mapper.Map(sourceProperty); - return destProperty; - } - - private static void EnsureUniqueAliases(IEnumerable properties) - { - var propertiesA = properties.ToArray(); - var distinctProperties = propertiesA - .Select(x => x.Alias.ToUpperInvariant()) - .Distinct() - .Count(); - if (distinctProperties != propertiesA.Length) - throw new InvalidOperationException("Cannot map properties due to alias conflict."); - } - - private static void EnsureUniqueNames(IEnumerable groups) - { - var groupsA = groups.ToArray(); - var distinctProperties = groupsA - .Select(x => x.Name.ToUpperInvariant()) - .Distinct() - .Count(); - if (distinctProperties != groupsA.Length) - throw new InvalidOperationException("Cannot map groups due to name conflict."); - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs deleted file mode 100644 index b819a38064..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeUdiResolver.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Core; -using Umbraco.Core.Models; - -namespace Umbraco.Web.Models.Mapping -{ - /// - /// Resolves a UDI for a content type based on it's type - /// - internal class ContentTypeUdiResolver - { - public Udi Resolve(IContentTypeComposition source) - { - if (source == null) return null; - - return Udi.Create( - source.GetType() == typeof(IMemberType) - ? Constants.UdiEntityType.MemberType - : source.GetType() == typeof(IMediaType) - ? Constants.UdiEntityType.MediaType : Constants.UdiEntityType.DocumentType, source.Key); - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs deleted file mode 100644 index e25568868f..0000000000 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeVariationsResolver.cs +++ /dev/null @@ -1,24 +0,0 @@ -using AutoMapper; -using Umbraco.Core.Models; -using Umbraco.Web.Models.ContentEditing; -using ContentVariation = Umbraco.Core.Models.ContentVariation; - -namespace Umbraco.Web.Models.Mapping -{ - internal class ContentTypeVariationsResolver : IValueResolver - where TSource : ContentTypeSave - where TDestination : IContentTypeComposition - where TSourcePropertyType : PropertyTypeBasic - { - public ContentVariation Resolve(TSource source, TDestination destination, ContentVariation destMember, ResolutionContext context) - { - //this will always be the case, a content type will always be allowed to be invariant - var result = ContentVariation.Nothing; - - if (source.AllowCultureVariant) - result |= ContentVariation.Culture; - - return result; - } - } -} diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs index b13888ba9d..eebadf2830 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeMapperProfile.cs @@ -31,18 +31,18 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { - mapper.Define(source => new PropertyEditorBasic(), Map); - mapper.Define(source => new DataTypeConfigurationFieldDisplay(), Map); - mapper.Define(source => new DataTypeBasic(), Map); - mapper.Define(source => new DataTypeBasic(), Map); - mapper.Define(source => new DataTypeDisplay(), (source, target) => Map(source, target, mapper)); - mapper.Define>(source => MapPreValues(source, mapper)); + mapper.Define((source, context) => new PropertyEditorBasic(), Map); + mapper.Define((source, context) => new DataTypeConfigurationFieldDisplay(), Map); + mapper.Define((source, context) => new DataTypeBasic(), Map); + mapper.Define((source, context) => new DataTypeBasic(), Map); + mapper.Define((source, context) => new DataTypeDisplay(), Map); + mapper.Define>((source, context) => MapPreValues(source, mapper)); mapper.Define(Map); - mapper.Define>(source => MapPreValues(source, mapper)); + mapper.Define>((source, context) => MapPreValues(source, mapper)); } // Umbraco.Code.MapAll - private static void Map(IDataEditor source, PropertyEditorBasic target) + private static void Map(IDataEditor source, PropertyEditorBasic target, MapperContext context) { target.Alias = source.Alias; target.Icon = source.Icon; @@ -50,7 +50,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -Value - private static void Map(ConfigurationField source, DataTypeConfigurationFieldDisplay target) + private static void Map(ConfigurationField source, DataTypeConfigurationFieldDisplay target, MapperContext context) { target.Config = source.Config; target.Description = source.Description; @@ -62,7 +62,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -Udi -HasPrevalues -IsSystemDataType -Id -Trashed -Key // Umbraco.Code.MapAll -ParentId -Path - private static void Map(IDataEditor source, DataTypeBasic target) + private static void Map(IDataEditor source, DataTypeBasic target, MapperContext context) { target.Alias = source.Alias; target.Group = source.Group; @@ -71,7 +71,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -HasPrevalues - private void Map(IDataType source, DataTypeBasic target) + private void Map(IDataType source, DataTypeBasic target, MapperContext context) { target.Id = source.Id; target.IsSystemDataType = SystemIds.Contains(source.Id); @@ -91,16 +91,16 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -HasPrevalues - private void Map(IDataType source, DataTypeDisplay target, Mapper mapper) + private void Map(IDataType source, DataTypeDisplay target, MapperContext context) { - target.AvailableEditors = MapAvailableEditors(source, mapper); + target.AvailableEditors = MapAvailableEditors(source, context.Mapper); target.Id = source.Id; target.IsSystemDataType = SystemIds.Contains(source.Id); target.Key = source.Key; target.Name = source.Name; target.ParentId = source.ParentId; target.Path = source.Path; - target.PreValues = MapPreValues(source, mapper); + target.PreValues = MapPreValues(source, context.Mapper); target.SelectedEditor = source.EditorAlias.IsNullOrWhiteSpace() ? null : source.EditorAlias; target.Trashed = source.Trashed; target.Udi = Udi.Create(Constants.UdiEntityType.DataType, source.Key); @@ -115,7 +115,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -CreateDate -DeleteDate -UpdateDate // Umbraco.Code.MapAll -Key -Path -CreatorId -Level -SortOrder -Configuration - private void Map(DataTypeSave source, IDataType target) + private void Map(DataTypeSave source, IDataType target, MapperContext context) { target.DatabaseType = MapDatabaseType(source); target.Editor = _propertyEditors[source.EditorAlias]; diff --git a/src/Umbraco.Web/Models/Mapping/DictionaryMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/DictionaryMapperProfile.cs index c9570e13e6..39c302ba0d 100644 --- a/src/Umbraco.Web/Models/Mapping/DictionaryMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/DictionaryMapperProfile.cs @@ -24,12 +24,13 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { - mapper.Define(source => new EntityBasic(), Map); - mapper.Define(source => new DictionaryDisplay(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new DictionaryDisplay(), Map); + mapper.Define((source, context) => new DictionaryOverviewDisplay(), Map); } // Umbraco.Code.MapAll -ParentId -Path -Trashed -Udi -Icon - private static void Map(IDictionaryItem source, EntityBasic target) + private static void Map(IDictionaryItem source, EntityBasic target, MapperContext context) { target.Alias = source.ItemKey; target.Id = source.Id; @@ -38,7 +39,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -Icon -Trashed -Alias - private void Map(IDictionaryItem source, DictionaryDisplay target) + private void Map(IDictionaryItem source, DictionaryDisplay target, MapperContext context) { target.Id = source.Id; target.Key = source.Key; @@ -80,7 +81,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -Level -Translations - private void Map(IDictionaryItem source, DictionaryOverviewDisplay target) + private void Map(IDictionaryItem source, DictionaryOverviewDisplay target, MapperContext context) { target.Id = source.Id; target.Name = source.ItemKey; diff --git a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs index 5b711d7251..7ff923c427 100644 --- a/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/EntityMapperProfile.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; using Examine; using Examine.LuceneEngine.Providers; -using Examine.LuceneEngine; using Umbraco.Core; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Models.Membership; @@ -14,203 +13,219 @@ using Umbraco.Examine; namespace Umbraco.Web.Models.Mapping { - internal class EntityMapperProfile : Profile + internal class EntityMapperProfile : IMapperProfile { - private static string GetContentTypeIcon(IEntitySlim entity) - => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; - - public EntityMapperProfile() + public void SetMaps(Mapper mapper) { - // create, capture, cache - var contentTypeUdiResolver = new ContentTypeUdiResolver(); - - CreateMap() - .ForMember(dest => dest.Name, opt => opt.MapFrom()) - .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key))) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => GetContentTypeIcon(src))) - .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .AfterMap((src, dest) => - { - if (src.NodeObjectType == Constants.ObjectTypes.Member && dest.Icon.IsNullOrWhiteSpace()) - { - dest.Icon = "icon-user"; - } - - dest.AdditionalData.Add("IsContainer", src.IsContainer); - }); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.Ignore()) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-box")) - .ForMember(dest => dest.Path, opt => opt.MapFrom(_ => "")) - .ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1)) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.Ignore()) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-tab")) - .ForMember(dest => dest.Path, opt => opt.MapFrom(_ => "")) - .ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1)) - //in v6 the 'alias' is it's lower cased name so we'll stick to that. - .ForMember(dest => dest.Alias, opt => opt.MapFrom(src => src.Name.ToLowerInvariant())) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.Ignore()) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-user")) - .ForMember(dest => dest.Path, opt => opt.MapFrom(_ => "")) - .ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1)) - .ForMember(dest => dest.Alias, opt => opt.MapFrom(src => src.Username)) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Template, src.Key))) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(_ => "icon-layout")) - .ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path)) - .ForMember(dest => dest.ParentId, opt => opt.MapFrom(_ => -1)) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => new Lazy(() => Convert.ToInt32(src.Id)))) - .ForMember(dest => dest.SortOrder, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => contentTypeUdiResolver.Resolve(src))) - .ForMember(dest => dest.Path, opt => opt.MapFrom(src => src.Path)) - .ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentId)) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); - - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(ObjectTypes.GetUdiType(src.NodeObjectType), src.Key))) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(src=> GetContentTypeIcon(src))) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .ForMember(dest => dest.Score, opt => opt.Ignore()) - .AfterMap((entity, basic) => - { - if (basic.Icon.IsNullOrWhiteSpace()) - { - if (entity.NodeObjectType == Constants.ObjectTypes.Member) - basic.Icon = "icon-user"; - else if (entity.NodeObjectType == Constants.ObjectTypes.DataType) - basic.Icon = "icon-autofill"; - else if (entity.NodeObjectType == Constants.ObjectTypes.DocumentType) - basic.Icon = "icon-item-arrangement"; - else if (entity.NodeObjectType == Constants.ObjectTypes.MediaType) - basic.Icon = "icon-thumbnails"; - else if (entity.NodeObjectType == Constants.ObjectTypes.TemplateType) - basic.Icon = "icon-newspaper-alt"; - } - }); - - CreateMap() - //default to document icon - .ForMember(dest => dest.Score, opt => opt.MapFrom(result => result.Score)) - .ForMember(dest => dest.Udi, opt => opt.Ignore()) - .ForMember(dest => dest.Icon, opt => opt.Ignore()) - .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Id)) - .ForMember(dest => dest.Name, opt => opt.Ignore()) - .ForMember(dest => dest.Key, opt => opt.Ignore()) - .ForMember(dest => dest.ParentId, opt => opt.Ignore()) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .ForMember(dest => dest.Path, opt => opt.Ignore()) - .ForMember(dest => dest.Trashed, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) - .AfterMap((src, dest) => - { - // TODO: Properly map this (not aftermap) - - //get the icon if there is one - dest.Icon = src.Values.ContainsKey(UmbracoExamineIndex.IconFieldName) - ? src.Values[UmbracoExamineIndex.IconFieldName] - : "icon-document"; - - dest.Name = src.Values.ContainsKey("nodeName") ? src.Values["nodeName"] : "[no name]"; - if (src.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName)) - { - Guid key; - if (Guid.TryParse(src.Values[UmbracoExamineIndex.NodeKeyFieldName], out key)) - { - dest.Key = key; - - //need to set the UDI - if (src.Values.ContainsKey(LuceneIndex.CategoryFieldName)) - { - switch (src.Values[LuceneIndex.CategoryFieldName]) - { - case IndexTypes.Member: - dest.Udi = new GuidUdi(Constants.UdiEntityType.Member, dest.Key); - break; - case IndexTypes.Content: - dest.Udi = new GuidUdi(Constants.UdiEntityType.Document, dest.Key); - break; - case IndexTypes.Media: - dest.Udi = new GuidUdi(Constants.UdiEntityType.Media, dest.Key); - break; - } - } - } - } - - if (src.Values.ContainsKey("parentID")) - { - int parentId; - if (int.TryParse(src.Values["parentID"], out parentId)) - { - dest.ParentId = parentId; - } - else - { - dest.ParentId = -1; - } - } - dest.Path = src.Values.ContainsKey(UmbracoExamineIndex.IndexPathFieldName) ? src.Values[UmbracoExamineIndex.IndexPathFieldName] : ""; - - if (src.Values.ContainsKey(LuceneIndex.ItemTypeFieldName)) - { - dest.AdditionalData.Add("contentType", src.Values[LuceneIndex.ItemTypeFieldName]); - } - }); - - CreateMap>() - .ConvertUsing(results => results.Select(Mapper.Map).ToList()); - - CreateMap, IEnumerable>() - .ConvertUsing(results => results.Select(Mapper.Map).ToList()); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new ContentTypeSort(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new SearchResultEntity(), Map); + mapper.Define((source, context) => new SearchResultEntity(), Map); + mapper.Define>((source, context) => source.Select(context.Mapper.Map).ToList()); + mapper.Define, IEnumerable>((source, context) => source.Select(context.Mapper.Map).ToList()); } - /// - /// Resolves the name for a content item/content variant - /// - private class NameResolver : IValueResolver + // Umbraco.Code.MapAll -Alias + private static void Map(IEntitySlim source, EntityBasic target, MapperContext context) { - public string Resolve(IEntitySlim source, EntityBasic destination, string destMember, ResolutionContext context) + target.Icon = MapContentTypeIcon(source); + target.Id = source.Id; + target.Key = source.Key; + target.Name = MapName(source, context); + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Trashed = source.Trashed; + target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); + + if (source.NodeObjectType == Constants.ObjectTypes.Member && target.Icon.IsNullOrWhiteSpace()) + target.Icon = "icon-user"; + + target.AdditionalData.Add("IsContainer", source.IsContainer); + } + + // Umbraco.Code.MapAll -Udi -Trashed + private static void Map(PropertyType source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = "icon-box"; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = ""; + } + + // Umbraco.Code.MapAll -Udi -Trashed + private static void Map(PropertyGroup source, EntityBasic target, MapperContext context) + { + target.Alias = source.Name.ToLowerInvariant(); + target.Icon = "icon-tab"; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = ""; + } + + // Umbraco.Code.MapAll -Udi -Trashed + private static void Map(IUser source, EntityBasic target, MapperContext context) + { + target.Alias = source.Username; + target.Icon = "icon-user"; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = ""; + } + + // Umbraco.Code.MapAll -Trashed + private static void Map(ITemplate source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = "icon-layout"; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = -1; + target.Path = source.Path; + target.Udi = Udi.Create(Constants.UdiEntityType.Template, source.Key); + } + + // Umbraco.Code.MapAll -SortOrder + private static void Map(EntityBasic source, ContentTypeSort target, MapperContext context) + { + target.Alias = source.Alias; + target.Id = new Lazy(() => Convert.ToInt32(source.Id)); + } + + // Umbraco.Code.MapAll -Trashed + private static void Map(IContentTypeComposition source, EntityBasic target, MapperContext context) + { + target.Alias = source.Alias; + target.Icon = source.Icon; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Udi = ContentTypeMapperProfile.MapContentTypeUdi(source); + } + + // Umbraco.Code.MapAll -Trashed -Alias -Score + private static void Map(EntitySlim source, SearchResultEntity target, MapperContext context) + { + target.Icon = MapContentTypeIcon(source); + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Udi = Udi.Create(ObjectTypes.GetUdiType(source.NodeObjectType), source.Key); + + if (target.Icon.IsNullOrWhiteSpace()) { - if (!(source is DocumentEntitySlim doc)) - return source.Name; - - // invariant = only 1 name - if (!doc.Variations.VariesByCulture()) return source.Name; - - // variant = depends on culture - var culture = context.Options.GetCulture(); - - // if there's no culture here, the issue is somewhere else (UI, whatever) - throw! - if (culture == null) - //throw new InvalidOperationException("Missing culture in mapping options."); - // TODO: we should throw, but this is used in various places that won't set a culture yet - return source.Name; - - // if we don't have a name for a culture, it means the culture is not available, and - // hey we should probably not be mapping it, but it's too late, return a fallback name - return doc.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"({source.Name})"; + if (source.NodeObjectType == Constants.ObjectTypes.Member) + target.Icon = "icon-user"; + else if (source.NodeObjectType == Constants.ObjectTypes.DataType) + target.Icon = "icon-autofill"; + else if (source.NodeObjectType == Constants.ObjectTypes.DocumentType) + target.Icon = "icon-item-arrangement"; + else if (source.NodeObjectType == Constants.ObjectTypes.MediaType) + target.Icon = "icon-thumbnails"; + else if (source.NodeObjectType == Constants.ObjectTypes.TemplateType) + target.Icon = "icon-newspaper-alt"; } } + + // Umbraco.Code.MapAll -Alias -Trashed + private static void Map(ISearchResult source, SearchResultEntity target, MapperContext context) + { + target.Id = source.Id; + target.Score = source.Score; + + // TODO: Properly map this (not aftermap) + + //get the icon if there is one + target.Icon = source.Values.ContainsKey(UmbracoExamineIndex.IconFieldName) + ? source.Values[UmbracoExamineIndex.IconFieldName] + : "icon-document"; + + target.Name = source.Values.ContainsKey("nodeName") ? source.Values["nodeName"] : "[no name]"; + + if (source.Values.ContainsKey(UmbracoExamineIndex.NodeKeyFieldName)) + { + if (Guid.TryParse(source.Values[UmbracoExamineIndex.NodeKeyFieldName], out var key)) + { + target.Key = key; + + //need to set the UDI + if (source.Values.ContainsKey(LuceneIndex.CategoryFieldName)) + { + switch (source.Values[LuceneIndex.CategoryFieldName]) + { + case IndexTypes.Member: + target.Udi = new GuidUdi(Constants.UdiEntityType.Member, target.Key); + break; + case IndexTypes.Content: + target.Udi = new GuidUdi(Constants.UdiEntityType.Document, target.Key); + break; + case IndexTypes.Media: + target.Udi = new GuidUdi(Constants.UdiEntityType.Media, target.Key); + break; + } + } + } + } + + if (source.Values.ContainsKey("parentID")) + { + if (int.TryParse(source.Values["parentID"], out var parentId)) + { + target.ParentId = parentId; + } + else + { + target.ParentId = -1; + } + } + + target.Path = source.Values.ContainsKey(UmbracoExamineIndex.IndexPathFieldName) ? source.Values[UmbracoExamineIndex.IndexPathFieldName] : ""; + + if (source.Values.ContainsKey(LuceneIndex.ItemTypeFieldName)) + { + target.AdditionalData.Add("contentType", source.Values[LuceneIndex.ItemTypeFieldName]); + } + } + + private static string MapContentTypeIcon(IEntitySlim entity) + => entity is ContentEntitySlim contentEntity ? contentEntity.ContentTypeIcon : null; + + private static string MapName(IEntitySlim source, MapperContext context) + { + if (!(source is DocumentEntitySlim doc)) + return source.Name; + + // invariant = only 1 name + if (!doc.Variations.VariesByCulture()) return source.Name; + + // variant = depends on culture + var culture = context.GetCulture(); + + // if there's no culture here, the issue is somewhere else (UI, whatever) - throw! + if (culture == null) + //throw new InvalidOperationException("Missing culture in mapping options."); + // TODO: we should throw, but this is used in various places that won't set a culture yet + return source.Name; + + // if we don't have a name for a culture, it means the culture is not available, and + // hey we should probably not be mapping it, but it's too late, return a fallback name + return doc.CultureNames.TryGetValue(culture, out var name) && !name.IsNullOrWhiteSpace() ? name : $"({source.Name})"; + } } } diff --git a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs index 8d45a84f32..648fe000e3 100644 --- a/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/LanguageMapperProfile.cs @@ -12,13 +12,13 @@ namespace Umbraco.Web.Models.Mapping { public void SetMaps(Mapper mapper) { - mapper.Define(source => new EntityBasic(), Map); - mapper.Define(source => new Language(), Map); - mapper.Define, IEnumerable>(source => new List(), (source, target) => Map(source, target, mapper)); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define((source, context) => new Language(), Map); + mapper.Define, IEnumerable>((source, context) => new List(), Map); } // Umbraco.Code.MapAll -Udi -Path -Trashed -AdditionalData -Icon - private static void Map(ILanguage source, EntityBasic target) + private static void Map(ILanguage source, EntityBasic target, MapperContext context) { target.Name = source.CultureName; target.Key = source.Key; @@ -28,7 +28,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll - private static void Map(ILanguage source, Language target) + private static void Map(ILanguage source, Language target, MapperContext context) { target.Id = source.Id; target.IsoCode = source.IsoCode; @@ -38,12 +38,12 @@ namespace Umbraco.Web.Models.Mapping target.FallbackLanguageId = source.FallbackLanguageId; } - private static void Map(IEnumerable source, IEnumerable target, Mapper mapper) + private static void Map(IEnumerable source, IEnumerable target, MapperContext context) { if (!(target is List list)) throw new NotSupportedException($"{nameof(target)} must be a List."); - var temp = source.Select(mapper.Map).ToList(); + var temp = source.Select(context.Mapper.Map).ToList(); //Put the default language first in the list & then sort rest by a-z var defaultLang = temp.SingleOrDefault(x => x.IsDefault); diff --git a/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs index ccb9aa3eef..8100b2fc95 100644 --- a/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MacroMapperProfile.cs @@ -5,7 +5,6 @@ using Umbraco.Core.Logging; using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; -using Umbraco.Web.Composing; using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping @@ -23,13 +22,13 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { - mapper.Define(source => new EntityBasic(), Map); - mapper.Define>(source => source.Properties.Values.Select(mapper.Map).ToList()); - mapper.Define(source => new MacroParameter(), Map); + mapper.Define((source, context) => new EntityBasic(), Map); + mapper.Define>((source, context) => source.Properties.Values.Select(context.Mapper.Map).ToList()); + mapper.Define((source, context) => new MacroParameter(), Map); } // Umbraco.Code.MapAll -Trashed -AdditionalData - private static void Map(IMacro source, EntityBasic target) + private static void Map(IMacro source, EntityBasic target, MapperContext context) { target.Alias = source.Alias; target.Icon = "icon-settings-alt"; @@ -42,7 +41,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -Value - private void Map(IMacroProperty source, MacroParameter target) + private void Map(IMacroProperty source, MacroParameter target, MapperContext context) { target.Alias = source.Alias; target.Name = source.Name; diff --git a/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs b/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs new file mode 100644 index 0000000000..1538f1a987 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MapperContextExtensions.cs @@ -0,0 +1,45 @@ +using Umbraco.Core.Mapping; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Provides extension methods for the class. + /// + internal static class MapperContextExtensions + { + private const string CultureKey = "Map.Culture"; + private const string IncludedPropertiesKey = "Map.IncludedProperties"; + + /// + /// Gets the context culture. + /// + public static string GetCulture(this MapperContext context) + { + return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null; + } + + /// + /// Sets a context culture. + /// + public static void SetCulture(this MapperContext context, string culture) + { + context.Items[CultureKey] = culture; + } + + /// + /// Get included properties. + /// + public static string[] GetIncludedProperties(this MapperContext context) + { + return context.HasItems && context.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s ? s : null; + } + + /// + /// Sets included properties. + /// + public static void SetIncludedProperties(this MapperContext context, string[] properties) + { + context.Items[IncludedPropertiesKey] = properties; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs b/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs index e704272ddb..10c52ff8c3 100644 --- a/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/MappingOperationOptionsExtensions.cs @@ -2,6 +2,7 @@ namespace Umbraco.Web.Models.Mapping { + // FIXME KILLE THIS CLASS /// /// Provides extension methods for AutoMapper's . /// @@ -33,13 +34,5 @@ namespace Umbraco.Web.Models.Mapping { return options.Items.TryGetValue(IncludedPropertiesKey, out var obj) && obj is string[] s ? s : null; } - - /// - /// Sets included properties. - /// - public static void SetIncludedProperties(this IMappingOperationOptions options, string[] properties) - { - options.Items[IncludedPropertiesKey] = properties; - } } } diff --git a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs index f5182f0b3b..e9c45bba9a 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaMapperProfile.cs @@ -1,88 +1,141 @@ -using AutoMapper; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Composing; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; -using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; +using Umbraco.Web.ContentApps; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Trees; +using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping { /// /// Declares model mappings for media. /// - internal class MediaMapperProfile : Profile + internal class MediaMapperProfile : IMapperProfile { - public MediaMapperProfile(TabsAndPropertiesResolver tabsAndPropertiesResolver, - ContentTreeNodeUrlResolver contentTreeNodeUrlResolver, - MediaAppResolver mediaAppResolver, - IUserService userService, - ILocalizedTextService textService, - IDataTypeService dataTypeService, - IMediaService mediaService, - IMediaTypeService mediaTypeService, - ILogger logger, - IContentTypeBaseServiceProvider contentTypeBaseServiceProvider) + private readonly ILogger _logger; + private readonly IUserService _userService; + private readonly IMediaService _mediaService; + private readonly IMediaTypeService _mediaTypeService; + private readonly IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; + private readonly ContentAppFactoryCollection _contentAppDefinitions; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly TabsAndPropertiesMapper _tabsAndPropertiesMapper; + + public MediaMapperProfile(ILogger logger, IUserService userService, IMediaService mediaService, IMediaTypeService mediaTypeService, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ContentAppFactoryCollection contentAppDefinitions, + IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService) { - // create, capture, cache - var mediaOwnerResolver = new OwnerResolver(userService); - var childOfListViewResolver = new MediaChildOfListViewResolver(mediaService, mediaTypeService); - var mediaTypeBasicResolver = new ContentTypeBasicResolver(contentTypeBaseServiceProvider); + _logger = logger; + _userService = userService; + _mediaService = mediaService; + _mediaTypeService = mediaTypeService; + _contentTypeBaseServiceProvider = contentTypeBaseServiceProvider; + _contentAppDefinitions = contentAppDefinitions; + _umbracoContextAccessor = umbracoContextAccessor; - //FROM IMedia TO MediaItemDisplay - CreateMap() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(content => Udi.Create(Constants.UdiEntityType.Media, content.Key))) - .ForMember(dest => dest.Owner, opt => opt.MapFrom(src => mediaOwnerResolver.Resolve(src))) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(content => content.ContentType.Icon)) - .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(content => content.ContentType.Alias)) - .ForMember(dest => dest.IsChildOfListView, opt => opt.MapFrom(childOfListViewResolver)) - .ForMember(dest => dest.Trashed, opt => opt.MapFrom(content => content.Trashed)) - .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(content => content.ContentType.Name)) - .ForMember(dest => dest.Properties, opt => opt.Ignore()) - .ForMember(dest => dest.TreeNodeUrl, opt => opt.MapFrom(contentTreeNodeUrlResolver)) - .ForMember(dest => dest.Notifications, opt => opt.Ignore()) - .ForMember(dest => dest.Errors, opt => opt.Ignore()) - .ForMember(dest => dest.State, opt => opt.MapFrom(_ => null)) - .ForMember(dest => dest.Edited, opt => opt.Ignore()) - .ForMember(dest => dest.Updater, opt => opt.Ignore()) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .ForMember(dest => dest.IsContainer, opt => opt.Ignore()) - .ForMember(dest => dest.Tabs, opt => opt.MapFrom(tabsAndPropertiesResolver)) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) - .ForMember(dest => dest.ContentType, opt => opt.MapFrom(mediaTypeBasicResolver)) - .ForMember(dest => dest.MediaLink, opt => opt.MapFrom(content => string.Join(",", content.GetUrls(Current.Configs.Settings().Content, logger)))) - .ForMember(dest => dest.ContentApps, opt => opt.MapFrom(mediaAppResolver)) - .ForMember(dest => dest.VariesByCulture, opt => opt.MapFrom(src => src.ContentType.VariesByCulture())); + _tabsAndPropertiesMapper = new TabsAndPropertiesMapper(localizedTextService); + } + public void SetMaps(Mapper mapper) + { + mapper.Define((source, context) => new MediaItemDisplay(), Map); + mapper.Define>((source, context) => new ContentItemBasic(), Map); + } - //FROM IMedia TO ContentItemBasic - CreateMap>() - .ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Media, src.Key))) - .ForMember(dest => dest.Owner, opt => opt.MapFrom(src => mediaOwnerResolver.Resolve(src))) - .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon)) - .ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed)) - .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) - .ForMember(dest => dest.State, opt => opt.MapFrom(_ => null)) - .ForMember(dest => dest.Edited, opt => opt.Ignore()) - .ForMember(dest => dest.Updater, opt => opt.Ignore()) - .ForMember(dest => dest.Alias, opt => opt.Ignore()) - .ForMember(dest => dest.AdditionalData, opt => opt.Ignore()) - .ForMember(dest => dest.VariesByCulture, opt => opt.MapFrom(src => src.ContentType.VariesByCulture())); + // Umbraco.Code.MapAll -Properties -Errors -Edited -Updater -Alias -IsContainer + private void Map(IMedia source, MediaItemDisplay target, MapperContext context) + { + target.ContentApps = _contentAppDefinitions.GetContentAppsFor(source); + target.ContentType = GetContentType(source, context.Mapper); + target.ContentTypeAlias = source.ContentType.Alias; + target.ContentTypeName = source.ContentType.Name; + target.CreateDate = source.CreateDate; + target.Icon = source.ContentType.Icon; + target.Id = source.Id; + target.IsChildOfListView = DermineIsChildOfListView(source); + target.Key = source.Key; + target.MediaLink = string.Join(",", source.GetUrls(Current.Configs.Settings().Content, _logger)); + target.Name = source.Name; + target.Owner = GetOwner(source, context.Mapper); + target.ParentId = source.ParentId; + target.Path = source.Path; + target.SortOrder = source.SortOrder; + target.State = null; + target.Tabs = _tabsAndPropertiesMapper.Map(source, context); + target.Trashed = source.Trashed; + target.TreeNodeUrl = GetTreeNodeUrl(source); + target.Udi = Udi.Create(Constants.UdiEntityType.Media, source.Key); + target.UpdateDate = source.UpdateDate; + target.VariesByCulture = source.ContentType.VariesByCulture(); + } + // Umbraco.Code.MapAll -Edited -Updater -Alias + private void Map(IMedia source, ContentItemBasic target, MapperContext context) + { + target.ContentTypeAlias = source.ContentType.Alias; + target.CreateDate = source.CreateDate; + target.Icon = source.ContentType.Icon; + target.Id = source.Id; + target.Key = source.Key; + target.Name = source.Name; + target.Owner = GetOwner(source, context.Mapper); + target.ParentId = source.ParentId; + target.Path = source.Path; + target.Properties = context.Mapper.Map>(source.Properties); + target.SortOrder = source.SortOrder; + target.State = null; + target.Trashed = source.Trashed; + target.Udi = Udi.Create(Constants.UdiEntityType.Media, source.Key); + target.UpdateDate = source.UpdateDate; + target.VariesByCulture = source.ContentType.VariesByCulture(); + } - //FROM IMedia TO ContentItemDto - CreateMap(); - //.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(Constants.UdiEntityType.Media, src.Key))) - //.ForMember(dest => dest.Owner, opt => opt.MapFrom(src => mediaOwnerResolver.Resolve(src))) - //.ForMember(dest => dest.Published, opt => opt.Ignore()) - //.ForMember(dest => dest.Edited, opt => opt.Ignore()) - //.ForMember(dest => dest.Updater, opt => opt.Ignore()) - //.ForMember(dest => dest.Icon, opt => opt.Ignore()) - //.ForMember(dest => dest.Alias, opt => opt.Ignore()) - //.ForMember(dest => dest.AdditionalData, opt => opt.Ignore()); + private UserProfile GetOwner(IContentBase source, Mapper mapper) + { + var profile = source.GetCreatorProfile(_userService); + return profile == null ? null : mapper.Map(profile); + } + + private bool DermineIsChildOfListView(IMedia source) + { + // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + var parent = _mediaService.GetParent(source); + return parent != null && (parent.ContentType.IsContainer || _mediaTypeService.HasContainerInPath(parent.Path)); + } + + private ContentTypeBasic GetContentType(IContentBase source, Mapper mapper) + { + // TODO: We can resolve the UmbracoContext from the IValueResolver options! + // OMG + if (HttpContext.Current != null && Composing.Current.UmbracoContext != null && Composing.Current.UmbracoContext.Security.CurrentUser != null + && Composing.Current.UmbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source); + var contentTypeBasic = mapper.Map(contentType); + + return contentTypeBasic; + } + //no access + return null; + } + + private string GetTreeNodeUrl(IContentBase source) + where TController : ContentTreeControllerBase + { + var umbracoContext = _umbracoContextAccessor.UmbracoContext; + if (umbracoContext == null) return null; + + var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext); + return urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); } } } diff --git a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs index a721714a14..a2410495b9 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberMapperProfile.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Models.Mapping internal class MemberMapperProfile : Profile { public MemberMapperProfile( - MemberTabsAndPropertiesResolver tabsAndPropertiesResolver, + MemberTabsAndPropertiesMapper tabsAndPropertiesMapper, MemberTreeNodeUrlResolver memberTreeNodeUrlResolver, MemberBasicPropertiesResolver memberBasicPropertiesResolver, IUserService userService, @@ -69,7 +69,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dest => dest.ContentTypeAlias, opt => opt.MapFrom(src => src.ContentType.Alias)) .ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name)) .ForMember(dest => dest.Properties, opt => opt.Ignore()) - .ForMember(dest => dest.Tabs, opt => opt.MapFrom(tabsAndPropertiesResolver)) + .ForMember(dest => dest.Tabs, opt => opt.MapFrom(tabsAndPropertiesMapper)) .ForMember(dest => dest.MemberProviderFieldMapping, opt => opt.MapFrom(src => memberProfiderFieldMappingResolver.Resolve(src))) .ForMember(dest => dest.MembershipScenario, opt => opt.MapFrom(src => membershipScenarioMappingResolver.Resolve(src))) .ForMember(dest => dest.Notifications, opt => opt.Ignore()) diff --git a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs similarity index 95% rename from src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs rename to src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs index 1e93618547..a96ef24e6f 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberTabsAndPropertiesMapper.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Web.Security; -using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Mapping; using Umbraco.Core.Composing; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; @@ -21,7 +21,7 @@ namespace Umbraco.Web.Models.Mapping /// This also ensures that the IsLocked out property is readonly when the member is not locked out - this is because /// an admin cannot actually set isLockedOut = true, they can only unlock. /// - internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver + internal class MemberTabsAndPropertiesMapper : TabsAndPropertiesMapper { private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILocalizedTextService _localizedTextService; @@ -29,7 +29,7 @@ namespace Umbraco.Web.Models.Mapping private readonly IMemberService _memberService; private readonly IUserService _userService; - public MemberTabsAndPropertiesResolver(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService, IMemberTypeService memberTypeService) + public MemberTabsAndPropertiesMapper(IUmbracoContextAccessor umbracoContextAccessor, ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService, IMemberTypeService memberTypeService) : base(localizedTextService) { _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); @@ -41,7 +41,7 @@ namespace Umbraco.Web.Models.Mapping /// /// Overridden to deal with custom member properties and permissions. - public override IEnumerable> Resolve(IMember source, MemberDisplay destination, IEnumerable> destMember, ResolutionContext context) + public override IEnumerable> Map(IMember source, MapperContext context) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); @@ -52,7 +52,7 @@ namespace Umbraco.Web.Models.Mapping .Select(x => x.Alias) .ToArray(); - var resolved = base.Resolve(source, destination, destMember, context); + var resolved = base.Map(source, context); if (provider.IsUmbracoMembershipProvider() == false) { @@ -176,7 +176,7 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - protected override List MapProperties(IContentBase content, List properties, ResolutionContext context) + protected override List MapProperties(IContentBase content, List properties, MapperContext context) { var result = base.MapProperties(content, properties, context); var member = (IMember)content; diff --git a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs similarity index 97% rename from src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs rename to src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs index ea0411eb15..8c5f347799 100644 --- a/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PropertyTypeGroupMapper.cs @@ -1,5 +1,4 @@ -using AutoMapper; -using System; +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core; @@ -11,14 +10,14 @@ using Umbraco.Web.Models.ContentEditing; namespace Umbraco.Web.Models.Mapping { - internal class PropertyTypeGroupResolver + internal class PropertyTypeGroupMapper where TPropertyType : PropertyTypeDisplay, new() { private readonly PropertyEditorCollection _propertyEditors; private readonly IDataTypeService _dataTypeService; private readonly ILogger _logger; - public PropertyTypeGroupResolver(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILogger logger) + public PropertyTypeGroupMapper(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILogger logger) { _propertyEditors = propertyEditors; _dataTypeService = dataTypeService; @@ -65,7 +64,7 @@ namespace Umbraco.Web.Models.Mapping .FirstOrDefault(x => x != null); } - public IEnumerable> Resolve(IContentTypeComposition source) + public IEnumerable> Map(IContentTypeComposition source) { // deal with groups var groups = new List>(); diff --git a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs index 741584a9ac..f9b971785b 100644 --- a/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/RedirectUrlMapperProfile.cs @@ -17,11 +17,11 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { - mapper.Define(source => new ContentRedirectUrl(), Map); + mapper.Define((source, context) => new ContentRedirectUrl(), Map); } // Umbraco.Code.MapAll - private void Map(IRedirectUrl source, ContentRedirectUrl target) + private void Map(IRedirectUrl source, ContentRedirectUrl target, MapperContext context) { target.ContentId = source.ContentId; target.CreateDateUtc = source.CreateDateUtc; diff --git a/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs index 3f2c2f8711..56e41e5a07 100644 --- a/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/RelationMapperProfile.cs @@ -9,14 +9,14 @@ namespace Umbraco.Web.Models.Mapping { public void SetMaps(Mapper mapper) { - mapper.Define(source => new RelationTypeDisplay(), Map); - mapper.Define(source => new RelationDisplay(), Map); + mapper.Define((source, context) => new RelationTypeDisplay(), Map); + mapper.Define((source, context) => new RelationDisplay(), Map); mapper.Define(Map); } // Umbraco.Code.MapAll -Icon -Trashed -Alias -AdditionalData // Umbraco.Code.MapAll -Relations -ParentId -Notifications - private static void Map(IRelationType source, RelationTypeDisplay target) + private static void Map(IRelationType source, RelationTypeDisplay target, MapperContext context) { target.ChildObjectType = source.ChildObjectType; target.Id = source.Id; @@ -33,7 +33,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -ParentName -ChildName - private static void Map(IRelation source, RelationDisplay target) + private static void Map(IRelation source, RelationDisplay target, MapperContext context) { target.ChildId = source.ChildId; target.Comment = source.Comment; @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate - private static void Map(RelationTypeSave source, IRelationType target) + private static void Map(RelationTypeSave source, IRelationType target, MapperContext context) { target.Alias = source.Alias; target.ChildObjectType = source.ChildObjectType; diff --git a/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs index 51faed3552..2dc61fabe5 100644 --- a/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/SectionMapperProfile.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { - mapper.Define(source => new Section(), Map); + mapper.Define((source, context) => new Section(), Map); // this is for AutoMapper ReverseMap - but really? mapper.Define(); @@ -32,14 +32,14 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -RoutePath - private void Map(ISection source, Section target) + private void Map(ISection source, Section target, MapperContext context) { target.Alias = source.Alias; target.Name = _textService.Localize("sections/" + source.Alias); } // Umbraco.Code.MapAll - private static void Map(Section source, ManifestSection target) + private static void Map(Section source, ManifestSection target, MapperContext context) { target.Alias = source.Alias; target.Name = source.Name; diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs similarity index 89% rename from src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs rename to src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs index dc3cc9e74f..a9fdea587d 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesMapper.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoMapper; using Umbraco.Core; +using Umbraco.Core.Mapping; using Umbraco.Core.Models; using Umbraco.Core.Services; using Umbraco.Web.Models.ContentEditing; @@ -10,18 +10,18 @@ using Umbraco.Web.Composing; namespace Umbraco.Web.Models.Mapping { - internal abstract class TabsAndPropertiesResolver + internal abstract class TabsAndPropertiesMapper { protected ILocalizedTextService LocalizedTextService { get; } protected IEnumerable IgnoreProperties { get; set; } - protected TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) + protected TabsAndPropertiesMapper(ILocalizedTextService localizedTextService) { LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); IgnoreProperties = new List(); } - protected TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) + protected TabsAndPropertiesMapper(ILocalizedTextService localizedTextService, IEnumerable ignoreProperties) : this(localizedTextService) { IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties)); @@ -46,7 +46,7 @@ namespace Umbraco.Web.Models.Mapping /// The generic properties tab is responsible for /// setting up the properties such as Created date, updated date, template selected, etc... /// - protected virtual void MapGenericProperties(IContentBase content, List> tabs, ResolutionContext context) + protected virtual void MapGenericProperties(IContentBase content, List> tabs, MapperContext context) { // add the generic properties tab, for properties that don't belong to a tab // get the properties, map and translate them, then add the tab @@ -103,7 +103,7 @@ namespace Umbraco.Web.Models.Mapping /// /// /// - protected virtual List MapProperties(IContentBase content, List properties, ResolutionContext context) + protected virtual List MapProperties(IContentBase content, List properties, MapperContext context) { //we need to map this way to pass the context through, I don't like it but we'll see what AutoMapper says: https://github.com/AutoMapper/AutoMapper/issues/2588 var result = context.Mapper.Map, IEnumerable>( @@ -119,14 +119,14 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates the tabs collection with properties assigned for display models /// - internal class TabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver>> + internal class TabsAndPropertiesMapper : TabsAndPropertiesMapper where TSource : IContentBase { - public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService) + public TabsAndPropertiesMapper(ILocalizedTextService localizedTextService) : base(localizedTextService) { } - public virtual IEnumerable> Resolve(TSource source, TDestination destination, IEnumerable> destMember, ResolutionContext context) + public virtual IEnumerable> Map(TSource source, MapperContext context) { var tabs = new List>(); diff --git a/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs index 7099400f9a..4424113698 100644 --- a/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/TagMapperProfile.cs @@ -7,11 +7,11 @@ namespace Umbraco.Web.Models.Mapping { public void SetMaps(Mapper mapper) { - mapper.Define(source => new TagModel(), Map); + mapper.Define((source, context) => new TagModel(), Map); } // Umbraco.Code.MapAll - private static void Map(ITag source, TagModel target) + private static void Map(ITag source, TagModel target, MapperContext context) { target.Id = source.Id; target.Text = source.Text; diff --git a/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs index fa6f9ceb53..246660b46b 100644 --- a/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/TemplateMapperProfile.cs @@ -8,12 +8,12 @@ namespace Umbraco.Web.Models.Mapping { public void SetMaps(Mapper mapper) { - mapper.Define(source => new TemplateDisplay(), Map); - mapper.Define(source => new Template(source.Name, source.Alias), Map); + mapper.Define((source, context) => new TemplateDisplay(), Map); + mapper.Define((source, context) => new Template(source.Name, source.Alias), Map); } // Umbraco.Code.MapAll - private static void Map(ITemplate source, TemplateDisplay target) + private static void Map(ITemplate source, TemplateDisplay target, MapperContext context) { target.Id = source.Id; target.Name = source.Name; @@ -29,7 +29,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -CreateDate -UpdateDate -DeleteDate // Umbraco.Code.MapAll -Path -VirtualPath -MasterTemplateId -IsMasterTemplate // Umbraco.Code.MapAll -GetFileContent - private static void Map(TemplateDisplay source, Template target) + private static void Map(TemplateDisplay source, Template target, MapperContext context) { target.MasterTemplateAlias = source.MasterTemplateAlias; target.Name = source.Name; diff --git a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs index f4e2342f90..d8c61fe6cc 100644 --- a/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs +++ b/src/Umbraco.Web/Models/Mapping/UserMapperProfile.cs @@ -40,28 +40,28 @@ namespace Umbraco.Web.Models.Mapping public void SetMaps(Mapper mapper) { - mapper.Define(source => new UserGroup { CreateDate = DateTime.UtcNow }, Map); + mapper.Define((source, context) => new UserGroup { CreateDate = DateTime.UtcNow }, Map); mapper.Define(Map); mapper.Define(Map); - mapper.Define(source => new UserGroupBasic(), (source, target) => Map(source, target, mapper)); - mapper.Define(source => new UserGroupBasic(), (source, target) => Map(source, target, mapper)); - mapper.Define(source => new AssignedUserGroupPermissions(), Map); - mapper.Define(source => new AssignedContentPermissions(), Map); - mapper.Define(source => new UserGroupDisplay(), (source, target) => Map(source, target, mapper)); - mapper.Define(source => new UserBasic(), (source, target) => Map(source, target, mapper)); - mapper.Define(source => new UserDetail(), Map); + mapper.Define((source, context) => new UserGroupBasic(), Map); + mapper.Define((source, context) => new UserGroupBasic(), Map); + mapper.Define((source, context) => new AssignedUserGroupPermissions(), Map); + mapper.Define((source, context) => new AssignedContentPermissions(), Map); + mapper.Define((source, context) => new UserGroupDisplay(), Map); + mapper.Define((source, context) => new UserBasic(), Map); + mapper.Define((source, context) => new UserDetail(), Map); // used for merging existing UserSave to an existing IUser instance - this will not create an IUser instance! mapper.Define(Map); // important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that // this will cause an N+1 and we'll need to change how this works. - mapper.Define(source => new UserDisplay(), (source, target) => Map(source, target, mapper)); + mapper.Define((source, context) => new UserDisplay(), Map); } // mappers - private static void Map(UserGroupSave source, IUserGroup target) + private static void Map(UserGroupSave source, IUserGroup target, MapperContext context) { if (!(target is UserGroup ttarget)) throw new NotSupportedException($"{nameof(target)} must be a UserGroup."); @@ -94,7 +94,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -SecurityStamp -Avatar -ProviderUserKey -RawPasswordValue // Umbraco.Code.MapAll -RawPasswordAnswerValue -Comments -IsApproved -IsLockedOut -LastLoginDate // Umbraco.Code.MapAll -LastPasswordChangeDate -LastLockoutDate -FailedPasswordAttempts - private void Map(UserInvite source, IUser target) + private void Map(UserInvite source, IUser target, MapperContext context) { target.Email = source.Email; target.Key = source.Key; @@ -112,7 +112,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -ProviderUserKey -RawPasswordValue -RawPasswordAnswerValue -PasswordQuestion -Comments // Umbraco.Code.MapAll -IsApproved -IsLockedOut -LastLoginDate -LastPasswordChangeDate -LastLockoutDate // Umbraco.Code.MapAll -FailedPasswordAttempts - private void Map(UserSave source, IUser target) + private void Map(UserSave source, IUser target, MapperContext context) { target.Name = source.Name; target.StartContentIds = source.StartContentIds; @@ -130,7 +130,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll - private static void Map(IProfile source, ContentEditing.UserProfile target) + private static void Map(IProfile source, ContentEditing.UserProfile target, MapperContext context) { target.Name = source.Name; target.UserId = source.Id; @@ -138,7 +138,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -ContentStartNode -UserCount -MediaStartNode -Key -Sections // Umbraco.Code.MapAll -Notifications -Udi -Trashed -AdditionalData - private void Map(IReadOnlyUserGroup source, UserGroupBasic target, Mapper mapper) + private void Map(IReadOnlyUserGroup source, UserGroupBasic target, MapperContext context) { target.Alias = source.Alias; target.Icon = source.Icon; @@ -146,12 +146,12 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Name; target.ParentId = -1; target.Path = "-1," + source.Id; - MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, mapper); + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context.Mapper); } // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData - private void Map(IUserGroup source, UserGroupBasic target, Mapper mapper) + private void Map(IUserGroup source, UserGroupBasic target, MapperContext context) { target.Alias = source.Alias; target.Icon = source.Icon; @@ -161,11 +161,11 @@ namespace Umbraco.Web.Models.Mapping target.ParentId = -1; target.Path = "-1," + source.Id; target.UserCount = source.UserCount; - MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, mapper); + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context.Mapper); } // Umbraco.Code.MapAll -Udi -Trashed -AdditionalData -Id -AssignedPermissions - private void Map(IUserGroup source, AssignedUserGroupPermissions target) + private void Map(IUserGroup source, AssignedUserGroupPermissions target, MapperContext context) { target.Alias = source.Alias; target.Icon = source.Icon; @@ -181,7 +181,7 @@ namespace Umbraco.Web.Models.Mapping } // Umbraco.Code.MapAll -Trashed -Alias -AssignedPermissions - private static void Map(EntitySlim source, AssignedContentPermissions target) + private static void Map(EntitySlim source, AssignedContentPermissions target, MapperContext context) { target.Icon = MapContentTypeIcon(source); target.Id = source.Id; @@ -197,7 +197,7 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -ContentStartNode -MediaStartNode -Sections -Notifications -Udi // Umbraco.Code.MapAll -Trashed -AdditionalData -Users -AssignedPermissions - private void Map(IUserGroup source, UserGroupDisplay target, Mapper mapper) + private void Map(IUserGroup source, UserGroupDisplay target, MapperContext context) { target.Alias = source.Alias; target.DefaultPermissions = MapUserGroupDefaultPermissions(source); @@ -209,12 +209,12 @@ namespace Umbraco.Web.Models.Mapping target.Path = "-1," + source.Id; target.UserCount = source.UserCount; - MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, mapper); + MapUserGroupBasic(target, source.AllowedSections, source.StartContentId, source.StartMediaId, context.Mapper); //Important! Currently we are never mapping to multiple UserGroupDisplay objects but if we start doing that // this will cause an N+1 and we'll need to change how this works. var users = _userService.GetAllInGroup(source.Id); - target.Users = mapper.Map>(users); + target.Users = context.Mapper.Map>(users); //Deal with assigned permissions: @@ -243,7 +243,7 @@ namespace Umbraco.Web.Models.Mapping { var contentPermissions = allContentPermissions[entity.Id]; - var assignedContentPermissions = mapper.Map(entity); + var assignedContentPermissions = context.Mapper.Map(entity); assignedContentPermissions.AssignedPermissions = AssignedUserGroupPermissions.ClonePermissions(target.DefaultPermissions); //since there is custom permissions assigned to this node for this group, we need to clear all of the default permissions @@ -262,12 +262,12 @@ namespace Umbraco.Web.Models.Mapping // Umbraco.Code.MapAll -Notifications -Udi -Icon -IsCurrentUser -Trashed -ResetPasswordValue // Umbraco.Code.MapAll -Alias -AdditionalData - private void Map(IUser source, UserDisplay target, Mapper mapper) + private void Map(IUser source, UserDisplay target, MapperContext context) { target.AvailableCultures = _textService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache); - target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService), UmbracoObjectTypes.Document, "content/contentRoot", mapper); - target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService), UmbracoObjectTypes.Media, "media/mediaRoot", mapper); + target.CalculatedStartContentIds = GetStartNodes(source.CalculateContentStartNodeIds(_entityService), UmbracoObjectTypes.Document, "content/contentRoot", context.Mapper); + target.CalculatedStartMediaIds = GetStartNodes(source.CalculateMediaStartNodeIds(_entityService), UmbracoObjectTypes.Media, "media/mediaRoot", context.Mapper); target.CreateDate = source.CreateDate; target.Culture = source.GetUserCulture(_textService, _globalSettings).ToString(); target.Email = source.Email; @@ -282,16 +282,16 @@ namespace Umbraco.Web.Models.Mapping target.Navigation = CreateUserEditorNavigation(); target.ParentId = -1; target.Path = "-1," + source.Id; - target.StartContentIds = GetStartNodes(source.StartContentIds.ToArray(), UmbracoObjectTypes.Document, "content/contentRoot", mapper); - target.StartMediaIds = GetStartNodes(source.StartMediaIds.ToArray(), UmbracoObjectTypes.Media, "media/mediaRoot", mapper); + target.StartContentIds = GetStartNodes(source.StartContentIds.ToArray(), UmbracoObjectTypes.Document, "content/contentRoot", context.Mapper); + target.StartMediaIds = GetStartNodes(source.StartMediaIds.ToArray(), UmbracoObjectTypes.Media, "media/mediaRoot", context.Mapper); target.UpdateDate = source.UpdateDate; - target.UserGroups = source.Groups.Select(mapper.Map); + target.UserGroups = source.Groups.Select(context.Mapper.Map); target.Username = source.Username; target.UserState = source.UserState; } // Umbraco.Code.MapAll -Notifications -IsCurrentUser -Udi -Icon -Trashed -Alias -AdditionalData - private void Map(IUser source, UserBasic target, Mapper mapper) + private void Map(IUser source, UserBasic target, MapperContext context) { //Loading in the user avatar's requires an external request if they don't have a local file avatar, this means that initial load of paging may incur a cost //Alternatively, if this is annoying the back office UI would need to be updated to request the avatars for the list of users separately so it doesn't look @@ -306,13 +306,13 @@ namespace Umbraco.Web.Models.Mapping target.Name = source.Name; target.ParentId = -1; target.Path = "-1," + source.Id; - target.UserGroups = source.Groups.Select(mapper.Map); + target.UserGroups = source.Groups.Select(context.Mapper.Map); target.Username = source.Username; target.UserState = source.UserState; } // Umbraco.Code.MapAll -SecondsUntilTimeout - private void Map(IUser source, UserDetail target) + private void Map(IUser source, UserDetail target, MapperContext context) { target.AllowedSections = source.AllowedSections; target.Avatars = source.GetUserAvatarUrls(_appCaches.RuntimeCache); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0ac89dfe82..3956182281 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -205,6 +205,7 @@ + @@ -423,7 +424,6 @@ - @@ -433,7 +433,6 @@ - @@ -441,7 +440,7 @@ - + @@ -653,7 +652,6 @@ - @@ -685,7 +683,7 @@ - + @@ -965,9 +963,9 @@ - - - + + + @@ -975,7 +973,7 @@ - +