discourse/lib/new_post_manager.rb

330 lines
9.5 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 if pattern.blank?
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
if !user.staged? && !user.in_any_groups?(SiteSetting.approve_unless_allowed_groups_map)
return :group
end
if (
manager.args[:title].present? && !user.staged? &&
!user.in_any_groups?(SiteSetting.approve_new_topics_unless_allowed_groups_map)
)
return :new_topics_unless_allowed_groups
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.in_any_groups?(SiteSetting.skip_review_media_groups_map)
)
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_allowed_groups_map.include?(Group::AUTO_GROUPS[:trust_level_0])
) ||
!(
SiteSetting.approve_new_topics_unless_allowed_groups_map.include?(
Group::AUTO_GROUPS[:trust_level_0],
)
) || SiteSetting.approve_unless_staged ||
WordWatcher.words_for_action_exist?(: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