mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
FEATURE: admins can invite users to groups via the web UI
This commit is contained in:
@ -0,0 +1,52 @@
|
|||||||
|
Discourse.GroupSelectorComponent = Em.Component.extend({
|
||||||
|
placeholder: function(){
|
||||||
|
return I18n.t(this.get("placeholderKey"));
|
||||||
|
}.property("placeholderKey"),
|
||||||
|
|
||||||
|
didInsertElement: function() {
|
||||||
|
var self = this;
|
||||||
|
var selectedGroups;
|
||||||
|
|
||||||
|
self.$('input').autocomplete({
|
||||||
|
allowAny: false,
|
||||||
|
onChangeItems: function(items){
|
||||||
|
selectedGroups = items;
|
||||||
|
self.set("groupNames", items.join(","));
|
||||||
|
},
|
||||||
|
transformComplete: function(g) {
|
||||||
|
return g.name;
|
||||||
|
},
|
||||||
|
dataSource: function(term) {
|
||||||
|
return Discourse.Group.findAll({search: term, ignore_automatic: true}).then(function(groups){
|
||||||
|
if(!selectedGroups){
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups.filter(function(group){
|
||||||
|
return !selectedGroups.any(function(s){return s === group.name});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
template: Discourse.GroupSelectorComponent.templateFunction()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO autocomplete should become an ember component, then we don't need this
|
||||||
|
Discourse.GroupSelectorComponent.reopenClass({
|
||||||
|
templateFunction: function() {
|
||||||
|
this.compiled = this.compiled || Handlebars.compile(
|
||||||
|
"<div class='autocomplete'>" +
|
||||||
|
"<ul>" +
|
||||||
|
"{{#each options}}" +
|
||||||
|
"<li>" +
|
||||||
|
"<a href=''>{{this.name}}</a>" +
|
||||||
|
"</li>" +
|
||||||
|
"{{/each}}" +
|
||||||
|
"</ul>" +
|
||||||
|
"</div>"
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.compiled;
|
||||||
|
}
|
||||||
|
});
|
@ -10,6 +10,10 @@
|
|||||||
export default Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
export default Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||||
modalClass: 'invite',
|
modalClass: 'invite',
|
||||||
|
|
||||||
|
isAdmin: function(){
|
||||||
|
return Discourse.User.currentProp("admin");
|
||||||
|
}.property(),
|
||||||
|
|
||||||
onShow: function(){
|
onShow: function(){
|
||||||
this.set('controllers.modal.modalClass', 'invite-modal');
|
this.set('controllers.modal.modalClass', 'invite-modal');
|
||||||
this.set('emailOrUsername', '');
|
this.set('emailOrUsername', '');
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
**/
|
**/
|
||||||
export default Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
export default Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||||
|
|
||||||
|
isAdmin: function(){
|
||||||
|
return Discourse.User.currentProp("admin");
|
||||||
|
}.property(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Can we submit the form?
|
Can we submit the form?
|
||||||
|
|
||||||
@ -89,8 +93,10 @@ export default Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
|||||||
if (this.get('disabled')) { return; }
|
if (this.get('disabled')) { return; }
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var groupNames = this.get("groupNames");
|
||||||
|
|
||||||
this.setProperties({ saving: true, error: false });
|
this.setProperties({ saving: true, error: false });
|
||||||
this.get('model').createInvite(this.get('email')).then(function() {
|
this.get('model').createInvite(this.get('email'), groupNames).then(function() {
|
||||||
self.setProperties({ saving: false, finished: true });
|
self.setProperties({ saving: false, finished: true });
|
||||||
}).catch(function() {
|
}).catch(function() {
|
||||||
self.setProperties({ saving: false, error: true });
|
self.setProperties({ saving: false, error: true });
|
||||||
|
@ -244,7 +244,6 @@ $.fn.autocomplete = function(options) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// chain to allow multiples
|
// chain to allow multiples
|
||||||
var oldClose = me.data("closeAutocomplete");
|
var oldClose = me.data("closeAutocomplete");
|
||||||
me.data("closeAutocomplete", function() {
|
me.data("closeAutocomplete", function() {
|
||||||
|
@ -72,8 +72,8 @@ Discourse.Group = Discourse.Model.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
Discourse.Group.reopenClass({
|
Discourse.Group.reopenClass({
|
||||||
findAll: function(){
|
findAll: function(opts){
|
||||||
return Discourse.ajax("/admin/groups.json").then(function(groups){
|
return Discourse.ajax("/admin/groups.json", { data: opts }).then(function(groups){
|
||||||
return groups.map(function(g) { return Discourse.Group.create(g); });
|
return groups.map(function(g) { return Discourse.Group.create(g); });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -217,10 +217,10 @@ Discourse.Topic = Discourse.Model.extend({
|
|||||||
@method createInvite
|
@method createInvite
|
||||||
@param {String} emailOrUsername The email or username of the user to be invited
|
@param {String} emailOrUsername The email or username of the user to be invited
|
||||||
**/
|
**/
|
||||||
createInvite: function(emailOrUsername) {
|
createInvite: function(emailOrUsername, groupNames) {
|
||||||
return Discourse.ajax("/t/" + this.get('id') + "/invite", {
|
return Discourse.ajax("/t/" + this.get('id') + "/invite", {
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { user: emailOrUsername }
|
data: { user: emailOrUsername, group_names: groupNames }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -375,10 +375,10 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
@param {String} email The email address of the user to invite to the site
|
@param {String} email The email address of the user to invite to the site
|
||||||
@returns {Promise} the result of the server call
|
@returns {Promise} the result of the server call
|
||||||
**/
|
**/
|
||||||
createInvite: function(email) {
|
createInvite: function(email, groupNames) {
|
||||||
return Discourse.ajax('/invites', {
|
return Discourse.ajax('/invites', {
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {email: email}
|
data: {email: email, group_names: groupNames}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<input class='ember-text-field group-names' type="text" {{bind-attr placeholder="placeholder"}} name="groups">
|
@ -1,4 +1,4 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body invite-modal">
|
||||||
{{#if error}}
|
{{#if error}}
|
||||||
<div class="alert alert-error">
|
<div class="alert alert-error">
|
||||||
<button class="close" data-dismiss="alert">×</button>
|
<button class="close" data-dismiss="alert">×</button>
|
||||||
@ -12,6 +12,11 @@
|
|||||||
|
|
||||||
<label>{{inviteInstructions}}</label>
|
<label>{{inviteInstructions}}</label>
|
||||||
{{textField value=email placeholderKey="topic.invite_reply.email_placeholder"}}
|
{{textField value=email placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||||
|
|
||||||
|
{{#if isAdmin}}
|
||||||
|
<label>{{i18n topic.automatically_add_to_groups}}</label>
|
||||||
|
{{group-selector includeAuto=false groupNames=groupNames placeholderKey="topic.invite_private.group_name"}}
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
class Admin::GroupsController < Admin::AdminController
|
class Admin::GroupsController < Admin::AdminController
|
||||||
def index
|
def index
|
||||||
groups = Group.order(:name).to_a
|
groups = Group.order(:name)
|
||||||
|
if search = params[:search]
|
||||||
|
search = search.to_s
|
||||||
|
groups = groups.where("name ilike ?", "%#{search}%")
|
||||||
|
end
|
||||||
|
if params[:ignore_automatic].to_s == "true"
|
||||||
|
groups = groups.where(automatic: false)
|
||||||
|
end
|
||||||
render_serialized(groups, BasicGroupSerializer)
|
render_serialized(groups, BasicGroupSerializer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -30,9 +30,11 @@ class InvitesController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
params.require(:email)
|
params.require(:email)
|
||||||
|
|
||||||
guardian.ensure_can_invite_to_forum!
|
group_ids = Group.lookup_group_ids(params)
|
||||||
|
|
||||||
if Invite.invite_by_email(params[:email], current_user)
|
guardian.ensure_can_invite_to_forum!(group_ids)
|
||||||
|
|
||||||
|
if Invite.invite_by_email(params[:email], current_user, topic=nil, group_ids)
|
||||||
render json: success_json
|
render json: success_json
|
||||||
else
|
else
|
||||||
render json: failed_json, status: 422
|
render json: failed_json, status: 422
|
||||||
|
@ -203,11 +203,7 @@ class TopicsController < ApplicationController
|
|||||||
|
|
||||||
topic = Topic.find_by(id: params[:topic_id])
|
topic = Topic.find_by(id: params[:topic_id])
|
||||||
|
|
||||||
if group_ids = params[:group_ids]
|
group_ids = Group.lookup_group_ids(params)
|
||||||
group_ids = group_ids.split(",").map(&:to_i)
|
|
||||||
group_ids = Group.where(id: group_ids).pluck(:id)
|
|
||||||
end
|
|
||||||
|
|
||||||
guardian.ensure_can_invite_to!(topic,group_ids)
|
guardian.ensure_can_invite_to!(topic,group_ids)
|
||||||
|
|
||||||
if topic.invite(current_user, username_or_email, group_ids)
|
if topic.invite(current_user, username_or_email, group_ids)
|
||||||
|
@ -164,6 +164,24 @@ class Group < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.lookup_group_ids(opts)
|
||||||
|
if group_ids = opts[:group_ids]
|
||||||
|
group_ids = group_ids.split(",").map(&:to_i)
|
||||||
|
group_ids = Group.where(id: group_ids).pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
group_ids ||= []
|
||||||
|
|
||||||
|
if group_names = opts[:group_names]
|
||||||
|
group_names = group_names.split(",")
|
||||||
|
if group_names.present?
|
||||||
|
group_ids += Group.where(name: group_names).pluck(:id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
group_ids
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def self.user_trust_level_change!(user_id, trust_level)
|
def self.user_trust_level_change!(user_id, trust_level)
|
||||||
name = "trust_level_#{trust_level}".to_sym
|
name = "trust_level_#{trust_level}".to_sym
|
||||||
|
@ -834,6 +834,7 @@ en:
|
|||||||
success_message: 'You successfully flagged this topic.'
|
success_message: 'You successfully flagged this topic.'
|
||||||
|
|
||||||
inviting: "Inviting..."
|
inviting: "Inviting..."
|
||||||
|
automatically_add_to_groups: "Upon registration, automatically add user to groups. (optional, admin only)"
|
||||||
|
|
||||||
invite_private:
|
invite_private:
|
||||||
title: 'Invite to Private Message'
|
title: 'Invite to Private Message'
|
||||||
@ -842,6 +843,7 @@ en:
|
|||||||
action: "Invite"
|
action: "Invite"
|
||||||
success: "Thanks! We've invited that user to participate in this private message."
|
success: "Thanks! We've invited that user to participate in this private message."
|
||||||
error: "Sorry, there was an error inviting that user."
|
error: "Sorry, there was an error inviting that user."
|
||||||
|
group_name: "group name"
|
||||||
|
|
||||||
invite_reply:
|
invite_reply:
|
||||||
title: 'Invite'
|
title: 'Invite'
|
||||||
|
@ -187,13 +187,14 @@ class Guardian
|
|||||||
is_me?(user)
|
is_me?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_invite_to_forum?
|
def can_invite_to_forum?(groups=nil)
|
||||||
authenticated? &&
|
authenticated? &&
|
||||||
!SiteSetting.enable_sso &&
|
!SiteSetting.enable_sso &&
|
||||||
(
|
(
|
||||||
(!SiteSetting.must_approve_users? && @user.has_trust_level?(:regular)) ||
|
(!SiteSetting.must_approve_users? && @user.has_trust_level?(:regular)) ||
|
||||||
is_staff?
|
is_staff?
|
||||||
)
|
) &&
|
||||||
|
(groups.blank? || is_admin?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_invite_to?(object, group_ids=nil)
|
def can_invite_to?(object, group_ids=nil)
|
||||||
|
@ -45,21 +45,20 @@ describe InvitesController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context 'while logged in' do
|
context 'while logged in' do
|
||||||
let!(:user) { log_in }
|
|
||||||
let(:email) { 'jake@adventuretime.ooo' }
|
let(:email) { 'jake@adventuretime.ooo' }
|
||||||
|
|
||||||
it "fails if you can't invite to the forum" do
|
it "fails if you can't invite to the forum" do
|
||||||
Guardian.any_instance.stubs(:can_invite_to_forum?).returns(false)
|
log_in
|
||||||
Invite.expects(:invite_by_email).never
|
|
||||||
post :create, email: email
|
post :create, email: email
|
||||||
response.should_not be_success
|
response.should_not be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
it "delegates to Invite#invite_by_email and returns success if you can invite" do
|
it "allows admins to invite to groups" do
|
||||||
Guardian.any_instance.stubs(:can_invite_to_forum?).returns(true)
|
group = Fabricate(:group)
|
||||||
Invite.expects(:invite_by_email).with(email, user).returns(Invite.new)
|
log_in(:admin)
|
||||||
post :create, email: email
|
post :create, email: email, group_names: group.name
|
||||||
response.should be_success
|
response.should be_success
|
||||||
|
Invite.find_by(email: email).invited_groups.count.should == 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user