mirror of
https://github.com/discourse/discourse.git
synced 2024-11-29 02:53:41 +08:00
f56c44d1c7
* FEATURE: Validate tags in WatchedWords We didn't validate watched words automatic tagging, so it was possible for an admin to created watched words with an empty tag list which would result in an exception when users tried to create a new topic that matched the misconfigured watched word. Bug report: https://meta.discourse.org/t/lib-topic-creator-fails-when-the-word-math-appears-in-the-topic-title-or-text/231018?u=falco
108 lines
2.7 KiB
Ruby
108 lines
2.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class WatchedWord < ActiveRecord::Base
|
|
|
|
def self.actions
|
|
@actions ||= Enum.new(
|
|
block: 1,
|
|
censor: 2,
|
|
require_approval: 3,
|
|
flag: 4,
|
|
link: 8,
|
|
replace: 5,
|
|
tag: 6,
|
|
silence: 7,
|
|
)
|
|
end
|
|
|
|
MAX_WORDS_PER_ACTION = 2000
|
|
|
|
before_validation do
|
|
self.word = self.class.normalize_word(self.word)
|
|
if self.action == WatchedWord.actions[:link] && !(self.replacement =~ /^https?:\/\//)
|
|
self.replacement = "#{Discourse.base_url}#{self.replacement&.starts_with?("/") ? "" : "/"}#{self.replacement}"
|
|
end
|
|
end
|
|
|
|
validates :word, presence: true, uniqueness: true, length: { maximum: 100 }
|
|
validates :action, presence: true
|
|
|
|
validate :replacement_is_url, if: -> { action == WatchedWord.actions[:link] }
|
|
validate :replacement_is_tag_list, if: -> { action == WatchedWord.actions[:tag] }
|
|
|
|
validates_each :word do |record, attr, val|
|
|
if WatchedWord.where(action: record.action).count >= MAX_WORDS_PER_ACTION
|
|
record.errors.add(:word, :too_many)
|
|
end
|
|
end
|
|
|
|
after_save :clear_cache
|
|
after_destroy :clear_cache
|
|
|
|
scope :by_action, -> { order("action ASC, word ASC") }
|
|
|
|
def self.normalize_word(w)
|
|
w.strip.squeeze('*')
|
|
end
|
|
|
|
def replacement_is_url
|
|
if !(replacement =~ URI::regexp)
|
|
errors.add(:base, :invalid_url)
|
|
end
|
|
end
|
|
|
|
def replacement_is_tag_list
|
|
tag_list = replacement&.split(',')
|
|
tags = Tag.where(name: tag_list)
|
|
if (tag_list.blank? || tags.empty? || tag_list.size != tags.size)
|
|
errors.add(:base, :invalid_tag_list)
|
|
end
|
|
end
|
|
|
|
def self.create_or_update_word(params)
|
|
new_word = normalize_word(params[:word])
|
|
w = WatchedWord.where("word ILIKE ?", new_word).first || WatchedWord.new(word: new_word)
|
|
w.replacement = params[:replacement] if params[:replacement]
|
|
w.action_key = params[:action_key] if params[:action_key]
|
|
w.action = params[:action] if params[:action]
|
|
w.save
|
|
w
|
|
end
|
|
|
|
def self.has_replacement?(action)
|
|
action == :replace || action == :tag || action == :link
|
|
end
|
|
|
|
def action_key=(arg)
|
|
self.action = self.class.actions[arg.to_sym]
|
|
end
|
|
|
|
def action_log_details
|
|
if replacement.present?
|
|
"#{word} → #{replacement}"
|
|
else
|
|
word
|
|
end
|
|
end
|
|
|
|
def clear_cache
|
|
WordWatcher.clear_cache!
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: watched_words
|
|
#
|
|
# id :integer not null, primary key
|
|
# word :string not null
|
|
# action :integer not null
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# replacement :string
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_watched_words_on_action_and_word (action,word) UNIQUE
|
|
#
|