Files
NodeBB/src/categories/create.js
Barış Soner Uşaklı ec5582b53c feat: add privilege give/rescind hooks (#8336)
* feat: add privilege give/rescind hooks

action:privileges.categories.give/rescind
action:privileges.global.give/rescind

breaking change, privileges.categories.give/rescind and privileges.global.give/rescind use full privilege name for groups ie `groups:find` instead of `find`

* fix: tests, privileges renamed
2020-05-26 21:57:38 -04:00

207 lines
7.3 KiB
JavaScript

'use strict';
const async = require('async');
const _ = require('lodash');
const db = require('../database');
const plugins = require('../plugins');
const privileges = require('../privileges');
const utils = require('../utils');
const cache = require('../cache');
module.exports = function (Categories) {
Categories.create = async function (data) {
const parentCid = data.parentCid ? data.parentCid : 0;
const cid = await db.incrObjectField('global', 'nextCid');
data.name = data.name || 'Category ' + cid;
const slug = cid + '/' + utils.slugify(data.name);
const order = data.order || cid; // If no order provided, place it at the end
const colours = Categories.assignColours();
let category = {
cid: cid,
name: data.name,
description: data.description ? data.description : '',
descriptionParsed: data.descriptionParsed ? data.descriptionParsed : '',
icon: data.icon ? data.icon : '',
bgColor: data.bgColor || colours[0],
color: data.color || colours[1],
slug: slug,
parentCid: parentCid,
topic_count: 0,
post_count: 0,
disabled: data.disabled ? 1 : 0,
order: order,
link: data.link || '',
numRecentReplies: 1,
class: (data.class ? data.class : 'col-md-3 col-xs-6'),
imageClass: 'cover',
isSection: 0,
};
if (data.backgroundImage) {
category.backgroundImage = data.backgroundImage;
}
const result = await plugins.fireHook('filter:category.create', { category: category, data: data });
category = result.category;
await db.setObject('category:' + category.cid, category);
if (!category.descriptionParsed) {
await Categories.parseDescription(category.cid, category.description);
}
await db.sortedSetsAdd(['categories:cid', 'cid:' + parentCid + ':children'], category.order, category.cid);
const defaultPrivileges = [
'groups:find',
'groups:read',
'groups:topics:read',
'groups:topics:create',
'groups:topics:reply',
'groups:topics:tag',
'groups:posts:edit',
'groups:posts:history',
'groups:posts:delete',
'groups:posts:upvote',
'groups:posts:downvote',
'groups:topics:delete',
];
const modPrivileges = defaultPrivileges.concat([
'groups:posts:view_deleted',
'groups:purge',
]);
await privileges.categories.give(defaultPrivileges, category.cid, 'registered-users');
await privileges.categories.give(modPrivileges, category.cid, ['administrators', 'Global Moderators']);
await privileges.categories.give(['groups:find', 'groups:read', 'groups:topics:read'], category.cid, ['guests', 'spiders']);
cache.del(['categories:cid', 'cid:' + parentCid + ':children']);
if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
category = await Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid);
}
if (data.cloneChildren) {
await duplicateCategoriesChildren(category.cid, data.cloneFromCid, data.uid);
}
plugins.fireHook('action:category.create', { category: category });
return category;
};
async function duplicateCategoriesChildren(parentCid, cid, uid) {
let children = await Categories.getChildren([cid], uid);
if (!children.length) {
return;
}
children = children[0];
children.forEach(function (child) {
child.parentCid = parentCid;
child.cloneFromCid = child.cid;
child.cloneChildren = true;
child.name = utils.decodeHTMLEntities(child.name);
child.description = utils.decodeHTMLEntities(child.description);
child.uid = uid;
});
await async.each(children, Categories.create);
}
Categories.assignColours = function () {
const backgrounds = ['#AB4642', '#DC9656', '#F7CA88', '#A1B56C', '#86C1B9', '#7CAFC2', '#BA8BAF', '#A16946'];
const text = ['#fff', '#fff', '#333', '#fff', '#333', '#fff', '#fff', '#fff'];
const index = Math.floor(Math.random() * backgrounds.length);
return [backgrounds[index], text[index]];
};
Categories.copySettingsFrom = async function (fromCid, toCid, copyParent) {
const [source, destination] = await Promise.all([
db.getObject('category:' + fromCid),
db.getObject('category:' + toCid),
]);
if (!source) {
throw new Error('[[error:invalid-cid]]');
}
const oldParent = parseInt(destination.parentCid, 10) || 0;
const newParent = parseInt(source.parentCid, 10) || 0;
if (copyParent && newParent !== parseInt(toCid, 10)) {
await db.sortedSetRemove('cid:' + oldParent + ':children', toCid);
await db.sortedSetAdd('cid:' + newParent + ':children', source.order, toCid);
cache.del(['cid:' + oldParent + ':children', 'cid:' + newParent + ':children']);
}
destination.description = source.description;
destination.descriptionParsed = source.descriptionParsed;
destination.icon = source.icon;
destination.bgColor = source.bgColor;
destination.color = source.color;
destination.link = source.link;
destination.numRecentReplies = source.numRecentReplies;
destination.class = source.class;
destination.image = source.image;
destination.imageClass = source.imageClass;
if (copyParent) {
destination.parentCid = source.parentCid || 0;
}
await db.setObject('category:' + toCid, destination);
await copyTagWhitelist(fromCid, toCid);
await Categories.copyPrivilegesFrom(fromCid, toCid);
return destination;
};
async function copyTagWhitelist(fromCid, toCid) {
const data = await db.getSortedSetRangeWithScores('cid:' + fromCid + ':tag:whitelist', 0, -1);
await db.delete('cid:' + toCid + ':tag:whitelist');
await db.sortedSetAdd('cid:' + toCid + ':tag:whitelist', data.map(item => item.score), data.map(item => item.value));
cache.del('cid:' + toCid + ':tag:whitelist');
}
Categories.copyPrivilegesFrom = async function (fromCid, toCid, group) {
group = group || '';
const data = await plugins.fireHook('filter:categories.copyPrivilegesFrom', {
privileges: group ? privileges.groupPrivilegeList.slice() : privileges.privilegeList.slice(),
fromCid: fromCid,
toCid: toCid,
group: group,
});
if (group) {
await copyPrivilegesByGroup(data.privileges, data.fromCid, data.toCid, group);
} else {
await copyPrivileges(data.privileges, data.fromCid, data.toCid);
}
};
async function copyPrivileges(privileges, fromCid, toCid) {
const toGroups = privileges.map(privilege => 'group:cid:' + toCid + ':privileges:' + privilege + ':members');
const fromGroups = privileges.map(privilege => 'group:cid:' + fromCid + ':privileges:' + privilege + ':members');
const currentMembers = await db.getSortedSetsMembers(toGroups.concat(fromGroups));
const copyGroups = _.uniq(_.flatten(currentMembers));
await async.each(copyGroups, async function (group) {
await copyPrivilegesByGroup(privileges, fromCid, toCid, group);
});
}
async function copyPrivilegesByGroup(privilegeList, fromCid, toCid, group) {
const fromGroups = privilegeList.map(privilege => 'group:cid:' + fromCid + ':privileges:' + privilege + ':members');
const toGroups = privilegeList.map(privilege => 'group:cid:' + toCid + ':privileges:' + privilege + ':members');
const [fromChecks, toChecks] = await Promise.all([
db.isMemberOfSortedSets(fromGroups, group),
db.isMemberOfSortedSets(toGroups, group),
]);
const givePrivs = privilegeList.filter((priv, index) => fromChecks[index] && !toChecks[index]);
const rescindPrivs = privilegeList.filter((priv, index) => !fromChecks[index] && toChecks[index]);
await privileges.categories.give(givePrivs, toCid, group);
await privileges.categories.rescind(rescindPrivs, toCid, group);
}
};