Fixes for U4-10900 Individual member data export functionality

This commit is contained in:
Shannon
2018-02-26 15:02:47 +11:00
parent e1821697d4
commit e8fd7e2373
9 changed files with 140 additions and 205 deletions

View File

@@ -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);

View File

@@ -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)

View File

@@ -293,7 +293,7 @@ AnotherContentFinder
public void Resolves_Actions()
{
var actions = _manager.ResolveActions();
Assert.AreEqual(39, actions.Count());
Assert.AreEqual(38, actions.Count());
}
[Test]

View File

@@ -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);
}
}
}
}
}

View File

@@ -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"
});
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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" />