Skip to content

Commit 4a8a663

Browse files
committed
1 parent 6491bda commit 4a8a663

File tree

12 files changed

+172
-36
lines changed

12 files changed

+172
-36
lines changed

app/assets/javascripts/admin/controllers/admin_flags_controller.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,32 @@ Discourse.AdminFlagsController = Ember.ArrayController.extend({
1414
@method clearFlags
1515
@param {Discourse.FlaggedPost} item The post whose flags we want to clear
1616
**/
17-
clearFlags: function(item) {
17+
disagreeFlags: function(item) {
1818
var adminFlagsController = this;
19-
item.clearFlags().then((function() {
19+
item.disagreeFlags().then((function() {
2020
adminFlagsController.removeObject(item);
2121
}), function() {
2222
bootbox.alert(Em.String.i18n("admin.flags.error"));
2323
});
2424
},
2525

26+
agreeFlags: function(item) {
27+
var adminFlagsController = this;
28+
item.agreeFlags().then((function() {
29+
adminFlagsController.removeObject(item);
30+
}), function() {
31+
bootbox.alert(Em.String.i18n("admin.flags.error"));
32+
});
33+
},
34+
35+
deferFlags: function(item) {
36+
var adminFlagsController = this;
37+
item.deferFlags().then((function() {
38+
adminFlagsController.removeObject(item);
39+
}), function() {
40+
bootbox.alert(Em.String.i18n("admin.flags.error"));
41+
});
42+
},
2643
/**
2744
Deletes a post
2845

app/assets/javascripts/admin/models/flagged_post.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,22 @@ Discourse.FlaggedPost = Discourse.Post.extend({
6363
}
6464
},
6565

66-
clearFlags: function() {
67-
return Discourse.ajax("/admin/flags/clear/" + this.id, { type: 'POST', cache: false });
66+
disagreeFlags: function() {
67+
return Discourse.ajax("/admin/flags/disagree/" + this.id, { type: 'POST', cache: false });
6868
},
6969

70+
deferFlags: function() {
71+
return Discourse.ajax("/admin/flags/defer/" + this.id, { type: 'POST', cache: false });
72+
},
73+
74+
agreeFlags: function() {
75+
return Discourse.ajax("/admin/flags/agree/" + this.id, { type: 'POST', cache: false });
76+
},
77+
78+
postHidden: function() {
79+
return (this.get('hidden') === "t");
80+
}.property(),
81+
7082
hiddenClass: function() {
7183
if (this.get('hidden') === "t") return "hidden-post";
7284
}.property()

app/assets/javascripts/admin/templates/flags.js.handlebars

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
</thead>
2424
<tbody>
2525
{{#each flag in content}}
26-
<tr {{bindAttr class="hiddenClass"}}>
26+
<tr {{bindAttr class="flag.hiddenClass"}}>
2727
<td class='user'>{{#linkTo 'adminUser' flag.user}}{{avatar flag.user imageSize="small"}}{{/linkTo}}</td>
2828
<td class='excerpt'>{{#if flag.topicHidden}}<i title='{{i18n topic_statuses.invisible.help}}' class='icon icon-eye-close'></i> {{/if}}<h3><a href='{{unbound flag.url}}'>{{flag.title}}</a></h3><br>{{{flag.excerpt}}}
2929
</td>
@@ -33,8 +33,15 @@
3333
<td class='last-flagged'>{{date flag.lastFlagged}}</td>
3434
<td class='action'>
3535
{{#if adminActiveFlagsView}}
36-
<button title='{{i18n admin.flags.clear_title}}' class='btn' {{action clearFlags flag}}>{{i18n admin.flags.clear}}</button>
37-
<button title='{{i18n admin.flags.delete_title}}' class='btn' {{action deletePost flag}}>{{i18n admin.flags.delete}}</button>
36+
{{#if flag.postHidden}}
37+
<button title='{{i18n admin.flags.disagree_unhide_title}}' class='btn' {{action disagreeFlags flag}}>{{i18n admin.flags.disagree_unhide}}</button>
38+
<button title='{{i18n admin.flags.defer_title}}' class='btn' {{action deferFlags flag}}>{{i18n admin.flags.defer}}</button>
39+
{{else}}
40+
<button title='{{i18n admin.flags.agree_hide_title}}' class='btn' {{action agreeFlags flag}}>{{i18n admin.flags.agree_hide}}</button>
41+
<button title='{{i18n admin.flags.disagree_title}}' class='btn' {{action disagreeFlags flag}}>{{i18n admin.flags.disagree}}</button>
42+
{{/if}}
43+
44+
<button title='{{i18n admin.flags.delete_post_title}}' class='btn' {{action deletePost flag}}>{{i18n admin.flags.delete_post}}</button>
3845
{{/if}}
3946
</td>
4047
</tr>

app/assets/stylesheets/admin/admin_base.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ table {
286286
.flaggers { padding: 0 10px; }
287287
.last-flagged { padding: 0 10px; }
288288
.flag-summary { font-size: 11px; }
289+
.action {
290+
button { margin: 4px; display: block;}
291+
padding-bottom: 20px;
292+
}
289293
}
290294

291295
/* Dashboard */

app/controllers/admin/flags_controller.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
class Admin::FlagsController < Admin::AdminController
22
def index
3-
43
# we may get out of sync, fix it here
54
PostAction.update_flagged_posts_count
65
posts, users = PostAction.flagged_posts_report(params[:filter])
@@ -12,11 +11,24 @@ def index
1211
end
1312
end
1413

15-
def clear
14+
def disagree
1615
p = Post.find(params[:id])
1716
PostAction.clear_flags!(p, current_user.id)
1817
p.reload
1918
p.unhide!
2019
render nothing: true
2120
end
21+
22+
def agree
23+
p = Post.find(params[:id])
24+
PostAction.defer_flags!(p, current_user.id)
25+
PostAction.hide_post!(p)
26+
render nothing: true
27+
end
28+
29+
def defer
30+
p = Post.find(params[:id])
31+
PostAction.defer_flags!(p, current_user.id)
32+
render nothing: true
33+
end
2234
end

app/models/email_log.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def self.for(reply_key)
3232
# created_at :datetime not null
3333
# updated_at :datetime not null
3434
# reply_key :string(32)
35+
# post_id :integer
36+
# topic_id :integer
3537
#
3638
# Indexes
3739
#

app/models/post_action.rb

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class AlreadyActed < StandardError; end
2121

2222
def self.update_flagged_posts_count
2323
posts_flagged_count = PostAction.joins(post: :topic)
24+
.where('defer = false or defer IS NULL')
2425
.where('post_actions.post_action_type_id' => PostActionType.notify_flag_type_ids,
2526
'posts.deleted_at' => nil,
2627
'topics.deleted_at' => nil)
@@ -70,6 +71,25 @@ def self.clear_flags!(post, moderator_id, action_type_id = nil)
7071
update_flagged_posts_count
7172
end
7273

74+
def self.defer_flags!(post, moderator_id)
75+
actions = PostAction.where(
76+
defer: nil,
77+
post_id: post.id,
78+
post_action_type_id:
79+
PostActionType.flag_types.values,
80+
deleted_at: nil
81+
)
82+
83+
actions.each do |a|
84+
a.defer = true
85+
a.defer_by = moderator_id
86+
# so callback is called
87+
a.save
88+
end
89+
90+
update_flagged_posts_count
91+
end
92+
7393
def self.act(user, post, post_action_type_id, opts={})
7494
begin
7595
title, target_usernames, target_group_names, subtype, body = nil
@@ -179,12 +199,12 @@ def message_quality
179199
# can weigh flags differently.
180200
def self.flag_counts_for(post_id)
181201
flag_counts = exec_sql("SELECT SUM(CASE
182-
WHEN pa.deleted_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
202+
WHEN pa.deleted_at IS NULL AND (pa.staff_took_action) THEN :flags_required_to_hide_post
183203
WHEN pa.deleted_at IS NULL AND (NOT pa.staff_took_action) THEN 1
184204
ELSE 0
185205
END) AS new_flags,
186206
SUM(CASE
187-
WHEN pa.deleted_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
207+
WHEN pa.deleted_at IS NOT NULL AND (pa.staff_took_action) THEN :flags_required_to_hide_post
188208
WHEN pa.deleted_at IS NOT NULL AND (NOT pa.staff_took_action) THEN 1
189209
ELSE 0
190210
END) AS old_flags
@@ -228,27 +248,52 @@ def self.flag_counts_for(post_id)
228248
PostAction.update_flagged_posts_count
229249
end
230250

231-
if PostActionType.auto_action_flag_types.include?(post_action_type) && SiteSetting.flags_required_to_hide_post > 0
232-
# automatic hiding of posts
251+
PostAction.auto_hide_if_needed(post, post_action_type)
252+
253+
SpamRulesEnforcer.enforce!(post.user) if post_action_type == :spam
254+
end
255+
256+
def self.auto_hide_if_needed(post, post_action_type)
257+
return if post.hidden
258+
259+
if PostActionType.auto_action_flag_types.include?(post_action_type) &&
260+
SiteSetting.flags_required_to_hide_post > 0
261+
233262
old_flags, new_flags = PostAction.flag_counts_for(post.id)
234263

235264
if new_flags >= SiteSetting.flags_required_to_hide_post
236-
reason = old_flags > 0 ? Post.hidden_reasons[:flag_threshold_reached_again] : Post.hidden_reasons[:flag_threshold_reached]
237-
Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason], id: post_id)
238-
Topic.update_all({ visible: false },
239-
["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)",
240-
topic_id: post.topic_id])
241-
242-
# inform user
243-
if post.user
244-
SystemMessage.create(post.user, :post_hidden,
245-
url: post.url,
246-
edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts)
247-
end
265+
hide_post!(post, guess_hide_reason(old_flags))
248266
end
249267
end
268+
end
250269

251-
SpamRulesEnforcer.enforce!(post.user) if post_action_type == :spam
270+
271+
def self.hide_post!(post, reason=nil)
272+
return if post.hidden
273+
274+
unless reason
275+
old_flags,_ = PostAction.flag_counts_for(post.id)
276+
reason = guess_hide_reason(old_flags)
277+
end
278+
279+
Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason], id: post.id)
280+
Topic.update_all({ visible: false },
281+
["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)",
282+
topic_id: post.topic_id])
283+
284+
# inform user
285+
if post.user
286+
SystemMessage.create(post.user,
287+
:post_hidden,
288+
url: post.url,
289+
edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts)
290+
end
291+
end
292+
293+
def self.guess_hide_reason(old_flags)
294+
old_flags > 0 ?
295+
Post.hidden_reasons[:flag_threshold_reached_again] :
296+
Post.hidden_reasons[:flag_threshold_reached]
252297
end
253298

254299
def self.flagged_posts_report(filter)
@@ -268,7 +313,11 @@ def self.flagged_posts_report(filter)
268313
post_actions = PostAction.includes({:related_post => :topic})
269314
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
270315
.where(post_id: post_lookup.keys)
271-
post_actions = post_actions.with_deleted if filter == 'old'
316+
if filter == 'old'
317+
post_actions = post_actions.with_deleted.where('deleted_at IS NOT NULL OR defer = true')
318+
else
319+
post_actions = post_actions.where('defer IS NULL OR defer = false')
320+
end
272321

273322
post_actions.each do |pa|
274323
post = post_lookup[pa.post_id]
@@ -309,13 +358,12 @@ def self.flagged_posts(filter)
309358

310359
# it may make sense to add a view that shows flags on deleted posts,
311360
# we don't clear the flags on post deletion, just supress counts
312-
# they may have deleted_at on the action not set
313361
if filter == 'old'
314-
sql.where2 "deleted_at is not null"
362+
sql.where2 "deleted_at is not null OR defer = true"
315363
sql.order_by "max desc"
316364
else
317365
sql.where "p.deleted_at is null and t.deleted_at is null"
318-
sql.where2 "deleted_at is null"
366+
sql.where2 "deleted_at is null and (defer IS NULL OR defer = false)"
319367
sql.order_by "cnt desc, max asc"
320368
end
321369

@@ -343,6 +391,8 @@ def self.target_moderators
343391
# message :text
344392
# related_post_id :integer
345393
# staff_took_action :boolean default(FALSE), not null
394+
# defer :boolean
395+
# defer_by :integer
346396
#
347397
# Indexes
348398
#

app/models/user.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ def send_approval_email
653653
# likes_received :integer default(0), not null
654654
# topic_reply_count :integer default(0), not null
655655
# blocked :boolean default(FALSE)
656+
# dynamic_favicon :boolean default(FALSE), not null
656657
#
657658
# Indexes
658659
#

config/locales/client.en.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,10 +1033,18 @@ en:
10331033
title: "Flags"
10341034
old: "Old"
10351035
active: "Active"
1036-
clear: "Clear Flags"
1037-
clear_title: "dismiss all flags on this post (will unhide hidden posts)"
1038-
delete: "Delete Post"
1039-
delete_title: "delete post (if its the first post delete topic)"
1036+
1037+
agree_hide: "Agree (Hide + PM)"
1038+
agree_hide_title: "Agree with flags, hide post and send user a private message"
1039+
defer: "Defer"
1040+
defer_title: "Defer flag handling to the system"
1041+
delete_post: "Delete Post"
1042+
delete_post_title: "delete post (if its the first post delete topic)"
1043+
disagree_unhide: "Disagree (Unhide)"
1044+
disagree_unhide_title: "Disagree with flag, remove flags from post and show post"
1045+
disagree: "Disagree"
1046+
disagree_title: "Disagree with flag, remove flags from post"
1047+
10401048
flagged_by: "Flagged by"
10411049
error: "Something went wrong"
10421050
view_message: "view message"

config/routes.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@
6969
get 'customize' => 'site_customizations#index', constraints: AdminConstraint.new
7070
get 'flags' => 'flags#index'
7171
get 'flags/:filter' => 'flags#index'
72-
post 'flags/clear/:id' => 'flags#clear'
72+
post 'flags/agree/:id' => 'flags#agree'
73+
post 'flags/disagree/:id' => 'flags#disagree'
74+
post 'flags/defer/:id' => 'flags#defer'
7375
resources :site_customizations, constraints: AdminConstraint.new
7476
resources :site_contents, constraints: AdminConstraint.new
7577
resources :site_content_types, constraints: AdminConstraint.new
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class AddDeferToPostActions < ActiveRecord::Migration
2+
def change
3+
# an action can be deferred by a moderator, used for flags
4+
add_column :post_actions, :defer, :boolean
5+
add_column :post_actions, :defer_by, :int
6+
end
7+
end

spec/models/post_action_spec.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@
8484
PostAction.flagged_posts_count.should == 0
8585
end
8686

87+
it "should ignore validated flags" do
88+
admin = Fabricate(:admin)
89+
PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
90+
post.hidden.should be_false
91+
PostAction.defer_flags!(post, admin.id)
92+
PostAction.flagged_posts_count.should == 0
93+
post.reload
94+
post.hidden.should be_false
95+
96+
PostAction.hide_post!(post)
97+
post.reload
98+
post.hidden.should be_true
99+
end
100+
87101
end
88102

89103
describe "when a user bookmarks something" do
@@ -220,7 +234,7 @@
220234
u2 = Fabricate(:walter_white)
221235
admin = Fabricate(:admin) # we need an admin for the messages
222236

223-
SiteSetting.flags_required_to_hide_post = 2
237+
SiteSetting.stubs(:flags_required_to_hide_post).returns(2)
224238

225239
PostAction.act(u1, post, PostActionType.types[:spam])
226240
PostAction.act(u2, post, PostActionType.types[:spam])

0 commit comments

Comments
 (0)