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