Poll Plugin: Allow staff to edit options.
This commit is contained in:
parent
e4c793a7e3
commit
ae3f135c33
|
@ -5,7 +5,12 @@
|
|||
# http://yamllint.com/
|
||||
|
||||
en:
|
||||
activerecord:
|
||||
attributes:
|
||||
post:
|
||||
poll_options: "Poll options"
|
||||
poll:
|
||||
must_contain_poll_options: "must contain a list of poll options"
|
||||
cannot_have_modified_options: "cannot have modified poll options after 5 minutes"
|
||||
cannot_have_modified_options: "cannot be modified after the first five minutes. Contact a moderator if you need to change them."
|
||||
cannot_add_or_remove_options: "can only be edited, not added or removed. If you need to add or remove options you should lock this thread and create a new one."
|
||||
prefix: "Poll:"
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
load File.expand_path("../poll.rb", __FILE__)
|
||||
|
||||
# Without this line we can't lookup the constant inside the after_initialize blocks,
|
||||
# probably because all of this is instance_eval'd inside an instance of
|
||||
# Plugin::Instance.
|
||||
# because all of this is instance_eval'd inside an instance of Plugin::Instance.
|
||||
PollPlugin = PollPlugin
|
||||
|
||||
after_initialize do
|
||||
|
@ -64,8 +63,6 @@ after_initialize do
|
|||
# Starting a topic title with "Poll:" will create a poll topic. If the title
|
||||
# starts with "poll:" but the first post doesn't contain a list of options in
|
||||
# it we need to raise an error.
|
||||
# Need to add an error when:
|
||||
# * there is no list of options.
|
||||
Post.class_eval do
|
||||
validate :poll_options
|
||||
def poll_options
|
||||
|
@ -77,30 +74,15 @@ after_initialize do
|
|||
self.errors.add(:raw, I18n.t('poll.must_contain_poll_options'))
|
||||
end
|
||||
|
||||
if self.created_at and self.created_at < 5.minutes.ago and poll.options.sort != poll.details.keys.sort
|
||||
self.errors.add(:raw, I18n.t('poll.cannot_have_modified_options'))
|
||||
end
|
||||
poll.ensure_can_be_edited!
|
||||
end
|
||||
end
|
||||
|
||||
# Save the list of options to PluginStore after the post is saved.
|
||||
Post.class_eval do
|
||||
after_save :save_poll_options_to_topic_metadata
|
||||
def save_poll_options_to_topic_metadata
|
||||
poll = PollPlugin::Poll.new(self)
|
||||
if poll.is_poll?
|
||||
details = poll.details || {}
|
||||
new_options = poll.options
|
||||
details.each do |key, value|
|
||||
unless new_options.include? key
|
||||
details.delete(key)
|
||||
end
|
||||
end
|
||||
new_options.each do |key|
|
||||
details[key] ||= 0
|
||||
end
|
||||
poll.set_details! details
|
||||
end
|
||||
after_save :save_poll_options_to_plugin_store
|
||||
def save_poll_options_to_plugin_store
|
||||
PollPlugin::Poll.new(self).update_options!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -24,6 +24,29 @@ module ::PollPlugin
|
|||
topic.title =~ /^#{I18n.t('poll.prefix')}/i
|
||||
end
|
||||
|
||||
# Called during validation of poll posts. Discourse already restricts edits to
|
||||
# the OP and staff, we want to make sure that:
|
||||
#
|
||||
# * OP cannot edit options after 5 minutes.
|
||||
# * Staff can only edit options after 5 minutes, not add/remove.
|
||||
def ensure_can_be_edited!
|
||||
# Return if this is a new post or the options were not modified.
|
||||
return if @post.id.nil? || (options.sort == details.keys.sort)
|
||||
|
||||
# First 5 minutes -- allow any modification.
|
||||
return unless @post.created_at < 5.minutes.ago
|
||||
|
||||
if User.find(@post.last_editor_id).staff?
|
||||
# Allow editing options, but not adding or removing.
|
||||
if options.length != details.keys.length
|
||||
@post.errors.add(:poll_options, I18n.t('poll.cannot_add_or_remove_options'))
|
||||
end
|
||||
else
|
||||
# Regular user, tell them to contact a moderator.
|
||||
@post.errors.add(:poll_options, I18n.t('poll.cannot_have_modified_options'))
|
||||
end
|
||||
end
|
||||
|
||||
def options
|
||||
cooked = PrettyText.cook(@post.raw, topic_id: @post.topic_id)
|
||||
parsed = Nokogiri::HTML(cooked)
|
||||
|
@ -35,6 +58,58 @@ module ::PollPlugin
|
|||
end
|
||||
end
|
||||
|
||||
def update_options!
|
||||
return unless self.is_poll?
|
||||
return if details && details.keys.sort == options.sort
|
||||
|
||||
if details.try(:length) == options.length
|
||||
|
||||
# Assume only renaming, no reordering. Preserve votes.
|
||||
old_details = self.details
|
||||
old_options = old_details.keys
|
||||
new_details = {}
|
||||
new_options = self.options
|
||||
rename = {}
|
||||
|
||||
0.upto(options.length-1) do |i|
|
||||
new_details[ new_options[i] ] = old_details[ old_options[i] ]
|
||||
|
||||
if new_options[i] != old_options[i]
|
||||
rename[ old_options[i] ] = new_options[i]
|
||||
end
|
||||
end
|
||||
self.set_details! new_details
|
||||
|
||||
# Update existing user votes.
|
||||
# Accessing PluginStoreRow directly isn't a very nice approach but there's
|
||||
# no way around it unfortunately.
|
||||
# TODO: Probably want to move this to a background job.
|
||||
PluginStoreRow.where(plugin_name: "poll", value: rename.keys).where('key LIKE ?', vote_key_prefix+"%").find_each do |row|
|
||||
# This could've been done more efficiently using `update_all` instead of
|
||||
# iterating over each individual vote, however this will be needed in the
|
||||
# future once we support multiple choice polls.
|
||||
row.value = rename[ row.value ]
|
||||
row.save
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
# Options were added or removed.
|
||||
new_options = self.options
|
||||
new_details = self.details || {}
|
||||
new_details.each do |key, value|
|
||||
unless new_options.include? key
|
||||
new_details.delete(key)
|
||||
end
|
||||
end
|
||||
new_options.each do |key|
|
||||
new_details[key] ||= 0
|
||||
end
|
||||
self.set_details! new_details
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def details
|
||||
@details ||= ::PluginStore.get("poll", details_key)
|
||||
end
|
||||
|
@ -71,8 +146,12 @@ module ::PollPlugin
|
|||
"poll_options_#{@post.id}"
|
||||
end
|
||||
|
||||
def vote_key_prefix
|
||||
"poll_vote_#{@post.id}_"
|
||||
end
|
||||
|
||||
def vote_key(user)
|
||||
"poll_vote_#{@post.id}_#{user.id}"
|
||||
"#{vote_key_prefix}#{user.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,4 +63,19 @@ describe PollPlugin::Poll do
|
|||
poll = PollPlugin::Poll.new(post)
|
||||
poll.serialize(user).should eq(nil)
|
||||
end
|
||||
|
||||
it "stores poll options to plugin store" do
|
||||
poll.set_vote!(user, "Onodera")
|
||||
poll.stubs(:options).returns(["Chitoge", "Onodera", "Inferno Cop"])
|
||||
poll.update_options!
|
||||
poll.details.keys.sort.should eq(["Chitoge", "Inferno Cop", "Onodera"])
|
||||
poll.details["Inferno Cop"].should eq(0)
|
||||
poll.details["Onodera"].should eq(1)
|
||||
|
||||
poll.stubs(:options).returns(["Chitoge", "Onodera v2", "Inferno Cop"])
|
||||
poll.update_options!
|
||||
poll.details.keys.sort.should eq(["Chitoge", "Inferno Cop", "Onodera v2"])
|
||||
poll.details["Onodera v2"].should eq(1)
|
||||
poll.get_vote(user).should eq("Onodera v2")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,22 +3,34 @@ require 'post_creator'
|
|||
|
||||
describe PostCreator do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:admin) { Fabricate(:admin) }
|
||||
|
||||
context "poll topic" do
|
||||
let(:poll_post) { PostCreator.create(user, {title: "Poll: This is a poll", raw: "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"}) }
|
||||
|
||||
it "cannot be created without a list of options" do
|
||||
post = PostCreator.create(user, {title: "Poll: This is a poll", raw: "body does not contain a list"})
|
||||
post.errors[:raw].should be_present
|
||||
end
|
||||
|
||||
it "cannot have options changed after 5 minutes" do
|
||||
post = PostCreator.create(user, {title: "Poll: This is a poll", raw: "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"})
|
||||
post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
|
||||
post.valid?.should be_true
|
||||
post.save
|
||||
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
|
||||
poll_post.valid?.should be_true
|
||||
poll_post.save
|
||||
Timecop.freeze(Time.now + 6.minutes) do
|
||||
post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"
|
||||
post.valid?.should be_false
|
||||
post.errors[:raw].should be_present
|
||||
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4\n[/poll]"
|
||||
poll_post.valid?.should be_false
|
||||
poll_post.errors[:poll_options].should be_present
|
||||
end
|
||||
end
|
||||
|
||||
it "allows staff to edit options after 5 minutes" do
|
||||
poll_post.last_editor_id = admin.id
|
||||
Timecop.freeze(Time.now + 6.minutes) do
|
||||
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n* option 4.1\n[/poll]"
|
||||
poll_post.valid?.should be_true
|
||||
poll_post.raw = "[poll]\n* option 1\n* option 2\n* option 3\n[/poll]"
|
||||
poll_post.valid?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue