From 8f2b002203a01c0a6c4e90521aa20423c7679acf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9gis=20Hanol?= <regis@hanol.fr>
Date: Fri, 16 Oct 2015 17:18:22 +0200
Subject: [PATCH] FIX: a poll option cannot be voted more than 100% of the time

---
 .../20151016163051_merge_polls_votes.rb       | 20 ++++++++++
 plugins/poll/plugin.rb                        | 38 +++++++++----------
 2 files changed, 38 insertions(+), 20 deletions(-)
 create mode 100644 plugins/poll/db/migrate/20151016163051_merge_polls_votes.rb

diff --git a/plugins/poll/db/migrate/20151016163051_merge_polls_votes.rb b/plugins/poll/db/migrate/20151016163051_merge_polls_votes.rb
new file mode 100644
index 00000000000..09db8648501
--- /dev/null
+++ b/plugins/poll/db/migrate/20151016163051_merge_polls_votes.rb
@@ -0,0 +1,20 @@
+class MergePollsVotes < ActiveRecord::Migration
+
+  def up
+    PostCustomField.where(name: "polls").order(:post_id).pluck(:post_id).each do |post_id|
+      polls_votes = {}
+      PostCustomField.where(post_id: post_id).where("name LIKE 'polls-votes-%'").find_each do |pcf|
+        user_id = pcf.name["polls-votes-".size..-1]
+        polls_votes["#{user_id}"] = ::JSON.parse(pcf.value)
+      end
+
+      pcf = PostCustomField.find_or_create_by(name: "polls-votes", post_id: post_id)
+      pcf.value = ::JSON.parse(pcf.value).merge(polls_votes).to_json
+      pcf.save
+    end
+  end
+
+  def down
+  end
+
+end
diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb
index a8589f2666d..3f789922da8 100644
--- a/plugins/poll/plugin.rb
+++ b/plugins/poll/plugin.rb
@@ -66,21 +66,18 @@ after_initialize do
 
           raise StandardError.new I18n.t("poll.requires_at_least_1_valid_option") if options.empty?
 
-          votes = post.custom_fields["#{VOTES_CUSTOM_FIELD}-#{user_id}"] || {}
-          vote = votes[poll_name] || []
+          post.custom_fields[VOTES_CUSTOM_FIELD] ||= {}
+          post.custom_fields[VOTES_CUSTOM_FIELD]["#{user_id}"] ||= {}
+          post.custom_fields[VOTES_CUSTOM_FIELD]["#{user_id}"][poll_name] = options
 
-          # increment counters only when the user hasn't casted a vote yet
-          poll["voters"] += 1 if vote.size == 0
+          votes = post.custom_fields[VOTES_CUSTOM_FIELD]
+          poll["voters"] = votes.values.count { |v| v.has_key?(poll_name) }
 
-          poll["options"].each do |option|
-            option["votes"] -= 1 if vote.include?(option["id"])
-            option["votes"] += 1 if options.include?(option["id"])
-          end
-
-          votes[poll_name] = options
+          all_options = Hash.new(0)
+          votes.map { |_, v| v[poll_name] }.flatten.each { |o| all_options[o] += 1 }
+          poll["options"].each { |option| option["votes"] = all_options[option["id"]] }
 
           post.custom_fields[POLLS_CUSTOM_FIELD] = polls
-          post.custom_fields["#{VOTES_CUSTOM_FIELD}-#{user_id}"] = votes
           post.save_custom_fields(true)
 
           MessageBus.publish("/polls/#{post_id}", { polls: polls })
@@ -325,9 +322,7 @@ after_initialize do
 
             # when the # of options has changed, reset all the votes
             if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size
-              PostCustomField.where(post_id: post.id)
-                             .where("name LIKE '#{VOTES_CUSTOM_FIELD}-%'")
-                             .destroy_all
+              PostCustomField.where(post_id: post.id, name: VOTES_CUSTOM_FIELD).destroy_all
               post.clear_custom_fields
               next
             end
@@ -354,12 +349,10 @@ after_initialize do
   end
 
   Post.register_custom_field_type(POLLS_CUSTOM_FIELD, :json)
-  Post.register_custom_field_type("#{VOTES_CUSTOM_FIELD}-*", :json)
+  Post.register_custom_field_type(VOTES_CUSTOM_FIELD, :json)
 
   TopicView.add_post_custom_fields_whitelister do |user|
-    whitelisted = [POLLS_CUSTOM_FIELD]
-    whitelisted << "#{VOTES_CUSTOM_FIELD}-#{user.id}" if user
-    whitelisted
+    user ? [POLLS_CUSTOM_FIELD, VOTES_CUSTOM_FIELD] : [POLLS_CUSTOM_FIELD]
   end
 
   # tells the front-end we have a poll for that post
@@ -371,6 +364,11 @@ after_initialize do
   add_to_serializer(:post, :polls, false) { post_custom_fields[POLLS_CUSTOM_FIELD] }
   add_to_serializer(:post, :include_polls?) { post_custom_fields.present? && post_custom_fields[POLLS_CUSTOM_FIELD].present? }
 
-  add_to_serializer(:post, :polls_votes, false) { post_custom_fields["#{VOTES_CUSTOM_FIELD}-#{scope.user.id}"] }
-  add_to_serializer(:post, :include_polls_votes?) { scope.user && post_custom_fields.present? && post_custom_fields["#{VOTES_CUSTOM_FIELD}-#{scope.user.id}"].present? }
+  add_to_serializer(:post, :polls_votes, false) { post_custom_fields[VOTES_CUSTOM_FIELD]["#{scope.user.id}"] }
+  add_to_serializer(:post, :include_polls_votes?) do
+    return unless scope.user
+    return unless post_custom_fields.present?
+    return unless post_custom_fields[VOTES_CUSTOM_FIELD].present?
+    post_custom_fields[VOTES_CUSTOM_FIELD].has_key?("#{scope.user.id}")
+  end
 end