Merge branch 'master' into develop

v1.18.x
Julian Lam 8 years ago
commit af2d9fb85b

@ -52,7 +52,7 @@
"morgan": "^1.3.2", "morgan": "^1.3.2",
"mousetrap": "^1.5.3", "mousetrap": "^1.5.3",
"nconf": "~0.8.2", "nconf": "~0.8.2",
"nodebb-plugin-composer-default": "4.4.1", "nodebb-plugin-composer-default": "4.4.2",
"nodebb-plugin-dbsearch": "2.0.2", "nodebb-plugin-dbsearch": "2.0.2",
"nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.1.5", "nodebb-plugin-emoji-one": "1.1.5",

@ -1,7 +1,7 @@
{ {
"alert.confirm-reload": "Are you sure you wish to reload NodeBB?", "alert.confirm-reload": "Bạn có thật sự muốn tải lại NodeBB",
"alert.confirm-restart": "Are you sure you wish to restart NodeBB?", "alert.confirm-restart": "Bạn có thật sự muốn khởi động lại NodeBB",
"acp-title": "%1 | NodeBB Admin Control Panel", "acp-title": "%1 | Bảng điểu khiển",
"settings-header-contents": "Contents" "settings-header-contents": "Nội dung"
} }

@ -1,11 +1,11 @@
{ {
"post-cache": "Post Cache", "post-cache": "Cache bài viết",
"posts-in-cache": "Posts in Cache", "posts-in-cache": "Cache cho bài viết",
"average-post-size": "Average Post Size", "average-post-size": "Kích thước bài viết",
"length-to-max": "Length / Max", "length-to-max": "Độ dài / Tối Đa",
"percent-full": "%1% Full", "percent-full": "%1% Đầy",
"post-cache-size": "Post Cache Size", "post-cache-size": "Kích thước cache bài viết",
"items-in-cache": "Items in Cache", "items-in-cache": "Thành phần trong Cache",
"control-panel": "Control Panel", "control-panel": "Bảng điều khiển",
"update-settings": "Update Cache Settings" "update-settings": "Cập nhật thiết lập Cache"
} }

@ -1,30 +1,30 @@
{ {
"x-b": "%1 b", "x-b": "%1 b",
"x-mb": "%1 mb", "x-mb": "%1 mb",
"uptime-seconds": "Uptime in Seconds", "uptime-seconds": "Thời gian hoạt động(giây)",
"uptime-days": "Uptime in Days", "uptime-days": "Thời gian hoạt động(Ngày)",
"mongo": "Mongo", "mongo": "Mongo",
"mongo.version": "MongoDB Version", "mongo.version": "Phiên bản MongoDB ",
"mongo.storage-engine": "Storage Engine", "mongo.storage-engine": "Lưu Trữ",
"mongo.collections": "Collections", "mongo.collections": "Tập dữ liệu",
"mongo.objects": "Objects", "mongo.objects": "Đối tượng",
"mongo.avg-object-size": "Avg. Object Size", "mongo.avg-object-size": "Kích thước trung bình",
"mongo.data-size": "Data Size", "mongo.data-size": "Kích thước dữ liệu",
"mongo.storage-size": "Storage Size", "mongo.storage-size": "Kích thước lưu trữ",
"mongo.index-size": "Index Size", "mongo.index-size": "Kích thước chỉ mục",
"mongo.file-size": "File Size", "mongo.file-size": "Kích thước tập tin",
"mongo.resident-memory": "Resident Memory", "mongo.resident-memory": "Resident Memory",
"mongo.virtual-memory": "Virtual Memory", "mongo.virtual-memory": "Bộ nhớ ảo",
"mongo.mapped-memory": "Mapped Memory", "mongo.mapped-memory": "Mapped Memory",
"mongo.raw-info": "MongoDB Raw Info", "mongo.raw-info": "Thông tin MongoDB",
"redis": "Redis", "redis": "Redis",
"redis.version": "Redis Version", "redis.version": "Phiên bản Redis",
"redis.connected-clients": "Connected Clients", "redis.connected-clients": "Người dùng kết nối",
"redis.connected-slaves": "Connected Slaves", "redis.connected-slaves": "Connected Slaves",
"redis.blocked-clients": "Blocked Clients", "redis.blocked-clients": "Người dùng vi phạm",
"redis.used-memory": "Used Memory", "redis.used-memory": "Bộ nhớ đã sử dụng",
"redis.memory-frag-ratio": "Memory Fragmentation Ratio", "redis.memory-frag-ratio": "Memory Fragmentation Ratio",
"redis.total-connections-recieved": "Total Connections Received", "redis.total-connections-recieved": "Total Connections Received",
"redis.total-commands-processed": "Total Commands Processed", "redis.total-commands-processed": "Total Commands Processed",

@ -103,5 +103,5 @@
"cookies.message": "Trang web này sử dụng cookie để đảm bảo trải nghiệm tốt nhất cho người dùng", "cookies.message": "Trang web này sử dụng cookie để đảm bảo trải nghiệm tốt nhất cho người dùng",
"cookies.accept": "Đã rõ!", "cookies.accept": "Đã rõ!",
"cookies.learn_more": "Xem thêm", "cookies.learn_more": "Xem thêm",
"edited": "Edited" "edited": "Đã cập nhật"
} }

@ -13,8 +13,8 @@
"notify_me": "Được thông báo khi có trả lời mới trong chủ đề này", "notify_me": "Được thông báo khi có trả lời mới trong chủ đề này",
"quote": "Trích dẫn", "quote": "Trích dẫn",
"reply": "Trả lời", "reply": "Trả lời",
"replies_to_this_post": "%1 Replies", "replies_to_this_post": "%1 trả lời",
"last_reply_time": "Last reply", "last_reply_time": "Trả lời cuối cùng",
"reply-as-topic": "Trả lời dưới dạng chủ đề", "reply-as-topic": "Trả lời dưới dạng chủ đề",
"guest-login-reply": "Hãy đăng nhập để trả lời", "guest-login-reply": "Hãy đăng nhập để trả lời",
"edit": "Chỉnh sửa", "edit": "Chỉnh sửa",

@ -6,7 +6,7 @@
"search": "搜索", "search": "搜索",
"enter_username": "输入用户名搜索", "enter_username": "输入用户名搜索",
"load_more": "加载更多", "load_more": "加载更多",
"users-found-search-took": "找到 %1 位用户!耗时 %2 秒。", "users-found-search-took": "找到 %1 位用户!耗时 %2 秒。",
"filter-by": "过滤选项", "filter-by": "过滤选项",
"online-only": "只看在线", "online-only": "只看在线",
"invite": "邀请注册", "invite": "邀请注册",

@ -241,7 +241,14 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components'
uploadModal.find('.upload-btn').on('click', function () { uploadModal.find('.upload-btn').on('click', function () {
var url = uploadModal.find('#uploadFromUrl').val(); var url = uploadModal.find('#uploadFromUrl').val();
if (!url) { if (!url) {
return; return false;
}
socket.emit('user.uploadProfileImageFromUrl', {
uid: ajaxify.data.uid,
url: url,
}, function (err, url) {
if (err) {
return app.alertError(err);
} }
uploadModal.modal('hide'); uploadModal.modal('hide');
@ -256,7 +263,7 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components'
paramName: 'uid', paramName: 'uid',
paramValue: ajaxify.data.theirid, paramValue: ajaxify.data.theirid,
}, onUploadComplete); }, onUploadComplete);
});
return false; return false;
}); });
}); });

