diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 5863d4d67c..6bfb23bb73 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -137,5 +137,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } diff --git a/public/less/generics.less b/public/less/generics.less index d2be83d01e..f94118b299 100644 --- a/public/less/generics.less +++ b/public/less/generics.less @@ -112,13 +112,15 @@ } .ban-modal { - input[type="number"] { - width: 7rem; - text-align: center; - margin-left: 1rem; - } - .form-inline, .form-group { width: 100%; } + + .units { + line-height: 5rem; + } +} + +.admin .ban-modal .units { + line-height: 1.846; } \ No newline at end of file diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index d570941940..45abb0e254 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -60,7 +60,7 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio bootbox.confirm('Do you really want to ban ' + (uids.length > 1 ? 'these users' : 'this user') + ' permanently?', function(confirm) { if (confirm) { - socket.emit('user.banUsers', { uids: uids }, done('User(s) banned!', '.ban', true)); + socket.emit('user.banUsers', { uids: uids, reason: '' }, done('User(s) banned!', '.ban', true)); } }); }); @@ -91,7 +91,7 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio return data; }, {}); var until = formData.length ? (Date.now() + formData.length * 1000*60*60 * (parseInt(formData.unit, 10) ? 24 : 1)) : 0; - socket.emit('user.banUsers', { uids: uids, until: until }, done('User(s) banned!', '.ban', true)); + socket.emit('user.banUsers', { uids: uids, until: until, reason: formData.reason }, done('User(s) banned!', '.ban', true)); } } } diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 97f062de11..2a6641b152 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -126,7 +126,7 @@ define('forum/account/header', [ }, {}); var until = formData.length ? (Date.now() + formData.length * 1000*60*60 * (parseInt(formData.unit, 10) ? 24 : 1)) : 0; - socket.emit('user.banUsers', { uids: [ajaxify.data.theirid], until: until }, function(err) { + socket.emit('user.banUsers', { uids: [ajaxify.data.theirid], until: until, reason: formData.reason || '' }, function(err) { if (err) { return app.alertError(err.message); } diff --git a/src/socket.io/user/ban.js b/src/socket.io/user/ban.js index 41c60411e0..80f701c1a2 100644 --- a/src/socket.io/user/ban.js +++ b/src/socket.io/user/ban.js @@ -12,12 +12,13 @@ module.exports = function(SocketUser) { if (Array.isArray(data)) { data = { uids: data, - until: 0 + until: 0, + reason: '' }; } toggleBan(socket.uid, data.uids, function(uid, next) { - banUser(data.until || 0, uid, next); + banUser(uid, data.until || 0, data.reason || '', next); }, function(err) { if (err) { return callback(err); @@ -68,7 +69,7 @@ module.exports = function(SocketUser) { ], callback); } - function banUser(until, uid, callback) { + function banUser(uid, until, reason, callback) { async.waterfall([ function (next) { user.isAdministrator(uid, next); @@ -77,7 +78,7 @@ module.exports = function(SocketUser) { if (isAdmin) { return next(new Error('[[error:cant-ban-other-admins]]')); } - user.ban(uid, until, next); + user.ban(uid, until, reason, next); }, function (next) { websockets.in('uid_' + uid).emit('event:banned'); diff --git a/src/user/admin.js b/src/user/admin.js index dd3134e442..2509028b55 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -53,13 +53,20 @@ module.exports = function(User) { ], callback); }; - User.ban = function(uid, until, callback) { + User.ban = function(uid, until, reason, callback) { // "until" (optional) is unix timestamp in milliseconds + // "reason" (optional) is a string if (!callback && typeof until === 'function') { callback = until; until = 0; + reason = ''; + } else if (!callback && typeof reason === 'function') { + callback = reason; + reason = ''; } + var now = Date.now(); + until = parseInt(until, 10); if (isNaN(until)) { return callback(new Error('[[error:ban-expiry-missing]]')); @@ -67,17 +74,21 @@ module.exports = function(User) { var tasks = [ async.apply(User.setUserField, uid, 'banned', 1), - async.apply(db.sortedSetAdd, 'users:banned', Date.now(), uid), - async.apply(db.sortedSetAdd, 'uid:' + uid + ':bans', Date.now(), until) + async.apply(db.sortedSetAdd, 'users:banned', now, uid), + async.apply(db.sortedSetAdd, 'uid:' + uid + ':bans', now, until) ]; - if (until > 0 && Date.now() < until) { + if (until > 0 && now < until) { tasks.push(async.apply(db.sortedSetAdd, 'users:banned:expire', until, uid)); tasks.push(async.apply(User.setUserField, uid, 'banned:expire', until)); } else { until = 0; } + if (reason) { + tasks.push(async.apply(db.sortedSetAdd, 'banned:' + uid + ':reasons', now, reason)); + } + async.series(tasks, function (err) { if (err) { return callback(err); diff --git a/src/user/info.js b/src/user/info.js index da85e25116..fdd401dcd8 100644 --- a/src/user/info.js +++ b/src/user/info.js @@ -13,7 +13,8 @@ module.exports = function(User) { function(next) { async.parallel({ flags: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':flag:pids', 0, 19), - bans: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':bans', 0,19) + bans: async.apply(db.getSortedSetRevRangeWithScores, 'uid:' + uid + ':bans', 0, 19), + reasons: async.apply(db.getSortedSetRevRangeWithScores, 'banned:' + uid + ':reasons', 0, 19) }, next); }, function(data, next) { @@ -64,15 +65,22 @@ module.exports = function(User) { } function formatBanData(data) { + var reasons = data.reasons.reduce(function(memo, cur) { + memo[cur.score] = cur.value; + return memo; + }, {}); + data.bans = data.bans.map(function(banObj) { banObj.until = parseInt(banObj.value, 10); banObj.untilReadable = new Date(banObj.until).toString(); banObj.timestamp = parseInt(banObj.score, 10); banObj.timestampReadable = new Date(banObj.score).toString(); banObj.timestampISO = new Date(banObj.score).toISOString(); + banObj.reason = reasons[banObj.score] || '[[user:info.banned-no-reason]]'; delete banObj.value; delete banObj.score; + delete data.reasons; return banObj; }); diff --git a/src/views/admin/partials/temporary-ban.tpl b/src/views/admin/partials/temporary-ban.tpl index a3f032a3b6..da8d02c79f 100644 --- a/src/views/admin/partials/temporary-ban.tpl +++ b/src/views/admin/partials/temporary-ban.tpl @@ -1,13 +1,32 @@ -