diff --git a/app/ui-flextab/client/tabs/userEdit.js b/app/ui-flextab/client/tabs/userEdit.js
index 2e7e4afa08da..8b097c3ddbcc 100644
--- a/app/ui-flextab/client/tabs/userEdit.js
+++ b/app/ui-flextab/client/tabs/userEdit.js
@@ -5,8 +5,10 @@ import { Template } from 'meteor/templating';
import { TAPi18n } from 'meteor/tap:i18n';
import { t, handleError } from '../../../utils';
import { Roles } from '../../../models';
+import { Notifications } from '../../../notifications';
import { hasAtLeastOnePermission } from '../../../authorization';
import toastr from 'toastr';
+import { callbacks } from '../../../callbacks';
import s from 'underscore.string';
Template.userEdit.helpers({
@@ -18,10 +20,23 @@ Template.userEdit.helpers({
return (Template.instance().user && hasAtLeastOnePermission('edit-other-user-info')) || (!Template.instance().user && hasAtLeastOnePermission('create-user'));
},
+ selectUrl() {
+ return Template.instance().url.get().trim() ? '' : 'disabled';
+ },
+
user() {
return Template.instance().user;
},
+ initialsUsername() {
+ const { user } = Template.instance();
+ return `@${ user && user.username }`;
+ },
+
+ avatarPreview() {
+ return Template.instance().avatar.get();
+ },
+
requirePasswordChange() {
return !Template.instance().user || Template.instance().user.requirePasswordChange;
},
@@ -41,6 +56,54 @@ Template.userEdit.helpers({
});
Template.userEdit.events({
+ 'click .js-select-avatar-initials'(e, template) {
+ template.avatar.set({
+ service: 'initials',
+ blob: `@${ template.user.username }`,
+ });
+ },
+
+ 'click .js-select-avatar-url'(e, template) {
+ const url = template.url.get().trim();
+ if (!url) {
+ return;
+ }
+
+ template.avatar.set({
+ service: 'url',
+ contentType: '',
+ blob: url,
+ });
+ },
+
+ 'input .js-avatar-url-input'(e, template) {
+ const text = e.target.value;
+ template.url.set(text);
+ },
+
+ 'change .js-select-avatar-upload [type=file]'(event, template) {
+ const e = event.originalEvent || event;
+ let { files } = e.target;
+ if (!files || files.length === 0) {
+ files = (e.dataTransfer && e.dataTransfer.files) || [];
+ }
+ Object.keys(files).forEach((key) => {
+ const blob = files[key];
+ if (!/image\/.+/.test(blob.type)) {
+ return;
+ }
+ const reader = new FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = function() {
+ template.avatar.set({
+ service: 'upload',
+ contentType: blob.type,
+ blob: reader.result,
+ });
+ };
+ });
+ },
+
'click .cancel'(e, t) {
e.stopPropagation();
e.preventDefault();
@@ -98,7 +161,9 @@ Template.userEdit.events({
Template.userEdit.onCreated(function() {
this.user = this.data != null ? this.data.user : undefined;
this.roles = this.user ? new ReactiveVar(this.user.roles) : new ReactiveVar([]);
-
+ this.avatar = new ReactiveVar;
+ this.url = new ReactiveVar('');
+ Notifications.onLogged('updateAvatar', () => this.avatar.set());
const { tabBar } = Template.currentData();
@@ -162,7 +227,6 @@ Template.userEdit.onCreated(function() {
return;
}
const userData = this.getUserData();
-
if (this.user != null) {
for (const key in userData) {
if (key) {
@@ -176,6 +240,28 @@ Template.userEdit.onCreated(function() {
}
}
+ const avatar = this.avatar.get();
+ if (avatar) {
+ let method;
+ const params = [];
+
+ if (avatar.service === 'initials') {
+ method = 'resetAvatar';
+ } else {
+ method = 'setAvatarFromService';
+ params.push(avatar.blob, avatar.contentType, avatar.service);
+ }
+
+ Meteor.call(method, ...params, Template.instance().user._id, function(err) {
+ if (err && err.details) {
+ toastr.error(t(err.message));
+ } else {
+ toastr.success(t('Avatar_changed_successfully'));
+ callbacks.run('userAvatarSet', avatar.service);
+ }
+ });
+ }
+
Meteor.call('insertOrUpdateUser', userData, (error) => {
if (error) {
return handleError(error);
diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json
index 6a8c2027ec50..d33628376c3f 100644
--- a/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -1089,6 +1089,8 @@
"edit-message_description": "Permission to edit a message within a room",
"edit-other-user-active-status": "Edit Other User Active Status",
"edit-other-user-active-status_description": "Permission to enable or disable other accounts",
+ "edit-other-user-avatar": "Edit Other User Avatar",
+ "edit-other-user-avatar_description": "Permission to change other user's avatar.",
"edit-other-user-info": "Edit Other User Information",
"edit-other-user-info_description": "Permission to change other user's name, username or email address.",
"edit-other-user-password": "Edit Other User Password",
diff --git a/server/methods/resetAvatar.js b/server/methods/resetAvatar.js
index 06dc8160c442..77bea775507a 100644
--- a/server/methods/resetAvatar.js
+++ b/server/methods/resetAvatar.js
@@ -1,12 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { FileUpload } from '../../app/file-upload';
-import { Users } from '../../app/models';
+import { Users } from '../../app/models/server';
import { settings } from '../../app/settings';
import { Notifications } from '../../app/notifications';
+import { hasPermission } from '../../app/authorization/server';
Meteor.methods({
- resetAvatar() {
+ resetAvatar(userId) {
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'resetAvatar',
@@ -19,7 +20,26 @@ Meteor.methods({
});
}
- const user = Meteor.user();
+ let user;
+
+ if (userId && userId !== Meteor.userId()) {
+ if (!hasPermission(Meteor.userId(), 'edit-other-user-avatar')) {
+ throw new Meteor.Error('error-unauthorized', 'Unauthorized', {
+ method: 'resetAvatar',
+ });
+ }
+
+ user = Users.findOneById(userId, { fields: { _id: 1, username: 1 } });
+ } else {
+ user = Meteor.user();
+ }
+
+ if (user == null) {
+ throw new Meteor.Error('error-invalid-desired-user', 'Invalid desired user', {
+ method: 'resetAvatar',
+ });
+ }
+
FileUpload.getStore('Avatars').deleteByName(user.username);
Users.unsetAvatarOrigin(user._id);
Notifications.notifyLogged('updateAvatar', {
diff --git a/server/methods/setAvatarFromService.js b/server/methods/setAvatarFromService.js
index dc8ce21cd727..c93cd2c2d4a5 100644
--- a/server/methods/setAvatarFromService.js
+++ b/server/methods/setAvatarFromService.js
@@ -3,12 +3,15 @@ import { Match, check } from 'meteor/check';
import { DDPRateLimiter } from 'meteor/ddp-rate-limiter';
import { settings } from '../../app/settings';
import { setUserAvatar } from '../../app/lib';
+import { Users } from '../../app/models/server';
+import { hasPermission } from '../../app/authorization/server';
Meteor.methods({
- setAvatarFromService(dataURI, contentType, service) {
+ setAvatarFromService(dataURI, contentType, service, userId) {
check(dataURI, String);
check(contentType, Match.Optional(String));
check(service, Match.Optional(String));
+ check(userId, Match.Optional(String));
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
@@ -22,7 +25,25 @@ Meteor.methods({
});
}
- const user = Meteor.user();
+ let user;
+
+ if (userId && userId !== Meteor.userId()) {
+ if (!hasPermission(Meteor.userId(), 'edit-other-user-avatar')) {
+ throw new Meteor.Error('error-unauthorized', 'Unauthorized', {
+ method: 'setAvatarFromService',
+ });
+ }
+
+ user = Users.findOneById(userId, { fields: { _id: 1, username: 1 } });
+ } else {
+ user = Meteor.user();
+ }
+
+ if (user == null) {
+ throw new Meteor.Error('error-invalid-desired-user', 'Invalid desired user', {
+ method: 'setAvatarFromService',
+ });
+ }
return setUserAvatar(user, dataURI, contentType, service);
},