@ -4,6 +4,12 @@
define('forum/account/settings', ['forum/account/header', 'components', 'sounds'], function (header, components, sounds) { define('forum/account/settings', ['forum/account/header', 'components', 'sounds'], function (header, components, sounds) {
var AccountSettings = {}; var AccountSettings = {};
$(window).on('action:ajaxify.start', function () {
if (ajaxify.data.template.name === 'account/settings' && $('#bootswatchSkin').val() !== config.bootswatchSkin) {
changePageSkin(config.bootswatchSkin);
}
});
AccountSettings.init = function () { AccountSettings.init = function () {
header.init(); header.init();
@ -24,10 +30,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
}); });
$('#bootswatchSkin').on('change', function () { $('#bootswatchSkin').on('change', function () {
var css = $('#bootswatchCSS'); changePageSkin($(this).val());
var val = $(this).val() === 'default' ? config['theme:src'] : '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + $(this).val() + '/bootstrap.min.css';
css.attr('href', val);
}); });
$('[data-property="homePageRoute"]').on('change', toggleCustomRoute); $('[data-property="homePageRoute"]').on('change', toggleCustomRoute);
@ -44,6 +47,26 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'
components.get('user/sessions').find('.timeago').timeago(); components.get('user/sessions').find('.timeago').timeago();
}; };
function changePageSkin(skinName) {
var css = $('#bootswatchCSS');
if (skinName === 'default') {
css.remove();
} else {
var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css';
if (css.length) {
css.attr('href', cssSource);
} else {
css = $('<link id="bootswatchCSS" href="' + cssSource + '" rel="stylesheet" media="screen">');
$('head').append(css);
}
}
var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) {
return className.startsWith('skin-');
});
$('body').removeClass(currentSkinClassName.join(' ')).addClass('skin-' + skinName);
}
function loadSettings() { function loadSettings() {
var settings = {}; var settings = {};

@ -7,11 +7,12 @@ define('forum/topic', [
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/events', 'forum/topic/events',
'forum/topic/posts', 'forum/topic/posts',
'forum/topic/images',
'forum/topic/replies', 'forum/topic/replies',
'navigator', 'navigator',
'sort', 'sort',
'components', 'components',
], function (infinitescroll, threadTools, postTools, events, posts, replies, navigator, sort, components) { ], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components) {
var Topic = {}; var Topic = {};
var currentUrl = ''; var currentUrl = '';
@ -238,7 +239,7 @@ define('forum/topic', [
return; return;
} }
posts.loadImages(threshold); images.loadImages(threshold);
var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : ''); var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : '');

@ -6,9 +6,10 @@ define('forum/topic/events', [
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/threadTools', 'forum/topic/threadTools',
'forum/topic/posts', 'forum/topic/posts',
'forum/topic/images',
'components', 'components',
'translator', 'translator',
], function (postTools, threadTools, posts, components, translator) { ], function (postTools, threadTools, posts, images, components, translator) {
var Events = {}; var Events = {};
var events = { var events = {
@ -128,9 +129,9 @@ define('forum/topic/events', [
editedPostEl.html(translator.unescape(data.post.content)); editedPostEl.html(translator.unescape(data.post.content));
editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive'); editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive');
app.replaceSelfLinks(editedPostEl.find('a')); app.replaceSelfLinks(editedPostEl.find('a'));
posts.wrapImagesInLinks(editedPostEl.parent()); images.wrapImagesInLinks(editedPostEl.parent());
posts.unloadImages(editedPostEl.parent()); images.unloadImages(editedPostEl.parent());
posts.loadImages(); images.loadImages();
editedPostEl.fadeIn(250); editedPostEl.fadeIn(250);
var editData = { var editData = {

@ -0,0 +1,119 @@
'use strict';
define('forum/topic/images', [
'forum/topic/postTools',
'navigator',
'components',
], function (postTools, navigator, components) {
var Images = {
_imageLoaderTimeout: undefined,
};
Images.unloadImages = function (posts) {
var images = posts.find('[component="post/content"] img:not(.not-responsive)');
if (config.delayImageLoading) {
images.each(function () {
$(this).attr('data-src', $(this).attr('src'));
}).attr('data-state', 'unloaded').attr('src', 'about:blank');
} else {
images.attr('data-state', 'loaded');
Images.wrapImagesInLinks(posts);
}
};
Images.loadImages = function (threshold) {
if (Images._imageLoaderTimeout) {
clearTimeout(Images._imageLoaderTimeout);
}
Images._imageLoaderTimeout = setTimeout(function () {
/*
If threshold is defined, images loaded above this threshold will modify
the user's scroll position so they are not scrolled away from content
they were reading. Images loaded below this threshold will push down content.
If no threshold is defined, loaded images will push down content, as per
default
*/
var images = components.get('post/content').find('img[data-state="unloaded"]');
var visible = images.filter(function () {
return utils.isElementInViewport(this);
});
var posts = $.unique(visible.map(function () {
return $(this).parents('[component="post"]').get(0);
}));
var scrollTop = $(window).scrollTop();
var adjusting = false;
var adjustQueue = [];
var oldHeight;
var newHeight;
function adjustPosition() {
adjusting = true;
oldHeight = document.body.clientHeight;
// Display the image
$(this).attr('data-state', 'loaded');
newHeight = document.body.clientHeight;
var imageRect = this.getBoundingClientRect();
if (imageRect.top < threshold) {
scrollTop += newHeight - oldHeight;
$(window).scrollTop(scrollTop);
}
if (adjustQueue.length) {
adjustQueue.pop()();
} else {
adjusting = false;
Images.wrapImagesInLinks(posts);
posts.length = 0;
}
}
// For each image, reset the source and adjust scrollTop when loaded
visible.attr('data-state', 'loading');
visible.each(function (index, image) {
image = $(image);
image.on('load', function () {
if (!adjusting) {
adjustPosition.call(this);
} else {
adjustQueue.push(adjustPosition.bind(this));
}
});
image.attr('src', image.attr('data-src'));
image.removeAttr('data-src');
});
}, 250);
};
Images.wrapImagesInLinks = function (posts) {
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
var $this = $(this);
var src = $this.attr('src');
var suffixRegex = /-resized(\.[\w]+)?$/;
if (src === 'about:blank') {
return;
}
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
src = src.replace(suffixRegex, '$1');
}
if (!$this.parent().is('a')) {
$this.wrap('<a href="' + src + '" target="_blank">');
}
});
};
return Images;
});

@ -5,19 +5,14 @@ define('forum/topic/posts', [
'forum/pagination', 'forum/pagination',
'forum/infinitescroll', 'forum/infinitescroll',
'forum/topic/postTools', 'forum/topic/postTools',
'forum/topic/images',
'navigator', 'navigator',
'components', 'components',
], function (pagination, infinitescroll, postTools, navigator, components) { ], function (pagination, infinitescroll, postTools, images, navigator, components) {
var Posts = { var Posts = { };
_imageLoaderTimeout: undefined,
};
Posts.onNewPost = function (data) { Posts.onNewPost = function (data) {
if (!data || !data.posts || !data.posts.length) { if (!data || !data.posts || !data.posts.length || parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
return;
}
if (parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) {
return; return;
} }
@ -63,7 +58,7 @@ define('forum/topic/posts', [
function onNewPostPagination(data) { function onNewPostPagination(data) {
function scrollToPost() { function scrollToPost() {
scrollToPostIfSelf(data.posts[0]); scrollToPostIfSelf(data.posts[0]);
Posts.loadImages(); images.loadImages();
} }
var posts = data.posts; var posts = data.posts;
@ -107,7 +102,7 @@ define('forum/topic/posts', [
html.addClass('new'); html.addClass('new');
} }
scrollToPostIfSelf(data.posts[0]); scrollToPostIfSelf(data.posts[0]);
Posts.loadImages(); images.loadImages();
}); });
} }
@ -247,7 +242,7 @@ define('forum/topic/posts', [
}; };
Posts.processPage = function (posts) { Posts.processPage = function (posts) {
Posts.unloadImages(posts); images.unloadImages(posts);
Posts.showBottomPostBar(); Posts.showBottomPostBar();
posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
app.createUserTooltips(posts); app.createUserTooltips(posts);
@ -260,110 +255,6 @@ define('forum/topic/posts', [
hidePostToolsForDeletedPosts(posts); hidePostToolsForDeletedPosts(posts);
}; };
Posts.unloadImages = function (posts) {
var images = posts.find('[component="post/content"] img:not(.not-responsive)');
if (config.delayImageLoading) {
images.each(function () {
$(this).attr('data-src', $(this).attr('src'));
}).attr('data-state', 'unloaded').attr('src', 'about:blank');
} else {
images.attr('data-state', 'loaded');
Posts.wrapImagesInLinks(posts);
}
};
Posts.loadImages = function (threshold) {
if (Posts._imageLoaderTimeout) {
clearTimeout(Posts._imageLoaderTimeout);
}
Posts._imageLoaderTimeout = setTimeout(function () {
/*
If threshold is defined, images loaded above this threshold will modify
the user's scroll position so they are not scrolled away from content
they were reading. Images loaded below this threshold will push down content.
If no threshold is defined, loaded images will push down content, as per
default
*/
var images = components.get('post/content').find('img[data-state="unloaded"]');
var visible = images.filter(function () {
return utils.isElementInViewport(this);
});
var posts = $.unique(visible.map(function () {
return $(this).parents('[component="post"]').get(0);
}));
var scrollTop = $(window).scrollTop();
var adjusting = false;
var adjustQueue = [];
var oldHeight;
var newHeight;
function adjustPosition() {
adjusting = true;
oldHeight = document.body.clientHeight;
// Display the image
$(this).attr('data-state', 'loaded');
newHeight = document.body.clientHeight;
var imageRect = this.getBoundingClientRect();
if (imageRect.top < threshold) {
scrollTop += newHeight - oldHeight;
$(window).scrollTop(scrollTop);
}
if (adjustQueue.length) {
adjustQueue.pop()();
} else {
adjusting = false;
Posts.wrapImagesInLinks(posts);
posts.length = 0;
}
}
// For each image, reset the source and adjust scrollTop when loaded
visible.attr('data-state', 'loading');
visible.each(function (index, image) {
image = $(image);
image.on('load', function () {
if (!adjusting) {
adjustPosition.call(this);
} else {
adjustQueue.push(adjustPosition.bind(this));
}
});
image.attr('src', image.attr('data-src'));
image.removeAttr('data-src');
});
}, 250);
};
Posts.wrapImagesInLinks = function (posts) {
posts.find('[component="post/content"] img:not(.emoji)').each(function () {
var $this = $(this);
var src = $this.attr('src');
var suffixRegex = /-resized(\.[\w]+)?$/;
if (src === 'about:blank') {
return;
}
if (utils.isRelativeUrl(src) && suffixRegex.test(src)) {
src = src.replace(suffixRegex, '$1');
}
if (!$this.parent().is('a')) {
$this.wrap('<a href="' + src + '" target="_blank">');
}
});
};
Posts.showBottomPostBar = function () { Posts.showBottomPostBar = function () {
var mainPost = components.get('post', 'index', 0); var mainPost = components.get('post', 'index', 0);
var placeHolder = $('.post-bar-placeholder'); var placeHolder = $('.post-bar-placeholder');

@ -16,9 +16,9 @@
return false; return false;
} }
var properties = item.properties; var properties = item.properties;
var loggedIn = data.config ? data.config.loggedIn : false;
if (properties) { if (properties) {
if ((properties.loggedIn && !data.config.loggedIn) || if ((properties.loggedIn && !loggedIn) ||
(properties.globalMod && !data.isGlobalMod && !data.isAdmin) || (properties.globalMod && !data.isGlobalMod && !data.isAdmin) ||
(properties.adminOnly && !data.isAdmin) || (properties.adminOnly && !data.isAdmin) ||
(properties.searchInstalled && !data.searchEnabled)) { (properties.searchInstalled && !data.searchEnabled)) {
@ -26,11 +26,11 @@
} }
} }
if (item.route.match('/users') && data.privateUserInfo && !data.config.loggedIn) { if (item.route.match('/users') && data.privateUserInfo && !loggedIn) {
return false; return false;
} }
if (item.route.match('/tags') && data.privateTagListing && !data.config.loggedIn) { if (item.route.match('/tags') && data.privateTagListing && !loggedIn) {
return false; return false;
} }

@ -0,0 +1,47 @@
'use strict';
var nconf = require('nconf');
var winston = require('winston');
var validator = require('validator');
var meta = require('../meta');
var plugins = require('../plugins');
exports.handle404 = function (req, res) {
var relativePath = nconf.get('relative_path');
var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js');
if (plugins.hasListeners('action:meta.override404')) {
return plugins.fireHook('action:meta.override404', {
req: req,
res: res,
error: {},
});
}
if (isClientScript.test(req.url)) {
res.type('text/javascript').status(200).send('');
} else if (req.path.startsWith(relativePath + '/assets/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
meta.errors.log404(req.path || '');
res.sendStatus(404);
} else if (req.accepts('html')) {
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
}
meta.errors.log404(req.path.replace(/^\/api/, '') || '');
res.status(404);
var path = String(req.path || '');
if (res.locals.isAPI) {
return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' });
}
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' });
});
} else {
res.status(404).type('txt').send('Not found');
}
};

@ -13,9 +13,8 @@ var privileges = require('../privileges');
var plugins = require('../plugins'); var plugins = require('../plugins');
var widgets = require('../widgets'); var widgets = require('../widgets');
var translator = require('../../public/src/modules/translator'); var translator = require('../../public/src/modules/translator');
var accountHelpers = require('../controllers/accounts/helpers');
var apiController = {}; var apiController = module.exports;
apiController.getConfig = function (req, res, next) { apiController.getConfig = function (req, res, next) {
var config = {}; var config = {};
@ -62,7 +61,7 @@ apiController.getConfig = function (req, res, next) {
config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest';
config.csrf_token = req.csrfToken(); config.csrf_token = req.csrfToken();
config.searchEnabled = plugins.hasListeners('filter:search.query'); config.searchEnabled = plugins.hasListeners('filter:search.query');
config.bootswatchSkin = 'default'; config.bootswatchSkin = meta.config.bootswatchSkin || 'default';
var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff; var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff;
config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff; config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff;
@ -220,92 +219,6 @@ apiController.getObject = function (req, res, next) {
}); });
}; };
apiController.getCurrentUser = function (req, res, next) {
if (!req.uid) {
return res.status(401).json('not-authorized');
}
async.waterfall([
function (next) {
user.getUserField(req.uid, 'userslug', next);
},
function (userslug, next) {
accountHelpers.getUserDataByUserSlug(userslug, req.uid, next);
},
], function (err, userData) {
if (err) {
return next(err);
}
res.json(userData);
});
};
apiController.getUserByUID = function (req, res, next) {
byType('uid', req, res, next);
};
apiController.getUserByUsername = function (req, res, next) {
byType('username', req, res, next);
};
apiController.getUserByEmail = function (req, res, next) {
byType('email', req, res, next);
};
function byType(type, req, res, next) {
apiController.getUserDataByField(req.uid, type, req.params[type], function (err, data) {
if (err || !data) {
return next(err);
}
res.json(data);
});
}
apiController.getUserDataByField = function (callerUid, field, fieldValue, callback) {
async.waterfall([
function (next) {
if (field === 'uid') {
next(null, fieldValue);
} else if (field === 'username') {
user.getUidByUsername(fieldValue, next);
} else if (field === 'email') {
user.getUidByEmail(fieldValue, next);
} else {
next();
}
},
function (uid, next) {
if (!uid) {
return next();
}
apiController.getUserDataByUID(callerUid, uid, next);
},
], callback);
};
apiController.getUserDataByUID = function (callerUid, uid, callback) {
if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) {
return callback(new Error('[[error:no-privileges]]'));
}
if (!parseInt(uid, 10)) {
return callback(new Error('[[error:no-user]]'));
}
async.parallel({
userData: async.apply(user.getUserData, uid),
settings: async.apply(user.getSettings, uid),
}, function (err, results) {
if (err || !results.userData) {
return callback(err || new Error('[[error:no-user]]'));
}
results.userData.email = results.settings.showemail ? results.userData.email : undefined;
results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined;
callback(null, results.userData);
});
};
apiController.getModerators = function (req, res, next) { apiController.getModerators = function (req, res, next) {
categories.getModerators(req.params.cid, function (err, moderators) { categories.getModerators(req.params.cid, function (err, moderators) {
if (err) { if (err) {
@ -314,16 +227,3 @@ apiController.getModerators = function (req, res, next) {
res.json({ moderators: moderators }); res.json({ moderators: moderators });
}); });
}; };
apiController.getRecentPosts = function (req, res, next) {
posts.getRecentPosts(req.uid, 0, 19, req.params.term, function (err, data) {
if (err) {
return next(err);
}
res.json(data);
});
};
module.exports = apiController;

@ -13,9 +13,6 @@ categoriesController.list = function (req, res, next) {
res.locals.metaTags = [{ res.locals.metaTags = [{
name: 'title', name: 'title',
content: String(meta.config.title || 'NodeBB'), content: String(meta.config.title || 'NodeBB'),
}, {
name: 'description',
content: String(meta.config.description || ''),
}, { }, {
property: 'og:title', property: 'og:title',
content: '[[pages:categories]]', content: '[[pages:categories]]',

@ -0,0 +1,63 @@
'use strict';
var nconf = require('nconf');
var winston = require('winston');
var validator = require('validator');
exports.handleURIErrors = function (err, req, res, next) {
// Handle cases where malformed URIs are passed in
if (err instanceof URIError) {
var tidMatch = req.path.match(/^\/topic\/(\d+)\//);
var cidMatch = req.path.match(/^\/category\/(\d+)\//);
if (tidMatch) {
res.redirect(nconf.get('relative_path') + tidMatch[0]);
} else if (cidMatch) {
res.redirect(nconf.get('relative_path') + cidMatch[0]);
} else {
winston.warn('[controller] Bad request: ' + req.path);
if (res.locals.isAPI) {
res.status(400).json({
error: '[[global:400.title]]',
});
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('400', { error: validator.escape(String(err.message)) });
});
}
}
} else {
next(err);
}
};
// this needs to have four arguments or express treats it as `(req, res, next)`
// don't remove `next`!
exports.handleErrors = function (err, req, res, next) { // eslint-disable-line no-unused-vars
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) {
return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
}
winston.error(req.path + '\n', err.stack);
res.status(err.status || 500);
var path = String(req.path || '');
if (res.locals.isAPI) {
res.json({ path: validator.escape(path), error: err.message });
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
});
}
};

@ -3,34 +3,35 @@
var async = require('async'); var async = require('async');
var nconf = require('nconf'); var nconf = require('nconf');
var validator = require('validator'); var validator = require('validator');
var winston = require('winston');
var meta = require('../meta'); var meta = require('../meta');
var user = require('../user'); var user = require('../user');
var plugins = require('../plugins'); var plugins = require('../plugins');
var helpers = require('./helpers'); var helpers = require('./helpers');
var Controllers = { var Controllers = module.exports;
topics: require('./topics'),
posts: require('./posts'), Controllers.topics = require('./topics');
categories: require('./categories'), Controllers.posts = require('./posts');
category: require('./category'), Controllers.categories = require('./categories');
unread: require('./unread'), Controllers.category = require('./category');
recent: require('./recent'), Controllers.unread = require('./unread');
popular: require('./popular'), Controllers.recent = require('./recent');
tags: require('./tags'), Controllers.popular = require('./popular');
search: require('./search'), Controllers.tags = require('./tags');
users: require('./users'), Controllers.search = require('./search');
groups: require('./groups'), Controllers.user = require('./user');
accounts: require('./accounts'), Controllers.users = require('./users');
authentication: require('./authentication'), Controllers.groups = require('./groups');
api: require('./api'), Controllers.accounts = require('./accounts');
admin: require('./admin'), Controllers.authentication = require('./authentication');
globalMods: require('./globalmods'), Controllers.api = require('./api');
mods: require('./mods'), Controllers.admin = require('./admin');
sitemap: require('./sitemap'), Controllers.globalMods = require('./globalmods');
}; Controllers.mods = require('./mods');
Controllers.sitemap = require('./sitemap');
Controllers['404'] = require('./404');
Controllers.errors = require('./errors');
Controllers.home = function (req, res, next) { Controllers.home = function (req, res, next) {
var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories'; var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
@ -321,19 +322,18 @@ Controllers.manifest = function (req, res) {
res.status(200).json(manifest); res.status(200).json(manifest);
}; };
Controllers.outgoing = function (req, res) { Controllers.outgoing = function (req, res, next) {
var url = req.query.url || ''; var url = req.query.url || '';
var data = {
if (!url) {
return next();
}
res.render('outgoing', {
outgoing: validator.escape(String(url)), outgoing: validator.escape(String(url)),
title: meta.config.title, title: meta.config.title,
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]), breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]),
}; });
if (url) {
res.render('outgoing', data);
} else {
res.status(404).redirect(nconf.get('relative_path') + '/404');
}
}; };
Controllers.termsOfUse = function (req, res, next) { Controllers.termsOfUse = function (req, res, next) {
@ -346,102 +346,3 @@ Controllers.termsOfUse = function (req, res, next) {
Controllers.ping = function (req, res) { Controllers.ping = function (req, res) {
res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); res.status(200).send(req.path === '/sping' ? 'healthy' : '200');
}; };
Controllers.handle404 = function (req, res) {
var relativePath = nconf.get('relative_path');
var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js');
if (plugins.hasListeners('action:meta.override404')) {
return plugins.fireHook('action:meta.override404', {
req: req,
res: res,
error: {},
});
}
if (isClientScript.test(req.url)) {
res.type('text/javascript').status(200).send('');
} else if (req.path.startsWith(relativePath + '/assets/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') {
meta.errors.log404(req.path || '');
res.sendStatus(404);
} else if (req.accepts('html')) {
if (process.env.NODE_ENV === 'development') {
winston.warn('Route requested but not found: ' + req.url);
}
meta.errors.log404(req.path.replace(/^\/api/, '') || '');
res.status(404);
var path = String(req.path || '');
if (res.locals.isAPI) {
return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' });
}
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' });
});
} else {
res.status(404).type('txt').send('Not found');
}
};
Controllers.handleURIErrors = function (err, req, res, next) {
// Handle cases where malformed URIs are passed in
if (err instanceof URIError) {
var tidMatch = req.path.match(/^\/topic\/(\d+)\//);
var cidMatch = req.path.match(/^\/category\/(\d+)\//);
if (tidMatch) {
res.redirect(nconf.get('relative_path') + tidMatch[0]);
} else if (cidMatch) {
res.redirect(nconf.get('relative_path') + cidMatch[0]);
} else {
winston.warn('[controller] Bad request: ' + req.path);
if (res.locals.isAPI) {
res.status(400).json({
error: '[[global:400.title]]',
});
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('400', { error: validator.escape(String(err.message)) });
});
}
}
} else {
next(err);
}
};
// this needs to have four arguments or express treats it as `(req, res, next)`
// don't remove `next`!
Controllers.handleErrors = function (err, req, res, next) { // eslint-disable-line no-unused-vars
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) {
return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
}
winston.error(req.path + '\n', err.stack);
res.status(err.status || 500);
var path = String(req.path || '');
if (res.locals.isAPI) {
res.json({ path: validator.escape(path), error: err.message });
} else {
var middleware = require('../middleware');
middleware.buildHeader(req, res, function () {
res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) });
});
}
};
module.exports = Controllers;

@ -1,24 +1,38 @@
'use strict'; 'use strict';
var async = require('async');
var posts = require('../posts'); var posts = require('../posts');
var helpers = require('./helpers'); var helpers = require('./helpers');
var postsController = {}; var postsController = module.exports;
postsController.redirectToPost = function (req, res, callback) { postsController.redirectToPost = function (req, res, next) {
var pid = parseInt(req.params.pid, 10); var pid = parseInt(req.params.pid, 10);
if (!pid) { if (!pid) {
return callback(); return next();
} }
posts.generatePostPath(pid, req.uid, function (err, path) { async.waterfall([
if (err || !path) { function (next) {
return callback(err); posts.generatePostPath(pid, req.uid, next);
},
function (path, next) {
if (!path) {
return next();
} }
helpers.redirect(res, path); helpers.redirect(res, path);
}); },
], next);
}; };
postsController.getRecentPosts = function (req, res, next) {
module.exports = postsController; async.waterfall([
function (next) {
posts.getRecentPosts(req.uid, 0, 19, req.params.term, next);
},
function (data) {
res.json(data);
},
], next);
};

@ -1,68 +1,57 @@
'use strict'; 'use strict';
var async = require('async');
var sitemap = require('../sitemap'); var sitemap = require('../sitemap');
var meta = require('../meta'); var meta = require('../meta');
var sitemapController = {}; var sitemapController = module.exports;
sitemapController.render = function (req, res, next) {
sitemap.render(function (err, tplData) {
if (err) {
return next(err);
}
req.app.render('sitemap', tplData, function (err, xml) { sitemapController.render = function (req, res, next) {
if (err) { async.waterfall([
return next(err); function (next) {
} sitemap.render(next);
},
function (tplData, next) {
req.app.render('sitemap', tplData, next);
},
function (xml) {
res.header('Content-Type', 'application/xml'); res.header('Content-Type', 'application/xml');
res.send(xml); res.send(xml);
}); },
}); ], next);
}; };
sitemapController.getPages = function (req, res, next) { sitemapController.getPages = function (req, res, next) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { sendSitemap(sitemap.getPages, res, next);
return next();
}
sitemap.getPages(function (err, xml) {
if (err) {
return next(err);
}
res.header('Content-Type', 'application/xml');
res.send(xml);
});
}; };
sitemapController.getCategories = function (req, res, next) { sitemapController.getCategories = function (req, res, next) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { sendSitemap(sitemap.getCategories, res, next);
return next();
}
sitemap.getCategories(function (err, xml) {
if (err) {
return next(err);
}
res.header('Content-Type', 'application/xml');
res.send(xml);
});
}; };
sitemapController.getTopicPage = function (req, res, next) { sitemapController.getTopicPage = function (req, res, next) {
sendSitemap(function (callback) {
sitemap.getTopicPage(parseInt(req.params[0], 10), callback);
}, res, next);
};
function sendSitemap(method, res, callback) {
if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) {
return next(); return callback();
} }
async.waterfall([
sitemap.getTopicPage(parseInt(req.params[0], 10), function (err, xml) { function (next) {
if (err) { method(next);
return next(err); },
} else if (!xml) { function (xml) {
return next(); if (!xml) {
return callback();
} }
res.header('Content-Type', 'application/xml'); res.header('Content-Type', 'application/xml');
res.send(xml); res.send(xml);
}); },
}; ], callback);
}
module.exports = sitemapController;

