diff --git a/nodebb b/nodebb index 5f6f2a77c5..417c6a9d80 100755 --- a/nodebb +++ b/nodebb @@ -118,8 +118,12 @@ var getRunningPid = function(callback) { version: async.apply(getCurrentVersion) }), function(payload, next) { - var toCheck = Object.keys(payload.plugins); + if (!payload.plugins.length) { + process.stdout.write('OK'.green + '\n'.reset); + return next(null, []); // no extraneous plugins installed + } + var toCheck = Object.keys(payload.plugins); request({ method: 'GET', url: 'https://packages.nodebb.org/api/v1/suggest?version=' + payload.version + '&package[]=' + toCheck.join('&package[]='), diff --git a/package.json b/package.json index 8beb09d177..db5d1a549e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "express-session": "^1.8.2", "express-useragent": "0.2.4", "html-to-text": "2.0.0", + "ip": "1.1.2", "jimp": "0.2.21", "less": "^2.0.0", "logrotate-stream": "^0.2.3", @@ -43,7 +44,7 @@ "mongodb": "~2.1.3", "morgan": "^1.3.2", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "3.0.7", + "nodebb-plugin-composer-default": "3.0.10", "nodebb-plugin-dbsearch": "1.0.0", "nodebb-plugin-emoji-extended": "1.0.3", "nodebb-plugin-markdown": "4.0.17", @@ -52,9 +53,9 @@ "nodebb-plugin-spam-be-gone": "0.4.5", "nodebb-rewards-essentials": "0.0.8", "nodebb-theme-lavender": "3.0.9", - "nodebb-theme-persona": "4.0.93", - "nodebb-theme-vanilla": "5.0.54", - "nodebb-widget-essentials": "2.0.7", + "nodebb-theme-persona": "4.0.99", + "nodebb-theme-vanilla": "5.0.56", + "nodebb-widget-essentials": "2.0.8", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", "nodemailer-smtp-transport": "^2.4.1", diff --git a/public/language/en_GB/error.json b/public/language/en_GB/error.json index b60c8a2cde..e9a4cbea7d 100644 --- a/public/language/en_GB/error.json +++ b/public/language/en_GB/error.json @@ -34,6 +34,7 @@ "user-banned": "User banned", "user-too-new": "Sorry, you are required to wait %1 second(s) before making your first post", + "blacklisted-ip": "Sorry, your IP address has been banned from this community. If you feel this is in error, please contact an administrator.", "no-category": "Category does not exist", "no-topic": "Topic does not exist", diff --git a/public/language/en_GB/user.json b/public/language/en_GB/user.json index 2de2819dad..d76e82b5b3 100644 --- a/public/language/en_GB/user.json +++ b/public/language/en_GB/user.json @@ -103,6 +103,8 @@ "enable_topic_searching": "Enable In-Topic Searching", "topic_search_help": "If enabled, in-topic searching will override the browser's default page search behaviour and allow you to search through the entire topic, instead of what is only shown on screen", + "scroll_to_my_post": "After posting a reply, show the new post", + "follow_topics_you_reply_to": "Follow topics that you reply to", "follow_topics_you_create": "Follow topics you create", diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index 930e518412..e733d8d9bd 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -21,6 +21,7 @@ @import "./settings"; @import "../flags"; +@import "../blacklist"; @import "./modules/alerts"; @import "./modules/selectable"; @@ -238,5 +239,5 @@ } [class^="col-"] .mdl-switch__label { - padding-right: 15px; + padding-right: 15px; } \ No newline at end of file diff --git a/public/less/blacklist.less b/public/less/blacklist.less new file mode 100644 index 0000000000..5f23cd5698 --- /dev/null +++ b/public/less/blacklist.less @@ -0,0 +1,6 @@ +#blacklist-rules { + width: 100%; + height: 450px; + display: block; + border: 1px solid #eee; +} \ No newline at end of file diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js index a57fc06701..b4a5f19601 100644 --- a/public/src/admin/manage/categories.js +++ b/public/src/admin/manage/categories.js @@ -152,28 +152,45 @@ define('admin/manage/categories', ['vendor/jquery/serializeObject/jquery.ba-seri * @param parentId {number} parent category identifier */ function renderList(categories, container, parentId){ - templates.parse('admin/partials/categories/category-rows', { - cid: parentId, - categories: categories - }, function(html) { - container.append(html); + // Translate category names if needed + var count = 0; + categories.forEach(function(category, idx, parent) { + translator.translate(category.name, function(translated) { + if (category.name !== translated) { + category.name = translated; + } + ++count; - // Handle and children categories in this level have - for(var x=0,numCategories=categories.length;x' + '' + child.name + ' '; }); - html = html ? ('
' + html + '') : html; + html = html ? ('' + html + '') : html; return html; }; diff --git a/src/categories/create.js b/src/categories/create.js index fbd448407d..69d1f4dcd8 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -17,15 +17,15 @@ module.exports = function(Categories) { db.incrObjectField('global', 'nextCid', next); }, function(cid, next) { - var slug = cid + '/' + utils.slugify(data.name), - order = data.order || cid, // If no order provided, place it at the end - colours = Categories.assignColours(); + var slug = cid + '/' + utils.slugify(data.name); + var order = data.order || cid; // If no order provided, place it at the end + var colours = Categories.assignColours(); category = { cid: cid, name: data.name, - description: ( data.description ? data.description : '' ), - icon: ( data.icon ? data.icon : '' ), + description: data.description ? data.description : '', + icon: data.icon ? data.icon : '', bgColor: data.bgColor || colours[0], color: data.color || colours[1], slug: slug, @@ -49,6 +49,7 @@ module.exports = function(Categories) { async.series([ async.apply(db.setObject, 'category:' + category.cid, category), + async.apply(Categories.parseDescription, category.cid, category.description), async.apply(db.sortedSetAdd, 'categories:cid', category.order, category.cid), async.apply(db.sortedSetAdd, 'cid:' + parentCid + ':children', category.order, category.cid), async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'administrators'), diff --git a/src/categories/update.js b/src/categories/update.js index ffd24e7d98..78e97e4076 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -4,6 +4,7 @@ var async = require('async'), db = require('../database'), utils = require('../../public/src/utils'), + translator = require('../../public/src/modules/translator'), plugins = require('../plugins'); module.exports = function(Categories) { @@ -27,7 +28,9 @@ module.exports = function(Categories) { if (modifiedFields.hasOwnProperty('name')) { - modifiedFields.slug = cid + '/' + utils.slugify(modifiedFields.name); + translator.translate(modifiedFields.name, function(translated) { + modifiedFields.slug = cid + '/' + utils.slugify(translated); + }); } plugins.fireHook('filter:category.update', {category: modifiedFields}, function(err, categoryData) { @@ -69,7 +72,7 @@ module.exports = function(Categories) { if (key === 'order') { updateOrder(cid, value, callback); } else if (key === 'description') { - parseDescription(cid, value, callback); + Categories.parseDescription(cid, value, callback); } else { callback(); } @@ -97,7 +100,7 @@ module.exports = function(Categories) { function (next) { db.setObjectField('category:' + cid, 'parentCid', newParent, next); } - ], function(err, results) { + ], function(err) { callback(err); }); }); @@ -121,13 +124,13 @@ module.exports = function(Categories) { }); } - function parseDescription(cid, description, callback) { + Categories.parseDescription = function(cid, description, callback) { plugins.fireHook('filter:parse.raw', description, function(err, parsedDescription) { if (err) { return callback(err); } Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, callback); }); - } + }; }; diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index 7028972491..0b180be686 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -42,7 +42,7 @@ settingsController.get = function(req, res, callback) { getHomePageRoutes(next); }, ips: function (next) { - user.getIPs(req.uid, 4, next); + user.getIPs(userData.uid, 4, next); }, sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID) }, next); diff --git a/src/controllers/admin.js b/src/controllers/admin.js index 8b9b1cafc7..2bba60cae6 100644 --- a/src/controllers/admin.js +++ b/src/controllers/admin.js @@ -5,6 +5,7 @@ var adminController = { categories: require('./admin/categories'), tags: require('./admin/tags'), flags: require('./admin/flags'), + blacklist: require('./admin/blacklist'), groups: require('./admin/groups'), appearance: require('./admin/appearance'), extend: { diff --git a/src/controllers/admin/blacklist.js b/src/controllers/admin/blacklist.js new file mode 100644 index 0000000000..2c0104f742 --- /dev/null +++ b/src/controllers/admin/blacklist.js @@ -0,0 +1,16 @@ +"use strict"; + +var meta = require('../../meta'); + +var blacklistController = {}; + +blacklistController.get = function(req, res, next) { + meta.blacklist.get(function(err, rules) { + if (err) { + return next(err); + } + res.render('admin/manage/ip-blacklist', {rules: rules, title: 'IP Blacklist'}); + }); +}; + +module.exports = blacklistController; diff --git a/src/controllers/category.js b/src/controllers/category.js index f4b939f60a..e3adde496f 100644 --- a/src/controllers/category.js +++ b/src/controllers/category.js @@ -51,7 +51,7 @@ categoryController.get = function(req, res, callback) { return helpers.notAllowed(req, res); } - if ((!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) { + if (!res.locals.isAPI && (!req.params.slug || results.categoryData.slug !== cid + '/' + req.params.slug) && (results.categoryData.slug && results.categoryData.slug !== cid + '/')) { return helpers.redirect(res, '/category/' + encodeURI(results.categoryData.slug)); } diff --git a/src/controllers/globalmods.js b/src/controllers/globalmods.js new file mode 100644 index 0000000000..3275c7929e --- /dev/null +++ b/src/controllers/globalmods.js @@ -0,0 +1,29 @@ +"use strict"; + +var user = require('../user'); +var adminFlagsController = require('./admin/flags'); +var adminBlacklistController = require('./admin/blacklist'); + +var globalModsController = {}; + +globalModsController.flagged = function(req, res, next) { + user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) { + if (err || !isAdminOrGlobalMod) { + return next(err); + } + + adminFlagsController.get(req, res, next); + }); +}; + +globalModsController.ipBlacklist = function(req, res, next) { + user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) { + if (err || !isAdminOrGlobalMod) { + return next(err); + } + + adminBlacklistController.get(req, res, next); + }); +}; + +module.exports = globalModsController; diff --git a/src/controllers/index.js b/src/controllers/index.js index 51c5c316ba..fa35523c2b 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -12,7 +12,6 @@ var helpers = require('./helpers'); var Controllers = { topics: require('./topics'), - posts: require('./posts'), categories: require('./categories'), category: require('./category'), unread: require('./unread'), @@ -25,7 +24,8 @@ var Controllers = { accounts: require('./accounts'), authentication: require('./authentication'), api: require('./api'), - admin: require('./admin') + admin: require('./admin'), + globalMods: require('./globalmods') }; diff --git a/src/controllers/posts.js b/src/controllers/posts.js deleted file mode 100644 index 5618069b9b..0000000000 --- a/src/controllers/posts.js +++ /dev/null @@ -1,19 +0,0 @@ -"use strict"; - -var user = require('../user'); -var adminFlagsController = require('./admin/flags'); - -var postsController = {}; - -postsController.flagged = function(req, res, next) { - user.isAdminOrGlobalMod(req.uid, function(err, isAdminOrGlobalMod) { - if (err || !isAdminOrGlobalMod) { - return next(err); - } - - adminFlagsController.get(req, res, next); - }); -}; - - -module.exports = postsController; diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 7a222dd09c..27c0ed5b81 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -4,7 +4,6 @@ var async = require('async'); var S = require('string'); var nconf = require('nconf'); -var validator = require('validator'); var user = require('../user'); var meta = require('../meta'); @@ -24,6 +23,7 @@ topicsController.get = function(req, res, callback) { var currentPage = parseInt(req.query.page, 10) || 1; var pageCount = 1; var userPrivileges; + var settings; if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) { return callback(); @@ -54,7 +54,7 @@ topicsController.get = function(req, res, callback) { return helpers.notAllowed(req, res); } - if ((!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) { + if (!res.locals.isAPI && (!req.params.slug || results.topic.slug !== tid + '/' + req.params.slug) && (results.topic.slug && results.topic.slug !== tid + '/')) { var url = '/topic/' + encodeURI(results.topic.slug); if (req.params.post_index){ url += '/'+req.params.post_index; @@ -62,7 +62,7 @@ topicsController.get = function(req, res, callback) { return helpers.redirect(res, url); } - var settings = results.settings; + settings = results.settings; var postCount = parseInt(results.topic.postcount, 10); pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage)); @@ -261,6 +261,7 @@ topicsController.get = function(req, res, callback) { data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1; data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1; + data.scrollToMyPost = settings.scrollToMyPost; data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss'; data.pagination = pagination.create(currentPage, pageCount); data.pagination.rel.forEach(function(rel) { diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js index fe80afaaf4..dbf294119e 100644 --- a/src/database/mongo/hash.js +++ b/src/database/mongo/hash.js @@ -1,7 +1,5 @@ "use strict"; -var winston = require('winston'); - module.exports = function(db, module) { var helpers = module.helpers.mongo; @@ -121,9 +119,8 @@ module.exports = function(db, module) { } var map = helpers.toMap(items); - var returnData = [], - index = 0, - item; + var returnData = []; + var item; for (var i=0; i' + ajaxifyData + ''; fn(err, translated); }); diff --git a/src/posts/parse.js b/src/posts/parse.js index efa5667ccc..1af374305d 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -3,6 +3,7 @@ var cache = require('./cache'); var plugins = require('../plugins'); +var translator = require('../../public/src/modules/translator'); module.exports = function(Posts) { @@ -24,6 +25,8 @@ module.exports = function(Posts) { return callback(err); } + data.postData.content = translator.escape(data.postData.content); + if (global.env === 'production' && data.postData.pid) { cache.set(data.postData.pid, data.postData.content); } diff --git a/src/routes/admin.js b/src/routes/admin.js index f5d4d039a1..56c97ff1f2 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -54,8 +54,8 @@ function addRoutes(router, middleware, controllers) { router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get); router.get('/manage/tags', middlewares, controllers.admin.tags.get); - router.get('/manage/flags', middlewares, controllers.admin.flags.get); + router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get); router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate); router.get('/manage/users/search', middlewares, controllers.admin.users.search); diff --git a/src/routes/authentication.js b/src/routes/authentication.js index 37439ba523..8e1824cad6 100644 --- a/src/routes/authentication.js +++ b/src/routes/authentication.js @@ -62,8 +62,8 @@ })); }); - router.post('/register', Auth.middleware.applyCSRF, controllers.authentication.register); - router.post('/login', Auth.middleware.applyCSRF, controllers.authentication.login); + router.post('/register', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.register); + router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login); router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout); hotswap.replace('auth', router); diff --git a/src/routes/index.js b/src/routes/index.js index 7e84f29af1..58754670d5 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -36,8 +36,9 @@ function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); } -function postRoutes(app, middleware, controllers) { - setupPageRoute(app, '/posts/flags', middleware, [], controllers.posts.flagged); +function globalModRoutes(app, middleware, controllers) { + setupPageRoute(app, '/ip-blacklist', middleware, [], controllers.globalMods.ipBlacklist); + setupPageRoute(app, '/posts/flags', middleware, [], controllers.globalMods.flagged); } function topicRoutes(app, middleware, controllers) { @@ -111,7 +112,7 @@ module.exports = function(app, middleware) { mainRoutes(router, middleware, controllers); topicRoutes(router, middleware, controllers); - postRoutes(router, middleware, controllers); + globalModRoutes(router, middleware, controllers); tagRoutes(router, middleware, controllers); categoryRoutes(router, middleware, controllers); accountRoutes(router, middleware, controllers); @@ -187,9 +188,12 @@ function handle404(app, middleware) { function handleErrors(app, middleware) { app.use(function(err, req, res, next) { - if (err.code === 'EBADCSRFTOKEN') { - winston.error(req.path + '\n', err.message); - return res.sendStatus(403); + switch (err.code) { + case 'EBADCSRFTOKEN': + winston.error(req.path + '\n', err.message); + return res.sendStatus(403); + case 'blacklisted-ip': + return res.status(403).type('text/plain').send(err.message); } if (parseInt(err.status, 10) === 302 && err.path) { diff --git a/src/search.js b/src/search.js index ca457d8b0b..9b7306c7fa 100644 --- a/src/search.js +++ b/src/search.js @@ -85,7 +85,9 @@ function searchInContent(data, callback) { topics.getMainPids(results.tids, next); }, function(mainPids, next) { - results.pids = mainPids.concat(results.pids).filter(function(pid, index, array) { + results.pids = mainPids.concat(results.pids).map(function(pid) { + return pid && pid.toString(); + }).filter(function(pid, index, array) { return pid && array.indexOf(pid) === index; }); diff --git a/src/settings.js b/src/settings.js index 3d90dcfa84..6f6cad2854 100644 --- a/src/settings.js +++ b/src/settings.js @@ -3,16 +3,19 @@ var meta = require('./meta'); function expandObjBy(obj1, obj2) { - var key, val1, val2, changed = false; + var key, val1, val2, xorValIsArray, changed = false; for (key in obj2) { if (obj2.hasOwnProperty(key)) { val2 = obj2[key]; val1 = obj1[key]; - if (!obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) { + xorValIsArray = Array.isArray(val1) ^ Array.isArray(val2); + if (xorValIsArray || !obj1.hasOwnProperty(key) || typeof val2 !== typeof val1) { obj1[key] = val2; changed = true; - } else if (typeof val2 === 'object' && expandObjBy(val1, val2)) { - changed = true; + } else if (typeof val2 === 'object' && !Array.isArray(val2)) { + if (expandObjBy(val1, val2)) { + changed = true; + } } } } @@ -26,10 +29,10 @@ function trim(obj1, obj2) { val1 = obj1[key]; if (!obj2.hasOwnProperty(key)) { delete obj1[key]; - } else if (typeof val1 === 'object') { + } else if (typeof val1 === 'object' && !Array.isArray(val1)) { trim(val1, obj2[key]); } - } + } } } diff --git a/src/socket.io/admin/rooms.js b/src/socket.io/admin/rooms.js index 6d8843b8ca..50ba4661de 100644 --- a/src/socket.io/admin/rooms.js +++ b/src/socket.io/admin/rooms.js @@ -29,15 +29,16 @@ pubsub.on('sync:stats:end', function(data) { stats[data.id] = data.stats; }); +pubsub.on('sync:stats:guests', function() { + var io = require('../index').server; + + var roomClients = io.sockets.adapter.rooms; + var guestCount = roomClients.online_guests ? roomClients.online_guests.length : 0; + pubsub.publish('sync:stats:guests:end', guestCount); +}); + SocketRooms.getTotalGuestCount = function(callback) { var count = 0; - pubsub.once('sync:stats:guests', function() { - var io = require('../index').server; - - var roomClients = io.sockets.adapter.rooms; - var guestCount = roomClients.online_guests ? roomClients.online_guests.length : 0; - pubsub.publish('sync:stats:guests:end', guestCount); - }); pubsub.on('sync:stats:guests:end', function(guestCount) { count += guestCount; diff --git a/src/socket.io/blacklist.js b/src/socket.io/blacklist.js new file mode 100644 index 0000000000..f4158dc94b --- /dev/null +++ b/src/socket.io/blacklist.js @@ -0,0 +1,27 @@ + +'use strict'; + +var async = require('async'); +var winston = require('winston'); + +var user = require('../user'); +var meta = require('../meta'); + +var SocketBlacklist = {}; + +SocketBlacklist.validate = function(socket, data, callback) { + meta.blacklist.validate(data.rules, callback); +}; + +SocketBlacklist.save = function(socket, rules, callback) { + user.isAdminOrGlobalMod(socket.uid, function(err, isAdminOrGlobalMod) { + if (err || !isAdminOrGlobalMod) { + return callback(err || new Error('[[error:no-privileges]]')); + } + + meta.blacklist.save(rules, callback); + }); +}; + + +module.exports = SocketBlacklist; diff --git a/src/socket.io/index.js b/src/socket.io/index.js index 032c4b9c68..8f6f3c4c5a 100644 --- a/src/socket.io/index.js +++ b/src/socket.io/index.js @@ -121,7 +121,7 @@ function onMessage(socket, payload) { function requireModules() { var modules = ['admin', 'categories', 'groups', 'meta', 'modules', - 'notifications', 'plugins', 'posts', 'topics', 'user' + 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist' ]; modules.forEach(function(module) { diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js index e2d7cf3084..121ede2a96 100644 --- a/src/socket.io/notifications.js +++ b/src/socket.io/notifications.js @@ -3,6 +3,7 @@ var async = require('async'); var user = require('../user'); var notifications = require('../notifications'); +var utils = require('../../public/src/utils'); var SocketNotifs = {}; @@ -15,7 +16,7 @@ SocketNotifs.get = function(socket, data, callback) { }; SocketNotifs.loadMore = function(socket, data, callback) { - if (!data || !parseInt(data.after, 10)) { + if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { return callback(new Error('[[error:invalid-data]]')); } if (!socket.uid) { diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 8cb975c15e..3ffe1c65c8 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -88,7 +88,7 @@ module.exports = function(SocketTopics) { }; SocketTopics.loadMoreUnreadTopics = function(socket, data, callback) { - if (!data || !data.after) { + if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { return callback(new Error('[[error:invalid-data]]')); } @@ -99,7 +99,7 @@ module.exports = function(SocketTopics) { }; SocketTopics.loadMoreFromSet = function(socket, data, callback) { - if (!data || !data.after || !data.set) { + if (!data || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0 || !data.set) { return callback(new Error('[[error:invalid-data]]')); } diff --git a/src/user/settings.js b/src/user/settings.js index f1c4081b50..fc72342bf9 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -76,6 +76,7 @@ module.exports = function(User) { settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; settings.bootswatchSkin = settings.bootswatchSkin || 'default'; + settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) ===1; callback(null, settings); }); @@ -120,7 +121,8 @@ module.exports = function(User) { restrictChat: data.restrictChat, topicSearchEnabled: data.topicSearchEnabled, groupTitle: data.groupTitle, - homePageRoute: data.homePageCustom || data.homePageRoute + homePageRoute: data.homePageCustom || data.homePageRoute, + scrollToMyPost: data.scrollToMyPost }; if (data.bootswatchSkin) { diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl index 414f51133e..f579065df5 100644 --- a/src/views/admin/manage/group.tpl +++ b/src/views/admin/manage/group.tpl @@ -9,12 +9,12 @@
-
+
-
+
diff --git a/src/views/admin/manage/ip-blacklist.tpl b/src/views/admin/manage/ip-blacklist.tpl new file mode 100644 index 0000000000..26170b286e --- /dev/null +++ b/src/views/admin/manage/ip-blacklist.tpl @@ -0,0 +1,40 @@ +
+
+

+ Configure your IP blacklist here. +

+

+ Occasionally, a user account ban is not enough of a deterrant. Other times, restricting access to the forum to a specific IP or a range of IPs + is the best way to protect a forum. In these scenarios, you can add troublesome IP addresses or entire CIDR blocks to this blacklist, and + they will be prevented from logging in to or registering a new account. +

+ +
+
+ +
+
+
+
Active Rules
+
+ + +
+
+
+
Syntax Hints
+
+

+ Define a single IP addresses per line. You can add IP blocks as long as they follow the CIDR format (e.g. + 192.168.100.0/22). +

+

+ You can add in comments by starting lines with the # symbol. +

+
+
+
+
+
+ +
\ No newline at end of file diff --git a/src/views/admin/partials/blacklist-validate.tpl b/src/views/admin/partials/blacklist-validate.tpl new file mode 100644 index 0000000000..4a642a23a3 --- /dev/null +++ b/src/views/admin/partials/blacklist-validate.tpl @@ -0,0 +1,14 @@ +

+ {valid.length} out of {numRules} rule(s) valid. +

+ + +

+ The following {invalid.length} rules are invalid: +

+
    + +
  • @value
  • + +
+ \ No newline at end of file diff --git a/src/views/admin/partials/menu.tpl b/src/views/admin/partials/menu.tpl index a0027c0621..8d0f0f853d 100644 --- a/src/views/admin/partials/menu.tpl +++ b/src/views/admin/partials/menu.tpl @@ -20,6 +20,7 @@
  • Registration Queue
  • Groups
  • Flags
  • +
  • IP Blacklist
  • @@ -173,6 +174,7 @@
  • Registration Queue
  • Groups
  • Flags
  • +
  • IP Blacklist