Merge branch '7.0.0' of https://github.com/umbraco/Umbraco-CMS into 7.0.0

This commit is contained in:
perploug
2013-10-10 08:52:40 +02:00
52 changed files with 1223 additions and 352 deletions

View File

@@ -99,6 +99,10 @@ namespace Umbraco.Core
/// </summary> /// </summary>
public static class Member public static class Member
{ {
public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider";
public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider";
/// <summary> /// <summary>
/// Property alias for a Members Password Question /// Property alias for a Members Password Question
/// </summary> /// </summary>

View File

@@ -411,6 +411,11 @@ namespace Umbraco.Core
/// Alias for the XPath DropDownList datatype. /// Alias for the XPath DropDownList datatype.
/// </summary> /// </summary>
public const string XPathDropDownListAlias = "Umbraco.XPathDropDownList"; public const string XPathDropDownListAlias = "Umbraco.XPathDropDownList";
/// <summary>
/// Alias for the email address property editor
/// </summary>
public const string EmailAddressAlias = "Umbraco.EmailAddress";
} }
} }
} }

View File

@@ -20,6 +20,17 @@ namespace Umbraco.Core.Models
private object _providerUserKey; private object _providerUserKey;
private Type _userTypeKey; private Type _userTypeKey;
/// <summary>
/// Constructor for creating a Member object
/// </summary>
/// <param name="name">Name of the content</param>
/// <param name="contentType">ContentType for the current Content object</param>
public Member(string name, IMemberType contentType)
: base(name, -1, contentType, new PropertyCollection())
{
_contentType = contentType;
}
public Member(string name, string email, string username, string password, int parentId, IMemberType contentType) public Member(string name, string email, string username, string password, int parentId, IMemberType contentType)
: base(name, parentId, contentType, new PropertyCollection()) : base(name, parentId, contentType, new PropertyCollection())
{ {
@@ -427,7 +438,7 @@ namespace Umbraco.Core.Models
public override void ChangeTrashedState(bool isTrashed, int parentId = -20) public override void ChangeTrashedState(bool isTrashed, int parentId = -20)
{ {
throw new NotImplementedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid"); throw new NotSupportedException("Members can't be trashed as no Recycle Bin exists, so use of this method is invalid");
} }
/* Internal experiment - only used for mapping queries. /* Internal experiment - only used for mapping queries.

View File

@@ -2,6 +2,8 @@
namespace Umbraco.Core.Models.Membership namespace Umbraco.Core.Models.Membership
{ {
//TODO: THere's still a bunch of properties that don't exist in this use that need to be mapped somehow.
internal class UmbracoMembershipMember : MembershipUser internal class UmbracoMembershipMember : MembershipUser
{ {
private readonly IMember _member; private readonly IMember _member;
@@ -21,5 +23,44 @@ namespace Umbraco.Core.Models.Membership
get { return _member.Email; } get { return _member.Email; }
set { _member.Email = value; } set { _member.Email = value; }
} }
public override object ProviderUserKey
{
get { return _member.Key; }
}
public override System.DateTime CreationDate
{
get { return _member.CreateDate; }
}
public override string UserName
{
get { return _member.Username; }
}
public override string Comment
{
get { return _member.Comments; }
set { _member.Comments = value; }
}
public override bool IsApproved
{
get { return _member.IsApproved; }
set { _member.IsApproved = value; }
}
public override bool IsLockedOut
{
get { return _member.IsLockedOut; }
}
public override System.DateTime LastLoginDate
{
get { return _member.LastLoginDate; }
set { _member.LastLoginDate = value; }
}
} }
} }

View File

@@ -1,63 +1,63 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace Umbraco.Web.Models namespace Umbraco.Core.Models
{ {
/// <summary> /// <summary>
/// Represents a paged result for a model collection /// Represents a paged result for a model collection
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
[DataContract(Name = "pagedCollection", Namespace = "")] [DataContract(Name = "pagedCollection", Namespace = "")]
public class PagedResult<T> public class PagedResult<T>
{ {
public PagedResult(long totalItems, long pageNumber, long pageSize) public PagedResult(long totalItems, long pageNumber, long pageSize)
{ {
TotalItems = totalItems; TotalItems = totalItems;
PageNumber = pageNumber; PageNumber = pageNumber;
PageSize = pageSize; PageSize = pageSize;
if (pageSize > 0) if (pageSize > 0)
{ {
TotalPages = (long) Math.Ceiling(totalItems/(Decimal) pageSize); TotalPages = (long) Math.Ceiling(totalItems/(Decimal) pageSize);
} }
else else
{ {
TotalPages = 1; TotalPages = 1;
} }
} }
[DataMember(Name = "pageNumber")] [DataMember(Name = "pageNumber")]
public long PageNumber { get; private set; } public long PageNumber { get; private set; }
[DataMember(Name = "pageSize")] [DataMember(Name = "pageSize")]
public long PageSize { get; private set; } public long PageSize { get; private set; }
[DataMember(Name = "totalPages")] [DataMember(Name = "totalPages")]
public long TotalPages { get; private set; } public long TotalPages { get; private set; }
[DataMember(Name = "totalItems")] [DataMember(Name = "totalItems")]
public long TotalItems { get; private set; } public long TotalItems { get; private set; }
[DataMember(Name = "items")] [DataMember(Name = "items")]
public IEnumerable<T> Items { get; set; } public IEnumerable<T> Items { get; set; }
/// <summary> /// <summary>
/// Calculates the skip size based on the paged parameters specified /// Calculates the skip size based on the paged parameters specified
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Returns 0 if the page number or page size is zero /// Returns 0 if the page number or page size is zero
/// </remarks> /// </remarks>
internal int SkipSize internal int SkipSize
{ {
get get
{ {
if (PageNumber > 0 && PageSize > 0) if (PageNumber > 0 && PageSize > 0)
{ {
return Convert.ToInt32((PageNumber - 1)*PageSize); return Convert.ToInt32((PageNumber - 1)*PageSize);
} }
return 0; return 0;
} }
} }
} }
} }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -17,6 +18,19 @@ namespace Umbraco.Core.Persistence
internal static event CreateTableEventHandler NewTable; internal static event CreateTableEventHandler NewTable;
/// <summary>
/// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter
/// </summary>
/// <param name="db"></param>
/// <param name="value"></param>
/// <returns></returns>
public static string EscapeAtSymbols(this Database db, string value)
{
//this fancy regex will only match a single @ not a double, etc...
var regex = new Regex("(?<!@)@(?!@)");
return regex.Replace(value, "@@");
}
public static void CreateTable<T>(this Database db) public static void CreateTable<T>(this Database db)
where T : new() where T : new()
{ {

View File

@@ -11,5 +11,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="groupName"></param> /// <param name="groupName"></param>
/// <returns></returns> /// <returns></returns>
IEnumerable<IMember> GetByMemberGroup(string groupName); IEnumerable<IMember> GetByMemberGroup(string groupName);
} }
} }

View File

@@ -265,8 +265,10 @@ namespace Umbraco.Core.Persistence.Repositories
//Updates Modified date //Updates Modified date
((Member)entity).UpdatingEntity(); ((Member)entity).UpdatingEntity();
var dirtyEntity = (ICanBeDirty) entity;
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed //Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId")) if (dirtyEntity.IsPropertyDirty("ParentId"))
{ {
var parent = Database.First<NodeDto>("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); var parent = Database.First<NodeDto>("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId });
((IUmbracoEntity)entity).Path = string.Concat(parent.Path, ",", entity.Id); ((IUmbracoEntity)entity).Path = string.Concat(parent.Path, ",", entity.Id);
@@ -302,17 +304,44 @@ namespace Umbraco.Core.Persistence.Repositories
//Updates the current version - cmsContentVersion //Updates the current version - cmsContentVersion
//Assumes a Version guid exists and Version date (modified date) has been set/updated //Assumes a Version guid exists and Version date (modified date) has been set/updated
Database.Update(dto.ContentVersionDto); Database.Update(dto.ContentVersionDto);
//Updates the cmsMember entry
Database.Update(dto); //Updates the cmsMember entry if it has changed
var changedCols = new List<string>();
if (dirtyEntity.IsPropertyDirty("Email"))
{
changedCols.Add("Email");
}
if (dirtyEntity.IsPropertyDirty("Username"))
{
changedCols.Add("LoginName");
}
// DO NOT update the password if it is null or empty
if (dirtyEntity.IsPropertyDirty("Password") && entity.Password.IsNullOrWhiteSpace() == false)
{
changedCols.Add("Password");
}
//only update the changed cols
if (changedCols.Count > 0)
{
Database.Update(dto, changedCols);
}
//TODO ContentType for the Member entity //TODO ContentType for the Member entity
//Create the PropertyData for this version - cmsPropertyData //Create the PropertyData for this version - cmsPropertyData
var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id); var propertyFactory = new PropertyFactory(entity.ContentType, entity.Version, entity.Id);
var propertyDataDtos = propertyFactory.BuildDto(((Member)entity).Properties);
var keyDictionary = new Dictionary<int, int>(); var keyDictionary = new Dictionary<int, int>();
//Add Properties //Add Properties
// - don't try to save the property if it doesn't exist (or doesn't have an ID) on the content type
// - this can occur if the member type doesn't contain the built-in properties that the
// - member object contains.
var existingProperties = entity.Properties
.Where(property => entity.ContentType.PropertyTypes.Any(x => x.Alias == property.Alias && x.HasIdentity))
.ToList();
var propertyDataDtos = propertyFactory.BuildDto(existingProperties);
foreach (var propertyDataDto in propertyDataDtos) foreach (var propertyDataDto in propertyDataDtos)
{ {
if (propertyDataDto.Id > 0) if (propertyDataDto.Id > 0)
@@ -337,7 +366,7 @@ namespace Umbraco.Core.Persistence.Repositories
UpdatePropertyTags(entity, _tagRepository); UpdatePropertyTags(entity, _tagRepository);
((ICanBeDirty)entity).ResetDirtyProperties(); dirtyEntity.ResetDirtyProperties();
} }
protected override void PersistDeletedItem(IMember entity) protected override void PersistDeletedItem(IMember entity)

View File

@@ -40,6 +40,7 @@ using System.Security.Permissions;
[assembly: InternalsVisibleTo("Concorde.Sync")] [assembly: InternalsVisibleTo("Concorde.Sync")]
[assembly: InternalsVisibleTo("Umbraco.Belle")] [assembly: InternalsVisibleTo("Umbraco.Belle")]
[assembly: InternalsVisibleTo("Umbraco.VisualStudio")] [assembly: InternalsVisibleTo("Umbraco.VisualStudio")]
[assembly: InternalsVisibleTo("umbraco.providers")]
//allow this to be mocked in our unit tests //allow this to be mocked in our unit tests
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Umbraco.Core.Models;
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// A validator that validates an email address
/// </summary>
[ValueValidator("Email")]
internal sealed class EmailValidator : ManifestValueValidator, IPropertyValidator
{
public override IEnumerable<ValidationResult> Validate(object value, string config, PreValueCollection preValues, PropertyEditor editor)
{
var asString = value.ToString();
var emailVal = new EmailAddressAttribute();
if (emailVal.IsValid(asString) == false)
{
//TODO: localize these!
yield return new ValidationResult("Email is invalid", new[] { "value" });
}
}
public IEnumerable<ValidationResult> Validate(object value, PreValueCollection preValues, PropertyEditor editor)
{
return Validate(value, null, preValues, editor);
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Umbraco.Core.Publishing
/// <summary> /// <summary>
/// The result of publishing a content item /// The result of publishing a content item
/// </summary> /// </summary>
internal class PublishStatus public class PublishStatus
{ {
public PublishStatus() public PublishStatus()
{ {

View File

@@ -6,7 +6,7 @@ namespace Umbraco.Core.Publishing
/// <remarks> /// <remarks>
/// Anything less than 10 = Success! /// Anything less than 10 = Success!
/// </remarks> /// </remarks>
internal enum PublishStatusType public enum PublishStatusType
{ {
/// <summary> /// <summary>
/// The publishing was successful. /// The publishing was successful.

View File

@@ -627,28 +627,53 @@ namespace Umbraco.Core.Services
/// <param name="content">The <see cref="IContent"/> to publish</param> /// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param> /// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns> /// <returns>True if publishing succeeded, otherwise False</returns>
[Obsolete("Use PublishWithStatus instead, that method will provide more detailed information on the outcome")]
public bool Publish(IContent content, int userId = 0) public bool Publish(IContent content, int userId = 0)
{ {
var result = SaveAndPublishDo(content, userId); var result = SaveAndPublishDo(content, userId);
return result.Success; return result.Success;
} }
/// <summary>
/// Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
public Attempt<PublishStatus> PublishWithStatus(IContent content, int userId = 0)
{
return SaveAndPublishDo(content, userId);
}
/// <summary> /// <summary>
/// Publishes a <see cref="IContent"/> object and all its children /// Publishes a <see cref="IContent"/> object and all its children
/// </summary> /// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param> /// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param> /// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns> /// <returns>True if publishing succeeded, otherwise False</returns>
[Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")]
public bool PublishWithChildren(IContent content, int userId = 0) public bool PublishWithChildren(IContent content, int userId = 0)
{ {
var result = PublishWithChildrenDo(content, userId, true); var result = PublishWithChildrenDo(content, userId, true);
//This used to just return false only when the parent content failed, otherwise would always return true so we'll //This used to just return false only when the parent content failed, otherwise would always return true so we'll
// do the same thing for the moment // do the same thing for the moment
if (!result.Any(x => x.Result.ContentItem.Id == content.Id)) if (!result.Any(x => x.Result.ContentItem.Id == content.Id))
return false; return false;
return result.Single(x => x.Result.ContentItem.Id == content.Id).Success; return result.Single(x => x.Result.ContentItem.Id == content.Id).Success;
}
/// <summary>
/// Publishes a <see cref="IContent"/> object and all its children
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished">set to true if you want to also publish children that are currently unpublished</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
public IEnumerable<Attempt<PublishStatus>> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false)
{
return PublishWithChildrenDo(content, userId, includeUnpublished);
} }
/// <summary> /// <summary>
@@ -669,12 +694,25 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional Id of the User issueing the publishing</param> /// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param> /// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns> /// <returns>True if publishing succeeded, otherwise False</returns>
[Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")]
public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true) public bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true)
{ {
var result = SaveAndPublishDo(content, userId, raiseEvents); var result = SaveAndPublishDo(content, userId, raiseEvents);
return result.Success; return result.Success;
} }
/// <summary>
/// Saves and Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
public Attempt<PublishStatus> SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true)
{
return SaveAndPublishDo(content, userId, raiseEvents);
}
/// <summary> /// <summary>
/// Saves a single <see cref="IContent"/> object /// Saves a single <see cref="IContent"/> object
/// </summary> /// </summary>
@@ -1329,42 +1367,30 @@ namespace Umbraco.Core.Services
} }
#region Internal Methods #region Internal Methods
/// <summary>
/// Internal method that Publishes a single <see cref="IContent"/> object for legacy purposes.
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
internal Attempt<PublishStatus> PublishInternal(IContent content, int userId = 0)
{
return SaveAndPublishDo(content, userId);
}
/// <summary> ///// <summary>
/// Internal method that Publishes a <see cref="IContent"/> object and all its children for legacy purposes. ///// Internal method that Publishes a single <see cref="IContent"/> object for legacy purposes.
/// </summary> ///// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param> ///// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param> ///// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished">If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published</param> ///// <returns>True if publishing succeeded, otherwise False</returns>
/// <returns>True if publishing succeeded, otherwise False</returns> //internal Attempt<PublishStatus> PublishInternal(IContent content, int userId = 0)
internal IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal( //{
IContent content, int userId = 0, bool includeUnpublished = false) // return SaveAndPublishDo(content, userId);
{ //}
return PublishWithChildrenDo(content, userId, includeUnpublished);
}
/// <summary> ///// <summary>
/// Saves and Publishes a single <see cref="IContent"/> object ///// Internal method that Publishes a <see cref="IContent"/> object and all its children for legacy purposes.
/// </summary> ///// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param> ///// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param> ///// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param> ///// <param name="includeUnpublished">If set to true, this will also publish descendants that are completely unpublished, normally this will only publish children that have previously been published</param>
/// <returns>True if publishing succeeded, otherwise False</returns> ///// <returns>True if publishing succeeded, otherwise False</returns>
internal Attempt<PublishStatus> SaveAndPublishInternal(IContent content, int userId = 0, bool raiseEvents = true) //internal IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal(
{ // IContent content, int userId = 0, bool includeUnpublished = false)
return SaveAndPublishDo(content, userId, raiseEvents); //{
} // return PublishWithChildrenDo(content, userId, includeUnpublished);
//}
/// <summary> /// <summary>
/// Gets a collection of <see cref="IContent"/> descendants by the first Parent. /// Gets a collection of <see cref="IContent"/> descendants by the first Parent.
@@ -1462,7 +1488,7 @@ namespace Umbraco.Core.Services
/// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that /// then the list will only contain one status item, otherwise it will contain status items for it and all of it's descendants that
/// are to be published. /// are to be published.
/// </returns> /// </returns>
private IEnumerable<Attempt<PublishStatus>> PublishWithChildrenDo( private IEnumerable<Attempt<PublishStatus>> PublishWithChildrenDo(
IContent content, int userId = 0, bool includeUnpublished = false) IContent content, int userId = 0, bool includeUnpublished = false)
{ {
if (content == null) throw new ArgumentNullException("content"); if (content == null) throw new ArgumentNullException("content");

View File

@@ -346,36 +346,7 @@ namespace Umbraco.Core.Services
Audit.Add(AuditTypes.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1); Audit.Add(AuditTypes.Delete, string.Format("Delete ContentTypes performed by user"), userId, -1);
} }
} }
/// <summary>
/// Gets an <see cref="IMemberType"/> object by its Id
/// </summary>
/// <param name="id">Id of the <see cref="IMemberType"/> to retrieve</param>
/// <returns><see cref="IMemberType"/></returns>
public IMemberType GetMemberType(int id)
{
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
{
return repository.Get(id);
}
}
/// <summary>
/// Gets an <see cref="IMemberType"/> object by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="IMemberType"/> to retrieve</param>
/// <returns><see cref="IMemberType"/></returns>
public IMemberType GetMemberType(string alias)
{
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query<IMemberType>.Builder.Where(x => x.Alias == alias);
var contentTypes = repository.GetByQuery(query);
return contentTypes.FirstOrDefault();
}
}
/// <summary> /// <summary>
/// Gets an <see cref="IMediaType"/> object by its Id /// Gets an <see cref="IMediaType"/> object by its Id
/// </summary> /// </summary>

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Membership;
using Umbraco.Core.Publishing;
namespace Umbraco.Core.Services namespace Umbraco.Core.Services
{ {
@@ -246,16 +247,35 @@ namespace Umbraco.Core.Services
/// <param name="content">The <see cref="IContent"/> to publish</param> /// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param> /// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns> /// <returns>True if publishing succeeded, otherwise False</returns>
[Obsolete("Use PublishWithStatus instead, that method will provide more detailed information on the outcome")]
bool Publish(IContent content, int userId = 0); bool Publish(IContent content, int userId = 0);
/// <summary>
/// Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>The published status attempt</returns>
Attempt<PublishStatus> PublishWithStatus(IContent content, int userId = 0);
/// <summary> /// <summary>
/// Publishes a <see cref="IContent"/> object and all its children /// Publishes a <see cref="IContent"/> object and all its children
/// </summary> /// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param> /// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param> /// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <returns>True if publishing succeeded, otherwise False</returns> /// <returns>True if publishing succeeded, otherwise False</returns>
[Obsolete("Use PublishWithChildrenWithStatus instead, that method will provide more detailed information on the outcome and also allows the includeUnpublished flag")]
bool PublishWithChildren(IContent content, int userId = 0); bool PublishWithChildren(IContent content, int userId = 0);
/// <summary>
/// Publishes a <see cref="IContent"/> object and all its children
/// </summary>
/// <param name="content">The <see cref="IContent"/> to publish along with its children</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="includeUnpublished"></param>
/// <returns>The list of statuses for all published items</returns>
IEnumerable<Attempt<PublishStatus>> PublishWithChildrenWithStatus(IContent content, int userId = 0, bool includeUnpublished = false);
/// <summary> /// <summary>
/// UnPublishes a single <see cref="IContent"/> object /// UnPublishes a single <see cref="IContent"/> object
/// </summary> /// </summary>
@@ -271,8 +291,18 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional Id of the User issueing the publishing</param> /// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param> /// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns> /// <returns>True if publishing succeeded, otherwise False</returns>
[Obsolete("Use SaveAndPublishWithStatus instead, that method will provide more detailed information on the outcome")]
bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true); bool SaveAndPublish(IContent content, int userId = 0, bool raiseEvents = true);
/// <summary>
/// Saves and Publishes a single <see cref="IContent"/> object
/// </summary>
/// <param name="content">The <see cref="IContent"/> to save and publish</param>
/// <param name="userId">Optional Id of the User issueing the publishing</param>
/// <param name="raiseEvents">Optional boolean indicating whether or not to raise save events.</param>
/// <returns>True if publishing succeeded, otherwise False</returns>
Attempt<PublishStatus> SaveAndPublishWithStatus(IContent content, int userId = 0, bool raiseEvents = true);
/// <summary> /// <summary>
/// Permanently deletes an <see cref="IContent"/> object. /// Permanently deletes an <see cref="IContent"/> object.
/// </summary> /// </summary>

View File

@@ -66,21 +66,7 @@ namespace Umbraco.Core.Services
/// <remarks>Deleting a <see cref="IContentType"/> will delete all the <see cref="IContent"/> objects based on this <see cref="IContentType"/></remarks> /// <remarks>Deleting a <see cref="IContentType"/> will delete all the <see cref="IContent"/> objects based on this <see cref="IContentType"/></remarks>
/// <param name="userId">Optional Id of the User deleting the ContentTypes</param> /// <param name="userId">Optional Id of the User deleting the ContentTypes</param>
void Delete(IEnumerable<IContentType> contentTypes, int userId = 0); void Delete(IEnumerable<IContentType> contentTypes, int userId = 0);
/// <summary>
/// Gets an <see cref="IMemberType"/> object by its Id
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/> to retrieve</param>
/// <returns><see cref="IMediaType"/></returns>
IMemberType GetMemberType(int id);
/// <summary>
/// Gets an <see cref="IMemberType"/> object by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="IMediaType"/> to retrieve</param>
/// <returns><see cref="IMediaType"/></returns>
IMemberType GetMemberType(string alias);
/// <summary> /// <summary>
/// Gets an <see cref="IMediaType"/> object by its Id /// Gets an <see cref="IMediaType"/> object by its Id
/// </summary> /// </summary>

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Core.Services
IEnumerable<IMember> GetMembersByMemberType(string memberTypeAlias); IEnumerable<IMember> GetMembersByMemberType(string memberTypeAlias);
IEnumerable<IMember> GetMembersByGroup(string memberGroupName); IEnumerable<IMember> GetMembersByGroup(string memberGroupName);
IEnumerable<IMember> GetAllMembers(params int[] ids); IEnumerable<IMember> GetAllMembers(params int[] ids);
//TODO: Need to get all members that start with a certain letter //TODO: Need to get all members that start with a certain letter
} }
@@ -37,5 +37,7 @@ namespace Umbraco.Core.Services
void Delete(IMember membershipUser); void Delete(IMember membershipUser);
void Save(IMember membershipUser); void Save(IMember membershipUser);
IEnumerable<IMember> FindMembersByEmail(string emailStringToMatch);
} }
} }

View File

@@ -12,7 +12,18 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns> /// <returns>An Enumerable list of <see cref="IContentType"/> objects</returns>
IEnumerable<IMemberType> GetAllMemberTypes(params int[] ids); IEnumerable<IMemberType> GetAllMemberTypes(params int[] ids);
IMemberType GetMemberType(string alias); /// <summary>
/// Gets an <see cref="IMemberType"/> object by its Id
/// </summary>
/// <param name="id">Id of the <see cref="IMediaType"/> to retrieve</param>
/// <returns><see cref="IMediaType"/></returns>
IMemberType GetMemberType(int id); IMemberType GetMemberType(int id);
/// <summary>
/// Gets an <see cref="IMemberType"/> object by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="IMediaType"/> to retrieve</param>
/// <returns><see cref="IMediaType"/></returns>
IMemberType GetMemberType(string alias);
} }
} }

View File

@@ -108,6 +108,25 @@ namespace Umbraco.Core.Services
} }
} }
/// <summary>
/// Does a search for members that contain the specified string in their email address
/// </summary>
/// <param name="emailStringToMatch"></param>
/// <returns></returns>
public IEnumerable<IMember> FindMembersByEmail(string emailStringToMatch)
{
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMemberRepository(uow))
{
var query = new Query<IMember>();
query.Where(member => member.Email.Contains(emailStringToMatch));
return repository.GetByQuery(query);
}
}
/// <summary> /// <summary>
/// Gets a list of Members with a certain string property value /// Gets a list of Members with a certain string property value
/// </summary> /// </summary>

View File

@@ -37,17 +37,11 @@ namespace Umbraco.Core.Services
} }
} }
public IMemberType GetMemberType(string alias) /// <summary>
{ /// Gets an <see cref="IMemberType"/> object by its Id
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) /// </summary>
{ /// <param name="id">Id of the <see cref="IMemberType"/> to retrieve</param>
var query = Query<IMemberType>.Builder.Where(x => x.Alias == alias); /// <returns><see cref="IMemberType"/></returns>
var memberTypes = repository.GetByQuery(query);
return memberTypes.FirstOrDefault();
}
}
public IMemberType GetMemberType(int id) public IMemberType GetMemberType(int id)
{ {
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork())) using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
@@ -56,5 +50,21 @@ namespace Umbraco.Core.Services
} }
} }
/// <summary>
/// Gets an <see cref="IMemberType"/> object by its Alias
/// </summary>
/// <param name="alias">Alias of the <see cref="IMemberType"/> to retrieve</param>
/// <returns><see cref="IMemberType"/></returns>
public IMemberType GetMemberType(string alias)
{
using (var repository = _repositoryFactory.CreateMemberTypeRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query<IMemberType>.Builder.Where(x => x.Alias == alias);
var contentTypes = repository.GetByQuery(query);
return contentTypes.FirstOrDefault();
}
}
} }
} }

View File

@@ -358,6 +358,7 @@
<Compile Include="Models\Membership\MembershipExtensions.cs" /> <Compile Include="Models\Membership\MembershipExtensions.cs" />
<Compile Include="Models\Membership\UmbracoMembershipMember.cs" /> <Compile Include="Models\Membership\UmbracoMembershipMember.cs" />
<Compile Include="Models\Membership\UmbracoMembershipUser.cs" /> <Compile Include="Models\Membership\UmbracoMembershipUser.cs" />
<Compile Include="Models\PagedResult.cs" />
<Compile Include="Models\PreValue.cs" /> <Compile Include="Models\PreValue.cs" />
<Compile Include="Models\MemberType.cs" /> <Compile Include="Models\MemberType.cs" />
<Compile Include="Models\PreValueCollection.cs" /> <Compile Include="Models\PreValueCollection.cs" />
@@ -450,6 +451,7 @@
<Compile Include="Persistence\Repositories\TagsRepository.cs" /> <Compile Include="Persistence\Repositories\TagsRepository.cs" />
<Compile Include="PropertyEditors\BackwardsCompatibleData.cs" /> <Compile Include="PropertyEditors\BackwardsCompatibleData.cs" />
<Compile Include="PropertyEditors\BackwardsCompatibleDataType.cs" /> <Compile Include="PropertyEditors\BackwardsCompatibleDataType.cs" />
<Compile Include="PropertyEditors\EmailValidator.cs" />
<Compile Include="PropertyEditors\IParameterEditor.cs" /> <Compile Include="PropertyEditors\IParameterEditor.cs" />
<Compile Include="PropertyEditors\LegacyParameterEditorAliasConverter.cs" /> <Compile Include="PropertyEditors\LegacyParameterEditorAliasConverter.cs" />
<Compile Include="PropertyEditors\LegacyPropertyEditorIdToAliasConverter.cs" /> <Compile Include="PropertyEditors\LegacyPropertyEditorIdToAliasConverter.cs" />

View File

@@ -189,6 +189,63 @@ namespace Umbraco.Tests.Persistence.Repositories
} }
} }
[Test]
public void MemberRepository_Does_Not_Replace_Password_When_Null()
{
IMember sut;
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
MemberTypeRepository memberTypeRepository;
using (var repository = CreateRepository(unitOfWork, out memberTypeRepository))
{
var memberType = MockedContentTypes.CreateSimpleMemberType();
memberTypeRepository.AddOrUpdate(memberType);
unitOfWork.Commit();
var member = MockedMember.CreateSimpleContent(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty", -1);
repository.AddOrUpdate(member);
unitOfWork.Commit();
sut = repository.Get(member.Id);
//when the password is null it will not overwrite what is already there.
sut.Password = null;
repository.AddOrUpdate(sut);
unitOfWork.Commit();
sut = repository.Get(member.Id);
Assert.That(sut.Password, Is.EqualTo("123"));
}
}
[Test]
public void MemberRepository_Can_Update_Email_And_Login_When_Changed()
{
IMember sut;
var provider = new PetaPocoUnitOfWorkProvider();
var unitOfWork = provider.GetUnitOfWork();
MemberTypeRepository memberTypeRepository;
using (var repository = CreateRepository(unitOfWork, out memberTypeRepository))
{
var memberType = MockedContentTypes.CreateSimpleMemberType();
memberTypeRepository.AddOrUpdate(memberType);
unitOfWork.Commit();
var member = MockedMember.CreateSimpleContent(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty", -1);
repository.AddOrUpdate(member);
unitOfWork.Commit();
sut = repository.Get(member.Id);
sut.Username = "This is new";
sut.Email = "thisisnew@hello.com";
repository.AddOrUpdate(sut);
unitOfWork.Commit();
sut = repository.Get(member.Id);
Assert.That(sut.Email, Is.EqualTo("thisisnew@hello.com"));
Assert.That(sut.Username, Is.EqualTo("This is new"));
}
}
[Test] [Test]
public void Can_Create_Correct_Subquery() public void Can_Create_Correct_Subquery()
{ {

View File

@@ -12,6 +12,7 @@ function sectionsDirective($timeout, $window, navigationService, sectionResource
scope.maxSections = 7; scope.maxSections = 7;
scope.overflowingSections = 0; scope.overflowingSections = 0;
scope.sections = [];
function loadSections(){ function loadSections(){
sectionResource.getSections() sectionResource.getSections()

View File

@@ -1,8 +1,10 @@
angular.module('umbraco.directives.validation') angular.module('umbraco.directives.validation')
.directive('valCompare',function () { .directive('valCompare',function () {
return { return {
require: "ngModel", require: "ngModel",
link: function(scope, elem, attrs, ctrl) { link: function (scope, elem, attrs, ctrl) {
//TODO: Pretty sure this should be done using a requires ^form in the directive declaration
var otherInput = elem.inheritedData("$formController")[attrs.valCompare]; var otherInput = elem.inheritedData("$formController")[attrs.valCompare];
ctrl.$parsers.push(function(value) { ctrl.$parsers.push(function(value) {

View File

@@ -102,7 +102,9 @@ function valPropertyMsg(serverValidationManager) {
var errCount = 0; var errCount = 0;
for (var e in formCtrl.$error) { for (var e in formCtrl.$error) {
errCount++; if (e) {
errCount++;
}
} }
if ((errCount === 1 && formCtrl.$error.valPropertyMsg !== undefined) || if ((errCount === 1 && formCtrl.$error.valPropertyMsg !== undefined) ||

View File

@@ -6,17 +6,36 @@
* NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string
**/ **/
function valRegex() { function valRegex() {
return { return {
require: 'ngModel', require: 'ngModel',
restrict: "A", restrict: "A",
link: function (scope, elm, attrs, ctrl) { link: function (scope, elm, attrs, ctrl) {
var flags = "";
if (attrs.valRegexFlags) {
try {
flags = scope.$eval(attrs.valRegexFlags);
if (!flags) {
flags = attrs.valRegexFlags;
}
}
catch (e) {
flags = attrs.valRegexFlags;
}
}
var regex; var regex;
try { try {
regex = new RegExp(scope.$eval(attrs.valRegex)); var resolved = scope.$eval(attrs.valRegex);
if (resolved) {
regex = new RegExp(resolved, flags);
}
else {
regex = new RegExp(attrs.valRegex, flags);
}
} }
catch(e) { catch(e) {
regex = new RegExp(attrs.valRegex); regex = new RegExp(attrs.valRegex, flags);
} }
var patternValidator = function (viewValue) { var patternValidator = function (viewValue) {

View File

@@ -213,7 +213,7 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
} }
args.scope.$broadcast("saved", { scope: args.scope }); args.scope.$broadcast("saved", { scope: args.scope });
if (!this.redirectToCreatedContent(args.newContent.id)) { if (!this.redirectToCreatedContent(args.redirectId ? args.redirectId : args.newContent.id)) {
//we are not redirecting because this is not new content, it is existing content. In this case //we are not redirecting because this is not new content, it is existing content. In this case
// we need to detect what properties have changed and re-bind them with the server data. // we need to detect what properties have changed and re-bind them with the server data.

View File

@@ -203,7 +203,7 @@ function umbDataFormatter() {
/** formats the display model used to display the member to the model used to save the member */ /** formats the display model used to display the member to the model used to save the member */
formatMemberPostData: function(displayModel, action) { formatMemberPostData: function(displayModel, action) {
//this is basically the same as for media but we need to explicitly add the username,email to the save model //this is basically the same as for media but we need to explicitly add the username,email, password to the save model
var saveModel = this.formatMediaPostData(displayModel, action); var saveModel = this.formatMediaPostData(displayModel, action);
var genericTab = _.find(displayModel.tabs, function (item) { var genericTab = _.find(displayModel.tabs, function (item) {
@@ -216,8 +216,17 @@ function umbDataFormatter() {
var propEmail = _.find(genericTab.properties, function (item) { var propEmail = _.find(genericTab.properties, function (item) {
return item.alias === "_umb_email"; return item.alias === "_umb_email";
}); });
var propPass = _.find(genericTab.properties, function (item) {
return item.alias === "_umb_password";
});
saveModel.email = propEmail.value; saveModel.email = propEmail.value;
saveModel.username = propLogin.value; saveModel.username = propLogin.value;
//NOTE: This would only be set for new members!
if (angular.isString(propPass.value)) {
// if we are resetting or changing passwords then that data will come from the property editor and
// it's value will be an object not just a string.
saveModel.password = propPass.value;
}
return saveModel; return saveModel;
}, },

View File

@@ -1,7 +1,8 @@
<form novalidate name="contentForm" <form novalidate name="contentForm"
ng-controller="Umbraco.Editors.DataType.EditController" ng-controller="Umbraco.Editors.DataType.EditController"
ng-show="loaded" ng-show="loaded"
ng-submit="save()"> ng-submit="save()"
val-status-changed>
<umb-panel val-show-validation> <umb-panel val-show-validation>
<umb-header> <umb-header>

View File

@@ -65,6 +65,8 @@ function MemberEditController($scope, $routeParams, $q, $timeout, $window, membe
contentEditingHelper.handleSuccessfulSave({ contentEditingHelper.handleSuccessfulSave({
scope: $scope, scope: $scope,
newContent: data, newContent: data,
//specify a custom id to redirect to since we want to use the GUID
redirectId: data.key,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
}); });

View File

@@ -0,0 +1,40 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.ChangePasswordController",
function($scope) {
//the model config will contain an object, if it does not we'll create defaults
//NOTE: We will not support doing the password regex on the client side because the regex on the server side
//based on the membership provider cannot always be ported to js from .net directly.
/*
{
requiresQuestionAnswer: true/false,
enableReset: true/false,
minPasswordLength: 10
}
*/
//set defaults if they are not available
if (!$scope.model.config || !$scope.model.config.requiresQuestionAnswer) {
$scope.model.config.requiresQuestionAnswer = false;
}
if (!$scope.model.config || !$scope.model.config.enableReset) {
$scope.model.config.enableReset = true;
}
if (!$scope.model.config || !$scope.model.config.minPasswordLength) {
$scope.model.config.minPasswordLength = 7;
}
$scope.confirm = "";
$scope.hasPassword = $scope.model.value !== undefined && $scope.model.value !== null && $scope.model.value !== "";
$scope.changing = !$scope.hasPassword;
$scope.doChange = function() {
$scope.changing = true;
};
$scope.cancelChange = function() {
$scope.changing = false;
};
});

View File

@@ -0,0 +1,33 @@
<div ng-controller="Umbraco.PropertyEditors.ChangePasswordController" ng-switch="changing">
<div ng-switch-when="false">
<!--<a href="" ng-click="doChange()">Change password</a> -->
<div class="control-group warning">
<span class="help-block">Password changing or resetting is currently not supported</span>
</div>
</div>
<div ng-switch-when="true">
<div class="control-group">
<input type="text" name="password" ng-model="model.value" id="{{model.alias}}"
class="umb-editor umb-textstring textstring"
required
val-server
ng-minlength="{{model.config.minPasswordLength}}" />
<span class="help-inline" val-msg-for="password" val-toggle-msg="required">Required</span>
<span class="help-inline" val-msg-for="password" val-toggle-msg="minlength">Minimum {{model.config.minPasswordLength}} characters</span>
<span class="help-inline" val-msg-for="password" val-toggle-msg="valServer"></span>
</div>
<div class="control-group">
<input type="text" name="confirmpassword" ng-model="model.confirm" id="{{model.alias}}"
class="umb-editor umb-textstring textstring"
val-compare="password" />
<span class="help-inline" val-msg-for="confirmpassword" val-toggle-msg="valCompare">Passwords must match</span>
</div>
<!--<a href="" ng-click="cancelChange()" ng-show="hasPassword">Cancel</a> -->
</div>
</div>

View File

@@ -1,6 +0,0 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.EmailController",
function($rootScope, $scope, dialogService, $routeParams, contentResource, contentTypeResource, editorContextService, notificationsService) {
});

View File

@@ -1,3 +1,13 @@
<input type="email" name="textbox" ng-model="model.value" id="{{model.alias}}" class="umb-editor umb-textstring textstring" val-server="value" /> <div>
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="email">Invalid email</span>
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span> <input type="email" name="textbox"
ng-model="model.value"
id="{{model.alias}}"
class="umb-editor umb-textstring textstring"
ng-required="model.config.IsRequired"
val-server="value" />
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="required">Required</span>
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="email">Invalid email</span>
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span>
</div>

View File

@@ -1,2 +1,9 @@
<input type="text" name="textbox" ng-model="model.value" id="{{model.alias}}" class="umb-editor umb-textstring textstring" val-server="value" /> <div>
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span> <input type="text" name="textbox" ng-model="model.value" id="{{model.alias}}"
class="umb-editor umb-textstring textstring"
val-server="value"
ng-required="model.config.IsRequired" />
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="required">Required</span>
<span class="help-inline" val-msg-for="textbox" val-toggle-msg="valServer"></span>
</div>

View File

@@ -185,32 +185,7 @@ namespace Umbraco.Web.Editors
// * any file attachments have been saved to their temporary location for us to use // * any file attachments have been saved to their temporary location for us to use
// * we have a reference to the DTO object and the persisted object // * we have a reference to the DTO object and the persisted object
// * Permissions are valid // * Permissions are valid
UpdateName(contentItem);
//TODO: We need to support 'send to publish'
contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate;
contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate;
//only set the template if it didn't change
var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
|| (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias)
|| (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace());
if (templateChanged)
{
var template = Services.FileService.GetTemplate(contentItem.TemplateAlias);
if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
{
//ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias);
LogHelper.Warn<ContentController>("No template exists with the specified alias: " + contentItem.TemplateAlias);
}
else
{
//NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template
contentItem.PersistedContent.Template = template;
}
}
MapPropertyValues(contentItem); MapPropertyValues(contentItem);
//We need to manually check the validation results here because: //We need to manually check the validation results here because:
@@ -255,7 +230,7 @@ namespace Umbraco.Web.Editors
else else
{ {
//publish the item and check if it worked, if not we will show a diff msg below //publish the item and check if it worked, if not we will show a diff msg below
publishStatus = ((ContentService)Services.ContentService).SaveAndPublishInternal(contentItem.PersistedContent, (int)Security.CurrentUser.Id); publishStatus = Services.ContentService.SaveAndPublishWithStatus(contentItem.PersistedContent, (int)Security.CurrentUser.Id);
} }
@@ -281,6 +256,40 @@ namespace Umbraco.Web.Editors
return display; return display;
} }
/// <summary>
/// Maps the dto property values to the persisted model
/// </summary>
/// <param name="contentItem"></param>
private void MapPropertyValues(ContentItemSave contentItem)
{
UpdateName(contentItem);
//TODO: We need to support 'send to publish'
contentItem.PersistedContent.ExpireDate = contentItem.ExpireDate;
contentItem.PersistedContent.ReleaseDate = contentItem.ReleaseDate;
//only set the template if it didn't change
var templateChanged = (contentItem.PersistedContent.Template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
|| (contentItem.PersistedContent.Template != null && contentItem.PersistedContent.Template.Alias != contentItem.TemplateAlias)
|| (contentItem.PersistedContent.Template != null && contentItem.TemplateAlias.IsNullOrWhiteSpace());
if (templateChanged)
{
var template = Services.FileService.GetTemplate(contentItem.TemplateAlias);
if (template == null && contentItem.TemplateAlias.IsNullOrWhiteSpace() == false)
{
//ModelState.AddModelError("Template", "No template exists with the specified alias: " + contentItem.TemplateAlias);
LogHelper.Warn<ContentController>("No template exists with the specified alias: " + contentItem.TemplateAlias);
}
else
{
//NOTE: this could be null if there was a template and the posted template is null, this should remove the assigned template
contentItem.PersistedContent.Template = template;
}
}
base.MapPropertyValues(contentItem);
}
/// <summary> /// <summary>
/// Publishes a document with a given ID /// Publishes a document with a given ID
/// </summary> /// </summary>

View File

@@ -78,7 +78,12 @@ namespace Umbraco.Web.Editors
return null; return null;
} }
protected void MapPropertyValues<TPersisted>(ContentBaseItemSave<TPersisted> contentItem) /// <summary>
/// Maps the dto property values to the persisted model
/// </summary>
/// <typeparam name="TPersisted"></typeparam>
/// <param name="contentItem"></param>
protected virtual void MapPropertyValues<TPersisted>(ContentBaseItemSave<TPersisted> contentItem)
where TPersisted : IContentBase where TPersisted : IContentBase
{ {
//Map the property values //Map the property values

View File

@@ -287,8 +287,6 @@ namespace Umbraco.Web.Editors
// * we have a reference to the DTO object and the persisted object // * we have a reference to the DTO object and the persisted object
// * Permissions are valid // * Permissions are valid
UpdateName(contentItem);
MapPropertyValues(contentItem); MapPropertyValues(contentItem);
//We need to manually check the validation results here because: //We need to manually check the validation results here because:
@@ -331,6 +329,18 @@ namespace Umbraco.Web.Editors
return display; return display;
} }
/// <summary>
/// Maps the property values to the persisted entity
/// </summary>
/// <param name="contentItem"></param>
protected override void MapPropertyValues<TPersisted>(ContentBaseItemSave<TPersisted> contentItem)
{
UpdateName(contentItem);
//use the base method to map the rest of the properties
base.MapPropertyValues(contentItem);
}
/// <summary> /// <summary>
/// Change the sort order for media /// Change the sort order for media
/// </summary> /// </summary>

View File

@@ -1,16 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.ComponentModel.DataAnnotations;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web.Http; using System.Web.Http;
using System.Web.Http.ModelBinding; using System.Web.Http.ModelBinding;
using System.Web.Security;
using AutoMapper; using AutoMapper;
using Examine.LuceneEngine.SearchCriteria; using Examine.LuceneEngine.SearchCriteria;
using Examine.SearchCriteria; using Examine.SearchCriteria;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Web.WebApi; using Umbraco.Web.WebApi;
using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc; using Umbraco.Web.Mvc;
@@ -19,7 +21,6 @@ using Umbraco.Web.WebApi.Filters;
using umbraco; using umbraco;
using Constants = Umbraco.Core.Constants; using Constants = Umbraco.Core.Constants;
using Examine; using Examine;
using System.Web.Security;
using Member = umbraco.cms.businesslogic.member.Member; using Member = umbraco.cms.businesslogic.member.Member;
namespace Umbraco.Web.Editors namespace Umbraco.Web.Editors
@@ -77,9 +78,8 @@ namespace Umbraco.Web.Editors
/// Gets an empty content item for the /// Gets an empty content item for the
/// </summary> /// </summary>
/// <param name="contentTypeAlias"></param> /// <param name="contentTypeAlias"></param>
/// <param name="parentId"></param>
/// <returns></returns> /// <returns></returns>
public MemberDisplay GetEmpty(string contentTypeAlias, string username, string password) public MemberDisplay GetEmpty(string contentTypeAlias)
{ {
var contentType = Services.MemberTypeService.GetMemberType(contentTypeAlias); var contentType = Services.MemberTypeService.GetMemberType(contentTypeAlias);
if (contentType == null) if (contentType == null)
@@ -87,7 +87,7 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound); throw new HttpResponseException(HttpStatusCode.NotFound);
} }
var emptyContent = new Core.Models.Member("", "", "", "", -1, contentType); var emptyContent = new Core.Models.Member("", contentType);
return Mapper.Map<IMember, MemberDisplay>(emptyContent); return Mapper.Map<IMember, MemberDisplay>(emptyContent);
} }
@@ -96,10 +96,16 @@ namespace Umbraco.Web.Editors
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[FileUploadCleanupFilter] [FileUploadCleanupFilter]
[MembershipProviderValidationFilter]
public MemberDisplay PostSave( public MemberDisplay PostSave(
[ModelBinder(typeof(MemberBinder))] [ModelBinder(typeof(MemberBinder))]
MemberSave contentItem) MemberSave contentItem)
{ {
if (Membership.Provider.Name != Constants.Conventions.Member.UmbracoMemberProviderName)
{
throw new NotSupportedException("Currently the member editor does not support providers that are not the default Umbraco membership provider ");
}
//If we've reached here it means: //If we've reached here it means:
// * Our model has been bound // * Our model has been bound
// * and validated // * and validated
@@ -107,35 +113,53 @@ namespace Umbraco.Web.Editors
// * we have a reference to the DTO object and the persisted object // * we have a reference to the DTO object and the persisted object
// * Permissions are valid // * Permissions are valid
UpdateName(contentItem); //map the properties to the persisted entity
//map the custom properties
contentItem.PersistedContent.Email = contentItem.Email;
//TODO: If we allow changing the alias then we'll need to change URLs, etc... in the editor, would prefer to use
// a unique id but then need to figure out how to handle that with custom membership providers - waiting on feedback from morten.
MapPropertyValues(contentItem); MapPropertyValues(contentItem);
//We need to manually check the validation results here because: //Unlike content/media - if there are errors for a member, we do NOT proceed to save them, we cannot so return the errors
// * We still need to save the entity even if there are validation value errors if (ModelState.IsValid == false)
// * Depending on if the entity is new, and if there are non property validation errors (i.e. the name is null) {
// then we cannot continue saving, we can only display errors var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
// * If there are validation errors and they were attempting to publish, we can only save, NOT publish and display forDisplay.Errors = ModelState.ToErrorDictionary();
// a message indicating this throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
if (!ModelState.IsValid)
{
if (ValidationHelper.ModelHasRequiredForPersistenceErrors(contentItem)
&& (contentItem.Action == ContentSaveAction.SaveNew))
{
//ok, so the absolute mandatory data is invalid and it's new, we cannot actually continue!
// add the modelstate to the outgoing object and throw validation response
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
}
} }
//TODO: WE need to support this! - requires UI updates, etc...
if (Membership.Provider.RequiresQuestionAndAnswer)
{
throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified");
}
//Depending on the action we need to first do a create or update using the membership provider
// this ensures that passwords are formatted correclty and also performs the validation on the provider itself.
switch (contentItem.Action)
{
case ContentSaveAction.Save:
//TODO: Update with the provider! - change password, etc...
break;
case ContentSaveAction.SaveNew:
MembershipCreateStatus status;
CreateWithUmbracoProvider(contentItem, out status);
break;
default:
//we don't support anything else for members
throw new HttpResponseException(HttpStatusCode.NotFound);
}
//If we've had problems creating/updating the user with the provider then return the error
if (ModelState.IsValid == false)
{
var forDisplay = Mapper.Map<IMember, MemberDisplay>(contentItem.PersistedContent);
forDisplay.Errors = ModelState.ToErrorDictionary();
throw new HttpResponseException(Request.CreateValidationErrorResponse(forDisplay));
}
//save the item //save the item
//NOTE: We are setting the password to NULL - this indicates to the system to not actually save the password
// so it will not get overwritten!
contentItem.PersistedContent.Password = null;
//create/save the IMember
Services.MemberService.Save(contentItem.PersistedContent); Services.MemberService.Save(contentItem.PersistedContent);
//return the updated model //return the updated model
@@ -156,6 +180,106 @@ namespace Umbraco.Web.Editors
return display; return display;
} }
/// <summary>
/// Maps the property values to the persisted entity
/// </summary>
/// <param name="contentItem"></param>
private void MapPropertyValues(MemberSave contentItem)
{
UpdateName(contentItem);
//map the custom properties - this will already be set for new entities in our member binder
contentItem.PersistedContent.Email = contentItem.Email;
contentItem.PersistedContent.Username = contentItem.Username;
//use the base method to map the rest of the properties
base.MapPropertyValues(contentItem);
}
/// <summary>
/// This is going to create the user with the membership provider and check for validation
/// </summary>
/// <param name="contentItem"></param>
/// <param name="status"></param>
/// <returns></returns>
/// <remarks>
/// If this is successful, it will go and re-fetch the IMember from the db because it will now have an ID because the Umbraco provider
/// uses the umbraco data store - then of course we need to re-map it to the saved property values.
/// </remarks>
private MembershipUser CreateWithUmbracoProvider(MemberSave contentItem, out MembershipCreateStatus status)
{
//if we are creating a new one, create the member using the membership provider first
//TODO: I think we should detect if the Umbraco membership provider is active, if so then we'll create the member first and the provider key doesn't matter
// but if we are using a 3rd party membership provider - then we should create our IMember first and use it's key as their provider user key!
//NOTE: We are casting directly to the umbraco membership provider so we can specify the member type that we want to use!
var umbracoMembershipProvider = (global::umbraco.providers.members.UmbracoMembershipProvider)Membership.Provider;
var membershipUser = umbracoMembershipProvider.CreateUser(
contentItem.ContentTypeAlias, contentItem.Username, contentItem.Password, contentItem.Email, "", "", true, Guid.NewGuid(), out status);
//TODO: Localize these!
switch (status)
{
case MembershipCreateStatus.Success:
//Go and re-fetch the persisted item
contentItem.PersistedContent = Services.MemberService.GetByUsername(contentItem.Username.Trim());
//remap the values to save
MapPropertyValues(contentItem);
break;
case MembershipCreateStatus.InvalidUserName:
ModelState.AddPropertyError(
new ValidationResult("Invalid user name", new[] { "value" }),
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
break;
case MembershipCreateStatus.InvalidPassword:
ModelState.AddPropertyError(
new ValidationResult("Invalid password", new[] { "value" }),
string.Format("{0}password", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
break;
case MembershipCreateStatus.InvalidQuestion:
case MembershipCreateStatus.InvalidAnswer:
throw new NotSupportedException("Currently the member editor does not support providers that have RequiresQuestionAndAnswer specified");
case MembershipCreateStatus.InvalidEmail:
ModelState.AddPropertyError(
new ValidationResult("Invalid email", new[] { "value" }),
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
break;
case MembershipCreateStatus.DuplicateUserName:
ModelState.AddPropertyError(
new ValidationResult("Username is already in use", new[] { "value" }),
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
break;
case MembershipCreateStatus.DuplicateEmail:
ModelState.AddPropertyError(
new ValidationResult("Email address is already in use", new[] { "value" }),
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
break;
case MembershipCreateStatus.InvalidProviderUserKey:
ModelState.AddPropertyError(
//specify 'default' just so that it shows up as a notification - is not assigned to a property
new ValidationResult("Invalid provider user key"), "default");
break;
case MembershipCreateStatus.DuplicateProviderUserKey:
ModelState.AddPropertyError(
//specify 'default' just so that it shows up as a notification - is not assigned to a property
new ValidationResult("Duplicate provider user key"), "default");
break;
case MembershipCreateStatus.ProviderError:
case MembershipCreateStatus.UserRejected:
ModelState.AddPropertyError(
//specify 'default' just so that it shows up as a notification - is not assigned to a property
new ValidationResult("User could not be created (rejected by provider)"), "default");
break;
default:
throw new ArgumentOutOfRangeException();
}
return membershipUser;
}
/// <summary> /// <summary>
/// Permanently deletes a member /// Permanently deletes a member

View File

@@ -0,0 +1,132 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Security;
using Umbraco.Core;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Editors
{
/// <summary>
/// This validates the submitted data in regards to the current membership provider
/// </summary>
internal class MembershipProviderValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
base.OnActionExecuting(actionContext);
//default provider!
var membershipProvider = Membership.Provider;
var contentItem = (MemberSave) actionContext.ActionArguments["contentItem"];
var validEmail = ValidateUniqueEmail(contentItem, membershipProvider, actionContext);
if (validEmail == false)
{
actionContext.ModelState.AddPropertyError(
new ValidationResult("Email address is already in use", new[] {"value"}),
string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
}
var validLogin = ValidateUniqueLogin(contentItem, membershipProvider, actionContext);
if (validLogin == false)
{
actionContext.ModelState.AddPropertyError(
new ValidationResult("Username is already in use", new[] { "value" }),
string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix));
}
}
internal bool ValidateUniqueLogin(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext)
{
if (contentItem == null) throw new ArgumentNullException("contentItem");
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
int totalRecs;
var existingByEmail = membershipProvider.FindUsersByName(contentItem.Username.Trim(), 0, int.MaxValue, out totalRecs);
switch (contentItem.Action)
{
case ContentSaveAction.Save:
if (contentItem.PersistedContent.Email.InvariantEquals(contentItem.Email.Trim()) == false)
{
//they are changing their login name
if (existingByEmail.Cast<MembershipUser>().Select(x => x.UserName)
.Any(x => x.InvariantEquals(contentItem.Username.Trim())))
{
//the user cannot use this login
return false;
}
}
break;
case ContentSaveAction.SaveNew:
//check if the user's login already exists
if (existingByEmail.Cast<MembershipUser>().Select(x => x.UserName)
.Any(x => x.InvariantEquals(contentItem.Username.Trim())))
{
//the user cannot use this login
return false;
}
break;
case ContentSaveAction.Publish:
case ContentSaveAction.PublishNew:
default:
//we don't support this for members
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return true;
}
internal bool ValidateUniqueEmail(MemberSave contentItem, MembershipProvider membershipProvider, HttpActionContext actionContext)
{
if (contentItem == null) throw new ArgumentNullException("contentItem");
if (membershipProvider == null) throw new ArgumentNullException("membershipProvider");
if (membershipProvider.RequiresUniqueEmail == false)
{
return true;
}
int totalRecs;
var existingByEmail = membershipProvider.FindUsersByEmail(contentItem.Email.Trim(), 0, int.MaxValue, out totalRecs);
switch (contentItem.Action)
{
case ContentSaveAction.Save:
//ok, we're updating the member, we need to check if they are changing their email and if so, does it exist already ?
if (contentItem.PersistedContent.Email.InvariantEquals(contentItem.Email.Trim()) == false)
{
//they are changing their email
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
.Any(x => x.InvariantEquals(contentItem.Email.Trim())))
{
//the user cannot use this email
return false;
}
}
break;
case ContentSaveAction.SaveNew:
//check if the user's email already exists
if (existingByEmail.Cast<MembershipUser>().Select(x => x.Email)
.Any(x => x.InvariantEquals(contentItem.Email.Trim())))
{
//the user cannot use this email
return false;
}
break;
case ContentSaveAction.Publish:
case ContentSaveAction.PublishNew:
default:
//we don't support this for members
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return true;
}
}
}

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Web.Mvc; using System.Web.Mvc;
@@ -48,6 +49,32 @@ namespace Umbraco.Web
// state.AddModelError("DataValidation", errorMessage); // state.AddModelError("DataValidation", errorMessage);
//} //}
/// <summary>
/// Adds the error to model state correctly for a property so we can use it on the client side.
/// </summary>
/// <param name="modelState"></param>
/// <param name="result"></param>
/// <param name="propertyAlias"></param>
internal static void AddPropertyError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, ValidationResult result, string propertyAlias)
{
//if there are no member names supplied then we assume that the validation message is for the overall property
// not a sub field on the property editor
if (!result.MemberNames.Any())
{
//add a model state error for the entire property
modelState.AddModelError(string.Format("{0}.{1}", "Properties", propertyAlias), result.ErrorMessage);
}
else
{
//there's assigned field names so we'll combine the field name with the property name
// so that we can try to match it up to a real sub field of this editor
foreach (var field in result.MemberNames)
{
modelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", propertyAlias, field), result.ErrorMessage);
}
}
}
public static IDictionary<string, object> ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState) public static IDictionary<string, object> ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState)
{ {
var modelStateError = new Dictionary<string, object>(); var modelStateError = new Dictionary<string, object>();

View File

@@ -1,4 +1,5 @@
using System.Runtime.Serialization; using System;
using System.Runtime.Serialization;
using Umbraco.Core.Models; using Umbraco.Core.Models;
namespace Umbraco.Web.Models.ContentEditing namespace Umbraco.Web.Models.ContentEditing
@@ -16,5 +17,11 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "email")] [DataMember(Name = "email")]
public string Email { get; set; } public string Email { get; set; }
/// <summary>
/// This is the unique Id stored in the database - but could also be the unique id for a custom membership provider
/// </summary>
[DataMember(Name = "key")]
public Guid Key { get; set; }
} }
} }

View File

@@ -1,4 +1,5 @@
using AutoMapper; using System.Collections.Generic;
using AutoMapper;
using Umbraco.Core; using Umbraco.Core;
using Umbraco.Core.Models; using Umbraco.Core.Models;
using Umbraco.Core.Models.Mapping; using Umbraco.Core.Models.Mapping;
@@ -53,7 +54,9 @@ namespace Umbraco.Web.Models.Mapping
config.CreateMap<IMember, ContentItemDto<IMember>>() config.CreateMap<IMember, ContentItemDto<IMember>>()
.ForMember( .ForMember(
dto => dto.Owner, dto => dto.Owner,
expression => expression.ResolveUsing<OwnerResolver<IMember>>()); expression => expression.ResolveUsing<OwnerResolver<IMember>>())
//do no map the custom member properties (currently anyways, they were never there in 6.x)
.ForMember(dto => dto.Properties, expression => expression.ResolveUsing<MemberDtoPropertiesValueResolver>());
} }
/// <summary> /// <summary>
@@ -70,7 +73,8 @@ namespace Umbraco.Web.Models.Mapping
Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Alias = string.Format("{0}login", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = ui.Text("login"), Label = ui.Text("login"),
Value = display.Username, Value = display.Username,
View = "textbox" View = "textbox",
Config = new Dictionary<string, object> { { "IsRequired", true } }
}, },
new ContentPropertyDisplay new ContentPropertyDisplay
{ {
@@ -84,10 +88,28 @@ namespace Umbraco.Web.Models.Mapping
Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix), Alias = string.Format("{0}email", Constants.PropertyEditors.InternalGenericPropertiesPrefix),
Label = ui.Text("general", "email"), Label = ui.Text("general", "email"),
Value = display.Email, Value = display.Email,
View = "textbox" View = "email",
Config = new Dictionary<string, object> {{"IsRequired", true}}
}); });
} }
/// <summary>
/// This ensures that the custom membership provider properties are not mapped (currently since they weren't there in v6)
/// </summary>
/// <remarks>
/// Because these properties don't exist on the form, if we don't remove them for this map we'll get validation errors when posting data
/// </remarks>
internal class MemberDtoPropertiesValueResolver : ValueResolver<IMember, IEnumerable<ContentPropertyDto>>
{
protected override IEnumerable<ContentPropertyDto> ResolveCore(IMember source)
{
var exclude = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray();
return source.Properties
.Where(x => exclude.Contains(x.Alias) == false)
.Select(Mapper.Map<Property, ContentPropertyDto>);
}
}
} }
} }