@ -66,7 +66,7 @@ function uploadAsImage(req, uploadedFile, callback) {
file.isFileTypeAllowed(uploadedFile.path, next); file.isFileTypeAllowed(uploadedFile.path, next);
}, },
function (next) { function (next) {
uploadFile(req.uid, uploadedFile, next); uploadsController.uploadFile(req.uid, uploadedFile, next);
}, },
function (fileObj, next) { function (fileObj, next) {
if (parseInt(meta.config.maximumImageWidth, 10) === 0) { if (parseInt(meta.config.maximumImageWidth, 10) === 0) {
@ -90,7 +90,7 @@ function uploadAsFile(req, uploadedFile, callback) {
if (parseInt(meta.config.allowFileUploads, 10) !== 1) { if (parseInt(meta.config.allowFileUploads, 10) !== 1) {
return next(new Error('[[error:uploads-are-disabled]]')); return next(new Error('[[error:uploads-are-disabled]]'));
} }
uploadFile(req.uid, uploadedFile, next); uploadsController.uploadFile(req.uid, uploadedFile, next);
}, },
], callback); ], callback);
} }
@ -161,7 +161,7 @@ uploadsController.uploadThumb = function (req, res, next) {
}, next); }, next);
} }
uploadFile(req.uid, uploadedFile, next); uploadsController.uploadFile(req.uid, uploadedFile, next);
}, },
], next); ], next);
}, next); }, next);
@ -192,7 +192,7 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) {
], callback); ], callback);
}; };
function uploadFile(uid, uploadedFile, callback) { uploadsController.uploadFile = function (uid, uploadedFile, callback) {
if (plugins.hasListeners('filter:uploadFile')) { if (plugins.hasListeners('filter:uploadFile')) {
return plugins.fireHook('filter:uploadFile', { return plugins.fireHook('filter:uploadFile', {
file: uploadedFile, file: uploadedFile,
@ -217,7 +217,7 @@ function uploadFile(uid, uploadedFile, callback) {
} }
saveFileToLocal(uploadedFile, callback); saveFileToLocal(uploadedFile, callback);
} };
function saveFileToLocal(uploadedFile, callback) { function saveFileToLocal(uploadedFile, callback) {
var extension = file.typeToExtension(uploadedFile.type); var extension = file.typeToExtension(uploadedFile.type);

@ -0,0 +1,99 @@
'use strict';
var async = require('async');
var user = require('../user');
var meta = require('../meta');
var accountHelpers = require('./accounts/helpers');
var userController = module.exports;
userController.getCurrentUser = function (req, res, next) {
if (!req.uid) {
return res.status(401).json('not-authorized');
}
async.waterfall([
function (next) {
user.getUserField(req.uid, 'userslug', next);
},
function (userslug, next) {
accountHelpers.getUserDataByUserSlug(userslug, req.uid, next);
},
function (userData) {
res.json(userData);
},
], next);
};
userController.getUserByUID = function (req, res, next) {
byType('uid', req, res, next);
};
userController.getUserByUsername = function (req, res, next) {
byType('username', req, res, next);
};
userController.getUserByEmail = function (req, res, next) {
byType('email', req, res, next);
};
function byType(type, req, res, next) {
async.waterfall([
function (next) {
userController.getUserDataByField(req.uid, type, req.params[type], next);
},
function (data, next) {
if (!data) {
return next();
}
res.json(data);
},
], next);
}
userController.getUserDataByField = function (callerUid, field, fieldValue, callback) {
async.waterfall([
function (next) {
if (field === 'uid') {
next(null, fieldValue);
} else if (field === 'username') {
user.getUidByUsername(fieldValue, next);
} else if (field === 'email') {
user.getUidByEmail(fieldValue, next);
} else {
next(null, null);
}
},
function (uid, next) {
if (!uid) {
return next(null, null);
}
userController.getUserDataByUID(callerUid, uid, next);
},
], callback);
};
userController.getUserDataByUID = function (callerUid, uid, callback) {
if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) {
return callback(new Error('[[error:no-privileges]]'));
}
if (!parseInt(uid, 10)) {
return callback(new Error('[[error:no-user]]'));
}
async.parallel({
userData: async.apply(user.getUserData, uid),
settings: async.apply(user.getSettings, uid),
}, function (err, results) {
if (err || !results.userData) {
return callback(err || new Error('[[error:no-user]]'));
}
results.userData.email = results.settings.showemail ? results.userData.email : undefined;
results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined;
callback(null, results.userData);
});
};

@ -8,9 +8,7 @@ var pagination = require('../pagination');
var db = require('../database'); var db = require('../database');
var helpers = require('./helpers'); var helpers = require('./helpers');
var usersController = module.exports;
var usersController = {};
usersController.index = function (req, res, next) { usersController.index = function (req, res, next) {
var section = req.query.section || 'joindate'; var section = req.query.section || 'joindate';
@ -33,6 +31,8 @@ usersController.index = function (req, res, next) {
}; };
usersController.search = function (req, res, next) { usersController.search = function (req, res, next) {
async.waterfall([
function (next) {
async.parallel({ async.parallel({
search: function (next) { search: function (next) {
user.search({ user.search({
@ -48,21 +48,22 @@ usersController.search = function (req, res, next) {
isAdminOrGlobalMod: function (next) { isAdminOrGlobalMod: function (next) {
user.isAdminOrGlobalMod(req.uid, next); user.isAdminOrGlobalMod(req.uid, next);
}, },
}, function (err, results) { }, next);
if (err) { },
return next(err); function (results, next) {
}
var section = req.query.section || 'joindate'; var section = req.query.section || 'joindate';
results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod; results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod;
results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query); results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query);
results.search['section_' + section] = true; results.search['section_' + section] = true;
render(req, res, results.search, next); render(req, res, results.search, next);
}); },
], next);
}; };
usersController.getOnlineUsers = function (req, res, next) { usersController.getOnlineUsers = function (req, res, next) {
async.waterfall([
function (next) {
async.parallel({ async.parallel({
users: function (next) { users: function (next) {
usersController.getUsers('users:online', req.uid, req.query, next); usersController.getUsers('users:online', req.uid, req.query, next);
@ -70,10 +71,9 @@ usersController.getOnlineUsers = function (req, res, next) {
guests: function (next) { guests: function (next) {
require('../socket.io/admin/rooms').getTotalGuestCount(next); require('../socket.io/admin/rooms').getTotalGuestCount(next);
}, },
}, function (err, results) { }, next);
if (err) { },
return next(err); function (results, next) {
}
var userData = results.users; var userData = results.users;
var hiddenCount = 0; var hiddenCount = 0;
if (!userData.isAdminOrGlobalMod) { if (!userData.isAdminOrGlobalMod) {
@ -88,7 +88,8 @@ usersController.getOnlineUsers = function (req, res, next) {
userData.anonymousUserCount = results.guests + hiddenCount; userData.anonymousUserCount = results.guests + hiddenCount;
render(req, res, userData, next); render(req, res, userData, next);
}); },
], next);
}; };
usersController.getUsersSortedByPosts = function (req, res, next) { usersController.getUsersSortedByPosts = function (req, res, next) {
@ -107,41 +108,36 @@ usersController.getUsersSortedByJoinDate = function (req, res, next) {
}; };
usersController.getBannedUsers = function (req, res, next) { usersController.getBannedUsers = function (req, res, next) {
usersController.getUsers('users:banned', req.uid, req.query, function (err, userData) { renderIfAdminOrGlobalMod('users:banned', req, res, next);
if (err) {
return next(err);
}
if (!userData.isAdminOrGlobalMod) {
return next();
}
render(req, res, userData, next);
});
}; };
usersController.getFlaggedUsers = function (req, res, next) { usersController.getFlaggedUsers = function (req, res, next) {
usersController.getUsers('users:flags', req.uid, req.query, function (err, userData) { renderIfAdminOrGlobalMod('users:flags', req, res, next);
if (err) {
return next(err);
}
if (!userData.isAdminOrGlobalMod) {
return next();
}
render(req, res, userData, next);
});
}; };
usersController.renderUsersPage = function (set, req, res, next) { function renderIfAdminOrGlobalMod(set, req, res, next) {
usersController.getUsers(set, req.uid, req.query, function (err, userData) { async.waterfall([
if (err) { function (next) {
return next(err); user.isAdminOrGlobalMod(req.uid, next);
},
function (isAdminOrGlobalMod, next) {
if (!isAdminOrGlobalMod) {
return helpers.notAllowed(req, res);
} }
usersController.renderUsersPage(set, req, res, next);
},
], next);
}
usersController.renderUsersPage = function (set, req, res, next) {
async.waterfall([
function (next) {
usersController.getUsers(set, req.uid, req.query, next);
},
function (userData, next) {
render(req, res, userData, next); render(req, res, userData, next);
}); },
], next);
}; };
usersController.getUsers = function (set, uid, query, callback) { usersController.getUsers = function (set, uid, query, callback) {
@ -169,6 +165,8 @@ usersController.getUsers = function (set, uid, query, callback) {
var start = Math.max(0, page - 1) * resultsPerPage; var start = Math.max(0, page - 1) * resultsPerPage;
var stop = start + resultsPerPage - 1; var stop = start + resultsPerPage - 1;
async.waterfall([
function (next) {
async.parallel({ async.parallel({
isAdminOrGlobalMod: function (next) { isAdminOrGlobalMod: function (next) {
user.isAdminOrGlobalMod(uid, next); user.isAdminOrGlobalMod(uid, next);
@ -176,11 +174,9 @@ usersController.getUsers = function (set, uid, query, callback) {
usersData: function (next) { usersData: function (next) {
usersController.getUsersAndCount(set, uid, start, stop, next); usersController.getUsersAndCount(set, uid, start, stop, next);
}, },
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (results, next) {
}
var pageCount = Math.ceil(results.usersData.count / resultsPerPage); var pageCount = Math.ceil(results.usersData.count / resultsPerPage);
var userData = { var userData = {
users: results.usersData.users, users: results.usersData.users,
@ -191,11 +187,14 @@ usersController.getUsers = function (set, uid, query, callback) {
isAdminOrGlobalMod: results.isAdminOrGlobalMod, isAdminOrGlobalMod: results.isAdminOrGlobalMod,
}; };
userData['section_' + (query.section || 'joindate')] = true; userData['section_' + (query.section || 'joindate')] = true;
callback(null, userData); next(null, userData);
}); },
], callback);
}; };
usersController.getUsersAndCount = function (set, uid, start, stop, callback) { usersController.getUsersAndCount = function (set, uid, start, stop, callback) {
async.waterfall([
function (next) {
async.parallel({ async.parallel({
users: function (next) { users: function (next) {
user.getUsersFromSet(set, uid, start, stop, next); user.getUsersFromSet(set, uid, start, stop, next);
@ -212,16 +211,16 @@ usersController.getUsersAndCount = function (set, uid, start, stop, callback) {
db.getObjectField('global', 'userCount', next); db.getObjectField('global', 'userCount', next);
} }
}, },
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (results, next) {
}
results.users = results.users.filter(function (user) { results.users = results.users.filter(function (user) {
return user && parseInt(user.uid, 10); return user && parseInt(user.uid, 10);
}); });
callback(null, results); next(null, results);
}); },
], callback);
}; };
function render(req, res, data, next) { function render(req, res, data, next) {
@ -232,16 +231,15 @@ function render(req, res, data, next) {
data.adminInviteOnly = registrationType === 'admin-invite-only'; data.adminInviteOnly = registrationType === 'admin-invite-only';
data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
user.getInvitesNumber(req.uid, function (err, numInvites) { async.waterfall([
if (err) { function (next) {
return next(err); user.getInvitesNumber(req.uid, next);
} },
function (numInvites) {
res.append('X-Total-Count', data.userCount); res.append('X-Total-Count', data.userCount);
data.invites = numInvites; data.invites = numInvites;
res.render('users', data); res.render('users', data);
}); },
], next);
} }
module.exports = usersController;

@ -6,23 +6,25 @@ var validator = require('validator');
var user = require('./user'); var user = require('./user');
var db = require('./database'); var db = require('./database');
var plugins = require('./plugins'); var plugins = require('./plugins');
var posts = require('./posts');
var privileges = require('./privileges');
var utils = require('../public/src/utils'); var utils = require('../public/src/utils');
(function (Groups) { var Groups = module.exports;
require('./groups/create')(Groups);
require('./groups/delete')(Groups);
require('./groups/update')(Groups);
require('./groups/membership')(Groups);
require('./groups/ownership')(Groups);
require('./groups/search')(Groups);
require('./groups/cover')(Groups);
var ephemeralGroups = ['guests']; require('./groups/data')(Groups);
require('./groups/create')(Groups);
require('./groups/delete')(Groups);
require('./groups/update')(Groups);
require('./groups/membership')(Groups);
require('./groups/ownership')(Groups);
require('./groups/search')(Groups);
require('./groups/cover')(Groups);
require('./groups/posts')(Groups);
require('./groups/user')(Groups);
var internals = {
getEphemeralGroup: function (groupName) { Groups.ephemeralGroups = ['guests'];
Groups.getEphemeralGroup = function (groupName) {
return { return {
name: groupName, name: groupName,
slug: utils.slugify(groupName), slug: utils.slugify(groupName),
@ -31,30 +33,24 @@ var utils = require('../public/src/utils');
hidden: '0', hidden: '0',
system: '1', system: '1',
}; };
}, };
removeEphemeralGroups: function (groups) {
Groups.removeEphemeralGroups = function (groups) {
for (var x = groups.length; x >= 0; x -= 1) { for (var x = groups.length; x >= 0; x -= 1) {
if (ephemeralGroups.indexOf(groups[x]) !== -1) { if (Groups.ephemeralGroups.indexOf(groups[x]) !== -1) {
groups.splice(x, 1); groups.splice(x, 1);
} }
} }
return groups; return groups;
}, };
};
Groups.internals = internals; var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/;
Groups.isPrivilegeGroup = function (groupName) {
var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/;
Groups.isPrivilegeGroup = function (groupName) {
return isPrivilegeGroupRegex.test(groupName); return isPrivilegeGroupRegex.test(groupName);
}; };
Groups.getEphemeralGroups = function () { Groups.getGroupsFromSet = function (set, uid, start, stop, callback) {
return ephemeralGroups;
};
Groups.getGroupsFromSet = function (set, uid, start, stop, callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
if (set === 'groups:visible:name') { if (set === 'groups:visible:name') {
@ -73,13 +69,15 @@ var utils = require('../public/src/utils');
Groups.getGroupsAndMembers(groupNames, next); Groups.getGroupsAndMembers(groupNames, next);
}, },
], callback); ], callback);
}; };
Groups.getGroups = function (set, start, stop, callback) { Groups.getGroups = function (set, start, stop, callback) {
db.getSortedSetRevRange(set, start, stop, callback); db.getSortedSetRevRange(set, start, stop, callback);
}; };
Groups.getGroupsAndMembers = function (groupNames, callback) { Groups.getGroupsAndMembers = function (groupNames, callback) {
async.waterfall([
function (next) {
async.parallel({ async.parallel({
groups: function (next) { groups: function (next) {
Groups.getGroupsData(groupNames, next); Groups.getGroupsData(groupNames, next);
@ -87,31 +85,30 @@ var utils = require('../public/src/utils');
members: function (next) { members: function (next) {
Groups.getMemberUsers(groupNames, 0, 3, next); Groups.getMemberUsers(groupNames, 0, 3, next);
}, },
}, function (err, data) { }, next);
if (err) { },
return callback(err); function (data, next) {
}
data.groups.forEach(function (group, index) { data.groups.forEach(function (group, index) {
if (!group) { if (group) {
return;
}
group.members = data.members[index] || []; group.members = data.members[index] || [];
group.truncated = group.memberCount > data.members.length; group.truncated = group.memberCount > data.members.length;
}
}); });
next(null, data.groups);
},
], callback);
};
callback(null, data.groups); Groups.get = function (groupName, options, callback) {
});
};
Groups.get = function (groupName, options, callback) {
if (!groupName) { if (!groupName) {
return callback(new Error('[[error:invalid-group]]')); return callback(new Error('[[error:invalid-group]]'));
} }
var stop = -1; var stop = -1;
var results;
async.waterfall([
function (next) {
async.parallel({ async.parallel({
base: function (next) { base: function (next) {
db.getObject('group:' + groupName, next); db.getObject('group:' + groupName, next);
@ -124,78 +121,65 @@ var utils = require('../public/src/utils');
Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next); Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next);
}, },
pending: function (next) { pending: function (next) {
async.waterfall([ Groups.getUsersFromSet('group:' + groupName + ':pending', next);
function (next) {
db.getSetMembers('group:' + groupName + ':pending', next);
},
function (uids, next) {
user.getUsersData(uids, next);
},
], next);
}, },
invited: function (next) { invited: function (next) {
async.waterfall([ Groups.getUsersFromSet('group:' + groupName + ':invited', next);
function (next) {
db.getSetMembers('group:' + groupName + ':invited', next);
},
function (uids, next) {
user.getUsersData(uids, next);
},
], next);
}, },
isMember: async.apply(Groups.isMember, options.uid, groupName), isMember: async.apply(Groups.isMember, options.uid, groupName),
isPending: async.apply(Groups.isPending, options.uid, groupName), isPending: async.apply(Groups.isPending, options.uid, groupName),
isInvited: async.apply(Groups.isInvited, options.uid, groupName), isInvited: async.apply(Groups.isInvited, options.uid, groupName),
isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName), isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName),
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (_results, next) {
} else if (!results.base) { results = _results;
if (!results.base) {
return callback(new Error('[[error:no-group]]')); return callback(new Error('[[error:no-group]]'));
} }
plugins.fireHook('filter:parse.raw', results.base.description, next);
},
function (descriptionParsed, next) {
var groupData = results.base;
Groups.escapeGroupData(groupData);
groupData.descriptionParsed = descriptionParsed;
groupData.userTitleEnabled = groupData.userTitleEnabled ? !!parseInt(groupData.userTitleEnabled, 10) : true;
groupData.createtimeISO = utils.toISOString(groupData.createtime);
groupData.members = results.members;
groupData.membersNextStart = stop + 1;
groupData.pending = results.pending.filter(Boolean);
groupData.invited = results.invited.filter(Boolean);
groupData.deleted = !!parseInt(groupData.deleted, 10);
groupData.hidden = !!parseInt(groupData.hidden, 10);
groupData.system = !!parseInt(groupData.system, 10);
groupData.memberCount = parseInt(groupData.memberCount, 10);
groupData.private = (groupData.private === null || groupData.private === undefined) ? true : !!parseInt(groupData.private, 10);
groupData.disableJoinRequests = parseInt(groupData.disableJoinRequests, 10) === 1;
groupData.isMember = results.isMember;
groupData.isPending = results.isPending;
groupData.isInvited = results.isInvited;
groupData.isOwner = results.isOwner;
groupData['cover:url'] = groupData['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName);
groupData['cover:position'] = validator.escape(String(groupData['cover:position'] || '50% 50%'));
groupData.labelColor = validator.escape(String(groupData.labelColor || '#000000'));
groupData.icon = validator.escape(String(groupData.icon || ''));
plugins.fireHook('filter:group.get', { group: groupData }, next);
},
function (results, next) {
next(null, results.group);
},
], callback);
};
results.base['cover:url'] = results.base['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName); Groups.getOwners = function (groupName, callback) {
results.base['cover:position'] = validator.escape(String(results.base['cover:position'] || '50% 50%'));
results.base.labelColor = validator.escape(String(results.base.labelColor || '#000000'));
results.base.icon = validator.escape(String(results.base.icon || ''));
plugins.fireHook('filter:parse.raw', results.base.description, function (err, descriptionParsed) {
if (err) {
return callback(err);
}
Groups.escapeGroupData(results.base);
results.base.descriptionParsed = descriptionParsed;
results.base.userTitleEnabled = results.base.userTitleEnabled ? !!parseInt(results.base.userTitleEnabled, 10) : true;
results.base.createtimeISO = utils.toISOString(results.base.createtime);
results.base.members = results.members;
results.base.membersNextStart = stop + 1;
results.base.pending = results.pending.filter(Boolean);
results.base.invited = results.invited.filter(Boolean);
results.base.deleted = !!parseInt(results.base.deleted, 10);
results.base.hidden = !!parseInt(results.base.hidden, 10);
results.base.system = !!parseInt(results.base.system, 10);
results.base.memberCount = parseInt(results.base.memberCount, 10);
results.base.private = (results.base.private === null || results.base.private === undefined) ? true : !!parseInt(results.base.private, 10);
results.base.disableJoinRequests = parseInt(results.base.disableJoinRequests, 10) === 1;
results.base.isMember = results.isMember;
results.base.isPending = results.isPending;
results.base.isInvited = results.isInvited;
results.base.isOwner = results.isOwner;
plugins.fireHook('filter:group.get', { group: results.base }, function (err, data) {
callback(err, data ? data.group : null);
});
});
});
};
Groups.getOwners = function (groupName, callback) {
db.getSetMembers('group:' + groupName + ':owners', callback); db.getSetMembers('group:' + groupName + ':owners', callback);
}; };
Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) { Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) {
async.waterfall([
function (next) {
async.parallel({ async.parallel({
owners: function (next) { owners: function (next) {
async.waterfall([ async.waterfall([
@ -210,11 +194,9 @@ var utils = require('../public/src/utils');
members: function (next) { members: function (next) {
user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next); user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next);
}, },
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (results, next) {
}
var ownerUids = []; var ownerUids = [];
results.owners.forEach(function (user) { results.owners.forEach(function (user) {
if (user) { if (user) {
@ -228,78 +210,58 @@ var utils = require('../public/src/utils');
}); });
results.members = results.owners.concat(results.members); results.members = results.owners.concat(results.members);
callback(null, results.members); next(null, results.members);
}); },
}; ], callback);
};
Groups.escapeGroupData = function (group) { Groups.escapeGroupData = function (group) {
if (group) { if (group) {
group.nameEncoded = encodeURIComponent(group.name); group.nameEncoded = encodeURIComponent(group.name);
group.displayName = validator.escape(String(group.name)); group.displayName = validator.escape(String(group.name));
group.description = validator.escape(String(group.description || '')); group.description = validator.escape(String(group.description || ''));
group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName; group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName;
} }
}; };
Groups.getByGroupslug = function (slug, options, callback) { Groups.getByGroupslug = function (slug, options, callback) {
db.getObjectField('groupslug:groupname', slug, function (err, groupName) { async.waterfall([
if (err) { function (next) {
return callback(err); db.getObjectField('groupslug:groupname', slug, next);
} else if (!groupName) { },
return callback(new Error('[[error:no-group]]')); function (groupName, next) {
if (!groupName) {
return next(new Error('[[error:no-group]]'));
} }
Groups.get(groupName, options, next);
},
], callback);
};
Groups.get(groupName, options, callback); Groups.getGroupNameByGroupSlug = function (slug, callback) {
});
};
Groups.getGroupNameByGroupSlug = function (slug, callback) {
db.getObjectField('groupslug:groupname', slug, callback); db.getObjectField('groupslug:groupname', slug, callback);
}; };
Groups.getGroupFields = function (groupName, fields, callback) {
Groups.getMultipleGroupFields([groupName], fields, function (err, groups) {
callback(err, groups ? groups[0] : null);
});
};
Groups.getMultipleGroupFields = function (groups, fields, callback) {
db.getObjectsFields(groups.map(function (group) {
return 'group:' + group;
}), fields, callback);
};
Groups.setGroupField = function (groupName, field, value, callback) {
db.setObjectField('group:' + groupName, field, value, function (err) {
if (err) {
return callback(err);
}
plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' });
callback();
});
};
Groups.isPrivate = function (groupName, callback) { Groups.isPrivate = function (groupName, callback) {
db.getObjectField('group:' + groupName, 'private', function (err, isPrivate) { isFieldOn(groupName, 'private', callback);
if (err) { };
return callback(err);
}
callback(null, parseInt(isPrivate, 10) !== 0); Groups.isHidden = function (groupName, callback) {
}); isFieldOn(groupName, 'hidden', callback);
}; };
Groups.isHidden = function (groupName, callback) {
db.getObjectField('group:' + groupName, 'hidden', function (err, isHidden) {
if (err) {
return callback(err);
}
callback(null, parseInt(isHidden, 10) === 1); function isFieldOn(groupName, field, callback) {
}); async.waterfall([
}; function (next) {
db.getObjectField('group:' + groupName, field, next);
},
function (value, next) {
next(null, parseInt(value, 10) === 1);
},
], callback);
}
Groups.exists = function (name, callback) { Groups.exists = function (name, callback) {
if (Array.isArray(name)) { if (Array.isArray(name)) {
var slugs = name.map(function (groupName) { var slugs = name.map(function (groupName) {
return utils.slugify(groupName); return utils.slugify(groupName);
@ -307,7 +269,7 @@ var utils = require('../public/src/utils');
async.parallel([ async.parallel([
function (next) { function (next) {
next(null, slugs.map(function (slug) { next(null, slugs.map(function (slug) {
return ephemeralGroups.indexOf(slug) !== -1; return Groups.ephemeralGroups.indexOf(slug) !== -1;
})); }));
}, },
async.apply(db.isSortedSetMembers, 'groups:createtime', name), async.apply(db.isSortedSetMembers, 'groups:createtime', name),
@ -323,130 +285,19 @@ var utils = require('../public/src/utils');
var slug = utils.slugify(name); var slug = utils.slugify(name);
async.parallel([ async.parallel([
function (next) { function (next) {
next(null, ephemeralGroups.indexOf(slug) !== -1); next(null, Groups.ephemeralGroups.indexOf(slug) !== -1);
}, },
async.apply(db.isSortedSetMember, 'groups:createtime', name), async.apply(db.isSortedSetMember, 'groups:createtime', name),
], function (err, results) { ], function (err, results) {
callback(err, !err ? (results[0] || results[1]) : null); callback(err, !err ? (results[0] || results[1]) : null);
}); });
} }
}; };
Groups.existsBySlug = function (slug, callback) { Groups.existsBySlug = function (slug, callback) {
if (Array.isArray(slug)) { if (Array.isArray(slug)) {
db.isObjectFields('groupslug:groupname', slug, callback); db.isObjectFields('groupslug:groupname', slug, callback);
} else { } else {
db.isObjectField('groupslug:groupname', slug, callback); db.isObjectField('groupslug:groupname', slug, callback);
} }
}; };
Groups.getLatestMemberPosts = function (groupName, max, uid, callback) {
async.waterfall([
function (next) {
Groups.getMembers(groupName, 0, -1, next);
},
function (uids, next) {
if (!Array.isArray(uids) || !uids.length) {
return callback(null, []);
}
var keys = uids.map(function (uid) {
return 'uid:' + uid + ':posts';
});
db.getSortedSetRevRange(keys, 0, max - 1, next);
},
function (pids, next) {
privileges.posts.filter('read', pids, uid, next);
},
function (pids, next) {
posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next);
},
], callback);
};
Groups.getGroupData = function (groupName, callback) {
Groups.getGroupsData([groupName], function (err, groupsData) {
callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null);
});
};
Groups.getGroupsData = function (groupNames, callback) {
if (!Array.isArray(groupNames) || !groupNames.length) {
return callback(null, []);
}
var keys = groupNames.map(function (groupName) {
return 'group:' + groupName;
});
var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) {
if (ephemeralGroups.indexOf(cur) !== -1) {
memo.push(idx);
}
return memo;
}, []);
db.getObjects(keys, function (err, groupData) {
if (err) {
return callback(err);
}
if (ephemeralIdx.length) {
ephemeralIdx.forEach(function (idx) {
groupData[idx] = internals.getEphemeralGroup(groupNames[idx]);
});
}
groupData.forEach(function (group) {
if (group) {
Groups.escapeGroupData(group);
group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true;
group.labelColor = validator.escape(String(group.labelColor || '#000000'));
group.icon = validator.escape(String(group.icon || ''));
group.createtimeISO = utils.toISOString(group.createtime);
group.hidden = parseInt(group.hidden, 10) === 1;
group.system = parseInt(group.system, 10) === 1;
group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10);
group.disableJoinRequests = parseInt(group.disableJoinRequests, 10) === 1;
group['cover:url'] = group['cover:url'] || require('./coverPhoto').getDefaultGroupCover(group.name);
group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%'));
}
});
plugins.fireHook('filter:groups.get', { groups: groupData }, function (err, data) {
callback(err, data ? data.groups : null);
});
});
};
Groups.getUserGroups = function (uids, callback) {
Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback);
};
Groups.getUserGroupsFromSet = function (set, uids, callback) {
async.waterfall([
function (next) {
db.getSortedSetRevRange(set, 0, -1, next);
},
function (groupNames, next) {
async.map(uids, function (uid, next) {
Groups.isMemberOfGroups(uid, groupNames, function (err, isMembers) {
if (err) {
return next(err);
}
var memberOf = [];
isMembers.forEach(function (isMember, index) {
if (isMember) {
memberOf.push(groupNames[index]);
}
});
Groups.getGroupsData(memberOf, next);
});
}, next);
},
], callback);
};
}(module.exports));

@ -0,0 +1,93 @@
'use strict';
var async = require('async');
var validator = require('validator');
var db = require('../database');
var plugins = require('../plugins');
var utils = require('../../public/src/utils');
module.exports = function (Groups) {
Groups.getGroupsData = function (groupNames, callback) {
if (!Array.isArray(groupNames) || !groupNames.length) {
return callback(null, []);
}
var keys = groupNames.map(function (groupName) {
return 'group:' + groupName;
});
var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) {
if (Groups.ephemeralGroups.indexOf(cur) !== -1) {
memo.push(idx);
}
return memo;
}, []);
async.waterfall([
function (next) {
db.getObjects(keys, next);
},
function (groupData, next) {
if (ephemeralIdx.length) {
ephemeralIdx.forEach(function (idx) {
groupData[idx] = Groups.getEphemeralGroup(groupNames[idx]);
});
}
groupData.forEach(function (group) {
if (group) {
Groups.escapeGroupData(group);
group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true;
group.labelColor = validator.escape(String(group.labelColor || '#000000'));
group.icon = validator.escape(String(group.icon || ''));
group.createtimeISO = utils.toISOString(group.createtime);
group.hidden = parseInt(group.hidden, 10) === 1;
group.system = parseInt(group.system, 10) === 1;
group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10);
group.disableJoinRequests = parseInt(group.disableJoinRequests, 10) === 1;
group['cover:url'] = group['cover:url'] || require('../coverPhoto').getDefaultGroupCover(group.name);
group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%'));
}
});
plugins.fireHook('filter:groups.get', { groups: groupData }, next);
},
function (results, next) {
next(null, results.groups);
},
], callback);
};
Groups.getGroupData = function (groupName, callback) {
Groups.getGroupsData([groupName], function (err, groupsData) {
callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null);
});
};
Groups.getGroupFields = function (groupName, fields, callback) {
Groups.getMultipleGroupFields([groupName], fields, function (err, groups) {
callback(err, groups ? groups[0] : null);
});
};
Groups.getMultipleGroupFields = function (groups, fields, callback) {
db.getObjectsFields(groups.map(function (group) {
return 'group:' + group;
}), fields, callback);
};
Groups.setGroupField = function (groupName, field, value, callback) {
async.waterfall([
function (next) {
db.setObjectField('group:' + groupName, field, value, next);
},
function (next) {
plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' });
next();
},
], callback);
};
};

@ -413,32 +413,33 @@ module.exports = function (Groups) {
}; };
Groups.getMemberCount = function (groupName, callback) { Groups.getMemberCount = function (groupName, callback) {
db.getObjectField('group:' + groupName, 'memberCount', function (err, count) { async.waterfall([
if (err) { function (next) {
return callback(err); db.getObjectField('group:' + groupName, 'memberCount', next);
} },
callback(null, parseInt(count, 10)); function (count, next) {
}); next(null, parseInt(count, 10));
},
], callback);
}; };
Groups.isMemberOfGroupList = function (uid, groupListKey, callback) { Groups.isMemberOfGroupList = function (uid, groupListKey, callback) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) { async.waterfall([
if (err) { function (next) {
return callback(err); db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next);
} },
groupNames = Groups.internals.removeEphemeralGroups(groupNames); function (groupNames, next) {
groupNames = Groups.removeEphemeralGroups(groupNames);
if (groupNames.length === 0) { if (groupNames.length === 0) {
return callback(null, false); return callback(null, false);
} }
Groups.isMemberOfGroups(uid, groupNames, function (err, isMembers) { Groups.isMemberOfGroups(uid, groupNames, next);
if (err) { },
return callback(err); function (isMembers, next) {
} next(null, isMembers.indexOf(true) !== -1);
},
callback(null, isMembers.indexOf(true) !== -1); ], callback);
});
});
}; };
Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) { Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) {
@ -446,19 +447,20 @@ module.exports = function (Groups) {
return 'group:' + groupName + ':members'; return 'group:' + groupName + ':members';
}); });
db.getSortedSetsMembers(sets, function (err, members) { var uniqueGroups;
if (err) { var members;
return callback(err); async.waterfall([
} function (next) {
db.getSortedSetsMembers(sets, next);
var uniqueGroups = _.unique(_.flatten(members)); },
uniqueGroups = Groups.internals.removeEphemeralGroups(uniqueGroups); function (_members, next) {
members = _members;
Groups.isMemberOfGroups(uid, uniqueGroups, function (err, isMembers) { uniqueGroups = _.unique(_.flatten(members));
if (err) { uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups);
return callback(err);
}
Groups.isMemberOfGroups(uid, uniqueGroups, next);
},
function (isMembers, next) {
var map = {}; var map = {};
uniqueGroups.forEach(function (groupName, index) { uniqueGroups.forEach(function (groupName, index) {
@ -474,62 +476,63 @@ module.exports = function (Groups) {
return false; return false;
}); });
callback(null, result); next(null, result);
}); },
}); ], callback);
}; };
Groups.isMembersOfGroupList = function (uids, groupListKey, callback) { Groups.isMembersOfGroupList = function (uids, groupListKey, callback) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) { var groupNames;
if (err) {
return callback(err);
}
var results = []; var results = [];
uids.forEach(function () { uids.forEach(function () {
results.push(false); results.push(false);
}); });
groupNames = Groups.internals.removeEphemeralGroups(groupNames); async.waterfall([
function (next) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next);
},
function (_groupNames, next) {
groupNames = Groups.removeEphemeralGroups(_groupNames);
if (groupNames.length === 0) { if (groupNames.length === 0) {
return callback(null, results); return callback(null, results);
} }
async.each(groupNames, function (groupName, next) { async.map(groupNames, function (groupName, next) {
Groups.isMembers(uids, groupName, function (err, isMembers) { Groups.isMembers(uids, groupName, next);
if (err) { }, next);
return next(err); },
} function (isGroupMembers, next) {
isGroupMembers.forEach(function (isMembers) {
results.forEach(function (isMember, index) { results.forEach(function (isMember, index) {
if (!isMember && isMembers[index]) { if (!isMember && isMembers[index]) {
results[index] = true; results[index] = true;
} }
}); });
next();
});
}, function (err) {
callback(err, results);
});
}); });
next(null, results);
},
], callback);
}; };
Groups.isInvited = function (uid, groupName, callback) { Groups.isInvited = function (uid, groupName, callback) {
if (!uid) { if (!uid) {
return callback(null, false); return setImmediate(callback, null, false);
} }
db.isSetMember('group:' + groupName + ':invited', uid, callback); db.isSetMember('group:' + groupName + ':invited', uid, callback);
}; };
Groups.isPending = function (uid, groupName, callback) { Groups.isPending = function (uid, groupName, callback) {
if (!uid) { if (!uid) {
return callback(null, false); return setImmediate(callback, null, false);
} }
db.isSetMember('group:' + groupName + ':pending', uid, callback); db.isSetMember('group:' + groupName + ':pending', uid, callback);
}; };
Groups.getPending = function (groupName, callback) { Groups.getPending = function (groupName, callback) {
if (!groupName) { if (!groupName) {
return callback(null, []); return setImmediate(callback, null, []);
} }
db.getSetMembers('group:' + groupName + ':pending', callback); db.getSetMembers('group:' + groupName + ':pending', callback);
}; };

@ -0,0 +1,32 @@
'use strict';
var async = require('async');
var db = require('../database');
var privileges = require('../privileges');
var posts = require('../posts');
module.exports = function (Groups) {
Groups.getLatestMemberPosts = function (groupName, max, uid, callback) {
async.waterfall([
function (next) {
Groups.getMembers(groupName, 0, -1, next);
},
function (uids, next) {
if (!Array.isArray(uids) || !uids.length) {
return callback(null, []);
}
var keys = uids.map(function (uid) {
return 'uid:' + uid + ':posts';
});
db.getSortedSetRevRange(keys, 0, max - 1, next);
},
function (pids, next) {
privileges.posts.filter('read', pids, uid, next);
},
function (pids, next) {
posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next);
},
], callback);
};
};

@ -16,7 +16,7 @@ module.exports = function (Groups) {
async.apply(db.getObjectValues, 'groupslug:groupname'), async.apply(db.getObjectValues, 'groupslug:groupname'),
function (groupNames, next) { function (groupNames, next) {
// Ephemeral groups and the registered-users groups are searchable // Ephemeral groups and the registered-users groups are searchable
groupNames = Groups.getEphemeralGroups().concat(groupNames).concat('registered-users'); groupNames = Groups.ephemeralGroups.concat(groupNames).concat('registered-users');
groupNames = groupNames.filter(function (name) { groupNames = groupNames.filter(function (name) {
return name.toLowerCase().indexOf(query) !== -1 && name !== 'administrators' && !Groups.isPrivilegeGroup(name); return name.toLowerCase().indexOf(query) !== -1 && name !== 'administrators' && !Groups.isPrivilegeGroup(name);
}); });

@ -0,0 +1,50 @@
'use strict';
var async = require('async');
var db = require('../database');
var user = require('../user');
module.exports = function (Groups) {
Groups.getUsersFromSet = function (set, callback) {
async.waterfall([
function (next) {
db.getSetMembers(set, next);
},
function (uids, next) {
user.getUsersData(uids, next);
},
], callback);
};
Groups.getUserGroups = function (uids, callback) {
Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback);
};
Groups.getUserGroupsFromSet = function (set, uids, callback) {
async.waterfall([
function (next) {
db.getSortedSetRevRange(set, 0, -1, next);
},
function (groupNames, next) {
async.map(uids, function (uid, next) {
async.waterfall([
function (next) {
Groups.isMemberOfGroups(uid, groupNames, next);
},
function (isMembers, next) {
var memberOf = [];
isMembers.forEach(function (isMember, index) {
if (isMember) {
memberOf.push(groupNames[index]);
}
});
Groups.getGroupsData(memberOf, next);
},
], next);
}, next);
},
], callback);
};
};

@ -58,6 +58,7 @@ module.exports = function (Meta) {
'public/src/client/topic/fork.js', 'public/src/client/topic/fork.js',
'public/src/client/topic/move.js', 'public/src/client/topic/move.js',
'public/src/client/topic/posts.js', 'public/src/client/topic/posts.js',
'public/src/client/topic/images.js',
'public/src/client/topic/postTools.js', 'public/src/client/topic/postTools.js',
'public/src/client/topic/threadTools.js', 'public/src/client/topic/threadTools.js',
'public/src/client/categories.js', 'public/src/client/categories.js',

@ -131,10 +131,10 @@ module.exports = function (Meta) {
} }
}); });
if (!hasDescription) { if (!hasDescription && Meta.config.description) {
meta.push({ meta.push({
name: 'description', name: 'description',
content: validator.escape(String(Meta.config.description || '')), content: validator.escape(String(Meta.config.description)),
}); });
} }
} }

@ -114,7 +114,7 @@ module.exports = function (Meta) {
themeData['theme:templates'] = config.templates ? config.templates : ''; themeData['theme:templates'] = config.templates ? config.templates : '';
themeData['theme:src'] = ''; themeData['theme:src'] = '';
db.setObject('config', themeData, next); Meta.configs.setMultiple(themeData, next);
// Re-set the themes path (for when NodeBB is reloaded) // Re-set the themes path (for when NodeBB is reloaded)
Meta.themes.setPath(config); Meta.themes.setPath(config);
@ -125,7 +125,10 @@ module.exports = function (Meta) {
break; break;
case 'bootswatch': case 'bootswatch':
Meta.configs.set('theme:src', data.src, callback); Meta.configs.setMultiple({
'theme:src': data.src,
bootswatchSkin: data.id.toLowerCase(),
}, callback);
break; break;
} }
}; };

@ -42,7 +42,6 @@ module.exports = function (middleware) {
middleware.renderHeader = function (req, res, data, callback) { middleware.renderHeader = function (req, res, data, callback) {
var registrationType = meta.config.registrationType || 'normal'; var registrationType = meta.config.registrationType || 'normal';
var templateValues = { var templateValues = {
bootswatchCSS: meta.config['theme:src'],
title: meta.config.title || '', title: meta.config.title || '',
description: meta.config.description || '', description: meta.config.description || '',
'cache-buster': meta.config['cache-buster'] || '', 'cache-buster': meta.config['cache-buster'] || '',
@ -117,9 +116,7 @@ module.exports = function (middleware) {
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; results.user.isEmailConfirmSent = !!results.isEmailConfirmSent;
if (res.locals.config && parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') { setBootswatchCSS(templateValues, res.locals.config);
templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css';
}
templateValues.browserTitle = controllers.helpers.buildTitle(data.title); templateValues.browserTitle = controllers.helpers.buildTitle(data.title);
templateValues.navigation = results.navigation; templateValues.navigation = results.navigation;
@ -191,5 +188,21 @@ module.exports = function (middleware) {
return title; return title;
} }
function setBootswatchCSS(obj, config) {
if (config && config.bootswatchSkin !== 'default') {
var skinToUse = '';
if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1) {
skinToUse = config.bootswatchSkin;
} else if (meta.config.bootswatchSkin !== 'default') {
skinToUse = meta.config.bootswatchSkin;
}
if (skinToUse) {
obj.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinToUse + '/bootstrap.min.css';
}
}
}
}; };

@ -53,22 +53,24 @@ middleware.ensureSelfOrGlobalPrivilege = function (req, res, next) {
The "self" part of this middleware hinges on you having used The "self" part of this middleware hinges on you having used
middleware.exposeUid prior to invoking this middleware. middleware.exposeUid prior to invoking this middleware.
*/ */
if (req.user) { async.waterfall([
if (req.user.uid === res.locals.uid) { function (next) {
return next(); if (!req.uid) {
return setImmediate(next, null, false);
} }
user.isAdminOrGlobalMod(req.uid, function (err, ok) { if (req.uid === parseInt(res.locals.uid, 10)) {
if (err) { return setImmediate(next, null, true);
return next(err);
} else if (ok) {
return next();
} }
controllers.helpers.notAllowed(req, res); user.isAdminOrGlobalMod(req.uid, next);
}); },
} else { function (isAdminOrGlobalMod, next) {
controllers.helpers.notAllowed(req, res); if (!isAdminOrGlobalMod) {
return controllers.helpers.notAllowed(req, res);
} }
next();
},
], next);
}; };
middleware.ensureSelfOrPrivileged = function (req, res, next) { middleware.ensureSelfOrPrivileged = function (req, res, next) {

@ -96,7 +96,7 @@ module.exports = function (privileges) {
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1; return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
}); });
groupNames = groups.getEphemeralGroups().concat(groupNames); groupNames = groups.ephemeralGroups.concat(groupNames);
var registeredUsersIndex = groupNames.indexOf('registered-users'); var registeredUsersIndex = groupNames.indexOf('registered-users');
if (registeredUsersIndex !== -1) { if (registeredUsersIndex !== -1) {
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]); groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
@ -155,6 +155,8 @@ module.exports = function (privileges) {
privileges.categories.get = function (cid, uid, callback) { privileges.categories.get = function (cid, uid, callback) {
var privs = ['topics:create', 'topics:read', 'read']; var privs = ['topics:create', 'topics:read', 'read'];
async.waterfall([
function (next) {
async.parallel({ async.parallel({
privileges: function (next) { privileges: function (next) {
helpers.isUserAllowedTo(privs, uid, cid, next); helpers.isUserAllowedTo(privs, uid, cid, next);
@ -165,10 +167,9 @@ module.exports = function (privileges) {
isModerator: function (next) { isModerator: function (next) {
user.isModerator(uid, cid, next); user.isModerator(uid, cid, next);
}, },
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (results, next) {
}
var privData = _.object(privs, results.privileges); var privData = _.object(privs, results.privileges);
var isAdminOrMod = results.isAdministrator || results.isModerator; var isAdminOrMod = results.isAdministrator || results.isModerator;
@ -181,8 +182,9 @@ module.exports = function (privileges) {
editable: isAdminOrMod, editable: isAdminOrMod,
view_deleted: isAdminOrMod, view_deleted: isAdminOrMod,
isAdminOrMod: isAdminOrMod, isAdminOrMod: isAdminOrMod,
}, callback); }, next);
}); },
], callback);
}; };
privileges.categories.isAdminOrMod = function (cid, uid, callback) { privileges.categories.isAdminOrMod = function (cid, uid, callback) {
@ -213,15 +215,14 @@ module.exports = function (privileges) {
return callback(null, false); return callback(null, false);
} }
categories.getCategoryField(cid, 'disabled', function (err, disabled) { async.waterfall([
if (err) { function (next) {
return callback(err); categories.getCategoryField(cid, 'disabled', next);
} },
function (disabled, next) {
if (parseInt(disabled, 10) === 1) { if (parseInt(disabled, 10) === 1) {
return callback(null, false); return callback(null, false);
} }
helpers.some([ helpers.some([
function (next) { function (next) {
helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) { helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) {
@ -234,8 +235,9 @@ module.exports = function (privileges) {
function (next) { function (next) {
user.isAdministrator(uid, next); user.isAdministrator(uid, next);
}, },
], next);
},
], callback); ], callback);
});
}; };
privileges.categories.filterCids = function (privilege, cids, uid, callback) { privileges.categories.filterCids = function (privilege, cids, uid, callback) {
@ -247,18 +249,19 @@ module.exports = function (privileges) {
return array.indexOf(cid) === index; return array.indexOf(cid) === index;
}); });
privileges.categories.getBase(privilege, cids, uid, function (err, results) { async.waterfall([
if (err) { function (next) {
return callback(err); privileges.categories.getBase(privilege, cids, uid, next);
} },
function (results, next) {
cids = cids.filter(function (cid, index) { cids = cids.filter(function (cid, index) {
return !results.categories[index].disabled && return !results.categories[index].disabled &&
(results.allowedTo[index] || results.isAdmin || results.isModerators[index]); (results.allowedTo[index] || results.isAdmin || results.isModerators[index]);
}); });
callback(null, cids.filter(Boolean)); next(null, cids.filter(Boolean));
}); },
], callback);
}; };
privileges.categories.getBase = function (privilege, cids, uid, callback) { privileges.categories.getBase = function (privilege, cids, uid, callback) {
@ -287,6 +290,8 @@ module.exports = function (privileges) {
return array.indexOf(uid) === index; return array.indexOf(uid) === index;
}); });
async.waterfall([
function (next) {
async.parallel({ async.parallel({
allowedTo: function (next) { allowedTo: function (next) {
helpers.isUsersAllowedTo(privilege, uids, cid, next); helpers.isUsersAllowedTo(privilege, uids, cid, next);
@ -297,16 +302,15 @@ module.exports = function (privileges) {
isAdmin: function (next) { isAdmin: function (next) {
user.isAdministrator(uids, next); user.isAdministrator(uids, next);
}, },
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (results, next) {
}
uids = uids.filter(function (uid, index) { uids = uids.filter(function (uid, index) {
return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index]; return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index];
}); });
callback(null, uids); next(null, uids);
}); },
], callback);
}; };
privileges.categories.give = function (privileges, cid, groupName, callback) { privileges.categories.give = function (privileges, cid, groupName, callback) {
@ -324,6 +328,8 @@ module.exports = function (privileges) {
} }
privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) { privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) {
async.waterfall([
function (next) {
async.parallel({ async.parallel({
isAdministrator: function (next) { isAdministrator: function (next) {
user.isAdministrator(uid, next); user.isAdministrator(uid, next);
@ -334,13 +340,12 @@ module.exports = function (privileges) {
moderatorOfTarget: function (next) { moderatorOfTarget: function (next) {
user.isModerator(uid, targetCid, next); user.isModerator(uid, targetCid, next);
}, },
}, function (err, results) { }, next);
if (err) { },
return callback(err); function (results, next) {
} next(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget));
},
callback(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget)); ], callback);
});
}; };
privileges.categories.userPrivileges = function (cid, uid, callback) { privileges.categories.userPrivileges = function (cid, uid, callback) {

@ -11,17 +11,17 @@ module.exports = function (app, middleware, controllers) {
router.get('/config', middleware.applyCSRF, controllers.api.getConfig); router.get('/config', middleware.applyCSRF, controllers.api.getConfig);
router.get('/widgets/render', controllers.api.renderWidgets); router.get('/widgets/render', controllers.api.renderWidgets);
router.get('/me', middleware.checkGlobalPrivacySettings, controllers.api.getCurrentUser); router.get('/me', middleware.checkGlobalPrivacySettings, controllers.user.getCurrentUser);
router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUID); router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUID);
router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUsername); router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUsername);
router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.api.getUserByEmail); router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.user.getUserByEmail);
router.get('/:type/pid/:id', controllers.api.getObject); router.get('/:type/pid/:id', controllers.api.getObject);
router.get('/:type/tid/:id', controllers.api.getObject); router.get('/:type/tid/:id', controllers.api.getObject);
router.get('/:type/cid/:id', controllers.api.getObject); router.get('/:type/cid/:id', controllers.api.getObject);
router.get('/categories/:cid/moderators', controllers.api.getModerators); router.get('/categories/:cid/moderators', controllers.api.getModerators);
router.get('/recent/posts/:term?', controllers.api.getRecentPosts); router.get('/recent/posts/:term?', controllers.posts.getRecentPosts);
router.get('/unread/:filter?/total', middleware.authenticate, controllers.unread.unreadTotal); router.get('/unread/:filter?/total', middleware.authenticate, controllers.unread.unreadTotal);
router.get('/topic/teaser/:topic_id', controllers.topics.teaser); router.get('/topic/teaser/:topic_id', controllers.topics.teaser);
router.get('/topic/pagination/:topic_id', controllers.topics.pagination); router.get('/topic/pagination/:topic_id', controllers.topics.pagination);

@ -199,9 +199,9 @@ module.exports = function (app, middleware, hotswapIds) {
}); });
app.use(relativePath + '/assets/vendor/jquery/timeago/locales', middleware.processTimeagoLocales); app.use(relativePath + '/assets/vendor/jquery/timeago/locales', middleware.processTimeagoLocales);
app.use(controllers.handle404); app.use(controllers['404'].handle404);
app.use(controllers.handleURIErrors); app.use(controllers.errors.handleURIErrors);
app.use(controllers.handleErrors); app.use(controllers.errors.handleErrors);
// Add plugin routes // Add plugin routes
async.series([ async.series([

@ -1,12 +1,14 @@
'use strict'; 'use strict';
var async = require('async');
var topics = require('../topics'); var topics = require('../topics');
var websockets = require('./index'); var websockets = require('./index');
var user = require('../user'); var user = require('../user');
var apiController = require('../controllers/api'); var apiController = require('../controllers/api');
var socketHelpers = require('./helpers'); var socketHelpers = require('./helpers');
var SocketTopics = {}; var SocketTopics = module.exports;
require('./topics/unread')(SocketTopics); require('./topics/unread')(SocketTopics);
require('./topics/move')(SocketTopics); require('./topics/move')(SocketTopics);
@ -23,18 +25,19 @@ SocketTopics.post = function (socket, data, callback) {
data.req = websockets.reqFromSocket(socket); data.req = websockets.reqFromSocket(socket);
data.timestamp = Date.now(); data.timestamp = Date.now();
topics.post(data, function (err, result) { async.waterfall([
if (err) { function (next) {
return callback(err); topics.post(data, next);
} },
function (result, next) {
callback(null, result.topicData); next(null, result.topicData);
socket.emit('event:new_post', { posts: [result.postData] }); socket.emit('event:new_post', { posts: [result.postData] });
socket.emit('event:new_topic', result.topicData); socket.emit('event:new_topic', result.topicData);
socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData });
}); },
], callback);
}; };
SocketTopics.postcount = function (socket, tid, callback) { SocketTopics.postcount = function (socket, tid, callback) {
@ -61,7 +64,7 @@ SocketTopics.createTopicFromPosts = function (socket, data, callback) {
}; };
SocketTopics.changeWatching = function (socket, data, callback) { SocketTopics.changeWatching = function (socket, data, callback) {
if (!data.tid || !data.type) { if (!data || !data.tid || !data.type) {
return callback(new Error('[[error:invalid-data]]')); return callback(new Error('[[error:invalid-data]]'));
} }
var commands = ['follow', 'unfollow', 'ignore']; var commands = ['follow', 'unfollow', 'ignore'];
@ -90,20 +93,23 @@ SocketTopics.isFollowed = function (socket, tid, callback) {
}; };
SocketTopics.search = function (socket, data, callback) { SocketTopics.search = function (socket, data, callback) {
if (!data) {
return callback(new Error('[[error:invalid-data]]'));
}
topics.search(data.tid, data.term, callback); topics.search(data.tid, data.term, callback);
}; };
SocketTopics.isModerator = function (socket, tid, callback) { SocketTopics.isModerator = function (socket, tid, callback) {
topics.getTopicField(tid, 'cid', function (err, cid) { async.waterfall([
if (err) { function (next) {
return callback(err); topics.getTopicField(tid, 'cid', next);
} },
user.isModerator(socket.uid, cid, callback); function (cid, next) {
}); user.isModerator(socket.uid, cid, next);
},
], callback);
}; };
SocketTopics.getTopic = function (socket, tid, callback) { SocketTopics.getTopic = function (socket, tid, callback) {
apiController.getTopicData(tid, socket.uid, callback); apiController.getTopicData(tid, socket.uid, callback);
}; };
module.exports = SocketTopics;

@ -12,7 +12,7 @@ var meta = require('../meta');
var events = require('../events'); var events = require('../events');
var emailer = require('../emailer'); var emailer = require('../emailer');
var db = require('../database'); var db = require('../database');
var apiController = require('../controllers/api'); var userController = require('../controllers/user');
var privileges = require('../privileges'); var privileges = require('../privileges');
var SocketUser = {}; var SocketUser = {};
@ -303,15 +303,15 @@ SocketUser.invite = function (socket, email, callback) {
}; };
SocketUser.getUserByUID = function (socket, uid, callback) { SocketUser.getUserByUID = function (socket, uid, callback) {
apiController.getUserDataByField(socket.uid, 'uid', uid, callback); userController.getUserDataByField(socket.uid, 'uid', uid, callback);
}; };
SocketUser.getUserByUsername = function (socket, username, callback) { SocketUser.getUserByUsername = function (socket, username, callback) {
apiController.getUserDataByField(socket.uid, 'username', username, callback); userController.getUserDataByField(socket.uid, 'username', username, callback);
}; };
SocketUser.getUserByEmail = function (socket, email, callback) { SocketUser.getUserByEmail = function (socket, email, callback) {
apiController.getUserDataByField(socket.uid, 'email', email, callback); userController.getUserDataByField(socket.uid, 'email', email, callback);
}; };
SocketUser.setModerationNote = function (socket, data, callback) { SocketUser.setModerationNote = function (socket, data, callback) {

@ -317,7 +317,7 @@ var social = require('./social');
term: term, term: term,
}, callback); }, callback);
} else { } else {
callback(new Error('no-plugins-available'), []); callback(new Error('[[error:no-plugins-available]]'), []);
} }
}; };
}(exports)); }(exports));

