From 1111ad6e995c374bca82a8825ed85643d1723550 Mon Sep 17 00:00:00 2001 From: peddler Date: Mon, 4 Sep 2023 00:04:05 +0530 Subject: [PATCH 1/5] Add private voting. This feature allows you to hide the particulars of who voted for which option from users who are not administrators or the creator of the poll. --- languages/en_GB/poll.json | 4 +++- languages/en_US/poll.json | 4 +++- lib/config.js | 3 ++- lib/poll.js | 9 ++++++++- lib/sockets.js | 28 ++++++++++++++++++++++------ public/js/poll/creator.js | 3 ++- public/js/poll/serializer.js | 10 +++++++++- public/js/poll/view.js | 8 ++++++-- templates/poll/creator.tpl | 7 +++++++ templates/poll/view/details.tpl | 3 +++ 10 files changed, 65 insertions(+), 14 deletions(-) diff --git a/languages/en_GB/poll.json b/languages/en_GB/poll.json index d81c3ef..20875ed 100755 --- a/languages/en_GB/poll.json +++ b/languages/en_GB/poll.json @@ -8,6 +8,7 @@ "default_title": "Default poll title", "max_votes": "Maximum number of votes per user", "disallow_vote_update": "Don't allow users to update their vote", + "private_votes": "Private votes", "info_choices": "A number greater than 1 will enable multiple choice polls.", "settings": "Settings", "save": "Save", @@ -41,8 +42,9 @@ "voting_unavailable_message": "This poll has ended or has been marked as deleted. You can still view the results.", "voting_update_disallowed_message": "You have already voted and changing your vote is not allowed for this poll", "vote_is_final": "Changing your vote is not allowed for this poll so your vote is final", + "votes_are_hidden": "The votes in this poll are hidden from other users.", "vote_count": "users voted for this option", "votes": "votes", "admin.create-poll": "Create Poll" -} \ No newline at end of file +} diff --git a/languages/en_US/poll.json b/languages/en_US/poll.json index b324b70..a265fc1 100755 --- a/languages/en_US/poll.json +++ b/languages/en_US/poll.json @@ -8,6 +8,7 @@ "default_title": "Default poll title", "max_votes": "Maximum number of votes per user", "disallow_vote_update": "Don't allow users to update their vote", + "private_votes": "Private votes", "info_choices": "A number greater than 1 will enable multiple choice polls.", "settings": "Settings", "save": "Save", @@ -41,7 +42,8 @@ "voting_unavailable_message": "This poll has ended or has been marked as deleted. You can still view the results.", "voting_update_disallowed_message": "You have already voted and changing your vote is not allowed for this poll", "vote_is_final": "Changing your vote is not allowed for this poll so your vote is final", + "votes_are_hidden": "The votes in this poll are hidden from other users.", "vote_count": "users voted for this option", "votes": "votes" -} \ No newline at end of file +} diff --git a/lib/config.js b/lib/config.js index 43a44e3..7209cc4 100755 --- a/lib/config.js +++ b/lib/config.js @@ -28,6 +28,7 @@ const pluginId = pluginInfo.id.replace('nodebb-plugin-', ''); maxvotes: 1, disallowVoteUpdate: 0, end: 0, + privateVotes: 0, }, }; @@ -45,4 +46,4 @@ const pluginId = pluginInfo.id.replace('nodebb-plugin-', ''); callback(null, Config.settings.createDefaultWrapper()); }, }; -}(exports)); +}(exports)); \ No newline at end of file diff --git a/lib/poll.js b/lib/poll.js index 2d7e012..42a4fc5 100755 --- a/lib/poll.js +++ b/lib/poll.js @@ -93,6 +93,7 @@ const Scheduler = require('./scheduler'); option.percentage = isNaN(percentage) ? 0 : percentage; }); pollData.settings.disallowVoteUpdate = parseInt(pollData.settings.disallowVoteUpdate, 10); + pollData.settings.privateVotes = parseInt(pollData.settings.privateVotes, 10); callback(null, pollData); }); }; @@ -253,6 +254,12 @@ const Scheduler = require('./scheduler'); Poll.setField(pollId, 'ended', 1); }; + Poll.isPrivateVotes = function (pollId, callback) { + Poll.getSettingsField(pollId, 'privateVotes', (err, result) => { + callback(err, parseInt(result, 10) === 1); + }); + }; + Poll.changePid = function (pollId, pid, callback) { async.parallel([ function (next) { @@ -298,4 +305,4 @@ const Scheduler = require('./scheduler'); Poll.setSettingsField = function (pollId, field, value, callback) { NodeBB.db.setObjectField(`poll:${pollId}:settings`, field, callback); }; -}(exports)); +}(exports)); \ No newline at end of file diff --git a/lib/sockets.js b/lib/sockets.js index 1c6509d..4e0fbfa 100755 --- a/lib/sockets.js +++ b/lib/sockets.js @@ -198,12 +198,28 @@ const Vote = require('./vote'); return callback(null, result); } - NodeBB.User.getUsersFields(result.votes, ['uid', 'username', 'userslug', 'picture'], (err, userData) => { - if (err) { - console.error(err); + async.parallel({ + isAdmin: function(next){ + NodeBB.User.isAdministrator(socket.uid, next); + }, + pollData: function(next){ + Poll.getInfo(data.pollId, next); + }, + privateVotes : function(next){ + Poll.isPrivateVotes(data.pollId, next); + }, + }, (err, data) => { + if (!data.isAdmin && data.pollData.uid!==socket.uid && !!data.privateVotes) { + result.votes=[]; + return callback(null, result); } - result.votes = userData; - callback(null, result); + NodeBB.User.getUsersFields(result.votes, ['uid', 'username', 'userslug', 'picture'], (err, userData) => { + if (err) { + console.error(err); + } + result.votes = userData; + callback(null, result); + }); }); }); }; @@ -244,4 +260,4 @@ const Vote = require('./vote'); return callback(null, can); }); } -}(exports)); +}(exports)); \ No newline at end of file diff --git a/public/js/poll/creator.js b/public/js/poll/creator.js index 715c3ca..6b60516 100755 --- a/public/js/poll/creator.js +++ b/public/js/poll/creator.js @@ -234,6 +234,7 @@ title: obj['settings.title'], maxvotes: obj['settings.maxvotes'], disallowVoteUpdate: obj['settings.disallowVoteUpdate'] === 'on' ? 'true' : 'false', + privateVotes: obj['settings.privateVotes'] === 'on' ? 'true' : 'false', end: obj['settings.end'], }, }; @@ -251,4 +252,4 @@ Poll.creator = Creator; init(); -}(window.Poll)); +}(window.Poll)); \ No newline at end of file diff --git a/public/js/poll/serializer.js b/public/js/poll/serializer.js index e1867b0..6619df0 100644 --- a/public/js/poll/serializer.js +++ b/public/js/poll/serializer.js @@ -22,6 +22,14 @@ module.exports = function (utils) { return parseInt(value, 10); }, }, + privateVotes: { + test: function (value) { + return /true|false/.test(value); + }, + parse: function (value) { + return value === 'true' || value === true ? 1 : 0; + }, + }, disallowVoteUpdate: { test: function (value) { return /true|false/.test(value); @@ -162,4 +170,4 @@ module.exports = function (utils) { window.Poll.serializer = Serializer; } return Serializer; -}; +}; \ No newline at end of file diff --git a/public/js/poll/view.js b/public/js/poll/view.js index e494799..881e1ec 100644 --- a/public/js/poll/view.js +++ b/public/js/poll/view.js @@ -127,7 +127,7 @@ return Poll.alertError(err.message); } - view.showOptionDetails(details); + view.showOptionDetails({...details, privateVotes: view.isPrivateVotes()}); }); }, }, @@ -204,6 +204,10 @@ return parseInt(this.pollData.settings.disallowVoteUpdate, 10) !== 1; }; + View.prototype.isPrivateVotes = function () { + return parseInt(this.pollData.settings.privateVotes, 10) === 1; + }; + View.prototype.pollEndedOrDeleted = function () { if (this.hasPollEndedOrDeleted()) { this.showResultsPanel(); @@ -369,4 +373,4 @@ } }, }; -}(window.Poll)); +}(window.Poll)); \ No newline at end of file diff --git a/templates/poll/creator.tpl b/templates/poll/creator.tpl index e579d53..84ddf43 100755 --- a/templates/poll/creator.tpl +++ b/templates/poll/creator.tpl @@ -33,6 +33,13 @@ +
+
+ + +
+
+
diff --git a/templates/poll/view/details.tpl b/templates/poll/view/details.tpl index 916df2a..a16e26b 100644 --- a/templates/poll/view/details.tpl +++ b/templates/poll/view/details.tpl @@ -4,3 +4,6 @@ {buildAvatar(votes, "24px", true)} + +

