Port v7@2aa0dfb2c5 - WIP

This commit is contained in:
Stephan
2018-03-27 10:04:07 +02:00
parent a2a4edb3be
commit 0a4878d2a3
119 changed files with 3016 additions and 1376 deletions

View File

@@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
{
/// <summary>
/// A model representing a tour.
/// </summary>
[DataContract(Name = "tour", Namespace = "")]
public class BackOfficeTour
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "group")]
public string Group { get; set; }
[DataMember(Name = "groupOrder")]
public int GroupOrder { get; set; }
[DataMember(Name = "allowDisable")]
public bool AllowDisable { get; set; }
[DataMember(Name = "requiredSections")]
public List<string> RequiredSections { get; set; }
[DataMember(Name = "steps")]
public BackOfficeTourStep[] Steps { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
{
/// <summary>
/// A model representing the file used to load a tour.
/// </summary>
[DataContract(Name = "tourFile", Namespace = "")]
public class BackOfficeTourFile
{
/// <summary>
/// The file name for the tour
/// </summary>
[DataMember(Name = "fileName")]
public string FileName { get; set; }
/// <summary>
/// The plugin folder that the tour comes from
/// </summary>
/// <remarks>
/// If this is null it means it's a Core tour
/// </remarks>
[DataMember(Name = "pluginName")]
public string PluginName { get; set; }
[DataMember(Name = "tours")]
public IEnumerable<BackOfficeTour> Tours { get; set; }
}
}

View File

@@ -0,0 +1,61 @@
using System.Text.RegularExpressions;
namespace Umbraco.Web.Models
{
public class BackOfficeTourFilter
{
public Regex PluginName { get; private set; }
public Regex TourFileName { get; private set; }
public Regex TourAlias { get; private set; }
/// <summary>
/// Create a filter to filter out a whole plugin's tours
/// </summary>
/// <param name="pluginName"></param>
/// <returns></returns>
public static BackOfficeTourFilter FilterPlugin(Regex pluginName)
{
return new BackOfficeTourFilter(pluginName, null, null);
}
/// <summary>
/// Create a filter to filter out a whole tour file
/// </summary>
/// <param name="tourFileName"></param>
/// <returns></returns>
public static BackOfficeTourFilter FilterFile(Regex tourFileName)
{
return new BackOfficeTourFilter(null, tourFileName, null);
}
/// <summary>
/// Create a filter to filter out a tour alias, this will filter out the same alias found in all files
/// </summary>
/// <param name="tourAlias"></param>
/// <returns></returns>
public static BackOfficeTourFilter FilterAlias(Regex tourAlias)
{
return new BackOfficeTourFilter(null, null, tourAlias);
}
/// <summary>
/// Constructor to create a tour filter
/// </summary>
/// <param name="pluginName">Value to filter out tours by a plugin, can be null</param>
/// <param name="tourFileName">Value to filter out a tour file, can be null</param>
/// <param name="tourAlias">Value to filter out a tour alias, can be null</param>
/// <remarks>
/// Depending on what is null will depend on how the filter is applied.
/// If pluginName is not NULL and it's matched then we check if tourFileName is not NULL and it's matched then we check tour alias is not NULL and then match it,
/// if any steps is NULL then the filters upstream are applied.
/// Example, pluginName = "hello", tourFileName="stuff", tourAlias=NULL = we will filter out the tour file "stuff" from the plugin "hello" but not from other plugins if the same file name exists.
/// Example, tourAlias="test.*" = we will filter out all tour aliases that start with the word "test" regardless of the plugin or file name
/// </remarks>
public BackOfficeTourFilter(Regex pluginName, Regex tourFileName, Regex tourAlias)
{
PluginName = pluginName;
TourFileName = tourFileName;
TourAlias = tourAlias;
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
namespace Umbraco.Web.Models
{
/// <summary>
/// A model representing a step in a tour.
/// </summary>
[DataContract(Name = "step", Namespace = "")]
public class BackOfficeTourStep
{
[DataMember(Name = "title")]
public string Title { get; set; }
[DataMember(Name = "content")]
public string Content { get; set; }
[DataMember(Name = "type")]
public string Type { get; set; }
[DataMember(Name = "element")]
public string Element { get; set; }
[DataMember(Name = "elementPreventClick")]
public bool ElementPreventClick { get; set; }
[DataMember(Name = "backdropOpacity")]
public float? BackdropOpacity { get; set; }
[DataMember(Name = "event")]
public string Event { get; set; }
[DataMember(Name = "view")]
public string View { get; set; }
[DataMember(Name = "eventElement")]
public string EventElement { get; set; }
[DataMember(Name = "customProperties")]
public JObject CustomProperties { get; set; }
}
}

View File

@@ -1,24 +1,33 @@
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
[DataContract(Name = "auditLog", Namespace = "")]
public class AuditLog
{
[DataMember(Name = "userId", IsRequired = true)]
[DataMember(Name = "userId")]
public int UserId { get; set; }
[DataMember(Name = "nodeId", IsRequired = true)]
[DataMember(Name = "userName")]
public string UserName { get; set; }
[DataMember(Name = "userAvatars")]
public string[] UserAvatars { get; set; }
[DataMember(Name = "nodeId")]
public int NodeId { get; set; }
[DataMember(Name = "timestamp", IsRequired = true)]
[DataMember(Name = "timestamp")]
public DateTime Timestamp { get; set; }
[DataMember(Name = "logType", IsRequired = true)]
[DataMember(Name = "logType")]
public string LogType { get; set; }
[DataMember(Name = "comment", IsRequired = true)]
[DataMember(Name = "comment")]
public string Comment { get; set; }
}
}

View File

@@ -5,7 +5,6 @@ using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// A model representing a content item to be displayed in the back office
/// </summary>
@@ -29,6 +28,12 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "template")]
public string TemplateAlias { get; set; }
[DataMember(Name = "allowedTemplates")]
public IDictionary<string, string> AllowedTemplates { get; set; }
[DataMember(Name = "documentType")]
public ContentTypeBasic DocumentType { get; set; }
[DataMember(Name = "urls")]
public string[] Urls { get; set; }

View File

@@ -30,6 +30,11 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "editor", IsRequired = false)]
public string Editor { get; set; }
/// <summary>
/// Flags the property to denote that it can contain sensitive data
/// </summary>
[DataMember(Name = "isSensitive", IsRequired = false)]
public bool IsSensitive { get; set; }
/// <summary>
/// Used internally during model mapping

View File

@@ -35,5 +35,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "validation")]
public PropertyTypeValidation Validation { get; set; }
[DataMember(Name = "readonly")]
public bool Readonly { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// A model representing the navigation ("apps") inside an editor in the back office
/// </summary>
[DataContract(Name = "user", Namespace = "")]
public class EditorNavigation
{
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "icon")]
public string Icon { get; set; }
[DataMember(Name = "view")]
public string View { get; set; }
[DataMember(Name = "active")]
public bool Active { get; set; }
}
}

View File

@@ -2,10 +2,7 @@
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 Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Models.Validation;

View File

@@ -10,6 +10,10 @@ namespace Umbraco.Web.Models.ContentEditing
[DataContract(Name = "content", Namespace = "")]
public class MediaItemDisplay : ListViewAwareContentItemDisplayBase<ContentPropertyDisplay, IMedia>
{
[DataMember(Name = "contentType")]
public ContentTypeBasic ContentType { get; set; }
[DataMember(Name = "mediaLink")]
public string MediaLink { get; set; }
}
}

View File

@@ -13,5 +13,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "memberCanEdit")]
public bool MemberCanEditProperty { get; set; }
[DataMember(Name = "isSensitiveData")]
public bool IsSensitiveData { get; set; }
}
}

View File

@@ -10,5 +10,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "memberCanEdit")]
public bool MemberCanEditProperty { get; set; }
[DataMember(Name = "isSensitiveData")]
public bool IsSensitiveData { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System.Runtime.Serialization;
namespace Umbraco.Web.Models.ContentEditing
{
/// <summary>
/// Used to create a folder with the MediaController
/// </summary>
[DataContract]
public class PostedFolder
{
[DataMember(Name = "parentId")]
public string ParentId { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
}
}

View File

@@ -65,7 +65,5 @@ namespace Umbraco.Web.Models.ContentEditing
/// </summary>
[DataMember(Name = "allowedSections")]
public IEnumerable<string> AllowedSections { get; set; }
}
}

View File

@@ -18,8 +18,13 @@ namespace Umbraco.Web.Models.ContentEditing
AvailableCultures = new Dictionary<string, string>();
StartContentIds = new List<EntityBasic>();
StartMediaIds = new List<EntityBasic>();
Navigation = new List<EditorNavigation>();
}
[DataMember(Name = "navigation")]
[ReadOnly(true)]
public IEnumerable<EditorNavigation> Navigation { get; set; }
/// <summary>
/// Gets the available cultures (i.e. to populate a drop down)
/// The key is the culture stored in the database, the value is the Name

View File

