discourse/lib/validators/post_validator.rb
Sam Saffron 30990006a9 DEV: enable frozen string literal on all files
This reduces chances of errors where consumers of strings mutate inputs
and reduces memory usage of the app.

Test suite passes now, but there may be some stuff left, so we will run
a few sites on a branch prior to merging
2019-05-13 09:31:32 +08:00

194 lines
6.3 KiB
Ruby

# frozen_string_literal: true
require_dependency 'validators/stripped_length_validator'
module Validators; end
class Validators::PostValidator < ActiveModel::Validator
def validate(record)
presence(record)
return if record.acting_user.try(:staged?)
return if record.acting_user.try(:admin?) && Discourse.static_doc_topic_ids.include?(record.topic_id)
post_body_validator(record)
max_posts_validator(record)
max_mention_validator(record)
max_images_validator(record)
max_attachments_validator(record)
can_post_links_validator(record)
unique_post_validator(record)
force_edit_last_validator(record)
end
def presence(post)
unless options[:skip_topic]
post.errors.add(:topic_id, :blank, options) if post.topic_id.blank?
end
if post.new_record? && post.user_id.nil?
post.errors.add(:user_id, :blank, options)
end
end
def post_body_validator(post)
return if options[:skip_post_body] || post.topic&.pm_with_non_human_user?
stripped_length(post)
raw_quality(post)
watched_words(post)
end
def stripped_length(post)
range = if private_message?(post)
# private message
SiteSetting.private_message_post_length
elsif post.is_first_post? || (post.topic.present? && post.topic.posts_count == 0)
# creating/editing first post
post.topic&.featured_link&.present? ? (0..SiteSetting.max_post_length) : SiteSetting.first_post_length
else
# regular post
SiteSetting.post_length
end
Validators::StrippedLengthValidator.validate(post, :raw, post.raw, range)
end
def raw_quality(post)
sentinel = TextSentinel.body_sentinel(post.raw, private_message: private_message?(post))
post.errors.add(:raw, I18n.t(:is_invalid)) unless sentinel.valid?
end
def watched_words(post)
if !post.acting_user&.staff? && !post.acting_user&.staged && matches = WordWatcher.new(post.raw).should_block?
post.errors.add(:base, I18n.t('contains_blocked_words', word: matches[0]))
end
end
# Ensure maximum amount of mentions in a post
def max_mention_validator(post)
return if post.acting_user.try(:staff?)
if acting_user_is_trusted?(post) || private_message?(post)
add_error_if_count_exceeded(post, :no_mentions_allowed, :too_many_mentions, post.raw_mentions.size, SiteSetting.max_mentions_per_post)
else
add_error_if_count_exceeded(post, :no_mentions_allowed_newuser, :too_many_mentions_newuser, post.raw_mentions.size, SiteSetting.newuser_max_mentions_per_post)
end
end
def max_posts_validator(post)
if post.new_record? && post.acting_user.present? && post.acting_user.posted_too_much_in_topic?(post.topic_id)
post.errors.add(:base, I18n.t(:too_many_replies, count: SiteSetting.newuser_max_replies_per_topic))
end
end
# Ensure new users can not put too many images in a post
def max_images_validator(post)
return if post.acting_user.blank? || post.acting_user&.staff?
if post.acting_user.trust_level < TrustLevel[SiteSetting.min_trust_to_post_images]
add_error_if_count_exceeded(
post,
:no_images_allowed_trust,
:no_images_allowed_trust,
post.image_count,
0
)
elsif post.acting_user.trust_level == TrustLevel[0]
add_error_if_count_exceeded(
post,
:no_images_allowed,
:too_many_images,
post.image_count,
SiteSetting.newuser_max_images
)
end
end
# Ensure new users can not put too many attachments in a post
def max_attachments_validator(post)
return if acting_user_is_trusted?(post) || private_message?(post)
add_error_if_count_exceeded(post, :no_attachments_allowed, :too_many_attachments, post.attachment_count, SiteSetting.newuser_max_attachments)
end
def can_post_links_validator(post)
if (post.link_count == 0 && !post.has_oneboxes?) || private_message?(post)
return newuser_links_validator(post)
end
guardian = Guardian.new(post.acting_user)
if post.linked_hosts.keys.all? { |h| guardian.can_post_link?(host: h) }
return newuser_links_validator(post)
end
post.errors.add(:base, I18n.t(:links_require_trust))
end
# Ensure new users can not put too many links in a post
def newuser_links_validator(post)
return if acting_user_is_trusted?(post) || private_message?(post)
add_error_if_count_exceeded(post, :no_links_allowed, :too_many_links, post.link_count, SiteSetting.newuser_max_links)
end
# Stop us from posting the same thing too quickly
def unique_post_validator(post)
return if SiteSetting.unique_posts_mins == 0
return if post.skip_unique_check
return if post.acting_user.try(:staff?)
# If the post is empty, default to the validates_presence_of
return if post.raw.blank?
if post.matches_recent_post?
post.errors.add(:raw, I18n.t(:just_posted_that))
end
end
def force_edit_last_validator(post)
return if SiteSetting.max_consecutive_replies == 0 || post.id || post.acting_user&.staff? || private_message?(post)
topic = post.topic
return if topic&.ordered_posts&.first&.user == post.user
last_posts_count = DB.query_single(<<~SQL, topic_id: post.topic_id, user_id: post.acting_user.id, max_replies: SiteSetting.max_consecutive_replies).first
SELECT COUNT(*)
FROM (
SELECT user_id
FROM posts
WHERE deleted_at IS NULL
AND NOT hidden
AND topic_id = :topic_id
ORDER BY post_number DESC
LIMIT :max_replies
) c
WHERE c.user_id = :user_id
SQL
return if last_posts_count < SiteSetting.max_consecutive_replies
guardian = Guardian.new(post.acting_user)
if guardian.can_edit?(topic.ordered_posts.last)
post.errors.add(:base, I18n.t(:max_consecutive_replies, count: SiteSetting.max_consecutive_replies))
end
end
private
def acting_user_is_trusted?(post, level = 1)
post.acting_user.present? && post.acting_user.has_trust_level?(TrustLevel[level])
end
def private_message?(post)
post.topic.try(:private_message?)
end
def add_error_if_count_exceeded(post, not_allowed_translation_key, limit_translation_key, current_count, max_count)
if current_count > max_count
if max_count == 0
post.errors.add(:base, I18n.t(not_allowed_translation_key))
else
post.errors.add(:base, I18n.t(limit_translation_key, count: max_count))
end
end
end
end