2013-02-05 13:16:51 -06:00
require_dependency 'rate_limiter'
require_dependency 'system_message'
class PostAction < ActiveRecord :: Base
2013-05-03 19:52:45 -05:00
class AlreadyActed < StandardError ; end
2013-02-07 09:45:24 -06:00
2013-02-05 13:16:51 -06:00
include RateLimiter :: OnCreateRecord
2013-05-06 23:39:01 -05:00
include Trashable
2013-02-05 13:16:51 -06:00
belongs_to :post
belongs_to :user
belongs_to :post_action_type
2013-05-29 15:49:34 -05:00
belongs_to :related_post , class_name : 'Post'
2014-02-05 16:54:16 -06:00
belongs_to :target_user , class_name : 'User'
2013-02-05 13:16:51 -06:00
rate_limit :post_action_rate_limiter
2013-05-31 10:41:40 -05:00
scope :spam_flags , - > { where ( post_action_type_id : PostActionType . types [ :spam ] ) }
2014-07-28 12:17:37 -05:00
scope :flags , - > { where ( post_action_type_id : PostActionType . notify_flag_type_ids ) }
scope :publics , - > { where ( post_action_type_id : PostActionType . public_type_ids ) }
2014-08-11 04:56:54 -05:00
scope :active , - > { where ( disagreed_at : nil , deferred_at : nil , agreed_at : nil , deleted_at : nil ) }
2013-05-31 10:41:40 -05:00
2013-08-14 22:44:30 -05:00
after_save :update_counters
after_save :enforce_rules
2016-12-21 22:03:40 -06:00
after_save :create_user_action
2016-12-21 23:46:22 -06:00
after_save :update_notifications
after_create :create_notifications
2014-03-30 20:34:01 -05:00
after_commit :notify_subscribers
2013-08-14 22:44:30 -05:00
2014-07-28 12:17:37 -05:00
def disposed_by_id
2014-08-11 04:56:54 -05:00
disagreed_by_id || agreed_by_id || deferred_by_id
2014-07-28 12:17:37 -05:00
end
def disposed_at
2014-08-11 04:56:54 -05:00
disagreed_at || agreed_at || deferred_at
2014-07-28 12:17:37 -05:00
end
def disposition
2014-07-30 16:35:42 -05:00
return :disagreed if disagreed_at
2014-07-28 12:17:37 -05:00
return :agreed if agreed_at
2014-08-11 04:56:54 -05:00
return :deferred if deferred_at
2014-07-28 12:17:37 -05:00
nil
end
2017-07-27 20:20:09 -05:00
def self . flag_count_by_date ( start_date , end_date , category_id = nil )
2015-06-24 08:19:39 -05:00
result = where ( 'post_actions.created_at >= ? AND post_actions.created_at <= ?' , start_date , end_date )
2017-10-17 12:31:45 -05:00
result = result . where ( post_action_type_id : PostActionType . flag_types_without_custom . values )
2015-06-24 08:19:39 -05:00
result = result . joins ( post : :topic ) . where ( " topics.category_id = ? " , category_id ) if category_id
result . group ( 'date(post_actions.created_at)' )
2017-07-27 20:20:09 -05:00
. order ( 'date(post_actions.created_at)' )
. count
2014-12-30 08:06:15 -06:00
end
2013-02-05 13:16:51 -06:00
def self . update_flagged_posts_count
2018-05-07 14:14:18 -05:00
flagged_relation = PostAction . active
2017-07-27 20:20:09 -05:00
. flags
. joins ( post : :topic )
. where ( 'posts.deleted_at' = > nil )
. where ( 'topics.deleted_at' = > nil )
. where ( 'posts.user_id > 0' )
2018-05-07 14:14:18 -05:00
. 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
2013-02-08 14:54:28 -06:00
$redis . set ( 'posts_flagged_count' , posts_flagged_count )
2013-05-17 14:11:37 -05:00
user_ids = User . staff . pluck ( :id )
2017-07-27 20:20:09 -05:00
MessageBus . publish ( '/flagged_counts' , { total : posts_flagged_count } , user_ids : user_ids )
2013-02-05 13:16:51 -06:00
end
def self . flagged_posts_count
$redis . get ( 'posts_flagged_count' ) . to_i
end
def self . counts_for ( collection , user )
2015-09-28 01:42:05 -05:00
return { } if collection . blank? || ! user
2013-02-05 13:16:51 -06:00
2014-07-28 12:17:37 -05:00
collection_ids = collection . map ( & :id )
2014-07-30 16:35:42 -05:00
user_id = user . try ( :id ) || 0
2013-02-05 13:16:51 -06:00
2014-07-28 12:17:37 -05:00
post_actions = PostAction . where ( post_id : collection_ids , user_id : user_id )
2014-06-04 10:41:11 -05:00
2013-02-05 13:16:51 -06:00
user_actions = { }
2014-07-28 12:17:37 -05:00
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
2013-02-05 13:16:51 -06:00
end
2013-02-07 09:45:24 -06:00
2013-02-05 13:16:51 -06:00
user_actions
2013-02-07 09:45:24 -06:00
end
2013-02-05 13:16:51 -06:00
2015-01-07 01:20:10 -06:00
def self . lookup_for ( user , topics , post_action_type_id )
return if topics . blank?
2015-04-16 02:29:18 -05:00
# in critical path 2x faster than AR
#
topic_ids = topics . map ( & :id )
2015-01-07 01:20:10 -06:00
map = { }
2015-04-16 02:29:18 -05:00
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
2015-01-07 01:20:10 -06:00
end
map
end
2014-08-04 10:29:01 -05:00
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
2017-07-27 20:20:09 -05:00
def self . count_per_day_for_type ( post_action_type , opts = nil )
2015-06-24 08:19:39 -05:00
opts || = { }
result = unscoped . where ( post_action_type_id : post_action_type )
2015-10-19 15:30:34 -05:00
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 ]
2015-06-24 08:19:39 -05:00
result = result . joins ( post : :topic ) . where ( 'topics.category_id = ?' , opts [ :category_id ] ) if opts [ :category_id ]
result . group ( 'date(post_actions.created_at)' )
2017-07-27 20:20:09 -05:00
. order ( 'date(post_actions.created_at)' )
. count
2014-07-28 12:17:37 -05:00
end
2017-07-27 20:20:09 -05:00
def self . agree_flags! ( post , moderator , delete_post = false )
2014-07-28 12:17:37 -05:00
actions = PostAction . active
2017-07-27 20:20:09 -05:00
. where ( post_id : post . id )
. where ( post_action_type_id : PostActionType . flag_types . values )
2014-07-28 12:17:37 -05:00
2015-02-02 14:52:02 -06:00
trigger_spam = false
2014-07-28 12:17:37 -05:00
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 )
2016-03-04 05:55:49 -06:00
trigger_spam = true if action . post_action_type_id == PostActionType . types [ :spam ]
2014-07-28 12:17:37 -05:00
end
2016-03-04 05:55:49 -06:00
DiscourseEvent . trigger ( :confirmed_spam_post , post ) if trigger_spam
2018-02-26 17:51:36 -06:00
DiscourseEvent . trigger ( :flag_reviewed , post )
2018-04-11 09:17:05 -05:00
DiscourseEvent . trigger ( :flag_agreed , actions . first ) if actions . first . present?
2015-02-02 14:52:02 -06:00
2014-07-28 12:17:37 -05:00
update_flagged_posts_count
2013-03-17 12:53:00 -05:00
end
2014-07-28 12:17:37 -05:00
def self . clear_flags! ( post , moderator )
2013-02-05 13:16:51 -06:00
# -1 is the automatic system cleary
2017-03-14 01:33:06 -05:00
action_type_ids =
if moderator . id == Discourse :: SYSTEM_USER_ID
PostActionType . auto_action_flag_types . values
else
2017-10-30 09:52:00 -05:00
PostActionType . notify_flag_type_ids
2017-03-14 01:33:06 -05:00
end
2014-07-28 12:17:37 -05:00
actions = PostAction . where ( post_id : post . id )
2017-07-27 20:20:09 -05:00
. where ( post_action_type_id : action_type_ids )
2014-07-28 12:17:37 -05:00
actions . each do | action |
2014-07-30 16:35:42 -05:00
action . disagreed_at = Time . zone . now
action . disagreed_by_id = moderator . id
2014-07-28 12:17:37 -05:00
# so callback is called
action . save
action . add_moderator_post_if_needed ( moderator , :disagreed )
2013-02-06 22:15:48 -06:00
end
2013-02-05 13:16:51 -06:00
2014-07-28 12:17:37 -05:00
# reset all cached counters
2017-10-17 12:31:45 -05:00
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 )
2018-02-26 17:51:36 -06:00
DiscourseEvent . trigger ( :flag_reviewed , post )
2018-04-11 09:17:05 -05:00
DiscourseEvent . trigger ( :flag_disagreed , actions . first ) if actions . first . present?
2014-07-28 12:17:37 -05:00
2013-02-05 13:16:51 -06:00
update_flagged_posts_count
end
2017-07-27 20:20:09 -05:00
def self . defer_flags! ( post , moderator , delete_post = false )
2014-07-28 12:17:37 -05:00
actions = PostAction . active
2017-07-27 20:20:09 -05:00
. where ( post_id : post . id )
2017-10-20 09:04:37 -05:00
. where ( post_action_type_id : PostActionType . notify_flag_type_ids )
2013-06-20 02:42:15 -05:00
2014-07-28 12:17:37 -05:00
actions . each do | action |
2014-08-11 04:56:54 -05:00
action . deferred_at = Time . zone . now
action . deferred_by_id = moderator . id
2013-06-20 02:42:15 -05:00
# so callback is called
2014-07-28 12:17:37 -05:00
action . save
2014-08-11 04:56:54 -05:00
action . add_moderator_post_if_needed ( moderator , :deferred , delete_post )
2013-06-20 02:42:15 -05:00
end
2018-02-26 17:51:36 -06:00
DiscourseEvent . trigger ( :flag_reviewed , post )
2018-04-11 09:17:05 -05:00
DiscourseEvent . trigger ( :flag_deferred , actions . first ) if actions . first . present?
2013-06-20 02:42:15 -05:00
update_flagged_posts_count
end
2017-07-27 20:20:09 -05:00
def add_moderator_post_if_needed ( moderator , disposition , delete_post = false )
2015-03-11 13:29:09 -05:00
return if ! SiteSetting . auto_respond_to_flag_actions
2015-03-03 10:52:46 -06:00
return if related_post . nil? || related_post . topic . nil?
2015-03-16 06:02:34 -05:00
return if staff_already_replied? ( related_post . topic )
2014-07-28 12:17:37 -05:00
message_key = " flags_dispositions. #{ disposition } "
message_key << " _and_deleted " if delete_post
2017-08-09 04:17:54 -05:00
I18n . with_locale ( SiteSetting . default_locale ) do
related_post . topic . add_moderator_post ( moderator , I18n . t ( message_key ) )
end
2014-07-28 12:17:37 -05:00
end
2015-03-16 06:02:34 -05:00
def staff_already_replied? ( topic )
2015-07-24 15:39:03 -05:00
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?
2014-08-18 10:00:14 -05:00
end
2013-08-19 06:14:26 -05:00
def self . create_message_for_post_action ( user , post , post_action_type_id , opts )
post_action_type = PostActionType . types [ post_action_type_id ]
2013-04-12 02:55:45 -05:00
2015-02-05 12:58:49 -06:00
return unless opts [ :message ] && [ :notify_moderators , :notify_user , :spam ] . include? ( post_action_type )
2013-08-19 06:14:26 -05:00
2016-05-04 13:49:05 -05:00
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 )
2016-04-03 10:33:56 -05:00
warning = opts [ :is_warning ] if opts [ :is_warning ] . present?
2014-12-11 12:34:52 -06:00
title = title . truncate ( 255 , separator : / \ s / )
2014-05-13 10:44:23 -05:00
opts = {
archetype : Archetype . private_message ,
2016-04-03 10:33:56 -05:00
is_warning : warning ,
2014-05-13 10:44:23 -05:00
title : title ,
raw : body
}
2015-02-05 12:58:49 -06:00
if [ :notify_moderators , :spam ] . include? ( post_action_type )
2014-05-13 10:44:23 -05:00
opts [ :subtype ] = TopicSubtype . notify_moderators
2016-08-05 08:27:46 -05:00
opts [ :target_group_names ] = target_moderators
2014-05-12 14:26:36 -05:00
else
2014-05-13 10:44:23 -05:00
opts [ :subtype ] = TopicSubtype . notify_user
2017-07-27 20:20:09 -05:00
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
2014-05-12 14:26:36 -05:00
end
2013-05-31 16:38:28 -05:00
2017-12-18 20:58:26 -06:00
PostCreator . new ( user , opts ) . create! & . id
2013-02-05 13:16:51 -06:00
end
2017-07-27 20:20:09 -05:00
def self . limit_action! ( user , post , post_action_type_id )
2016-03-05 16:51:30 -06:00
RateLimiter . new ( user , " post_action- #{ post . id } _ #{ post_action_type_id } " , 4 , 1 . minute ) . performed!
end
2014-08-20 13:14:19 -05:00
def self . act ( user , post , post_action_type_id , opts = { } )
2017-07-27 20:20:09 -05:00
limit_action! ( user , post , post_action_type_id )
2016-03-05 16:51:30 -06:00
2014-07-28 12:17:37 -05:00
related_post_id = create_message_for_post_action ( user , post , post_action_type_id , opts )
staff_took_action = opts [ :take_action ] || false
2013-08-19 06:14:26 -05:00
2017-07-27 20:20:09 -05:00
targets_topic =
if opts [ :flag_topic ] && post . topic
post . topic . reload . posts_count != 1
end
2014-02-18 14:18:31 -06:00
2014-07-18 15:14:47 -05:00
where_attrs = {
post_id : post . id ,
user_id : user . id ,
post_action_type_id : post_action_type_id
}
2015-02-19 09:43:12 -06:00
action_attrs = {
2014-07-28 12:17:37 -05:00
staff_took_action : staff_took_action ,
2014-07-18 15:14:47 -05:00
related_post_id : related_post_id ,
targets_topic : ! ! targets_topic
}
# First try to revive a trashed record
2015-02-19 09:43:12 -06:00
post_action = PostAction . where ( where_attrs )
2017-07-27 20:20:09 -05:00
. with_deleted
. where ( " deleted_at IS NOT NULL " )
. first
2015-02-19 09:43:12 -06:00
if post_action
post_action . recover!
2015-02-20 11:33:55 -06:00
action_attrs . each { | attr , val | post_action . send ( " #{ attr } = " , val ) }
2015-02-19 09:43:12 -06:00
post_action . save
2016-12-21 23:46:22 -06:00
PostActionNotifier . post_action_created ( post_action )
2015-02-19 09:43:12 -06:00
else
post_action = create ( where_attrs . merge ( action_attrs ) )
2014-07-22 20:42:24 -05:00
if post_action && post_action . errors . count == 0
BadgeGranter . queue_badge_grant ( Badge :: Trigger :: PostAction , post_action : post_action )
2014-07-18 15:14:47 -05:00
end
2014-06-17 01:29:49 -05:00
end
2018-02-26 15:27:18 -06:00
2018-04-11 09:17:05 -05:00
if post_action && PostActionType . notify_flag_type_ids . include? ( post_action_type_id )
DiscourseEvent . trigger ( :flag_created , post_action )
end
2018-02-26 15:27:18 -06:00
GivenDailyLike . increment_for ( user . id ) if post_action_type_id == PostActionType . types [ :like ]
2014-06-17 01:29:49 -05:00
2014-07-28 12:17:37 -05:00
# agree with other flags
2015-06-25 02:19:32 -05:00
if staff_took_action
PostAction . agree_flags! ( post , user )
post_action . try ( :update_counters )
end
2014-07-28 12:17:37 -05:00
2014-06-17 01:29:49 -05:00
post_action
2013-08-19 06:14:26 -05:00
rescue ActiveRecord :: RecordNotUnique
# can happen despite being .create
# since already bookmarked
2014-08-17 21:03:46 -05:00
PostAction . where ( where_attrs ) . first
2013-08-19 06:14:26 -05:00
end
2017-02-10 08:35:04 -06:00
def self . copy ( original_post , target_post )
cols_to_copy = ( column_names - %w{ id post_id } ) . join ( ', ' )
exec_sql << ~ 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
2013-02-05 13:16:51 -06:00
def self . remove_act ( user , post , post_action_type_id )
2016-03-05 16:51:30 -06:00
2017-07-27 20:20:09 -05:00
limit_action! ( user , post , post_action_type_id )
2016-03-05 16:51:30 -06:00
2014-06-04 10:41:11 -05:00
finder = PostAction . where ( post_id : post . id , user_id : user . id , post_action_type_id : post_action_type_id )
2014-08-19 09:14:17 -05:00
finder = finder . with_deleted . includes ( :post ) if user . try ( :staff? )
2014-06-04 10:41:11 -05:00
if action = finder . first
2013-08-14 22:44:30 -05:00
action . remove_act! ( user )
2014-08-19 09:14:17 -05:00
action . post . unhide! if action . staff_took_action
2018-02-26 15:27:18 -06:00
GivenDailyLike . decrement_for ( user . id ) if post_action_type_id == PostActionType . types [ :like ]
2013-02-05 13:16:51 -06:00
end
end
2013-05-12 20:48:01 -05:00
def remove_act! ( user )
2013-07-09 14:20:18 -05:00
trash! ( user )
2014-03-30 20:34:01 -05:00
# NOTE: save is called to ensure all callbacks are called
# trash will not trigger callbacks, and triggering after_commit
# is not trivial
save
2013-05-12 20:48:01 -05:00
end
2013-02-07 09:45:24 -06:00
def is_bookmark?
2013-03-01 06:07:44 -06:00
post_action_type_id == PostActionType . types [ :bookmark ]
2013-02-05 13:16:51 -06:00
end
2013-02-07 09:45:24 -06:00
def is_like?
2013-03-01 06:07:44 -06:00
post_action_type_id == PostActionType . types [ :like ]
2013-02-05 13:16:51 -06:00
end
def is_flag?
2018-02-27 21:22:51 -06:00
! ! PostActionType . notify_flag_types [ post_action_type_id ]
2013-02-07 09:45:24 -06:00
end
2013-02-05 13:16:51 -06:00
2013-04-12 02:55:45 -05:00
def is_private_message?
post_action_type_id == PostActionType . types [ :notify_user ] ||
post_action_type_id == PostActionType . types [ :notify_moderators ]
end
2013-05-10 15:58:23 -05:00
2013-02-05 13:16:51 -06:00
# A custom rate limiter for this model
def post_action_rate_limiter
2013-02-28 12:54:12 -06:00
return unless is_flag? || is_bookmark? || is_like?
2013-02-05 13:16:51 -06:00
return @rate_limiter if @rate_limiter . present?
%w( like flag bookmark ) . each do | type |
if send ( " is_ #{ type } ? " )
2015-04-15 18:44:30 -05:00
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
2017-07-27 20:20:09 -05:00
limit = ( limit * multiplier ) . to_i
2015-04-15 18:44:30 -05:00
end
2017-07-27 20:20:09 -05:00
@rate_limiter = RateLimiter . new ( user , " create_ #{ type } " , limit , 1 . day . to_i )
2013-02-05 13:16:51 -06:00
return @rate_limiter
end
end
end
2013-02-08 15:55:40 -06:00
2013-02-07 09:45:24 -06:00
before_create do
2018-02-27 21:22:51 -06:00
post_action_type_ids = is_flag? ? PostActionType . notify_flag_types . values : post_action_type_id
2014-07-28 12:17:37 -05:00
raise AlreadyActed if PostAction . where ( user_id : user_id )
2017-07-27 20:20:09 -05:00
. 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?
2013-02-06 17:45:58 -06:00
end
2013-05-10 15:58:23 -05:00
# Returns the flag counts for a post, taking into account that some users
# can weigh flags differently.
def self . flag_counts_for ( post_id )
flag_counts = exec_sql ( " SELECT SUM(CASE
2014-07-30 16:35:42 -05:00
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
2013-05-10 15:58:23 -05:00
ELSE 0
END ) AS new_flags ,
SUM ( CASE
2014-07-30 16:35:42 -05:00
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
2013-05-10 15:58:23 -05:00
ELSE 0
END ) AS old_flags
FROM post_actions AS pa
INNER JOIN users AS u ON u . id = pa . user_id
2014-07-30 16:35:42 -05:00
WHERE pa . post_id = :post_id
AND pa . post_action_type_id IN ( :post_action_types )
AND pa . deleted_at IS NULL " ,
2013-05-10 15:58:23 -05:00
post_id : post_id ,
post_action_types : PostActionType . auto_action_flag_types . values ,
flags_required_to_hide_post : SiteSetting . flags_required_to_hide_post ) . first
[ flag_counts [ 'old_flags' ] . to_i , flag_counts [ 'new_flags' ] . to_i ]
end
2013-08-14 22:44:30 -05:00
def post_action_type_key
PostActionType . types [ post_action_type_id ]
end
def update_counters
2013-02-05 13:16:51 -06:00
# Update denormalized counts
2014-08-14 13:20:52 -05:00
column = " #{ post_action_type_key } _count "
2014-07-28 12:17:37 -05:00
count = PostAction . where ( post_id : post_id )
2017-07-27 20:20:09 -05:00
. where ( post_action_type_id : post_action_type_id )
. count
2013-02-05 13:16:51 -06:00
2013-05-27 11:45:10 -05:00
# We probably want to refactor this method to something cleaner.
2013-08-14 22:44:30 -05:00
case post_action_type_key
2013-05-27 11:45:10 -05:00
when :vote
# Voting also changes the sort_order
2014-07-28 12:17:37 -05:00
Post . where ( id : post_id ) . update_all [ " vote_count = :count, sort_order = :max - :count " , count : count , max : Topic . max_sort_order ]
2013-05-27 11:45:10 -05:00
when :like
2014-12-05 12:37:43 -06:00
# 'like_score' is weighted higher for staff accounts
2014-07-28 12:17:37 -05:00
score = PostAction . joins ( :user )
2017-07-27 20:20:09 -05:00
. where ( post_id : post_id )
. sum ( " CASE WHEN users.moderator OR users.admin THEN #{ SiteSetting . staff_like_weight } ELSE 1 END " )
2014-07-28 12:17:37 -05:00
Post . where ( id : post_id ) . update_all [ " like_count = :count, like_score = :score " , count : count , score : score ]
2013-02-05 13:16:51 -06:00
else
2017-10-17 12:31:45 -05:00
if ActiveRecord :: Base . connection . column_exists? ( :posts , column )
Post . where ( id : post_id ) . update_all [ " #{ column } = ? " , count ]
end
2013-02-05 13:16:51 -06:00
end
2013-05-27 11:45:10 -05:00
2014-07-28 12:17:37 -05:00
topic_id = Post . with_deleted . where ( id : post_id ) . pluck ( :topic_id ) . first
2015-01-07 21:35:56 -06:00
# topic_user
2017-07-27 20:20:09 -05:00
if [ :like , :bookmark ] . include? post_action_type_key
2015-01-07 21:35:56 -06:00
TopicUser . update_post_action_cache ( user_id : user_id ,
topic_id : topic_id ,
post_action_type : post_action_type_key )
end
2016-12-02 00:03:31 -06:00
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
2013-02-05 13:16:51 -06:00
2013-05-29 15:49:34 -05:00
if PostActionType . notify_flag_type_ids . include? ( post_action_type_id )
2013-02-05 13:16:51 -06:00
PostAction . update_flagged_posts_count
end
2013-08-14 22:44:30 -05:00
end
2013-06-20 02:42:15 -05:00
2013-08-14 22:44:30 -05:00
def enforce_rules
2014-06-04 10:41:11 -05:00
post = Post . with_deleted . where ( id : post_id ) . first
2015-02-02 14:55:44 -06:00
PostAction . auto_close_if_threshold_reached ( post . topic )
2014-10-01 11:53:17 -05:00
PostAction . auto_hide_if_needed ( user , post , post_action_type_key )
2016-06-15 09:51:26 -05:00
SpamRulesEnforcer . enforce! ( post . user )
2013-06-20 02:42:15 -05:00
end
2016-12-21 22:03:40 -06:00
def create_user_action
if is_bookmark? || is_like?
UserActionCreator . log_post_action ( self )
end
end
2016-12-21 23:46:22 -06:00
def update_notifications
if self . deleted_at . present?
PostActionNotifier . post_action_deleted ( self )
end
end
def create_notifications
PostActionNotifier . post_action_created ( self )
end
2014-03-23 21:22:03 -05:00
def notify_subscribers
if ( is_like? || is_flag? ) && post
2014-08-28 22:34:32 -05:00
post . publish_change_to_clients! :acted
2014-03-23 21:22:03 -05:00
end
end
2014-12-05 12:37:43 -06:00
MAXIMUM_FLAGS_PER_POST = 3
2015-02-02 14:55:44 -06:00
def self . auto_close_if_threshold_reached ( topic )
2015-03-03 10:52:46 -06:00
return if topic . nil? || topic . closed?
2014-12-05 12:37:43 -06:00
flags = PostAction . active
2017-07-27 20:20:09 -05:00
. 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) " )
2014-12-05 12:37:43 -06:00
2014-12-06 09:29:54 -06:00
# 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
2014-12-05 12:37:43 -06:00
2014-12-06 09:29:54 -06:00
# the threshold has been reached, we will close the topic waiting for intervention
2017-03-31 01:35:05 -05:00
topic . update_status ( " closed " , true , Discourse . system_user ,
message : I18n . t (
" temporarily_closed_due_to_flags " ,
count : SiteSetting . num_hours_to_close_topic
)
)
2017-05-11 17:23:18 -05:00
topic . set_or_create_timer (
TopicTimer . types [ :open ] ,
2017-03-31 01:35:05 -05:00
SiteSetting . num_hours_to_close_topic ,
by_user : Discourse . system_user
)
2014-12-05 12:37:43 -06:00
end
2014-10-01 11:53:17 -05:00
def self . auto_hide_if_needed ( acting_user , post , post_action_type )
2018-03-20 08:38:23 -05:00
return if post . hidden?
return if ( ! acting_user . staff? ) && post . user . staff?
2013-06-20 02:42:15 -05:00
2014-10-01 11:53:17 -05:00
if post_action_type == :spam &&
2014-11-19 10:26:57 -06:00
acting_user . has_trust_level? ( TrustLevel [ 3 ] ) &&
2014-10-01 11:53:17 -05:00
post . user . trust_level == TrustLevel [ 0 ]
2017-07-27 20:20:09 -05:00
hide_post! ( post , post_action_type , Post . hidden_reasons [ :flagged_by_tl3_user ] )
2014-10-01 11:53:17 -05:00
elsif PostActionType . auto_action_flag_types . include? ( post_action_type ) &&
SiteSetting . flags_required_to_hide_post > 0
2013-06-20 02:42:15 -05:00
2016-03-04 05:55:49 -06:00
_old_flags , new_flags = PostAction . flag_counts_for ( post . id )
2013-02-05 13:16:51 -06:00
if new_flags > = SiteSetting . flags_required_to_hide_post
2015-12-29 15:59:26 -06:00
hide_post! ( post , post_action_type , guess_hide_reason ( post ) )
2013-02-05 13:16:51 -06:00
end
end
2013-06-20 02:42:15 -05:00
end
2013-05-31 10:41:40 -05:00
2017-07-27 20:20:09 -05:00
def self . hide_post! ( post , post_action_type , reason = nil )
2013-06-20 02:42:15 -05:00
return if post . hidden
unless reason
2015-12-29 15:59:26 -06:00
reason = guess_hide_reason ( post )
2013-06-20 02:42:15 -05:00
end
2018-04-05 05:53:22 -05:00
hiding_again = post . hidden_at . present?
2015-12-29 15:59:26 -06:00
post . hidden = true
post . hidden_at = Time . zone . now
post . hidden_reason_id = reason
post . save
2014-08-19 09:14:17 -05:00
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 )
2013-06-20 02:42:15 -05:00
# inform user
if post . user
2014-04-30 09:58:01 -05:00
options = {
url : post . url ,
edit_delay : SiteSetting . cooldown_minutes_after_hiding_posts ,
flag_reason : I18n . t ( " flag_reasons. #{ post_action_type } " ) ,
}
2017-09-12 14:43:03 -05:00
2018-04-05 05:53:22 -05:00
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 )
2013-06-20 02:42:15 -05:00
end
end
2015-12-29 15:59:26 -06:00
def self . guess_hide_reason ( post )
post . hidden_at ?
2013-06-20 02:42:15 -05:00
Post . hidden_reasons [ :flag_threshold_reached_again ] :
Post . hidden_reasons [ :flag_threshold_reached ]
2013-02-05 13:16:51 -06:00
end
2013-04-12 02:55:45 -05:00
2014-04-30 09:58:01 -05:00
def self . post_action_type_for_post ( post_id )
2017-10-26 13:41:11 -05:00
post_action = PostAction . find_by ( deferred_at : nil , post_id : post_id , post_action_type_id : PostActionType . notify_flag_types . values , deleted_at : nil )
2014-04-30 09:58:01 -05:00
PostActionType . types [ post_action . post_action_type_id ]
end
2013-05-09 02:37:34 -05:00
def self . target_moderators
Group [ :moderators ] . name
2013-04-12 02:55:45 -05:00
end
2013-02-05 13:16:51 -06:00
end
2013-05-23 21:48:32 -05:00
# == 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
2014-08-27 00:19:25 -05:00
# created_at :datetime not null
# updated_at :datetime not null
2013-07-09 14:20:18 -05:00
# deleted_by_id :integer
2013-05-23 21:48:32 -05:00
# related_post_id :integer
2013-06-16 19:48:58 -05:00
# staff_took_action :boolean default(FALSE), not null
2014-08-22 12:01:44 -05:00
# deferred_by_id :integer
# targets_topic :boolean default(FALSE), not null
2014-07-28 12:17:37 -05:00
# agreed_at :datetime
# agreed_by_id :integer
2014-08-22 12:01:44 -05:00
# deferred_at :datetime
2014-07-30 22:14:40 -05:00
# disagreed_at :datetime
# disagreed_by_id :integer
2013-05-23 21:48:32 -05:00
#
# Indexes
#
2015-09-17 19:41:10 -05:00
# 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)
2013-05-23 21:48:32 -05:00
#