diff --git a/src/Umbraco.Core/Constants-Conventions.cs b/src/Umbraco.Core/Constants-Conventions.cs index 64f1449399..ffa84f025e 100644 --- a/src/Umbraco.Core/Constants-Conventions.cs +++ b/src/Umbraco.Core/Constants-Conventions.cs @@ -236,8 +236,7 @@ namespace Umbraco.Core }, { PasswordQuestion, - new PropertyType(new Guid(PropertyEditors.Textbox), DataTypeDatabaseType - .Nvarchar) + new PropertyType(new Guid(PropertyEditors.Textbox), DataTypeDatabaseType.Nvarchar) { Alias = PasswordQuestion, Name = PasswordQuestionLabel diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 9425446560..78be963eac 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -20,6 +20,17 @@ namespace Umbraco.Core.Models private object _providerUserKey; private Type _userTypeKey; + /// + /// Constructor for creating a Member object + /// + /// Name of the content + /// ContentType for the current Content object + public Member(string name, IMemberType contentType) + : base(name, -1, contentType, new PropertyCollection()) + { + _contentType = contentType; + } + internal Member(string name, string email, string username, string password, int parentId, IMemberType contentType) : base(name, parentId, contentType, new PropertyCollection()) { diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index eafaf74a4f..723645b89c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -22,14 +22,17 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IMemberTypeRepository _memberTypeRepository; - public MemberRepository(IDatabaseUnitOfWork work, IMemberTypeRepository memberTypeRepository) : base(work) + public MemberRepository(IDatabaseUnitOfWork work, IMemberTypeRepository memberTypeRepository) + : base(work) { + if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); _memberTypeRepository = memberTypeRepository; } public MemberRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IMemberTypeRepository memberTypeRepository) : base(work, cache) { + if (memberTypeRepository == null) throw new ArgumentNullException("memberTypeRepository"); _memberTypeRepository = memberTypeRepository; } @@ -104,11 +107,11 @@ namespace Umbraco.Core.Persistence.Repositories sql.Select("umbracoNode.*", "cmsContent.contentType", "cmsContentType.alias AS ContentTypeAlias", "cmsContentVersion.VersionId", "cmsContentVersion.VersionDate", "cmsContentVersion.LanguageLocale", "cmsMember.Email", - "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id AS PropertyDataId", "cmsPropertyData.propertytypeid", + "cmsMember.LoginName", "cmsMember.Password", "cmsPropertyData.id AS PropertyDataId", "cmsPropertyData.propertytypeid", "cmsPropertyData.dataDate", "cmsPropertyData.dataInt", "cmsPropertyData.dataNtext", "cmsPropertyData.dataNvarchar", "cmsPropertyType.id", "cmsPropertyType.Alias", "cmsPropertyType.Description", "cmsPropertyType.Name", "cmsPropertyType.mandatory", "cmsPropertyType.validationRegExp", - "cmsPropertyType.helpText", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", + "cmsPropertyType.helpText", "cmsPropertyType.sortOrder AS PropertyTypeSortOrder", "cmsPropertyType.propertyTypeGroupId", "cmsPropertyType.dataTypeId", "cmsDataType.controlId", "cmsDataType.dbType") .From() .InnerJoin().On(left => left.NodeId, right => right.NodeId) @@ -247,7 +250,7 @@ namespace Umbraco.Core.Persistence.Repositories { property.Id = keyDictionary[property.PropertyTypeId]; } - + ((Member)entity).ResetDirtyProperties(); } @@ -256,8 +259,10 @@ namespace Umbraco.Core.Persistence.Repositories //Updates Modified date ((Member)entity).UpdatingEntity(); + var dirtyEntity = (ICanBeDirty)entity; + //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("WHERE id = @ParentId", new { ParentId = ((IUmbracoEntity)entity).ParentId }); ((IUmbracoEntity)entity).Path = string.Concat(parent.Path, ",", entity.Id); @@ -293,13 +298,32 @@ namespace Umbraco.Core.Persistence.Repositories //Updates the current version - cmsContentVersion //Assumes a Version guid exists and Version date (modified date) has been set/updated Database.Update(dto.ContentVersionDto); - //Updates the cmsMember entry - Database.Update(dto); + + //Updates the cmsMember entry if it has changed + var changedCols = new List(); + 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 //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 keyDictionary = new Dictionary(); //Add Properties @@ -333,8 +357,8 @@ namespace Umbraco.Core.Persistence.Repositories property.Id = keyDictionary[property.PropertyTypeId]; } } - - ((ICanBeDirty)entity).ResetDirtyProperties(); + + dirtyEntity.ResetDirtyProperties(); } protected override void PersistDeletedItem(IMember entity) @@ -416,9 +440,10 @@ namespace Umbraco.Core.Persistence.Repositories public bool Exists(string username) { var sql = new Sql(); + var escapedUserName = Database.EscapeAtSymbols(username); sql.Select("COUNT(*)") .From() - .Where(x => x.LoginName == username); + .Where(x => x.LoginName == escapedUserName); return Database.ExecuteScalar(sql) > 0; } diff --git a/src/Umbraco.Core/Services/IMemberService.cs b/src/Umbraco.Core/Services/IMemberService.cs index 703e68be71..ff758236a4 100644 --- a/src/Umbraco.Core/Services/IMemberService.cs +++ b/src/Umbraco.Core/Services/IMemberService.cs @@ -23,6 +23,8 @@ namespace Umbraco.Core.Services IEnumerable GetMembersByGroup(string memberGroupName); IEnumerable GetAllMembers(params int[] ids); + //TODO: Need to get all members that start with a certain letter + void DeleteMembersOfType(int memberTypeId); } @@ -51,7 +53,7 @@ namespace Umbraco.Core.Services void Delete(IMember membershipUser); - void Save(IMember membershipUser); + void Save(IMember membershipUser, bool raiseEvents = true); IEnumerable FindMembersByEmail(string emailStringToMatch); } diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index b76caadff2..803bfabd31 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -393,8 +393,15 @@ namespace Umbraco.Core.Services /// Saves an updated Member /// /// - public void Save(IMember member) + /// + public void Save(IMember member, bool raiseEvents = true) { + if (raiseEvents) + { + if (Saving.IsRaisedEventCancelled(new SaveEventArgs(member), this)) + return; + } + var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateMemberRepository(uow)) { @@ -404,6 +411,9 @@ namespace Umbraco.Core.Services var xml = member.ToXml(); CreateAndSaveMemberXml(xml, member.Id, uow.Database); } + + if (raiseEvents) + Saved.RaiseEvent(new SaveEventArgs(member, false), this); } #endregion @@ -483,14 +493,91 @@ namespace Umbraco.Core.Services int result = exists ? db.Update(poco) : Convert.ToInt32(db.Insert(poco)); } + #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 Save + /// + public static event TypedEventHandler> Saved; + + #endregion + + ///// + ///// 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) + // { + // Alias = Constants.Conventions.Member.Comments, + // Name = Constants.Conventions.Member.CommentsLabel, + // SortOrder = 0, + // Id = --identity + // }); + // propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer) + // { + // Alias = Constants.Conventions.Member.IsApproved, + // Name = Constants.Conventions.Member.IsApprovedLabel, + // SortOrder = 3, + // Id = --identity + // }); + // propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.TrueFalseAlias, DataTypeDatabaseType.Integer) + // { + // Alias = Constants.Conventions.Member.IsLockedOut, + // Name = Constants.Conventions.Member.IsLockedOutLabel, + // SortOrder = 4, + // Id = --identity + // }); + // propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date) + // { + // Alias = Constants.Conventions.Member.LastLockoutDate, + // Name = Constants.Conventions.Member.LastLockoutDateLabel, + // SortOrder = 5, + // Id = --identity + // }); + // propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date) + // { + // Alias = Constants.Conventions.Member.LastLoginDate, + // Name = Constants.Conventions.Member.LastLoginDateLabel, + // SortOrder = 6, + // Id = --identity + // }); + // propGroup.PropertyTypes.Add(new PropertyType(Constants.PropertyEditors.NoEditAlias, DataTypeDatabaseType.Date) + // { + // Alias = Constants.Conventions.Member.LastPasswordChangeDate, + // Name = Constants.Conventions.Member.LastPasswordChangeDateLabel, + // SortOrder = 7, + // Id = --identity + // }); + + // memType.PropertyGroups.Add(propGroup); + + // return new Member(name, email, username, password, -1, memType); + //} } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 86a871df11..b2253c8dd0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -221,6 +221,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.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); + 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.CreateSimpleMember(memberType, "Johnny Hefty", "johnny@example.com", "123", "hefty"); + 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] public void Can_Create_Correct_Subquery() { diff --git a/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs new file mode 100644 index 0000000000..43269731e7 --- /dev/null +++ b/src/Umbraco.Web/Strategies/Migrations/ClearMediaXmlCacheForDeletedItemsAfterUpgrade.cs @@ -0,0 +1,52 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.Migrations; +using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; +using umbraco.interfaces; + +namespace Umbraco.Web.Strategies.Migrations +{ + /// + /// This will execute after upgrading to remove any xml cache for media that are currently in the bin + /// + /// + /// This will execute for specific versions - + /// + /// * If current is less than or equal to 7.0.0 + /// + public class ClearMediaXmlCacheForDeletedItemsAfterUpgrade : IApplicationStartupHandler + { + public ClearMediaXmlCacheForDeletedItemsAfterUpgrade() + { + MigrationRunner.Migrated += MigrationRunner_Migrated; + } + + void MigrationRunner_Migrated(MigrationRunner sender, Core.Events.MigrationEventArgs e) + { + var target70 = new Version(7, 0, 0); + + if (e.ConfiguredVersion <= target70) + { + + var sql = @"DELETE a + FROM cmsContentXml a + INNER JOIN umbracoNode b + ON a.nodeId = b.id + WHERE nodeObjectType = 'B796F64C-1F99-4FFB-B886-4BF4BC011A9C' AND " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("path") + " like '%-21%'"; + +// var sql = @"DELETE FROM cmsContentXml WHERE nodeId IN +// (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml +// INNER JOIN umbracoNode ON cmsContentXml.nodeId = umbracoNode.id +// WHERE nodeObjectType = 'B796F64C-1F99-4FFB-B886-4BF4BC011A9C' AND " + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName("path") + " like '%-21%')"; + + var count = e.MigrationContext.Database.Execute(sql); + + LogHelper.Info("Cleared " + count + " items from the media xml cache that were trashed and not meant to be there"); + + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 3a260ca223..c1d56a5031 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -391,6 +391,7 @@ + @@ -421,10 +422,9 @@ AssignDomain2.aspx - + ASPXCodeBehind - ASPXCodeBehind