diff --git a/install/data/defaults.json b/install/data/defaults.json index 076a1435d0..a5921ab940 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -137,7 +137,8 @@ "sitemapTopics": 500, "maintenanceMode": 0, "maintenanceModeStatus": 503, - "voteVisibility": "privileged", + "upvoteVisibility": "all", + "downvoteVisibility": "privileged", "maximumInvites": 0, "username:disableEdit": 0, "email:disableEdit": 0, diff --git a/public/language/en-GB/admin/settings/reputation.json b/public/language/en-GB/admin/settings/reputation.json index a78d0e3fd5..479069e3a4 100644 --- a/public/language/en-GB/admin/settings/reputation.json +++ b/public/language/en-GB/admin/settings/reputation.json @@ -2,10 +2,14 @@ "reputation": "Reputation Settings", "disable": "Disable Reputation System", "disable-down-voting": "Disable Down Voting", - "vote-visibility": "Vote visibility", - "vote-visibility-all": "Everyone can see votes", - "vote-visibility-loggedin": "Only logged in users can see votes", - "vote-visibility-privileged": "Only privileged users like admins & moderators can see votes", + "upvote-visibility": "Up Vote visibility", + "upvote-visibility-all": "Everyone can see up votes", + "upvote-visibility-loggedin": "Only logged in users can see up votes", + "upvote-visibility-privileged": "Only privileged users like admins & moderators can see up votes", + "downvote-visibility": "Down Vote visibility", + "downvote-visibility-all": "Everyone can see down votes", + "downvote-visibility-loggedin": "Only logged in users can see down votes", + "downvote-visibility-privileged": "Only privileged users like admins & moderators can see down votes", "thresholds": "Activity Thresholds", "min-rep-upvote": "Minimum reputation to upvote posts", "upvotes-per-day": "Upvotes per day (set to 0 for unlimited upvotes)", diff --git a/public/openapi/read/topic/topic_id.yaml b/public/openapi/read/topic/topic_id.yaml index 366ff32675..f0fde1b6c0 100644 --- a/public/openapi/read/topic/topic_id.yaml +++ b/public/openapi/read/topic/topic_id.yaml @@ -382,7 +382,9 @@ get: type: number downvote:disabled: type: number - voteVisibility: + upvoteVisibility: + type: string + downvoteVisibility: type: string feeds:disableRSS: type: number diff --git a/public/openapi/write/posts/pid/voters.yaml b/public/openapi/write/posts/pid/voters.yaml index 868b587c36..80647dd420 100644 --- a/public/openapi/write/posts/pid/voters.yaml +++ b/public/openapi/write/posts/pid/voters.yaml @@ -28,6 +28,8 @@ get: type: number downvoteCount: type: number + showUpvotes: + type: boolean showDownvotes: type: boolean upvoters: diff --git a/public/src/client/topic/votes.js b/public/src/client/topic/votes.js index d78d197f27..ae690de93a 100644 --- a/public/src/client/topic/votes.js +++ b/public/src/client/topic/votes.js @@ -9,17 +9,24 @@ define('forum/topic/votes', [ Votes.addVoteHandler = function () { _showTooltip = {}; - if (canSeeVotes()) { + if (canSeeUpVotes()) { components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', loadDataAndCreateTooltip); components.get('topic').on('mouseleave', '[data-pid] [component="post/vote-count"]', destroyTooltip); } }; - function canSeeVotes() { - const { voteVisibility, privileges } = ajaxify.data; + function canSeeUpVotes() { + const { upvoteVisibility, privileges } = ajaxify.data; return privileges.isAdminOrMod || - voteVisibility === 'all' || - (voteVisibility === 'loggedin' && config.loggedIn); + upvoteVisibility === 'all' || + (upvoteVisibility === 'loggedin' && config.loggedIn); + } + + function canSeeVotes() { + const { upvoteVisibility, downvoteVisibility, privileges } = ajaxify.data; + return privileges.isAdminOrMod || + upvoteVisibility === 'all' || downvoteVisibility === 'all' || + ((upvoteVisibility === 'loggedin' || downvoteVisibility === 'loggedin') && config.loggedIn); } function destroyTooltip() { diff --git a/src/api/posts.js b/src/api/posts.js index 2bf4a7c3d2..4e3917a008 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -314,12 +314,19 @@ postsAPI.getVoters = async function (caller, data) { } const { pid } = data; const cid = await posts.getCidByPid(pid); - if (!await canSeeVotes(caller.uid, cid)) { + const [canSeeUpvotes, canSeeDownvotes] = await Promise.all([ + canSeeVotes(caller.uid, cid, 'upvoteVisibility'), + canSeeVotes(caller.uid, cid, 'downvoteVisibility'), + ]); + + if (!canSeeUpvotes && !canSeeDownvotes) { throw new Error('[[error:no-privileges]]'); } - const showDownvotes = !meta.config['downvote:disabled']; + const repSystemDisabled = meta.config['reputation:disabled']; + const showUpvotes = canSeeUpvotes && !repSystemDisabled; + const showDownvotes = canSeeDownvotes && !meta.config['downvote:disabled'] && !repSystemDisabled; const [upvoteUids, downvoteUids] = await Promise.all([ - db.getSetMembers(`pid:${data.pid}:upvote`), + showUpvotes ? db.getSetMembers(`pid:${data.pid}:upvote`) : [], showDownvotes ? db.getSetMembers(`pid:${data.pid}:downvote`) : [], ]); @@ -331,6 +338,7 @@ postsAPI.getVoters = async function (caller, data) { return { upvoteCount: upvoters.length, downvoteCount: downvoters.length, + showUpvotes: showUpvotes, showDownvotes: showDownvotes, upvoters: upvoters, downvoters: downvoters, @@ -343,7 +351,7 @@ postsAPI.getUpvoters = async function (caller, data) { } const { pid } = data; const cid = await posts.getCidByPid(pid); - if (!await canSeeVotes(caller.uid, cid)) { + if (!await canSeeVotes(caller.uid, cid, 'upvoteVisibility')) { throw new Error('[[error:no-privileges]]'); } @@ -370,7 +378,7 @@ postsAPI.getUpvoters = async function (caller, data) { }; }; -async function canSeeVotes(uid, cids) { +async function canSeeVotes(uid, cids, type) { const isArray = Array.isArray(cids); if (!isArray) { cids = [cids]; @@ -389,8 +397,8 @@ async function canSeeVotes(uid, cids) { ( cidToAllowed[cid] && ( - meta.config.voteVisibility === 'all' || - (meta.config.voteVisibility === 'loggedin' && parseInt(uid, 10) > 0) + meta.config[type] === 'all' || + (meta.config[type] === 'loggedin' && parseInt(uid, 10) > 0) ) ) ); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 6a3eba85c5..d83ce8e602 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -97,7 +97,8 @@ topicsController.get = async function getTopic(req, res, next) { topicData.topicStaleDays = meta.config.topicStaleDays; topicData['reputation:disabled'] = meta.config['reputation:disabled']; topicData['downvote:disabled'] = meta.config['downvote:disabled']; - topicData.voteVisibility = meta.config.voteVisibility; + topicData.upvoteVisibility = meta.config.upvoteVisibility; + topicData.downvoteVisibility = meta.config.downvoteVisibility; topicData['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; topicData['signatures:hideDuplicates'] = meta.config['signatures:hideDuplicates']; topicData.bookmarkThreshold = meta.config.bookmarkThreshold; diff --git a/src/upgrades/3.8.4/downvote-visibility-config.js b/src/upgrades/3.8.4/downvote-visibility-config.js new file mode 100644 index 0000000000..d3ac559250 --- /dev/null +++ b/src/upgrades/3.8.4/downvote-visibility-config.js @@ -0,0 +1,20 @@ +/* eslint-disable no-await-in-loop */ + +'use strict'; + +const db = require('../../database'); + +module.exports = { + name: 'Add downvote visibility config field', + timestamp: Date.UTC(2024, 6, 17), + method: async function () { + const current = await db.getObjectField('config', 'voteVisibility'); + if (current) { + await db.setObject('config', { + upvoteVisibility: current, + downvoteVisibility: current, + }); + await db.deleteObjectField('config', 'voteVisibility'); + } + }, +}; diff --git a/src/views/admin/settings/reputation.tpl b/src/views/admin/settings/reputation.tpl index b086439498..daedbd76cb 100644 --- a/src/views/admin/settings/reputation.tpl +++ b/src/views/admin/settings/reputation.tpl @@ -14,12 +14,20 @@ +
+ + +
- - + + +
diff --git a/src/views/modals/votes.tpl b/src/views/modals/votes.tpl index 6c0b54e277..e9a31422ec 100644 --- a/src/views/modals/votes.tpl +++ b/src/views/modals/votes.tpl @@ -1,9 +1,11 @@ +{{{ if showUpvotes }}}

[[global:upvoters]] ({upvoteCount})

{{{ each upvoters }}} {buildAvatar(@value, "24px", true)} {{{ end }}}
+{{{ end }}} {{{ if showDownvotes }}}

[[global:downvoters]] ({downvoteCount})