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