View File

@@ -0,0 +1,29 @@
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
[PropertyEditor(Constants.PropertyEditors.EmailAddressAlias, "Email address", "email")]
public class EmailAddressPropertyEditor : PropertyEditor
{
protected override PropertyValueEditor CreateValueEditor()
{
var editor = base.CreateValueEditor();
//add an email address validator
editor.Validators.Add(new EmailValidator());
return editor;
}
protected override PreValueEditor CreatePreValueEditor()
{
return new EmailAddressePreValueEditor();
}
internal class EmailAddressePreValueEditor : PreValueEditor
{
[PreValueField("Required?", "boolean")]
public bool IsRequired { get; set; }
}
}
}

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Configuration.Provider; using System.Configuration.Provider;
using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -8,6 +10,7 @@ using System.Web.Hosting;
using System.Web.Security; using System.Web.Security;
using Umbraco.Core; using Umbraco.Core;
using Umbraco.Core.Logging; using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services; using Umbraco.Core.Services;
using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Membership;
@@ -254,8 +257,9 @@ namespace Umbraco.Web.Security.Providers
} }
/// <summary> /// <summary>
/// Adds a new membership user to the data source. /// Adds a new membership user to the data source with the specified member type
/// </summary> /// </summary>
/// <param name="memberType">A specific member type to create the member for</param>
/// <param name="username">The user name for the new user.</param> /// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param> /// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param> /// <param name="email">The e-mail address for the new user.</param>
@@ -267,16 +271,16 @@ namespace Umbraco.Web.Security.Providers
/// <returns> /// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user. /// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns> /// </returns>
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, public MembershipUser CreateUser(string memberType, string username, string password, string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey, out MembershipCreateStatus status) bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{ {
LogHelper.Debug<MembersMembershipProvider>("Member signup requested: username -> " + username + ". email -> " +email); LogHelper.Debug<MembersMembershipProvider>("Member signup requested: username -> " + username + ". email -> " + email);
// Validate password // Validate password
if (IsPasswordValid(password) == false) if (IsPasswordValid(password) == false)
{ {
status = MembershipCreateStatus.InvalidPassword; status = MembershipCreateStatus.InvalidPassword;
return null; return null;
} }
// Validate email // Validate email
@@ -324,18 +328,41 @@ namespace Umbraco.Web.Security.Providers
return null; return null;
} }
var member = MemberService.CreateMember(email, username, password, DefaultMemberTypeAlias); var member = MemberService.CreateMember(email, username, password, memberType);
member.IsApproved = isApproved; member.IsApproved = isApproved;
member.PasswordQuestion = passwordQuestion; member.PasswordQuestion = passwordQuestion;
member.PasswordAnswer = passwordAnswer; member.PasswordAnswer = passwordAnswer;
//encrypts/hashes the password depending on the settings
member.Password = EncryptOrHashPassword(member.Password);
MemberService.Save(member); MemberService.Save(member);
status = MembershipCreateStatus.Success; status = MembershipCreateStatus.Success;
return member.AsConcreteMembershipUser(); return member.AsConcreteMembershipUser();
} }
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer,
bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
return CreateUser(DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status);
}
/// <summary> /// <summary>
/// Processes a request to update the password question and answer for a membership user. /// Processes a request to update the password question and answer for a membership user.
/// </summary> /// </summary>
@@ -360,7 +387,7 @@ namespace Umbraco.Web.Security.Providers
} }
var member = MemberService.GetByUsername(username); var member = MemberService.GetByUsername(username);
var encodedPassword = EncodePassword(password); var encodedPassword = EncryptOrHashPassword(password);
if (member.Password == encodedPassword) if (member.Password == encodedPassword)
{ {
@@ -427,12 +454,12 @@ namespace Umbraco.Web.Security.Providers
var member = MemberService.GetByUsername(username); var member = MemberService.GetByUsername(username);
if (member == null) return false; if (member == null) return false;
var encodedPassword = EncodePassword(oldPassword); var encodedPassword = EncryptOrHashPassword(oldPassword);
if (member.Password == encodedPassword) if (member.Password == encodedPassword)
{ {
member.Password = EncodePassword(newPassword); member.Password = EncryptOrHashPassword(newPassword);
MemberService.Save(member); MemberService.Save(member);
return true; return true;
@@ -464,7 +491,7 @@ namespace Umbraco.Web.Security.Providers
if (_requiresQuestionAndAnswer == false || (_requiresQuestionAndAnswer && answer == member.PasswordAnswer)) if (_requiresQuestionAndAnswer == false || (_requiresQuestionAndAnswer && answer == member.PasswordAnswer))
{ {
member.Password = member.Password =
EncodePassword(Membership.GeneratePassword(_minRequiredPasswordLength, EncryptOrHashPassword(Membership.GeneratePassword(_minRequiredPasswordLength,
_minRequiredNonAlphanumericCharacters)); _minRequiredNonAlphanumericCharacters));
MemberService.Save(member); MemberService.Save(member);
} }
@@ -505,7 +532,7 @@ namespace Umbraco.Web.Security.Providers
if (member.IsLockedOut) if (member.IsLockedOut)
throw new ProviderException("The member is locked out."); throw new ProviderException("The member is locked out.");
var encodedPassword = EncodePassword(password); var encodedPassword = EncryptOrHashPassword(password);
var authenticated = (encodedPassword == member.Password); var authenticated = (encodedPassword == member.Password);
@@ -687,7 +714,16 @@ namespace Umbraco.Web.Security.Providers
/// </returns> /// </returns>
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{ {
throw new System.NotImplementedException(); var byEmail = MemberService.FindMembersByEmail(emailToMatch).ToArray();
totalRecords = byEmail.Length;
var pagedResult = new PagedResult<IMember>(totalRecords, pageIndex, pageSize);
var collection = new MembershipUserCollection();
foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize))
{
collection.Add(m.AsConcreteMembershipUser());
}
return collection;
} }
#region Private methods #region Private methods
@@ -714,11 +750,9 @@ namespace Umbraco.Web.Security.Providers
private bool IsEmaiValid(string email) private bool IsEmaiValid(string email)
{ {
const string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|" var validator = new EmailAddressAttribute();
+ @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)"
+ @"@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$";
return Regex.IsMatch(email, pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); return validator.IsValid(email);
} }
/// <summary> /// <summary>
@@ -726,7 +760,7 @@ namespace Umbraco.Web.Security.Providers
/// </summary> /// </summary>
/// <param name="password">The password.</param> /// <param name="password">The password.</param>
/// <returns>The encoded password.</returns> /// <returns>The encoded password.</returns>
private string EncodePassword(string password) private string EncryptOrHashPassword(string password)
{ {
var encodedPassword = password; var encodedPassword = password;
switch (PasswordFormat) switch (PasswordFormat)

View File

@@ -309,6 +309,7 @@
<Compile Include="Editors\DataTypeValidateAttribute.cs" /> <Compile Include="Editors\DataTypeValidateAttribute.cs" />
<Compile Include="Editors\LogController.cs" /> <Compile Include="Editors\LogController.cs" />
<Compile Include="Editors\MacroController.cs" /> <Compile Include="Editors\MacroController.cs" />
<Compile Include="Editors\MembershipProviderValidationFilterAttribute.cs" />
<Compile Include="Editors\MemberTypeController.cs" /> <Compile Include="Editors\MemberTypeController.cs" />
<Compile Include="Models\ContentEditing\StyleSheet.cs" /> <Compile Include="Models\ContentEditing\StyleSheet.cs" />
<Compile Include="Editors\StylesheetController.cs" /> <Compile Include="Editors\StylesheetController.cs" />
@@ -327,6 +328,7 @@
<Compile Include="Models\Mapping\MacroModelMapper.cs" /> <Compile Include="Models\Mapping\MacroModelMapper.cs" />
<Compile Include="Models\Mapping\MemberModelMapper.cs" /> <Compile Include="Models\Mapping\MemberModelMapper.cs" />
<Compile Include="PropertyEditors\ColorListPreValueEditor.cs" /> <Compile Include="PropertyEditors\ColorListPreValueEditor.cs" />
<Compile Include="PropertyEditors\EmailAddressPropertyEditor.cs" />
<Compile Include="PropertyEditors\ListViewPropertyEditor.cs" /> <Compile Include="PropertyEditors\ListViewPropertyEditor.cs" />
<Compile Include="PropertyEditors\MemberPickerPropertyEditor.cs" /> <Compile Include="PropertyEditors\MemberPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\ParameterEditors\TextAreaParameterEditor.cs" /> <Compile Include="PropertyEditors\ParameterEditors\TextAreaParameterEditor.cs" />
@@ -352,7 +354,6 @@
<Compile Include="Models\Mapping\DataTypeModelMapper.cs" /> <Compile Include="Models\Mapping\DataTypeModelMapper.cs" />
<Compile Include="Models\Mapping\EntityModelMapper.cs" /> <Compile Include="Models\Mapping\EntityModelMapper.cs" />
<Compile Include="Models\Mapping\PreValueDisplayResolver.cs" /> <Compile Include="Models\Mapping\PreValueDisplayResolver.cs" />
<Compile Include="Models\PagedResult.cs" />
<Compile Include="PropertyEditors\CheckBoxListPropertyEditor.cs" /> <Compile Include="PropertyEditors\CheckBoxListPropertyEditor.cs" />
<Compile Include="PropertyEditors\ColorPickerPropertyEditor.cs" /> <Compile Include="PropertyEditors\ColorPickerPropertyEditor.cs" />
<Compile Include="PropertyEditors\DatePropertyEditor.cs" /> <Compile Include="PropertyEditors\DatePropertyEditor.cs" />

View File

@@ -25,7 +25,8 @@ namespace Umbraco.Web.WebApi.Binders
protected override IMember GetExisting(MemberSave model) protected override IMember GetExisting(MemberSave model)
{ {
//TODO: We're going to remove the built-in member properties from this editor - not sure if we should be persisting them elsewhere ? //TODO: We're going to remove the built-in member properties from this editor - We didn't support these in 6.x so
// pretty hard to support them in 7 when the member type editor is using the old APIs!
var toRemove = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray(); var toRemove = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray();
var member = ApplicationContext.Services.MemberService.GetByUsername(model.Username); var member = ApplicationContext.Services.MemberService.GetByUsername(model.Username);
@@ -38,11 +39,21 @@ namespace Umbraco.Web.WebApi.Binders
protected override IMember CreateNew(MemberSave model) protected override IMember CreateNew(MemberSave model)
{ {
var contentType = ApplicationContext.Services.ContentTypeService.GetMemberType(model.ContentTypeAlias); var contentType = ApplicationContext.Services.MemberTypeService.GetMemberType(model.ContentTypeAlias);
if (contentType == null) if (contentType == null)
{ {
throw new InvalidOperationException("No member type found wth alias " + model.ContentTypeAlias); throw new InvalidOperationException("No member type found wth alias " + model.ContentTypeAlias);
} }
//TODO: We're going to remove the built-in member properties from this editor - We didn't support these in 6.x so
// pretty hard to support them in 7 when the member type editor is using the old APIs!
var toRemove = Constants.Conventions.Member.StandardPropertyTypeStubs.Select(x => x.Value.Alias).ToArray();
foreach (var remove in toRemove)
{
contentType.RemovePropertyType(remove);
}
//return the new member with the details filled in
return new Member(model.Name, model.Email, model.Username, model.Password, -1, contentType); return new Member(model.Name, model.Email, model.Username, model.Password, -1, contentType);
} }

View File

@@ -139,7 +139,7 @@ namespace Umbraco.Web.WebApi.Filters
foreach (var result in editor.ValueEditor.Validators.SelectMany(v => v.Validate(postedValue, preValues, editor))) foreach (var result in editor.ValueEditor.Validators.SelectMany(v => v.Validate(postedValue, preValues, editor)))
{ {
AddError(actionContext.ModelState, result, p.Alias); actionContext.ModelState.AddPropertyError(result, p.Alias);
} }
//Now we need to validate the property based on the PropertyType validation (i.e. regex and required) //Now we need to validate the property based on the PropertyType validation (i.e. regex and required)
@@ -148,7 +148,7 @@ namespace Umbraco.Web.WebApi.Filters
{ {
foreach (var result in p.PropertyEditor.ValueEditor.RequiredValidator.Validate(postedValue, "", preValues, editor)) foreach (var result in p.PropertyEditor.ValueEditor.RequiredValidator.Validate(postedValue, "", preValues, editor))
{ {
AddError(actionContext.ModelState, result, p.Alias); actionContext.ModelState.AddPropertyError(result, p.Alias);
} }
} }
@@ -156,7 +156,7 @@ namespace Umbraco.Web.WebApi.Filters
{ {
foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor)) foreach (var result in p.PropertyEditor.ValueEditor.RegexValidator.Validate(postedValue, p.ValidationRegExp, preValues, editor))
{ {
AddError(actionContext.ModelState, result, p.Alias); actionContext.ModelState.AddPropertyError(result, p.Alias);
} }
} }
} }
@@ -164,30 +164,6 @@ namespace Umbraco.Web.WebApi.Filters
return actionContext.ModelState.IsValid; return actionContext.ModelState.IsValid;
} }
/// <summary>
/// Adds the error to model state correctly for a property so we can use it on the client side.
/// </summary>
/// <param name="modelState"></param>
/// <param name="result"></param>
/// <param name="propertyAlias"></param>
private void AddError(ModelStateDictionary modelState, ValidationResult result, string propertyAlias)
{
//if there are no member names supplied then we assume that the validation message is for the overall property
// not a sub field on the property editor
if (!result.MemberNames.Any())
{
//add a model state error for the entire property
modelState.AddModelError(string.Format("{0}.{1}", "Properties", propertyAlias), result.ErrorMessage);
}
else
{
//there's assigned field names so we'll combine the field name with the property name
// so that we can try to match it up to a real sub field of this editor
foreach (var field in result.MemberNames)
{
modelState.AddModelError(string.Format("{0}.{1}.{2}", "Properties", propertyAlias, field), result.ErrorMessage);
}
}
}
} }
} }

