Fixes for U4-10900 Individual member data export functionality
This commit is contained in:
@@ -199,15 +199,7 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
/// <param name="memberTypeId">Id of the MemberType</param>
|
||||
void DeleteMembersOfType(int memberTypeId);
|
||||
|
||||
/// <summary>
|
||||
/// Exports member data based on their unique Id
|
||||
/// </summary>
|
||||
/// <param name="key">The unique <see cref="Guid">member identifier</see></param>
|
||||
/// <param name="currentUser">The <see cref="IUser">user</see> requesting the export</param>
|
||||
/// <returns><see cref="HttpResponseMessage"/></returns>
|
||||
HttpResponseMessage ExportMemberData(Guid key, IUser currentUser);
|
||||
|
||||
|
||||
[Obsolete("Use the overload with 'long' parameter types instead")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
IEnumerable<IMember> FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
|
||||
|
||||
@@ -258,108 +258,6 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports member data based on their unique Id
|
||||
/// </summary>
|
||||
/// <param name="key">The unique <see cref="Guid">member identifier</see></param>
|
||||
/// <param name="currentUser">The <see cref="IUser">user</see> requesting the export</param>
|
||||
/// <returns><see cref="HttpResponseMessage"/></returns>
|
||||
public HttpResponseMessage ExportMemberData(Guid key, IUser currentUser)
|
||||
{
|
||||
var httpResponseMessage = new HttpResponseMessage();
|
||||
if (currentUser.HasAccessToSensitiveData() == false)
|
||||
{
|
||||
httpResponseMessage.StatusCode = HttpStatusCode.Forbidden;
|
||||
return httpResponseMessage;
|
||||
}
|
||||
|
||||
var memberPropertyFilter = new List<string>
|
||||
{
|
||||
"RawPasswordValue",
|
||||
"ParentId",
|
||||
"SortOrder",
|
||||
"Level",
|
||||
"Path",
|
||||
"CreatorId",
|
||||
"Version",
|
||||
"ContentTypeId",
|
||||
"HasIdentity",
|
||||
"PropertyGroups",
|
||||
"PropertyTypes",
|
||||
"ProviderUserKey",
|
||||
"ContentType"
|
||||
};
|
||||
var propertiesFilter = new List<string>
|
||||
{
|
||||
"PropertyType",
|
||||
"Version",
|
||||
"Id",
|
||||
"HasIdentity",
|
||||
"Key"
|
||||
};
|
||||
|
||||
var member = GetByKey(key);
|
||||
var memberProperties = member.GetType().GetProperties();
|
||||
var fileName = $"{member.Name}_{member.Email}.txt";
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var textWriter = new StreamWriter(memoryStream))
|
||||
{
|
||||
foreach (var memberProperty in memberProperties)
|
||||
{
|
||||
if (memberPropertyFilter.Contains(memberProperty.Name))
|
||||
continue;
|
||||
|
||||
var propertyValue = memberProperty.GetValue(member, null);
|
||||
var type = propertyValue?.GetType();
|
||||
|
||||
if (type == typeof(PropertyCollection))
|
||||
{
|
||||
textWriter.WriteLine("");
|
||||
textWriter.WriteLine("PROPERTIES");
|
||||
textWriter.WriteLine("**********");
|
||||
|
||||
if (propertyValue is PropertyCollection propertyCollection)
|
||||
{
|
||||
foreach (var property in propertyCollection)
|
||||
{
|
||||
var propProperties = property.GetType().GetProperties();
|
||||
|
||||
textWriter.WriteLine("Name : " + property.PropertyType.Name);
|
||||
|
||||
foreach (var p in propProperties)
|
||||
{
|
||||
if (propertiesFilter.Contains(p.Name)) continue;
|
||||
var pValue = p.GetValue(property, null);
|
||||
textWriter.WriteLine(p.Name + " : " + pValue);
|
||||
}
|
||||
|
||||
textWriter.WriteLine("------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
textWriter.WriteLine(memberProperty.Name + " : " + propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
textWriter.Flush();
|
||||
}
|
||||
|
||||
httpResponseMessage.Content = new ByteArrayContent(memoryStream.ToArray());
|
||||
httpResponseMessage.Content.Headers.Add("x-filename", fileName);
|
||||
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
|
||||
httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName;
|
||||
httpResponseMessage.StatusCode = HttpStatusCode.OK;
|
||||
|
||||
return httpResponseMessage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use the overload with 'long' parameter types instead")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public IEnumerable<IMember> FindMembersByDisplayName(string displayNameToMatch, int pageIndex, int pageSize, out int totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith)
|
||||
|
||||
@@ -293,7 +293,7 @@ AnotherContentFinder
|
||||
public void Resolves_Actions()
|
||||
{
|
||||
var actions = _manager.ResolveActions();
|
||||
Assert.AreEqual(39, actions.Count());
|
||||
Assert.AreEqual(38, actions.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
@@ -783,8 +786,132 @@ namespace Umbraco.Web.Editors
|
||||
[HttpGet]
|
||||
public HttpResponseMessage ExportMemberData(Guid key)
|
||||
{
|
||||
var currentUser = UmbracoContext.Current.Security.CurrentUser;
|
||||
return Services.MemberService.ExportMemberData(key, currentUser);
|
||||
var currentUser = Security.CurrentUser;
|
||||
|
||||
var httpResponseMessage = Request.CreateResponse();
|
||||
if (currentUser.HasAccessToSensitiveData() == false)
|
||||
{
|
||||
httpResponseMessage.StatusCode = HttpStatusCode.Forbidden;
|
||||
return httpResponseMessage;
|
||||
}
|
||||
|
||||
var member = Services.MemberService.GetByKey(key);
|
||||
var memberProperties = member.GetType().GetProperties().ToList();
|
||||
//since we want to write the property types last, we'll re-order this list
|
||||
var propertyTypesProperties = memberProperties.Where(x => x.PropertyType == typeof(PropertyCollection)).ToList();
|
||||
foreach (var propertyType in propertyTypesProperties)
|
||||
{
|
||||
memberProperties.Remove(propertyType);
|
||||
}
|
||||
//now re-add them to the end (there will only be one, but we'll do this just to be complete)
|
||||
foreach (var propertyTypesProperty in propertyTypesProperties)
|
||||
{
|
||||
memberProperties.Add(propertyTypesProperty);
|
||||
}
|
||||
|
||||
var fileName = $"{member.Name}_{member.Email}.txt";
|
||||
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
using (var textWriter = new StreamWriter(memoryStream))
|
||||
{
|
||||
foreach (var memberProperty in memberProperties)
|
||||
{
|
||||
ReportWriter.WritePropertyValue(member, memberProperty, textWriter);
|
||||
}
|
||||
|
||||
textWriter.Flush();
|
||||
}
|
||||
|
||||
httpResponseMessage.Content = new ByteArrayContent(memoryStream.ToArray());
|
||||
httpResponseMessage.Content.Headers.Add("x-filename", fileName);
|
||||
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
httpResponseMessage.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
|
||||
httpResponseMessage.Content.Headers.ContentDisposition.FileName = fileName;
|
||||
httpResponseMessage.StatusCode = HttpStatusCode.OK;
|
||||
|
||||
return httpResponseMessage;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ReportWriter
|
||||
{
|
||||
private static readonly List<string> MemberPropertyFilter = new List<string>
|
||||
{
|
||||
"RawPasswordValue",
|
||||
"RawPasswordAnswerValue",
|
||||
"ParentId",
|
||||
"SortOrder",
|
||||
"Level",
|
||||
"Path",
|
||||
"CreatorId",
|
||||
"Version",
|
||||
"ContentTypeId",
|
||||
"HasIdentity",
|
||||
"PropertyGroups",
|
||||
"PropertyTypes",
|
||||
"ProviderUserKey",
|
||||
"ContentType",
|
||||
"DeletedDate"
|
||||
};
|
||||
private static readonly List<string> PropertiesFilter = new List<string>
|
||||
{
|
||||
"PropertyType",
|
||||
"Version",
|
||||
"Id",
|
||||
"HasIdentity",
|
||||
"Key",
|
||||
"DeletedDate"
|
||||
};
|
||||
|
||||
public static void WritePropertyValue(object owner, PropertyInfo prop, StreamWriter textWriter)
|
||||
{
|
||||
if (owner == null) throw new ArgumentNullException(nameof(owner));
|
||||
if (prop == null) throw new ArgumentNullException(nameof(prop));
|
||||
if (textWriter == null) throw new ArgumentNullException(nameof(textWriter));
|
||||
if (MemberPropertyFilter.Contains(prop.Name))
|
||||
return;
|
||||
|
||||
var propertyValue = prop.GetValue(owner, null) ?? string.Empty;
|
||||
var type = prop.PropertyType;
|
||||
|
||||
if (propertyValue is DateTime time)
|
||||
{
|
||||
textWriter.WriteLine(prop.Name + " : " + time.ToIsoString());
|
||||
}
|
||||
else if (type == typeof(PropertyCollection))
|
||||
{
|
||||
textWriter.WriteLine("");
|
||||
textWriter.WriteLine("PROPERTIES");
|
||||
textWriter.WriteLine("**********");
|
||||
|
||||
if (propertyValue is PropertyCollection propertyCollection)
|
||||
{
|
||||
foreach (var property in propertyCollection)
|
||||
{
|
||||
var propProperties = property.GetType().GetProperties();
|
||||
|
||||
textWriter.WriteLine("Name : " + property.PropertyType.Name);
|
||||
|
||||
foreach (var p in propProperties)
|
||||
{
|
||||
if (PropertiesFilter.Contains(p.Name)) continue;
|
||||
|
||||
WritePropertyValue(property, p, textWriter); //recurse
|
||||
}
|
||||
|
||||
textWriter.WriteLine("------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
textWriter.WriteLine(prop.Name + " : " + propertyValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
using umbraco.cms.Actions;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Search;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
@@ -182,9 +182,12 @@ namespace Umbraco.Web.Trees
|
||||
//add delete option for all members
|
||||
menu.Items.Add<ActionDelete>(ui.Text("actions", ActionDelete.Instance.Alias));
|
||||
|
||||
if (UmbracoContext.Current.Security.CurrentUser.HasAccessToSensitiveData())
|
||||
if (Security.CurrentUser.HasAccessToSensitiveData())
|
||||
{
|
||||
menu.Items.Add<ActionExportMember>(ui.Text("actions", ActionExportMember.Instance.Alias));
|
||||
menu.Items.Add(new MenuItem("export", Services.TextService.Localize("actions/export"))
|
||||
{
|
||||
Icon = "download-alt"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
<TargetFrameworkProfile />
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
@@ -66,6 +67,7 @@
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp50</s:String></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp70</s:String></wpf:ResourceDictionary>
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using umbraco.BasePages;
|
||||
using umbraco.interfaces;
|
||||
|
||||
namespace umbraco.cms.Actions
|
||||
{
|
||||
public class ActionExportMember: IAction
|
||||
{
|
||||
//create singleton
|
||||
#pragma warning disable 612, 618
|
||||
private static readonly ActionExportMember m_instance = new ActionExportMember();
|
||||
#pragma warning restore 612, 618
|
||||
|
||||
/// <summary>
|
||||
/// A public constructor exists ONLY for backwards compatibility in regards to 3rd party add-ons.
|
||||
/// All Umbraco assemblies should use the singleton instantiation (this.Instance)
|
||||
/// When this applicatio is refactored, this constuctor should be made private.
|
||||
/// </summary>
|
||||
[Obsolete("Use the singleton instantiation instead of a constructor")]
|
||||
public ActionExportMember() { }
|
||||
|
||||
public static ActionExportMember Instance
|
||||
{
|
||||
get { return m_instance; }
|
||||
}
|
||||
|
||||
#region IAction Members
|
||||
|
||||
public char Letter
|
||||
{
|
||||
get
|
||||
{
|
||||
return 'E';
|
||||
}
|
||||
}
|
||||
|
||||
public string JsFunctionName
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("{0}.actionExportMember()", ClientTools.Scripts.GetAppActions);
|
||||
}
|
||||
}
|
||||
|
||||
public string JsSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string Alias
|
||||
{
|
||||
get
|
||||
{
|
||||
return "export";
|
||||
}
|
||||
}
|
||||
|
||||
public string Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
return "download-alt";
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowInNotifier
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public bool CanBePermissionAssigned
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,6 @@
|
||||
<Compile Include="Actions\ActionDisable.cs" />
|
||||
<Compile Include="Actions\ActionEmptyTranscan.cs" />
|
||||
<Compile Include="Actions\ActionExport.cs" />
|
||||
<Compile Include="Actions\ActionExportMember.cs" />
|
||||
<Compile Include="Actions\ActionImport.cs" />
|
||||
<Compile Include="Actions\ActionMove.cs" />
|
||||
<Compile Include="Actions\ActionNew.cs" />
|
||||
|
||||
Reference in New Issue
Block a user