diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index 72564398c0..0fd308ffd0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -280,7 +280,7 @@ * }); * * - * @param {Array} id user id. + * @param {Int} userId user id. * @returns {Promise} resourcePromise object containing the user. * */ @@ -406,6 +406,36 @@ "Failed to save user"); } + /** + * @ngdoc method + * @name umbraco.resources.usersResource#deleteNonLoggedInUser + * @methodOf umbraco.resources.usersResource + * + * @description + * Deletes a user that hasn't already logged in (and hence we know has made no content updates that would create related records) + * + * ##usage + *
+          * usersResource.deleteNonLoggedInUser(1)
+          *    .then(function() {
+          *        alert("user was deleted");
+          *    });
+          * 
+ * + * @param {Int} userId user id. + * @returns {Promise} resourcePromise object. + * + */ + function deleteNonLoggedInUser(userId) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostDeleteNonLoggedInUser", { id: userId })), + 'Failed to delete the user ' + userId); + } + var resource = { disableUsers: disableUsers, @@ -417,6 +447,7 @@ createUser: createUser, inviteUser: inviteUser, saveUser: saveUser, + deleteNonLoggedInUser: deleteNonLoggedInUser, clearAvatar: clearAvatar }; diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js index 0f3519c7ba..a1b30e38b7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.controller.js @@ -31,6 +31,8 @@ vm.disableUser = disableUser; vm.enableUser = enableUser; vm.unlockUser = unlockUser; + vm.resendInvite = resendInvite; + vm.deleteNonLoggedInUser = deleteNonLoggedInUser; vm.changeAvatar = changeAvatar; vm.clearAvatar = clearAvatar; vm.save = save; @@ -49,7 +51,9 @@ "sections_users", "content_contentRoot", "media_mediaRoot", - "user_noStartNodes" + "user_noStartNodes", + "user_defaultInvitationMessage", + "user_deleteUserConfirmation" ]; localizationService.localizeMany(labelKeys).then(function (values) { @@ -61,6 +65,8 @@ vm.labels.contentRoot = values[5]; vm.labels.mediaRoot = values[6]; vm.labels.noStartNodes = values[7]; + vm.labels.defaultInvitationMessage = values[8]; + vm.labels.deleteUserConfirmation = values[9]; }); // get user @@ -350,6 +356,44 @@ }); } + function resendInvite() { + vm.resendInviteButtonState = "busy"; + + if (vm.resendInviteMessage) { + vm.user.message = vm.resendInviteMessage; + } + else { + vm.user.message = vm.labels.defaultInvitationMessage; + } + + usersResource.inviteUser(vm.user).then(function (data) { + vm.resendInviteButtonState = "success"; + vm.resendInviteMessage = ""; + formHelper.showNotifications(data); + }, function (error) { + vm.resendInviteButtonState = "error"; + formHelper.showNotifications(error.data); + }); + } + + function deleteNonLoggedInUser() { + vm.deleteNotLoggedInUserButtonState = "busy"; + + var confirmationMessage = vm.labels.deleteUserConfirmation; + if (!confirm(confirmationMessage)) { + vm.deleteNotLoggedInUserButtonState = "danger"; + return; + } + + usersResource.deleteNonLoggedInUser(vm.user.id).then(function (data) { + formHelper.showNotifications(data); + goToPage(vm.breadcrumbs[0]); + }, function (error) { + vm.deleteNotLoggedInUserButtonState = "error"; + formHelper.showNotifications(error.data); + }); + } + function clearAvatar() { // get user usersResource.clearAvatar(vm.user.id).then(function (data) { diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index 9f161f0968..7c93f1fd84 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -241,7 +241,7 @@
-
- + + + +
+ + + +
+
Last login: diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 98fdc57521..e2a2f776a0 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -46,6 +46,7 @@ Set permissions Unlock Create Content Template + Resend Invitation Content @@ -1441,6 +1442,9 @@ To manage your website, simply open the Umbraco back office and start adding con An error occurred while unlocking the user Member was exported to file An error occurred while exporting the member + User %0% was deleted + Invite user + Invitation has been re-sent to %0% Uses CSS syntax ex: h1, .redHeader, .blueTex @@ -2021,6 +2025,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]> Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index b77ae6b5b7..4baecaa7c9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -46,6 +46,7 @@ Set permissions Unlock Create Content Template + Resend Invitation Content @@ -1439,6 +1440,9 @@ To manage your website, simply open the Umbraco back office and start adding con An error occurred while unlocking the user Member was exported to file An error occurred while exporting the member + User %0% was deleted + Invite user + Invitation has been re-sent to %0% Uses CSS syntax ex: h1, .redHeader, .blueTex @@ -2014,6 +2018,9 @@ To manage your website, simply open the Umbraco back office and start adding con ]]> Invite + Resending invitation... + Delete User + Are you sure you wish to delete this user account? Validation diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index f1670df346..f089c44afd 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -412,6 +412,8 @@ namespace Umbraco.Web.Editors await SendUserInviteEmailAsync(display, Security.CurrentUser.Name, Security.CurrentUser.Email, user, userSave.Message); + display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/resendInviteHeader"), Services.TextService.Localize("speechBubbles/resendInviteSuccess", new[] { user.Name })); + return display; } @@ -699,6 +701,36 @@ namespace Umbraco.Web.Editors Services.TextService.Localize("speechBubbles/setUserGroupOnUsersSuccess")); } + /// + /// Deletes the non-logged in user provided id + /// + /// User Id + /// + /// Limited to users that haven't logged in to avoid issues with related records constrained + /// with a foreign key on the user Id + /// + public async Task PostDeleteNonLoggedInUser(int id) + { + var user = Services.UserService.GetUserById(id); + if (user == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + + // Check user hasn't logged in. If they have they may have made content changes which will mean + // the Id is associated with audit trails, versions etc. and can't be removed. + if (user.LastLoginDate != default(DateTime)) + { + throw new HttpResponseException(HttpStatusCode.BadRequest); + } + + var userName = user.Name; + Services.UserService.Delete(user, true); + + return Request.CreateNotificationSuccessResponse( + Services.TextService.Localize("speechBubbles/deleteUserSuccess", new[] { userName })); + } + public class PagedUserResult : PagedResult { public PagedUserResult(long totalItems, long pageNumber, long pageSize) : base(totalItems, pageNumber, pageSize)