View File

@@ -31,8 +31,8 @@ namespace umbraco.cms.businesslogic.member
public class Member : Content public class Member : Content
{ {
#region Constants and static members #region Constants and static members
public static readonly string UmbracoMemberProviderName = "UmbracoMembershipProvider"; public static readonly string UmbracoMemberProviderName = Constants.Conventions.Member.UmbracoMemberProviderName;
public static readonly string UmbracoRoleProviderName = "UmbracoRoleProvider"; public static readonly string UmbracoRoleProviderName = Constants.Conventions.Member.UmbracoRoleProviderName;
public static readonly Guid _objectType = new Guid(Constants.ObjectTypes.Member); public static readonly Guid _objectType = new Guid(Constants.ObjectTypes.Member);
private static readonly object m_Locker = new object(); private static readonly object m_Locker = new object();
@@ -212,6 +212,8 @@ namespace umbraco.cms.businesslogic.member
/// <returns>The new member</returns> /// <returns>The new member</returns>
public static Member MakeNew(string Name, string LoginName, string Email, MemberType mbt, User u) public static Member MakeNew(string Name, string LoginName, string Email, MemberType mbt, User u)
{ {
if (mbt == null) throw new ArgumentNullException("mbt");
var loginName = (!String.IsNullOrEmpty(LoginName)) ? LoginName : Name; var loginName = (!String.IsNullOrEmpty(LoginName)) ? LoginName : Name;
if (String.IsNullOrEmpty(loginName)) if (String.IsNullOrEmpty(loginName))

View File

@@ -815,7 +815,7 @@ namespace umbraco.cms.businesslogic.web
if (!e.Cancel) if (!e.Cancel)
{ {
var result = ((ContentService)ApplicationContext.Current.Services.ContentService).PublishInternal(Content, u.Id); var result = ApplicationContext.Current.Services.ContentService.PublishWithStatus(Content, u.Id);
_published = result.Success; _published = result.Success;
FireAfterPublish(e); FireAfterPublish(e);
@@ -831,8 +831,7 @@ namespace umbraco.cms.businesslogic.web
[Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.PublishWithChildren()", false)] [Obsolete("Obsolete, Use Umbraco.Core.Services.ContentService.PublishWithChildren()", false)]
public bool PublishWithChildrenWithResult(User u) public bool PublishWithChildrenWithResult(User u)
{ {
var result = ((ContentService)ApplicationContext.Current.Services.ContentService) var result = ApplicationContext.Current.Services.ContentService.PublishWithChildrenWithStatus(Content, u.Id, true);
.PublishWithChildrenInternal(Content, u.Id, true);
//This used to just return false only when the parent content failed, otherwise would always return true so we'll //This used to just return false only when the parent content failed, otherwise would always return true so we'll
// do the same thing for the moment // do the same thing for the moment
return result.Single(x => x.Result.ContentItem.Id == Id).Success; return result.Single(x => x.Result.ContentItem.Id == Id).Success;
@@ -872,8 +871,8 @@ namespace umbraco.cms.businesslogic.web
if (!e.Cancel) if (!e.Cancel)
{ {
IEnumerable<Attempt<PublishStatus>> publishedResults = ((ContentService)ApplicationContext.Current.Services.ContentService) IEnumerable<Attempt<PublishStatus>> publishedResults = ApplicationContext.Current.Services.ContentService
.PublishWithChildrenInternal(Content, u.Id); .PublishWithChildrenWithStatus(Content, u.Id);
FireAfterPublish(e); FireAfterPublish(e);
} }
@@ -889,8 +888,8 @@ namespace umbraco.cms.businesslogic.web
if (!e.Cancel) if (!e.Cancel)
{ {
publishedResults = ((ContentService) ApplicationContext.Current.Services.ContentService) publishedResults = ApplicationContext.Current.Services.ContentService
.PublishWithChildrenInternal(Content, userId, includeUnpublished); .PublishWithChildrenWithStatus(Content, userId, includeUnpublished);
FireAfterPublish(e); FireAfterPublish(e);
} }
@@ -918,8 +917,8 @@ namespace umbraco.cms.businesslogic.web
if (!publishArgs.Cancel) if (!publishArgs.Cancel)
{ {
//NOTE: The 'false' parameter will cause the PublishingStrategy events to fire which will ensure that the cache is refreshed. //NOTE: The 'false' parameter will cause the PublishingStrategy events to fire which will ensure that the cache is refreshed.
result = ((ContentService)ApplicationContext.Current.Services.ContentService) result = ApplicationContext.Current.Services.ContentService
.SaveAndPublishInternal(Content, userId); .SaveAndPublishWithStatus(Content, userId);
base.VersionDate = Content.UpdateDate; base.VersionDate = Content.UpdateDate;
this.UpdateDate = Content.UpdateDate; this.UpdateDate = Content.UpdateDate;
@@ -1007,8 +1006,8 @@ namespace umbraco.cms.businesslogic.web
if (!publishArgs.Cancel) if (!publishArgs.Cancel)
{ {
//NOTE: The 'false' parameter will cause the PublishingStrategy events to fire which will ensure that the cache is refreshed. //NOTE: The 'false' parameter will cause the PublishingStrategy events to fire which will ensure that the cache is refreshed.
var result = ((ContentService)ApplicationContext.Current.Services.ContentService) var result = ApplicationContext.Current.Services.ContentService
.SaveAndPublishInternal(Content, u.Id); .SaveAndPublishWithStatus(Content, u.Id);
base.VersionDate = Content.UpdateDate; base.VersionDate = Content.UpdateDate;
this.UpdateDate = Content.UpdateDate; this.UpdateDate = Content.UpdateDate;

View File

@@ -1,20 +1,24 @@
#region namespace #region namespace
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using System.Web.Security; using System.Web.Security;
using System.Configuration; using System.Configuration;
using Umbraco.Core;
using Umbraco.Core.Models;
using umbraco.BusinessLogic; using umbraco.BusinessLogic;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Web.Util; using System.Web.Util;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Configuration.Provider; using System.Configuration.Provider;
using umbraco.cms.businesslogic; using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.member;
using System.Security; using System.Security;
using System.Security.Permissions; using System.Security.Permissions;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Member = umbraco.cms.businesslogic.member.Member;
using MemberType = umbraco.cms.businesslogic.member.MemberType;
#endregion #endregion
namespace umbraco.providers.members namespace umbraco.providers.members
@@ -354,6 +358,68 @@ namespace umbraco.providers.members
} }
} }
/// <summary>
/// Adds a new membership user to the data source.
/// </summary>
/// <param name="memberTypeAlias"></param>
/// <param name="username">The user name for the new user.</param>
/// <param name="password">The password for the new user.</param>
/// <param name="email">The e-mail address for the new user.</param>
/// <param name="passwordQuestion">The password question for the new user.</param>
/// <param name="passwordAnswer">The password answer for the new user</param>
/// <param name="isApproved">Whether or not the new user is approved to be validated.</param>
/// <param name="providerUserKey">The unique identifier from the membership data source for the user.</param>
/// <param name="status">A <see cref="T:System.Web.Security.MembershipCreateStatus"></see> enumeration value indicating whether the user was created successfully.</param>
/// <returns>
/// A <see cref="T:System.Web.Security.MembershipUser"></see> object populated with the information for the newly created user.
/// </returns>
public MembershipUser CreateUser(string memberTypeAlias, string username, string password, string email, string passwordQuestion,
string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
if (Member.GetMemberFromLoginName(username) != null)
status = MembershipCreateStatus.DuplicateUserName;
else if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail)
status = MembershipCreateStatus.DuplicateEmail;
else
{
var memberType = MemberType.GetByAlias(memberTypeAlias);
if (memberType == null)
{
throw new InvalidOperationException("Could not find a member type with alias " + memberTypeAlias + ". Ensure your membership provider configuration is up to date and that the default member type exists.");
}
Member m = Member.MakeNew(username, email, memberType, User.GetUser(0));
m.Password = password;
MembershipUser mUser =
ConvertToMembershipUser(m);
// custom fields
if (!String.IsNullOrEmpty(m_PasswordRetrievalQuestionPropertyTypeAlias))
UpdateMemberProperty(m, m_PasswordRetrievalQuestionPropertyTypeAlias, passwordQuestion);
if (!String.IsNullOrEmpty(m_PasswordRetrievalAnswerPropertyTypeAlias))
UpdateMemberProperty(m, m_PasswordRetrievalAnswerPropertyTypeAlias, passwordAnswer);
if (!String.IsNullOrEmpty(m_ApprovedPropertyTypeAlias))
UpdateMemberProperty(m, m_ApprovedPropertyTypeAlias, isApproved);
if (!String.IsNullOrEmpty(m_LastLoginPropertyTypeAlias))
{
mUser.LastActivityDate = DateTime.Now;
UpdateMemberProperty(m, m_LastLoginPropertyTypeAlias, mUser.LastActivityDate);
}
// save
m.Save();
status = MembershipCreateStatus.Success;
return mUser;
}
return null;
}
/// <summary> /// <summary>
/// Adds a new membership user to the data source. /// Adds a new membership user to the data source.
/// </summary> /// </summary>
@@ -371,41 +437,7 @@ namespace umbraco.providers.members
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion,
string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{ {
if (Member.GetMemberFromLoginName(username) != null) return CreateUser(m_DefaultMemberTypeAlias, username, password, email, passwordQuestion, passwordAnswer, isApproved, providerUserKey, out status);
status = MembershipCreateStatus.DuplicateUserName;
else if (Member.GetMemberFromEmail(email) != null && RequiresUniqueEmail)
status = MembershipCreateStatus.DuplicateEmail;
else
{
Member m = Member.MakeNew(username, email, MemberType.GetByAlias(m_DefaultMemberTypeAlias), User.GetUser(0));
m.Password = password;
MembershipUser mUser =
ConvertToMembershipUser(m);
// custom fields
if (!String.IsNullOrEmpty(m_PasswordRetrievalQuestionPropertyTypeAlias))
UpdateMemberProperty(m, m_PasswordRetrievalQuestionPropertyTypeAlias, passwordQuestion);
if (!String.IsNullOrEmpty(m_PasswordRetrievalAnswerPropertyTypeAlias))
UpdateMemberProperty(m, m_PasswordRetrievalAnswerPropertyTypeAlias, passwordAnswer);
if (!String.IsNullOrEmpty(m_ApprovedPropertyTypeAlias))
UpdateMemberProperty(m, m_ApprovedPropertyTypeAlias, isApproved);
if (!String.IsNullOrEmpty(m_LastLoginPropertyTypeAlias)) {
mUser.LastActivityDate = DateTime.Now;
UpdateMemberProperty(m, m_LastLoginPropertyTypeAlias, mUser.LastActivityDate);
}
// save
m.Save();
status = MembershipCreateStatus.Success;
return mUser;
}
return null;
} }
/// <summary> /// <summary>
@@ -439,7 +471,16 @@ namespace umbraco.providers.members
/// </returns> /// </returns>
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
{ {
throw new Exception("The method or operation is not implemented."); var byEmail = ApplicationContext.Current.Services.MemberService.FindMembersByEmail(emailToMatch).ToArray();
totalRecords = byEmail.Length;
var pagedResult = new PagedResult<IMember>(totalRecords, pageIndex, pageSize);
var collection = new MembershipUserCollection();
foreach (var m in byEmail.Skip(pagedResult.SkipSize).Take(pageSize))
{
collection.Add(ConvertToMembershipUser(m));
}
return collection;
} }
/// <summary> /// <summary>
@@ -752,6 +793,26 @@ namespace umbraco.providers.members
return null; return null;
} }
private static string GetMemberProperty(IMember m, string propertyAlias, bool isBool)
{
if (!String.IsNullOrEmpty(propertyAlias))
{
if (m.Properties[propertyAlias] != null &&
m.Properties[propertyAlias].Value != null)
{
if (isBool)
{
// Umbraco stored true as 1, which means it can be bool.tryParse'd
return m.Properties[propertyAlias].Value.ToString().Replace("1", "true").Replace("0", "false");
}
else
return m.Properties[propertyAlias].Value.ToString();
}
}
return null;
}
/// <summary> /// <summary>
/// Verifies that the specified user name and password exist in the data source. /// Verifies that the specified user name and password exist in the data source.
/// </summary> /// </summary>
@@ -973,6 +1034,53 @@ namespace umbraco.providers.members
DateTime.Now, DateTime.Now, DateTime.Now); DateTime.Now, DateTime.Now, DateTime.Now);
} }
} }
/// <summary>
/// Converts to membership user.
/// </summary>
/// <param name="m">The m.</param>
/// <returns></returns>
private MembershipUser ConvertToMembershipUser(IMember m)
{
if (m == null) return null;
else
{
DateTime lastLogin = DateTime.Now;
bool isApproved = true;
bool isLocked = false;
string comment = "";
string passwordQuestion = "";
// last login
if (!String.IsNullOrEmpty(m_LastLoginPropertyTypeAlias))
{
DateTime.TryParse(GetMemberProperty(m, m_LastLoginPropertyTypeAlias, false), out lastLogin);
}
// approved
if (!String.IsNullOrEmpty(m_ApprovedPropertyTypeAlias))
{
bool.TryParse(GetMemberProperty(m, m_ApprovedPropertyTypeAlias, true), out isApproved);
}
// locked
if (!String.IsNullOrEmpty(m_LockPropertyTypeAlias))
{
bool.TryParse(GetMemberProperty(m, m_LockPropertyTypeAlias, true), out isLocked);
}
// comment
if (!String.IsNullOrEmpty(m_CommentPropertyTypeAlias))
{
comment = GetMemberProperty(m, m_CommentPropertyTypeAlias, false);
}
// password question
if (!String.IsNullOrEmpty(m_PasswordRetrievalQuestionPropertyTypeAlias))
{
passwordQuestion = GetMemberProperty(m, m_PasswordRetrievalQuestionPropertyTypeAlias, false);
}
return new MembershipUser(m_providerName, m.Username, m.Id, m.Email, passwordQuestion, comment, isApproved, isLocked, m.CreateDate, lastLogin,
DateTime.Now, DateTime.Now, DateTime.Now);
}
}
#endregion #endregion
} }
} }

View File

@@ -99,6 +99,10 @@
<Project>{ccd75ec3-63db-4184-b49d-51c1dd337230}</Project> <Project>{ccd75ec3-63db-4184-b49d-51c1dd337230}</Project>
<Name>umbraco.cms</Name> <Name>umbraco.cms</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
<Project>{31785bc3-256c-4613-b2f5-a1b0bdded8c1}</Project>
<Name>Umbraco.Core</Name>
</ProjectReference>
<ProjectReference Include="..\umbraco.datalayer\umbraco.datalayer.csproj"> <ProjectReference Include="..\umbraco.datalayer\umbraco.datalayer.csproj">
<Project>{C7CB79F0-1C97-4B33-BFA7-00731B579AE2}</Project> <Project>{C7CB79F0-1C97-4B33-BFA7-00731B579AE2}</Project>
<Name>umbraco.datalayer</Name> <Name>umbraco.datalayer</Name>