From f331b5eab28be2654db7b8f99ea105b0b58c6a0f Mon Sep 17 00:00:00 2001 From: Krzysztof Kotlarek Date: Wed, 2 Oct 2019 10:38:34 +1000 Subject: [PATCH] FEATURE: topic title is validated for blocked words (#8127) Currently, the topic is only validated for censored words and should be validated for blocked words as well. Blocked word validation is now used by both Post and Topic. To avoid code duplication, I extracted blocked words validation code into separate Validator, and use it in both places. The only downside is that even if the topic contains blocked words validation message is saying "Your post contains a word that's not allowed: tomato" but I think this is descriptive enough. --- app/models/topic.rb | 1 + lib/validators/post_validator.rb | 15 +-------------- lib/validators/watched_words_validator.rb | 16 ++++++++++++++++ spec/models/topic_spec.rb | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 lib/validators/watched_words_validator.rb diff --git a/app/models/topic.rb b/app/models/topic.rb index b99b9c7015c..208b639d505 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -74,6 +74,7 @@ class Topic < ActiveRecord::Base presence: true, topic_title_length: true, censored_words: true, + watched_words: true, quality_title: { unless: :private_message? }, max_emojis: true, unique_among: { unless: Proc.new { |t| (SiteSetting.allow_duplicate_topic_titles? || t.private_message?) }, diff --git a/lib/validators/post_validator.rb b/lib/validators/post_validator.rb index 09c564b3b19..dc391b63bf5 100644 --- a/lib/validators/post_validator.rb +++ b/lib/validators/post_validator.rb @@ -36,7 +36,7 @@ class Validators::PostValidator < ActiveModel::Validator return if options[:skip_post_body] || post.topic&.pm_with_non_human_user? stripped_length(post) raw_quality(post) - watched_words(post) + WatchedWordsValidator.new(attributes: [:raw]).validate(post) if !post.acting_user&.staged end def stripped_length(post) @@ -59,19 +59,6 @@ class Validators::PostValidator < ActiveModel::Validator post.errors.add(:raw, I18n.t(:is_invalid)) unless sentinel.valid? end - def watched_words(post) - if !post.acting_user&.staged && matches = WordWatcher.new(post.raw).should_block?.presence - if matches.size == 1 - key = 'contains_blocked_word' - translation_args = { word: matches[0] } - else - key = 'contains_blocked_words' - translation_args = { words: matches.join(', ') } - end - post.errors.add(:base, I18n.t(key, translation_args)) - end - end - # Ensure maximum amount of mentions in a post def max_mention_validator(post) return if post.acting_user.try(:staff?) diff --git a/lib/validators/watched_words_validator.rb b/lib/validators/watched_words_validator.rb new file mode 100644 index 00000000000..cfa7e5a6b66 --- /dev/null +++ b/lib/validators/watched_words_validator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class WatchedWordsValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if matches = WordWatcher.new(value).should_block?.presence + if matches.size == 1 + key = 'contains_blocked_word' + translation_args = { word: matches[0] } + else + key = 'contains_blocked_words' + translation_args = { words: matches.join(', ') } + end + record.errors.add(:base, I18n.t(key, translation_args)) + end + end +end diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index e9da4a7c639..0c3f6383531 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -86,6 +86,22 @@ describe Topic do end end end + + describe 'blocked words' do + describe 'when title contains watched words' do + it 'should not be valid' do + Fabricate(:watched_word, word: 'pineapple', action: WatchedWord.actions[:block]) + + topic.title = 'pen PinEapple apple pen is a complete sentence' + + expect(topic).to_not be_valid + + expect(topic.errors.full_messages.first).to include(I18n.t( + 'contains_blocked_word', word: 'PinEapple' + )) + end + end + end end end