@ -330,7 +330,7 @@ module.exports = function (Topics) {
function check(item, min, max, minError, maxError, callback) { function check(item, min, max, minError, maxError, callback) {
// Trim and remove HTML (latter for composers that send in HTML, like redactor) // Trim and remove HTML (latter for composers that send in HTML, like redactor)
if (typeof item === 'string') { if (typeof item === 'string') {
item = S(item.trim()).stripTags().s; item = S(item).stripTags().s.trim();
} }
if (!item || item.length < parseInt(min, 10)) { if (!item || item.length < parseInt(min, 10)) {

@ -6,70 +6,38 @@ var _ = require('underscore');
var groups = require('./groups'); var groups = require('./groups');
var plugins = require('./plugins'); var plugins = require('./plugins');
var db = require('./database'); var db = require('./database');
var topics = require('./topics');
var privileges = require('./privileges'); var privileges = require('./privileges');
var meta = require('./meta'); var meta = require('./meta');
(function (User) { var User = module.exports;
User.email = require('./user/email');
User.notifications = require('./user/notifications'); User.email = require('./user/email');
User.reset = require('./user/reset'); User.notifications = require('./user/notifications');
User.digest = require('./user/digest'); User.reset = require('./user/reset');
User.digest = require('./user/digest');
require('./user/data')(User);
require('./user/auth')(User); require('./user/data')(User);
require('./user/bans')(User); require('./user/auth')(User);
require('./user/create')(User); require('./user/bans')(User);
require('./user/posts')(User); require('./user/create')(User);
require('./user/topics')(User); require('./user/posts')(User);
require('./user/categories')(User); require('./user/topics')(User);
require('./user/follow')(User); require('./user/categories')(User);
require('./user/profile')(User); require('./user/follow')(User);
require('./user/admin')(User); require('./user/profile')(User);
require('./user/delete')(User); require('./user/admin')(User);
require('./user/settings')(User); require('./user/delete')(User);
require('./user/search')(User); require('./user/settings')(User);
require('./user/jobs')(User); require('./user/search')(User);
require('./user/picture')(User); require('./user/jobs')(User);
require('./user/approval')(User); require('./user/picture')(User);
require('./user/invite')(User); require('./user/approval')(User);
require('./user/password')(User); require('./user/invite')(User);
require('./user/info')(User); require('./user/password')(User);
require('./user/info')(User);
User.updateLastOnlineTime = function (uid, callback) { require('./user/online')(User);
callback = callback || function () {};
db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) { User.getUidsFromSet = function (set, start, stop, callback) {
var now = Date.now();
if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) {
return callback(err);
}
User.setUserField(uid, 'lastonline', now, callback);
});
};
User.updateOnlineUsers = function (uid, callback) {
callback = callback || function () {};
var now = Date.now();
async.waterfall([
function (next) {
db.sortedSetScore('users:online', uid, next);
},
function (userOnlineTime, next) {
if (now - parseInt(userOnlineTime, 10) < 300000) {
return callback();
}
db.sortedSetAdd('users:online', now, uid, next);
},
function (next) {
topics.pushUnreadCount(uid);
plugins.fireHook('action:user.online', { uid: uid, timestamp: now });
next();
},
], callback);
};
User.getUidsFromSet = function (set, start, stop, callback) {
if (set === 'users:online') { if (set === 'users:online') {
var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1;
var now = Date.now(); var now = Date.now();
@ -77,9 +45,9 @@ var meta = require('./meta');
} else { } else {
db.getSortedSetRevRange(set, start, stop, callback); db.getSortedSetRevRange(set, start, stop, callback);
} }
}; };
User.getUsersFromSet = function (set, uid, start, stop, callback) { User.getUsersFromSet = function (set, uid, start, stop, callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
User.getUidsFromSet(set, start, stop, next); User.getUidsFromSet(set, start, stop, next);
@ -88,9 +56,9 @@ var meta = require('./meta');
User.getUsers(uids, uid, next); User.getUsers(uids, uid, next);
}, },
], callback); ], callback);
}; };
User.getUsersWithFields = function (uids, fields, uid, callback) { User.getUsersWithFields = function (uids, fields, uid, callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
plugins.fireHook('filter:users.addFields', { fields: fields }, next); plugins.fireHook('filter:users.addFields', { fields: fields }, next);
@ -126,86 +94,64 @@ var meta = require('./meta');
next(null, data.users); next(null, data.users);
}, },
], callback); ], callback);
}; };
User.getUsers = function (uids, uid, callback) { User.getUsers = function (uids, uid, callback) {
var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags',
'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; 'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline'];
User.getUsersWithFields(uids, fields, uid, callback); User.getUsersWithFields(uids, fields, uid, callback);
}; };
User.getStatus = function (userData) { User.getStatus = function (userData) {
var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000; var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000;
return isOnline ? (userData.status || 'online') : 'offline'; return isOnline ? (userData.status || 'online') : 'offline';
}; };
User.isOnline = function (uid, callback) { User.exists = function (uid, callback) {
if (Array.isArray(uid)) {
db.sortedSetScores('users:online', uid, function (err, lastonline) {
if (err) {
return callback(err);
}
var now = Date.now();
var isOnline = uid.map(function (uid, index) {
return now - lastonline[index] < 300000;
});
callback(null, isOnline);
});
} else {
db.sortedSetScore('users:online', uid, function (err, lastonline) {
if (err) {
return callback(err);
}
var isOnline = Date.now() - parseInt(lastonline, 10) < 300000;
callback(null, isOnline);
});
}
};
User.exists = function (uid, callback) {
db.isSortedSetMember('users:joindate', uid, callback); db.isSortedSetMember('users:joindate', uid, callback);
}; };
User.existsBySlug = function (userslug, callback) { User.existsBySlug = function (userslug, callback) {
User.getUidByUserslug(userslug, function (err, exists) { User.getUidByUserslug(userslug, function (err, exists) {
callback(err, !!exists); callback(err, !!exists);
}); });
}; };
User.getUidByUsername = function (username, callback) { User.getUidByUsername = function (username, callback) {
if (!username) { if (!username) {
return callback(null, 0); return callback(null, 0);
} }
db.sortedSetScore('username:uid', username, callback); db.sortedSetScore('username:uid', username, callback);
}; };
User.getUidsByUsernames = function (usernames, callback) { User.getUidsByUsernames = function (usernames, callback) {
db.sortedSetScores('username:uid', usernames, callback); db.sortedSetScores('username:uid', usernames, callback);
}; };
User.getUidByUserslug = function (userslug, callback) { User.getUidByUserslug = function (userslug, callback) {
if (!userslug) { if (!userslug) {
return callback(null, 0); return callback(null, 0);
} }
db.sortedSetScore('userslug:uid', userslug, callback); db.sortedSetScore('userslug:uid', userslug, callback);
}; };
User.getUsernamesByUids = function (uids, callback) {
User.getUsersFields(uids, ['username'], function (err, users) {
if (err) {
return callback(err);
}
User.getUsernamesByUids = function (uids, callback) {
async.waterfall([
function (next) {
User.getUsersFields(uids, ['username'], next);
},
function (users, next) {
users = users.map(function (user) { users = users.map(function (user) {
return user.username; return user.username;
}); });
callback(null, users); next(null, users);
}); },
}; ], callback);
};
User.getUsernameByUserslug = function (slug, callback) { User.getUsernameByUserslug = function (slug, callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
User.getUidByUserslug(slug, next); User.getUidByUserslug(slug, next);
@ -214,119 +160,112 @@ var meta = require('./meta');
User.getUserField(uid, 'username', next); User.getUserField(uid, 'username', next);
}, },
], callback); ], callback);
}; };
User.getUidByEmail = function (email, callback) { User.getUidByEmail = function (email, callback) {
db.sortedSetScore('email:uid', email.toLowerCase(), callback); db.sortedSetScore('email:uid', email.toLowerCase(), callback);
}; };
User.getUidsByEmails = function (emails, callback) { User.getUidsByEmails = function (emails, callback) {
emails = emails.map(function (email) { emails = emails.map(function (email) {
return email && email.toLowerCase(); return email && email.toLowerCase();
}); });
db.sortedSetScores('email:uid', emails, callback); db.sortedSetScores('email:uid', emails, callback);
}; };
User.getUsernameByEmail = function (email, callback) { User.getUsernameByEmail = function (email, callback) {
db.sortedSetScore('email:uid', email.toLowerCase(), function (err, uid) { async.waterfall([
if (err) { function (next) {
return callback(err); db.sortedSetScore('email:uid', email.toLowerCase(), next);
} },
User.getUserField(uid, 'username', callback); function (uid, next) {
}); User.getUserField(uid, 'username', next);
}; },
], callback);
};
User.isModerator = function (uid, cid, callback) { User.isModerator = function (uid, cid, callback) {
privileges.users.isModerator(uid, cid, callback); privileges.users.isModerator(uid, cid, callback);
}; };
User.isModeratorOfAnyCategory = function (uid, callback) { User.isModeratorOfAnyCategory = function (uid, callback) {
User.getModeratedCids(uid, function (err, cids) { User.getModeratedCids(uid, function (err, cids) {
callback(err, Array.isArray(cids) ? !!cids.length : false); callback(err, Array.isArray(cids) ? !!cids.length : false);
}); });
}; };
User.isAdministrator = function (uid, callback) { User.isAdministrator = function (uid, callback) {
privileges.users.isAdministrator(uid, callback); privileges.users.isAdministrator(uid, callback);
}; };
User.isGlobalModerator = function (uid, callback) { User.isGlobalModerator = function (uid, callback) {
privileges.users.isGlobalModerator(uid, callback); privileges.users.isGlobalModerator(uid, callback);
}; };
User.isPrivileged = function (uid, callback) { User.isAdminOrGlobalMod = function (uid, callback) {
async.parallel([
async.apply(User.isAdministrator, uid),
async.apply(User.isGlobalModerator, uid),
async.apply(User.isModeratorOfAnyCategory, uid),
], function (err, results) {
callback(err, results ? results.some(Boolean) : false);
});
};
User.isAdminOrGlobalMod = function (uid, callback) {
async.parallel({ async.parallel({
isAdmin: async.apply(User.isAdministrator, uid), isAdmin: async.apply(User.isAdministrator, uid),
isGlobalMod: async.apply(User.isGlobalModerator, uid), isGlobalMod: async.apply(User.isGlobalModerator, uid),
}, function (err, results) { }, function (err, results) {
callback(err, results ? (results.isAdmin || results.isGlobalMod) : false); callback(err, results ? (results.isAdmin || results.isGlobalMod) : false);
}); });
}; };
User.isAdminOrSelf = function (callerUid, uid, callback) { User.isAdminOrSelf = function (callerUid, uid, callback) {
if (parseInt(callerUid, 10) === parseInt(uid, 10)) { isSelfOrMethod(callerUid, uid, User.isAdministrator, callback);
return callback(); };
}
User.isAdministrator(callerUid, function (err, isAdmin) { User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) {
if (err || !isAdmin) { isSelfOrMethod(callerUid, uid, User.isAdminOrGlobalMod, callback);
return callback(err || new Error('[[error:no-privileges]]')); };
}
callback();
});
};
User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) { function isSelfOrMethod(callerUid, uid, method, callback) {
if (parseInt(callerUid, 10) === parseInt(uid, 10)) { if (parseInt(callerUid, 10) === parseInt(uid, 10)) {
return callback(); return callback();
} }
User.isAdminOrGlobalMod(callerUid, function (err, isAdminOrGlobalMod) { async.waterfall([
if (err || !isAdminOrGlobalMod) { function (next) {
return callback(err || new Error('[[error:no-privileges]]')); method(callerUid, next);
},
function (isPass, next) {
if (!isPass) {
return next(new Error('[[error:no-privileges]]'));
} }
callback(); next();
}); },
}; ], callback);
}
User.getAdminsandGlobalMods = function (callback) { User.getAdminsandGlobalMods = function (callback) {
async.parallel({ async.waterfall([
admins: async.apply(groups.getMembers, 'administrators', 0, -1), function (next) {
mods: async.apply(groups.getMembers, 'Global Moderators', 0, -1), async.parallel([
}, function (err, results) { async.apply(groups.getMembers, 'administrators', 0, -1),
if (err) { async.apply(groups.getMembers, 'Global Moderators', 0, -1),
return callback(err); ], next);
} },
var uids = results.admins.concat(results.mods).filter(function (uid, index, array) { function (results, next) {
return uid && array.indexOf(uid) === index; User.getUsersData(_.union(results), next);
}); },
User.getUsersData(uids, callback); ], callback);
}); };
};
User.getAdminsandGlobalModsandModerators = function (callback) { User.getAdminsandGlobalModsandModerators = function (callback) {
async.waterfall([
function (next) {
async.parallel([ async.parallel([
async.apply(groups.getMembers, 'administrators', 0, -1), async.apply(groups.getMembers, 'administrators', 0, -1),
async.apply(groups.getMembers, 'Global Moderators', 0, -1), async.apply(groups.getMembers, 'Global Moderators', 0, -1),
async.apply(User.getModeratorUids), async.apply(User.getModeratorUids),
], function (err, results) { ], next);
if (err) { },
return callback(err); function (results, next) {
} User.getUsersData(_.union.apply(_, results), next);
},
User.getUsersData(_.union.apply(_, results), callback); ], callback);
}); };
};
User.getModeratorUids = function (callback) { User.getModeratorUids = function (callback) {
async.waterfall([ async.waterfall([
async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), async.apply(db.getSortedSetRange, 'categories:cid', 0, -1),
function (cids, next) { function (cids, next) {
@ -334,18 +273,15 @@ var meta = require('./meta');
return 'cid:' + cid + ':privileges:mods'; return 'cid:' + cid + ':privileges:mods';
}); });
groups.getMembersOfGroups(groupNames, function (err, memberSets) { groups.getMembersOfGroups(groupNames, next);
if (err) { },
return next(err); function (memberSets, next) {
}
next(null, _.union.apply(_, memberSets)); next(null, _.union.apply(_, memberSets));
});
}, },
], callback); ], callback);
}; };
User.getModeratedCids = function (uid, callback) { User.getModeratedCids = function (uid, callback) {
var cids; var cids;
async.waterfall([ async.waterfall([
function (next) { function (next) {
@ -362,9 +298,9 @@ var meta = require('./meta');
next(null, cids); next(null, cids);
}, },
], callback); ], callback);
}; };
User.addInterstitials = function (callback) { User.addInterstitials = function (callback) {
plugins.registerHook('core', { plugins.registerHook('core', {
hook: 'filter:register.interstitial', hook: 'filter:register.interstitial',
method: function (data, callback) { method: function (data, callback) {
@ -389,5 +325,5 @@ var meta = require('./meta');
}); });
callback(); callback();
}; };
}(exports));

