mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 04:34:13 -06:00
5f64fd0a21
Introduce new patterns for direct sql that are safe and fast. MiniSql is not prone to memory bloat that can happen with direct PG usage. It also has an extremely fast materializer and very a convenient API - DB.exec(sql, *params) => runs sql returns row count - DB.query(sql, *params) => runs sql returns usable objects (not a hash) - DB.query_hash(sql, *params) => runs sql returns an array of hashes - DB.query_single(sql, *params) => runs sql and returns a flat one dimensional array - DB.build(sql) => returns a sql builder See more at: https://github.com/discourse/mini_sql
661 lines
22 KiB
Ruby
661 lines
22 KiB
Ruby
require_dependency 'rate_limiter'
|
|
require_dependency 'system_message'
|
|
|
|
class PostAction < ActiveRecord::Base
|
|
class AlreadyActed < StandardError; end
|
|
|
|
include RateLimiter::OnCreateRecord
|
|
include Trashable
|
|
|
|
belongs_to :post
|
|
belongs_to :user
|
|
belongs_to :post_action_type
|
|
belongs_to :related_post, class_name: 'Post'
|
|
belongs_to :target_user, class_name: 'User'
|
|
|
|
rate_limit :post_action_rate_limiter
|
|
|
|
scope :spam_flags, -> { where(post_action_type_id: PostActionType.types[:spam]) }
|
|
scope :flags, -> { where(post_action_type_id: PostActionType.notify_flag_type_ids) }
|
|
scope :publics, -> { where(post_action_type_id: PostActionType.public_type_ids) }
|
|
scope :active, -> { where(disagreed_at: nil, deferred_at: nil, agreed_at: nil, deleted_at: nil) }
|
|
|
|
after_save :update_counters
|
|
after_save :enforce_rules
|
|
after_save :create_user_action
|
|
after_save :update_notifications
|
|
after_create :create_notifications
|
|
after_commit :notify_subscribers
|
|
|
|
def disposed_by_id
|
|
disagreed_by_id || agreed_by_id || deferred_by_id
|
|
end
|
|
|
|
def disposed_at
|
|
disagreed_at || agreed_at || deferred_at
|
|
end
|
|
|
|
def disposition
|
|
return :disagreed if disagreed_at
|
|
return :agreed if agreed_at
|
|
return :deferred if deferred_at
|
|
nil
|
|
end
|
|
|
|
def self.flag_count_by_date(start_date, end_date, category_id = nil)
|
|
result = where('post_actions.created_at >= ? AND post_actions.created_at <= ?', start_date, end_date)
|
|
result = result.where(post_action_type_id: PostActionType.flag_types_without_custom.values)
|
|
result = result.joins(post: :topic).where("topics.category_id = ?", category_id) if category_id
|
|
result.group('date(post_actions.created_at)')
|
|
.order('date(post_actions.created_at)')
|
|
.count
|
|
end
|
|
|
|
def self.update_flagged_posts_count
|
|
flagged_relation = PostAction.active
|
|
.flags
|
|
.joins(post: :topic)
|
|
.where('posts.deleted_at' => nil)
|
|
.where('topics.deleted_at' => nil)
|
|
.where('posts.user_id > 0')
|
|
.group("posts.id")
|
|
|
|
if SiteSetting.min_flags_staff_visibility > 1
|
|
flagged_relation = flagged_relation
|
|
.having("count(*) >= ?", SiteSetting.min_flags_staff_visibility)
|
|
end
|
|
|
|
posts_flagged_count = flagged_relation
|
|
.pluck("posts.id")
|
|
.count
|
|
|
|
$redis.set('posts_flagged_count', posts_flagged_count)
|
|
user_ids = User.staff.pluck(:id)
|
|
MessageBus.publish('/flagged_counts', { total: posts_flagged_count }, user_ids: user_ids)
|
|
end
|
|
|
|
def self.flagged_posts_count
|
|
$redis.get('posts_flagged_count').to_i
|
|
end
|
|
|
|
def self.counts_for(collection, user)
|
|
return {} if collection.blank? || !user
|
|
|
|
collection_ids = collection.map(&:id)
|
|
user_id = user.try(:id) || 0
|
|
|
|
post_actions = PostAction.where(post_id: collection_ids, user_id: user_id)
|
|
|
|
user_actions = {}
|
|
post_actions.each do |post_action|
|
|
user_actions[post_action.post_id] ||= {}
|
|
user_actions[post_action.post_id][post_action.post_action_type_id] = post_action
|
|
end
|
|
|
|
user_actions
|
|
end
|
|
|
|
def self.lookup_for(user, topics, post_action_type_id)
|
|
return if topics.blank?
|
|
# in critical path 2x faster than AR
|
|
#
|
|
topic_ids = topics.map(&:id)
|
|
map = {}
|
|
builder = SqlBuilder.new <<SQL
|
|
SELECT p.topic_id, p.post_number
|
|
FROM post_actions pa
|
|
JOIN posts p ON pa.post_id = p.id
|
|
WHERE p.deleted_at IS NULL AND pa.deleted_at IS NULL AND
|
|
pa.post_action_type_id = :post_action_type_id AND
|
|
pa.user_id = :user_id AND
|
|
p.topic_id IN (:topic_ids)
|
|
ORDER BY p.topic_id, p.post_number
|
|
SQL
|
|
|
|
builder.map_exec(OpenStruct, user_id: user.id, post_action_type_id: post_action_type_id, topic_ids: topic_ids).each do |row|
|
|
(map[row.topic_id] ||= []) << row.post_number
|
|
end
|
|
|
|
map
|
|
end
|
|
|
|
def self.active_flags_counts_for(collection)
|
|
return {} if collection.blank?
|
|
|
|
collection_ids = collection.map(&:id)
|
|
|
|
post_actions = PostAction.active.flags.where(post_id: collection_ids)
|
|
|
|
user_actions = {}
|
|
post_actions.each do |post_action|
|
|
user_actions[post_action.post_id] ||= {}
|
|
user_actions[post_action.post_id][post_action.post_action_type_id] ||= []
|
|
user_actions[post_action.post_id][post_action.post_action_type_id] << post_action
|
|
end
|
|
|
|
user_actions
|
|
end
|
|
|
|
def self.count_per_day_for_type(post_action_type, opts = nil)
|
|
opts ||= {}
|
|
result = unscoped.where(post_action_type_id: post_action_type)
|
|
result = result.where('post_actions.created_at >= ?', opts[:start_date] || (opts[:since_days_ago] || 30).days.ago)
|
|
result = result.where('post_actions.created_at <= ?', opts[:end_date]) if opts[:end_date]
|
|
result = result.joins(post: :topic).where('topics.category_id = ?', opts[:category_id]) if opts[:category_id]
|
|
result.group('date(post_actions.created_at)')
|
|
.order('date(post_actions.created_at)')
|
|
.count
|
|
end
|
|
|
|
def self.agree_flags!(post, moderator, delete_post = false)
|
|
actions = PostAction.active
|
|
.where(post_id: post.id)
|
|
.where(post_action_type_id: PostActionType.flag_types.values)
|
|
|
|
trigger_spam = false
|
|
actions.each do |action|
|
|
action.agreed_at = Time.zone.now
|
|
action.agreed_by_id = moderator.id
|
|
# so callback is called
|
|
action.save
|
|
action.add_moderator_post_if_needed(moderator, :agreed, delete_post)
|
|
trigger_spam = true if action.post_action_type_id == PostActionType.types[:spam]
|
|
end
|
|
|
|
DiscourseEvent.trigger(:confirmed_spam_post, post) if trigger_spam
|
|
DiscourseEvent.trigger(:flag_reviewed, post)
|
|
DiscourseEvent.trigger(:flag_agreed, actions.first) if actions.first.present?
|
|
|
|
update_flagged_posts_count
|
|
end
|
|
|
|
def self.clear_flags!(post, moderator)
|
|
# -1 is the automatic system cleary
|
|
action_type_ids =
|
|
if moderator.id == Discourse::SYSTEM_USER_ID
|
|
PostActionType.auto_action_flag_types.values
|
|
else
|
|
PostActionType.notify_flag_type_ids
|
|
end
|
|
|
|
actions = PostAction.where(post_id: post.id)
|
|
.where(post_action_type_id: action_type_ids)
|
|
|
|
actions.each do |action|
|
|
action.disagreed_at = Time.zone.now
|
|
action.disagreed_by_id = moderator.id
|
|
# so callback is called
|
|
action.save
|
|
action.add_moderator_post_if_needed(moderator, :disagreed)
|
|
end
|
|
|
|
# reset all cached counters
|
|
cached = {}
|
|
action_type_ids.each do |atid|
|
|
column = "#{PostActionType.types[atid]}_count"
|
|
cached[column] = 0 if ActiveRecord::Base.connection.column_exists?(:posts, column)
|
|
end
|
|
|
|
Post.with_deleted.where(id: post.id).update_all(cached)
|
|
DiscourseEvent.trigger(:flag_reviewed, post)
|
|
DiscourseEvent.trigger(:flag_disagreed, actions.first) if actions.first.present?
|
|
|
|
update_flagged_posts_count
|
|
end
|
|
|
|
def self.defer_flags!(post, moderator, delete_post = false)
|
|
actions = PostAction.active
|
|
.where(post_id: post.id)
|
|
.where(post_action_type_id: PostActionType.notify_flag_type_ids)
|
|
|
|
actions.each do |action|
|
|
action.deferred_at = Time.zone.now
|
|
action.deferred_by_id = moderator.id
|
|
# so callback is called
|
|
action.save
|
|
action.add_moderator_post_if_needed(moderator, :deferred, delete_post)
|
|
end
|
|
|
|
DiscourseEvent.trigger(:flag_reviewed, post)
|
|
DiscourseEvent.trigger(:flag_deferred, actions.first) if actions.first.present?
|
|
update_flagged_posts_count
|
|
end
|
|
|
|
def add_moderator_post_if_needed(moderator, disposition, delete_post = false)
|
|
return if !SiteSetting.auto_respond_to_flag_actions
|
|
return if related_post.nil? || related_post.topic.nil?
|
|
return if staff_already_replied?(related_post.topic)
|
|
message_key = "flags_dispositions.#{disposition}"
|
|
message_key << "_and_deleted" if delete_post
|
|
|
|
I18n.with_locale(SiteSetting.default_locale) do
|
|
related_post.topic.add_moderator_post(moderator, I18n.t(message_key))
|
|
end
|
|
end
|
|
|
|
def staff_already_replied?(topic)
|
|
topic.posts.where("user_id IN (SELECT id FROM users WHERE moderator OR admin) OR (post_type != :regular_post_type)", regular_post_type: Post.types[:regular]).exists?
|
|
end
|
|
|
|
def self.create_message_for_post_action(user, post, post_action_type_id, opts)
|
|
post_action_type = PostActionType.types[post_action_type_id]
|
|
|
|
return unless opts[:message] && [:notify_moderators, :notify_user, :spam].include?(post_action_type)
|
|
|
|
title = I18n.t("post_action_types.#{post_action_type}.email_title", title: post.topic.title, locale: SiteSetting.default_locale)
|
|
body = I18n.t("post_action_types.#{post_action_type}.email_body", message: opts[:message], link: "#{Discourse.base_url}#{post.url}", locale: SiteSetting.default_locale)
|
|
warning = opts[:is_warning] if opts[:is_warning].present?
|
|
title = title.truncate(SiteSetting.max_topic_title_length, separator: /\s/)
|
|
|
|
opts = {
|
|
archetype: Archetype.private_message,
|
|
is_warning: warning,
|
|
title: title,
|
|
raw: body
|
|
}
|
|
|
|
if [:notify_moderators, :spam].include?(post_action_type)
|
|
opts[:subtype] = TopicSubtype.notify_moderators
|
|
opts[:target_group_names] = target_moderators
|
|
else
|
|
opts[:subtype] = TopicSubtype.notify_user
|
|
|
|
opts[:target_usernames] =
|
|
if post_action_type == :notify_user
|
|
post.user.username
|
|
elsif post_action_type != :notify_moderators
|
|
# this is a hack to allow a PM with no recipients, we should think through
|
|
# a cleaner technique, a PM with myself is valid for flagging
|
|
'x'
|
|
end
|
|
end
|
|
|
|
PostCreator.new(user, opts).create!&.id
|
|
end
|
|
|
|
def self.limit_action!(user, post, post_action_type_id)
|
|
RateLimiter.new(user, "post_action-#{post.id}_#{post_action_type_id}", 4, 1.minute).performed!
|
|
end
|
|
|
|
def self.act(user, post, post_action_type_id, opts = {})
|
|
limit_action!(user, post, post_action_type_id)
|
|
|
|
related_post_id = create_message_for_post_action(user, post, post_action_type_id, opts)
|
|
staff_took_action = opts[:take_action] || false
|
|
|
|
targets_topic =
|
|
if opts[:flag_topic] && post.topic
|
|
post.topic.reload.posts_count != 1
|
|
end
|
|
|
|
where_attrs = {
|
|
post_id: post.id,
|
|
user_id: user.id,
|
|
post_action_type_id: post_action_type_id
|
|
}
|
|
|
|
action_attrs = {
|
|
staff_took_action: staff_took_action,
|
|
related_post_id: related_post_id,
|
|
targets_topic: !!targets_topic
|
|
}
|
|
|
|
# First try to revive a trashed record
|
|
post_action = PostAction.where(where_attrs)
|
|
.with_deleted
|
|
.where("deleted_at IS NOT NULL")
|
|
.first
|
|
|
|
if post_action
|
|
post_action.recover!
|
|
action_attrs.each { |attr, val| post_action.send("#{attr}=", val) }
|
|
post_action.save
|
|
PostActionNotifier.post_action_created(post_action)
|
|
else
|
|
post_action = create(where_attrs.merge(action_attrs))
|
|
if post_action && post_action.errors.count == 0
|
|
BadgeGranter.queue_badge_grant(Badge::Trigger::PostAction, post_action: post_action)
|
|
end
|
|
end
|
|
|
|
if post_action && PostActionType.notify_flag_type_ids.include?(post_action_type_id)
|
|
DiscourseEvent.trigger(:flag_created, post_action)
|
|
end
|
|
|
|
GivenDailyLike.increment_for(user.id) if post_action_type_id == PostActionType.types[:like]
|
|
|
|
# agree with other flags
|
|
if staff_took_action
|
|
PostAction.agree_flags!(post, user)
|
|
post_action.try(:update_counters)
|
|
end
|
|
|
|
post_action
|
|
rescue ActiveRecord::RecordNotUnique
|
|
# can happen despite being .create
|
|
# since already bookmarked
|
|
PostAction.where(where_attrs).first
|
|
end
|
|
|
|
def self.copy(original_post, target_post)
|
|
cols_to_copy = (column_names - %w{id post_id}).join(', ')
|
|
|
|
DB.exec <<~SQL
|
|
INSERT INTO post_actions(post_id, #{cols_to_copy})
|
|
SELECT #{target_post.id}, #{cols_to_copy}
|
|
FROM post_actions
|
|
WHERE post_id = #{original_post.id}
|
|
SQL
|
|
|
|
target_post.post_actions.each { |post_action| post_action.update_counters }
|
|
end
|
|
|
|
def self.remove_act(user, post, post_action_type_id)
|
|
|
|
limit_action!(user, post, post_action_type_id)
|
|
|
|
finder = PostAction.where(post_id: post.id, user_id: user.id, post_action_type_id: post_action_type_id)
|
|
finder = finder.with_deleted.includes(:post) if user.try(:staff?)
|
|
if action = finder.first
|
|
action.remove_act!(user)
|
|
action.post.unhide! if action.staff_took_action
|
|
GivenDailyLike.decrement_for(user.id) if post_action_type_id == PostActionType.types[:like]
|
|
end
|
|
end
|
|
|
|
def remove_act!(user)
|
|
trash!(user)
|
|
# NOTE: save is called to ensure all callbacks are called
|
|
# trash will not trigger callbacks, and triggering after_commit
|
|
# is not trivial
|
|
save
|
|
end
|
|
|
|
def is_bookmark?
|
|
post_action_type_id == PostActionType.types[:bookmark]
|
|
end
|
|
|
|
def is_like?
|
|
post_action_type_id == PostActionType.types[:like]
|
|
end
|
|
|
|
def is_flag?
|
|
!!PostActionType.notify_flag_types[post_action_type_id]
|
|
end
|
|
|
|
def is_private_message?
|
|
post_action_type_id == PostActionType.types[:notify_user] ||
|
|
post_action_type_id == PostActionType.types[:notify_moderators]
|
|
end
|
|
|
|
# A custom rate limiter for this model
|
|
def post_action_rate_limiter
|
|
return unless is_flag? || is_bookmark? || is_like?
|
|
|
|
return @rate_limiter if @rate_limiter.present?
|
|
|
|
%w(like flag bookmark).each do |type|
|
|
if send("is_#{type}?")
|
|
limit = SiteSetting.send("max_#{type}s_per_day")
|
|
|
|
if is_like? && user && user.trust_level >= 2
|
|
multiplier = SiteSetting.send("tl#{user.trust_level}_additional_likes_per_day_multiplier").to_f
|
|
multiplier = 1.0 if multiplier < 1.0
|
|
|
|
limit = (limit * multiplier).to_i
|
|
end
|
|
|
|
@rate_limiter = RateLimiter.new(user, "create_#{type}", limit, 1.day.to_i)
|
|
return @rate_limiter
|
|
end
|
|
end
|
|
end
|
|
|
|
before_create do
|
|
post_action_type_ids = is_flag? ? PostActionType.notify_flag_types.values : post_action_type_id
|
|
raise AlreadyActed if PostAction.where(user_id: user_id)
|
|
.where(post_id: post_id)
|
|
.where(post_action_type_id: post_action_type_ids)
|
|
.where(deleted_at: nil)
|
|
.where(disagreed_at: nil)
|
|
.where(targets_topic: targets_topic)
|
|
.exists?
|
|
end
|
|
|
|
# Returns the flag counts for a post, taking into account that some users
|
|
# can weigh flags differently.
|
|
def self.flag_counts_for(post_id)
|
|
params = {
|
|
post_id: post_id,
|
|
post_action_types: PostActionType.auto_action_flag_types.values,
|
|
flags_required_to_hide_post: SiteSetting.flags_required_to_hide_post
|
|
}
|
|
|
|
DB.query_single(<<~SQL, params)
|
|
SELECT COALESCE(SUM(CASE
|
|
WHEN pa.disagreed_at IS NOT NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
|
|
WHEN pa.disagreed_at IS NOT NULL AND NOT pa.staff_took_action THEN 1
|
|
ELSE 0
|
|
END),0) AS old_flags,
|
|
COALESCE(SUM(CASE
|
|
WHEN pa.disagreed_at IS NULL AND pa.staff_took_action THEN :flags_required_to_hide_post
|
|
WHEN pa.disagreed_at IS NULL AND NOT pa.staff_took_action THEN 1
|
|
ELSE 0
|
|
END), 0) AS new_flags
|
|
FROM post_actions AS pa
|
|
INNER JOIN users AS u ON u.id = pa.user_id
|
|
WHERE pa.post_id = :post_id
|
|
AND pa.post_action_type_id in (:post_action_types)
|
|
AND pa.deleted_at IS NULL
|
|
SQL
|
|
end
|
|
|
|
def post_action_type_key
|
|
PostActionType.types[post_action_type_id]
|
|
end
|
|
|
|
def update_counters
|
|
# Update denormalized counts
|
|
column = "#{post_action_type_key}_count"
|
|
count = PostAction.where(post_id: post_id)
|
|
.where(post_action_type_id: post_action_type_id)
|
|
.count
|
|
|
|
# We probably want to refactor this method to something cleaner.
|
|
case post_action_type_key
|
|
when :vote
|
|
# Voting also changes the sort_order
|
|
Post.where(id: post_id).update_all ["vote_count = :count, sort_order = :max - :count", count: count, max: Topic.max_sort_order]
|
|
when :like
|
|
# 'like_score' is weighted higher for staff accounts
|
|
score = PostAction.joins(:user)
|
|
.where(post_id: post_id)
|
|
.sum("CASE WHEN users.moderator OR users.admin THEN #{SiteSetting.staff_like_weight} ELSE 1 END")
|
|
Post.where(id: post_id).update_all ["like_count = :count, like_score = :score", count: count, score: score]
|
|
else
|
|
if ActiveRecord::Base.connection.column_exists?(:posts, column)
|
|
Post.where(id: post_id).update_all ["#{column} = ?", count]
|
|
end
|
|
end
|
|
|
|
topic_id = Post.with_deleted.where(id: post_id).pluck(:topic_id).first
|
|
|
|
# topic_user
|
|
if [:like, :bookmark].include? post_action_type_key
|
|
TopicUser.update_post_action_cache(user_id: user_id,
|
|
topic_id: topic_id,
|
|
post_action_type: post_action_type_key)
|
|
end
|
|
|
|
if column == "like_count"
|
|
topic_count = Post.where(topic_id: topic_id).sum(column)
|
|
Topic.where(id: topic_id).update_all ["#{column} = ?", topic_count]
|
|
end
|
|
|
|
if PostActionType.notify_flag_type_ids.include?(post_action_type_id)
|
|
PostAction.update_flagged_posts_count
|
|
end
|
|
|
|
end
|
|
|
|
def enforce_rules
|
|
post = Post.with_deleted.where(id: post_id).first
|
|
PostAction.auto_close_if_threshold_reached(post.topic)
|
|
PostAction.auto_hide_if_needed(user, post, post_action_type_key)
|
|
SpamRulesEnforcer.enforce!(post.user)
|
|
end
|
|
|
|
def create_user_action
|
|
if is_bookmark? || is_like?
|
|
UserActionCreator.log_post_action(self)
|
|
end
|
|
end
|
|
|
|
def update_notifications
|
|
if self.deleted_at.present?
|
|
PostActionNotifier.post_action_deleted(self)
|
|
end
|
|
end
|
|
|
|
def create_notifications
|
|
PostActionNotifier.post_action_created(self)
|
|
end
|
|
|
|
def notify_subscribers
|
|
if (is_like? || is_flag?) && post
|
|
post.publish_change_to_clients! :acted
|
|
end
|
|
end
|
|
|
|
MAXIMUM_FLAGS_PER_POST = 3
|
|
|
|
def self.auto_close_if_threshold_reached(topic)
|
|
return if topic.nil? || topic.closed?
|
|
|
|
flags = PostAction.active
|
|
.flags
|
|
.joins(:post)
|
|
.where("posts.topic_id = ?", topic.id)
|
|
.where("post_actions.user_id > 0")
|
|
.group("post_actions.user_id")
|
|
.pluck("post_actions.user_id, COUNT(post_id)")
|
|
|
|
# we need a minimum number of unique flaggers
|
|
return if flags.count < SiteSetting.num_flaggers_to_close_topic
|
|
# we need a minimum number of flags
|
|
return if flags.sum { |f| f[1] } < SiteSetting.num_flags_to_close_topic
|
|
|
|
# the threshold has been reached, we will close the topic waiting for intervention
|
|
topic.update_status("closed", true, Discourse.system_user,
|
|
message: I18n.t(
|
|
"temporarily_closed_due_to_flags",
|
|
count: SiteSetting.num_hours_to_close_topic
|
|
)
|
|
)
|
|
|
|
topic.set_or_create_timer(
|
|
TopicTimer.types[:open],
|
|
SiteSetting.num_hours_to_close_topic,
|
|
by_user: Discourse.system_user
|
|
)
|
|
end
|
|
|
|
def self.auto_hide_if_needed(acting_user, post, post_action_type)
|
|
return if post.hidden?
|
|
return if (!acting_user.staff?) && post.user.staff?
|
|
|
|
if post_action_type == :spam &&
|
|
acting_user.has_trust_level?(TrustLevel[3]) &&
|
|
post.user.trust_level == TrustLevel[0]
|
|
|
|
hide_post!(post, post_action_type, Post.hidden_reasons[:flagged_by_tl3_user])
|
|
|
|
elsif 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
|
|
hide_post!(post, post_action_type, guess_hide_reason(post))
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.hide_post!(post, post_action_type, reason = nil)
|
|
return if post.hidden
|
|
|
|
unless reason
|
|
reason = guess_hide_reason(post)
|
|
end
|
|
|
|
hiding_again = post.hidden_at.present?
|
|
|
|
post.hidden = true
|
|
post.hidden_at = Time.zone.now
|
|
post.hidden_reason_id = reason
|
|
post.save
|
|
|
|
Topic.where("id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)", topic_id: post.topic_id).update_all(visible: false)
|
|
|
|
# inform user
|
|
if post.user
|
|
options = {
|
|
url: post.url,
|
|
edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts,
|
|
flag_reason: I18n.t("flag_reasons.#{post_action_type}", locale: SiteSetting.default_locale),
|
|
}
|
|
|
|
Jobs.enqueue_in(5.seconds, :send_system_message,
|
|
user_id: post.user.id,
|
|
message_type: hiding_again ? :post_hidden_again : :post_hidden,
|
|
message_options: options)
|
|
end
|
|
end
|
|
|
|
def self.guess_hide_reason(post)
|
|
post.hidden_at ?
|
|
Post.hidden_reasons[:flag_threshold_reached_again] :
|
|
Post.hidden_reasons[:flag_threshold_reached]
|
|
end
|
|
|
|
def self.post_action_type_for_post(post_id)
|
|
post_action = PostAction.find_by(deferred_at: nil, post_id: post_id, post_action_type_id: PostActionType.notify_flag_types.values, deleted_at: nil)
|
|
PostActionType.types[post_action.post_action_type_id]
|
|
end
|
|
|
|
def self.target_moderators
|
|
Group[:moderators].name
|
|
end
|
|
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: post_actions
|
|
#
|
|
# id :integer not null, primary key
|
|
# post_id :integer not null
|
|
# user_id :integer not null
|
|
# post_action_type_id :integer not null
|
|
# deleted_at :datetime
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# deleted_by_id :integer
|
|
# related_post_id :integer
|
|
# staff_took_action :boolean default(FALSE), not null
|
|
# deferred_by_id :integer
|
|
# targets_topic :boolean default(FALSE), not null
|
|
# agreed_at :datetime
|
|
# agreed_by_id :integer
|
|
# deferred_at :datetime
|
|
# disagreed_at :datetime
|
|
# disagreed_by_id :integer
|
|
#
|
|
# Indexes
|
|
#
|
|
# idx_unique_actions (user_id,post_action_type_id,post_id,targets_topic) UNIQUE
|
|
# idx_unique_flags (user_id,post_id,targets_topic) UNIQUE
|
|
# index_post_actions_on_post_id (post_id)
|
|
# index_post_actions_on_user_id_and_post_action_type_id (user_id,post_action_type_id)
|
|
#
|