diff --git a/plugins/poll/app/models/poll.rb b/plugins/poll/app/models/poll.rb
index 6c007648ae2..fcf71f9158b 100644
--- a/plugins/poll/app/models/poll.rb
+++ b/plugins/poll/app/models/poll.rb
@@ -78,6 +78,7 @@ end
# created_at :datetime not null
# updated_at :datetime not null
# chart_type :integer default("bar"), not null
+# groups :string
#
# Indexes
#
diff --git a/plugins/poll/app/serializers/poll_serializer.rb b/plugins/poll/app/serializers/poll_serializer.rb
index 3d73264d036..53b7c1d5e2b 100644
--- a/plugins/poll/app/serializers/poll_serializer.rb
+++ b/plugins/poll/app/serializers/poll_serializer.rb
@@ -13,7 +13,8 @@ class PollSerializer < ApplicationSerializer
:voters,
:close,
:preloaded_voters,
- :chart_type
+ :chart_type,
+ :groups
def public
true
@@ -35,6 +36,10 @@ class PollSerializer < ApplicationSerializer
object.step.present? && object.number?
end
+ def include_groups?
+ groups.present?
+ end
+
def options
object.poll_options.map { |o| PollOptionSerializer.new(o, root: false).as_json }
end
diff --git a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6 b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
index 4e3c7e0391c..2c6e8335d63 100644
--- a/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
+++ b/plugins/poll/assets/javascripts/controllers/poll-ui-builder.js.es6
@@ -82,6 +82,13 @@ export default Controller.extend({
return options;
},
+ @computed("site.groups")
+ siteGroups(groups) {
+ const values = [{ name: "", value: null }];
+ groups.forEach(g => values.push({ name: g.name, value: g.name }));
+ return values;
+ },
+
@computed("pollType", "regularPollType")
isRegular(pollType, regularPollType) {
return pollType === regularPollType;
@@ -184,6 +191,7 @@ export default Controller.extend({
"pollMin",
"pollMax",
"pollStep",
+ "pollGroups",
"autoClose",
"chartType",
"date",
@@ -199,6 +207,7 @@ export default Controller.extend({
pollMin,
pollMax,
pollStep,
+ pollGroups,
autoClose,
chartType,
date,
@@ -228,6 +237,7 @@ export default Controller.extend({
if (publicPoll) pollHeader += ` public=true`;
if (chartType && pollType !== "number")
pollHeader += ` chartType=${chartType}`;
+ if (pollGroups) pollHeader += ` groups=${pollGroups}`;
if (autoClose) {
let closeDate = moment(
date + " " + time,
@@ -323,6 +333,7 @@ export default Controller.extend({
pollStep: 1,
autoClose: false,
chartType: BAR_CHART_TYPE,
+ pollGroups: null,
date: moment()
.add(1, "day")
.format("YYYY-MM-DD"),
diff --git a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs
index 739e0a40dcd..21615b87c85 100644
--- a/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs
+++ b/plugins/poll/assets/javascripts/discourse/templates/modal/poll-ui-builder.hbs
@@ -17,6 +17,14 @@
valueAttribute="value"}}
+
+
+ {{combo-box content=siteGroups
+ value=pollGroups
+ allowInitialValueMutation=true
+ valueAttribute="value"}}
+
+
{{#unless isNumber}}
diff --git a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6 b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
index 6b7adcba89b..14de1cae4af 100644
--- a/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
+++ b/plugins/poll/assets/javascripts/lib/discourse-markdown/poll.js.es6
@@ -11,6 +11,7 @@ const WHITELISTED_ATTRIBUTES = [
"public",
"results",
"chartType",
+ "groups",
"status",
"step",
"type"
diff --git a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
index 402f4519e11..03fe68805cb 100644
--- a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
+++ b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
@@ -333,16 +333,43 @@ createWidget("discourse-poll-container", {
: "discourse-poll-pie-chart";
return this.attach(resultsWidget, attrs);
} else if (options) {
- return h(
- "ul",
- options.map(option => {
- return this.attach("discourse-poll-option", {
- option,
- isMultiple: attrs.isMultiple,
- vote: attrs.vote
- });
- })
+ const contents = [];
+
+ const pollGroups =
+ poll.groups && poll.groups.split(",").map(g => g.toLowerCase());
+
+ const userGroups =
+ this.currentUser &&
+ this.currentUser.groups &&
+ this.currentUser.groups.map(g => g.name.toLowerCase());
+
+ if (
+ pollGroups &&
+ userGroups &&
+ !pollGroups.some(g => userGroups.includes(g))
+ ) {
+ contents.push(
+ h(
+ "div.alert.alert-danger",
+ I18n.t("poll.results.groups.title", { groups: poll.groups })
+ )
+ );
+ }
+
+ contents.push(
+ h(
+ "ul",
+ options.map(option => {
+ return this.attach("discourse-poll-option", {
+ option,
+ isMultiple: attrs.isMultiple,
+ vote: attrs.vote
+ });
+ })
+ )
);
+
+ return contents;
}
}
});
@@ -954,6 +981,16 @@ export default createWidget("discourse-poll", {
this.register.lookup("route:application").send("showLogin");
},
+ _toggleOption(option) {
+ const { vote } = this.attrs;
+ const chosenIdx = vote.indexOf(option.id);
+ if (chosenIdx !== -1) {
+ vote.splice(chosenIdx, 1);
+ } else {
+ vote.push(option.id);
+ }
+ },
+
toggleOption(option) {
const { attrs } = this;
@@ -961,20 +998,13 @@ export default createWidget("discourse-poll", {
if (!this.currentUser) return this.showLogin();
const { vote } = attrs;
- const chosenIdx = vote.indexOf(option.id);
-
if (!this.isMultiple()) {
vote.length = 0;
}
- if (chosenIdx !== -1) {
- vote.splice(chosenIdx, 1);
- } else {
- vote.push(option.id);
- }
-
+ this._toggleOption(option);
if (!this.isMultiple()) {
- return this.castVotes();
+ return this.castVotes().catch(() => this._toggleOption(option));
}
},
diff --git a/plugins/poll/config/locales/client.en.yml b/plugins/poll/config/locales/client.en.yml
index 70c89983cfd..c776f7feba6 100644
--- a/plugins/poll/config/locales/client.en.yml
+++ b/plugins/poll/config/locales/client.en.yml
@@ -31,6 +31,8 @@ en:
title: "Votes are public."
results:
+ groups:
+ title: "You need to be a member of %{groups} to vote in this poll."
vote:
title: "Results will be shown on vote."
closed:
@@ -112,6 +114,8 @@ en:
vote: On vote
closed: When closed
staff: Staff only
+ poll_groups:
+ label: Allowed groups
poll_chart_type:
label: Chart type
poll_config:
diff --git a/plugins/poll/db/migrate/20191206123012_add_group_name_to_polls.rb b/plugins/poll/db/migrate/20191206123012_add_group_name_to_polls.rb
new file mode 100644
index 00000000000..7cad643a356
--- /dev/null
+++ b/plugins/poll/db/migrate/20191206123012_add_group_name_to_polls.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddGroupNameToPolls < ActiveRecord::Migration[5.2]
+ def change
+ add_column :polls, :groups, :string
+ end
+end
diff --git a/plugins/poll/lib/polls_updater.rb b/plugins/poll/lib/polls_updater.rb
index 343611ee65a..1b2e62f8a14 100644
--- a/plugins/poll/lib/polls_updater.rb
+++ b/plugins/poll/lib/polls_updater.rb
@@ -3,7 +3,7 @@
module DiscoursePoll
class PollsUpdater
- POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility}
+ POLL_ATTRIBUTES ||= %w{close_at max min results status step type visibility groups}
def self.update(post, polls)
::Poll.transaction do
@@ -38,6 +38,7 @@ module DiscoursePoll
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
attributes["close_at"] = Time.zone.parse(new_poll["close"]) rescue nil
attributes["status"] = old_poll["status"]
+ attributes["groups"] = new_poll["groups"]
poll = ::Poll.new(attributes)
if is_different?(old_poll, poll, new_poll_options)
diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb
index b7d4d2f7c98..d1b54eeeeb7 100644
--- a/plugins/poll/plugin.rb
+++ b/plugins/poll/plugin.rb
@@ -71,6 +71,14 @@ after_initialize do
raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) unless poll
raise StandardError.new I18n.t("poll.poll_must_be_open_to_vote") if poll.is_closed?
+ if poll.groups
+ poll_groups = poll.groups.split(",").map(&:downcase)
+ user_groups = user.groups.map { |g| g.name.downcase }
+ if (poll_groups & user_groups).empty?
+ raise StandardError.new I18n.t("js.poll.results.groups.title", group: poll.groups)
+ end
+ end
+
# remove options that aren't available in the poll
available_options = poll.poll_options.map { |o| o.digest }.to_set
options.select! { |o| available_options.include?(o) }
@@ -322,7 +330,8 @@ after_initialize do
min: poll["min"],
max: poll["max"],
step: poll["step"],
- chart_type: poll["charttype"] || "bar"
+ chart_type: poll["charttype"] || "bar",
+ groups: poll["groups"]
)
poll["options"].each do |option|
diff --git a/plugins/poll/spec/controllers/polls_controller_spec.rb b/plugins/poll/spec/controllers/polls_controller_spec.rb
index 78f86e30288..4236e6c51d2 100644
--- a/plugins/poll/spec/controllers/polls_controller_spec.rb
+++ b/plugins/poll/spec/controllers/polls_controller_spec.rb
@@ -186,6 +186,18 @@ describe ::DiscoursePoll::PollsController do
expect(json["errors"][0]).to eq(I18n.t("poll.poll_must_be_open_to_vote"))
end
+ it "ensures user has required trust level" do
+ poll = create_post(raw: "[poll groups=#{Fabricate(:group).name}]\n- A\n- B\n[/poll]")
+
+ put :vote, params: {
+ post_id: poll.id, poll_name: "poll", options: ["5c24fc1df56d764b550ceae1b9319125"]
+ }, format: :json
+
+ expect(response.status).not_to eq(200)
+ json = ::JSON.parse(response.body)
+ expect(json["errors"][0]).to eq(I18n.t("js.poll.results.groups.title", trust_level: 2))
+ end
+
it "doesn't discard anonymous votes when someone votes" do
the_poll = poll.polls.first
the_poll.update_attribute(:anonymous_voters, 17)
diff --git a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6 b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
index 5474b12b096..24601d3c0f0 100644
--- a/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
+++ b/plugins/poll/test/javascripts/controllers/poll-ui-builder-test.js.es6
@@ -283,6 +283,14 @@ test("regular pollOutput", function(assert) {
"[poll type=regular public=true chartType=bar]\n* 1\n* 2\n[/poll]\n",
"it should return the right output"
);
+
+ controller.set("pollGroups", "test");
+
+ assert.equal(
+ controller.get("pollOutput"),
+ "[poll type=regular public=true chartType=bar groups=test]\n* 1\n* 2\n[/poll]\n",
+ "it should return the right output"
+ );
});
test("multiple pollOutput", function(assert) {