FEATURE: Support an end date for user silencing
This commit is contained in:
parent
52480d554a
commit
971e302ff2
|
@ -70,7 +70,9 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||||
unsuspend() {
|
unsuspend() {
|
||||||
this.get("model").unsuspend().catch(popupAjaxError);
|
this.get("model").unsuspend().catch(popupAjaxError);
|
||||||
},
|
},
|
||||||
|
showSilenceModal() {
|
||||||
|
this.get('adminTools').showSilenceModal(this.get('model'));
|
||||||
|
},
|
||||||
|
|
||||||
toggleUsernameEdit() {
|
toggleUsernameEdit() {
|
||||||
this.set('userUsernameValue', this.get('model.username'));
|
this.set('userUsernameValue', this.get('model.username'));
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import ModalFunctionality from 'discourse/mixins/modal-functionality';
|
||||||
|
import computed from 'ember-addons/ember-computed-decorators';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
|
export default Ember.Controller.extend(ModalFunctionality, {
|
||||||
|
silenceUntil: null,
|
||||||
|
reason: null,
|
||||||
|
message: null,
|
||||||
|
silencing: false,
|
||||||
|
user: null,
|
||||||
|
post: null,
|
||||||
|
successCallback: null,
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
this.setProperties({
|
||||||
|
silenceUntil: null,
|
||||||
|
reason: null,
|
||||||
|
message: null,
|
||||||
|
silencing: false,
|
||||||
|
loadingUser: true,
|
||||||
|
post: null,
|
||||||
|
successCallback: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
@computed('silenceUntil', 'reason', 'silencing')
|
||||||
|
submitDisabled(silenceUntil, reason, silencing) {
|
||||||
|
return (silencing || Ember.isEmpty(silenceUntil) || !reason || reason.length < 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
silence() {
|
||||||
|
if (this.get('submitDisabled')) { return; }
|
||||||
|
|
||||||
|
this.set('silencing', true);
|
||||||
|
this.get('user').silence({
|
||||||
|
silenced_till: this.get('silenceUntil'),
|
||||||
|
reason: this.get('reason'),
|
||||||
|
message: this.get('message'),
|
||||||
|
post_id: this.get('post.id')
|
||||||
|
}).then(result => {
|
||||||
|
this.send('closeModal');
|
||||||
|
let callback = this.get('successCallback');
|
||||||
|
if (callback) {
|
||||||
|
callback(result);
|
||||||
|
}
|
||||||
|
}).catch(popupAjaxError).finally(() => this.set('silencing', false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -8,6 +8,8 @@ import Group from 'discourse/models/group';
|
||||||
import TL3Requirements from 'admin/models/tl3-requirements';
|
import TL3Requirements from 'admin/models/tl3-requirements';
|
||||||
import { userPath } from 'discourse/lib/url';
|
import { userPath } from 'discourse/lib/url';
|
||||||
|
|
||||||
|
const wrapAdmin = user => user ? AdminUser.create(user) : null;
|
||||||
|
|
||||||
const AdminUser = Discourse.User.extend({
|
const AdminUser = Discourse.User.extend({
|
||||||
adminUserView: true,
|
adminUserView: true,
|
||||||
customGroups: Ember.computed.filter("groups", g => !g.automatic && Group.create(g)),
|
customGroups: Ember.computed.filter("groups", g => !g.automatic && Group.create(g)),
|
||||||
|
@ -232,6 +234,7 @@ const AdminUser = Discourse.User.extend({
|
||||||
}.property('trust_level'),
|
}.property('trust_level'),
|
||||||
|
|
||||||
isSuspended: Em.computed.equal('suspended', true),
|
isSuspended: Em.computed.equal('suspended', true),
|
||||||
|
isSilenced: Ember.computed.equal('silenced', true),
|
||||||
canSuspend: Em.computed.not('staff'),
|
canSuspend: Em.computed.not('staff'),
|
||||||
|
|
||||||
suspendDuration: function() {
|
suspendDuration: function() {
|
||||||
|
@ -301,44 +304,36 @@ const AdminUser = Discourse.User.extend({
|
||||||
|
|
||||||
unsilence() {
|
unsilence() {
|
||||||
this.set('silencingUser', true);
|
this.set('silencingUser', true);
|
||||||
return ajax('/admin/users/' + this.id + '/unsilence', {
|
|
||||||
|
return ajax(`/admin/users/${this.id}/unsilence`, {
|
||||||
type: 'PUT'
|
type: 'PUT'
|
||||||
}).then(function() {
|
}).then(result => {
|
||||||
window.location.reload();
|
this.setProperties(result.unsilence);
|
||||||
}).catch(function(e) {
|
}).catch(e => {
|
||||||
var error = I18n.t('admin.user.unsilence_failed', { error: "http: " + e.status + " - " + e.body });
|
let error = I18n.t('admin.user.unsilence_failed', {
|
||||||
|
error: `http: ${e.status} - ${e.body}`
|
||||||
|
});
|
||||||
bootbox.alert(error);
|
bootbox.alert(error);
|
||||||
|
}).finally(() => {
|
||||||
|
this.set('silencingUser', false);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
silence() {
|
silence(data) {
|
||||||
const user = this,
|
this.set('silencingUser', true);
|
||||||
message = I18n.t("admin.user.silence_confirm");
|
return ajax(`/admin/users/${this.id}/silence`, {
|
||||||
|
type: 'PUT',
|
||||||
const performSilence = function() {
|
data
|
||||||
user.set('silencingUser', true);
|
}).then(result => {
|
||||||
return ajax('/admin/users/' + user.id + '/silence', {
|
this.setProperties(result.silence);
|
||||||
type: 'PUT'
|
}).catch(e => {
|
||||||
}).then(function() {
|
let error = I18n.t('admin.user.silence_failed', {
|
||||||
window.location.reload();
|
error: `http: ${e.status} - ${e.body}`
|
||||||
}).catch(function(e) {
|
|
||||||
var error = I18n.t('admin.user.silence_failed', { error: "http: " + e.status + " - " + e.body });
|
|
||||||
bootbox.alert(error);
|
|
||||||
user.set('silencingUser', false);
|
|
||||||
});
|
});
|
||||||
};
|
bootbox.alert(error);
|
||||||
|
}).finally(() => {
|
||||||
const buttons = [{
|
this.set('silencingUser', false);
|
||||||
"label": I18n.t("composer.cancel"),
|
});
|
||||||
"class": "cancel",
|
|
||||||
"link": true
|
|
||||||
}, {
|
|
||||||
"label": `${iconHTML('exclamation-triangle')} ` + I18n.t('admin.user.silence_accept'),
|
|
||||||
"class": "btn btn-danger",
|
|
||||||
"callback": function() { performSilence(); }
|
|
||||||
}];
|
|
||||||
|
|
||||||
bootbox.dialog(message, buttons, { "classes": "delete-user-modal" });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
sendActivationEmail() {
|
sendActivationEmail() {
|
||||||
|
@ -475,17 +470,14 @@ const AdminUser = Discourse.User.extend({
|
||||||
}
|
}
|
||||||
}.property('tl3_requirements'),
|
}.property('tl3_requirements'),
|
||||||
|
|
||||||
suspendedBy: function() {
|
@computed('suspended_by')
|
||||||
if (this.get('suspended_by')) {
|
suspendedBy: wrapAdmin,
|
||||||
return AdminUser.create(this.get('suspended_by'));
|
|
||||||
}
|
|
||||||
}.property('suspended_by'),
|
|
||||||
|
|
||||||
approvedBy: function() {
|
@computed('silenced_by')
|
||||||
if (this.get('approved_by')) {
|
silencedBy: wrapAdmin,
|
||||||
return AdminUser.create(this.get('approved_by'));
|
|
||||||
}
|
@computed('approved_by')
|
||||||
}.property('approved_by')
|
approvedBy: wrapAdmin,
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,12 @@ export default Ember.Service.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
showSuspendModal(user, opts) {
|
_showControlModal(type, user, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
let controller = showModal('admin-suspend-user', {
|
let controller = showModal(`admin-${type}-user`, {
|
||||||
admin: true,
|
admin: true,
|
||||||
modalClass: 'suspend-user-modal'
|
modalClass: `${type}-user-modal`
|
||||||
});
|
});
|
||||||
if (opts.post) {
|
if (opts.post) {
|
||||||
controller.set('post', opts.post);
|
controller.set('post', opts.post);
|
||||||
|
@ -44,6 +44,14 @@ export default Ember.Service.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showSilenceModal(user, opts) {
|
||||||
|
this._showControlModal('silence', user, opts);
|
||||||
|
},
|
||||||
|
|
||||||
|
showSuspendModal(user, opts) {
|
||||||
|
this._showControlModal('suspend', user, opts);
|
||||||
|
},
|
||||||
|
|
||||||
_deleteSpammer(adminUser) {
|
_deleteSpammer(adminUser) {
|
||||||
return adminUser.checkEmail().then(() => {
|
return adminUser.checkEmail().then(() => {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
{{#d-modal-body title="admin.user.silence_modal_title"}}
|
||||||
|
{{#conditional-loading-spinner condition=loadingUser}}
|
||||||
|
|
||||||
|
<div class='until-controls'>
|
||||||
|
<label>
|
||||||
|
{{future-date-input
|
||||||
|
class="silence-until"
|
||||||
|
label="admin.user.silence_duration"
|
||||||
|
includeFarFuture=true
|
||||||
|
input=silenceUntil}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='reason-controls'>
|
||||||
|
<label>
|
||||||
|
<div class='silence-reason-label'>
|
||||||
|
{{{i18n 'admin.user.silence_reason_label'}}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{text-field
|
||||||
|
value=reason
|
||||||
|
class="silence-reason"
|
||||||
|
placeholderKey="admin.user.silence_reason_placeholder"}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<div class='silence-message-label'>
|
||||||
|
{{i18n "admin.user.silence_message"}}
|
||||||
|
</div>
|
||||||
|
{{textarea
|
||||||
|
value=message
|
||||||
|
class="silence-message"
|
||||||
|
placeholder=(i18n "admin.user.silence_message_placeholder")}}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{{/conditional-loading-spinner}}
|
||||||
|
|
||||||
|
{{/d-modal-body}}
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{d-button
|
||||||
|
class="btn-danger perform-silence"
|
||||||
|
action="silence"
|
||||||
|
disabled=submitDisabled
|
||||||
|
icon="microphone-slash"
|
||||||
|
label="admin.user.silence"}}
|
||||||
|
{{d-modal-cancel close=(action "closeModal")}}
|
||||||
|
{{conditional-loading-spinner condition=loading size="small"}}
|
||||||
|
</div>
|
|
@ -349,19 +349,49 @@
|
||||||
|
|
||||||
<div class="display-row {{if model.silenced 'highlight-danger'}}">
|
<div class="display-row {{if model.silenced 'highlight-danger'}}">
|
||||||
<div class='field'>{{i18n 'admin.user.silenced'}}</div>
|
<div class='field'>{{i18n 'admin.user.silenced'}}</div>
|
||||||
<div class='value'>{{i18n-yes-no model.silenced}}</div>
|
<div class='value'>
|
||||||
|
{{i18n-yes-no model.silenced}}
|
||||||
|
{{#if model.isSilenced}}
|
||||||
|
{{#unless model.silencedForever}}
|
||||||
|
{{i18n "admin.user.suspended_until" until=model.silencedTillDate}}
|
||||||
|
{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
<div class='controls'>
|
<div class='controls'>
|
||||||
{{#conditional-loading-spinner size="small" condition=model.silencingUser}}
|
{{#conditional-loading-spinner size="small" condition=model.silencingUser}}
|
||||||
{{#if model.silenced}}
|
{{#if model.silenced}}
|
||||||
{{d-button action="unsilence" icon="thumbs-o-up" label="admin.user.unsilence"}}
|
{{d-button
|
||||||
|
class="btn-danger unsilence-user"
|
||||||
|
action="unsilence"
|
||||||
|
icon="microphone-slash"
|
||||||
|
label="admin.user.unsilence"}}
|
||||||
{{i18n 'admin.user.silence_explanation'}}
|
{{i18n 'admin.user.silence_explanation'}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{d-button action="silence" icon="ban" label="admin.user.silence"}}
|
{{d-button
|
||||||
|
class="btn-danger silence-user"
|
||||||
|
action=(action "showSilenceModal")
|
||||||
|
icon="microphone-slash"
|
||||||
|
label="admin.user.silence"}}
|
||||||
{{i18n 'admin.user.silence_explanation'}}
|
{{i18n 'admin.user.silence_explanation'}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/conditional-loading-spinner}}
|
{{/conditional-loading-spinner}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{#if model.isSilenced}}
|
||||||
|
<div class='display-row highlight-danger silence-info'>
|
||||||
|
<div class='field'>{{i18n 'admin.user.silenced_by'}}</div>
|
||||||
|
<div class='value'>
|
||||||
|
{{#link-to 'adminUser' silencedBy}}{{avatar model.silencedBy imageSize="tiny"}}{{/link-to}}
|
||||||
|
{{#link-to 'adminUser' silencedBy}}{{model.silencedBy.username}}{{/link-to}}
|
||||||
|
</div>
|
||||||
|
<div class='controls'>
|
||||||
|
<b>{{i18n 'admin.user.silence_reason'}}</b>:
|
||||||
|
{{model.silence_reason}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{#if currentUser.admin}}
|
{{#if currentUser.admin}}
|
||||||
|
|
|
@ -16,6 +16,8 @@ import PreloadStore from 'preload-store';
|
||||||
import { defaultHomepage } from 'discourse/lib/utilities';
|
import { defaultHomepage } from 'discourse/lib/utilities';
|
||||||
import { userPath } from 'discourse/lib/url';
|
import { userPath } from 'discourse/lib/url';
|
||||||
|
|
||||||
|
const isForever = dt => moment().diff(dt, 'years') < -500;
|
||||||
|
|
||||||
const User = RestModel.extend({
|
const User = RestModel.extend({
|
||||||
|
|
||||||
hasPMs: Em.computed.gt("private_messages_stats.all", 0),
|
hasPMs: Em.computed.gt("private_messages_stats.all", 0),
|
||||||
|
@ -178,14 +180,16 @@ const User = RestModel.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("suspended_till")
|
@computed("suspended_till")
|
||||||
suspendedForever(suspendedTill) {
|
suspendedForever: isForever,
|
||||||
return moment().diff(suspendedTill, 'years') < -500;
|
|
||||||
},
|
@computed("silenced_till")
|
||||||
|
silencedForever: isForever,
|
||||||
|
|
||||||
@computed("suspended_till")
|
@computed("suspended_till")
|
||||||
suspendedTillDate(suspendedTill) {
|
suspendedTillDate: longDate,
|
||||||
return longDate(suspendedTill);
|
|
||||||
},
|
@computed("silenced_till")
|
||||||
|
silencedTillDate: longDate,
|
||||||
|
|
||||||
changeUsername(new_username) {
|
changeUsername(new_username) {
|
||||||
return ajax(userPath(`${this.get('username_lower')}/preferences/username`), {
|
return ajax(userPath(`${this.get('username_lower')}/preferences/username`), {
|
||||||
|
|
|
@ -274,14 +274,48 @@ class Admin::UsersController < Admin::AdminController
|
||||||
|
|
||||||
def silence
|
def silence
|
||||||
guardian.ensure_can_silence_user! @user
|
guardian.ensure_can_silence_user! @user
|
||||||
UserSilencer.silence(@user, current_user, keep_posts: true)
|
|
||||||
render body: nil
|
message = params[:message]
|
||||||
|
|
||||||
|
silencer = UserSilencer.new(
|
||||||
|
@user,
|
||||||
|
current_user,
|
||||||
|
silenced_till: params[:silenced_till],
|
||||||
|
reason: params[:reason],
|
||||||
|
context: message,
|
||||||
|
keep_posts: true
|
||||||
|
)
|
||||||
|
if silencer.silence && message.present?
|
||||||
|
Jobs.enqueue(
|
||||||
|
:critical_user_email,
|
||||||
|
type: :account_silenced,
|
||||||
|
user_id: @user.id,
|
||||||
|
user_history_id: silencer.user_history.id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
render_json_dump(
|
||||||
|
silence: {
|
||||||
|
silenced: true,
|
||||||
|
silence_reason: params[:reason],
|
||||||
|
silenced_till: @user.silenced_till,
|
||||||
|
suspended_at: @user.silenced_at
|
||||||
|
}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsilence
|
def unsilence
|
||||||
guardian.ensure_can_unsilence_user! @user
|
guardian.ensure_can_unsilence_user! @user
|
||||||
UserSilencer.unsilence(@user, current_user)
|
UserSilencer.unsilence(@user, current_user)
|
||||||
render body: nil
|
|
||||||
|
render_json_dump(
|
||||||
|
unsilence: {
|
||||||
|
silenced: false,
|
||||||
|
silence_reason: nil,
|
||||||
|
silenced_till: nil,
|
||||||
|
suspended_at: nil
|
||||||
|
}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject_bulk
|
def reject_bulk
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Jobs
|
||||||
|
|
||||||
HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new(
|
HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new(
|
||||||
user_archive: ['topic_title', 'category', 'sub_category', 'is_pm', 'post', 'like_count', 'reply_count', 'url', 'created_at'],
|
user_archive: ['topic_title', 'category', 'sub_category', 'is_pm', 'post', 'like_count', 'reply_count', 'url', 'created_at'],
|
||||||
user_list: ['id', 'name', 'username', 'email', 'title', 'created_at', 'last_seen_at', 'last_posted_at', 'last_emailed_at', 'trust_level', 'approved', 'suspended_at', 'suspended_till', 'silenced', 'active', 'admin', 'moderator', 'ip_address', 'staged'],
|
user_list: ['id', 'name', 'username', 'email', 'title', 'created_at', 'last_seen_at', 'last_posted_at', 'last_emailed_at', 'trust_level', 'approved', 'suspended_at', 'suspended_till', 'silenced_till', 'active', 'admin', 'moderator', 'ip_address', 'staged'],
|
||||||
user_stats: ['topics_entered', 'posts_read_count', 'time_read', 'topic_count', 'post_count', 'likes_given', 'likes_received'],
|
user_stats: ['topics_entered', 'posts_read_count', 'time_read', 'topic_count', 'post_count', 'likes_given', 'likes_received'],
|
||||||
user_profile: ['location', 'website', 'views'],
|
user_profile: ['location', 'website', 'views'],
|
||||||
user_sso: ['external_id', 'external_email', 'external_username', 'external_name', 'external_avatar_url'],
|
user_sso: ['external_id', 'external_email', 'external_username', 'external_name', 'external_avatar_url'],
|
||||||
|
@ -181,7 +181,7 @@ module Jobs
|
||||||
|
|
||||||
def get_base_user_array(user)
|
def get_base_user_array(user)
|
||||||
user_array = []
|
user_array = []
|
||||||
user_array.push(user.id, escape_comma(user.name), user.username, user.email, escape_comma(user.title), user.created_at, user.last_seen_at, user.last_posted_at, user.last_emailed_at, user.trust_level, user.approved, user.suspended_at, user.suspended_till, user.silenced, user.active, user.admin, user.moderator, user.ip_address, user.staged, user.user_stat.topics_entered, user.user_stat.posts_read_count, user.user_stat.time_read, user.user_stat.topic_count, user.user_stat.post_count, user.user_stat.likes_given, user.user_stat.likes_received, escape_comma(user.user_profile.location), user.user_profile.website, user.user_profile.views)
|
user_array.push(user.id, escape_comma(user.name), user.username, user.email, escape_comma(user.title), user.created_at, user.last_seen_at, user.last_posted_at, user.last_emailed_at, user.trust_level, user.approved, user.suspended_at, user.suspended_till, user.silenced_till, user.active, user.admin, user.moderator, user.ip_address, user.staged, user.user_stat.topics_entered, user.user_stat.posts_read_count, user.user_stat.time_read, user.user_stat.topic_count, user.user_stat.post_count, user.user_stat.likes_given, user.user_stat.likes_received, escape_comma(user.user_profile.location), user.user_profile.website, user.user_profile.views)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_single_sign_on(user, user_info_array)
|
def add_single_sign_on(user, user_info_array)
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Jobs
|
||||||
ub.badge_id = #{Badge::Anniversary} AND
|
ub.badge_id = #{Badge::Anniversary} AND
|
||||||
ub.granted_at BETWEEN '#{fmt_start_date}' AND '#{fmt_end_date}'
|
ub.granted_at BETWEEN '#{fmt_start_date}' AND '#{fmt_end_date}'
|
||||||
WHERE u.active AND
|
WHERE u.active AND
|
||||||
NOT u.silenced AND
|
u.silenced_till IS NULL AND
|
||||||
NOT p.hidden AND
|
NOT p.hidden AND
|
||||||
p.deleted_at IS NULL AND
|
p.deleted_at IS NULL AND
|
||||||
t.visible AND
|
t.visible AND
|
||||||
|
|
|
@ -68,6 +68,21 @@ class UserNotifications < ActionMailer::Base
|
||||||
email_token: opts[:email_token])
|
email_token: opts[:email_token])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def account_silenced(user, opts = nil)
|
||||||
|
opts ||= {}
|
||||||
|
|
||||||
|
return unless user_history = opts[:user_history]
|
||||||
|
|
||||||
|
build_email(
|
||||||
|
user.email,
|
||||||
|
template: "user_notifications.account_silenced",
|
||||||
|
locale: user_locale(user),
|
||||||
|
reason: user_history.details,
|
||||||
|
message: user_history.context,
|
||||||
|
silenced_till: I18n.l(user.silenced_till, format: :long)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def account_suspended(user, opts = nil)
|
def account_suspended(user, opts = nil)
|
||||||
opts ||= {}
|
opts ||= {}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ class DirectoryItem < ActiveRecord::Base
|
||||||
LEFT OUTER JOIN posts AS p ON ua.target_post_id = p.id
|
LEFT OUTER JOIN posts AS p ON ua.target_post_id = p.id
|
||||||
LEFT OUTER JOIN categories AS c ON t.category_id = c.id
|
LEFT OUTER JOIN categories AS c ON t.category_id = c.id
|
||||||
WHERE u.active
|
WHERE u.active
|
||||||
AND NOT u.silenced
|
AND u.silenced_till IS NULL
|
||||||
AND t.deleted_at IS NULL
|
AND t.deleted_at IS NULL
|
||||||
AND COALESCE(t.visible, true)
|
AND COALESCE(t.visible, true)
|
||||||
AND p.deleted_at IS NULL
|
AND p.deleted_at IS NULL
|
||||||
|
|
|
@ -147,8 +147,8 @@ class User < ActiveRecord::Base
|
||||||
# TODO-PERF: There is no indexes on any of these
|
# TODO-PERF: There is no indexes on any of these
|
||||||
# and NotifyMailingListSubscribers does a select-all-and-loop
|
# and NotifyMailingListSubscribers does a select-all-and-loop
|
||||||
# may want to create an index on (active, silence, suspended_till)?
|
# may want to create an index on (active, silence, suspended_till)?
|
||||||
scope :silenced, -> { where(silenced: true) }
|
scope :silenced, -> { where("silenced_till IS NOT NULL AND silenced_till > ?", Time.zone.now) }
|
||||||
scope :not_silenced, -> { where(silenced: false) }
|
scope :not_silenced, -> { where("silenced_till IS NULL OR silenced_till <= ?", Time.zone.now) }
|
||||||
scope :suspended, -> { where('suspended_till IS NOT NULL AND suspended_till > ?', Time.zone.now) }
|
scope :suspended, -> { where('suspended_till IS NOT NULL AND suspended_till > ?', Time.zone.now) }
|
||||||
scope :not_suspended, -> { where('suspended_till IS NULL OR suspended_till <= ?', Time.zone.now) }
|
scope :not_suspended, -> { where('suspended_till IS NULL OR suspended_till <= ?', Time.zone.now) }
|
||||||
scope :activated, -> { where(active: true) }
|
scope :activated, -> { where(active: true) }
|
||||||
|
@ -660,6 +660,22 @@ class User < ActiveRecord::Base
|
||||||
!!(suspended_till && suspended_till > DateTime.now)
|
!!(suspended_till && suspended_till > DateTime.now)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def silenced?
|
||||||
|
!!(silenced_till && silenced_till > DateTime.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def silenced_record
|
||||||
|
UserHistory.for(self, :silence_user).order('id DESC').first
|
||||||
|
end
|
||||||
|
|
||||||
|
def silence_reason
|
||||||
|
silenced_record.try(:details) if silenced?
|
||||||
|
end
|
||||||
|
|
||||||
|
def silenced_at
|
||||||
|
silenced_record.try(:created_at) if silenced?
|
||||||
|
end
|
||||||
|
|
||||||
def suspend_record
|
def suspend_record
|
||||||
UserHistory.for(self, :suspend_user).order('id DESC').first
|
UserHistory.for(self, :suspend_user).order('id DESC').first
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,6 +18,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
||||||
:can_be_anonymized,
|
:can_be_anonymized,
|
||||||
:suspend_reason,
|
:suspend_reason,
|
||||||
:suspended_till,
|
:suspended_till,
|
||||||
|
:silence_reason,
|
||||||
:primary_group_id,
|
:primary_group_id,
|
||||||
:badge_count,
|
:badge_count,
|
||||||
:warnings_received_count,
|
:warnings_received_count,
|
||||||
|
@ -29,6 +30,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
||||||
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
||||||
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
|
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
|
||||||
has_one :suspended_by, serializer: BasicUserSerializer, embed: :objects
|
has_one :suspended_by, serializer: BasicUserSerializer, embed: :objects
|
||||||
|
has_one :silenced_by, serializer: BasicUserSerializer, embed: :objects
|
||||||
has_one :tl3_requirements, serializer: TrustLevel3RequirementsSerializer, embed: :objects
|
has_one :tl3_requirements, serializer: TrustLevel3RequirementsSerializer, embed: :objects
|
||||||
has_many :groups, embed: :object, serializer: BasicGroupSerializer
|
has_many :groups, embed: :object, serializer: BasicGroupSerializer
|
||||||
|
|
||||||
|
@ -72,6 +74,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
||||||
object.suspend_record.try(:acting_user)
|
object.suspend_record.try(:acting_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def silence_reason
|
||||||
|
object.silence_reason
|
||||||
|
end
|
||||||
|
|
||||||
|
def silenced_by
|
||||||
|
object.silenced_record.try(:acting_user)
|
||||||
|
end
|
||||||
|
|
||||||
def include_tl3_requirements?
|
def include_tl3_requirements?
|
||||||
object.has_trust_level?(TrustLevel[2])
|
object.has_trust_level?(TrustLevel[2])
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,6 +23,7 @@ class AdminUserListSerializer < BasicUserSerializer
|
||||||
:suspended_till,
|
:suspended_till,
|
||||||
:suspended,
|
:suspended,
|
||||||
:silenced,
|
:silenced,
|
||||||
|
:silenced_till,
|
||||||
:time_read,
|
:time_read,
|
||||||
:staged
|
:staged
|
||||||
|
|
||||||
|
@ -40,6 +41,22 @@ class AdminUserListSerializer < BasicUserSerializer
|
||||||
|
|
||||||
alias_method :include_associated_accounts?, :include_email?
|
alias_method :include_associated_accounts?, :include_email?
|
||||||
|
|
||||||
|
def silenced
|
||||||
|
object.silenced?
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_silenced?
|
||||||
|
object.silenced?
|
||||||
|
end
|
||||||
|
|
||||||
|
def silenced_till
|
||||||
|
object.silenced_till
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_silenced_till?
|
||||||
|
object.silenced_till?
|
||||||
|
end
|
||||||
|
|
||||||
def suspended
|
def suspended
|
||||||
object.suspended?
|
object.suspended?
|
||||||
end
|
end
|
||||||
|
|
|
@ -275,8 +275,13 @@ class StaffActionLogger
|
||||||
|
|
||||||
def log_silence_user(user, opts = {})
|
def log_silence_user(user, opts = {})
|
||||||
raise Discourse::InvalidParameters.new(:user) unless user
|
raise Discourse::InvalidParameters.new(:user) unless user
|
||||||
UserHistory.create(params(opts).merge(action: UserHistory.actions[:silence_user],
|
UserHistory.create(
|
||||||
target_user_id: user.id))
|
params(opts).merge(
|
||||||
|
action: UserHistory.actions[:silence_user],
|
||||||
|
target_user_id: user.id,
|
||||||
|
details: opts[:details]
|
||||||
|
)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def log_unsilence_user(user, opts = {})
|
def log_unsilence_user(user, opts = {})
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
class UserSilencer
|
class UserSilencer
|
||||||
|
|
||||||
|
attr_reader :user_history
|
||||||
|
|
||||||
def initialize(user, by_user = nil, opts = {})
|
def initialize(user, by_user = nil, opts = {})
|
||||||
@user, @by_user, @opts = user, by_user, opts
|
@user, @by_user, @opts = user, by_user, opts
|
||||||
end
|
end
|
||||||
|
@ -14,14 +16,26 @@ class UserSilencer
|
||||||
|
|
||||||
def silence
|
def silence
|
||||||
hide_posts unless @opts[:keep_posts]
|
hide_posts unless @opts[:keep_posts]
|
||||||
unless @user.silenced?
|
unless @user.silenced_till.present?
|
||||||
@user.silenced = true
|
@user.silenced_till = @opts[:silenced_till] || 1000.years.from_now
|
||||||
if @user.save
|
if @user.save
|
||||||
message_type = @opts[:message] || :silenced_by_staff
|
message_type = @opts[:message] || :silenced_by_staff
|
||||||
post = SystemMessage.create(@user, message_type)
|
|
||||||
if post && @by_user
|
if @opts[:context].present?
|
||||||
StaffActionLogger.new(@by_user).log_silence_user(@user, context: "#{message_type}: '#{post.topic&.title rescue ''}' #{@opts[:reason]}")
|
context = @opts[:context]
|
||||||
|
else
|
||||||
|
context = "#{message_type}: '#{post.topic&.title rescue ''}' #{@opts[:reason]}"
|
||||||
|
SystemMessage.create(@user, message_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @by_user
|
||||||
|
@user_history = StaffActionLogger.new(@by_user).log_silence_user(
|
||||||
|
@user,
|
||||||
|
context: context,
|
||||||
|
details: @opts[:reason]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
@ -37,7 +51,7 @@ class UserSilencer
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsilence
|
def unsilence
|
||||||
@user.silenced = false
|
@user.silenced_till = nil
|
||||||
if @user.save
|
if @user.save
|
||||||
SystemMessage.create(@user, :unsilenced)
|
SystemMessage.create(@user, :unsilenced)
|
||||||
StaffActionLogger.new(@by_user).log_unsilence_user(@user) if @by_user
|
StaffActionLogger.new(@by_user).log_unsilence_user(@user) if @by_user
|
||||||
|
|
|
@ -3281,6 +3281,14 @@ en:
|
||||||
suspend_message: "Email Message"
|
suspend_message: "Email Message"
|
||||||
suspend_message_placeholder: "Optionally, provide more information about the suspension and it will be emailed to the user."
|
suspend_message_placeholder: "Optionally, provide more information about the suspension and it will be emailed to the user."
|
||||||
suspended_by: "Suspended by"
|
suspended_by: "Suspended by"
|
||||||
|
silence_reason: "Reason"
|
||||||
|
silenced_by: "Silenced By"
|
||||||
|
silence_modal_title: "Silence User"
|
||||||
|
silence_duration: "How long will the user be silenced for?"
|
||||||
|
silence_reason_label: "Why are you silencing this user?"
|
||||||
|
silence_reason_placeholder: "Silence Reason"
|
||||||
|
silence_message: "Email Message"
|
||||||
|
silence_message_placeholder: "(leave blank to send default message)"
|
||||||
suspended_until: "(until %{until})"
|
suspended_until: "(until %{until})"
|
||||||
cant_suspend: "This user cannot be suspended."
|
cant_suspend: "This user cannot be suspended."
|
||||||
delete_all_posts: "Delete all posts"
|
delete_all_posts: "Delete all posts"
|
||||||
|
|
|
@ -2660,6 +2660,17 @@ en:
|
||||||
|
|
||||||
%{message}
|
%{message}
|
||||||
|
|
||||||
|
account_silenced:
|
||||||
|
title: "Account Silenced"
|
||||||
|
subject_template: "[%{email_prefix}] Your account has been silenced"
|
||||||
|
text_body_template: |
|
||||||
|
You have been silenced from the forum until %{silenced_till}.
|
||||||
|
|
||||||
|
%{reason}
|
||||||
|
|
||||||
|
%{message}
|
||||||
|
|
||||||
|
|
||||||
account_exists:
|
account_exists:
|
||||||
title: "Account already exists"
|
title: "Account already exists"
|
||||||
subject_template: "[%{email_prefix}] Account already exists"
|
subject_template: "[%{email_prefix}] Account already exists"
|
||||||
|
|
|
@ -72,6 +72,17 @@ ColumnDropper.drop(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ColumnDropper.drop(
|
||||||
|
table: 'users',
|
||||||
|
after_migration: 'AddSilencedTillToUsers',
|
||||||
|
columns: %w[
|
||||||
|
silenced
|
||||||
|
],
|
||||||
|
on_drop: ->() {
|
||||||
|
STDERR.puts 'Removing user silenced column!'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# User for the smoke tests
|
# User for the smoke tests
|
||||||
if ENV["SMOKE"] == "1"
|
if ENV["SMOKE"] == "1"
|
||||||
UserEmail.seed do |ue|
|
UserEmail.seed do |ue|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
class AddSilencedTillToUsers < ActiveRecord::Migration[5.1]
|
||||||
|
def up
|
||||||
|
add_column :users, :silenced_till, :timestamp, null: true
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE users
|
||||||
|
SET silenced_till = CURRENT_TIMESTAMP + INTERVAL '1000 YEAR'
|
||||||
|
WHERE silenced
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_column :users, :silenced_till
|
||||||
|
end
|
||||||
|
end
|
|
@ -141,10 +141,10 @@ SQL
|
||||||
SELECT invited_by_id
|
SELECT invited_by_id
|
||||||
FROM invites i
|
FROM invites i
|
||||||
JOIN users u2 ON u2.id = i.user_id
|
JOIN users u2 ON u2.id = i.user_id
|
||||||
WHERE i.deleted_at IS NULL AND u2.active AND u2.trust_level >= #{trust_level.to_i} AND not u2.silenced
|
WHERE i.deleted_at IS NULL AND u2.active AND u2.trust_level >= #{trust_level.to_i} AND u2.silenced_till IS NULL
|
||||||
GROUP BY invited_by_id
|
GROUP BY invited_by_id
|
||||||
HAVING COUNT(*) >= #{count.to_i}
|
HAVING COUNT(*) >= #{count.to_i}
|
||||||
) AND u.active AND NOT u.silenced AND u.id > 0 AND
|
) AND u.active AND u.silenced_till IS NULL AND u.id > 0 AND
|
||||||
(:backfill OR u.id IN (:user_ids) )
|
(:backfill OR u.id IN (:user_ids) )
|
||||||
"
|
"
|
||||||
end
|
end
|
||||||
|
|
|
@ -167,7 +167,7 @@ describe AdminUserIndexQuery do
|
||||||
|
|
||||||
describe "with a silenced user" do
|
describe "with a silenced user" do
|
||||||
|
|
||||||
let!(:user) { Fabricate(:user, silenced: true) }
|
let!(:user) { Fabricate(:user, silenced_till: 1.year.from_now) }
|
||||||
|
|
||||||
it "finds the silenced user" do
|
it "finds the silenced user" do
|
||||||
query = ::AdminUserIndexQuery.new(query: 'silenced')
|
query = ::AdminUserIndexQuery.new(query: 'silenced')
|
||||||
|
|
|
@ -57,7 +57,7 @@ describe Email::Receiver do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises a SilencedUserError when the sender has been silenced" do
|
it "raises a SilencedUserError when the sender has been silenced" do
|
||||||
Fabricate(:user, email: "silenced@bar.com", silenced: true)
|
Fabricate(:user, email: "silenced@bar.com", silenced_till: 1.year.from_now)
|
||||||
expect { process(:silenced_sender) }.to raise_error(Email::Receiver::SilencedUserError)
|
expect { process(:silenced_sender) }.to raise_error(Email::Receiver::SilencedUserError)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -195,7 +195,7 @@ describe Guardian do
|
||||||
|
|
||||||
context "author is silenced" do
|
context "author is silenced" do
|
||||||
before do
|
before do
|
||||||
user.silenced = true
|
user.silenced_till = 1.year.from_now
|
||||||
user.save
|
user.save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -853,13 +853,13 @@ describe Guardian do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "allows new posts from silenced users included in the pm" do
|
it "allows new posts from silenced users included in the pm" do
|
||||||
user.update_attribute(:silenced, true)
|
user.update_attribute(:silenced_till, 1.year.from_now)
|
||||||
private_message.topic_allowed_users.create!(user_id: user.id)
|
private_message.topic_allowed_users.create!(user_id: user.id)
|
||||||
expect(Guardian.new(user).can_create?(Post, private_message)).to be_truthy
|
expect(Guardian.new(user).can_create?(Post, private_message)).to be_truthy
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't allow new posts from silenced users not invited to the pm" do
|
it "doesn't allow new posts from silenced users not invited to the pm" do
|
||||||
user.update_attribute(:silenced, true)
|
user.update_attribute(:silenced_till, 1.year.from_now)
|
||||||
expect(Guardian.new(user).can_create?(Post, private_message)).to be_falsey
|
expect(Guardian.new(user).can_create?(Post, private_message)).to be_falsey
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1376,7 +1376,7 @@ describe Guardian do
|
||||||
|
|
||||||
context 'when user is silenced' do
|
context 'when user is silenced' do
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
user.toggle!(:silenced)
|
user.update_column(:silenced_till, 1.year.from_now)
|
||||||
expect(Guardian.new(user).can_moderate?(post)).to be(false)
|
expect(Guardian.new(user).can_moderate?(post)).to be(false)
|
||||||
expect(Guardian.new(user).can_moderate?(topic)).to be(false)
|
expect(Guardian.new(user).can_moderate?(topic)).to be(false)
|
||||||
end
|
end
|
||||||
|
|
|
@ -495,15 +495,7 @@ describe Admin::UsersController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context '.destroy' do
|
context '.destroy' do
|
||||||
before do
|
let(:delete_me) { Fabricate(:user) }
|
||||||
@delete_me = Fabricate(:user)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "raises an error when the user doesn't have permission" do
|
|
||||||
Guardian.any_instance.expects(:can_delete_user?).with(@delete_me).returns(false)
|
|
||||||
delete :destroy, params: { id: @delete_me.id }, format: :json
|
|
||||||
expect(response).to be_forbidden
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns a 403 if the user doesn't exist" do
|
it "returns a 403 if the user doesn't exist" do
|
||||||
delete :destroy, params: { id: 123123 }, format: :json
|
delete :destroy, params: { id: 123123 }, format: :json
|
||||||
|
@ -511,31 +503,26 @@ describe Admin::UsersController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "user has post" do
|
context "user has post" do
|
||||||
|
let(:topic) { create_topic(user: delete_me) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@user = Fabricate(:user)
|
_post = create_post(topic: topic, user: delete_me)
|
||||||
topic = create_topic(user: @user)
|
|
||||||
_post = create_post(topic: topic, user: @user)
|
|
||||||
@user.stubs(:first_post_created_at).returns(Time.zone.now)
|
|
||||||
User.expects(:find_by).with(id: @delete_me.id).returns(@user)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns an error" do
|
it "returns an error" do
|
||||||
delete :destroy, params: { id: @delete_me.id }, format: :json
|
delete :destroy, params: { id: delete_me.id }, format: :json
|
||||||
expect(response).to be_forbidden
|
expect(response).to be_forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't return an error if delete_posts == true" do
|
it "doesn't return an error if delete_posts == true" do
|
||||||
UserDestroyer.any_instance.expects(:destroy).with(@user, has_entry('delete_posts' => true)).returns(true)
|
delete :destroy, params: { id: delete_me.id, delete_posts: true }, format: :json
|
||||||
delete :destroy, params: { id: @delete_me.id, delete_posts: true }, format: :json
|
|
||||||
expect(response).to be_success
|
expect(response).to be_success
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "deletes the user record" do
|
it "deletes the user record" do
|
||||||
UserDestroyer.any_instance.expects(:destroy).returns(true)
|
UserDestroyer.any_instance.expects(:destroy).returns(true)
|
||||||
delete :destroy, params: { id: @delete_me.id }, format: :json
|
delete :destroy, params: { id: delete_me.id }, format: :json
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -590,9 +577,10 @@ describe Admin::UsersController do
|
||||||
|
|
||||||
it "raises an error when the user doesn't have permission" do
|
it "raises an error when the user doesn't have permission" do
|
||||||
Guardian.any_instance.expects(:can_silence_user?).with(@reg_user).returns(false)
|
Guardian.any_instance.expects(:can_silence_user?).with(@reg_user).returns(false)
|
||||||
UserSilencer.expects(:silence).never
|
|
||||||
put :silence, params: { user_id: @reg_user.id }, format: :json
|
put :silence, params: { user_id: @reg_user.id }, format: :json
|
||||||
expect(response).to be_forbidden
|
expect(response).to be_forbidden
|
||||||
|
@reg_user.reload
|
||||||
|
expect(@reg_user).not_to be_silenced
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns a 403 if the user doesn't exist" do
|
it "returns a 403 if the user doesn't exist" do
|
||||||
|
@ -601,8 +589,43 @@ describe Admin::UsersController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "punishes the user for spamming" do
|
it "punishes the user for spamming" do
|
||||||
UserSilencer.expects(:silence).with(@reg_user, @user, anything)
|
|
||||||
put :silence, params: { user_id: @reg_user.id }, format: :json
|
put :silence, params: { user_id: @reg_user.id }, format: :json
|
||||||
|
expect(response).to be_success
|
||||||
|
@reg_user.reload
|
||||||
|
expect(@reg_user).to be_silenced
|
||||||
|
end
|
||||||
|
|
||||||
|
it "will set a length of time if provided" do
|
||||||
|
future_date = 1.month.from_now.to_date
|
||||||
|
put(
|
||||||
|
:silence,
|
||||||
|
params: {
|
||||||
|
user_id: @reg_user.id,
|
||||||
|
silenced_till: future_date
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
)
|
||||||
|
@reg_user.reload
|
||||||
|
expect(@reg_user.silenced_till).to eq(future_date)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "will send a message if provided" do
|
||||||
|
Jobs.expects(:enqueue).with(
|
||||||
|
:critical_user_email,
|
||||||
|
has_entries(
|
||||||
|
type: :account_silenced,
|
||||||
|
user_id: @reg_user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
put(
|
||||||
|
:silence,
|
||||||
|
params: {
|
||||||
|
user_id: @reg_user.id,
|
||||||
|
message: "Email this to the user"
|
||||||
|
},
|
||||||
|
format: :json
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -710,7 +710,7 @@ describe PostsController do
|
||||||
expect(parsed["action"]).to eq("enqueued")
|
expect(parsed["action"]).to eq("enqueued")
|
||||||
|
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.silenced).to eq(true)
|
expect(user).to be_silenced
|
||||||
|
|
||||||
qp = QueuedPost.first
|
qp = QueuedPost.first
|
||||||
|
|
||||||
|
@ -718,7 +718,7 @@ describe PostsController do
|
||||||
qp.approve!(mod)
|
qp.approve!(mod)
|
||||||
|
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.silenced).to eq(false)
|
expect(user).not_to be_silenced
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't enqueue replies when the topic is closed" do
|
it "doesn't enqueue replies when the topic is closed" do
|
||||||
|
@ -763,7 +763,7 @@ describe PostsController do
|
||||||
expect(parsed["action"]).to eq("enqueued")
|
expect(parsed["action"]).to eq("enqueued")
|
||||||
|
|
||||||
user.reload
|
user.reload
|
||||||
expect(user.silenced).to eq(true)
|
expect(user).to be_silenced
|
||||||
end
|
end
|
||||||
|
|
||||||
it "can send a message to a group" do
|
it "can send a message to a group" do
|
||||||
|
|
|
@ -24,7 +24,7 @@ describe Jobs::GrantAnniversaryBadges do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't award to a silenced user" do
|
it "doesn't award to a silenced user" do
|
||||||
user = Fabricate(:user, created_at: 400.days.ago, silenced: true)
|
user = Fabricate(:user, created_at: 400.days.ago, silenced_till: 1.year.from_now)
|
||||||
Fabricate(:post, user: user, created_at: 1.week.ago)
|
Fabricate(:post, user: user, created_at: 1.week.ago)
|
||||||
granter.execute({})
|
granter.execute({})
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ describe Jobs::NotifyMailingListSubscribers do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "to a silenced user" do
|
context "to a silenced user" do
|
||||||
before { mailing_list_user.update(silenced: true) }
|
before { mailing_list_user.update(silenced_till: 1.year.from_now) }
|
||||||
include_examples "no emails"
|
include_examples "no emails"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -677,7 +677,7 @@ describe Topic do
|
||||||
|
|
||||||
context "when moderator post fails to be created" do
|
context "when moderator post fails to be created" do
|
||||||
before do
|
before do
|
||||||
user.toggle!(:silenced)
|
user.update_column(:silenced_till, 1.year.from_now)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not increment moderator_posts_count" do
|
it "should not increment moderator_posts_count" do
|
||||||
|
@ -833,7 +833,7 @@ describe Topic do
|
||||||
it_should_behave_like 'a status that closes a topic'
|
it_should_behave_like 'a status that closes a topic'
|
||||||
|
|
||||||
context 'topic was set to close when it was created' do
|
context 'topic was set to close when it was created' do
|
||||||
it 'puts the autoclose duration in the moderator post' do
|
it 'includes the autoclose duration in the moderator post' do
|
||||||
freeze_time(Time.new(2000, 1, 1))
|
freeze_time(Time.new(2000, 1, 1))
|
||||||
@topic.created_at = 3.days.ago
|
@topic.created_at = 3.days.ago
|
||||||
@topic.update_status(status, true, @user)
|
@topic.update_status(status, true, @user)
|
||||||
|
@ -842,7 +842,7 @@ describe Topic do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'topic was set to close after it was created' do
|
context 'topic was set to close after it was created' do
|
||||||
it 'puts the autoclose duration in the moderator post' do
|
it 'includes the autoclose duration in the moderator post' do
|
||||||
freeze_time(Time.new(2000, 1, 1))
|
freeze_time(Time.new(2000, 1, 1))
|
||||||
|
|
||||||
@topic.created_at = 7.days.ago
|
@topic.created_at = 7.days.ago
|
||||||
|
|
|
@ -1532,4 +1532,32 @@ describe User do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "silenced?" do
|
||||||
|
|
||||||
|
it "is not silenced by default" do
|
||||||
|
expect(Fabricate(:user)).not_to be_silenced
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is not silenced with a date in the past" do
|
||||||
|
expect(Fabricate(:user, silenced_till: 1.month.ago)).not_to be_silenced
|
||||||
|
end
|
||||||
|
|
||||||
|
it "is is silenced with a date in the future" do
|
||||||
|
expect(Fabricate(:user, silenced_till: 1.month.from_now)).to be_silenced
|
||||||
|
end
|
||||||
|
|
||||||
|
context "finders" do
|
||||||
|
let!(:user0) { Fabricate(:user, silenced_till: 1.month.ago) }
|
||||||
|
let!(:user1) { Fabricate(:user, silenced_till: 1.month.from_now) }
|
||||||
|
|
||||||
|
it "doesn't return old silenced records" do
|
||||||
|
expect(User.silenced).to_not include(user0)
|
||||||
|
expect(User.silenced).to include(user1)
|
||||||
|
expect(User.not_silenced).to include(user0)
|
||||||
|
expect(User.not_silenced).to_not include(user1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -275,7 +275,7 @@ describe SpamRule::AutoSilence do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "silenced, but has higher trust level now" do
|
context "silenced, but has higher trust level now" do
|
||||||
let(:user) { Fabricate(:user, silenced: true, trust_level: TrustLevel[1]) }
|
let(:user) { Fabricate(:user, silenced_till: 1.year.from_now, trust_level: TrustLevel[1]) }
|
||||||
subject { described_class.new(user) }
|
subject { described_class.new(user) }
|
||||||
|
|
||||||
it 'returns false' do
|
it 'returns false' do
|
||||||
|
|
|
@ -7,8 +7,8 @@ describe UserSilencer do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'silence' do
|
describe 'silence' do
|
||||||
let(:user) { stub_everything(save: true) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:silencer) { UserSilencer.new(user) }
|
let(:silencer) { UserSilencer.new(user) }
|
||||||
subject(:silence_user) { silencer.silence }
|
subject(:silence_user) { silencer.silence }
|
||||||
|
|
||||||
it 'silences the user' do
|
it 'silences the user' do
|
||||||
|
@ -53,7 +53,7 @@ describe UserSilencer do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "doesn't send a pm if the user is already silenced" do
|
it "doesn't send a pm if the user is already silenced" do
|
||||||
user.stubs(:silenced?).returns(true)
|
user.silenced_till = 1.year.from_now
|
||||||
SystemMessage.unstub(:create)
|
SystemMessage.unstub(:create)
|
||||||
SystemMessage.expects(:create).never
|
SystemMessage.expects(:create).never
|
||||||
expect(silence_user).to eq(false)
|
expect(silence_user).to eq(false)
|
||||||
|
@ -73,7 +73,7 @@ describe UserSilencer do
|
||||||
subject(:unsilence_user) { UserSilencer.unsilence(user, Fabricate.build(:admin)) }
|
subject(:unsilence_user) { UserSilencer.unsilence(user, Fabricate.build(:admin)) }
|
||||||
|
|
||||||
it 'unsilences the user' do
|
it 'unsilences the user' do
|
||||||
u = Fabricate(:user, silenced: true)
|
u = Fabricate(:user, silenced_till: 1.year.from_now)
|
||||||
expect { UserSilencer.unsilence(u) }.to change { u.reload.silenced? }
|
expect { UserSilencer.unsilence(u) }.to change { u.reload.silenced? }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue