v1.18.x
Barış Soner Uşaklı 6 years ago
parent e12a803b16
commit b6771836cf

@ -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
}
}
]

@ -8,10 +8,7 @@
"id": "ID: <small>optional</small>",
"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",

@ -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);
}

@ -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;

@ -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);
};

@ -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);
};

@ -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) {

@ -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),

@ -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);
};

@ -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);
};

@ -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);
},
};

@ -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);
},
};

@ -59,31 +59,16 @@
</div>
</div>
<strong>[[admin/general/navigation:properties]]</strong>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:adminOnly" <!-- IF enabled.properties.adminOnly -->checked<!-- ENDIF enabled.properties.adminOnly -->/>
<span class="mdl-switch__label"><strong>[[admin/general/navigation:only-admins]]</strong></span>
</label>
</div>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:globalMod" <!-- IF enabled.properties.globalMod -->checked<!-- ENDIF enabled.properties.globalMod -->/>
<span class="mdl-switch__label"><strong>[[admin/general/navigation:only-global-mods-and-admins]]</strong></span>
</label>
</div>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:loggedIn" <!-- IF enabled.properties.loggedIn -->checked<!-- ENDIF enabled.properties.loggedIn -->/>
<span class="mdl-switch__label"><strong>[[admin/general/navigation:only-logged-in]]</strong></span>
</label>
</div>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:guestOnly" <!-- IF enabled.properties.guestOnly -->checked<!-- ENDIF enabled.properties.guestOnly -->/>
<span class="mdl-switch__label"><strong>[[admin/general/navigation:only-guest]]</strong></span>
</label>
<strong>[[admin/general/navigation:groups]]</strong>
<div>
<select name="groups" class="form-control" size="10" multiple>
<!-- BEGIN enabled.groups -->
<option value="{enabled.groups.displayName}"<!-- IF enabled.groups.selected --> selected<!-- ENDIF enabled.groups.selected -->>{enabled.groups.displayName}</option>
<!-- END enabled.groups -->
</select>
</div>
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" name="property:targetBlank" <!-- IF enabled.properties.targetBlank -->checked<!-- ENDIF enabled.properties.targetBlank -->/>

@ -5,12 +5,14 @@
<label>Container:</label>
<textarea rows="4" class="form-control container-html" name="container" placeholder="Drag and drop a container or enter HTML here."></textarea>
<div class="checkbox">
<label><input name="hide-guests" type="checkbox"> Hide from anonymous users?</label>
</div>
<div class="checkbox">
<label><input name="hide-registered" type="checkbox"> Hide from registered users?</input></label>
<br/>
<label>Groups:</label>
<div>
<select name="groups" class="form-control" multiple size="10">
<!-- BEGIN groups -->
<option value="{groups.displayName}">{groups.displayName}</option>
<!-- END groups -->
</select>
</div>
<div class="checkbox">

@ -4,9 +4,12 @@ var fs = require('fs');
var path = require('path');
var async = require('async');
var nconf = require('nconf');
var benchpress = require('benchpressjs');
var plugins = require('../plugins');
var groups = require('../groups');
var admin = {};
var admin = module.exports;
admin.get = function (callback) {
async.parallel({
@ -26,7 +29,7 @@ admin.get = function (callback) {
plugins.fireHook('filter:widgets.getWidgets', [], next);
},
adminTemplate: function (next) {
fs.readFile(path.resolve(nconf.get('views_dir'), 'admin/partials/widget-settings.tpl'), 'utf8', next);
renderAdminTemplate(next);
},
}, function (err, widgetData) {
if (err) {
@ -78,4 +81,17 @@ admin.get = function (callback) {
});
};
module.exports = admin;
function renderAdminTemplate(callback) {
async.waterfall([
function (next) {
async.parallel({
source: async.apply(fs.readFile, path.resolve(nconf.get('views_dir'), 'admin/partials/widget-settings.tpl'), 'utf8'),
groups: async.apply(groups.getNonPrivilegeGroups, 'groups:createtime', 0, -1),
}, next);
},
function (results, next) {
results.groups.sort((a, b) => b.system - a.system);
benchpress.compileParse(results.source, { groups: results.groups }, next);
},
], callback);
}

@ -6,6 +6,7 @@ var _ = require('lodash');
var Benchpress = require('benchpressjs');
var plugins = require('../plugins');
var groups = require('../groups');
var translator = require('../translator');
var db = require('../database');
var apiController = require('../controllers/api');
@ -39,13 +40,6 @@ widgets.render = function (uid, options, callback) {
}
async.map(widgetsByLocation[location], function (widget, next) {
if (!widget || !widget.data
|| (!!widget.data['hide-registered'] && uid !== 0)
|| (!!widget.data['hide-guests'] && uid === 0)
|| (!!widget.data['hide-mobile'] && options.req.useragent.isMobile)) {
return next();
}
renderWidget(widget, uid, options, next);
}, function (err, renderedWidgets) {
if (err) {
@ -65,8 +59,23 @@ widgets.render = function (uid, options, callback) {
function renderWidget(widget, uid, options, callback) {
var userLang;
if (!widget || !widget.data || (!!widget.data['hide-mobile'] && options.req.useragent.isMobile)) {
return setImmediate(callback);
}
async.waterfall([
function (next) {
if (!widget.data.groups.length) {
return next(null, true);
}
groups.isMemberOfAny(uid, widget.data.groups, next);
},
function (isVisible, next) {
if (!isVisible) {
return callback();
}
if (options.res.locals.isAPI) {
apiController.loadConfig(options.req, next);
} else {
@ -115,12 +124,9 @@ function renderWidget(widget, uid, options, callback) {
}
widgets.getWidgetDataForTemplates = function (templates, callback) {
var keys = templates.map(function (tpl) {
return 'widgets:' + tpl;
});
async.waterfall([
function (next) {
const keys = templates.map(tpl => 'widgets:' + tpl);
db.getObjects(keys, next);
},
function (data, next) {
@ -136,6 +142,15 @@ widgets.getWidgetDataForTemplates = function (templates, callback) {
if (templateWidgetData && templateWidgetData[location]) {
try {
returnData[template][location] = JSON.parse(templateWidgetData[location]);
returnData[template][location] = returnData[template][location].map(function (widget) {
if (widget) {
widget.data.groups = widget.data.groups || [];
if (widget.data.groups && !Array.isArray(widget.data.groups)) {
widget.data.groups = [widget.data.groups];
}
}
return widget;
});
} catch (err) {
winston.error('can not parse widget data. template: ' + template + ' location: ' + location);
returnData[template][location] = [];
@ -250,5 +265,3 @@ widgets.resetTemplate = function (template, callback) {
widgets.resetTemplates = function (templates, callback) {
async.eachSeries(templates, widgets.resetTemplate, callback);
};
module.exports = widgets;

@ -206,6 +206,22 @@ describe('Groups', function () {
done();
});
});
it('should return true for uid 0 and guests group', function (done) {
Groups.isMembers([1, 0], 'guests', function (err, isMembers) {
assert.ifError(err);
assert.deepStrictEqual(isMembers, [false, true]);
done();
});
});
it('should return true for uid 0 and guests group', function (done) {
Groups.isMemberOfGroups(0, ['guests', 'registered-users'], function (err, isMembers) {
assert.ifError(err);
assert.deepStrictEqual(isMembers, [true, false]);
done();
});
});
});
describe('.isMemberOfGroupList', function () {

Loading…
Cancel
Save