From b6771836cf73bce37af84034e92dd48eb8b571b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 10 Nov 2018 20:51:07 -0500 Subject: [PATCH] closes #6937 --- install/data/navigation.json | 8 +- .../en-GB/admin/general/navigation.json | 5 +- public/src/admin/general/navigation.js | 7 ++ public/src/modules/helpers.js | 10 +-- src/controllers/admin/navigation.js | 26 +++++-- src/groups/index.js | 12 +++ src/groups/membership.js | 73 ++++++++++--------- src/middleware/header.js | 2 +- src/navigation/admin.js | 21 ++++-- src/navigation/index.js | 17 +++-- .../1.11.0/navigation_visibility_groups.js | 36 +++++++++ .../1.11.0/widget_visibility_groups.js | 47 ++++++++++++ src/views/admin/general/navigation.tpl | 33 +++------ src/views/admin/partials/widget-settings.tpl | 14 ++-- src/widgets/admin.js | 22 +++++- src/widgets/index.js | 39 ++++++---- test/groups.js | 16 ++++ 17 files changed, 270 insertions(+), 118 deletions(-) create mode 100644 src/upgrades/1.11.0/navigation_visibility_groups.js create mode 100644 src/upgrades/1.11.0/widget_visibility_groups.js diff --git a/install/data/navigation.json b/install/data/navigation.json index 3f47367ce8..b338b6a0f4 100644 --- a/install/data/navigation.json +++ b/install/data/navigation.json @@ -15,9 +15,7 @@ "iconClass": "fa-inbox", "textClass": "visible-xs-inline", "text": "[[global:header.unread]]", - "properties": { - "loggedIn": true - } + "groups": ["registered-users"] }, { "route": "/recent", @@ -66,9 +64,9 @@ "iconClass": "fa-cogs", "textClass": "visible-xs-inline", "text": "[[global:header.admin]]", + "groups": ["administrators"], "properties": { - "targetBlank": false, - "adminOnly": true + "targetBlank": false } } ] \ No newline at end of file diff --git a/public/language/en-GB/admin/general/navigation.json b/public/language/en-GB/admin/general/navigation.json index a199668ae2..8313be9656 100644 --- a/public/language/en-GB/admin/general/navigation.json +++ b/public/language/en-GB/admin/general/navigation.json @@ -8,10 +8,7 @@ "id": "ID: optional", "properties": "Properties:", - "only-admins": "Only display to Admins", - "only-global-mods-and-admins": "Only display to Global Moderators and Admins", - "only-logged-in": "Only display to logged in users", - "only-guest": "Only display to guests", + "groups": "Groups:", "open-new-window": "Open in a new window", "btn.delete": "Delete", diff --git a/public/src/admin/general/navigation.js b/public/src/admin/general/navigation.js index 8987c1aa23..8a9e1718e6 100644 --- a/public/src/admin/general/navigation.js +++ b/public/src/admin/general/navigation.js @@ -103,6 +103,13 @@ define('admin/general/navigation', ['translator', 'iconSelect', 'benchpress', 'j form.forEach(function (input) { if (input.name.slice(0, 9) === 'property:' && input.value === 'on') { properties[input.name.slice(9)] = true; + } else if (data[input.name]) { + if (!Array.isArray(data[input.name])) { + data[input.name] = [ + data[input.name], + ]; + } + data[input.name].push(input.value); } else { data[input.name] = translator.escape(input.value); } diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index dcc015f435..ad5862981d 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -39,16 +39,8 @@ if (!item) { return false; } - var properties = item.properties; + var loggedIn = data.config ? data.config.loggedIn : false; - if (properties) { - if ((properties.loggedIn && !loggedIn) - || (properties.guestOnly && loggedIn) - || (properties.globalMod && !data.isGlobalMod && !data.isAdmin) - || (properties.adminOnly && !data.isAdmin)) { - return false; - } - } if (item.route.match('/users') && data.privateUserInfo && !loggedIn) { return false; diff --git a/src/controllers/admin/navigation.js b/src/controllers/admin/navigation.js index e54c6b0eb1..cc3aa3ce24 100644 --- a/src/controllers/admin/navigation.js +++ b/src/controllers/admin/navigation.js @@ -1,22 +1,38 @@ 'use strict'; var async = require('async'); +const _ = require('lodash'); var navigationAdmin = require('../../navigation/admin'); +const groups = require('../../groups'); + var navigationController = module.exports; navigationController.get = function (req, res, next) { async.waterfall([ - navigationAdmin.getAdmin, - function (data) { - data.enabled.forEach(function (enabled, index) { + function (next) { + async.parallel({ + admin: async.apply(navigationAdmin.getAdmin), + groups: async.apply(groups.getNonPrivilegeGroups, 'groups:createtime', 0, -1), + }, next); + }, + function (result) { + result.admin.enabled.forEach(function (enabled, index) { enabled.index = index; enabled.selected = index === 0; + const groupData = _.cloneDeep(result.groups); + + enabled.groups = groupData.map(function (group) { + group.selected = enabled.groups.includes(group.name); + return group; + }); + + enabled.groups.sort((a, b) => b.system - a.system); }); - data.navigation = data.enabled.slice(); + result.admin.navigation = result.admin.enabled.slice(); - res.render('admin/general/navigation', data); + res.render('admin/general/navigation', result.admin); }, ], next); }; diff --git a/src/groups/index.js b/src/groups/index.js index 96b4bf2e14..0d21f24525 100644 --- a/src/groups/index.js +++ b/src/groups/index.js @@ -73,6 +73,18 @@ Groups.getGroupsFromSet = function (set, uid, start, stop, callback) { ], callback); }; +Groups.getNonPrivilegeGroups = function (set, start, stop, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange(set, start, stop, next); + }, + function (groupNames, next) { + groupNames = groupNames.concat(Groups.ephemeralGroups).filter(groupName => !Groups.isPrivilegeGroup(groupName)); + Groups.getGroupsData(groupNames, next); + }, + ], callback); +}; + Groups.getGroups = function (set, start, stop, callback) { db.getSortedSetRevRange(set, start, stop, callback); }; diff --git a/src/groups/membership.js b/src/groups/membership.js index 0e7414193d..86e8102be7 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -164,18 +164,17 @@ module.exports = function (Groups) { Groups.isMembers = function (uids, groupName, callback) { var cachedData = {}; function getFromCache() { - setImmediate(callback, null, uids.map(function (uid) { - return cachedData[uid + ':' + groupName]; - })); + setImmediate(callback, null, uids.map(uid => cachedData[uid + ':' + groupName])); } - if (!groupName || !uids.length) { - return callback(null, uids.map(function () { return false; })); + return setImmediate(callback, null, uids.map(() => false)); } - var nonCachedUids = uids.filter(function (uid) { - return filterNonCached(cachedData, uid, groupName); - }); + if (groupName === 'guests') { + return setImmediate(callback, null, uids.map(uid => parseInt(uid, 10) === 0)); + } + + var nonCachedUids = uids.filter(uid => filterNonCached(cachedData, uid, groupName)); if (!nonCachedUids.length) { return getFromCache(callback); @@ -196,44 +195,25 @@ module.exports = function (Groups) { ], callback); }; - function filterNonCached(cachedData, uid, groupName) { - var isMember = Groups.cache.get(uid + ':' + groupName); - var isInCache = isMember !== undefined; - if (isInCache) { - Groups.cache.hits += 1; - cachedData[uid + ':' + groupName] = isMember; - } else { - Groups.cache.misses += 1; - } - return !isInCache; - } - Groups.isMemberOfGroups = function (uid, groups, callback) { var cachedData = {}; function getFromCache(next) { - setImmediate(next, null, groups.map(function (groupName) { - return cachedData[uid + ':' + groupName]; - })); + setImmediate(next, null, groups.map(groupName => cachedData[uid + ':' + groupName])); } if (!uid || parseInt(uid, 10) <= 0 || !groups.length) { - return callback(null, groups.map(function () { return false; })); + return callback(null, groups.map(groupName => groupName === 'guests')); } - var nonCachedGroups = groups.filter(function (groupName) { - return filterNonCached(cachedData, uid, groupName); - }); + var nonCachedGroups = groups.filter(groupName => filterNonCached(cachedData, uid, groupName)); if (!nonCachedGroups.length) { return getFromCache(callback); } - var nonCachedGroupsMemberSets = nonCachedGroups.map(function (groupName) { - return 'group:' + groupName + ':members'; - }); - async.waterfall([ function (next) { + const nonCachedGroupsMemberSets = nonCachedGroups.map(groupName => 'group:' + groupName + ':members'); db.isMemberOfSortedSets(nonCachedGroupsMemberSets, uid, next); }, function (isMembers, next) { @@ -247,6 +227,32 @@ module.exports = function (Groups) { ], callback); }; + function filterNonCached(cachedData, uid, groupName) { + var isMember = Groups.cache.get(uid + ':' + groupName); + var isInCache = isMember !== undefined; + if (isInCache) { + Groups.cache.hits += 1; + cachedData[uid + ':' + groupName] = isMember; + } else { + Groups.cache.misses += 1; + } + return !isInCache; + } + + Groups.isMemberOfAny = function (uid, groups, callback) { + if (!groups.length) { + return setImmediate(callback, null, false); + } + async.waterfall([ + function (next) { + Groups.isMemberOfGroups(uid, groups, next); + }, + function (isMembers, next) { + next(null, isMembers.some(isMember => !!isMember)); + }, + ], callback); + }; + Groups.getMemberCount = function (groupName, callback) { async.waterfall([ function (next) { @@ -318,10 +324,7 @@ module.exports = function (Groups) { Groups.isMembersOfGroupList = function (uids, groupListKey, callback) { var groupNames; - var results = []; - uids.forEach(function () { - results.push(false); - }); + var results = uids.map(() => false); async.waterfall([ function (next) { diff --git a/src/middleware/header.js b/src/middleware/header.js index d29592a499..be2e100044 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -115,7 +115,7 @@ module.exports = function (middleware) { next(null, translated); }); }, - navigation: navigation.get, + navigation: async.apply(navigation.get, req.uid), tags: async.apply(meta.tags.parse, req, data, res.locals.metaTags, res.locals.linkTags), banned: async.apply(user.isBanned, req.uid), banReason: async.apply(user.getBannedReason, req.uid), diff --git a/src/navigation/admin.js b/src/navigation/admin.js index 308445db4c..9d304e1ced 100644 --- a/src/navigation/admin.js +++ b/src/navigation/admin.js @@ -1,16 +1,18 @@ 'use strict'; var async = require('async'); +const _ = require('lodash'); + var plugins = require('../plugins'); var db = require('../database'); var translator = require('../translator'); var pubsub = require('../pubsub'); var admin = module.exports; -admin.cache = null; +let cache = null; pubsub.on('admin:navigation:save', function () { - admin.cache = null; + cache = null; }); admin.save = function (data, callback) { @@ -25,7 +27,7 @@ admin.save = function (data, callback) { return JSON.stringify(item); }); - admin.cache = null; + cache = null; pubsub.publish('admin:navigation:save'); async.waterfall([ function (next) { @@ -45,16 +47,25 @@ admin.getAdmin = function (callback) { }; admin.get = function (callback) { + if (cache) { + return setImmediate(callback, null, _.cloneDeep(cache)); + } async.waterfall([ function (next) { db.getSortedSetRange('navigation:enabled', 0, -1, next); }, function (data, next) { data = data.map(function (item) { - return JSON.parse(item); + item = JSON.parse(item); + item.groups = item.groups || []; + if (item.groups && !Array.isArray(item.groups)) { + item.groups = [item.groups]; + } + return item; }); - next(null, data); + cache = data; + next(null, _.cloneDeep(cache)); }, ], callback); }; diff --git a/src/navigation/index.js b/src/navigation/index.js index e6aadbcf7d..8f2795e053 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -2,18 +2,14 @@ var async = require('async'); var nconf = require('nconf'); -var _ = require('lodash'); var admin = require('./admin'); var translator = require('../translator'); +const groups = require('../groups'); var navigation = module.exports; -navigation.get = function (callback) { - if (admin.cache) { - return callback(null, _.cloneDeep(admin.cache)); - } - +navigation.get = function (uid, callback) { async.waterfall([ admin.get, function (data, next) { @@ -33,8 +29,13 @@ navigation.get = function (callback) { return item; }); - admin.cache = data; - next(null, _.cloneDeep(admin.cache)); + async.filter(data, function (navItem, next) { + if (!navItem.groups.length) { + return setImmediate(next, null, true); + } + groups.isMemberOfAny(uid, navItem.groups, next); + }, next); }, ], callback); }; + diff --git a/src/upgrades/1.11.0/navigation_visibility_groups.js b/src/upgrades/1.11.0/navigation_visibility_groups.js new file mode 100644 index 0000000000..f0c45cd6fd --- /dev/null +++ b/src/upgrades/1.11.0/navigation_visibility_groups.js @@ -0,0 +1,36 @@ +'use strict'; + +var async = require('async'); + +module.exports = { + name: 'Navigation item visibility groups', + timestamp: Date.UTC(2018, 10, 10), + method: function (callback) { + const navigationAdmin = require('../../navigation/admin'); + + async.waterfall([ + function (next) { + navigationAdmin.get(next); + }, + function (data, next) { + data.forEach(function (navItem) { + if (navItem) { + navItem.groups = []; + if (navItem.properties.adminOnly) { + navItem.groups.push('administrators'); + } else if (navItem.properties.globalMod) { + navItem.groups.push('Global Moderators'); + } + + if (navItem.properties.loggedIn) { + navItem.groups.push('registered-users'); + } else if (navItem.properties.guestOnly) { + navItem.groups.push('guests'); + } + } + }); + navigationAdmin.save(data, next); + }, + ], callback); + }, +}; diff --git a/src/upgrades/1.11.0/widget_visibility_groups.js b/src/upgrades/1.11.0/widget_visibility_groups.js new file mode 100644 index 0000000000..38220f4d68 --- /dev/null +++ b/src/upgrades/1.11.0/widget_visibility_groups.js @@ -0,0 +1,47 @@ +'use strict'; + +var async = require('async'); + +module.exports = { + name: 'Widget visibility groups', + timestamp: Date.UTC(2018, 10, 10), + method: function (callback) { + const widgetAdmin = require('../../widgets/admin'); + const widgets = require('../../widgets'); + async.waterfall([ + function (next) { + widgetAdmin.get(next); + }, + function (data, next) { + async.eachSeries(data.areas, function (area, next) { + if (area.data.length) { + // area.data is actually an array of widgets + area.widgets = area.data; + area.widgets.forEach(function (widget) { + if (widget && widget.data) { + const groupsToShow = ['administrators', 'Global Moderators']; + if (widget.data['hide-guests'] !== 'on') { + groupsToShow.push('guests'); + } + if (widget.data['hide-registered'] !== 'on') { + groupsToShow.push('registered-users'); + } + + widget.data.groups = groupsToShow; + + // if we are showing to all 4 groups, set to empty array + // empty groups is shown to everyone + if (groupsToShow.length === 4) { + widget.data.groups.length = 0; + } + } + }); + widgets.setArea(area, next); + } else { + next(); + } + }, next); + }, + ], callback); + }, +}; diff --git a/src/views/admin/general/navigation.tpl b/src/views/admin/general/navigation.tpl index 1ba3477a07..eb1c0edeb7 100644 --- a/src/views/admin/general/navigation.tpl +++ b/src/views/admin/general/navigation.tpl @@ -59,31 +59,16 @@ - [[admin/general/navigation:properties]] -
- -
-
- -
-
- -
-
- + + [[admin/general/navigation:groups]] +
+
+