@@ -11,33 +11,34 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ActionButtonsResolver
{
private readonly Lazy<IUserService> _userService;
public ActionButtonsResolver(Lazy<IUserService> userService)
public ActionButtonsResolver(IUserService userService, IContentService contentService)
{
_userService = userService;
UserService = userService;
ContentService = contentService;
}
private IUserService UserService { get; }
private IContentService ContentService { get; }
public IEnumerable<string> Resolve(IContent source)
{
//cannot check permissions without a context
if (UmbracoContext.Current == null)
{
//cannot check permissions without a context
return Enumerable.Empty<string>();
string path;
if (source.HasIdentity)
path = source.Path;
else
{
var parent = ContentService.GetById(source.ParentId);
path = parent == null ? "-1" : parent.Path;
}
var svc = _userService.Value;
var permissions = svc.GetPermissions(
//TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is
// with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null
// refrence exception :(
UmbracoContext.Current.Security.CurrentUser,
// Here we need to do a special check since this could be new content, in which case we need to get the permissions
// from the parent, not the existing one otherwise permissions would be coming from the root since Id is 0.
source.HasIdentity ? source.Id : source.ParentId)
.GetAllPermissions();
return permissions;
//TODO: This is certainly not ideal usage here - perhaps the best way to deal with this in the future is
// with the IUmbracoContextAccessor. In the meantime, if used outside of a web app this will throw a null
// refrence exception :(
return UserService.GetPermissionsForPath(UmbracoContext.Current.Security.CurrentUser, path).GetAllPermissions();
}
}
}

View File

@@ -0,0 +1,20 @@
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class AuditMapperProfile : Profile
{
public AuditMapperProfile()
{
CreateMap<IAuditItem, AuditLog>()
.ForMember(log => log.UserAvatars, expression => expression.Ignore())
.ForMember(log => log.UserName, expression => expression.Ignore())
.ForMember(log => log.NodeId, expression => expression.MapFrom(item => item.Id))
.ForMember(log => log.Timestamp, expression => expression.MapFrom(item => item.CreateDate))
.ForMember(log => log.LogType, expression => expression.MapFrom(item => item.AuditType));
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using AutoMapper;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Extends AutoMapper's <see cref="Mapper"/> class to handle Umbraco's context.
/// </summary>
internal static class ContextMapper
{
private const string UmbracoContextKey = "ContextMapper.UmbracoContext";
public static TDestination Map<TSource, TDestination>(TSource obj, UmbracoContext umbracoContext)
=> Mapper.Map<TSource, TDestination>(obj, opt => opt.Items[UmbracoContextKey] = umbracoContext);
public static UmbracoContext GetUmbracoContext(this ResolutionContext resolutionContext, bool throwIfMissing = true)
{
if (resolutionContext.Options.Items.TryGetValue(UmbracoContextKey, out var obj) && obj is UmbracoContext umbracoContext)
return umbracoContext;
// not sure this is a good idea at all
//return Current.UmbracoContext;
// better fail fast
if (throwIfMissing)
throw new InvalidOperationException("AutoMapper ResolutionContext does not contain an UmbracoContext.");
return null;
}
}
}

View File

@@ -0,0 +1,26 @@
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class ContentChildOfListViewResolver : IValueResolver<IContent, ContentItemDisplay, bool>
{
private readonly IContentService _contentService;
private readonly IContentTypeService _contentTypeService;
public ContentChildOfListViewResolver(IContentService contentService, IContentTypeService contentTypeService)
{
_contentService = contentService;
_contentTypeService = contentTypeService;
}
public bool Resolve(IContent source, ContentItemDisplay destination, bool destMember, ResolutionContext context)
{
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = _contentService.GetParent(source);
return parent != null && (parent.ContentType.IsContainer || _contentTypeService.HasContainerInPath(parent.Path));
}
}
}

View File

@@ -1,21 +1,28 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Linq;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Routing;
using Umbraco.Web.Trees;
using Umbraco.Web._Legacy.Actions;
namespace Umbraco.Web.Models.Mapping
{
internal class ContentUrlResolver : IValueResolver<IContent, ContentItemDisplay, string[]>
{
public string[] Resolve(IContent source, ContentItemDisplay destination, string[] destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext();
var urls = umbracoContext == null
? new[] {"Cannot generate urls without a current Umbraco Context"}
: source.GetContentUrls(umbracoContext).ToArray();
return urls;
}
}
/// <summary>
/// Declares how model mappings for content
/// </summary>
@@ -26,13 +33,17 @@ namespace Umbraco.Web.Models.Mapping
// create, capture, cache
var contentOwnerResolver = new OwnerResolver<IContent>(userService);
var creatorResolver = new CreatorResolver(userService);
var actionButtonsResolver = new ActionButtonsResolver(new Lazy<IUserService>(() => userService));
var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService);
var actionButtonsResolver = new ActionButtonsResolver(userService, contentService);
var tabsAndPropertiesResolver = new TabsAndPropertiesResolver<IContent, ContentItemDisplay>(textService);
var childOfListViewResolver = new ContentChildOfListViewResolver(contentService, contentTypeService);
var contentTypeBasicResolver = new ContentTypeBasicResolver<IContent, ContentItemDisplay>();
var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver<IContent, ContentTreeController>();
var defaultTemplateResolver = new DefaultTemplateResolver();
var contentUrlResolver = new ContentUrlResolver();
//FROM IContent TO ContentItemDisplay
CreateMap<IContent, ContentItemDisplay>()
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, src.Key)))
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src => Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key)))
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
.ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src)))
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
@@ -40,29 +51,35 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.ContentTypeName, opt => opt.MapFrom(src => src.ContentType.Name))
.ForMember(dest => dest.IsContainer, opt => opt.MapFrom(src => src.ContentType.IsContainer))
.ForMember(dest => dest.IsBlueprint, opt => opt.MapFrom(src => src.Blueprint))
.ForMember(dest => dest.IsChildOfListView, opt => opt.Ignore())
.ForMember(dest => dest.IsChildOfListView, opt => opt.ResolveUsing(childOfListViewResolver))
.ForMember(dest => dest.Trashed, opt => opt.MapFrom(src => src.Trashed))
.ForMember(dest => dest.PublishDate, opt => opt.MapFrom(src => src.PublishDate))
.ForMember(dest => dest.TemplateAlias, opt => opt.MapFrom(src => src.Template.Alias))
.ForMember(dest => dest.Urls, opt => opt.MapFrom(src =>
UmbracoContext.Current == null
? new[] {"Cannot generate urls without a current Umbraco Context"}
: src.GetContentUrls(UmbracoContext.Current)))
.ForMember(dest => dest.TemplateAlias, opt => opt.ResolveUsing(defaultTemplateResolver))
.ForMember(dest => dest.Urls, opt => opt.ResolveUsing(contentUrlResolver))
.ForMember(dest => dest.Properties, opt => opt.Ignore())
.ForMember(dest => dest.AllowPreview, opt => opt.Ignore())
.ForMember(dest => dest.TreeNodeUrl, opt => opt.Ignore())
.ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(contentTreeNodeUrlResolver))
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
.ForMember(dest => dest.Errors, opt => opt.Ignore())
.ForMember(dest => dest.Alias, opt => opt.Ignore())
.ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(src => tabsAndPropertiesResolver.Resolve(src)))
.ForMember(dest => dest.DocumentType, opt => opt.ResolveUsing(contentTypeBasicResolver))
.ForMember(dest => dest.AllowedTemplates, opt =>
opt.MapFrom(content => content.ContentType.AllowedTemplates
.Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false)
.ToDictionary(t => t.Alias, t => t.Name)))
.ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver))
.ForMember(dest => dest.AllowedActions, opt => opt.ResolveUsing(src => actionButtonsResolver.Resolve(src)))
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
.AfterMap((src, dest) => AfterMap(src, dest, dataTypeService, textService, contentTypeService, contentService));
.AfterMap((content, display) =>
{
if (content.ContentType.IsContainer)
TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, textService);
});
//FROM IContent TO ContentItemBasic<ContentPropertyBasic, IContent>
CreateMap<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>()
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, src.Key)))
Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key)))
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
.ForMember(dest => dest.Updater, opt => opt.ResolveUsing(src => creatorResolver.Resolve(src)))
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => src.ContentType.Icon))
@@ -74,141 +91,12 @@ namespace Umbraco.Web.Models.Mapping
//FROM IContent TO ContentItemDto<IContent>
CreateMap<IContent, ContentItemDto<IContent>>()
.ForMember(dest => dest.Udi, opt => opt.MapFrom(src =>
Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBluePrint : Constants.UdiEntityType.Document, src.Key)))
Udi.Create(src.Blueprint ? Constants.UdiEntityType.DocumentBlueprint : Constants.UdiEntityType.Document, src.Key)))
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(src => contentOwnerResolver.Resolve(src)))
.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());
}
/// <summary>
/// Maps the generic tab with custom properties for content
/// </summary>
/// <param name="content"></param>
/// <param name="display"></param>
/// <param name="dataTypeService"></param>
/// <param name="localizedText"></param>
/// <param name="contentTypeService"></param>
/// <param name="contentService"></param>
private static void AfterMap(IContent content, ContentItemDisplay display, IDataTypeService dataTypeService,
ILocalizedTextService localizedText, IContentTypeService contentTypeService, IContentService contentService)
{
// map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = content.Parent(contentService);
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<ContentTreeController>(controller => controller.GetTreeNode(display.Id.ToString(), null));
display.TreeNodeUrl = url;
}
//fill in the template config to be passed to the template drop down.
var templateItemConfig = new Dictionary<string, string> {{"", localizedText.Localize("general/choose") } };
foreach (var t in content.ContentType.AllowedTemplates
.Where(t => t.Alias.IsNullOrWhiteSpace() == false && t.Name.IsNullOrWhiteSpace() == false))
{
templateItemConfig.Add(t.Alias, t.Name);
}
if (content.ContentType.IsContainer)
{
TabsAndPropertiesResolver.AddListView(display, "content", dataTypeService, localizedText);
}
var properties = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype",
Label = localizedText.Localize("content/documentType"),
Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName),
View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View
},
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}releasedate",
Label = localizedText.Localize("content/releaseDate"),
Value = display.ReleaseDate?.ToIsoString(),
//Not editible for people without publish permission (U4-287)
View = display.AllowedActions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View,
Config = new Dictionary<string, object>
{
{"offsetTime", "1"}
}
//TODO: Fix up hard coded datepicker
},
new ContentPropertyDisplay
{
Alias = string.Format("{0}expiredate", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("content/unpublishDate"),
Value = display.ExpireDate.HasValue ? display.ExpireDate.Value.ToIsoString() : null,
//Not editible for people without publish permission (U4-287)
View = display.AllowedActions.Contains(ActionPublish.Instance.Letter.ToString(CultureInfo.InvariantCulture)) ? "datepicker" : Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View,
Config = new Dictionary<string, object>
{
{"offsetTime", "1"}
}
//TODO: Fix up hard coded datepicker
},
new ContentPropertyDisplay
{
Alias = string.Format("{0}template", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("template/template"),
Value = display.TemplateAlias,
View = "dropdown", //TODO: Hard coding until we make a real dropdown property editor to lookup
Config = new Dictionary<string, object>
{
{"items", templateItemConfig}
}
}
};
TabsAndPropertiesResolver.MapGenericProperties(content, display, localizedText, properties.ToArray(),
genericProperties =>
{
//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
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 currentDocumentType = contentTypeService.Get(display.ContentTypeAlias);
var currentDocumentTypeName = currentDocumentType == null ? string.Empty : localizedText.UmbracoDictionaryTranslate(currentDocumentType.Name);
var currentDocumentTypeId = currentDocumentType == null ? string.Empty : currentDocumentType.Id.ToString(CultureInfo.InvariantCulture);
//TODO: Hard coding this is not good
var docTypeLink = string.Format("#/settings/documenttypes/edit/{0}", currentDocumentTypeId);
//Replace the doc type property
var docTypeProperty = genericProperties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
docTypeProperty.Value = new List<object>
{
new
{
linkText = currentDocumentTypeName,
url = docTypeLink,
target = "_self",
icon = "icon-item-arrangement"
}
};
//TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor
docTypeProperty.View = "urllist";
}
// inject 'Link to document' as the first generic property
genericProperties.Insert(0, new ContentPropertyDisplay
{
Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("content/urls"),
Value = string.Join(",", display.Urls),
View = "urllist" //TODO: Hard coding this because the templatepicker doesn't necessarily need to be a resolvable (real) property editor
});
});
}
}
}

