mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-09-03 22:52:24 +02:00
Merge remote-tracking branch 'origin/master' into mongodb-3.0-driver-2.0
This commit is contained in:
@@ -14,15 +14,16 @@ module.exports = function(Categories) {
|
||||
}
|
||||
|
||||
var slug = cid + '/' + utils.slugify(data.name),
|
||||
order = data.order || cid; // If no order provided, place it at the end
|
||||
order = data.order || cid, // If no order provided, place it at the end
|
||||
colours = Categories.assignColours();
|
||||
|
||||
var category = {
|
||||
cid: cid,
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
icon: data.icon,
|
||||
bgColor: data.bgColor,
|
||||
color: data.color,
|
||||
bgColor: data.bgColor || colours[0],
|
||||
color: data.color || colours[1],
|
||||
slug: slug,
|
||||
parentCid: 0,
|
||||
topic_count: 0,
|
||||
@@ -52,4 +53,12 @@ module.exports = function(Categories) {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Categories.assignColours = function() {
|
||||
var backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'],
|
||||
text = ['#fff', '#fff', '#333', '#fff', '#333', '#fff', '#fff', '#fff'],
|
||||
index = Math.floor(Math.random() * backgrounds.length);
|
||||
|
||||
return [backgrounds[index], text[index]];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ uploadsController.uploadFavicon = function(req, res, next) {
|
||||
var allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
|
||||
|
||||
if (validateUpload(req, res, next, uploadedFile, allowedTypes)) {
|
||||
file.saveFileToLocal('favicon.ico', 'files', uploadedFile.path, function(err, image) {
|
||||
file.saveFileToLocal('favicon.ico', 'system', uploadedFile.path, function(err, image) {
|
||||
fs.unlink(uploadedFile.path);
|
||||
if (err) {
|
||||
return next(err);
|
||||
@@ -55,7 +55,7 @@ function upload(name, req, res, next) {
|
||||
var allowedTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif'];
|
||||
if (validateUpload(req, res, next, uploadedFile, allowedTypes)) {
|
||||
var filename = name + path.extname(uploadedFile.name);
|
||||
uploadImage(filename, 'files', uploadedFile, req, res, next);
|
||||
uploadImage(filename, 'system', uploadedFile, req, res, next);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ apiController.getConfig = function(req, res, next) {
|
||||
config.disableSocialButtons = parseInt(meta.config.disableSocialButtons, 10) === 1;
|
||||
config.disableChat = parseInt(meta.config.disableChat, 10) === 1;
|
||||
config.maxReconnectionAttempts = meta.config.maxReconnectionAttempts || 5;
|
||||
config.reconnectionDelay = meta.config.reconnectionDelay || 200;
|
||||
config.reconnectionDelay = meta.config.reconnectionDelay || 1500;
|
||||
config.tagsPerTopic = meta.config.tagsPerTopic || 5;
|
||||
config.minimumTagLength = meta.config.minimumTagLength || 3;
|
||||
config.maximumTagLength = meta.config.maximumTagLength || 15;
|
||||
|
||||
@@ -55,7 +55,7 @@ categoriesController.popular = function(req, res, next) {
|
||||
var data = {
|
||||
topics: topics,
|
||||
'feeds:disableRSS': parseInt(meta.config['feeds:disableRSS'], 10) === 1,
|
||||
rssFeedUrl: nconf.get('relative_path') + '/popular.rss',
|
||||
rssFeedUrl: nconf.get('relative_path') + '/popular/' + (req.params.term || 'daily') + '.rss',
|
||||
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[global:header.popular]]'}])
|
||||
};
|
||||
|
||||
@@ -200,8 +200,7 @@ categoriesController.get = function(req, res, next) {
|
||||
var topicCount = parseInt(results.categoryData.topic_count, 10);
|
||||
|
||||
if (topicIndex < 0 || topicIndex > Math.max(topicCount - 1, 0)) {
|
||||
var url = '/category/' + cid + '/' + req.params.slug + (topicIndex > topicCount ? '/' + topicCount : '');
|
||||
return res.locals.isAPI ? res.status(302).json(url) : res.redirect(url);
|
||||
return helpers.redirect(res, '/category/' + cid + '/' + req.params.slug + (topicIndex > topicCount ? '/' + topicCount : ''));
|
||||
}
|
||||
|
||||
userPrivileges = results.privileges;
|
||||
|
||||
@@ -51,31 +51,35 @@ groupsController.details = function(req, res, next) {
|
||||
}
|
||||
}
|
||||
], function(err, ok) {
|
||||
if (ok) {
|
||||
async.parallel({
|
||||
group: function(next) {
|
||||
groups.get(res.locals.groupName, {
|
||||
expand: true,
|
||||
uid: uid
|
||||
}, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
groups.getLatestMemberPosts(res.locals.groupName, 10, uid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!results.group) {
|
||||
return helpers.notFound(req, res);
|
||||
}
|
||||
|
||||
res.render('groups/details', results);
|
||||
});
|
||||
} else {
|
||||
return res.locals.isAPI ? res.status(302).json('/groups') : res.redirect('/groups');
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
return helpers.redirect(res, '/groups');
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
group: function(next) {
|
||||
groups.get(res.locals.groupName, {
|
||||
expand: true,
|
||||
uid: uid
|
||||
}, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
groups.getLatestMemberPosts(res.locals.groupName, 10, uid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!results.group) {
|
||||
return helpers.notFound(req, res);
|
||||
}
|
||||
|
||||
res.render('groups/details', results);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -6,12 +6,19 @@ var nconf = require('nconf'),
|
||||
|
||||
translator = require('../../public/src/translator'),
|
||||
categories = require('../categories'),
|
||||
plugins = require('../plugins'),
|
||||
meta = require('../meta');
|
||||
|
||||
var helpers = {};
|
||||
|
||||
helpers.notFound = function(req, res, error) {
|
||||
if (res.locals.isAPI) {
|
||||
if (plugins.hasListeners('action:meta.override404')) {
|
||||
plugins.fireHook('action:meta.override404', {
|
||||
req: req,
|
||||
res: res,
|
||||
error: error
|
||||
});
|
||||
} else if (res.locals.isAPI) {
|
||||
res.status(404).json({path: req.path.replace(/^\/api/, ''), error: error});
|
||||
} else {
|
||||
res.status(404).render('404', {path: req.path, error: error});
|
||||
@@ -38,6 +45,14 @@ helpers.notAllowed = function(req, res, error) {
|
||||
}
|
||||
};
|
||||
|
||||
helpers.redirect = function(res, url) {
|
||||
if (res.locals.isAPI) {
|
||||
res.status(302).json(url);
|
||||
} else {
|
||||
res.redirect(nconf.get('relative_path') + url);
|
||||
}
|
||||
};
|
||||
|
||||
helpers.buildCategoryBreadcrumbs = function(cid, callback) {
|
||||
var breadcrumbs = [];
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
var async = require('async'),
|
||||
nconf = require('nconf'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
meta = require('../meta'),
|
||||
plugins = require('../plugins'),
|
||||
utils = require('../../public/src/utils'),
|
||||
templatesController = {};
|
||||
|
||||
|
||||
var availableTemplatesCache = null;
|
||||
var configCache = null;
|
||||
|
||||
templatesController.getTemplatesListing = function(req, res, next) {
|
||||
async.parallel({
|
||||
availableTemplates: function(next) {
|
||||
getAvailableTemplates(next);
|
||||
},
|
||||
templatesConfig: function(next) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
readConfigFile(next);
|
||||
},
|
||||
function(config, next) {
|
||||
config.custom_mapping['^/?$'] = meta.config.homePageRoute || 'categories';
|
||||
|
||||
plugins.fireHook('filter:templates.get_config', config, next);
|
||||
}
|
||||
], next);
|
||||
},
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json(results);
|
||||
});
|
||||
};
|
||||
|
||||
function readConfigFile(callback) {
|
||||
if (configCache) {
|
||||
return callback(null, configCache);
|
||||
}
|
||||
fs.readFile(path.join(nconf.get('views_dir'), 'config.json'), function(err, config) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
try {
|
||||
config = JSON.parse(config.toString());
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
configCache = config;
|
||||
callback(null, config);
|
||||
});
|
||||
}
|
||||
|
||||
function getAvailableTemplates(callback) {
|
||||
if (availableTemplatesCache) {
|
||||
return callback(null, availableTemplatesCache);
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
views: function(next) {
|
||||
utils.walk(nconf.get('views_dir'), next);
|
||||
},
|
||||
extended: function(next) {
|
||||
plugins.fireHook('filter:templates.get_virtual', [], next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
var availableTemplates = results.views.filter(function(value, index, self) {
|
||||
return value && self.indexOf(value) === index;
|
||||
}).map(function(el) {
|
||||
return el && el.replace(nconf.get('views_dir') + '/', '');
|
||||
});
|
||||
|
||||
availableTemplatesCache = availableTemplates.concat(results.extended);
|
||||
callback(null, availableTemplatesCache);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = templatesController;
|
||||
@@ -56,12 +56,8 @@ topicsController.get = function(req, res, next) {
|
||||
var postCount = parseInt(results.topic.postcount, 10);
|
||||
var pageCount = Math.max(1, Math.ceil((postCount - 1) / settings.postsPerPage));
|
||||
|
||||
if (utils.isNumber(req.params.post_index)) {
|
||||
var url = '';
|
||||
if (req.params.post_index < 1 || req.params.post_index > postCount) {
|
||||
url = '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : '');
|
||||
return res.locals.isAPI ? res.status(302).json(url) : res.redirect(url);
|
||||
}
|
||||
if (utils.isNumber(req.params.post_index) && (req.params.post_index < 1 || req.params.post_index > postCount)) {
|
||||
return helpers.redirect(res, '/topic/' + req.params.topic_id + '/' + req.params.slug + (req.params.post_index > postCount ? '/' + postCount : ''));
|
||||
}
|
||||
|
||||
if (settings.usePagination && (req.query.page < 1 || req.query.page > pageCount)) {
|
||||
@@ -266,7 +262,7 @@ topicsController.get = function(req, res, next) {
|
||||
});
|
||||
|
||||
topics.increaseViewCount(tid);
|
||||
|
||||
|
||||
plugins.fireHook('filter:topic.build', {req: req, res: res, templateData: data}, function(err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
|
||||
@@ -5,6 +5,7 @@ var usersController = {};
|
||||
var async = require('async'),
|
||||
user = require('../user'),
|
||||
meta = require('../meta'),
|
||||
pagination = require('../pagination'),
|
||||
plugins = require('../plugins'),
|
||||
db = require('../database');
|
||||
|
||||
@@ -67,11 +68,13 @@ usersController.getUsers = function(set, count, req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var pageCount = Math.ceil(data.count / (parseInt(meta.config.userSearchResultsPerPage, 10) || 20));
|
||||
var userData = {
|
||||
search_display: 'hidden',
|
||||
loadmore_display: data.count > count ? 'block' : 'hide',
|
||||
users: data.users,
|
||||
show_anon: 'hide'
|
||||
show_anon: 'hide',
|
||||
pagination: pagination.create(1, pageCount)
|
||||
};
|
||||
|
||||
res.render('users', userData);
|
||||
@@ -94,7 +97,7 @@ function getUsersAndCount(set, uid, count, callback) {
|
||||
return user && parseInt(user.uid, 10);
|
||||
});
|
||||
|
||||
callback(null, {users: results.users, count: results.count});
|
||||
callback(null, results);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -204,14 +204,17 @@ module.exports = function(db, module) {
|
||||
if (!key) {
|
||||
return callback();
|
||||
}
|
||||
var scoreQuery = {};
|
||||
|
||||
var query = {_key: key};
|
||||
if (min !== '-inf') {
|
||||
scoreQuery.$gte = min;
|
||||
query.score = {$gte: min};
|
||||
}
|
||||
if (max !== '+inf') {
|
||||
scoreQuery.$lte = max;
|
||||
query.score = query.score || {};
|
||||
query.score.$lte = max;
|
||||
}
|
||||
db.collection('objects').count({_key: key, score: scoreQuery}, function(err, count) {
|
||||
|
||||
db.collection('objects').count(query, function(err, count) {
|
||||
callback(err, count ? count : 0);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -27,10 +27,7 @@ file.saveFileToLocal = function(filename, folder, tempPath, callback) {
|
||||
});
|
||||
});
|
||||
|
||||
os.on('error', function (err) {
|
||||
winston.error(err.message);
|
||||
callback(err);
|
||||
});
|
||||
os.on('error', callback);
|
||||
|
||||
is.pipe(os);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ var async = require('async'),
|
||||
posts = require('./posts'),
|
||||
privileges = require('./privileges'),
|
||||
utils = require('../public/src/utils'),
|
||||
util = require('util'),
|
||||
|
||||
uploadsController = require('./controllers/uploads');
|
||||
|
||||
@@ -1084,11 +1085,51 @@ var async = require('async'),
|
||||
case 'alpha': // intentional fall-through
|
||||
default:
|
||||
groups = groups.sort(function(a, b) {
|
||||
return a.slug > b.slug;
|
||||
return a.slug > b.slug ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
next(null, groups);
|
||||
};
|
||||
|
||||
Groups.searchMembers = function(data, callback) {
|
||||
|
||||
function findUids(query, searchBy, startsWith, callback) {
|
||||
if (!query) {
|
||||
return Groups.getMembers(data.groupName, 0, -1, callback);
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
Groups.getMembers(data.groupName, 0, -1, next);
|
||||
},
|
||||
function(members, next) {
|
||||
user.getMultipleUserFields(members, ['uid'].concat(searchBy), next);
|
||||
},
|
||||
function(users, next) {
|
||||
var uids = [];
|
||||
|
||||
for(var k=0; k<searchBy.length; ++k) {
|
||||
for(var i=0; i<users.length; ++i) {
|
||||
var field = users[i][searchBy[k]];
|
||||
if ((startsWith && field.toLowerCase().startsWith(query)) || (!startsWith && field.toLowerCase().indexOf(query) !== -1)) {
|
||||
uids.push(users[i].uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (searchBy.length > 1) {
|
||||
uids = uids.filter(function(uid, index, array) {
|
||||
return array.indexOf(uid) === index;
|
||||
});
|
||||
}
|
||||
|
||||
next(null, uids);
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
data.findUids = findUids;
|
||||
user.search(data, callback);
|
||||
};
|
||||
|
||||
}(module.exports));
|
||||
|
||||
@@ -38,6 +38,7 @@ module.exports = function(Meta) {
|
||||
'public/vendor/xregexp/unicode/unicode-base.js',
|
||||
'public/vendor/buzz/buzz.min.js',
|
||||
'public/vendor/mousetrap/mousetrap.js',
|
||||
'public/vendor/autosize.js',
|
||||
'./node_modules/templates.js/lib/templates.js',
|
||||
'public/src/utils.js',
|
||||
'public/src/app.js',
|
||||
@@ -209,17 +210,24 @@ module.exports = function(Meta) {
|
||||
|
||||
Meta.js.getFromFile = function(minify, callback) {
|
||||
var scriptPath = path.join(__dirname, '../../public/nodebb.min.js'),
|
||||
mapPath = path.join(__dirname, '../../public/nodebb.min.js.map');
|
||||
mapPath = path.join(__dirname, '../../public/nodebb.min.js.map'),
|
||||
paths = [scriptPath];
|
||||
fs.exists(scriptPath, function(exists) {
|
||||
if (exists) {
|
||||
if (nconf.get('isPrimary') === 'true') {
|
||||
winston.verbose('[meta/js] Reading client-side scripts from file');
|
||||
async.map([scriptPath, mapPath], fs.readFile, function(err, files) {
|
||||
Meta.js.cache = files[0];
|
||||
Meta.js.map = files[1];
|
||||
fs.exists(mapPath, function(exists) {
|
||||
if (exists) {
|
||||
paths.push(mapPath);
|
||||
}
|
||||
|
||||
emitter.emit('meta:js.compiled');
|
||||
callback();
|
||||
winston.verbose('[meta/js] Reading client-side scripts from file');
|
||||
async.map(paths, fs.readFile, function(err, files) {
|
||||
Meta.js.cache = files[0];
|
||||
Meta.js.map = files[1] || '';
|
||||
|
||||
emitter.emit('meta:js.compiled');
|
||||
callback();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback();
|
||||
|
||||
@@ -32,77 +32,86 @@ middleware.isAdmin = function(req, res, next) {
|
||||
};
|
||||
|
||||
middleware.buildHeader = function(req, res, next) {
|
||||
res.locals.renderAdminHeader = true;
|
||||
|
||||
async.parallel({
|
||||
config: function(next) {
|
||||
controllers.api.getConfig(req, res, next);
|
||||
},
|
||||
footer: function(next) {
|
||||
app.render('admin/footer', {}, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.locals.config = results.config;
|
||||
res.locals.adminFooter = results.footer;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
middleware.renderHeader = function(req, res, next) {
|
||||
var uid = req.user ? req.user.uid : 0;
|
||||
async.parallel([
|
||||
function(next) {
|
||||
var custom_header = {
|
||||
'plugins': [],
|
||||
'authentication': []
|
||||
};
|
||||
|
||||
user.getUserFields(uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], function(err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var custom_header = {
|
||||
'plugins': [],
|
||||
'authentication': []
|
||||
};
|
||||
|
||||
userData.uid = uid;
|
||||
userData['email:confirmed'] = parseInt(userData['email:confirmed'], 10) === 1;
|
||||
|
||||
async.parallel({
|
||||
scripts: function(next) {
|
||||
plugins.fireHook('filter:admin.scripts.get', [], function(err, scripts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
var arr = [];
|
||||
scripts.forEach(function(script) {
|
||||
arr.push({src: nconf.get('url') + script});
|
||||
});
|
||||
user.getUserFields(uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], function(err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
next(null, arr);
|
||||
});
|
||||
},
|
||||
custom_header: function(next) {
|
||||
plugins.fireHook('filter:admin.header.build', custom_header, next);
|
||||
},
|
||||
config: function(next) {
|
||||
controllers.api.getConfig(req, res, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
userData.uid = uid;
|
||||
userData['email:confirmed'] = parseInt(userData['email:confirmed'], 10) === 1;
|
||||
|
||||
async.parallel({
|
||||
scripts: function(next) {
|
||||
plugins.fireHook('filter:admin.scripts.get', [], function(err, scripts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.locals.config = results.config;
|
||||
|
||||
var data = {
|
||||
relative_path: nconf.get('relative_path'),
|
||||
configJSON: JSON.stringify(results.config),
|
||||
user: userData,
|
||||
userJSON: JSON.stringify(userData),
|
||||
plugins: results.custom_header.plugins,
|
||||
authentication: results.custom_header.authentication,
|
||||
scripts: results.scripts,
|
||||
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
|
||||
env: process.env.NODE_ENV ? true : false
|
||||
};
|
||||
|
||||
app.render('admin/header', data, function(err, template) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.locals.adminHeader = template;
|
||||
next();
|
||||
var arr = [];
|
||||
scripts.forEach(function(script) {
|
||||
arr.push({src: nconf.get('url') + script});
|
||||
});
|
||||
|
||||
next(null, arr);
|
||||
});
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
app.render('admin/footer', {}, function(err, template) {
|
||||
res.locals.adminFooter = template;
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
], next);
|
||||
},
|
||||
custom_header: function(next) {
|
||||
plugins.fireHook('filter:admin.header.build', custom_header, next);
|
||||
},
|
||||
config: function(next) {
|
||||
controllers.api.getConfig(req, res, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.locals.config = results.config;
|
||||
|
||||
var data = {
|
||||
relative_path: nconf.get('relative_path'),
|
||||
configJSON: JSON.stringify(results.config),
|
||||
user: userData,
|
||||
userJSON: JSON.stringify(userData),
|
||||
plugins: results.custom_header.plugins,
|
||||
authentication: results.custom_header.authentication,
|
||||
scripts: results.scripts,
|
||||
'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '',
|
||||
env: process.env.NODE_ENV ? true : false,
|
||||
};
|
||||
|
||||
data.template = {name: res.locals.template};
|
||||
data.template[res.locals.template] = true;
|
||||
|
||||
app.render('admin/header', data, next);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function(webserver) {
|
||||
|
||||
@@ -60,19 +60,14 @@ middleware.redirectToAccountIfLoggedIn = function(req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
res.status(302).json(nconf.get('relative_path') + '/user/' + userslug);
|
||||
} else {
|
||||
res.redirect(nconf.get('relative_path') + '/user/' + userslug);
|
||||
}
|
||||
controllers.helpers.redirect(res, '/user/' + userslug);
|
||||
});
|
||||
};
|
||||
|
||||
middleware.redirectToLoginIfGuest = function(req, res, next) {
|
||||
if (!req.user || parseInt(req.user.uid, 10) === 0) {
|
||||
req.session.returnTo = nconf.get('relative_path') + req.url.replace(/^\/api/, '');
|
||||
return res.redirect(nconf.get('relative_path') + '/login');
|
||||
return controllers.helpers.redirect(res, '/login');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -85,13 +80,7 @@ middleware.addSlug = function(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var url = nconf.get('relative_path') + name + encodeURI(slug);
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
res.status(302).json(url);
|
||||
} else {
|
||||
res.redirect(url);
|
||||
}
|
||||
controllers.helpers.redirect(res, name + encodeURI(slug));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,17 +154,9 @@ middleware.checkAccountPermissions = function(req, res, next) {
|
||||
};
|
||||
|
||||
middleware.isAdmin = function(req, res, next) {
|
||||
function render() {
|
||||
if (res.locals.isAPI) {
|
||||
return controllers.helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
middleware.buildHeader(req, res, function() {
|
||||
controllers.helpers.notAllowed(req, res);
|
||||
});
|
||||
}
|
||||
if (!req.user) {
|
||||
return render();
|
||||
req.session.returnTo = nconf.get('relative_path') + req.url.replace(/^\/api/, '');
|
||||
return controllers.helpers.redirect(res, '/login');
|
||||
}
|
||||
|
||||
user.isAdministrator((req.user && req.user.uid) ? req.user.uid : 0, function (err, isAdmin) {
|
||||
@@ -183,7 +164,13 @@ middleware.isAdmin = function(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
render();
|
||||
if (res.locals.isAPI) {
|
||||
return controllers.helpers.notAllowed(req, res);
|
||||
}
|
||||
|
||||
middleware.buildHeader(req, res, function() {
|
||||
controllers.helpers.notAllowed(req, res);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -284,7 +271,6 @@ middleware.renderHeader = function(req, res, callback) {
|
||||
href: nconf.get('relative_path') + '/favicon.ico'
|
||||
});
|
||||
|
||||
|
||||
async.parallel({
|
||||
customCSS: function(next) {
|
||||
templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1;
|
||||
@@ -339,7 +325,7 @@ middleware.renderHeader = function(req, res, callback) {
|
||||
results.user.isAdmin = results.isAdmin || false;
|
||||
results.user.uid = parseInt(results.user.uid, 10);
|
||||
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
|
||||
|
||||
|
||||
templateValues.browserTitle = results.title;
|
||||
templateValues.isAdmin = results.user.isAdmin;
|
||||
templateValues.user = results.user;
|
||||
@@ -348,6 +334,9 @@ middleware.renderHeader = function(req, res, callback) {
|
||||
templateValues.customJS = results.customJS;
|
||||
templateValues.maintenanceHeader = parseInt(meta.config.maintenanceMode, 10) === 1 && !results.isAdmin;
|
||||
|
||||
templateValues.template = {name: res.locals.template};
|
||||
templateValues.template[res.locals.template] = true;
|
||||
|
||||
app.render('header', templateValues, callback);
|
||||
});
|
||||
});
|
||||
@@ -376,8 +365,9 @@ middleware.processRender = function(req, res, next) {
|
||||
}
|
||||
|
||||
options.loggedIn = req.user ? parseInt(req.user.uid, 10) !== 0 : false;
|
||||
options.template = {};
|
||||
options.template = {name: template};
|
||||
options.template[template] = true;
|
||||
res.locals.template = template;
|
||||
|
||||
if ('function' !== typeof fn) {
|
||||
fn = defaultFn;
|
||||
@@ -402,20 +392,18 @@ middleware.processRender = function(req, res, next) {
|
||||
str = str + res.locals.adminFooter;
|
||||
}
|
||||
|
||||
if (res.locals.renderHeader) {
|
||||
middleware.renderHeader(req, res, function(err, template) {
|
||||
if (res.locals.renderHeader || res.locals.renderAdminHeader) {
|
||||
var method = res.locals.renderHeader ? middleware.renderHeader : middleware.admin.renderHeader;
|
||||
method(req, res, function(err, template) {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
str = template + str;
|
||||
var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB';
|
||||
translator.translate(str, language, function(translated) {
|
||||
fn(err, translated);
|
||||
});
|
||||
});
|
||||
} else if (res.locals.adminHeader) {
|
||||
str = res.locals.adminHeader + str;
|
||||
var language = res.locals.config ? res.locals.config.userLang || 'en_GB' : 'en_GB';
|
||||
translator.translate(str, language, function(translated) {
|
||||
fn(err, translated);
|
||||
});
|
||||
} else {
|
||||
fn(err, str);
|
||||
}
|
||||
@@ -458,7 +446,6 @@ middleware.maintenanceMode = function(req, res, next) {
|
||||
'/nodebb.min.js',
|
||||
'/vendor/fontawesome/fonts/fontawesome-webfont.woff',
|
||||
'/src/modules/[\\w]+\.js',
|
||||
'/api/get_templates_listing',
|
||||
'/api/login',
|
||||
'/api/?',
|
||||
'/language/.+'
|
||||
|
||||
@@ -207,12 +207,12 @@ var async = require('async'),
|
||||
|
||||
Notifications.pushGroup = function(notification, groupName, callback) {
|
||||
callback = callback || function() {};
|
||||
groups.get(groupName, {}, function(err, groupObj) {
|
||||
if (err || !groupObj || !Array.isArray(groupObj.members) || !groupObj.members.length) {
|
||||
groups.getMembers(groupName, 0, -1, function(err, members) {
|
||||
if (err || !Array.isArray(members) || !members.length) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
Notifications.push(notification, groupObj.members, callback);
|
||||
Notifications.push(notification, members, callback);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ var fs = require('fs'),
|
||||
|
||||
Plugins.getAll = function(callback) {
|
||||
var url = (nconf.get('registry') || 'https://packages.nodebb.org') + '/api/v1/plugins?version=' + require('../package.json').version;
|
||||
|
||||
|
||||
require('request')(url, function(err, res, body) {
|
||||
var plugins = [];
|
||||
|
||||
@@ -262,9 +262,9 @@ var fs = require('fs'),
|
||||
|
||||
function(dirs, next) {
|
||||
dirs = dirs.filter(function(dir){
|
||||
return dir.startsWith('nodebb-plugin-') ||
|
||||
dir.startsWith('nodebb-widget-') ||
|
||||
dir.startsWith('nodebb-rewards-') ||
|
||||
return dir.startsWith('nodebb-plugin-') ||
|
||||
dir.startsWith('nodebb-widget-') ||
|
||||
dir.startsWith('nodebb-rewards-') ||
|
||||
dir.startsWith('nodebb-theme-');
|
||||
}).map(function(dir){
|
||||
return path.join(npmPluginPath, dir);
|
||||
@@ -272,11 +272,7 @@ var fs = require('fs'),
|
||||
|
||||
async.filter(dirs, function(dir, callback){
|
||||
fs.stat(dir, function(err, stats){
|
||||
if (err) {
|
||||
return callback(false);
|
||||
}
|
||||
|
||||
callback(stats.isDirectory());
|
||||
callback(!err && stats.isDirectory());
|
||||
});
|
||||
}, function(plugins){
|
||||
next(null, plugins);
|
||||
@@ -287,25 +283,11 @@ var fs = require('fs'),
|
||||
var plugins = [];
|
||||
|
||||
async.each(files, function(file, next) {
|
||||
var configPath;
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
Plugins.loadPluginInfo(file, next);
|
||||
},
|
||||
function(pluginData, next) {
|
||||
var packageName = path.basename(file);
|
||||
|
||||
if (!pluginData) {
|
||||
winston.warn("Plugin `" + packageName + "` is corrupted or invalid. Please check either package.json or plugin.json for errors.");
|
||||
return next(null, {
|
||||
id: packageName,
|
||||
installed: true,
|
||||
error: true,
|
||||
active: null
|
||||
});
|
||||
}
|
||||
|
||||
Plugins.isActive(pluginData.name, function(err, active) {
|
||||
if (err) {
|
||||
return next(new Error('no-active-state'));
|
||||
@@ -319,12 +301,12 @@ var fs = require('fs'),
|
||||
next(null, pluginData);
|
||||
});
|
||||
}
|
||||
], function(err, config) {
|
||||
], function(err, pluginData) {
|
||||
if (err) {
|
||||
return next(); // Silently fail
|
||||
}
|
||||
|
||||
plugins.push(config);
|
||||
plugins.push(pluginData);
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
|
||||
@@ -113,7 +113,16 @@ module.exports = function(Plugins) {
|
||||
require('npm').load({}, next);
|
||||
},
|
||||
function(res, next) {
|
||||
require('npm').commands.install([id + '@' + (version || 'latest')], next);
|
||||
require('npm').commands.install([id + '@' + (version || 'latest')], function(err, a, b) {
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
function(next) {
|
||||
Plugins.isActive(id, next);
|
||||
},
|
||||
function(isActive, next) {
|
||||
meta.reloadRequired = isActive;
|
||||
next(null, isActive);
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
@@ -14,23 +14,13 @@ module.exports = function(Plugins) {
|
||||
Plugins.loadPlugin = function(pluginPath, callback) {
|
||||
Plugins.loadPluginInfo(pluginPath, function(err, pluginData) {
|
||||
if (err) {
|
||||
if (err.message === '[[error:parse-error]]') {
|
||||
return callback();
|
||||
}
|
||||
return callback(pluginPath.match('nodebb-theme') ? null : err);
|
||||
}
|
||||
|
||||
var staticDir;
|
||||
if (!pluginData) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (pluginData.compatibility && semver.validRange(pluginData.compatibility)) {
|
||||
if (!semver.gtr(pkg.version, pluginData.compatibility)) {
|
||||
// NodeBB may not be new enough to run this plugin
|
||||
process.stdout.write('\n');
|
||||
winston.warn('[plugins/' + pluginData.id + '] This plugin may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing.');
|
||||
winston.warn('[plugins/' + pluginData.id + '] In the event of an unresponsive NodeBB caused by this plugin, run ./nodebb reset plugin="' + pluginData.id + '".');
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
}
|
||||
versionWarning(pluginData);
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
@@ -63,6 +53,23 @@ module.exports = function(Plugins) {
|
||||
});
|
||||
};
|
||||
|
||||
function versionWarning(pluginData) {
|
||||
function display() {
|
||||
process.stdout.write('\n');
|
||||
winston.warn('[plugins/' + pluginData.id + '] This plugin may not be compatible with your version of NodeBB. This may cause unintended behaviour or crashing.');
|
||||
winston.warn('[plugins/' + pluginData.id + '] In the event of an unresponsive NodeBB caused by this plugin, run ./nodebb reset plugin="' + pluginData.id + '".');
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
|
||||
if (pluginData.nbbpm && pluginData.nbbpm.compatibility && semver.validRange(pluginData.nbbpm.compatibility)) {
|
||||
if (!semver.gtr(pkg.version, pluginData.nbbpm.compatibility)) {
|
||||
display();
|
||||
}
|
||||
} else {
|
||||
display();
|
||||
}
|
||||
}
|
||||
|
||||
function registerHooks(pluginData, pluginPath, callback) {
|
||||
function libraryNotFound() {
|
||||
winston.warn('[plugins.reload] Library not found for plugin: ' + pluginData.id);
|
||||
@@ -220,9 +227,9 @@ module.exports = function(Plugins) {
|
||||
var pluginDir = pluginPath.split(path.sep);
|
||||
pluginDir = pluginDir[pluginDir.length -1];
|
||||
|
||||
winston.error('[plugins/' + pluginDir + '] Error in plugin.json/package.json! ' + err.message);
|
||||
winston.error('[plugins/' + pluginDir + '] Error in plugin.json or package.json! ' + err.message);
|
||||
|
||||
callback();
|
||||
callback(new Error('[[error:parse-error]]'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,7 +14,14 @@ var winston = require('winston'),
|
||||
utils = require('../public/src/utils'),
|
||||
plugins = require('./plugins'),
|
||||
events = require('./events'),
|
||||
meta = require('./meta');
|
||||
meta = require('./meta'),
|
||||
LRU = require('lru-cache');
|
||||
|
||||
var cache = LRU({
|
||||
max: 1048576,
|
||||
length: function (n) { return n.length },
|
||||
maxAge: 1000 * 60 * 60
|
||||
});
|
||||
|
||||
(function(PostTools) {
|
||||
|
||||
@@ -100,6 +107,7 @@ var winston = require('winston'),
|
||||
});
|
||||
},
|
||||
postData: function(next) {
|
||||
cache.del(postData.pid);
|
||||
PostTools.parsePost(postData, data.uid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
@@ -148,6 +156,7 @@ var winston = require('winston'),
|
||||
}
|
||||
|
||||
if (isDelete) {
|
||||
cache.del(postData.pid);
|
||||
posts.delete(pid, callback);
|
||||
} else {
|
||||
posts.restore(pid, function(err, postData) {
|
||||
@@ -165,7 +174,7 @@ var winston = require('winston'),
|
||||
if (err || !canEdit) {
|
||||
return callback(err || new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
cache.del(pid);
|
||||
posts.purge(pid, callback);
|
||||
});
|
||||
};
|
||||
@@ -173,8 +182,18 @@ var winston = require('winston'),
|
||||
PostTools.parsePost = function(postData, uid, callback) {
|
||||
postData.content = postData.content || '';
|
||||
|
||||
var cachedContent = cache.get(postData.pid);
|
||||
if (cachedContent) {
|
||||
postData.content = cachedContent;
|
||||
return callback(null, postData);
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:parse.post', {postData: postData, uid: uid}, function(err, data) {
|
||||
callback(err, data ? data.postData : null);
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
cache.set(data.postData.pid, data.postData.content);
|
||||
callback(null, data.postData);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -184,4 +203,8 @@ var winston = require('winston'),
|
||||
plugins.fireHook('filter:parse.signature', {userData: userData, uid: uid}, callback);
|
||||
};
|
||||
|
||||
PostTools.resetCache = function() {
|
||||
cache.reset();
|
||||
};
|
||||
|
||||
}(exports));
|
||||
|
||||
26
src/posts.js
26
src/posts.js
@@ -16,6 +16,7 @@ var async = require('async'),
|
||||
require('./posts/create')(Posts);
|
||||
require('./posts/delete')(Posts);
|
||||
require('./posts/user')(Posts);
|
||||
require('./posts/topics')(Posts);
|
||||
require('./posts/category')(Posts);
|
||||
require('./posts/summary')(Posts);
|
||||
require('./posts/recent')(Posts);
|
||||
@@ -25,20 +26,6 @@ var async = require('async'),
|
||||
db.isSortedSetMember('posts:pid', pid, callback);
|
||||
};
|
||||
|
||||
Posts.getPostsByTid = function(tid, set, start, end, uid, reverse, callback) {
|
||||
Posts.getPidsFromSet(set, start, end, reverse, function(err, pids) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!Array.isArray(pids) || !pids.length) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
Posts.getPostsByPids(pids, uid, callback);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.getPidsFromSet = function(set, start, end, reverse, callback) {
|
||||
if (isNaN(start) || isNaN(end)) {
|
||||
return callback(null, []);
|
||||
@@ -243,17 +230,6 @@ var async = require('async'),
|
||||
});
|
||||
};
|
||||
|
||||
Posts.isMain = function(pid, callback) {
|
||||
Posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
topics.getTopicField(tid, 'mainPid', function(err, mainPid) {
|
||||
callback(err, parseInt(pid, 10) === parseInt(mainPid, 10));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Posts.updatePostVoteCount = function(pid, voteCount, callback) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
|
||||
44
src/posts/topics.js
Normal file
44
src/posts/topics.js
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
topics = require('../topics');
|
||||
|
||||
module.exports = function(Posts) {
|
||||
|
||||
Posts.getPostsByTid = function(tid, set, start, end, uid, reverse, callback) {
|
||||
Posts.getPidsFromSet(set, start, end, reverse, function(err, pids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!Array.isArray(pids) || !pids.length) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
Posts.getPostsByPids(pids, uid, callback);
|
||||
});
|
||||
};
|
||||
|
||||
Posts.isMain = function(pid, callback) {
|
||||
Posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
topics.getTopicField(tid, 'mainPid', function(err, mainPid) {
|
||||
callback(err, parseInt(pid, 10) === parseInt(mainPid, 10));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Posts.getTopicFields = function(pid, fields, callback) {
|
||||
Posts.getPostField(pid, 'tid', function(err, tid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
topics.getTopicFields(tid, fields, callback);
|
||||
});
|
||||
};
|
||||
|
||||
};
|
||||
@@ -33,10 +33,18 @@ module.exports = function(Posts) {
|
||||
|
||||
var userData = results.userData;
|
||||
userData.forEach(function(userData, i) {
|
||||
userData.groups = results.groups[i];
|
||||
|
||||
userData.groups = [];
|
||||
|
||||
results.groups[i].forEach(function(group, index) {
|
||||
group.selected = group.name === results.userSettings[i].groupTitle;
|
||||
userData.groups[index] = {
|
||||
name: group.name,
|
||||
slug: group.slug,
|
||||
labelColor: group.labelColor,
|
||||
icon: group.icon,
|
||||
userTitle: group.userTitle,
|
||||
userTitleEnabled: group.userTitleEnabled,
|
||||
selected: group.name === results.userSettings[i].groupTitle
|
||||
};
|
||||
});
|
||||
userData.status = user.getStatus(userData.status, results.online[i]);
|
||||
});
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
var express = require('express');
|
||||
|
||||
|
||||
function apiRoutes(app, middleware, controllers) {
|
||||
// todo, needs to be in api namespace
|
||||
app.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
|
||||
function apiRoutes(router, middleware, controllers) {
|
||||
router.get('/users/csv', middleware.authenticate, controllers.admin.users.getCSV);
|
||||
|
||||
var multipart = require('connect-multiparty');
|
||||
var multipartMiddleware = multipart();
|
||||
|
||||
var middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.authenticate];
|
||||
|
||||
app.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
|
||||
app.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
|
||||
app.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
|
||||
app.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
|
||||
router.post('/category/uploadpicture', middlewares, controllers.admin.uploads.uploadCategoryPicture);
|
||||
router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
|
||||
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
|
||||
router.post('/uploadgravatardefault', middlewares, controllers.admin.uploads.uploadGravatarDefault);
|
||||
}
|
||||
|
||||
function adminRouter(middleware, controllers) {
|
||||
@@ -25,8 +24,6 @@ function adminRouter(middleware, controllers) {
|
||||
|
||||
addRoutes(router, middleware, controllers);
|
||||
|
||||
apiRoutes(router, middleware, controllers);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -35,6 +32,8 @@ function apiRouter(middleware, controllers) {
|
||||
|
||||
addRoutes(router, middleware, controllers);
|
||||
|
||||
apiRoutes(router, middleware, controllers);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ var express = require('express'),
|
||||
|
||||
posts = require('../posts'),
|
||||
categories = require('../categories'),
|
||||
uploadsController = require('../controllers/uploads'),
|
||||
templatesController = require('../controllers/templates');
|
||||
uploadsController = require('../controllers/uploads');
|
||||
|
||||
module.exports = function(app, middleware, controllers) {
|
||||
|
||||
@@ -17,7 +16,6 @@ module.exports = function(app, middleware, controllers) {
|
||||
|
||||
router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.accounts.getUserByUID);
|
||||
router.get('/post/:pid', controllers.posts.getPost);
|
||||
router.get('/get_templates_listing', templatesController.getTemplatesListing);
|
||||
router.get('/categories/:cid/moderators', getModerators);
|
||||
router.get('/recent/posts/:term?', getRecentPosts);
|
||||
|
||||
|
||||
@@ -135,14 +135,17 @@ function generateForCategory(req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var feed = generateTopicsFeed({
|
||||
generateTopicsFeed({
|
||||
title: categoryData.name,
|
||||
description: categoryData.description,
|
||||
feed_url: '/category/' + cid + '.rss',
|
||||
site_url: '/category/' + categoryData.cid,
|
||||
}, categoryData.topics);
|
||||
|
||||
sendFeed(feed, res);
|
||||
}, categoryData.topics, function(err, feed) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
sendFeed(feed, res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,12 +159,32 @@ function generateForRecent(req, res, next) {
|
||||
}
|
||||
|
||||
function generateForPopular(req, res, next) {
|
||||
generateForTopics({
|
||||
title: 'Popular Topics',
|
||||
description: 'A list of topics that are sorted by post count',
|
||||
feed_url: '/popular.rss',
|
||||
site_url: '/popular'
|
||||
}, 'topics:posts', req, res, next);
|
||||
var uid = req.user ? req.user.uid : 0;
|
||||
var terms = {
|
||||
daily: 'day',
|
||||
weekly: 'week',
|
||||
monthly: 'month',
|
||||
alltime: 'alltime'
|
||||
};
|
||||
var term = terms[req.params.term] || 'day';
|
||||
|
||||
topics.getPopular(term, uid, 19, function(err, topics) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
generateTopicsFeed({
|
||||
title: 'Popular Topics',
|
||||
description: 'A list of topics that are sorted by post count',
|
||||
feed_url: '/popular/' + (req.params.term || 'daily') + '.rss',
|
||||
site_url: '/popular/' + (req.params.term || 'daily')
|
||||
}, topics, function(err, feed) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
sendFeed(feed, res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function disabledRSS(req, res, next) {
|
||||
@@ -178,35 +201,58 @@ function generateForTopics(options, set, req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var feed = generateTopicsFeed(options, data.topics);
|
||||
|
||||
sendFeed(feed, res);
|
||||
|
||||
generateTopicsFeed(options, data.topics, function(err, feed) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
sendFeed(feed, res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function generateTopicsFeed(feedOptions, topics) {
|
||||
|
||||
feedOptions.ttl = 60;
|
||||
feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url;
|
||||
feedOptions.site_url = nconf.get('url') + feedOptions.site_url;
|
||||
|
||||
var feed = new rss(feedOptions);
|
||||
|
||||
if (topics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(topics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
topics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
function generateTopicsFeed(feedOptions, feedTopics, callback) {
|
||||
var tids = feedTopics.map(function(topic) {
|
||||
return topic ? topic.tid : null;
|
||||
});
|
||||
|
||||
topics.getMainPids(tids, function(err, pids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
posts.getPostsFields(pids, ['content'], function(err, posts) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return feed;
|
||||
feedTopics.forEach(function(topic, index) {
|
||||
if (topic && posts[index]) {
|
||||
topic.mainPost = posts[index].content;
|
||||
}
|
||||
});
|
||||
|
||||
feedOptions.ttl = 60;
|
||||
feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url;
|
||||
feedOptions.site_url = nconf.get('url') + feedOptions.site_url;
|
||||
|
||||
var feed = new rss(feedOptions);
|
||||
|
||||
if (feedTopics.length > 0) {
|
||||
feed.pubDate = new Date(parseInt(feedTopics[0].lastposttime, 10)).toUTCString();
|
||||
}
|
||||
|
||||
feedTopics.forEach(function(topicData) {
|
||||
feed.item({
|
||||
title: topicData.title,
|
||||
description: topicData.mainPost,
|
||||
url: nconf.get('url') + '/topic/' + topicData.slug,
|
||||
author: topicData.username,
|
||||
date: new Date(parseInt(topicData.lastposttime, 10)).toUTCString()
|
||||
});
|
||||
});
|
||||
callback(null, feed);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function generateForRecentPosts(req, res, next) {
|
||||
@@ -291,6 +337,7 @@ module.exports = function(app, middleware, controllers){
|
||||
app.get('/category/:category_id.rss', hasCategoryPrivileges, disabledRSS, generateForCategory);
|
||||
app.get('/recent.rss', disabledRSS, generateForRecent);
|
||||
app.get('/popular.rss', disabledRSS, generateForPopular);
|
||||
app.get('/popular/:term.rss', disabledRSS, generateForPopular);
|
||||
app.get('/recentposts.rss', disabledRSS, generateForRecentPosts);
|
||||
app.get('/category/:category_id/recentposts.rss', hasCategoryPrivileges, disabledRSS, generateForCategoryRecentPosts);
|
||||
app.get('/user/:userslug/topics.rss', disabledRSS, generateForUserTopics);
|
||||
|
||||
@@ -173,30 +173,38 @@ module.exports = function(app, middleware) {
|
||||
|
||||
function handle404(app, middleware) {
|
||||
app.use(function(req, res, next) {
|
||||
var relativePath = nconf.get('relative_path');
|
||||
var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'),
|
||||
isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
|
||||
if (!plugins.hasListeners('action:meta.override404')) {
|
||||
var relativePath = nconf.get('relative_path');
|
||||
var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'),
|
||||
isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js');
|
||||
|
||||
if (isClientScript.test(req.url)) {
|
||||
res.type('text/javascript').status(200).send('');
|
||||
} else if (isLanguage.test(req.url)) {
|
||||
res.status(200).json({});
|
||||
} else if (req.accepts('html')) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.warn('Route requested but not found: ' + req.url);
|
||||
if (isClientScript.test(req.url)) {
|
||||
res.type('text/javascript').status(200).send('');
|
||||
} else if (isLanguage.test(req.url)) {
|
||||
res.status(200).json({});
|
||||
} else if (req.accepts('html')) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
winston.warn('Route requested but not found: ' + req.url);
|
||||
}
|
||||
|
||||
res.status(404);
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
return res.json({path: req.path, error: 'not-found'});
|
||||
}
|
||||
|
||||
middleware.buildHeader(req, res, function() {
|
||||
res.render('404', {path: req.path});
|
||||
});
|
||||
} else {
|
||||
res.status(404).type('txt').send('Not found');
|
||||
}
|
||||
|
||||
res.status(404);
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
return res.json({path: req.path, error: 'not-found'});
|
||||
}
|
||||
|
||||
middleware.buildHeader(req, res, function() {
|
||||
res.render('404', {path: req.path});
|
||||
});
|
||||
} else {
|
||||
res.status(404).type('txt').send('Not found');
|
||||
plugins.fireHook('action:meta.override404', {
|
||||
req: req,
|
||||
res: res,
|
||||
error: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -210,7 +218,7 @@ function handleErrors(app, middleware) {
|
||||
}
|
||||
|
||||
if (parseInt(err.status, 10) === 302 && err.path) {
|
||||
return res.locals.isAPI ? res.status(302).json(err) : res.redirect(err.path);
|
||||
return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path);
|
||||
}
|
||||
|
||||
res.status(err.status || 500);
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
var path = require('path'),
|
||||
nconf = require('nconf'),
|
||||
|
||||
meta = require('../meta'),
|
||||
db = require('../database'),
|
||||
plugins = require('../plugins'),
|
||||
var meta = require('../meta'),
|
||||
middleware = require('../middleware');
|
||||
|
||||
|
||||
function sendMinifiedJS(req, res, next) {
|
||||
return res.type('text/javascript').send(meta.js.cache);
|
||||
res.type('text/javascript').send(meta.js.cache);
|
||||
}
|
||||
|
||||
function sendStylesheet(req, res, next) {
|
||||
|
||||
@@ -104,10 +104,12 @@ SocketAdmin.themes.updateBranding = function(socket, data, callback) {
|
||||
};
|
||||
|
||||
SocketAdmin.plugins.toggleActive = function(socket, plugin_id, callback) {
|
||||
require('../postTools').resetCache();
|
||||
plugins.toggleActive(plugin_id, callback);
|
||||
};
|
||||
|
||||
SocketAdmin.plugins.toggleInstall = function(socket, data, callback) {
|
||||
require('../postTools').resetCache();
|
||||
plugins.toggleInstall(data.id, data.version, callback);
|
||||
};
|
||||
|
||||
@@ -121,7 +123,7 @@ SocketAdmin.plugins.orderActivePlugins = function(socket, data, callback) {
|
||||
db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
}, callback);
|
||||
};
|
||||
|
||||
@@ -339,12 +341,12 @@ SocketAdmin.getMoreFlags = function(socket, data, callback) {
|
||||
posts.getUserFlags(byUsername, sortBy, socket.uid, start, end, function(err, posts) {
|
||||
callback(err, {posts: posts, next: end + 1});
|
||||
});
|
||||
} else {
|
||||
} else {
|
||||
var set = sortBy === 'count' ? 'posts:flags:count' : 'posts:flagged';
|
||||
posts.getFlags(set, socket.uid, start, end, function(err, posts) {
|
||||
callback(err, {posts: posts, next: end + 1});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SocketAdmin.takeHeapSnapshot = function(socket, data, callback) {
|
||||
|
||||
@@ -68,7 +68,7 @@ Categories.getPrivilegeSettings = function(socket, cid, callback) {
|
||||
async.reduce(privileges, [], function(members, privilege, next) {
|
||||
groups.get('cid:' + cid + ':privileges:' + privilege, { expand: true }, function(err, groupObj) {
|
||||
if (err || !groupObj) {
|
||||
return next(err, members);
|
||||
return next(null, members);
|
||||
}
|
||||
|
||||
members = members.concat(groupObj.members);
|
||||
|
||||
@@ -184,6 +184,15 @@ SocketGroups.search = function(socket, data, callback) {
|
||||
groups.search(data.query || '', data.options || {}, callback);
|
||||
};
|
||||
|
||||
SocketGroups.searchMembers = function(socket, data, callback) {
|
||||
if (!data) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
data.uid = socket.uid;
|
||||
groups.searchMembers(data, callback);
|
||||
};
|
||||
|
||||
SocketGroups.kick = function(socket, data, callback) {
|
||||
if (!data) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
|
||||
@@ -70,7 +70,7 @@ function onConnect(socket) {
|
||||
if (err || !userData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
socket.emit('event:connect');
|
||||
if (userData.status !== 'offline') {
|
||||
socket.broadcast.emit('event:user_status_change', {uid: socket.uid, status: userData.status || 'online'});
|
||||
@@ -163,34 +163,31 @@ function requireModules() {
|
||||
});
|
||||
}
|
||||
|
||||
function authorize(socket, next) {
|
||||
var handshake = socket.request,
|
||||
sessionID;
|
||||
function authorize(socket, callback) {
|
||||
var handshake = socket.request;
|
||||
|
||||
if (!handshake) {
|
||||
return next(new Error('[[error:not-authorized]]'));
|
||||
return callback(new Error('[[error:not-authorized]]'));
|
||||
}
|
||||
|
||||
cookieParser(handshake, {}, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
cookieParser(handshake, {}, next);
|
||||
},
|
||||
function(next) {
|
||||
db.sessionStore.get(handshake.signedCookies['express.sid'], function(err, sessionData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (sessionData && sessionData.passport && sessionData.passport.user) {
|
||||
socket.uid = parseInt(sessionData.passport.user, 10);
|
||||
} else {
|
||||
socket.uid = 0;
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
var sessionID = handshake.signedCookies['express.sid'];
|
||||
|
||||
db.sessionStore.get(sessionID, function(err, sessionData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (sessionData && sessionData.passport && sessionData.passport.user) {
|
||||
socket.uid = parseInt(sessionData.passport.user, 10);
|
||||
} else {
|
||||
socket.uid = 0;
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
], callback);
|
||||
}
|
||||
|
||||
function addRedisAdapter(io) {
|
||||
@@ -201,7 +198,7 @@ function addRedisAdapter(io) {
|
||||
var sub = redis.connect({return_buffers: true});
|
||||
|
||||
io.adapter(redisAdapter({pubClient: pub, subClient: sub}));
|
||||
} else {
|
||||
} else if (nconf.get('isCluster') === 'true') {
|
||||
winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ SocketModules.composer.stopNotifyTyping = function(socket, data) {
|
||||
SocketModules.composer.getFormattingOptions = function(socket, data, callback) {
|
||||
plugins.fireHook('filter:composer.formatting', {
|
||||
options: [
|
||||
// { className: 'fa fa-bold' } Just an example of what needs to be set via plugins
|
||||
{ name: 'tags', className: 'fa fa-tags', mobile: true }
|
||||
]
|
||||
}, function(err, payload) {
|
||||
callback(err, payload.options);
|
||||
|
||||
@@ -16,6 +16,7 @@ var async = require('async'),
|
||||
groups = require('../groups'),
|
||||
user = require('../user'),
|
||||
websockets = require('./index'),
|
||||
socketTopics = require('./topics'),
|
||||
events = require('../events'),
|
||||
utils = require('../../public/src/utils'),
|
||||
|
||||
@@ -345,27 +346,64 @@ function deleteOrRestore(command, socket, data, callback) {
|
||||
}
|
||||
|
||||
SocketPosts.purge = function(socket, data, callback) {
|
||||
if(!data || !parseInt(data.pid, 10)) {
|
||||
function purgePost() {
|
||||
postTools.purge(socket.uid, data.pid, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
websockets.in('topic_' + data.tid).emit('event:post_purged', data.pid);
|
||||
|
||||
events.log({
|
||||
type: 'post-purge',
|
||||
uid: socket.uid,
|
||||
pid: data.pid,
|
||||
ip: socket.ip
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
if (!data || !parseInt(data.pid, 10)) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
postTools.purge(socket.uid, data.pid, function(err) {
|
||||
if(err) {
|
||||
|
||||
isMainAndLastPost(data.pid, function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
websockets.in('topic_' + data.tid).emit('event:post_purged', data.pid);
|
||||
if (!results.isMain) {
|
||||
return purgePost();
|
||||
}
|
||||
|
||||
events.log({
|
||||
type: 'post-purge',
|
||||
uid: socket.uid,
|
||||
pid: data.pid,
|
||||
ip: socket.ip
|
||||
if (!results.isLast) {
|
||||
return callback(new Error('[[error:cant-purge-main-post]]'));
|
||||
}
|
||||
|
||||
posts.getTopicFields(data.pid, ['tid', 'cid'], function(err, topic) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
socketTopics.doTopicAction('delete', 'event:topic_deleted', socket, {tids: [topic.tid], cid: topic.cid}, callback);
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
function isMainAndLastPost(pid, callback) {
|
||||
async.parallel({
|
||||
isMain: function(next) {
|
||||
posts.isMain(pid, next);
|
||||
},
|
||||
isLast: function(next) {
|
||||
posts.getTopicFields(pid, ['postcount'], function(err, topic) {
|
||||
next(err, topic ? parseInt(topic.postcount, 10) === 1 : false);
|
||||
});
|
||||
}
|
||||
}, callback);
|
||||
}
|
||||
|
||||
SocketPosts.getPrivileges = function(socket, pids, callback) {
|
||||
privileges.posts.get(pids, socket.uid, function(err, privileges) {
|
||||
if (err) {
|
||||
|
||||
@@ -205,34 +205,34 @@ SocketTopics.markAsUnreadForAll = function(socket, tids, callback) {
|
||||
};
|
||||
|
||||
SocketTopics.delete = function(socket, data, callback) {
|
||||
doTopicAction('delete', 'event:topic_deleted', socket, data, callback);
|
||||
SocketTopics.doTopicAction('delete', 'event:topic_deleted', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketTopics.restore = function(socket, data, callback) {
|
||||
doTopicAction('restore', 'event:topic_restored', socket, data, callback);
|
||||
SocketTopics.doTopicAction('restore', 'event:topic_restored', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketTopics.purge = function(socket, data, callback) {
|
||||
doTopicAction('purge', 'event:topic_purged', socket, data, callback);
|
||||
SocketTopics.doTopicAction('purge', 'event:topic_purged', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketTopics.lock = function(socket, data, callback) {
|
||||
doTopicAction('lock', 'event:topic_locked', socket, data, callback);
|
||||
SocketTopics.doTopicAction('lock', 'event:topic_locked', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketTopics.unlock = function(socket, data, callback) {
|
||||
doTopicAction('unlock', 'event:topic_unlocked', socket, data, callback);
|
||||
SocketTopics.doTopicAction('unlock', 'event:topic_unlocked', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketTopics.pin = function(socket, data, callback) {
|
||||
doTopicAction('pin', 'event:topic_pinned', socket, data, callback);
|
||||
SocketTopics.doTopicAction('pin', 'event:topic_pinned', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketTopics.unpin = function(socket, data, callback) {
|
||||
doTopicAction('unpin', 'event:topic_unpinned', socket, data, callback);
|
||||
SocketTopics.doTopicAction('unpin', 'event:topic_unpinned', socket, data, callback);
|
||||
};
|
||||
|
||||
function doTopicAction(action, event, socket, data, callback) {
|
||||
SocketTopics.doTopicAction = function(action, event, socket, data, callback) {
|
||||
if (!socket.uid) {
|
||||
return;
|
||||
}
|
||||
@@ -274,7 +274,7 @@ function doTopicAction(action, event, socket, data, callback) {
|
||||
});
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
};
|
||||
|
||||
function emitToTopicAndCategory(event, data) {
|
||||
websockets.in('topic_' + data.tid).emit(event, data);
|
||||
|
||||
@@ -216,12 +216,7 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
mainPost: function(next) {
|
||||
getMainPosts([topicData.mainPid], uid, next);
|
||||
},
|
||||
posts: function(next) {
|
||||
Topics.getTopicPosts(tid, set, start, end, uid, reverse, next);
|
||||
},
|
||||
posts: async.apply(getMainPostAndReplies, topicData, set, uid, start, end, reverse),
|
||||
category: async.apply(Topics.getCategoryData, tid),
|
||||
threadTools: async.apply(plugins.fireHook, 'filter:topic.thread_tools', {topic: topicData, uid: uid, tools: []}),
|
||||
tags: async.apply(Topics.getTopicTagsObjects, tid),
|
||||
@@ -231,7 +226,7 @@ var async = require('async'),
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
topicData.posts = Array.isArray(results.mainPost) && results.mainPost.length ? [results.mainPost[0]].concat(results.posts) : results.posts;
|
||||
topicData.posts = results.posts;
|
||||
topicData.category = results.category;
|
||||
topicData.thread_tools = results.threadTools.tools;
|
||||
topicData.tags = results.tags;
|
||||
@@ -249,13 +244,49 @@ var async = require('async'),
|
||||
});
|
||||
};
|
||||
|
||||
function getMainPostAndReplies(topic, set, uid, start, end, reverse, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
posts.getPidsFromSet(set, start, end, reverse, next);
|
||||
},
|
||||
function(pids, next) {
|
||||
if ((!Array.isArray(pids) || !pids.length) && !topic.mainPid) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
if (topic.mainPid) {
|
||||
pids.unshift(topic.mainPid);
|
||||
}
|
||||
posts.getPostsByPids(pids, uid, next);
|
||||
},
|
||||
function(posts, next) {
|
||||
if (!posts.length) {
|
||||
return next(null, []);
|
||||
}
|
||||
|
||||
if (topic.mainPid) {
|
||||
posts[0].index = 0;
|
||||
}
|
||||
|
||||
var indices = Topics.calculatePostIndices(start, end, topic.postcount, reverse);
|
||||
for (var i=1; i<posts.length; ++i) {
|
||||
if (posts[i]) {
|
||||
posts[i].index = indices[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
Topics.addPostData(posts, uid, callback);
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
Topics.getMainPost = function(tid, uid, callback) {
|
||||
Topics.getMainPosts([tid], uid, function(err, mainPosts) {
|
||||
callback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null);
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getMainPosts = function(tids, uid, callback) {
|
||||
Topics.getMainPids = function(tids, callback) {
|
||||
Topics.getTopicsFields(tids, ['mainPid'], function(err, topicData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
@@ -264,7 +295,15 @@ var async = require('async'),
|
||||
var mainPids = topicData.map(function(topic) {
|
||||
return topic ? topic.mainPid : null;
|
||||
});
|
||||
callback(null, mainPids);
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getMainPosts = function(tids, uid, callback) {
|
||||
Topics.getMainPids(tids, function(err, mainPids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
getMainPosts(mainPids, uid, callback);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -166,6 +166,7 @@ module.exports = function(Topics) {
|
||||
|
||||
data.topicData = data.topicData[0];
|
||||
data.topicData.unreplied = 1;
|
||||
data.topicData.mainPost = data.postData;
|
||||
|
||||
plugins.fireHook('action:topic.post', data.topicData);
|
||||
|
||||
@@ -280,6 +281,10 @@ module.exports = function(Topics) {
|
||||
Topics.notifyFollowers(postData, uid);
|
||||
}
|
||||
|
||||
if (postData.index > 0) {
|
||||
plugins.fireHook('action:topic.reply', postData);
|
||||
}
|
||||
|
||||
postData.topic.title = validator.escape(postData.topic.title);
|
||||
next(null, postData);
|
||||
}
|
||||
|
||||
@@ -15,53 +15,70 @@ module.exports = function(User) {
|
||||
var startsWith = data.hasOwnProperty('startsWith') ? data.startsWith : true;
|
||||
var page = data.page || 1;
|
||||
var uid = data.uid || 0;
|
||||
var paginate = data.hasOwnProperty('paginate') ? data.paginate : true;
|
||||
|
||||
if (searchBy.indexOf('ip') !== -1) {
|
||||
return searchByIP(query, uid, callback);
|
||||
}
|
||||
|
||||
var startTime = process.hrtime();
|
||||
var keys = searchBy.map(function(searchBy) {
|
||||
return searchBy + ':uid';
|
||||
});
|
||||
|
||||
var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20;
|
||||
var start = Math.max(0, page - 1) * resultsPerPage;
|
||||
var end = start + resultsPerPage;
|
||||
var pageCount = 1;
|
||||
var matchCount = 0;
|
||||
var filterBy = Array.isArray(data.filterBy) ? data.filterBy : [];
|
||||
|
||||
var searchResult = {};
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
findUids(query, keys, startsWith, next);
|
||||
if (data.findUids) {
|
||||
data.findUids(query, searchBy, startsWith, next);
|
||||
} else {
|
||||
findUids(query, searchBy, startsWith, next);
|
||||
}
|
||||
},
|
||||
function(uids, next) {
|
||||
var filterBy = Array.isArray(data.filterBy) ? data.filterBy : [];
|
||||
filterAndSortUids(uids, filterBy, data.sortBy, next);
|
||||
},
|
||||
function(uids, next) {
|
||||
matchCount = uids.length;
|
||||
uids = uids.slice(start, end);
|
||||
searchResult.matchCount = uids.length;
|
||||
|
||||
if (paginate) {
|
||||
var pagination = user.paginate(page, uids);
|
||||
uids = pagination.data;
|
||||
searchResult.pagination = pagination.pagination;
|
||||
}
|
||||
|
||||
User.getUsers(uids, uid, next);
|
||||
},
|
||||
function(userData, next) {
|
||||
var data = {
|
||||
timing: (process.elapsedTimeSince(startTime) / 1000).toFixed(2),
|
||||
users: userData,
|
||||
matchCount: matchCount
|
||||
};
|
||||
searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2);
|
||||
searchResult.users = userData;
|
||||
|
||||
var currentPage = Math.max(1, Math.ceil((start + 1) / resultsPerPage));
|
||||
pageCount = Math.ceil(matchCount / resultsPerPage);
|
||||
data.pagination = pagination.create(currentPage, pageCount);
|
||||
|
||||
next(null, data);
|
||||
next(null, searchResult);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
function findUids(query, keys, startsWith, callback) {
|
||||
User.paginate = function(page, data) {
|
||||
var resultsPerPage = parseInt(meta.config.userSearchResultsPerPage, 10) || 20;
|
||||
var start = Math.max(0, page - 1) * resultsPerPage;
|
||||
var end = start + resultsPerPage;
|
||||
|
||||
var pageCount = Math.ceil(data.length / resultsPerPage);
|
||||
var currentPage = Math.max(1, Math.ceil((start + 1) / resultsPerPage));
|
||||
|
||||
return {
|
||||
pagination: pagination.create(currentPage, pageCount),
|
||||
data: data.slice(start, end)
|
||||
};
|
||||
};
|
||||
|
||||
function findUids(query, searchBy, startsWith, callback) {
|
||||
if (!query) {
|
||||
return db.getSortedSetRevRange('users:joindate', 0, -1, callback);
|
||||
}
|
||||
|
||||
var keys = searchBy.map(function(searchBy) {
|
||||
return searchBy + ':uid';
|
||||
});
|
||||
|
||||
db.getObjects(keys, function(err, hashes) {
|
||||
if (err || !hashes) {
|
||||
return callback(err, []);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="alert alert-danger">
|
||||
<strong>[[global:500.title]]</strong>
|
||||
<p>[[global:500.message]]</p>
|
||||
<p>{path}<p>
|
||||
<p>{path}</p>
|
||||
<!-- IF error --><p>{error}</p><!-- ENDIF error -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<ul class="nav nav-pills">
|
||||
<!-- BEGIN templates -->
|
||||
<li class="<!-- IF @first -->active<!-- ENDIF @first -->"><a href="#" data-template="{templates.template}" data-toggle="pill">{templates.template}</a></li>
|
||||
<li class="<!-- IF @first -->active<!-- ENDIF @first -->"><a href="#" data-template="{template}" data-toggle="pill">{template}</a></li>
|
||||
<!-- END templates -->
|
||||
</ul>
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
<div class="col-xs-12">
|
||||
<div class="tab-content">
|
||||
<!-- BEGIN templates -->
|
||||
<div class="tab-pane <!-- IF @first -->active<!-- ENDIF @first -->" data-template="{templates.template}">
|
||||
<div class="tab-pane <!-- IF @first -->active<!-- ENDIF @first -->" data-template="{template}">
|
||||
<!-- BEGIN areas -->
|
||||
<div class="area" data-template="{templates.template}" data-location="{templates.areas.location}">
|
||||
<h4>{templates.areas.name} <small>{templates.template} / {templates.areas.location}</small></h4>
|
||||
<div class="area" data-template="{template}" data-location="{areas.location}">
|
||||
<h4>{areas.name} <small>{template} / {areas.location}</small></h4>
|
||||
<div class="well widget-area">
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="row dashboard">
|
||||
<div class="col-lg-9">
|
||||
<!-- Override for now, until the right sidebar graphs are fixed (pending socket.io resolution) -->
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Forum Traffic</div>
|
||||
<div class="panel-body">
|
||||
@@ -87,7 +88,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-3">
|
||||
<!-- Override for now, until the right sidebar graphs are fixed (pending socket.io resolution) -->
|
||||
<div class="col-lg-3 hide">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Anonymous vs Registered Users</div>
|
||||
<div class="panel-body">
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
<script>
|
||||
var RELATIVE_PATH = "{relative_path}";
|
||||
var config = JSON.parse('{configJSON}');
|
||||
var app = {};
|
||||
var app = {
|
||||
template: "{template.name}"
|
||||
};
|
||||
app.user = JSON.parse('{userJSON}');
|
||||
</script>
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<div class="panel-heading">Users Control Panel</div>
|
||||
<div class="panel-body">
|
||||
<button id="createUser" class="btn btn-primary">New User</button>
|
||||
<a target="_blank" href="/admin/users/csv" class="btn btn-primary">Download CSV</a>
|
||||
<a target="_blank" href="{relative_path}/api/admin/users/csv" class="btn btn-primary">Download CSV</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<form>
|
||||
<label>Site Logo</label>
|
||||
<input id="logoUrl" type="text" class="form-control" placeholder="Path to a logo to display on forum header" data-field="brand:logo" /><br />
|
||||
<input data-action="upload" data-target="logoUrl" data-route="{relative_path}/admin/uploadlogo" type="button" class="btn btn-default" value="Upload Logo"></input>
|
||||
<input data-action="upload" data-target="logoUrl" data-route="{relative_path}/api/admin/uploadlogo" type="button" class="btn btn-default" value="Upload Logo"></input>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,7 +45,7 @@
|
||||
<form>
|
||||
<label>Favicon</label><br />
|
||||
<input id="faviconUrl" type="text" class="form-control" placeholder="favicon.ico" data-field="brand:favicon" /><br />
|
||||
<input data-action="upload" data-target="faviconUrl" data-route="{relative_path}/admin/uploadfavicon" type="button" class="btn btn-default" value="Upload"></input>
|
||||
<input data-action="upload" data-target="faviconUrl" data-route="{relative_path}/api/admin/uploadfavicon" type="button" class="btn btn-default" value="Upload"></input>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="maxReconnectionAttempts">Max Reconnection Attempts</label>
|
||||
<input class="form-control" id="maxReconnectionAttempts" type="text" value="5" data-field="maxReconnectionAttempts" />
|
||||
<input class="form-control" id="maxReconnectionAttempts" type="text" value="5" placeholder="Default: 5" data-field="maxReconnectionAttempts" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="reconnectionDelay">Reconnection Delay</label>
|
||||
<input class="form-control" id="reconnectionDelay" type="text" value="200" data-field="reconnectionDelay" />
|
||||
<input class="form-control" id="reconnectionDelay" type="text" value="1500" placeholder="Default: 1500" data-field="reconnectionDelay" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<div class="form-group">
|
||||
<label>Custom Gravatar Default Image</label>
|
||||
<input id="customGravatarDefaultImage" type="text" class="form-control" placeholder="A custom image to use instead of gravatar defaults" data-field="customGravatarDefaultImage" /><br />
|
||||
<input data-action="upload" data-target="customGravatarDefaultImage" data-route="{relative_path}/admin/uploadgravatardefault" type="button" class="btn btn-default" value="Upload"></input>
|
||||
<input data-action="upload" data-target="customGravatarDefaultImage" data-route="{relative_path}/api/admin/uploadgravatardefault" type="button" class="btn btn-default" value="Upload"></input>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"custom_mapping": {
|
||||
"^\/?$": "categories",
|
||||
"^admin?$": "admin/general/dashboard",
|
||||
"^users/sort-posts": "users",
|
||||
"^users/latest": "users",
|
||||
"^users/sort-reputation": "users",
|
||||
"^users/search": "users",
|
||||
"^user/.*/edit": "account/edit",
|
||||
"^user/.*/following": "account/following",
|
||||
"^user/.*/followers": "account/followers",
|
||||
"^user/.*/settings": "account/settings",
|
||||
"^user/.*/favourites": "account/favourites",
|
||||
"^user/.*/watched": "account/watched",
|
||||
"^user/.*/posts": "account/posts",
|
||||
"^user/.*/topics": "account/topics",
|
||||
"^user/.*/groups": "account/groups",
|
||||
"^user/[^\/]+": "account/profile",
|
||||
"^reset/.*": "reset_code",
|
||||
"^tags/.*": "tag",
|
||||
"^groups/?$": "groups/list",
|
||||
"^groups/.*": "groups/details"
|
||||
},
|
||||
"force_refresh": {
|
||||
"logout": true
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ var async = require('async'),
|
||||
|
||||
|
||||
(function(Widgets) {
|
||||
|
||||
Widgets.render = function(uid, area, callback) {
|
||||
if (!area.locations || !area.template) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
|
||||
Reference in New Issue
Block a user