diff --git a/install/package.json b/install/package.json
index ef5caf1c55..42f4dd4303 100644
--- a/install/package.json
+++ b/install/package.json
@@ -25,6 +25,7 @@
"body-parser": "^1.18.2",
"bootstrap": "^3.3.7",
"chart.js": "^2.7.1",
+ "clipboard": "1.7.1",
"colors": "^1.1.2",
"compression": "^1.7.1",
"commander": "^2.12.2",
diff --git a/public/language/en-GB/admin/manage/ip-blacklist.json b/public/language/en-GB/admin/manage/ip-blacklist.json
index cd79294266..588fbd62b6 100644
--- a/public/language/en-GB/admin/manage/ip-blacklist.json
+++ b/public/language/en-GB/admin/manage/ip-blacklist.json
@@ -14,5 +14,6 @@
"alerts.applied-success": "Blacklist Applied",
"analytics.blacklist-hourly": "Figure 1 – Blacklist hits per hour",
- "analytics.blacklist-daily": "Figure 2 – Blacklist hits per day"
+ "analytics.blacklist-daily": "Figure 2 – Blacklist hits per day",
+ "ip-banned": "IP banned"
}
\ No newline at end of file
diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json
index a4892868b3..e084a9f24f 100644
--- a/public/language/en-GB/topic.json
+++ b/public/language/en-GB/topic.json
@@ -33,6 +33,8 @@
"locked": "Locked",
"pinned": "Pinned",
"moved": "Moved",
+ "copy-ip": "Copy IP",
+ "ban-ip": "Ban IP",
"bookmark_instructions" : "Click here to return to the last read post in this thread.",
diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js
index 934a3942b7..9cbfdbb366 100644
--- a/public/src/client/topic/postTools.js
+++ b/public/src/client/topic/postTools.js
@@ -8,8 +8,7 @@ define('forum/topic/postTools', [
'translator',
'forum/topic/votes',
'forum/topic/move-post',
- 'benchpress',
-], function (share, navigator, components, translator, votes, movePost, Benchpress) {
+], function (share, navigator, components, translator, votes, movePost) {
var PostTools = {};
var staleReplyAnyway = false;
@@ -45,11 +44,12 @@ define('forum/topic/postTools', [
}
data.posts.display_move_tools = data.posts.display_move_tools && index !== 0;
- Benchpress.parse('partials/topic/post-menu-list', data, function (html) {
- translator.translate(html, function (html) {
- dropdownMenu.html(html);
- $(window).trigger('action:post.tools.load');
+ app.parseAndTranslate('partials/topic/post-menu-list', data, function (html) {
+ dropdownMenu.html(html);
+ require(['clipboard'], function (clipboard) {
+ new clipboard('[data-clipboard-text]');
});
+ $(window).trigger('action:post.tools.load');
});
});
});
@@ -192,6 +192,16 @@ define('forum/topic/postTools', [
movePost.openMovePostModal($(this));
});
+ postContainer.on('click', '[component="post/ban-ip"]', function () {
+ var ip = $(this).attr('data-ip');
+ socket.emit('blacklist.addRule', ip, function (err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[admin/manage/blacklist:ban-ip]]');
+ });
+ });
+
postContainer.on('click', '[component="post/chat"]', function () {
openChat($(this));
});
diff --git a/src/controllers/admin/blacklist.js b/src/controllers/admin/blacklist.js
index a5f8136463..23d66d7c8a 100644
--- a/src/controllers/admin/blacklist.js
+++ b/src/controllers/admin/blacklist.js
@@ -7,7 +7,6 @@ var analytics = require('../../analytics');
var blacklistController = module.exports;
blacklistController.get = function (req, res, next) {
- // Analytics.getBlacklistAnalytics
async.parallel({
rules: async.apply(meta.blacklist.get),
analytics: async.apply(analytics.getBlacklistAnalytics),
diff --git a/src/meta/blacklist.js b/src/meta/blacklist.js
index 2b2f897284..c441c71b95 100644
--- a/src/meta/blacklist.js
+++ b/src/meta/blacklist.js
@@ -9,9 +9,8 @@ var pubsub = require('../pubsub');
var plugins = require('../plugins');
var analytics = require('../analytics');
-var Blacklist = {
- _rules: [],
-};
+var Blacklist = module.exports;
+Blacklist._rules = [];
Blacklist.load = function (callback) {
callback = callback || function () {};
@@ -182,4 +181,22 @@ Blacklist.validate = function (rules, callback) {
});
};
-module.exports = Blacklist;
+Blacklist.addRule = function (rule, callback) {
+ var valid;
+ async.waterfall([
+ function (next) {
+ Blacklist.validate(rule, next);
+ },
+ function (result, next) {
+ valid = result.valid;
+ if (!valid.length) {
+ return next(new Error('[[error:invalid-rule]]'));
+ }
+ Blacklist.get(next);
+ },
+ function (rules, next) {
+ rules = rules + '\n' + valid[0];
+ Blacklist.save(rules, next);
+ },
+ ], callback);
+};
diff --git a/src/meta/js.js b/src/meta/js.js
index fb3d12f683..a6dc73331e 100644
--- a/src/meta/js.js
+++ b/src/meta/js.js
@@ -98,6 +98,7 @@ JS.scripts = {
'jqueryui.js': 'public/vendor/jquery/js/jquery-ui.js',
'zxcvbn.js': 'node_modules/zxcvbn/dist/zxcvbn.js',
ace: 'node_modules/ace-builds/src-min',
+ 'clipboard.js': 'node_modules/clipboard/dist/clipboard.min.js',
},
};
diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js
index 020ea4e024..c555424377 100644
--- a/src/plugins/hooks.js
+++ b/src/plugins/hooks.js
@@ -82,23 +82,20 @@ module.exports = function (Plugins) {
var hookList = Plugins.loadedHooks[hook];
var hookType = hook.split(':')[0];
- try {
- switch (hookType) {
- case 'filter':
- fireFilterHook(hook, hookList, params, callback);
- break;
- case 'action':
- fireActionHook(hook, hookList, params, callback);
- break;
- case 'static':
- fireStaticHook(hook, hookList, params, callback);
- break;
- default:
- winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
- break;
- }
- } catch (err) {
- callback(err);
+ switch (hookType) {
+ case 'filter':
+ fireFilterHook(hook, hookList, params, callback);
+ break;
+ case 'action':
+ fireActionHook(hook, hookList, params, callback);
+ break;
+ case 'static':
+ fireStaticHook(hook, hookList, params, callback);
+ break;
+ default:
+ winston.warn('[plugins] Unknown hookType: ' + hookType + ', hook : ' + hook);
+ callback();
+ break;
}
};
diff --git a/src/socket.io/blacklist.js b/src/socket.io/blacklist.js
index 1435d20737..d4f1481508 100644
--- a/src/socket.io/blacklist.js
+++ b/src/socket.io/blacklist.js
@@ -26,3 +26,18 @@ SocketBlacklist.save = function (socket, rules, callback) {
},
], callback);
};
+
+SocketBlacklist.addRule = function (socket, rule, callback) {
+ async.waterfall([
+ function (next) {
+ user.isAdminOrGlobalMod(socket.uid, next);
+ },
+ function (isAdminOrGlobalMod, next) {
+ if (!isAdminOrGlobalMod) {
+ return callback(new Error('[[error:no-privileges]]'));
+ }
+
+ meta.blacklist.addRule(rule, next);
+ },
+ ], callback);
+};
diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js
index a9bbc6137b..a61e50ec6c 100644
--- a/src/socket.io/posts/tools.js
+++ b/src/socket.io/posts/tools.js
@@ -10,6 +10,8 @@ var socketTopics = require('../topics');
var privileges = require('../../privileges');
var plugins = require('../../plugins');
var social = require('../../social');
+var user = require('../../user');
+
module.exports = function (SocketPosts) {
SocketPosts.loadPostTools = function (socket, data, callback) {
@@ -20,10 +22,16 @@ module.exports = function (SocketPosts) {
function (next) {
async.parallel({
posts: function (next) {
- posts.getPostFields(data.pid, ['deleted', 'bookmarks', 'uid'], next);
+ posts.getPostFields(data.pid, ['deleted', 'bookmarks', 'uid', 'ip'], next);
+ },
+ isAdmin: function (next) {
+ user.isAdministrator(socket.uid, next);
+ },
+ isGlobalMod: function (next) {
+ user.isGlobalModerator(socket.uid, next);
},
- isAdminOrMod: function (next) {
- privileges.categories.isAdminOrMod(data.cid, socket.uid, next);
+ isModerator: function (next) {
+ user.isModerator(socket.uid, data.cid, next);
},
canEdit: function (next) {
privileges.posts.canEdit(data.pid, socket.uid, next);
@@ -54,7 +62,12 @@ module.exports = function (SocketPosts) {
results.posts.display_delete_tools = results.canDelete.flag;
results.posts.display_flag_tools = socket.uid && !results.posts.selfPost && results.canFlag.flag;
results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools;
- results.posts.display_move_tools = results.isAdminOrMod;
+ results.posts.display_move_tools = results.isAdmin || results.isModerator;
+ results.posts.display_ip_ban = (results.isAdmin || results.isGlobalMod) && !results.posts.selfPost;
+
+ if (!results.isAdmin && !results.isGlobalMod && !results.isModerator) {
+ results.posts.ip = undefined;
+ }
next(null, results);
},
], callback);
diff --git a/test/plugins.js b/test/plugins.js
index 47f3969a40..e948cbb160 100644
--- a/test/plugins.js
+++ b/test/plugins.js
@@ -70,22 +70,6 @@ describe('Plugins', function () {
});
});
- it('should not crash if there is an exception in a hook', function (done) {
- function filterMethod(data, callback) {
- var crash;
- crash.a = 5;
- callback(null, data);
- }
-
- plugins.registerHook('test-plugin-crash', { hook: 'filter:test.crashHook', method: filterMethod });
-
- plugins.fireHook('filter:test.crashHook', { foo: 1 }, function (err, data) {
- assert(err);
- assert.equal(err.message, 'Cannot set property \'a\' of undefined');
- done();
- });
- });
-
it('should get plugin data from nbbpm', function (done) {
plugins.get('nodebb-plugin-markdown', function (err, data) {
assert.ifError(err);