diff --git a/src/Umbraco.Core/Models/MemberType.cs b/src/Umbraco.Core/Models/MemberType.cs index 939c4ca8b5..4e70b4157f 100644 --- a/src/Umbraco.Core/Models/MemberType.cs +++ b/src/Umbraco.Core/Models/MemberType.cs @@ -76,11 +76,11 @@ namespace Umbraco.Core.Models /// public bool MemberCanEditProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - return MemberTypePropertyTypes[propertyTypeAlias].IsEditable; + return propertyProfile.IsEditable; } - return false; } @@ -91,11 +91,11 @@ namespace Umbraco.Core.Models /// public bool MemberCanViewProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - return MemberTypePropertyTypes[propertyTypeAlias].IsVisible; + return propertyProfile.IsVisible; } - return false; } @@ -106,11 +106,11 @@ namespace Umbraco.Core.Models /// public bool IsSensitiveProperty(string propertyTypeAlias) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - return MemberTypePropertyTypes[propertyTypeAlias].IsSensitive; + return propertyProfile.IsSensitive; } - return false; } @@ -121,9 +121,10 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanEditProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsEditable = value; + propertyProfile.IsEditable = value; } else { @@ -139,9 +140,10 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetMemberCanViewProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsVisible = value; + propertyProfile.IsVisible = value; } else { @@ -157,9 +159,10 @@ namespace Umbraco.Core.Models /// Boolean value, true or false public void SetIsSensitiveProperty(string propertyTypeAlias, bool value) { - if (MemberTypePropertyTypes.ContainsKey(propertyTypeAlias)) + MemberTypePropertyProfileAccess propertyProfile; + if (MemberTypePropertyTypes.TryGetValue(propertyTypeAlias, out propertyProfile)) { - MemberTypePropertyTypes[propertyTypeAlias].IsSensitive = value; + propertyProfile.IsSensitive = value; } else { diff --git a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs index 7588309fce..439ac68360 100644 --- a/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/MemberTypeReadOnlyFactory.cs @@ -57,7 +57,7 @@ namespace Umbraco.Core.Persistence.Factories //Add the standard PropertyType to the current list propertyTypes.Add(standardPropertyType.Value); - //Internal dictionary for adding "MemberCanEdit" and "VisibleOnProfile" properties to each PropertyType + //Internal dictionary for adding "MemberCanEdit", "VisibleOnProfile", "IsSensitive" properties to each PropertyType memberType.MemberTypePropertyTypes.Add(standardPropertyType.Key, new MemberTypePropertyProfileAccess(false, false, false)); } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 5865878aa9..d473e19485 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -90,7 +90,8 @@ namespace Umbraco.Core.Persistence.Repositories sql.Select("umbracoNode.*", "cmsContentType.*", "cmsPropertyType.id AS PropertyTypeId", "cmsPropertyType.Alias", "cmsPropertyType.Name", "cmsPropertyType.Description", "cmsPropertyType.mandatory", "cmsPropertyType.UniqueID", "cmsPropertyType.validationRegExp", "cmsPropertyType.dataTypeId", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", - "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", + "cmsPropertyType.propertyTypeGroupId AS PropertyTypesGroupId", + "cmsMemberType.memberCanEdit", "cmsMemberType.viewOnProfile", "cmsMemberType.isSensitive", "cmsDataType.propertyEditorAlias", "cmsDataType.dbType", "cmsPropertyTypeGroup.id AS PropertyTypeGroupId", "cmsPropertyTypeGroup.text AS PropertyGroupName", "cmsPropertyTypeGroup.uniqueID AS PropertyGroupUniqueID", "cmsPropertyTypeGroup.sortorder AS PropertyGroupSortOrder", "cmsPropertyTypeGroup.contenttypeNodeId") @@ -362,4 +363,4 @@ namespace Umbraco.Core.Persistence.Repositories return Attempt.Fail(propertyEditor); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 61d00b4a49..7a8cb6da50 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -221,6 +221,7 @@ Add another text box Remove this text box Content root + Your user permissions do not allow access to this property Create a new Content Template from '%0%' diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index dbf20229ef..92ec93ab71 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -227,6 +227,7 @@ Add another text box Remove this text box Content root + Your user permissions do not allow access to this property Create a new Content Template from '%0%' diff --git a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs index 343b018000..14793ace5e 100644 --- a/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/ContentPropertyDisplay.cs @@ -34,6 +34,9 @@ namespace Umbraco.Web.Models.ContentEditing public bool HideLabel { get; set; } [DataMember(Name = "validation")] - public PropertyTypeValidation Validation { get; set; } + public PropertyTypeValidation Validation { get; set; } + + [DataMember(Name = "isSensitiveData")] + public bool IsSensitive { get; set; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs new file mode 100644 index 0000000000..78fc71dbc0 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/AutoMapperExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models.Mapping +{ + internal static class AutoMapperExtensions + { + /// + /// This maps an object and passes in the current so the mapping logic can use it + /// + /// + /// + /// + /// + /// + public static TOut MapWithUmbracoContext(TIn obj, UmbracoContext umbCtx) + { + return Mapper.Map(obj, opt => opt.Items["UmbracoContext"] = umbCtx); + } + + /// + /// Returns an from the mapping options + /// + /// + /// + /// + /// If an UmbracoContext is not found in the mapping options, it will try to retrieve it from the singleton + /// + public static UmbracoContext GetUmbracoContext(this ResolutionResult res) + { + //get the context from the mapping options set during a mapping operation + object umbCtx; + if (res.Context.Options.Items.TryGetValue("UmbracoContext", out umbCtx)) + { + var umbracoContext = umbCtx as UmbracoContext; + if (umbracoContext != null) return umbracoContext; + } + + //return the singleton (this could be null) + return UmbracoContext.Current; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs index 2fa919716c..9d5975b564 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs @@ -47,7 +47,7 @@ namespace Umbraco.Web.Models.Mapping : content.GetContentUrls(UmbracoContext.Current))) .ForMember(display => display.Properties, expression => expression.Ignore()) .ForMember(display => display.AllowPreview, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) + .ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new ContentTreeNodeUrlResolver())) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.Alias, expression => expression.Ignore()) @@ -101,16 +101,9 @@ namespace Umbraco.Web.Models.Mapping ILocalizedTextService localizedText, IContentTypeService contentTypeService) { // map the IsChildOfListView (this is actually if it is a descendant of a list view!) + //TODO: STOP using these extension methods, they are not testable and require singletons to be setup var parent = content.Parent(); - display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); - display.TreeNodeUrl = url; - } + display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); //set default template if template isn't set if (string.IsNullOrEmpty(display.TemplateAlias)) @@ -122,8 +115,6 @@ namespace Umbraco.Web.Models.Mapping { TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText); } - - TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText); } /// @@ -134,9 +125,7 @@ namespace Umbraco.Web.Models.Mapping { protected override ContentTypeBasic ResolveCore(IContent source) { - //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons - //If this is a web request and there's a user signed in and the - // user has access to the settings section, we will + //TODO: We can resolve the UmbracoContext from the IValueResolver options! if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) { @@ -194,4 +183,4 @@ namespace Umbraco.Web.Models.Mapping } } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs index f61d9adf77..e7104f2939 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyBasicConverter.cs @@ -16,9 +16,9 @@ namespace Umbraco.Web.Models.Mapping internal class ContentPropertyBasicConverter : TypeConverter where T : ContentPropertyBasic, new() { - protected Lazy DataTypeService { get; private set; } + protected IDataTypeService DataTypeService { get; private set; } - public ContentPropertyBasicConverter(Lazy dataTypeService) + public ContentPropertyBasicConverter(IDataTypeService dataTypeService) { DataTypeService = dataTypeService; } @@ -42,7 +42,7 @@ namespace Umbraco.Web.Models.Mapping var result = new T { Id = property.Id, - Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService.Value), + Value = editor.ValueEditor.ConvertDbToEditor(property, property.PropertyType, DataTypeService), Alias = property.Alias, PropertyEditor = editor, Editor = editor.Alias @@ -51,4 +51,4 @@ namespace Umbraco.Web.Models.Mapping return result; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs index 20ff209371..8267e46e25 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDisplayConverter.cs @@ -13,17 +13,19 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter { - public ContentPropertyDisplayConverter(Lazy dataTypeService) + private readonly ILocalizedTextService _textService; + + public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService) : base(dataTypeService) { - + _textService = textService; } protected override ContentPropertyDisplay ConvertCore(Property originalProp) { var display = base.ConvertCore(originalProp); - var dataTypeService = DataTypeService.Value; + var dataTypeService = DataTypeService; var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(originalProp.PropertyType.DataTypeDefinitionId); //configure the editor for display with the pre-values @@ -54,7 +56,11 @@ namespace Umbraco.Web.Models.Mapping display.View = valEditor.View; } + //Translate + display.Label = _textService.UmbracoDictionaryTranslate(display.Label); + display.Description = _textService.UmbracoDictionaryTranslate(display.Description); + return display; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs index 3a6e199f96..f3f9fbe9d5 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyDtoConverter.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models.Mapping /// internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter { - public ContentPropertyDtoConverter(Lazy dataTypeService) + public ContentPropertyDtoConverter(IDataTypeService dataTypeService) : base(dataTypeService) { } @@ -21,7 +21,7 @@ namespace Umbraco.Web.Models.Mapping { var propertyDto = base.ConvertCore(originalProperty); - var dataTypeService = DataTypeService.Value; + var dataTypeService = DataTypeService; propertyDto.IsRequired = originalProperty.PropertyType.Mandatory; propertyDto.ValidationRegExp = originalProperty.PropertyType.ValidationRegExp; @@ -35,4 +35,4 @@ namespace Umbraco.Web.Models.Mapping return propertyDto; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs index ba51eb1790..5e29bcf125 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentPropertyModelMapper.cs @@ -16,8 +16,6 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); - //FROM Property TO ContentPropertyBasic config.CreateMap>() .ForMember(tab => tab.Label, expression => expression.MapFrom(@group => @group.Name)) @@ -27,15 +25,15 @@ namespace Umbraco.Web.Models.Mapping //FROM Property TO ContentPropertyBasic config.CreateMap() - .ConvertUsing(new ContentPropertyBasicConverter(lazyDataTypeService)); + .ConvertUsing(new ContentPropertyBasicConverter(applicationContext.Services.DataTypeService)); //FROM Property TO ContentPropertyDto config.CreateMap() - .ConvertUsing(new ContentPropertyDtoConverter(lazyDataTypeService)); + .ConvertUsing(new ContentPropertyDtoConverter(applicationContext.Services.DataTypeService)); //FROM Property TO ContentPropertyDisplay config.CreateMap() - .ConvertUsing(new ContentPropertyDisplayConverter(lazyDataTypeService)); + .ConvertUsing(new ContentPropertyDisplayConverter(applicationContext.Services.DataTypeService, applicationContext.Services.TextService)); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs new file mode 100644 index 0000000000..dfea70a968 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/ContentTreeNodeUrlResolver.cs @@ -0,0 +1,34 @@ +using System.Web.Mvc; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the tree node url for the content or media + /// + internal class ContentTreeNodeUrlResolver : IValueResolver + where TSource : IContentBase + where TController : ContentTreeControllerBase + { + + public ResolutionResult Resolve(ResolutionResult source) + { + return source.New(ResolveCore(source, (TSource)source.Value), typeof(string)); + } + + private string ResolveCore(ResolutionResult res, TSource source) + { + var umbCtx = res.GetUmbracoContext(); + //map the tree node url + if (umbCtx != null) + { + var urlHelper = new UrlHelper(umbCtx.HttpContext.Request.RequestContext); + var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + return url; + } + return null; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs index 32d25028e4..60d9eb7d3b 100644 --- a/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/DataTypeModelMapper.cs @@ -19,9 +19,7 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - var lazyDataTypeService = new Lazy(() => applicationContext.Services.DataTypeService); - - config.CreateMap(); + config.CreateMap(); //just maps the standard properties, does not map the value! config.CreateMap() @@ -67,7 +65,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(x => x.Udi, expression => expression.MapFrom(content => Udi.Create(Constants.UdiEntityType.DataType, content.Key))) .ForMember(display => display.AvailableEditors, expression => expression.ResolveUsing(new AvailablePropertyEditorsResolver(UmbracoConfig.For.UmbracoSettings().Content))) .ForMember(display => display.PreValues, expression => expression.ResolveUsing( - new PreValueDisplayResolver(lazyDataTypeService))) + new PreValueDisplayResolver(applicationContext.Services.DataTypeService))) .ForMember(display => display.SelectedEditor, expression => expression.MapFrom( definition => definition.PropertyEditorAlias.IsNullOrWhiteSpace() ? null : definition.PropertyEditorAlias)) .ForMember(x => x.HasPrevalues, expression => expression.Ignore()) @@ -90,7 +88,7 @@ namespace Umbraco.Web.Models.Mapping config.CreateMap>() .ConvertUsing(definition => { - var resolver = new PreValueDisplayResolver(lazyDataTypeService); + var resolver = new PreValueDisplayResolver(applicationContext.Services.DataTypeService); return resolver.Convert(definition); }); @@ -124,4 +122,4 @@ namespace Umbraco.Web.Models.Mapping }); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs index 157205eb51..9a0480f771 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaModelMapper.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.Trashed, expression => expression.MapFrom(content => content.Trashed)) .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) + .ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new ContentTreeNodeUrlResolver())) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.Published, expression => expression.Ignore()) @@ -74,23 +74,14 @@ namespace Umbraco.Web.Models.Mapping { // Adapted from ContentModelMapper //map the IsChildOfListView (this is actually if it is a descendant of a list view!) + //TODO: STOP using these extension methods, they are not testable and require singletons to be setup var parent = media.Parent(); display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || contentTypeService.HasContainerInPath(parent.Path)); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Id.ToString(), null)); - display.TreeNodeUrl = url; - } - + if (media.ContentType.IsContainer) { TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText); } - - TabsAndPropertiesResolver.MapGenericProperties(media, display, localizedText); } /// @@ -101,9 +92,7 @@ namespace Umbraco.Web.Models.Mapping { protected override ContentTypeBasic ResolveCore(IMedia source) { - //TODO: This would be much nicer with the IUmbracoContextAccessor so we don't use singletons - //If this is a web request and there's a user signed in and the - // user has access to the settings section, we will + //TODO: We can resolve the UmbracoContext from the IValueResolver options! if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants diff --git a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs index e3c82cf782..5f5c42f9dc 100644 --- a/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/MemberModelMapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Web; -using System.Web.Mvc; using System.Web.Routing; using System.Web.Security; using AutoMapper; @@ -14,7 +13,6 @@ using Umbraco.Web.Models.ContentEditing; using System.Linq; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Security; -using Umbraco.Web.Trees; using UserProfile = Umbraco.Web.Models.ContentEditing.UserProfile; namespace Umbraco.Web.Models.Mapping @@ -56,9 +54,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(member => member.SortOrder, expression => expression.Ignore()) .ForMember(member => member.AdditionalData, expression => expression.Ignore()) .ForMember(member => member.FailedPasswordAttempts, expression => expression.Ignore()) - .ForMember(member => member.DeletedDate, expression => expression.Ignore()) - //TODO: Support these eventually - .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) + .ForMember(member => member.DeletedDate, expression => expression.Ignore()) + //TODO: Support these eventually + .ForMember(member => member.PasswordQuestion, expression => expression.Ignore()) .ForMember(member => member.RawPasswordAnswerValue, expression => expression.Ignore()); //FROM IMember TO MediaItemDisplay @@ -69,10 +67,10 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.ContentTypeAlias, expression => expression.MapFrom(content => content.ContentType.Alias)) .ForMember(display => display.ContentTypeName, expression => expression.MapFrom(content => content.ContentType.Name)) .ForMember(display => display.Properties, expression => expression.Ignore()) - .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService))) + .ForMember(display => display.Tabs, expression => expression.ResolveUsing(new MemberTabsAndPropertiesResolver(applicationContext.Services.TextService, applicationContext.Services.MemberService, applicationContext.Services.UserService))) .ForMember(display => display.MemberProviderFieldMapping, expression => expression.ResolveUsing(new MemberProviderFieldMappingResolver())) .ForMember(display => display.MembershipScenario, - expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(new Lazy(() => applicationContext.Services.MemberTypeService)))) + expression => expression.ResolveUsing(new MembershipScenarioMappingResolver(applicationContext.Services.MemberTypeService))) .ForMember(display => display.Notifications, expression => expression.Ignore()) .ForMember(display => display.Errors, expression => expression.Ignore()) .ForMember(display => display.Published, expression => expression.Ignore()) @@ -81,9 +79,9 @@ namespace Umbraco.Web.Models.Mapping .ForMember(display => display.IsChildOfListView, expression => expression.Ignore()) .ForMember(display => display.Trashed, expression => expression.Ignore()) .ForMember(display => display.IsContainer, expression => expression.Ignore()) - .ForMember(display => display.TreeNodeUrl, expression => expression.Ignore()) - .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()) - .AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService)); + .ForMember(display => display.TreeNodeUrl, opt => opt.ResolveUsing(new MemberTreeNodeUrlResolver())) + .ForMember(display => display.HasPublishedVersion, expression => expression.Ignore()); + //.AfterMap((member, display) => MapGenericCustomProperties(applicationContext.Services.MemberService, applicationContext.Services.UserService, member, display, applicationContext.Services.TextService)); //FROM IMember TO MemberBasic config.CreateMap() @@ -100,14 +98,14 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.HasPublishedVersion, expression => expression.Ignore()); //FROM MembershipUser TO MemberBasic - config.CreateMap() + config.CreateMap() //we're giving this entity an ID of 0 - we cannot really map it but it needs an id so the system knows it's not a new entity .ForMember(member => member.Id, expression => expression.MapFrom(user => int.MaxValue)) .ForMember(display => display.Udi, expression => expression.Ignore()) .ForMember(member => member.CreateDate, expression => expression.MapFrom(user => user.CreationDate)) .ForMember(member => member.UpdateDate, expression => expression.MapFrom(user => user.LastActivityDate)) .ForMember(member => member.Key, expression => expression.MapFrom(user => user.ProviderUserKey.TryConvertTo().Result.ToString("N"))) - .ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile {Name = "Admin", UserId = 0})) + .ForMember(member => member.Owner, expression => expression.UseValue(new UserProfile { Name = "Admin", UserId = 0 })) .ForMember(member => member.Icon, expression => expression.UseValue("icon-user")) .ForMember(member => member.Name, expression => expression.MapFrom(user => user.UserName)) .ForMember(member => member.Email, expression => expression.MapFrom(content => content.Email)) @@ -137,119 +135,11 @@ namespace Umbraco.Web.Models.Mapping .ForMember(dto => dto.Properties, expression => expression.ResolveUsing(new MemberDtoPropertiesValueResolver())); } - /// - /// Maps the generic tab with custom properties for content - /// - /// - /// - /// - /// - /// - /// - /// If this is a new entity and there is an approved field then we'll set it to true by default. - /// - private static void MapGenericCustomProperties(IMemberService memberService, IUserService userService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) - { - var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); - - //map the tree node url - if (HttpContext.Current != null) - { - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(display.Key.ToString("N"), null)); - display.TreeNodeUrl = url; - } - - var genericProperties = new List - { - new ContentPropertyDisplay - { - Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/membertype"), - Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), - View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View - }, - GetLoginProperty(memberService, member, display, localizedText), - new ContentPropertyDisplay - { - Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("general/email"), - Value = display.Email, - View = "email", - Validation = {Mandatory = true} - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("password"), - //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists - // only when creating a new member and we want to have a generated password pre-filled. - Value = new Dictionary - { - {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, - {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, - }, - //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor - View = "changepassword", - //initialize the dictionary with the configuration from the default membership provider - Config = new Dictionary(membersProvider.GetConfiguration(userService)) - { - //the password change toggle will only be displayed if there is already a password assigned. - {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} - } - }, - new ContentPropertyDisplay - { - Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), - Label = localizedText.Localize("content/membergroup"), - Value = GetMemberGroupValue(display.Username), - View = "membergroups", - Config = new Dictionary {{"IsRequired", true}} - } - }; - - TabsAndPropertiesResolver.MapGenericProperties(member, display, localizedText, genericProperties, properties => - { - if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null - && UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) - { - var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); - - //Replace the doctype property - var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); - docTypeProperty.Value = new List - { - new - { - linkText = member.ContentType.Name, - url = memberTypeLink, - target = "_self", - icon = "icon-item-arrangement" - } - }; - docTypeProperty.View = "urllist"; - } - }); - - //check if there's an approval field - var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; - if (member.HasIdentity == false && provider != null) - { - var approvedField = provider.ApprovedPropertyTypeAlias; - var prop = display.Properties.FirstOrDefault(x => x.Alias == approvedField); - if (prop != null) - { - prop.Value = 1; - } - } - } - /// /// Returns the login property display field /// /// /// - /// /// /// /// @@ -257,13 +147,13 @@ namespace Umbraco.Web.Models.Mapping /// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively /// allow that. /// - internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, MemberDisplay display, ILocalizedTextService localizedText) + internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, ILocalizedTextService localizedText) { var prop = new ContentPropertyDisplay { Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Label = localizedText.Localize("login"), - Value = display.Username + Value = member.Username }; var scenario = memberService.GetMembershipScenario(); @@ -329,24 +219,60 @@ 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. + /// + /// This also ensures that the IsSensitive property display value is set based on the configured IMemberType property type /// - internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver + internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver, IValueResolver { private readonly ILocalizedTextService _localizedTextService; + private readonly IMemberService _memberService; + private readonly IUserService _userService; - public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService) + public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IMemberService memberService, IUserService userService) : base(localizedTextService) { _localizedTextService = localizedTextService; + _memberService = memberService; + _userService = userService; } public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, - IEnumerable ignoreProperties) : base(localizedTextService, ignoreProperties) + IEnumerable ignoreProperties, IMemberService memberService, IUserService userService) : base(localizedTextService, ignoreProperties) { _localizedTextService = localizedTextService; + _memberService = memberService; + _userService = userService; } - protected override IEnumerable> ResolveCore(IContentBase content) + /// + /// Explicitly implement to override behavior + /// + /// + /// + /// + /// This is required to get access to the object which allows us access to the mapping options + /// + ResolutionResult IValueResolver.Resolve(ResolutionResult source) + { + //call the base class logic + var result = base.Resolve(source); + + var member = (IMember)source.Value; + //now we can customize the result and use the ResolutionResult options to get the UmbracoContext + var tabs = (List>) result.Value; + + //now we can customize the result with the current context, we can get the UmbracoContext from the options + CustomizeProperties(source.GetUmbracoContext(), member, tabs); + + return result; + } + + /// + /// Overridden to deal with custom member properties + /// + /// + /// + protected override List> ResolveCore(IContentBase content) { var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); @@ -355,7 +281,7 @@ namespace Umbraco.Web.Models.Mapping .Select(x => x.Alias) .ToArray(); - var result = base.ResolveCore(content).ToArray(); + var result = base.ResolveCore(content); if (provider.IsUmbracoMembershipProvider() == false) { @@ -366,12 +292,10 @@ namespace Umbraco.Web.Models.Mapping isLockedOutProperty.View = "readonlyvalue"; isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); } - - return result; } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; //This is kind of a hack because a developer is supposed to be allowed to set their property editor - would have been much easier // if we just had all of the membeship provider fields on the member table :( @@ -382,17 +306,150 @@ namespace Umbraco.Web.Models.Mapping isLockedOutProperty.View = "readonlyvalue"; isLockedOutProperty.Value = _localizedTextService.Localize("general/no"); } - - return result; } + + return result; + } + + protected override IEnumerable GetCustomGenericProperties(IContentBase content) + { + var member = (IMember) content; + var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + var genericProperties = new List + { + new ContentPropertyDisplay + { + Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("content/membertype"), + //Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName), + Value = _localizedTextService.UmbracoDictionaryTranslate(member.ContentType.Name), + View = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View + }, + GetLoginProperty(_memberService, member, _localizedTextService), + new ContentPropertyDisplay + { + Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("general/email"), + Value = member.Email, + View = "email", + Validation = {Mandatory = true} + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("password"), + //NOTE: The value here is a json value - but the only property we care about is the generatedPassword one if it exists, the newPassword exists + // only when creating a new member and we want to have a generated password pre-filled. + Value = new Dictionary + { + {"generatedPassword", member.GetAdditionalDataValueIgnoreCase("GeneratedPassword", null)}, + {"newPassword", member.GetAdditionalDataValueIgnoreCase("NewPassword", null)}, + }, + //TODO: Hard coding this because the changepassword doesn't necessarily need to be a resolvable (real) property editor + View = "changepassword", + //initialize the dictionary with the configuration from the default membership provider + Config = new Dictionary(membersProvider.GetConfiguration(_userService)) + { + //the password change toggle will only be displayed if there is already a password assigned. + {"hasPassword", member.RawPasswordValue.IsNullOrWhiteSpace() == false} + } + }, + new ContentPropertyDisplay + { + Alias = string.Format("{0}membergroup", Constants.PropertyEditors.InternalGenericPropertiesPrefix), + Label = _localizedTextService.Localize("content/membergroup"), + Value = GetMemberGroupValue(member.Username), + View = "membergroups", + Config = new Dictionary {{"IsRequired", true}} + } + }; + + return genericProperties; + } + + /// + /// Performs some customizations for the properties based on the current context + /// + /// + /// + /// The current tab collection + /// + /// If this is a new entity and there is an approved field then we'll set it to true by default. + /// + private void CustomizeProperties(UmbracoContext umbracoContext, IMember member, List> tabs) + { + var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); + + if (umbracoContext != null && umbracoContext.Security.CurrentUser != null + && umbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings))) + { + var memberTypeLink = string.Format("#/member/memberTypes/edit/{0}", member.ContentTypeId); + + //Replace the doctype property + var docTypeProperty = tabs.SelectMany(x => x.Properties) + .First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix)); + docTypeProperty.Value = new List + { + new + { + linkText = member.ContentType.Name, + url = memberTypeLink, + target = "_self", + icon = "icon-item-arrangement" + } + }; + docTypeProperty.View = "urllist"; + } + + //check if there's an approval field + var provider = membersProvider as global::umbraco.providers.members.UmbracoMembershipProvider; + if (member.HasIdentity == false && provider != null) + { + var approvedField = provider.ApprovedPropertyTypeAlias; + var prop = tabs.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == approvedField); + if (prop != null) + { + prop.Value = 1; + } + } + } + + /// + /// Overridden to assign the IsSensitive property values + /// + /// + /// + /// + protected override List MapProperties(IContentBase content, List properties) + { + var result = base.MapProperties(content, properties); + var member = (IMember)content; + var memberType = member.ContentType; + var labelPropEditor = PropertyEditorResolver.Current.GetByAlias(Constants.PropertyEditors.NoEditAlias).ValueEditor.View; + //now update the IsSensitive value + foreach (var prop in result) + { + prop.IsSensitive = memberType.IsSensitiveProperty(prop.Alias); + //check permissions for viewing sensitive data + + if (prop.IsSensitive) + { + //replace this editor with a label + prop.View = labelPropEditor; + //replace the value + prop.Value = string.Format("{0}", _localizedTextService.Localize("content/isSensitiveValue")); + } + } + return result; } } internal class MembershipScenarioMappingResolver : ValueResolver { - private readonly Lazy _memberTypeService; + private readonly IMemberTypeService _memberTypeService; - public MembershipScenarioMappingResolver(Lazy memberTypeService) + public MembershipScenarioMappingResolver(IMemberTypeService memberTypeService) { _memberTypeService = memberTypeService; } @@ -405,7 +462,7 @@ namespace Umbraco.Web.Models.Mapping { return MembershipScenario.NativeUmbraco; } - var memberType = _memberTypeService.Value.Get(Constants.Conventions.MemberTypes.DefaultAlias); + var memberType = _memberTypeService.Get(Constants.Conventions.MemberTypes.DefaultAlias); return memberType != null ? MembershipScenario.CustomProviderWithUmbracoLink : MembershipScenario.StandaloneCustomProvider; @@ -432,7 +489,7 @@ namespace Umbraco.Web.Models.Mapping } else { - var umbracoProvider = (IUmbracoMemberTypeMembershipProvider) provider; + var umbracoProvider = (IUmbracoMemberTypeMembershipProvider)provider; return new Dictionary { @@ -444,4 +501,7 @@ namespace Umbraco.Web.Models.Mapping } } } -} \ No newline at end of file +} + + + diff --git a/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs new file mode 100644 index 0000000000..a0b99252d2 --- /dev/null +++ b/src/Umbraco.Web/Models/Mapping/MemberTreeNodeUrlResolver.cs @@ -0,0 +1,32 @@ +using System.Web.Mvc; +using AutoMapper; +using Umbraco.Core.Models; +using Umbraco.Web.Trees; + +namespace Umbraco.Web.Models.Mapping +{ + /// + /// Gets the tree node url for the IMember + /// + internal class MemberTreeNodeUrlResolver : IValueResolver + { + + public ResolutionResult Resolve(ResolutionResult source) + { + return source.New(ResolveCore(source, (IMember)source.Value), typeof(string)); + } + + private string ResolveCore(ResolutionResult res, IMember source) + { + var umbCtx = res.GetUmbracoContext(); + //map the tree node url + if (umbCtx != null) + { + var urlHelper = new UrlHelper(umbCtx.HttpContext.Request.RequestContext); + var url = urlHelper.GetUmbracoApiService(controller => controller.GetTreeNode(source.Key.ToString("N"), null)); + return url; + } + return null; + } + } +} diff --git a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs index 295a5df63d..f4e7a78504 100644 --- a/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/PreValueDisplayResolver.cs @@ -13,9 +13,9 @@ namespace Umbraco.Web.Models.Mapping { internal class PreValueDisplayResolver : ValueResolver> { - private readonly Lazy _dataTypeService; + private readonly IDataTypeService _dataTypeService; - public PreValueDisplayResolver(Lazy dataTypeService) + public PreValueDisplayResolver(IDataTypeService dataTypeService) { _dataTypeService = dataTypeService; } @@ -55,7 +55,7 @@ namespace Umbraco.Web.Models.Mapping } //set up the defaults - var dataTypeService = _dataTypeService.Value; + var dataTypeService = _dataTypeService; var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(source.Id); IDictionary dictionaryVals = preVals.FormatAsDictionary().ToDictionary(x => x.Key, x => (object)x.Value); var result = Enumerable.Empty().ToArray(); @@ -77,4 +77,4 @@ namespace Umbraco.Web.Models.Mapping return Convert(source); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index 4ac35b48f8..55a72cb704 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Models.Mapping /// /// Creates the tabs collection with properties assigned for display models /// - internal class TabsAndPropertiesResolver : ValueResolver>> + internal class TabsAndPropertiesResolver : ValueResolver>> { private readonly ILocalizedTextService _localizedTextService; protected IEnumerable IgnoreProperties { get; set; } @@ -35,60 +35,6 @@ namespace Umbraco.Web.Models.Mapping IgnoreProperties = ignoreProperties; } - /// - /// Maps properties on to the generic properties tab - /// - /// - /// - /// - /// - /// Any additional custom properties to assign to the generic properties tab. - /// - /// - /// - /// The generic properties tab is mapped during AfterMap and is responsible for - /// setting up the properties such as Created date, updated date, template selected, etc... - /// - public static void MapGenericProperties( - TPersisted content, - ContentItemDisplayBase display, - ILocalizedTextService localizedTextService, - IEnumerable customProperties = null, - Action> onGenericPropertiesMapped = null) - where TPersisted : IContentBase - { - var genericProps = display.Tabs.Single(x => x.Id == 0); - - //store the current props to append to the newly inserted ones - var currProps = genericProps.Properties.ToArray(); - - var contentProps = new List(); - - if (customProperties != null) - { - //add the custom ones - contentProps.AddRange(customProperties); - } - - //now add the user props - contentProps.AddRange(currProps); - - //callback - if (onGenericPropertiesMapped != null) - { - onGenericPropertiesMapped(contentProps); - } - - //re-assign - genericProps.Properties = contentProps; - - //Show or hide properties tab based on wether it has or not any properties - if (genericProps.Properties.Any() == false) - { - display.Tabs = display.Tabs.Where(x => x.Id != 0); - } - } - /// /// Adds the container (listview) tab to the document /// @@ -199,7 +145,7 @@ namespace Umbraco.Web.Models.Mapping display.Tabs = tabs; } - protected override IEnumerable> ResolveCore(IContentBase content) + protected override List> ResolveCore(IContentBase content) { var tabs = new List>(); @@ -224,10 +170,8 @@ namespace Umbraco.Web.Models.Mapping if (properties.Count == 0) continue; - // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. - var mappedProperties = Mapper.Map, IEnumerable>(properties.OrderBy(prop => prop.PropertyType.SortOrder)); - - TranslateProperties(mappedProperties); + //map the properties + var mappedProperties = MapProperties(content, properties); // add the tab // we need to pick an identifier... there is no "right" way... @@ -245,12 +189,40 @@ namespace Umbraco.Web.Models.Mapping }); } + MapGenericProperties(content, tabs); + + // activate the first tab + tabs[0].IsActive = true; + + return tabs; + } + + /// + /// Returns a collection of custom generic properties that exist on the generic properties tab + /// + /// + protected virtual IEnumerable GetCustomGenericProperties(IContentBase content) + { + return Enumerable.Empty(); + } + + /// + /// Maps properties on to the generic properties tab + /// + /// + /// + /// + /// 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) + { // 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 var noGroupProperties = content.GetNonGroupedProperties() - .Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored - var genericproperties = Mapper.Map, IEnumerable>(noGroupProperties).ToList(); - TranslateProperties(genericproperties); + .Where(x => IgnoreProperties.Contains(x.Alias) == false) // skip ignored + .ToList(); + var genericproperties = MapProperties(content, noGroupProperties); tabs.Add(new Tab { @@ -260,20 +232,54 @@ namespace Umbraco.Web.Models.Mapping Properties = genericproperties }); - // activate the first tab - tabs.First().IsActive = true; + var genericProps = tabs.Single(x => x.Id == 0); - return tabs; - } + //store the current props to append to the newly inserted ones + var currProps = genericProps.Properties.ToArray(); - private void TranslateProperties(IEnumerable properties) - { - // Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper - foreach (var prop in properties) + var contentProps = new List(); + + var customProperties = GetCustomGenericProperties(content); + if (customProperties != null) { - prop.Label = _localizedTextService.UmbracoDictionaryTranslate(prop.Label); - prop.Description = _localizedTextService.UmbracoDictionaryTranslate(prop.Description); + //add the custom ones + contentProps.AddRange(customProperties); + } + + //now add the user props + contentProps.AddRange(currProps); + + //re-assign + genericProps.Properties = contentProps; + + //Show or hide properties tab based on wether it has or not any properties + if (genericProps.Properties.Any() == false) + { + //loop throug the tabs, remove the one with the id of zero and exit the loop + for (var i = 0; i < tabs.Count; i++) + { + if (tabs[i].Id != 0) continue; + tabs.RemoveAt(i); + break; + } } } + + /// + /// Maps a list of to a list of + /// + /// + /// + /// + protected virtual List MapProperties(IContentBase content, List properties) + { + var result = Mapper.Map, IEnumerable>( + // Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties. + properties.OrderBy(prop => prop.PropertyType.SortOrder)) + .ToList(); + + return result; + } + } }