Merge remote-tracking branch 'refs/remotes/origin/master' into develop

v1.18.x
Barış Soner Uşaklı 8 years ago
commit ff88be91fa

@ -41,6 +41,7 @@
"express-useragent": "1.0.7",
"html-to-text": "3.3.0",
"ip": "1.1.5",
"ip-range-check": "^0.0.2",
"jimp": "0.2.28",
"jquery": "^3.1.0",
"json-2-csv": "^2.0.22",
@ -55,7 +56,7 @@
"morgan": "^1.3.2",
"mousetrap": "^1.5.3",
"nconf": "~0.8.2",
"nodebb-plugin-composer-default": "5.0.5",
"nodebb-plugin-composer-default": "5.0.6",
"nodebb-plugin-dbsearch": "2.0.6",
"nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.2.1",
@ -65,9 +66,9 @@
"nodebb-plugin-spam-be-gone": "0.5.1",
"nodebb-rewards-essentials": "0.0.9",
"nodebb-theme-lavender": "4.0.5",
"nodebb-theme-persona": "5.0.22",
"nodebb-theme-persona": "5.0.30",
"nodebb-theme-slick": "1.1.0",
"nodebb-theme-vanilla": "6.0.17",
"nodebb-theme-vanilla": "6.0.24",
"nodebb-widget-essentials": "3.0.1",
"nodemailer": "2.6.4",
"nodemailer-sendmail-transport": "1.0.0",

@ -17,14 +17,14 @@
"mongo.file-size": "文件大小",
"mongo.resident-memory": "驻留内存",
"mongo.virtual-memory": "虚拟内存",
"mongo.mapped-memory": "映射",
"mongo.mapped-memory": "映射内存",
"mongo.raw-info": "MongoDB 原始信息",
"redis": "Redis",
"redis.version": "Redis 版本",
"redis.connected-clients": "已连接客户端",
"redis.connected-slaves": "已连接从",
"redis.blocked-clients": "的客户端",
"redis.blocked-clients": "阻的客户端",
"redis.used-memory": "已使用内存",
"redis.memory-frag-ratio": "内存碎片比率",
"redis.total-connections-recieved": "已接收的连接总数",

@ -4,7 +4,7 @@
"error.404": "404 页面不存在",
"error.503": "503 服务不可用",
"manage-error-log": "管理错误日志",
"export-error-log": "提取错误日志(.csv)",
"export-error-log": "导出错误日志 (.csv)",
"clear-error-log": "清空错误日志",
"route": "路由",
"count": "计数",

@ -1,6 +1,6 @@
{
"events": "事件",
"no-events": "暂无事件",
"no-events": "暂无事件",
"control-panel": "事件控制面板",
"delete-events": "清除事件"
}

@ -5,5 +5,5 @@
"current-skin": "当前皮肤",
"skin-updated": "皮肤已更新",
"applied-success": "%1 皮肤已成功应用",
"revert-success": "皮肤恢复到基础颜色"
"revert-success": "皮肤恢复到基础颜色"
}

@ -5,7 +5,7 @@
"enable-http": "启用 HTTP 日志",
"enable-socket": "启用 socket.io 事件日志",
"file-path": "日志文件路径",
"file-path-placeholder": "如/path/to/log/file.log ::: 如想在终端中显示日志请留空此项",
"file-path-placeholder": "如 /path/to/log/file.log ::: 如想在终端中显示日志请留空此项",
"control-panel": "日志记录器控制面板",
"update-settings": "更新日志记录器设置"

