diff --git a/app/models/concerns/has_custom_fields.rb b/app/models/concerns/has_custom_fields.rb index 7a61a589540..0b2f4548891 100644 --- a/app/models/concerns/has_custom_fields.rb +++ b/app/models/concerns/has_custom_fields.rb @@ -94,8 +94,8 @@ module HasCustomFields !@custom_fields || @custom_fields_orig == @custom_fields end - def save_custom_fields - if !custom_fields_clean? + def save_custom_fields(force=false) + if force || !custom_fields_clean? dup = @custom_fields.dup array_fields = {} @@ -108,6 +108,12 @@ module HasCustomFields else array_fields[f.name] << f end + elsif dup[f.name].is_a? Hash + if dup[f.name].to_json != f.value + f.destroy + else + dup.delete(f.name) + end else if dup[f.name] != f.value f.destroy diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index bc000987abf..027e8cf3cf2 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -78,12 +78,12 @@ class Plugin::Instance end # Add validation method but check that the plugin is enabled - def validate(klass, attr, &block) + def validate(klass, name, &block) klass = klass.to_s.classify.constantize - klass.send(:define_method, attr, &block) + klass.send(:define_method, name, &block) plugin = self - klass.validate(attr, if: -> { plugin.enabled? }) + klass.validate(name, if: -> { plugin.enabled? }) end # will make sure all the assets this plugin needs are registered diff --git a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 index dfd5d4f62d5..944b29d4857 100644 --- a/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 +++ b/plugins/poll/assets/javascripts/initializers/extend-for-poll.js.es6 @@ -32,6 +32,9 @@ export default { // don't even bother when there's no poll if (!polls) { return; } + // clean-up if needed + this._cleanUpPollViews(); + const pollViews = {}; // iterate over all polls @@ -47,14 +50,20 @@ export default { }); this.messageBus.subscribe("/polls/" + this.get("post.id"), results => { - pollViews[results.poll.name].get("controller").set("model", Em.Object.create(results.poll)); + if (results && results.polls) { + _.forEach(results.polls, poll => { + if (pollViews[poll.name]) { + pollViews[poll.name].get("controller").set("model", Em.Object.create(poll)); + } + }); + } }); this.set("pollViews", pollViews); - }.on("postViewInserted"), + }.on("postViewInserted", "postViewUpdated"), _cleanUpPollViews: function() { - this.messageBus.unsubscribe("/polls/*"); + this.messageBus.unsubscribe("/polls/" + this.get("post.id")); if (this.get("pollViews")) { _.forEach(this.get("pollViews"), v => v.destroy()); diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb index 7c66b591dfc..1f8ba3aa9f5 100644 --- a/plugins/poll/plugin.rb +++ b/plugins/poll/plugin.rb @@ -64,9 +64,9 @@ after_initialize do post.custom_fields[POLLS_CUSTOM_FIELD] = polls post.custom_fields["#{VOTES_CUSTOM_FIELD}-#{user_id}"] = votes - post.save_custom_fields + post.save_custom_fields(true) - DiscourseBus.publish("/polls/#{post_id}", { poll: poll }) + DiscourseBus.publish("/polls/#{post_id}", { polls: polls }) render json: { poll: poll, vote: options } end @@ -97,10 +97,9 @@ after_initialize do polls[poll_name]["status"] = status - post.custom_fields[POLLS_CUSTOM_FIELD] = polls - post.save_custom_fields + post.save_custom_fields(true) - DiscourseBus.publish("/polls/#{post_id}", { poll: polls[poll_name] }) + DiscourseBus.publish("/polls/#{post_id}", { polls: polls }) render json: { poll: polls[poll_name] } end @@ -120,7 +119,6 @@ after_initialize do Post.class_eval do attr_accessor :polls - # save the polls when the post is created after_save do next if self.polls.blank? || !self.polls.is_a?(Hash) @@ -129,7 +127,7 @@ after_initialize do DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do post.custom_fields[POLLS_CUSTOM_FIELD] = polls - post.save_custom_fields + post.save_custom_fields(true) end end end @@ -137,7 +135,7 @@ after_initialize do DATA_PREFIX ||= "data-poll-".freeze DEFAULT_POLL_NAME ||= "poll".freeze - validate(:post, :polls) do + validate(:post, :validate_polls) do # only care when raw has changed! return unless self.raw_changed? @@ -200,8 +198,8 @@ after_initialize do polls[poll["name"]] = poll end - # are we updating a post outside the 5-minute edit window? - if self.id.present? && self.created_at < 5.minutes.ago + # are we updating a post? + if self.id.present? post = self DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do # load previous polls @@ -211,43 +209,54 @@ after_initialize do if polls.keys != previous_polls.keys || polls.values.map { |p| p["options"] } != previous_polls.values.map { |p| p["options"] } - # cannot add/remove/change/re-order polls - if polls.keys != previous_polls.keys - post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes")) - return + # outside the 5-minute edit window? + if post.created_at < 5.minutes.ago + # cannot add/remove/change/re-order polls + if polls.keys != previous_polls.keys + post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes")) + return + end + + # deal with option changes + if User.staff.pluck(:id).include?(post.last_editor_id) + # staff can only edit options + polls.each_key do |poll_name| + if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size + post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes")) + return + end + end + else + # OP cannot change polls + post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes")) + return + end end - # deal with option changes - if User.staff.pluck(:id).include?(post.last_editor_id) - # staff can only edit options - polls.each_key do |poll_name| - if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size - post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes")) - return - end + # merge votes when same number of options + polls.each_key do |poll_name| + next unless previous_polls.has_key?(poll_name) + next unless polls[poll_name]["options"].size == previous_polls[poll_name]["options"].size + + polls[poll_name]["total_votes"] = previous_polls[poll_name]["total_votes"] + for o in 0...polls[poll_name]["options"].size + polls[poll_name]["options"][o]["votes"] = previous_polls[poll_name]["options"][o]["votes"] end - # merge votes - polls.each_key do |poll_name| - polls[poll_name]["total_votes"] = previous_polls[poll_name]["total_votes"] - for o in 0...polls[poll_name]["options"].size - polls[poll_name]["options"][o]["votes"] = previous_polls[poll_name]["options"][o]["votes"] - end - end - else - # OP cannot change polls after 5 minutes - post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes")) - return end + + # immediately store the polls + post.custom_fields[POLLS_CUSTOM_FIELD] = polls + post.save_custom_fields(true) + + # push the changes + DiscourseBus.publish("/polls/#{post_id}", { polls: polls }) end - - # immediately store the polls - post.custom_fields[POLLS_CUSTOM_FIELD] = polls - post.save_custom_fields end else - # polls will be saved once we have a post id self.polls = polls end + + true end Post.register_custom_field_type(POLLS_CUSTOM_FIELD, :json)