@ -16,13 +16,7 @@ module.exports = function (User) {
}; };
User.getIPs = function (uid, stop, callback) { User.getIPs = function (uid, stop, callback) {
db.getSortedSetRevRange('uid:' + uid + ':ip', 0, stop, function (err, ips) { db.getSortedSetRevRange('uid:' + uid + ':ip', 0, stop, callback);
if (err) {
return callback(err);
}
callback(null, ips);
});
}; };
User.getUsersCSV = function (callback) { User.getUsersCSV = function (callback) {

@ -1,5 +1,6 @@
'use strict'; 'use strict';
var async = require('async');
var validator = require('validator'); var validator = require('validator');
var nconf = require('nconf'); var nconf = require('nconf');
var winston = require('winston'); var winston = require('winston');
@ -63,6 +64,7 @@ module.exports = function (User) {
addField('lastonline'); addField('lastonline');
} }
<<<<<<< HEAD
db.getObjectsFields(keys, fields, function (err, users) { db.getObjectsFields(keys, fields, function (err, users) {
if (err) { if (err) {
return callback(err); return callback(err);
@ -74,6 +76,16 @@ module.exports = function (User) {
modifyUserData(users, fieldsToRemove, callback); modifyUserData(users, fieldsToRemove, callback);
}); });
=======
async.waterfall([
function (next) {
db.getObjectsFields(keys, fields, next);
},
function (users, next) {
modifyUserData(users, fieldsToRemove, next);
},
], callback);
>>>>>>> master
}; };
User.getMultipleUserFields = function (uids, fields, callback) { User.getMultipleUserFields = function (uids, fields, callback) {
@ -105,6 +117,7 @@ module.exports = function (User) {
return 'user:' + uid; return 'user:' + uid;
}); });
<<<<<<< HEAD
db.getObjects(keys, function (err, users) { db.getObjects(keys, function (err, users) {
if (err) { if (err) {
return callback(err); return callback(err);
@ -116,6 +129,16 @@ module.exports = function (User) {
modifyUserData(users, [], callback); modifyUserData(users, [], callback);
}); });
=======
async.waterfall([
function (next) {
db.getObjects(keys, next);
},
function (users, next) {
modifyUserData(users, [], next);
},
], callback);
>>>>>>> master
}; };
function modifyUserData(users, fieldsToRemove, callback) { function modifyUserData(users, fieldsToRemove, callback) {
@ -178,51 +201,53 @@ module.exports = function (User) {
User.setUserField = function (uid, field, value, callback) { User.setUserField = function (uid, field, value, callback) {
callback = callback || function () {}; callback = callback || function () {};
db.setObjectField('user:' + uid, field, value, function (err) { async.waterfall([
if (err) { function (next) {
return callback(err); db.setObjectField('user:' + uid, field, value, next);
} },
function (next) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'set' }); plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'set' });
callback(); next();
}); },
], callback);
}; };
User.setUserFields = function (uid, data, callback) { User.setUserFields = function (uid, data, callback) {
callback = callback || function () {}; callback = callback || function () {};
db.setObject('user:' + uid, data, function (err) { async.waterfall([
if (err) { function (next) {
return callback(err); db.setObject('user:' + uid, data, next);
} },
function (next) {
for (var field in data) { for (var field in data) {
if (data.hasOwnProperty(field)) { if (data.hasOwnProperty(field)) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' }); plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' });
} }
} }
callback(); next();
}); },
], callback);
}; };
User.incrementUserFieldBy = function (uid, field, value, callback) { User.incrementUserFieldBy = function (uid, field, value, callback) {
callback = callback || function () {}; incrDecrUserFieldBy(uid, field, value, 'increment', callback);
db.incrObjectFieldBy('user:' + uid, field, value, function (err, value) {
if (err) {
return callback(err);
}
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'increment' });
callback(null, value);
});
}; };
User.decrementUserFieldBy = function (uid, field, value, callback) { User.decrementUserFieldBy = function (uid, field, value, callback) {
incrDecrUserFieldBy(uid, field, -value, 'decrement', callback);
};
function incrDecrUserFieldBy(uid, field, value, type, callback) {
callback = callback || function () {}; callback = callback || function () {};
db.incrObjectFieldBy('user:' + uid, field, -value, function (err, value) { async.waterfall([
if (err) { function (next) {
return callback(err); db.incrObjectFieldBy('user:' + uid, field, value, next);
} },
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'decrement' }); function (value, next) {
plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: type });
callback(null, value); next(null, value);
}); },
}; ], callback);
}
}; };

