discourse/plugins/poll/lib/polls_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

169 lines
5.2 KiB
Ruby

# frozen_string_literal: true
module DiscoursePoll
class PollsValidator
MAX_VALUE = 2_147_483_647
def initialize(post)
@post = post
end
def validate_polls
polls = {}
DiscoursePoll::Poll::extract(@post.raw, @post.topic_id, @post.user_id).each do |poll|
return false unless valid_arguments?(poll)
return false unless valid_numbers?(poll)
return false unless unique_poll_name?(polls, poll)
return false unless unique_options?(poll)
return false unless at_least_two_options?(poll)
return false unless valid_number_of_options?(poll)
return false unless valid_multiple_choice_settings?(poll)
polls[poll["name"]] = poll
end
polls
end
private
def valid_arguments?(poll)
valid = true
if poll["type"].present? && !::Poll.types.has_key?(poll["type"])
@post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "type", value: poll["type"]))
valid = false
end
if poll["status"].present? && !::Poll.statuses.has_key?(poll["status"])
@post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "status", value: poll["status"]))
valid = false
end
if poll["results"].present? && !::Poll.results.has_key?(poll["results"])
@post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "results", value: poll["results"]))
valid = false
end
valid
end
def unique_poll_name?(polls, poll)
if polls.has_key?(poll["name"])
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@post.errors.add(:base, I18n.t("poll.multiple_polls_without_name"))
else
@post.errors.add(:base, I18n.t("poll.multiple_polls_with_same_name", name: poll["name"]))
end
return false
end
true
end
def unique_options?(poll)
if poll["options"].map { |o| o["id"] }.uniq.size != poll["options"].size
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_different_options"))
else
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_different_options", name: poll["name"]))
end
return false
end
true
end
def at_least_two_options?(poll)
if poll["options"].size < 2
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_2_options"))
else
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_2_options", name: poll["name"]))
end
return false
end
true
end
def valid_number_of_options?(poll)
if poll["options"].size > SiteSetting.poll_maximum_options
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options))
else
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_less_options", name: poll["name"], count: SiteSetting.poll_maximum_options))
end
return false
end
true
end
def valid_multiple_choice_settings?(poll)
if poll["type"] == "multiple"
options = poll["options"].size
min = (poll["min"].presence || 1).to_i
max = (poll["max"].presence || options).to_i
if min > max || min <= 0 || max <= 0 || max > options || min >= options
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@post.errors.add(:base, I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"))
else
@post.errors.add(:base, I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: poll["name"]))
end
return false
end
end
true
end
def valid_numbers?(poll)
return true if poll["type"] != "number"
valid = true
min = poll["min"].to_i
max = (poll["max"].presence || MAX_VALUE).to_i
step = (poll["step"].presence || 1).to_i
if min < 0
@post.errors.add(:base, "Min #{I18n.t("errors.messages.greater_than", count: 0)}")
valid = false
elsif min > MAX_VALUE
@post.errors.add(:base, "Min #{I18n.t("errors.messages.less_than", count: MAX_VALUE)}")
valid = false
end
if max < min
@post.errors.add(:base, "Max #{I18n.t("errors.messages.greater_than", count: "min")}")
valid = false
elsif max > MAX_VALUE
@post.errors.add(:base, "Max #{I18n.t("errors.messages.less_than", count: MAX_VALUE)}")
valid = false
end
if step <= 0
@post.errors.add(:base, "Step #{I18n.t("errors.messages.greater_than", count: 0)}")
valid = false
elsif ((max - min + 1) / step) < 2
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_2_options"))
else
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_2_options", name: poll["name"]))
end
valid = false
end
valid
end
end
end