View File

@@ -15,13 +15,11 @@ namespace Umbraco.Web.Models.Mapping
internal class ContentPropertyBasicConverter<TDestination> : ITypeConverter<Property, TDestination>
where TDestination : ContentPropertyBasic, new()
{
private readonly Lazy<IDataTypeService> _dataTypeService;
protected IDataTypeService DataTypeService { get; }
protected IDataTypeService DataTypeService => _dataTypeService.Value;
public ContentPropertyBasicConverter(Lazy<IDataTypeService> dataTypeService)
public ContentPropertyBasicConverter(IDataTypeService dataTypeService)
{
_dataTypeService = dataTypeService;
DataTypeService = dataTypeService;
}
/// <summary>

View File

@@ -15,10 +15,13 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyDisplayConverter : ContentPropertyBasicConverter<ContentPropertyDisplay>
{
public ContentPropertyDisplayConverter(Lazy<IDataTypeService> dataTypeService)
: base(dataTypeService)
{ }
private readonly ILocalizedTextService _textService;
public ContentPropertyDisplayConverter(IDataTypeService dataTypeService, ILocalizedTextService textService)
: base(dataTypeService)
{
_textService = textService;
}
public override ContentPropertyDisplay Convert(Property originalProp, ContentPropertyDisplay dest, ResolutionContext context)
{
var display = base.Convert(originalProp, dest, context);
@@ -58,6 +61,10 @@ namespace Umbraco.Web.Models.Mapping
display.View = valEditor.View;
}
//Translate
display.Label = _textService.UmbracoDictionaryTranslate(display.Label);
display.Description = _textService.UmbracoDictionaryTranslate(display.Description);
return display;
}
}

View File