@ -0,0 +1,70 @@
'use strict';
var async = require('async');
var db = require('../database');
var topics = require('../topics');
var plugins = require('../plugins');
module.exports = function (User) {
User.updateLastOnlineTime = function (uid, callback) {
callback = callback || function () {};
db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) {
var now = Date.now();
if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) {
return callback(err);
}
User.setUserField(uid, 'lastonline', now, callback);
});
};
User.updateOnlineUsers = function (uid, callback) {
callback = callback || function () {};
var now = Date.now();
async.waterfall([
function (next) {
db.sortedSetScore('users:online', uid, next);
},
function (userOnlineTime, next) {
if (now - parseInt(userOnlineTime, 10) < 300000) {
return callback();
}
db.sortedSetAdd('users:online', now, uid, next);
},
function (next) {
topics.pushUnreadCount(uid);
plugins.fireHook('action:user.online', { uid: uid, timestamp: now });
next();
},
], callback);
};
User.isOnline = function (uid, callback) {
var now = Date.now();
async.waterfall([
function (next) {
if (Array.isArray(uid)) {
db.sortedSetScores('users:online', uid, next);
} else {
db.sortedSetScore('users:online', uid, next);
}
},
function (lastonline, next) {
function checkOnline(lastonline) {
return now - lastonline < 300000;
}
var isOnline;
if (Array.isArray(uid)) {
isOnline = uid.map(function (uid, index) {
return checkOnline(lastonline[index]);
});
} else {
isOnline = checkOnline(lastonline);
}
next(null, isOnline);
},
], callback);
};
};

