mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: flag dispositions normalization
All flags should end up in one of the three dispositions - Agree - Disagree - Defer In the administration area, the *active* flags section displays 4 buttons - Agree (hide post + send PM) - Disagree - Defer - Delete Clicking "Delete" will open a modal that offer to - Delete Post & Defer Flags - Delete Post & Agree with Flags - Delete Spammer (if available) When the flag has a list associated, the list will now display 1 response and 1 reply and a "show more..." link if there are more in the conversation. Replying to the conversation will NOT give a disposition. Moderators must click the buttons that does that. If someone clicks one buttons, this will add a default moderator message from that moderator saying what happened. The *old* flags section now displays the proper dispositions and is super duper fast (no more N+9999 queries). FIX: the old list includes deleted topics FIX: the lists now properly display the topic states (deleted, closed, archived, hidden, PM) FIX: flagging a topic that you've already flagged the first post
This commit is contained in:
@@ -1,105 +1,139 @@
|
||||
module FlagQuery
|
||||
def self.flagged_posts_report(current_user, filter, offset = 0, per_page = 25)
|
||||
|
||||
def self.flagged_posts_report(current_user, filter, offset=0, per_page=25)
|
||||
actions = flagged_post_actions(filter)
|
||||
|
||||
guardian = Guardian.new(current_user)
|
||||
|
||||
if !guardian.is_admin?
|
||||
actions = actions.joins(:post => :topic)
|
||||
.where('category_id in (?)', guardian.allowed_category_ids)
|
||||
actions = actions.where('category_id in (?)', guardian.allowed_category_ids)
|
||||
end
|
||||
|
||||
post_ids = actions
|
||||
.limit(per_page)
|
||||
.offset(offset)
|
||||
.group(:post_id)
|
||||
.order('min(post_actions.created_at) DESC')
|
||||
.pluck(:post_id).uniq
|
||||
post_ids = actions.limit(per_page)
|
||||
.offset(offset)
|
||||
.group(:post_id)
|
||||
.order('min(post_actions.created_at) DESC')
|
||||
.pluck(:post_id)
|
||||
.uniq
|
||||
|
||||
return nil if post_ids.blank?
|
||||
|
||||
actions = actions
|
||||
.order('post_actions.created_at DESC')
|
||||
.includes({:related_post => :topic})
|
||||
|
||||
posts = SqlBuilder.new("SELECT p.id, t.title, p.cooked, p.user_id,
|
||||
p.topic_id, p.post_number, p.hidden, t.visible topic_visible,
|
||||
p.deleted_at, t.deleted_at topic_deleted_at
|
||||
FROM posts p
|
||||
JOIN topics t ON t.id = p.topic_id
|
||||
WHERE p.id in (:post_ids)").map_exec(OpenStruct, post_ids: post_ids)
|
||||
posts = SqlBuilder.new("
|
||||
SELECT p.id,
|
||||
p.cooked,
|
||||
p.user_id,
|
||||
p.topic_id,
|
||||
p.post_number,
|
||||
p.hidden,
|
||||
p.deleted_at
|
||||
FROM posts p
|
||||
WHERE p.id in (:post_ids)").map_exec(OpenStruct, post_ids: post_ids)
|
||||
|
||||
post_lookup = {}
|
||||
users = Set.new
|
||||
user_ids = Set.new
|
||||
topic_ids = Set.new
|
||||
|
||||
posts.each do |p|
|
||||
users << p.user_id
|
||||
user_ids << p.user_id
|
||||
topic_ids << p.topic_id
|
||||
p.excerpt = Post.excerpt(p.cooked)
|
||||
p.topic_slug = Slug.for(p.title)
|
||||
p.delete_field(:cooked)
|
||||
post_lookup[p.id] = p
|
||||
end
|
||||
|
||||
# maintain order
|
||||
posts = post_ids.map{|id| post_lookup[id]}
|
||||
|
||||
post_actions = actions.where(:post_id => post_ids)
|
||||
post_actions = actions.order('post_actions.created_at DESC')
|
||||
.includes(related_post: { topic: { posts: :user }})
|
||||
.where(post_id: post_ids)
|
||||
|
||||
post_actions.each do |pa|
|
||||
post = post_lookup[pa.post_id]
|
||||
post.post_actions ||= []
|
||||
action = pa.attributes
|
||||
# TODO: add serializer so we can skip this
|
||||
action = {
|
||||
id: pa.id,
|
||||
post_id: pa.post_id,
|
||||
user_id: pa.user_id,
|
||||
post_action_type_id: pa.post_action_type_id,
|
||||
created_at: pa.created_at,
|
||||
disposed_by_id: pa.disposed_by_id,
|
||||
disposed_at: pa.disposed_at,
|
||||
disposition: pa.disposition,
|
||||
related_post_id: pa.related_post_id,
|
||||
targets_topic: pa.targets_topic,
|
||||
staff_took_action: pa.staff_took_action
|
||||
}
|
||||
action[:name_key] = PostActionType.types.key(pa.post_action_type_id)
|
||||
if (pa.related_post && pa.related_post.topic)
|
||||
action.merge!(topic_id: pa.related_post.topic_id,
|
||||
slug: pa.related_post.topic.slug,
|
||||
permalink: pa.related_post.topic.url)
|
||||
|
||||
if pa.related_post && pa.related_post.topic
|
||||
conversation = {}
|
||||
related_topic = pa.related_post.topic
|
||||
if response = related_topic.posts[0]
|
||||
conversation[:response] = {
|
||||
excerpt: excerpt(response.cooked),
|
||||
user_id: response.user_id
|
||||
}
|
||||
user_ids << response.user_id
|
||||
if reply = related_topic.posts[1]
|
||||
conversation[:reply] = {
|
||||
excerpt: excerpt(reply.cooked),
|
||||
user_id: reply.user_id
|
||||
}
|
||||
user_ids << reply.user_id
|
||||
conversation[:has_more] = related_topic.posts_count > 2
|
||||
end
|
||||
end
|
||||
|
||||
action.merge!(permalink: related_topic.relative_url, conversation: conversation)
|
||||
end
|
||||
|
||||
post.post_actions << action
|
||||
users << pa.user_id
|
||||
users << pa.deleted_by_id if pa.deleted_by_id
|
||||
|
||||
user_ids << pa.user_id
|
||||
user_ids << pa.disposed_by_id if pa.disposed_by_id
|
||||
end
|
||||
|
||||
# TODO add serializer so we can skip this
|
||||
# maintain order
|
||||
posts = post_ids.map { |id| post_lookup[id] }
|
||||
# TODO: add serializer so we can skip this
|
||||
posts.map!(&:marshal_dump)
|
||||
[posts, User.where(id: users.to_a).to_a]
|
||||
|
||||
[
|
||||
posts,
|
||||
Topic.with_deleted.where(id: topic_ids.to_a).to_a,
|
||||
User.includes(:user_stat).where(id: user_ids.to_a).to_a
|
||||
]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def self.flagged_post_ids(filter, offset, limit)
|
||||
<<SQL
|
||||
def self.flagged_post_actions(filter)
|
||||
post_actions = PostAction.flags
|
||||
.joins("INNER JOIN posts ON posts.id = post_actions.post_id")
|
||||
.joins("INNER JOIN topics ON topics.id = posts.topic_id")
|
||||
|
||||
SELECT p.id from posts p
|
||||
JOIN topics t ON t.id = p.topic_id
|
||||
WHERE p.id IN (
|
||||
SELECT post_id from post_actions
|
||||
WHERE
|
||||
)
|
||||
/*offset*/
|
||||
/*limit*/
|
||||
if filter == "old"
|
||||
post_actions.with_deleted
|
||||
.where("post_actions.deleted_at IS NOT NULL OR
|
||||
post_actions.defered_at IS NOT NULL OR
|
||||
post_actions.agreed_at IS NOT NULL")
|
||||
else
|
||||
post_actions.active
|
||||
.where("posts.deleted_at" => nil)
|
||||
.where("topics.deleted_at" => nil)
|
||||
end
|
||||
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.flagged_post_actions(filter)
|
||||
post_actions = PostAction
|
||||
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
|
||||
.joins(:post => :topic)
|
||||
|
||||
if filter == 'old'
|
||||
post_actions
|
||||
.with_deleted
|
||||
.where('post_actions.deleted_at IS NOT NULL OR
|
||||
defer = true OR
|
||||
topics.deleted_at IS NOT NULL OR
|
||||
posts.deleted_at IS NOT NULL')
|
||||
else
|
||||
post_actions
|
||||
.where('defer IS NULL OR
|
||||
defer = false')
|
||||
.where('posts.deleted_at IS NULL AND
|
||||
topics.deleted_at IS NULL')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.excerpt(cooked)
|
||||
excerpt = Post.excerpt(cooked, 200)
|
||||
# remove the first link if it's the first node
|
||||
fragment = Nokogiri::HTML.fragment(excerpt)
|
||||
if fragment.children.first == fragment.css("a:first").first
|
||||
fragment.children.first.remove
|
||||
end
|
||||
fragment.to_html.strip
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ module PostGuardian
|
||||
end
|
||||
end
|
||||
|
||||
def can_clear_flags?(post)
|
||||
def can_defer_flags?(post)
|
||||
is_staff? && post
|
||||
end
|
||||
|
||||
@@ -54,7 +54,11 @@ module PostGuardian
|
||||
end
|
||||
|
||||
def can_delete_all_posts?(user)
|
||||
is_staff? && user && !user.admin? && (user.first_post.nil? || user.first_post.created_at >= SiteSetting.delete_user_max_post_age.days.ago) && user.post_count <= SiteSetting.delete_all_posts_max.to_i
|
||||
is_staff? &&
|
||||
user &&
|
||||
!user.admin? &&
|
||||
(user.first_post_created_at.nil? || user.first_post_created_at >= SiteSetting.delete_user_max_post_age.days.ago) &&
|
||||
user.post_count <= SiteSetting.delete_all_posts_max.to_i
|
||||
end
|
||||
|
||||
# Creating Method
|
||||
|
||||
@@ -35,12 +35,11 @@ module UserGuardian
|
||||
end
|
||||
|
||||
def can_delete_user?(user)
|
||||
return false if user.nil?
|
||||
return false if user.admin?
|
||||
return false if user.nil? || user.admin?
|
||||
if is_me?(user)
|
||||
user.post_count <= 1
|
||||
else
|
||||
is_staff? && (user.first_post.nil? || user.first_post.created_at > SiteSetting.delete_user_max_post_age.to_i.days.ago)
|
||||
is_staff? && (user.first_post_created_at.nil? || user.first_post_created_at > SiteSetting.delete_user_max_post_age.to_i.days.ago)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ class PostCreator
|
||||
save_post
|
||||
extract_links
|
||||
store_unique_post_key
|
||||
consider_clearing_flags
|
||||
track_topic
|
||||
update_topic_stats
|
||||
update_user_counts
|
||||
@@ -147,21 +146,6 @@ class PostCreator
|
||||
end
|
||||
end
|
||||
|
||||
def clear_possible_flags(topic)
|
||||
# at this point we know the topic is a PM and has been replied to ... check if we need to clear any flags
|
||||
#
|
||||
first_post = Post.select(:id).where(topic_id: topic.id).find_by("post_number = 1")
|
||||
post_action = nil
|
||||
|
||||
if first_post
|
||||
post_action = PostAction.find_by(related_post_id: first_post.id, deleted_at: nil, post_action_type_id: PostActionType.types[:notify_moderators])
|
||||
end
|
||||
|
||||
if post_action
|
||||
post_action.remove_act!(@user)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_topic
|
||||
@@ -233,20 +217,23 @@ class PostCreator
|
||||
@post.store_unique_post_key
|
||||
end
|
||||
|
||||
def consider_clearing_flags
|
||||
return if @opts[:import_mode]
|
||||
return unless @topic.private_message? && @post.post_number > 1 && @topic.user_id != @post.user_id
|
||||
|
||||
clear_possible_flags(@topic)
|
||||
end
|
||||
|
||||
def update_user_counts
|
||||
@user.create_user_stat if @user.user_stat.nil?
|
||||
|
||||
if @user.user_stat.first_post_created_at.nil?
|
||||
@user.user_stat.first_post_created_at = @post.created_at
|
||||
end
|
||||
|
||||
@user.user_stat.post_count += 1
|
||||
@user.user_stat.topic_count += 1 if @post.post_number == 1
|
||||
|
||||
# We don't count replies to your own topics
|
||||
if !@opts[:import_mode] && @user.id != @topic.user_id
|
||||
@user.user_stat.update_topic_reply_count
|
||||
@user.user_stat.save!
|
||||
end
|
||||
|
||||
@user.user_stat.save!
|
||||
|
||||
@user.last_posted_at = @post.created_at
|
||||
@user.save!
|
||||
end
|
||||
|
||||
@@ -62,7 +62,8 @@ class PostDestroyer
|
||||
feature_users_in_the_topic
|
||||
Topic.reset_highest(@post.topic_id)
|
||||
end
|
||||
trash_post_actions
|
||||
trash_public_post_actions
|
||||
agree_with_flags
|
||||
trash_user_actions
|
||||
@post.update_flagged_posts_count
|
||||
remove_associated_replies
|
||||
@@ -130,15 +131,18 @@ class PostDestroyer
|
||||
Jobs.enqueue(:feature_topic_users, topic_id: @post.topic_id, except_post_id: @post.id)
|
||||
end
|
||||
|
||||
def trash_post_actions
|
||||
@post.post_actions.each do |pa|
|
||||
pa.trash!(@user)
|
||||
end
|
||||
def trash_public_post_actions
|
||||
public_post_actions = PostAction.publics.where(post_id: @post.id)
|
||||
public_post_actions.each { |pa| pa.trash!(@user) }
|
||||
|
||||
f = PostActionType.types.map{|k,v| ["#{k}_count", 0]}
|
||||
f = PostActionType.public_types.map { |k,v| ["#{k}_count", 0] }
|
||||
Post.with_deleted.where(id: @post.id).update_all(Hash[*f.flatten])
|
||||
end
|
||||
|
||||
def agree_with_flags
|
||||
PostAction.agree_flags!(@post, @user, delete_post: true)
|
||||
end
|
||||
|
||||
def trash_user_actions
|
||||
UserAction.where(target_post_id: @post.id).each do |ua|
|
||||
row = {
|
||||
|
||||
@@ -29,9 +29,7 @@ class PostJobsEnqueuer
|
||||
end
|
||||
|
||||
def after_post_create
|
||||
if @post.post_number > 1
|
||||
TopicTrackingState.publish_unread(@post)
|
||||
end
|
||||
TopicTrackingState.publish_unread(@post) if @post.post_number > 1
|
||||
|
||||
Jobs.enqueue_in(
|
||||
SiteSetting.email_time_window_mins.minutes,
|
||||
|
||||
@@ -105,7 +105,7 @@ class PostRevisor
|
||||
@post.hidden_at = nil
|
||||
@post.topic.update_attributes(visible: true)
|
||||
|
||||
PostAction.clear_flags!(@post, -1)
|
||||
PostAction.clear_flags!(@post, Discourse.system_user)
|
||||
end
|
||||
|
||||
@post.extract_quoted_post_numbers
|
||||
|
||||
Reference in New Issue
Block a user