@@ -11,7 +11,7 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyDtoConverter : ContentPropertyBasicConverter<ContentPropertyDto>
{
public ContentPropertyDtoConverter(Lazy<IDataTypeService> dataTypeService)
public ContentPropertyDtoConverter(IDataTypeService dataTypeService)
: base(dataTypeService)
{ }

View File

@@ -1,5 +1,4 @@
using System;
using AutoMapper;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
@@ -12,13 +11,11 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentPropertyMapperProfile : Profile
{
private readonly IDataTypeService _dataTypeService;
public ContentPropertyMapperProfile(IDataTypeService dataTypeService)
public ContentPropertyMapperProfile(IDataTypeService dataTypeService, ILocalizedTextService textService)
{
_dataTypeService = dataTypeService;
var lazyDataTypeService = new Lazy<IDataTypeService>(() => _dataTypeService);
var contentPropertyBasicConverter = new ContentPropertyBasicConverter<ContentPropertyBasic>(dataTypeService);
var contentPropertyDtoConverter = new ContentPropertyDtoConverter(dataTypeService);
var contentPropertyDisplayConverter = new ContentPropertyDisplayConverter(dataTypeService, textService);
//FROM Property TO ContentPropertyBasic
CreateMap<PropertyGroup, Tab<ContentPropertyDisplay>>()
@@ -28,16 +25,13 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(tab => tab.Alias, expression => expression.Ignore());
//FROM Property TO ContentPropertyBasic
CreateMap<Property, ContentPropertyBasic>()
.ConvertUsing(new ContentPropertyBasicConverter<ContentPropertyBasic>(lazyDataTypeService));
CreateMap<Property, ContentPropertyBasic>().ConvertUsing(contentPropertyBasicConverter);
//FROM Property TO ContentPropertyDto
CreateMap<Property, ContentPropertyDto>()
.ConvertUsing(new ContentPropertyDtoConverter(lazyDataTypeService));
CreateMap<Property, ContentPropertyDto>().ConvertUsing(contentPropertyDtoConverter);
//FROM Property TO ContentPropertyDisplay
CreateMap<Property, ContentPropertyDisplay>()
.ConvertUsing(new ContentPropertyDisplayConverter(lazyDataTypeService));
CreateMap<Property, ContentPropertyDisplay>().ConvertUsing(contentPropertyDisplayConverter);
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Web.Mvc;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Gets the tree node url for the content or media
/// </summary>
internal class ContentTreeNodeUrlResolver<TSource, TController> : IValueResolver<TSource, object, string>
where TSource : IContentBase
where TController : ContentTreeControllerBase
{
public string Resolve(TSource source, object destination, string destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false);
if (umbracoContext == null) return null;
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
return urlHelper.GetUmbracoApiService<TController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Linq;
using System.Web;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Resolves a <see cref="ContentTypeBasic"/> from the <see cref="IContent"/> item and checks if the current user
/// has access to see this data
/// </summary>
internal class ContentTypeBasicResolver<TSource, TDestination> : IValueResolver<TSource, TDestination, ContentTypeBasic>
where TSource : IContentBase
{
public ContentTypeBasic Resolve(TSource source, TDestination destination, ContentTypeBasic destMember, ResolutionContext context)
{
//TODO: We can resolve the UmbracoContext from the IValueResolver options!
// OMG
if (HttpContext.Current != null && UmbracoContext.Current != null && UmbracoContext.Current.Security.CurrentUser != null
&& UmbracoContext.Current.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
{
ContentTypeBasic contentTypeBasic;
if (source is IContent content)
contentTypeBasic = Mapper.Map<ContentTypeBasic>(content.ContentType);
else if (source is IMedia media)
contentTypeBasic = Mapper.Map<ContentTypeBasic>(media.ContentType);
else
throw new NotSupportedException($"Expected TSource to be IContent or IMedia, got {typeof(TSource).Name}.");
return contentTypeBasic;
}
//no access
return null;
}
}
}

View File

@@ -14,39 +14,8 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentTypeMapperProfile : Profile
{
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
private readonly IContentTypeService _contentTypeService;
private readonly IMediaTypeService _mediaTypeService;
public ContentTypeMapperProfile(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService, IContentTypeService contentTypeService, IMediaTypeService mediaTypeService)
{
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
_fileService = fileService;
_contentTypeService = contentTypeService;
_mediaTypeService = mediaTypeService;
// v7 creates this map twice which makes no sense, and AutoMapper 6 detects it
// assuming the second map took over, and removing the first one for now
/*
CreateMap<PropertyTypeBasic, PropertyType>()
.ConstructUsing(basic => new PropertyType(_dataTypeService.GetDataTypeDefinitionById(basic.DataTypeId)))
.ForMember(type => type.ValidationRegExp, opt => opt.ResolveUsing(basic => basic.Validation.Pattern))
.ForMember(type => type.Mandatory, opt => opt.ResolveUsing(basic => basic.Validation.Mandatory))
.ForMember(type => type.Name, opt => opt.ResolveUsing(basic => basic.Label))
.ForMember(type => type.DataTypeDefinitionId, opt => opt.ResolveUsing(basic => basic.DataTypeId))
.ForMember(type => type.DataTypeId, opt => opt.Ignore())
.ForMember(type => type.PropertyEditorAlias, opt => opt.Ignore())
.ForMember(type => type.HelpText, opt => opt.Ignore())
.ForMember(type => type.Key, opt => opt.Ignore())
.ForMember(type => type.CreateDate, opt => opt.Ignore())
.ForMember(type => type.UpdateDate, opt => opt.Ignore())
.ForMember(type => type.DeletedDate, opt => opt.Ignore())
.ForMember(type => type.HasIdentity, opt => opt.Ignore());
*/
CreateMap<DocumentTypeSave, IContentType>()
//do the base mapping
.MapBaseContentTypeSaveToEntity<DocumentTypeSave, PropertyTypeBasic, IContentType>()
@@ -57,13 +26,15 @@ namespace Umbraco.Web.Models.Mapping
{
dest.AllowedTemplates = source.AllowedTemplates
.Where(x => x != null)
.Select(s => _fileService.GetTemplate(s))
.Select(s => fileService.GetTemplate(s))
.ToArray();
if (source.DefaultTemplate != null)
dest.SetDefaultTemplate(_fileService.GetTemplate(source.DefaultTemplate));
dest.SetDefaultTemplate(fileService.GetTemplate(source.DefaultTemplate));
else
dest.SetDefaultTemplate(null);
ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, _contentTypeService);
ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService);
});
CreateMap<MediaTypeSave, IMediaType>()
@@ -72,7 +43,7 @@ namespace Umbraco.Web.Models.Mapping
.ConstructUsing((source) => new MediaType(source.ParentId))
.AfterMap((source, dest) =>
{
ContentTypeProfileExtensions.AfterMapMediaTypeSaveToEntity(source, dest, _mediaTypeService);
ContentTypeProfileExtensions.AfterMapMediaTypeSaveToEntity(source, dest, mediaTypeService);
});
CreateMap<MemberTypeSave, IMemberType>()
@@ -81,9 +52,9 @@ namespace Umbraco.Web.Models.Mapping
.ConstructUsing(source => new MemberType(source.ParentId))
.AfterMap((source, dest) =>
{
ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, _contentTypeService);
ContentTypeProfileExtensions.AfterMapContentTypeSaveToEntity(source, dest, contentTypeService);
//map the MemberCanEditProperty,MemberCanViewProperty
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
foreach (var propertyType in source.Groups.SelectMany(x => x.Properties))
{
var localCopy = propertyType;
@@ -92,6 +63,7 @@ namespace Umbraco.Web.Models.Mapping
{
dest.SetMemberCanEditProperty(localCopy.Alias, localCopy.MemberCanEditProperty);
dest.SetMemberCanViewProperty(localCopy.Alias, localCopy.MemberCanViewProperty);
dest.SetIsSensitiveProperty(localCopy.Alias, localCopy.IsSensitiveData);
}
}
});
@@ -100,10 +72,10 @@ namespace Umbraco.Web.Models.Mapping
CreateMap<IMemberType, MemberTypeDisplay>()
//map base logic
.MapBaseContentTypeEntityToDisplay<IMemberType, MemberTypeDisplay, MemberPropertyTypeDisplay>(_propertyEditors, _dataTypeService, _contentTypeService)
.MapBaseContentTypeEntityToDisplay<IMemberType, MemberTypeDisplay, MemberPropertyTypeDisplay>(propertyEditors, dataTypeService, contentTypeService)
.AfterMap((memberType, display) =>
{
//map the MemberCanEditProperty,MemberCanViewProperty
//map the MemberCanEditProperty,MemberCanViewProperty,IsSensitiveData
foreach (var propertyType in memberType.PropertyTypes)
{
var localCopy = propertyType;
@@ -112,13 +84,14 @@ namespace Umbraco.Web.Models.Mapping
{
displayProp.MemberCanEditProperty = memberType.MemberCanEditProperty(localCopy.Alias);
displayProp.MemberCanViewProperty = memberType.MemberCanViewProperty(localCopy.Alias);
displayProp.IsSensitiveData = memberType.IsSensitiveProperty(localCopy.Alias);
}
}
});
CreateMap<IMediaType, MediaTypeDisplay>()
//map base logic
.MapBaseContentTypeEntityToDisplay<IMediaType, MediaTypeDisplay, PropertyTypeDisplay>(_propertyEditors, _dataTypeService, _contentTypeService)
.MapBaseContentTypeEntityToDisplay<IMediaType, MediaTypeDisplay, PropertyTypeDisplay>(propertyEditors, dataTypeService, contentTypeService)
.AfterMap((source, dest) =>
{
//default listview
@@ -127,14 +100,14 @@ namespace Umbraco.Web.Models.Mapping
if (string.IsNullOrEmpty(source.Name) == false)
{
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name;
if (_dataTypeService.GetDataType(name) != null)
if (dataTypeService.GetDataType(name) != null)
dest.ListViewEditorName = name;
}
});
CreateMap<IContentType, DocumentTypeDisplay>()
//map base logic
.MapBaseContentTypeEntityToDisplay<IContentType, DocumentTypeDisplay, PropertyTypeDisplay>(_propertyEditors, _dataTypeService, _contentTypeService)
.MapBaseContentTypeEntityToDisplay<IContentType, DocumentTypeDisplay, PropertyTypeDisplay>(propertyEditors, dataTypeService, contentTypeService)
.ForMember(dto => dto.AllowedTemplates, opt => opt.Ignore())
.ForMember(dto => dto.DefaultTemplate, opt => opt.Ignore())
.ForMember(display => display.Notifications, opt => opt.Ignore())
@@ -152,7 +125,7 @@ namespace Umbraco.Web.Models.Mapping
if (string.IsNullOrEmpty(source.Alias) == false)
{
var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias;
if (_dataTypeService.GetDataType(name) != null)
if (dataTypeService.GetDataType(name) != null)
dest.ListViewEditorName = name;
}
@@ -175,7 +148,7 @@ namespace Umbraco.Web.Models.Mapping
.ConstructUsing(propertyTypeBasic =>
{
var dataType = _dataTypeService.GetDataType(propertyTypeBasic.DataTypeId);
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);
})
@@ -226,7 +199,7 @@ namespace Umbraco.Web.Models.Mapping
//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());
var templates = fileService.GetTemplates(source.AllowedTemplates.ToArray());
dest.AllowedTemplates = source.AllowedTemplates
.Select(x => Mapper.Map<EntityBasic>(templates.SingleOrDefault(t => t.Alias == x)))
.WhereNotNull()
@@ -238,7 +211,7 @@ namespace Umbraco.Web.Models.Mapping
//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);
var template = fileService.GetTemplate(source.DefaultTemplate);
dest.DefaultTemplate = template == null ? null : Mapper.Map<EntityBasic>(template);
}
}

View File

@@ -0,0 +1,24 @@
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class DefaultTemplateResolver : IValueResolver<IContent, ContentItemDisplay, string>
{
public string Resolve(IContent source, ContentItemDisplay destination, string destMember, ResolutionContext context)
{
if (source == null || source.Template == null) return null;
var alias = source.Template.Alias;
//set default template if template isn't set
if (string.IsNullOrEmpty(alias))
alias = source.ContentType.DefaultTemplate == null
? string.Empty
: source.ContentType.DefaultTemplate.Alias;
return alias;
}
}
}

View File