@ -50,12 +50,7 @@ module.exports = function (User) {
}, next); }, next);
}, },
function (image, next) { function (image, next) {
User.setUserFields(uid, { next(null, image);
uploadedpicture: image.url,
picture: image.url,
}, function (err) {
next(err, image);
});
}, },
], callback); ], callback);
}; };

@ -18,6 +18,14 @@ module.exports = function (User) {
var updateUid = data.uid; var updateUid = data.uid;
var oldData; var oldData;
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
}
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
return callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
}
async.waterfall([ async.waterfall([
function (next) { function (next) {
plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next); plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next);
@ -27,8 +35,6 @@ module.exports = function (User) {
data = data.data; data = data.data;
async.series([ async.series([
async.apply(isAboutMeValid, data),
async.apply(isSignatureValid, data),
async.apply(isEmailAvailable, data, updateUid), async.apply(isEmailAvailable, data, updateUid),
async.apply(isUsernameAvailable, data, updateUid), async.apply(isUsernameAvailable, data, updateUid),
async.apply(isGroupTitleValid, data), async.apply(isGroupTitleValid, data),
@ -68,22 +74,6 @@ module.exports = function (User) {
], callback); ], callback);
}; };
function isAboutMeValid(data, callback) {
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
} else {
callback();
}
}
function isSignatureValid(data, callback) {
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
} else {
callback();
}
}
function isEmailAvailable(data, uid, callback) { function isEmailAvailable(data, uid, callback) {
if (!data.email) { if (!data.email) {
return callback(); return callback();

@ -74,7 +74,7 @@ module.exports = function (User) {
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1; settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1;
settings.bootswatchSkin = settings.bootswatchSkin || 'default'; settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default';
settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1; settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1;
callback(null, settings); callback(null, settings);

@ -36,13 +36,7 @@
<li data-plugin-id="{installed.id}" class="clearfix"> <li data-plugin-id="{installed.id}" class="clearfix">
<div class="pull-right"> <div class="pull-right">
<button class="btn btn-default disabled"><i class="fa fa-exclamation-triangle"></i> [[admin/extend/plugins:plugin-item.unknown]]</button> <button class="btn btn-default disabled"><i class="fa fa-exclamation-triangle"></i> [[admin/extend/plugins:plugin-item.unknown]]</button>
<<<<<<< HEAD
<button data-action="toggleInstall" data-installed="1" class="btn btn-danger"><i class="fa fa-trash-o"></i> Uninstall</button>
=======
<button data-action="toggleInstall" data-installed="1" class="btn btn-danger"><i class="fa fa-trash-o"></i> [[admin/extend/plugins:plugin-item.uninstall]]</button> <button data-action="toggleInstall" data-installed="1" class="btn btn-danger"><i class="fa fa-trash-o"></i> [[admin/extend/plugins:plugin-item.uninstall]]</button>
>>>>>>> `admin/extend` translations
</div> </div>
<h2><strong>{installed.id}</strong></h2> <h2><strong>{installed.id}</strong></h2>

@ -61,11 +61,9 @@ module.exports.listen = function (callback) {
logger.init(app); logger.init(app);
initializeNodeBB(function (err) { async.waterfall([
if (err) { initializeNodeBB,
return callback(err); function (next) {
}
winston.info('NodeBB Ready'); winston.info('NodeBB Ready');
require('./socket.io').server.emit('event:nodebb.ready', { require('./socket.io').server.emit('event:nodebb.ready', {
@ -74,8 +72,9 @@ module.exports.listen = function (callback) {
plugins.fireHook('action:nodebb.ready'); plugins.fireHook('action:nodebb.ready');
listen(callback); listen(next);
}); },
], callback);
}; };
function initializeNodeBB(callback) { function initializeNodeBB(callback) {
@ -107,7 +106,9 @@ function initializeNodeBB(callback) {
meta.blacklist.load, meta.blacklist.load,
], next); ], next);
}, },
], callback); ], function (err) {
callback(err);
});
} }
function setupExpressApp(app) { function setupExpressApp(app) {

@ -390,6 +390,7 @@ describe('Categories', function () {
it('should get all categories', function (done) { it('should get all categories', function (done) {
socketCategories.getAll({ uid: adminUid }, {}, function (err, data) { socketCategories.getAll({ uid: adminUid }, {}, function (err, data) {
assert.ifError(err); assert.ifError(err);
assert(data);
done(); done();
}); });
}); });
@ -615,6 +616,72 @@ describe('Categories', function () {
}); });
describe('privileges', function () {
var privileges = require('../src/privileges');
it('should return empty array if uids is empty array', function (done) {
privileges.categories.filterUids('find', categoryObj.cid, [], function (err, uids) {
assert.ifError(err);
assert.equal(uids.length, 0);
done();
});
});
it('should filter uids by privilege', function (done) {
privileges.categories.filterUids('find', categoryObj.cid, [1, 2, 3, 4], function (err, uids) {
assert.ifError(err);
assert.deepEqual(uids, [1, 2]);
done();
});
});
it('should load user privileges', function (done) {
privileges.categories.userPrivileges(categoryObj.cid, 1, function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
find: false,
mods: false,
'posts:delete': false,
read: false,
'topics:reply': false,
'topics:read': false,
'topics:create': false,
'topics:delete': false,
'posts:edit': false,
});
done();
});
});
it('should load group privileges', function (done) {
privileges.categories.groupPrivileges(categoryObj.cid, 'registered-users', function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
'groups:find': true,
'groups:posts:edit': true,
'groups:topics:delete': false,
'groups:topics:create': true,
'groups:topics:reply': true,
'groups:posts:delete': true,
'groups:read': true,
'groups:topics:read': true,
});
done();
});
});
it('should return false if cid is falsy', function (done) {
privileges.categories.isUserAllowedTo('find', null, adminUid, function (err, isAllowed) {
assert.ifError(err);
assert.equal(isAllowed, false);
done();
});
});
});
after(function (done) { after(function (done) {
db.emptydb(done); db.emptydb(done);
}); });

