discourse/lib/new_post_manager.rb
Selase Krakani 3d554aa10e
FIX: Keep ReviewableQueuedPosts even with user delete reviewable actions (#22501)
Performing a `Delete User`/`Delete and Block User` reviewable actions for a
queued post reviewable from the `review.show` route results in an error
popup even if the action completes successfully.

This happens because unlike other reviewable types, a user delete action
on a queued post reviewable results in the deletion of the reviewable
itself. A subsequent attempt to reload the reviewable record results in
404. The deletion happens as part of the call to `UserDestroyer` which
includes a step for destroying reviewables created by the user being
destroyed. At the root of this is the creator of the queued post
being set as the creator of the reviewable as instead of the system
user.

This change assigns the creator of the reviewable to the system user and
uses the more approapriate `target_created_by` column for the creator of the
post being queued.
2023-07-18 11:50:31 +00:00

322 lines
9.3 KiB
Ruby

# frozen_string_literal: true
# Determines what actions should be taken with new posts.
#
# The default action is to create the post, but this can be extended
# with `NewPostManager.add_handler` to take other approaches depending
# on the user or input.
class NewPostManager
attr_reader :user, :args
def self.sorted_handlers
@sorted_handlers ||= clear_handlers!
end
def self.handlers
sorted_handlers.map { |h| h[:proc] }
end
def self.plugin_payload_attributes
@payload_attributes ||= []
end
def self.add_plugin_payload_attribute(attribute)
plugin_payload_attributes << attribute
end
def self.clear_handlers!
@sorted_handlers = []
end
def self.add_handler(priority = 0, &block)
sorted_handlers << { priority: priority, proc: block }
@sorted_handlers.sort_by! { |h| -h[:priority] }
end
def self.is_first_post?(manager)
user = manager.user
args = manager.args
!!(args[:first_post_checks] && user.post_count == 0 && user.topic_count == 0)
end
def self.is_fast_typer?(manager)
args = manager.args
is_first_post?(manager) &&
args[:typing_duration_msecs].to_i < SiteSetting.min_first_post_typing_time &&
SiteSetting.auto_silence_fast_typers_on_first_post &&
manager.user.trust_level <= SiteSetting.auto_silence_fast_typers_max_trust_level
end
def self.auto_silence?(manager)
is_first_post?(manager) &&
WordWatcher.new("#{manager.args[:title]} #{manager.args[:raw]}").should_silence?
end
def self.matches_auto_silence_regex?(manager)
args = manager.args
pattern = SiteSetting.auto_silence_first_post_regex
return false unless pattern.present?
return false unless is_first_post?(manager)
begin
regex = Regexp.new(pattern, Regexp::IGNORECASE)
rescue => e
Rails.logger.warn "Invalid regex in auto_silence_first_post_regex #{e}"
return false
end
"#{args[:title]} #{args[:raw]}" =~ regex
end
def self.exempt_user?(user)
user.staff?
end
def self.post_needs_approval?(manager)
user = manager.user
return :email_auth_res_enqueue if manager.args[:email_auth_res_action] == :enqueue
return :skip if exempt_user?(user)
return :email_spam if manager.args[:email_spam]
if (
user.trust_level <= TrustLevel.levels[:basic] &&
(user.post_count + user.topic_count) < SiteSetting.approve_post_count
)
return :post_count
end
return :trust_level if user.trust_level < SiteSetting.approve_unless_trust_level.to_i
if (
manager.args[:title].present? &&
user.trust_level < SiteSetting.approve_new_topics_unless_trust_level.to_i
)
return :new_topics_unless_trust_level
end
if WordWatcher.new("#{manager.args[:title]} #{manager.args[:raw]}").requires_approval?
return :watched_word
end
return :fast_typer if is_fast_typer?(manager)
return :auto_silence_regex if auto_silence?(manager) || matches_auto_silence_regex?(manager)
return :staged if SiteSetting.approve_unless_staged? && user.staged?
return :category if post_needs_approval_in_its_category?(manager)
if (
manager.args[:image_sizes].present? &&
user.trust_level < SiteSetting.review_media_unless_trust_level.to_i
)
return :contains_media
end
:skip
end
def self.post_needs_approval_in_its_category?(manager)
if manager.args[:topic_id].present?
cat = Category.joins(:topics).find_by(topics: { id: manager.args[:topic_id] })
return false unless cat
topic = Topic.find(manager.args[:topic_id])
cat.require_reply_approval? && !manager.user.guardian.can_review_topic?(topic)
elsif manager.args[:category].present?
cat = Category.find(manager.args[:category])
cat.require_topic_approval? && !manager.user.guardian.is_category_group_moderator?(cat)
else
false
end
end
def self.default_handler(manager)
reason = post_needs_approval?(manager)
return if reason == :skip
validator = PostValidator.new
post = Post.new(raw: manager.args[:raw])
post.user = manager.user
validator.validate(post)
if post.errors[:raw].present?
result = NewPostResult.new(:created_post, false)
result.errors.add(:base, post.errors[:raw])
return result
elsif manager.args[:topic_id]
topic = Topic.unscoped.where(id: manager.args[:topic_id]).first
unless manager.user.guardian.can_create_post_on_topic?(topic)
result = NewPostResult.new(:created_post, false)
result.errors.add(:base, I18n.t(:topic_not_found))
return result
end
elsif manager.args[:category]
category = Category.find_by(id: manager.args[:category])
unless manager.user.guardian.can_create_topic_on_category?(category)
result = NewPostResult.new(:created_post, false)
result.errors.add(:base, I18n.t("js.errors.reasons.forbidden"))
return result
end
end
result = manager.enqueue(reason)
I18n.with_locale(SiteSetting.default_locale) do
if is_fast_typer?(manager)
UserSilencer.silence(
manager.user,
Discourse.system_user,
keep_posts: true,
reason: I18n.t("user.new_user_typed_too_fast"),
)
elsif auto_silence?(manager) || matches_auto_silence_regex?(manager)
UserSilencer.silence(
manager.user,
Discourse.system_user,
keep_posts: true,
reason: I18n.t("user.content_matches_auto_silence_regex"),
)
elsif reason == :email_spam && is_first_post?(manager)
UserSilencer.silence(
manager.user,
Discourse.system_user,
keep_posts: true,
reason: I18n.t("user.email_in_spam_header"),
)
end
end
result
end
def self.queue_enabled?
SiteSetting.approve_post_count > 0 || SiteSetting.approve_unless_trust_level.to_i > 0 ||
SiteSetting.approve_new_topics_unless_trust_level.to_i > 0 ||
SiteSetting.approve_unless_staged ||
WordWatcher.words_for_action_exists?(:require_approval) || handlers.size > 1
end
def initialize(user, args)
@user = user
@args = args.delete_if { |_, v| v.nil? }
end
def perform
if !self.class.exempt_user?(@user) &&
matches = WordWatcher.new("#{@args[:title]} #{@args[:raw]}").should_block?.presence
result = NewPostResult.new(:created_post, false)
if matches.size == 1
key = "contains_blocked_word"
translation_args = { word: CGI.escapeHTML(matches[0]) }
else
key = "contains_blocked_words"
translation_args = { words: CGI.escapeHTML(matches.join(", ")) }
end
result.errors.add(:base, I18n.t(key, translation_args))
return result
end
# Perform handlers until one returns a result
NewPostManager.handlers.any? do |handler|
result = handler.call(self)
return result if result
end
# We never queue private messages
if @args[:archetype] == Archetype.private_message ||
(
args[:topic_id] &&
Topic.where(id: args[:topic_id], archetype: Archetype.private_message).exists?
)
return perform_create_post
end
NewPostManager.default_handler(self) || perform_create_post
end
# Enqueue this post
def enqueue(reason = nil)
result = NewPostResult.new(:enqueued)
payload = { raw: @args[:raw], tags: @args[:tags] }
%w[typing_duration_msecs composer_open_duration_msecs reply_to_post_number].each do |a|
payload[a] = @args[a].to_i if @args[a]
end
self.class.plugin_payload_attributes.each { |a| payload[a] = @args[a] if @args[a].present? }
payload[:via_email] = true if !!@args[:via_email]
payload[:raw_email] = @args[:raw_email] if @args[:raw_email].present?
reviewable =
ReviewableQueuedPost.new(
created_by: Discourse.system_user,
payload: payload,
topic_id: @args[:topic_id],
reviewable_by_moderator: true,
target_created_by: @user,
)
reviewable.payload["title"] = @args[:title] if @args[:title].present?
reviewable.category_id = args[:category] if args[:category].present?
reviewable.created_new!
create_options = reviewable.create_options
creator =
(
if @args[:topic_id]
PostCreator.new(@user, create_options)
else
TopicCreator.new(@user, Guardian.new(@user), create_options)
end
)
errors = Set.new
creator.valid?
creator.errors.full_messages.each { |msg| errors << msg }
errors = creator.errors.full_messages.uniq
if errors.blank?
if reviewable.save
reviewable.add_score(
Discourse.system_user,
ReviewableScore.types[:needs_approval],
reason: reason,
force_review: true,
)
else
reviewable.errors.full_messages.each { |msg| errors << msg }
end
end
result.reviewable = reviewable
result.reason = reason if reason
result.check_errors(errors)
result.pending_count = ReviewableQueuedPost.where(target_created_by: @user).pending.count
result
end
def perform_create_post
result = NewPostResult.new(:create_post)
creator = PostCreator.new(@user, @args)
post = creator.create
result.check_errors_from(creator)
if result.success?
result.post = post
else
@user.flag_linked_posts_as_spam if creator.spam?
end
result
end
end