mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-23 17:09:51 +02:00
Merge commit 'b88a8de6a159d0b2cd5114e084b38c82c24d5a2a' into v1.10.x
This commit is contained in:
@@ -65,7 +65,7 @@
|
||||
"mousetrap": "^1.6.1",
|
||||
"mubsub-nbb": "^1.5.0",
|
||||
"nconf": "^0.10.0",
|
||||
"nodebb-plugin-composer-default": "6.0.28",
|
||||
"nodebb-plugin-composer-default": "6.0.29",
|
||||
"nodebb-plugin-dbsearch": "2.0.19",
|
||||
"nodebb-plugin-emoji": "^2.2.2",
|
||||
"nodebb-plugin-emoji-android": "2.0.0",
|
||||
@@ -75,9 +75,9 @@
|
||||
"nodebb-plugin-spam-be-gone": "0.5.4",
|
||||
"nodebb-rewards-essentials": "0.0.11",
|
||||
"nodebb-theme-lavender": "5.0.5",
|
||||
"nodebb-theme-persona": "9.0.17",
|
||||
"nodebb-theme-slick": "1.2.5",
|
||||
"nodebb-theme-vanilla": "10.0.15",
|
||||
"nodebb-theme-persona": "9.0.19",
|
||||
"nodebb-theme-slick": "1.2.6",
|
||||
"nodebb-theme-vanilla": "10.0.17",
|
||||
"nodebb-widget-essentials": "4.0.7",
|
||||
"nodemailer": "^4.6.5",
|
||||
"passport": "^0.4.0",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"edit-posts": "Редактиране на публикации",
|
||||
"view-edit-history": "Преглед на историята на редакциите",
|
||||
"delete-posts": "Изтриване на публикации",
|
||||
"view_deleted": "View Deleted Posts",
|
||||
"view_deleted": "Преглед на изтритите публикации",
|
||||
"upvote-posts": "Положително гласуване за публикации",
|
||||
"downvote-posts": "Отрицателно гласуване за публикации",
|
||||
"delete-topics": "Изтриване на теми",
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
"headers.allow-from": "Задайте „ALLOW-FROM“, за да поставите NodeBB в „iFrame“",
|
||||
"headers.powered-by": "Персонализиране на заглавната част „Захранван от“, която се изпраща от NodeBB",
|
||||
"headers.acao": "Произход за разрешаване на управлението на достъпа",
|
||||
"headers.acao-regex": "Access-Control-Allow-Origin Regular Expression",
|
||||
"headers.acao-regex": "Регулярен израз за произхода за разрешаване на управлението на достъпа",
|
||||
"headers.acao-help": "За да забраните достъпа до всички уеб сайтове, оставете празно",
|
||||
"headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty",
|
||||
"headers.acao-regex-help": "Въведете регулярен израз за съвпадение с динамичните произходи. За да забраните достъпа на всички уеб сайтове, оставете това празно.",
|
||||
"headers.acac": "Удостоверителни данни за разрешаване на управлението на достъпа",
|
||||
"headers.acam": "Методи за разрешаване на управлението на достъпа",
|
||||
"headers.acah": "Заглавки за разрешаване на управлението на достъпа",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"edit-posts": "Upravit příspěvek",
|
||||
"view-edit-history": "Zobrazit historii editace",
|
||||
"delete-posts": "Odstranit příspěvky",
|
||||
"view_deleted": "View Deleted Posts",
|
||||
"view_deleted": "Zobrazit odstraněné příspěvky",
|
||||
"upvote-posts": "Souhlasné příspěvky",
|
||||
"downvote-posts": "Nesouhlasné příspěvky",
|
||||
"delete-topics": "Odstranit témata",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"headers.acao": "Access-Control-Allow-Origin",
|
||||
"headers.acao-regex": "Access-Control-Allow-Origin Regular Expression",
|
||||
"headers.acao-help": "Pro zakázání přístupu na všechny stránky, zanechte prázdné",
|
||||
"headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty",
|
||||
"headers.acao-regex-help": "Zde zadejte regulární výrazy, které odpovídají dynamickým originálům. Pro zakázání všech stránek, ponechte prázdné.",
|
||||
"headers.acac": "Access-Control-Allow-Credentials",
|
||||
"headers.acam": "Access-Control-Allow-Methods",
|
||||
"headers.acah": "Access-Control-Allow-Headers",
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"headers.acac": "Access-Control-Allow-Credentials",
|
||||
"headers.acam": "Access-Control-Allow-Methods",
|
||||
"headers.acah": "Access-Control-Allow-Headers",
|
||||
"hsts": "Strict Transport Security",
|
||||
"hsts.subdomains": "Include subdomains in HSTS header",
|
||||
"hsts.preload": "Allow preloading of HSTS header",
|
||||
"hsts.help": "An HSTS header is already pre-configured for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. <a href=\"%1\">More information <i class=\"fa fa-external-link\"></i></a>",
|
||||
"traffic-management": "Traffic Management",
|
||||
"traffic.help": "NodeBB deploys equipped with a module that automatically denies requests in high-traffic situations. You can tune these settings here, although the defaults are a good starting point.",
|
||||
"traffic.enable": "Enable Traffic Management",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"details.grant": "Grant/Rescind Ownership",
|
||||
"details.kick": "Kick",
|
||||
"details.kick_confirm": "Are you sure you want to remove this member from the group?",
|
||||
|
||||
"details.add-member": "Add Member",
|
||||
"details.owner_options": "Group Administration",
|
||||
"details.group_name": "Group Name",
|
||||
"details.member_count": "Member Count",
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
"popular-month": "Popular topics this month",
|
||||
"popular-alltime": "All time popular topics",
|
||||
"recent": "Recent Topics",
|
||||
"top": "Top Voted Topics",
|
||||
"top-day": "Top voted topics today",
|
||||
"top-week": "Top voted topics this week",
|
||||
"top-month": "Top voted topics this month",
|
||||
"top-alltime": "Top Voted Topics",
|
||||
"moderator-tools": "Moderator Tools",
|
||||
"flagged-content": "Flagged Content",
|
||||
"ip-blacklist": "IP Blacklist",
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"following": "Following",
|
||||
"blocks": "Blocks",
|
||||
"block_toggle": "Toggle Block",
|
||||
"block_user": "Block User",
|
||||
"unblock_user": "Unblock User",
|
||||
"aboutme": "About me",
|
||||
"signature": "Signature",
|
||||
"birthday": "Birthday",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"edit-posts": "Upraviť príspevky",
|
||||
"view-edit-history": "Zobraziť históriu úprav",
|
||||
"delete-posts": "Odstrániť príspevky",
|
||||
"view_deleted": "View Deleted Posts",
|
||||
"view_deleted": "Zobraziť odstránené príspevky",
|
||||
"upvote-posts": "Súhlasné príspevky",
|
||||
"downvote-posts": "Nesúhlasné príspevky",
|
||||
"delete-topics": "Odstrániť témy",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"headers.acao": "Access-Control-Allow-Origin",
|
||||
"headers.acao-regex": "Access-Control-Allow-Origin Regular Expression",
|
||||
"headers.acao-help": "Ak chcete zamietnuť prístup na všetky stránky, nechajte prázdne",
|
||||
"headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty",
|
||||
"headers.acao-regex-help": "Sem zadajte regulárne výrazy, ktoré zodpovedajú dynamickým originálom. Pre zakázanie všetkých stránok, ponechajte prázdne.",
|
||||
"headers.acac": "Access-Control-Allow-Credentials",
|
||||
"headers.acam": "Access-Control-Allow-Methods",
|
||||
"headers.acah": "Access-Control-Allow-Headers",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"edit-posts": "修改回复",
|
||||
"view-edit-history": "查看修改历史",
|
||||
"delete-posts": "删除回复",
|
||||
"view_deleted": "View Deleted Posts",
|
||||
"view_deleted": "查看已删除回复",
|
||||
"upvote-posts": "顶",
|
||||
"downvote-posts": "踩",
|
||||
"delete-topics": "删除主题",
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
"headers.allow-from": "设置 ALLOW-FROM 来放置 NodeBB 于 iFrame 中",
|
||||
"headers.powered-by": "自定义由 NodeBB 发送的 \"Powered By\" 头部 ",
|
||||
"headers.acao": "Access-Control-Allow-Origin",
|
||||
"headers.acao-regex": "Access-Control-Allow-Origin Regular Expression",
|
||||
"headers.acao-regex": "Access-Control-Allow-Origin 正则表达式",
|
||||
"headers.acao-help": "要拒绝所有网站,请留空",
|
||||
"headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty",
|
||||
"headers.acao-regex-help": "输入正则表达式以匹配动态来源。要拒绝所有网站,请留空",
|
||||
"headers.acac": "Access-Control-Allow-Credentials",
|
||||
"headers.acam": "Access-Control-Allow-Methods",
|
||||
"headers.acah": "Access-Control-Allow-Headers",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"language-settings": "Language Settings",
|
||||
"description": "The default language determines the language settings for all users who are visiting your forum. <br />Individual users can override the default language on their account settings page.",
|
||||
"default-language": "Default Language",
|
||||
"auto-detect": "Auto Detect Language Setting for Guests"
|
||||
"language-settings": "語言設定",
|
||||
"description": "所有使用者的預設語言,使用者可以在個人設定內修改",
|
||||
"default-language": "預設語言",
|
||||
"auto-detect": "使用者為訪客時,自動偵測語言設定"
|
||||
}
|
||||
@@ -6,20 +6,20 @@
|
||||
"no-flags": "Hooray! No flags found.",
|
||||
"assignee": "Assignee",
|
||||
"update": "更新",
|
||||
"updated": "Updated",
|
||||
"updated": "更新完成",
|
||||
"target-purged": "The content this flag referred to has been purged and is no longer available.",
|
||||
|
||||
"quick-filters": "快速篩選",
|
||||
"filter-active": "There are one or more filters active in this list of flags",
|
||||
"filter-reset": "Remove Filters",
|
||||
"filter-reset": "移除篩選",
|
||||
"filters": "Filter Options",
|
||||
"filter-reporterId": "Reporter UID",
|
||||
"filter-targetUid": "Flagged UID",
|
||||
"filter-type": "Flag Type",
|
||||
"filter-type-all": "All Content",
|
||||
"filter-type-post": "文章",
|
||||
"filter-type-user": "User",
|
||||
"filter-state": "State",
|
||||
"filter-type-user": "用戶",
|
||||
"filter-state": "狀態",
|
||||
"filter-assignee": "Assignee UID",
|
||||
"filter-cid": "分類",
|
||||
"filter-quick-mine": "Assigned to me",
|
||||
@@ -32,11 +32,11 @@
|
||||
"start-new-chat": "Start New Chat",
|
||||
"go-to-target": "View Flag Target",
|
||||
|
||||
"user-view": "View Profile",
|
||||
"user-edit": "Edit Profile",
|
||||
"user-view": "查看個人資料",
|
||||
"user-edit": "編輯個人資料",
|
||||
|
||||
"notes": "Flag Notes",
|
||||
"add-note": "Add Note",
|
||||
"notes": "標記備註",
|
||||
"add-note": "新增備註",
|
||||
"no-notes": "No shared notes.",
|
||||
|
||||
"history": "Flag History",
|
||||
@@ -47,7 +47,7 @@
|
||||
"state-open": "New/Open",
|
||||
"state-wip": "Work in Progress",
|
||||
"state-resolved": "Resolved",
|
||||
"state-rejected": "Rejected",
|
||||
"state-rejected": "已拒絕",
|
||||
"no-assignee": "Not Assigned",
|
||||
"note-added": "Note Added",
|
||||
|
||||
|
||||
@@ -5,20 +5,14 @@ define('admin/manage/group', [
|
||||
'forum/groups/memberlist',
|
||||
'iconSelect',
|
||||
'admin/modules/colorpicker',
|
||||
'translator',
|
||||
'benchpress',
|
||||
], function (memberList, iconSelect, colorpicker, translator, Benchpress) {
|
||||
], function (memberList, iconSelect, colorpicker) {
|
||||
var Groups = {};
|
||||
|
||||
Groups.init = function () {
|
||||
var groupDetailsSearch = $('#group-details-search');
|
||||
var groupDetailsSearchResults = $('#group-details-search-results');
|
||||
var groupIcon = $('#group-icon');
|
||||
var changeGroupUserTitle = $('#change-group-user-title');
|
||||
var changeGroupLabelColor = $('#change-group-label-color');
|
||||
var groupLabelPreview = $('#group-label-preview');
|
||||
var searchDelay;
|
||||
|
||||
|
||||
var groupName = ajaxify.data.group.name;
|
||||
|
||||
@@ -36,87 +30,6 @@ define('admin/manage/group', [
|
||||
groupLabelPreview.css('background', changeGroupLabelColor.val() || '#000000');
|
||||
});
|
||||
|
||||
groupDetailsSearch.on('keyup', function () {
|
||||
if (searchDelay) {
|
||||
clearTimeout(searchDelay);
|
||||
}
|
||||
|
||||
searchDelay = setTimeout(function () {
|
||||
var searchText = groupDetailsSearch.val();
|
||||
var foundUser;
|
||||
|
||||
socket.emit('admin.user.search', {
|
||||
query: searchText,
|
||||
}, function (err, results) {
|
||||
if (!err && results && results.users.length > 0) {
|
||||
var numResults = results.users.length;
|
||||
var x;
|
||||
if (numResults > 20) {
|
||||
numResults = 20;
|
||||
}
|
||||
|
||||
groupDetailsSearchResults.empty();
|
||||
|
||||
for (x = 0; x < numResults; x += 1) {
|
||||
foundUser = $('<li />');
|
||||
foundUser
|
||||
.attr({
|
||||
title: results.users[x].username,
|
||||
'data-uid': results.users[x].uid,
|
||||
'data-username': results.users[x].username,
|
||||
'data-userslug': results.users[x].userslug,
|
||||
'data-picture': results.users[x].picture,
|
||||
'data-usericon-bgColor': results.users[x]['icon:bgColor'],
|
||||
'data-usericon-text': results.users[x]['icon:text'],
|
||||
})
|
||||
.append(results.users[x].picture ?
|
||||
$('<img />').addClass('avatar avatar-sm').attr('src', results.users[x].picture) :
|
||||
$('<div />').addClass('avatar avatar-sm').css('background-color', results.users[x]['icon:bgColor']).html(results.users[x]['icon:text']))
|
||||
.append($('<span />').html(results.users[x].username));
|
||||
|
||||
groupDetailsSearchResults.append(foundUser);
|
||||
}
|
||||
} else {
|
||||
groupDetailsSearchResults.translateHtml('<li>[[admin/manage/groups:edit.no-users-found]]</li>');
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
groupDetailsSearchResults.on('click', 'li[data-uid]', function () {
|
||||
var userLabel = $(this);
|
||||
var uid = parseInt(userLabel.attr('data-uid'), 10);
|
||||
|
||||
socket.emit('admin.groups.join', {
|
||||
groupName: groupName,
|
||||
uid: uid,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
var member = {
|
||||
uid: userLabel.attr('data-uid'),
|
||||
username: userLabel.attr('data-username'),
|
||||
userslug: userLabel.attr('data-userslug'),
|
||||
picture: userLabel.attr('data-picture'),
|
||||
'icon:bgColor': userLabel.attr('data-usericon-bgColor'),
|
||||
'icon:text': userLabel.attr('data-usericon-text'),
|
||||
};
|
||||
|
||||
Benchpress.parse('admin/partials/groups/memberlist', 'group.members', {
|
||||
group: {
|
||||
isOwner: ajaxify.data.group.isOwner,
|
||||
members: [member],
|
||||
},
|
||||
}, function (html) {
|
||||
translator.translate(html, function (html) {
|
||||
$('[component="groups/members"] tbody').prepend(html);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('[component="groups/members"]').on('click', '[data-action]', function () {
|
||||
var btnEl = $(this);
|
||||
var userRow = btnEl.parents('[data-uid]');
|
||||
|
||||
@@ -17,7 +17,8 @@ define('forum/account/blocks', ['forum/account/header', 'autocomplete'], functio
|
||||
$('.block-edit').on('click', '[data-action="toggle"]', function () {
|
||||
var uid = parseInt(this.getAttribute('data-uid'), 10);
|
||||
socket.emit('user.toggleBlock', {
|
||||
uid: uid,
|
||||
blockeeUid: uid,
|
||||
blockerUid: ajaxify.data.uid,
|
||||
}, Blocks.refreshList);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -169,10 +169,9 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components'
|
||||
confirmBtn.html('<i class="fa fa-spinner fa-spin"></i>');
|
||||
confirmBtn.prop('disabled', true);
|
||||
|
||||
socket.emit('user.checkPassword', {
|
||||
uid: parseInt(ajaxify.data.uid, 10),
|
||||
socket.emit('user.deleteAccount', {
|
||||
password: $('#confirm-password').val(),
|
||||
}, function (err, ok) {
|
||||
}, function (err) {
|
||||
function restoreButton() {
|
||||
translator.translate('[[modules:bootbox.confirm]]', function (confirmText) {
|
||||
confirmBtn.text(confirmText);
|
||||
@@ -183,19 +182,10 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components'
|
||||
if (err) {
|
||||
restoreButton();
|
||||
return app.alertError(err.message);
|
||||
} else if (!ok) {
|
||||
restoreButton();
|
||||
return app.alertError('[[error:invalid-password]]');
|
||||
}
|
||||
|
||||
confirmBtn.html('<i class="fa fa-check"></i>');
|
||||
socket.emit('user.deleteAccount', {}, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
window.location.href = config.relative_path + '/';
|
||||
});
|
||||
window.location.href = config.relative_path + '/';
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
@@ -53,6 +53,7 @@ define('forum/account/header', [
|
||||
components.get('account/unban').on('click', unbanAccount);
|
||||
components.get('account/delete').on('click', deleteAccount);
|
||||
components.get('account/flag').on('click', flagAccount);
|
||||
components.get('account/block').on('click', toggleBlockAccount);
|
||||
};
|
||||
|
||||
function hidePrivateLinks() {
|
||||
@@ -191,6 +192,25 @@ define('forum/account/header', [
|
||||
});
|
||||
}
|
||||
|
||||
function toggleBlockAccount() {
|
||||
var targetEl = this;
|
||||
socket.emit('user.toggleBlock', {
|
||||
blockeeUid: ajaxify.data.uid,
|
||||
blockerUid: app.user.uid,
|
||||
}, function (err, blocked) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
translator.translate('[[user:' + (blocked ? 'unblock' : 'block') + '_user]]', function (label) {
|
||||
$(targetEl).text(label);
|
||||
});
|
||||
});
|
||||
|
||||
// Keep dropdown open
|
||||
return false;
|
||||
}
|
||||
|
||||
function removeCover() {
|
||||
translator.translate('[[user:remove_cover_picture_confirm]]', function (translated) {
|
||||
bootbox.confirm(translated, function (confirm) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
define('forum/groups/memberlist', ['components', 'forum/infinitescroll'], function () {
|
||||
define('forum/groups/memberlist', ['autocomplete'], function (autocomplete) {
|
||||
var MemberList = {};
|
||||
var searchInterval;
|
||||
var groupName;
|
||||
@@ -11,10 +11,40 @@ define('forum/groups/memberlist', ['components', 'forum/infinitescroll'], functi
|
||||
templateName = _templateName || 'groups/details';
|
||||
groupName = ajaxify.data.group.name;
|
||||
|
||||
handleMemberAdd();
|
||||
handleMemberSearch();
|
||||
handleMemberInfiniteScroll();
|
||||
};
|
||||
|
||||
function handleMemberAdd() {
|
||||
$('[component="groups/members/add"]').on('click', function () {
|
||||
var modal = bootbox.dialog({
|
||||
title: '[[groups:details.add-member]]',
|
||||
message: '<input class="form-control" type="text" placeholder="[[global:search]]"/>',
|
||||
});
|
||||
autocomplete.user(modal.find('input'), function (ev, ui) {
|
||||
var user = ui.item.user;
|
||||
if (user) {
|
||||
addUserToGroup(user, function () {
|
||||
modal.modal('hide');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addUserToGroup(user, callback) {
|
||||
socket.emit('groups.addMember', { groupName: groupName, uid: user.uid }, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err);
|
||||
}
|
||||
parseAndTranslate([user], function (html) {
|
||||
$('[component="groups/members"] tbody').prepend(html);
|
||||
});
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function handleMemberSearch() {
|
||||
$('[component="groups/members/search"]').on('keyup', function () {
|
||||
var query = $(this).val();
|
||||
|
||||
@@ -91,6 +91,9 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) {
|
||||
canBanUser: function (next) {
|
||||
privileges.users.canBanUser(callerUID, uid, next);
|
||||
},
|
||||
isBlocked: function (next) {
|
||||
user.blocks.is(uid, callerUID, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
@@ -129,6 +132,11 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) {
|
||||
userData.moderationNote = undefined;
|
||||
}
|
||||
|
||||
userData.isBlocked = results.isBlocked;
|
||||
if (isAdmin || isSelf) {
|
||||
userData.blocksCount = parseInt(userData.blocksCount, 10) || 0;
|
||||
}
|
||||
|
||||
userData.yourid = callerUID;
|
||||
userData.theirid = userData.uid;
|
||||
userData.isTargetAdmin = results.isTargetAdmin;
|
||||
@@ -165,7 +173,6 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) {
|
||||
userData.websiteName = userData.website.replace(validator.escape('http://'), '').replace(validator.escape('https://'), '');
|
||||
userData.followingCount = parseInt(userData.followingCount, 10) || 0;
|
||||
userData.followerCount = parseInt(userData.followerCount, 10) || 0;
|
||||
userData.blocksCount = parseInt(userData.blocksCount, 10) || 0;
|
||||
|
||||
userData.email = validator.escape(String(userData.email || ''));
|
||||
userData.fullname = validator.escape(String(userData.fullname || ''));
|
||||
|
||||
@@ -39,32 +39,41 @@ Controllers.errors = require('./errors');
|
||||
Controllers.composer = require('./composer');
|
||||
|
||||
Controllers.reset = function (req, res, next) {
|
||||
const renderReset = function (code, valid) {
|
||||
res.render('reset_code', {
|
||||
valid: valid,
|
||||
displayExpiryNotice: req.session.passwordExpired,
|
||||
code: code,
|
||||
minimumPasswordLength: parseInt(meta.config.minimumPasswordLength, 10),
|
||||
minimumPasswordStrength: parseInt(meta.config.minimumPasswordStrength, 10),
|
||||
breadcrumbs: helpers.buildBreadcrumbs([
|
||||
{
|
||||
text: '[[reset_password:reset_password]]',
|
||||
url: '/reset',
|
||||
},
|
||||
{
|
||||
text: '[[reset_password:update_password]]',
|
||||
},
|
||||
]),
|
||||
title: '[[pages:reset]]',
|
||||
});
|
||||
delete req.session.passwordExpired;
|
||||
};
|
||||
|
||||
if (req.params.code) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.reset.validate(req.params.code, next);
|
||||
},
|
||||
function (valid) {
|
||||
res.render('reset_code', {
|
||||
valid: valid,
|
||||
displayExpiryNotice: req.session.passwordExpired,
|
||||
code: req.params.code,
|
||||
minimumPasswordLength: parseInt(meta.config.minimumPasswordLength, 10),
|
||||
minimumPasswordStrength: parseInt(meta.config.minimumPasswordStrength, 10),
|
||||
breadcrumbs: helpers.buildBreadcrumbs([
|
||||
{
|
||||
text: '[[reset_password:reset_password]]',
|
||||
url: '/reset',
|
||||
},
|
||||
{
|
||||
text: '[[reset_password:update_password]]',
|
||||
},
|
||||
]),
|
||||
title: '[[pages:reset]]',
|
||||
});
|
||||
delete req.session.passwordExpired;
|
||||
},
|
||||
], next);
|
||||
// Save to session and redirect
|
||||
req.session.reset_code = req.params.code;
|
||||
res.redirect(nconf.get('relative_path') + '/reset');
|
||||
} else if (req.session.reset_code) {
|
||||
// Validate and save to local variable before removing from session
|
||||
user.reset.validate(req.session.reset_code, function (err, valid) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
renderReset(req.session.reset_code, valid);
|
||||
delete req.session.reset_code;
|
||||
});
|
||||
} else {
|
||||
res.render('reset', {
|
||||
code: null,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var validator = require('validator');
|
||||
|
||||
var helpers = require('./helpers');
|
||||
var recentController = require('./recent');
|
||||
@@ -26,7 +26,7 @@ popularController.get = function (req, res, next) {
|
||||
data.breadcrumbs = helpers.buildBreadcrumbs(breadcrumbs);
|
||||
}
|
||||
var feedQs = data.rssFeedUrl.split('?')[1];
|
||||
data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (req.query.term || 'alltime') + '.rss';
|
||||
data.rssFeedUrl = nconf.get('relative_path') + '/popular/' + (validator.escape(String(req.query.term)) || 'alltime') + '.rss';
|
||||
if (req.loggedIn) {
|
||||
data.rssFeedUrl += '?' + feedQs;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ recentController.getData = function (req, url, sort, callback) {
|
||||
var rssToken;
|
||||
|
||||
if (!helpers.validFilters[filter] || (!term && req.query.term)) {
|
||||
return callback();
|
||||
return callback(null, null);
|
||||
}
|
||||
term = term || 'alltime';
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var validator = require('validator');
|
||||
|
||||
var helpers = require('./helpers');
|
||||
var recentController = require('./recent');
|
||||
|
||||
var topController = module.exports;
|
||||
@@ -15,6 +19,16 @@ topController.get = function (req, res, next) {
|
||||
if (!data) {
|
||||
return next();
|
||||
}
|
||||
var term = helpers.terms[req.query.term] || 'alltime';
|
||||
if (req.originalUrl.startsWith(nconf.get('relative_path') + '/api/top') || req.originalUrl.startsWith(nconf.get('relative_path') + '/top')) {
|
||||
data.title = '[[pages:top-' + term + ']]';
|
||||
}
|
||||
|
||||
var feedQs = data.rssFeedUrl.split('?')[1];
|
||||
data.rssFeedUrl = nconf.get('relative_path') + '/top/' + (validator.escape(String(req.query.term)) || 'alltime') + '.rss';
|
||||
if (req.loggedIn) {
|
||||
data.rssFeedUrl += '?' + feedQs;
|
||||
}
|
||||
res.render('top', data);
|
||||
},
|
||||
], next);
|
||||
|
||||
@@ -112,7 +112,7 @@ if (process.env.minifier_child) {
|
||||
if (err) {
|
||||
process.send({
|
||||
type: 'error',
|
||||
message: err.stack,
|
||||
message: err.stack || err.message || 'unknown error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -277,7 +277,7 @@ function buildCSS(data, callback) {
|
||||
from: undefined,
|
||||
}).then(function (result) {
|
||||
process.nextTick(callback, null, { code: result.css });
|
||||
}, function (err) {
|
||||
}).catch(function (err) {
|
||||
process.nextTick(callback, err);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,9 +104,9 @@ Themes.get = function (callback) {
|
||||
// Minor adjustments for API output
|
||||
configObj.type = 'local';
|
||||
if (configObj.screenshot) {
|
||||
configObj.screenshot_url = 'css/previews/' + encodeURIComponent(configObj.id);
|
||||
configObj.screenshot_url = nconf.get('relative_path') + '/css/previews/' + encodeURIComponent(configObj.id);
|
||||
} else {
|
||||
configObj.screenshot_url = 'assets/images/themes/default.png';
|
||||
configObj.screenshot_url = nconf.get('relative_path') + '/assets/images/themes/default.png';
|
||||
}
|
||||
next(null, configObj);
|
||||
} catch (err) {
|
||||
|
||||
@@ -223,16 +223,4 @@ module.exports = function (middleware) {
|
||||
return next();
|
||||
}
|
||||
};
|
||||
|
||||
middleware.handleBlocking = function (req, res, next) {
|
||||
user.blocks.is(res.locals.uid, req.uid, function (err, blocked) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
} else if (blocked) {
|
||||
res.status(404).render('404', { title: '[[global:404.title]]' });
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ var express = require('express');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var hotswap = require('./hotswap');
|
||||
var file = require('./file');
|
||||
|
||||
var app;
|
||||
var middleware;
|
||||
@@ -138,56 +137,6 @@ Plugins.reloadRoutes = function (callback) {
|
||||
});
|
||||
};
|
||||
|
||||
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
|
||||
|
||||
// DEPRECATED: remove in v1.8.0
|
||||
Plugins.getTemplates = function (callback) {
|
||||
var templates = {};
|
||||
var tplName;
|
||||
|
||||
winston.warn('[deprecated] Plugins.getTemplates is DEPRECATED to be removed in v1.8.0');
|
||||
|
||||
Plugins.data.getActive(function (err, plugins) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.eachSeries(plugins, function (plugin, next) {
|
||||
if (plugin.templates || themeNamePattern.test(plugin.id)) {
|
||||
winston.verbose('[plugins] Loading templates (' + plugin.id + ')');
|
||||
var templatesPath = path.join(__dirname, '../node_modules', plugin.id, plugin.templates || 'templates');
|
||||
file.walk(templatesPath, function (err, pluginTemplates) {
|
||||
if (pluginTemplates) {
|
||||
pluginTemplates.forEach(function (pluginTemplate) {
|
||||
if (pluginTemplate.endsWith('.tpl')) {
|
||||
tplName = '/' + pluginTemplate.replace(templatesPath, '').substring(1);
|
||||
|
||||
if (templates.hasOwnProperty(tplName)) {
|
||||
winston.verbose('[plugins] ' + tplName + ' replaced by ' + plugin.id);
|
||||
}
|
||||
|
||||
templates[tplName] = pluginTemplate;
|
||||
} else {
|
||||
winston.warn('[plugins] Skipping ' + pluginTemplate + ' by plugin ' + plugin.id);
|
||||
}
|
||||
});
|
||||
} else if (err) {
|
||||
winston.error(err);
|
||||
} else {
|
||||
winston.warn('[plugins/' + plugin.id + '] A templates directory was defined for this plugin, but was not found.');
|
||||
}
|
||||
|
||||
next(false);
|
||||
});
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
}, function (err) {
|
||||
callback(err, templates);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Plugins.get = function (id, callback) {
|
||||
var url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins/' + id;
|
||||
|
||||
@@ -228,6 +177,7 @@ Plugins.list = function (matching, callback) {
|
||||
};
|
||||
|
||||
Plugins.normalise = function (apiReturn, callback) {
|
||||
var themeNamePattern = /^(@.*?\/)?nodebb-theme-.*$/;
|
||||
var pluginMap = {};
|
||||
var dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
|
||||
apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
|
||||
|
||||
@@ -4,8 +4,8 @@ var helpers = require('./helpers');
|
||||
var setupPageRoute = helpers.setupPageRoute;
|
||||
|
||||
module.exports = function (app, middleware, controllers) {
|
||||
var middlewares = [middleware.checkGlobalPrivacySettings, middleware.exposeUid, middleware.handleBlocking];
|
||||
var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, middleware.exposeUid, middleware.handleBlocking];
|
||||
var middlewares = [middleware.checkGlobalPrivacySettings, middleware.exposeUid];
|
||||
var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions, middleware.exposeUid];
|
||||
|
||||
setupPageRoute(app, '/me/*', middleware, [], middleware.redirectMeToUserslug);
|
||||
setupPageRoute(app, '/uid/:uid*', middleware, [], middleware.redirectUidToUserslug);
|
||||
|
||||
@@ -16,11 +16,19 @@ var db = require('../database');
|
||||
var utils = require('../utils');
|
||||
var controllers404 = require('../controllers/404.js');
|
||||
|
||||
var terms = {
|
||||
daily: 'day',
|
||||
weekly: 'week',
|
||||
monthly: 'month',
|
||||
alltime: 'alltime',
|
||||
};
|
||||
|
||||
module.exports = function (app, middleware) {
|
||||
app.get('/topic/:topic_id.rss', middleware.maintenanceMode, generateForTopic);
|
||||
app.get('/category/:category_id.rss', middleware.maintenanceMode, generateForCategory);
|
||||
app.get('/recent.rss', middleware.maintenanceMode, generateForRecent);
|
||||
app.get('/top.rss', middleware.maintenanceMode, generateForTop);
|
||||
app.get('/top/:term.rss', middleware.maintenanceMode, generateForTop);
|
||||
app.get('/popular.rss', middleware.maintenanceMode, generateForPopular);
|
||||
app.get('/popular/:term.rss', middleware.maintenanceMode, generateForPopular);
|
||||
app.get('/recentposts.rss', middleware.maintenanceMode, generateForRecentPosts);
|
||||
@@ -212,7 +220,8 @@ function generateForTop(req, res, next) {
|
||||
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
|
||||
return controllers404.send404(req, res);
|
||||
}
|
||||
|
||||
var term = terms[req.params.term] || 'day';
|
||||
var uid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (req.query.token && req.query.uid) {
|
||||
@@ -222,13 +231,27 @@ function generateForTop(req, res, next) {
|
||||
}
|
||||
},
|
||||
function (token, next) {
|
||||
generateForTopics({
|
||||
uid: token && token === req.query.token ? req.query.uid : req.uid,
|
||||
uid = token && token === req.query.token ? req.query.uid : req.uid;
|
||||
|
||||
topics.getSortedTopics({
|
||||
uid: uid,
|
||||
start: 0,
|
||||
stop: 19,
|
||||
term: term,
|
||||
sort: 'votes',
|
||||
}, next);
|
||||
},
|
||||
function (result, next) {
|
||||
generateTopicsFeed({
|
||||
uid: uid,
|
||||
title: 'Top Voted Topics',
|
||||
description: 'A list of topics that have received the most votes',
|
||||
feed_url: '/top.rss',
|
||||
site_url: '/top',
|
||||
}, 'topics:votes', req, res, next);
|
||||
feed_url: '/top/' + (req.params.term || 'daily') + '.rss',
|
||||
site_url: '/top/' + (req.params.term || 'daily'),
|
||||
}, result.topics, next);
|
||||
},
|
||||
function (feed) {
|
||||
sendFeed(feed, res);
|
||||
},
|
||||
], next);
|
||||
}
|
||||
@@ -237,12 +260,7 @@ function generateForPopular(req, res, next) {
|
||||
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
|
||||
return controllers404.send404(req, res);
|
||||
}
|
||||
var terms = {
|
||||
daily: 'day',
|
||||
weekly: 'week',
|
||||
monthly: 'month',
|
||||
alltime: 'alltime',
|
||||
};
|
||||
|
||||
var term = terms[req.params.term] || 'day';
|
||||
var uid;
|
||||
async.waterfall([
|
||||
|
||||
@@ -71,14 +71,27 @@ SocketGroups.leave = function (socket, data, callback) {
|
||||
groups.leave(data.groupName, socket.uid, callback);
|
||||
};
|
||||
|
||||
SocketGroups.addMember = isOwner(function (socket, data, callback) {
|
||||
if (data.groupName === 'administrators' || groups.isPrivilegeGroup(data.groupName)) {
|
||||
return callback(new Error('[[error:not-allowed]]'));
|
||||
}
|
||||
groups.join(data.groupName, data.uid, callback);
|
||||
});
|
||||
|
||||
function isOwner(next) {
|
||||
return function (socket, data, callback) {
|
||||
async.parallel({
|
||||
isAdmin: async.apply(user.isAdministrator, socket.uid),
|
||||
isGlobalModerator: async.apply(user.isGlobalModerator, socket.uid),
|
||||
isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName),
|
||||
group: async.apply(groups.getGroupData, data.groupName),
|
||||
}, function (err, results) {
|
||||
if (err || (!results.isOwner && !results.isAdmin)) {
|
||||
return callback(err || new Error('[[error:no-privileges]]'));
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var isOwner = results.isOwner || results.isAdmin || (results.isGlobalModerator && !results.group.system);
|
||||
if (!isOwner) {
|
||||
return callback(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
next(socket, data, callback);
|
||||
});
|
||||
|
||||
@@ -36,6 +36,11 @@ SocketUser.deleteAccount = function (socket, data, callback) {
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.isPasswordCorrect(socket.uid, data.password, function (err, ok) {
|
||||
next(err || !ok ? new Error('[[error:invalid-password]]') : undefined);
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
user.isAdministrator(socket.uid, next);
|
||||
},
|
||||
@@ -56,7 +61,15 @@ SocketUser.deleteAccount = function (socket, data, callback) {
|
||||
});
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
], function (err) {
|
||||
if (err) {
|
||||
return setTimeout(function () {
|
||||
callback(err);
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
SocketUser.emailExists = function (socket, data, callback) {
|
||||
@@ -115,6 +128,7 @@ SocketUser.reset.commit = function (socket, data, callback) {
|
||||
async.parallel({
|
||||
uid: async.apply(db.getObjectField, 'reset:uid', data.code),
|
||||
reset: async.apply(user.reset.commit, data.code, data.password),
|
||||
hook: async.apply(plugins.fireHook, 'action:password.reset', { uid: socket.uid }),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
|
||||
@@ -109,13 +109,6 @@ module.exports = function (SocketUser) {
|
||||
], callback);
|
||||
}
|
||||
|
||||
SocketUser.checkPassword = function (socket, data, callback) {
|
||||
isPrivilegedOrSelfAndPasswordMatch(socket.uid, data, function (err) {
|
||||
// Return a bool (without delayed response to prevent brute-force checking of password validity)
|
||||
setTimeout(callback.bind(null, null, !err), 1000);
|
||||
});
|
||||
};
|
||||
|
||||
SocketUser.changePassword = function (socket, data, callback) {
|
||||
if (!socket.uid) {
|
||||
return callback(new Error('[[error:invalid-uid]]'));
|
||||
@@ -208,19 +201,24 @@ module.exports = function (SocketUser) {
|
||||
};
|
||||
|
||||
SocketUser.toggleBlock = function (socket, data, callback) {
|
||||
let current;
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.blocks.can(data.uid, next);
|
||||
user.blocks.can(socket.uid, data.blockerUid, data.blockeeUid, next);
|
||||
},
|
||||
function (can, next) {
|
||||
if (!can) {
|
||||
return next(new Error('[[error:cannot-block-privileged]]'));
|
||||
}
|
||||
user.blocks.is(data.uid, socket.uid, next);
|
||||
user.blocks.is(data.blockeeUid, data.blockerUid, next);
|
||||
},
|
||||
function (is, next) {
|
||||
user.blocks[is ? 'remove' : 'add'](data.uid, socket.uid, next);
|
||||
current = is;
|
||||
user.blocks[is ? 'remove' : 'add'](data.blockeeUid, data.blockerUid, next);
|
||||
},
|
||||
], callback);
|
||||
], function (err) {
|
||||
callback(err, !current);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ start.start = function () {
|
||||
var webserver = require('./webserver');
|
||||
require('./socket.io').init(webserver.server);
|
||||
|
||||
if (nconf.get('isPrimary') === 'true' && !nconf.get('jobsDisabled')) {
|
||||
if (nconf.get('runJobs')) {
|
||||
require('./notifications').startJobs();
|
||||
require('./user').startJobs();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ module.exports = {
|
||||
name: 'Hash all IP addresses stored in Recent IPs zset',
|
||||
timestamp: Date.UTC(2017, 5, 22),
|
||||
method: function (callback) {
|
||||
const progress = this.progress;
|
||||
var hashed = /[a-f0-9]{32}/;
|
||||
let hash;
|
||||
|
||||
@@ -18,6 +19,7 @@ module.exports = {
|
||||
async.each(ips, function (set, next) {
|
||||
// Short circuit if already processed
|
||||
if (hashed.test(set.value)) {
|
||||
progress.incr();
|
||||
return setImmediate(next);
|
||||
}
|
||||
|
||||
@@ -26,8 +28,14 @@ module.exports = {
|
||||
async.series([
|
||||
async.apply(db.sortedSetAdd, 'ip:recent', set.score, hash),
|
||||
async.apply(db.sortedSetRemove, 'ip:recent', set.value),
|
||||
], next);
|
||||
], function (err) {
|
||||
progress.incr();
|
||||
next(err);
|
||||
});
|
||||
}, next);
|
||||
}, { withScores: 1 }, callback);
|
||||
}, {
|
||||
withScores: 1,
|
||||
progress: this.progress,
|
||||
}, callback);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var db = require('../database');
|
||||
var LRU = require('lru-cache');
|
||||
|
||||
|
||||
var db = require('../database');
|
||||
var pubsub = require('../pubsub');
|
||||
|
||||
module.exports = function (User) {
|
||||
User.blocks = {
|
||||
_cache: LRU({
|
||||
@@ -19,9 +22,29 @@ module.exports = function (User) {
|
||||
});
|
||||
};
|
||||
|
||||
User.blocks.can = function (uid, callback) {
|
||||
User.blocks.can = function (callerUid, blockerUid, blockeeUid, callback) {
|
||||
// Administrators and global moderators cannot be blocked
|
||||
User.isAdminOrGlobalMod(uid, (err, can) => callback(err, !can));
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
isCallerAdminOrMod: function (next) {
|
||||
User.isAdminOrGlobalMod(callerUid, next);
|
||||
},
|
||||
isBlockeeAdminOrMod: function (next) {
|
||||
User.isAdminOrGlobalMod(blockeeUid, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
if (results.isBlockeeAdminOrMod) {
|
||||
return callback(null, false);
|
||||
}
|
||||
if (parseInt(callerUid, 10) !== parseInt(blockerUid, 10) && !results.isCallerAdminOrMod) {
|
||||
return callback(null, false);
|
||||
}
|
||||
next(null, true);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
User.blocks.list = function (uid, callback) {
|
||||
@@ -35,11 +58,15 @@ module.exports = function (User) {
|
||||
}
|
||||
|
||||
blocked = blocked.map(uid => parseInt(uid, 10)).filter(Boolean);
|
||||
User.blocks._cache.set(uid, blocked);
|
||||
User.blocks._cache.set(parseInt(uid, 10), blocked);
|
||||
callback(null, blocked);
|
||||
});
|
||||
};
|
||||
|
||||
pubsub.on('user:blocks:cache:del', function (uid) {
|
||||
User.blocks._cache.del(uid);
|
||||
});
|
||||
|
||||
User.blocks.add = function (targetUid, uid, callback) {
|
||||
async.waterfall([
|
||||
async.apply(this.applyChecks, true, targetUid, uid),
|
||||
@@ -47,9 +74,9 @@ module.exports = function (User) {
|
||||
async.apply(User.incrementUserFieldBy, uid, 'blocksCount', 1),
|
||||
function (_blank, next) {
|
||||
User.blocks._cache.del(uid);
|
||||
pubsub.publish('user:blocks:cache:del', uid);
|
||||
setImmediate(next);
|
||||
},
|
||||
async.apply(User.blocks.list, uid),
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -60,9 +87,9 @@ module.exports = function (User) {
|
||||
async.apply(User.decrementUserFieldBy, uid, 'blocksCount', 1),
|
||||
function (_blank, next) {
|
||||
User.blocks._cache.del(uid);
|
||||
pubsub.publish('user:blocks:cache:del', uid);
|
||||
setImmediate(next);
|
||||
},
|
||||
async.apply(User.blocks.list, uid),
|
||||
], callback);
|
||||
};
|
||||
|
||||
|
||||
@@ -200,6 +200,7 @@ module.exports = function (User) {
|
||||
async.series([
|
||||
async.apply(db.sortedSetRemove, 'email:uid', oldEmail.toLowerCase()),
|
||||
async.apply(db.sortedSetRemove, 'email:sorted', oldEmail.toLowerCase() + ':' + uid),
|
||||
async.apply(User.auth.revokeAllSessions, uid),
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
@@ -339,6 +340,7 @@ module.exports = function (User) {
|
||||
}),
|
||||
async.apply(User.reset.updateExpiry, data.uid),
|
||||
async.apply(User.auth.revokeAllSessions, data.uid),
|
||||
async.apply(plugins.fireHook, 'action:password.change', { uid: uid }),
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
|
||||
@@ -74,13 +74,6 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label for="add-member">[[admin/manage/groups:edit.add-user]]</label>
|
||||
<input type="text" class="form-control" id="group-details-search" placeholder="[[admin/manage/groups:edit.add-user-search]]" />
|
||||
<ul class="members user-list" id="group-details-search-results"></ul>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" component="groups/members/search" placeholder="[[global:search]]"/>
|
||||
<span class="input-group-addon search-button"><i class="fa fa-search"></i></span>
|
||||
</div><br />
|
||||
<div class="row">
|
||||
<!-- IF group.isOwner -->
|
||||
<div class="col-lg-1">
|
||||
<button component="groups/members/add" type="button" class="btn btn-primary" title="[[groups:details.add-member]]"><i class="fa fa-user-plus"></i></button>
|
||||
</div>
|
||||
<!-- ENDIF group.isOwner -->
|
||||
<div class="<!-- IF group.isOwner -->col-lg-11<!-- ELSE -->col-lg-12<!-- ENDIF group.isOwner -->">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" component="groups/members/search" placeholder="[[global:search]]"/>
|
||||
<span class="input-group-addon search-button"><i class="fa fa-search"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table component="groups/members" class="table table-striped table-hover" data-nextstart="{group.membersNextStart}">
|
||||
<tbody>
|
||||
@@ -10,9 +19,9 @@
|
||||
<td>
|
||||
<a href="{config.relative_path}/user/{group.members.userslug}">
|
||||
<!-- IF group.members.picture -->
|
||||
<img class="avatar avatar-sm" src="{group.members.picture}" />
|
||||
<img class="avatar avatar-sm avatar-rounded" src="{group.members.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {group.members.icon:bgColor};">{group.members.icon:text}</div>
|
||||
<div class="avatar avatar-sm avatar-rounded" style="background-color: {group.members.icon:bgColor};">{group.members.icon:text}</div>
|
||||
<!-- ENDIF group.members.picture -->
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- BEGIN themes -->
|
||||
<div class="col-lg-4 col-md-6 col-sm-12 col-xs-12" data-type="{themes.type}" data-theme="{themes.id}"<!-- IF themes.css --> data-css="{themes.css}"<!-- ENDIF themes.css -->>
|
||||
<div class="theme-card mdl-card mdl-shadow--2dp">
|
||||
<div class="mdl-card__title mdl-card--expand" style="background-image: url('{relative_path}/{themes.screenshot_url}');"></div>
|
||||
<div class="mdl-card__title mdl-card--expand" style="background-image: url('{themes.screenshot_url}');"></div>
|
||||
<div class="mdl-card__supporting-text">
|
||||
<h2 class="mdl-card__title-text">{themes.name}</h2>
|
||||
<p>
|
||||
|
||||
@@ -63,6 +63,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/advanced:hsts]]</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="hsts-maxage">[[admin/settings/advanced:hsts.maxAge]]</label>
|
||||
<input class="form-control" id="hsts-maxage" type="number" placeholder="31536000" data-field="hsts-maxage" /><br />
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="hsts-subdomains" checked>
|
||||
<span class="mdl-switch__label"><strong>[[admin/settings/advanced:hsts.subdomains]]</strong></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="hsts-preload">
|
||||
<span class="mdl-switch__label"><strong>[[admin/settings/advanced:hsts.preload]]</strong></span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
[[admin/settings/advanced:hsts.help, https:\/\/hstspreload.org\/]]
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/advanced:traffic-management]]</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
|
||||
@@ -195,6 +195,11 @@ function setupExpressApp(app, callback) {
|
||||
|
||||
app.use(helmet());
|
||||
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));
|
||||
app.use(helmet.hsts({
|
||||
maxAge: parseInt(meta.config['hsts-maxage'], 10) || 31536000,
|
||||
includeSubdomains: !!parseInt(meta.config['hsts-subdomains'], 10),
|
||||
preload: !!parseInt(meta.config['hsts-preload'], 10),
|
||||
}));
|
||||
app.use(middleware.addHeaders);
|
||||
app.use(middleware.processRender);
|
||||
auth.initialize(app, middleware);
|
||||
|
||||
@@ -2151,7 +2151,6 @@ describe('Controllers', function () {
|
||||
request(nconf.get('url') + '/api/compose', { json: true }, function (err, res, body) {
|
||||
assert.ifError(err);
|
||||
assert.equal(res.statusCode, 404);
|
||||
console.log(body);
|
||||
|
||||
plugins.unregisterHook('myTestPlugin', 'filter:composer.build', hookMethod);
|
||||
done();
|
||||
|
||||
114
test/user.js
114
test/user.js
@@ -641,30 +641,33 @@ describe('User', function () {
|
||||
});
|
||||
|
||||
it('should update a user\'s profile', function (done) {
|
||||
var data = {
|
||||
uid: uid,
|
||||
username: 'updatedUserName',
|
||||
email: 'updatedEmail@me.com',
|
||||
fullname: 'updatedFullname',
|
||||
website: 'http://nodebb.org',
|
||||
location: 'izmir',
|
||||
groupTitle: 'testGroup',
|
||||
birthday: '01/01/1980',
|
||||
signature: 'nodebb is good',
|
||||
};
|
||||
socketUser.updateProfile({ uid: uid }, data, function (err, result) {
|
||||
User.create({ username: 'justforupdate', email: 'just@for.updated', password: '123456' }, function (err, uid) {
|
||||
assert.ifError(err);
|
||||
|
||||
assert.equal(result.username, 'updatedUserName');
|
||||
assert.equal(result.userslug, 'updatedusername');
|
||||
assert.equal(result.email, 'updatedEmail@me.com');
|
||||
|
||||
db.getObject('user:' + uid, function (err, userData) {
|
||||
var data = {
|
||||
uid: uid,
|
||||
username: 'updatedUserName',
|
||||
email: 'updatedEmail@me.com',
|
||||
fullname: 'updatedFullname',
|
||||
website: 'http://nodebb.org',
|
||||
location: 'izmir',
|
||||
groupTitle: 'testGroup',
|
||||
birthday: '01/01/1980',
|
||||
signature: 'nodebb is good',
|
||||
};
|
||||
socketUser.updateProfile({ uid: uid }, data, function (err, result) {
|
||||
assert.ifError(err);
|
||||
Object.keys(data).forEach(function (key) {
|
||||
assert.equal(data[key], userData[key]);
|
||||
|
||||
assert.equal(result.username, 'updatedUserName');
|
||||
assert.equal(result.userslug, 'updatedusername');
|
||||
assert.equal(result.email, 'updatedEmail@me.com');
|
||||
|
||||
db.getObject('user:' + uid, function (err, userData) {
|
||||
assert.ifError(err);
|
||||
Object.keys(data).forEach(function (key) {
|
||||
assert.equal(data[key], userData[key]);
|
||||
});
|
||||
done();
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -699,20 +702,23 @@ describe('User', function () {
|
||||
assert.ifError(err);
|
||||
db.getSortedSetRevRange('user:' + uid + ':usernames', 0, -1, function (err, data) {
|
||||
assert.ifError(err);
|
||||
assert.equal(data.length, 1);
|
||||
assert(data[0].startsWith('updatedAgain'));
|
||||
assert(data[1].startsWith('updatedUserName'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should change email', function (done) {
|
||||
socketUser.changeUsernameEmail({ uid: uid }, { uid: uid, email: 'updatedAgain@me.com', password: '123456' }, function (err) {
|
||||
User.create({ username: 'pooremailupdate', email: 'poor@update.me', password: '123456' }, function (err, uid) {
|
||||
assert.ifError(err);
|
||||
db.getObjectField('user:' + uid, 'email', function (err, email) {
|
||||
socketUser.changeUsernameEmail({ uid: uid }, { uid: uid, email: 'updatedAgain@me.com', password: '123456' }, function (err) {
|
||||
assert.ifError(err);
|
||||
assert.equal(email, 'updatedAgain@me.com');
|
||||
done();
|
||||
db.getObjectField('user:' + uid, 'email', function (err, email) {
|
||||
assert.ifError(err);
|
||||
assert.equal(email, 'updatedAgain@me.com');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1791,14 +1797,41 @@ describe('User', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.toggle()', function () {
|
||||
it('should toggle block', function (done) {
|
||||
socketUser.toggleBlock({ uid: 1 }, { blockerUid: 1, blockeeUid: blockeeUid }, function (err) {
|
||||
assert.ifError(err);
|
||||
User.blocks.is(blockeeUid, 1, function (err, blocked) {
|
||||
assert.ifError(err);
|
||||
assert(blocked);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should toggle block', function (done) {
|
||||
socketUser.toggleBlock({ uid: 1 }, { blockerUid: 1, blockeeUid: blockeeUid }, function (err) {
|
||||
assert.ifError(err);
|
||||
User.blocks.is(blockeeUid, 1, function (err, blocked) {
|
||||
assert.ifError(err);
|
||||
assert(!blocked);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('.add()', function () {
|
||||
it('should block a uid', function (done) {
|
||||
User.blocks.add(blockeeUid, 1, function (err, blocked_uids) {
|
||||
User.blocks.add(blockeeUid, 1, function (err) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(Array.isArray(blocked_uids), true);
|
||||
assert.strictEqual(blocked_uids.length, 1);
|
||||
assert.strictEqual(blocked_uids.includes(blockeeUid), true);
|
||||
done();
|
||||
User.blocks.list(1, function (err, blocked_uids) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(Array.isArray(blocked_uids), true);
|
||||
assert.strictEqual(blocked_uids.length, 1);
|
||||
assert.strictEqual(blocked_uids.includes(blockeeUid), true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1820,11 +1853,14 @@ describe('User', function () {
|
||||
|
||||
describe('.remove()', function () {
|
||||
it('should unblock a uid', function (done) {
|
||||
User.blocks.remove(blockeeUid, 1, function (err, blocked_uids) {
|
||||
User.blocks.remove(blockeeUid, 1, function (err) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(Array.isArray(blocked_uids), true);
|
||||
assert.strictEqual(blocked_uids.length, 0);
|
||||
done();
|
||||
User.blocks.list(1, function (err, blocked_uids) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(Array.isArray(blocked_uids), true);
|
||||
assert.strictEqual(blocked_uids.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1929,6 +1965,14 @@ describe('User', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter uids that are blocking targetUid', function (done) {
|
||||
User.blocks.filterUids(blockeeUid, [1, 2], function (err, filtered) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(filtered, [2]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user