@@ -27,7 +27,7 @@ namespace Umbraco.Web.Models.Mapping
CreateMap<EntitySlim, EntityBasic>()
.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.Trashed, opt => opt.MapFrom(src => src.Trashed))
.ForMember(dest => dest.Alias, opt => opt.Ignore())
.AfterMap((src, dest) =>
{

View File

@@ -0,0 +1,26 @@
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
internal class MediaChildOfListViewResolver : IValueResolver<IMedia, MediaItemDisplay, bool>
{
private readonly IMediaService _mediaService;
private readonly IMediaTypeService _mediaTypeService;
public MediaChildOfListViewResolver(IMediaService mediaService, IMediaTypeService mediaTypeService)
{
_mediaService = mediaService;
_mediaTypeService = mediaTypeService;
}
public bool Resolve(IMedia source, MediaItemDisplay destination, bool destMember, ResolutionContext context)
{
// 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));
}
}
}

View File

@@ -1,14 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AutoMapper;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Trees;
@@ -19,11 +14,14 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class MediaMapperProfile : Profile
{
public MediaMapperProfile(IUserService userService, ILocalizedTextService textService, IDataTypeService dataTypeService, IMediaService mediaService, ILogger logger)
public MediaMapperProfile(IUserService userService, ILocalizedTextService textService, IDataTypeService dataTypeService, IMediaService mediaService, IMediaTypeService mediaTypeService, ILogger logger)
{
// create, capture, cache
var mediaOwnerResolver = new OwnerResolver<IMedia>(userService);
var tabsAndPropertiesResolver = new TabsAndPropertiesResolver(textService);
var tabsAndPropertiesResolver = new TabsAndPropertiesResolver<IMedia, MediaItemDisplay>(textService);
var childOfListViewResolver = new MediaChildOfListViewResolver(mediaService, mediaTypeService);
var contentTreeNodeUrlResolver = new ContentTreeNodeUrlResolver<IMedia, MediaTreeController>();
var mediaTypeBasicResolver = new ContentTypeBasicResolver<IMedia, MediaItemDisplay>();
//FROM IMedia TO MediaItemDisplay
CreateMap<IMedia, MediaItemDisplay>()
@@ -31,20 +29,26 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.Owner, opt => opt.ResolveUsing(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.Ignore())
.ForMember(dest => dest.IsChildOfListView, opt => opt.ResolveUsing(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.Ignore())
.ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(contentTreeNodeUrlResolver))
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
.ForMember(dest => dest.Errors, opt => opt.Ignore())
.ForMember(dest => dest.Published, 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.ResolveUsing(src => tabsAndPropertiesResolver.Resolve(src)))
.ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver))
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore())
.AfterMap((src, dest) => AfterMap(src, dest, dataTypeService, textService, logger, mediaService));
.ForMember(dest => dest.ContentType, opt => opt.ResolveUsing(mediaTypeBasicResolver))
.ForMember(dest => dest.MediaLink, opt => opt.ResolveUsing(content => string.Join(",", content.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger))))
.AfterMap((media, display) =>
{
if (media.ContentType.IsContainer)
TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, textService);
});
//FROM IMedia TO ContentItemBasic<ContentPropertyBasic, IMedia>
CreateMap<IMedia, ContentItemBasic<ContentPropertyBasic, IMedia>>()
@@ -68,74 +72,5 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.Alias, opt => opt.Ignore())
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
}
private static void AfterMap(IMedia media, MediaItemDisplay display, IDataTypeService dataTypeService, ILocalizedTextService localizedText, ILogger logger, IMediaService mediaService)
{
// Adapted from ContentModelMapper
//map the IsChildOfListView (this is actually if it is a descendant of a list view!)
var parent = media.Parent();
display.IsChildOfListView = parent != null && (parent.ContentType.IsContainer || Current.Services.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<MediaTreeController>(controller => controller.GetTreeNode(display.Id.ToString(), null));
display.TreeNodeUrl = url;
}
if (media.ContentType.IsContainer)
{
TabsAndPropertiesResolver.AddListView(display, "media", dataTypeService, localizedText);
}
var genericProperties = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("content/mediatype"),
Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName),
View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View
}
};
TabsAndPropertiesResolver.MapGenericProperties(media, 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 mediaTypeLink = string.Format("#/settings/mediatypes/edit/{0}", media.ContentTypeId);
//Replace the doctype property
var docTypeProperty = properties.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
docTypeProperty.Value = new List<object>
{
new
{
linkText = media.ContentType.Name,
url = mediaTypeLink,
target = "_self",
icon = "icon-item-arrangement"
}
};
docTypeProperty.View = "urllist";
}
// inject 'Link to media' as the first generic property
var links = media.GetUrls(UmbracoConfig.For.UmbracoSettings().Content, logger);
if (links.Any())
{
var link = new ContentPropertyDisplay
{
Alias = string.Format("{0}urls", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("media/urls"),
Value = string.Join(",", links),
View = "urllist"
};
properties.Insert(0, link);
}
});
}
}
}

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// A resolver to map <see cref="IMember"/> properties to a collection of <see cref="ContentPropertyBasic"/>
/// </summary>
internal class MemberBasicPropertiesResolver : IValueResolver<IMember, MemberBasic, IEnumerable<ContentPropertyBasic>>
{
public IEnumerable<ContentPropertyBasic> Resolve(IMember source, MemberBasic destination, IEnumerable<ContentPropertyBasic> destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext();
var result = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyBasic>>(
// Sort properties so items from different compositions appear in correct order (see U4-9298). Map sorted properties.
source.Properties.OrderBy(prop => prop.PropertyType.SortOrder))
.ToList();
var memberType = source.ContentType;
//now update the IsSensitive value
foreach (var prop in result)
{
//check if this property is flagged as sensitive
var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias);
//check permissions for viewing sensitive data
if (isSensitiveProperty && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
{
//mark this property as sensitive
prop.IsSensitive = true;
//clear the value
prop.Value = null;
}
}
return result;
}
}
}

View File

@@ -1,19 +1,11 @@
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using System.Linq;
using Umbraco.Core.Security;
using Umbraco.Core.Services.Implement;
using Umbraco.Web.Composing;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Models.Mapping
{
@@ -26,18 +18,15 @@ namespace Umbraco.Web.Models.Mapping
{
// create, capture, cache
var memberOwnerResolver = new OwnerResolver<IMember>(userService);
var tabsAndPropertiesResolver = new MemberTabsAndPropertiesResolver(textService);
var tabsAndPropertiesResolver = new MemberTabsAndPropertiesResolver(textService, memberService, userService);
var memberProfiderFieldMappingResolver = new MemberProviderFieldResolver();
var membershipScenarioMappingResolver = new MembershipScenarioResolver(new Lazy<IMemberTypeService>(() => memberTypeService));
var membershipScenarioMappingResolver = new MembershipScenarioResolver(memberTypeService);
var memberDtoPropertiesResolver = new MemberDtoPropertiesResolver();
var memberTreeNodeUrlResolver = new MemberTreeNodeUrlResolver();
var memberBasicPropertiesResolver = new MemberBasicPropertiesResolver();
//FROM MembershipUser TO MediaItemDisplay - used when using a non-umbraco membership provider
CreateMap<MembershipUser, MemberDisplay>()
.ConvertUsing(user =>
{
var member = Mapper.Map<MembershipUser, IMember>(user);
return Mapper.Map<IMember, MemberDisplay>(member);
});
CreateMap<MembershipUser, MemberDisplay>().ConvertUsing<MembershipUserTypeConverter>();
//FROM MembershipUser TO IMember - used when using a non-umbraco membership provider
CreateMap<MembershipUser, IMember>()
@@ -75,7 +64,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.ResolveUsing(src => tabsAndPropertiesResolver.Resolve(src)))
.ForMember(dest => dest.Tabs, opt => opt.ResolveUsing(tabsAndPropertiesResolver))
.ForMember(dest => dest.MemberProviderFieldMapping, opt => opt.ResolveUsing(src => memberProfiderFieldMappingResolver.Resolve(src)))
.ForMember(dest => dest.MembershipScenario, opt => opt.ResolveUsing(src => membershipScenarioMappingResolver.Resolve(src)))
.ForMember(dest => dest.Notifications, opt => opt.Ignore())
@@ -86,8 +75,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.IsChildOfListView, opt => opt.Ignore())
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
.ForMember(dest => dest.IsContainer, opt => opt.Ignore())
.ForMember(dest => dest.TreeNodeUrl, opt => opt.Ignore())
.AfterMap((src, dest) => MapGenericCustomProperties(memberService, userService, src, dest, textService));
.ForMember(dest => dest.TreeNodeUrl, opt => opt.ResolveUsing(memberTreeNodeUrlResolver));
//FROM IMember TO MemberBasic
CreateMap<IMember, MemberBasic>()
@@ -100,7 +88,8 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.Trashed, opt => opt.Ignore())
.ForMember(dest => dest.Published, opt => opt.Ignore())
.ForMember(dest => dest.Updater, opt => opt.Ignore())
.ForMember(dest => dest.Alias, opt => opt.Ignore());
.ForMember(dest => dest.Alias, opt => opt.Ignore())
.ForMember(dto => dto.Properties, expression => expression.ResolveUsing(memberBasicPropertiesResolver));
//FROM MembershipUser TO MemberBasic
CreateMap<MembershipUser, MemberBasic>()
@@ -110,7 +99,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.CreateDate, opt => opt.MapFrom(src => src.CreationDate))
.ForMember(dest => dest.UpdateDate, opt => opt.MapFrom(src => src.LastActivityDate))
.ForMember(dest => dest.Key, opt => opt.MapFrom(src => src.ProviderUserKey.TryConvertTo<Guid>().Result.ToString("N")))
.ForMember(dest => dest.Owner, opt => opt.UseValue(new ContentEditing.UserProfile {Name = "Admin", UserId = 0}))
.ForMember(dest => dest.Owner, opt => opt.UseValue(new UserProfile {Name = "Admin", UserId = 0}))
.ForMember(dest => dest.Icon, opt => opt.UseValue("icon-user"))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.UserName))
.ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email))
@@ -148,170 +137,5 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.Alias, opt => opt.Ignore())
.ForMember(dest => dest.Path, opt => opt.Ignore());
}
/// <summary>
/// Maps the generic tab with custom properties for content
/// </summary>
/// <param name="memberService"></param>
/// <param name="userService"></param>
/// <param name="member"></param>
/// <param name="display"></param>
/// <param name="localizedText"></param>
/// <remarks>
/// If this is a new entity and there is an approved field then we'll set it to true by default.
/// </remarks>
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<MemberTreeController>(controller => controller.GetTreeNode(display.Key.ToString("N"), null));
display.TreeNodeUrl = url;
}
var genericProperties = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("content/membertype"),
Value = localizedText.UmbracoDictionaryTranslate(display.ContentTypeName),
View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().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<string, object>
{
// fixme why ignoreCase, what are we doing here?!
{"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<string, object>(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<string, object> {{"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<object>
{
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 IUmbracoMemberTypeMembershipProvider;
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;
}
}
}
/// <summary>
/// Returns the login property display field
/// </summary>
/// <param name="memberService"></param>
/// <param name="member"></param>
/// <param name="display"></param>
/// <param name="localizedText"></param>
/// <returns></returns>
/// <remarks>
/// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if
/// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively
/// allow that.
/// </remarks>
internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, MemberDisplay display, ILocalizedTextService localizedText)
{
var prop = new ContentPropertyDisplay
{
Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = localizedText.Localize("login"),
Value = display.Username
};
var scenario = memberService.GetMembershipScenario();
//only allow editing if this is a new member, or if the membership provider is the umbraco one
if (member.HasIdentity == false || scenario == MembershipScenario.NativeUmbraco)
{
prop.View = "textbox";
prop.Validation.Mandatory = true;
}
else
{
prop.View = "readonlyvalue";
}
return prop;
}
internal static IDictionary<string, bool> GetMemberGroupValue(string username)
{
var userRoles = username.IsNullOrWhiteSpace() ? null : Roles.GetRolesForUser(username);
// create a dictionary of all roles (except internal roles) + "false"
var result = Roles.GetAllRoles().Distinct()
// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access
.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)
.ToDictionary(x => x, x => false);
// if user has no roles, just return the dictionary
if (userRoles == null) return result;
// else update the dictionary to "true" for the user roles (except internal roles)
foreach (var userRole in userRoles.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false))
result[userRole] = true;
return result;
}
}
}

