From efb7d688f04d3ea97b7a7576a04bd22328d2b6d0 Mon Sep 17 00:00:00 2001
From: "Misty (Bot)"
\nInvite Only - Users can invite others from the users page.
\nAdmin Invite Only - Only administrators can invite others from users and admin/manage/users pages.
\nNo registration - No user registration.
",
"registration-approval-type.help": "Normal - Users are registered immediately.
\nAdmin Approval - User registrations are placed in an approval queue for administrators.
\nAdmin Approval for IPs - Normal for new users, Admin Approval for IP addresses that already have an account.
",
+ "registration-queue-auto-approve-time": "Automatic Approval Time",
+ "registration-queue-auto-approve-time-help": "Hours before user is approved automatically. 0 to disable.",
+ "registration-queue-show-average-time": "Show users average time it takes to approve a new user",
"registration.max-invites": "Maximum Invitations per User",
"max-invites": "Maximum Invitations per User",
"max-invites-help": "0 for no restriction. Admins get infinite invitations
Only applicable for \"Invite Only\"",
diff --git a/public/language/en-GB/register.json b/public/language/en-GB/register.json
index 461295ef5f..c6136b9d0a 100644
--- a/public/language/en-GB/register.json
+++ b/public/language/en-GB/register.json
@@ -1,27 +1,28 @@
{
- "register": "Register",
- "cancel_registration": "Cancel Registration",
- "help.email": "By default, your email will be hidden from the public.",
- "help.username_restrictions": "A unique username between %1 and %2 characters. Others can mention you with @username.",
- "help.minimum_password_length": "Your password's length must be at least %1 characters.",
- "email_address": "Email Address",
- "email_address_placeholder": "Enter Email Address",
- "username": "Username",
- "username_placeholder": "Enter Username",
- "password": "Password",
- "password_placeholder": "Enter Password",
- "confirm_password": "Confirm Password",
- "confirm_password_placeholder": "Confirm Password",
- "register_now_button": "Register Now",
- "alternative_registration": "Alternative Registration",
- "terms_of_use": "Terms of Use",
- "agree_to_terms_of_use": "I agree to the Terms of Use",
- "terms_of_use_error": "You must agree to the Terms of Use",
- "registration-added-to-queue": "Your registration has been added to the approval queue. You will receive an email when it is accepted by an administrator.",
- "interstitial.intro": "We require some additional information before we can create your account.",
- "interstitial.errors-found": "We could not complete your registration:",
-
- "gdpr_agree_data": "I consent to the collection and processing of my personal information on this website.",
- "gdpr_agree_email": "I consent to receive digest and notification emails from this website.",
- "gdpr_consent_denied": "You must give consent to this site to collect/process your information, and to send you emails."
-}
\ No newline at end of file
+ "register": "Register",
+ "cancel_registration": "Cancel Registration",
+ "help.email": "By default, your email will be hidden from the public.",
+ "help.username_restrictions": "A unique username between %1 and %2 characters. Others can mention you with @username.",
+ "help.minimum_password_length": "Your password's length must be at least %1 characters.",
+ "email_address": "Email Address",
+ "email_address_placeholder": "Enter Email Address",
+ "username": "Username",
+ "username_placeholder": "Enter Username",
+ "password": "Password",
+ "password_placeholder": "Enter Password",
+ "confirm_password": "Confirm Password",
+ "confirm_password_placeholder": "Confirm Password",
+ "register_now_button": "Register Now",
+ "alternative_registration": "Alternative Registration",
+ "terms_of_use": "Terms of Use",
+ "agree_to_terms_of_use": "I agree to the Terms of Use",
+ "terms_of_use_error": "You must agree to the Terms of Use",
+ "registration-added-to-queue": "Your registration has been added to the approval queue. You will receive an email when it is accepted by an administrator.",
+ "registration-queue-average-time": "Our average time for approving memberships is %1 hours %2 minutes.",
+ "registration-queue-auto-approve-time": "Your membership to this forum will be fully activated in up to %1 hours.",
+ "interstitial.intro": "We require some additional information before we can create your account.",
+ "interstitial.errors-found": "We could not complete your registration:",
+ "gdpr_agree_data": "I consent to the collection and processing of my personal information on this website.",
+ "gdpr_agree_email": "I consent to receive digest and notification emails from this website.",
+ "gdpr_consent_denied": "You must give consent to this site to collect/process your information, and to send you emails."
+}
diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js
index 2b59a835a0..7836f8fe17 100644
--- a/src/controllers/authentication.js
+++ b/src/controllers/authentication.js
@@ -118,7 +118,15 @@ authenticationController.register = async function (req, res) {
async function addToApprovalQueue(req, userData) {
userData.ip = req.ip;
await user.addToApprovalQueue(userData);
- return { message: '[[register:registration-added-to-queue]]' };
+ let message = '[[register:registration-added-to-queue]]';
+ if (meta.config.showAverageApprovalTime) {
+ const average_time = await db.getObjectField('registration:queue:approval:times', 'average');
+ message += ` [[register:registration-queue-average-time, ${Math.floor(average_time / 60)}, ${average_time % 60}]]`;
+ }
+ if (meta.config.autoApproveTime > 0) {
+ message += ` [[register:registration-queue-auto-approve-time, ${meta.config.autoApproveTime}]]`;
+ }
+ return { message: message };
}
authenticationController.registerComplete = function (req, res, next) {
diff --git a/src/user/approval.js b/src/user/approval.js
index 797ef9045f..2476da5cb1 100644
--- a/src/user/approval.js
+++ b/src/user/approval.js
@@ -1,6 +1,7 @@
'use strict';
const validator = require('validator');
+const cronJob = require('cron').CronJob;
const db = require('../database');
const meta = require('../meta');
@@ -12,6 +13,10 @@ const slugify = require('../slugify');
const plugins = require('../plugins');
module.exports = function (User) {
+ new cronJob('0 * * * * *', function () {
+ User.autoApprove();
+ }, null, true);
+
User.addToApprovalQueue = async function (userData) {
userData.username = userData.username.trim();
userData.userslug = slugify(userData.username);
@@ -59,7 +64,7 @@ module.exports = function (User) {
if (!userData) {
throw new Error('[[error:invalid-data]]');
}
-
+ const creation_time = await db.sortedSetScore('registration:queue', username);
const uid = await User.create(userData);
await User.setUserField(uid, 'password', userData.hashedPassword);
await removeFromQueue(username);
@@ -71,6 +76,9 @@ module.exports = function (User) {
template: 'registration_accepted',
uid: uid,
});
+ const total = await db.incrObjectField('registration:queue:approval:times', 'totalTime', Math.floor((Date.now() - creation_time) / 60000));
+ const counter = await db.incrObjectField('registration:queue:approval:times', 'counter', 1);
+ await db.setObjectField('registration:queue:approval:times', 'average', total / counter);
return uid;
};
@@ -140,4 +148,16 @@ module.exports = function (User) {
const uids = await User.getUidsFromSet('ip:' + user.ip + ':uid', 0, -1);
user.ipMatch = await User.getUsersFields(uids, ['uid', 'username', 'picture']);
}
+
+ User.autoApprove = async function () {
+ if (meta.config.autoApproveTime <= 0) {
+ return;
+ }
+ const users = await db.getSortedSetRevRangeWithScores('registration:queue', 0, -1);
+ const now = Date.now();
+ for (const user of users.filter(user => now - user.score >= meta.config.autoApproveTime * 3600000)) {
+ // eslint-disable-next-line no-await-in-loop
+ await User.acceptRegistration(user.value);
+ }
+ };
};
diff --git a/src/views/admin/settings/user.tpl b/src/views/admin/settings/user.tpl
index 45edc8f5a5..c6f0db9062 100644
--- a/src/views/admin/settings/user.tpl
+++ b/src/views/admin/settings/user.tpl
@@ -186,6 +186,19 @@
[[admin/settings/user:registration-approval-type.help, {config.relative_path}]]
+ [[admin/settings/user:registration-queue-auto-approve-time-help]] +
+ctrl and shift to select multiple options.",
"details.badge_preview": "Badge Preview",
"details.change_icon": "Change Icon",
"details.change_label_colour": "Change Label Colour",
diff --git a/public/openapi/components/schemas/GroupObject.yaml b/public/openapi/components/schemas/GroupObject.yaml
index e9802cdf7e..dbef43abc3 100644
--- a/public/openapi/components/schemas/GroupObject.yaml
+++ b/public/openapi/components/schemas/GroupObject.yaml
@@ -42,6 +42,11 @@ GroupFullObject:
textColor:
type: string
description: A six-character hexadecimal colour code
+ memberPostCids:
+ type: array
+ items:
+ type: number
+ example: [1, 2, 3]
icon:
type: string
description: A FontAwesome icon string
@@ -56,6 +61,32 @@ GroupFullObject:
type: string
descriptionParsed:
type: string
+ categories:
+ type: array
+ items:
+ type: object
+ properties:
+ cid:
+ type: number
+ description: A category identifier
+ name:
+ type: string
+ level:
+ type: string
+ icon:
+ type: string
+ parentCid:
+ type: number
+ description: The category identifier for the category that is the immediate
+ ancestor of the current category
+ color:
+ type: string
+ bgColor:
+ type: string
+ selected:
+ type: boolean
+ imageClass:
+ type: string
members:
type: array
items:
@@ -130,4 +161,9 @@ GroupDataObject:
type: string
description: "`createtime` rendered as an ISO 8601 format"
cover:position:
- type: string
\ No newline at end of file
+ type: string
+ memberPostCids:
+ type: array
+ items:
+ type: number
+ example: [1, 2, 3]
\ No newline at end of file
diff --git a/public/openapi/read/admin/manage/groups.yaml b/public/openapi/read/admin/manage/groups.yaml
index 649147348c..7b7ae1db46 100644
--- a/public/openapi/read/admin/manage/groups.yaml
+++ b/public/openapi/read/admin/manage/groups.yaml
@@ -64,6 +64,11 @@ get:
type: string
ownerUid:
type: number
+ memberPostCids:
+ type: array
+ items:
+ type: number
+ example: [1, 2, 3]
required:
- name
- description
diff --git a/public/openapi/read/groups.yaml b/public/openapi/read/groups.yaml
index 90a8c31d1d..a73b29864f 100644
--- a/public/openapi/read/groups.yaml
+++ b/public/openapi/read/groups.yaml
@@ -58,6 +58,11 @@ get:
type: string
cover:position:
type: string
+ memberPostCids:
+ type: array
+ items:
+ type: number
+ example: [1, 2, 3]
members:
type: array
items:
diff --git a/public/openapi/write/groups.yaml b/public/openapi/write/groups.yaml
index 8ae4d703f8..8d325c758e 100644
--- a/public/openapi/write/groups.yaml
+++ b/public/openapi/write/groups.yaml
@@ -37,6 +37,11 @@ post:
enum: [0, 1]
createtime:
type: number
+ memberPostCids:
+ type: array
+ items:
+ type: number
+ example: [1, 2, 3]
required:
- name
responses:
diff --git a/public/src/admin/manage/group.js b/public/src/admin/manage/group.js
index 0d2ff00403..a64a8d88c5 100644
--- a/public/src/admin/manage/group.js
+++ b/public/src/admin/manage/group.js
@@ -80,6 +80,7 @@ define('admin/manage/group', [
userTitleEnabled: $('#group-userTitleEnabled').is(':checked'),
private: $('#group-private').is(':checked'),
hidden: $('#group-hidden').is(':checked'),
+ memberPostCids: $('#memberPostCids').val(),
disableJoinRequests: $('#group-disableJoinRequests').is(':checked'),
disableLeave: $('#group-disableLeave').is(':checked'),
},
diff --git a/src/groups/data.js b/src/groups/data.js
index c866902b4c..6747fc0078 100644
--- a/src/groups/data.js
+++ b/src/groups/data.js
@@ -75,6 +75,7 @@ function modifyGroup(group, fields) {
group.icon = validator.escape(String(group.icon || ''));
group.createtimeISO = utils.toISOString(group.createtime);
group.private = ([null, undefined].includes(group.private)) ? 1 : group.private;
+ group.memberPostCids = (group.memberPostCids || '').split(',').map(cid => parseInt(cid, 10)).filter(Boolean);
group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url'];
diff --git a/src/groups/index.js b/src/groups/index.js
index 4140e558f9..732f415e6b 100644
--- a/src/groups/index.js
+++ b/src/groups/index.js
@@ -1,6 +1,7 @@
'use strict';
const user = require('../user');
+const categories = require('../categories');
const db = require('../database');
const plugins = require('../plugins');
const slugify = require('../slugify');
@@ -119,9 +120,10 @@ Groups.get = async function (groupName, options) {
stop = (parseInt(options.userListCount, 10) || 4) - 1;
}
- const [groupData, members, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([
+ const [groupData, members, selectCategories, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([
Groups.getGroupData(groupName),
Groups.getOwnersAndMembers(groupName, options.uid, 0, stop),
+ categories.buildForSelect(groupName, 'topics:read', []),
Groups.getUsersFromSet('group:' + groupName + ':pending', ['username', 'userslug', 'picture']),
Groups.getUsersFromSet('group:' + groupName + ':invited', ['username', 'userslug', 'picture']),
Groups.isMember(options.uid, groupName),
@@ -135,6 +137,10 @@ Groups.get = async function (groupName, options) {
}
const descriptionParsed = await plugins.fireHook('filter:parse.raw', groupData.description);
groupData.descriptionParsed = descriptionParsed;
+ groupData.categories = selectCategories.map((category) => {
+ category.selected = groupData.memberPostCids.includes(category.cid);
+ return category;
+ });
groupData.members = members;
groupData.membersNextStart = stop + 1;
groupData.pending = pending.filter(Boolean);
diff --git a/src/groups/posts.js b/src/groups/posts.js
index a96cd9e217..465f060b4b 100644
--- a/src/groups/posts.js
+++ b/src/groups/posts.js
@@ -1,6 +1,7 @@
'use strict';
const db = require('../database');
+const groups = require('.');
const privileges = require('../privileges');
const posts = require('../posts');
@@ -13,6 +14,10 @@ module.exports = function (Groups) {
let groupNames = await Groups.getUserGroupMembership('groups:visible:createtime', [postData.uid]);
groupNames = groupNames[0];
+ // Only process those groups that have the cid in its memberPostCids setting (or no setting at all)
+ const groupsCids = await groups.getGroupsFields(groupNames, ['memberPostCids']);
+ groupNames = groupNames.filter((groupName, idx) => !groupsCids[idx].memberPostCids.length || groupsCids[idx].memberPostCids.includes(postData.cid));
+
const keys = groupNames.map(groupName => 'group:' + groupName + ':member:pids');
await db.sortedSetsAdd(keys, postData.timestamp, postData.pid);
await Promise.all(groupNames.map(name => truncateMemberPosts(name)));
diff --git a/src/groups/update.js b/src/groups/update.js
index eb477944c5..50027fc9bd 100644
--- a/src/groups/update.js
+++ b/src/groups/update.js
@@ -2,6 +2,7 @@
const winston = require('winston');
+const categories = require('../categories');
const plugins = require('../plugins');
const slugify = require('../slugify');
const db = require('../database');
@@ -18,11 +19,10 @@ module.exports = function (Groups) {
throw new Error('[[error:no-group]]');
}
- const result = await plugins.fireHook('filter:group.update', {
+ ({ values } = await plugins.fireHook('filter:group.update', {
groupName: groupName,
values: values,
- });
- values = result.values;
+ }));
const payload = {
description: values.description || '',
@@ -66,6 +66,12 @@ module.exports = function (Groups) {
if (values.hasOwnProperty('hidden')) {
await updateVisibility(groupName, values.hidden);
}
+
+ if (values.hasOwnProperty('memberPostCids')) {
+ const validCids = await categories.getCidsByPrivilege('categories:cid', groupName, 'topics:read');
+ payload.memberPostCids = values.memberPostCids.filter(cid => validCids.includes(cid)).join(',') || '';
+ }
+
await db.setObject('group:' + groupName, payload);
await Groups.renameGroup(groupName, values.name);
diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js
index 529cc8059d..6ce56fd988 100644
--- a/src/plugins/hooks.js
+++ b/src/plugins/hooks.js
@@ -6,7 +6,7 @@ const utils = require('../utils');
module.exports = function (Plugins) {
Plugins.deprecatedHooks = {
-
+ 'filter:privileges:isUserAllowedTo': 'filter:privileges:isAllowedTo',
};
Plugins.internals = {
diff --git a/src/privileges/admin.js b/src/privileges/admin.js
index dc8ad7e343..305ea98fde 100644
--- a/src/privileges/admin.js
+++ b/src/privileges/admin.js
@@ -144,7 +144,7 @@ module.exports = function (privileges) {
privileges.admin.get = async function (uid) {
const [userPrivileges, isAdministrator] = await Promise.all([
- helpers.isUserAllowedTo(privileges.admin.userPrivilegeList, uid, 0),
+ helpers.isAllowedTo(privileges.admin.userPrivilegeList, uid, 0),
user.isAdministrator(uid),
]);
@@ -157,7 +157,7 @@ module.exports = function (privileges) {
privileges.admin.can = async function (privilege, uid) {
const [isUserAllowedTo, isAdministrator] = await Promise.all([
- helpers.isUserAllowedTo(privilege, uid, [0]),
+ helpers.isAllowedTo(privilege, uid, [0]),
user.isAdministrator(uid),
]);
return isAdministrator || isUserAllowedTo[0];
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
index d1c867e27e..b915f289da 100644
--- a/src/privileges/categories.js
+++ b/src/privileges/categories.js
@@ -46,7 +46,7 @@ module.exports = function (privileges) {
const privs = ['topics:create', 'topics:read', 'topics:tag', 'read'];
const [userPrivileges, isAdministrator, isModerator] = await Promise.all([
- helpers.isUserAllowedTo(privs, uid, cid),
+ helpers.isAllowedTo(privs, uid, cid),
user.isAdministrator(uid),
user.isModerator(uid, cid),
]);
@@ -80,7 +80,7 @@ module.exports = function (privileges) {
if (!cid) {
return false;
}
- const results = await helpers.isUserAllowedTo(privilege, uid, Array.isArray(cid) ? cid : [cid]);
+ const results = await helpers.isAllowedTo(privilege, uid, Array.isArray(cid) ? cid : [cid]);
if (Array.isArray(results) && results.length) {
return Array.isArray(cid) ? results : results[0];
@@ -113,8 +113,8 @@ module.exports = function (privileges) {
privileges.categories.getBase = async function (privilege, cids, uid) {
return await utils.promiseParallel({
categories: categories.getCategoriesFields(cids, ['disabled']),
- allowedTo: helpers.isUserAllowedTo(privilege, uid, cids),
- view_deleted: helpers.isUserAllowedTo('posts:view_deleted', uid, cids),
+ allowedTo: helpers.isAllowedTo(privilege, uid, cids),
+ view_deleted: helpers.isAllowedTo('posts:view_deleted', uid, cids),
isAdmin: user.isAdministrator(uid),
});
};
diff --git a/src/privileges/global.js b/src/privileges/global.js
index 7c1f3f7dcd..8a11e8a71a 100644
--- a/src/privileges/global.js
+++ b/src/privileges/global.js
@@ -75,7 +75,7 @@ module.exports = function (privileges) {
privileges.global.get = async function (uid) {
const [userPrivileges, isAdministrator] = await Promise.all([
- helpers.isUserAllowedTo(privileges.global.userPrivilegeList, uid, 0),
+ helpers.isAllowedTo(privileges.global.userPrivilegeList, uid, 0),
user.isAdministrator(uid),
]);
@@ -88,7 +88,7 @@ module.exports = function (privileges) {
privileges.global.can = async function (privilege, uid) {
const [isAdministrator, isUserAllowedTo] = await Promise.all([
user.isAdministrator(uid),
- helpers.isUserAllowedTo(privilege, uid, [0]),
+ helpers.isAllowedTo(privilege, uid, [0]),
]);
return isAdministrator || isUserAllowedTo[0];
};
diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js
index 9d1fb78e61..01bd609ece 100644
--- a/src/privileges/helpers.js
+++ b/src/privileges/helpers.js
@@ -26,26 +26,45 @@ helpers.isUsersAllowedTo = async function (privilege, uids, cid) {
return result.allowed;
};
-helpers.isUserAllowedTo = async function (privilege, uid, cid) {
+helpers.isAllowedTo = async function (privilege, uidOrGroupName, cid) {
let allowed;
if (Array.isArray(privilege) && !Array.isArray(cid)) {
- allowed = await isUserAllowedToPrivileges(privilege, uid, cid);
+ allowed = await isAllowedToPrivileges(privilege, uidOrGroupName, cid);
} else if (Array.isArray(cid) && !Array.isArray(privilege)) {
- allowed = await isUserAllowedToCids(privilege, uid, cid);
+ allowed = await isAllowedToCids(privilege, uidOrGroupName, cid);
}
if (allowed) {
- const result = await plugins.fireHook('filter:privileges:isUserAllowedTo', { allowed: allowed, privilege: privilege, uid: uid, cid: cid });
- return result.allowed;
+ ({ allowed } = await plugins.fireHook('filter:privileges:isUserAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
+ ({ allowed } = await plugins.fireHook('filter:privileges:isAllowedTo', { allowed: allowed, privilege: privilege, uid: uidOrGroupName, cid: cid }));
+ return allowed;
}
throw new Error('[[error:invalid-data]]');
};
-async function isUserAllowedToCids(privilege, uid, cids) {
+async function isAllowedToCids(privilege, uidOrGroupName, cids) {
if (!privilege) {
return cids.map(() => false);
}
- if (parseInt(uid, 10) <= 0) {
- return await isSystemGroupAllowedToCids(privilege, uid, cids);
+
+ // Group handling
+ if (isNaN(parseInt(uidOrGroupName, 10)) && (uidOrGroupName || '').length) {
+ const groupKeys = [];
+ cids.forEach(function (cid) {
+ groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege);
+ });
+ const sets = await Promise.all([
+ groups.isMemberOfGroups(uidOrGroupName, groupKeys),
+ groups.isMemberOfGroups('registered-users', groupKeys),
+ ]);
+ return sets[0].reduce((memo, cur, idx) => {
+ memo.push(cur || sets[1][idx]);
+ return memo;
+ }, []);
+ }
+
+ // User handling
+ if (parseInt(uidOrGroupName, 10) <= 0) {
+ return await isSystemGroupAllowedToCids(privilege, uidOrGroupName, cids);
}
const userKeys = [];
@@ -55,12 +74,29 @@ async function isUserAllowedToCids(privilege, uid, cids) {
groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege);
});
- return await checkIfAllowed(uid, userKeys, groupKeys);
+ return await checkIfAllowed(uidOrGroupName, userKeys, groupKeys);
}
-async function isUserAllowedToPrivileges(privileges, uid, cid) {
- if (parseInt(uid, 10) <= 0) {
- return await isSystemGroupAllowedToPrivileges(privileges, uid, cid);
+async function isAllowedToPrivileges(privileges, uidOrGroupName, cid) {
+ // Group handling
+ if (isNaN(parseInt(uidOrGroupName, 10)) && (uidOrGroupName || '').length) {
+ const groupKeys = [];
+ privileges.forEach(function (privilege) {
+ groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege);
+ });
+ const sets = await Promise.all([
+ groups.isMemberOfGroups(uidOrGroupName, groupKeys),
+ groups.isMemberOfGroups('registered-users', groupKeys),
+ ]);
+ return sets[0].reduce((memo, cur, idx) => {
+ memo.push(cur || sets[1][idx]);
+ return memo;
+ }, []);
+ }
+
+ // User handling
+ if (parseInt(uidOrGroupName, 10) <= 0) {
+ return await isSystemGroupAllowedToPrivileges(privileges, uidOrGroupName, cid);
}
const userKeys = [];
@@ -70,7 +106,7 @@ async function isUserAllowedToPrivileges(privileges, uid, cid) {
groupKeys.push('cid:' + cid + ':privileges:groups:' + privilege);
});
- return await checkIfAllowed(uid, userKeys, groupKeys);
+ return await checkIfAllowed(uidOrGroupName, userKeys, groupKeys);
}
async function checkIfAllowed(uid, userKeys, groupKeys) {
diff --git a/src/privileges/posts.js b/src/privileges/posts.js
index ac595c0c31..f1274b892e 100644
--- a/src/privileges/posts.js
+++ b/src/privileges/posts.js
@@ -25,11 +25,11 @@ module.exports = function (privileges) {
isAdmin: user.isAdministrator(uid),
isModerator: user.isModerator(uid, uniqueCids),
isOwner: posts.isOwner(pids, uid),
- 'topics:read': helpers.isUserAllowedTo('topics:read', uid, uniqueCids),
- read: helpers.isUserAllowedTo('read', uid, uniqueCids),
- 'posts:edit': helpers.isUserAllowedTo('posts:edit', uid, uniqueCids),
- 'posts:history': helpers.isUserAllowedTo('posts:history', uid, uniqueCids),
- 'posts:view_deleted': helpers.isUserAllowedTo('posts:view_deleted', uid, uniqueCids),
+ 'topics:read': helpers.isAllowedTo('topics:read', uid, uniqueCids),
+ read: helpers.isAllowedTo('read', uid, uniqueCids),
+ 'posts:edit': helpers.isAllowedTo('posts:edit', uid, uniqueCids),
+ 'posts:history': helpers.isAllowedTo('posts:history', uid, uniqueCids),
+ 'posts:view_deleted': helpers.isAllowedTo('posts:view_deleted', uid, uniqueCids),
});
const isModerator = _.zipObject(uniqueCids, results.isModerator);
diff --git a/src/privileges/topics.js b/src/privileges/topics.js
index d4b34194ff..75a3e01fb3 100644
--- a/src/privileges/topics.js
+++ b/src/privileges/topics.js
@@ -23,7 +23,7 @@ module.exports = function (privileges) {
];
const topicData = await topics.getTopicFields(tid, ['cid', 'uid', 'locked', 'deleted']);
const [userPrivileges, isAdministrator, isModerator, disabled] = await Promise.all([
- helpers.isUserAllowedTo(privs, uid, topicData.cid),
+ helpers.isAllowedTo(privs, uid, topicData.cid),
user.isAdministrator(uid),
user.isModerator(uid, topicData.cid),
categories.getCategoryField(topicData.cid, 'disabled'),
@@ -121,7 +121,7 @@ module.exports = function (privileges) {
user.isModerator(uid, topicData.cid),
user.isAdministrator(uid),
topics.isOwner(tid, uid),
- helpers.isUserAllowedTo('topics:delete', uid, [topicData.cid]),
+ helpers.isAllowedTo('topics:delete', uid, [topicData.cid]),
]);
if (isAdministrator) {
diff --git a/src/privileges/users.js b/src/privileges/users.js
index 1cf9441aee..2f43ceca13 100644
--- a/src/privileges/users.js
+++ b/src/privileges/users.js
@@ -41,7 +41,7 @@ module.exports = function (privileges) {
return await filterIsModerator(cids, uid, cids.map(() => true));
}
const uniqueCids = _.uniq(cids);
- const isAllowed = await helpers.isUserAllowedTo('moderate', uid, uniqueCids);
+ const isAllowed = await helpers.isAllowedTo('moderate', uid, uniqueCids);
const cidToIsAllowed = _.zipObject(uniqueCids, isAllowed);
const isModerator = cids.map(cid => cidToIsAllowed[cid]);
diff --git a/src/views/admin/manage/group.tpl b/src/views/admin/manage/group.tpl
index 382a26cf8e..282d864b06 100644
--- a/src/views/admin/manage/group.tpl
+++ b/src/views/admin/manage/group.tpl
@@ -95,6 +95,22 @@
[[groups:details.member-post-cids-help]]
+