2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-04-12 04:04:20 +08:00
|
|
|
# Responsible for destroying a User record
|
|
|
|
class UserDestroyer
|
|
|
|
class PostsExistError < RuntimeError
|
|
|
|
end
|
|
|
|
|
2014-02-14 00:42:35 +08:00
|
|
|
def initialize(actor)
|
|
|
|
@actor = actor
|
|
|
|
raise Discourse::InvalidParameters.new("acting user is nil") unless @actor && @actor.is_a?(User)
|
|
|
|
@guardian = Guardian.new(actor)
|
2013-04-12 04:04:20 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Returns false if the user failed to be deleted.
|
|
|
|
# Returns a frozen instance of the User if the delete succeeded.
|
2013-07-25 01:48:55 +08:00
|
|
|
def destroy(user, opts = {})
|
2013-04-12 04:04:20 +08:00
|
|
|
raise Discourse::InvalidParameters.new("user is nil") unless user && user.is_a?(User)
|
2021-06-11 13:07:34 +08:00
|
|
|
raise PostsExistError if !opts[:delete_posts] && user.posts.joins(:topic).count != 0
|
2017-03-18 05:01:45 +08:00
|
|
|
@guardian.ensure_can_delete_user!(user)
|
2014-10-20 22:59:06 +08:00
|
|
|
|
2019-01-17 04:54:21 +08:00
|
|
|
# default to using a transaction
|
|
|
|
opts[:transaction] = true if opts[:transaction] != false
|
|
|
|
|
2019-01-18 23:04:29 +08:00
|
|
|
prepare_for_destroy(user) if opts[:prepare_for_destroy] == true
|
|
|
|
|
2019-04-10 23:00:14 +08:00
|
|
|
result = nil
|
|
|
|
|
2019-01-17 04:54:21 +08:00
|
|
|
optional_transaction(open_transaction: opts[:transaction]) do
|
2020-02-26 03:07:57 +08:00
|
|
|
UserSecurityKey.where(user_id: user.id).delete_all
|
2020-03-12 08:16:00 +08:00
|
|
|
Bookmark.where(user_id: user.id).delete_all
|
2015-08-24 14:05:08 +08:00
|
|
|
Draft.where(user_id: user.id).delete_all
|
2019-01-04 01:03:01 +08:00
|
|
|
Reviewable.where(created_by_id: user.id).delete_all
|
2015-04-25 04:04:44 +08:00
|
|
|
|
2020-12-23 11:19:30 +08:00
|
|
|
category_topic_ids = Category.where("topic_id IS NOT NULL").pluck(:topic_id)
|
|
|
|
|
2013-07-25 01:48:55 +08:00
|
|
|
if opts[:delete_posts]
|
2022-11-29 00:32:57 +08:00
|
|
|
DiscoursePluginRegistry.user_destroyer_on_content_deletion_callbacks.each do |cb|
|
|
|
|
cb.call(user, @guardian, opts)
|
|
|
|
end
|
|
|
|
|
2021-02-02 05:57:31 +08:00
|
|
|
agree_with_flags(user) if opts[:delete_as_spammer]
|
|
|
|
block_external_urls(user) if opts[:block_urls]
|
|
|
|
delete_posts(user, category_topic_ids, opts)
|
2013-07-25 01:48:55 +08:00
|
|
|
end
|
2014-10-20 22:59:06 +08:00
|
|
|
|
2014-04-01 02:06:25 +08:00
|
|
|
user.post_actions.find_each { |post_action| post_action.remove_act!(Discourse.system_user) }
|
2014-10-20 22:59:06 +08:00
|
|
|
|
2018-10-03 01:46:43 +08:00
|
|
|
# Add info about the user to staff action logs
|
|
|
|
UserHistory.staff_action_records(
|
|
|
|
Discourse.system_user,
|
|
|
|
acting_user: user.username,
|
2022-05-11 20:39:31 +08:00
|
|
|
).update_all(
|
|
|
|
["details = CONCAT(details, ?)", "\nuser_id: #{user.id}\nusername: #{user.username}"],
|
|
|
|
)
|
2018-10-03 01:46:43 +08:00
|
|
|
|
2017-07-25 23:44:46 +08:00
|
|
|
# keep track of emails used
|
|
|
|
user_emails = user.user_emails.pluck(:email)
|
|
|
|
|
2019-04-10 23:00:14 +08:00
|
|
|
if result = user.destroy
|
|
|
|
if opts[:block_email]
|
|
|
|
user_emails.each do |email|
|
|
|
|
ScreenedEmail.block(email, ip_address: result.ip_address)&.record_match!
|
2013-07-26 03:30:03 +08:00
|
|
|
end
|
2019-04-10 23:00:14 +08:00
|
|
|
end
|
2014-11-22 01:16:06 +08:00
|
|
|
|
2019-04-10 23:00:14 +08:00
|
|
|
if opts[:block_ip] && result.ip_address
|
|
|
|
ScreenedIpAddress.watch(result.ip_address)&.record_match!
|
|
|
|
if result.registration_ip_address && result.ip_address != result.registration_ip_address
|
|
|
|
ScreenedIpAddress.watch(result.registration_ip_address)&.record_match!
|
2013-10-22 02:49:51 +08:00
|
|
|
end
|
2019-04-10 23:00:14 +08:00
|
|
|
end
|
2014-11-22 01:16:06 +08:00
|
|
|
|
2019-04-10 23:00:14 +08:00
|
|
|
Post.unscoped.where(user_id: result.id).update_all(user_id: nil)
|
2013-11-02 04:55:56 +08:00
|
|
|
|
2019-04-10 23:00:14 +08:00
|
|
|
# If this user created categories, fix those up:
|
|
|
|
Category
|
|
|
|
.where(user_id: result.id)
|
|
|
|
.each do |c|
|
|
|
|
c.user_id = Discourse::SYSTEM_USER_ID
|
|
|
|
c.save!
|
|
|
|
if topic = Topic.unscoped.find_by(id: c.topic_id)
|
|
|
|
topic.recover!
|
|
|
|
topic.user_id = Discourse::SYSTEM_USER_ID
|
|
|
|
topic.save!
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2013-11-02 04:55:56 +08:00
|
|
|
end
|
|
|
|
|
2021-05-04 00:49:53 +08:00
|
|
|
Invite
|
|
|
|
.where(email: user_emails)
|
|
|
|
.each do |invite|
|
|
|
|
# invited_users will be removed by dependent destroy association when user is destroyed
|
|
|
|
invite.invited_groups.destroy_all
|
|
|
|
invite.topic_invites.destroy_all
|
|
|
|
invite.destroy
|
|
|
|
end
|
|
|
|
|
2019-04-10 23:00:14 +08:00
|
|
|
unless opts[:quiet]
|
|
|
|
if @actor == user
|
|
|
|
deleted_by = Discourse.system_user
|
|
|
|
opts[:context] = I18n.t("staff_action_logs.user_delete_self", url: opts[:context])
|
|
|
|
else
|
|
|
|
deleted_by = @actor
|
2017-10-03 23:28:41 +08:00
|
|
|
end
|
2019-04-10 23:00:14 +08:00
|
|
|
StaffActionLogger.new(deleted_by).log_user_deletion(user, opts.slice(:context))
|
2021-02-23 19:17:54 +08:00
|
|
|
if opts.slice(:context).blank?
|
|
|
|
Rails.logger.warn("User destroyed without context from: #{caller_locations(14, 1)[0]}")
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2013-04-15 23:39:30 +08:00
|
|
|
end
|
2023-01-05 03:55:52 +08:00
|
|
|
MessageBus.publish "/logout/#{result.id}", result.id, user_ids: [result.id]
|
2013-04-12 04:04:20 +08:00
|
|
|
end
|
|
|
|
end
|
2019-04-10 23:00:14 +08:00
|
|
|
|
|
|
|
# After the user is deleted, remove the reviewable
|
2019-09-05 01:12:48 +08:00
|
|
|
if reviewable = ReviewableUser.pending.find_by(target: user)
|
2021-06-15 23:35:45 +08:00
|
|
|
reviewable.perform(@actor, :delete_user)
|
2019-04-10 23:00:14 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
result
|
2013-04-12 04:04:20 +08:00
|
|
|
end
|
|
|
|
|
2019-01-17 04:54:21 +08:00
|
|
|
protected
|
|
|
|
|
2021-02-02 05:57:31 +08:00
|
|
|
def block_external_urls(user)
|
|
|
|
TopicLink
|
|
|
|
.where(user: user, internal: false)
|
|
|
|
.find_each do |link|
|
|
|
|
next if Oneboxer.engine(link.url) != Onebox::Engine::AllowlistedGenericOnebox
|
|
|
|
ScreenedUrl.watch(link.url, link.domain, ip_address: user.ip_address)&.record_match!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def agree_with_flags(user)
|
|
|
|
ReviewableFlaggedPost
|
|
|
|
.where(target_created_by: user)
|
|
|
|
.find_each do |reviewable|
|
2021-09-06 14:11:44 +08:00
|
|
|
if reviewable.actions_for(@guardian).has?(:agree_and_keep)
|
|
|
|
reviewable.perform(@actor, :agree_and_keep)
|
2021-02-02 05:57:31 +08:00
|
|
|
end
|
2023-01-09 20:20:10 +08:00
|
|
|
end
|
2023-08-21 18:03:03 +08:00
|
|
|
|
|
|
|
ReviewablePost
|
|
|
|
.where(target_created_by: user)
|
|
|
|
.find_each do |reviewable|
|
|
|
|
if reviewable.actions_for(@guardian).has?(:reject_and_delete)
|
|
|
|
reviewable.perform(@actor, :reject_and_delete)
|
|
|
|
end
|
|
|
|
end
|
2021-02-02 05:57:31 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def delete_posts(user, category_topic_ids, opts)
|
|
|
|
user.posts.find_each do |post|
|
|
|
|
if post.is_first_post? && category_topic_ids.include?(post.topic_id)
|
|
|
|
post.update!(user: Discourse.system_user)
|
|
|
|
else
|
|
|
|
PostDestroyer.new(@actor.staff? ? @actor : Discourse.system_user, post).destroy
|
|
|
|
end
|
|
|
|
|
|
|
|
if post.topic && post.is_first_post?
|
|
|
|
Topic.unscoped.where(id: post.topic_id).update_all(user_id: nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-18 23:04:29 +08:00
|
|
|
def prepare_for_destroy(user)
|
|
|
|
PostAction.where(user_id: user.id).delete_all
|
|
|
|
UserAction.where(
|
|
|
|
"user_id = :user_id OR target_user_id = :user_id OR acting_user_id = :user_id",
|
|
|
|
user_id: user.id,
|
|
|
|
).delete_all
|
|
|
|
PostTiming.where(user_id: user.id).delete_all
|
|
|
|
TopicViewItem.where(user_id: user.id).delete_all
|
|
|
|
TopicUser.where(user_id: user.id).delete_all
|
|
|
|
TopicAllowedUser.where(user_id: user.id).delete_all
|
|
|
|
Notification.where(user_id: user.id).delete_all
|
|
|
|
end
|
|
|
|
|
2019-01-17 04:54:21 +08:00
|
|
|
def optional_transaction(open_transaction: true)
|
|
|
|
if open_transaction
|
|
|
|
User.transaction { yield }
|
|
|
|
else
|
|
|
|
yield
|
|
|
|
end
|
|
|
|
end
|
2013-11-06 03:35:28 +08:00
|
|
|
end
|