diff --git a/app/assets/javascripts/admin/controllers/admin_flags_controller.js b/app/assets/javascripts/admin/controllers/admin_flags_controller.js
index 6b1bfcc84ce..f1843e93d2a 100644
--- a/app/assets/javascripts/admin/controllers/admin_flags_controller.js
+++ b/app/assets/javascripts/admin/controllers/admin_flags_controller.js
@@ -14,15 +14,32 @@ Discourse.AdminFlagsController = Ember.ArrayController.extend({
@method clearFlags
@param {Discourse.FlaggedPost} item The post whose flags we want to clear
**/
- clearFlags: function(item) {
+ disagreeFlags: function(item) {
var adminFlagsController = this;
- item.clearFlags().then((function() {
+ item.disagreeFlags().then((function() {
adminFlagsController.removeObject(item);
}), function() {
bootbox.alert(Em.String.i18n("admin.flags.error"));
});
},
+ agreeFlags: function(item) {
+ var adminFlagsController = this;
+ item.agreeFlags().then((function() {
+ adminFlagsController.removeObject(item);
+ }), function() {
+ bootbox.alert(Em.String.i18n("admin.flags.error"));
+ });
+ },
+
+ deferFlags: function(item) {
+ var adminFlagsController = this;
+ item.deferFlags().then((function() {
+ adminFlagsController.removeObject(item);
+ }), function() {
+ bootbox.alert(Em.String.i18n("admin.flags.error"));
+ });
+ },
/**
Deletes a post
diff --git a/app/assets/javascripts/admin/models/flagged_post.js b/app/assets/javascripts/admin/models/flagged_post.js
index 9392786e5a8..a93d74a3293 100644
--- a/app/assets/javascripts/admin/models/flagged_post.js
+++ b/app/assets/javascripts/admin/models/flagged_post.js
@@ -63,10 +63,22 @@ Discourse.FlaggedPost = Discourse.Post.extend({
}
},
- clearFlags: function() {
- return Discourse.ajax("/admin/flags/clear/" + this.id, { type: 'POST', cache: false });
+ disagreeFlags: function() {
+ return Discourse.ajax("/admin/flags/disagree/" + this.id, { type: 'POST', cache: false });
},
+ deferFlags: function() {
+ return Discourse.ajax("/admin/flags/defer/" + this.id, { type: 'POST', cache: false });
+ },
+
+ agreeFlags: function() {
+ return Discourse.ajax("/admin/flags/agree/" + this.id, { type: 'POST', cache: false });
+ },
+
+ postHidden: function() {
+ return (this.get('hidden') === "t");
+ }.property(),
+
hiddenClass: function() {
if (this.get('hidden') === "t") return "hidden-post";
}.property()
diff --git a/app/assets/javascripts/admin/templates/flags.js.handlebars b/app/assets/javascripts/admin/templates/flags.js.handlebars
index b38a67f519d..e63bd7e84d7 100644
--- a/app/assets/javascripts/admin/templates/flags.js.handlebars
+++ b/app/assets/javascripts/admin/templates/flags.js.handlebars
@@ -23,7 +23,7 @@
{{#each flag in content}}
-
+
{{#linkTo 'adminUser' flag.user}}{{avatar flag.user imageSize="small"}}{{/linkTo}} |
{{#if flag.topicHidden}} {{/if}} {{{flag.excerpt}}}
|
@@ -33,8 +33,15 @@
{{date flag.lastFlagged}} |
{{#if adminActiveFlagsView}}
-
-
+ {{#if flag.postHidden}}
+
+
+ {{else}}
+
+
+ {{/if}}
+
+
{{/if}}
|
diff --git a/app/assets/stylesheets/admin/admin_base.scss b/app/assets/stylesheets/admin/admin_base.scss
index a6a4b4a8c5e..e1b599745e9 100644
--- a/app/assets/stylesheets/admin/admin_base.scss
+++ b/app/assets/stylesheets/admin/admin_base.scss
@@ -286,6 +286,10 @@ table {
.flaggers { padding: 0 10px; }
.last-flagged { padding: 0 10px; }
.flag-summary { font-size: 11px; }
+ .action {
+ button { margin: 4px; display: block;}
+ padding-bottom: 20px;
+ }
}
/* Dashboard */
diff --git a/app/controllers/admin/flags_controller.rb b/app/controllers/admin/flags_controller.rb
index 7bbd5fdaec4..0dae7442419 100644
--- a/app/controllers/admin/flags_controller.rb
+++ b/app/controllers/admin/flags_controller.rb
@@ -1,6 +1,5 @@
class Admin::FlagsController < Admin::AdminController
def index
-
# we may get out of sync, fix it here
PostAction.update_flagged_posts_count
posts, users = PostAction.flagged_posts_report(params[:filter])
@@ -12,11 +11,24 @@ class Admin::FlagsController < Admin::AdminController
end
end
- def clear
+ def disagree
p = Post.find(params[:id])
PostAction.clear_flags!(p, current_user.id)
p.reload
p.unhide!
render nothing: true
end
+
+ def agree
+ p = Post.find(params[:id])
+ PostAction.defer_flags!(p, current_user.id)
+ PostAction.hide_post!(p)
+ render nothing: true
+ end
+
+ def defer
+ p = Post.find(params[:id])
+ PostAction.defer_flags!(p, current_user.id)
+ render nothing: true
+ end
end
diff --git a/app/models/email_log.rb b/app/models/email_log.rb
index 9b912fdb6ed..2efbdaf01a0 100644
--- a/app/models/email_log.rb
+++ b/app/models/email_log.rb
@@ -32,6 +32,8 @@ end
# created_at :datetime not null
# updated_at :datetime not null
# reply_key :string(32)
+# post_id :integer
+# topic_id :integer
#
# Indexes
#
diff --git a/app/models/post_action.rb b/app/models/post_action.rb
index 6547cd7fde5..358fb655748 100644
--- a/app/models/post_action.rb
+++ b/app/models/post_action.rb
@@ -21,6 +21,7 @@ class PostAction < ActiveRecord::Base
def self.update_flagged_posts_count
posts_flagged_count = PostAction.joins(post: :topic)
+ .where('defer = false or defer IS NULL')
.where('post_actions.post_action_type_id' => PostActionType.notify_flag_type_ids,
'posts.deleted_at' => nil,
'topics.deleted_at' => nil)
@@ -70,6 +71,25 @@ class PostAction < ActiveRecord::Base
update_flagged_posts_count
end
+ def self.defer_flags!(post, moderator_id)
+ actions = PostAction.where(
+ defer: nil,
+ post_id: post.id,
+ post_action_type_id:
+ PostActionType.flag_types.values,
+ deleted_at: nil
+ )
+
+ actions.each do |a|
+ a.defer = true
+ a.defer_by = moderator_id
+ # so callback is called
+ a.save
+ end
+
+ update_flagged_posts_count
+ end
+
def self.act(user, post, post_action_type_id, opts={})
begin
title, target_usernames, target_group_names, subtype, body = nil
@@ -179,12 +199,12 @@ class PostAction < ActiveRecord::Base
# can weigh flags differently.
def self.flag_counts_for(post_id)
flag_counts = exec_sql("SELECT SUM(CASE
- WHEN pa.deleted_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
+ WHEN pa.deleted_at IS NULL AND (pa.staff_took_action) THEN :flags_required_to_hide_post
WHEN pa.deleted_at IS NULL AND (NOT pa.staff_took_action) THEN 1
ELSE 0
END) AS new_flags,
SUM(CASE
- WHEN pa.deleted_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
+ WHEN pa.deleted_at IS NOT NULL AND (pa.staff_took_action) THEN :flags_required_to_hide_post
WHEN pa.deleted_at IS NOT NULL AND (NOT pa.staff_took_action) THEN 1
ELSE 0
END) AS old_flags
@@ -228,27 +248,52 @@ class PostAction < ActiveRecord::Base
PostAction.update_flagged_posts_count
end
- if PostActionType.auto_action_flag_types.include?(post_action_type) && SiteSetting.flags_required_to_hide_post > 0
- # automatic hiding of posts
+ PostAction.auto_hide_if_needed(post, post_action_type)
+
+ SpamRulesEnforcer.enforce!(post.user) if post_action_type == :spam
+ end
+
+ def self.auto_hide_if_needed(post, post_action_type)
+ return if post.hidden
+
+ if PostActionType.auto_action_flag_types.include?(post_action_type) &&
+ SiteSetting.flags_required_to_hide_post > 0
+
old_flags, new_flags = PostAction.flag_counts_for(post.id)
if new_flags >= SiteSetting.flags_required_to_hide_post
- reason = old_flags > 0 ? Post.hidden_reasons[:flag_threshold_reached_again] : Post.hidden_reasons[:flag_threshold_reached]
- Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason], id: post_id)
- Topic.update_all({ visible: false },
- ["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)",
- topic_id: post.topic_id])
-
- # inform user
- if post.user
- SystemMessage.create(post.user, :post_hidden,
- url: post.url,
- edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts)
- end
+ hide_post!(post, guess_hide_reason(old_flags))
end
end
+ end
- SpamRulesEnforcer.enforce!(post.user) if post_action_type == :spam
+
+ def self.hide_post!(post, reason=nil)
+ return if post.hidden
+
+ unless reason
+ old_flags,_ = PostAction.flag_counts_for(post.id)
+ reason = guess_hide_reason(old_flags)
+ end
+
+ Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason], id: post.id)
+ Topic.update_all({ visible: false },
+ ["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)",
+ topic_id: post.topic_id])
+
+ # inform user
+ if post.user
+ SystemMessage.create(post.user,
+ :post_hidden,
+ url: post.url,
+ edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts)
+ end
+ end
+
+ def self.guess_hide_reason(old_flags)
+ old_flags > 0 ?
+ Post.hidden_reasons[:flag_threshold_reached_again] :
+ Post.hidden_reasons[:flag_threshold_reached]
end
def self.flagged_posts_report(filter)
@@ -268,7 +313,11 @@ class PostAction < ActiveRecord::Base
post_actions = PostAction.includes({:related_post => :topic})
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
.where(post_id: post_lookup.keys)
- post_actions = post_actions.with_deleted if filter == 'old'
+ if filter == 'old'
+ post_actions = post_actions.with_deleted.where('deleted_at IS NOT NULL OR defer = true')
+ else
+ post_actions = post_actions.where('defer IS NULL OR defer = false')
+ end
post_actions.each do |pa|
post = post_lookup[pa.post_id]
@@ -309,13 +358,12 @@ class PostAction < ActiveRecord::Base
# it may make sense to add a view that shows flags on deleted posts,
# we don't clear the flags on post deletion, just supress counts
- # they may have deleted_at on the action not set
if filter == 'old'
- sql.where2 "deleted_at is not null"
+ sql.where2 "deleted_at is not null OR defer = true"
sql.order_by "max desc"
else
sql.where "p.deleted_at is null and t.deleted_at is null"
- sql.where2 "deleted_at is null"
+ sql.where2 "deleted_at is null and (defer IS NULL OR defer = false)"
sql.order_by "cnt desc, max asc"
end
@@ -343,6 +391,8 @@ end
# message :text
# related_post_id :integer
# staff_took_action :boolean default(FALSE), not null
+# defer :boolean
+# defer_by :integer
#
# Indexes
#
diff --git a/app/models/user.rb b/app/models/user.rb
index 2be3b030679..9a41d5a6d06 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -653,6 +653,7 @@ end
# likes_received :integer default(0), not null
# topic_reply_count :integer default(0), not null
# blocked :boolean default(FALSE)
+# dynamic_favicon :boolean default(FALSE), not null
#
# Indexes
#
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 5e2bc97438e..fe19d5e12b2 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1033,10 +1033,18 @@ en:
title: "Flags"
old: "Old"
active: "Active"
- clear: "Clear Flags"
- clear_title: "dismiss all flags on this post (will unhide hidden posts)"
- delete: "Delete Post"
- delete_title: "delete post (if its the first post delete topic)"
+
+ agree_hide: "Agree (Hide + PM)"
+ agree_hide_title: "Agree with flags, hide post and send user a private message"
+ defer: "Defer"
+ defer_title: "Defer flag handling to the system"
+ delete_post: "Delete Post"
+ delete_post_title: "delete post (if its the first post delete topic)"
+ disagree_unhide: "Disagree (Unhide)"
+ disagree_unhide_title: "Disagree with flag, remove flags from post and show post"
+ disagree: "Disagree"
+ disagree_title: "Disagree with flag, remove flags from post"
+
flagged_by: "Flagged by"
error: "Something went wrong"
view_message: "view message"
diff --git a/config/routes.rb b/config/routes.rb
index 93470f04d8e..424ce3a3612 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -69,7 +69,9 @@ Discourse::Application.routes.draw do
get 'customize' => 'site_customizations#index', constraints: AdminConstraint.new
get 'flags' => 'flags#index'
get 'flags/:filter' => 'flags#index'
- post 'flags/clear/:id' => 'flags#clear'
+ post 'flags/agree/:id' => 'flags#agree'
+ post 'flags/disagree/:id' => 'flags#disagree'
+ post 'flags/defer/:id' => 'flags#defer'
resources :site_customizations, constraints: AdminConstraint.new
resources :site_contents, constraints: AdminConstraint.new
resources :site_content_types, constraints: AdminConstraint.new
diff --git a/db/migrate/20130619063902_add_defer_to_post_actions.rb b/db/migrate/20130619063902_add_defer_to_post_actions.rb
new file mode 100644
index 00000000000..ac290b92d8a
--- /dev/null
+++ b/db/migrate/20130619063902_add_defer_to_post_actions.rb
@@ -0,0 +1,7 @@
+class AddDeferToPostActions < ActiveRecord::Migration
+ def change
+ # an action can be deferred by a moderator, used for flags
+ add_column :post_actions, :defer, :boolean
+ add_column :post_actions, :defer_by, :int
+ end
+end
diff --git a/spec/models/post_action_spec.rb b/spec/models/post_action_spec.rb
index bac455e97c1..8100525e3b1 100644
--- a/spec/models/post_action_spec.rb
+++ b/spec/models/post_action_spec.rb
@@ -84,6 +84,20 @@ describe PostAction do
PostAction.flagged_posts_count.should == 0
end
+ it "should ignore validated flags" do
+ admin = Fabricate(:admin)
+ PostAction.act(codinghorror, post, PostActionType.types[:off_topic])
+ post.hidden.should be_false
+ PostAction.defer_flags!(post, admin.id)
+ PostAction.flagged_posts_count.should == 0
+ post.reload
+ post.hidden.should be_false
+
+ PostAction.hide_post!(post)
+ post.reload
+ post.hidden.should be_true
+ end
+
end
describe "when a user bookmarks something" do
@@ -220,7 +234,7 @@ describe PostAction do
u2 = Fabricate(:walter_white)
admin = Fabricate(:admin) # we need an admin for the messages
- SiteSetting.flags_required_to_hide_post = 2
+ SiteSetting.stubs(:flags_required_to_hide_post).returns(2)
PostAction.act(u1, post, PostActionType.types[:spam])
PostAction.act(u2, post, PostActionType.types[:spam])