diff --git a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs index c01f1653dd..aa8eb7187f 100644 --- a/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs +++ b/src/Umbraco.Core/Models/Identity/IdentityModelMappings.cs @@ -15,6 +15,7 @@ namespace Umbraco.Core.Models.Identity config.CreateMap() .ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime())) .ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email)) + .ForMember(user => user.EmailConfirmed, expression => expression.MapFrom(user => user.EmailConfirmedDate.HasValue)) .ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id)) .ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null)) .ForMember(user => user.UserName, expression => expression.MapFrom(user => user.Username)) diff --git a/src/Umbraco.Core/Models/Membership/IUser.cs b/src/Umbraco.Core/Models/Membership/IUser.cs index 3e45166d40..0363791741 100644 --- a/src/Umbraco.Core/Models/Membership/IUser.cs +++ b/src/Umbraco.Core/Models/Membership/IUser.cs @@ -19,6 +19,7 @@ namespace Umbraco.Core.Models.Membership string Language { get; set; } DateTime? EmailConfirmedDate { get; set; } + DateTime? InvitedDate { get; set; } /// /// Gets the groups that user is part of diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 8014da9b05..2047d4346a 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -104,6 +104,7 @@ namespace Umbraco.Core.Models.Membership private string _username; private DateTime? _emailConfirmedDate; + private DateTime? _invitedDate; private string _email; private string _rawPasswordValue; private IEnumerable _allowedSections; @@ -140,6 +141,7 @@ namespace Umbraco.Core.Models.Membership public readonly PropertyInfo IsApprovedSelector = ExpressionHelper.GetPropertyInfo(x => x.IsApproved); public readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); public readonly PropertyInfo EmailConfirmedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.EmailConfirmedDate); + public readonly PropertyInfo InvitedDateSelector = ExpressionHelper.GetPropertyInfo(x => x.InvitedDate); public readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); @@ -168,6 +170,12 @@ namespace Umbraco.Core.Models.Membership set { SetPropertyValueAndDetectChanges(value, ref _emailConfirmedDate, Ps.Value.EmailConfirmedDateSelector); } } [DataMember] + public DateTime? InvitedDate + { + get { return _invitedDate; } + set { SetPropertyValueAndDetectChanges(value, ref _invitedDate, Ps.Value.InvitedDateSelector); } + } + [DataMember] public string Username { get { return _username; } diff --git a/src/Umbraco.Core/Models/Membership/UserState.cs b/src/Umbraco.Core/Models/Membership/UserState.cs index 23a8f2a2a4..3dd48d512d 100644 --- a/src/Umbraco.Core/Models/Membership/UserState.cs +++ b/src/Umbraco.Core/Models/Membership/UserState.cs @@ -5,9 +5,10 @@ /// public enum UserState { - Active = 0, - Disabled = 1, - LockedOut = 2, - Invited = 3 + All = 0, + Active = 1, + Disabled = 2, + LockedOut = 3, + Invited = 4 } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Rdbms/UserDto.cs b/src/Umbraco.Core/Models/Rdbms/UserDto.cs index a55eece583..41620cccaf 100644 --- a/src/Umbraco.Core/Models/Rdbms/UserDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/UserDto.cs @@ -74,6 +74,10 @@ namespace Umbraco.Core.Models.Rdbms [NullSetting(NullSetting = NullSettings.Null)] public DateTime? EmailConfirmedDate { get; set; } + [Column("invitedDate")] + [NullSetting(NullSetting = NullSettings.Null)] + public DateTime? InvitedDate { get; set; } + [Column("createDate")] [NullSetting(NullSetting = NullSettings.NotNull)] [Constraint(Default = SystemMethods.CurrentDateTime)] diff --git a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs index 40a7dbe579..e4e0d939f7 100644 --- a/src/Umbraco.Core/Persistence/Factories/UserFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/UserFactory.cs @@ -34,6 +34,8 @@ namespace Umbraco.Core.Persistence.Factories user.CreateDate = dto.CreateDate; user.UpdateDate = dto.UpdateDate; user.Avatar = dto.Avatar; + user.EmailConfirmedDate = dto.EmailConfirmedDate; + user.InvitedDate = dto.InvitedDate; //on initial construction we don't want to have dirty properties tracked // http://issues.umbraco.org/issue/U4-1946 @@ -66,7 +68,8 @@ namespace Umbraco.Core.Persistence.Factories CreateDate = entity.CreateDate, UpdateDate = entity.UpdateDate, Avatar = entity.Avatar, - EmailConfirmedDate = entity.EmailConfirmedDate + EmailConfirmedDate = entity.EmailConfirmedDate, + InvitedDate = entity.InvitedDate }; foreach (var startNodeId in entity.StartContentIds) diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs index b91080bf2e..74238de4ca 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSevenZero/UpdateUserTables.cs @@ -25,6 +25,9 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSevenZe if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("emailConfirmedDate")) == false) Create.Column("emailConfirmedDate").OnTable("umbracoUser").AsDateTime().Nullable(); + + if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("invitedDate")) == false) + Create.Column("invitedDate").OnTable("umbracoUser").AsDateTime().Nullable(); } public override void Down() diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs index 63a6900634..f6fd38f70f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IUserRepository.cs @@ -56,6 +56,7 @@ namespace Umbraco.Core.Persistence.Repositories IEnumerable GetPagedResultsByQuery(IQuery query, long pageIndex, int pageSize, out long totalRecords, Expression> orderBy, Direction orderDirection, string[] userGroups = null, UserState? userState = null, IQuery filter = null); IProfile GetProfile(string username); - IProfile GetProfile(int id); + IProfile GetProfile(int id); + IDictionary GetUserStates(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 9f2b915265..4f668671a6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -75,7 +75,28 @@ namespace Umbraco.Core.Persistence.Repositories return null; return new UserProfile(dto.Id, dto.UserName); - } + } + + public IDictionary GetUserStates() + { + var sql = @"SELECT +(SELECT COUNT(id) FROM umbracoUser) AS CountOfAll, +(SELECT COUNT(id) FROM umbracoUser WHERE userDisabled = 0 AND userNoConsole = 0 AND lastLoginDate IS NOT NULL) AS CountOfActive, +(SELECT COUNT(id) FROM umbracoUser WHERE userDisabled = 1) AS CountOfDisabled, +(SELECT COUNT(id) FROM umbracoUser WHERE userNoConsole = 1) AS CountOfLockedOut, +(SELECT COUNT(id) FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL) AS CountOfInvited"; + + var result = Database.First(sql); + + return new Dictionary + { + {UserState.All, result.CountOfAll}, + {UserState.Active, result.CountOfActive}, + {UserState.Disabled, result.CountOfDisabled}, + {UserState.Invited, result.CountOfInvited}, + {UserState.LockedOut, result.CountOfLockedOut} + }; + } protected override IEnumerable PerformGetAll(params int[] ids) { @@ -271,7 +292,8 @@ namespace Umbraco.Core.Persistence.Repositories {"createDate", "CreateDate"}, {"updateDate", "UpdateDate"}, {"avatar", "Avatar"}, - {"emailConfirmedDate", "EmailConfirmedDate"} + {"emailConfirmedDate", "EmailConfirmedDate"}, + {"invitedDate", "InvitedDate"} }; //create list of properties that have changed diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index 6b798862b0..3903cc5096 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -10,6 +10,23 @@ namespace Umbraco.Core.Services /// public interface IUserService : IMembershipUserService { + /// + /// This is basically facets of UserStates key = state, value = count + /// + IDictionary GetUserStates(); + + /// + /// Get paged users + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState? userState = null, string[] userGroups = null, string filter = ""); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index f589916c3b..2cee874926 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -487,7 +487,16 @@ namespace Umbraco.Core.Services return ret; } - } + } + + public IDictionary GetUserStates() + { + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateUserRepository(uow); + return repository.GetUserStates(); + } + } public IEnumerable GetAll(long pageIndex, int pageSize, out long totalRecords, string orderBy, Direction orderDirection, UserState? userState = null, string[] userGroups = null, string filter = "") { diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index 45a0325df3..5f1229ee37 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -392,6 +392,10 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } + + //They've successfully set their password, we can now update their user account to be approved + Security.CurrentUser.IsApproved = true; + Services.UserService.Save(Security.CurrentUser); return Request.CreateResponse(HttpStatusCode.OK); } diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 367bc68653..b44c6d9d9b 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Runtime.Serialization; using System.Threading.Tasks; using System.Web; using System.Web.Http; @@ -196,7 +197,7 @@ namespace Umbraco.Web.Editors /// /// /// - public PagedResult GetPagedUsers( + public PagedUserResult GetPagedUsers( int pageNumber = 1, int pageSize = 10, string orderBy = "username", @@ -210,13 +211,16 @@ namespace Umbraco.Web.Editors if (total == 0) { - return new PagedResult(0, 0, 0); + return new PagedUserResult(0, 0, 0); } - return new PagedResult(total, pageNumber, pageSize) + var paged = new PagedUserResult(total, pageNumber, pageSize) { - Items = Mapper.Map>(result) + Items = Mapper.Map>(result), + UserStates = Services.UserService.GetUserStates() }; + + return paged; } /// @@ -325,7 +329,10 @@ namespace Umbraco.Web.Editors //map the save info over onto the user user = Mapper.Map(userSave, user); - //Save the user first + //ensure the invited date is set + user.InvitedDate = DateTime.Now; + + //Save the updated user Services.UserService.Save(user); var display = Mapper.Map(user); @@ -490,5 +497,19 @@ namespace Umbraco.Web.Editors return true; } + + public class PagedUserResult : PagedResult + { + public PagedUserResult(long totalItems, long pageNumber, long pageSize) : base(totalItems, pageNumber, pageSize) + { + UserStates = new Dictionary(); + } + + /// + /// This is basically facets of UserStates key = state, value = count + /// + [DataMember(Name = "userStates")] + public IDictionary UserStates { get; set; } + } } } \ No newline at end of file