@ -24,7 +24,7 @@
"keep-updated": "请确保您已及时更新 NodeBB 以获得最新的安全补丁与 Bug 修复。",
"up-to-date": "<p>正在使用 <strong>最新版本</strong> <i class=\"fa fa-check\"></i></p>",
"upgrade-available": "<p> 新版本 (v%1) 已经发布! 建议 <a href=\"https://docs.nodebb.org/configuring/upgrade/\">更新你的 NodeBB</a>。</p>",
"prerelease-upgrade-available": "<p>正在使用NodeBB过期的实验版本。新的版本 (v%1) 已经发布。 请考虑<a href=\"https://docs.nodebb.org/configuring/upgrade/\">更新你的 NodeBB</a>。",
"prerelease-upgrade-available": "<p>正在使用过时的测试版 NodeBB。新的版本 (v%1) 已经发布。 请考虑<a href=\"https://docs.nodebb.org/configuring/upgrade/\">更新你的 NodeBB</a>。",
"prerelease-warning": "<p>正在使用<strong>测试版</strong> NodeBB。可能会出现意外的 Bug。<i class=\"fa fa-exclamation-triangle\"></i></p>",
"running-in-development": "<span>论坛正处于开发模式,这可能使其暴露于潜在的危险之中;请联系您的系统管理员。</span>",

@ -60,7 +60,7 @@
"alert.copy-success": "设置已复制!",
"alert.set-parent-category": "设置父版块",
"alert.updated": "版块已更新",
"alert.updated-success": "版块ID %1 成功更新。",
"alert.updated-success": "版块 ID %1 成功更新。",
"alert.upload-image": "上传版块图片",
"alert.find-user": "查找用户",
"alert.user-search": "在这里查找用户…",

@ -11,7 +11,7 @@
"list.ip-spam": "频率:%1 显示: %2",
"invitations": "邀请",
"invitations.description": "下面是一份完整的邀请请求列表。请使用Ctrl-F键以及电子邮件或者用户名以便搜索这个列表。<br><br>那些已经接受他们邀请的用户的用户名将显示在电子邮箱右边。",
"invitations.description": "下面列出了所有已发送的邀请。您可以使用 Ctrl+F 快捷键搜索列表中的邮箱地址或用户名。<br><br>如果用户接受了邀请,他的用户名将会被显示在邮箱右边。",
"invitations.inviter-username": "邀请人用户名",
"invitations.invitee-email": "受邀请的电子邮箱",
"invitations.invitee-username": "受邀请的用户名(如果已经注册)",

@ -2,7 +2,7 @@
"category": "版块",
"subcategories": "子版块",
"new_topic_button": "新主题",
"guest-login-post": "登录发表",
"guest-login-post": "登录发表",
"no_topics": "<strong>此版块还没有任何内容。</strong><br />赶紧来发帖吧!",
"browsing": "正在浏览",
"no_replies": "尚无回复",
@ -10,7 +10,7 @@
"share_this_category": "分享此版块",
"watch": "关注",
"ignore": "忽略",
"watching": "正在关注",
"watching": "关注",
"ignoring": "已忽略",
"watching.description": "显示未读主题",
"ignoring.description": "不显示未读主题",