@ -21,6 +21,7 @@ describe('Admin Controllers', function () {
var jar; var jar;
before(function (done) { before(function (done) {
groups.resetCache();
async.series({ async.series({
category: function (next) { category: function (next) {
categories.create({ categories.create({
@ -43,9 +44,10 @@ describe('Admin Controllers', function () {
cid = results.category.cid; cid = results.category.cid;
topics.post({ uid: adminUid, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, function (err, result) { topics.post({ uid: adminUid, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, function (err, result) {
assert.ifError(err);
tid = result.topicData.tid; tid = result.topicData.tid;
pid = result.postData.pid; pid = result.postData.pid;
done(err); done();
}); });
}); });
}); });

@ -27,7 +27,7 @@ describe('Controllers', function () {
}, next); }, next);
}, },
user: function (next) { user: function (next) {
user.create({ username: 'foo', password: 'barbar' }, next); user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' }, next);
}, },
navigation: function (next) { navigation: function (next) {
var navigation = require('../src/navigation/admin'); var navigation = require('../src/navigation/admin');
@ -498,14 +498,26 @@ describe('Controllers', function () {
hidden: 0, hidden: 0,
}, function (err) { }, function (err) {
assert.ifError(err); assert.ifError(err);
request(nconf.get('url') + '/groups/group-details', function (err, res, body) { groups.join('group-details', fooUid, function (err) {
assert.ifError(err);
topics.post({
uid: fooUid,
title: 'topic title',
content: 'test topic content',
cid: cid,
}, function (err) {
assert.ifError(err);
request(nconf.get('url') + '/api/groups/group-details', { json: true }, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(body); assert(body);
assert.equal(body.posts[0].content, 'test topic content');
done(); done();
}); });
}); });
}); });
});
});
it('should load group members page', function (done) { it('should load group members page', function (done) {
request(nconf.get('url') + '/groups/group-details/members', function (err, res, body) { request(nconf.get('url') + '/groups/group-details/members', function (err, res, body) {
@ -532,6 +544,15 @@ describe('Controllers', function () {
}); });
}); });
it('should get recent posts', function (done) {
request(nconf.get('url') + '/api/recent/posts/month', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should get post data', function (done) { it('should get post data', function (done) {
request(nconf.get('url') + '/api/post/pid/' + pid, function (err, res, body) { request(nconf.get('url') + '/api/post/pid/' + pid, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
@ -890,6 +911,42 @@ describe('Controllers', function () {
}, },
], done); ], done);
}); });
it('should 404 if user does not exist', function (done) {
request(nconf.get('url') + '/api/user/email/doesnotexist', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 404);
assert(body);
done();
});
});
it('should load user by uid', function (done) {
request(nconf.get('url') + '/api/user/uid/' + fooUid, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should load user by username', function (done) {
request(nconf.get('url') + '/api/user/username/foo', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should load user by email', function (done) {
request(nconf.get('url') + '/api/user/email/foo@test.com', function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
}); });
describe('account follow page', function () { describe('account follow page', function () {
@ -943,7 +1000,7 @@ describe('Controllers', function () {
describe('post redirect', function () { describe('post redirect', function () {
it('should 404 for invalid pid', function (done) { it('should 404 for invalid pid', function (done) {
request(nconf.get('url') + '/post/fail', function (err, res) { request(nconf.get('url') + '/api/post/fail', function (err, res) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 404); assert.equal(res.statusCode, 404);
done(); done();

@ -11,6 +11,7 @@ var categories = require('../src/categories');
var privileges = require('../src/privileges'); var privileges = require('../src/privileges');
var user = require('../src/user'); var user = require('../src/user');
var groups = require('../src/groups'); var groups = require('../src/groups');
var socketPosts = require('../src/socket.io/posts');
describe('Post\'s', function () { describe('Post\'s', function () {
var voterUid; var voterUid;
@ -66,7 +67,6 @@ describe('Post\'s', function () {
}); });
describe('voting', function () { describe('voting', function () {
var socketPosts = require('../src/socket.io/posts');
it('should upvote a post', function (done) { it('should upvote a post', function (done) {
socketPosts.upvote({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_1' }, function (err, result) { socketPosts.upvote({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_1' }, function (err, result) {
assert.ifError(err); assert.ifError(err);
@ -138,7 +138,7 @@ describe('Post\'s', function () {
describe('bookmarking', function () { describe('bookmarking', function () {
it('should bookmark a post', function (done) { it('should bookmark a post', function (done) {
posts.bookmark(postData.pid, voterUid, function (err, data) { socketPosts.bookmark({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_' + postData.tid }, function (err, data) {
assert.ifError(err); assert.ifError(err);
assert.equal(data.isBookmarked, true); assert.equal(data.isBookmarked, true);
posts.hasBookmarked(postData.pid, voterUid, function (err, hasBookmarked) { posts.hasBookmarked(postData.pid, voterUid, function (err, hasBookmarked) {
@ -150,7 +150,7 @@ describe('Post\'s', function () {
}); });
it('should unbookmark a post', function (done) { it('should unbookmark a post', function (done) {
posts.unbookmark(postData.pid, voterUid, function (err, data) { socketPosts.unbookmark({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_' + postData.tid }, function (err, data) {
assert.ifError(err); assert.ifError(err);
assert.equal(data.isBookmarked, false); assert.equal(data.isBookmarked, false);
posts.hasBookmarked([postData.pid], voterUid, function (err, hasBookmarked) { posts.hasBookmarked([postData.pid], voterUid, function (err, hasBookmarked) {
@ -163,8 +163,6 @@ describe('Post\'s', function () {
}); });
describe('post tools', function () { describe('post tools', function () {
var socketPosts = require('../src/socket.io/posts');
it('should error if data is invalid', function (done) { it('should error if data is invalid', function (done) {
socketPosts.loadPostTools({ uid: globalModUid }, null, function (err) { socketPosts.loadPostTools({ uid: globalModUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]'); assert.equal(err.message, '[[error:invalid-data]]');
@ -209,7 +207,6 @@ describe('Post\'s', function () {
var mainPid; var mainPid;
var replyPid; var replyPid;
var socketPosts = require('../src/socket.io/posts');
before(function (done) { before(function (done) {
createTopicWithReply(function (topicPostData, replyData) { createTopicWithReply(function (topicPostData, replyData) {
tid = topicPostData.topicData.tid; tid = topicPostData.topicData.tid;
@ -299,7 +296,6 @@ describe('Post\'s', function () {
var pid; var pid;
var replyPid; var replyPid;
var tid; var tid;
var socketPosts = require('../src/socket.io/posts');
var meta = require('../src/meta'); var meta = require('../src/meta');
before(function (done) { before(function (done) {
topics.post({ topics.post({
@ -430,7 +426,6 @@ describe('Post\'s', function () {
var replyPid; var replyPid;
var tid; var tid;
var moveTid; var moveTid;
var socketPosts = require('../src/socket.io/posts');
before(function (done) { before(function (done) {
async.waterfall([ async.waterfall([
@ -554,7 +549,6 @@ describe('Post\'s', function () {
}); });
}); });
var socketPosts = require('../src/socket.io/posts');
it('should error with invalid data', function (done) { it('should error with invalid data', function (done) {
socketPosts.reply({ uid: 0 }, null, function (err) { socketPosts.reply({ uid: 0 }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]'); assert.equal(err.message, '[[error:invalid-data]]');

@ -419,11 +419,16 @@ describe('socket.io', function () {
}); });
it('should set theme to bootswatch', function (done) { it('should set theme to bootswatch', function (done) {
socketAdmin.themes.set({ uid: adminUid }, { type: 'bootswatch', src: 'darkly' }, function (err) { socketAdmin.themes.set({ uid: adminUid }, {
type: 'bootswatch',
src: '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css',
id: 'darkly',
}, function (err) {
assert.ifError(err); assert.ifError(err);
meta.configs.get('theme:src', function (err, id) { meta.configs.getFields(['theme:src', 'bootswatchSkin'], function (err, fields) {
assert.ifError(err); assert.ifError(err);
assert.equal(id, 'darkly'); assert.equal(fields['theme:src'], '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css');
assert.equal(fields.bootswatchSkin, 'darkly');
done(); done();
}); });
}); });

@ -12,6 +12,7 @@ var User = require('../src/user');
var groups = require('../src/groups'); var groups = require('../src/groups');
var helpers = require('./helpers'); var helpers = require('./helpers');
var socketPosts = require('../src/socket.io/posts'); var socketPosts = require('../src/socket.io/posts');
var socketTopics = require('../src/socket.io/topics');
describe('Topic\'s', function () { describe('Topic\'s', function () {
var topic; var topic;
@ -49,11 +50,34 @@ describe('Topic\'s', function () {
}); });
describe('.post', function () { describe('.post', function () {
it('should fail to create topic with invalid data', function (done) {
socketTopics.post({ uid: 0 }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should create a new topic with proper parameters', function (done) { it('should create a new topic with proper parameters', function (done) {
topics.post({ uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId }, function (err, result) { topics.post({ uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId }, function (err, result) {
assert.equal(err, null, 'was created with error'); assert.ifError(err);
assert.ok(result); assert(result);
topic.tid = result.topicData.tid;
done();
});
});
it('should get post count', function (done) {
socketTopics.postcount({ uid: adminUid }, topic.tid, function (err, count) {
assert.ifError(err);
assert.equal(count, 1);
done();
});
});
it('should load topic', function (done) {
socketTopics.getTopic({ uid: adminUid }, topic.tid, function (err, data) {
assert.ifError(err);
assert.equal(data.tid, topic.tid);
done(); done();
}); });
}); });
@ -246,7 +270,7 @@ describe('Topic\'s', function () {
var newTopic; var newTopic;
var followerUid; var followerUid;
var moveCid; var moveCid;
var socketTopics = require('../src/socket.io/topics');
before(function (done) { before(function (done) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
@ -589,8 +613,7 @@ describe('Topic\'s', function () {
assert.ok(result); assert.ok(result);
replies.push(result); replies.push(result);
next(); next();
} });
);
} }
before(function (done) { before(function (done) {
@ -619,25 +642,45 @@ describe('Topic\'s', function () {
function (next) { postReply(next); }, function (next) { postReply(next); },
function (next) { function (next) {
topicPids = replies.map(function (reply) { return reply.pid; }); topicPids = replies.map(function (reply) { return reply.pid; });
topics.setUserBookmark(newTopic.tid, topic.userId, originalBookmark, next); socketTopics.bookmark({ uid: topic.userId }, { tid: newTopic.tid, index: originalBookmark }, next);
}], }],
done); done);
}); });
it('should fail with invalid data', function (done) {
socketTopics.bookmark({ uid: topic.userId }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should have 12 replies', function (done) { it('should have 12 replies', function (done) {
assert.equal(12, replies.length); assert.equal(12, replies.length);
done(); done();
}); });
it('should fail with invalid data', function (done) {
socketTopics.createTopicFromPosts({ uid: 0 }, null, function (err) {
assert.equal(err.message, '[[error:not-logged-in]]');
done();
});
});
it('should fail with invalid data', function (done) {
socketTopics.createTopicFromPosts({ uid: 1 }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should not update the user\'s bookmark', function (done) { it('should not update the user\'s bookmark', function (done) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
topics.createTopicFromPosts( socketTopics.createTopicFromPosts({ uid: topic.userId }, {
topic.userId, title: 'Fork test, no bookmark update',
'Fork test, no bookmark update', pids: topicPids.slice(-2),
topicPids.slice(-2), fromTid: newTopic.tid,
newTopic.tid, }, next);
next);
}, },
function (forkedTopicData, next) { function (forkedTopicData, next) {
topics.getUserBookmark(newTopic.tid, topic.userId, next); topics.getUserBookmark(newTopic.tid, topic.userId, next);
@ -1388,6 +1431,13 @@ describe('Topic\'s', function () {
}); });
}); });
it('should error if not logged in', function (done) {
socketTopics.changeWatching({ uid: 0 }, { tid: tid, type: 'ignore' }, function (err) {
assert.equal(err.message, '[[error:not-logged-in]]');
done();
});
});
it('should filter ignoring uids', function (done) { it('should filter ignoring uids', function (done) {
socketTopics.changeWatching({ uid: followerUid }, { tid: tid, type: 'ignore' }, function (err) { socketTopics.changeWatching({ uid: followerUid }, { tid: tid, type: 'ignore' }, function (err) {
assert.ifError(err); assert.ifError(err);
@ -1418,7 +1468,7 @@ describe('Topic\'s', function () {
topics.toggleFollow(tid, followerUid, function (err, isFollowing) { topics.toggleFollow(tid, followerUid, function (err, isFollowing) {
assert.ifError(err); assert.ifError(err);
assert(isFollowing); assert(isFollowing);
topics.isFollowing([tid], followerUid, function (err, isFollowing) { socketTopics.isFollowed({ uid: followerUid }, tid, function (err, isFollowing) {
assert.ifError(err); assert.ifError(err);
assert(isFollowing); assert(isFollowing);
done(); done();
@ -1427,6 +1477,44 @@ describe('Topic\'s', function () {
}); });
}); });
describe('topics search', function () {
it('should error with invalid data', function (done) {
socketTopics.search({ uid: adminUid }, null, function (err) {
assert.equal(err.message, '[[error:invalid-data]]');
done();
});
});
it('should error if no search plugin', function (done) {
socketTopics.search({ uid: adminUid }, { tid: topic.tid, term: 'test' }, function (err) {
assert.equal(err.message, '[[error:no-plugins-available]]');
done();
});
});
it('should return results', function (done) {
var plugins = require('../src/plugins');
plugins.registerHook('myTestPlugin', {
hook: 'filter:topic.search',
method: function (data, callback) {
callback(null, [1, 2, 3]);
},
});
socketTopics.search({ uid: adminUid }, { tid: topic.tid, term: 'test' }, function (err, results) {
assert.ifError(err);
assert.deepEqual(results, [1, 2, 3]);
done();
});
});
});
it('should check if user is moderator', function (done) {
socketTopics.isModerator({ uid: adminUid }, topic.tid, function (err, isModerator) {
assert.ifError(err);
assert(!isModerator);
done();
});
});
after(function (done) { after(function (done) {
db.emptydb(done); db.emptydb(done);

Loading…
Cancel
Save