mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: option to restrict group leaving, closes #7770
This commit is contained in:
@@ -25,7 +25,8 @@
|
|||||||
"edit.show-badge": "Show Badge",
|
"edit.show-badge": "Show Badge",
|
||||||
"edit.private-details": "If enabled, joining of groups requires approval from a group owner.",
|
"edit.private-details": "If enabled, joining of groups requires approval from a group owner.",
|
||||||
"edit.private-override": "Warning: Private groups is disabled at system level, which overrides this option.",
|
"edit.private-override": "Warning: Private groups is disabled at system level, which overrides this option.",
|
||||||
"edit.disable-requests": "Disable join requests",
|
"edit.disable-join": "Disable join requests",
|
||||||
|
"edit.disable-leave": "Disallow users from leaving the group",
|
||||||
"edit.hidden": "Hidden",
|
"edit.hidden": "Hidden",
|
||||||
"edit.hidden-details": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
|
"edit.hidden-details": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
|
||||||
"edit.add-user": "Add User to Group",
|
"edit.add-user": "Add User to Group",
|
||||||
|
|||||||
@@ -117,6 +117,8 @@
|
|||||||
"group-needs-owner": "This group requires at least one owner",
|
"group-needs-owner": "This group requires at least one owner",
|
||||||
"group-already-invited": "This user has already been invited",
|
"group-already-invited": "This user has already been invited",
|
||||||
"group-already-requested": "Your membership request has already been submitted",
|
"group-already-requested": "Your membership request has already been submitted",
|
||||||
|
"group-join-disabled": "You are not able to join this group at this time",
|
||||||
|
"group-leave-disabled": "You are not able to leave this group at this time",
|
||||||
|
|
||||||
"post-already-deleted": "This post has already been deleted",
|
"post-already-deleted": "This post has already been deleted",
|
||||||
"post-already-restored": "This post has already been restored",
|
"post-already-restored": "This post has already been restored",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"details.latest_posts": "Latest Posts",
|
"details.latest_posts": "Latest Posts",
|
||||||
"details.private": "Private",
|
"details.private": "Private",
|
||||||
"details.disableJoinRequests": "Disable join requests",
|
"details.disableJoinRequests": "Disable join requests",
|
||||||
|
"details.disableLeave": "Disallow users from leaving the group",
|
||||||
"details.grant": "Grant/Rescind Ownership",
|
"details.grant": "Grant/Rescind Ownership",
|
||||||
"details.kick": "Kick",
|
"details.kick": "Kick",
|
||||||
"details.kick_confirm": "Are you sure you want to remove this member from the group?",
|
"details.kick_confirm": "Are you sure you want to remove this member from the group?",
|
||||||
|
|||||||
@@ -160,7 +160,7 @@
|
|||||||
// Groups helpers
|
// Groups helpers
|
||||||
function membershipBtn(groupObj) {
|
function membershipBtn(groupObj) {
|
||||||
if (groupObj.isMember && groupObj.name !== 'administrators') {
|
if (groupObj.isMember && groupObj.name !== 'administrators') {
|
||||||
return '<button class="btn btn-danger" data-action="leave" data-group="' + groupObj.displayName + '"><i class="fa fa-times"></i> [[groups:membership.leave-group]]</button>';
|
return '<button class="btn btn-danger" data-action="leave" data-group="' + groupObj.displayName + '"' + (groupObj.disableLeave ? ' disabled' : '') + '><i class="fa fa-times"></i> [[groups:membership.leave-group]]</button>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupObj.isPending && groupObj.name !== 'administrators') {
|
if (groupObj.isPending && groupObj.name !== 'administrators') {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ module.exports = function (Groups) {
|
|||||||
if (data.name === 'administrators') {
|
if (data.name === 'administrators') {
|
||||||
disableJoinRequests = 1;
|
disableJoinRequests = 1;
|
||||||
}
|
}
|
||||||
|
const disableLeave = parseInt(data.disableLeave, 10) === 1 ? 1 : 0;
|
||||||
const isHidden = parseInt(data.hidden, 10) === 1;
|
const isHidden = parseInt(data.hidden, 10) === 1;
|
||||||
|
|
||||||
validateGroupName(data.name);
|
validateGroupName(data.name);
|
||||||
@@ -36,6 +37,7 @@ module.exports = function (Groups) {
|
|||||||
system: isSystem ? 1 : 0,
|
system: isSystem ? 1 : 0,
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
disableJoinRequests: disableJoinRequests,
|
disableJoinRequests: disableJoinRequests,
|
||||||
|
disableLeave: disableLeave,
|
||||||
};
|
};
|
||||||
|
|
||||||
plugins.fireHook('filter:group.create', { group: groupData, data: data });
|
plugins.fireHook('filter:group.create', { group: groupData, data: data });
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const utils = require('../utils');
|
|||||||
|
|
||||||
const intFields = [
|
const intFields = [
|
||||||
'createtime', 'memberCount', 'hidden', 'system', 'private',
|
'createtime', 'memberCount', 'hidden', 'system', 'private',
|
||||||
'userTitleEnabled', 'disableJoinRequests',
|
'userTitleEnabled', 'disableJoinRequests', 'disableLeave',
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = function (Groups) {
|
module.exports = function (Groups) {
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ module.exports = function (Groups) {
|
|||||||
payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0';
|
payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.hasOwnProperty('disableLeave')) {
|
||||||
|
payload.disableLeave = values.disableLeave ? '1' : '0';
|
||||||
|
}
|
||||||
|
|
||||||
await checkNameChange(groupName, values.name);
|
await checkNameChange(groupName, values.name);
|
||||||
if (values.hasOwnProperty('private')) {
|
if (values.hasOwnProperty('private')) {
|
||||||
await updatePrivacy(groupName, values.private);
|
await updatePrivacy(groupName, values.private);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ SocketGroups.join = async (socket, data) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (results.groupData.private && results.groupData.disableJoinRequests) {
|
if (results.groupData.private && results.groupData.disableJoinRequests) {
|
||||||
throw new Error('[[error:join-requests-disabled]]');
|
throw new Error('[[error:group-join-disabled]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!results.groupData.private || results.isAdmin) {
|
if (!results.groupData.private || results.isAdmin) {
|
||||||
@@ -68,6 +68,11 @@ SocketGroups.leave = async (socket, data) => {
|
|||||||
throw new Error('[[error:cant-remove-self-as-admin]]');
|
throw new Error('[[error:cant-remove-self-as-admin]]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupData = await groups.getGroupData(data.groupName);
|
||||||
|
if (groupData.disableLeave) {
|
||||||
|
throw new Error('[[error:group-leave-disabled]]');
|
||||||
|
}
|
||||||
|
|
||||||
await groups.leave(data.groupName, socket.uid);
|
await groups.leave(data.groupName, socket.uid);
|
||||||
logGroupEvent(socket, 'group-leave', {
|
logGroupEvent(socket, 'group-leave', {
|
||||||
groupName: data.groupName,
|
groupName: data.groupName,
|
||||||
|
|||||||
@@ -69,7 +69,16 @@
|
|||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<label>
|
||||||
<input id="group-disableJoinRequests" name="disableJoinRequests" type="checkbox"<!-- IF group.disableJoinRequests --> checked<!-- ENDIF group.disableJoinRequests -->>
|
<input id="group-disableJoinRequests" name="disableJoinRequests" type="checkbox"<!-- IF group.disableJoinRequests --> checked<!-- ENDIF group.disableJoinRequests -->>
|
||||||
<strong>[[admin/manage/groups:edit.disable-requests]]</strong>
|
<strong>[[admin/manage/groups:edit.disable-join]]</strong>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input id="group-disableLeave" name="disableLeave" type="checkbox"{{{if group.disableLeave}}} checked{{{end}}}>
|
||||||
|
<strong>[[admin/manage/groups:edit.disable-leave]]</strong>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ describe('Groups', function () {
|
|||||||
disableJoinRequests: 0,
|
disableJoinRequests: 0,
|
||||||
}, next);
|
}, next);
|
||||||
},
|
},
|
||||||
|
async () => {
|
||||||
|
await Groups.create({
|
||||||
|
name: 'PrivateNoLeave',
|
||||||
|
description: 'Private group',
|
||||||
|
private: 1,
|
||||||
|
disableLeave: 1,
|
||||||
|
});
|
||||||
|
},
|
||||||
function (next) {
|
function (next) {
|
||||||
// Create a new user
|
// Create a new user
|
||||||
User.create({
|
User.create({
|
||||||
@@ -62,8 +70,8 @@ describe('Groups', function () {
|
|||||||
},
|
},
|
||||||
], function (err, results) {
|
], function (err, results) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
testUid = results[3];
|
testUid = results[4];
|
||||||
adminUid = results[4];
|
adminUid = results[5];
|
||||||
Groups.join('administrators', adminUid, done);
|
Groups.join('administrators', adminUid, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -72,7 +80,7 @@ describe('Groups', function () {
|
|||||||
it('should list the groups present', function (done) {
|
it('should list the groups present', function (done) {
|
||||||
Groups.getGroupsFromSet('groups:visible:createtime', 0, -1, function (err, groups) {
|
Groups.getGroupsFromSet('groups:visible:createtime', 0, -1, function (err, groups) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(groups.length, 4);
|
assert.equal(groups.length, 5);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -120,7 +128,7 @@ describe('Groups', function () {
|
|||||||
it('should return the groups when search query is empty', function (done) {
|
it('should return the groups when search query is empty', function (done) {
|
||||||
socketGroups.search({ uid: adminUid }, { query: '' }, function (err, groups) {
|
socketGroups.search({ uid: adminUid }, { query: '' }, function (err, groups) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.equal(4, groups.length);
|
assert.equal(5, groups.length);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -730,11 +738,21 @@ describe('Groups', function () {
|
|||||||
it('should fail to join if group is private and join requests are disabled', function (done) {
|
it('should fail to join if group is private and join requests are disabled', function (done) {
|
||||||
meta.config.allowPrivateGroups = 1;
|
meta.config.allowPrivateGroups = 1;
|
||||||
socketGroups.join({ uid: testUid }, { groupName: 'PrivateNoJoin' }, function (err) {
|
socketGroups.join({ uid: testUid }, { groupName: 'PrivateNoJoin' }, function (err) {
|
||||||
assert.equal(err.message, '[[error:join-requests-disabled]]');
|
assert.equal(err.message, '[[error:group-join-disabled]]');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail to leave if group is private and leave is disabled', async () => {
|
||||||
|
await socketGroups.join({ uid: testUid }, { groupName: 'PrivateNoLeave' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await socketGroups.leave({ uid: testUid }, { groupName: 'PrivateNoLeave' });
|
||||||
|
} catch (err) {
|
||||||
|
assert.equal(err.message, '[[error:group-leave-disabled]]');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should join if user is admin', function (done) {
|
it('should join if user is admin', function (done) {
|
||||||
socketGroups.join({ uid: adminUid }, { groupName: 'PrivateCanJoin' }, function (err) {
|
socketGroups.join({ uid: adminUid }, { groupName: 'PrivateCanJoin' }, function (err) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user