@ -1,7 +1,7 @@
'use strict';
define('notifications', ['sounds', 'translator', 'components'], function (sounds, translator, components) {
define('notifications', ['sounds', 'translator', 'components', 'navigator'], function (sounds, translator, components, navigator) {
var Notifications = {};
var unreadNotifs = {};
@ -20,12 +20,19 @@ define('notifications', ['sounds', 'translator', 'components'], function (sounds
Notifications.loadNotifications(notifList);
});
notifList.on('click', '[data-nid]', function () {
var unread = $(this).hasClass('unread');
notifList.on('click', '[data-nid]', function (ev) {
var notifEl = $(this);
if (scrollToPostIndexIfOnPage(notifEl)) {
ev.stopPropagation();
ev.preventDefault();
notifTrigger.dropdown('toggle');
}
var unread = notifEl.hasClass('unread');
if (!unread) {
return;
}
var nid = $(this).attr('data-nid');
var nid = notifEl.attr('data-nid');
socket.emit('notifications.markRead', nid, function (err) {
if (err) {
return app.alertError(err.message);
@ -107,6 +114,19 @@ define('notifications', ['sounds', 'translator', 'components'], function (sounds
});
};
function scrollToPostIndexIfOnPage(notifEl) {
// Scroll to index if already in topic (gh#5873)
var pid = notifEl.attr('data-pid');
var tid = notifEl.attr('data-tid');
var path = notifEl.attr('data-path');
var postEl = components.get('post', 'pid', pid);
if (path.startsWith(config.relative_path + '/post/') && pid && postEl.length && ajaxify.data.template.topic && parseInt(ajaxify.data.tid, 10) === parseInt(tid, 10)) {
navigator.scrollToIndex(postEl.attr('data-index'), true);
return true;
}
return false;
}
Notifications.loadNotifications = function (notifList) {
socket.emit('notifications.get', null, function (err, data) {
if (err) {

@ -182,9 +182,8 @@ function filterLinks(links, states) {
admin: true,
}, link.visibility);
// Iterate through states and permit if every test passes (or is not defined)
var permit = Object.keys(states).some(function (state) {
return states[state] === link.visibility[state];
return states[state] && link.visibility[state];
});
links[index].public = permit;

@ -47,24 +47,21 @@ infoController.get = function (req, res, callback) {
},
}, next);
},
], function (err, data) {
if (err) {
return callback(err);
}
function (data) {
userData.history = data.history;
userData.sessions = data.sessions;
userData.usernames = data.usernames;
userData.emails = data.emails;
userData.history = data.history;
userData.sessions = data.sessions;
userData.usernames = data.usernames;
userData.emails = data.emails;
if (userData.isAdminOrGlobalModeratorOrModerator) {
userData.moderationNotes = data.notes.notes;
var pageCount = Math.ceil(data.notes.count / itemsPerPage);
userData.pagination = pagination.create(page, pageCount, req.query);
}
userData.title = '[[pages:account/info]]';
userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: '/user/' + userData.userslug }, { text: '[[user:account_info]]' }]);
if (userData.isAdminOrGlobalModeratorOrModerator) {
userData.moderationNotes = data.notes.notes;
var pageCount = Math.ceil(data.notes.count / itemsPerPage);
userData.pagination = pagination.create(page, pageCount, req.query);
}
userData.title = '[[pages:account/info]]';
userData.breadcrumbs = helpers.buildBreadcrumbs([{ text: userData.username, url: '/user/' + userData.userslug }, { text: '[[user:account_info]]' }]);
res.render('account/info', userData);
});
res.render('account/info', userData);
},
], callback);
};

@ -1,6 +1,7 @@
'use strict';
var ip = require('ip');
var ipRangeCheck = require('ip-range-check');
var winston = require('winston');
var async = require('async');
@ -27,6 +28,7 @@ Blacklist.load = function (callback) {
ipv4: rules.ipv4,
ipv6: rules.ipv6,
cidr: rules.cidr,
cidr6: rules.cidr6,
};
next();
},
@ -53,11 +55,12 @@ Blacklist.get = function (callback) {
Blacklist.test = function (clientIp, callback) {
if (
Blacklist._rules.ipv4.indexOf(clientIp) === -1 &&// not explicitly specified in ipv4 list
Blacklist._rules.ipv6.indexOf(clientIp) === -1 &&// not explicitly specified in ipv6 list
Blacklist._rules.ipv4.indexOf(clientIp) === -1 && // not explicitly specified in ipv4 list
Blacklist._rules.ipv6.indexOf(clientIp) === -1 && // not explicitly specified in ipv6 list
!Blacklist._rules.cidr.some(function (subnet) {
return ip.cidrSubnet(subnet).contains(clientIp);
}) // not in a blacklisted cidr range
}) && // not in a blacklisted IPv4 cidr range
!ipRangeCheck(clientIp, Blacklist._rules.cidr6) // not in a blacklisted IPv6 cidr range
) {
if (typeof callback === 'function') {
setImmediate(callback);
@ -81,9 +84,11 @@ Blacklist.validate = function (rules, callback) {
var ipv4 = [];
var ipv6 = [];
var cidr = [];
var cidr6 = [];
var invalid = [];
var isCidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;
var isIPv4CidrSubnet = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;
var isIPv6CidrSubnet = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/;
var inlineCommentMatch = /#.*$/;
var whitelist = ['127.0.0.1', '::1', '::ffff:0:127.0.0.1'];
@ -109,7 +114,11 @@ Blacklist.validate = function (rules, callback) {
ipv6.push(rule);
return true;
}
if (isCidrSubnet.test(rule)) {
if (isIPv4CidrSubnet.test(rule)) {
cidr.push(rule);
return true;
}
if (isIPv6CidrSubnet.test(rule)) {
cidr.push(rule);
return true;
}
@ -123,6 +132,7 @@ Blacklist.validate = function (rules, callback) {
ipv4: ipv4,
ipv6: ipv6,
cidr: cidr,
cidr6: cidr6,
valid: rules,
invalid: invalid,
});

@ -70,7 +70,6 @@ Notifications.getMultiple = function (nids, callback) {
}
}
});
next(null, notifications);
},
], callback);

