diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs index 4894be7b87..6636259c92 100644 --- a/src/Umbraco.Core/Models/UserExtensions.cs +++ b/src/Umbraco.Core/Models/UserExtensions.cs @@ -1,6 +1,9 @@ using System; using System.Globalization; using System.Linq; +using System.Net; +using Umbraco.Core.Cache; +using Umbraco.Core.IO; using Umbraco.Core.Models.Membership; using Umbraco.Core.Services; @@ -8,6 +11,69 @@ namespace Umbraco.Core.Models { public static class UserExtensions { + /// + /// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL + /// + /// + /// + /// + /// + /// A list of 5 different sized avatar URLs + /// + internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, ICacheProvider staticCache) + { + if (user.Avatar.IsNullOrWhiteSpace()) + { + var gravatarHash = user.Email.ToMd5(); + var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash; + + //try gravatar + var gravatarAccess = staticCache.GetCacheItem("UserAvatar" + user.Id, () => + { + // Test if we can reach this URL, will fail when there's network or firewall errors + var request = (HttpWebRequest)WebRequest.Create(gravatarUrl); + // Require response within 10 seconds + request.Timeout = 10000; + try + { + using ((HttpWebResponse)request.GetResponse()) { } + } + catch (Exception) + { + // There was an HTTP or other error, return an null instead + return false; + } + return true; + }); + + if (gravatarAccess) + { + return new[] + { + gravatarUrl + "?s=30&d=mm", + gravatarUrl + "?s=60&d=mm", + gravatarUrl + "?s=90&d=mm", + gravatarUrl + "?s=150&d=mm", + gravatarUrl + "?s=300&d=mm" + }; + } + + return null; + } + + //use the custom avatar + var avatarUrl = FileSystemProviderManager.Current.MediaFileSystem.GetUrl(user.Avatar); + return new[] + { + avatarUrl + "?width=30&height=30&mode=crop", + avatarUrl + "?width=60&height=60&mode=crop", + avatarUrl + "?width=90&height=90&mode=crop", + avatarUrl + "?width=150&height=150&mode=crop", + avatarUrl + "?width=300&height=300&mode=crop" + }; + + } + /// /// Returns the culture info associated with this user, based on the language they're assigned to in the back office /// diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index 4f3fb1b6e7..296985f084 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -269,7 +269,8 @@ namespace Umbraco.Core.Persistence.Repositories {"lastLoginDate", "LastLoginDate"}, {"failedLoginAttempts", "FailedPasswordAttempts"}, {"createDate", "CreateDate"}, - {"updateDate", "UpdateDate"} + {"updateDate", "UpdateDate"}, + {"avatar", "Avatar"} }; //create list of properties that have changed 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 c70729246f..c08fbb0cdf 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 @@ -11,6 +11,17 @@ function usersResource($http, umbRequestHelper, $q, umbDataFormatter) { + function clearAvatar(userId) { + + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostClearAvatar", + { id: userId })), + 'Failed to clear the user avatar ' + userId); + } + function disableUsers(userIds) { if (!userIds) { throw "userIds not specified"; @@ -188,7 +199,8 @@ createUser: createUser, saveUser: saveUser, getUserGroup: getUserGroup, - getUserGroups: getUserGroups + getUserGroups: getUserGroups, + clearAvatar: clearAvatar }; return resource; diff --git a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js index 1504abf7c1..f17fefcac8 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/main.controller.js @@ -60,7 +60,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ $scope.authenticated = data.authenticated; $scope.user = data.user; - updateChecker.check().then(function(update) { + updateChecker.check().then(function (update) { if (update && update !== "null") { if (update.type !== "None") { var notification = { @@ -87,7 +87,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ } //if this is a new login (i.e. the user entered credentials), then clear out local storage - could contain sensitive data - if (data.loginType === "credentials") { + if (data.loginType === "credentials") { localStorageService.clearAll(); } @@ -96,30 +96,15 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ tmhDynamicLocale.set($scope.user.locale); } - if ($scope.user.emailHash) { + if ($scope.user.avatars) { - //let's attempt to load the avatar, it might not exist or we might not have - // internet access, well get an empty string back - $http.get(umbRequestHelper.getApiUrl("gravatarApiBaseUrl", "GetCurrentUserGravatarUrl")) - .then( - function successCallback(response) { - // if we can't download the gravatar for some reason, an null gets returned, we cannot do anything - if (response.data !== "null") { - if ($scope.user && $scope.user.emailHash) { - var avatarBaseUrl = "https://www.gravatar.com/avatar/"; - var hash = $scope.user.emailHash; + $scope.avatar = []; + if (angular.isArray($scope.user.avatars)) { + for (var i = 0; i < $scope.user.avatars.length; i++) { + $scope.avatar.push({ value: $scope.user.avatars[i] }); + } + } - $scope.avatar = [ - { value: avatarBaseUrl + hash + ".jpg?s=30&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=60&d=mm" }, - { value: avatarBaseUrl + hash + ".jpg?s=90&d=mm" } - ]; - } - } - - }, function errorCallback(response) { - //cannot load it from the server so we cannot do anything - }); } })); @@ -143,7 +128,7 @@ function MainController($scope, $rootScope, $location, $routeParams, $timeout, $ //register it angular.module('umbraco').controller("Umbraco.MainController", MainController). - config(function (tmhDynamicLocaleProvider) { - //Set url for locale files - tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); - }); + config(function (tmhDynamicLocaleProvider) { + //Set url for locale files + tmhDynamicLocaleProvider.localeLocationPattern('lib/angular/1.1.5/i18n/angular-locale_{{locale}}.js'); + }); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index 9ce2506e76..b540df524c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -96,7 +96,7 @@ } } else if (evt.Message) { - file.serverErrorMessage = evt.Message; + vm.zipFile.serverErrorMessage = evt.Message; } } }); 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 c69fe3ad6e..888b14b93f 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 @@ -1,7 +1,7 @@ (function () { "use strict"; - function UserEditController($scope, $timeout, $location, $routeParams, usersResource, contentEditingHelper, localizationService, notificationsService) { + function UserEditController($scope, $timeout, $location, $routeParams, usersResource, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper) { var vm = this; var localizeSaving = localizationService.localize("general_saving"); @@ -9,6 +9,7 @@ vm.page = {}; vm.user = {}; vm.breadcrumbs = []; + vm.avatarFile = {}; vm.goToPage = goToPage; vm.openUserGroupPicker = openUserGroupPicker; @@ -18,8 +19,10 @@ vm.disableUser = disableUser; vm.resetPassword = resetPassword; vm.getUserStateType = getUserStateType; - vm.changeAvatar = changeAvatar; + vm.clearAvatar = clearAvatar; vm.save = save; + vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB" + vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); function init() { @@ -31,7 +34,7 @@ makeBreadcrumbs(vm.user); vm.loading = false; }); - + } function save() { @@ -47,7 +50,7 @@ // when server side validation fails - as opposed to content where we are capable of saving the content // item if server side validation fails redirectOnFailure: false, - rebindCallback: function (orignal, saved) {} + rebindCallback: function (orignal, saved) { } }).then(function (saved) { vm.user = saved; @@ -72,17 +75,17 @@ selection: vm.user.userGroups, closeButtonLabel: "Cancel", show: true, - submit: function(model) { + submit: function (model) { // apply changes - if(model.selection) { + if (model.selection) { vm.user.userGroups = model.selection; } vm.userGroupPicker.show = false; vm.userGroupPicker = null; }, - close: function(oldModel) { + close: function (oldModel) { // rollback on close - if(oldModel.selection) { + if (oldModel.selection) { vm.user.userGroups = oldModel.selection; } vm.userGroupPicker.show = false; @@ -97,14 +100,14 @@ view: "contentpicker", multiPicker: true, show: true, - submit: function(model) { - if(model.selection) { + submit: function (model) { + if (model.selection) { vm.user.startNodesContent = model.selection; } vm.contentPicker.show = false; vm.contentPicker = null; }, - close: function(oldModel) { + close: function (oldModel) { vm.contentPicker.show = false; vm.contentPicker = null; } @@ -120,14 +123,14 @@ entityType: "media", multiPicker: true, show: true, - submit: function(model) { - if(model.selection) { + submit: function (model) { + if (model.selection) { vm.user.startNodesMedia = model.selection; } vm.contentPicker.show = false; vm.contentPicker = null; }, - close: function(oldModel) { + close: function (oldModel) { vm.contentPicker.show = false; vm.contentPicker = null; } @@ -144,6 +147,13 @@ function resetPassword() { alert("reset password"); + } + + function clearAvatar() { + // get user + usersResource.clearAvatar(vm.user.id).then(function (data) { + vm.user.avatars = data; + }); } function getUserStateType(state) { @@ -157,10 +167,63 @@ } } - function changeAvatar() { - alert("change avatar"); + $scope.changeAvatar = function (files, event) { + if (files && files.length > 0) { + upload(files[0]); + } + }; + + function upload(file) { + + Upload.upload({ + url: umbRequestHelper.getApiUrl("userApiBaseUrl", "PostSetAvatar", { id: vm.user.id }), + fields: {}, + file: file + }).progress(function (evt) { + + //TODO: Do progress, etc... + // set uploading status on file + vm.avatarFile.uploadStatus = "uploading"; + + }).success(function (data, status, headers, config) { + + // set done status on file + vm.avatarFile.uploadStatus = "done"; + + vm.user.avatars = data; + + }).error(function (evt, status, headers, config) { + + // set status done + vm.avatarFile.uploadStatus = "error"; + + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.avatarFile.serverErrorMessage = "File not found"; + } + else if (status == 400) { + //it's a validation error + vm.avatarFile.serverErrorMessage = evt.message; + } + else { + //it's an unhandled error + //if the service returns a detailed error + if (evt.InnerException) { + vm.avatarFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + vm.avatarFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + vm.avatarFile.serverErrorMessage = evt.Message; + } + } + }); } + function makeBreadcrumbs() { vm.breadcrumbs = [ { @@ -173,7 +236,7 @@ } ]; } - + init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/users/user.html b/src/Umbraco.Web.UI.Client/src/views/users/user.html index a703a45139..0c6ded037a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/user.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/user.html @@ -119,18 +119,30 @@
- + + - - - + img-src="{{vm.user.avatars[4]}}"> + + + Change avatar +
+ + Clear avatar + +