diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index c656b899a2..d9011d530d 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -27,7 +27,9 @@ define('forum/topic/posts', [ data.privileges = ajaxify.data.privileges; data.posts.forEach(function(post) { post.selfPost = !!app.user.uid && parseInt(post.uid, 10) === parseInt(app.user.uid, 10); - post.display_moderator_tools = post.selfPost || ajaxify.data.privileges.isAdminOrMod; + post.display_edit_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_delete_tools = (ajaxify.data.privileges.editOwnPosts && post.selfPost) || ajaxify.data.privileges.isAdminOrMod; + post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = ajaxify.data.privileges.isAdminOrMod; post.display_post_menu = ajaxify.data.privileges.isAdminOrMod || post.selfPost || ((app.user.uid || ajaxify.data.postSharing.length) && !post.deleted); }); diff --git a/src/categories/create.js b/src/categories/create.js index f80e78f24e..a42c260155 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -48,7 +48,7 @@ module.exports = function(Categories) { function(data, next) { category = data.category; - var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'upload:post:image']; + var defaultPrivileges = ['find', 'read', 'topics:read', 'topics:create', 'topics:reply', 'edit', 'delete', 'upload:post:image']; async.series([ async.apply(db.setObject, 'category:' + category.cid, category), diff --git a/src/privileges.js b/src/privileges.js index 1f28a7cb91..cc1e4d48ed 100644 --- a/src/privileges.js +++ b/src/privileges.js @@ -8,6 +8,9 @@ privileges.userPrivilegeList = [ 'topics:read', 'topics:create', 'topics:reply', + 'edit', + 'delete', + 'topics:delete', 'upload:post:image', 'upload:post:file', 'purge', @@ -20,6 +23,9 @@ privileges.groupPrivilegeList = [ 'groups:topics:read', 'groups:topics:create', 'groups:topics:reply', + 'groups:edit', + 'groups:delete', + 'groups:topics:delete', 'groups:upload:post:image', 'groups:upload:post:file', 'groups:purge', @@ -33,4 +39,4 @@ require('./privileges/topics')(privileges); require('./privileges/posts')(privileges); require('./privileges/users')(privileges); -module.exports = privileges; \ No newline at end of file +module.exports = privileges; diff --git a/src/privileges/categories.js b/src/privileges/categories.js index ce2c30506e..96eaba84c4 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -23,6 +23,9 @@ module.exports = function(privileges) { {name: 'Access Topics'}, {name: 'Create Topics'}, {name: 'Reply to Topics'}, + {name: 'Edit Posts'}, + {name: 'Delete Posts'}, + {name: 'Delete Topics'}, {name: 'Upload Images'}, {name: 'Upload Files'}, {name: 'Purge'}, @@ -362,6 +365,15 @@ module.exports = function(privileges) { 'topics:reply': function(next) { groups.isMember(uid, 'cid:' + cid + ':privileges:topics:reply', next); }, + 'edit': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:edit', next); + }, + 'delete': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:delete', next); + }, + 'topics:delete': function(next) { + groups.isMember(uid, 'cid:' + cid + ':privileges:topics:delete', next); + }, mods: function(next) { user.isModerator(uid, cid, next); } @@ -380,6 +392,15 @@ module.exports = function(privileges) { 'groups:topics:reply': function(next) { groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:reply', next); }, + 'groups:edit': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:edit', next); + }, + 'groups:delete': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:delete', next); + }, + 'groups:topics:delete': function(next) { + groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:delete', next); + }, 'groups:topics:read': function(next) { groups.isMember(groupName, 'cid:' + cid + ':privileges:groups:topics:read', next); } diff --git a/src/privileges/posts.js b/src/privileges/posts.js index a0d06f3600..c946e8c744 100644 --- a/src/privileges/posts.js +++ b/src/privileges/posts.js @@ -30,6 +30,7 @@ module.exports = function(privileges) { isOwner: async.apply(posts.isOwner, pids, uid), 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, cids), read: async.apply(helpers.isUserAllowedTo, 'read', uid, cids), + edit: async.apply(helpers.isUserAllowedTo, 'edit', uid, cids), }, next); } ], function(err, results) { @@ -41,7 +42,7 @@ module.exports = function(privileges) { for (var i=0; i postDeleteDuration * 1000)) { return callback(new Error('[[error:post-delete-duration-expired, ' + meta.config.postDeleteDuration + ']]')); @@ -234,10 +239,13 @@ module.exports = function(privileges) { return callback(null, {isLocked: true}); } - posts.isOwner(pid, uid, next); + async.parallel({ + owner: async.apply(posts.isOwner, pid, uid), + edit: async.apply(privileges.posts.can, 'edit', pid, uid) + }, next); }, - function(isOwner, next) { - next(null, {editable: isOwner}); + function(result, next) { + next(null, {editable: result.owner && result.edit}); } ], callback); } diff --git a/src/privileges/topics.js b/src/privileges/topics.js index d1c8958045..c54102c77e 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -22,6 +22,9 @@ module.exports = function(privileges) { async.parallel({ 'topics:reply': async.apply(helpers.isUserAllowedTo, 'topics:reply', uid, [topic.cid]), 'topics:read': async.apply(helpers.isUserAllowedTo, 'topics:read', uid, [topic.cid]), + 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [topic.cid]), + edit: async.apply(helpers.isUserAllowedTo, 'edit', uid, [topic.cid]), + 'delete': async.apply(helpers.isUserAllowedTo, 'delete', uid, [topic.cid]), read: async.apply(helpers.isUserAllowedTo, 'read', uid, [topic.cid]), isOwner: function(next) { next(null, !!parseInt(uid, 10) && parseInt(uid, 10) === parseInt(topic.uid, 10)); @@ -40,7 +43,7 @@ module.exports = function(privileges) { var locked = parseInt(topic.locked, 10) === 1; var isAdminOrMod = results.isAdministrator || results.isModerator; var editable = isAdminOrMod; - var deletable = isAdminOrMod || results.isOwner; + var deletable = isAdminOrMod || (results.isOwner && results['topics:delete'][0]); plugins.fireHook('filter:privileges.topics.get', { 'topics:reply': (results['topics:reply'][0] && !locked) || isAdminOrMod, @@ -53,7 +56,9 @@ module.exports = function(privileges) { isAdminOrMod: isAdminOrMod, disabled: disabled, tid: tid, - uid: uid + uid: uid, + editOwnPosts: results.edit[0], + deleteOwnPosts: results['delete'][0] }, callback); }); }; @@ -176,6 +181,29 @@ module.exports = function(privileges) { ], callback); }; + privileges.topics.canDelete = function(tid, uid, callback) { + topics.getTopicField(tid, 'cid', function(err, cid) { + if (err) { + return callback(err); + } + helpers.some([ + async.apply(user.isModerator, uid, cid), + async.apply(user.isAdministrator, uid), + function(next) { + async.parallel({ + owner: async.apply(topics.isOwner, tid, uid), + 'topics:delete': async.apply(helpers.isUserAllowedTo, 'topics:delete', uid, [cid]) + }, function(err, result) { + if (err) { + return next(err); + } + next(null, result.owner && result['topics:delete'][0]); + }); + } + ], callback); + }); + }; + privileges.topics.canEdit = function(tid, uid, callback) { privileges.topics.isOwnerOrAdminOrMod(tid, uid, callback); }; diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index bcb7e59642..dfc99ac2dc 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -28,6 +28,12 @@ module.exports = function(SocketPosts) { isAdminOrMod: function(next) { privileges.categories.isAdminOrMod(data.cid, socket.uid, next); }, + canEdit: function(next) { + privileges.posts.canEdit(data.pid, socket.uid, next); + }, + canDelete: function(next) { + privileges.posts.canDelete(data.pid, socket.uid, next); + }, favourited: function(next) { favourites.getFavouritesByPostIDs([data.pid], socket.uid, next); }, @@ -45,7 +51,9 @@ module.exports = function(SocketPosts) { results.posts.deleted = parseInt(results.posts.deleted, 10) === 1; results.posts.favourited = results.favourited[0]; results.posts.selfPost = socket.uid && socket.uid === parseInt(results.posts.uid, 10); - results.posts.display_moderator_tools = results.isAdminOrMod || results.posts.selfPost; + results.posts.display_edit_tools = results.canEdit; + results.posts.display_delete_tools = results.canDelete; + results.posts.display_moderator_tools = results.posts.display_edit_tools || results.posts.display_delete_tools; results.posts.display_move_tools = results.isAdminOrMod; callback(null, results); }); @@ -165,4 +173,4 @@ module.exports = function(SocketPosts) { }, callback); } -}; \ No newline at end of file +}; diff --git a/src/topics/create.js b/src/topics/create.js index a173c1d661..1b561cdcaa 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -292,6 +292,8 @@ module.exports = function(Topics) { postData.favourited = false; postData.votes = 0; + postData.display_edit_tools = true; + postData.display_delete_tools = true; postData.display_moderator_tools = true; postData.display_move_tools = true; postData.selfPost = false; diff --git a/src/topics/posts.js b/src/topics/posts.js index 6bdf103420..66fc39e926 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -141,7 +141,9 @@ module.exports = function(Topics) { var loggedIn = !!parseInt(topicPrivileges.uid, 10); topicData.posts.forEach(function(post) { if (post) { - post.display_moderator_tools = topicPrivileges.isAdminOrMod || post.selfPost; + post.display_edit_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['edit']); + post.display_delete_tools = topicPrivileges.isAdminOrMod || (post.selfPost && topicPrivileges['delete']); + post.display_moderator_tools = post.display_edit_tools || post.display_delete_tools; post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0; post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || ((loggedIn || topicData.postSharing.length) && !post.deleted); post.ip = topicPrivileges.isAdminOrMod ? post.ip : undefined; diff --git a/src/topics/tools.js b/src/topics/tools.js index fd5321eb6b..3975193f76 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -1,11 +1,12 @@ 'use strict'; -var async = require('async'), +var async = require('async'); - db = require('../database'), - categories = require('../categories'), - plugins = require('../plugins'), - privileges = require('../privileges'); +var db = require('../database'); +var categories = require('../categories'); +var meta = require('../meta'); +var plugins = require('../plugins'); +var privileges = require('../privileges'); module.exports = function(Topics) { @@ -32,10 +33,10 @@ module.exports = function(Topics) { if (!exists) { return next(new Error('[[error:no-topic]]')); } - privileges.topics.isOwnerOrAdminOrMod(tid, uid, next); + privileges.topics.canDelete(tid, uid, next); }, - function (isOwnerOrAdminOrMod, next) { - if (!isOwnerOrAdminOrMod) { + function (canDelete, next) { + if (!canDelete) { return next(new Error('[[error:no-privileges]]')); } Topics.getTopicFields(tid, ['tid', 'cid', 'uid', 'deleted', 'title', 'mainPid'], next); diff --git a/src/upgrade.js b/src/upgrade.js index dc4af652c9..f30021da4e 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -10,7 +10,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2016, 7, 5); + latestSchema = Date.UTC(2016, 8, 6); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -682,6 +682,111 @@ Upgrade.upgrade = function(callback) { winston.info('[2016/08/05] Removing best posts with negative scores skipped!'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2016, 8, 6); + + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2016/08/06] Granting edit/delete/delete topic on existing categories'); + + var groupsAPI = require('./groups'); + var privilegesAPI = require('./privileges'); + + db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + async.eachSeries(cids, function(cid, next) { + privilegesAPI.categories.list(cid, function(err, data) { + var groups = data.groups; + var users = data.users; + + async.waterfall([ + function(next) { + async.eachSeries(groups, function(group, next) { + if (group.privileges['groups:topics:reply']) { + return async.parallel([ + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:edit', group.name), + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:groups:delete', group.name) + ], function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:groups:edit, cid:' + cid + ':privileges:groups:delete granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function(next) { + async.eachSeries(groups, function(group, next) { + if (group.privileges['groups:topics:create']) { + return groupsAPI.join('cid:' + cid + ':privileges:groups:topics:delete', group.name, function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:groups:topics:delete granted to gid: ' + group.name); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function(next) { + async.eachSeries(users, function(user, next) { + if (user.privileges['topics:reply']) { + return async.parallel([ + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:edit', user.uid), + async.apply(groupsAPI.join, 'cid:' + cid + ':privileges:delete', user.uid) + ], function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:edit, cid:' + cid + ':privileges:delete granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + }, + function(next) { + async.eachSeries(users, function(user, next) { + if (user.privileges['topics:create']) { + return groupsAPI.join('cid:' + cid + ':privileges:topics:delete', user.uid, function(err) { + if (!err) { + winston.info('cid:' + cid + ':privileges:topics:delete granted to uid: ' + user.uid); + } + + return next(err); + }); + } + + next(null); + }, next); + } + ], function(err) { + if (!err) { + winston.info('-- cid ' + cid + ' upgraded'); + } + + next(err); + }); + }); + }, function(err) { + if (err) { + return next(err); + } + + winston.info('[2016/08/06] Granting edit/delete/delete topic on existing categories - done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + } else { + winston.info('[2016/08/06] Granting edit/delete/delete topic on existing categories - skipped!'); + next(); + } } // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!! diff --git a/src/views/admin/partials/categories/groups.tpl b/src/views/admin/partials/categories/groups.tpl index eb432bfd27..ff2558bd20 100644 --- a/src/views/admin/partials/categories/groups.tpl +++ b/src/views/admin/partials/categories/groups.tpl @@ -10,8 +10,11 @@
  • Access Topics
  • Create Topics
  • Reply to Topics
  • +
  • Edit Posts
  • +
  • Delete Posts
  • +
  • Delete Topics
  • {groups.displayName} - \ No newline at end of file + diff --git a/src/views/admin/partials/categories/users.tpl b/src/views/admin/partials/categories/users.tpl index 9ec7c262fd..c97d452d5d 100644 --- a/src/views/admin/partials/categories/users.tpl +++ b/src/views/admin/partials/categories/users.tpl @@ -10,10 +10,13 @@
  • Access Topics
  • Create Topics
  • Reply to Topics
  • +
  • Edit Posts
  • +
  • Delete Posts
  • +
  • Delete Topics
  • Moderator
  • {users.username} - \ No newline at end of file +