using System; using System.Collections.Generic; using System.Web.Security; using Umbraco.Core.Configuration; using Umbraco.Core.Events; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.UnitOfWork; using System.Linq; using Umbraco.Core.IO; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Security; namespace Umbraco.Core.Services { /// /// Represents the MemberService. /// public class MemberService : RepositoryService, IMemberService { private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IMemberGroupService _memberGroupService; private IMemberTypeService _memberTypeService; #region Constructor public MemberService( IDatabaseUnitOfWorkProvider provider, ILogger logger, IEventMessagesFactory eventMessagesFactory, IMemberGroupService memberGroupService, IDataTypeService dataTypeService) : base(provider, logger, eventMessagesFactory) { if (memberGroupService == null) throw new ArgumentNullException(nameof(memberGroupService)); if (dataTypeService == null) throw new ArgumentNullException(nameof(dataTypeService)); _memberGroupService = memberGroupService; _dataTypeService = dataTypeService; } // don't change or remove this, will need it later private IMemberTypeService MemberTypeService => _memberTypeService; //// handle circular dependencies //internal IMemberTypeService MemberTypeService //{ // get // { // if (_memberTypeService == null) // throw new InvalidOperationException("MemberService.MemberTypeService has not been initialized."); // return _memberTypeService; // } // set { _memberTypeService = value; } //} #endregion #region Count /// /// Gets the total number of Members based on the count type /// /// /// The way the Online count is done is the same way that it is done in the MS SqlMembershipProvider - We query for any members /// that have their last active date within the Membership.UserIsOnlineTimeWindow (which is in minutes). It isn't exact science /// but that is how MS have made theirs so we'll follow that principal. /// /// to count by /// with number of Members for passed in type public int GetCount(MemberCountType countType) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); IQuery query; switch (countType) { case MemberCountType.All: query = repository.Query; break; case MemberCountType.Online: var fromDate = DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow); query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.LastLoginDate && ((Member)x).DateTimePropertyValue > fromDate); break; case MemberCountType.LockedOut: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsLockedOut && ((Member)x).BoolPropertyValue); break; case MemberCountType.Approved: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == Constants.Conventions.Member.IsApproved && ((Member)x).BoolPropertyValue); break; default: throw new ArgumentOutOfRangeException(nameof(countType)); // causes rollback; } var count = repository.GetCountByQuery(query); uow.Complete(); return count; } } /// /// Gets the count of Members by an optional MemberType alias /// /// If no alias is supplied then the count for all Member will be returned /// Optional alias for the MemberType when counting number of Members /// with number of Members public int Count(string memberTypeAlias = null) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var count = repository.Count(memberTypeAlias); uow.Complete(); return count; } } #endregion #region Create /// /// Creates an object without persisting it /// /// This method is convenient for when you need to add properties to a new Member /// before persisting it in order to limit the amount of times its saved. /// Also note that the returned will not have an Id until its saved. /// Username of the Member to create /// Email of the Member to create /// Name of the Member to create /// Alias of the MemberType the Member should be based on /// public IMember CreateMember(string username, string email, string name, string memberTypeAlias) { var memberType = GetMemberType(memberTypeAlias); if (memberType == null) throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); var member = new Member(name, email.ToLower().Trim(), username, memberType); CreateMember(null, member, 0, false); return member; } /// /// Creates an object without persisting it /// /// This method is convenient for when you need to add properties to a new Member /// before persisting it in order to limit the amount of times its saved. /// Also note that the returned will not have an Id until its saved. /// Username of the Member to create /// Email of the Member to create /// Name of the Member to create /// MemberType the Member should be based on /// public IMember CreateMember(string username, string email, string name, IMemberType memberType) { if (memberType == null) throw new ArgumentNullException(nameof(memberType)); var member = new Member(name, email.ToLower().Trim(), username, memberType); CreateMember(null, member, 0, false); return member; } /// /// Creates and persists a Member /// /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create /// Name of the Member to create /// Alias of the MemberType the Member should be based on /// public IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias) { using (var uow = UowProvider.CreateUnitOfWork()) { // locking the media tree secures media types too uow.WriteLock(Constants.Locks.MediaTree); var memberType = GetMemberType(memberTypeAlias); // + locks if (memberType == null) throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback var member = new Member(name, email.ToLower().Trim(), username, memberType); CreateMember(uow, member, 0, true); uow.Complete(); return member; } } /// /// Creates and persists a Member /// /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create /// MemberType the Member should be based on /// public IMember CreateMemberWithIdentity(string username, string email, IMemberType memberType) { return CreateMemberWithIdentity(username, email, username, "", memberType); } /// /// Creates and persists a Member /// /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create /// Name of the Member to create /// MemberType the Member should be based on /// public IMember CreateMemberWithIdentity(string username, string email, string name, IMemberType memberType) { return CreateMemberWithIdentity(username, email, name, "", memberType); } /// /// Creates and persists a new /// /// An can be of type or /// Username of the to create /// Email of the to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// Alias of the Type /// IMember IMembershipMemberService.CreateWithIdentity(string username, string email, string passwordValue, string memberTypeAlias) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MediaTree); // ensure it all still make sense var memberType = GetMemberType(memberTypeAlias); // + locks if (memberType == null) throw new ArgumentException("No member type with that alias.", nameof(memberTypeAlias)); // causes rollback var member = new Member(username, email.ToLower().Trim(), username, passwordValue, memberType); CreateMember(uow, member, -1, true); uow.Complete(); return member; } } /// /// Creates and persists a Member /// /// Using this method will persist the Member object before its returned /// meaning that it will have an Id available (unlike the CreateMember method) /// Username of the Member to create /// Email of the Member to create /// Name of the Member to create /// This value should be the encoded/encrypted/hashed value for the password that will be stored in the database /// MemberType the Member should be based on /// private IMember CreateMemberWithIdentity(string username, string email, string name, string passwordValue, IMemberType memberType) { if (memberType == null) throw new ArgumentNullException(nameof(memberType)); using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MediaTree); // ensure it all still make sense var vrfy = GetMemberType(memberType.Alias); // + locks if (vrfy == null || vrfy.Id != memberType.Id) throw new ArgumentException($"Member type with alias {memberType.Alias} does not exist or is a different member type."); // causes rollback var member = new Member(name, email.ToLower().Trim(), username, passwordValue, memberType); CreateMember(uow, member, -1, true); uow.Complete(); return member; } } private void CreateMember(IDatabaseUnitOfWork uow, Member member, int userId, bool withIdentity) { // there's no Creating event for members if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) { member.WasCancelled = true; return; } member.CreatorId = userId; if (withIdentity) { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) { member.WasCancelled = true; return; } var repository = uow.CreateRepository(); repository.AddOrUpdate(member); // fixme kill repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); Saved.RaiseEvent(new SaveEventArgs(member, false), this); } Created.RaiseEvent(new NewEventArgs(member, false, member.ContentType.Alias, -1), this); var msg = withIdentity ? "Member '{0}' was created with Id {1}" : "Member '{0}' was created"; Audit(AuditType.New, string.Format(msg, member.Name, member.Id), member.CreatorId, member.Id); } #endregion #region Get, Has, Is, Exists... /// /// Gets a Member by its integer id /// /// Id /// public IMember GetById(int id) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var member = repository.Get(id); uow.Complete(); return member; } } /// /// Gets a Member by the unique key /// /// The guid key corresponds to the unique id in the database /// and the user id in the membership provider. /// Id /// public IMember GetByKey(Guid id) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Key == id); var member = repository.GetByQuery(query).FirstOrDefault(); uow.Complete(); return member; } } /// /// Gets a list of paged objects /// /// Current page index /// Size of the page /// Total number of records found (out) /// public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.GetPagedResultsByQuery(null, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); uow.Complete(); return members; } } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, string memberTypeAlias = null, string filter = "") { return GetAll(pageIndex, pageSize, out totalRecords, orderBy, orderDirection, true, memberTypeAlias, filter); } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, bool orderBySystemField, string memberTypeAlias, string filter) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = memberTypeAlias == null ? null : repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, orderBySystemField, filter); uow.Complete(); return members; } } /// /// Gets an by its provider key /// /// Id to use for retrieval /// public IMember GetByProviderKey(object id) { var asGuid = id.TryConvertTo(); if (asGuid.Success) return GetByKey(asGuid.Result); var asInt = id.TryConvertTo(); if (asInt.Success) return GetById(asInt.Result); return null; } /// /// Get an by email /// /// Email to use for retrieval /// public IMember GetByEmail(string email) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Email.Equals(email)); var member = repository.GetByQuery(query).FirstOrDefault(); uow.Complete(); return member; } } /// /// Get an by username /// /// Username to use for retrieval /// public IMember GetByUsername(string username) { //TODO: Somewhere in here, whether at this level or the repository level, we need to add // a caching mechanism since this method is used by all the membership providers and could be // called quite a bit when dealing with members. using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.Username.Equals(username)); var member = repository.GetByQuery(query).FirstOrDefault(); uow.Complete(); return member; } } /// /// Gets all Members for the specified MemberType alias /// /// Alias of the MemberType /// public IEnumerable GetMembersByMemberType(string memberTypeAlias) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => x.ContentTypeAlias == memberTypeAlias); var members = repository.GetByQuery(query); uow.Complete(); return members; } } /// /// Gets all Members for the MemberType id /// /// Id of the MemberType /// public IEnumerable GetMembersByMemberType(int memberTypeId) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.Get(memberTypeId); var query = repository.Query.Where(x => x.ContentTypeId == memberTypeId); var members = repository.GetByQuery(query); uow.Complete(); return members; } } /// /// Gets all Members within the specified MemberGroup name /// /// Name of the MemberGroup /// public IEnumerable GetMembersByGroup(string memberGroupName) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.GetByMemberGroup(memberGroupName); uow.Complete(); return members; } } /// /// Gets all Members with the ids specified /// /// If no Ids are specified all Members will be retrieved /// Optional list of Member Ids /// public IEnumerable GetAllMembers(params int[] ids) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.GetAll(ids); uow.Complete(); return members; } } /// /// Finds Members based on their display name /// /// Display name to match /// Current page index /// Size of the page /// Total number of records found (out) /// The type of match to make as . Default is /// public IEnumerable FindMembersByDisplayName(string displayNameToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query; switch (matchType) { case StringPropertyMatchType.Exact: query.Where(member => member.Name.Equals(displayNameToMatch)); break; case StringPropertyMatchType.Contains: query.Where(member => member.Name.Contains(displayNameToMatch)); break; case StringPropertyMatchType.StartsWith: query.Where(member => member.Name.StartsWith(displayNameToMatch)); break; case StringPropertyMatchType.EndsWith: query.Where(member => member.Name.EndsWith(displayNameToMatch)); break; case StringPropertyMatchType.Wildcard: query.Where(member => member.Name.SqlWildcard(displayNameToMatch, TextColumnType.NVarchar)); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback } var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Name", Direction.Ascending, true); uow.Complete(); return members; } } /// /// Finds a list of objects by a partial email string /// /// Partial email string to match /// Current page index /// Size of the page /// Total number of records found (out) /// The type of match to make as . Default is /// public IEnumerable FindByEmail(string emailStringToMatch, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query; switch (matchType) { case StringPropertyMatchType.Exact: query.Where(member => member.Email.Equals(emailStringToMatch)); break; case StringPropertyMatchType.Contains: query.Where(member => member.Email.Contains(emailStringToMatch)); break; case StringPropertyMatchType.StartsWith: query.Where(member => member.Email.StartsWith(emailStringToMatch)); break; case StringPropertyMatchType.EndsWith: query.Where(member => member.Email.EndsWith(emailStringToMatch)); break; case StringPropertyMatchType.Wildcard: query.Where(member => member.Email.SqlWildcard(emailStringToMatch, TextColumnType.NVarchar)); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); } var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "Email", Direction.Ascending, true); uow.Complete(); return members; } } /// /// Finds a list of objects by a partial username /// /// Partial username to match /// Current page index /// Size of the page /// Total number of records found (out) /// The type of match to make as . Default is /// public IEnumerable FindByUsername(string login, long pageIndex, int pageSize, out long totalRecords, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query; switch (matchType) { case StringPropertyMatchType.Exact: query.Where(member => member.Username.Equals(login)); break; case StringPropertyMatchType.Contains: query.Where(member => member.Username.Contains(login)); break; case StringPropertyMatchType.StartsWith: query.Where(member => member.Username.StartsWith(login)); break; case StringPropertyMatchType.EndsWith: query.Where(member => member.Username.EndsWith(login)); break; case StringPropertyMatchType.Wildcard: query.Where(member => member.Email.SqlWildcard(login, TextColumnType.NVarchar)); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); } var members = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, "LoginName", Direction.Ascending, true); uow.Complete(); return members; } } /// /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for /// Value to match /// The type of match to make as . Default is /// public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, string value, StringPropertyMatchType matchType = StringPropertyMatchType.Exact) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); IQuery query; switch (matchType) { case StringPropertyMatchType.Exact: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlEquals(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlEquals(value, TextColumnType.NVarchar))); break; case StringPropertyMatchType.Contains: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlContains(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlContains(value, TextColumnType.NVarchar))); break; case StringPropertyMatchType.StartsWith: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlStartsWith(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlStartsWith(value, TextColumnType.NVarchar))); break; case StringPropertyMatchType.EndsWith: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && (((Member)x).LongStringPropertyValue.SqlEndsWith(value, TextColumnType.NText) || ((Member)x).ShortStringPropertyValue.SqlEndsWith(value, TextColumnType.NVarchar))); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback } var members = repository.GetByQuery(query); uow.Complete(); return members; } } /// /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for /// Value to match /// The type of match to make as . Default is /// public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, int value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); IQuery query; switch (matchType) { case ValuePropertyMatchType.Exact: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue > value); break; case ValuePropertyMatchType.LessThan: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).IntegerPropertyValue <= value); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback } var members = repository.GetByQuery(query); uow.Complete(); return members; } } /// /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for /// Value to match /// public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, bool value) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).BoolPropertyValue == value); var members = repository.GetByQuery(query); uow.Complete(); return members; } } /// /// Gets a list of Members based on a property search /// /// Alias of the PropertyType to search for /// Value to match /// The type of match to make as . Default is /// public IEnumerable GetMembersByPropertyValue(string propertyTypeAlias, DateTime value, ValuePropertyMatchType matchType = ValuePropertyMatchType.Exact) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); IQuery query; switch (matchType) { case ValuePropertyMatchType.Exact: query = repository.Query.Where( x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).DateTimePropertyValue == value); break; case ValuePropertyMatchType.GreaterThan: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).DateTimePropertyValue > value); break; case ValuePropertyMatchType.LessThan: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).DateTimePropertyValue < value); break; case ValuePropertyMatchType.GreaterThanOrEqualTo: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).DateTimePropertyValue >= value); break; case ValuePropertyMatchType.LessThanOrEqualTo: query = repository.Query.Where(x => ((Member)x).PropertyTypeAlias == propertyTypeAlias && ((Member)x).DateTimePropertyValue <= value); break; default: throw new ArgumentOutOfRangeException(nameof(matchType)); // causes rollback } //TODO: Since this is by property value, we need a GetByPropertyQuery on the repo! var members = repository.GetByQuery(query); uow.Complete(); return members; } } /// /// Checks if a Member with the id exists /// /// Id of the Member /// True if the Member exists otherwise False public bool Exists(int id) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var exists = repository.Exists(id); uow.Complete(); return exists; } } /// /// Checks if a Member with the username exists /// /// Username to check /// True if the Member exists otherwise False public bool Exists(string username) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var exists = repository.Exists(username); uow.Complete(); return exists; } } #endregion #region Save /// /// Saves an /// /// to Save /// Optional parameter to raise events. /// Default is True otherwise set to False to not raise events public void Save(IMember member, bool raiseEvents = true) { if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) return; using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.AddOrUpdate(member); // fixme get rid of xml repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); uow.Complete(); } if (raiseEvents) Saved.RaiseEvent(new SaveEventArgs(member, false), this); Audit(AuditType.Save, "Save Member performed by user", 0, member.Id); } /// /// Saves a list of objects /// /// to save /// Optional parameter to raise events. /// Default is True otherwise set to False to not raise events public void Save(IEnumerable members, bool raiseEvents = true) { var membersA = members.ToArray(); if (raiseEvents && Saving.IsRaisedEventCancelled(new SaveEventArgs(membersA), this)) return; using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); foreach (var member in membersA) { repository.AddOrUpdate(member); // fixme get rid of xml stuff repository.AddOrUpdateContentXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); // generate preview for blame history? if (UmbracoConfig.For.UmbracoSettings().Content.GlobalPreviewStorageEnabled) repository.AddOrUpdatePreviewXml(member, m => _entitySerializer.Serialize(_dataTypeService, m)); } uow.Complete(); } if (raiseEvents) Saved.RaiseEvent(new SaveEventArgs(membersA, false), this); Audit(AuditType.Save, "Save Member items performed by user", 0, -1); } #endregion #region Delete /// /// Deletes an /// /// to Delete public void Delete(IMember member) { if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(member), this)) return; using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); DeleteLocked(repository, member); uow.Complete(); } Audit(AuditType.Delete, "Delete Member performed by user", 0, member.Id); } private void DeleteLocked(IMemberRepository repository, IMember member) { // a member has no descendants repository.Delete(member); var args = new DeleteEventArgs(member, false); // raise event & get flagged files Deleted.RaiseEvent(args, this); IOHelper.DeleteFiles(args.MediaFilesToDelete, // remove flagged files (file, e) => Logger.Error("An error occurred while deleting file attached to nodes: " + file, e)); } #endregion #region Roles public void AddRole(string roleName) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.CreateIfNotExists(roleName); uow.Complete(); } } public IEnumerable GetAllRoles() { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var result = repository.GetAll().Select(x => x.Name).Distinct(); uow.Complete(); return result; } } public IEnumerable GetAllRoles(int memberId) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var result = repository.GetMemberGroupsForMember(memberId); var roles = result.Select(x => x.Name).Distinct(); uow.Complete(); return roles; } } public IEnumerable GetAllRoles(string username) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var result = repository.GetMemberGroupsForMember(username); var roles = result.Select(x => x.Name).Distinct(); uow.Complete(); return roles; } } public IEnumerable GetMembersInRole(string roleName) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.GetByMemberGroup(roleName); uow.Complete(); return members; } } public IEnumerable FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.ReadLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); var members = repository.FindMembersInRole(roleName, usernameToMatch, matchType); uow.Complete(); return members; } } // FIXME CURRENT WIP public bool DeleteRole(string roleName, bool throwIfBeingUsed) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); if (throwIfBeingUsed) { // get members in role var memberRepository = uow.CreateRepository(); var membersInRole = memberRepository.GetByMemberGroup(roleName); if (membersInRole.Any()) throw new InvalidOperationException("The role " + roleName + " is currently assigned to members"); } var query = repository.QueryFactory.Create().Where(g => g.Name == roleName); var found = repository.GetByQuery(query).ToArray(); foreach (var memberGroup in found) _memberGroupService.Delete(memberGroup); // FIXME BAD BAD BAD! uow.Complete(); return found.Length > 0; } } public void AssignRole(string username, string roleName) { AssignRoles(new[] { username }, new[] { roleName }); } public void AssignRoles(string[] usernames, string[] roleNames) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.AssignRoles(usernames, roleNames); uow.Complete(); } } public void DissociateRole(string username, string roleName) { DissociateRoles(new[] { username }, new[] { roleName }); } public void DissociateRoles(string[] usernames, string[] roleNames) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.DissociateRoles(usernames, roleNames); uow.Complete(); } } public void AssignRole(int memberId, string roleName) { AssignRoles(new[] { memberId }, new[] { roleName }); } public void AssignRoles(int[] memberIds, string[] roleNames) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.AssignRoles(memberIds, roleNames); uow.Complete(); } } public void DissociateRole(int memberId, string roleName) { DissociateRoles(new[] { memberId }, new[] { roleName }); } public void DissociateRoles(int[] memberIds, string[] roleNames) { using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); repository.DissociateRoles(memberIds, roleNames); uow.Complete(); } } #endregion #region Private Methods private void Audit(AuditType type, string message, int userId, int objectId) { using (var uow = UowProvider.CreateUnitOfWork()) { var repo = uow.CreateRepository(); repo.AddOrUpdate(new AuditItem(objectId, message, type, userId)); uow.Complete(); } } #endregion #region Event Handlers /// /// Occurs before Delete /// public static event TypedEventHandler> Deleting; /// /// Occurs after Delete /// public static event TypedEventHandler> Deleted; /// /// Occurs before Save /// public static event TypedEventHandler> Saving; /// /// Occurs after Create /// /// /// Please note that the Member object has been created, but might not have been saved /// so it does not have an identity yet (meaning no Id has been set). /// public static event TypedEventHandler> Created; /// /// Occurs after Save /// public static event TypedEventHandler> Saved; #endregion #region Membership /// /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method /// /// This method exists so that Umbraco developers can use one entry point to create/update /// Members if they choose to. /// The Member to save the password for /// The password to encrypt and save public void SavePassword(IMember member, string password) { if (member == null) throw new ArgumentNullException("member"); var provider = MembershipProviderExtensions.GetMembersMembershipProvider(); if (provider.IsUmbracoMembershipProvider()) provider.ChangePassword(member.Username, "", password); else throw new NotSupportedException("When using a non-Umbraco membership provider you must change the member password by using the MembershipProvider.ChangePassword method"); //go re-fetch the member and update the properties that may have changed var result = GetByUsername(member.Username); //should never be null but it could have been deleted by another thread. // fixme - should LOCK! instead if (result == null) return; member.RawPasswordValue = result.RawPasswordValue; member.LastPasswordChangeDate = result.LastPasswordChangeDate; member.UpdateDate = result.UpdateDate; // fixme - not saving? } /// /// A helper method that will create a basic/generic member for use with a generic membership provider /// /// internal static IMember CreateGenericMembershipProviderMember(string name, string email, string username, string password) { var identity = int.MaxValue; var memType = new MemberType(-1); var propGroup = new PropertyGroup { Name = "Membership", Id = --identity }; propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TextboxAlias, DataTypeDatabaseType.Ntext, Constants.Conventions.Member.Comments) { Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Id = --identity, Key = identity.ToGuid() }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsApproved) { Name = Constants.Conventions.Member.IsApprovedLabel, SortOrder = 3, Id = --identity, Key = identity.ToGuid() }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer, Constants.Conventions.Member.IsLockedOut) { Name = Constants.Conventions.Member.IsLockedOutLabel, SortOrder = 4, Id = --identity, Key = identity.ToGuid() }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLockoutDate) { Name = Constants.Conventions.Member.LastLockoutDateLabel, SortOrder = 5, Id = --identity, Key = identity.ToGuid() }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastLoginDate) { Name = Constants.Conventions.Member.LastLoginDateLabel, SortOrder = 6, Id = --identity, Key = identity.ToGuid() }); propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date, Constants.Conventions.Member.LastPasswordChangeDate) { Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, SortOrder = 7, Id = --identity, Key = identity.ToGuid() }); memType.PropertyGroups.Add(propGroup); // should we "create member"? var member = new Member(name, email, username, password, memType); //we've assigned ids to the property types and groups but we also need to assign fake ids to the properties themselves. foreach (var property in member.Properties) { property.Id = --identity; } return member; } #endregion #region Content Types /// /// Delete Members of the specified MemberType id /// /// Id of the MemberType public void DeleteMembersOfType(int memberTypeId) { // note: no tree to manage here using (var uow = UowProvider.CreateUnitOfWork()) { uow.WriteLock(Constants.Locks.MemberTree); var repository = uow.CreateRepository(); //TODO: What about content that has the contenttype as part of its composition? var query = repository.Query.Where(x => x.ContentTypeId == memberTypeId); var members = repository.GetByQuery(query).ToArray(); if (Deleting.IsRaisedEventCancelled(new DeleteEventArgs(members), this)) return; // causes rollback foreach (var member in members) { // delete media // triggers the deleted event (and handles the files) DeleteLocked(repository, member); } uow.Complete(); } } private IMemberType GetMemberType(string memberTypeAlias) { var memberType = MemberTypeService.Get(memberTypeAlias); if (memberType == null) throw new Exception(string.Format("No MemberType matching alias: \"{0}\".", memberTypeAlias)); return memberType; } [Obsolete("use MemberTypeService.GetDefault()")] // fixme kill! public string GetDefaultMemberType() { return MemberTypeService.GetDefault(); } #endregion #region Xml - Should Move! /// /// Rebuilds all xml content in the cmsContentXml table for all members /// /// /// Only rebuild the xml structures for the content type ids passed in, if none then rebuilds the structures /// for all members = USE WITH CARE! /// /// True if publishing succeeded, otherwise False public void RebuildXmlStructures(params int[] memberTypeIds) { using (var uow = UowProvider.CreateUnitOfWork()) { var repository = uow.CreateRepository(); repository.RebuildXmlStructures( member => _entitySerializer.Serialize(_dataTypeService, member), contentTypeIds: memberTypeIds.Length == 0 ? null : memberTypeIds); uow.Complete(); } Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); } #endregion } }