From f225fc3e3d8337976cc195d2ae296ccdf2eb6e88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Tue, 27 Dec 2016 21:20:05 +0300 Subject: [PATCH 01/12] closes #5314 --- src/plugins/load.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/load.js b/src/plugins/load.js index cf3340fafd..60d584f99f 100644 --- a/src/plugins/load.js +++ b/src/plugins/load.js @@ -195,19 +195,19 @@ module.exports = function (Plugins) { } function mapClientSideScripts(pluginData, callback) { - function mapScripts(scripts, globalScripts) { + function mapScripts(scripts, param) { if (Array.isArray(scripts) && scripts.length) { if (global.env === 'development') { winston.verbose('[plugins] Found ' + scripts.length + ' js file(s) for plugin ' + pluginData.id); } - globalScripts = globalScripts.concat(scripts.map(function (file) { + Plugins[param] = Plugins[param].concat(scripts.map(function (file) { return resolveModulePath(path.join(__dirname, '../../node_modules/', pluginData.id, file), file); })).filter(Boolean); } } - mapScripts(pluginData.scripts, Plugins.clientScripts); - mapScripts(pluginData.acpScripts, Plugins.acpScripts); + mapScripts(pluginData.scripts, 'clientScripts'); + mapScripts(pluginData.acpScripts, 'acpScripts'); callback(); } From 277f447f3e040d2e664598dac3e1b29780ff89b3 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 27 Dec 2016 15:29:46 -0500 Subject: [PATCH 02/12] fix minSchemaDate in upgrade.js --- src/upgrade.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/upgrade.js b/src/upgrade.js index 344a217c13..4aca187a7a 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -8,7 +8,7 @@ var winston = require('winston'); var Upgrade = {}; -var minSchemaDate = Date.UTC(2015, 10, 6); // This value gets updated every new MAJOR version +var minSchemaDate = Date.UTC(2016, 8, 7); // This value gets updated every new MAJOR version var schemaDate; var thisSchemaDate; From ab797b91a43eb91afcb8f8f87093fe65cd802bc5 Mon Sep 17 00:00:00 2001 From: pichalite Date: Wed, 28 Dec 2016 19:00:14 +0000 Subject: [PATCH 03/12] Let global mods change user avatar --- src/socket.io/user/picture.js | 2 +- src/user.js | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index 82404f1fe5..eacc5197aa 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -23,7 +23,7 @@ module.exports = function (SocketUser) { async.waterfall([ function (next) { - user.isAdminOrSelf(socket.uid, data.uid, next); + user.isAdminOrGlobalModOrSelf(socket.uid, data.uid, next); }, function (next) { switch(type) { diff --git a/src/user.js b/src/user.js index 7c0fa5e67e..00e5824690 100644 --- a/src/user.js +++ b/src/user.js @@ -276,6 +276,18 @@ var meta = require('./meta'); 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({ From 5b646495af90ec0041d5922e643efd2265406b5a Mon Sep 17 00:00:00 2001 From: pichalite Date: Thu, 29 Dec 2016 01:01:35 +0000 Subject: [PATCH 04/12] Confirm before removing user and group cover picture --- public/language/en-GB/groups.json | 3 ++- public/language/en-GB/user.json | 1 + public/src/client/account/header.js | 24 ++++++++++++++++-------- public/src/client/groups/details.js | 27 ++++++++++++++++++--------- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/public/language/en-GB/groups.json b/public/language/en-GB/groups.json index 2efc9a69fc..a55cc8603f 100644 --- a/public/language/en-GB/groups.json +++ b/public/language/en-GB/groups.json @@ -61,5 +61,6 @@ "new-group.group_name": "Group Name:", "upload-group-cover": "Upload group cover", "bulk-invite-instructions": "Enter a list of comma separated usernames to invite to this group", - "bulk-invite": "Bulk Invite" + "bulk-invite": "Bulk Invite", + "remove_group_cover_confirm": "Are you sure you want to remove the cover picture?" } \ No newline at end of file diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index f0cb35f615..6215486cb3 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -67,6 +67,7 @@ "upload_a_picture": "Upload a picture", "remove_uploaded_picture" : "Remove Uploaded Picture", "upload_cover_picture": "Upload cover picture", + "remove_cover_picture_confirm": "Are you sure you want to remove the cover picture?", "settings": "Settings", "show_email": "Show My Email", diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index d225e2cae1..e99b7ee0a8 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -168,14 +168,22 @@ define('forum/account/header', [ } function removeCover() { - socket.emit('user.removeCover', { - uid: ajaxify.data.uid - }, function (err) { - if (!err) { - ajaxify.refresh(); - } else { - app.alertError(err.message); - } + translator.translate('[[user:remove_cover_picture_confirm]]', function (translated) { + bootbox.confirm(translated, function (confirm) { + if (!confirm) { + return; + } + + socket.emit('user.removeCover', { + uid: ajaxify.data.uid + }, function (err) { + if (!err) { + ajaxify.refresh(); + } else { + app.alertError(err.message); + } + }); + }); }); } diff --git a/public/src/client/groups/details.js b/public/src/client/groups/details.js index e8c449f5f6..96610baf38 100644 --- a/public/src/client/groups/details.js +++ b/public/src/client/groups/details.js @@ -7,8 +7,9 @@ define('forum/groups/details', [ 'components', 'coverPhoto', 'uploader', + 'translator', 'vendor/colorpicker/colorpicker' -], function (memberList, iconSelect, components, coverPhoto, uploader) { +], function (memberList, iconSelect, components, coverPhoto, uploader, translator) { var Details = {}; var groupName; @@ -265,14 +266,22 @@ define('forum/groups/details', [ } function removeCover() { - socket.emit('groups.cover.remove', { - groupName: ajaxify.data.group.name - }, function (err) { - if (!err) { - ajaxify.refresh(); - } else { - app.alertError(err.message); - } + translator.translate('[[groups:remove_group_cover_confirm]]', function (translated) { + bootbox.confirm(translated, function (confirm) { + if (!confirm) { + return; + } + + socket.emit('groups.cover.remove', { + groupName: ajaxify.data.group.name + }, function (err) { + if (!err) { + ajaxify.refresh(); + } else { + app.alertError(err.message); + } + }); + }); }); } From 8a68e1d91814c9299769ccb4b2812d7c6152fb76 Mon Sep 17 00:00:00 2001 From: pichalite Date: Thu, 29 Dec 2016 17:28:42 +0000 Subject: [PATCH 05/12] Delete cover position data when cover photo is deleted --- src/groups/cover.js | 2 +- src/user/picture.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/groups/cover.js b/src/groups/cover.js index b3a380d80c..65ff0368cb 100644 --- a/src/groups/cover.js +++ b/src/groups/cover.js @@ -116,7 +116,7 @@ module.exports = function (Groups) { } Groups.removeCover = function (data, callback) { - db.deleteObjectFields('group:' + data.groupName, ['cover:url', 'cover:thumb:url'], callback); + db.deleteObjectFields('group:' + data.groupName, ['cover:url', 'cover:thumb:url', 'cover:position'], callback); }; }; \ No newline at end of file diff --git a/src/user/picture.js b/src/user/picture.js index 64f9c900c2..0fd1d1a3bd 100644 --- a/src/user/picture.js +++ b/src/user/picture.js @@ -222,6 +222,6 @@ module.exports = function (User) { }; User.removeCoverPicture = function (data, callback) { - db.deleteObjectField('user:' + data.uid, 'cover:url', callback); + db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback); }; }; From 8ddf7eb922f0174f481f3f0a80a530bd5880ca3d Mon Sep 17 00:00:00 2001 From: Anil Mandepudi Date: Thu, 29 Dec 2016 11:25:05 -0800 Subject: [PATCH 06/12] Use scrollStop in chat (#5326) --- public/src/modules/chat.js | 7 +++++-- public/src/modules/scrollStop.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js index d90dc210d4..f231fd765d 100644 --- a/public/src/modules/chat.js +++ b/public/src/modules/chat.js @@ -8,8 +8,9 @@ define('chat', [ 'sounds', 'forum/chats', 'forum/chats/messages', - 'translator' -], function (components, taskbar, S, sounds, Chats, ChatsMessages, translator) { + 'translator', + 'scrollStop' +], function (components, taskbar, S, sounds, Chats, ChatsMessages, translator, scrollStop) { var module = {}; var newMessage = false; @@ -196,6 +197,8 @@ define('chat', [ handle: '.modal-header' }); }); + + scrollStop.apply(chatModal.find('[component="chat/messages"]')); chatModal.find('#chat-close-btn').on('click', function () { module.close(chatModal); diff --git a/public/src/modules/scrollStop.js b/public/src/modules/scrollStop.js index 3bb96aed8b..13d722402c 100644 --- a/public/src/modules/scrollStop.js +++ b/public/src/modules/scrollStop.js @@ -21,7 +21,7 @@ define('scrollStop', function () { if ( (e.originalEvent.deltaY < 0 && scrollTop === 0) || // scroll up - (e.originalEvent.deltaY > 0 && (elementHeight + scrollTop) > scrollHeight) // scroll down + (e.originalEvent.deltaY > 0 && (elementHeight + scrollTop) >= scrollHeight) // scroll down ) { return false; } From 0ea477abde9f75255c9f215e7e32d6c646a344b3 Mon Sep 17 00:00:00 2001 From: pichalite Date: Thu, 29 Dec 2016 23:16:23 +0000 Subject: [PATCH 07/12] Add more user tests --- test/user.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/user.js b/test/user.js index 8621575fe2..03a8ff73c4 100644 --- a/test/user.js +++ b/test/user.js @@ -13,6 +13,7 @@ var Meta = require('../src/meta'); var Password = require('../src/password'); var groups = require('../src/groups'); var helpers = require('./helpers'); +var meta = require('../src/meta'); describe('User', function () { var userData; @@ -506,6 +507,47 @@ describe('User', function () { done(); }); }); + + it('should return error if profile image uploads disabled', function (done) { + meta.config.allowProfileImageUploads = 0; + var path = require('path'); + var picture = { + path: path.join(nconf.get('base_dir'), 'public', 'logo.png'), + size: 7189, + name: 'logo.png' + }; + User.uploadPicture(uid, picture, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:profile-image-uploads-disabled]]'); + done(); + }); + }); + + it('should return error if profile image is too big', function (done) { + meta.config.allowProfileImageUploads = 1; + var path = require('path'); + var picture = { + path: path.join(nconf.get('base_dir'), 'public', 'logo.png'), + size: 265000, + name: 'logo.png' + }; + User.uploadPicture(uid, picture, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:file-too-big, 256]]'); + done(); + }); + }); + + it('should return error if profile image file has no extension', function (done) { + var path = require('path'); + var picture = { + path: path.join(nconf.get('base_dir'), 'public', 'logo.png'), + size: 7189, + name: 'logo' + }; + User.uploadPicture(uid, picture, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:invalid-image-extension]]'); + done(); + }); + }); it('should get profile pictures', function (done) { io.emit('user.getProfilePictures', {uid: uid}, function (err, data) { From a0ff3734cf670e6833c78f4f9fda9917a9af013e Mon Sep 17 00:00:00 2001 From: pichalite Date: Fri, 30 Dec 2016 00:51:22 +0000 Subject: [PATCH 08/12] Add more user tests --- test/user.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/test/user.js b/test/user.js index 03a8ff73c4..b0ed74887b 100644 --- a/test/user.js +++ b/test/user.js @@ -14,6 +14,7 @@ var Password = require('../src/password'); var groups = require('../src/groups'); var helpers = require('./helpers'); var meta = require('../src/meta'); +var plugins = require('../src/plugins'); describe('User', function () { var userData; @@ -548,6 +549,65 @@ describe('User', function () { done(); }); }); + + it('should return error if no plugins listening for filter:uploadImage when uploading from url', function (done) { + var url = nconf.get('url') + '/logo.png'; + User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:no-plugin]]'); + done(); + }); + }); + + it('should return error if the extension is invalid when uploading from url', function (done) { + var url = nconf.get('url') + '/favicon.ico'; + + function filterMethod(data, callback) { + data.foo += 5; + callback(null, data); + } + + plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); + + User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:invalid-image-extension]]'); + done(); + }); + }); + + it('should return error if the file is too big when uploading from url', function (done) { + var url = nconf.get('url') + '/logo.png'; + meta.config.maximumProfileImageSize = 1; + + function filterMethod(data, callback) { + data.foo += 5; + callback(null, data); + } + + plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); + + User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + assert.equal(err.message, '[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]'); + done(); + }); + }); + + it('should upload picture when uploading from url', function (done) { + var url = nconf.get('url') + '/logo.png'; + meta.config.maximumProfileImageSize = ''; + + function filterMethod(data, callback) { + data.foo += 5; + callback(null, {url: url}); + } + + plugins.registerHook('test-plugin', {hook: 'filter:uploadImage', method: filterMethod}); + + User.uploadFromUrl(uid, url, function (err, uploadedPicture) { + assert.ifError(err); + assert.equal(uploadedPicture.url, url); + done(); + }); + }); it('should get profile pictures', function (done) { io.emit('user.getProfilePictures', {uid: uid}, function (err, data) { From cb82824c13ba49494c74470e971c5dcbea112d57 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Mon, 2 Jan 2017 08:59:55 -0700 Subject: [PATCH 09/12] Fix ACP title bug with hashes (#5331) --- public/src/admin/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js index 58e79aa95c..7a5a213195 100644 --- a/public/src/admin/admin.js +++ b/public/src/admin/admin.js @@ -87,7 +87,7 @@ url = url .replace(/\/\d+$/, '') .split('/').slice(0, 3).join('/') - .split('?')[0].replace(/(\/+$)|(^\/+)/, ''); + .split(/[?#]/)[0].replace(/(\/+$)|(^\/+)/, ''); // If index is requested, load the dashboard if (url === 'admin') { From 6b2dde02b57ee14328b03c06f4f12eff3cc3ee15 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 3 Jan 2017 15:08:16 +0300 Subject: [PATCH 10/12] closes #5333 --- src/controllers/authentication.js | 2 +- test/authentication.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/controllers/authentication.js b/src/controllers/authentication.js index 4612226d59..90a1715cbb 100644 --- a/src/controllers/authentication.js +++ b/src/controllers/authentication.js @@ -319,7 +319,7 @@ authenticationController.onSuccessfulLogin = function (req, uid, callback) { user.auth.addSession(uid, req.sessionID, next); }, function (next) { - db.setObjectField('uid:' + uid + 'sessionUUID:sessionId', uuid, req.sessionID, next); + db.setObjectField('uid:' + uid + ':sessionUUID:sessionId', uuid, req.sessionID, next); }, function (next) { user.updateLastOnlineTime(uid, next); diff --git a/test/authentication.js b/test/authentication.js index e3ab49a5f5..fb26e0bfbf 100644 --- a/test/authentication.js +++ b/test/authentication.js @@ -120,7 +120,12 @@ describe('authentication', function () { assert(body); assert.equal(body.username, 'regular'); assert.equal(body.email, 'regular@nodebb.org'); - done(); + db.getObject('uid:' + regularUid + ':sessionUUID:sessionId', function (err, sessions) { + assert.ifError(err); + assert(sessions); + assert(Object.keys(sessions).length > 0); + done(); + }); }); }); }); From ba1889f441f1402aee3bcfdc8db962380ddd69a4 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 3 Jan 2017 20:02:24 +0300 Subject: [PATCH 11/12] messaging refactor --- src/messaging.js | 723 +++++++++++++++++------------------------- src/messaging/data.js | 136 ++++++++ 2 files changed, 428 insertions(+), 431 deletions(-) create mode 100644 src/messaging/data.js diff --git a/src/messaging.js b/src/messaging.js index d5d43e1041..5bb668ae6e 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -2,7 +2,6 @@ var async = require('async'); -var winston = require('winston'); var S = require('string'); var db = require('./database'); @@ -10,471 +9,333 @@ var user = require('./user'); var plugins = require('./plugins'); var meta = require('./meta'); var utils = require('../public/src/utils'); -var notifications = require('./notifications'); -var userNotifications = require('./user/notifications'); -(function (Messaging) { - - require('./messaging/create')(Messaging); - require('./messaging/delete')(Messaging); - require('./messaging/edit')(Messaging); - require('./messaging/rooms')(Messaging); - require('./messaging/unread')(Messaging); - require('./messaging/notifications')(Messaging); - - Messaging.getMessageField = function (mid, field, callback) { - Messaging.getMessageFields(mid, [field], function (err, fields) { - callback(err, fields ? fields[field] : null); - }); - }; - - Messaging.getMessageFields = function (mid, fields, callback) { - db.getObjectFields('message:' + mid, fields, callback); - }; - - Messaging.setMessageField = function (mid, field, content, callback) { - db.setObjectField('message:' + mid, field, content, callback); - }; - - Messaging.setMessageFields = function (mid, data, callback) { - db.setObject('message:' + mid, data, callback); - }; - - Messaging.getMessages = function (params, callback) { - var uid = params.uid; - var roomId = params.roomId; - var isNew = params.isNew || false; - var start = params.hasOwnProperty('start') ? params.start : 0; - var stop = parseInt(start, 10) + ((params.count || 50) - 1); - - var indices = {}; - async.waterfall([ - function (next) { - canGetMessages(params.callerUid, params.uid, next); - }, - function (canGet, next) { - if (!canGet) { - return callback(null, null); - } - db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', start, stop, next); - }, - function (mids, next) { - if (!Array.isArray(mids) || !mids.length) { - return callback(null, []); - } +var Messaging = module.exports; + +require('./messaging/data')(Messaging); +require('./messaging/create')(Messaging); +require('./messaging/delete')(Messaging); +require('./messaging/edit')(Messaging); +require('./messaging/rooms')(Messaging); +require('./messaging/unread')(Messaging); +require('./messaging/notifications')(Messaging); + + +Messaging.getMessages = function (params, callback) { + var uid = params.uid; + var roomId = params.roomId; + var isNew = params.isNew || false; + var start = params.hasOwnProperty('start') ? params.start : 0; + var stop = parseInt(start, 10) + ((params.count || 50) - 1); + + var indices = {}; + async.waterfall([ + function (next) { + canGet('filter:messaging.canGetMessages', params.callerUid, params.uid, next); + }, + function (canGet, next) { + if (!canGet) { + return callback(null, null); + } + db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', start, stop, next); + }, + function (mids, next) { + if (!Array.isArray(mids) || !mids.length) { + return callback(null, []); + } - mids.forEach(function (mid, index) { - indices[mid] = start + index; - }); + mids.forEach(function (mid, index) { + indices[mid] = start + index; + }); - mids.reverse(); + mids.reverse(); - Messaging.getMessagesData(mids, uid, roomId, isNew, next); - }, - function (messageData, next) { - messageData.forEach(function (messageData) { - messageData.index = indices[messageData.messageId.toString()]; - }); - next(null, messageData); - } - ], callback); - }; + Messaging.getMessagesData(mids, uid, roomId, isNew, next); + }, + function (messageData, next) { + messageData.forEach(function (messageData) { + messageData.index = indices[messageData.messageId.toString()]; + }); + next(null, messageData); + } + ], callback); +}; + +function canGet(hook, callerUid, uid, callback) { + plugins.fireHook(hook, { + callerUid: callerUid, + uid: uid, + canGet: parseInt(callerUid, 10) === parseInt(uid, 10) + }, function (err, data) { + callback(err, data ? data.canGet : false); + }); +} + +Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) { + plugins.fireHook('filter:parse.raw', message, function (err, parsed) { + if (err) { + return callback(err); + } - function canGetMessages(callerUid, uid, callback) { - plugins.fireHook('filter:messaging.canGetMessages', { - callerUid: callerUid, + var messageData = { + message: message, + parsed: parsed, + fromuid: fromuid, uid: uid, - canGet: parseInt(callerUid, 10) === parseInt(uid, 10) - }, function (err, data) { - callback(err, data ? data.canGet : false); - }); - } + roomId: roomId, + isNew: isNew, + parsedMessage: parsed + }; - Messaging.getMessagesData = function (mids, uid, roomId, isNew, callback) { - - var keys = mids.map(function (mid) { - return 'message:' + mid; + plugins.fireHook('filter:messaging.parse', messageData, function (err, messageData) { + callback(err, messageData ? messageData.parsedMessage : ''); }); - - var messages; - - async.waterfall([ - function (next) { - db.getObjects(keys, next); - }, - function (_messages, next) { - messages = _messages.map(function (msg, idx) { - if (msg) { - msg.messageId = parseInt(mids[idx], 10); - } - return msg; - }).filter(Boolean); - - var uids = messages.map(function (msg) { - return msg && msg.fromuid; - }); - - user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], next); - }, - function (users, next) { - messages.forEach(function (message, index) { - message.fromUser = users[index]; - var self = parseInt(message.fromuid, 10) === parseInt(uid, 10); - message.self = self ? 1 : 0; - message.timestampISO = utils.toISOString(message.timestamp); - message.newSet = false; - message.roomId = String(message.roomId || roomId); - if (message.hasOwnProperty('edited')) { - message.editedISO = new Date(parseInt(message.edited, 10)).toISOString(); - } - }); - - async.map(messages, function (message, next) { - Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) { - if (err) { - return next(err); - } - message.content = result; - message.cleanedContent = S(result).stripTags().decodeHTMLEntities().s; - next(null, message); - }); - }, next); - }, - function (messages, next) { - if (messages.length > 1) { - // 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 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) { - // If the previous message was from the other person, this is also a new set - message.newSet = true; - } - - return message; - }); - - next(undefined, messages); - } else if (messages.length === 1) { - // For single messages, we don't know the context, so look up the previous message and compare - var key = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; - async.waterfall([ - async.apply(db.sortedSetRank, key, messages[0].messageId), - function (index, next) { - // Continue only if this isn't the first message in sorted set - if (index > 0) { - db.getSortedSetRange(key, index - 1, index - 1, next); - } else { - messages[0].newSet = true; - return next(undefined, messages); - } - }, - function (mid, next) { - Messaging.getMessageFields(mid, ['fromuid', 'timestamp'], next); - } - ], function (err, fields) { - if (err) { - return next(err); - } - - if ( - (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000 * 60 * 5)) || - (parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10)) - ) { - // If it's been 5 minutes, this is a new set of messages - messages[0].newSet = true; - } - - next(undefined, messages); - }); - } else { - next(null, []); - } + }); +}; + +Messaging.isNewSet = function (uid, roomId, timestamp, callback) { + var setKey = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; + + async.waterfall([ + function (next) { + db.getSortedSetRevRangeWithScores(setKey, 0, 0, next); + }, + function (messages, next) { + if (messages && messages.length) { + next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5)); + } else { + next(null, true); } - ], callback); - - }; - - Messaging.parse = function (message, fromuid, uid, roomId, isNew, callback) { - plugins.fireHook('filter:parse.raw', message, function (err, parsed) { - if (err) { - return callback(err); - } - - var messageData = { - message: message, - parsed: parsed, - fromuid: fromuid, - uid: uid, - roomId: roomId, - isNew: isNew, - parsedMessage: parsed - }; - - plugins.fireHook('filter:messaging.parse', messageData, function (err, messageData) { - callback(err, messageData ? messageData.parsedMessage : ''); - }); - }); - }; - - Messaging.isNewSet = function (uid, roomId, timestamp, callback) { - var setKey = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; - - async.waterfall([ - function (next) { - db.getSortedSetRevRangeWithScores(setKey, 0, 0, next); - }, - function (messages, next) { - if (messages && messages.length) { - next(null, parseInt(timestamp, 10) > parseInt(messages[0].score, 10) + (1000 * 60 * 5)); - } else { - next(null, true); - } + } + ], callback); +}; + + +Messaging.getRecentChats = function (callerUid, uid, start, stop, callback) { + async.waterfall([ + function (next) { + canGet('filter:messaging.canGetRecentChats', callerUid, uid, next); + }, + function (canGet, next) { + if (!canGet) { + return callback(null, null); } - ], callback); - }; - - - Messaging.getRecentChats = function (callerUid, uid, start, stop, callback) { - async.waterfall([ - function (next) { - canGetRecentChats(callerUid, uid, next); - }, - function (canGet, next) { - if (!canGet) { - return callback(null, null); - } - db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, next); - }, - function (roomIds, next) { - async.parallel({ - roomData: function (next) { - Messaging.getRoomsData(roomIds, next); - }, - unread: function (next) { - db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next); - }, - users: function (next) { - async.map(roomIds, function (roomId, next) { - db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9, function (err, uids) { - if (err) { - return next(err); - } - uids = uids.filter(function (value) { - return value && parseInt(value, 10) !== parseInt(uid, 10); - }); - user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); + db.getSortedSetRevRange('uid:' + uid + ':chat:rooms', start, stop, next); + }, + function (roomIds, next) { + async.parallel({ + roomData: function (next) { + Messaging.getRoomsData(roomIds, next); + }, + unread: function (next) { + db.isSortedSetMembers('uid:' + uid + ':chat:rooms:unread', roomIds, next); + }, + users: function (next) { + async.map(roomIds, function (roomId, next) { + db.getSortedSetRevRange('chat:room:' + roomId + ':uids', 0, 9, function (err, uids) { + if (err) { + return next(err); + } + uids = uids.filter(function (value) { + return value && parseInt(value, 10) !== parseInt(uid, 10); }); - }, next); - }, - teasers: function (next) { - async.map(roomIds, function (roomId, next) { - Messaging.getTeaser(uid, roomId, next); - }, next); + user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); + }); + }, next); + }, + teasers: function (next) { + async.map(roomIds, function (roomId, next) { + Messaging.getTeaser(uid, roomId, next); + }, next); + } + }, next); + }, + function (results, next) { + results.roomData.forEach(function (room, index) { + room.users = results.users[index]; + room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2; + room.unread = results.unread[index]; + room.teaser = results.teasers[index]; + + room.users.forEach(function (userData) { + if (userData && parseInt(userData.uid, 10)) { + userData.status = user.getStatus(userData); } - }, next); - }, - function (results, next) { - results.roomData.forEach(function (room, index) { - room.users = results.users[index]; - room.groupChat = room.hasOwnProperty('groupChat') ? room.groupChat : room.users.length > 2; - room.unread = results.unread[index]; - room.teaser = results.teasers[index]; - - room.users.forEach(function (userData) { - if (userData && parseInt(userData.uid, 10)) { - userData.status = user.getStatus(userData); - } - }); - room.users = room.users.filter(function (user) { - return user && parseInt(user.uid, 10); - }); - room.lastUser = room.users[0]; - - room.usernames = Messaging.generateUsernames(room.users, uid); }); + room.users = room.users.filter(function (user) { + return user && parseInt(user.uid, 10); + }); + room.lastUser = room.users[0]; - next(null, {rooms: results.roomData, nextStart: stop + 1}); - } - ], callback); - }; - - Messaging.generateUsernames = function (users, excludeUid) { - users = users.filter(function (user) { - return user && parseInt(user.uid, 10) !== excludeUid; - }); - return users.map(function (user) { - return user.username; - }).join(', '); - }; - - function canGetRecentChats(callerUid, uid, callback) { - plugins.fireHook('filter:messaging.canGetRecentChats', { - callerUid: callerUid, - uid: uid, - canGet: parseInt(callerUid, 10) === parseInt(uid, 10) - }, function (err, data) { - callback(err, data ? data.canGet : false); - }); - } - - Messaging.getTeaser = function (uid, roomId, callback) { - var teaser; - async.waterfall([ - function (next) { - db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, 0, next); - }, - function (mids, next) { - if (!mids || !mids.length) { - return next(null, null); - } - Messaging.getMessageFields(mids[0], ['fromuid', 'content', 'timestamp'], next); - }, - function (_teaser, next) { - teaser = _teaser; - if (!teaser) { - return callback(); - } - if (teaser.content) { - teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; - } + room.usernames = Messaging.generateUsernames(room.users, uid); + }); - teaser.timestampISO = utils.toISOString(teaser.timestamp); - user.getUserFields(teaser.fromuid, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); - }, - function (user, next) { - teaser.user = user; - next(null, teaser); + next(null, {rooms: results.roomData, nextStart: stop + 1}); + } + ], callback); +}; + +Messaging.generateUsernames = function (users, excludeUid) { + users = users.filter(function (user) { + return user && parseInt(user.uid, 10) !== excludeUid; + }); + return users.map(function (user) { + return user.username; + }).join(', '); +}; + +Messaging.getTeaser = function (uid, roomId, callback) { + var teaser; + async.waterfall([ + function (next) { + db.getSortedSetRevRange('uid:' + uid + ':chat:room:' + roomId + ':mids', 0, 0, next); + }, + function (mids, next) { + if (!mids || !mids.length) { + return next(null, null); + } + Messaging.getMessageFields(mids[0], ['fromuid', 'content', 'timestamp'], next); + }, + function (_teaser, next) { + teaser = _teaser; + if (!teaser) { + return callback(); + } + if (teaser.content) { + teaser.content = S(teaser.content).stripTags().decodeHTMLEntities().s; } - ], callback); - }; - Messaging.canMessageUser = function (uid, toUid, callback) { - if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) { - return callback(new Error('[[error:chat-disabled]]')); + teaser.timestampISO = utils.toISOString(teaser.timestamp); + user.getUserFields(teaser.fromuid, ['uid', 'username', 'userslug', 'picture', 'status', 'lastonline'] , next); + }, + function (user, next) { + teaser.user = user; + next(null, teaser); } + ], callback); +}; - if (parseInt(uid, 10) === parseInt(toUid, 10)) { - return callback(new Error('[[error:cant-chat-with-yourself')); - } +Messaging.canMessageUser = function (uid, toUid, callback) { + if (parseInt(meta.config.disableChat) === 1 || !uid || uid === toUid) { + return callback(new Error('[[error:chat-disabled]]')); + } - async.waterfall([ - function (next) { - user.exists(toUid, next); - }, - function (exists, next) { - if (!exists) { - return callback(new Error('[[error:no-user]]')); - } - user.getUserFields(uid, ['banned', 'email:confirmed'], next); - }, - function (userData, next) { - if (parseInt(userData.banned, 10) === 1) { - return callback(new Error('[[error:user-banned]]')); - } + if (parseInt(uid, 10) === parseInt(toUid, 10)) { + return callback(new Error('[[error:cant-chat-with-yourself')); + } - if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { - return callback(new Error('[[error:email-not-confirmed-chat]]')); - } + async.waterfall([ + function (next) { + user.exists(toUid, next); + }, + function (exists, next) { + if (!exists) { + return callback(new Error('[[error:no-user]]')); + } + user.getUserFields(uid, ['banned', 'email:confirmed'], next); + }, + function (userData, next) { + if (parseInt(userData.banned, 10) === 1) { + return callback(new Error('[[error:user-banned]]')); + } - async.parallel({ - settings: async.apply(user.getSettings, toUid), - isAdmin: async.apply(user.isAdministrator, uid), - isFollowing: async.apply(user.isFollowing, toUid, uid) - }, next); - }, - function (results, next) { - if (!results.settings.restrictChat || results.isAdmin || results.isFollowing) { - return next(); - } + if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { + return callback(new Error('[[error:email-not-confirmed-chat]]')); + } - next(new Error('[[error:chat-restricted]]')); + async.parallel({ + settings: async.apply(user.getSettings, toUid), + isAdmin: async.apply(user.isAdministrator, uid), + isFollowing: async.apply(user.isFollowing, toUid, uid) + }, next); + }, + function (results, next) { + if (!results.settings.restrictChat || results.isAdmin || results.isFollowing) { + return next(); } - ], callback); - }; - Messaging.canMessageRoom = function (uid, roomId, callback) { - if (parseInt(meta.config.disableChat) === 1 || !uid) { - return callback(new Error('[[error:chat-disabled]]')); + next(new Error('[[error:chat-restricted]]')); } + ], callback); +}; - async.waterfall([ - function (next) { - Messaging.isUserInRoom(uid, roomId, next); - }, - function (inRoom, next) { - if (!inRoom) { - return next(new Error('[[error:not-in-room]]')); - } +Messaging.canMessageRoom = function (uid, roomId, callback) { + if (parseInt(meta.config.disableChat) === 1 || !uid) { + return callback(new Error('[[error:chat-disabled]]')); + } - Messaging.getUserCountInRoom(roomId, next); - }, - function (count, next) { - if (count < 2) { - return next(new Error('[[error:no-users-in-room]]')); - } + async.waterfall([ + function (next) { + Messaging.isUserInRoom(uid, roomId, next); + }, + function (inRoom, next) { + if (!inRoom) { + return next(new Error('[[error:not-in-room]]')); + } - user.getUserFields(uid, ['banned', 'email:confirmed'], next); - }, - function (userData, next) { - if (parseInt(userData.banned, 10) === 1) { - return next(new Error('[[error:user-banned]]')); - } + Messaging.getUserCountInRoom(roomId, next); + }, + function (count, next) { + if (count < 2) { + return next(new Error('[[error:no-users-in-room]]')); + } - if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { - return next(new Error('[[error:email-not-confirmed-chat]]')); - } + user.getUserFields(uid, ['banned', 'email:confirmed'], next); + }, + function (userData, next) { + if (parseInt(userData.banned, 10) === 1) { + return next(new Error('[[error:user-banned]]')); + } - next(); + if (parseInt(meta.config.requireEmailConfirmation, 10) === 1 && parseInt(userData['email:confirmed'], 10) !== 1) { + return next(new Error('[[error:email-not-confirmed-chat]]')); } - ], callback); - }; - Messaging.hasPrivateChat = function (uid, withUid, callback) { - if (parseInt(uid, 10) === parseInt(withUid, 10)) { - return callback(null, 0); + next(); } - async.waterfall([ - function (next) { - async.parallel({ - myRooms: async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':chat:rooms', 0, -1), - theirRooms: async.apply(db.getSortedSetRevRange, 'uid:' + withUid + ':chat:rooms', 0, -1) - }, next); - }, - function (results, next) { - var roomIds = results.myRooms.filter(function (roomId) { - return roomId && results.theirRooms.indexOf(roomId) !== -1; - }); + ], callback); +}; - if (!roomIds.length) { - return callback(); - } +Messaging.hasPrivateChat = function (uid, withUid, callback) { + if (parseInt(uid, 10) === parseInt(withUid, 10)) { + return callback(null, 0); + } + async.waterfall([ + function (next) { + async.parallel({ + myRooms: async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':chat:rooms', 0, -1), + theirRooms: async.apply(db.getSortedSetRevRange, 'uid:' + withUid + ':chat:rooms', 0, -1) + }, next); + }, + function (results, next) { + var roomIds = results.myRooms.filter(function (roomId) { + return roomId && results.theirRooms.indexOf(roomId) !== -1; + }); - var index = 0; - var roomId = 0; - async.whilst(function () { - return index < roomIds.length && !roomId; - }, function (next) { - Messaging.getUserCountInRoom(roomIds[index], function (err, count) { - if (err) { - return next(err); - } - if (count === 2) { - roomId = roomIds[index]; - next(null, roomId); - } else { - ++ index; - next(); - } - }); - }, function (err) { - next(err, roomId); - }); + if (!roomIds.length) { + return callback(); } - ], callback); - }; - -}(exports)); + var index = 0; + var roomId = 0; + async.whilst(function () { + return index < roomIds.length && !roomId; + }, function (next) { + Messaging.getUserCountInRoom(roomIds[index], function (err, count) { + if (err) { + return next(err); + } + if (count === 2) { + roomId = roomIds[index]; + next(null, roomId); + } else { + ++ index; + next(); + } + }); + }, function (err) { + next(err, roomId); + }); + } + ], callback); +}; diff --git a/src/messaging/data.js b/src/messaging/data.js new file mode 100644 index 0000000000..d1a00bd8f4 --- /dev/null +++ b/src/messaging/data.js @@ -0,0 +1,136 @@ +'use strict'; + +var async = require('async'); +var S = require('string'); + +var db = require('../database'); +var user = require('../user'); +var utils = require('../../public/src/utils'); + +module.exports = function (Messaging) { + + Messaging.getMessageField = function (mid, field, callback) { + Messaging.getMessageFields(mid, [field], function (err, fields) { + callback(err, fields ? fields[field] : null); + }); + }; + + Messaging.getMessageFields = function (mid, fields, callback) { + db.getObjectFields('message:' + mid, fields, callback); + }; + + Messaging.setMessageField = function (mid, field, content, callback) { + db.setObjectField('message:' + mid, field, content, callback); + }; + + Messaging.setMessageFields = function (mid, data, callback) { + db.setObject('message:' + mid, data, callback); + }; + + Messaging.getMessagesData = function (mids, uid, roomId, isNew, callback) { + + var messages; + + async.waterfall([ + function (next) { + var keys = mids.map(function (mid) { + return 'message:' + mid; + }); + + db.getObjects(keys, next); + }, + function (_messages, next) { + messages = _messages.map(function (msg, idx) { + if (msg) { + msg.messageId = parseInt(mids[idx], 10); + } + return msg; + }).filter(Boolean); + + var uids = messages.map(function (msg) { + return msg && msg.fromuid; + }); + + user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], next); + }, + function (users, next) { + messages.forEach(function (message, index) { + message.fromUser = users[index]; + var self = parseInt(message.fromuid, 10) === parseInt(uid, 10); + message.self = self ? 1 : 0; + message.timestampISO = utils.toISOString(message.timestamp); + message.newSet = false; + message.roomId = String(message.roomId || roomId); + if (message.hasOwnProperty('edited')) { + message.editedISO = new Date(parseInt(message.edited, 10)).toISOString(); + } + }); + + async.map(messages, function (message, next) { + Messaging.parse(message.content, message.fromuid, uid, roomId, isNew, function (err, result) { + if (err) { + return next(err); + } + message.content = result; + message.cleanedContent = S(result).stripTags().decodeHTMLEntities().s; + next(null, message); + }); + }, next); + }, + function (messages, next) { + if (messages.length > 1) { + // 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 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) { + // If the previous message was from the other person, this is also a new set + message.newSet = true; + } + + return message; + }); + + next(undefined, messages); + } else if (messages.length === 1) { + // For single messages, we don't know the context, so look up the previous message and compare + var key = 'uid:' + uid + ':chat:room:' + roomId + ':mids'; + async.waterfall([ + async.apply(db.sortedSetRank, key, messages[0].messageId), + function (index, next) { + // Continue only if this isn't the first message in sorted set + if (index > 0) { + db.getSortedSetRange(key, index - 1, index - 1, next); + } else { + messages[0].newSet = true; + return next(undefined, messages); + } + }, + function (mid, next) { + Messaging.getMessageFields(mid, ['fromuid', 'timestamp'], next); + } + ], function (err, fields) { + if (err) { + return next(err); + } + + if ( + (parseInt(messages[0].timestamp, 10) > parseInt(fields.timestamp, 10) + (1000 * 60 * 5)) || + (parseInt(messages[0].fromuid, 10) !== parseInt(fields.fromuid, 10)) + ) { + // If it's been 5 minutes, this is a new set of messages + messages[0].newSet = true; + } + + next(undefined, messages); + }); + } else { + next(null, []); + } + } + ], callback); + }; + +}; From 949b4aaa1ffbefa67d441f52833c1f09b55c592c Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 3 Jan 2017 13:07:24 -0500 Subject: [PATCH 12/12] added logo to .gitignore so it doesn't get updated by test runner --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e744e31036..5f402cd667 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ pidfile /public/acp.min.js.map /public/installer.css /public/installer.min.js +/public/logo.png # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio *.iml