@ -10,6 +10,7 @@ var topics = require('../topics');
var user = require('../user');
var helpers = require('./helpers');
var plugins = require('../plugins');
var utils = require('../utils');
module.exports = function (privileges) {
privileges.posts = {};
@ -190,6 +191,22 @@ module.exports = function (privileges) {
], callback);
};
privileges.posts.canFlag = function (pid, uid, callback) {
async.waterfall([
function (next) {
async.parallel({
userReputation: async.apply(user.getUserField, uid, 'reputation'),
isAdminOrMod: async.apply(isAdminOrMod, pid, uid),
}, next);
},
function (results, next) {
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 1;
var canFlag = results.isAdminOrMod || parseInt(results.userReputation, 10) >= minimumReputation;
next(null, { flag: canFlag });
},
], callback);
};
privileges.posts.canMove = function (pid, uid, callback) {
async.waterfall([
function (next) {

@ -31,6 +31,9 @@ module.exports = function (SocketPosts) {
canDelete: function (next) {
privileges.posts.canDelete(data.pid, socket.uid, next);
},
canFlag: function (next) {
privileges.posts.canFlag(data.pid, socket.uid, next);
},
bookmarked: function (next) {
posts.hasBookmarked(data.pid, socket.uid, next);
},
@ -49,6 +52,7 @@ module.exports = function (SocketPosts) {
results.posts.selfPost = socket.uid && socket.uid === parseInt(results.posts.uid, 10);
results.posts.display_edit_tools = results.canEdit.flag;
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;
next(null, results);

@ -166,6 +166,7 @@ module.exports = function (User) {
var data = JSON.parse(note);
uids.push(data.uid);
data.timestampISO = utils.toISOString(data.timestamp);
data.note = validator.escape(String(data.note));
return data;
} catch (err) {
return next(err);

@ -185,7 +185,9 @@ module.exports = function (User) {
function (next) {
db.sortedSetAdd('users:notvalidated', Date.now(), uid, next);
},
async.apply(User.reset.cleanByUid, uid),
function (next) {
User.reset.cleanByUid(uid, next);
},
], function (err) {
next(err);
});

@ -170,17 +170,12 @@ UserReset.clean = function (callback) {
};
UserReset.cleanByUid = function (uid, callback) {
if (typeof callback !== 'function') {
callback = function () {};
}
var toClean = [];
uid = parseInt(uid, 10);
async.waterfall([
async.apply(db.getSortedSetRange.bind(db), 'reset:issueDate', 0, -1),
function (tokens, next) {
batch.processArray(tokens, function (tokens, next) {
function (next) {
batch.processSortedSet('reset:issueDate', function (tokens, next) {
db.getObjectFields('reset:uid', tokens, function (err, results) {
for (var code in results) {
if (results.hasOwnProperty(code) && parseInt(results[code], 10) === uid) {

@ -1236,15 +1236,16 @@ describe('User', function () {
setTimeout(next, 50);
},
function (next) {
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'second moderation note' }, next);
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: '<svg/onload=alert(document.location);//' }, next);
},
function (next) {
User.getModerationNotes(testUid, 0, -1, next);
},
], function (err, notes) {
assert.ifError(err);
assert.equal(notes[0].note, 'second moderation note');
assert.equal(notes[0].note, '&lt;svg&#x2F;onload=alert(document.location);&#x2F;&#x2F;');
assert.equal(notes[0].uid, adminUid);
assert.equal(notes[1].note, 'this is a test user');
assert(notes[0].timestamp);
done();
});

Loading…
Cancel
Save