From 2ccf60a6ec77219dd6ce57afa2b848b1474430e6 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 1 Mar 2017 13:49:55 -0500 Subject: [PATCH 01/87] fixed duplicate uninstall button --- src/views/admin/partials/installed_plugin_item.tpl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/views/admin/partials/installed_plugin_item.tpl b/src/views/admin/partials/installed_plugin_item.tpl index b76ae9f964..2e95606ef8 100644 --- a/src/views/admin/partials/installed_plugin_item.tpl +++ b/src/views/admin/partials/installed_plugin_item.tpl @@ -36,13 +36,7 @@
  • - -<<<<<<< HEAD - - -======= ->>>>>>> `admin/extend` translations

    {installed.id}

    From 1bee91fa2286987743dea737bee2741f6ae1ac0a Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 1 Mar 2017 22:42:10 +0300 Subject: [PATCH 02/87] groups refactor --- src/groups.js | 666 ++++++++++++++--------------------- src/groups/data.js | 93 +++++ src/groups/membership.js | 125 +++---- src/groups/posts.js | 32 ++ src/groups/search.js | 2 +- src/groups/user.js | 50 +++ src/privileges/categories.js | 2 +- 7 files changed, 499 insertions(+), 471 deletions(-) create mode 100644 src/groups/data.js create mode 100644 src/groups/posts.js create mode 100644 src/groups/user.js diff --git a/src/groups.js b/src/groups.js index 863a61016c..7e2e216caa 100644 --- a/src/groups.js +++ b/src/groups.js @@ -6,215 +6,197 @@ var validator = require('validator'); var user = require('./user'); var db = require('./database'); var plugins = require('./plugins'); -var posts = require('./posts'); -var privileges = require('./privileges'); var utils = require('../public/src/utils'); -(function (Groups) { - require('./groups/create')(Groups); - require('./groups/delete')(Groups); - require('./groups/update')(Groups); - require('./groups/membership')(Groups); - require('./groups/ownership')(Groups); - require('./groups/search')(Groups); - require('./groups/cover')(Groups); - - var ephemeralGroups = ['guests']; +var Groups = module.exports; + +require('./groups/data')(Groups); +require('./groups/create')(Groups); +require('./groups/delete')(Groups); +require('./groups/update')(Groups); +require('./groups/membership')(Groups); +require('./groups/ownership')(Groups); +require('./groups/search')(Groups); +require('./groups/cover')(Groups); +require('./groups/posts')(Groups); +require('./groups/user')(Groups); + + +Groups.ephemeralGroups = ['guests']; + +Groups.getEphemeralGroup = function (groupName) { + return { + name: groupName, + slug: utils.slugify(groupName), + description: '', + deleted: '0', + hidden: '0', + system: '1', + }; +}; - var internals = { - getEphemeralGroup: function (groupName) { - return { - name: groupName, - slug: utils.slugify(groupName), - description: '', - deleted: '0', - hidden: '0', - system: '1', - }; - }, - removeEphemeralGroups: function (groups) { - for (var x = groups.length; x >= 0; x -= 1) { - if (ephemeralGroups.indexOf(groups[x]) !== -1) { - groups.splice(x, 1); - } +Groups.removeEphemeralGroups = function (groups) { + for (var x = groups.length; x >= 0; x -= 1) { + if (Groups.ephemeralGroups.indexOf(groups[x]) !== -1) { + groups.splice(x, 1); + } + } + + return groups; +}; + +var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/; +Groups.isPrivilegeGroup = function (groupName) { + return isPrivilegeGroupRegex.test(groupName); +}; + +Groups.getGroupsFromSet = function (set, uid, start, stop, callback) { + async.waterfall([ + function (next) { + if (set === 'groups:visible:name') { + db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next); + } else { + db.getSortedSetRevRange(set, start, stop, next); } - - return groups; }, - }; - - Groups.internals = internals; - - var isPrivilegeGroupRegex = /^cid:\d+:privileges:[\w:]+$/; - Groups.isPrivilegeGroup = function (groupName) { - return isPrivilegeGroupRegex.test(groupName); - }; - - Groups.getEphemeralGroups = function () { - return ephemeralGroups; - }; - - Groups.getGroupsFromSet = function (set, uid, start, stop, callback) { - async.waterfall([ - function (next) { - if (set === 'groups:visible:name') { - db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next); - } else { - db.getSortedSetRevRange(set, start, stop, next); - } - }, - function (groupNames, next) { - if (set === 'groups:visible:name') { - groupNames = groupNames.map(function (name) { - return name.split(':')[1]; - }); - } - - Groups.getGroupsAndMembers(groupNames, next); - }, - ], callback); - }; - - Groups.getGroups = function (set, start, stop, callback) { - db.getSortedSetRevRange(set, start, stop, callback); - }; - - Groups.getGroupsAndMembers = function (groupNames, callback) { - async.parallel({ - groups: function (next) { - Groups.getGroupsData(groupNames, next); - }, - members: function (next) { - Groups.getMemberUsers(groupNames, 0, 3, next); - }, - }, function (err, data) { - if (err) { - return callback(err); + function (groupNames, next) { + if (set === 'groups:visible:name') { + groupNames = groupNames.map(function (name) { + return name.split(':')[1]; + }); } + Groups.getGroupsAndMembers(groupNames, next); + }, + ], callback); +}; + +Groups.getGroups = function (set, start, stop, callback) { + db.getSortedSetRevRange(set, start, stop, callback); +}; + +Groups.getGroupsAndMembers = function (groupNames, callback) { + async.waterfall([ + function (next) { + async.parallel({ + groups: function (next) { + Groups.getGroupsData(groupNames, next); + }, + members: function (next) { + Groups.getMemberUsers(groupNames, 0, 3, next); + }, + }, next); + }, + function (data, next) { data.groups.forEach(function (group, index) { - if (!group) { - return; + if (group) { + group.members = data.members[index] || []; + group.truncated = group.memberCount > data.members.length; } - - group.members = data.members[index] || []; - group.truncated = group.memberCount > data.members.length; }); + next(null, data.groups); + }, + ], callback); +}; + +Groups.get = function (groupName, options, callback) { + if (!groupName) { + return callback(new Error('[[error:invalid-group]]')); + } + + var stop = -1; + + var results; + async.waterfall([ + function (next) { + async.parallel({ + base: function (next) { + db.getObject('group:' + groupName, next); + }, + members: function (next) { + if (options.truncateUserList) { + stop = (parseInt(options.userListCount, 10) || 4) - 1; + } - callback(null, data.groups); - }); - }; - - Groups.get = function (groupName, options, callback) { - if (!groupName) { - return callback(new Error('[[error:invalid-group]]')); - } - - var stop = -1; - - async.parallel({ - base: function (next) { - db.getObject('group:' + groupName, next); - }, - members: function (next) { - if (options.truncateUserList) { - stop = (parseInt(options.userListCount, 10) || 4) - 1; - } - - Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next); - }, - pending: function (next) { - async.waterfall([ - function (next) { - db.getSetMembers('group:' + groupName + ':pending', next); - }, - function (uids, next) { - user.getUsersData(uids, next); - }, - ], next); - }, - invited: function (next) { - async.waterfall([ - function (next) { - db.getSetMembers('group:' + groupName + ':invited', next); - }, - function (uids, next) { - user.getUsersData(uids, next); - }, - ], next); - }, - isMember: async.apply(Groups.isMember, options.uid, groupName), - isPending: async.apply(Groups.isPending, options.uid, groupName), - isInvited: async.apply(Groups.isInvited, options.uid, groupName), - isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName), - }, function (err, results) { - if (err) { - return callback(err); - } else if (!results.base) { + Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next); + }, + pending: function (next) { + Groups.getUsersFromSet('group:' + groupName + ':pending', next); + }, + invited: function (next) { + Groups.getUsersFromSet('group:' + groupName + ':invited', next); + }, + isMember: async.apply(Groups.isMember, options.uid, groupName), + isPending: async.apply(Groups.isPending, options.uid, groupName), + isInvited: async.apply(Groups.isInvited, options.uid, groupName), + isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName), + }, next); + }, + function (_results, next) { + results = _results; + if (!results.base) { return callback(new Error('[[error:no-group]]')); } - - results.base['cover:url'] = results.base['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName); - results.base['cover:position'] = validator.escape(String(results.base['cover:position'] || '50% 50%')); - results.base.labelColor = validator.escape(String(results.base.labelColor || '#000000')); - results.base.icon = validator.escape(String(results.base.icon || '')); - - plugins.fireHook('filter:parse.raw', results.base.description, function (err, descriptionParsed) { - if (err) { - return callback(err); - } - - Groups.escapeGroupData(results.base); - - results.base.descriptionParsed = descriptionParsed; - results.base.userTitleEnabled = results.base.userTitleEnabled ? !!parseInt(results.base.userTitleEnabled, 10) : true; - results.base.createtimeISO = utils.toISOString(results.base.createtime); - results.base.members = results.members; - results.base.membersNextStart = stop + 1; - results.base.pending = results.pending.filter(Boolean); - results.base.invited = results.invited.filter(Boolean); - results.base.deleted = !!parseInt(results.base.deleted, 10); - results.base.hidden = !!parseInt(results.base.hidden, 10); - results.base.system = !!parseInt(results.base.system, 10); - results.base.memberCount = parseInt(results.base.memberCount, 10); - results.base.private = (results.base.private === null || results.base.private === undefined) ? true : !!parseInt(results.base.private, 10); - results.base.disableJoinRequests = parseInt(results.base.disableJoinRequests, 10) === 1; - results.base.isMember = results.isMember; - results.base.isPending = results.isPending; - results.base.isInvited = results.isInvited; - results.base.isOwner = results.isOwner; - - plugins.fireHook('filter:group.get', { group: results.base }, function (err, data) { - callback(err, data ? data.group : null); - }); - }); - }); - }; - - Groups.getOwners = function (groupName, callback) { - db.getSetMembers('group:' + groupName + ':owners', callback); - }; - - Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) { - async.parallel({ - owners: function (next) { - async.waterfall([ - function (next) { - db.getSetMembers('group:' + groupName + ':owners', next); - }, - function (uids, next) { - user.getUsers(uids, uid, next); - }, - ], next); - }, - members: function (next) { - user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next); - }, - }, function (err, results) { - if (err) { - return callback(err); - } - + plugins.fireHook('filter:parse.raw', results.base.description, next); + }, + function (descriptionParsed, next) { + var groupData = results.base; + Groups.escapeGroupData(groupData); + + groupData.descriptionParsed = descriptionParsed; + groupData.userTitleEnabled = groupData.userTitleEnabled ? !!parseInt(groupData.userTitleEnabled, 10) : true; + groupData.createtimeISO = utils.toISOString(groupData.createtime); + groupData.members = results.members; + groupData.membersNextStart = stop + 1; + groupData.pending = results.pending.filter(Boolean); + groupData.invited = results.invited.filter(Boolean); + groupData.deleted = !!parseInt(groupData.deleted, 10); + groupData.hidden = !!parseInt(groupData.hidden, 10); + groupData.system = !!parseInt(groupData.system, 10); + groupData.memberCount = parseInt(groupData.memberCount, 10); + groupData.private = (groupData.private === null || groupData.private === undefined) ? true : !!parseInt(groupData.private, 10); + groupData.disableJoinRequests = parseInt(groupData.disableJoinRequests, 10) === 1; + groupData.isMember = results.isMember; + groupData.isPending = results.isPending; + groupData.isInvited = results.isInvited; + groupData.isOwner = results.isOwner; + groupData['cover:url'] = groupData['cover:url'] || require('./coverPhoto').getDefaultGroupCover(groupName); + groupData['cover:position'] = validator.escape(String(groupData['cover:position'] || '50% 50%')); + groupData.labelColor = validator.escape(String(groupData.labelColor || '#000000')); + groupData.icon = validator.escape(String(groupData.icon || '')); + + plugins.fireHook('filter:group.get', { group: groupData }, next); + }, + function (results, next) { + next(null, results.group); + }, + ], callback); +}; + +Groups.getOwners = function (groupName, callback) { + db.getSetMembers('group:' + groupName + ':owners', callback); +}; + +Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) { + async.waterfall([ + function (next) { + async.parallel({ + owners: function (next) { + async.waterfall([ + function (next) { + db.getSetMembers('group:' + groupName + ':owners', next); + }, + function (uids, next) { + user.getUsers(uids, uid, next); + }, + ], next); + }, + members: function (next) { + user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next); + }, + }, next); + }, + function (results, next) { var ownerUids = []; results.owners.forEach(function (user) { if (user) { @@ -228,229 +210,97 @@ var utils = require('../public/src/utils'); }); results.members = results.owners.concat(results.members); - callback(null, results.members); - }); - }; - - Groups.escapeGroupData = function (group) { - if (group) { - group.nameEncoded = encodeURIComponent(group.name); - group.displayName = validator.escape(String(group.name)); - group.description = validator.escape(String(group.description || '')); - group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName; - } - }; - - Groups.getByGroupslug = function (slug, options, callback) { - db.getObjectField('groupslug:groupname', slug, function (err, groupName) { - if (err) { - return callback(err); - } else if (!groupName) { - return callback(new Error('[[error:no-group]]')); - } - - Groups.get(groupName, options, callback); - }); - }; - - Groups.getGroupNameByGroupSlug = function (slug, callback) { - db.getObjectField('groupslug:groupname', slug, callback); - }; - - Groups.getGroupFields = function (groupName, fields, callback) { - Groups.getMultipleGroupFields([groupName], fields, function (err, groups) { - callback(err, groups ? groups[0] : null); - }); - }; - - Groups.getMultipleGroupFields = function (groups, fields, callback) { - db.getObjectsFields(groups.map(function (group) { - return 'group:' + group; - }), fields, callback); - }; - - Groups.setGroupField = function (groupName, field, value, callback) { - db.setObjectField('group:' + groupName, field, value, function (err) { - if (err) { - return callback(err); + next(null, results.members); + }, + ], callback); +}; + +Groups.escapeGroupData = function (group) { + if (group) { + group.nameEncoded = encodeURIComponent(group.name); + group.displayName = validator.escape(String(group.name)); + group.description = validator.escape(String(group.description || '')); + group.userTitle = validator.escape(String(group.userTitle || '')) || group.displayName; + } +}; + +Groups.getByGroupslug = function (slug, options, callback) { + async.waterfall([ + function (next) { + db.getObjectField('groupslug:groupname', slug, next); + }, + function (groupName, next) { + if (!groupName) { + return next(new Error('[[error:no-group]]')); } - plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' }); - callback(); - }); - }; + Groups.get(groupName, options, next); + }, + ], callback); +}; - Groups.isPrivate = function (groupName, callback) { - db.getObjectField('group:' + groupName, 'private', function (err, isPrivate) { - if (err) { - return callback(err); - } +Groups.getGroupNameByGroupSlug = function (slug, callback) { + db.getObjectField('groupslug:groupname', slug, callback); +}; - callback(null, parseInt(isPrivate, 10) !== 0); - }); - }; +Groups.isPrivate = function (groupName, callback) { + async.waterfall([ + function (next) { + db.getObjectField('group:' + groupName, 'private', next); + }, + function (isPrivate, next) { + next(null, parseInt(isPrivate, 10) !== 0); + }, + ], callback); +}; - Groups.isHidden = function (groupName, callback) { - db.getObjectField('group:' + groupName, 'hidden', function (err, isHidden) { - if (err) { - return callback(err); - } +Groups.isHidden = function (groupName, callback) { + async.waterfall([ + function (next) { + db.getObjectField('group:' + groupName, 'hidden', next); + }, + function (isHidden, next) { + next(null, parseInt(isHidden, 10) === 1); + }, + ], callback); +}; - callback(null, parseInt(isHidden, 10) === 1); +Groups.exists = function (name, callback) { + if (Array.isArray(name)) { + var slugs = name.map(function (groupName) { + return utils.slugify(groupName); }); - }; - - Groups.exists = function (name, callback) { - if (Array.isArray(name)) { - var slugs = name.map(function (groupName) { - return utils.slugify(groupName); - }); - async.parallel([ - function (next) { - next(null, slugs.map(function (slug) { - return ephemeralGroups.indexOf(slug) !== -1; - })); - }, - async.apply(db.isSortedSetMembers, 'groups:createtime', name), - ], function (err, results) { - if (err) { - return callback(err); - } - callback(null, name.map(function (n, index) { - return results[0][index] || results[1][index]; - })); - }); - } else { - var slug = utils.slugify(name); - async.parallel([ - function (next) { - next(null, ephemeralGroups.indexOf(slug) !== -1); - }, - async.apply(db.isSortedSetMember, 'groups:createtime', name), - ], function (err, results) { - callback(err, !err ? (results[0] || results[1]) : null); - }); - } - }; - - Groups.existsBySlug = function (slug, callback) { - if (Array.isArray(slug)) { - db.isObjectFields('groupslug:groupname', slug, callback); - } else { - db.isObjectField('groupslug:groupname', slug, callback); - } - }; - - Groups.getLatestMemberPosts = function (groupName, max, uid, callback) { - async.waterfall([ + async.parallel([ function (next) { - Groups.getMembers(groupName, 0, -1, next); - }, - function (uids, next) { - if (!Array.isArray(uids) || !uids.length) { - return callback(null, []); - } - var keys = uids.map(function (uid) { - return 'uid:' + uid + ':posts'; - }); - db.getSortedSetRevRange(keys, 0, max - 1, next); - }, - function (pids, next) { - privileges.posts.filter('read', pids, uid, next); - }, - function (pids, next) { - posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next); + next(null, slugs.map(function (slug) { + return Groups.ephemeralGroups.indexOf(slug) !== -1; + })); }, - ], callback); - }; - - Groups.getGroupData = function (groupName, callback) { - Groups.getGroupsData([groupName], function (err, groupsData) { - callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null); - }); - }; - - Groups.getGroupsData = function (groupNames, callback) { - if (!Array.isArray(groupNames) || !groupNames.length) { - return callback(null, []); - } - - var keys = groupNames.map(function (groupName) { - return 'group:' + groupName; - }); - - var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) { - if (ephemeralGroups.indexOf(cur) !== -1) { - memo.push(idx); - } - return memo; - }, []); - - db.getObjects(keys, function (err, groupData) { + async.apply(db.isSortedSetMembers, 'groups:createtime', name), + ], function (err, results) { if (err) { return callback(err); } - - if (ephemeralIdx.length) { - ephemeralIdx.forEach(function (idx) { - groupData[idx] = internals.getEphemeralGroup(groupNames[idx]); - }); - } - - groupData.forEach(function (group) { - if (group) { - Groups.escapeGroupData(group); - group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true; - group.labelColor = validator.escape(String(group.labelColor || '#000000')); - group.icon = validator.escape(String(group.icon || '')); - group.createtimeISO = utils.toISOString(group.createtime); - group.hidden = parseInt(group.hidden, 10) === 1; - group.system = parseInt(group.system, 10) === 1; - group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10); - group.disableJoinRequests = parseInt(group.disableJoinRequests, 10) === 1; - - group['cover:url'] = group['cover:url'] || require('./coverPhoto').getDefaultGroupCover(group.name); - group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url']; - group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%')); - } - }); - - plugins.fireHook('filter:groups.get', { groups: groupData }, function (err, data) { - callback(err, data ? data.groups : null); - }); + callback(null, name.map(function (n, index) { + return results[0][index] || results[1][index]; + })); }); - }; - - Groups.getUserGroups = function (uids, callback) { - Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback); - }; - - Groups.getUserGroupsFromSet = function (set, uids, callback) { - async.waterfall([ + } else { + var slug = utils.slugify(name); + async.parallel([ function (next) { - db.getSortedSetRevRange(set, 0, -1, next); - }, - function (groupNames, next) { - var groupSets = groupNames.map(function (name) { - return 'group:' + name + ':members'; - }); - - async.map(uids, function (uid, next) { - db.isMemberOfSortedSets(groupSets, uid, function (err, isMembers) { - if (err) { - return next(err); - } - - var memberOf = []; - isMembers.forEach(function (isMember, index) { - if (isMember) { - memberOf.push(groupNames[index]); - } - }); - - Groups.getGroupsData(memberOf, next); - }); - }, next); + next(null, Groups.ephemeralGroups.indexOf(slug) !== -1); }, - ], callback); - }; -}(module.exports)); + async.apply(db.isSortedSetMember, 'groups:createtime', name), + ], function (err, results) { + callback(err, !err ? (results[0] || results[1]) : null); + }); + } +}; + +Groups.existsBySlug = function (slug, callback) { + if (Array.isArray(slug)) { + db.isObjectFields('groupslug:groupname', slug, callback); + } else { + db.isObjectField('groupslug:groupname', slug, callback); + } +}; diff --git a/src/groups/data.js b/src/groups/data.js new file mode 100644 index 0000000000..24e7f99a02 --- /dev/null +++ b/src/groups/data.js @@ -0,0 +1,93 @@ +'use strict'; + +var async = require('async'); +var validator = require('validator'); + +var db = require('../database'); +var plugins = require('../plugins'); +var utils = require('../../public/src/utils'); + +module.exports = function (Groups) { + Groups.getGroupsData = function (groupNames, callback) { + if (!Array.isArray(groupNames) || !groupNames.length) { + return callback(null, []); + } + + var keys = groupNames.map(function (groupName) { + return 'group:' + groupName; + }); + + var ephemeralIdx = groupNames.reduce(function (memo, cur, idx) { + if (Groups.ephemeralGroups.indexOf(cur) !== -1) { + memo.push(idx); + } + return memo; + }, []); + + async.waterfall([ + function (next) { + db.getObjects(keys, next); + }, + function (groupData, next) { + if (ephemeralIdx.length) { + ephemeralIdx.forEach(function (idx) { + groupData[idx] = Groups.getEphemeralGroup(groupNames[idx]); + }); + } + + groupData.forEach(function (group) { + if (group) { + Groups.escapeGroupData(group); + group.userTitleEnabled = group.userTitleEnabled ? parseInt(group.userTitleEnabled, 10) === 1 : true; + group.labelColor = validator.escape(String(group.labelColor || '#000000')); + group.icon = validator.escape(String(group.icon || '')); + group.createtimeISO = utils.toISOString(group.createtime); + group.hidden = parseInt(group.hidden, 10) === 1; + group.system = parseInt(group.system, 10) === 1; + group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10); + group.disableJoinRequests = parseInt(group.disableJoinRequests, 10) === 1; + + group['cover:url'] = group['cover:url'] || require('../coverPhoto').getDefaultGroupCover(group.name); + group['cover:thumb:url'] = group['cover:thumb:url'] || group['cover:url']; + group['cover:position'] = validator.escape(String(group['cover:position'] || '50% 50%')); + } + }); + + plugins.fireHook('filter:groups.get', { groups: groupData }, next); + }, + function (results, next) { + next(null, results.groups); + }, + ], callback); + }; + + Groups.getGroupData = function (groupName, callback) { + Groups.getGroupsData([groupName], function (err, groupsData) { + callback(err, Array.isArray(groupsData) && groupsData[0] ? groupsData[0] : null); + }); + }; + + Groups.getGroupFields = function (groupName, fields, callback) { + Groups.getMultipleGroupFields([groupName], fields, function (err, groups) { + callback(err, groups ? groups[0] : null); + }); + }; + + Groups.getMultipleGroupFields = function (groups, fields, callback) { + db.getObjectsFields(groups.map(function (group) { + return 'group:' + group; + }), fields, callback); + }; + + Groups.setGroupField = function (groupName, field, value, callback) { + async.waterfall([ + function (next) { + db.setObjectField('group:' + groupName, field, value, next); + }, + function (next) { + plugins.fireHook('action:group.set', { field: field, value: value, type: 'set' }); + next(); + }, + ], callback); + }; +}; diff --git a/src/groups/membership.js b/src/groups/membership.js index 31a32da17c..6bcafd2343 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -413,32 +413,33 @@ module.exports = function (Groups) { }; Groups.getMemberCount = function (groupName, callback) { - db.getObjectField('group:' + groupName, 'memberCount', function (err, count) { - if (err) { - return callback(err); - } - callback(null, parseInt(count, 10)); - }); + async.waterfall([ + function (next) { + db.getObjectField('group:' + groupName, 'memberCount', next); + }, + function (count, next) { + next(null, parseInt(count, 10)); + }, + ], callback); }; Groups.isMemberOfGroupList = function (uid, groupListKey, callback) { - db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) { - if (err) { - return callback(err); - } - groupNames = Groups.internals.removeEphemeralGroups(groupNames); - if (groupNames.length === 0) { - return callback(null, false); - } - - Groups.isMemberOfGroups(uid, groupNames, function (err, isMembers) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next); + }, + function (groupNames, next) { + groupNames = Groups.removeEphemeralGroups(groupNames); + if (groupNames.length === 0) { + return callback(null, false); } - callback(null, isMembers.indexOf(true) !== -1); - }); - }); + Groups.isMemberOfGroups(uid, groupNames, next); + }, + function (isMembers, next) { + next(null, isMembers.indexOf(true) !== -1); + }, + ], callback); }; Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) { @@ -446,19 +447,20 @@ module.exports = function (Groups) { return 'group:' + groupName + ':members'; }); - db.getSortedSetsMembers(sets, function (err, members) { - if (err) { - return callback(err); - } - - var uniqueGroups = _.unique(_.flatten(members)); - uniqueGroups = Groups.internals.removeEphemeralGroups(uniqueGroups); - - Groups.isMemberOfGroups(uid, uniqueGroups, function (err, isMembers) { - if (err) { - return callback(err); - } + var uniqueGroups; + var members; + async.waterfall([ + function (next) { + db.getSortedSetsMembers(sets, next); + }, + function (_members, next) { + members = _members; + uniqueGroups = _.unique(_.flatten(members)); + uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups); + Groups.isMemberOfGroups(uid, uniqueGroups, next); + }, + function (isMembers, next) { var map = {}; uniqueGroups.forEach(function (groupName, index) { @@ -474,62 +476,63 @@ module.exports = function (Groups) { return false; }); - callback(null, result); - }); - }); + next(null, result); + }, + ], callback); }; Groups.isMembersOfGroupList = function (uids, groupListKey, callback) { - db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, function (err, groupNames) { - if (err) { - return callback(err); - } + var groupNames; + var results = []; + uids.forEach(function () { + results.push(false); + }); - var results = []; - uids.forEach(function () { - results.push(false); - }); + async.waterfall([ + function (next) { + db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next); + }, + function (_groupNames, next) { + groupNames = Groups.removeEphemeralGroups(_groupNames); - groupNames = Groups.internals.removeEphemeralGroups(groupNames); - if (groupNames.length === 0) { - return callback(null, results); - } + if (groupNames.length === 0) { + return callback(null, results); + } - async.each(groupNames, function (groupName, next) { - Groups.isMembers(uids, groupName, function (err, isMembers) { - if (err) { - return next(err); - } + async.map(groupNames, function (groupName, next) { + Groups.isMembers(uids, groupName, next); + }, next); + }, + function (isGroupMembers, next) { + isGroupMembers.forEach(function (isMembers) { results.forEach(function (isMember, index) { if (!isMember && isMembers[index]) { results[index] = true; } }); - next(); }); - }, function (err) { - callback(err, results); - }); - }); + next(null, results); + }, + ], callback); }; Groups.isInvited = function (uid, groupName, callback) { if (!uid) { - return callback(null, false); + return setImmediate(callback, null, false); } db.isSetMember('group:' + groupName + ':invited', uid, callback); }; Groups.isPending = function (uid, groupName, callback) { if (!uid) { - return callback(null, false); + return setImmediate(callback, null, false); } db.isSetMember('group:' + groupName + ':pending', uid, callback); }; Groups.getPending = function (groupName, callback) { if (!groupName) { - return callback(null, []); + return setImmediate(callback, null, []); } db.getSetMembers('group:' + groupName + ':pending', callback); }; diff --git a/src/groups/posts.js b/src/groups/posts.js new file mode 100644 index 0000000000..6f4d65520d --- /dev/null +++ b/src/groups/posts.js @@ -0,0 +1,32 @@ +'use strict'; + +var async = require('async'); + +var db = require('../database'); +var privileges = require('../privileges'); +var posts = require('../posts'); + +module.exports = function (Groups) { + Groups.getLatestMemberPosts = function (groupName, max, uid, callback) { + async.waterfall([ + function (next) { + Groups.getMembers(groupName, 0, -1, next); + }, + function (uids, next) { + if (!Array.isArray(uids) || !uids.length) { + return callback(null, []); + } + var keys = uids.map(function (uid) { + return 'uid:' + uid + ':posts'; + }); + db.getSortedSetRevRange(keys, 0, max - 1, next); + }, + function (pids, next) { + privileges.posts.filter('read', pids, uid, next); + }, + function (pids, next) { + posts.getPostSummaryByPids(pids, uid, { stripTags: false }, next); + }, + ], callback); + }; +}; diff --git a/src/groups/search.js b/src/groups/search.js index 3c580b5e8d..62df79696c 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -16,7 +16,7 @@ module.exports = function (Groups) { async.apply(db.getObjectValues, 'groupslug:groupname'), function (groupNames, next) { // Ephemeral groups and the registered-users groups are searchable - groupNames = Groups.getEphemeralGroups().concat(groupNames).concat('registered-users'); + groupNames = Groups.ephemeralGroups.concat(groupNames).concat('registered-users'); groupNames = groupNames.filter(function (name) { return name.toLowerCase().indexOf(query) !== -1 && name !== 'administrators' && !Groups.isPrivilegeGroup(name); }); diff --git a/src/groups/user.js b/src/groups/user.js new file mode 100644 index 0000000000..9a68478ade --- /dev/null +++ b/src/groups/user.js @@ -0,0 +1,50 @@ +'use strict'; + +var async = require('async'); + +var db = require('../database'); +var user = require('../user'); + +module.exports = function (Groups) { + Groups.getUsersFromSet = function (set, callback) { + async.waterfall([ + function (next) { + db.getSetMembers(set, next); + }, + function (uids, next) { + user.getUsersData(uids, next); + }, + ], callback); + }; + + Groups.getUserGroups = function (uids, callback) { + Groups.getUserGroupsFromSet('groups:visible:createtime', uids, callback); + }; + + Groups.getUserGroupsFromSet = function (set, uids, callback) { + async.waterfall([ + function (next) { + db.getSortedSetRevRange(set, 0, -1, next); + }, + function (groupNames, next) { + async.map(uids, function (uid, next) { + async.waterfall([ + function (next) { + Groups.isMemberOfGroups(uid, groupNames, next); + }, + function (isMembers, next) { + var memberOf = []; + isMembers.forEach(function (isMember, index) { + if (isMember) { + memberOf.push(groupNames[index]); + } + }); + + Groups.getGroupsData(memberOf, next); + }, + ], next); + }, next); + }, + ], callback); + }; +}; diff --git a/src/privileges/categories.js b/src/privileges/categories.js index ff3d16148f..d0bde8ced3 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -96,7 +96,7 @@ module.exports = function (privileges) { return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1; }); - groupNames = groups.getEphemeralGroups().concat(groupNames); + groupNames = groups.ephemeralGroups.concat(groupNames); var registeredUsersIndex = groupNames.indexOf('registered-users'); if (registeredUsersIndex !== -1) { groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]); From 3e396a8553353f3d947769dad4749156cf414a0d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 1 Mar 2017 23:19:34 +0300 Subject: [PATCH 03/87] sitemap refactor --- src/controllers/sitemap.js | 87 ++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/src/controllers/sitemap.js b/src/controllers/sitemap.js index 453aed4f6e..c87b1565a9 100644 --- a/src/controllers/sitemap.js +++ b/src/controllers/sitemap.js @@ -1,68 +1,61 @@ 'use strict'; +var async = require('async'); + var sitemap = require('../sitemap'); var meta = require('../meta'); -var sitemapController = {}; -sitemapController.render = function (req, res, next) { - sitemap.render(function (err, tplData) { - if (err) { - return next(err); - } +var sitemapController = module.exports; - req.app.render('sitemap', tplData, function (err, xml) { - if (err) { - return next(err); - } +sitemapController.render = function (req, res, next) { + async.waterfall([ + function (next) { + sitemap.render(next); + }, + function (tplData, next) { + req.app.render('sitemap', tplData, next); + }, + function (xml) { res.header('Content-Type', 'application/xml'); res.send(xml); - }); - }); + }, + ], next); }; sitemapController.getPages = function (req, res, next) { - if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { - return next(); - } - - sitemap.getPages(function (err, xml) { - if (err) { - return next(err); - } - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); + sendSitemap(function (callback) { + sitemap.getPages(callback); + }, res, next); }; sitemapController.getCategories = function (req, res, next) { - if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { - return next(); - } - - sitemap.getCategories(function (err, xml) { - if (err) { - return next(err); - } - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); + sendSitemap(function (callback) { + sitemap.getCategories(callback); + }, res, next); }; sitemapController.getTopicPage = function (req, res, next) { + sendSitemap(function (callback) { + sitemap.getTopicPage(parseInt(req.params[0], 10), callback); + }, res, next); +}; + +function sendSitemap(method, res, callback) { if (parseInt(meta.config['feeds:disableSitemap'], 10) === 1) { - return next(); + return callback(); } + async.waterfall([ + function (next) { + method(next); + }, + function (xml) { + if (!xml) { + return callback(); + } - sitemap.getTopicPage(parseInt(req.params[0], 10), function (err, xml) { - if (err) { - return next(err); - } else if (!xml) { - return next(); - } - - res.header('Content-Type', 'application/xml'); - res.send(xml); - }); -}; + res.header('Content-Type', 'application/xml'); + res.send(xml); + }, + ], callback); +} -module.exports = sitemapController; From 566ff95d3cc5f62e3eee586bf4c957af869666de Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 1 Mar 2017 23:22:17 +0300 Subject: [PATCH 04/87] call methods directly --- src/controllers/sitemap.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/controllers/sitemap.js b/src/controllers/sitemap.js index c87b1565a9..846c6facb2 100644 --- a/src/controllers/sitemap.js +++ b/src/controllers/sitemap.js @@ -23,15 +23,11 @@ sitemapController.render = function (req, res, next) { }; sitemapController.getPages = function (req, res, next) { - sendSitemap(function (callback) { - sitemap.getPages(callback); - }, res, next); + sendSitemap(sitemap.getPages, res, next); }; sitemapController.getCategories = function (req, res, next) { - sendSitemap(function (callback) { - sitemap.getCategories(callback); - }, res, next); + sendSitemap(sitemap.getCategories, res, next); }; sitemapController.getTopicPage = function (req, res, next) { From 218803614867f63aa6ef87fa32bd27818c69c64e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 00:14:30 +0300 Subject: [PATCH 05/87] controllers/users refactor --- src/controllers/users.js | 270 +++++++++++++++++++-------------------- src/middleware/index.js | 30 +++-- 2 files changed, 150 insertions(+), 150 deletions(-) diff --git a/src/controllers/users.js b/src/controllers/users.js index ae3643bb8d..45cb5f1e21 100644 --- a/src/controllers/users.js +++ b/src/controllers/users.js @@ -8,9 +8,7 @@ var pagination = require('../pagination'); var db = require('../database'); var helpers = require('./helpers'); - -var usersController = {}; - +var usersController = module.exports; usersController.index = function (req, res, next) { var section = req.query.section || 'joindate'; @@ -33,62 +31,65 @@ usersController.index = function (req, res, next) { }; usersController.search = function (req, res, next) { - async.parallel({ - search: function (next) { - user.search({ - query: req.query.term, - searchBy: req.query.searchBy || 'username', - page: req.query.page || 1, - sortBy: req.query.sortBy, - onlineOnly: req.query.onlineOnly === 'true', - bannedOnly: req.query.bannedOnly === 'true', - flaggedOnly: req.query.flaggedOnly === 'true', + async.waterfall([ + function (next) { + async.parallel({ + search: function (next) { + user.search({ + query: req.query.term, + searchBy: req.query.searchBy || 'username', + page: req.query.page || 1, + sortBy: req.query.sortBy, + onlineOnly: req.query.onlineOnly === 'true', + bannedOnly: req.query.bannedOnly === 'true', + flaggedOnly: req.query.flaggedOnly === 'true', + }, next); + }, + isAdminOrGlobalMod: function (next) { + user.isAdminOrGlobalMod(req.uid, next); + }, }, next); }, - isAdminOrGlobalMod: function (next) { - user.isAdminOrGlobalMod(req.uid, next); + function (results, next) { + var section = req.query.section || 'joindate'; + + results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod; + results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query); + results.search['section_' + section] = true; + render(req, res, results.search, next); }, - }, function (err, results) { - if (err) { - return next(err); - } - - var section = req.query.section || 'joindate'; - - results.search.isAdminOrGlobalMod = results.isAdminOrGlobalMod; - results.search.pagination = pagination.create(req.query.page, results.search.pageCount, req.query); - results.search['section_' + section] = true; - render(req, res, results.search, next); - }); + ], next); }; usersController.getOnlineUsers = function (req, res, next) { - async.parallel({ - users: function (next) { - usersController.getUsers('users:online', req.uid, req.query, next); - }, - guests: function (next) { - require('../socket.io/admin/rooms').getTotalGuestCount(next); + async.waterfall([ + function (next) { + async.parallel({ + users: function (next) { + usersController.getUsers('users:online', req.uid, req.query, next); + }, + guests: function (next) { + require('../socket.io/admin/rooms').getTotalGuestCount(next); + }, + }, next); }, - }, function (err, results) { - if (err) { - return next(err); - } - var userData = results.users; - var hiddenCount = 0; - if (!userData.isAdminOrGlobalMod) { - userData.users = userData.users.filter(function (user) { - if (user && user.status === 'offline') { - hiddenCount += 1; - } - return user && user.status !== 'offline'; - }); - } + function (results, next) { + var userData = results.users; + var hiddenCount = 0; + if (!userData.isAdminOrGlobalMod) { + userData.users = userData.users.filter(function (user) { + if (user && user.status === 'offline') { + hiddenCount += 1; + } + return user && user.status !== 'offline'; + }); + } - userData.anonymousUserCount = results.guests + hiddenCount; + userData.anonymousUserCount = results.guests + hiddenCount; - render(req, res, userData, next); - }); + render(req, res, userData, next); + }, + ], next); }; usersController.getUsersSortedByPosts = function (req, res, next) { @@ -107,41 +108,36 @@ usersController.getUsersSortedByJoinDate = function (req, res, next) { }; usersController.getBannedUsers = function (req, res, next) { - usersController.getUsers('users:banned', req.uid, req.query, function (err, userData) { - if (err) { - return next(err); - } - - if (!userData.isAdminOrGlobalMod) { - return next(); - } - - render(req, res, userData, next); - }); + renderIfAdminOrGlobalMod('users:banned', req, res, next); }; usersController.getFlaggedUsers = function (req, res, next) { - usersController.getUsers('users:flags', req.uid, req.query, function (err, userData) { - if (err) { - return next(err); - } - - if (!userData.isAdminOrGlobalMod) { - return next(); - } - - render(req, res, userData, next); - }); + renderIfAdminOrGlobalMod('users:flags', req, res, next); }; -usersController.renderUsersPage = function (set, req, res, next) { - usersController.getUsers(set, req.uid, req.query, function (err, userData) { - if (err) { - return next(err); - } +function renderIfAdminOrGlobalMod(set, req, res, next) { + async.waterfall([ + function (next) { + user.isAdminOrGlobalMod(req.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return helpers.notAllowed(req, res); + } + usersController.renderUsersPage(set, req, res, next); + }, + ], next); +} - render(req, res, userData, next); - }); +usersController.renderUsersPage = function (set, req, res, next) { + async.waterfall([ + function (next) { + usersController.getUsers(set, req.uid, req.query, next); + }, + function (userData, next) { + render(req, res, userData, next); + }, + ], next); }; usersController.getUsers = function (set, uid, query, callback) { @@ -169,59 +165,62 @@ usersController.getUsers = function (set, uid, query, callback) { var start = Math.max(0, page - 1) * resultsPerPage; var stop = start + resultsPerPage - 1; - async.parallel({ - isAdminOrGlobalMod: function (next) { - user.isAdminOrGlobalMod(uid, next); + async.waterfall([ + function (next) { + async.parallel({ + isAdminOrGlobalMod: function (next) { + user.isAdminOrGlobalMod(uid, next); + }, + usersData: function (next) { + usersController.getUsersAndCount(set, uid, start, stop, next); + }, + }, next); }, - usersData: function (next) { - usersController.getUsersAndCount(set, uid, start, stop, next); + function (results, next) { + var pageCount = Math.ceil(results.usersData.count / resultsPerPage); + var userData = { + users: results.usersData.users, + pagination: pagination.create(page, pageCount, query), + userCount: results.usersData.count, + title: setToData[set].title || '[[pages:users/latest]]', + breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs), + isAdminOrGlobalMod: results.isAdminOrGlobalMod, + }; + userData['section_' + (query.section || 'joindate')] = true; + next(null, userData); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - var pageCount = Math.ceil(results.usersData.count / resultsPerPage); - var userData = { - users: results.usersData.users, - pagination: pagination.create(page, pageCount, query), - userCount: results.usersData.count, - title: setToData[set].title || '[[pages:users/latest]]', - breadcrumbs: helpers.buildBreadcrumbs(breadcrumbs), - isAdminOrGlobalMod: results.isAdminOrGlobalMod, - }; - userData['section_' + (query.section || 'joindate')] = true; - callback(null, userData); - }); + ], callback); }; usersController.getUsersAndCount = function (set, uid, start, stop, callback) { - async.parallel({ - users: function (next) { - user.getUsersFromSet(set, uid, start, stop, next); + async.waterfall([ + function (next) { + async.parallel({ + users: function (next) { + user.getUsersFromSet(set, uid, start, stop, next); + }, + count: function (next) { + if (set === 'users:online') { + var now = Date.now(); + db.sortedSetCount('users:online', now - 300000, '+inf', next); + } else if (set === 'users:banned') { + db.sortedSetCard('users:banned', next); + } else if (set === 'users:flags') { + db.sortedSetCard('users:flags', next); + } else { + db.getObjectField('global', 'userCount', next); + } + }, + }, next); }, - count: function (next) { - if (set === 'users:online') { - var now = Date.now(); - db.sortedSetCount('users:online', now - 300000, '+inf', next); - } else if (set === 'users:banned') { - db.sortedSetCard('users:banned', next); - } else if (set === 'users:flags') { - db.sortedSetCard('users:flags', next); - } else { - db.getObjectField('global', 'userCount', next); - } + function (results, next) { + results.users = results.users.filter(function (user) { + return user && parseInt(user.uid, 10); + }); + + next(null, results); }, - }, function (err, results) { - if (err) { - return callback(err); - } - results.users = results.users.filter(function (user) { - return user && parseInt(user.uid, 10); - }); - - callback(null, results); - }); + ], callback); }; function render(req, res, data, next) { @@ -232,16 +231,15 @@ function render(req, res, data, next) { data.adminInviteOnly = registrationType === 'admin-invite-only'; data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1; - user.getInvitesNumber(req.uid, function (err, numInvites) { - if (err) { - return next(err); - } - - res.append('X-Total-Count', data.userCount); - data.invites = numInvites; + async.waterfall([ + function (next) { + user.getInvitesNumber(req.uid, next); + }, + function (numInvites) { + res.append('X-Total-Count', data.userCount); + data.invites = numInvites; - res.render('users', data); - }); + res.render('users', data); + }, + ], next); } - -module.exports = usersController; diff --git a/src/middleware/index.js b/src/middleware/index.js index ea8e94e518..faf2ad832d 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -53,22 +53,24 @@ middleware.ensureSelfOrGlobalPrivilege = function (req, res, next) { The "self" part of this middleware hinges on you having used middleware.exposeUid prior to invoking this middleware. */ - if (req.user) { - if (req.user.uid === res.locals.uid) { - return next(); - } + async.waterfall([ + function (next) { + if (!req.uid) { + return setImmediate(next, null, false); + } - user.isAdminOrGlobalMod(req.uid, function (err, ok) { - if (err) { - return next(err); - } else if (ok) { - return next(); + if (req.uid === parseInt(res.locals.uid, 10)) { + return setImmediate(next, null, true); } - controllers.helpers.notAllowed(req, res); - }); - } else { - controllers.helpers.notAllowed(req, res); - } + user.isAdminOrGlobalMod(req.uid, next); + }, + function (isAdminOrGlobalMod, next) { + if (!isAdminOrGlobalMod) { + return controllers.helpers.notAllowed(req, res); + } + next(); + }, + ], next); }; middleware.pageView = function (req, res, next) { From 284485c885b413771ae866b00fffd4d157306726 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 10:51:05 +0300 Subject: [PATCH 06/87] check data.config --- public/src/modules/helpers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/src/modules/helpers.js b/public/src/modules/helpers.js index 7e93618cc7..3f02a86758 100644 --- a/public/src/modules/helpers.js +++ b/public/src/modules/helpers.js @@ -16,9 +16,9 @@ return false; } var properties = item.properties; - + var loggedIn = data.config ? data.config.loggedIn : false; if (properties) { - if ((properties.loggedIn && !data.config.loggedIn) || + if ((properties.loggedIn && !loggedIn) || (properties.globalMod && !data.isGlobalMod && !data.isAdmin) || (properties.adminOnly && !data.isAdmin) || (properties.searchInstalled && !data.searchEnabled)) { @@ -26,11 +26,11 @@ } } - if (item.route.match('/users') && data.privateUserInfo && !data.config.loggedIn) { + if (item.route.match('/users') && data.privateUserInfo && !loggedIn) { return false; } - if (item.route.match('/tags') && data.privateTagListing && !data.config.loggedIn) { + if (item.route.match('/tags') && data.privateTagListing && !loggedIn) { return false; } From bc8d297377c4fd6cf53afa5c3a1fdd8d44350d76 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 14:57:33 +0300 Subject: [PATCH 07/87] move out error and 404 controllers --- src/controllers/404.js | 47 +++++++++++ src/controllers/errors.js | 63 +++++++++++++++ src/controllers/index.js | 160 +++++++------------------------------- src/routes/index.js | 6 +- 4 files changed, 143 insertions(+), 133 deletions(-) create mode 100644 src/controllers/404.js create mode 100644 src/controllers/errors.js diff --git a/src/controllers/404.js b/src/controllers/404.js new file mode 100644 index 0000000000..bc4e2e1d00 --- /dev/null +++ b/src/controllers/404.js @@ -0,0 +1,47 @@ +'use strict'; + +var nconf = require('nconf'); +var winston = require('winston'); +var validator = require('validator'); + +var meta = require('../meta'); +var plugins = require('../plugins'); + +exports.handle404 = function (req, res) { + var relativePath = nconf.get('relative_path'); + var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js'); + + if (plugins.hasListeners('action:meta.override404')) { + return plugins.fireHook('action:meta.override404', { + req: req, + res: res, + error: {}, + }); + } + + if (isClientScript.test(req.url)) { + res.type('text/javascript').status(200).send(''); + } else if (req.path.startsWith(relativePath + '/assets/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') { + meta.errors.log404(req.path || ''); + res.sendStatus(404); + } else if (req.accepts('html')) { + if (process.env.NODE_ENV === 'development') { + winston.warn('Route requested but not found: ' + req.url); + } + + meta.errors.log404(req.path.replace(/^\/api/, '') || ''); + res.status(404); + + var path = String(req.path || ''); + + if (res.locals.isAPI) { + return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' }); + } + var middleware = require('../middleware'); + middleware.buildHeader(req, res, function () { + res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' }); + }); + } else { + res.status(404).type('txt').send('Not found'); + } +}; diff --git a/src/controllers/errors.js b/src/controllers/errors.js new file mode 100644 index 0000000000..6ab0dc6471 --- /dev/null +++ b/src/controllers/errors.js @@ -0,0 +1,63 @@ +'use strict'; + +var nconf = require('nconf'); +var winston = require('winston'); +var validator = require('validator'); + +exports.handleURIErrors = function (err, req, res, next) { + // Handle cases where malformed URIs are passed in + if (err instanceof URIError) { + var tidMatch = req.path.match(/^\/topic\/(\d+)\//); + var cidMatch = req.path.match(/^\/category\/(\d+)\//); + + if (tidMatch) { + res.redirect(nconf.get('relative_path') + tidMatch[0]); + } else if (cidMatch) { + res.redirect(nconf.get('relative_path') + cidMatch[0]); + } else { + winston.warn('[controller] Bad request: ' + req.path); + if (res.locals.isAPI) { + res.status(400).json({ + error: '[[global:400.title]]', + }); + } else { + var middleware = require('../middleware'); + middleware.buildHeader(req, res, function () { + res.render('400', { error: validator.escape(String(err.message)) }); + }); + } + } + } else { + next(err); + } +}; + +// this needs to have four arguments or express treats it as `(req, res, next)` +// don't remove `next`! +exports.handleErrors = function (err, req, res, next) { // eslint-disable-line no-unused-vars + switch (err.code) { + case 'EBADCSRFTOKEN': + winston.error(req.path + '\n', err.message); + return res.sendStatus(403); + case 'blacklisted-ip': + return res.status(403).type('text/plain').send(err.message); + } + + if (parseInt(err.status, 10) === 302 && err.path) { + return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path); + } + + winston.error(req.path + '\n', err.stack); + + res.status(err.status || 500); + + var path = String(req.path || ''); + if (res.locals.isAPI) { + res.json({ path: validator.escape(path), error: err.message }); + } else { + var middleware = require('../middleware'); + middleware.buildHeader(req, res, function () { + res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) }); + }); + } +}; diff --git a/src/controllers/index.js b/src/controllers/index.js index 59f5527ef4..00059dd825 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -3,34 +3,34 @@ var async = require('async'); var nconf = require('nconf'); var validator = require('validator'); -var winston = require('winston'); var meta = require('../meta'); var user = require('../user'); var plugins = require('../plugins'); var helpers = require('./helpers'); -var Controllers = { - topics: require('./topics'), - posts: require('./posts'), - categories: require('./categories'), - category: require('./category'), - unread: require('./unread'), - recent: require('./recent'), - popular: require('./popular'), - tags: require('./tags'), - search: require('./search'), - users: require('./users'), - groups: require('./groups'), - accounts: require('./accounts'), - authentication: require('./authentication'), - api: require('./api'), - admin: require('./admin'), - globalMods: require('./globalmods'), - mods: require('./mods'), - sitemap: require('./sitemap'), -}; - +var Controllers = module.exports; + +Controllers.topics = require('./topics'); +Controllers.posts = require('./posts'); +Controllers.categories = require('./categories'); +Controllers.category = require('./category'); +Controllers.unread = require('./unread'); +Controllers.recent = require('./recent'); +Controllers.popular = require('./popular'); +Controllers.tags = require('./tags'); +Controllers.search = require('./search'); +Controllers.users = require('./users'); +Controllers.groups = require('./groups'); +Controllers.accounts = require('./accounts'); +Controllers.authentication = require('./authentication'); +Controllers.api = require('./api'); +Controllers.admin = require('./admin'); +Controllers.globalMods = require('./globalmods'); +Controllers.mods = require('./mods'); +Controllers.sitemap = require('./sitemap'); +Controllers['404'] = require('./404'); +Controllers.errors = require('./errors'); Controllers.home = function (req, res, next) { var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories'; @@ -321,19 +321,18 @@ Controllers.manifest = function (req, res) { res.status(200).json(manifest); }; -Controllers.outgoing = function (req, res) { +Controllers.outgoing = function (req, res, next) { var url = req.query.url || ''; - var data = { + + if (!url) { + return next(); + } + + res.render('outgoing', { outgoing: validator.escape(String(url)), title: meta.config.title, breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[notifications:outgoing_link]]' }]), - }; - - if (url) { - res.render('outgoing', data); - } else { - res.status(404).redirect(nconf.get('relative_path') + '/404'); - } + }); }; Controllers.termsOfUse = function (req, res, next) { @@ -346,102 +345,3 @@ Controllers.termsOfUse = function (req, res, next) { Controllers.ping = function (req, res) { res.status(200).send(req.path === '/sping' ? 'healthy' : '200'); }; - -Controllers.handle404 = function (req, res) { - var relativePath = nconf.get('relative_path'); - var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js'); - - if (plugins.hasListeners('action:meta.override404')) { - return plugins.fireHook('action:meta.override404', { - req: req, - res: res, - error: {}, - }); - } - - if (isClientScript.test(req.url)) { - res.type('text/javascript').status(200).send(''); - } else if (req.path.startsWith(relativePath + '/assets/uploads') || (req.get('accept') && req.get('accept').indexOf('text/html') === -1) || req.path === '/favicon.ico') { - meta.errors.log404(req.path || ''); - res.sendStatus(404); - } else if (req.accepts('html')) { - if (process.env.NODE_ENV === 'development') { - winston.warn('Route requested but not found: ' + req.url); - } - - meta.errors.log404(req.path.replace(/^\/api/, '') || ''); - res.status(404); - - var path = String(req.path || ''); - - if (res.locals.isAPI) { - return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' }); - } - var middleware = require('../middleware'); - middleware.buildHeader(req, res, function () { - res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' }); - }); - } else { - res.status(404).type('txt').send('Not found'); - } -}; - -Controllers.handleURIErrors = function (err, req, res, next) { - // Handle cases where malformed URIs are passed in - if (err instanceof URIError) { - var tidMatch = req.path.match(/^\/topic\/(\d+)\//); - var cidMatch = req.path.match(/^\/category\/(\d+)\//); - - if (tidMatch) { - res.redirect(nconf.get('relative_path') + tidMatch[0]); - } else if (cidMatch) { - res.redirect(nconf.get('relative_path') + cidMatch[0]); - } else { - winston.warn('[controller] Bad request: ' + req.path); - if (res.locals.isAPI) { - res.status(400).json({ - error: '[[global:400.title]]', - }); - } else { - var middleware = require('../middleware'); - middleware.buildHeader(req, res, function () { - res.render('400', { error: validator.escape(String(err.message)) }); - }); - } - } - } else { - next(err); - } -}; - -// this needs to have four arguments or express treats it as `(req, res, next)` -// don't remove `next`! -Controllers.handleErrors = function (err, req, res, next) { // eslint-disable-line no-unused-vars - switch (err.code) { - case 'EBADCSRFTOKEN': - winston.error(req.path + '\n', err.message); - return res.sendStatus(403); - case 'blacklisted-ip': - return res.status(403).type('text/plain').send(err.message); - } - - if (parseInt(err.status, 10) === 302 && err.path) { - return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path); - } - - winston.error(req.path + '\n', err.stack); - - res.status(err.status || 500); - - var path = String(req.path || ''); - if (res.locals.isAPI) { - res.json({ path: validator.escape(path), error: err.message }); - } else { - var middleware = require('../middleware'); - middleware.buildHeader(req, res, function () { - res.render('500', { path: validator.escape(path), error: validator.escape(String(err.message)) }); - }); - } -}; - -module.exports = Controllers; diff --git a/src/routes/index.js b/src/routes/index.js index 86c7960f6a..d0029798de 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -198,9 +198,9 @@ module.exports = function (app, middleware, hotswapIds) { }); app.use(relativePath + '/assets/vendor/jquery/timeago/locales', middleware.processTimeagoLocales); - app.use(controllers.handle404); - app.use(controllers.handleURIErrors); - app.use(controllers.handleErrors); + app.use(controllers['404'].handle404); + app.use(controllers.errors.handleURIErrors); + app.use(controllers.errors.handleErrors); // Add plugin routes async.series([ From b1b87d339f2d0aa01ec9c76e1362aa3141449e53 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 16:11:11 +0300 Subject: [PATCH 08/87] more refactors --- src/controllers/api.js | 102 +----- src/controllers/index.js | 1 + src/controllers/posts.js | 38 ++- src/controllers/user.js | 94 ++++++ src/routes/api.js | 10 +- src/socket.io/user.js | 8 +- src/user.js | 658 ++++++++++++++++++--------------------- src/user/admin.js | 8 +- src/user/data.js | 103 +++--- src/user/online.js | 70 +++++ 10 files changed, 558 insertions(+), 534 deletions(-) create mode 100644 src/controllers/user.js create mode 100644 src/user/online.js diff --git a/src/controllers/api.js b/src/controllers/api.js index 66392e6160..ab52da69cd 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -13,9 +13,8 @@ var privileges = require('../privileges'); var plugins = require('../plugins'); var widgets = require('../widgets'); var translator = require('../../public/src/modules/translator'); -var accountHelpers = require('../controllers/accounts/helpers'); -var apiController = {}; +var apiController = module.exports; apiController.getConfig = function (req, res, next) { var config = {}; @@ -220,92 +219,6 @@ apiController.getObject = function (req, res, next) { }); }; -apiController.getCurrentUser = function (req, res, next) { - if (!req.uid) { - return res.status(401).json('not-authorized'); - } - async.waterfall([ - function (next) { - user.getUserField(req.uid, 'userslug', next); - }, - function (userslug, next) { - accountHelpers.getUserDataByUserSlug(userslug, req.uid, next); - }, - ], function (err, userData) { - if (err) { - return next(err); - } - res.json(userData); - }); -}; - -apiController.getUserByUID = function (req, res, next) { - byType('uid', req, res, next); -}; - -apiController.getUserByUsername = function (req, res, next) { - byType('username', req, res, next); -}; - -apiController.getUserByEmail = function (req, res, next) { - byType('email', req, res, next); -}; - -function byType(type, req, res, next) { - apiController.getUserDataByField(req.uid, type, req.params[type], function (err, data) { - if (err || !data) { - return next(err); - } - res.json(data); - }); -} - -apiController.getUserDataByField = function (callerUid, field, fieldValue, callback) { - async.waterfall([ - function (next) { - if (field === 'uid') { - next(null, fieldValue); - } else if (field === 'username') { - user.getUidByUsername(fieldValue, next); - } else if (field === 'email') { - user.getUidByEmail(fieldValue, next); - } else { - next(); - } - }, - function (uid, next) { - if (!uid) { - return next(); - } - apiController.getUserDataByUID(callerUid, uid, next); - }, - ], callback); -}; - -apiController.getUserDataByUID = function (callerUid, uid, callback) { - if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) { - return callback(new Error('[[error:no-privileges]]')); - } - - if (!parseInt(uid, 10)) { - return callback(new Error('[[error:no-user]]')); - } - - async.parallel({ - userData: async.apply(user.getUserData, uid), - settings: async.apply(user.getSettings, uid), - }, function (err, results) { - if (err || !results.userData) { - return callback(err || new Error('[[error:no-user]]')); - } - - results.userData.email = results.settings.showemail ? results.userData.email : undefined; - results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined; - - callback(null, results.userData); - }); -}; - apiController.getModerators = function (req, res, next) { categories.getModerators(req.params.cid, function (err, moderators) { if (err) { @@ -314,16 +227,3 @@ apiController.getModerators = function (req, res, next) { res.json({ moderators: moderators }); }); }; - - -apiController.getRecentPosts = function (req, res, next) { - posts.getRecentPosts(req.uid, 0, 19, req.params.term, function (err, data) { - if (err) { - return next(err); - } - - res.json(data); - }); -}; - -module.exports = apiController; diff --git a/src/controllers/index.js b/src/controllers/index.js index 00059dd825..49c1c4c2c2 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -20,6 +20,7 @@ Controllers.recent = require('./recent'); Controllers.popular = require('./popular'); Controllers.tags = require('./tags'); Controllers.search = require('./search'); +Controllers.user = require('./user'); Controllers.users = require('./users'); Controllers.groups = require('./groups'); Controllers.accounts = require('./accounts'); diff --git a/src/controllers/posts.js b/src/controllers/posts.js index 90596d5b34..8afb3f5729 100644 --- a/src/controllers/posts.js +++ b/src/controllers/posts.js @@ -1,24 +1,38 @@ 'use strict'; +var async = require('async'); + var posts = require('../posts'); var helpers = require('./helpers'); -var postsController = {}; +var postsController = module.exports; -postsController.redirectToPost = function (req, res, callback) { +postsController.redirectToPost = function (req, res, next) { var pid = parseInt(req.params.pid, 10); if (!pid) { - return callback(); + return next(); } - posts.generatePostPath(pid, req.uid, function (err, path) { - if (err || !path) { - return callback(err); - } - - helpers.redirect(res, path); - }); + async.waterfall([ + function (next) { + posts.generatePostPath(pid, req.uid, next); + }, + function (path, next) { + if (!path) { + return next(); + } + helpers.redirect(res, path); + }, + ], next); }; - -module.exports = postsController; +postsController.getRecentPosts = function (req, res, next) { + async.waterfall([ + function (next) { + posts.getRecentPosts(req.uid, 0, 19, req.params.term, next); + }, + function (data) { + res.json(data); + }, + ], next); +}; diff --git a/src/controllers/user.js b/src/controllers/user.js new file mode 100644 index 0000000000..c1049adffe --- /dev/null +++ b/src/controllers/user.js @@ -0,0 +1,94 @@ +'use strict'; + +var async = require('async'); + +var user = require('../user'); +var meta = require('../meta'); +var accountHelpers = require('./accounts/helpers'); + +var userController = module.exports; + +userController.getCurrentUser = function (req, res, next) { + if (!req.uid) { + return res.status(401).json('not-authorized'); + } + async.waterfall([ + function (next) { + user.getUserField(req.uid, 'userslug', next); + }, + function (userslug, next) { + accountHelpers.getUserDataByUserSlug(userslug, req.uid, next); + }, + function (userData) { + res.json(userData); + }, + ], next); +}; + + +userController.getUserByUID = function (req, res, next) { + byType('uid', req, res, next); +}; + +userController.getUserByUsername = function (req, res, next) { + byType('username', req, res, next); +}; + +userController.getUserByEmail = function (req, res, next) { + byType('email', req, res, next); +}; + +function byType(type, req, res, next) { + userController.getUserDataByField(req.uid, type, req.params[type], function (err, data) { + if (err || !data) { + return next(err); + } + res.json(data); + }); +} + +userController.getUserDataByField = function (callerUid, field, fieldValue, callback) { + async.waterfall([ + function (next) { + if (field === 'uid') { + next(null, fieldValue); + } else if (field === 'username') { + user.getUidByUsername(fieldValue, next); + } else if (field === 'email') { + user.getUidByEmail(fieldValue, next); + } else { + next(); + } + }, + function (uid, next) { + if (!uid) { + return next(); + } + userController.getUserDataByUID(callerUid, uid, next); + }, + ], callback); +}; + +userController.getUserDataByUID = function (callerUid, uid, callback) { + if (!parseInt(callerUid, 10) && parseInt(meta.config.privateUserInfo, 10) === 1) { + return callback(new Error('[[error:no-privileges]]')); + } + + if (!parseInt(uid, 10)) { + return callback(new Error('[[error:no-user]]')); + } + + async.parallel({ + userData: async.apply(user.getUserData, uid), + settings: async.apply(user.getSettings, uid), + }, function (err, results) { + if (err || !results.userData) { + return callback(err || new Error('[[error:no-user]]')); + } + + results.userData.email = results.settings.showemail ? results.userData.email : undefined; + results.userData.fullname = results.settings.showfullname ? results.userData.fullname : undefined; + + callback(null, results.userData); + }); +}; diff --git a/src/routes/api.js b/src/routes/api.js index 16ead5c588..9b5a7f77c7 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -11,17 +11,17 @@ module.exports = function (app, middleware, controllers) { router.get('/config', middleware.applyCSRF, controllers.api.getConfig); router.get('/widgets/render', controllers.api.renderWidgets); - router.get('/me', middleware.checkGlobalPrivacySettings, controllers.api.getCurrentUser); - router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUID); - router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.api.getUserByUsername); - router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.api.getUserByEmail); + router.get('/me', middleware.checkGlobalPrivacySettings, controllers.user.getCurrentUser); + router.get('/user/uid/:uid', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUID); + router.get('/user/username/:username', middleware.checkGlobalPrivacySettings, controllers.user.getUserByUsername); + router.get('/user/email/:email', middleware.checkGlobalPrivacySettings, controllers.user.getUserByEmail); router.get('/:type/pid/:id', controllers.api.getObject); router.get('/:type/tid/:id', controllers.api.getObject); router.get('/:type/cid/:id', controllers.api.getObject); router.get('/categories/:cid/moderators', controllers.api.getModerators); - router.get('/recent/posts/:term?', controllers.api.getRecentPosts); + router.get('/recent/posts/:term?', controllers.posts.getRecentPosts); router.get('/unread/:filter?/total', middleware.authenticate, controllers.unread.unreadTotal); router.get('/topic/teaser/:topic_id', controllers.topics.teaser); router.get('/topic/pagination/:topic_id', controllers.topics.pagination); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index f7208b116a..78f696a19b 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -12,7 +12,7 @@ var meta = require('../meta'); var events = require('../events'); var emailer = require('../emailer'); var db = require('../database'); -var apiController = require('../controllers/api'); +var userController = require('../controllers/user'); var privileges = require('../privileges'); var SocketUser = {}; @@ -303,15 +303,15 @@ SocketUser.invite = function (socket, email, callback) { }; SocketUser.getUserByUID = function (socket, uid, callback) { - apiController.getUserDataByField(socket.uid, 'uid', uid, callback); + userController.getUserDataByField(socket.uid, 'uid', uid, callback); }; SocketUser.getUserByUsername = function (socket, username, callback) { - apiController.getUserDataByField(socket.uid, 'username', username, callback); + userController.getUserDataByField(socket.uid, 'username', username, callback); }; SocketUser.getUserByEmail = function (socket, email, callback) { - apiController.getUserDataByField(socket.uid, 'email', email, callback); + userController.getUserDataByField(socket.uid, 'email', email, callback); }; SocketUser.setModerationNote = function (socket, data, callback) { diff --git a/src/user.js b/src/user.js index 968417f816..48098feadf 100644 --- a/src/user.js +++ b/src/user.js @@ -6,378 +6,324 @@ var _ = require('underscore'); var groups = require('./groups'); var plugins = require('./plugins'); var db = require('./database'); -var topics = require('./topics'); var privileges = require('./privileges'); var meta = require('./meta'); -(function (User) { - User.email = require('./user/email'); - User.notifications = require('./user/notifications'); - User.reset = require('./user/reset'); - User.digest = require('./user/digest'); - - require('./user/data')(User); - require('./user/auth')(User); - require('./user/bans')(User); - require('./user/create')(User); - require('./user/posts')(User); - require('./user/topics')(User); - require('./user/categories')(User); - require('./user/follow')(User); - require('./user/profile')(User); - require('./user/admin')(User); - require('./user/delete')(User); - require('./user/settings')(User); - require('./user/search')(User); - require('./user/jobs')(User); - require('./user/picture')(User); - require('./user/approval')(User); - require('./user/invite')(User); - require('./user/password')(User); - require('./user/info')(User); - - User.updateLastOnlineTime = function (uid, callback) { - callback = callback || function () {}; - db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) { - var now = Date.now(); - if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) { - return callback(err); - } - User.setUserField(uid, 'lastonline', now, callback); - }); - }; - - User.updateOnlineUsers = function (uid, callback) { - callback = callback || function () {}; - +var User = module.exports; + +User.email = require('./user/email'); +User.notifications = require('./user/notifications'); +User.reset = require('./user/reset'); +User.digest = require('./user/digest'); + +require('./user/data')(User); +require('./user/auth')(User); +require('./user/bans')(User); +require('./user/create')(User); +require('./user/posts')(User); +require('./user/topics')(User); +require('./user/categories')(User); +require('./user/follow')(User); +require('./user/profile')(User); +require('./user/admin')(User); +require('./user/delete')(User); +require('./user/settings')(User); +require('./user/search')(User); +require('./user/jobs')(User); +require('./user/picture')(User); +require('./user/approval')(User); +require('./user/invite')(User); +require('./user/password')(User); +require('./user/info')(User); +require('./user/online')(User); + +User.getUidsFromSet = function (set, start, stop, callback) { + if (set === 'users:online') { + var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; var now = Date.now(); - async.waterfall([ - function (next) { - db.sortedSetScore('users:online', uid, next); - }, - function (userOnlineTime, next) { - if (now - parseInt(userOnlineTime, 10) < 300000) { - return callback(); - } - db.sortedSetAdd('users:online', now, uid, next); - }, - function (next) { - topics.pushUnreadCount(uid); - plugins.fireHook('action:user.online', { uid: uid, timestamp: now }); - next(); - }, - ], callback); - }; - - User.getUidsFromSet = function (set, start, stop, callback) { - if (set === 'users:online') { - var count = parseInt(stop, 10) === -1 ? stop : stop - start + 1; - var now = Date.now(); - db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - 300000, callback); - } else { - db.getSortedSetRevRange(set, start, stop, callback); - } - }; - - User.getUsersFromSet = function (set, uid, start, stop, callback) { - async.waterfall([ - function (next) { - User.getUidsFromSet(set, start, stop, next); - }, - function (uids, next) { - User.getUsers(uids, uid, next); - }, - ], callback); - }; - - User.getUsersWithFields = function (uids, fields, uid, callback) { - async.waterfall([ - function (next) { - plugins.fireHook('filter:users.addFields', { fields: fields }, next); - }, - function (data, next) { - data.fields = data.fields.filter(function (field, index, array) { - return array.indexOf(field) === index; - }); - - async.parallel({ - userData: function (next) { - User.getUsersFields(uids, data.fields, next); - }, - isAdmin: function (next) { - User.isAdministrator(uids, next); - }, - }, next); - }, - function (results, next) { - results.userData.forEach(function (user, index) { - if (user) { - user.status = User.getStatus(user); - user.administrator = results.isAdmin[index]; - user.banned = parseInt(user.banned, 10) === 1; - user.banned_until = parseInt(user['banned:expire'], 10) || 0; - user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned'; - user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1; - } - }); - plugins.fireHook('filter:userlist.get', { users: results.userData, uid: uid }, next); - }, - function (data, next) { - next(null, data.users); - }, - ], callback); - }; - - User.getUsers = function (uids, uid, callback) { - var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', - 'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; - - User.getUsersWithFields(uids, fields, uid, callback); - }; - - User.getStatus = function (userData) { - var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000; - return isOnline ? (userData.status || 'online') : 'offline'; - }; - - User.isOnline = function (uid, callback) { - if (Array.isArray(uid)) { - db.sortedSetScores('users:online', uid, function (err, lastonline) { - if (err) { - return callback(err); - } - var now = Date.now(); - var isOnline = uid.map(function (uid, index) { - return now - lastonline[index] < 300000; - }); - callback(null, isOnline); + db.getSortedSetRevRangeByScore(set, start, count, '+inf', now - 300000, callback); + } else { + db.getSortedSetRevRange(set, start, stop, callback); + } +}; + +User.getUsersFromSet = function (set, uid, start, stop, callback) { + async.waterfall([ + function (next) { + User.getUidsFromSet(set, start, stop, next); + }, + function (uids, next) { + User.getUsers(uids, uid, next); + }, + ], callback); +}; + +User.getUsersWithFields = function (uids, fields, uid, callback) { + async.waterfall([ + function (next) { + plugins.fireHook('filter:users.addFields', { fields: fields }, next); + }, + function (data, next) { + data.fields = data.fields.filter(function (field, index, array) { + return array.indexOf(field) === index; }); - } else { - db.sortedSetScore('users:online', uid, function (err, lastonline) { - if (err) { - return callback(err); + + async.parallel({ + userData: function (next) { + User.getUsersFields(uids, data.fields, next); + }, + isAdmin: function (next) { + User.isAdministrator(uids, next); + }, + }, next); + }, + function (results, next) { + results.userData.forEach(function (user, index) { + if (user) { + user.status = User.getStatus(user); + user.administrator = results.isAdmin[index]; + user.banned = parseInt(user.banned, 10) === 1; + user.banned_until = parseInt(user['banned:expire'], 10) || 0; + user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned'; + user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1; } - var isOnline = Date.now() - parseInt(lastonline, 10) < 300000; - callback(null, isOnline); }); - } - }; - - User.exists = function (uid, callback) { - db.isSortedSetMember('users:joindate', uid, callback); - }; - - User.existsBySlug = function (userslug, callback) { - User.getUidByUserslug(userslug, function (err, exists) { - callback(err, !!exists); - }); - }; - - User.getUidByUsername = function (username, callback) { - if (!username) { - return callback(null, 0); - } - db.sortedSetScore('username:uid', username, callback); - }; - - User.getUidsByUsernames = function (usernames, callback) { - db.sortedSetScores('username:uid', usernames, callback); - }; - - User.getUidByUserslug = function (userslug, callback) { - if (!userslug) { - return callback(null, 0); - } - db.sortedSetScore('userslug:uid', userslug, callback); - }; - - User.getUsernamesByUids = function (uids, callback) { - User.getUsersFields(uids, ['username'], function (err, users) { - if (err) { - return callback(err); - } - + plugins.fireHook('filter:userlist.get', { users: results.userData, uid: uid }, next); + }, + function (data, next) { + next(null, data.users); + }, + ], callback); +}; + +User.getUsers = function (uids, uid, callback) { + var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', + 'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; + + User.getUsersWithFields(uids, fields, uid, callback); +}; + +User.getStatus = function (userData) { + var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000; + return isOnline ? (userData.status || 'online') : 'offline'; +}; + +User.exists = function (uid, callback) { + db.isSortedSetMember('users:joindate', uid, callback); +}; + +User.existsBySlug = function (userslug, callback) { + User.getUidByUserslug(userslug, function (err, exists) { + callback(err, !!exists); + }); +}; + +User.getUidByUsername = function (username, callback) { + if (!username) { + return callback(null, 0); + } + db.sortedSetScore('username:uid', username, callback); +}; + +User.getUidsByUsernames = function (usernames, callback) { + db.sortedSetScores('username:uid', usernames, callback); +}; + +User.getUidByUserslug = function (userslug, callback) { + if (!userslug) { + return callback(null, 0); + } + db.sortedSetScore('userslug:uid', userslug, callback); +}; + +User.getUsernamesByUids = function (uids, callback) { + async.waterfall([ + function (next) { + User.getUsersFields(uids, ['username'], next); + }, + function (users, next) { users = users.map(function (user) { return user.username; }); - callback(null, users); - }); - }; - - User.getUsernameByUserslug = function (slug, callback) { - async.waterfall([ - function (next) { - User.getUidByUserslug(slug, next); - }, - function (uid, next) { - User.getUserField(uid, 'username', next); - }, - ], callback); - }; - - User.getUidByEmail = function (email, callback) { - db.sortedSetScore('email:uid', email.toLowerCase(), callback); - }; - - User.getUidsByEmails = function (emails, callback) { - emails = emails.map(function (email) { - return email && email.toLowerCase(); - }); - db.sortedSetScores('email:uid', emails, callback); - }; - - User.getUsernameByEmail = function (email, callback) { - db.sortedSetScore('email:uid', email.toLowerCase(), function (err, uid) { - if (err) { - return callback(err); - } - User.getUserField(uid, 'username', callback); - }); - }; - - User.isModerator = function (uid, cid, callback) { - privileges.users.isModerator(uid, cid, callback); - }; - - User.isModeratorOfAnyCategory = function (uid, callback) { - User.getModeratedCids(uid, function (err, cids) { - callback(err, Array.isArray(cids) ? !!cids.length : false); - }); - }; - - User.isAdministrator = function (uid, callback) { - privileges.users.isAdministrator(uid, callback); - }; - - User.isGlobalModerator = function (uid, callback) { - privileges.users.isGlobalModerator(uid, callback); - }; - - User.isAdminOrGlobalMod = function (uid, callback) { - async.parallel({ - isAdmin: async.apply(User.isAdministrator, uid), - isGlobalMod: async.apply(User.isGlobalModerator, uid), - }, function (err, results) { - callback(err, results ? (results.isAdmin || results.isGlobalMod) : false); - }); - }; - - User.isAdminOrSelf = function (callerUid, uid, callback) { - if (parseInt(callerUid, 10) === parseInt(uid, 10)) { - return callback(); - } - User.isAdministrator(callerUid, function (err, isAdmin) { - if (err || !isAdmin) { - return callback(err || new Error('[[error:no-privileges]]')); - } - callback(); - }); - }; - - User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) { - if (parseInt(callerUid, 10) === parseInt(uid, 10)) { - return callback(); - } - User.isAdminOrGlobalMod(callerUid, function (err, isAdminOrGlobalMod) { - if (err || !isAdminOrGlobalMod) { - return callback(err || new Error('[[error:no-privileges]]')); - } - callback(); - }); - }; - - User.getAdminsandGlobalMods = function (callback) { - async.parallel({ - admins: async.apply(groups.getMembers, 'administrators', 0, -1), - mods: async.apply(groups.getMembers, 'Global Moderators', 0, -1), - }, function (err, results) { - if (err) { - return callback(err); + next(null, users); + }, + ], callback); +}; + +User.getUsernameByUserslug = function (slug, callback) { + async.waterfall([ + function (next) { + User.getUidByUserslug(slug, next); + }, + function (uid, next) { + User.getUserField(uid, 'username', next); + }, + ], callback); +}; + +User.getUidByEmail = function (email, callback) { + db.sortedSetScore('email:uid', email.toLowerCase(), callback); +}; + +User.getUidsByEmails = function (emails, callback) { + emails = emails.map(function (email) { + return email && email.toLowerCase(); + }); + db.sortedSetScores('email:uid', emails, callback); +}; + +User.getUsernameByEmail = function (email, callback) { + async.waterfall([ + function (next) { + db.sortedSetScore('email:uid', email.toLowerCase(), next); + }, + function (uid, next) { + User.getUserField(uid, 'username', next); + }, + ], callback); +}; + +User.isModerator = function (uid, cid, callback) { + privileges.users.isModerator(uid, cid, callback); +}; + +User.isModeratorOfAnyCategory = function (uid, callback) { + User.getModeratedCids(uid, function (err, cids) { + callback(err, Array.isArray(cids) ? !!cids.length : false); + }); +}; + +User.isAdministrator = function (uid, callback) { + privileges.users.isAdministrator(uid, callback); +}; + +User.isGlobalModerator = function (uid, callback) { + privileges.users.isGlobalModerator(uid, callback); +}; + +User.isAdminOrGlobalMod = function (uid, callback) { + async.parallel({ + isAdmin: async.apply(User.isAdministrator, uid), + isGlobalMod: async.apply(User.isGlobalModerator, uid), + }, function (err, results) { + callback(err, results ? (results.isAdmin || results.isGlobalMod) : false); + }); +}; + +User.isAdminOrSelf = function (callerUid, uid, callback) { + isSelfOrMethod(callerUid, uid, User.isAdministrator, callback); +}; + +User.isAdminOrGlobalModOrSelf = function (callerUid, uid, callback) { + isSelfOrMethod(callerUid, uid, User.isAdminOrGlobalMod, callback); +}; + +function isSelfOrMethod(callerUid, uid, method, callback) { + if (parseInt(callerUid, 10) === parseInt(uid, 10)) { + return callback(); + } + async.waterfall([ + function (next) { + method(callerUid, next); + }, + function (isPass, next) { + if (!isPass) { + return next(new Error('[[error:no-privileges]]')); } - var uids = results.admins.concat(results.mods).filter(function (uid, index, array) { - return uid && array.indexOf(uid) === index; + next(); + }, + ], callback); +} + +User.getAdminsandGlobalMods = function (callback) { + async.waterfall([ + function (next) { + async.parallel([ + async.apply(groups.getMembers, 'administrators', 0, -1), + async.apply(groups.getMembers, 'Global Moderators', 0, -1), + ], next); + }, + function (results, next) { + User.getUsersData(_.union(results), next); + }, + ], callback); +}; + +User.getAdminsandGlobalModsandModerators = function (callback) { + async.waterfall([ + function (next) { + async.parallel([ + async.apply(groups.getMembers, 'administrators', 0, -1), + async.apply(groups.getMembers, 'Global Moderators', 0, -1), + async.apply(User.getModeratorUids), + ], next); + }, + function (results, next) { + User.getUsersData(_.union.apply(_, results), next); + }, + ], callback); +}; + +User.getModeratorUids = function (callback) { + async.waterfall([ + async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), + function (cids, next) { + var groupNames = cids.map(function (cid) { + return 'cid:' + cid + ':privileges:mods'; }); - User.getUsersData(uids, callback); - }); - }; - - User.getAdminsandGlobalModsandModerators = function (callback) { - async.parallel([ - async.apply(groups.getMembers, 'administrators', 0, -1), - async.apply(groups.getMembers, 'Global Moderators', 0, -1), - async.apply(User.getModeratorUids), - ], function (err, results) { - if (err) { - return callback(err); - } - User.getUsersData(_.union.apply(_, results), callback); - }); - }; + groups.getMembersOfGroups(groupNames, next); + }, + function (memberSets, next) { + next(null, _.union.apply(_, memberSets)); + }, + ], callback); +}; + +User.getModeratedCids = function (uid, callback) { + var cids; + async.waterfall([ + function (next) { + db.getSortedSetRange('categories:cid', 0, -1, next); + }, + function (_cids, next) { + cids = _cids; + User.isModerator(uid, cids, next); + }, + function (isMods, next) { + cids = cids.filter(function (cid, index) { + return cid && isMods[index]; + }); + next(null, cids); + }, + ], callback); +}; + +User.addInterstitials = function (callback) { + plugins.registerHook('core', { + hook: 'filter:register.interstitial', + method: function (data, callback) { + if (meta.config.termsOfUse && !data.userData.acceptTos) { + data.interstitials.push({ + template: 'partials/acceptTos', + data: { + termsOfUse: meta.config.termsOfUse, + }, + callback: function (userData, formData, next) { + if (formData['agree-terms'] === 'on') { + userData.acceptTos = true; + } - User.getModeratorUids = function (callback) { - async.waterfall([ - async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), - function (cids, next) { - var groupNames = cids.map(function (cid) { - return 'cid:' + cid + ':privileges:mods'; + next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]')); + }, }); + } - groups.getMembersOfGroups(groupNames, function (err, memberSets) { - if (err) { - return next(err); - } - - next(null, _.union.apply(_, memberSets)); - }); - }, - ], callback); - }; - - User.getModeratedCids = function (uid, callback) { - var cids; - async.waterfall([ - function (next) { - db.getSortedSetRange('categories:cid', 0, -1, next); - }, - function (_cids, next) { - cids = _cids; - User.isModerator(uid, cids, next); - }, - function (isMods, next) { - cids = cids.filter(function (cid, index) { - return cid && isMods[index]; - }); - next(null, cids); - }, - ], callback); - }; - - User.addInterstitials = function (callback) { - plugins.registerHook('core', { - hook: 'filter:register.interstitial', - method: function (data, callback) { - if (meta.config.termsOfUse && !data.userData.acceptTos) { - data.interstitials.push({ - template: 'partials/acceptTos', - data: { - termsOfUse: meta.config.termsOfUse, - }, - callback: function (userData, formData, next) { - if (formData['agree-terms'] === 'on') { - userData.acceptTos = true; - } - - next(userData.acceptTos ? null : new Error('[[register:terms_of_use_error]]')); - }, - }); - } + callback(null, data); + }, + }); - callback(null, data); - }, - }); + callback(); +}; - callback(); - }; -}(exports)); diff --git a/src/user/admin.js b/src/user/admin.js index 7ef6c0dda7..1d6cd8c7ad 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -17,13 +17,7 @@ module.exports = function (User) { }; User.getIPs = function (uid, stop, callback) { - db.getSortedSetRevRange('uid:' + uid + ':ip', 0, stop, function (err, ips) { - if (err) { - return callback(err); - } - - callback(null, ips); - }); + db.getSortedSetRevRange('uid:' + uid + ':ip', 0, stop, callback); }; User.getUsersCSV = function (callback) { diff --git a/src/user/data.js b/src/user/data.js index 443de8e7aa..120da7e3a2 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -1,5 +1,6 @@ 'use strict'; +var async = require('async'); var validator = require('validator'); var nconf = require('nconf'); var winston = require('winston'); @@ -54,13 +55,14 @@ module.exports = function (User) { addField('lastonline'); } - db.getObjectsFields(keys, fields, function (err, users) { - if (err) { - return callback(err); - } - - modifyUserData(users, fieldsToRemove, callback); - }); + async.waterfall([ + function (next) { + db.getObjectsFields(keys, fields, next); + }, + function (users, next) { + modifyUserData(users, fieldsToRemove, next); + }, + ], callback); }; User.getMultipleUserFields = function (uids, fields, callback) { @@ -83,13 +85,14 @@ module.exports = function (User) { return 'user:' + uid; }); - db.getObjects(keys, function (err, users) { - if (err) { - return callback(err); - } - - modifyUserData(users, [], callback); - }); + async.waterfall([ + function (next) { + db.getObjects(keys, next); + }, + function (users, next) { + modifyUserData(users, [], next); + }, + ], callback); }; function modifyUserData(users, fieldsToRemove, callback) { @@ -152,51 +155,53 @@ module.exports = function (User) { User.setUserField = function (uid, field, value, callback) { callback = callback || function () {}; - db.setObjectField('user:' + uid, field, value, function (err) { - if (err) { - return callback(err); - } - plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'set' }); - callback(); - }); + async.waterfall([ + function (next) { + db.setObjectField('user:' + uid, field, value, next); + }, + function (next) { + plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'set' }); + next(); + }, + ], callback); }; User.setUserFields = function (uid, data, callback) { callback = callback || function () {}; - db.setObject('user:' + uid, data, function (err) { - if (err) { - return callback(err); - } - for (var field in data) { - if (data.hasOwnProperty(field)) { - plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' }); + async.waterfall([ + function (next) { + db.setObject('user:' + uid, data, next); + }, + function (next) { + for (var field in data) { + if (data.hasOwnProperty(field)) { + plugins.fireHook('action:user.set', { uid: uid, field: field, value: data[field], type: 'set' }); + } } - } - callback(); - }); + next(); + }, + ], callback); }; User.incrementUserFieldBy = function (uid, field, value, callback) { - callback = callback || function () {}; - db.incrObjectFieldBy('user:' + uid, field, value, function (err, value) { - if (err) { - return callback(err); - } - plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'increment' }); - - callback(null, value); - }); + incrDecrUserFieldBy(uid, field, value, 'increment', callback); }; User.decrementUserFieldBy = function (uid, field, value, callback) { - callback = callback || function () {}; - db.incrObjectFieldBy('user:' + uid, field, -value, function (err, value) { - if (err) { - return callback(err); - } - plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: 'decrement' }); - - callback(null, value); - }); + incrDecrUserFieldBy(uid, field, -value, 'decrement', callback); }; + + function incrDecrUserFieldBy(uid, field, value, type, callback) { + callback = callback || function () {}; + async.waterfall([ + function (next) { + db.incrObjectFieldBy('user:' + uid, field, value, next); + }, + function (value, next) { + plugins.fireHook('action:user.set', { uid: uid, field: field, value: value, type: type }); + + next(null, value); + }, + ], callback); + } }; diff --git a/src/user/online.js b/src/user/online.js new file mode 100644 index 0000000000..6cb19cd22f --- /dev/null +++ b/src/user/online.js @@ -0,0 +1,70 @@ +'use strict'; + +var async = require('async'); + +var db = require('../database'); +var topics = require('../topics'); +var plugins = require('../plugins'); + +module.exports = function (User) { + User.updateLastOnlineTime = function (uid, callback) { + callback = callback || function () {}; + db.getObjectFields('user:' + uid, ['status', 'lastonline'], function (err, userData) { + var now = Date.now(); + if (err || userData.status === 'offline' || now - parseInt(userData.lastonline, 10) < 300000) { + return callback(err); + } + User.setUserField(uid, 'lastonline', now, callback); + }); + }; + + User.updateOnlineUsers = function (uid, callback) { + callback = callback || function () {}; + + var now = Date.now(); + async.waterfall([ + function (next) { + db.sortedSetScore('users:online', uid, next); + }, + function (userOnlineTime, next) { + if (now - parseInt(userOnlineTime, 10) < 300000) { + return callback(); + } + db.sortedSetAdd('users:online', now, uid, next); + }, + function (next) { + topics.pushUnreadCount(uid); + plugins.fireHook('action:user.online', { uid: uid, timestamp: now }); + next(); + }, + ], callback); + }; + + User.isOnline = function (uid, callback) { + var now = Date.now(); + async.waterfall([ + function (next) { + if (Array.isArray(uid)) { + db.sortedSetScores('users:online', uid, next); + } else { + db.sortedSetScore('users:online', uid, next); + } + }, + function (lastonline, next) { + function checkOnline(lastonline) { + return now - lastonline < 300000; + } + + var isOnline; + if (Array.isArray(uid)) { + isOnline = uid.map(function (uid, index) { + return checkOnline(lastonline[index]); + }); + } else { + isOnline = checkOnline(lastonline); + } + next(null, isOnline); + }, + ], callback); + }; +}; From 65cf7a01ec3cc242707aa822fecfdd160392402c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 16:29:17 +0300 Subject: [PATCH 09/87] test for recent posts, fix redirect test --- test/controllers.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/controllers.js b/test/controllers.js index d79235d16f..1cd9f17eda 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -532,6 +532,15 @@ describe('Controllers', function () { }); }); + it('should get recent posts', function (done) { + request(nconf.get('url') + '/api/recent/posts/month', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + it('should get post data', function (done) { request(nconf.get('url') + '/api/post/pid/' + pid, function (err, res, body) { assert.ifError(err); @@ -943,7 +952,7 @@ describe('Controllers', function () { describe('post redirect', function () { it('should 404 for invalid pid', function (done) { - request(nconf.get('url') + '/post/fail', function (err, res) { + request(nconf.get('url') + '/api/post/fail', function (err, res) { assert.ifError(err); assert.equal(res.statusCode, 404); done(); From b060bda8a06676e21c6d70903d66d61fa97a21c9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 17:25:32 +0300 Subject: [PATCH 10/87] some more refactors --- src/groups.js | 21 +++++++++------------ src/user/profile.js | 26 ++++++++------------------ src/webserver.js | 27 ++++++++++++++------------- 3 files changed, 31 insertions(+), 43 deletions(-) diff --git a/src/groups.js b/src/groups.js index 7e2e216caa..fd8c4a958f 100644 --- a/src/groups.js +++ b/src/groups.js @@ -243,26 +243,23 @@ Groups.getGroupNameByGroupSlug = function (slug, callback) { }; Groups.isPrivate = function (groupName, callback) { - async.waterfall([ - function (next) { - db.getObjectField('group:' + groupName, 'private', next); - }, - function (isPrivate, next) { - next(null, parseInt(isPrivate, 10) !== 0); - }, - ], callback); + isFieldOn(groupName, 'private', callback); }; Groups.isHidden = function (groupName, callback) { + isFieldOn(groupName, 'hidden', callback); +}; + +function isFieldOn(groupName, field, callback) { async.waterfall([ function (next) { - db.getObjectField('group:' + groupName, 'hidden', next); + db.getObjectField('group:' + groupName, field, next); }, - function (isHidden, next) { - next(null, parseInt(isHidden, 10) === 1); + function (value, next) { + next(null, parseInt(value, 10) === 1); }, ], callback); -}; +} Groups.exists = function (name, callback) { if (Array.isArray(name)) { diff --git a/src/user/profile.js b/src/user/profile.js index b0d40cf02d..ab7d9f9ae2 100644 --- a/src/user/profile.js +++ b/src/user/profile.js @@ -15,6 +15,14 @@ module.exports = function (User) { var fields = ['username', 'email', 'fullname', 'website', 'location', 'groupTitle', 'birthday', 'signature', 'aboutme']; + if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) { + return callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]')); + } + + if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) { + return callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]')); + } + async.waterfall([ function (next) { plugins.fireHook('filter:user.updateProfile', { uid: uid, data: data, fields: fields }, next); @@ -24,8 +32,6 @@ module.exports = function (User) { data = data.data; async.series([ - async.apply(isAboutMeValid, data), - async.apply(isSignatureValid, data), async.apply(isEmailAvailable, data, uid), async.apply(isUsernameAvailable, data, uid), async.apply(isGroupTitleValid, data), @@ -61,22 +67,6 @@ module.exports = function (User) { ], callback); }; - function isAboutMeValid(data, callback) { - if (data.aboutme !== undefined && data.aboutme.length > meta.config.maximumAboutMeLength) { - callback(new Error('[[error:about-me-too-long, ' + meta.config.maximumAboutMeLength + ']]')); - } else { - callback(); - } - } - - function isSignatureValid(data, callback) { - if (data.signature !== undefined && data.signature.length > meta.config.maximumSignatureLength) { - callback(new Error('[[error:signature-too-long, ' + meta.config.maximumSignatureLength + ']]')); - } else { - callback(); - } - } - function isEmailAvailable(data, uid, callback) { if (!data.email) { return callback(); diff --git a/src/webserver.js b/src/webserver.js index f3abeece54..e254d71008 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -61,21 +61,20 @@ module.exports.listen = function (callback) { logger.init(app); - initializeNodeBB(function (err) { - if (err) { - return callback(err); - } - - winston.info('NodeBB Ready'); + async.waterfall([ + initializeNodeBB, + function (next) { + winston.info('NodeBB Ready'); - require('./socket.io').server.emit('event:nodebb.ready', { - 'cache-buster': meta.config['cache-buster'], - }); + require('./socket.io').server.emit('event:nodebb.ready', { + 'cache-buster': meta.config['cache-buster'], + }); - plugins.fireHook('action:nodebb.ready'); + plugins.fireHook('action:nodebb.ready'); - listen(callback); - }); + listen(next); + }, + ], callback); }; function initializeNodeBB(callback) { @@ -107,7 +106,9 @@ function initializeNodeBB(callback) { meta.blacklist.load, ], next); }, - ], callback); + ], function (err) { + callback(err); + }); } function setupExpressApp(app) { From d60930a51dd2e69ac6f8f26027b628f3787c9d86 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 17:59:45 +0300 Subject: [PATCH 11/87] test latest member posts --- test/controllers.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/controllers.js b/test/controllers.js index 1cd9f17eda..2ddbd6d914 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -498,11 +498,23 @@ describe('Controllers', function () { hidden: 0, }, function (err) { assert.ifError(err); - request(nconf.get('url') + '/groups/group-details', function (err, res, body) { + groups.join('group-details', fooUid, function (err) { assert.ifError(err); - assert.equal(res.statusCode, 200); - assert(body); - done(); + topics.post({ + uid: fooUid, + title: 'topic title', + content: 'test topic content', + cid: cid, + }, function (err) { + assert.ifError(err); + request(nconf.get('url') + '/api/groups/group-details', {json: true}, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + assert.equal(body.posts[0].content, 'test topic content'); + done(); + }); + }); }); }); }); From 5ca8456340566fa6182944949d0917ae9dd88000 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 18:11:31 +0300 Subject: [PATCH 12/87] move image code to client/topic/images --- public/src/client/topic.js | 5 +- public/src/client/topic/events.js | 9 ++- public/src/client/topic/images.js | 119 ++++++++++++++++++++++++++++++ public/src/client/topic/posts.js | 117 ++--------------------------- test/controllers.js | 2 +- 5 files changed, 134 insertions(+), 118 deletions(-) create mode 100644 public/src/client/topic/images.js diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 9088830554..d620da1e70 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -7,11 +7,12 @@ define('forum/topic', [ 'forum/topic/postTools', 'forum/topic/events', 'forum/topic/posts', + 'forum/topic/images', 'forum/topic/replies', 'navigator', 'sort', 'components', -], function (infinitescroll, threadTools, postTools, events, posts, replies, navigator, sort, components) { +], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components) { var Topic = {}; var currentUrl = ''; @@ -238,7 +239,7 @@ define('forum/topic', [ return; } - posts.loadImages(threshold); + images.loadImages(threshold); var newUrl = 'topic/' + ajaxify.data.slug + (index > 1 ? ('/' + index) : ''); diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index f3e2d925c6..22a6c69d6c 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -6,9 +6,10 @@ define('forum/topic/events', [ 'forum/topic/postTools', 'forum/topic/threadTools', 'forum/topic/posts', + 'forum/topic/images', 'components', 'translator', -], function (postTools, threadTools, posts, components, translator) { +], function (postTools, threadTools, posts, images, components, translator) { var Events = {}; var events = { @@ -128,9 +129,9 @@ define('forum/topic/events', [ editedPostEl.html(translator.unescape(data.post.content)); editedPostEl.find('img:not(.not-responsive)').addClass('img-responsive'); app.replaceSelfLinks(editedPostEl.find('a')); - posts.wrapImagesInLinks(editedPostEl.parent()); - posts.unloadImages(editedPostEl.parent()); - posts.loadImages(); + images.wrapImagesInLinks(editedPostEl.parent()); + images.unloadImages(editedPostEl.parent()); + images.loadImages(); editedPostEl.fadeIn(250); var editData = { diff --git a/public/src/client/topic/images.js b/public/src/client/topic/images.js new file mode 100644 index 0000000000..cd44dd0914 --- /dev/null +++ b/public/src/client/topic/images.js @@ -0,0 +1,119 @@ +'use strict'; + + +define('forum/topic/images', [ + 'forum/topic/postTools', + 'navigator', + 'components', +], function (postTools, navigator, components) { + var Images = { + _imageLoaderTimeout: undefined, + }; + + Images.unloadImages = function (posts) { + var images = posts.find('[component="post/content"] img:not(.not-responsive)'); + + if (config.delayImageLoading) { + images.each(function () { + $(this).attr('data-src', $(this).attr('src')); + }).attr('data-state', 'unloaded').attr('src', 'about:blank'); + } else { + images.attr('data-state', 'loaded'); + Images.wrapImagesInLinks(posts); + } + }; + + Images.loadImages = function (threshold) { + if (Images._imageLoaderTimeout) { + clearTimeout(Images._imageLoaderTimeout); + } + + Images._imageLoaderTimeout = setTimeout(function () { + /* + If threshold is defined, images loaded above this threshold will modify + the user's scroll position so they are not scrolled away from content + they were reading. Images loaded below this threshold will push down content. + + If no threshold is defined, loaded images will push down content, as per + default + */ + + var images = components.get('post/content').find('img[data-state="unloaded"]'); + var visible = images.filter(function () { + return utils.isElementInViewport(this); + }); + var posts = $.unique(visible.map(function () { + return $(this).parents('[component="post"]').get(0); + })); + var scrollTop = $(window).scrollTop(); + var adjusting = false; + var adjustQueue = []; + var oldHeight; + var newHeight; + + function adjustPosition() { + adjusting = true; + oldHeight = document.body.clientHeight; + + // Display the image + $(this).attr('data-state', 'loaded'); + newHeight = document.body.clientHeight; + + var imageRect = this.getBoundingClientRect(); + if (imageRect.top < threshold) { + scrollTop += newHeight - oldHeight; + $(window).scrollTop(scrollTop); + } + + if (adjustQueue.length) { + adjustQueue.pop()(); + } else { + adjusting = false; + + Images.wrapImagesInLinks(posts); + posts.length = 0; + } + } + + // For each image, reset the source and adjust scrollTop when loaded + visible.attr('data-state', 'loading'); + visible.each(function (index, image) { + image = $(image); + + image.on('load', function () { + if (!adjusting) { + adjustPosition.call(this); + } else { + adjustQueue.push(adjustPosition.bind(this)); + } + }); + + image.attr('src', image.attr('data-src')); + image.removeAttr('data-src'); + }); + }, 250); + }; + + Images.wrapImagesInLinks = function (posts) { + posts.find('[component="post/content"] img:not(.emoji)').each(function () { + var $this = $(this); + var src = $this.attr('src'); + var suffixRegex = /-resized(\.[\w]+)?$/; + + if (src === 'about:blank') { + return; + } + + if (utils.isRelativeUrl(src) && suffixRegex.test(src)) { + src = src.replace(suffixRegex, '$1'); + } + + if (!$this.parent().is('a')) { + $this.wrap(''); + } + }); + }; + + + return Images; +}); diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 689d443ad3..86c387e39d 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -5,12 +5,11 @@ define('forum/topic/posts', [ 'forum/pagination', 'forum/infinitescroll', 'forum/topic/postTools', + 'forum/topic/images', 'navigator', 'components', -], function (pagination, infinitescroll, postTools, navigator, components) { - var Posts = { - _imageLoaderTimeout: undefined, - }; +], function (pagination, infinitescroll, postTools, images, navigator, components) { + var Posts = { }; Posts.onNewPost = function (data) { if (!data || !data.posts || !data.posts.length) { @@ -63,7 +62,7 @@ define('forum/topic/posts', [ function onNewPostPagination(data) { function scrollToPost() { scrollToPostIfSelf(data.posts[0]); - Posts.loadImages(); + images.loadImages(); } var posts = data.posts; @@ -107,7 +106,7 @@ define('forum/topic/posts', [ html.addClass('new'); } scrollToPostIfSelf(data.posts[0]); - Posts.loadImages(); + images.loadImages(); }); } @@ -247,7 +246,7 @@ define('forum/topic/posts', [ }; Posts.processPage = function (posts) { - Posts.unloadImages(posts); + images.unloadImages(posts); Posts.showBottomPostBar(); posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive'); app.createUserTooltips(posts); @@ -260,110 +259,6 @@ define('forum/topic/posts', [ hidePostToolsForDeletedPosts(posts); }; - Posts.unloadImages = function (posts) { - var images = posts.find('[component="post/content"] img:not(.not-responsive)'); - - if (config.delayImageLoading) { - images.each(function () { - $(this).attr('data-src', $(this).attr('src')); - }).attr('data-state', 'unloaded').attr('src', 'about:blank'); - } else { - images.attr('data-state', 'loaded'); - Posts.wrapImagesInLinks(posts); - } - }; - - Posts.loadImages = function (threshold) { - if (Posts._imageLoaderTimeout) { - clearTimeout(Posts._imageLoaderTimeout); - } - - Posts._imageLoaderTimeout = setTimeout(function () { - /* - If threshold is defined, images loaded above this threshold will modify - the user's scroll position so they are not scrolled away from content - they were reading. Images loaded below this threshold will push down content. - - If no threshold is defined, loaded images will push down content, as per - default - */ - - var images = components.get('post/content').find('img[data-state="unloaded"]'); - var visible = images.filter(function () { - return utils.isElementInViewport(this); - }); - var posts = $.unique(visible.map(function () { - return $(this).parents('[component="post"]').get(0); - })); - var scrollTop = $(window).scrollTop(); - var adjusting = false; - var adjustQueue = []; - var oldHeight; - var newHeight; - - function adjustPosition() { - adjusting = true; - oldHeight = document.body.clientHeight; - - // Display the image - $(this).attr('data-state', 'loaded'); - newHeight = document.body.clientHeight; - - var imageRect = this.getBoundingClientRect(); - if (imageRect.top < threshold) { - scrollTop += newHeight - oldHeight; - $(window).scrollTop(scrollTop); - } - - if (adjustQueue.length) { - adjustQueue.pop()(); - } else { - adjusting = false; - - Posts.wrapImagesInLinks(posts); - posts.length = 0; - } - } - - // For each image, reset the source and adjust scrollTop when loaded - visible.attr('data-state', 'loading'); - visible.each(function (index, image) { - image = $(image); - - image.on('load', function () { - if (!adjusting) { - adjustPosition.call(this); - } else { - adjustQueue.push(adjustPosition.bind(this)); - } - }); - - image.attr('src', image.attr('data-src')); - image.removeAttr('data-src'); - }); - }, 250); - }; - - Posts.wrapImagesInLinks = function (posts) { - posts.find('[component="post/content"] img:not(.emoji)').each(function () { - var $this = $(this); - var src = $this.attr('src'); - var suffixRegex = /-resized(\.[\w]+)?$/; - - if (src === 'about:blank') { - return; - } - - if (utils.isRelativeUrl(src) && suffixRegex.test(src)) { - src = src.replace(suffixRegex, '$1'); - } - - if (!$this.parent().is('a')) { - $this.wrap(''); - } - }); - }; - Posts.showBottomPostBar = function () { var mainPost = components.get('post', 'index', 0); var placeHolder = $('.post-bar-placeholder'); diff --git a/test/controllers.js b/test/controllers.js index 2ddbd6d914..b745004d59 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -507,7 +507,7 @@ describe('Controllers', function () { cid: cid, }, function (err) { assert.ifError(err); - request(nconf.get('url') + '/api/groups/group-details', {json: true}, function (err, res, body) { + request(nconf.get('url') + '/api/groups/group-details', { json: true }, function (err, res, body) { assert.ifError(err); assert.equal(res.statusCode, 200); assert(body); From 202bafa3e303575a6cc3db6ee0170110b4948275 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 18:21:01 +0300 Subject: [PATCH 13/87] add images to bundle --- public/src/client/topic/posts.js | 6 +----- src/meta/js.js | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 86c387e39d..72f580b582 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -12,11 +12,7 @@ define('forum/topic/posts', [ var Posts = { }; Posts.onNewPost = function (data) { - if (!data || !data.posts || !data.posts.length) { - return; - } - - if (parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) { + if (!data || !data.posts || !data.posts.length || parseInt(data.posts[0].tid, 10) !== parseInt(ajaxify.data.tid, 10)) { return; } diff --git a/src/meta/js.js b/src/meta/js.js index 679236d9c8..a39caf5919 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -59,6 +59,7 @@ module.exports = function (Meta) { 'public/src/client/topic/fork.js', 'public/src/client/topic/move.js', 'public/src/client/topic/posts.js', + 'public/src/client/topic/images.js', 'public/src/client/topic/postTools.js', 'public/src/client/topic/threadTools.js', 'public/src/client/categories.js', From cd448f90cd2d520e8a0260674b5f698ee9d71cfa Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 19:03:49 +0300 Subject: [PATCH 14/87] more topic tests --- src/socket.io/topics.js | 46 +++++++++-------- src/topics.js | 2 +- test/topics.js | 107 +++++++++++++++++++++++++++++++++++----- 3 files changed, 121 insertions(+), 34 deletions(-) diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 5eb9ce61dd..3b8a69d0f4 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -1,12 +1,14 @@ 'use strict'; +var async = require('async'); + var topics = require('../topics'); var websockets = require('./index'); var user = require('../user'); var apiController = require('../controllers/api'); var socketHelpers = require('./helpers'); -var SocketTopics = {}; +var SocketTopics = module.exports; require('./topics/unread')(SocketTopics); require('./topics/move')(SocketTopics); @@ -23,18 +25,19 @@ SocketTopics.post = function (socket, data, callback) { data.req = websockets.reqFromSocket(socket); data.timestamp = Date.now(); - topics.post(data, function (err, result) { - if (err) { - return callback(err); - } - - callback(null, result.topicData); + async.waterfall([ + function (next) { + topics.post(data, next); + }, + function (result, next) { + next(null, result.topicData); - socket.emit('event:new_post', { posts: [result.postData] }); - socket.emit('event:new_topic', result.topicData); + socket.emit('event:new_post', { posts: [result.postData] }); + socket.emit('event:new_topic', result.topicData); - socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); - }); + socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); + }, + ], callback); }; SocketTopics.postcount = function (socket, tid, callback) { @@ -61,7 +64,7 @@ SocketTopics.createTopicFromPosts = function (socket, data, callback) { }; SocketTopics.changeWatching = function (socket, data, callback) { - if (!data.tid || !data.type) { + if (!data || !data.tid || !data.type) { return callback(new Error('[[error:invalid-data]]')); } var commands = ['follow', 'unfollow', 'ignore']; @@ -90,20 +93,23 @@ SocketTopics.isFollowed = function (socket, tid, callback) { }; SocketTopics.search = function (socket, data, callback) { + if (!data) { + return callback(new Error('[[error:invalid-data]]')); + } topics.search(data.tid, data.term, callback); }; SocketTopics.isModerator = function (socket, tid, callback) { - topics.getTopicField(tid, 'cid', function (err, cid) { - if (err) { - return callback(err); - } - user.isModerator(socket.uid, cid, callback); - }); + async.waterfall([ + function (next) { + topics.getTopicField(tid, 'cid', next); + }, + function (cid, next) { + user.isModerator(socket.uid, cid, next); + }, + ], callback); }; SocketTopics.getTopic = function (socket, tid, callback) { apiController.getTopicData(tid, socket.uid, callback); }; - -module.exports = SocketTopics; diff --git a/src/topics.js b/src/topics.js index 0eba6545fd..77cea4ec98 100644 --- a/src/topics.js +++ b/src/topics.js @@ -317,7 +317,7 @@ var social = require('./social'); term: term, }, callback); } else { - callback(new Error('no-plugins-available'), []); + callback(new Error('[[error:no-plugins-available]]'), []); } }; }(exports)); diff --git a/test/topics.js b/test/topics.js index 2573225511..ea2ac6ecfc 100644 --- a/test/topics.js +++ b/test/topics.js @@ -12,6 +12,7 @@ var User = require('../src/user'); var groups = require('../src/groups'); var helpers = require('./helpers'); var socketPosts = require('../src/socket.io/posts'); +var socketTopics = require('../src/socket.io/topics'); describe('Topic\'s', function () { var topic; @@ -49,11 +50,34 @@ describe('Topic\'s', function () { }); describe('.post', function () { + it('should fail to create topic with invalid data', function (done) { + socketTopics.post({ uid: 0 }, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + it('should create a new topic with proper parameters', function (done) { topics.post({ uid: topic.userId, title: topic.title, content: topic.content, cid: topic.categoryId }, function (err, result) { - assert.equal(err, null, 'was created with error'); - assert.ok(result); + assert.ifError(err); + assert(result); + topic.tid = result.topicData.tid; + done(); + }); + }); + + it('should get post count', function (done) { + socketTopics.postcount({ uid: adminUid }, topic.tid, function (err, count) { + assert.ifError(err); + assert.equal(count, 1); + done(); + }); + }); + it('should load topic', function (done) { + socketTopics.getTopic({ uid: adminUid }, topic.tid, function (err, data) { + assert.ifError(err); + assert.equal(data.tid, topic.tid); done(); }); }); @@ -246,7 +270,7 @@ describe('Topic\'s', function () { var newTopic; var followerUid; var moveCid; - var socketTopics = require('../src/socket.io/topics'); + before(function (done) { async.waterfall([ function (next) { @@ -589,8 +613,7 @@ describe('Topic\'s', function () { assert.ok(result); replies.push(result); next(); - } - ); + }); } before(function (done) { @@ -619,7 +642,7 @@ describe('Topic\'s', function () { function (next) { postReply(next); }, function (next) { topicPids = replies.map(function (reply) { return reply.pid; }); - topics.setUserBookmark(newTopic.tid, topic.userId, originalBookmark, next); + socketTopics.bookmark({ uid: topic.userId }, { tid: newTopic.tid, index: originalBookmark }, next); }], done); }); @@ -629,15 +652,28 @@ describe('Topic\'s', function () { done(); }); + it('should fail with invalid data', function (done) { + socketTopics.createTopicFromPosts({ uid: 0 }, null, function (err) { + assert.equal(err.message, '[[error:not-logged-in]]'); + done(); + }); + }); + + it('should fail with invalid data', function (done) { + socketTopics.createTopicFromPosts({ uid: 1 }, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + it('should not update the user\'s bookmark', function (done) { async.waterfall([ function (next) { - topics.createTopicFromPosts( - topic.userId, - 'Fork test, no bookmark update', - topicPids.slice(-2), - newTopic.tid, - next); + socketTopics.createTopicFromPosts({ uid: topic.userId }, { + title: 'Fork test, no bookmark update', + pids: topicPids.slice(-2), + fromTid: newTopic.tid, + }, next); }, function (forkedTopicData, next) { topics.getUserBookmark(newTopic.tid, topic.userId, next); @@ -1388,6 +1424,13 @@ describe('Topic\'s', function () { }); }); + it('should error if not logged in', function (done) { + socketTopics.changeWatching({ uid: 0 }, { tid: tid, type: 'ignore' }, function (err) { + assert.equal(err.message, '[[error:not-logged-in]]'); + done(); + }); + }); + it('should filter ignoring uids', function (done) { socketTopics.changeWatching({ uid: followerUid }, { tid: tid, type: 'ignore' }, function (err) { assert.ifError(err); @@ -1418,7 +1461,7 @@ describe('Topic\'s', function () { topics.toggleFollow(tid, followerUid, function (err, isFollowing) { assert.ifError(err); assert(isFollowing); - topics.isFollowing([tid], followerUid, function (err, isFollowing) { + socketTopics.isFollowed({ uid: followerUid }, tid, function (err, isFollowing) { assert.ifError(err); assert(isFollowing); done(); @@ -1427,6 +1470,44 @@ describe('Topic\'s', function () { }); }); + describe('topics search', function () { + it('should error with invalid data', function (done) { + socketTopics.search({ uid: adminUid }, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + + it('should error if no search plugin', function (done) { + socketTopics.search({ uid: adminUid }, { tid: topic.tid, term: 'test' }, function (err) { + assert.equal(err.message, '[[error:no-plugins-available]]'); + done(); + }); + }); + + it('should return results', function (done) { + var plugins = require('../src/plugins'); + plugins.registerHook('myTestPlugin', { + hook: 'filter:topic.search', + method: function (data, callback) { + callback(null, [1, 2, 3]); + }, + }); + socketTopics.search({ uid: adminUid }, { tid: topic.tid, term: 'test' }, function (err, results) { + assert.ifError(err); + assert.deepEqual(results, [1, 2, 3]); + done(); + }); + }); + }); + + it('should check if user is moderator', function (done) { + socketTopics.isModerator({ uid: adminUid }, topic.tid, function (err, isModerator) { + assert.ifError(err); + assert(!isModerator); + done(); + }); + }); after(function (done) { db.emptydb(done); From 63b3c29a1a2b9d2a4fd679047f5890b6f07d7eeb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 19:21:56 +0300 Subject: [PATCH 15/87] test invalid data --- test/topics.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/topics.js b/test/topics.js index ea2ac6ecfc..5a65cf8ec8 100644 --- a/test/topics.js +++ b/test/topics.js @@ -647,6 +647,13 @@ describe('Topic\'s', function () { done); }); + it('should fail with invalid data', function (done) { + socketTopics.bookmark({ uid: topic.userId }, null, function (err) { + assert.equal(err.message, '[[error:invalid-data]]'); + done(); + }); + }); + it('should have 12 replies', function (done) { assert.equal(12, replies.length); done(); From bce1208e2f641d71763741f5295706dc7f3b7768 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 20:51:03 +0300 Subject: [PATCH 16/87] make uploadFile public --- src/controllers/uploads.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 25375e91a9..7a309d5200 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -66,7 +66,7 @@ function uploadAsImage(req, uploadedFile, callback) { file.isFileTypeAllowed(uploadedFile.path, next); }, function (next) { - uploadFile(req.uid, uploadedFile, next); + uploadsController.uploadFile(req.uid, uploadedFile, next); }, function (fileObj, next) { if (parseInt(meta.config.maximumImageWidth, 10) === 0) { @@ -90,7 +90,7 @@ function uploadAsFile(req, uploadedFile, callback) { if (parseInt(meta.config.allowFileUploads, 10) !== 1) { return next(new Error('[[error:uploads-are-disabled]]')); } - uploadFile(req.uid, uploadedFile, next); + uploadsController.uploadFile(req.uid, uploadedFile, next); }, ], callback); } @@ -161,7 +161,7 @@ uploadsController.uploadThumb = function (req, res, next) { }, next); } - uploadFile(req.uid, uploadedFile, next); + uploadsController.uploadFile(req.uid, uploadedFile, next); }, ], next); }, next); @@ -192,7 +192,7 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) { ], callback); }; -function uploadFile(uid, uploadedFile, callback) { +uploadsController.uploadFile = function (uid, uploadedFile, callback) { if (plugins.hasListeners('filter:uploadFile')) { return plugins.fireHook('filter:uploadFile', { file: uploadedFile, @@ -217,7 +217,7 @@ function uploadFile(uid, uploadedFile, callback) { } saveFileToLocal(uploadedFile, callback); -} +}; function saveFileToLocal(uploadedFile, callback) { var extension = file.typeToExtension(uploadedFile.type); From 4369a6d2908b8418c6bd4123309e9bbae7052d15 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 21:58:33 +0300 Subject: [PATCH 17/87] more user tests --- src/controllers/user.js | 15 ++++++++++----- test/controllers.js | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index c1049adffe..008faa4a06 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -39,12 +39,17 @@ userController.getUserByEmail = function (req, res, next) { }; function byType(type, req, res, next) { - userController.getUserDataByField(req.uid, type, req.params[type], function (err, data) { - if (err || !data) { - return next(err); + async.waterfall([ + function (next) { + userController.getUserDataByField(req.uid, type, req.params[type], next); + }, + function (data, next) { + if (!data) { + return next(); + } + res.json(data); } - res.json(data); - }); + ], next); } userController.getUserDataByField = function (callerUid, field, fieldValue, callback) { diff --git a/test/controllers.js b/test/controllers.js index b745004d59..e86ae0c00e 100644 --- a/test/controllers.js +++ b/test/controllers.js @@ -27,7 +27,7 @@ describe('Controllers', function () { }, next); }, user: function (next) { - user.create({ username: 'foo', password: 'barbar' }, next); + user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' }, next); }, navigation: function (next) { var navigation = require('../src/navigation/admin'); @@ -911,6 +911,42 @@ describe('Controllers', function () { }, ], done); }); + + it('should 404 if user does not exist', function (done) { + request(nconf.get('url') + '/api/user/email/doesnotexist', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 404); + assert(body); + done(); + }); + }); + + it('should load user by uid', function (done) { + request(nconf.get('url') + '/api/user/uid/' + fooUid, function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + + it('should load user by username', function (done) { + request(nconf.get('url') + '/api/user/username/foo', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); + + it('should load user by email', function (done) { + request(nconf.get('url') + '/api/user/email/foo@test.com', function (err, res, body) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + assert(body); + done(); + }); + }); }); describe('account follow page', function () { From 54c951cc816b83168083000ae3e08af6c07d4c73 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 2 Mar 2017 22:12:56 +0300 Subject: [PATCH 18/87] fix tests --- src/controllers/user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 008faa4a06..0f93f549b3 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -48,7 +48,7 @@ function byType(type, req, res, next) { return next(); } res.json(data); - } + }, ], next); } @@ -62,12 +62,12 @@ userController.getUserDataByField = function (callerUid, field, fieldValue, call } else if (field === 'email') { user.getUidByEmail(fieldValue, next); } else { - next(); + next(null, null); } }, function (uid, next) { if (!uid) { - return next(); + return next(null, null); } userController.getUserDataByUID(callerUid, uid, next); }, From 3b8d14bf12bf568b5ec099b9947219472516f5c5 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 3 Mar 2017 16:15:01 +0300 Subject: [PATCH 19/87] closes #5493 --- src/controllers/categories.js | 10 +++++++--- src/meta/tags.js | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index d5cd04b504..29ad406f98 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -13,9 +13,6 @@ categoriesController.list = function (req, res, next) { res.locals.metaTags = [{ name: 'title', content: String(meta.config.title || 'NodeBB'), - }, { - name: 'description', - content: String(meta.config.description || ''), }, { property: 'og:title', content: '[[pages:categories]]', @@ -24,6 +21,13 @@ categoriesController.list = function (req, res, next) { content: 'website', }]; + if (meta.config.description) { + res.locals.metaTags.push({ + name: 'description', + content: String(meta.config.description || ''), + }); + } + var ogImage = meta.config['og:image'] || meta.config['brand:logo'] || ''; if (ogImage) { if (!ogImage.startsWith('http')) { diff --git a/src/meta/tags.js b/src/meta/tags.js index 56269633ae..afef4bab0f 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -131,7 +131,7 @@ module.exports = function (Meta) { } }); - if (!hasDescription) { + if (!hasDescription && Meta.config.description) { meta.push({ name: 'description', content: validator.escape(String(Meta.config.description || '')), From ea871384d8371ba5963ffe6e2e07e15d22aa6ce6 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 3 Mar 2017 16:45:18 +0300 Subject: [PATCH 20/87] remove fallbacks --- src/controllers/categories.js | 2 +- src/meta/tags.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 29ad406f98..844163adb7 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -24,7 +24,7 @@ categoriesController.list = function (req, res, next) { if (meta.config.description) { res.locals.metaTags.push({ name: 'description', - content: String(meta.config.description || ''), + content: String(meta.config.description), }); } diff --git a/src/meta/tags.js b/src/meta/tags.js index afef4bab0f..fa88fa30b8 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -134,7 +134,7 @@ module.exports = function (Meta) { if (!hasDescription && Meta.config.description) { meta.push({ name: 'description', - content: validator.escape(String(Meta.config.description || '')), + content: validator.escape(String(Meta.config.description)), }); } } From a3b4c83e8b901f1a98ef22b662ba9443d1302e20 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 3 Mar 2017 19:59:37 +0300 Subject: [PATCH 21/87] remove description from categories it gets added in src/meta/tags --- src/controllers/categories.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 844163adb7..e02f107b4a 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -21,13 +21,6 @@ categoriesController.list = function (req, res, next) { content: 'website', }]; - if (meta.config.description) { - res.locals.metaTags.push({ - name: 'description', - content: String(meta.config.description), - }); - } - var ogImage = meta.config['og:image'] || meta.config['brand:logo'] || ''; if (ogImage) { if (!ogImage.startsWith('http')) { From 324e5be7bda6a949c31e4877d4aac656c6d3f7cb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 3 Mar 2017 21:04:01 +0300 Subject: [PATCH 22/87] privileges tests --- src/privileges/categories.js | 191 ++++++++++++++++++----------------- test/categories.js | 67 ++++++++++++ test/controllers-admin.js | 4 +- 3 files changed, 168 insertions(+), 94 deletions(-) diff --git a/src/privileges/categories.js b/src/privileges/categories.js index d0bde8ced3..6678f5d5aa 100644 --- a/src/privileges/categories.js +++ b/src/privileges/categories.js @@ -155,34 +155,36 @@ module.exports = function (privileges) { privileges.categories.get = function (cid, uid, callback) { var privs = ['topics:create', 'topics:read', 'read']; - async.parallel({ - privileges: function (next) { - helpers.isUserAllowedTo(privs, uid, cid, next); - }, - isAdministrator: function (next) { - user.isAdministrator(uid, next); + async.waterfall([ + function (next) { + async.parallel({ + privileges: function (next) { + helpers.isUserAllowedTo(privs, uid, cid, next); + }, + isAdministrator: function (next) { + user.isAdministrator(uid, next); + }, + isModerator: function (next) { + user.isModerator(uid, cid, next); + }, + }, next); }, - isModerator: function (next) { - user.isModerator(uid, cid, next); + function (results, next) { + var privData = _.object(privs, results.privileges); + var isAdminOrMod = results.isAdministrator || results.isModerator; + + plugins.fireHook('filter:privileges.categories.get', { + 'topics:create': privData['topics:create'] || isAdminOrMod, + 'topics:read': privData['topics:read'] || isAdminOrMod, + read: privData.read || isAdminOrMod, + cid: cid, + uid: uid, + editable: isAdminOrMod, + view_deleted: isAdminOrMod, + isAdminOrMod: isAdminOrMod, + }, next); }, - }, function (err, results) { - if (err) { - return callback(err); - } - var privData = _.object(privs, results.privileges); - var isAdminOrMod = results.isAdministrator || results.isModerator; - - plugins.fireHook('filter:privileges.categories.get', { - 'topics:create': privData['topics:create'] || isAdminOrMod, - 'topics:read': privData['topics:read'] || isAdminOrMod, - read: privData.read || isAdminOrMod, - cid: cid, - uid: uid, - editable: isAdminOrMod, - view_deleted: isAdminOrMod, - isAdminOrMod: isAdminOrMod, - }, callback); - }); + ], callback); }; privileges.categories.isAdminOrMod = function (cid, uid, callback) { @@ -213,29 +215,29 @@ module.exports = function (privileges) { return callback(null, false); } - categories.getCategoryField(cid, 'disabled', function (err, disabled) { - if (err) { - return callback(err); - } - - if (parseInt(disabled, 10) === 1) { - return callback(null, false); - } - - helpers.some([ - function (next) { - helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) { - next(err, Array.isArray(results) && results.length ? results[0] : false); - }); - }, - function (next) { - user.isModerator(uid, cid, next); - }, - function (next) { - user.isAdministrator(uid, next); - }, - ], callback); - }); + async.waterfall([ + function (next) { + categories.getCategoryField(cid, 'disabled', next); + }, + function (disabled, next) { + if (parseInt(disabled, 10) === 1) { + return callback(null, false); + } + helpers.some([ + function (next) { + helpers.isUserAllowedTo(privilege, uid, [cid], function (err, results) { + next(err, Array.isArray(results) && results.length ? results[0] : false); + }); + }, + function (next) { + user.isModerator(uid, cid, next); + }, + function (next) { + user.isAdministrator(uid, next); + }, + ], next); + }, + ], callback); }; privileges.categories.filterCids = function (privilege, cids, uid, callback) { @@ -247,18 +249,19 @@ module.exports = function (privileges) { return array.indexOf(cid) === index; }); - privileges.categories.getBase(privilege, cids, uid, function (err, results) { - if (err) { - return callback(err); - } - - cids = cids.filter(function (cid, index) { - return !results.categories[index].disabled && - (results.allowedTo[index] || results.isAdmin || results.isModerators[index]); - }); + async.waterfall([ + function (next) { + privileges.categories.getBase(privilege, cids, uid, next); + }, + function (results, next) { + cids = cids.filter(function (cid, index) { + return !results.categories[index].disabled && + (results.allowedTo[index] || results.isAdmin || results.isModerators[index]); + }); - callback(null, cids.filter(Boolean)); - }); + next(null, cids.filter(Boolean)); + }, + ], callback); }; privileges.categories.getBase = function (privilege, cids, uid, callback) { @@ -287,26 +290,27 @@ module.exports = function (privileges) { return array.indexOf(uid) === index; }); - async.parallel({ - allowedTo: function (next) { - helpers.isUsersAllowedTo(privilege, uids, cid, next); - }, - isModerators: function (next) { - user.isModerator(uids, cid, next); + async.waterfall([ + function (next) { + async.parallel({ + allowedTo: function (next) { + helpers.isUsersAllowedTo(privilege, uids, cid, next); + }, + isModerators: function (next) { + user.isModerator(uids, cid, next); + }, + isAdmin: function (next) { + user.isAdministrator(uids, next); + }, + }, next); }, - isAdmin: function (next) { - user.isAdministrator(uids, next); + function (results, next) { + uids = uids.filter(function (uid, index) { + return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index]; + }); + next(null, uids); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - uids = uids.filter(function (uid, index) { - return results.allowedTo[index] || results.isModerators[index] || results.isAdmin[index]; - }); - callback(null, uids); - }); + ], callback); }; privileges.categories.give = function (privileges, cid, groupName, callback) { @@ -324,23 +328,24 @@ module.exports = function (privileges) { } privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) { - async.parallel({ - isAdministrator: function (next) { - user.isAdministrator(uid, next); - }, - moderatorOfCurrent: function (next) { - user.isModerator(uid, currentCid, next); + async.waterfall([ + function (next) { + async.parallel({ + isAdministrator: function (next) { + user.isAdministrator(uid, next); + }, + moderatorOfCurrent: function (next) { + user.isModerator(uid, currentCid, next); + }, + moderatorOfTarget: function (next) { + user.isModerator(uid, targetCid, next); + }, + }, next); }, - moderatorOfTarget: function (next) { - user.isModerator(uid, targetCid, next); + function (results, next) { + next(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget)); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - callback(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget)); - }); + ], callback); }; privileges.categories.userPrivileges = function (cid, uid, callback) { diff --git a/test/categories.js b/test/categories.js index bdd91602de..8ef059d8cb 100644 --- a/test/categories.js +++ b/test/categories.js @@ -390,6 +390,7 @@ describe('Categories', function () { it('should get all categories', function (done) { socketCategories.getAll({ uid: adminUid }, {}, function (err, data) { assert.ifError(err); + assert(data); done(); }); }); @@ -615,6 +616,72 @@ describe('Categories', function () { }); + describe('privileges', function () { + var privileges = require('../src/privileges'); + + it('should return empty array if uids is empty array', function (done) { + privileges.categories.filterUids('find', categoryObj.cid, [], function (err, uids) { + assert.ifError(err); + assert.equal(uids.length, 0); + done(); + }); + }); + + it('should filter uids by privilege', function (done) { + privileges.categories.filterUids('find', categoryObj.cid, [1, 2, 3, 4], function (err, uids) { + assert.ifError(err); + assert.deepEqual(uids, [1, 2]); + done(); + }); + }); + + it('should load user privileges', function (done) { + privileges.categories.userPrivileges(categoryObj.cid, 1, function (err, data) { + assert.ifError(err); + assert.deepEqual(data, { + find: false, + mods: false, + 'posts:delete': false, + read: false, + 'topics:reply': false, + 'topics:read': false, + 'topics:create': false, + 'topics:delete': false, + 'posts:edit': false, + }); + + done(); + }); + }); + + it('should load group privileges', function (done) { + privileges.categories.groupPrivileges(categoryObj.cid, 'registered-users', function (err, data) { + assert.ifError(err); + assert.deepEqual(data, { + 'groups:find': true, + 'groups:posts:edit': true, + 'groups:topics:delete': false, + 'groups:topics:create': true, + 'groups:topics:reply': true, + 'groups:posts:delete': true, + 'groups:read': true, + 'groups:topics:read': true, + }); + + done(); + }); + }); + + it('should return false if cid is falsy', function (done) { + privileges.categories.isUserAllowedTo('find', null, adminUid, function (err, isAllowed) { + assert.ifError(err); + assert.equal(isAllowed, false); + done(); + }); + }); + }); + + after(function (done) { db.emptydb(done); }); diff --git a/test/controllers-admin.js b/test/controllers-admin.js index ad59b4705d..5a17ca96a9 100644 --- a/test/controllers-admin.js +++ b/test/controllers-admin.js @@ -21,6 +21,7 @@ describe('Admin Controllers', function () { var jar; before(function (done) { + groups.resetCache(); async.series({ category: function (next) { categories.create({ @@ -43,9 +44,10 @@ describe('Admin Controllers', function () { cid = results.category.cid; topics.post({ uid: adminUid, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, function (err, result) { + assert.ifError(err); tid = result.topicData.tid; pid = result.postData.pid; - done(err); + done(); }); }); }); From ab9f4ad636a2ce9abb994b75679bc632967165bf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 3 Mar 2017 21:49:14 +0300 Subject: [PATCH 23/87] test bookmark tru socket.io --- test/posts.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/test/posts.js b/test/posts.js index 39c66701eb..55d13e1ea4 100644 --- a/test/posts.js +++ b/test/posts.js @@ -11,6 +11,7 @@ var categories = require('../src/categories'); var privileges = require('../src/privileges'); var user = require('../src/user'); var groups = require('../src/groups'); +var socketPosts = require('../src/socket.io/posts'); describe('Post\'s', function () { var voterUid; @@ -66,7 +67,6 @@ describe('Post\'s', function () { }); describe('voting', function () { - var socketPosts = require('../src/socket.io/posts'); it('should upvote a post', function (done) { socketPosts.upvote({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_1' }, function (err, result) { assert.ifError(err); @@ -138,7 +138,7 @@ describe('Post\'s', function () { describe('bookmarking', function () { it('should bookmark a post', function (done) { - posts.bookmark(postData.pid, voterUid, function (err, data) { + socketPosts.bookmark({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_' + postData.tid }, function (err, data) { assert.ifError(err); assert.equal(data.isBookmarked, true); posts.hasBookmarked(postData.pid, voterUid, function (err, hasBookmarked) { @@ -150,7 +150,7 @@ describe('Post\'s', function () { }); it('should unbookmark a post', function (done) { - posts.unbookmark(postData.pid, voterUid, function (err, data) { + socketPosts.unbookmark({ uid: voterUid }, { pid: postData.pid, room_id: 'topic_' + postData.tid }, function (err, data) { assert.ifError(err); assert.equal(data.isBookmarked, false); posts.hasBookmarked([postData.pid], voterUid, function (err, hasBookmarked) { @@ -163,8 +163,6 @@ describe('Post\'s', function () { }); describe('post tools', function () { - var socketPosts = require('../src/socket.io/posts'); - it('should error if data is invalid', function (done) { socketPosts.loadPostTools({ uid: globalModUid }, null, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); @@ -209,7 +207,6 @@ describe('Post\'s', function () { var mainPid; var replyPid; - var socketPosts = require('../src/socket.io/posts'); before(function (done) { createTopicWithReply(function (topicPostData, replyData) { tid = topicPostData.topicData.tid; @@ -299,7 +296,6 @@ describe('Post\'s', function () { var pid; var replyPid; var tid; - var socketPosts = require('../src/socket.io/posts'); var meta = require('../src/meta'); before(function (done) { topics.post({ @@ -430,7 +426,6 @@ describe('Post\'s', function () { var replyPid; var tid; var moveTid; - var socketPosts = require('../src/socket.io/posts'); before(function (done) { async.waterfall([ @@ -503,7 +498,6 @@ describe('Post\'s', function () { describe('flagging a post', function () { var meta = require('../src/meta'); - var socketPosts = require('../src/socket.io/posts'); it('should fail to flag a post due to low reputation', function (done) { meta.config['privileges:flag'] = 10; flagPost(function (err) { @@ -546,7 +540,6 @@ describe('Post\'s', function () { }); function flagPost(next) { - var socketPosts = require('../src/socket.io/posts'); socketPosts.flag({ uid: voteeUid }, { pid: postData.pid, reason: 'reason' }, next); } @@ -577,8 +570,6 @@ describe('Post\'s', function () { }); describe('updating a flag', function () { - var socketPosts = require('../src/socket.io/posts'); - it('should update a flag', function (done) { async.waterfall([ function (next) { @@ -671,8 +662,6 @@ describe('Post\'s', function () { }); describe('dismissing a flag', function () { - var socketPosts = require('../src/socket.io/posts'); - it('should dismiss a flag', function (done) { socketPosts.dismissFlag({ uid: globalModUid }, postData.pid, function (err) { assert.ifError(err); @@ -769,7 +758,6 @@ describe('Post\'s', function () { }); }); - var socketPosts = require('../src/socket.io/posts'); it('should error with invalid data', function (done) { socketPosts.reply({ uid: 0 }, null, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); From 99dff11462dee129e95a7094988187192c1159cd Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Sat, 4 Mar 2017 09:22:16 +0000 Subject: [PATCH 24/87] Latest translations and fallbacks --- public/language/vi/admin/admin.json | 8 ++--- public/language/vi/admin/advanced/cache.json | 18 +++++----- .../language/vi/admin/advanced/database.json | 34 +++++++++---------- public/language/vi/global.json | 2 +- public/language/vi/topic.json | 4 +-- public/language/zh-CN/users.json | 2 +- 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/public/language/vi/admin/admin.json b/public/language/vi/admin/admin.json index 9c01f56006..726cef0e8b 100644 --- a/public/language/vi/admin/admin.json +++ b/public/language/vi/admin/admin.json @@ -1,7 +1,7 @@ { - "alert.confirm-reload": "Are you sure you wish to reload NodeBB?", - "alert.confirm-restart": "Are you sure you wish to restart NodeBB?", + "alert.confirm-reload": "Bạn có thật sự muốn tải lại NodeBB", + "alert.confirm-restart": "Bạn có thật sự muốn khởi động lại NodeBB", - "acp-title": "%1 | NodeBB Admin Control Panel", - "settings-header-contents": "Contents" + "acp-title": "%1 | Bảng điểu khiển", + "settings-header-contents": "Nội dung" } \ No newline at end of file diff --git a/public/language/vi/admin/advanced/cache.json b/public/language/vi/admin/advanced/cache.json index 5a954f1232..505b1a4510 100644 --- a/public/language/vi/admin/advanced/cache.json +++ b/public/language/vi/admin/advanced/cache.json @@ -1,11 +1,11 @@ { - "post-cache": "Post Cache", - "posts-in-cache": "Posts in Cache", - "average-post-size": "Average Post Size", - "length-to-max": "Length / Max", - "percent-full": "%1% Full", - "post-cache-size": "Post Cache Size", - "items-in-cache": "Items in Cache", - "control-panel": "Control Panel", - "update-settings": "Update Cache Settings" + "post-cache": "Cache bài viết", + "posts-in-cache": "Cache cho bài viết", + "average-post-size": "Kích thước bài viết", + "length-to-max": "Độ dài / Tối Đa", + "percent-full": "%1% Đầy", + "post-cache-size": "Kích thước cache bài viết", + "items-in-cache": "Thành phần trong Cache", + "control-panel": "Bảng điều khiển", + "update-settings": "Cập nhật thiết lập Cache" } \ No newline at end of file diff --git a/public/language/vi/admin/advanced/database.json b/public/language/vi/admin/advanced/database.json index f7db6220ee..fe446d1759 100644 --- a/public/language/vi/admin/advanced/database.json +++ b/public/language/vi/admin/advanced/database.json @@ -1,30 +1,30 @@ { "x-b": "%1 b", "x-mb": "%1 mb", - "uptime-seconds": "Uptime in Seconds", - "uptime-days": "Uptime in Days", + "uptime-seconds": "Thời gian hoạt động(giây)", + "uptime-days": "Thời gian hoạt động(Ngày)", "mongo": "Mongo", - "mongo.version": "MongoDB Version", - "mongo.storage-engine": "Storage Engine", - "mongo.collections": "Collections", - "mongo.objects": "Objects", - "mongo.avg-object-size": "Avg. Object Size", - "mongo.data-size": "Data Size", - "mongo.storage-size": "Storage Size", - "mongo.index-size": "Index Size", - "mongo.file-size": "File Size", + "mongo.version": "Phiên bản MongoDB ", + "mongo.storage-engine": "Lưu Trữ", + "mongo.collections": "Tập dữ liệu", + "mongo.objects": "Đối tượng", + "mongo.avg-object-size": "Kích thước trung bình", + "mongo.data-size": "Kích thước dữ liệu", + "mongo.storage-size": "Kích thước lưu trữ", + "mongo.index-size": "Kích thước chỉ mục", + "mongo.file-size": "Kích thước tập tin", "mongo.resident-memory": "Resident Memory", - "mongo.virtual-memory": "Virtual Memory", + "mongo.virtual-memory": "Bộ nhớ ảo", "mongo.mapped-memory": "Mapped Memory", - "mongo.raw-info": "MongoDB Raw Info", + "mongo.raw-info": "Thông tin MongoDB", "redis": "Redis", - "redis.version": "Redis Version", - "redis.connected-clients": "Connected Clients", + "redis.version": "Phiên bản Redis", + "redis.connected-clients": "Người dùng kết nối", "redis.connected-slaves": "Connected Slaves", - "redis.blocked-clients": "Blocked Clients", - "redis.used-memory": "Used Memory", + "redis.blocked-clients": "Người dùng vi phạm", + "redis.used-memory": "Bộ nhớ đã sử dụng", "redis.memory-frag-ratio": "Memory Fragmentation Ratio", "redis.total-connections-recieved": "Total Connections Received", "redis.total-commands-processed": "Total Commands Processed", diff --git a/public/language/vi/global.json b/public/language/vi/global.json index 5a4e6a1406..65db660c60 100644 --- a/public/language/vi/global.json +++ b/public/language/vi/global.json @@ -103,5 +103,5 @@ "cookies.message": "Trang web này sử dụng cookie để đảm bảo trải nghiệm tốt nhất cho người dùng", "cookies.accept": "Đã rõ!", "cookies.learn_more": "Xem thêm", - "edited": "Edited" + "edited": "Đã cập nhật" } \ No newline at end of file diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json index aaa940bdc3..5c1cd238c7 100644 --- a/public/language/vi/topic.json +++ b/public/language/vi/topic.json @@ -13,8 +13,8 @@ "notify_me": "Được thông báo khi có trả lời mới trong chủ đề này", "quote": "Trích dẫn", "reply": "Trả lời", - "replies_to_this_post": "%1 Replies", - "last_reply_time": "Last reply", + "replies_to_this_post": "%1 trả lời", + "last_reply_time": "Trả lời cuối cùng", "reply-as-topic": "Trả lời dưới dạng chủ đề", "guest-login-reply": "Hãy đăng nhập để trả lời", "edit": "Chỉnh sửa", diff --git a/public/language/zh-CN/users.json b/public/language/zh-CN/users.json index d6795469f2..253d4db756 100644 --- a/public/language/zh-CN/users.json +++ b/public/language/zh-CN/users.json @@ -6,7 +6,7 @@ "search": "搜索", "enter_username": "输入用户名搜索", "load_more": "加载更多", - "users-found-search-took": "找到 %1 位用户!耗时 %2 毫秒。", + "users-found-search-took": "找到 %1 位用户!耗时 %2 秒。", "filter-by": "过滤选项", "online-only": "只看在线", "invite": "邀请注册", From e85e35cf0cefc8cdac6ded36d25621a83d88e8b4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sun, 5 Mar 2017 00:46:11 +0300 Subject: [PATCH 25/87] closes #5497 --- public/src/client/account/settings.js | 31 +++++++++++++++++++++++---- src/controllers/api.js | 2 +- src/meta/themes.js | 7 ++++-- src/middleware/header.js | 21 ++++++++++++++---- src/user/settings.js | 2 +- test/socket.io.js | 11 +++++++--- 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 537baab5de..eba41faf9c 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -4,6 +4,12 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds'], function (header, components, sounds) { var AccountSettings = {}; + $(window).on('action:ajaxify.start', function () { + if (ajaxify.data.template.name === 'account/settings' && $('#bootswatchSkin').val() !== config.bootswatchSkin) { + changePageSkin(config.bootswatchSkin); + } + }); + AccountSettings.init = function () { header.init(); @@ -24,10 +30,7 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' }); $('#bootswatchSkin').on('change', function () { - var css = $('#bootswatchCSS'); - var val = $(this).val() === 'default' ? config['theme:src'] : '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + $(this).val() + '/bootstrap.min.css'; - - css.attr('href', val); + changePageSkin($(this).val()); }); $('[data-property="homePageRoute"]').on('change', toggleCustomRoute); @@ -44,6 +47,26 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' components.get('user/sessions').find('.timeago').timeago(); }; + function changePageSkin(skinName) { + var css = $('#bootswatchCSS'); + if (skinName === 'default') { + css.remove(); + } else { + var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css'; + if (css.length) { + css.attr('href', cssSource); + } else { + css = $(''); + $('head').append(css); + } + } + + var currentSkinClassName = $('body').attr('class').split(/\s+/).filter(function (className) { + return className.startsWith('skin-'); + }); + $('body').removeClass(currentSkinClassName.join(' ')).addClass('skin-' + skinName); + } + function loadSettings() { var settings = {}; diff --git a/src/controllers/api.js b/src/controllers/api.js index ab52da69cd..cfa78a6908 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -61,7 +61,7 @@ apiController.getConfig = function (req, res, next) { config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.csrf_token = req.csrfToken(); config.searchEnabled = plugins.hasListeners('filter:search.query'); - config.bootswatchSkin = 'default'; + config.bootswatchSkin = meta.config.bootswatchSkin || 'default'; var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff; config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff; diff --git a/src/meta/themes.js b/src/meta/themes.js index e950847421..8853c9a086 100644 --- a/src/meta/themes.js +++ b/src/meta/themes.js @@ -104,7 +104,7 @@ module.exports = function (Meta) { themeData['theme:templates'] = config.templates ? config.templates : ''; themeData['theme:src'] = ''; - db.setObject('config', themeData, next); + Meta.configs.setMultiple(themeData, next); // Re-set the themes path (for when NodeBB is reloaded) Meta.themes.setPath(config); @@ -115,7 +115,10 @@ module.exports = function (Meta) { break; case 'bootswatch': - Meta.configs.set('theme:src', data.src, callback); + Meta.configs.setMultiple({ + 'theme:src': data.src, + bootswatchSkin: data.id.toLowerCase(), + }, callback); break; } }; diff --git a/src/middleware/header.js b/src/middleware/header.js index accbbf2fe6..44a541f4bf 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -42,7 +42,6 @@ module.exports = function (middleware) { middleware.renderHeader = function (req, res, data, callback) { var registrationType = meta.config.registrationType || 'normal'; var templateValues = { - bootswatchCSS: meta.config['theme:src'], title: meta.config.title || '', description: meta.config.description || '', 'cache-buster': meta.config['cache-buster'] || '', @@ -117,9 +116,7 @@ module.exports = function (middleware) { results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; - if (res.locals.config && parseInt(meta.config.disableCustomUserSkins, 10) !== 1 && res.locals.config.bootswatchSkin !== 'default') { - templateValues.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + res.locals.config.bootswatchSkin + '/bootstrap.min.css'; - } + setBootswatchCSS(templateValues, res.locals.config); templateValues.browserTitle = controllers.helpers.buildTitle(data.title); templateValues.navigation = results.navigation; @@ -191,5 +188,21 @@ module.exports = function (middleware) { return title; } + + function setBootswatchCSS(obj, config) { + if (config && config.bootswatchSkin !== 'default') { + var skinToUse = ''; + + if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1) { + skinToUse = config.bootswatchSkin; + } else if (meta.config.bootswatchSkin !== 'default') { + skinToUse = meta.config.bootswatchSkin; + } + + if (skinToUse) { + obj.bootswatchCSS = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinToUse + '/bootstrap.min.css'; + } + } + } }; diff --git a/src/user/settings.js b/src/user/settings.js index 870dd2e59c..bef20e0087 100644 --- a/src/user/settings.js +++ b/src/user/settings.js @@ -74,7 +74,7 @@ module.exports = function (User) { settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1; settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1; settings.delayImageLoading = parseInt(getSetting(settings, 'delayImageLoading', 1), 10) === 1; - settings.bootswatchSkin = settings.bootswatchSkin || 'default'; + settings.bootswatchSkin = settings.bootswatchSkin || meta.config.bootswatchSkin || 'default'; settings.scrollToMyPost = parseInt(getSetting(settings, 'scrollToMyPost', 1), 10) === 1; callback(null, settings); diff --git a/test/socket.io.js b/test/socket.io.js index 095c3b5440..497f0ed922 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -427,11 +427,16 @@ describe('socket.io', function () { }); it('should set theme to bootswatch', function (done) { - socketAdmin.themes.set({ uid: adminUid }, { type: 'bootswatch', src: 'darkly' }, function (err) { + socketAdmin.themes.set({ uid: adminUid }, { + type: 'bootswatch', + src: '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css', + id: 'darkly', + }, function (err) { assert.ifError(err); - meta.configs.get('theme:src', function (err, id) { + meta.configs.getFields(['theme:src', 'bootswatchSkin'], function (err, fields) { assert.ifError(err); - assert.equal(id, 'darkly'); + assert.equal(fields['theme:src'], '//maxcdn.bootstrapcdn.com/bootswatch/latest/darkly/bootstrap.min.css'); + assert.equal(fields.bootswatchSkin, 'darkly'); done(); }); }); From f761191fcaede5bb6b0ea388531cb0e640b8c9f1 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Sun, 5 Mar 2017 14:45:30 +0100 Subject: [PATCH 26/87] Add OSD --- src/meta.js | 1 + src/meta/build.js | 8 +++++++- src/meta/osd.js | 41 +++++++++++++++++++++++++++++++++++++++++ src/meta/tags.js | 8 ++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/meta/osd.js diff --git a/src/meta.js b/src/meta.js index 6cbccd70a1..f0c1756053 100644 --- a/src/meta.js +++ b/src/meta.js @@ -21,6 +21,7 @@ var utils = require('../public/src/utils'); require('./meta/errors')(Meta); require('./meta/tags')(Meta); require('./meta/dependencies')(Meta); + require('./meta/osd')(Meta); Meta.templates = require('./meta/templates'); Meta.blacklist = require('./meta/blacklist'); Meta.languages = require('./meta/languages'); diff --git a/src/meta/build.js b/src/meta/build.js index 9ba5ec89bf..cba910bf83 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -5,7 +5,7 @@ var winston = require('winston'); var buildStart; -var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound']; +var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound', 'osd']; exports.buildAll = function (callback) { exports.build(valid.join(','), callback); @@ -117,6 +117,12 @@ exports.buildTargets = function (targets, callback) { meta.sounds.build(step.bind(this, startTime, target, next)); break; + case 'osd': + winston.info('[build] Building OpenSearchDocument XML'); + startTime = Date.now(); + meta.osd.build(step.bind(this, startTime, target, next)); + break; + default: winston.warn('[build] Unknown build target: \'' + target + '\''); setImmediate(next); diff --git a/src/meta/osd.js b/src/meta/osd.js new file mode 100644 index 0000000000..35a614b6b7 --- /dev/null +++ b/src/meta/osd.js @@ -0,0 +1,41 @@ +'use strict'; + +var path = require('path'); +var xml = require('xml'); +var fs = require('fs'); +var nconf = require('nconf'); + +var osdFilePath = path.join(__dirname, '../../build/public/osd.xml'); + +module.exports = function(Meta){ + Meta.osd = {}; + Meta.osd.build = function(callback){ + var osdObject = { + "OpenSearchDescription": [ + { + _attr: { + "xmlns": "http://a9.com/-/spec/opensearch/1.1/" + } + }, + { + "ShortName": String(Meta.config.title || Meta.config.browserTitle || 'NodeBB') + }, + { + "Description": String(Meta.config.description || '') + }, + { + "Url": [ + { + _attr: { + "type": "text/html", + "method": "get", + "template": nconf.get('url') + '/search?term={searchTerms}&in=titlesposts' + } + } + ] + } + ] + }; + fs.writeFile(osdFilePath, xml([osdObject], {declaration: true, indent: '\t'}), callback); + } +}; \ No newline at end of file diff --git a/src/meta/tags.js b/src/meta/tags.js index fa88fa30b8..4655c2b555 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -60,6 +60,14 @@ module.exports = function (Meta) { rel: 'manifest', href: nconf.get('relative_path') + '/manifest.json', }]; + + if(plugins.hasListeners('filter:search.query')){ + defaultLinks.push({ + rel: 'search', + type: 'application/opensearchdescription+xml', + href: nconf.get('relative_path') + '/assets/osd.xml', + }); + } // Touch icons for mobile-devices if (Meta.config['brand:touchIcon']) { From 431f5e1f0f451d4c1e0785440fa10d9a8397e094 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Sun, 5 Mar 2017 14:56:50 +0100 Subject: [PATCH 27/87] Add missing dependency This deoendency is required by rss, but travis doesn't seem to notice that --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index d4bb4e53b5..9889f45992 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "underscore.deep": "^0.5.1", "validator": "^6.1.0", "winston": "^2.1.0", + "xml": "^1.0.1", "xregexp": "~3.1.0" }, "devDependencies": { From eca150f39293e3d9c8743e8ad03b956a137aa1c5 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Sun, 5 Mar 2017 15:10:35 +0100 Subject: [PATCH 28/87] Fix Tests --- src/meta/build.js | 2 +- src/meta/osd.js | 26 +++++++++++++------------- src/meta/tags.js | 12 ++++++------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/meta/build.js b/src/meta/build.js index cba910bf83..2c82459777 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -122,7 +122,7 @@ exports.buildTargets = function (targets, callback) { startTime = Date.now(); meta.osd.build(step.bind(this, startTime, target, next)); break; - + default: winston.warn('[build] Unknown build target: \'' + target + '\''); setImmediate(next); diff --git a/src/meta/osd.js b/src/meta/osd.js index 35a614b6b7..3928c535c3 100644 --- a/src/meta/osd.js +++ b/src/meta/osd.js @@ -7,35 +7,35 @@ var nconf = require('nconf'); var osdFilePath = path.join(__dirname, '../../build/public/osd.xml'); -module.exports = function(Meta){ +module.exports = function (Meta) { Meta.osd = {}; - Meta.osd.build = function(callback){ + Meta.osd.build = function (callback) { var osdObject = { - "OpenSearchDescription": [ + OpenSearchDescription: [ { _attr: { - "xmlns": "http://a9.com/-/spec/opensearch/1.1/" + xmlns: 'http://a9.com/-/spec/opensearch/1.1/', } }, { - "ShortName": String(Meta.config.title || Meta.config.browserTitle || 'NodeBB') + ShortName: String(Meta.config.title || Meta.config.browserTitle || 'NodeBB'), }, { - "Description": String(Meta.config.description || '') + Description: String(Meta.config.description || ''), }, { - "Url": [ + Url: [ { _attr: { - "type": "text/html", - "method": "get", - "template": nconf.get('url') + '/search?term={searchTerms}&in=titlesposts' + type: 'text/html', + method: 'get', + template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', } } ] } ] }; - fs.writeFile(osdFilePath, xml([osdObject], {declaration: true, indent: '\t'}), callback); - } -}; \ No newline at end of file + fs.writeFile(osdFilePath, xml([osdObject], { declaration: true, indent: '\t' }), callback); + }; +}; diff --git a/src/meta/tags.js b/src/meta/tags.js index 4655c2b555..d54a07addc 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -60,13 +60,13 @@ module.exports = function (Meta) { rel: 'manifest', href: nconf.get('relative_path') + '/manifest.json', }]; - - if(plugins.hasListeners('filter:search.query')){ + + if (plugins.hasListeners('filter:search.query')) { defaultLinks.push({ - rel: 'search', - type: 'application/opensearchdescription+xml', - href: nconf.get('relative_path') + '/assets/osd.xml', - }); + rel: 'search', + type: 'application/opensearchdescription+xml', + href: nconf.get('relative_path') + '/assets/osd.xml', + }); } // Touch icons for mobile-devices From 0710c6488daa0b802c39cff22cd97a00b9777ce1 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Sun, 5 Mar 2017 15:15:41 +0100 Subject: [PATCH 29/87] Fix trailing commas --- src/meta/osd.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/meta/osd.js b/src/meta/osd.js index 3928c535c3..dd11ff5d44 100644 --- a/src/meta/osd.js +++ b/src/meta/osd.js @@ -15,7 +15,7 @@ module.exports = function (Meta) { { _attr: { xmlns: 'http://a9.com/-/spec/opensearch/1.1/', - } + }, }, { ShortName: String(Meta.config.title || Meta.config.browserTitle || 'NodeBB'), @@ -30,11 +30,11 @@ module.exports = function (Meta) { type: 'text/html', method: 'get', template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', - } - } + }, + }, ] - } - ] + }, + ], }; fs.writeFile(osdFilePath, xml([osdObject], { declaration: true, indent: '\t' }), callback); }; From 4082ca40c12fbb6f12909340f6dfe9a4f4e5ce0d Mon Sep 17 00:00:00 2001 From: RoiEX Date: Sun, 5 Mar 2017 15:20:35 +0100 Subject: [PATCH 30/87] Add last comma --- src/meta/osd.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/osd.js b/src/meta/osd.js index dd11ff5d44..05763baf50 100644 --- a/src/meta/osd.js +++ b/src/meta/osd.js @@ -32,7 +32,7 @@ module.exports = function (Meta) { template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', }, }, - ] + ], }, ], }; From 531df7bd09527790e1eeb4f663718f2e013d84b0 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 6 Mar 2017 13:54:43 +0300 Subject: [PATCH 31/87] closes #5504 --- public/src/client/account/edit.js | 33 +++++++++++++++++++------------ src/user/picture.js | 7 +------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/public/src/client/account/edit.js b/public/src/client/account/edit.js index 20e85bb46b..13b84e3023 100644 --- a/public/src/client/account/edit.js +++ b/public/src/client/account/edit.js @@ -241,22 +241,29 @@ define('forum/account/edit', ['forum/account/header', 'translator', 'components' uploadModal.find('.upload-btn').on('click', function () { var url = uploadModal.find('#uploadFromUrl').val(); if (!url) { - return; + return false; } - - uploadModal.modal('hide'); - - pictureCropper.handleImageCrop({ + socket.emit('user.uploadProfileImageFromUrl', { + uid: ajaxify.data.uid, url: url, - socketMethod: 'user.uploadCroppedPicture', - aspectRatio: '1 / 1', - allowSkippingCrop: false, - restrictImageDimension: true, - imageDimension: ajaxify.data.profileImageDimension, - paramName: 'uid', - paramValue: ajaxify.data.theirid, - }, onUploadComplete); + }, function (err, url) { + if (err) { + return app.alertError(err); + } + uploadModal.modal('hide'); + + pictureCropper.handleImageCrop({ + url: url, + socketMethod: 'user.uploadCroppedPicture', + aspectRatio: '1 / 1', + allowSkippingCrop: false, + restrictImageDimension: true, + imageDimension: ajaxify.data.profileImageDimension, + paramName: 'uid', + paramValue: ajaxify.data.theirid, + }, onUploadComplete); + }); return false; }); }); diff --git a/src/user/picture.js b/src/user/picture.js index be489364b9..13288774b2 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -50,12 +50,7 @@ module.exports = function (User) { }, next); }, function (image, next) { - User.setUserFields(uid, { - uploadedpicture: image.url, - picture: image.url, - }, function (err) { - next(err, image); - }); + next(null, image); }, ], callback); }; From f6721c249feb446d8b86faee44f57f7d050bad80 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 6 Mar 2017 14:37:46 +0300 Subject: [PATCH 32/87] up composer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d4bb4e53b5..def5528d63 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.4.1", + "nodebb-plugin-composer-default": "4.4.2", "nodebb-plugin-dbsearch": "1.0.5", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", From 743a7e00b4b0c88489bbc98dc88ca4dbfe060b33 Mon Sep 17 00:00:00 2001 From: Accalia de Elementia Date: Mon, 6 Mar 2017 13:14:15 +0000 Subject: [PATCH 33/87] fix: auto cropper on image upload selects maximum image area by default --- public/src/modules/pictureCropper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/src/modules/pictureCropper.js b/public/src/modules/pictureCropper.js index 0ed62d3d8c..bbe0a76735 100644 --- a/public/src/modules/pictureCropper.js +++ b/public/src/modules/pictureCropper.js @@ -42,6 +42,7 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe var img = document.getElementById('cropped-image'); var cropperTool = new cropper.default(img, { aspectRatio: data.aspectRatio, + autoCropArea: 1, viewMode: 1, cropmove: function () { if (data.restrictImageDimension) { From 8226fd6566d04464684c859c13f8e8684c17a11b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 6 Mar 2017 09:40:11 -0500 Subject: [PATCH 34/87] enhancing:tm: post reply length checker to account for line breaks in redactor --- src/topics/create.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topics/create.js b/src/topics/create.js index 815d703d01..da16489f05 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -329,7 +329,7 @@ module.exports = function (Topics) { function check(item, min, max, minError, maxError, callback) { // Trim and remove HTML (latter for composers that send in HTML, like redactor) if (typeof item === 'string') { - item = S(item.trim()).stripTags().s; + item = S(item).stripTags().s.trim(); } if (!item || item.length < parseInt(min, 10)) { From f0d9bddd56eeb9ad1b7d8db13e194defe6ae6dbf Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 6 Mar 2017 09:58:02 -0500 Subject: [PATCH 35/87] fix merge conflict artifact that was left in --- src/user/data.js | 52 ++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/user/data.js b/src/user/data.js index 22810999f8..3377d5370e 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -64,28 +64,24 @@ module.exports = function (User) { addField('lastonline'); } -<<<<<<< HEAD - db.getObjectsFields(keys, fields, function (err, users) { - if (err) { - return callback(err); - } - - users = uids.map(function (uid) { - return users[ref[uid]]; - }); - - modifyUserData(users, fieldsToRemove, callback); - }); -======= async.waterfall([ function (next) { - db.getObjectsFields(keys, fields, next); + db.getObjectsFields(keys, fields, function (err, users) { + if (err) { + return callback(err); + } + + users = uids.map(function (uid) { + return users[ref[uid]]; + }); + + next(null, users); + }); }, function (users, next) { modifyUserData(users, fieldsToRemove, next); }, ], callback); ->>>>>>> master }; User.getMultipleUserFields = function (uids, fields, callback) { @@ -117,28 +113,24 @@ module.exports = function (User) { return 'user:' + uid; }); -<<<<<<< HEAD - db.getObjects(keys, function (err, users) { - if (err) { - return callback(err); - } - - users = uids.map(function (uid) { - return users[ref[uid]]; - }); - - modifyUserData(users, [], callback); - }); -======= async.waterfall([ function (next) { - db.getObjects(keys, next); + db.getObjects(keys, function (err, users) { + if (err) { + return callback(err); + } + + users = uids.map(function (uid) { + return users[ref[uid]]; + }); + + next(null, users); + }); }, function (users, next) { modifyUserData(users, [], next); }, ], callback); ->>>>>>> master }; function modifyUserData(users, fieldsToRemove, callback) { From 603d45fbfa493ed78c60c438a083accc38b6bb90 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 19:47:03 +0100 Subject: [PATCH 36/87] Changed Serving Method --- src/meta/build.js | 8 +------- src/meta/osd.js | 14 +++++++++----- src/meta/tags.js | 2 +- src/routes/meta.js | 1 + 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/meta/build.js b/src/meta/build.js index 2c82459777..9ba5ec89bf 100644 --- a/src/meta/build.js +++ b/src/meta/build.js @@ -5,7 +5,7 @@ var winston = require('winston'); var buildStart; -var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound', 'osd']; +var valid = ['js', 'clientCSS', 'acpCSS', 'tpl', 'lang', 'sound']; exports.buildAll = function (callback) { exports.build(valid.join(','), callback); @@ -117,12 +117,6 @@ exports.buildTargets = function (targets, callback) { meta.sounds.build(step.bind(this, startTime, target, next)); break; - case 'osd': - winston.info('[build] Building OpenSearchDocument XML'); - startTime = Date.now(); - meta.osd.build(step.bind(this, startTime, target, next)); - break; - default: winston.warn('[build] Unknown build target: \'' + target + '\''); setImmediate(next); diff --git a/src/meta/osd.js b/src/meta/osd.js index 05763baf50..4111a463d0 100644 --- a/src/meta/osd.js +++ b/src/meta/osd.js @@ -1,15 +1,13 @@ 'use strict'; -var path = require('path'); var xml = require('xml'); -var fs = require('fs'); var nconf = require('nconf'); -var osdFilePath = path.join(__dirname, '../../build/public/osd.xml'); +var plugins = require('../plugins'); module.exports = function (Meta) { Meta.osd = {}; - Meta.osd.build = function (callback) { + function generateXML () { var osdObject = { OpenSearchDescription: [ { @@ -36,6 +34,12 @@ module.exports = function (Meta) { }, ], }; - fs.writeFile(osdFilePath, xml([osdObject], { declaration: true, indent: '\t' }), callback); + return xml([osdObject], { declaration: true, indent: '\t' }); + } + Meta.osd.handleOSDRequest = function (req, res, next) { + if (plugins.hasListeners('filter:search.query')) { + res.type('application/xml').send(generateXML()); + } + next(); }; }; diff --git a/src/meta/tags.js b/src/meta/tags.js index d54a07addc..5b1097d427 100644 --- a/src/meta/tags.js +++ b/src/meta/tags.js @@ -65,7 +65,7 @@ module.exports = function (Meta) { defaultLinks.push({ rel: 'search', type: 'application/opensearchdescription+xml', - href: nconf.get('relative_path') + '/assets/osd.xml', + href: nconf.get('relative_path') + '/osd.xml', }); } diff --git a/src/routes/meta.js b/src/routes/meta.js index cfeeac5b9b..0d97468c84 100644 --- a/src/routes/meta.js +++ b/src/routes/meta.js @@ -8,4 +8,5 @@ module.exports = function (app, middleware, controllers) { app.get('/robots.txt', controllers.robots); app.get('/manifest.json', controllers.manifest); app.get('/css/previews/:theme', controllers.admin.themes.get); + app.get('/osd.xml', require('../meta').osd.handleOSDRequest); }; From 710377ceeac4dc786dd1fa3c3e4ad4ddedccd0ff Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 19:51:53 +0100 Subject: [PATCH 37/87] Remove space --- src/meta/osd.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/osd.js b/src/meta/osd.js index 4111a463d0..5db9578047 100644 --- a/src/meta/osd.js +++ b/src/meta/osd.js @@ -7,7 +7,7 @@ var plugins = require('../plugins'); module.exports = function (Meta) { Meta.osd = {}; - function generateXML () { + function generateXML() { var osdObject = { OpenSearchDescription: [ { From 186c1f40e9a6fe8e08d308484c65c25859ee1ef9 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 20:07:06 +0100 Subject: [PATCH 38/87] Refactor Code --- src/controllers/index.js | 1 + src/controllers/osd.js | 44 +++++++++++++++++++++++++++++++++++++++ src/meta.js | 1 - src/meta/osd.js | 45 ---------------------------------------- src/routes/meta.js | 2 +- 5 files changed, 46 insertions(+), 47 deletions(-) create mode 100644 src/controllers/osd.js delete mode 100644 src/meta/osd.js diff --git a/src/controllers/index.js b/src/controllers/index.js index 49c1c4c2c2..a02e3c9fbd 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -32,6 +32,7 @@ Controllers.mods = require('./mods'); Controllers.sitemap = require('./sitemap'); Controllers['404'] = require('./404'); Controllers.errors = require('./errors'); +Controllers.handleOSDRequest = require('./osd'); Controllers.home = function (req, res, next) { var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories'; diff --git a/src/controllers/osd.js b/src/controllers/osd.js new file mode 100644 index 0000000000..b05fac72c9 --- /dev/null +++ b/src/controllers/osd.js @@ -0,0 +1,44 @@ +'use strict'; + +var xml = require('xml'); +var nconf = require('nconf'); + +var plugins = require('../plugins'); +var meta = require('../meta'); + +module.exports = function (req, res, next) { + if (plugins.hasListeners('filter:search.query')) { + res.type('application/xml').send(generateXML()); + } else { + next(); + } +}; + +function generateXML() { + return xml([{ + OpenSearchDescription: [ + { + _attr: { + xmlns: 'http://a9.com/-/spec/opensearch/1.1/', + }, + }, + { + ShortName: String(meta.config.title || meta.config.browserTitle || 'NodeBB'), + }, + { + Description: String(meta.config.description || ''), + }, + { + Url: [ + { + _attr: { + type: 'text/html', + method: 'get', + template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', + }, + }, + ], + }, + ], + }], { declaration: true, indent: '\t' }); +} diff --git a/src/meta.js b/src/meta.js index f0c1756053..6cbccd70a1 100644 --- a/src/meta.js +++ b/src/meta.js @@ -21,7 +21,6 @@ var utils = require('../public/src/utils'); require('./meta/errors')(Meta); require('./meta/tags')(Meta); require('./meta/dependencies')(Meta); - require('./meta/osd')(Meta); Meta.templates = require('./meta/templates'); Meta.blacklist = require('./meta/blacklist'); Meta.languages = require('./meta/languages'); diff --git a/src/meta/osd.js b/src/meta/osd.js deleted file mode 100644 index 5db9578047..0000000000 --- a/src/meta/osd.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -var xml = require('xml'); -var nconf = require('nconf'); - -var plugins = require('../plugins'); - -module.exports = function (Meta) { - Meta.osd = {}; - function generateXML() { - var osdObject = { - OpenSearchDescription: [ - { - _attr: { - xmlns: 'http://a9.com/-/spec/opensearch/1.1/', - }, - }, - { - ShortName: String(Meta.config.title || Meta.config.browserTitle || 'NodeBB'), - }, - { - Description: String(Meta.config.description || ''), - }, - { - Url: [ - { - _attr: { - type: 'text/html', - method: 'get', - template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', - }, - }, - ], - }, - ], - }; - return xml([osdObject], { declaration: true, indent: '\t' }); - } - Meta.osd.handleOSDRequest = function (req, res, next) { - if (plugins.hasListeners('filter:search.query')) { - res.type('application/xml').send(generateXML()); - } - next(); - }; -}; diff --git a/src/routes/meta.js b/src/routes/meta.js index 0d97468c84..e17533af05 100644 --- a/src/routes/meta.js +++ b/src/routes/meta.js @@ -8,5 +8,5 @@ module.exports = function (app, middleware, controllers) { app.get('/robots.txt', controllers.robots); app.get('/manifest.json', controllers.manifest); app.get('/css/previews/:theme', controllers.admin.themes.get); - app.get('/osd.xml', require('../meta').osd.handleOSDRequest); + app.get('/osd.xml', controllers.handleOSDRequest); }; From f1b4a9b786846c5c9aab447fb01bfaca92e75087 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 20:23:34 +0100 Subject: [PATCH 39/87] Shrink JSON --- src/controllers/osd.js | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/controllers/osd.js b/src/controllers/osd.js index b05fac72c9..b93a0cbcce 100644 --- a/src/controllers/osd.js +++ b/src/controllers/osd.js @@ -17,27 +17,15 @@ module.exports = function (req, res, next) { function generateXML() { return xml([{ OpenSearchDescription: [ - { + { _attr: { xmlns: 'http://a9.com/-/spec/opensearch/1.1/' }}, + { ShortName: String(meta.config.title || meta.config.browserTitle || 'NodeBB') }, + { Description: String(meta.config.description || '') }, + { Url: [{ _attr: { - xmlns: 'http://a9.com/-/spec/opensearch/1.1/', - }, - }, - { - ShortName: String(meta.config.title || meta.config.browserTitle || 'NodeBB'), - }, - { - Description: String(meta.config.description || ''), - }, - { - Url: [ - { - _attr: { - type: 'text/html', - method: 'get', - template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', - }, - }, - ], + type: 'text/html', + method: 'get', + template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts' }, + }], }, ], }], { declaration: true, indent: '\t' }); From 0f9530bbc088d8a301d626062ddf6e85e4c93ebd Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 20:24:00 +0100 Subject: [PATCH 40/87] Shrink JSON fix --- src/controllers/osd.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/osd.js b/src/controllers/osd.js index b93a0cbcce..cafbaccda3 100644 --- a/src/controllers/osd.js +++ b/src/controllers/osd.js @@ -24,9 +24,9 @@ function generateXML() { _attr: { type: 'text/html', method: 'get', - template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts' }, - }], - }, + template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', + }, + }]}, ], }], { declaration: true, indent: '\t' }); } From fcda83205230b96d96f68d6a1a2c1b6d212bddf4 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 20:39:26 +0100 Subject: [PATCH 41/87] Add spaces --- src/controllers/osd.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/osd.js b/src/controllers/osd.js index cafbaccda3..13d9b659d5 100644 --- a/src/controllers/osd.js +++ b/src/controllers/osd.js @@ -17,7 +17,7 @@ module.exports = function (req, res, next) { function generateXML() { return xml([{ OpenSearchDescription: [ - { _attr: { xmlns: 'http://a9.com/-/spec/opensearch/1.1/' }}, + { _attr: { xmlns: 'http://a9.com/-/spec/opensearch/1.1/' } }, { ShortName: String(meta.config.title || meta.config.browserTitle || 'NodeBB') }, { Description: String(meta.config.description || '') }, { Url: [{ @@ -26,7 +26,7 @@ function generateXML() { method: 'get', template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', }, - }]}, + }] }, ], }], { declaration: true, indent: '\t' }); } From 6420e224532e50f27c138bf1eb08aeca0a25570c Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 20:43:06 +0100 Subject: [PATCH 42/87] Remove uneccessary curly brackets --- src/controllers/osd.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/osd.js b/src/controllers/osd.js index 13d9b659d5..01bb474033 100644 --- a/src/controllers/osd.js +++ b/src/controllers/osd.js @@ -20,13 +20,13 @@ function generateXML() { { _attr: { xmlns: 'http://a9.com/-/spec/opensearch/1.1/' } }, { ShortName: String(meta.config.title || meta.config.browserTitle || 'NodeBB') }, { Description: String(meta.config.description || '') }, - { Url: [{ + { Url: { _attr: { type: 'text/html', method: 'get', template: nconf.get('url') + '/search?term={searchTerms}&in=titlesposts', }, - }] }, + } }, ], }], { declaration: true, indent: '\t' }); } From 33e474759b5328b800f7263413e500c2b0f0b6b1 Mon Sep 17 00:00:00 2001 From: RoiEX Date: Mon, 6 Mar 2017 21:00:20 +0100 Subject: [PATCH 43/87] Change var names --- src/controllers/index.js | 2 +- src/controllers/osd.js | 2 +- src/routes/meta.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index a02e3c9fbd..a3b9361651 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -30,9 +30,9 @@ Controllers.admin = require('./admin'); Controllers.globalMods = require('./globalmods'); Controllers.mods = require('./mods'); Controllers.sitemap = require('./sitemap'); +Controllers.osd = require('./osd'); Controllers['404'] = require('./404'); Controllers.errors = require('./errors'); -Controllers.handleOSDRequest = require('./osd'); Controllers.home = function (req, res, next) { var route = meta.config.homePageRoute || (meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories'; diff --git a/src/controllers/osd.js b/src/controllers/osd.js index 01bb474033..c83f7f142c 100644 --- a/src/controllers/osd.js +++ b/src/controllers/osd.js @@ -6,7 +6,7 @@ var nconf = require('nconf'); var plugins = require('../plugins'); var meta = require('../meta'); -module.exports = function (req, res, next) { +module.exports.handle = function (req, res, next) { if (plugins.hasListeners('filter:search.query')) { res.type('application/xml').send(generateXML()); } else { diff --git a/src/routes/meta.js b/src/routes/meta.js index e17533af05..de0bb52406 100644 --- a/src/routes/meta.js +++ b/src/routes/meta.js @@ -8,5 +8,5 @@ module.exports = function (app, middleware, controllers) { app.get('/robots.txt', controllers.robots); app.get('/manifest.json', controllers.manifest); app.get('/css/previews/:theme', controllers.admin.themes.get); - app.get('/osd.xml', controllers.handleOSDRequest); + app.get('/osd.xml', controllers.osd.handle); }; From 992e3128bdb7772b1bdc10e2cf746b0bf6d593db Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 13:09:14 +0300 Subject: [PATCH 44/87] closes #5499 --- public/src/client/account/settings.js | 5 ++++- src/controllers/accounts/settings.js | 1 + src/controllers/api.js | 5 +++-- src/middleware/header.js | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index eba41faf9c..bffefc725f 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -49,9 +49,12 @@ define('forum/account/settings', ['forum/account/header', 'components', 'sounds' function changePageSkin(skinName) { var css = $('#bootswatchCSS'); - if (skinName === 'default') { + if (skinName === 'noskin' || (skinName === 'default' && config.defaultBootswatchSkin === 'noskin')) { css.remove(); } else { + if (skinName === 'default') { + skinName = config.defaultBootswatchSkin; + } var cssSource = '//maxcdn.bootstrapcdn.com/bootswatch/latest/' + skinName + '/bootstrap.min.css'; if (css.length) { css.attr('href', cssSource); diff --git a/src/controllers/accounts/settings.js b/src/controllers/accounts/settings.js index edd5d58282..2f360f883c 100644 --- a/src/controllers/accounts/settings.js +++ b/src/controllers/accounts/settings.js @@ -108,6 +108,7 @@ settingsController.get = function (req, res, callback) { userData.bootswatchSkinOptions = [ + { name: 'No skin', value: 'noskin' }, { name: 'Default', value: 'default' }, { name: 'Cerulean', value: 'cerulean' }, { name: 'Cosmo', value: 'cosmo' }, diff --git a/src/controllers/api.js b/src/controllers/api.js index cfa78a6908..5ddbd0731b 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -61,7 +61,8 @@ apiController.getConfig = function (req, res, next) { config.categoryTopicSort = meta.config.categoryTopicSort || 'newest_to_oldest'; config.csrf_token = req.csrfToken(); config.searchEnabled = plugins.hasListeners('filter:search.query'); - config.bootswatchSkin = meta.config.bootswatchSkin || 'default'; + config.bootswatchSkin = meta.config.bootswatchSkin || 'noskin'; + config.defaultBootswatchSkin = meta.config.bootswatchSkin || 'noskin'; var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff; config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff; @@ -90,7 +91,7 @@ apiController.getConfig = function (req, res, next) { config.categoryTopicSort = settings.categoryTopicSort || config.categoryTopicSort; config.topicSearchEnabled = settings.topicSearchEnabled || false; config.delayImageLoading = settings.delayImageLoading !== undefined ? settings.delayImageLoading : true; - config.bootswatchSkin = settings.bootswatchSkin || config.bootswatchSkin; + config.bootswatchSkin = (settings.bootswatchSkin && settings.bootswatchSkin !== 'default') ? settings.bootswatchSkin : config.bootswatchSkin; plugins.fireHook('filter:config.get', config, next); }, ], function (err, config) { diff --git a/src/middleware/header.js b/src/middleware/header.js index 44a541f4bf..e5d4de66bb 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -190,12 +190,12 @@ module.exports = function (middleware) { } function setBootswatchCSS(obj, config) { - if (config && config.bootswatchSkin !== 'default') { + if (config && config.bootswatchSkin !== 'noskin') { var skinToUse = ''; if (parseInt(meta.config.disableCustomUserSkins, 10) !== 1) { skinToUse = config.bootswatchSkin; - } else if (meta.config.bootswatchSkin !== 'default') { + } else if (meta.config.bootswatchSkin) { skinToUse = meta.config.bootswatchSkin; } From 69c6ee5834f9f0c384a31b48d77014ce91b3b0bb Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 13:38:31 +0300 Subject: [PATCH 45/87] closes #5483 --- public/src/ajaxify.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 12ebff1540..a8d4085c85 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -102,7 +102,8 @@ $(document).ready(function () { url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase(); var isClientToAdmin = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0; var isAdminToClient = !url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') === 0; - var uploadsOrApi = url.startsWith('uploads') || url.startsWith('api'); + var uploadsOrApi = url.startsWith('assets/uploads') || url.startsWith('uploads') || url.startsWith('api'); + if (isClientToAdmin || isAdminToClient || uploadsOrApi) { window.open(RELATIVE_PATH + '/' + url, '_top'); return true; From b7e6104bbc5bc802ad29be1a236139fc3c2daa64 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 15:59:50 +0300 Subject: [PATCH 46/87] closes #5511 --- src/controllers/uploads.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js index 7a309d5200..82556ee12c 100644 --- a/src/controllers/uploads.js +++ b/src/controllers/uploads.js @@ -122,7 +122,7 @@ function resizeImage(fileObj, callback) { var extname = path.extname(fileObj.url); var basename = path.basename(fileObj.url, extname); - fileObj.url = path.join(dirname, basename + '-resized' + extname); + fileObj.url = dirname + '/' + basename + '-resized' + extname; next(null, fileObj); }, From 8a123f7c1c78b94632f4df952591a2db4f37bb85 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 16:13:09 +0300 Subject: [PATCH 47/87] closes #5509 --- public/src/ajaxify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index a8d4085c85..913dbb6fd6 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -99,7 +99,7 @@ $(document).ready(function () { }; ajaxify.handleRedirects = function (url) { - url = ajaxify.removeRelativePath(url.replace(/\/$/, '')).toLowerCase(); + url = ajaxify.removeRelativePath(url.replace(/^\/|\/$/g, '')).toLowerCase(); var isClientToAdmin = url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') !== 0; var isAdminToClient = !url.startsWith('admin') && window.location.pathname.indexOf(RELATIVE_PATH + '/admin') === 0; var uploadsOrApi = url.startsWith('assets/uploads') || url.startsWith('uploads') || url.startsWith('api'); From 1301fb1f340d2f2a0fb7dae43809d480aa57c66f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 19:51:52 +0300 Subject: [PATCH 48/87] on chat leave close the modal --- public/src/client/chats.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/src/client/chats.js b/public/src/client/chats.js index c42a8e9330..582c578f65 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -282,11 +282,17 @@ define('forum/chats', [ if (err) { return app.alertError(err.message); } - if (parseInt(roomId, 10) === ajaxify.data.roomId) { + if (parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10)) { ajaxify.go('user/' + ajaxify.data.userslug + '/chats'); } else { el.remove(); } + require(['chat'], function (chat) { + var modal = chat.getModal(roomId); + if (modal.length) { + chat.close(modal); + } + }); }); }; From e7ced2457ecc1f2a781f41578eabad453154e924 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 20:26:49 +0300 Subject: [PATCH 49/87] clone query object --- src/pagination.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pagination.js b/src/pagination.js index 3e57c5b201..3494da3047 100644 --- a/src/pagination.js +++ b/src/pagination.js @@ -1,6 +1,7 @@ 'use strict'; var qs = require('querystring'); +var _ = require('underscore'); var pagination = {}; @@ -37,10 +38,10 @@ pagination.create = function (currentPage, pageCount, queryObj) { return a - b; }); - queryObj = queryObj || {}; - delete queryObj._; + queryObj = _.clone(queryObj || {}); + var pages = pagesToShow.map(function (page) { queryObj.page = page; return { page: page, active: page === currentPage, qs: qs.stringify(queryObj) }; From 743a3fb4421672d241a0aa49eb6cdc1c238f73be Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 7 Mar 2017 20:58:43 +0300 Subject: [PATCH 50/87] fix test --- src/pagination.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pagination.js b/src/pagination.js index 3494da3047..4058e7f8f4 100644 --- a/src/pagination.js +++ b/src/pagination.js @@ -38,10 +38,10 @@ pagination.create = function (currentPage, pageCount, queryObj) { return a - b; }); - delete queryObj._; - queryObj = _.clone(queryObj || {}); + delete queryObj._; + var pages = pagesToShow.map(function (page) { queryObj.page = page; return { page: page, active: page === currentPage, qs: qs.stringify(queryObj) }; From 1102ca8ebb2d4c20b76b09dc01353be6e388674a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 7 Mar 2017 14:35:38 -0500 Subject: [PATCH 51/87] fix issue where an auto-redirect to SSO on login route was failing if invoked via API call (ajaxify) --- src/controllers/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/index.js b/src/controllers/index.js index a3b9361651..93b8e9a383 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -131,7 +131,7 @@ Controllers.login = function (req, res, next) { if (!data.allowLocalLogin && !data.allowRegistration && data.alternate_logins && data.authentication.length === 1) { if (res.locals.isAPI) { return helpers.redirect(res, { - external: data.authentication[0].url, + external: nconf.get('relative_path') + data.authentication[0].url, }); } return res.redirect(nconf.get('relative_path') + data.authentication[0].url); From bacbb6c75f6e6097f324a414f6332d16bbd11473 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 7 Mar 2017 20:16:59 -0500 Subject: [PATCH 52/87] holy moly it took me an hour to find this missing method that got removed in a bad merge conflict resolution... --- src/user.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/user.js b/src/user.js index 48098feadf..d521b77f0d 100644 --- a/src/user.js +++ b/src/user.js @@ -202,6 +202,16 @@ User.isGlobalModerator = function (uid, callback) { privileges.users.isGlobalModerator(uid, callback); }; +User.isPrivileged = function (uid, callback) { + async.parallel([ + async.apply(User.isAdministrator, uid), + async.apply(User.isGlobalModerator, uid), + async.apply(User.isModeratorOfAnyCategory, uid), + ], function (err, results) { + callback(err, results ? results.some(Boolean) : false); + }); +}; + User.isAdminOrGlobalMod = function (uid, callback) { async.parallel({ isAdmin: async.apply(User.isAdministrator, uid), From 04b66787ea6d04d2ec06253456d25357b8c2a135 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 7 Mar 2017 20:53:54 -0500 Subject: [PATCH 53/87] removed hardcoded stepsize of 1 for chart, so it can space the ticks out properly --- public/src/client/flags/list.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/src/client/flags/list.js b/public/src/client/flags/list.js index 4c516eac38..0806195ac4 100644 --- a/public/src/client/flags/list.js +++ b/public/src/client/flags/list.js @@ -76,7 +76,6 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart) yAxes: [{ ticks: { beginAtZero: true, - stepSize: 1, }, }], }, From 92ad6bb4e8daa2321bf450bf88daec4be669d388 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Wed, 8 Mar 2017 09:22:29 +0000 Subject: [PATCH 54/87] Latest translations and fallbacks --- public/language/sr/user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/language/sr/user.json b/public/language/sr/user.json index 08c61c887c..62a64140a7 100644 --- a/public/language/sr/user.json +++ b/public/language/sr/user.json @@ -31,7 +31,7 @@ "signature": "Потпис", "birthday": "Рођендан", "chat": "Ђаскање", - "chat_with": "Настави ћаскање са %1", + "chat_with": "Ћаскај са %1", "new_chat_with": "Започни ново ћаскање са %1", "flag-profile": "Означи профил", "follow": "Прати", From 6f4950964511de35d07925a657f4f4526a2f2053 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 8 Mar 2017 15:09:58 +0300 Subject: [PATCH 55/87] fix newSet on redis --- public/src/client/chats/messages.js | 4 +++- src/messaging.js | 2 +- src/messaging/data.js | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index c8300c485c..987ac46c79 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -57,8 +57,10 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function messages.appendChatMessage = function (chatContentEl, data) { var lastSpeaker = parseInt(chatContentEl.find('.chat-message').last().attr('data-uid'), 10); + var lasttimestamp = parseInt(chatContentEl.find('.chat-message').last().attr('data-timestamp'), 10); if (!Array.isArray(data)) { - data.newSet = lastSpeaker !== data.fromuid; + data.newSet = lastSpeaker !== parseInt(data.fromuid, 10) || + parseInt(data.timestamp, 10) > parseInt(lasttimestamp, 10) + (1000 * 60 * 3); } messages.parseMessage(data, function (html) { diff --git a/src/messaging.js b/src/messaging.js index e141adff4f..e507609d81 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -102,7 +102,7 @@ Messaging.isNewSet = function (uid, roomId, timestamp, callback) { }, function (messages, next) { if (messages && messages.length) { - next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5)); + next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + Messaging.newMessageCutoff); } else { next(null, true); } diff --git a/src/messaging/data.js b/src/messaging/data.js index e3cb67fbd8..98d4dcded8 100644 --- a/src/messaging/data.js +++ b/src/messaging/data.js @@ -8,6 +8,8 @@ var user = require('../user'); var utils = require('../../public/src/utils'); module.exports = function (Messaging) { + Messaging.newMessageCutoff = 1000 * 60 * 3; + Messaging.getMessageField = function (mid, field, callback) { Messaging.getMessageFields(mid, [field], function (err, fields) { callback(err, fields ? fields[field] : null); @@ -80,7 +82,7 @@ module.exports = function (Messaging) { // Add a spacer in between messages with time gaps between them messages = messages.map(function (message, index) { // Compare timestamps with the previous message, and check if a spacer needs to be added - if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index - 1].timestamp, 10) + (1000 * 60 * 5)) { + if (index > 0 && parseInt(message.timestamp, 10) > parseInt(messages[index - 1].timestamp, 10) + Messaging.newMessageCutoff) { // If it's been 5 minutes, this is a new set of messages message.newSet = true; } else if (index > 0 && message.fromuid !== messages[index - 1].fromuid) { @@ -115,7 +117,7 @@ module.exports = function (Messaging) { } if ( - (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000 * 60 * 5)) || + (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + Messaging.newMessageCutoff) || (parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10)) ) { // If it's been 5 minutes, this is a new set of messages From 5359a14bac363b1c91b8d33af892b26bc8df5aff Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 8 Mar 2017 09:19:43 -0500 Subject: [PATCH 56/87] better handling of redactor post length calculation on post edit --- src/socket.io/posts/edit.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/socket.io/posts/edit.js b/src/socket.io/posts/edit.js index 4d5602effe..d7b51d859a 100644 --- a/src/socket.io/posts/edit.js +++ b/src/socket.io/posts/edit.js @@ -3,6 +3,7 @@ var async = require('async'); var validator = require('validator'); var _ = require('underscore'); +var S = require('string'); var posts = require('../../posts'); var groups = require('../../groups'); @@ -12,6 +13,9 @@ var websockets = require('../index'); module.exports = function (SocketPosts) { SocketPosts.edit = function (socket, data, callback) { + // Trim and remove HTML (latter for composers that send in HTML, like redactor) + var contentLen = S(data.content.length).stripTags().s.trim().length; + if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); } else if (!data || !data.pid || !data.content) { @@ -24,9 +28,9 @@ module.exports = function (SocketPosts) { return callback(new Error('[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]')); } else if (data.tags && data.tags.length > parseInt(meta.config.maximumTagsPerTopic, 10)) { return callback(new Error('[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]')); - } else if (!data.content || data.content.length < parseInt(meta.config.minimumPostLength, 10)) { + } else if (!data.content || contentLen < parseInt(meta.config.minimumPostLength, 10)) { return callback(new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]')); - } else if (data.content.length > parseInt(meta.config.maximumPostLength, 10)) { + } else if (contentLen > parseInt(meta.config.maximumPostLength, 10)) { return callback(new Error('[[error:content-too-long, ' + meta.config.maximumPostLength + ']]')); } From fdb4766728ad03983a3a72ea00fe9996ea45bde4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 8 Mar 2017 09:32:55 -0500 Subject: [PATCH 57/87] wow. --- src/socket.io/posts/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket.io/posts/edit.js b/src/socket.io/posts/edit.js index d7b51d859a..1e41c4ab61 100644 --- a/src/socket.io/posts/edit.js +++ b/src/socket.io/posts/edit.js @@ -14,7 +14,7 @@ var websockets = require('../index'); module.exports = function (SocketPosts) { SocketPosts.edit = function (socket, data, callback) { // Trim and remove HTML (latter for composers that send in HTML, like redactor) - var contentLen = S(data.content.length).stripTags().s.trim().length; + var contentLen = S(data.content).stripTags().s.trim().length; if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); From 364b819a4280346f0c07832853ff4e449713f3a4 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 8 Mar 2017 09:41:32 -0500 Subject: [PATCH 58/87] moving sanity checks above contentLen calculation --- src/socket.io/posts/edit.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/socket.io/posts/edit.js b/src/socket.io/posts/edit.js index 1e41c4ab61..5ab6973ce1 100644 --- a/src/socket.io/posts/edit.js +++ b/src/socket.io/posts/edit.js @@ -13,14 +13,16 @@ var websockets = require('../index'); module.exports = function (SocketPosts) { SocketPosts.edit = function (socket, data, callback) { - // Trim and remove HTML (latter for composers that send in HTML, like redactor) - var contentLen = S(data.content).stripTags().s.trim().length; - if (!socket.uid) { return callback(new Error('[[error:not-logged-in]]')); } else if (!data || !data.pid || !data.content) { return callback(new Error('[[error:invalid-data]]')); - } else if (data.title && data.title.length < parseInt(meta.config.minimumTitleLength, 10)) { + } + + // Trim and remove HTML (latter for composers that send in HTML, like redactor) + var contentLen = S(data.content).stripTags().s.trim().length; + + if (data.title && data.title.length < parseInt(meta.config.minimumTitleLength, 10)) { return callback(new Error('[[error:title-too-short, ' + meta.config.minimumTitleLength + ']]')); } else if (data.title && data.title.length > parseInt(meta.config.maximumTitleLength, 10)) { return callback(new Error('[[error:title-too-long, ' + meta.config.maximumTitleLength + ']]')); @@ -28,7 +30,7 @@ module.exports = function (SocketPosts) { return callback(new Error('[[error:not-enough-tags, ' + meta.config.minimumTagsPerTopic + ']]')); } else if (data.tags && data.tags.length > parseInt(meta.config.maximumTagsPerTopic, 10)) { return callback(new Error('[[error:too-many-tags, ' + meta.config.maximumTagsPerTopic + ']]')); - } else if (!data.content || contentLen < parseInt(meta.config.minimumPostLength, 10)) { + } else if (contentLen < parseInt(meta.config.minimumPostLength, 10)) { return callback(new Error('[[error:content-too-short, ' + meta.config.minimumPostLength + ']]')); } else if (contentLen > parseInt(meta.config.maximumPostLength, 10)) { return callback(new Error('[[error:content-too-long, ' + meta.config.maximumPostLength + ']]')); From 9f2e5e9c4b1bf804dc1074a2caecbbfd912e30ca Mon Sep 17 00:00:00 2001 From: psychobunny Date: Wed, 8 Mar 2017 11:31:58 -0500 Subject: [PATCH 59/87] closes #4685 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ba13271b9..82606a8e35 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "socket.io-redis": "3.1.0", "socketio-wildcard": "~0.3.0", "string": "^3.0.0", - "templates.js": "0.3.6", + "templates.js": "0.3.10", "toobusy-js": "^0.5.1", "uglify-js": "^2.6.0", "underscore": "^1.8.3", From 47af3eb7e54c843a418bb4f909e04cb400313f2c Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 8 Mar 2017 19:49:27 +0300 Subject: [PATCH 60/87] use the real count --- src/controllers/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/search.js b/src/controllers/search.js index be98589c54..4ca7937687 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -60,7 +60,7 @@ searchController.search = function (req, res, next) { var searchData = results.search; searchData.categories = categoriesData; - searchData.categoriesCount = results.categories.length; + searchData.categoriesCount = categoriesData.length; searchData.pagination = pagination.create(page, searchData.pageCount, req.query); searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts'; searchData.showAsTopics = req.query.showAs === 'topics'; From 32d37465fb3efc67182dce51a056ec146bbd0a22 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 8 Mar 2017 20:09:35 +0300 Subject: [PATCH 61/87] limit size of select --- src/controllers/search.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/search.js b/src/controllers/search.js index 4ca7937687..bf818a39d5 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -60,7 +60,7 @@ searchController.search = function (req, res, next) { var searchData = results.search; searchData.categories = categoriesData; - searchData.categoriesCount = categoriesData.length; + searchData.categoriesCount = Math.max(10, Math.min(20, categoriesData.length)); searchData.pagination = pagination.create(page, searchData.pageCount, req.query); searchData.showAsPosts = !req.query.showAs || req.query.showAs === 'posts'; searchData.showAsTopics = req.query.showAs === 'topics'; From f092b7754e82c79b9710955fa7939e016620aa29 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 8 Mar 2017 22:43:02 +0300 Subject: [PATCH 62/87] only create 1 alert for chat spam error --- public/src/client/chats/messages.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/public/src/client/chats/messages.js b/public/src/client/chats/messages.js index 987ac46c79..2a1889cf25 100644 --- a/public/src/client/chats/messages.js +++ b/public/src/client/chats/messages.js @@ -35,7 +35,14 @@ define('forum/chats/messages', ['components', 'sounds', 'translator'], function if (err.message === '[[error:email-not-confirmed-chat]]') { return app.showEmailConfirmWarning(err); } - return app.alertError(err.message); + + return app.alert({ + alert_id: 'chat_spam_error', + title: '[[global:alert.error]]', + message: err.message, + type: 'danger', + timeout: 10000, + }); } sounds.play('chat-outgoing'); From 57691d9ad8cadabc96ee00121fe5341f1b5a9c8b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 17:18:30 +0300 Subject: [PATCH 63/87] test for system group rename --- src/groups/update.js | 116 ++++++++++++++++++++++++------------------- test/groups.js | 9 ++++ 2 files changed, 74 insertions(+), 51 deletions(-) diff --git a/src/groups/update.js b/src/groups/update.js index 99e8dcc65a..95ec22753c 100644 --- a/src/groups/update.js +++ b/src/groups/update.js @@ -91,16 +91,18 @@ module.exports = function (Groups) { async.apply(db.sortedSetRemove, 'groups:visible:name', groupName.toLowerCase() + ':' + groupName), ], callback); } else { - db.getObjectFields('group:' + groupName, ['createtime', 'memberCount'], function (err, groupData) { - if (err) { - return callback(err); - } - async.parallel([ - async.apply(db.sortedSetAdd, 'groups:visible:createtime', groupData.createtime, groupName), - async.apply(db.sortedSetAdd, 'groups:visible:memberCount', groupData.memberCount, groupName), - async.apply(db.sortedSetAdd, 'groups:visible:name', 0, groupName.toLowerCase() + ':' + groupName), - ], callback); - }); + async.waterfall([ + function (next) { + db.getObjectFields('group:' + groupName, ['createtime', 'memberCount'], next); + }, + function (groupData, next) { + async.parallel([ + async.apply(db.sortedSetAdd, 'groups:visible:createtime', groupData.createtime, groupName), + async.apply(db.sortedSetAdd, 'groups:visible:memberCount', groupData.memberCount, groupName), + async.apply(db.sortedSetAdd, 'groups:visible:name', 0, groupName.toLowerCase() + ':' + groupName), + ], next); + }, + ], callback); } } @@ -155,40 +157,48 @@ module.exports = function (Groups) { function checkNameChange(currentName, newName, callback) { if (currentName === newName) { - return callback(); + return setImmediate(callback); } var currentSlug = utils.slugify(currentName); var newSlug = utils.slugify(newName); if (currentSlug === newSlug) { - return callback(); + return setImmediate(callback); } - Groups.existsBySlug(newSlug, function (err, exists) { - if (err || exists) { - return callback(err || new Error('[[error:group-already-exists]]')); - } - callback(); - }); + async.waterfall([ + function (next) { + Groups.existsBySlug(newSlug, next); + }, + function (exists, next) { + next(!exists ? new Error('[[error:group-already-exists]]') : null); + }, + ], callback); } function renameGroup(oldName, newName, callback) { if (oldName === newName || !newName || newName.length === 0) { - return callback(); + return setImmediate(callback); } + var group; + async.waterfall([ + function (next) { + db.getObject('group:' + oldName, next); + }, + function (_group, next) { + group = _group; + if (!group) { + return callback(); + } - db.getObject('group:' + oldName, function (err, group) { - if (err || !group) { - return callback(err); - } - - if (parseInt(group.system, 10) === 1) { - return callback(); - } - - Groups.exists(newName, function (err, exists) { - if (err || exists) { - return callback(err || new Error('[[error:group-already-exists]]')); + if (parseInt(group.system, 10) === 1) { + return callback(new Error('[[error:not-allowed-to-rename-system-group]]')); } + Groups.exists(newName, next); + }, + function (exists, next) { + if (exists) { + return callback(new Error('[[error:group-already-exists]]')); + } async.series([ async.apply(db.setObjectField, 'group:' + oldName, 'name', newName), async.apply(db.setObjectField, 'group:' + oldName, 'slug', utils.slugify(newName)), @@ -222,29 +232,33 @@ module.exports = function (Groups) { next(); }, - ], callback); - }); + ], next); + }, + ], function (err) { + callback(err); }); } function renameGroupMember(group, oldName, newName, callback) { - db.isSortedSetMember(group, oldName, function (err, isMember) { - if (err || !isMember) { - return callback(err); - } - var score; - async.waterfall([ - function (next) { - db.sortedSetScore(group, oldName, next); - }, - function (_score, next) { - score = _score; - db.sortedSetRemove(group, oldName, next); - }, - function (next) { - db.sortedSetAdd(group, score, newName, next); - }, - ], callback); - }); + var score; + async.waterfall([ + function (next) { + db.isSortedSetMember(group, oldName, next); + }, + function (isMember, next) { + if (!isMember) { + return callback(); + } + + db.sortedSetScore(group, oldName, next); + }, + function (_score, next) { + score = _score; + db.sortedSetRemove(group, oldName, next); + }, + function (next) { + db.sortedSetAdd(group, score, newName, next); + }, + ], callback); } }; diff --git a/test/groups.js b/test/groups.js index 4d587b3aad..6b52787efa 100644 --- a/test/groups.js +++ b/test/groups.js @@ -315,6 +315,15 @@ describe('Groups', function () { }); }); }); + + it('should fail if system groups is being renamed', function (done) { + Groups.update('administrators', { + name: 'administrators_fail', + }, function (err) { + assert.equal(err.message, '[[error:not-allowed-to-rename-system-group]]'); + done(); + }); + }); }); describe('.destroy()', function () { From c619b850ce53083ddeb9dbd932580c9e1912508f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 17:28:19 +0300 Subject: [PATCH 64/87] fix tests --- src/groups/update.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups/update.js b/src/groups/update.js index 95ec22753c..9bf53886e5 100644 --- a/src/groups/update.js +++ b/src/groups/update.js @@ -169,7 +169,7 @@ module.exports = function (Groups) { Groups.existsBySlug(newSlug, next); }, function (exists, next) { - next(!exists ? new Error('[[error:group-already-exists]]') : null); + next(exists ? new Error('[[error:group-already-exists]]') : null); }, ], callback); } From 8d493bf3b0c72faaed5ff7f70bc3282113767eaf Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 19:52:48 +0300 Subject: [PATCH 65/87] post parse test --- src/posts/parse.js | 35 +++++++++++++++-------------------- test/posts.js | 44 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/posts/parse.js b/src/posts/parse.js index 946c589056..8ac2028bbd 100644 --- a/src/posts/parse.js +++ b/src/posts/parse.js @@ -1,5 +1,6 @@ 'use strict'; +var async = require('async'); var nconf = require('nconf'); var url = require('url'); var winston = require('winston'); @@ -14,31 +15,26 @@ var urlRegex = /href="([^"]+)"/g; module.exports = function (Posts) { Posts.parsePost = function (postData, callback) { - postData.content = postData.content || ''; + postData.content = String(postData.content || ''); if (postData.pid && cache.has(String(postData.pid))) { postData.content = cache.get(String(postData.pid)); return callback(null, postData); } - // Casting post content into a string, just in case - if (typeof postData.content !== 'string') { - postData.content = postData.content.toString(); - } - - plugins.fireHook('filter:parse.post', { postData: postData }, function (err, data) { - if (err) { - return callback(err); - } - - data.postData.content = translator.escape(data.postData.content); + async.waterfall([ + function (next) { + plugins.fireHook('filter:parse.post', { postData: postData }, next); + }, + function (data, next) { + data.postData.content = translator.escape(data.postData.content); - if (global.env === 'production' && data.postData.pid) { - cache.set(String(data.postData.pid), data.postData.content); - } - - callback(null, data.postData); - }); + if (global.env === 'production' && data.postData.pid) { + cache.set(String(data.postData.pid), data.postData.content); + } + next(null, data.postData); + }, + ], callback); }; Posts.parseSignature = function (userData, uid, callback) { @@ -51,7 +47,6 @@ module.exports = function (Posts) { var parsed; var current = urlRegex.exec(content); var absolute; - while (current !== null) { if (current[1]) { try { @@ -78,7 +73,7 @@ module.exports = function (Posts) { }; function sanitizeSignature(signature) { - var string = S(signature); + var string = S(signature); var tagsToStrip = []; if (parseInt(meta.config['signatures:disableLinks'], 10) === 1) { diff --git a/test/posts.js b/test/posts.js index 55d13e1ea4..1a79200e12 100644 --- a/test/posts.js +++ b/test/posts.js @@ -743,6 +743,48 @@ describe('Post\'s', function () { }); }); + describe('parse', function () { + it('should store post content in cache', function (done) { + var oldValue = global.env; + global.env = 'production'; + var postData = { + pid: 9999, + content: 'some post content', + }; + posts.parsePost(postData, function (err) { + assert.ifError(err); + posts.parsePost(postData, function (err) { + assert.ifError(err); + global.env = oldValue; + done(); + }); + }); + }); + + it('should parse signature and remove links and images', function (done) { + var meta = require('../src/meta'); + meta.config['signatures:disableLinks'] = 1; + meta.config['signatures:disableImages'] = 1; + var userData = { + signature: 'test derp', + }; + + posts.parseSignature(userData, 1, function (err, data) { + assert.ifError(err); + assert.equal(data.userData.signature, 'test derp'); + done(); + }); + }); + + it('should turn relative links in post body to absolute urls', function (done) { + var nconf = require('nconf'); + var content = 'test youtube'; + var parsedContent = posts.relativeToAbsolute(content); + assert.equal(parsedContent, 'test youtube'); + done(); + }); + }); + describe('socket methods', function () { var pid; before(function (done) { @@ -809,7 +851,7 @@ describe('Post\'s', function () { }); it('shold error with invalid data', function (done) { - socketPosts.loadMoreBookmarks({ uid: voterUid }, { uid: voterUid, after: null }, function (err, postData) { + socketPosts.loadMoreBookmarks({ uid: voterUid }, { uid: voterUid, after: null }, function (err) { assert.equal(err.message, '[[error:invalid-data]]'); done(); }); From 3d719eec992078c35f36e9bc486633fa2d2544f2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 20:18:30 +0300 Subject: [PATCH 66/87] enable sigs after test --- test/posts.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/posts.js b/test/posts.js index 1a79200e12..49a4783872 100644 --- a/test/posts.js +++ b/test/posts.js @@ -772,6 +772,8 @@ describe('Post\'s', function () { posts.parseSignature(userData, 1, function (err, data) { assert.ifError(err); assert.equal(data.userData.signature, 'test derp'); + meta.config['signatures:disableLinks'] = 0; + meta.config['signatures:disableImages'] = 0; done(); }); }); From 74d2835a1f0d35070c2303a2e2456a3b3ca99dd9 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 20:18:42 +0300 Subject: [PATCH 67/87] escape body class parts --- src/middleware/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/render.js b/src/middleware/render.js index 48d4a0b526..88e9879887 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -123,7 +123,7 @@ module.exports = function (middleware) { winston.error(err.message); p = ''; } - + p = validator.escape(String(p)); parts[index] = index ? parts[0] + '-' + p : 'page-' + (p || 'home'); }); return parts.join(' '); From 1c1d97dd547316290d227b53f46126a846cf2153 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 21:32:43 +0300 Subject: [PATCH 68/87] up spam be gone --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82606a8e35..ee0cc57714 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "nodebb-plugin-markdown": "7.1.1", "nodebb-plugin-mentions": "1.1.3", "nodebb-plugin-soundpack-default": "1.0.0", - "nodebb-plugin-spam-be-gone": "0.4.10", + "nodebb-plugin-spam-be-gone": "0.4.13", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.15", "nodebb-theme-persona": "4.2.4", From 0c8058ba9dbb51f23ddf6ae70bdde7df02cc4492 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 9 Mar 2017 22:36:28 +0300 Subject: [PATCH 69/87] closes #5516 --- src/topics/unread.js | 4 ++-- test/topics.js | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/topics/unread.js b/src/topics/unread.js index cc3101ee05..b2f5b199d1 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -206,7 +206,7 @@ module.exports = function (Topics) { Topics.markAsRead = function (tids, uid, callback) { callback = callback || function () {}; if (!Array.isArray(tids) || !tids.length) { - return callback(); + return setImmediate(callback, null, false); } tids = tids.filter(function (tid, index, array) { @@ -214,7 +214,7 @@ module.exports = function (Topics) { }); if (!tids.length) { - return callback(null, false); + return setImmediate(callback, null, false); } async.waterfall([ diff --git a/test/topics.js b/test/topics.js index 5a65cf8ec8..2222395833 100644 --- a/test/topics.js +++ b/test/topics.js @@ -1181,6 +1181,14 @@ describe('Topic\'s', function () { }); }); }); + + it('should not do anything if tids is empty array', function (done) { + socketTopics.markAsRead({ uid: adminUid }, [], function (err, markedRead) { + assert.ifError(err); + assert(!markedRead); + done(); + }); + }); }); describe('tags', function () { From 92fa9fecb6fc552b4c632fae729b392fbba3680f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 10 Mar 2017 00:31:50 +0300 Subject: [PATCH 70/87] add mongodb connection options #3670 increase reconnect tries add keepAlive setting set autoReconnect to true, should be on by default --- src/database/mongo.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/database/mongo.js b/src/database/mongo.js index 9bc321703b..6e3af3ac64 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -84,6 +84,9 @@ var connOptions = { server: { poolSize: parseInt(nconf.get('mongo:poolSize'), 10) || 10, + socketOptions: { autoReconnect: true, keepAlive: nconf.get('mongo:keepAlive') || 0 }, + reconnectTries : 3600, + reconnectInterval : 1000, }, }; From 0c7dee05efeb910e79d9e4129784d4aa0e43b9cd Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 10 Mar 2017 00:38:34 +0300 Subject: [PATCH 71/87] fix lint --- src/database/mongo.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index 6e3af3ac64..bb3d9f05c2 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -85,8 +85,8 @@ server: { poolSize: parseInt(nconf.get('mongo:poolSize'), 10) || 10, socketOptions: { autoReconnect: true, keepAlive: nconf.get('mongo:keepAlive') || 0 }, - reconnectTries : 3600, - reconnectInterval : 1000, + reconnectTries: 3600, + reconnectInterval: 1000, }, }; From fd8e647ef5308c73c4626e93dba6a43efa8c40d9 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 9 Mar 2017 16:47:07 -0500 Subject: [PATCH 72/87] closes #5506 --- public/language/en-GB/groups.json | 1 + public/src/client/groups/details.js | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/public/language/en-GB/groups.json b/public/language/en-GB/groups.json index a55cc8603f..08c8d4d1f5 100644 --- a/public/language/en-GB/groups.json +++ b/public/language/en-GB/groups.json @@ -32,6 +32,7 @@ "details.disableJoinRequests": "Disable join requests", "details.grant": "Grant/Rescind Ownership", "details.kick": "Kick", + "details.kick_confirm": "Are you sure you want to remove this member from the group?", "details.owner_options": "Group Administration", "details.group_name": "Group Name", diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index 4f7a11a892..e7225bb16c 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -75,15 +75,23 @@ define('forum/groups/details', [ break; case 'kick': - socket.emit('groups.kick', { - uid: uid, - groupName: groupName, - }, function (err) { - if (!err) { - userRow.slideUp().remove(); - } else { - app.alertError(err.message); - } + translator.translate('[[groups:details.kick_confirm]]', function (translated) { + bootbox.confirm(translated, function (confirm) { + if (!confirm) { + return; + } + + socket.emit('groups.kick', { + uid: uid, + groupName: groupName, + }, function (err) { + if (!err) { + userRow.slideUp().remove(); + } else { + app.alertError(err.message); + } + }); + }); }); break; From 8da8e4d8c33f0e9501aeeb00c33320be3fb94bcf Mon Sep 17 00:00:00 2001 From: psychobunny Date: Thu, 9 Mar 2017 16:50:26 -0500 Subject: [PATCH 73/87] closes #5469 --- public/language/en-GB/admin/manage/flags.json | 2 +- src/views/admin/manage/flags.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/en-GB/admin/manage/flags.json b/public/language/en-GB/admin/manage/flags.json index bfc488a409..8286861d01 100644 --- a/public/language/en-GB/admin/manage/flags.json +++ b/public/language/en-GB/admin/manage/flags.json @@ -9,7 +9,7 @@ "search": "Search", "dismiss-all": "Dismiss All", "none-flagged": "No flagged posts!", - "posted-in": "Posted in %1", + "posted-in": "Posted in", "read-more": "Read More", "flagged-x-times": "This post has been flagged %1 time(s):", "dismiss": "Dismiss this Flag", diff --git a/src/views/admin/manage/flags.tpl b/src/views/admin/manage/flags.tpl index 995dd13a8f..a6351009d8 100644 --- a/src/views/admin/manage/flags.tpl +++ b/src/views/admin/manage/flags.tpl @@ -105,7 +105,7 @@ - [[posted-in, {posts.category.name}]], + [[admin/manage/flags:posted-in]] {posts.category.name} [[admin/manage/flags:read-more]] From 5e0de4b8fbed018877657fb71ea4c03bed382f88 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 10 Mar 2017 12:21:59 +0300 Subject: [PATCH 74/87] closes #5517 --- src/database/mongo.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/database/mongo.js b/src/database/mongo.js index bb3d9f05c2..8e6494ffd7 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -47,13 +47,8 @@ module.init = function (callback) { callback = callback || function () { }; - var mongoClient; - try { - mongoClient = require('mongodb').MongoClient; - } catch (err) { - winston.error('Unable to initialize MongoDB! Is MongoDB installed? Error :' + err.message); - return callback(err); - } + + var mongoClient = require('mongodb').MongoClient; var usernamePassword = ''; if (nconf.get('mongo:username') && nconf.get('mongo:password')) { @@ -90,7 +85,7 @@ }, }; - connOptions = _.deepExtend((nconf.get('mongo:options') || {}), connOptions); + connOptions = _.deepExtend(connOptions, nconf.get('mongo:options') || {}); mongoClient.connect(connString, connOptions, function (err, _db) { if (err) { @@ -110,10 +105,7 @@ if (nconf.get('mongo:password') && nconf.get('mongo:username')) { db.authenticate(nconf.get('mongo:username'), nconf.get('mongo:password'), function (err) { - if (err) { - return callback(err); - } - callback(); + callback(err); }); } else { winston.warn('You have no mongo password setup!'); From dc4d394cd5157fd4edd19ef229981372ab678b59 Mon Sep 17 00:00:00 2001 From: "Misty (Bot)" Date: Fri, 10 Mar 2017 09:22:18 +0000 Subject: [PATCH 75/87] Latest translations and fallbacks --- public/language/pl/admin/settings/group.json | 2 +- public/language/pl/admin/settings/tags.json | 2 +- public/language/pl/admin/settings/uploads.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/language/pl/admin/settings/group.json b/public/language/pl/admin/settings/group.json index 6db8cb32b4..c5900c2a39 100644 --- a/public/language/pl/admin/settings/group.json +++ b/public/language/pl/admin/settings/group.json @@ -1,5 +1,5 @@ { - "general": "General", + "general": "Ogólne", "private-groups": "Prywatne Grupy", "private-groups.help": "If enabled, joining of groups requires the approval of the group owner (Default: enabled)", "private-groups.warning": "Beware! If this option is disabled and you have private groups, they automatically become public.", diff --git a/public/language/pl/admin/settings/tags.json b/public/language/pl/admin/settings/tags.json index d67523c8e6..ef8efabbcd 100644 --- a/public/language/pl/admin/settings/tags.json +++ b/public/language/pl/admin/settings/tags.json @@ -1,6 +1,6 @@ { "tag": "Ustawienia Tagów", - "min-per-topic": "Minimum Tags per Topic", + "min-per-topic": "Minimalna ilość Tagów na Temat", "max-per-topic": "Maximum Tags per Topic", "min-length": "Minimum Tag Length", "max-length": "Maximum Tag Length", diff --git a/public/language/pl/admin/settings/uploads.json b/public/language/pl/admin/settings/uploads.json index 9a610fb576..05edbff41a 100644 --- a/public/language/pl/admin/settings/uploads.json +++ b/public/language/pl/admin/settings/uploads.json @@ -1,6 +1,6 @@ { "posts": "Posty", - "allow-files": "Allow users to upload regular files", + "allow-files": "Pozwolić użytkownikom wgrywać pliki", "private": "Make uploaded files private", "max-image-width": "Resize images down to specified width (in pixels)", "max-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)", From a67d48e71994a23177cf40abd454905545054810 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 12:50:19 -0500 Subject: [PATCH 76/87] #5513 (#5520) closes #5513 --- public/src/client/account/topics.js | 1 + public/src/client/recent.js | 1 + public/src/client/tag.js | 1 + public/src/client/topic/posts.js | 1 + public/src/client/unread.js | 1 + src/socket.io/topics/infinitescroll.js | 14 +++++++------- test/topics.js | 8 ++++---- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/public/src/client/account/topics.js b/public/src/client/account/topics.js index 340c2e11b9..c16ffd913d 100644 --- a/public/src/client/account/topics.js +++ b/public/src/client/account/topics.js @@ -27,6 +27,7 @@ define('forum/account/topics', ['forum/account/header', 'forum/infinitescroll'], infinitescroll.loadMore('topics.loadMoreFromSet', { set: set, after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, }, function (data, done) { if (data.topics && data.topics.length) { onTopicsLoaded(data.topics, done); diff --git a/public/src/client/recent.js b/public/src/client/recent.js index 981baabd12..b8f9380198 100644 --- a/public/src/client/recent.js +++ b/public/src/client/recent.js @@ -132,6 +132,7 @@ define('forum/recent', ['forum/infinitescroll', 'components'], function (infinit infinitescroll.loadMore('topics.loadMoreRecentTopics', { after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, cid: utils.params().cid, filter: ajaxify.data.selectedFilter.filter, set: $('[component="category"]').attr('data-set') ? $('[component="category"]').attr('data-set') : 'topics:recent', diff --git a/public/src/client/tag.js b/public/src/client/tag.js index 2b74c17193..e07126cb2e 100644 --- a/public/src/client/tag.js +++ b/public/src/client/tag.js @@ -27,6 +27,7 @@ define('forum/tag', ['forum/recent', 'forum/infinitescroll'], function (recent, infinitescroll.loadMore('topics.loadMoreFromSet', { set: 'tag:' + ajaxify.data.tag + ':topics', after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, }, function (data, done) { if (data.topics && data.topics.length) { recent.onTopicsLoaded('tag', data.topics, false, done); diff --git a/public/src/client/topic/posts.js b/public/src/client/topic/posts.js index 72f580b582..fb9074b3be 100644 --- a/public/src/client/topic/posts.js +++ b/public/src/client/topic/posts.js @@ -227,6 +227,7 @@ define('forum/topic/posts', [ infinitescroll.loadMore('topics.loadMore', { tid: tid, after: after, + count: config.postsPerPage, direction: direction, topicPostSort: config.topicPostSort, }, function (data, done) { diff --git a/public/src/client/unread.js b/public/src/client/unread.js index f5b1dd73ed..01ef9cff52 100644 --- a/public/src/client/unread.js +++ b/public/src/client/unread.js @@ -92,6 +92,7 @@ define('forum/unread', ['forum/recent', 'topicSelect', 'forum/infinitescroll', ' var cid = params.cid; infinitescroll.loadMore('topics.loadMoreUnreadTopics', { after: $('[component="category"]').attr('data-nextstart'), + count: config.topicsPerPage, cid: cid, filter: ajaxify.data.selectedFilter.filter, }, function (data, done) { diff --git a/src/socket.io/topics/infinitescroll.js b/src/socket.io/topics/infinitescroll.js index 023ad6266e..cf7c37e050 100644 --- a/src/socket.io/topics/infinitescroll.js +++ b/src/socket.io/topics/infinitescroll.js @@ -40,19 +40,19 @@ module.exports = function (SocketTopics) { var reverse = data.topicPostSort === 'newest_to_oldest' || data.topicPostSort === 'most_votes'; var start = Math.max(0, parseInt(data.after, 10)); - var infScrollPostsPerPage = 10; + var infScrollPostsPerPage = Math.max(0, Math.min(meta.config.postsPerPage, parseInt(data.postsPerPage, 10) || meta.config.postsPerPage) - 1); if (data.direction > 0) { if (reverse) { start = results.topic.postcount - start; } } else if (reverse) { - start = results.topic.postcount - start - infScrollPostsPerPage - 1; + start = results.topic.postcount - start - infScrollPostsPerPage; } else { - start = start - infScrollPostsPerPage - 1; + start = start - infScrollPostsPerPage; } - var stop = start + (infScrollPostsPerPage - 1); + var stop = start + (infScrollPostsPerPage); start = Math.max(0, start); stop = Math.max(0, stop); @@ -93,7 +93,7 @@ module.exports = function (SocketTopics) { } var start = parseInt(data.after, 10); - var stop = start + 9; + var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage, parseInt(data.topicsPerPage, 10) || meta.config.topicsPerPage) - 1); topics.getUnreadTopics({ cid: data.cid, uid: socket.uid, start: start, stop: stop, filter: data.filter }, callback); }; @@ -104,7 +104,7 @@ module.exports = function (SocketTopics) { } var start = parseInt(data.after, 10); - var stop = start + 9; + var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage, parseInt(data.topicsPerPage, 10) || meta.config.topicsPerPage) - 1); topics.getRecentTopics(data.cid, socket.uid, start, stop, data.filter, callback); }; @@ -115,7 +115,7 @@ module.exports = function (SocketTopics) { } var start = parseInt(data.after, 10); - var stop = start + 9; + var stop = start + Math.max(0, Math.min(meta.config.topicsPerPage, parseInt(data.topicsPerPage, 10) || meta.config.topicsPerPage) - 1); topics.getTopicsFromSet(data.set, socket.uid, start, stop, callback); }; diff --git a/test/topics.js b/test/topics.js index 097ffb8c37..ad481ade1e 100644 --- a/test/topics.js +++ b/test/topics.js @@ -902,7 +902,7 @@ describe('Topic\'s', function () { }); it('should infinite load topic posts', function (done) { - socketTopics.loadMore({ uid: adminUid }, { tid: tid, after: 0 }, function (err, data) { + socketTopics.loadMore({ uid: adminUid }, { tid: tid, after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data.mainPost); assert(data.posts); @@ -921,7 +921,7 @@ describe('Topic\'s', function () { it('should load more unread topics', function (done) { socketTopics.markUnread({ uid: adminUid }, tid, function (err) { assert.ifError(err); - socketTopics.loadMoreUnreadTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0 }, function (err, data) { + socketTopics.loadMoreUnreadTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); @@ -939,7 +939,7 @@ describe('Topic\'s', function () { it('should load more recent topics', function (done) { - socketTopics.loadMoreRecentTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0 }, function (err, data) { + socketTopics.loadMoreRecentTopics({ uid: adminUid }, { cid: topic.categoryId, after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); @@ -955,7 +955,7 @@ describe('Topic\'s', function () { }); it('should load more from custom set', function (done) { - socketTopics.loadMoreFromSet({ uid: adminUid }, { set: 'uid:' + adminUid + ':topics', after: 0 }, function (err, data) { + socketTopics.loadMoreFromSet({ uid: adminUid }, { set: 'uid:' + adminUid + ':topics', after: 0, count: 10 }, function (err, data) { assert.ifError(err); assert(data); assert(Array.isArray(data.topics)); From b3f8428691e08f70db0543904f79cb7a5d5340e2 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 13:36:24 -0500 Subject: [PATCH 77/87] closes #5510 --- src/controllers/topics.js | 18 ++++++++++++++++++ src/topics/data.js | 4 ++++ src/topics/delete.js | 9 ++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 1d813868ae..5202f3ac2b 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -146,6 +146,24 @@ topicsController.get = function (req, res, callback) { next(null, data.topicData); }); }, + function (topicData, next) { + if (!topicData.deleterUid) { + return next(null, topicData); + } + + user.getUserFields(topicData.deleterUid, ['username', 'userslug'], function(err, deleter) { + if (err) { + return next(err); + } + + topicData.deleter = deleter; + topicData.deleter.timestampISO = topicData.deletedTimestampISO + delete topicData.deleterUid; + delete topicData.deletedTimestampISO; + + next(null, topicData); + }); + }, function (topicData, next) { function findPost(index) { for (var i = 0; i < topicData.posts.length; i += 1) { diff --git a/src/topics/data.js b/src/topics/data.js index cf1df2787c..17e060f679 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -86,4 +86,8 @@ module.exports = function (Topics) { Topics.deleteTopicField = function (tid, field, callback) { db.deleteObjectField('topic:' + tid, field, callback); }; + + Topics.deleteTopicFields = function (tid, fields, callback) { + db.deleteObjectFields('topic:' + tid, fields, callback); + }; }; diff --git a/src/topics/delete.js b/src/topics/delete.js index 7fdd0bc1a7..b23cfbd513 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -18,7 +18,11 @@ module.exports = function (Topics) { async.parallel([ function (next) { - Topics.setTopicField(tid, 'deleted', 1, next); + Topics.setTopicFields(tid, { + deleted: 1, + deleterUid: uid, + deletedTimestampISO: (new Date()).toISOString() + }, next); }, function (next) { db.sortedSetsRemove(['topics:recent', 'topics:posts', 'topics:views'], tid, next); @@ -47,6 +51,9 @@ module.exports = function (Topics) { function (next) { Topics.setTopicField(tid, 'deleted', 0, next); }, + function (next) { + Topics.deleteTopicFields(tid, ['deleterUid', 'deletedTimestampISO'], next); + }, function (next) { Topics.updateRecent(tid, topicData.lastposttime, next); }, From 5aa45e9181a00856fbf6c2e6bcfb8745fd787ceb Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 13:37:02 -0500 Subject: [PATCH 78/87] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee0cc57714..09e1bfa9dc 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "nodebb-plugin-spam-be-gone": "0.4.13", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.15", - "nodebb-theme-persona": "4.2.4", + "nodebb-theme-persona": "4.2.5", "nodebb-theme-vanilla": "5.2.0", "nodebb-widget-essentials": "2.0.13", "nodemailer": "2.6.4", From f2eb4b808386d61c53b7bdb5cad8925b5fe9414b Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:03:07 -0500 Subject: [PATCH 79/87] closes #5439 --- public/language/en-GB/admin/settings/general.json | 3 ++- public/src/ajaxify.js | 9 +++++++-- src/controllers/api.js | 4 ++++ src/views/admin/settings/general.tpl | 5 +++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/public/language/en-GB/admin/settings/general.json b/public/language/en-GB/admin/settings/general.json index c26740ee4f..8db88bb958 100644 --- a/public/language/en-GB/admin/settings/general.json +++ b/public/language/en-GB/admin/settings/general.json @@ -26,5 +26,6 @@ "touch-icon.upload": "Upload", "touch-icon.help": "Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.", "outgoing-links": "Outgoing Links", - "outgoing-links.warning-page": "Use Outgoing Links Warning Page" + "outgoing-links.warning-page": "Use Outgoing Links Warning Page", + "outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page" } \ No newline at end of file diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 913dbb6fd6..539de6c47b 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -366,8 +366,13 @@ $(document).ready(function () { window.open(this.href, '_blank'); e.preventDefault(); } else if (config.useOutgoingLinksPage) { - ajaxify.go('outgoing?url=' + encodeURIComponent(this.href)); - e.preventDefault(); + var safeUrls = config.outgoingLinksWhitelist.trim().split(/[\s,]+/g); + var href = this.href; + + if (!safeUrls.some(function(url) { return href.indexOf(url) !== -1; } )) { + ajaxify.go('outgoing?url=' + encodeURIComponent(href)); + e.preventDefault(); + } } } } diff --git a/src/controllers/api.js b/src/controllers/api.js index 5ddbd0731b..f7158481e7 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -64,6 +64,10 @@ apiController.getConfig = function (req, res, next) { config.bootswatchSkin = meta.config.bootswatchSkin || 'noskin'; config.defaultBootswatchSkin = meta.config.bootswatchSkin || 'noskin'; + if (config.useOutgoingLinksPage) { + config.outgoingLinksWhitelist = meta.config['outgoingLinks:whitelist']; + } + var timeagoCutoff = meta.config.timeagoCutoff === undefined ? 30 : meta.config.timeagoCutoff; config.timeagoCutoff = timeagoCutoff !== '' ? Math.max(0, parseInt(timeagoCutoff, 10)) : timeagoCutoff; diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index 990509e41f..c78691037e 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -123,6 +123,11 @@ [[admin/settings/general:outgoing-links.warning-page]] + +
    + + +
    From 447b7b02ca625ee0c179b2d12dc5ae792fc33911 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:12:17 -0500 Subject: [PATCH 80/87] use tagsinput for #5439 instead --- public/src/admin/settings/general.js | 5 +++++ src/views/admin/settings/general.tpl | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/public/src/admin/settings/general.js b/public/src/admin/settings/general.js index af66449e02..0f01aa6371 100644 --- a/public/src/admin/settings/general.js +++ b/public/src/admin/settings/general.js @@ -17,6 +17,11 @@ define('admin/settings/general', ['admin/settings'], function () { $('button[data-action="removeOgImage"]').on('click', function () { $('input[data-field="removeOgImage"]').val(''); }); + + $('[data-field-type="tagsinput"]').tagsinput({ + confirmKeys: [13, 44], + trimValue: true, + }); }; return Module; diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index c78691037e..2afbd532b9 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -125,8 +125,8 @@
    - - +
    +
    From 3b41c40b938d9558e6f235631fd614e77df5ac05 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 10 Mar 2017 22:22:08 +0300 Subject: [PATCH 81/87] refactor deleter code to use unixtimestamp --- src/controllers/topics.js | 18 ------------------ src/topics.js | 10 ++++++++++ src/topics/delete.js | 4 ++-- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 5202f3ac2b..1d813868ae 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -146,24 +146,6 @@ topicsController.get = function (req, res, callback) { next(null, data.topicData); }); }, - function (topicData, next) { - if (!topicData.deleterUid) { - return next(null, topicData); - } - - user.getUserFields(topicData.deleterUid, ['username', 'userslug'], function(err, deleter) { - if (err) { - return next(err); - } - - topicData.deleter = deleter; - topicData.deleter.timestampISO = topicData.deletedTimestampISO - delete topicData.deleterUid; - delete topicData.deletedTimestampISO; - - next(null, topicData); - }); - }, function (topicData, next) { function findPost(index) { for (var i = 0; i < topicData.posts.length; i += 1) { diff --git a/src/topics.js b/src/topics.js index 77cea4ec98..164200016d 100644 --- a/src/topics.js +++ b/src/topics.js @@ -180,6 +180,7 @@ var social = require('./social'); isIgnoring: async.apply(Topics.isIgnoring, [topicData.tid], uid), bookmark: async.apply(Topics.getUserBookmark, topicData.tid, uid), postSharing: async.apply(social.getActivePostSharing), + deleter: async.apply(getDeleter, topicData), related: function (next) { async.waterfall([ function (next) { @@ -202,6 +203,8 @@ var social = require('./social'); topicData.isIgnoring = results.isIgnoring[0]; topicData.bookmark = results.bookmark; topicData.postSharing = results.postSharing; + topicData.deleter = results.deleter; + topicData.deletedTimestampISO = utils.toISOString(topicData.deletedTimestamp); topicData.related = results.related || []; topicData.unreplied = parseInt(topicData.postcount, 10) === 1; @@ -258,6 +261,13 @@ var social = require('./social'); ], callback); } + function getDeleter(topicData, callback) { + if (!topicData.deleterUid) { + return setImmediate(callback, null, null); + } + user.getUserFields(topicData.deleterUid, ['username', 'userslug', 'picture'], callback); + } + Topics.getMainPost = function (tid, uid, callback) { Topics.getMainPosts([tid], uid, function (err, mainPosts) { callback(err, Array.isArray(mainPosts) && mainPosts.length ? mainPosts[0] : null); diff --git a/src/topics/delete.js b/src/topics/delete.js index b23cfbd513..30992dd94c 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -21,7 +21,7 @@ module.exports = function (Topics) { Topics.setTopicFields(tid, { deleted: 1, deleterUid: uid, - deletedTimestampISO: (new Date()).toISOString() + deletedTimestamp: Date.now(), }, next); }, function (next) { @@ -52,7 +52,7 @@ module.exports = function (Topics) { Topics.setTopicField(tid, 'deleted', 0, next); }, function (next) { - Topics.deleteTopicFields(tid, ['deleterUid', 'deletedTimestampISO'], next); + Topics.deleteTopicFields(tid, ['deleterUid', 'deletedTimestamp'], next); }, function (next) { Topics.updateRecent(tid, topicData.lastposttime, next); From debe34b96c15708caef08c4b319ad2beac155675 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:25:31 -0500 Subject: [PATCH 82/87] better styling for tagsinput in ACP and added it to general -> keywords --- public/less/admin/settings.less | 16 ++++++++++++++++ src/views/admin/settings/general.tpl | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/public/less/admin/settings.less b/public/less/admin/settings.less index 84572def68..decfc8b7ed 100644 --- a/public/less/admin/settings.less +++ b/public/less/admin/settings.less @@ -16,4 +16,20 @@ [data-action="upload"][type="text"] { width: 95%; } + + .bootstrap-tagsinput { + width: 100%; + border: 0; + box-shadow: none; + padding-left: 0; + + input { + width: 100%; + margin-left: 1px; + margin-top: 9px; + border-bottom: 1px dotted #ccc !important; + padding-bottom: 5px; + padding-left: 0; + } + } } \ No newline at end of file diff --git a/src/views/admin/settings/general.tpl b/src/views/admin/settings/general.tpl index 2afbd532b9..9a96706394 100644 --- a/src/views/admin/settings/general.tpl +++ b/src/views/admin/settings/general.tpl @@ -31,8 +31,8 @@
    - -
    +
    +
    From bb146e049c9e54eacd69a16469b89aab837707d2 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 10 Mar 2017 22:36:04 +0300 Subject: [PATCH 83/87] fix lint --- public/src/ajaxify.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/ajaxify.js b/public/src/ajaxify.js index 539de6c47b..93eb4791b0 100644 --- a/public/src/ajaxify.js +++ b/public/src/ajaxify.js @@ -369,7 +369,7 @@ $(document).ready(function () { var safeUrls = config.outgoingLinksWhitelist.trim().split(/[\s,]+/g); var href = this.href; - if (!safeUrls.some(function(url) { return href.indexOf(url) !== -1; } )) { + if (!safeUrls.some(function (url) { return href.indexOf(url) !== -1; })) { ajaxify.go('outgoing?url=' + encodeURIComponent(href)); e.preventDefault(); } From 62f445ad7f95b3bf76e8dc2a8bc0a2d6b23d775b Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:33:47 -0500 Subject: [PATCH 84/87] ACP: added tagsinput to other fields that are "comma separated" --- public/src/admin/settings.js | 8 ++++++++ public/src/admin/settings/general.js | 5 ----- src/views/admin/settings/group.tpl | 2 +- src/views/admin/settings/uploads.tpl | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index 041de4a40f..3c2575734b 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -102,6 +102,7 @@ define('admin/settings', ['uploader'], function (uploader) { }); handleUploads(); + setupTagsInput(); $('#clear-sitemap-cache').off('click').on('click', function () { socket.emit('admin.settings.clearSitemapCache', function () { @@ -142,6 +143,13 @@ define('admin/settings', ['uploader'], function (uploader) { }); } + function setupTagsInput() { + $('[data-field-type="tagsinput"]').tagsinput({ + confirmKeys: [13, 44], + trimValue: true, + }); + } + Settings.remove = function (key) { socket.emit('admin.config.remove', key); }; diff --git a/public/src/admin/settings/general.js b/public/src/admin/settings/general.js index 0f01aa6371..af66449e02 100644 --- a/public/src/admin/settings/general.js +++ b/public/src/admin/settings/general.js @@ -17,11 +17,6 @@ define('admin/settings/general', ['admin/settings'], function () { $('button[data-action="removeOgImage"]').on('click', function () { $('input[data-field="removeOgImage"]').val(''); }); - - $('[data-field-type="tagsinput"]').tagsinput({ - confirmKeys: [13, 44], - trimValue: true, - }); }; return Module; diff --git a/src/views/admin/settings/group.tpl b/src/views/admin/settings/group.tpl index 1c0b660361..fd696cb5ad 100644 --- a/src/views/admin/settings/group.tpl +++ b/src/views/admin/settings/group.tpl @@ -43,7 +43,7 @@

    [[admin/settings/group:default-cover-help]]

    -
    +
    diff --git a/src/views/admin/settings/uploads.tpl b/src/views/admin/settings/uploads.tpl index 6c9b59db80..feff2dbff3 100644 --- a/src/views/admin/settings/uploads.tpl +++ b/src/views/admin/settings/uploads.tpl @@ -50,7 +50,7 @@
    - +

    [[admin/settings/uploads:allowed-file-extensions-help]]

    @@ -131,7 +131,7 @@

    [[admin/settings/uploads:default-covers-help]]

    - +
    From 5ca9e5e14cb0d29c1bf69934d2492c040ba33373 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:36:30 -0500 Subject: [PATCH 85/87] ACP: distinguish from regular text --- public/less/admin/admin.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/less/admin/admin.less b/public/less/admin/admin.less index 23f3bead8c..bcf6ddca58 100644 --- a/public/less/admin/admin.less +++ b/public/less/admin/admin.less @@ -272,4 +272,8 @@ body { border: 1px dashed @brand-success; background: lighten(@brand-success, 10%); opacity: 0.5; +} + +form small { + color: @gray-light; } \ No newline at end of file From 286d0ab203d07f2faab8ed97eaf4f6e28143cd9a Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:40:10 -0500 Subject: [PATCH 86/87] prevent tagsinput initialization from flagging as unsaved --- public/src/admin/settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js index 3c2575734b..5495e3b71b 100644 --- a/public/src/admin/settings.js +++ b/public/src/admin/settings.js @@ -148,6 +148,7 @@ define('admin/settings', ['uploader'], function (uploader) { confirmKeys: [13, 44], trimValue: true, }); + app.flags._unsaved = false; } Settings.remove = function (key) { From 9dc54f801ebbf42733808d02eed1e52f414c126a Mon Sep 17 00:00:00 2001 From: psychobunny Date: Fri, 10 Mar 2017 14:55:52 -0500 Subject: [PATCH 87/87] up persona --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09e1bfa9dc..395b439cfb 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "nodebb-plugin-spam-be-gone": "0.4.13", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.15", - "nodebb-theme-persona": "4.2.5", + "nodebb-theme-persona": "4.2.6", "nodebb-theme-vanilla": "5.2.0", "nodebb-widget-essentials": "2.0.13", "nodemailer": "2.6.4",