Merge pull request #3908 from pitaj/master

Maximum invites and invites are stored
v1.18.x
Julian Lam 9 years ago
commit 4cc4824087

@ -87,5 +87,7 @@
"registration-error": "Registration Error",
"parse-error": "Something went wrong while parsing server response",
"wrong-login-type-email": "Please use your email to login",
"wrong-login-type-username": "Please use your username to login"
}
"wrong-login-type-username": "Please use your username to login",
"invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2)."
}

@ -114,5 +114,7 @@
"registration-error": "Registration Error",
"parse-error": "Something went wrong while parsing server response",
"wrong-login-type-email": "Please use your email to login",
"wrong-login-type-username": "Please use your username to login"
}
"wrong-login-type-username": "Please use your username to login",
"invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2)."
}

@ -87,5 +87,7 @@
"registration-error": "Registration Error",
"parse-error": "Something went wrong while parsing server response",
"wrong-login-type-email": "Please use your email to login",
"wrong-login-type-username": "Please use your username to login"
}
"wrong-login-type-username": "Please use your username to login",
"invite-maximum-met": "You have invited the maximum amount of people (%1 out of %2)."
}

@ -28,7 +28,7 @@ define('forum/register', ['csrf', 'translator'], function(csrf, translator) {
var query = utils.params();
if (query.email && query.token) {
email.val(query.email);
email.val(decodeURIComponent(query.email));
$('#token').val(query.token);
}
@ -160,7 +160,7 @@ define('forum/register', ['csrf', 'translator'], function(csrf, translator) {
socket.emit('user.exists', {
username: username
}, function(err, exists) {
if(err) {
if (err) {
return app.alertError(err.message);
}

@ -1,6 +1,7 @@
"use strict";
var user = require('../../user'),
var async = require('async'),
user = require('../../user'),
meta = require('../../meta');
@ -31,12 +32,50 @@ usersController.banned = function(req, res, next) {
};
usersController.registrationQueue = function(req, res, next) {
user.getRegistrationQueue(0, -1, function(err, data) {
var invitations;
async.parallel({
users: function(next) {
user.getRegistrationQueue(0, -1, next);
},
invites: function(next) {
async.waterfall([
function(next) {
user.getAllInvites(next);
},
function(_invitations, next) {
invitations = _invitations;
async.map(invitations, function(invites, next) {
user.getUserField(invites.uid, 'username', next);
}, next);
},
function(usernames, next) {
invitations.forEach(function(invites, index) {
invites.username = usernames[index];
});
async.map(invitations, function(invites, next) {
async.map(invites.invitations, user.getUsernameByEmail, next);
}, next);
},
function(usernames, next) {
invitations.forEach(function(invites, index) {
invites.invitations = invites.invitations.map(function(email, i) {
return {
email: email,
username: usernames[index][i] === '[[global:guest]]' ? '' : usernames[index][i]
};
});
});
next(null, invitations);
}
], next);
}
}, function(err, data) {
if (err) {
return next(err);
}
res.render('admin/manage/registration', {users: data});
})
res.render('admin/manage/registration', data);
});
};
function getUsers(set, req, res, next) {

@ -117,7 +117,7 @@ Controllers.register = function(req, res, next) {
async.waterfall([
function(next) {
if (registrationType === 'invite-only') {
if (registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
user.verifyInvitation(req.query, next);
} else {
next();

@ -150,14 +150,27 @@ usersController.getUsersForSearch = function(req, res, next) {
};
function render(req, res, data, next) {
plugins.fireHook('filter:users.build', {req: req, res: res, templateData: data}, function(err, data) {
plugins.fireHook('filter:users.build', { req: req, res: res, templateData: data }, function(err, data) {
if (err) {
return next(err);
}
data.templateData.inviteOnly = meta.config.registrationType === 'invite-only';
var registrationType = meta.config.registrationType;
data.templateData.maximumInvites = meta.config.maximumInvites;
data.templateData.inviteOnly = registrationType === 'invite-only' || registrationType === 'admin-invite-only';
data.templateData.adminInviteOnly = registrationType === 'admin-invite-only';
data.templateData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
res.render('users', data.templateData);
user.getInvitesNumber(req.uid, function(err, num) {
if (err) {
return next(err);
}
data.templateData.invites = num;
res.render('users', data.templateData);
});
});
}

@ -267,14 +267,44 @@ SocketUser.loadMore = function(socket, data, callback) {
SocketUser.invite = function(socket, email, callback) {
if (!email || !socket.uid) {
return callback(new Error('[[error:invald-data]]'));
return callback(new Error('[[error:invalid-data]]'));
}
if (meta.config.registrationType !== 'invite-only') {
var registrationType = meta.config.registrationType
if (registrationType !== 'invite-only' && registrationType !== 'admin-invite-only') {
return callback(new Error('[[error:forum-not-invite-only]]'));
}
user.sendInvitationEmail(socket.uid, email, callback);
var max = meta.config.maximumInvites;
user.isAdministrator(socket.uid, function(err, admin) {
if (err) {
return callback(err);
}
if (registrationType === 'admin-invite-only' && !admin) {
return callback(new Error('[[error:no-privileges]]'));
}
if (max) {
async.waterfall([
function(next) {
user.getInvitesNumber(socket.uid, next);
},
function(invites, next) {
if (!admin && invites > max) {
return next(new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]'));
}
next();
},
function(next) {
user.sendInvitationEmail(socket.uid, email, next);
}
], callback);
} else {
user.sendInvitationEmail(socket.uid, email, callback);
}
});
};

@ -16,13 +16,68 @@ var async = require('async'),
module.exports = function(User) {
User.getInvites = function(uid, callback) {
db.getSetMembers('invitation:uid:' + uid, callback);
};
User.getInvitesNumber = function(uid, callback) {
db.setCount('invitation:uid:' + uid, callback);
};
User.getInvitingUsers = function(callback) {
db.getSetMembers('invitation:uids', callback);
};
User.getAllInvites = function(callback) {
var uids;
async.waterfall([
User.getInvitingUsers,
function(_uids, next) {
uids = _uids;
async.map(uids, User.getInvites, next);
},
function(invitations, next) {
invitations = invitations.map(function(invites, index) {
return {
uid: uids[index],
invitations: invites
};
});
next(null, invitations);
}
], callback);
};
User.sendInvitationEmail = function(uid, email, callback) {
callback = callback || function() {};
var token = utils.generateUUID();
var registerLink = nconf.get('url') + '/register?token=' + token + '&email=' + email;
var registerLink = nconf.get('url') + '/register?token=' + token + '&email=' + encodeURIComponent(email);
var oneDay = 86400000;
async.waterfall([
function(next) {
User.getUidByEmail(email, next);
},
function(exists, next) {
if (exists) {
return next(new Error('[[error:email-taken]]'));
}
next();
},
function(next) {
async.parallel([
function(next) {
db.setAdd('invitation:uid:' + uid, email, next);
},
function(next) {
db.setAdd('invitation:uids', uid, next);
}
], function(err) {
next(err);
});
},
function(next) {
db.set('invitation:email:' + email, token, next);
},
@ -73,4 +128,4 @@ module.exports = function(User) {
db.delete('invitation:email:' + email, callback);
};
};
};

@ -1,4 +1,14 @@
<div class="registration">
<div class="registration panel panel-primary">
<div class="panel-heading">
Queue
</div>
<!-- IF !users.length -->
<p class="panel-body">
There are no users in the registration queue. <br>
To enable this feature, go to <a href="{config.relative_path}/admin/settings/user">Settings -> User -> Authentication</a> and set
<strong>Registration Type</strong> to "Admin Approval".
</p>
<!-- ENDIF !users.length -->
<table class="table table-striped users-list">
<tr>
<th>Name</th>
@ -7,11 +17,6 @@
<th>Time</th>
<th></th>
</tr>
<!-- IF !users.length -->
<p>
There are no users in the registration queue. To enable this feature go to <a href="{config.relative_path}/admin/settings/user">Settings -> User -> Authentication</a> and set <strong>Registration Type</strong> to "Admin Approval".
</p>
<!-- ENDIF !users.length -->
<!-- BEGIN users -->
<tr data-username="{users.username}">
<td>
@ -50,4 +55,31 @@
</tr>
<!-- END users -->
</table>
</div>
</div>
<div class="invitations panel panel-success">
<div class="panel-heading">
Invitations
</div>
<p class="panel-body">
Below is a complete list of invitations sent. Use ctrl-f to search through the list by email or username.
<br><br>
The username will be displayed to the right of the emails for users who have redeemed their invitations.
</p>
<table class="table table-striped users-list">
<tr>
<th>Inviter Username</th>
<th>Invitee Email</th>
<th>Invitee Username (if registered)</th>
</tr>
<!-- BEGIN invites -->
<tr>
<!-- BEGIN invites.invitations -->
<td><!-- IF @first -->{invites.username}<!-- ENDIF @first --></td>
<td>{invites.invitations.email}</td>
<td>{invites.invitations.username}</td>
</tr>
<!-- END invites.invitations -->
<!-- END invites -->
</table>
</div>

@ -39,9 +39,19 @@
<option value="normal">Normal</option>
<option value="admin-approval">Admin Approval</option>
<option value="invite-only">Invite Only</option>
<option value="admin-invite-only">Admin Invite Only</option>
<option value="disabled">No registration</option>
</select>
</div>
<div class="form-group">
<label>Maximum Invitations per User</label>
<input type="number" class="form-control" data-field="maximumInvites" placeholder="0">
<p class="help-block">
0 for no restriction. Admins get infinite invitations<br>
Only applicable for "Invite Only"
</p>
</div>
</form>
</div>
</div>
@ -273,4 +283,4 @@
</div>
</div>
<!-- IMPORT admin/settings/footer.tpl -->
<!-- IMPORT admin/settings/footer.tpl -->

Loading…
Cancel
Save