mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-06 11:27:01 +02:00
Merge commit '813fdaf6f6b8cefa4fd7336d8888af9aefa01905' into v1.7.x
This commit is contained in:
@@ -338,30 +338,27 @@ Categories.buildForSelect = function (uid, privilege, callback) {
|
||||
};
|
||||
|
||||
Categories.buildForSelectCategories = function (categories, callback) {
|
||||
function recursive(category, categoriesData, level) {
|
||||
if (category.link) {
|
||||
return;
|
||||
}
|
||||
|
||||
function recursive(category, categoriesData, level, depth) {
|
||||
var bullet = level ? '• ' : '';
|
||||
category.value = category.cid;
|
||||
category.level = level;
|
||||
category.text = level + bullet + category.name;
|
||||
category.depth = depth;
|
||||
categoriesData.push(category);
|
||||
|
||||
category.children.forEach(function (child) {
|
||||
recursive(child, categoriesData, ' ' + level);
|
||||
recursive(child, categoriesData, ' ' + level, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
var categoriesData = [];
|
||||
|
||||
categories = categories.filter(function (category) {
|
||||
return category && !category.link && !parseInt(category.parentCid, 10);
|
||||
return category && !parseInt(category.parentCid, 10);
|
||||
});
|
||||
|
||||
categories.forEach(function (category) {
|
||||
recursive(category, categoriesData, '');
|
||||
recursive(category, categoriesData, '', 0);
|
||||
});
|
||||
callback(null, categoriesData);
|
||||
};
|
||||
|
||||
@@ -61,8 +61,9 @@ module.exports = function (Categories) {
|
||||
'topics:tag',
|
||||
'posts:edit',
|
||||
'posts:delete',
|
||||
'posts:upvote',
|
||||
'posts:downvote',
|
||||
'topics:delete',
|
||||
'upload:post:image',
|
||||
];
|
||||
|
||||
async.series([
|
||||
|
||||
@@ -31,7 +31,11 @@ module.exports = function (Categories) {
|
||||
category.name = validator.escape(String(category.name || ''));
|
||||
category.disabled = category.hasOwnProperty('disabled') ? parseInt(category.disabled, 10) === 1 : undefined;
|
||||
category.isSection = category.hasOwnProperty('isSection') ? parseInt(category.isSection, 10) === 1 : undefined;
|
||||
category.icon = category.icon || 'hidden';
|
||||
|
||||
if (category.hasOwnProperty('icon')) {
|
||||
category.icon = category.icon || 'hidden';
|
||||
}
|
||||
|
||||
if (category.hasOwnProperty('post_count')) {
|
||||
category.post_count = category.post_count || 0;
|
||||
category.totalPostCount = category.post_count;
|
||||
|
||||
@@ -8,7 +8,7 @@ var dirname = require('./paths').baseDir;
|
||||
|
||||
// check to make sure dependencies are installed
|
||||
try {
|
||||
fs.readFileSync(path.join(dirname, 'package.json'));
|
||||
fs.accessSync(path.join(dirname, 'package.json'), fs.constants.R_OK);
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
console.warn('package.json not found.');
|
||||
@@ -18,6 +18,8 @@ try {
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
|
||||
try {
|
||||
fs.accessSync(path.join(dirname, 'node_modules/colors/package.json'), fs.constants.R_OK);
|
||||
|
||||
require('colors');
|
||||
console.log('OK'.green);
|
||||
} catch (e) {
|
||||
@@ -29,15 +31,30 @@ try {
|
||||
}
|
||||
|
||||
try {
|
||||
fs.readFileSync(path.join(dirname, 'node_modules/async/package.json'), 'utf8');
|
||||
fs.readFileSync(path.join(dirname, 'node_modules/commander/package.json'), 'utf8');
|
||||
fs.readFileSync(path.join(dirname, 'node_modules/colors/package.json'), 'utf8');
|
||||
fs.readFileSync(path.join(dirname, 'node_modules/nconf/package.json'), 'utf8');
|
||||
fs.accessSync(path.join(dirname, 'node_modules/semver/package.json'), fs.constants.R_OK);
|
||||
|
||||
var semver = require('semver');
|
||||
var defaultPackage = require('../../install/package.json');
|
||||
|
||||
var checkVersion = function (packageName) {
|
||||
var version = JSON.parse(fs.readFileSync(path.join(dirname, 'node_modules', packageName, 'package.json'), 'utf8')).version;
|
||||
if (!semver.satisfies(version, defaultPackage.dependencies[packageName])) {
|
||||
var e = new TypeError('Incorrect dependency version: ' + packageName);
|
||||
e.code = 'DEP_WRONG_VERSION';
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
checkVersion('nconf');
|
||||
checkVersion('async');
|
||||
checkVersion('commander');
|
||||
checkVersion('colors');
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
console.warn('Dependencies not yet installed.');
|
||||
if (['ENOENT', 'DEP_WRONG_VERSION', 'MODULE_NOT_FOUND'].indexOf(e.code) !== -1) {
|
||||
console.warn('Dependencies outdated or not yet installed.');
|
||||
console.log('Installing them now...\n');
|
||||
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.installAll();
|
||||
|
||||
require('colors');
|
||||
@@ -241,7 +258,7 @@ program
|
||||
'When running particular upgrade scripts, options are ignored.',
|
||||
'By default all options are enabled. Passing any options disables that default.',
|
||||
'Only package and dependency updates: ' + './nodebb upgrade -mi'.yellow,
|
||||
'Only database update: ' + './nodebb upgrade -d'.yellow,
|
||||
'Only database update: ' + './nodebb upgrade -s'.yellow,
|
||||
].join('\n'));
|
||||
})
|
||||
.action(function (scripts, options) {
|
||||
@@ -280,16 +297,12 @@ program
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('*', {}, {
|
||||
noHelp: true,
|
||||
})
|
||||
.action(function () {
|
||||
program.help();
|
||||
});
|
||||
|
||||
require('./colors');
|
||||
|
||||
if (process.argv.length === 2) {
|
||||
program.help();
|
||||
}
|
||||
|
||||
program.executables = false;
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
@@ -33,6 +33,7 @@ function installAll() {
|
||||
var prod = global.env !== 'development';
|
||||
var command = 'npm install';
|
||||
try {
|
||||
fs.accessSync(path.join(modulesPath, 'nconf/package.json'), fs.constants.R_OK);
|
||||
var packageManager = require('nconf').get('package_manager');
|
||||
if (packageManager === 'yarn') {
|
||||
command = 'yarn';
|
||||
|
||||
@@ -23,6 +23,7 @@ var steps = {
|
||||
install: {
|
||||
message: 'Bringing base dependencies up to date...',
|
||||
handler: function (next) {
|
||||
process.stdout.write(' started\n'.green);
|
||||
packageInstall.installAll();
|
||||
next();
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ var async = require('async');
|
||||
var messaging = require('../../messaging');
|
||||
var meta = require('../../meta');
|
||||
var user = require('../../user');
|
||||
var privileges = require('../../privileges');
|
||||
var helpers = require('../helpers');
|
||||
|
||||
var chatsController = module.exports;
|
||||
@@ -26,6 +27,13 @@ chatsController.get = function (req, res, callback) {
|
||||
if (!uid) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
privileges.global.can('chat', req.uid, next);
|
||||
},
|
||||
function (canChat, next) {
|
||||
if (!canChat) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
messaging.getRecentChats(req.uid, uid, 0, 19, next);
|
||||
},
|
||||
function (_recentChats, next) {
|
||||
|
||||
@@ -28,6 +28,9 @@ editController.get = function (req, res, callback) {
|
||||
userData.maximumProfileImageSize = parseInt(meta.config.maximumProfileImageSize, 10);
|
||||
userData.allowProfileImageUploads = parseInt(meta.config.allowProfileImageUploads, 10) === 1;
|
||||
userData.allowAccountDelete = parseInt(meta.config.allowAccountDelete, 10) === 1;
|
||||
userData.allowWebsite = !userData.isSelf || parseInt(userData.reputation, 10) >= (parseInt(meta.config['min:rep:website'], 10) || 0);
|
||||
userData.allowAboutMe = !userData.isSelf || parseInt(userData.reputation, 10) >= (parseInt(meta.config['min:rep:aboutme'], 10) || 0);
|
||||
userData.allowSignature = !userData.isSelf || parseInt(userData.reputation, 10) >= (parseInt(meta.config['min:rep:signature'], 10) || 0);
|
||||
userData.profileImageDimension = parseInt(meta.config.profileImageDimension, 10) || 200;
|
||||
userData.defaultAvatar = user.getDefaultAvatar();
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) {
|
||||
sso: function (next) {
|
||||
plugins.fireHook('filter:auth.list', { uid: uid, associations: [] }, next);
|
||||
},
|
||||
canEdit: function (next) {
|
||||
privileges.users.canEdit(callerUID, uid, next);
|
||||
},
|
||||
canBanUser: function (next) {
|
||||
privileges.users.canBanUser(callerUID, uid, next);
|
||||
},
|
||||
@@ -113,7 +116,7 @@ helpers.getUserDataByUserSlug = function (userslug, callerUID, callback) {
|
||||
userData.isAdminOrGlobalModerator = isAdmin || isGlobalModerator;
|
||||
userData.isAdminOrGlobalModeratorOrModerator = isAdmin || isGlobalModerator || isModerator;
|
||||
userData.isSelfOrAdminOrGlobalModerator = isSelf || isAdmin || isGlobalModerator;
|
||||
userData.canEdit = isAdmin || (isGlobalModerator && !results.isTargetAdmin);
|
||||
userData.canEdit = results.canEdit;
|
||||
userData.canBan = results.canBanUser;
|
||||
userData.canChangePassword = isAdmin || (isSelf && parseInt(meta.config['password:disableEdit'], 10) !== 1);
|
||||
userData.isSelf = isSelf;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
var adminController = {
|
||||
dashboard: require('./admin/dashboard'),
|
||||
categories: require('./admin/categories'),
|
||||
privileges: require('./admin/privileges'),
|
||||
adminsMods: require('./admin/admins-mods'),
|
||||
tags: require('./admin/tags'),
|
||||
postQueue: require('./admin/postqueue'),
|
||||
blacklist: require('./admin/blacklist'),
|
||||
|
||||
50
src/controllers/admin/admins-mods.js
Normal file
50
src/controllers/admin/admins-mods.js
Normal file
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var groups = require('../../groups');
|
||||
var categories = require('../../categories');
|
||||
|
||||
var AdminsMods = module.exports;
|
||||
|
||||
AdminsMods.get = function (req, res, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
admins: function (next) {
|
||||
groups.get('administrators', { uid: req.uid }, next);
|
||||
},
|
||||
globalMods: function (next) {
|
||||
groups.get('Global Moderators', { uid: req.uid }, next);
|
||||
},
|
||||
categories: function (next) {
|
||||
getModeratorsOfCategories(req.uid, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results) {
|
||||
res.render('admin/manage/admins-mods', results);
|
||||
},
|
||||
], next);
|
||||
};
|
||||
|
||||
function getModeratorsOfCategories(uid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
categories.buildForSelect(uid, 'find', next);
|
||||
},
|
||||
function (categoryData, next) {
|
||||
async.map(categoryData, function (category, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
categories.getModerators(category.cid, next);
|
||||
},
|
||||
function (moderators, next) {
|
||||
category.moderators = moderators;
|
||||
next(null, category);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
var async = require('async');
|
||||
|
||||
var categories = require('../../categories');
|
||||
var privileges = require('../../privileges');
|
||||
var analytics = require('../../analytics');
|
||||
var plugins = require('../../plugins');
|
||||
var translator = require('../../translator');
|
||||
@@ -15,7 +14,6 @@ categoriesController.get = function (req, res, callback) {
|
||||
function (next) {
|
||||
async.parallel({
|
||||
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
|
||||
privileges: async.apply(privileges.categories.list, req.params.category_id),
|
||||
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
|
||||
}, next);
|
||||
},
|
||||
@@ -36,7 +34,6 @@ categoriesController.get = function (req, res, callback) {
|
||||
req: req,
|
||||
res: res,
|
||||
category: category,
|
||||
privileges: data.privileges,
|
||||
allCategories: data.allCategories,
|
||||
}, next);
|
||||
},
|
||||
@@ -44,7 +41,6 @@ categoriesController.get = function (req, res, callback) {
|
||||
data.category.name = translator.escape(String(data.category.name));
|
||||
res.render('admin/manage/category', {
|
||||
category: data.category,
|
||||
privileges: data.privileges,
|
||||
allCategories: data.allCategories,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -48,6 +48,7 @@ dashboardController.get = function (req, res, next) {
|
||||
version: nconf.get('version'),
|
||||
notices: results.notices,
|
||||
stats: results.stats,
|
||||
canRestart: !!process.send,
|
||||
});
|
||||
},
|
||||
], next);
|
||||
|
||||
39
src/controllers/admin/privileges.js
Normal file
39
src/controllers/admin/privileges.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var categories = require('../../categories');
|
||||
var privileges = require('../../privileges');
|
||||
|
||||
var privilegesController = module.exports;
|
||||
|
||||
privilegesController.get = function (req, res, callback) {
|
||||
var cid = req.params.cid ? req.params.cid : 0;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
privileges: function (next) {
|
||||
if (!cid) {
|
||||
privileges.global.list(next);
|
||||
} else {
|
||||
privileges.categories.list(cid, next);
|
||||
}
|
||||
},
|
||||
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
|
||||
}, next);
|
||||
},
|
||||
function (data) {
|
||||
data.allCategories.forEach(function (category) {
|
||||
if (category) {
|
||||
category.selected = parseInt(category.cid, 10) === parseInt(cid, 10);
|
||||
}
|
||||
});
|
||||
|
||||
res.render('admin/manage/privileges', {
|
||||
privileges: data.privileges,
|
||||
allCategories: data.allCategories,
|
||||
cid: cid,
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
@@ -129,7 +129,7 @@ helpers.buildCategoryBreadcrumbs = function (cid, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!meta.config.homePageRoute && meta.config.homePageCustom) {
|
||||
if (meta.config.homePageRoute && meta.config.homePageRoute !== 'categories') {
|
||||
breadcrumbs.unshift({
|
||||
text: '[[global:header.categories]]',
|
||||
url: nconf.get('relative_path') + '/categories',
|
||||
|
||||
@@ -1,61 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var plugins = require('../plugins');
|
||||
var meta = require('../meta');
|
||||
var user = require('../user');
|
||||
var pubsub = require('../pubsub');
|
||||
|
||||
var adminHomePageRoute;
|
||||
var getRoute;
|
||||
|
||||
function configUpdated() {
|
||||
adminHomePageRoute = (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
|
||||
getRoute = parseInt(meta.config.allowUserHomePage, 10) ? getRouteAllowUserHomePage : getRouteDisableUserHomePage;
|
||||
function adminHomePageRoute() {
|
||||
return (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
|
||||
}
|
||||
|
||||
function getRouteDisableUserHomePage(uid, next) {
|
||||
next(null, adminHomePageRoute);
|
||||
function getUserHomeRoute(uid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.getSettings(uid, next);
|
||||
},
|
||||
function (settings, next) {
|
||||
var route = adminHomePageRoute();
|
||||
|
||||
if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
|
||||
route = settings.homePageRoute || route;
|
||||
}
|
||||
|
||||
next(null, route);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function getRouteAllowUserHomePage(uid, next) {
|
||||
user.getSettings(uid, function (err, settings) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var route = adminHomePageRoute;
|
||||
|
||||
if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
|
||||
route = settings.homePageRoute || route;
|
||||
}
|
||||
|
||||
next(null, route);
|
||||
});
|
||||
}
|
||||
|
||||
pubsub.on('config:update', configUpdated);
|
||||
configUpdated();
|
||||
|
||||
function rewrite(req, res, next) {
|
||||
if (req.path !== '/' && req.path !== '/api/' && req.path !== '/api') {
|
||||
return next();
|
||||
}
|
||||
|
||||
getRoute(req.uid, function (err, route) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
if (parseInt(meta.config.allowUserHomePage, 10)) {
|
||||
getUserHomeRoute(req.uid, next);
|
||||
} else {
|
||||
next(null, adminHomePageRoute());
|
||||
}
|
||||
},
|
||||
function (route, next) {
|
||||
var hook = 'action:homepage.get:' + route;
|
||||
|
||||
var hook = 'action:homepage.get:' + route;
|
||||
if (!plugins.hasListeners(hook)) {
|
||||
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
|
||||
} else {
|
||||
res.locals.homePageRoute = route;
|
||||
}
|
||||
|
||||
if (!plugins.hasListeners(hook)) {
|
||||
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
|
||||
} else {
|
||||
res.locals.homePageRoute = route;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
next();
|
||||
},
|
||||
], next);
|
||||
}
|
||||
|
||||
exports.rewrite = rewrite;
|
||||
|
||||
@@ -126,7 +126,7 @@ modsController.flags.detail = function (req, res, next) {
|
||||
assignees: results.assignees,
|
||||
type_bool: ['post', 'user', 'empty'].reduce(function (memo, cur) {
|
||||
if (cur !== 'empty') {
|
||||
memo[cur] = results.flagData.type === cur && !!Object.keys(results.flagData.target).length;
|
||||
memo[cur] = results.flagData.type === cur && (!results.flagData.target || !!Object.keys(results.flagData.target).length);
|
||||
} else {
|
||||
memo[cur] = !Object.keys(results.flagData.target).length;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ searchController.search = function (req, res, next) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
results.categories = results.categories.filter(function (category) {
|
||||
return category && !category.link;
|
||||
});
|
||||
|
||||
var categoriesData = [
|
||||
{ value: 'all', text: '[[unread:all_categories]]' },
|
||||
{ value: 'watched', text: '[[category:watched-categories]]' },
|
||||
|
||||
@@ -8,7 +8,7 @@ var topics = require('../topics');
|
||||
var pagination = require('../pagination');
|
||||
var helpers = require('./helpers');
|
||||
|
||||
var tagsController = {};
|
||||
var tagsController = module.exports;
|
||||
|
||||
tagsController.getTag = function (req, res, next) {
|
||||
var tag = validator.escape(String(req.params.tag));
|
||||
@@ -33,7 +33,7 @@ tagsController.getTag = function (req, res, next) {
|
||||
templateData.nextStart = stop + 1;
|
||||
async.parallel({
|
||||
topicCount: function (next) {
|
||||
topics.getTagTopicCount(tag, next);
|
||||
topics.getTagTopicCount(req.params.tag, next);
|
||||
},
|
||||
tids: function (next) {
|
||||
topics.getTagTids(req.params.tag, start, stop, next);
|
||||
@@ -47,44 +47,41 @@ tagsController.getTag = function (req, res, next) {
|
||||
topicCount = results.topicCount;
|
||||
topics.getTopics(results.tids, req.uid, next);
|
||||
},
|
||||
], function (err, topics) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
function (topics) {
|
||||
res.locals.metaTags = [
|
||||
{
|
||||
name: 'title',
|
||||
content: tag,
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: tag,
|
||||
},
|
||||
];
|
||||
templateData.topics = topics;
|
||||
|
||||
res.locals.metaTags = [
|
||||
{
|
||||
name: 'title',
|
||||
content: tag,
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: tag,
|
||||
},
|
||||
];
|
||||
templateData.topics = topics;
|
||||
var pageCount = Math.max(1, Math.ceil(topicCount / settings.topicsPerPage));
|
||||
templateData.pagination = pagination.create(page, pageCount);
|
||||
|
||||
var pageCount = Math.max(1, Math.ceil(topicCount / settings.topicsPerPage));
|
||||
templateData.pagination = pagination.create(page, pageCount);
|
||||
|
||||
res.render('tag', templateData);
|
||||
});
|
||||
res.render('tag', templateData);
|
||||
},
|
||||
], next);
|
||||
};
|
||||
|
||||
tagsController.getTags = function (req, res, next) {
|
||||
topics.getTags(0, 99, function (err, tags) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
tags = tags.filter(Boolean);
|
||||
var data = {
|
||||
tags: tags,
|
||||
nextStart: 100,
|
||||
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[tags:tags]]' }]),
|
||||
title: '[[pages:tags]]',
|
||||
};
|
||||
res.render('tags', data);
|
||||
});
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
topics.getTags(0, 99, next);
|
||||
},
|
||||
function (tags) {
|
||||
tags = tags.filter(Boolean);
|
||||
var data = {
|
||||
tags: tags,
|
||||
nextStart: 100,
|
||||
breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[tags:tags]]' }]),
|
||||
title: '[[pages:tags]]',
|
||||
};
|
||||
res.render('tags', data);
|
||||
},
|
||||
], next);
|
||||
};
|
||||
|
||||
module.exports = tagsController;
|
||||
|
||||
@@ -387,7 +387,7 @@ topicsController.pagination = function (req, res, callback) {
|
||||
}
|
||||
|
||||
var postCount = parseInt(results.topic.postcount, 10);
|
||||
var pageCount = Math.max(1, Math.ceil((postCount - 1) / results.settings.postsPerPage));
|
||||
var pageCount = Math.max(1, Math.ceil(postCount / results.settings.postsPerPage));
|
||||
|
||||
var paginationData = pagination.create(currentPage, pageCount);
|
||||
paginationData.rel.forEach(function (rel) {
|
||||
|
||||
@@ -37,9 +37,6 @@ uploadsController.upload = function (req, res, filesIterator) {
|
||||
|
||||
uploadsController.uploadPost = function (req, res, next) {
|
||||
uploadsController.upload(req, res, function (uploadedFile, next) {
|
||||
if (!parseInt(req.body.cid, 10)) {
|
||||
return next(new Error('[[error:category-not-selected]]'));
|
||||
}
|
||||
var isImage = uploadedFile.type.match(/image./);
|
||||
if (isImage) {
|
||||
uploadAsImage(req, uploadedFile, next);
|
||||
@@ -52,7 +49,7 @@ uploadsController.uploadPost = function (req, res, next) {
|
||||
function uploadAsImage(req, uploadedFile, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.categories.can('upload:post:image', req.body.cid, req.uid, next);
|
||||
privileges.global.can('upload:post:image', req.uid, next);
|
||||
},
|
||||
function (canUpload, next) {
|
||||
if (!canUpload) {
|
||||
@@ -82,7 +79,7 @@ function uploadAsImage(req, uploadedFile, callback) {
|
||||
function uploadAsFile(req, uploadedFile, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.categories.can('upload:post:file', req.body.cid, req.uid, next);
|
||||
privileges.global.can('upload:post:file', req.uid, next);
|
||||
},
|
||||
function (canUpload, next) {
|
||||
if (!canUpload) {
|
||||
|
||||
@@ -83,8 +83,8 @@ module.exports = function (db, module) {
|
||||
if (!key) {
|
||||
return callback();
|
||||
}
|
||||
db.collection('objects').findAndModify({ _key: key }, {}, { $inc: { value: 1 } }, { new: true, upsert: true }, function (err, result) {
|
||||
callback(err, result && result.value ? result.value.value : null);
|
||||
db.collection('objects').findAndModify({ _key: key }, {}, { $inc: { data: 1 } }, { new: true, upsert: true }, function (err, result) {
|
||||
callback(err, result && result.value ? result.value.data : null);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -108,6 +108,7 @@ module.exports = function (db, module) {
|
||||
if (!data) {
|
||||
return callback(null, null);
|
||||
}
|
||||
delete data.expireAt;
|
||||
var keys = Object.keys(data);
|
||||
if (keys.length === 4 && data.hasOwnProperty('_key') && data.hasOwnProperty('score') && data.hasOwnProperty('value')) {
|
||||
return callback(null, 'zset');
|
||||
|
||||
@@ -37,19 +37,22 @@ redisModule.questions = [
|
||||
];
|
||||
|
||||
redisModule.init = function (callback) {
|
||||
redisClient = redisModule.connect();
|
||||
callback = callback || function () { };
|
||||
redisClient = redisModule.connect({}, function (err) {
|
||||
if (err) {
|
||||
winston.error('NodeBB could not connect to your Redis database. Redis returned the following error', err);
|
||||
return callback(err);
|
||||
}
|
||||
redisModule.client = redisClient;
|
||||
|
||||
redisModule.client = redisClient;
|
||||
require('./redis/main')(redisClient, redisModule);
|
||||
require('./redis/hash')(redisClient, redisModule);
|
||||
require('./redis/sets')(redisClient, redisModule);
|
||||
require('./redis/sorted')(redisClient, redisModule);
|
||||
require('./redis/list')(redisClient, redisModule);
|
||||
|
||||
require('./redis/main')(redisClient, redisModule);
|
||||
require('./redis/hash')(redisClient, redisModule);
|
||||
require('./redis/sets')(redisClient, redisModule);
|
||||
require('./redis/sorted')(redisClient, redisModule);
|
||||
require('./redis/list')(redisClient, redisModule);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
redisModule.initSessionStore = function (callback) {
|
||||
@@ -66,7 +69,8 @@ redisModule.initSessionStore = function (callback) {
|
||||
}
|
||||
};
|
||||
|
||||
redisModule.connect = function (options) {
|
||||
redisModule.connect = function (options, callback) {
|
||||
callback = callback || function () {};
|
||||
var redis_socket_or_host = nconf.get('redis:host');
|
||||
var cxn;
|
||||
|
||||
@@ -88,7 +92,11 @@ redisModule.connect = function (options) {
|
||||
|
||||
cxn.on('error', function (err) {
|
||||
winston.error(err.stack);
|
||||
process.exit(1);
|
||||
callback(err);
|
||||
});
|
||||
|
||||
cxn.on('ready', function () {
|
||||
callback();
|
||||
});
|
||||
|
||||
if (nconf.get('redis:password')) {
|
||||
@@ -99,7 +107,7 @@ redisModule.connect = function (options) {
|
||||
if (dbIdx >= 0) {
|
||||
cxn.select(dbIdx, function (err) {
|
||||
if (err) {
|
||||
winston.error('NodeBB could not connect to your Redis database. Redis returned the following error', err);
|
||||
winston.error('NodeBB could not select Redis database. Redis returned the following error', err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ var htmlToText = require('html-to-text');
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var _ = require('lodash');
|
||||
|
||||
var User = require('./user');
|
||||
var Plugins = require('./plugins');
|
||||
@@ -289,11 +290,10 @@ function buildCustomTemplates(config) {
|
||||
file.walk(viewsDir, next);
|
||||
},
|
||||
function (paths, next) {
|
||||
paths = paths.reduce(function (obj, p) {
|
||||
var relative = path.relative(viewsDir, p);
|
||||
obj['/' + relative] = p;
|
||||
return obj;
|
||||
}, {});
|
||||
paths = _.fromPairs(paths.map(function (p) {
|
||||
var relative = path.relative(viewsDir, p).replace(/\\/g, '/');
|
||||
return [relative, p];
|
||||
}));
|
||||
meta.templates.processImports(paths, template.path, template.text, next);
|
||||
},
|
||||
function (source, next) {
|
||||
|
||||
@@ -241,7 +241,7 @@ Flags.validate = function (payload, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0;
|
||||
var minimumReputation = utils.isNumber(meta.config['min:rep:flag']) ? parseInt(meta.config['min:rep:flag'], 10) : 0;
|
||||
// Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply)
|
||||
if (!editable.flag && parseInt(data.reporter.reputation, 10) < minimumReputation) {
|
||||
return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
|
||||
@@ -257,7 +257,7 @@ Flags.validate = function (payload, callback) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0;
|
||||
var minimumReputation = utils.isNumber(meta.config['min:rep:flag']) ? parseInt(meta.config['min:rep:flag'], 10) : 0;
|
||||
// Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply)
|
||||
if (!editable && parseInt(data.reporter.reputation, 10) < minimumReputation) {
|
||||
return callback(new Error('[[error:not-enough-reputation-to-flag]]'));
|
||||
@@ -387,7 +387,7 @@ Flags.create = function (type, id, uid, reason, timestamp, callback) {
|
||||
tasks.push(async.apply(Flags.update, flagId, uid, { state: 'open' }));
|
||||
}
|
||||
|
||||
async.parallel(tasks, function (err) {
|
||||
async.series(tasks, function (err) {
|
||||
next(err, flagId);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -90,11 +90,11 @@ module.exports = function (Groups) {
|
||||
return callback(new Error('[[error:group-name-too-long]]'));
|
||||
}
|
||||
|
||||
if (!Groups.isPrivilegeGroup(name) && name.indexOf(':') !== -1) {
|
||||
if (!Groups.isPrivilegeGroup(name) && name.includes(':')) {
|
||||
return callback(new Error('[[error:invalid-group-name]]'));
|
||||
}
|
||||
|
||||
if (name.indexOf('/') !== -1 || !utils.slugify(name)) {
|
||||
if (name.includes('/') || !utils.slugify(name)) {
|
||||
return callback(new Error('[[error:invalid-group-name]]'));
|
||||
}
|
||||
|
||||
|
||||
@@ -157,16 +157,17 @@ function completeConfigSetup(config, next) {
|
||||
}
|
||||
}
|
||||
|
||||
nconf.overrides(config);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
install.save(config, next);
|
||||
},
|
||||
function (next) {
|
||||
require('./database').init(next);
|
||||
},
|
||||
function (next) {
|
||||
require('./database').createIndices(next);
|
||||
},
|
||||
function (next) {
|
||||
install.save(config, next);
|
||||
},
|
||||
], next);
|
||||
}
|
||||
|
||||
@@ -353,6 +354,11 @@ function createGlobalModeratorsGroup(next) {
|
||||
], next);
|
||||
}
|
||||
|
||||
function giveGlobalPrivileges(next) {
|
||||
var privileges = require('./privileges');
|
||||
privileges.global.give(['chat', 'upload:post:image'], 'registered-users', next);
|
||||
}
|
||||
|
||||
function createCategories(next) {
|
||||
var Categories = require('./categories');
|
||||
|
||||
@@ -498,6 +504,7 @@ install.setup = function (callback) {
|
||||
createCategories,
|
||||
createAdministrator,
|
||||
createGlobalModeratorsGroup,
|
||||
giveGlobalPrivileges,
|
||||
createMenuItems,
|
||||
createWelcomePost,
|
||||
enableDefaultPlugins,
|
||||
@@ -517,7 +524,7 @@ install.setup = function (callback) {
|
||||
], function (err, results) {
|
||||
if (err) {
|
||||
winston.warn('NodeBB Setup Aborted.\n ' + err.stack);
|
||||
process.exit();
|
||||
process.exit(1);
|
||||
} else {
|
||||
var data = {};
|
||||
if (results[6]) {
|
||||
|
||||
@@ -76,6 +76,7 @@ module.exports = function (Messaging) {
|
||||
|
||||
notifications.create({
|
||||
type: 'new-chat',
|
||||
subject: '[[email:notif.chat.subject, ' + messageObj.fromUser.username + ']]',
|
||||
bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
|
||||
bodyLong: messageObj.content,
|
||||
nid: 'chat_' + fromuid + '_' + roomId,
|
||||
|
||||
@@ -143,6 +143,11 @@ function build(targets, callback) {
|
||||
target = target.toLowerCase().replace(/-/g, '');
|
||||
if (!aliases[target]) {
|
||||
winston.warn('[build] Unknown target: ' + target);
|
||||
if (target.indexOf(',') !== -1) {
|
||||
winston.warn('[build] Are you specifying multiple targets? Separate them with spaces:');
|
||||
winston.warn('[build] e.g. `./nodebb build adminjs tpl`');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ var JS = module.exports;
|
||||
|
||||
JS.scripts = {
|
||||
base: [
|
||||
'node_modules/promise-polyfill/dist/polyfill.js',
|
||||
'node_modules/jquery/dist/jquery.js',
|
||||
'node_modules/socket.io-client/dist/socket.io.js',
|
||||
'public/vendor/jquery/timeago/jquery.timeago.js',
|
||||
@@ -36,7 +37,6 @@ JS.scripts = {
|
||||
'public/src/ajaxify.js',
|
||||
'public/src/overrides.js',
|
||||
'public/src/widgets.js',
|
||||
'node_modules/promise-polyfill/promise.js',
|
||||
],
|
||||
|
||||
// files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load
|
||||
@@ -343,6 +343,11 @@ JS.buildBundle = function (target, fork, callback) {
|
||||
function (next) {
|
||||
getBundleScriptList(target, next);
|
||||
},
|
||||
function (files, next) {
|
||||
mkdirp(path.join(__dirname, '../../build/public'), function (err) {
|
||||
next(err, files);
|
||||
});
|
||||
},
|
||||
function (files, next) {
|
||||
var minify = global.env !== 'development';
|
||||
var filePath = path.join(__dirname, '../../build/public', fileNames[target]);
|
||||
|
||||
@@ -75,7 +75,7 @@ function forkAction(action, callback) {
|
||||
freeChild(proc);
|
||||
|
||||
if (message.type === 'error') {
|
||||
return callback(message.err);
|
||||
return callback(message.message);
|
||||
}
|
||||
|
||||
if (message.type === 'end') {
|
||||
@@ -103,7 +103,7 @@ if (process.env.minifier_child) {
|
||||
if (typeof actions[action.act] !== 'function') {
|
||||
process.send({
|
||||
type: 'error',
|
||||
err: Error('Unknown action'),
|
||||
message: 'Unknown action',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -112,7 +112,7 @@ if (process.env.minifier_child) {
|
||||
if (err) {
|
||||
process.send({
|
||||
type: 'error',
|
||||
err: err,
|
||||
message: err.stack,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ var async = require('async');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var nconf = require('nconf');
|
||||
var _ = require('lodash');
|
||||
|
||||
var plugins = require('../plugins');
|
||||
var file = require('../file');
|
||||
@@ -24,7 +25,7 @@ function processImports(paths, templatePath, source, callback) {
|
||||
return callback(null, source);
|
||||
}
|
||||
|
||||
var partial = '/' + matches[1];
|
||||
var partial = matches[1];
|
||||
if (paths[partial] && templatePath !== partial) {
|
||||
fs.readFile(paths[partial], 'utf8', function (err, partialSource) {
|
||||
if (err) {
|
||||
@@ -43,124 +44,108 @@ function processImports(paths, templatePath, source, callback) {
|
||||
}
|
||||
Templates.processImports = processImports;
|
||||
|
||||
Templates.compile = function (callback) {
|
||||
callback = callback || function () {};
|
||||
function getTemplateDirs(callback) {
|
||||
var pluginTemplates = _.values(plugins.pluginsData)
|
||||
.filter(function (pluginData) {
|
||||
return !pluginData.id.startsWith('nodebb-theme-');
|
||||
})
|
||||
.map(function (pluginData) {
|
||||
return path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.templates || 'templates');
|
||||
});
|
||||
|
||||
var themeConfig = require(nconf.get('theme_config'));
|
||||
var baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')];
|
||||
var theme = themeConfig.baseTheme;
|
||||
|
||||
var themePath;
|
||||
var themeTemplates = [nconf.get('theme_templates_path')];
|
||||
while (theme) {
|
||||
themePath = path.join(nconf.get('themes_path'), theme);
|
||||
themeConfig = require(path.join(themePath, 'theme.json'));
|
||||
|
||||
themeTemplates.push(path.join(themePath, themeConfig.templates || 'templates'));
|
||||
theme = themeConfig.baseTheme;
|
||||
}
|
||||
|
||||
themeTemplates.push(nconf.get('base_templates_path'));
|
||||
themeTemplates = _.uniq(themeTemplates.reverse());
|
||||
|
||||
var coreTemplatesPath = nconf.get('core_templates_path');
|
||||
|
||||
var templateDirs = _.uniq([coreTemplatesPath].concat(themeTemplates, pluginTemplates));
|
||||
|
||||
async.filter(templateDirs, file.exists, callback);
|
||||
}
|
||||
|
||||
function getTemplateFiles(dirs, callback) {
|
||||
async.waterfall([
|
||||
function (cb) {
|
||||
async.map(dirs, function (dir, next) {
|
||||
file.walk(dir, function (err, files) {
|
||||
if (err) { return next(err); }
|
||||
|
||||
files = files.filter(function (path) {
|
||||
return path.endsWith('.tpl');
|
||||
}).map(function (file) {
|
||||
return {
|
||||
name: path.relative(dir, file).replace(/\\/g, '/'),
|
||||
path: file,
|
||||
};
|
||||
});
|
||||
next(null, files);
|
||||
});
|
||||
}, cb);
|
||||
},
|
||||
function (buckets, cb) {
|
||||
var dict = {};
|
||||
buckets.forEach(function (files) {
|
||||
files.forEach(function (file) {
|
||||
dict[file.name] = file.path;
|
||||
});
|
||||
});
|
||||
|
||||
cb(null, dict);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function compile(callback) {
|
||||
callback = callback || function () {};
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
preparePaths(baseTemplatesPaths, next);
|
||||
rimraf(viewsPath, function (err) { next(err); });
|
||||
},
|
||||
function (paths, next) {
|
||||
async.each(Object.keys(paths), function (relativePath, next) {
|
||||
function (next) {
|
||||
mkdirp(viewsPath, function (err) { next(err); });
|
||||
},
|
||||
getTemplateDirs,
|
||||
getTemplateFiles,
|
||||
function (files, next) {
|
||||
async.each(Object.keys(files), function (name, next) {
|
||||
var filePath = files[name];
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
fs.readFile(paths[relativePath], 'utf8', next);
|
||||
fs.readFile(filePath, 'utf8', next);
|
||||
},
|
||||
function (source, next) {
|
||||
processImports(paths, relativePath, source, next);
|
||||
processImports(files, name, source, next);
|
||||
},
|
||||
function (source, next) {
|
||||
mkdirp(path.join(viewsPath, path.dirname(relativePath)), function (err) {
|
||||
mkdirp(path.join(viewsPath, path.dirname(name)), function (err) {
|
||||
next(err, source);
|
||||
});
|
||||
},
|
||||
function (compiled, next) {
|
||||
fs.writeFile(path.join(viewsPath, relativePath), compiled, next);
|
||||
fs.writeFile(path.join(viewsPath, name), compiled, next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
rimraf(path.join(viewsPath, '*.js'), next);
|
||||
},
|
||||
function (next) {
|
||||
winston.verbose('[meta/templates] Successfully compiled templates.');
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
function getBaseTemplates(theme) {
|
||||
var baseTemplatesPaths = [];
|
||||
var baseThemePath;
|
||||
var baseThemeConfig;
|
||||
|
||||
while (theme) {
|
||||
baseThemePath = path.join(nconf.get('themes_path'), theme);
|
||||
baseThemeConfig = require(path.join(baseThemePath, 'theme.json'));
|
||||
|
||||
baseTemplatesPaths.push(path.join(baseThemePath, baseThemeConfig.templates || 'templates'));
|
||||
theme = baseThemeConfig.baseTheme;
|
||||
}
|
||||
|
||||
return baseTemplatesPaths.reverse();
|
||||
}
|
||||
|
||||
function preparePaths(baseTemplatesPaths, callback) {
|
||||
var coreTemplatesPath = nconf.get('core_templates_path');
|
||||
var pluginTemplates;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
rimraf(viewsPath, next);
|
||||
},
|
||||
function (next) {
|
||||
mkdirp(viewsPath, next);
|
||||
},
|
||||
function (viewsPath, next) {
|
||||
plugins.fireHook('static:templates.precompile', {}, next);
|
||||
},
|
||||
function (next) {
|
||||
plugins.getTemplates(next);
|
||||
},
|
||||
function (_pluginTemplates, next) {
|
||||
pluginTemplates = _pluginTemplates;
|
||||
winston.verbose('[meta/templates] Compiling templates');
|
||||
|
||||
async.parallel({
|
||||
coreTpls: function (next) {
|
||||
file.walk(coreTemplatesPath, next);
|
||||
},
|
||||
baseThemes: function (next) {
|
||||
async.map(baseTemplatesPaths, function (baseTemplatePath, next) {
|
||||
file.walk(baseTemplatePath, function (err, paths) {
|
||||
paths = paths.map(function (tpl) {
|
||||
return {
|
||||
base: baseTemplatePath,
|
||||
path: tpl.replace(baseTemplatePath, ''),
|
||||
};
|
||||
});
|
||||
|
||||
next(err, paths);
|
||||
});
|
||||
}, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (data, next) {
|
||||
var baseThemes = data.baseThemes;
|
||||
var coreTpls = data.coreTpls;
|
||||
var paths = {};
|
||||
|
||||
coreTpls.forEach(function (el, i) {
|
||||
paths[coreTpls[i].replace(coreTemplatesPath, '')] = coreTpls[i];
|
||||
});
|
||||
|
||||
baseThemes.forEach(function (baseTpls) {
|
||||
baseTpls.forEach(function (el, i) {
|
||||
paths[baseTpls[i].path] = path.join(baseTpls[i].base, baseTpls[i].path);
|
||||
});
|
||||
});
|
||||
|
||||
for (var tpl in pluginTemplates) {
|
||||
if (pluginTemplates.hasOwnProperty(tpl)) {
|
||||
paths[tpl] = pluginTemplates[tpl];
|
||||
}
|
||||
}
|
||||
|
||||
next(null, paths);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
Templates.compile = compile;
|
||||
|
||||
@@ -12,6 +12,7 @@ var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
var navigation = require('../navigation');
|
||||
var translator = require('../translator');
|
||||
var privileges = require('../privileges');
|
||||
var utils = require('../utils');
|
||||
|
||||
var controllers = {
|
||||
@@ -77,6 +78,9 @@ module.exports = function (middleware) {
|
||||
isModerator: function (next) {
|
||||
user.isModeratorOfAnyCategory(req.uid, next);
|
||||
},
|
||||
privileges: function (next) {
|
||||
privileges.global.get(req.uid, next);
|
||||
},
|
||||
user: function (next) {
|
||||
var userData = {
|
||||
uid: 0,
|
||||
@@ -132,6 +136,8 @@ module.exports = function (middleware) {
|
||||
results.user.isAdmin = results.isAdmin;
|
||||
results.user.isGlobalMod = results.isGlobalMod;
|
||||
results.user.isMod = !!results.isModerator;
|
||||
results.user.privileges = results.privileges;
|
||||
|
||||
results.user.uid = parseInt(results.user.uid, 10);
|
||||
results.user.email = String(results.user.email);
|
||||
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
|
||||
@@ -183,6 +189,7 @@ module.exports = function (middleware) {
|
||||
templateValues.isAdmin = results.user.isAdmin;
|
||||
templateValues.isGlobalMod = results.user.isGlobalMod;
|
||||
templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
|
||||
templateValues.canChat = results.canChat && parseInt(meta.config.disableChat, 10) !== 1;
|
||||
templateValues.user = results.user;
|
||||
templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true });
|
||||
templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS;
|
||||
|
||||
@@ -220,7 +220,7 @@ function pushToUids(uids, notification, callback) {
|
||||
async.eachLimit(uids, 3, function (uid, next) {
|
||||
emailer.send('notification', uid, {
|
||||
path: notification.path,
|
||||
subject: '[[notifications:new_notification_from, ' + meta.config.title + ']]',
|
||||
subject: notification.subject || '[[notifications:new_notification_from, ' + meta.config.title + ']]',
|
||||
intro: utils.stripHTMLTags(notification.bodyShort),
|
||||
body: utils.stripHTMLTags(notification.bodyLong || ''),
|
||||
showUnsubscribe: true,
|
||||
|
||||
@@ -138,10 +138,13 @@ Plugins.reloadRoutes = function (callback) {
|
||||
});
|
||||
};
|
||||
|
||||
// DEPRECATED: remove in v1.8.0
|
||||
Plugins.getTemplates = function (callback) {
|
||||
var templates = {};
|
||||
var tplName;
|
||||
|
||||
winston.warn('[deprecated] Plugins.getTemplates is DEPRECATED to be removed in v1.8.0');
|
||||
|
||||
Plugins.data.getActive(function (err, plugins) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
@@ -213,8 +216,8 @@ Plugins.list = function (matching, callback) {
|
||||
require('request')(url, {
|
||||
json: true,
|
||||
}, function (err, res, body) {
|
||||
if (err) {
|
||||
winston.error('Error parsing plugins', err);
|
||||
if (err || (res && res.statusCode !== 200)) {
|
||||
winston.error('Error loading ' + url, err || body);
|
||||
return Plugins.normalise([], callback);
|
||||
}
|
||||
|
||||
@@ -225,7 +228,7 @@ Plugins.list = function (matching, callback) {
|
||||
Plugins.normalise = function (apiReturn, callback) {
|
||||
var pluginMap = {};
|
||||
var dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
|
||||
apiReturn = apiReturn || [];
|
||||
apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
|
||||
for (var i = 0; i < apiReturn.length; i += 1) {
|
||||
apiReturn[i].id = apiReturn[i].name;
|
||||
apiReturn[i].installed = false;
|
||||
|
||||
@@ -12,6 +12,7 @@ var plugins = require('../plugins');
|
||||
var cache = require('./cache');
|
||||
var pubsub = require('../pubsub');
|
||||
var utils = require('../utils');
|
||||
var translator = require('../translator');
|
||||
|
||||
module.exports = function (Posts) {
|
||||
pubsub.on('post:edit', function (pid) {
|
||||
@@ -140,7 +141,7 @@ module.exports = function (Posts) {
|
||||
db.setObject('topic:' + tid, results.topic, next);
|
||||
},
|
||||
function (next) {
|
||||
topics.updateTags(tid, data.tags, next);
|
||||
topics.updateTopicTags(tid, data.tags, next);
|
||||
},
|
||||
function (next) {
|
||||
topics.getTopicTagsObjects(tid, next);
|
||||
@@ -149,6 +150,7 @@ module.exports = function (Posts) {
|
||||
topicData.tags = data.tags;
|
||||
topicData.oldTitle = results.topic.title;
|
||||
topicData.timestamp = results.topic.timestamp;
|
||||
var renamed = translator.escape(validator.escape(String(title))) !== results.topic.title;
|
||||
plugins.fireHook('action:topic.edit', { topic: topicData, uid: data.uid });
|
||||
next(null, {
|
||||
tid: tid,
|
||||
@@ -158,7 +160,7 @@ module.exports = function (Posts) {
|
||||
oldTitle: results.topic.title,
|
||||
slug: topicData.slug,
|
||||
isMainPost: true,
|
||||
renamed: title !== results.topic.title,
|
||||
renamed: renamed,
|
||||
tags: tags,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ var meta = require('../meta');
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var plugins = require('../plugins');
|
||||
var privileges = require('../privileges');
|
||||
|
||||
module.exports = function (Posts) {
|
||||
var votesInProgress = {};
|
||||
@@ -15,16 +16,27 @@ module.exports = function (Posts) {
|
||||
return callback(new Error('[[error:reputation-system-disabled]]'));
|
||||
}
|
||||
|
||||
if (voteInProgress(pid, uid)) {
|
||||
return callback(new Error('[[error:already-voting-for-this-post]]'));
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.posts.can('posts:upvote', pid, uid, next);
|
||||
},
|
||||
function (canUpvote, next) {
|
||||
if (!canUpvote) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
putVoteInProgress(pid, uid);
|
||||
if (voteInProgress(pid, uid)) {
|
||||
return next(new Error('[[error:already-voting-for-this-post]]'));
|
||||
}
|
||||
|
||||
toggleVote('upvote', pid, uid, function (err, data) {
|
||||
clearVoteProgress(pid, uid);
|
||||
callback(err, data);
|
||||
});
|
||||
putVoteInProgress(pid, uid);
|
||||
|
||||
toggleVote('upvote', pid, uid, function (err, data) {
|
||||
clearVoteProgress(pid, uid);
|
||||
next(err, data);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Posts.downvote = function (pid, uid, callback) {
|
||||
@@ -36,16 +48,27 @@ module.exports = function (Posts) {
|
||||
return callback(new Error('[[error:downvoting-disabled]]'));
|
||||
}
|
||||
|
||||
if (voteInProgress(pid, uid)) {
|
||||
return callback(new Error('[[error:already-voting-for-this-post]]'));
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.posts.can('posts:downvote', pid, uid, next);
|
||||
},
|
||||
function (canUpvote, next) {
|
||||
if (!canUpvote) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
putVoteInProgress(pid, uid);
|
||||
if (voteInProgress(pid, uid)) {
|
||||
return next(new Error('[[error:already-voting-for-this-post]]'));
|
||||
}
|
||||
|
||||
toggleVote('downvote', pid, uid, function (err, data) {
|
||||
clearVoteProgress(pid, uid);
|
||||
callback(err, data);
|
||||
});
|
||||
putVoteInProgress(pid, uid);
|
||||
|
||||
toggleVote('downvote', pid, uid, function (err, data) {
|
||||
clearVoteProgress(pid, uid);
|
||||
next(err, data);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Posts.unvote = function (pid, uid, callback) {
|
||||
@@ -106,7 +129,7 @@ module.exports = function (Posts) {
|
||||
};
|
||||
|
||||
function voteInProgress(pid, uid) {
|
||||
return Array.isArray(votesInProgress[uid]) && votesInProgress[uid].indexOf(parseInt(pid, 10)) !== -1;
|
||||
return Array.isArray(votesInProgress[uid]) && votesInProgress[uid].includes(parseInt(pid, 10));
|
||||
}
|
||||
|
||||
function putVoteInProgress(pid, uid) {
|
||||
@@ -156,7 +179,7 @@ module.exports = function (Posts) {
|
||||
return callback(new Error('[[error:self-vote]]'));
|
||||
}
|
||||
|
||||
if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['privileges:downvote'], 10)) {
|
||||
if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['min:rep:downvote'], 10)) {
|
||||
return callback(new Error('[[error:not-enough-reputation-to-downvote]]'));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ privileges.privilegeLabels = [
|
||||
{ name: 'Tag Topics' },
|
||||
{ name: 'Edit Posts' },
|
||||
{ name: 'Delete Posts' },
|
||||
{ name: 'Upvote Posts' },
|
||||
{ name: 'Downvote Posts' },
|
||||
{ name: 'Delete Topics' },
|
||||
{ name: 'Upload Images' },
|
||||
{ name: 'Upload Files' },
|
||||
{ name: 'Purge' },
|
||||
{ name: 'Moderate' },
|
||||
];
|
||||
@@ -27,9 +27,9 @@ privileges.userPrivilegeList = [
|
||||
'topics:tag',
|
||||
'posts:edit',
|
||||
'posts:delete',
|
||||
'posts:upvote',
|
||||
'posts:downvote',
|
||||
'topics:delete',
|
||||
'upload:post:image',
|
||||
'upload:post:file',
|
||||
'purge',
|
||||
'moderate',
|
||||
];
|
||||
@@ -40,6 +40,7 @@ privileges.groupPrivilegeList = privileges.userPrivilegeList.map(function (privi
|
||||
|
||||
privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList);
|
||||
|
||||
require('./privileges/global')(privileges);
|
||||
require('./privileges/categories')(privileges);
|
||||
require('./privileges/topics')(privileges);
|
||||
require('./privileges/posts')(privileges);
|
||||
|
||||
@@ -15,121 +15,20 @@ module.exports = function (privileges) {
|
||||
|
||||
privileges.categories.list = function (cid, callback) {
|
||||
// Method used in admin/category controller to show all users/groups with privs in that given cid
|
||||
var privilegeLabels = privileges.privilegeLabels.slice();
|
||||
var userPrivilegeList = privileges.userPrivilegeList.slice();
|
||||
var groupPrivilegeList = privileges.groupPrivilegeList.slice();
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
labels: function (next) {
|
||||
async.parallel({
|
||||
users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privilegeLabels),
|
||||
groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privilegeLabels),
|
||||
users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privileges.privilegeLabels.slice()),
|
||||
groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privileges.privilegeLabels.slice()),
|
||||
}, next);
|
||||
},
|
||||
users: function (next) {
|
||||
var userPrivileges;
|
||||
var memberSets;
|
||||
async.waterfall([
|
||||
async.apply(plugins.fireHook, 'filter:privileges.list', userPrivilegeList),
|
||||
function (_privs, next) {
|
||||
userPrivileges = _privs;
|
||||
groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
|
||||
return 'cid:' + cid + ':privileges:' + privilege;
|
||||
}), next);
|
||||
},
|
||||
function (_memberSets, next) {
|
||||
memberSets = _memberSets.map(function (set) {
|
||||
return set.map(function (uid) {
|
||||
return parseInt(uid, 10);
|
||||
});
|
||||
});
|
||||
|
||||
var members = _.uniq(_.flatten(memberSets));
|
||||
|
||||
user.getUsersFields(members, ['picture', 'username'], next);
|
||||
},
|
||||
function (memberData, next) {
|
||||
memberData.forEach(function (member) {
|
||||
member.privileges = {};
|
||||
for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
|
||||
member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
|
||||
}
|
||||
});
|
||||
|
||||
next(null, memberData);
|
||||
},
|
||||
], next);
|
||||
helpers.getUserPrivileges(cid, 'filter:privileges.list', privileges.userPrivilegeList, next);
|
||||
},
|
||||
groups: function (next) {
|
||||
var groupPrivileges;
|
||||
async.waterfall([
|
||||
async.apply(plugins.fireHook, 'filter:privileges.groups.list', groupPrivilegeList),
|
||||
function (_privs, next) {
|
||||
groupPrivileges = _privs;
|
||||
async.parallel({
|
||||
memberSets: function (next) {
|
||||
groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
|
||||
return 'cid:' + cid + ':privileges:' + privilege;
|
||||
}), next);
|
||||
},
|
||||
groupNames: function (next) {
|
||||
groups.getGroups('groups:createtime', 0, -1, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var memberSets = results.memberSets;
|
||||
var uniqueGroups = _.uniq(_.flatten(memberSets));
|
||||
|
||||
var groupNames = results.groupNames.filter(function (groupName) {
|
||||
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
|
||||
});
|
||||
|
||||
groupNames = groups.ephemeralGroups.concat(groupNames);
|
||||
var registeredUsersIndex = groupNames.indexOf('registered-users');
|
||||
if (registeredUsersIndex !== -1) {
|
||||
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
|
||||
} else {
|
||||
groupNames = ['registered-users'].concat(groupNames);
|
||||
}
|
||||
|
||||
var adminIndex = groupNames.indexOf('administrators');
|
||||
if (adminIndex !== -1) {
|
||||
groupNames.splice(adminIndex, 1);
|
||||
}
|
||||
|
||||
var memberPrivs;
|
||||
|
||||
var memberData = groupNames.map(function (member) {
|
||||
memberPrivs = {};
|
||||
|
||||
for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
|
||||
memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
|
||||
}
|
||||
return {
|
||||
name: member,
|
||||
privileges: memberPrivs,
|
||||
};
|
||||
});
|
||||
|
||||
next(null, memberData);
|
||||
},
|
||||
function (memberData, next) {
|
||||
// Grab privacy info for the groups as well
|
||||
async.map(memberData, function (member, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.isPrivate(member.name, next);
|
||||
},
|
||||
function (isPrivate, next) {
|
||||
member.isPrivate = isPrivate;
|
||||
next(null, member);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], next);
|
||||
helpers.getGroupPrivileges(cid, 'filter:privileges.groups.list', privileges.groupPrivilegeList, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
@@ -299,19 +198,13 @@ module.exports = function (privileges) {
|
||||
};
|
||||
|
||||
privileges.categories.give = function (privileges, cid, groupName, callback) {
|
||||
giveOrRescind(groups.join, privileges, cid, groupName, callback);
|
||||
helpers.giveOrRescind(groups.join, privileges, cid, groupName, callback);
|
||||
};
|
||||
|
||||
privileges.categories.rescind = function (privileges, cid, groupName, callback) {
|
||||
giveOrRescind(groups.leave, privileges, cid, groupName, callback);
|
||||
helpers.giveOrRescind(groups.leave, privileges, cid, groupName, callback);
|
||||
};
|
||||
|
||||
function giveOrRescind(method, privileges, cid, groupName, callback) {
|
||||
async.eachSeries(privileges, function (privilege, next) {
|
||||
method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
|
||||
}, callback);
|
||||
}
|
||||
|
||||
privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
|
||||
128
src/privileges/global.js
Normal file
128
src/privileges/global.js
Normal file
@@ -0,0 +1,128 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
|
||||
var user = require('../user');
|
||||
var groups = require('../groups');
|
||||
var helpers = require('./helpers');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
module.exports = function (privileges) {
|
||||
privileges.global = {};
|
||||
|
||||
privileges.global.privilegeLabels = [
|
||||
{ name: 'Chat' },
|
||||
{ name: 'Upload Images' },
|
||||
{ name: 'Upload Files' },
|
||||
];
|
||||
|
||||
privileges.global.userPrivilegeList = [
|
||||
'chat',
|
||||
'upload:post:image',
|
||||
'upload:post:file',
|
||||
];
|
||||
|
||||
privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) {
|
||||
return 'groups:' + privilege;
|
||||
});
|
||||
|
||||
privileges.global.list = function (callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
labels: function (next) {
|
||||
async.parallel({
|
||||
users: async.apply(plugins.fireHook, 'filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()),
|
||||
groups: async.apply(plugins.fireHook, 'filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()),
|
||||
}, next);
|
||||
},
|
||||
users: function (next) {
|
||||
helpers.getUserPrivileges(0, 'filter:privileges.global.list', privileges.global.userPrivilegeList, next);
|
||||
},
|
||||
groups: function (next) {
|
||||
helpers.getGroupPrivileges(0, 'filter:privileges.global.groups.list', privileges.global.groupPrivilegeList, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (payload, next) {
|
||||
// This is a hack because I can't do {labels.users.length} to echo the count in templates.js
|
||||
payload.columnCount = payload.labels.users.length + 2;
|
||||
next(null, payload);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
privileges.global.get = function (uid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
privileges: function (next) {
|
||||
helpers.isUserAllowedTo(privileges.global.userPrivilegeList, uid, 0, next);
|
||||
},
|
||||
isAdministrator: function (next) {
|
||||
user.isAdministrator(uid, next);
|
||||
},
|
||||
isGlobalModerator: function (next) {
|
||||
user.isGlobalModerator(uid, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var privData = _.zipObject(privileges.global.userPrivilegeList, results.privileges);
|
||||
var isAdminOrMod = results.isAdministrator || results.isGlobalModerator;
|
||||
|
||||
plugins.fireHook('filter:privileges.global.get', {
|
||||
chat: privData.chat || isAdminOrMod,
|
||||
'upload:post:image': privData['upload:post:image'] || isAdminOrMod,
|
||||
'upload:post:file': privData['upload:post:file'] || isAdminOrMod,
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
privileges.global.can = function (privilege, uid, callback) {
|
||||
helpers.some([
|
||||
function (next) {
|
||||
helpers.isUserAllowedTo(privilege, uid, [0], function (err, results) {
|
||||
next(err, Array.isArray(results) && results.length ? results[0] : false);
|
||||
});
|
||||
},
|
||||
function (next) {
|
||||
user.isGlobalModerator(uid, next);
|
||||
},
|
||||
function (next) {
|
||||
user.isAdministrator(uid, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
privileges.global.give = function (privileges, groupName, callback) {
|
||||
helpers.giveOrRescind(groups.join, privileges, 0, groupName, callback);
|
||||
};
|
||||
|
||||
privileges.global.rescind = function (privileges, groupName, callback) {
|
||||
helpers.giveOrRescind(groups.leave, privileges, 0, groupName, callback);
|
||||
};
|
||||
|
||||
privileges.global.userPrivileges = function (uid, callback) {
|
||||
var tasks = {};
|
||||
|
||||
privileges.global.userPrivilegeList.forEach(function (privilege) {
|
||||
tasks[privilege] = async.apply(groups.isMember, uid, 'cid:0:privileges:' + privilege);
|
||||
});
|
||||
|
||||
async.parallel(tasks, callback);
|
||||
};
|
||||
|
||||
privileges.global.groupPrivileges = function (groupName, callback) {
|
||||
var tasks = {};
|
||||
|
||||
privileges.global.groupPrivilegeList.forEach(function (privilege) {
|
||||
tasks[privilege] = async.apply(groups.isMember, groupName, 'cid:0:privileges:' + privilege);
|
||||
});
|
||||
|
||||
async.parallel(tasks, callback);
|
||||
};
|
||||
};
|
||||
@@ -2,7 +2,11 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
|
||||
var groups = require('../groups');
|
||||
var user = require('../user');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
var helpers = module.exports;
|
||||
|
||||
@@ -111,3 +115,115 @@ function isGuestAllowedToPrivileges(privileges, cid, callback) {
|
||||
|
||||
groups.isMemberOfGroups('guests', groupKeys, callback);
|
||||
}
|
||||
|
||||
helpers.getUserPrivileges = function (cid, hookName, userPrivilegeList, callback) {
|
||||
var userPrivileges;
|
||||
var memberSets;
|
||||
async.waterfall([
|
||||
async.apply(plugins.fireHook, hookName, userPrivilegeList.slice()),
|
||||
function (_privs, next) {
|
||||
userPrivileges = _privs;
|
||||
groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
|
||||
return 'cid:' + cid + ':privileges:' + privilege;
|
||||
}), next);
|
||||
},
|
||||
function (_memberSets, next) {
|
||||
memberSets = _memberSets.map(function (set) {
|
||||
return set.map(function (uid) {
|
||||
return parseInt(uid, 10);
|
||||
});
|
||||
});
|
||||
|
||||
var members = _.uniq(_.flatten(memberSets));
|
||||
|
||||
user.getUsersFields(members, ['picture', 'username'], next);
|
||||
},
|
||||
function (memberData, next) {
|
||||
memberData.forEach(function (member) {
|
||||
member.privileges = {};
|
||||
for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
|
||||
member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
|
||||
}
|
||||
});
|
||||
|
||||
next(null, memberData);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
helpers.getGroupPrivileges = function (cid, hookName, groupPrivilegeList, callback) {
|
||||
var groupPrivileges;
|
||||
async.waterfall([
|
||||
async.apply(plugins.fireHook, hookName, groupPrivilegeList.slice()),
|
||||
function (_privs, next) {
|
||||
groupPrivileges = _privs;
|
||||
async.parallel({
|
||||
memberSets: function (next) {
|
||||
groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
|
||||
return 'cid:' + cid + ':privileges:' + privilege;
|
||||
}), next);
|
||||
},
|
||||
groupNames: function (next) {
|
||||
groups.getGroups('groups:createtime', 0, -1, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var memberSets = results.memberSets;
|
||||
var uniqueGroups = _.uniq(_.flatten(memberSets));
|
||||
|
||||
var groupNames = results.groupNames.filter(function (groupName) {
|
||||
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
|
||||
});
|
||||
|
||||
groupNames = groups.ephemeralGroups.concat(groupNames);
|
||||
var registeredUsersIndex = groupNames.indexOf('registered-users');
|
||||
if (registeredUsersIndex !== -1) {
|
||||
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
|
||||
} else {
|
||||
groupNames = ['registered-users'].concat(groupNames);
|
||||
}
|
||||
|
||||
var adminIndex = groupNames.indexOf('administrators');
|
||||
if (adminIndex !== -1) {
|
||||
groupNames.splice(adminIndex, 1);
|
||||
}
|
||||
|
||||
var memberPrivs;
|
||||
|
||||
var memberData = groupNames.map(function (member) {
|
||||
memberPrivs = {};
|
||||
|
||||
for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
|
||||
memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
|
||||
}
|
||||
return {
|
||||
name: member,
|
||||
privileges: memberPrivs,
|
||||
};
|
||||
});
|
||||
|
||||
next(null, memberData);
|
||||
},
|
||||
function (memberData, next) {
|
||||
// Grab privacy info for the groups as well
|
||||
async.map(memberData, function (member, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.isPrivate(member.name, next);
|
||||
},
|
||||
function (isPrivate, next) {
|
||||
member.isPrivate = isPrivate;
|
||||
next(null, member);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
helpers.giveOrRescind = function (method, privileges, cid, groupName, callback) {
|
||||
async.eachSeries(privileges, function (privilege, next) {
|
||||
method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
@@ -200,7 +200,7 @@ module.exports = function (privileges) {
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var minimumReputation = utils.isNumber(meta.config['privileges:flag']) ? parseInt(meta.config['privileges:flag'], 10) : 0;
|
||||
var minimumReputation = utils.isNumber(meta.config['min:rep:flag']) ? parseInt(meta.config['min:rep:flag'], 10) : 0;
|
||||
var canFlag = results.isAdminOrMod || parseInt(results.userReputation, 10) >= minimumReputation;
|
||||
next(null, { flag: canFlag });
|
||||
},
|
||||
|
||||
@@ -141,9 +141,13 @@ module.exports = function (privileges) {
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var canEdit = results.isAdmin || (results.isGlobalMod && !results.isTargetAdmin);
|
||||
|
||||
next(null, canEdit);
|
||||
results.canEdit = results.isAdmin || (results.isGlobalMod && !results.isTargetAdmin);
|
||||
results.callerUid = callerUid;
|
||||
results.uid = uid;
|
||||
plugins.fireHook('filter:user.canEdit', results, next);
|
||||
},
|
||||
function (data, next) {
|
||||
next(null, data.canEdit);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -55,6 +55,7 @@ function addRoutes(router, middleware, controllers) {
|
||||
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
|
||||
router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
|
||||
|
||||
router.get('/manage/privileges/:cid?', middlewares, controllers.admin.privileges.get);
|
||||
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
|
||||
router.get('/manage/post-queue', middlewares, controllers.admin.postQueue.get);
|
||||
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
|
||||
@@ -71,6 +72,8 @@ function addRoutes(router, middleware, controllers) {
|
||||
router.get('/manage/users/banned', middlewares, controllers.admin.users.banned);
|
||||
router.get('/manage/registration', middlewares, controllers.admin.users.registrationQueue);
|
||||
|
||||
router.get('/manage/admins-mods', middlewares, controllers.admin.adminsMods.get);
|
||||
|
||||
router.get('/manage/groups', middlewares, controllers.admin.groups.list);
|
||||
router.get('/manage/groups/:name', middlewares, controllers.admin.groups.get);
|
||||
|
||||
|
||||
@@ -83,7 +83,11 @@ Categories.setPrivilege = function (socket, data, callback) {
|
||||
};
|
||||
|
||||
Categories.getPrivilegeSettings = function (socket, cid, callback) {
|
||||
privileges.categories.list(cid, callback);
|
||||
if (!parseInt(cid, 10)) {
|
||||
privileges.global.list(callback);
|
||||
} else {
|
||||
privileges.categories.list(cid, callback);
|
||||
}
|
||||
};
|
||||
|
||||
Categories.copyPrivilegesToChildren = function (socket, cid, callback) {
|
||||
|
||||
@@ -13,11 +13,19 @@ Tags.create = function (socket, data, callback) {
|
||||
};
|
||||
|
||||
Tags.update = function (socket, data, callback) {
|
||||
if (!data) {
|
||||
if (!Array.isArray(data)) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
topics.updateTag(data.tag, data, callback);
|
||||
topics.updateTags(data, callback);
|
||||
};
|
||||
|
||||
Tags.rename = function (socket, data, callback) {
|
||||
if (!Array.isArray(data)) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
topics.renameTags(data, callback);
|
||||
};
|
||||
|
||||
Tags.deleteTags = function (socket, data, callback) {
|
||||
|
||||
@@ -11,6 +11,7 @@ var Messaging = require('../messaging');
|
||||
var utils = require('../utils');
|
||||
var server = require('./');
|
||||
var user = require('../user');
|
||||
var privileges = require('../privileges');
|
||||
|
||||
var SocketModules = module.exports;
|
||||
|
||||
@@ -73,6 +74,12 @@ SocketModules.chats.newRoom = function (socket, data, callback) {
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.global.can('chat', socket.uid, next);
|
||||
},
|
||||
function (canChat, next) {
|
||||
if (!canChat) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
Messaging.canMessageUser(socket.uid, data.touid, next);
|
||||
},
|
||||
function (next) {
|
||||
@@ -92,6 +99,13 @@ SocketModules.chats.send = function (socket, data, callback) {
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.global.can('chat', socket.uid, next);
|
||||
},
|
||||
function (canChat, next) {
|
||||
if (!canChat) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:messaging.send', {
|
||||
data: data,
|
||||
uid: socket.uid,
|
||||
@@ -133,6 +147,13 @@ SocketModules.chats.loadRoom = function (socket, data, callback) {
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.global.can('chat', socket.uid, next);
|
||||
},
|
||||
function (canChat, next) {
|
||||
if (!canChat) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
Messaging.isUserInRoom(socket.uid, data.roomId, next);
|
||||
},
|
||||
function (inRoom, next) {
|
||||
@@ -174,6 +195,13 @@ SocketModules.chats.addUserToRoom = function (socket, data, callback) {
|
||||
var uid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
privileges.global.can('chat', socket.uid, next);
|
||||
},
|
||||
function (canChat, next) {
|
||||
if (!canChat) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
Messaging.getUserCountInRoom(data.roomId, next);
|
||||
},
|
||||
function (userCount, next) {
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = function (SocketUser) {
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
isAdminOrSelfAndPasswordMatch(socket.uid, data, next);
|
||||
isPrivilegedOrSelfAndPasswordMatch(socket.uid, data, next);
|
||||
},
|
||||
function (next) {
|
||||
SocketUser.updateProfile(socket, data, next);
|
||||
@@ -29,7 +29,7 @@ module.exports = function (SocketUser) {
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.isAdminOrSelf(socket.uid, data.uid, next);
|
||||
user.isAdminOrGlobalModOrSelf(socket.uid, data.uid, next);
|
||||
},
|
||||
function (next) {
|
||||
user.updateCoverPicture(data, next);
|
||||
@@ -43,7 +43,7 @@ module.exports = function (SocketUser) {
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.isAdminOrSelf(socket.uid, data.uid, next);
|
||||
user.isAdminOrGlobalModOrSelf(socket.uid, data.uid, next);
|
||||
},
|
||||
function (next) {
|
||||
user.uploadCroppedPicture(data, next);
|
||||
@@ -58,7 +58,7 @@ module.exports = function (SocketUser) {
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
user.isAdminOrSelf(socket.uid, data.uid, next);
|
||||
user.isAdminOrGlobalModOrSelf(socket.uid, data.uid, next);
|
||||
},
|
||||
function (next) {
|
||||
user.removeCoverPicture(data, next);
|
||||
@@ -66,11 +66,13 @@ module.exports = function (SocketUser) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function isAdminOrSelfAndPasswordMatch(uid, data, callback) {
|
||||
function isPrivilegedOrSelfAndPasswordMatch(uid, data, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
isAdmin: async.apply(user.isAdministrator, uid),
|
||||
isTargetAdmin: async.apply(user.isAdministrator, data.uid),
|
||||
isGlobalMod: async.apply(user.isGlobalModerator, uid),
|
||||
hasPassword: async.apply(user.hasPassword, data.uid),
|
||||
passwordMatch: function (next) {
|
||||
if (data.password) {
|
||||
@@ -84,7 +86,11 @@ module.exports = function (SocketUser) {
|
||||
function (results, next) {
|
||||
var isSelf = parseInt(uid, 10) === parseInt(data.uid, 10);
|
||||
|
||||
if (!results.isAdmin && !isSelf) {
|
||||
if (results.isTargetAdmin && !results.isAdmin) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
if ((!results.isAdmin || !results.isGlobalMod) && !isSelf) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ Topics.getPageCount = function (tid, uid, callback) {
|
||||
user.getSettings(uid, next);
|
||||
},
|
||||
function (settings, next) {
|
||||
next(null, Math.ceil((parseInt(postCount, 10) - 1) / settings.postsPerPage));
|
||||
next(null, Math.ceil(parseInt(postCount, 10) / settings.postsPerPage));
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -64,6 +64,9 @@ module.exports = function (Topics) {
|
||||
'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids',
|
||||
], timestamp, topicData.tid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', 0, topicData.tid, next);
|
||||
},
|
||||
function (next) {
|
||||
categories.updateRecentTid(topicData.cid, topicData.tid, next);
|
||||
},
|
||||
|
||||
@@ -219,6 +219,7 @@ module.exports = function (Topics) {
|
||||
|
||||
notifications.create({
|
||||
type: 'new-reply',
|
||||
subject: title,
|
||||
bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
|
||||
bodyLong: postData.content,
|
||||
pid: postData.pid,
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var validator = require('validator');
|
||||
|
||||
var db = require('../database');
|
||||
var meta = require('../meta');
|
||||
var _ = require('lodash');
|
||||
var plugins = require('../plugins');
|
||||
var utils = require('../utils');
|
||||
|
||||
var batch = require('../batch');
|
||||
|
||||
module.exports = function (Topics) {
|
||||
Topics.createTags = function (tags, tid, timestamp, callback) {
|
||||
@@ -95,13 +96,61 @@ module.exports = function (Topics) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
Topics.updateTag = function (tag, data, callback) {
|
||||
if (!tag) {
|
||||
return setImmediate(callback, new Error('[[error:invalid-tag]]'));
|
||||
}
|
||||
db.setObject('tag:' + tag, data, callback);
|
||||
Topics.updateTags = function (data, callback) {
|
||||
async.eachSeries(data, function (tagData, next) {
|
||||
db.setObject('tag:' + tagData.value, {
|
||||
color: tagData.color,
|
||||
bgColor: tagData.bgColor,
|
||||
}, next);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
Topics.renameTags = function (data, callback) {
|
||||
async.eachSeries(data, function (tagData, next) {
|
||||
renameTag(tagData.value, tagData.newName, next);
|
||||
}, callback);
|
||||
};
|
||||
|
||||
function renameTag(tag, newTagName, callback) {
|
||||
if (!newTagName || tag === newTagName) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Topics.createEmptyTag(newTagName, next);
|
||||
},
|
||||
function (next) {
|
||||
batch.processSortedSet('tag:' + tag + ':topics', function (tids, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.sortedSetScores('tag:' + tag + ':topics', tids, next);
|
||||
},
|
||||
function (scores, next) {
|
||||
db.sortedSetAdd('tag:' + newTagName + ':topics', scores, tids, next);
|
||||
},
|
||||
function (next) {
|
||||
var keys = tids.map(function (tid) {
|
||||
return 'topic:' + tid + ':tags';
|
||||
});
|
||||
|
||||
async.series([
|
||||
async.apply(db.sortedSetRemove, 'tag:' + tag + ':topics', tids),
|
||||
async.apply(db.setsRemove, keys, tag),
|
||||
async.apply(db.setsAdd, keys, newTagName),
|
||||
], next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
Topics.deleteTag(tag, next);
|
||||
},
|
||||
function (next) {
|
||||
updateTagCount(newTagName, next);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function updateTagCount(tag, callback) {
|
||||
callback = callback || function () {};
|
||||
async.waterfall([
|
||||
@@ -147,7 +196,9 @@ module.exports = function (Topics) {
|
||||
return 'tag:' + tag;
|
||||
}), next);
|
||||
},
|
||||
], callback);
|
||||
], function (err) {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
function removeTagsFromTopics(tags, callback) {
|
||||
@@ -191,6 +242,7 @@ module.exports = function (Topics) {
|
||||
},
|
||||
function (tagData, next) {
|
||||
tags.forEach(function (tag, index) {
|
||||
tag.valueEscaped = validator.escape(String(tag.value));
|
||||
tag.color = tagData[index] ? tagData[index].color : '';
|
||||
tag.bgColor = tagData[index] ? tagData[index].bgColor : '';
|
||||
});
|
||||
@@ -264,7 +316,7 @@ module.exports = function (Topics) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
Topics.updateTags = function (tid, tags, callback) {
|
||||
Topics.updateTopicTags = function (tid, tags, callback) {
|
||||
callback = callback || function () {};
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
|
||||
@@ -250,29 +250,35 @@ module.exports = function (Topics) {
|
||||
var topic;
|
||||
var oldCid;
|
||||
var cid = data.cid;
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Topics.exists(tid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
if (!exists) {
|
||||
return next(new Error('[[error:no-topic]]'));
|
||||
}
|
||||
Topics.getTopicFields(tid, ['cid', 'lastposttime', 'pinned', 'deleted', 'postcount'], next);
|
||||
Topics.getTopicData(tid, next);
|
||||
},
|
||||
function (topicData, next) {
|
||||
topic = topicData;
|
||||
if (!topic) {
|
||||
return next(new Error('[[error:no-topic]]'));
|
||||
}
|
||||
if (parseInt(cid, 10) === parseInt(topic.cid, 10)) {
|
||||
return next(new Error('[[error:cant-move-topic-to-same-category]]'));
|
||||
}
|
||||
db.sortedSetsRemove([
|
||||
'cid:' + topicData.cid + ':tids',
|
||||
'cid:' + topicData.cid + ':tids:pinned',
|
||||
'cid:' + topicData.cid + ':tids:posts',
|
||||
'cid:' + topicData.cid + ':tids:votes',
|
||||
'cid:' + topicData.cid + ':tids:lastposttime',
|
||||
'cid:' + topicData.cid + ':recent_tids',
|
||||
'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids',
|
||||
], tid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + cid + ':tids:lastposttime', topic.lastposttime, tid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + cid + ':uid:' + topic.uid + ':tids', topic.timestamp, tid, next);
|
||||
},
|
||||
function (next) {
|
||||
if (parseInt(topic.pinned, 10)) {
|
||||
db.sortedSetAdd('cid:' + cid + ':tids:pinned', Date.now(), tid, next);
|
||||
@@ -285,6 +291,10 @@ module.exports = function (Topics) {
|
||||
topic.postcount = topic.postcount || 0;
|
||||
db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next);
|
||||
},
|
||||
function (next) {
|
||||
var votes = (parseInt(topic.upvotes, 10) || 0) - (parseInt(topic.downvotes, 10) || 0);
|
||||
db.sortedSetAdd('cid:' + cid + ':tids:votes', votes, tid, next);
|
||||
},
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
|
||||
@@ -1,37 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
var db = require('../../database');
|
||||
var meta = require('../../meta');
|
||||
|
||||
module.exports = {
|
||||
name: 'Generate customHTML block from old customJS setting',
|
||||
timestamp: Date.UTC(2017, 9, 12),
|
||||
method: function (callback) {
|
||||
var newHTML = meta.config.customJS;
|
||||
var newJS = [];
|
||||
|
||||
// Forgive me for parsing HTML with regex...
|
||||
var scriptMatch = /^<script\s?(?!async|deferred)?>([\s\S]+?)<\/script>/m;
|
||||
var match = scriptMatch.exec(newHTML);
|
||||
|
||||
while (match) {
|
||||
if (match[1]) {
|
||||
// Append to newJS array
|
||||
newJS.push(match[1].trim());
|
||||
|
||||
// Remove the match from the existing value
|
||||
newHTML = ((match.index > 0 ? newHTML.slice(0, match.index) : '') + newHTML.slice(match.index + match[0].length)).trim();
|
||||
db.getObjectField('config', 'customJS', function (err, newHTML) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
match = scriptMatch.exec(newHTML);
|
||||
}
|
||||
var newJS = [];
|
||||
|
||||
// Combine newJS array
|
||||
newJS = newJS.join('\n\n');
|
||||
// Forgive me for parsing HTML with regex...
|
||||
var scriptMatch = /^<script\s?(?!async|deferred)?>([\s\S]+?)<\/script>/m;
|
||||
var match = scriptMatch.exec(newHTML);
|
||||
|
||||
// Write both values to config
|
||||
meta.configs.setMultiple({
|
||||
customHTML: newHTML,
|
||||
customJS: newJS,
|
||||
}, callback);
|
||||
while (match) {
|
||||
if (match[1]) {
|
||||
// Append to newJS array
|
||||
newJS.push(match[1].trim());
|
||||
|
||||
// Remove the match from the existing value
|
||||
newHTML = ((match.index > 0 ? newHTML.slice(0, match.index) : '') + newHTML.slice(match.index + match[0].length)).trim();
|
||||
}
|
||||
|
||||
match = scriptMatch.exec(newHTML);
|
||||
}
|
||||
|
||||
// Combine newJS array
|
||||
newJS = newJS.join('\n\n');
|
||||
|
||||
// Write both values to config
|
||||
meta.configs.setMultiple({
|
||||
customHTML: newHTML,
|
||||
customJS: newJS,
|
||||
}, callback);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,7 +48,7 @@ module.exports = {
|
||||
done = true;
|
||||
return next();
|
||||
}
|
||||
|
||||
delete item.expireAt;
|
||||
if (Object.keys(item).length === 3 && item.hasOwnProperty('_key') && item.hasOwnProperty('value')) {
|
||||
client.collection('objects').update({ _key: item._key }, { $rename: { value: 'data' } }, next);
|
||||
} else {
|
||||
|
||||
@@ -16,7 +16,7 @@ module.exports = {
|
||||
var topicData;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjectFields('topic:' + tid, ['mainPid', 'cid'], next);
|
||||
db.getObjectFields('topic:' + tid, ['mainPid', 'cid', 'pinned'], next);
|
||||
},
|
||||
function (_topicData, next) {
|
||||
topicData = _topicData;
|
||||
@@ -44,7 +44,11 @@ module.exports = {
|
||||
db.sortedSetAdd('topics:votes', votes, tid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', votes, tid, next);
|
||||
if (parseInt(topicData.pinned, 10) !== 1) {
|
||||
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', votes, tid, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
], function (err) {
|
||||
next(err);
|
||||
|
||||
52
src/upgrades/1.7.4/fix_user_topics_per_category.js
Normal file
52
src/upgrades/1.7.4/fix_user_topics_per_category.js
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var batch = require('../../batch');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Fix topics in categories per user if they were moved',
|
||||
timestamp: Date.UTC(2018, 0, 22),
|
||||
method: function (callback) {
|
||||
var progress = this.progress;
|
||||
|
||||
batch.processSortedSet('topics:tid', function (tids, next) {
|
||||
async.eachLimit(tids, 500, function (tid, _next) {
|
||||
progress.incr();
|
||||
var topicData;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjectFields('topic:' + tid, ['cid', 'tid', 'uid', 'oldCid', 'timestamp'], next);
|
||||
},
|
||||
function (_topicData, next) {
|
||||
topicData = _topicData;
|
||||
if (!topicData.cid || !topicData.oldCid) {
|
||||
return _next();
|
||||
}
|
||||
|
||||
db.isSortedSetMember('cid:' + topicData.oldCid + ':uid:' + topicData.uid, topicData.tid, next);
|
||||
},
|
||||
function (isMember, next) {
|
||||
if (isMember) {
|
||||
async.series([
|
||||
function (next) {
|
||||
db.sortedSetRemove('cid:' + topicData.oldCid + ':uid:' + topicData.uid + ':tids', tid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids', topicData.timestamp, tid, next);
|
||||
},
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
], _next);
|
||||
}, next);
|
||||
}, {
|
||||
progress: progress,
|
||||
batch: 500,
|
||||
}, callback);
|
||||
},
|
||||
};
|
||||
12
src/upgrades/1.8.0/chat_privilege.js
Normal file
12
src/upgrades/1.8.0/chat_privilege.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
var groups = require('../../groups');
|
||||
|
||||
module.exports = {
|
||||
name: 'Give chat privilege to registered-users',
|
||||
timestamp: Date.UTC(2017, 11, 18),
|
||||
method: function (callback) {
|
||||
groups.join('cid:0:privileges:groups:chat', 'registered-users', callback);
|
||||
},
|
||||
};
|
||||
53
src/upgrades/1.8.0/fix_moved_topics_byvotes.js
Normal file
53
src/upgrades/1.8.0/fix_moved_topics_byvotes.js
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var batch = require('../../batch');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Fix sort by votes for moved topics',
|
||||
timestamp: Date.UTC(2018, 0, 8),
|
||||
method: function (callback) {
|
||||
var progress = this.progress;
|
||||
|
||||
batch.processSortedSet('topics:tid', function (tids, next) {
|
||||
async.eachLimit(tids, 500, function (tid, _next) {
|
||||
progress.incr();
|
||||
var topicData;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getObjectFields('topic:' + tid, ['cid', 'oldCid', 'upvotes', 'downvotes', 'pinned'], next);
|
||||
},
|
||||
function (_topicData, next) {
|
||||
topicData = _topicData;
|
||||
if (!topicData.cid || !topicData.oldCid) {
|
||||
return _next();
|
||||
}
|
||||
|
||||
var upvotes = parseInt(topicData.upvotes, 10) || 0;
|
||||
var downvotes = parseInt(topicData.downvotes, 10) || 0;
|
||||
var votes = upvotes - downvotes;
|
||||
|
||||
async.series([
|
||||
function (next) {
|
||||
db.sortedSetRemove('cid:' + topicData.oldCid + ':tids:votes', tid, next);
|
||||
},
|
||||
function (next) {
|
||||
if (parseInt(topicData.pinned, 10) !== 1) {
|
||||
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', votes, tid, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
], _next);
|
||||
}, next);
|
||||
}, {
|
||||
progress: progress,
|
||||
batch: 500,
|
||||
}, callback);
|
||||
},
|
||||
};
|
||||
45
src/upgrades/1.8.0/global_upload_privilege.js
Normal file
45
src/upgrades/1.8.0/global_upload_privilege.js
Normal file
@@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
var async = require('async');
|
||||
var groups = require('../../groups');
|
||||
var privileges = require('../../privileges');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Give upload privilege to registered-users globally if it is given on a category',
|
||||
timestamp: Date.UTC(2018, 0, 3),
|
||||
method: function (callback) {
|
||||
db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
async.eachSeries(cids, function (cid, next) {
|
||||
getGroupPrivileges(cid, function (err, groupPrivileges) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var privs = [];
|
||||
if (groupPrivileges['groups:upload:post:image']) {
|
||||
privs.push('upload:post:image');
|
||||
}
|
||||
if (groupPrivileges['groups:upload:post:file']) {
|
||||
privs.push('upload:post:file');
|
||||
}
|
||||
privileges.global.give(privs, 'registered-users', next);
|
||||
});
|
||||
}, callback);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
function getGroupPrivileges(cid, callback) {
|
||||
var tasks = {};
|
||||
|
||||
['groups:upload:post:image', 'groups:upload:post:file'].forEach(function (privilege) {
|
||||
tasks[privilege] = async.apply(groups.isMember, 'registered-users', 'cid:' + cid + ':privileges:' + privilege);
|
||||
});
|
||||
|
||||
async.parallel(tasks, callback);
|
||||
}
|
||||
25
src/upgrades/1.8.0/rename_min_reputation_settings.js
Normal file
25
src/upgrades/1.8.0/rename_min_reputation_settings.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Rename privileges:downvote and privileges:flag to min:rep:downvote, min:rep:flag respectively',
|
||||
timestamp: Date.UTC(2018, 0, 12),
|
||||
method: function (callback) {
|
||||
db.getObjectFields('config', ['privileges:downvote', 'privileges:flag'], function (err, config) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
db.setObject('config', {
|
||||
'min:rep:downvote': parseInt(config['privileges:downvote'], 10) || 0,
|
||||
'min:rep:flag': parseInt(config['privileges:downvote'], 10) || 0,
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
db.deleteObjectFields('config', ['privileges:downvote', 'privileges:flag'], callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
22
src/upgrades/1.8.0/vote_privilege.js
Normal file
22
src/upgrades/1.8.0/vote_privilege.js
Normal file
@@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var privileges = require('../../privileges');
|
||||
var db = require('../../database');
|
||||
|
||||
module.exports = {
|
||||
name: 'Give vote privilege to registered-users on all categories',
|
||||
timestamp: Date.UTC(2018, 0, 9),
|
||||
method: function (callback) {
|
||||
db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
async.eachSeries(cids, function (cid, next) {
|
||||
privileges.categories.give(['posts:upvote', 'posts:downvote'], cid, 'registered-users', next);
|
||||
}, callback);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -90,8 +90,9 @@ Digest.getSubscribers = function (interval, callback) {
|
||||
};
|
||||
|
||||
Digest.send = function (data, callback) {
|
||||
var emailsSent = 0;
|
||||
if (!data || !data.subscribers || !data.subscribers.length) {
|
||||
return callback();
|
||||
return callback(null, emailsSent);
|
||||
}
|
||||
var now = new Date();
|
||||
|
||||
@@ -131,7 +132,7 @@ Digest.send = function (data, callback) {
|
||||
|
||||
return topicObj;
|
||||
});
|
||||
|
||||
emailsSent += 1;
|
||||
emailer.send('digest', userObj.uid, {
|
||||
subject: '[' + meta.config.title + '] [[email:digest.subject, ' + (now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate()) + ']]',
|
||||
username: userObj.username,
|
||||
@@ -151,6 +152,6 @@ Digest.send = function (data, callback) {
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
callback(err, data.subscribers.length);
|
||||
callback(err, emailsSent);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,14 +17,6 @@ module.exports = function (User) {
|
||||
var updateUid = data.uid;
|
||||
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([
|
||||
function (next) {
|
||||
plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next);
|
||||
@@ -33,13 +25,7 @@ module.exports = function (User) {
|
||||
fields = data.fields;
|
||||
data = data.data;
|
||||
|
||||
async.series([
|
||||
async.apply(isEmailAvailable, data, updateUid),
|
||||
async.apply(isUsernameAvailable, data, updateUid),
|
||||
async.apply(isGroupTitleValid, data),
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
validateData(uid, data, next);
|
||||
},
|
||||
function (next) {
|
||||
User.getUserFields(updateUid, fields, next);
|
||||
@@ -73,6 +59,19 @@ module.exports = function (User) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function validateData(callerUid, data, callback) {
|
||||
async.series([
|
||||
async.apply(isEmailAvailable, data, data.uid),
|
||||
async.apply(isUsernameAvailable, data, data.uid),
|
||||
async.apply(isGroupTitleValid, data),
|
||||
async.apply(isWebsiteValid, callerUid, data),
|
||||
async.apply(isAboutMeValid, callerUid, data),
|
||||
async.apply(isSignatureValid, callerUid, data),
|
||||
], function (err) {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
function isEmailAvailable(data, uid, callback) {
|
||||
if (!data.email) {
|
||||
return callback();
|
||||
@@ -141,6 +140,52 @@ module.exports = function (User) {
|
||||
}
|
||||
}
|
||||
|
||||
function isWebsiteValid(callerUid, data, callback) {
|
||||
if (!data.website) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
checkMinReputation(callerUid, data.uid, 'min:rep:website', callback);
|
||||
}
|
||||
|
||||
function isAboutMeValid(callerUid, data, callback) {
|
||||
if (!data.aboutme) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) {
|
||||
return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]'));
|
||||
}
|
||||
|
||||
checkMinReputation(callerUid, data.uid, 'min:rep:aboutme', callback);
|
||||
}
|
||||
|
||||
function isSignatureValid(callerUid, data, callback) {
|
||||
if (!data.signature) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) {
|
||||
return callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]'));
|
||||
}
|
||||
checkMinReputation(callerUid, data.uid, 'min:rep:signature', callback);
|
||||
}
|
||||
|
||||
function checkMinReputation(callerUid, uid, setting, callback) {
|
||||
var isSelf = parseInt(callerUid, 10) === parseInt(uid, 10);
|
||||
if (!isSelf) {
|
||||
return setImmediate(callback);
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.getUserField(uid, 'reputation', next);
|
||||
},
|
||||
function (reputation, next) {
|
||||
if (parseInt(reputation, 10) < (parseInt(meta.config[setting], 10) || 0)) {
|
||||
return next(new Error('[[error:not-enough-reputation-' + setting.replace(/:/g, '-') + ']]'));
|
||||
}
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function updateEmail(uid, newEmail, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
|
||||
@@ -119,7 +119,7 @@ UserReset.commit = function (code, password, callback) {
|
||||
user.hashPassword(password, next);
|
||||
},
|
||||
function (hash, next) {
|
||||
async.parallel([
|
||||
async.series([
|
||||
async.apply(user.setUserFields, uid, { password: hash, 'email:confirmed': 1 }),
|
||||
async.apply(db.deleteObjectField, 'reset:uid', code),
|
||||
async.apply(db.sortedSetRemove, 'reset:issueDate', code),
|
||||
@@ -128,7 +128,10 @@ UserReset.commit = function (code, password, callback) {
|
||||
async.apply(user.auth.resetLockout, uid),
|
||||
async.apply(db.delete, 'uid:' + uid + ':confirm:email:sent'),
|
||||
async.apply(db.sortedSetRemove, 'users:notvalidated', uid),
|
||||
], next);
|
||||
async.apply(UserReset.cleanByUid, uid),
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -81,6 +81,10 @@ module.exports = function (User) {
|
||||
}
|
||||
|
||||
function filterAndSortUids(uids, data, callback) {
|
||||
uids = uids.filter(function (uid) {
|
||||
return parseInt(uid, 10);
|
||||
});
|
||||
|
||||
var fields = [];
|
||||
|
||||
if (data.sortBy) {
|
||||
|
||||
@@ -101,13 +101,15 @@
|
||||
<div class="panel-heading">[[admin/general/dashboard:control-panel]]</div>
|
||||
<div class="panel-body text-center">
|
||||
<p>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-warning reload">[[admin/general/dashboard:reload]]</button>
|
||||
<button class="btn btn-danger restart">[[admin/general/dashboard:restart]]</button>
|
||||
</div>
|
||||
<button class="btn btn-block btn-warning reload"<!-- IF !canRestart --> disabled<!-- END -->>[[admin/general/dashboard:reload]]</button>
|
||||
<button class="btn btn-block btn-danger restart"<!-- IF !canRestart --> disabled<!-- END -->>[[admin/general/dashboard:restart]]</button>
|
||||
</p>
|
||||
<p class="help-block">
|
||||
<p class="<!-- IF canRestart -->help-block<!-- ELSE -->alert alert-warning<!-- END -->">
|
||||
<!-- IF canRestart -->
|
||||
[[admin/general/dashboard:restart-warning]]
|
||||
<!-- ELSE -->
|
||||
[[admin/general/dashboard:restart-disabled]]
|
||||
<!-- END -->
|
||||
</p>
|
||||
<p>
|
||||
<a href="{config.relative_path}/admin/settings/advanced" class="btn btn-info btn-block" data-placement="bottom" data-toggle="tooltip" title="[[admin/general/dashboard:maintenance-mode-title]]">[[admin/general/dashboard:maintenance-mode]]</a>
|
||||
|
||||
64
src/views/admin/manage/admins-mods.tpl
Normal file
64
src/views/admin/manage/admins-mods.tpl
Normal file
@@ -0,0 +1,64 @@
|
||||
<div class="admins-mods">
|
||||
<h4><!-- IF admins.icon --><i class="fa {admins.icon}"></i> <!-- ENDIF admins.icon -->[[admin/manage/admins-mods:administrators]]</h4>
|
||||
<div class="administrator-area">
|
||||
<!-- BEGIN admins.members -->
|
||||
<div class="user-card pull-left" data-uid="{admins.members.uid}">
|
||||
<!-- IF admins.members.picture -->
|
||||
<img class="avatar avatar-sm" src="{admins.members.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {admins.members.icon:bgColor};">{admins.members.icon:text}</div>
|
||||
<!-- ENDIF admins.members.picture -->
|
||||
<a href="{config.relative_path}/user/{admins.members.userslug}">{admins.members.username}</a>
|
||||
<i class="remove-user-icon fa fa-times" role="button"></i>
|
||||
</div>
|
||||
<!-- END admins.members -->
|
||||
</div>
|
||||
<input id="admin-search" class="form-control" placeholder="[[admin/manage/admins-mods:add-administrator]]" />
|
||||
|
||||
<br/>
|
||||
|
||||
<h4><!-- IF globalMods.icon --><i class="fa {globalMods.icon}"></i> <!-- ENDIF globalMods.icon -->[[admin/manage/admins-mods:global-moderators]]</h4>
|
||||
<div class="global-moderator-area">
|
||||
<!-- BEGIN globalMods.members -->
|
||||
<div class="user-card pull-left" data-uid="{globalMods.members.uid}">
|
||||
<!-- IF globalMods.members.picture -->
|
||||
<img class="avatar avatar-sm" src="{globalMods.members.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {globalMods.members.icon:bgColor};">{globalMods.members.icon:text}</div>
|
||||
<!-- ENDIF globalMods.members.picture -->
|
||||
<a href="{config.relative_path}/user/{globalMods.members.userslug}">{globalMods.members.username}</a>
|
||||
<i class="remove-user-icon fa fa-times" role="button"></i>
|
||||
</div>
|
||||
<!-- END globalMods.members -->
|
||||
</div>
|
||||
|
||||
<div id="no-global-mods-warning" class="<!-- IF globalMods.members.length -->hidden<!-- ENDIF globalMods.members.length -->">[[admin/manage/admins-mods:no-global-moderators]]</div>
|
||||
|
||||
<input id="global-mod-search" class="form-control" placeholder="[[admin/manage/admins-mods:add-global-moderator]]" />
|
||||
|
||||
<br/>
|
||||
|
||||
<!-- BEGIN categories -->
|
||||
<div class="categories category-wrapper category-depth-{categories.depth}">
|
||||
<h4><!-- IF categories.icon --><i class="fa {categories.icon}"></i> <!-- ENDIF categories.icon -->[[admin/manage/admins-mods:moderators-of-category, {categories.name}]]</h4>
|
||||
<div class="moderator-area" data-cid="{categories.cid}">
|
||||
<!-- BEGIN categories.moderators -->
|
||||
<div class="user-card pull-left" data-uid="{categories.moderators.uid}">
|
||||
<!-- IF categories.moderators.picture -->
|
||||
<img class="avatar avatar-sm" src="{categories.moderators.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {categories.moderators.icon:bgColor};">{categories.moderators.icon:text}</div>
|
||||
<!-- ENDIF categories.moderators.picture -->
|
||||
<a href="{config.relative_path}/user/{categories.moderators.userslug}">{categories.moderators.username}</a>
|
||||
<i class="remove-user-icon fa fa-times" role="button"></i>
|
||||
</div>
|
||||
<!-- END categories.moderators -->
|
||||
</div>
|
||||
|
||||
<div data-cid="{categories.cid}" class="no-moderator-warning <!-- IF categories.moderators.length -->hidden<!-- ENDIF categories.moderators.length -->">[[admin/manage/admins-mods:no-moderators]]</div>
|
||||
|
||||
<input data-cid="{categories.cid}" class="form-control moderator-search" placeholder="[[admin/manage/admins-mods:add-moderator]]" />
|
||||
</div>
|
||||
<br/>
|
||||
<!-- END categories -->
|
||||
</div>
|
||||
@@ -2,15 +2,7 @@
|
||||
|
||||
<form role="form" class="category" data-cid="{category.cid}">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="active"><a href="#category-settings" data-toggle="tab">
|
||||
[[admin/manage/categories:settings]]
|
||||
</a></li>
|
||||
<li><a href="#privileges" data-toggle="tab">[[admin/manage/categories:privileges]]</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="col-md-3 pull-right">
|
||||
<select id="category-selector" class="form-control">
|
||||
<!-- BEGIN allCategories -->
|
||||
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
|
||||
@@ -18,7 +10,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="tab-content">
|
||||
@@ -174,19 +166,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade col-xs-12" id="privileges">
|
||||
<p>
|
||||
[[admin/manage/categories:privileges.description]]
|
||||
</p>
|
||||
<p class="text-warning">
|
||||
[[admin/manage/categories:privileges.warning]]
|
||||
</p>
|
||||
<hr />
|
||||
<div class="privilege-table-container">
|
||||
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
31
src/views/admin/manage/privileges.tpl
Normal file
31
src/views/admin/manage/privileges.tpl
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="row">
|
||||
<form role="form" class="category">
|
||||
<div class="row">
|
||||
<div class="col-md-3 pull-right">
|
||||
<select id="category-selector" class="form-control">
|
||||
<option value="global" <!-- IF !cid --> selected <!-- ENDIF !cid -->>[[admin/manage/privileges:global]]</option>
|
||||
<option disabled>_____________</option>
|
||||
<!-- BEGIN allCategories -->
|
||||
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
|
||||
<!-- END allCategories -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="">
|
||||
<p>
|
||||
[[admin/manage/categories:privileges.description]]
|
||||
</p>
|
||||
<hr />
|
||||
<div class="privilege-table-container">
|
||||
<!-- IF cid -->
|
||||
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
||||
<!-- ELSE -->
|
||||
<!-- IMPORT admin/partials/global/privileges.tpl -->
|
||||
<!-- ENDIF cid -->
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -41,6 +41,7 @@
|
||||
<p>[[admin/manage/tags:description]]</p>
|
||||
<button class="btn btn-primary btn-block" id="create">[[admin/manage/tags:create]]</button>
|
||||
<button class="btn btn-primary btn-block" id="modify">[[admin/manage/tags:modify]]</button>
|
||||
<button class="btn btn-primary btn-block" id="rename">[[admin/manage/tags:rename]]</button>
|
||||
<button class="btn btn-warning btn-block" id="deleteSelected">[[admin/manage/tags:delete]]</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,4 +75,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rename-modal hidden">
|
||||
<div class="form-group">
|
||||
<label for="value">[[admin/manage/tags:name]]</label>
|
||||
<input id="value" data-name="value" value="{tags.value}" class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
<div class="btn-group pull-right">
|
||||
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[admin/manage/users:edit]] <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" class="admin-user"><i class="fa fa-fw fa-shield"></i> [[admin/manage/users:make-admin]]</a></li>
|
||||
<li><a href="#" class="remove-admin-user"><i class="fa fa-fw fa-ban"></i> [[admin/manage/users:remove-admin]]</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" class="validate-email"><i class="fa fa-fw fa-check"></i> [[admin/manage/users:validate-email]]</a></li>
|
||||
<li><a href="#" class="send-validation-email"><i class="fa fa-fw fa-mail-forward"></i> [[admin/manage/users:send-validation-email]]</a></li>
|
||||
<li><a href="#" class="password-reset-email"><i class="fa fa-fw fa-key"></i> [[admin/manage/users:password-reset-email]]</a></li>
|
||||
@@ -50,7 +47,7 @@
|
||||
|
||||
<div class="search {search_display}">
|
||||
<label>[[admin/manage/users:search.uid]]</label>
|
||||
<input class="form-control" id="search-user-uid" data-search-type="uid" type="text" placeholder="[[admin/manage/users:search.uid-placeholder]]"/><br />
|
||||
<input class="form-control" id="search-user-uid" data-search-type="uid" type="number" placeholder="[[admin/manage/users:search.uid-placeholder]]"/><br />
|
||||
|
||||
<label>[[admin/manage/users:search.username]]</label>
|
||||
<input class="form-control" id="search-user-name" data-search-type="username" type="text" placeholder="[[admin/manage/users:search.username-placeholder]]"/><br />
|
||||
|
||||
86
src/views/admin/partials/global/privileges.tpl
Normal file
86
src/views/admin/partials/global/privileges.tpl
Normal file
@@ -0,0 +1,86 @@
|
||||
<table class="table table-striped privilege-table">
|
||||
<thead>
|
||||
<tr class="privilege-table-header">
|
||||
<th colspan="3"></th>
|
||||
</tr><tr><!-- zebrastripe reset --></tr>
|
||||
<tr>
|
||||
<th colspan="2">[[admin/manage/categories:privileges.section-user]]</th>
|
||||
<!-- BEGIN privileges.labels.users -->
|
||||
<th class="text-center">{privileges.labels.users.name}</th>
|
||||
<!-- END privileges.labels.users -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- IF privileges.users.length -->
|
||||
<!-- BEGIN privileges.users -->
|
||||
<tr data-uid="{privileges.users.uid}">
|
||||
<td>
|
||||
<!-- IF ../picture -->
|
||||
<img class="avatar avatar-sm" src="{privileges.users.picture}" title="{privileges.users.username}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {../icon:bgColor};">{../icon:text}</div>
|
||||
<!-- ENDIF ../picture -->
|
||||
</td>
|
||||
<td>{privileges.users.username}</td>
|
||||
{function.spawnPrivilegeStates, privileges.users.username, ../privileges}
|
||||
</tr>
|
||||
<!-- END privileges.users -->
|
||||
<tr>
|
||||
<td colspan="{privileges.columnCount}">
|
||||
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
|
||||
[[admin/manage/categories:privileges.search-user]]
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ELSE -->
|
||||
<tr>
|
||||
<td colspan="{privileges.columnCount}">
|
||||
[[admin/manage/privileges:global.no-users]]
|
||||
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
|
||||
[[admin/manage/categories:privileges.search-user]]
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- ENDIF privileges.users.length -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="table table-striped privilege-table">
|
||||
<thead>
|
||||
<tr class="privilege-table-header">
|
||||
<th colspan="3"></th>
|
||||
</tr><tr><!-- zebrastripe reset --></tr>
|
||||
<tr>
|
||||
<th colspan="2">[[admin/manage/categories:privileges.section-group]]</th>
|
||||
<!-- BEGIN privileges.labels.groups -->
|
||||
<th class="text-center">{privileges.labels.groups.name}</th>
|
||||
<!-- END privileges.labels.groups -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- BEGIN privileges.groups -->
|
||||
<tr data-group-name="{privileges.groups.name}" data-private="<!-- IF privileges.groups.isPrivate -->1<!-- ELSE -->0<!-- ENDIF privileges.groups.isPrivate -->">
|
||||
<td>
|
||||
<!-- IF privileges.groups.isPrivate -->
|
||||
<i class="fa fa-lock text-muted" title="[[admin/manage/categories:privileges.group-private]]"></i>
|
||||
<!-- ENDIF privileges.groups.isPrivate -->
|
||||
{privileges.groups.name}
|
||||
</td>
|
||||
<td></td>
|
||||
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
|
||||
</tr>
|
||||
<!-- END privileges.groups -->
|
||||
<tr>
|
||||
<td colspan="{privileges.columnCount}">
|
||||
<div class="btn-toolbar">
|
||||
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.group">
|
||||
[[admin/manage/categories:privileges.search-group]]
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="help-block">
|
||||
[[admin/manage/categories:privileges.inherit]]
|
||||
</div>
|
||||
@@ -15,7 +15,9 @@
|
||||
<h3 class="menu-section-title">[[admin/menu:section-manage]]</h3>
|
||||
<ul class="menu-section-list">
|
||||
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/admins-mods">[[admin/menu:manage/admins-mods]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
||||
@@ -188,7 +190,9 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">[[admin/menu:section-manage]]</a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/admins-mods">[[admin/menu:manage/admins-mods]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
|
||||
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/reputation:thresholds]]</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<form>
|
||||
<strong>[[admin/settings/reputation:min-rep-downvote]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="privileges:downvote"><br />
|
||||
<strong>[[admin/settings/reputation:min-rep-flag]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="privileges:flag"><br />
|
||||
<strong>[[admin/settings/reputation:min-rep-downvote]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:downvote"><br />
|
||||
<strong>[[admin/settings/reputation:min-rep-flag]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:flag"><br />
|
||||
<strong>[[admin/settings/reputation:min-rep-website]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:website"><br />
|
||||
<strong>[[admin/settings/reputation:min-rep-aboutme]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:aboutme"><br />
|
||||
<strong>[[admin/settings/reputation:min-rep-signature]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:signature"><br />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user