View File

@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Web.Security;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
@@ -16,44 +20,50 @@ 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.
/// </remarks>
internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver
internal class MemberTabsAndPropertiesResolver : TabsAndPropertiesResolver<IMember, MemberDisplay>
{
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<string> ignoreProperties) : base(localizedTextService, ignoreProperties)
public MemberTabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties, IMemberService memberService, IUserService userService)
: base(localizedTextService, ignoreProperties)
{
_localizedTextService = localizedTextService;
_memberService = memberService;
_userService = userService;
}
public override IEnumerable<Tab<ContentPropertyDisplay>> Resolve(IContentBase content)
/// <inheritdoc />
/// <remarks>Overriden to deal with custom member properties and permissions.</remarks>
public override IEnumerable<Tab<ContentPropertyDisplay>> Resolve(IMember source, MemberDisplay destination, IEnumerable<Tab<ContentPropertyDisplay>> destMember, ResolutionContext context)
{
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
IgnoreProperties = content.PropertyTypes
IgnoreProperties = source.PropertyTypes
.Where(x => x.HasIdentity == false)
.Select(x => x.Alias)
.ToArray();
var result = base.Resolve(content).ToArray();
var resolved = base.Resolve(source, destination, destMember, context);
if (provider.IsUmbracoMembershipProvider() == false)
{
//it's a generic provider so update the locked out property based on our known constant alias
var isLockedOutProperty = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut);
if (isLockedOutProperty != null && isLockedOutProperty.Value.ToString() != "1")
var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == Constants.Conventions.Member.IsLockedOut);
if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1")
{
isLockedOutProperty.View = "readonlyvalue";
isLockedOutProperty.Value = _localizedTextService.Localize("general/no");
}
return result;
}
else
{
@@ -62,15 +72,186 @@ namespace Umbraco.Web.Models.Mapping
//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 :(
// TODO: But is there a way to map the IMember.IsLockedOut to the property ? i dunno.
var isLockedOutProperty = result.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == umbracoProvider.LockPropertyTypeAlias);
if (isLockedOutProperty != null && isLockedOutProperty.Value.ToString() != "1")
var isLockedOutProperty = resolved.SelectMany(x => x.Properties).FirstOrDefault(x => x.Alias == umbracoProvider.LockPropertyTypeAlias);
if (isLockedOutProperty?.Value != null && isLockedOutProperty.Value.ToString() != "1")
{
isLockedOutProperty.View = "readonlyvalue";
isLockedOutProperty.Value = _localizedTextService.Localize("general/no");
}
return result;
}
var umbracoContext = context.GetUmbracoContext();
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}", source.ContentTypeId);
//Replace the doctype property
var docTypeProperty = resolved.SelectMany(x => x.Properties)
.First(x => x.Alias == string.Format("{0}doctype", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
docTypeProperty.Value = new List<object>
{
new
{
linkText = source.ContentType.Name,
url = memberTypeLink,
target = "_self",
icon = "icon-item-arrangement"
}
};
docTypeProperty.View = "urllist";
}
return resolved;
}
protected override IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
{
var member = (IMember) content;
var membersProvider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
var genericProperties = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}doctype",
Label = _localizedTextService.Localize("content/membertype"),
Value = _localizedTextService.UmbracoDictionaryTranslate(member.ContentType.Name),
View = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View
},
GetLoginProperty(_memberService, member, _localizedTextService),
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}email",
Label = _localizedTextService.Localize("general/email"),
Value = member.Email,
View = "email",
Validation = {Mandatory = true}
},
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}password",
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<string, object>
{
// fixme why ignoreCase, what are we doing here?!
{"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<string, object>(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 = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup",
Label = _localizedTextService.Localize("content/membergroup"),
Value = GetMemberGroupValue(member.Username),
View = "membergroups",
Config = new Dictionary<string, object> {{"IsRequired", true}}
}
};
return genericProperties;
}
/// <summary>
/// Overridden to assign the IsSensitive property values
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <returns></returns>
protected override List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
{
var result = base.MapProperties(umbracoContext, content, properties);
var member = (IMember)content;
var memberType = member.ContentType;
//now update the IsSensitive value
foreach (var prop in result)
{
//check if this property is flagged as sensitive
var isSensitiveProperty = memberType.IsSensitiveProperty(prop.Alias);
//check permissions for viewing sensitive data
if (isSensitiveProperty && umbracoContext.Security.CurrentUser.HasAccessToSensitiveData() == false)
{
//mark this property as sensitive
prop.IsSensitive = true;
//mark this property as readonly so that it does not post any data
prop.Readonly = true;
//replace this editor with a sensitivevalue
prop.View = "sensitivevalue";
//clear the value
prop.Value = null;
}
}
return result;
}
/// <summary>
/// Returns the login property display field
/// </summary>
/// <param name="memberService"></param>
/// <param name="member"></param>
/// <param name="display"></param>
/// <param name="localizedText"></param>
/// <returns></returns>
/// <remarks>
/// If the membership provider installed is the umbraco membership provider, then we will allow changing the username, however if
/// the membership provider is a custom one, we cannot allow chaning the username because MembershipProvider's do not actually natively
/// allow that.
/// </remarks>
internal static ContentPropertyDisplay GetLoginProperty(IMemberService memberService, IMember member, ILocalizedTextService localizedText)
{
var prop = new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}login",
Label = localizedText.Localize("login"),
Value = member.Username
};
var scenario = memberService.GetMembershipScenario();
//only allow editing if this is a new member, or if the membership provider is the umbraco one
if (member.HasIdentity == false || scenario == MembershipScenario.NativeUmbraco)
{
prop.View = "textbox";
prop.Validation.Mandatory = true;
}
else
{
prop.View = "readonlyvalue";
}
return prop;
}
internal static IDictionary<string, bool> GetMemberGroupValue(string username)
{
var userRoles = username.IsNullOrWhiteSpace() ? null : Roles.GetRolesForUser(username);
// create a dictionary of all roles (except internal roles) + "false"
var result = Roles.GetAllRoles().Distinct()
// if a role starts with __umbracoRole we won't show it as it's an internal role used for public access
.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false)
.ToDictionary(x => x, x => false);
// if user has no roles, just return the dictionary
if (userRoles == null) return result;
// else update the dictionary to "true" for the user roles (except internal roles)
foreach (var userRole in userRoles.Where(x => x.StartsWith(Constants.Conventions.Member.InternalRolePrefix) == false))
result[userRole] = true;
return result;
}
}
}

View File

@@ -0,0 +1,23 @@
using System.Web.Mvc;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Trees;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Gets the tree node url for the IMember
/// </summary>
internal class MemberTreeNodeUrlResolver : IValueResolver<IMember, MemberDisplay, string>
{
public string Resolve(IMember source, MemberDisplay destination, string destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext(throwIfMissing: false);
if (umbracoContext == null) return null;
var urlHelper = new UrlHelper(umbracoContext.HttpContext.Request.RequestContext);
return urlHelper.GetUmbracoApiService<MemberTreeController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
}
}
}

View File