[[poll:votes_are_hidden]]

+ From 95c71b2255789618bbd028ebf3c0a5b6e64e7f63 Mon Sep 17 00:00:00 2001 From: peddler Date: Mon, 4 Sep 2023 00:48:21 +0530 Subject: [PATCH 2/5] add hidden voting mode in which no non-admin user can see the per user voting details --- languages/en_GB/poll.json | 2 ++ languages/en_US/poll.json | 2 ++ lib/config.js | 1 + lib/poll.js | 7 +++++++ lib/sockets.js | 5 ++++- public/js/poll/creator.js | 1 + public/js/poll/serializer.js | 8 ++++++++ public/js/poll/view.js | 6 +++++- templates/poll/creator.tpl | 7 +++++++ templates/poll/view/details.tpl | 5 ++++- 10 files changed, 41 insertions(+), 3 deletions(-) diff --git a/languages/en_GB/poll.json b/languages/en_GB/poll.json index 20875ed..208024f 100755 --- a/languages/en_GB/poll.json +++ b/languages/en_GB/poll.json @@ -9,6 +9,7 @@ "max_votes": "Maximum number of votes per user", "disallow_vote_update": "Don't allow users to update their vote", "private_votes": "Private votes", + "hidden_votes": "Hidden votes", "info_choices": "A number greater than 1 will enable multiple choice polls.", "settings": "Settings", "save": "Save", @@ -42,6 +43,7 @@ "voting_unavailable_message": "This poll has ended or has been marked as deleted. You can still view the results.", "voting_update_disallowed_message": "You have already voted and changing your vote is not allowed for this poll", "vote_is_final": "Changing your vote is not allowed for this poll so your vote is final", + "votes_are_private": "The votes in this poll are hidden from other users excluding the creator of the poll.", "votes_are_hidden": "The votes in this poll are hidden from other users.", "vote_count": "users voted for this option", diff --git a/languages/en_US/poll.json b/languages/en_US/poll.json index a265fc1..2525caa 100755 --- a/languages/en_US/poll.json +++ b/languages/en_US/poll.json @@ -9,6 +9,7 @@ "max_votes": "Maximum number of votes per user", "disallow_vote_update": "Don't allow users to update their vote", "private_votes": "Private votes", + "hidden_votes": "Hidden votes", "info_choices": "A number greater than 1 will enable multiple choice polls.", "settings": "Settings", "save": "Save", @@ -42,6 +43,7 @@ "voting_unavailable_message": "This poll has ended or has been marked as deleted. You can still view the results.", "voting_update_disallowed_message": "You have already voted and changing your vote is not allowed for this poll", "vote_is_final": "Changing your vote is not allowed for this poll so your vote is final", + "votes_are_private": "The votes in this poll are hidden from other users excluding the creator of the poll.", "votes_are_hidden": "The votes in this poll are hidden from other users.", "vote_count": "users voted for this option", diff --git a/lib/config.js b/lib/config.js index 7209cc4..9c9f255 100755 --- a/lib/config.js +++ b/lib/config.js @@ -29,6 +29,7 @@ const pluginId = pluginInfo.id.replace('nodebb-plugin-', ''); disallowVoteUpdate: 0, end: 0, privateVotes: 0, + hiddenVotes: 0, }, }; diff --git a/lib/poll.js b/lib/poll.js index 42a4fc5..5c5c181 100755 --- a/lib/poll.js +++ b/lib/poll.js @@ -94,6 +94,7 @@ const Scheduler = require('./scheduler'); }); pollData.settings.disallowVoteUpdate = parseInt(pollData.settings.disallowVoteUpdate, 10); pollData.settings.privateVotes = parseInt(pollData.settings.privateVotes, 10); + pollData.settings.hiddenVotes = parseInt(pollData.settings.hiddenVotes, 10); callback(null, pollData); }); }; @@ -260,6 +261,12 @@ const Scheduler = require('./scheduler'); }); }; + Poll.isHiddenVotes = function (pollId, callback) { + Poll.getSettingsField(pollId, 'hiddenVotes', (err, result) => { + callback(err, parseInt(result, 10) === 1); + }); + }; + Poll.changePid = function (pollId, pid, callback) { async.parallel([ function (next) { diff --git a/lib/sockets.js b/lib/sockets.js index 4e0fbfa..dad7799 100755 --- a/lib/sockets.js +++ b/lib/sockets.js @@ -208,8 +208,11 @@ const Vote = require('./vote'); privateVotes : function(next){ Poll.isPrivateVotes(data.pollId, next); }, + hiddenVotes : function(next){ + Poll.isHiddenVotes(data.pollId, next); + }, }, (err, data) => { - if (!data.isAdmin && data.pollData.uid!==socket.uid && !!data.privateVotes) { + if (!data.isAdmin && ((data.pollData.uid!==socket.uid && !!data.privateVotes) || !!data.hiddenVotes)) { result.votes=[]; return callback(null, result); } diff --git a/public/js/poll/creator.js b/public/js/poll/creator.js index 6b60516..bab0d0a 100755 --- a/public/js/poll/creator.js +++ b/public/js/poll/creator.js @@ -235,6 +235,7 @@ maxvotes: obj['settings.maxvotes'], disallowVoteUpdate: obj['settings.disallowVoteUpdate'] === 'on' ? 'true' : 'false', privateVotes: obj['settings.privateVotes'] === 'on' ? 'true' : 'false', + hiddenVotes: obj['settings.hiddenVotes'] === 'on' ? 'true' : 'false', end: obj['settings.end'], }, }; diff --git a/public/js/poll/serializer.js b/public/js/poll/serializer.js index 6619df0..40554f2 100644 --- a/public/js/poll/serializer.js +++ b/public/js/poll/serializer.js @@ -30,6 +30,14 @@ module.exports = function (utils) { return value === 'true' || value === true ? 1 : 0; }, }, + hiddenVotes: { + test: function (value) { + return /true|false/.test(value); + }, + parse: function (value) { + return value === 'true' || value === true ? 1 : 0; + }, + }, disallowVoteUpdate: { test: function (value) { return /true|false/.test(value); diff --git a/public/js/poll/view.js b/public/js/poll/view.js index 881e1ec..196f7e5 100644 --- a/public/js/poll/view.js +++ b/public/js/poll/view.js @@ -127,7 +127,7 @@ return Poll.alertError(err.message); } - view.showOptionDetails({...details, privateVotes: view.isPrivateVotes()}); + view.showOptionDetails({...details, privateVotes: view.isPrivateVotes(), hiddenVotes: view.isHiddenVotes()}); }); }, }, @@ -208,6 +208,10 @@ return parseInt(this.pollData.settings.privateVotes, 10) === 1; }; + View.prototype.isHiddenVotes = function () { + return parseInt(this.pollData.settings.hiddenVotes, 10) === 1; + }; + View.prototype.pollEndedOrDeleted = function () { if (this.hasPollEndedOrDeleted()) { this.showResultsPanel(); diff --git a/templates/poll/creator.tpl b/templates/poll/creator.tpl index 84ddf43..234b0f7 100755 --- a/templates/poll/creator.tpl +++ b/templates/poll/creator.tpl @@ -33,6 +33,13 @@
+
+
+ + +
+
+
diff --git a/templates/poll/view/details.tpl b/templates/poll/view/details.tpl index a16e26b..457b516 100644 --- a/templates/poll/view/details.tpl +++ b/templates/poll/view/details.tpl @@ -5,5 +5,8 @@ -

[[poll:votes_are_hidden]]

+

[[poll:votes_are_private]]

+ +

[[poll:votes_are_hidden]]

+ From a81f6993ea118d099a53a02274afda0660ffab80 Mon Sep 17 00:00:00 2001 From: peddler Date: Mon, 4 Sep 2023 00:50:45 +0530 Subject: [PATCH 3/5] update readme with details of new functions --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 37df801..49d1da0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Currently supported settings: maxvotes="1" //Max number of votes per user. If larger than 1, a multiple choice poll will be created disallowVoteUpdate="0" //if set, users won't be able to update/remove their vote title="Poll title" //Poll title + privateVotes="true" //if set, only the poll creator and admins can see per user voting details + hiddenVotes="true" //if set, only admins can see per user voting details There's also a helpful modal available that will allow you to easily create a poll: ![](https://i.imgur.com/2fPnWLb.png) From c2cccfa05b797466fb0e50a7b0f64d31adf4ab1c Mon Sep 17 00:00:00 2001 From: peddler Date: Mon, 4 Sep 2023 01:01:41 +0530 Subject: [PATCH 4/5] UI fix --- templates/poll/view/details.tpl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/templates/poll/view/details.tpl b/templates/poll/view/details.tpl index 457b516..fa28bc2 100644 --- a/templates/poll/view/details.tpl +++ b/templates/poll/view/details.tpl @@ -4,9 +4,10 @@ {buildAvatar(votes, "24px", true)} + +

[[poll:votes_are_hidden]]

+

[[poll:votes_are_private]]

- -

[[poll:votes_are_hidden]]

From 9a6d1412ed1774ba340624eaa7221dbe7396a83f Mon Sep 17 00:00:00 2001 From: peddler Date: Mon, 4 Sep 2023 01:01:56 +0530 Subject: [PATCH 5/5] Clarify readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49d1da0..4a28779 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Currently supported settings: disallowVoteUpdate="0" //if set, users won't be able to update/remove their vote title="Poll title" //Poll title privateVotes="true" //if set, only the poll creator and admins can see per user voting details - hiddenVotes="true" //if set, only admins can see per user voting details + hiddenVotes="true" //if set, only admins can see per user voting details (overrides privateVotes) There's also a helpful modal available that will allow you to easily create a poll: ![](https://i.imgur.com/2fPnWLb.png)