mirror of
https://github.com/discourse/discourse.git
synced 2025-05-22 22:43:33 +08:00
Support for inviting to a forum from a user's invite page.
This commit is contained in:
@ -9,6 +9,11 @@
|
|||||||
**/
|
**/
|
||||||
Discourse.InviteController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
Discourse.InviteController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Can we submit the form?
|
||||||
|
|
||||||
|
@property disabled
|
||||||
|
**/
|
||||||
disabled: function() {
|
disabled: function() {
|
||||||
if (this.get('saving')) return true;
|
if (this.get('saving')) return true;
|
||||||
if (this.blank('email')) return true;
|
if (this.blank('email')) return true;
|
||||||
@ -16,30 +21,79 @@ Discourse.InviteController = Discourse.ObjectController.extend(Discourse.ModalFu
|
|||||||
return false;
|
return false;
|
||||||
}.property('email', 'saving'),
|
}.property('email', 'saving'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
The current text for the invite button
|
||||||
|
|
||||||
|
@property buttonTitle
|
||||||
|
**/
|
||||||
buttonTitle: function() {
|
buttonTitle: function() {
|
||||||
if (this.get('saving')) return I18n.t('topic.inviting');
|
if (this.get('saving')) return I18n.t('topic.inviting');
|
||||||
return I18n.t('topic.invite_reply.action');
|
return I18n.t('topic.invite_reply.action');
|
||||||
}.property('saving'),
|
}.property('saving'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
We are inviting to a topic if the model isn't the current user. The current user would
|
||||||
|
mean we are inviting to the forum in general.
|
||||||
|
|
||||||
|
@property invitingToTopic
|
||||||
|
**/
|
||||||
|
invitingToTopic: function() {
|
||||||
|
return this.get('model') !== Discourse.User.current();
|
||||||
|
}.property('model'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
Instructional text for the modal.
|
||||||
|
|
||||||
|
@property inviteInstructions
|
||||||
|
**/
|
||||||
|
inviteInstructions: function() {
|
||||||
|
if (this.get('invitingToTopic')) {
|
||||||
|
return I18n.t('topic.invite_reply.to_topic');
|
||||||
|
} else {
|
||||||
|
return I18n.t('topic.invite_reply.to_forum');
|
||||||
|
}
|
||||||
|
}.property('invitingToTopic'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
The "success" text for when the invite was created.
|
||||||
|
|
||||||
|
@property successMessage
|
||||||
|
**/
|
||||||
successMessage: function() {
|
successMessage: function() {
|
||||||
return I18n.t('topic.invite_reply.success', { email: this.get('email') });
|
return I18n.t('topic.invite_reply.success', { email: this.get('email') });
|
||||||
}.property('email'),
|
}.property('email'),
|
||||||
|
|
||||||
actions: {
|
/**
|
||||||
createInvite: function() {
|
Reset the modal to allow a new user to be invited.
|
||||||
if (this.get('disabled')) return;
|
|
||||||
|
|
||||||
var inviteController = this;
|
@method reset
|
||||||
this.set('saving', true);
|
**/
|
||||||
this.set('error', false);
|
reset: function() {
|
||||||
this.get('model').inviteUser(this.get('email')).then(function() {
|
this.setProperties({
|
||||||
// Success
|
email: null,
|
||||||
inviteController.set('saving', false);
|
error: false,
|
||||||
return inviteController.set('finished', true);
|
saving: false,
|
||||||
}, function() {
|
finished: false
|
||||||
// Failure
|
});
|
||||||
inviteController.set('error', true);
|
},
|
||||||
return inviteController.set('saving', false);
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create the invite and update the modal accordingly.
|
||||||
|
|
||||||
|
@method createInvite
|
||||||
|
**/
|
||||||
|
createInvite: function() {
|
||||||
|
|
||||||
|
if (this.get('disabled')) { return; }
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.setProperties({ saving: true, error: false });
|
||||||
|
this.get('model').createInvite(this.get('email')).then(function() {
|
||||||
|
self.setProperties({ saving: false, finished: true });
|
||||||
|
}).fail(function() {
|
||||||
|
self.setProperties({ saving: false, error: true });
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
@module Discourse
|
@module Discourse
|
||||||
**/
|
**/
|
||||||
Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.ModalFunctionality, {
|
||||||
|
|
||||||
modalClass: 'invite',
|
modalClass: 'invite',
|
||||||
|
|
||||||
onShow: function(){
|
onShow: function(){
|
||||||
@ -26,28 +25,25 @@ Discourse.InvitePrivateController = Discourse.ObjectController.extend(Discourse.
|
|||||||
return I18n.t('topic.invite_private.action');
|
return I18n.t('topic.invite_private.action');
|
||||||
}.property('saving'),
|
}.property('saving'),
|
||||||
|
|
||||||
invite: function() {
|
actions: {
|
||||||
|
invite: function() {
|
||||||
|
if (this.get('disabled')) return;
|
||||||
|
|
||||||
if (this.get('disabled')) return;
|
var self = this;
|
||||||
|
this.setProperties({saving: true, error: false});
|
||||||
|
|
||||||
var invitePrivateController = this;
|
// Invite the user to the private message
|
||||||
this.set('saving', true);
|
this.get('model').createInvite(this.get('emailOrUsername')).then(function(result) {
|
||||||
this.set('error', false);
|
self.setProperties({saving: true, finished: true});
|
||||||
// Invite the user to the private message
|
|
||||||
this.get('content').inviteUser(this.get('emailOrUsername')).then(function(result) {
|
|
||||||
// Success
|
|
||||||
invitePrivateController.set('saving', false);
|
|
||||||
invitePrivateController.set('finished', true);
|
|
||||||
|
|
||||||
if(result && result.user) {
|
if(result && result.user) {
|
||||||
invitePrivateController.get('content.details.allowed_users').pushObject(result.user);
|
self.get('model.details.allowed_users').pushObject(result.user);
|
||||||
}
|
}
|
||||||
}, function() {
|
}).fail(function() {
|
||||||
// Failure
|
self.setProperties({error: true, saving: false});
|
||||||
invitePrivateController.set('error', true);
|
});
|
||||||
invitePrivateController.set('saving', false);
|
return false;
|
||||||
});
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
**/
|
**/
|
||||||
Discourse.UserInvitedController = Ember.ArrayController.extend({
|
Discourse.UserInvitedController = Ember.ArrayController.extend({
|
||||||
|
|
||||||
|
/**
|
||||||
|
Observe the search term box with a debouncer and change the results.
|
||||||
|
|
||||||
|
@observes searchTerm
|
||||||
|
**/
|
||||||
_searchTermChanged: Discourse.debounce(function() {
|
_searchTermChanged: Discourse.debounce(function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
Discourse.Invite.findInvitedBy(self.get('user'), this.get('searchTerm')).then(function (invites) {
|
Discourse.Invite.findInvitedBy(self.get('user'), this.get('searchTerm')).then(function (invites) {
|
||||||
@ -15,20 +20,51 @@ Discourse.UserInvitedController = Ember.ArrayController.extend({
|
|||||||
});
|
});
|
||||||
}, 250).observes('searchTerm'),
|
}, 250).observes('searchTerm'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
The maximum amount of invites that will be displayed in the view
|
||||||
|
|
||||||
|
@property maxInvites
|
||||||
|
**/
|
||||||
maxInvites: function() {
|
maxInvites: function() {
|
||||||
return Discourse.SiteSettings.invites_shown;
|
return Discourse.SiteSettings.invites_shown;
|
||||||
}.property(),
|
}.property(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
Can the currently logged in user invite users to the site
|
||||||
|
|
||||||
|
@property canInviteToForum
|
||||||
|
**/
|
||||||
|
canInviteToForum: function() {
|
||||||
|
return Discourse.User.currentProp('can_invite_to_forum');
|
||||||
|
}.property(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
Should the search filter input box be displayed?
|
||||||
|
|
||||||
|
@property showSearch
|
||||||
|
**/
|
||||||
showSearch: function() {
|
showSearch: function() {
|
||||||
if (Em.isNone(this.get('searchTerm')) && this.get('model.length') === 0) { return false; }
|
if (Em.isNone(this.get('searchTerm')) && this.get('model.length') === 0) { return false; }
|
||||||
return true;
|
return true;
|
||||||
}.property('searchTerm', 'model.length'),
|
}.property('searchTerm', 'model.length'),
|
||||||
|
|
||||||
|
/**
|
||||||
|
Were the results limited by our `maxInvites`
|
||||||
|
|
||||||
|
@property truncated
|
||||||
|
**/
|
||||||
truncated: function() {
|
truncated: function() {
|
||||||
return this.get('model.length') === Discourse.SiteSettings.invites_shown;
|
return this.get('model.length') === Discourse.SiteSettings.invites_shown;
|
||||||
}.property('model.length'),
|
}.property('model.length'),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Rescind a given invite
|
||||||
|
|
||||||
|
@method rescive
|
||||||
|
@param {Discourse.Invite} invite the invite to rescind.
|
||||||
|
**/
|
||||||
rescind: function(invite) {
|
rescind: function(invite) {
|
||||||
invite.rescind();
|
invite.rescind();
|
||||||
return false;
|
return false;
|
||||||
|
@ -196,11 +196,16 @@ Discourse.Topic = Discourse.Model.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invite a user to this topic
|
/**
|
||||||
inviteUser: function(user) {
|
Invite a user to this topic
|
||||||
|
|
||||||
|
@method createInvite
|
||||||
|
@param {String} emailOrUsername The email or username of the user to be invited
|
||||||
|
**/
|
||||||
|
createInvite: function(emailOrUsername) {
|
||||||
return Discourse.ajax("/t/" + this.get('id') + "/invite", {
|
return Discourse.ajax("/t/" + this.get('id') + "/invite", {
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { user: user }
|
data: { user: emailOrUsername }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -282,13 +282,27 @@ Discourse.User = Discourse.Model.extend({
|
|||||||
Determines whether the current user is allowed to upload a file.
|
Determines whether the current user is allowed to upload a file.
|
||||||
|
|
||||||
@method isAllowedToUploadAFile
|
@method isAllowedToUploadAFile
|
||||||
@param {string} type The type of the upload (image, attachment)
|
@param {String} type The type of the upload (image, attachment)
|
||||||
@returns true if the current user is allowed to upload a file
|
@returns true if the current user is allowed to upload a file
|
||||||
**/
|
**/
|
||||||
isAllowedToUploadAFile: function(type) {
|
isAllowedToUploadAFile: function(type) {
|
||||||
return this.get('staff') ||
|
return this.get('staff') ||
|
||||||
this.get('trust_level') > 0 ||
|
this.get('trust_level') > 0 ||
|
||||||
Discourse.SiteSettings['newuser_max_' + type + 's'] > 0;
|
Discourse.SiteSettings['newuser_max_' + type + 's'] > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
Invite a user to the site
|
||||||
|
|
||||||
|
@method createInvite
|
||||||
|
@param {String} email The email address of the user to invite to the site
|
||||||
|
@returns {Promise} the result of the server call
|
||||||
|
**/
|
||||||
|
createInvite: function(email) {
|
||||||
|
return Discourse.ajax('/invites', {
|
||||||
|
type: 'POST',
|
||||||
|
data: {email: email}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -36,12 +36,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
|||||||
|
|
||||||
showInvite: function() {
|
showInvite: function() {
|
||||||
Discourse.Route.showModal(this, 'invite', this.modelFor('topic'));
|
Discourse.Route.showModal(this, 'invite', this.modelFor('topic'));
|
||||||
this.controllerFor('invite').setProperties({
|
this.controllerFor('invite').reset();
|
||||||
email: null,
|
|
||||||
error: false,
|
|
||||||
saving: false,
|
|
||||||
finished: false
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showPrivateInvite: function() {
|
showPrivateInvite: function() {
|
||||||
|
@ -22,6 +22,19 @@ Discourse.UserInvitedRoute = Discourse.Route.extend({
|
|||||||
searchTerm: ''
|
searchTerm: ''
|
||||||
});
|
});
|
||||||
this.controllerFor('user').set('indexStream', false);
|
this.controllerFor('user').set('indexStream', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
/**
|
||||||
|
Shows the invite modal to invite users to the forum.
|
||||||
|
|
||||||
|
@method showInvite
|
||||||
|
**/
|
||||||
|
showInvite: function() {
|
||||||
|
Discourse.Route.showModal(this, 'invite', Discourse.User.current());
|
||||||
|
this.controllerFor('invite').reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
@ -9,7 +9,8 @@
|
|||||||
{{#if finished}}
|
{{#if finished}}
|
||||||
{{{successMessage}}}
|
{{{successMessage}}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<label>{{i18n topic.invite_reply.email}}</label>
|
|
||||||
|
<label>{{inviteInstructions}}</label>
|
||||||
{{textField value=email placeholderKey="topic.invite_reply.email_placeholder"}}
|
{{textField value=email placeholderKey="topic.invite_reply.email_placeholder"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
<h2>{{i18n user.invited.title}}</h2>
|
<h2>{{i18n user.invited.title}}</h2>
|
||||||
|
|
||||||
|
{{#if canInviteToForum}}
|
||||||
|
<button {{action showInvite}} class='btn right'>{{i18n user.invited.create}}</button>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if showSearch}}
|
{{#if showSearch}}
|
||||||
<form>
|
<form>
|
||||||
{{textField value=searchTerm placeholderKey="user.invited.search"}}
|
{{textField value=searchTerm placeholderKey="user.invited.search"}}
|
||||||
|
@ -8,12 +8,18 @@
|
|||||||
**/
|
**/
|
||||||
Discourse.InviteView = Discourse.ModalBodyView.extend({
|
Discourse.InviteView = Discourse.ModalBodyView.extend({
|
||||||
templateName: 'modal/invite',
|
templateName: 'modal/invite',
|
||||||
title: I18n.t('topic.invite_reply.title'),
|
|
||||||
|
|
||||||
|
title: function() {
|
||||||
|
if (this.get('controller.invitingToTopic')) {
|
||||||
|
return I18n.t('topic.invite_reply.title');
|
||||||
|
} else {
|
||||||
|
return I18n.t('user.invited.create');
|
||||||
|
}
|
||||||
|
}.property('controller.invitingToTopic'),
|
||||||
|
|
||||||
keyUp: function(e) {
|
keyUp: function(e) {
|
||||||
// Add the invitee if they hit enter
|
// Add the invitee if they hit enter
|
||||||
if (e.keyCode === 13) { this.get('controller').createInvite(); }
|
if (e.keyCode === 13) { this.get('controller').send('createInvite'); }
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +108,10 @@
|
|||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.btn.right {
|
||||||
|
float: right
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def preload_current_user_data
|
def preload_current_user_data
|
||||||
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, root: false)))
|
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)))
|
||||||
serializer = ActiveModel::ArraySerializer.new(TopicTrackingState.report([current_user.id]), each_serializer: TopicTrackingStateSerializer)
|
serializer = ActiveModel::ArraySerializer.new(TopicTrackingState.report([current_user.id]), each_serializer: TopicTrackingStateSerializer)
|
||||||
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,7 @@ class InvitesController < ApplicationController
|
|||||||
skip_before_filter :check_xhr
|
skip_before_filter :check_xhr
|
||||||
skip_before_filter :redirect_to_login_if_required
|
skip_before_filter :redirect_to_login_if_required
|
||||||
|
|
||||||
before_filter :ensure_logged_in, only: [:destroy]
|
before_filter :ensure_logged_in, only: [:destroy, :create]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
invite = Invite.where(invite_key: params[:id]).first
|
invite = Invite.where(invite_key: params[:id]).first
|
||||||
@ -27,6 +27,18 @@ class InvitesController < ApplicationController
|
|||||||
redirect_to "/"
|
redirect_to "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
params.require(:email)
|
||||||
|
|
||||||
|
guardian.ensure_can_invite_to_forum!
|
||||||
|
|
||||||
|
if Invite.invite_by_email(params[:email], current_user)
|
||||||
|
render json: success_json
|
||||||
|
else
|
||||||
|
render json: failed_json, status: 422
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
params.require(:email)
|
params.require(:email)
|
||||||
|
|
||||||
|
@ -8,11 +8,19 @@ class InviteMailer < ActionMailer::Base
|
|||||||
first_topic = invite.topics.order(:created_at).first
|
first_topic = invite.topics.order(:created_at).first
|
||||||
|
|
||||||
# If they were invited to a topic
|
# If they were invited to a topic
|
||||||
build_email(invite.email,
|
if first_topic.present?
|
||||||
template: 'invite_mailer',
|
build_email(invite.email,
|
||||||
invitee_name: invite.invited_by.username,
|
template: 'invite_mailer',
|
||||||
invite_link: "#{Discourse.base_url}/invites/#{invite.invite_key}",
|
invitee_name: invite.invited_by.username,
|
||||||
topic_title: first_topic.try(:title))
|
invite_link: "#{Discourse.base_url}/invites/#{invite.invite_key}",
|
||||||
|
topic_title: first_topic.try(:title))
|
||||||
|
else
|
||||||
|
build_email(invite.email,
|
||||||
|
template: 'invite_forum_mailer',
|
||||||
|
invitee_name: invite.invited_by.username,
|
||||||
|
invite_link: "#{Discourse.base_url}/invites/#{invite.invite_key}")
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -44,6 +44,30 @@ class Invite < ActiveRecord::Base
|
|||||||
InviteRedeemer.new(self).redeem unless expired? || destroyed?
|
InviteRedeemer.new(self).redeem unless expired? || destroyed?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Create an invite for a user, supplying an optional topic
|
||||||
|
#
|
||||||
|
# Return the previously existing invite if already exists. Returns nil if the invite can't be created.
|
||||||
|
def self.invite_by_email(email, invited_by, topic=nil)
|
||||||
|
lower_email = Email.downcase(email)
|
||||||
|
invite = Invite.with_deleted.where('invited_by_id = ? and email = ?', invited_by.id, lower_email).first
|
||||||
|
|
||||||
|
if invite.blank?
|
||||||
|
invite = Invite.create(invited_by: invited_by, email: lower_email)
|
||||||
|
unless invite.valid?
|
||||||
|
topic.grant_permission_to_user(lower_email) if topic.present? && topic.email_already_exists_for?(invite)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Recover deleted invites if we invite them again
|
||||||
|
invite.recover! if invite.deleted_at.present?
|
||||||
|
|
||||||
|
topic.topic_invites.create(invite_id: invite.id) if topic.present?
|
||||||
|
Jobs.enqueue(:invite_email, invite_id: invite.id)
|
||||||
|
invite
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
@ -443,28 +443,8 @@ class Topic < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Invite a user by email and return the invite. Return the previously existing invite
|
|
||||||
# if already exists. Returns nil if the invite can't be created.
|
|
||||||
def invite_by_email(invited_by, email)
|
def invite_by_email(invited_by, email)
|
||||||
lower_email = Email.downcase(email)
|
Invite.invite_by_email(email, invited_by, self)
|
||||||
invite = Invite.with_deleted.where('invited_by_id = ? and email = ?', invited_by.id, lower_email).first
|
|
||||||
|
|
||||||
if invite.blank?
|
|
||||||
invite = Invite.create(invited_by: invited_by, email: lower_email)
|
|
||||||
unless invite.valid?
|
|
||||||
|
|
||||||
grant_permission_to_user(lower_email) if email_already_exists_for?(invite)
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Recover deleted invites if we invite them again
|
|
||||||
invite.recover if invite.deleted_at.present?
|
|
||||||
|
|
||||||
topic_invites.create(invite_id: invite.id)
|
|
||||||
Jobs.enqueue(:invite_email, invite_id: invite.id)
|
|
||||||
invite
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_already_exists_for?(invite)
|
def email_already_exists_for?(invite)
|
||||||
|
@ -14,7 +14,8 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||||||
:external_links_in_new_tab,
|
:external_links_in_new_tab,
|
||||||
:dynamic_favicon,
|
:dynamic_favicon,
|
||||||
:trust_level,
|
:trust_level,
|
||||||
:can_edit
|
:can_edit,
|
||||||
|
:can_invite_to_forum
|
||||||
|
|
||||||
def include_site_flagged_posts_count?
|
def include_site_flagged_posts_count?
|
||||||
object.staff?
|
object.staff?
|
||||||
@ -36,4 +37,12 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_invite_to_forum
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_can_invite_to_forum?
|
||||||
|
scope.can_invite_to_forum?
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -339,6 +339,7 @@ en:
|
|||||||
time_read: "Read Time"
|
time_read: "Read Time"
|
||||||
days_visited: "Days Visited"
|
days_visited: "Days Visited"
|
||||||
account_age_days: "Account age in days"
|
account_age_days: "Account age in days"
|
||||||
|
create: "Invite Friends to this Forum"
|
||||||
|
|
||||||
password:
|
password:
|
||||||
title: "Password"
|
title: "Password"
|
||||||
@ -736,7 +737,9 @@ en:
|
|||||||
title: 'Invite Friends to Reply'
|
title: 'Invite Friends to Reply'
|
||||||
action: 'Email Invite'
|
action: 'Email Invite'
|
||||||
help: 'send invitations to friends so they can reply to this topic with a single click'
|
help: 'send invitations to friends so they can reply to this topic with a single click'
|
||||||
email: "We'll send your friend a brief email allowing them to immediately reply to this topic by clicking a link, no login required."
|
to_topic: "We'll send your friend a brief email allowing them to immediately reply to this topic by clicking a link, no login required."
|
||||||
|
to_forum: "We'll send your friend a brief email allowing them to join the forum by clicking a link."
|
||||||
|
|
||||||
email_placeholder: 'email address'
|
email_placeholder: 'email address'
|
||||||
success: "Thanks! We mailed out an invitation to <b>{{email}}</b>. We'll let you know when they redeem your invitation. Check the invitations tab on your user page to keep track of who you've invited."
|
success: "Thanks! We mailed out an invitation to <b>{{email}}</b>. We'll let you know when they redeem your invitation. Check the invitations tab on your user page to keep track of who you've invited."
|
||||||
error: "Sorry, we couldn't invite that person. Perhaps they are already a user?"
|
error: "Sorry, we couldn't invite that person. Perhaps they are already a user?"
|
||||||
|
@ -813,6 +813,19 @@ en:
|
|||||||
|
|
||||||
[1]: %{invite_link}
|
[1]: %{invite_link}
|
||||||
|
|
||||||
|
invite_forum_mailer:
|
||||||
|
subject_template: "[%{site_name}] %{invitee_name} invited you to join %{site_name}"
|
||||||
|
text_body_template: |
|
||||||
|
%{invitee_name} invited you to %{site_name}.
|
||||||
|
|
||||||
|
If you're interested, click the link below to join:
|
||||||
|
|
||||||
|
[Visit %{site_name}][1]
|
||||||
|
|
||||||
|
You were invited by a trusted user, so you'll be able to join immediately, without needing to log in.
|
||||||
|
|
||||||
|
[1]: %{invite_link}
|
||||||
|
|
||||||
test_mailer:
|
test_mailer:
|
||||||
subject_template: "[%{site_name}] Email Deliverability Test"
|
subject_template: "[%{site_name}] Email Deliverability Test"
|
||||||
text_body_template: |
|
text_body_template: |
|
||||||
|
@ -191,15 +191,18 @@ class Guardian
|
|||||||
is_me?(user)
|
is_me?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_invite_to?(object)
|
def can_invite_to_forum?
|
||||||
authenticated? &&
|
authenticated? &&
|
||||||
can_see?(object) &&
|
|
||||||
(
|
(
|
||||||
(!SiteSetting.must_approve_users? && @user.has_trust_level?(:regular)) ||
|
(!SiteSetting.must_approve_users? && @user.has_trust_level?(:regular)) ||
|
||||||
is_staff?
|
is_staff?
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_invite_to?(object)
|
||||||
|
can_see?(object) && can_invite_to_forum?
|
||||||
|
end
|
||||||
|
|
||||||
def can_see_deleted_posts?
|
def can_see_deleted_posts?
|
||||||
is_staff?
|
is_staff?
|
||||||
end
|
end
|
||||||
|
@ -181,6 +181,26 @@ describe Guardian do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'can_invite_to_forum?' do
|
||||||
|
let(:user) { Fabricate.build(:user) }
|
||||||
|
let(:moderator) { Fabricate.build(:moderator) }
|
||||||
|
|
||||||
|
it "doesn't allow anonymous users to invite" do
|
||||||
|
Guardian.new.can_invite_to_forum?.should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when the site requires approving users and is mod' do
|
||||||
|
SiteSetting.expects(:must_approve_users?).returns(true)
|
||||||
|
Guardian.new(moderator).can_invite_to_forum?.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when the site requires approving users and is regular' do
|
||||||
|
SiteSetting.expects(:must_approve_users?).returns(true)
|
||||||
|
Guardian.new(user).can_invite_to_forum?.should be_false
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe 'can_invite_to?' do
|
describe 'can_invite_to?' do
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:user) { topic.user }
|
let(:user) { topic.user }
|
||||||
@ -198,7 +218,7 @@ describe Guardian do
|
|||||||
Guardian.new(moderator).can_invite_to?(topic).should be_true
|
Guardian.new(moderator).can_invite_to?(topic).should be_true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns true when the site requires approving users and is regular' do
|
it 'returns false when the site requires approving users and is regular' do
|
||||||
SiteSetting.expects(:must_approve_users?).returns(true)
|
SiteSetting.expects(:must_approve_users?).returns(true)
|
||||||
Guardian.new(coding_horror).can_invite_to?(topic).should be_false
|
Guardian.new(coding_horror).can_invite_to?(topic).should be_false
|
||||||
end
|
end
|
||||||
|
@ -35,13 +35,39 @@ describe InvitesController do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context '.create' do
|
||||||
|
it 'requires you to be logged in' do
|
||||||
|
lambda {
|
||||||
|
post :create, email: 'jake@adventuretime.ooo'
|
||||||
|
}.should raise_error(Discourse::NotLoggedIn)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'while logged in' do
|
||||||
|
let!(:user) { log_in }
|
||||||
|
let(:email) { 'jake@adventuretime.ooo' }
|
||||||
|
|
||||||
|
it "fails if you can't invite to the forum" do
|
||||||
|
Guardian.any_instance.stubs(:can_invite_to_forum?).returns(false)
|
||||||
|
Invite.expects(:invite_by_email).never
|
||||||
|
post :create, email: email
|
||||||
|
response.should_not be_success
|
||||||
|
end
|
||||||
|
|
||||||
|
it "delegates to Invite#invite_by_email and returns success if you can invite" do
|
||||||
|
Guardian.any_instance.stubs(:can_invite_to_forum?).returns(true)
|
||||||
|
Invite.expects(:invite_by_email).with(email, user).returns(Invite.new)
|
||||||
|
post :create, email: email
|
||||||
|
response.should be_success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context '.show' do
|
context '.show' do
|
||||||
|
|
||||||
context 'with an invalid invite id' do
|
context 'with an invalid invite id' do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
get :show, id: "doesn't exist"
|
get :show, id: "doesn't exist"
|
||||||
end
|
end
|
||||||
@ -53,7 +79,6 @@ describe InvitesController do
|
|||||||
it "should not change the session" do
|
it "should not change the session" do
|
||||||
session[:current_user_id].should be_blank
|
session[:current_user_id].should be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a deleted invite' do
|
context 'with a deleted invite' do
|
||||||
@ -71,10 +96,8 @@ describe InvitesController do
|
|||||||
it "should not change the session" do
|
it "should not change the session" do
|
||||||
session[:current_user_id].should be_blank
|
session[:current_user_id].should be_blank
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
context 'with a valid invite id' do
|
context 'with a valid invite id' do
|
||||||
let(:topic) { Fabricate(:topic) }
|
let(:topic) { Fabricate(:topic) }
|
||||||
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
|
let(:invite) { topic.invite_by_email(topic.user, "iceking@adventuretime.ooo") }
|
||||||
|
Reference in New Issue
Block a user