@@ -9,9 +9,9 @@ namespace Umbraco.Web.Models.Mapping
{
internal class MembershipScenarioResolver
{
private readonly Lazy<IMemberTypeService> _memberTypeService;
private readonly IMemberTypeService _memberTypeService;
public MembershipScenarioResolver(Lazy<IMemberTypeService> memberTypeService)
public MembershipScenarioResolver(IMemberTypeService memberTypeService)
{
_memberTypeService = memberTypeService;
}
@@ -24,7 +24,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;

View File

@@ -0,0 +1,21 @@
using System.Web.Security;
using AutoMapper;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// A converter to go from a <see cref="MembershipUser"/> to a <see cref="MemberDisplay"/>
/// </summary>
internal class MembershipUserTypeConverter : ITypeConverter<MembershipUser, MemberDisplay>
{
public MemberDisplay Convert(MembershipUser source, MemberDisplay destination, ResolutionContext context)
{
//first convert to IMember
var member = Mapper.Map<MembershipUser, IMember>(source);
//then convert to MemberDisplay
return ContextMapper.Map<IMember, MemberDisplay>(member, context.GetUmbracoContext());
}
}
}

View File

@@ -1,18 +1,16 @@
using AutoMapper;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Core.Models;
using Umbraco.Web.Composing;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// A model mapper used to map models for the various dashboards
/// </summary>
internal class DashboardMapperProfile : Profile
internal class RedirectUrlMapperProfile : Profile
{
public DashboardMapperProfile()
public RedirectUrlMapperProfile()
{
CreateMap<IRedirectUrl, ContentRedirectUrl>()
.ForMember(x => x.OriginalUrl, expression => expression.MapFrom(item => UmbracoContext.Current.UrlProvider.GetUrlFromRoute(item.ContentId, item.Url)))
.ForMember(x => x.OriginalUrl, expression => expression.MapFrom(item => Current.UmbracoContext.UrlProvider.GetUrlFromRoute(item.ContentId, item.Url)))
.ForMember(x => x.DestinationUrl, expression => expression.Ignore())
.ForMember(x => x.RedirectId, expression => expression.MapFrom(item => item.Key));
}

View File

@@ -10,17 +10,14 @@ using Umbraco.Web.Composing;
namespace Umbraco.Web.Models.Mapping
{
/// <summary>
/// Creates the tabs collection with properties assigned for display models
/// </summary>
internal class TabsAndPropertiesResolver
internal abstract class TabsAndPropertiesResolver
{
private readonly ILocalizedTextService _localizedTextService;
protected ILocalizedTextService LocalizedTextService { get; }
protected IEnumerable<string> IgnoreProperties { get; set; }
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService)
{
_localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
LocalizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService));
IgnoreProperties = new List<string>();
}
@@ -29,87 +26,7 @@ namespace Umbraco.Web.Models.Mapping
{
IgnoreProperties = ignoreProperties ?? throw new ArgumentNullException(nameof(ignoreProperties));
}
/// <summary>
/// Maps properties on to the generic properties tab
/// </summary>
/// <param name="content"></param>
/// <param name="display"></param>
/// <param name="localizedTextService"></param>
/// <param name="customProperties">
/// Any additional custom properties to assign to the generic properties tab.
/// </param>
/// <param name="onGenericPropertiesMapped"></param>
/// <remarks>
/// 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...
/// </remarks>
public static void MapGenericProperties<TPersisted>(
TPersisted content,
ContentItemDisplayBase<ContentPropertyDisplay, TPersisted> display,
ILocalizedTextService localizedTextService,
IEnumerable<ContentPropertyDisplay> customProperties = null,
Action<List<ContentPropertyDisplay>> 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 labelEditor = Current.PropertyEditors[Constants.PropertyEditors.Aliases.NoEdit].GetValueEditor().View;
var contentProps = new List<ContentPropertyDisplay>
{
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}id",
Label = "Id",
Value = Convert.ToInt32(display.Id).ToInvariantString() + "<br/><small class='muted'>" + display.Key + "</small>",
View = labelEditor
},
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}creator",
Label = localizedTextService.Localize("content/createBy"),
Description = localizedTextService.Localize("content/createByDesc"),
Value = display.Owner.Name,
View = labelEditor
},
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}createdate",
Label = localizedTextService.Localize("content/createDate"),
Description = localizedTextService.Localize("content/createDateDesc"),
Value = display.CreateDate.ToIsoString(),
View = labelEditor
},
new ContentPropertyDisplay
{
Alias = $"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}updatedate",
Label = localizedTextService.Localize("content/updateDate"),
Description = localizedTextService.Localize("content/updateDateDesc"),
Value = display.UpdateDate.ToIsoString(),
View = labelEditor
}
};
if (customProperties != null)
{
//add the custom ones
contentProps.AddRange(customProperties);
}
//now add the user props
contentProps.AddRange(currProps);
//callback
onGenericPropertiesMapped?.Invoke(contentProps);
//re-assign
genericProps.Properties = contentProps;
}
/// <summary>
/// Adds the container (listview) tab to the document
/// </summary>
@@ -136,7 +53,7 @@ namespace Umbraco.Web.Models.Mapping
dtdId = Constants.DataTypes.DefaultMembersListView;
break;
default:
throw new ArgumentOutOfRangeException("entityType does not match a required value");
throw new ArgumentOutOfRangeException(nameof(entityType), "entityType does not match a required value");
}
//first try to get the custom one if there is one
@@ -220,15 +137,117 @@ namespace Umbraco.Web.Models.Mapping
display.Tabs = tabs;
}
public virtual IEnumerable<Tab<ContentPropertyDisplay>> Resolve(IContentBase content)
/// <summary>
/// Returns a collection of custom generic properties that exist on the generic properties tab
/// </summary>
/// <returns></returns>
protected virtual IEnumerable<ContentPropertyDisplay> GetCustomGenericProperties(IContentBase content)
{
return Enumerable.Empty<ContentPropertyDisplay>();
}
/// <summary>
/// Maps properties on to the generic properties tab
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="tabs"></param>
/// <remarks>
/// The generic properties tab is responsible for
/// setting up the properties such as Created date, updated date, template selected, etc...
/// </remarks>
protected virtual void MapGenericProperties(UmbracoContext umbracoContext, IContentBase content, List<Tab<ContentPropertyDisplay>> 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
.ToList();
var genericproperties = MapProperties(umbracoContext, content, noGroupProperties);
tabs.Add(new Tab<ContentPropertyDisplay>
{
Id = 0,
Label = LocalizedTextService.Localize("general/properties"),
Alias = "Generic properties",
Properties = genericproperties
});
var genericProps = 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<ContentPropertyDisplay>();
var customProperties = GetCustomGenericProperties(content);
if (customProperties != null)
{
//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;
}
}
}
/// <summary>
/// Maps a list of <see cref="Property"/> to a list of <see cref="ContentPropertyDisplay"/>
/// </summary>
/// <param name="umbracoContext"></param>
/// <param name="content"></param>
/// <param name="properties"></param>
/// <returns></returns>
protected virtual List<ContentPropertyDisplay> MapProperties(UmbracoContext umbracoContext, IContentBase content, List<Property> properties)
{
var result = Mapper.Map<IEnumerable<Property>, IEnumerable<ContentPropertyDisplay>>(
// 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;
}
}
/// <summary>
/// Creates the tabs collection with properties assigned for display models
/// </summary>
internal class TabsAndPropertiesResolver<TSource, TDestination> : TabsAndPropertiesResolver, IValueResolver<TSource, TDestination, IEnumerable<Tab<ContentPropertyDisplay>>>
where TSource : IContentBase
{
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService)
: base(localizedTextService)
{ }
public TabsAndPropertiesResolver(ILocalizedTextService localizedTextService, IEnumerable<string> ignoreProperties)
: base(localizedTextService, ignoreProperties)
{ }
public virtual IEnumerable<Tab<ContentPropertyDisplay>> Resolve(TSource source, TDestination destination, IEnumerable<Tab<ContentPropertyDisplay>> destMember, ResolutionContext context)
{
var umbracoContext = context.GetUmbracoContext();
var tabs = new List<Tab<ContentPropertyDisplay>>();
// add the tabs, for properties that belong to a tab
// need to aggregate the tabs, as content.PropertyGroups contains all the composition tabs,
// and there might be duplicates (content does not work like contentType and there is no
// content.CompositionPropertyGroups).
var groupsGroupsByName = content.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name);
var groupsGroupsByName = source.PropertyGroups.OrderBy(x => x.SortOrder).GroupBy(x => x.Name);
foreach (var groupsByName in groupsGroupsByName)
{
var properties = new List<Property>();
@@ -236,7 +255,7 @@ namespace Umbraco.Web.Models.Mapping
// merge properties for groups with the same name
foreach (var group in groupsByName)
{
var groupProperties = content.GetPropertiesForGroup(group)
var groupProperties = source.GetPropertiesForGroup(group)
.Where(x => IgnoreProperties.Contains(x.Alias) == false); // skip ignored
properties.AddRange(groupProperties);
@@ -245,14 +264,12 @@ 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<Property>, IEnumerable<ContentPropertyDisplay>>(properties.OrderBy(prop => prop.PropertyType.SortOrder));
TranslateProperties(mappedProperties);
//map the properties
var mappedProperties = MapProperties(umbracoContext, source, properties);
// add the tab
// we need to pick an identifier... there is no "right" way...
var g = groupsByName.FirstOrDefault(x => x.Id == content.ContentTypeId) // try local
var g = groupsByName.FirstOrDefault(x => x.Id == source.ContentTypeId) // try local
?? groupsByName.First(); // else pick one randomly
var groupId = g.Id;
var groupName = groupsByName.Key;
@@ -260,41 +277,19 @@ namespace Umbraco.Web.Models.Mapping
{
Id = groupId,
Alias = groupName,
Label = _localizedTextService.UmbracoDictionaryTranslate(groupName),
Label = LocalizedTextService.UmbracoDictionaryTranslate(groupName),
Properties = mappedProperties,
IsActive = false
});
}
// 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<Property>, IEnumerable<ContentPropertyDisplay>>(noGroupProperties).ToList();
TranslateProperties(genericproperties);
MapGenericProperties(umbracoContext, source, tabs);
tabs.Add(new Tab<ContentPropertyDisplay>
{
Id = 0,
Label = _localizedTextService.Localize("general/properties"),
Alias = "Generic properties",
Properties = genericproperties
});
// activate the first tab
tabs.First().IsActive = true;
// activate the first tab, if any
if (tabs.Count > 0)
tabs[0].IsActive = true;
return tabs;
}
private void TranslateProperties(IEnumerable<ContentPropertyDisplay> properties)
{
// Not sure whether it's a good idea to add this to the ContentPropertyDisplay mapper
foreach (var prop in properties)
{
prop.Label = _localizedTextService.UmbracoDictionaryTranslate(prop.Label);
prop.Description = _localizedTextService.UmbracoDictionaryTranslate(prop.Description);
}
}
}
}

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web.Models.Mapping
var userGroupDefaultPermissionsResolver = new UserGroupDefaultPermissionsResolver(textService, actions);
CreateMap<UserGroupSave, IUserGroup>()
.ConstructUsing((UserGroupSave save) => new UserGroup { CreateDate = DateTime.Now })
.ConstructUsing(save => new UserGroup { CreateDate = DateTime.UtcNow })
.IgnoreEntityCommonProperties()
.ForMember(dest => dest.Id, opt => opt.Condition(source => GetIntId(source.Id) > 0))
.ForMember(dest => dest.Id, opt => opt.MapFrom(source => GetIntId(source.Id)))
@@ -44,6 +44,7 @@ namespace Umbraco.Web.Models.Mapping
CreateMap<UserSave, IUser>()
.IgnoreEntityCommonProperties()
.ForMember(dest => dest.Id, opt => opt.Condition(src => GetIntId(src.Id) > 0))
.ForMember(detail => detail.TourData, opt => opt.Ignore())
.ForMember(dest => dest.SessionTimeout, opt => opt.Ignore())
.ForMember(dest => dest.EmailConfirmedDate, opt => opt.Ignore())
.ForMember(dest => dest.UserType, opt => opt.Ignore())
@@ -75,6 +76,7 @@ namespace Umbraco.Web.Models.Mapping
CreateMap<UserInvite, IUser>()
.IgnoreEntityCommonProperties()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(detail => detail.TourData, opt => opt.Ignore())
.ForMember(dest => dest.StartContentIds, opt => opt.Ignore())
.ForMember(dest => dest.StartMediaIds, opt => opt.Ignore())
.ForMember(dest => dest.UserType, opt => opt.Ignore())
@@ -200,9 +202,22 @@ namespace Umbraco.Web.Models.Mapping
var allContentPermissions = userService.GetPermissions(@group, true)
.ToDictionary(x => x.EntityId, x => x);
var contentEntities = allContentPermissions.Keys.Count == 0
? Array.Empty<IEntitySlim>()
: entityService.GetAll(UmbracoObjectTypes.Document, allContentPermissions.Keys.ToArray());
IEntitySlim[] contentEntities;
if (allContentPermissions.Keys.Count == 0)
{
contentEntities = Array.Empty<IEntitySlim>();
}
else
{
// a group can end up with way more than 2000 assigned permissions,
// so we need to break them into groups in order to avoid breaking
// the entity service due to too many Sql parameters.
var list = new List<IEntitySlim>();
foreach (var idGroup in allContentPermissions.Keys.InGroupsOf(2000))
list.AddRange(entityService.GetAll(UmbracoObjectTypes.Document, idGroup.ToArray()));
contentEntities = list.ToArray();
}
var allAssignedPermissions = new List<AssignedContentPermissions>();
foreach (var entity in contentEntities)
@@ -229,10 +244,11 @@ namespace Umbraco.Web.Models.Mapping
//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.
CreateMap<IUser, UserDisplay>()
.ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache)))
.ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(runtimeCache)))
.ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username))
.ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate))
.ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups))
.ForMember(detail => detail.Navigation, opt => opt.MapFrom(user => CreateUserEditorNavigation(textService)))
.ForMember(
dest => dest.CalculatedStartContentIds,
opt => opt.MapFrom(src => GetStartNodeValues(
@@ -278,7 +294,7 @@ namespace Umbraco.Web.Models.Mapping
//like the load time is waiting.
.ForMember(detail =>
detail.Avatars,
opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache)))
opt => opt.MapFrom(user => user.GetUserAvatarUrls(runtimeCache)))
.ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username))
.ForMember(dest => dest.UserGroups, opt => opt.MapFrom(user => user.Groups))
.ForMember(dest => dest.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?)user.LastLoginDate))
@@ -298,7 +314,7 @@ namespace Umbraco.Web.Models.Mapping
.ForMember(dest => dest.AdditionalData, opt => opt.Ignore());
CreateMap<IUser, UserDetail>()
.ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetCurrentUserAvatarUrls(userService, runtimeCache)))
.ForMember(dest => dest.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(runtimeCache)))
.ForMember(dest => dest.UserId, opt => opt.MapFrom(user => GetIntId(user.Id)))
.ForMember(dest => dest.StartContentIds, opt => opt.MapFrom(user => user.CalculateContentStartNodeIds(entityService)))
.ForMember(dest => dest.StartMediaIds, opt => opt.MapFrom(user => user.CalculateMediaStartNodeIds(entityService)))
@@ -340,18 +356,21 @@ namespace Umbraco.Web.Models.Mapping
CreateMap<IProfile, ContentEditing.UserProfile>()
.ForMember(dest => dest.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id)));
}
CreateMap<IUser, UserData>()
.ConstructUsing((IUser user) => new UserData())
.ForMember(dest => dest.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(dest => dest.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections.ToArray()))
.ForMember(dest => dest.RealName, opt => opt.MapFrom(user => user.Name))
.ForMember(dest => dest.Roles, opt => opt.MapFrom(user => user.Groups.Select(x => x.Alias).ToArray()))
.ForMember(dest => dest.StartContentNodes, opt => opt.MapFrom(user => user.CalculateContentStartNodeIds(entityService)))
.ForMember(dest => dest.StartMediaNodes, opt => opt.MapFrom(user => user.CalculateMediaStartNodeIds(entityService)))
.ForMember(dest => dest.Username, opt => opt.MapFrom(user => user.Username))
.ForMember(dest => dest.Culture, opt => opt.MapFrom(user => user.GetUserCulture(textService)))
.ForMember(dest => dest.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp));
private IEnumerable<EditorNavigation> CreateUserEditorNavigation(ILocalizedTextService textService)
{
return new[]
{
new EditorNavigation
{
Active = true,
Alias = "details",
Icon = "icon-umb-users",
Name = textService.Localize("general/user"),
View = "views/users/views/user/details.html"
}
};
}
private IEnumerable<EntityBasic> GetStartNodeValues(int[] startNodeIds,

View File

@@ -0,0 +1,9 @@
namespace Umbraco.Web.Models.Trees
{
/// <summary>
/// Represents the export member menu item
/// </summary>
[ActionMenuItem("umbracoMenuActions")]
public sealed class ExportMember : ActionMenuItem
{ }
}

View File

@@ -192,7 +192,7 @@ namespace Umbraco.Web.Models.Trees
else
{
// if that doesn't work, try to get the legacy confirm view
var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action, currentSection);
var attempt2 = LegacyTreeDataConverter.GetLegacyConfirmView(Action);
if (attempt2)
{
var view = attempt2.Result;

View File

@@ -30,7 +30,7 @@ namespace Umbraco.Web.Models.Trees
/// <param name="name">The text to display for the menu item, will default to the IAction alias if not specified</param>
internal MenuItem Add(IAction action, string name)
{
var item = new MenuItem(action);
var item = new MenuItem(action, name);
DetectLegacyActionMenu(action.GetType(), item);

View File

@@ -0,0 +1,60 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Web.Models
{
/// <summary>
/// A model representing the tours a user has taken/completed
/// </summary>
[DataContract(Name = "userTourStatus", Namespace = "")]
public class UserTourStatus : IEquatable<UserTourStatus>
{
/// <summary>
/// The tour alias
/// </summary>
[DataMember(Name = "alias")]
public string Alias { get; set; }
/// <summary>
/// If the tour is completed
/// </summary>
[DataMember(Name = "completed")]
public bool Completed { get; set; }
/// <summary>
/// If the tour is disabled
/// </summary>
[DataMember(Name = "disabled")]
public bool Disabled { get; set; }
public bool Equals(UserTourStatus other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(Alias, other.Alias);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((UserTourStatus) obj);
}
public override int GetHashCode()
{
return Alias.GetHashCode();
}
public static bool operator ==(UserTourStatus left, UserTourStatus right)
{
return Equals(left, right);
}
public static bool operator !=(UserTourStatus left, UserTourStatus right)
{
return !Equals(left, right);
}
}
}