v0.9.3
- plugins.fireHook('action:user.loggedOut', {req: req, res: res, uid: uid});
plugins.fireHook('static:user.loggedOut', {req: req, res: res, uid: uid}, function() {
res.status(200).send('');
});
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index a558d665dd..8032ed7a0e 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -1,13 +1,13 @@
'use strict';
-var nconf = require('nconf'),
- async = require('async'),
- validator = require('validator'),
+var nconf = require('nconf');
+var async = require('async');
+var validator = require('validator');
- translator = require('../../public/src/modules/translator'),
- categories = require('../categories'),
- plugins = require('../plugins'),
- meta = require('../meta');
+var translator = require('../../public/src/modules/translator');
+var categories = require('../categories');
+var plugins = require('../plugins');
+var meta = require('../meta');
var helpers = {};
diff --git a/src/controllers/index.js b/src/controllers/index.js
index 282d6c1f0a..f0a6e8d023 100644
--- a/src/controllers/index.js
+++ b/src/controllers/index.js
@@ -12,6 +12,7 @@ var helpers = require('./helpers');
var Controllers = {
topics: require('./topics'),
+ posts: require('./posts'),
categories: require('./categories'),
category: require('./category'),
unread: require('./unread'),
@@ -95,17 +96,24 @@ Controllers.reset = function(req, res, next) {
};
Controllers.login = function(req, res, next) {
- var data = {},
- loginStrategies = require('../routes/authentication').getLoginStrategies(),
- registrationType = meta.config.registrationType || 'normal';
+ var data = {};
+ var loginStrategies = require('../routes/authentication').getLoginStrategies();
+ var registrationType = meta.config.registrationType || 'normal';
+
+ var allowLoginWith = (meta.config.allowLoginWith || 'username-email');
+
+ var errorText;
+ if (req.query.error === 'csrf-invalid') {
+ errorText = '[[error:csrf-invalid]]';
+ }
data.alternate_logins = loginStrategies.length > 0;
data.authentication = loginStrategies;
data.allowLocalLogin = parseInt(meta.config.allowLocalLogin, 10) === 1 || parseInt(req.query.local, 10) === 1;
data.allowRegistration = registrationType === 'normal' || registrationType === 'admin-approval';
- data.allowLoginWith = '[[login:' + (meta.config.allowLoginWith || 'username-email') + ']]';
+ data.allowLoginWith = '[[login:' + allowLoginWith + ']]';
data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:login]]'}]);
- data.error = req.flash('error')[0];
+ data.error = req.flash('error')[0] || errorText;
data.title = '[[pages:login]]';
if (!data.allowLocalLogin && !data.allowRegistration && data.alternate_logins && data.authentication.length === 1) {
@@ -113,8 +121,18 @@ Controllers.login = function(req, res, next) {
external: data.authentication[0].url
});
}
+ if (req.uid) {
+ user.getUserFields(req.uid, ['username', 'email'], function(err, user) {
+ if (err) {
+ return next(err);
+ }
+ data.username = allowLoginWith === 'email' ? user.email : user.username;
+ res.render('login', data);
+ });
+ } else {
+ res.render('login', data);
+ }
- res.render('login', data);
};
Controllers.register = function(req, res, next) {
@@ -124,6 +142,11 @@ Controllers.register = function(req, res, next) {
return next();
}
+ var errorText;
+ if (req.query.error === 'csrf-invalid') {
+ errorText = '[[error:csrf-invalid]]';
+ }
+
async.waterfall([
function(next) {
if (registrationType === 'invite-only' || registrationType === 'admin-invite-only') {
@@ -153,7 +176,7 @@ Controllers.register = function(req, res, next) {
data.termsOfUse = termsOfUse.postData.content;
data.breadcrumbs = helpers.buildBreadcrumbs([{text: '[[register:register]]'}]);
data.regFormEntry = [];
- data.error = req.flash('error')[0];
+ data.error = req.flash('error')[0] || errorText;
data.title = '[[pages:register]]';
res.render('register', data);
diff --git a/src/controllers/posts.js b/src/controllers/posts.js
new file mode 100644
index 0000000000..c8b490f65b
--- /dev/null
+++ b/src/controllers/posts.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var posts = require('../posts');
+var helpers = require('./helpers');
+
+var postsController = {};
+
+postsController.redirectToPost = function(req, res, callback) {
+ var pid = parseInt(req.params.pid, 10);
+ if (!pid) {
+ return callback();
+ }
+
+ posts.generatePostPath(pid, req.uid, function(err, path) {
+ if (err) {
+ return callback(err);
+ }
+
+ helpers.redirect(res, path);
+ });
+};
+
+
+module.exports = postsController;
diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js
index 223f7818a1..087889d340 100644
--- a/src/controllers/uploads.js
+++ b/src/controllers/uploads.js
@@ -83,7 +83,7 @@ function resizeImage(fileObj, callback) {
function(next) {
fullPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), '..', fileObj.url);
- image.load(fullPath, next);
+ image.size(fullPath, next);
},
function (imageData, next) {
if (imageData.width < parseInt(meta.config.maximumImageWidth, 10) || 760) {
@@ -185,6 +185,9 @@ function uploadFile(uid, uploadedFile, callback) {
if (meta.config.hasOwnProperty('allowedFileExtensions')) {
var allowed = file.allowedExtensions();
var extension = path.extname(uploadedFile.name);
+ if (!extension) {
+ extension = '.' + mime.extension(uploadedFile.type);
+ }
if (allowed.length > 0 && allowed.indexOf(extension) === -1) {
return callback(new Error('[[error:invalid-file-type, ' + allowed.join(', ') + ']]'));
}
@@ -195,7 +198,7 @@ function uploadFile(uid, uploadedFile, callback) {
function saveFileToLocal(uploadedFile, callback) {
var extension = path.extname(uploadedFile.name);
- if(!extension) {
+ if (!extension) {
extension = '.' + mime.extension(uploadedFile.type);
}
diff --git a/src/image.js b/src/image.js
index 7efe86c6ee..fca120d1e5 100644
--- a/src/image.js
+++ b/src/image.js
@@ -94,11 +94,19 @@ image.normalise = function(path, extension, callback) {
}
};
-image.load = function(path, callback) {
- new Jimp(path, function(err, data) {
- callback(err, data ? data.bitmap : null);
- });
-};
+image.size = function(path, callback) {
+ if (plugins.hasListeners('filter:image.size')) {
+ plugins.fireHook('filter:image.size', {
+ path: path,
+ }, function(err, image) {
+ callback(err, image);
+ });
+ } else {
+ new Jimp(path, function(err, data) {
+ callback(err, data ? data.bitmap : null);
+ });
+ }
+}
image.convertImageToBase64 = function(path, callback) {
fs.readFile(path, function(err, data) {
diff --git a/src/messaging/create.js b/src/messaging/create.js
index 3b2b5dcf26..cf78affb64 100644
--- a/src/messaging/create.js
+++ b/src/messaging/create.js
@@ -55,7 +55,8 @@ module.exports = function(Messaging) {
message = {
content: content,
timestamp: timestamp,
- fromuid: fromuid
+ fromuid: fromuid,
+ roomId: roomId
};
plugins.fireHook('filter:messaging.save', message, next);
diff --git a/src/meta/configs.js b/src/meta/configs.js
index 3ff42f66ae..69cc375b85 100644
--- a/src/meta/configs.js
+++ b/src/meta/configs.js
@@ -135,4 +135,4 @@ module.exports = function(Meta) {
db.deleteObjectField('config', field);
};
-};
\ No newline at end of file
+};
diff --git a/src/middleware/cls.js b/src/middleware/cls.js
new file mode 100644
index 0000000000..0118fa998f
--- /dev/null
+++ b/src/middleware/cls.js
@@ -0,0 +1,38 @@
+var path = require('path');
+var sockets = require('path');
+var websockets = require('../socket.io/');
+var continuationLocalStorage = require('continuation-local-storage');
+var APP_NAMESPACE = require(path.join(__dirname, '../../package.json')).name;
+var namespace = continuationLocalStorage.createNamespace(APP_NAMESPACE);
+
+(function(cls) {
+ cls.http = function (req, res, next) {
+ namespace.run(function() {
+ namespace.set('request', req);
+ next && next();
+ });
+ };
+
+ cls.socket = function (socket, payload, event, next) {
+ namespace.run(function() {
+ namespace.set('request', websockets.reqFromSocket(socket, payload, event));
+ next && next();
+ });
+ };
+
+ cls.get = function (key) {
+ return namespace.get(key);
+ };
+
+ cls.set = function (key, value) {
+ return namespace.set(key, value);
+ };
+
+ cls.setItem = cls.set;
+ cls.getItem = cls.set;
+ cls.namespace = namespace;
+ cls.continuationLocalStorage = continuationLocalStorage;
+
+})(exports);
+
+
diff --git a/src/middleware/index.js b/src/middleware/index.js
index 1cbac02323..f7e41b8f9b 100644
--- a/src/middleware/index.js
+++ b/src/middleware/index.js
@@ -14,6 +14,7 @@ var meta = require('../meta'),
compression = require('compression'),
favicon = require('serve-favicon'),
session = require('express-session'),
+ cls = require('./cls'),
useragent = require('express-useragent');
@@ -73,6 +74,7 @@ module.exports = function(app) {
app.use(middleware.addHeaders);
app.use(middleware.processRender);
+ app.use(cls.http);
auth.initialize(app, middleware);
return middleware;
diff --git a/src/middleware/middleware.js b/src/middleware/middleware.js
index d09270c3c9..bc07719ade 100644
--- a/src/middleware/middleware.js
+++ b/src/middleware/middleware.js
@@ -87,7 +87,9 @@ middleware.addHeaders = function (req, res, next) {
headers = _.pick(headers, Boolean); // Remove falsy headers
for(var key in headers) {
- res.setHeader(key, headers[key]);
+ if (headers.hasOwnProperty(key)) {
+ res.setHeader(key, headers[key]);
+ }
}
next();
@@ -103,6 +105,10 @@ middleware.pluginHooks = function(req, res, next) {
};
middleware.redirectToAccountIfLoggedIn = function(req, res, next) {
+ if (req.session.forceLogin) {
+ return next();
+ }
+
if (!req.user) {
return next();
}
@@ -159,16 +165,49 @@ middleware.checkAccountPermissions = function(req, res, next) {
});
};
+middleware.redirectUidToUserslug = function(req, res, next) {
+ var uid = parseInt(req.params.uid, 10);
+ if (!uid) {
+ return next();
+ }
+ user.getUserField(uid, 'userslug', function(err, userslug) {
+ if (err || !userslug) {
+ return next(err);
+ }
+
+ var path = req.path.replace(/^\/api/, '')
+ .replace('uid', 'user')
+ .replace(uid, function() { return userslug; });
+ controllers.helpers.redirect(res, path);
+ });
+};
+
middleware.isAdmin = function(req, res, next) {
if (!req.uid) {
return controllers.helpers.notAllowed(req, res);
}
user.isAdministrator(req.uid, function (err, isAdmin) {
- if (err || isAdmin) {
+ if (err) {
return next(err);
}
+ if (isAdmin) {
+ var loginTime = req.session.meta ? req.session.meta.datetime : 0;
+ if (loginTime && parseInt(loginTime, 10) > Date.now() - 3600000) {
+ return next();
+ }
+
+ req.session.returnTo = nconf.get('relative_path') + req.path.replace(/^\/api/, '');
+ req.session.forceLogin = 1;
+ if (res.locals.isAPI) {
+ res.status(401).json({});
+ } else {
+ res.redirect('/login');
+ }
+ return;
+ }
+
if (res.locals.isAPI) {
return controllers.helpers.notAllowed(req, res);
}
diff --git a/src/middleware/render.js b/src/middleware/render.js
index 0dd0543600..aacc20c281 100644
--- a/src/middleware/render.js
+++ b/src/middleware/render.js
@@ -98,7 +98,7 @@ module.exports = function(middleware) {
};
function buildBodyClass(req) {
- var clean = req.path.replace(/^\/api/, '').replace(/^\//, '');
+ var clean = req.path.replace(/^\/api/, '').replace(/^\/|\/$/g, '');
var parts = clean.split('/').slice(0, 3);
parts.forEach(function(p, index) {
parts[index] = index ? parts[0] + '-' + p : 'page-' + (p || 'home');
diff --git a/src/notifications.js b/src/notifications.js
index 8a6b41d303..4f6d73f385 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -12,6 +12,7 @@ var User = require('./user');
var groups = require('./groups');
var meta = require('./meta');
var plugins = require('./plugins');
+var utils = require('../public/src/utils');
(function(Notifications) {
@@ -45,6 +46,8 @@ var plugins = require('./plugins');
return next(null, null);
}
+ notification.datetimeISO = utils.toISOString(notification.datetime);
+
if (notification.bodyLong) {
notification.bodyLong = S(notification.bodyLong).escapeHTML().s;
}
@@ -85,7 +88,7 @@ var plugins = require('./plugins');
// Removes nids that have been pruned
db.isSortedSetMembers('notifications', nids, function(err, exists) {
if (err) {
- return callbacK(err);
+ return callback(err);
}
nids = nids.filter(function(notifId, idx) {
diff --git a/src/plugins/hooks.js b/src/plugins/hooks.js
index 3330608154..1b186857f4 100644
--- a/src/plugins/hooks.js
+++ b/src/plugins/hooks.js
@@ -5,9 +5,29 @@ var winston = require('winston'),
module.exports = function(Plugins) {
Plugins.deprecatedHooks = {
- 'filter:user.delete': 'static:user.delete',
- 'filter:user.custom_fields': null,
- 'action:user.loggedOut': 'static:user.loggedOut'
+ 'filter:user.custom_fields': null // remove in v1.1.0
+ };
+
+ Plugins.deprecatedHooksParams = {
+ 'action:homepage.get': '{req, res}',
+ 'filter:register.check': '{req, res}',
+ 'action:user.loggedOut': '{req, res}',
+ 'static:user.loggedOut': '{req, res}',
+ 'filter:categories.build': '{req, res}',
+ 'filter:category.build': '{req, res}',
+ 'filter:group.build': '{req, res}',
+ 'filter:register.build': '{req, res}',
+ 'filter:composer.build': '{req, res}',
+ 'filter:popular.build': '{req, res}',
+ 'filter:recent.build': '{req, res}',
+ 'filter:topic.build': '{req, res}',
+ 'filter:users.build': '{req, res}',
+ 'filter:admin.category.get': '{req, res}',
+ 'filter:middleware.renderHeader': '{req, res}',
+ 'filter:widget.render': '{req, res}',
+ 'filter:middleware.buildHeader': '{req, locals}',
+ 'action:middleware.pageView': '{req}',
+ 'action:meta.override404': '{req}'
};
/*
@@ -29,12 +49,23 @@ module.exports = function(Plugins) {
var method;
if (Object.keys(Plugins.deprecatedHooks).indexOf(data.hook) !== -1) {
- winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' +
+ winston.warn('[plugins/' + id + '] Hook `' + data.hook + '` is deprecated, ' +
(Plugins.deprecatedHooks[data.hook] ?
'please use `' + Plugins.deprecatedHooks[data.hook] + '` instead.' :
'there is no alternative.'
)
);
+ } else {
+ // handle hook's startsWith, i.e. action:homepage.get
+ var _parts = data.hook.split(':');
+ _parts.pop();
+ var _hook = _parts.join(':');
+ if (Plugins.deprecatedHooksParams[_hook]) {
+ winston.warn('[plugins/' + id + '] Hook `' + _hook + '` parameters: `' + Plugins.deprecatedHooksParams[_hook] + '`, are being deprecated, '
+ + 'all plugins should now use the `middleware/cls` module instead of hook\'s arguments to get a reference to the `req`, `res` and/or `socket` object(s) (from which you can get the current `uid` if you need to.) '
+ + '- for more info, visit https://docs.nodebb.org/en/latest/plugins/create.html#getting-a-reference-to-req-res-socket-and-uid-within-any-plugin-hook');
+ delete Plugins.deprecatedHooksParams[_hook];
+ }
}
if (data.hook && data.method) {
diff --git a/src/posts/topics.js b/src/posts/topics.js
index a9946fdace..a653e69b03 100644
--- a/src/posts/topics.js
+++ b/src/posts/topics.js
@@ -1,8 +1,10 @@
'use strict';
-var async = require('async'),
- topics = require('../topics');
+var async = require('async');
+
+var topics = require('../topics');
+var utils = require('../../public/src/utils');
module.exports = function(Posts) {
@@ -42,4 +44,45 @@ module.exports = function(Posts) {
], callback);
};
+ Posts.generatePostPath = function (pid, uid, callback) {
+ Posts.generatePostPaths([pid], uid, function(err, paths) {
+ callback(err, Array.isArray(paths) && paths.length ? paths[0] : null);
+ });
+ };
+
+ Posts.generatePostPaths = function (pids, uid, callback) {
+ async.waterfall([
+ function (next) {
+ Posts.getPostsFields(pids, ['pid', 'tid'], next);
+ },
+ function (postData, next) {
+ async.parallel({
+ indices: function(next) {
+ Posts.getPostIndices(postData, uid, next);
+ },
+ topics: function(next) {
+ var tids = postData.map(function(post) {
+ return post ? post.tid : null;
+ });
+
+ topics.getTopicsFields(tids, ['slug'], next);
+ }
+ }, next);
+ },
+ function (results, next) {
+ var paths = pids.map(function(pid, index) {
+ var slug = results.topics[index] ? results.topics[index].slug : null;
+ var postIndex = utils.isNumber(results.indices[index]) ? parseInt(results.indices[index], 10) + 1 : null;
+
+ if (slug && postIndex) {
+ return '/topic/' + slug + '/' + postIndex;
+ }
+ return null;
+ });
+
+ next(null, paths);
+ }
+ ], callback);
+ };
+
};
\ No newline at end of file
diff --git a/src/routes/accounts.js b/src/routes/accounts.js
index 8c7a505d0b..9ee4b3af20 100644
--- a/src/routes/accounts.js
+++ b/src/routes/accounts.js
@@ -7,6 +7,8 @@ module.exports = function (app, middleware, controllers) {
var middlewares = [middleware.checkGlobalPrivacySettings];
var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions];
+ setupPageRoute(app, '/uid/:uid/:section?', middleware, [], middleware.redirectUidToUserslug);
+
setupPageRoute(app, '/user/:userslug', middleware, middlewares, controllers.accounts.profile.get);
setupPageRoute(app, '/user/:userslug/following', middleware, middlewares, controllers.accounts.follow.getFollowing);
setupPageRoute(app, '/user/:userslug/followers', middleware, middlewares, controllers.accounts.follow.getFollowers);
diff --git a/src/routes/index.js b/src/routes/index.js
index 8efdc1f2a7..86198b56dd 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -1,23 +1,23 @@
"use strict";
-var nconf = require('nconf'),
- path = require('path'),
- async = require('async'),
- winston = require('winston'),
- controllers = require('../controllers'),
- plugins = require('../plugins'),
- express = require('express'),
- validator = require('validator'),
+var nconf = require('nconf');
+var path = require('path');
+var async = require('async');
+var winston = require('winston');
+var controllers = require('../controllers');
+var plugins = require('../plugins');
+var express = require('express');
+var validator = require('validator');
- accountRoutes = require('./accounts'),
+var accountRoutes = require('./accounts');
- metaRoutes = require('./meta'),
- apiRoutes = require('./api'),
- adminRoutes = require('./admin'),
- feedRoutes = require('./feeds'),
- pluginRoutes = require('./plugins'),
- authRoutes = require('./authentication'),
- helpers = require('./helpers');
+var metaRoutes = require('./meta');
+var apiRoutes = require('./api');
+var adminRoutes = require('./admin');
+var feedRoutes = require('./feeds');
+var pluginRoutes = require('./plugins');
+var authRoutes = require('./authentication');
+var helpers = require('./helpers');
var setupPageRoute = helpers.setupPageRoute;
@@ -46,6 +46,10 @@ function topicRoutes(app, middleware, controllers) {
setupPageRoute(app, '/topic/:topic_id/:slug?', middleware, [], controllers.topics.get);
}
+function postRoutes(app, middleware, controllers) {
+ setupPageRoute(app, '/post/:pid', middleware, [], controllers.posts.redirectToPost);
+}
+
function tagRoutes(app, middleware, controllers) {
setupPageRoute(app, '/tags/:tag', middleware, [middleware.privateTagListing], controllers.tags.getTag);
setupPageRoute(app, '/tags', middleware, [middleware.privateTagListing], controllers.tags.getTags);
@@ -71,7 +75,6 @@ function userRoutes(app, middleware, controllers) {
setupPageRoute(app, '/users/banned', middleware, middlewares, controllers.users.getBannedUsers);
}
-
function groupRoutes(app, middleware, controllers) {
var middlewares = [middleware.checkGlobalPrivacySettings, middleware.exposeGroupName];
@@ -124,6 +127,7 @@ module.exports = function(app, middleware, hotswapIds) {
mainRoutes(router, middleware, controllers);
topicRoutes(router, middleware, controllers);
+ postRoutes(router, middleware, controllers);
globalModRoutes(router, middleware, controllers);
tagRoutes(router, middleware, controllers);
categoryRoutes(router, middleware, controllers);
diff --git a/src/socket.io/helpers.js b/src/socket.io/helpers.js
index e4621811ca..69a4a26b54 100644
--- a/src/socket.io/helpers.js
+++ b/src/socket.io/helpers.js
@@ -3,7 +3,6 @@
var async = require('async');
var winston = require('winston');
var S = require('string');
-var nconf = require('nconf');
var websockets = require('./index');
var user = require('../user');
@@ -53,24 +52,24 @@ SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification)
if (!pid || !fromuid || !notification) {
return;
}
- posts.getPostFields(pid, ['tid', 'uid', 'content'], function(err, postData) {
- if (err) {
- return;
- }
-
- if (!postData.uid || fromuid === parseInt(postData.uid, 10)) {
- return;
- }
-
- async.parallel({
- username: async.apply(user.getUserField, fromuid, 'username'),
- topicTitle: async.apply(topics.getTopicField, postData.tid, 'title'),
- postObj: async.apply(posts.parsePost, postData)
- }, function(err, results) {
- if (err) {
+ fromuid = parseInt(fromuid, 10);
+ var postData;
+ async.waterfall([
+ function (next) {
+ posts.getPostFields(pid, ['tid', 'uid', 'content'], next);
+ },
+ function (_postData, next) {
+ postData = _postData;
+ if (!postData.uid || fromuid === parseInt(postData.uid, 10)) {
return;
}
-
+ async.parallel({
+ username: async.apply(user.getUserField, fromuid, 'username'),
+ topicTitle: async.apply(topics.getTopicField, postData.tid, 'title'),
+ postObj: async.apply(posts.parsePost, postData)
+ }, next);
+ },
+ function (results, next) {
var title = S(results.topicTitle).decodeHTMLEntities().s;
var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ',');
@@ -78,16 +77,20 @@ SocketHelpers.sendNotificationToPostOwner = function(pid, fromuid, notification)
bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]',
bodyLong: results.postObj.content,
pid: pid,
+ path: '/post/' + pid,
nid: 'post:' + pid + ':uid:' + fromuid,
from: fromuid,
mergeId: notification + '|' + pid,
topicTitle: results.topicTitle
- }, function(err, notification) {
- if (!err && notification) {
- notifications.push(notification, [postData.uid]);
- }
- });
- });
+ }, next);
+ }
+ ], function(err, notification) {
+ if (err) {
+ return winston.error(err);
+ }
+ if (notification) {
+ notifications.push(notification, [postData.uid]);
+ }
});
};
@@ -97,27 +100,38 @@ SocketHelpers.sendNotificationToTopicOwner = function(tid, fromuid, notification
return;
}
- async.parallel({
- username: async.apply(user.getUserField, fromuid, 'username'),
- topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug', 'title']),
- }, function(err, results) {
- if (err || fromuid === parseInt(results.topicData.uid, 10)) {
- return;
- }
+ fromuid = parseInt(fromuid, 10);
- var title = S(results.topicData.title).decodeHTMLEntities().s;
- var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ',');
-
- notifications.create({
- bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]',
- path: nconf.get('relative_path') + '/topic/' + results.topicData.slug,
- nid: 'tid:' + tid + ':uid:' + fromuid,
- from: fromuid
- }, function(err, notification) {
- if (!err && notification) {
- notifications.push(notification, [results.topicData.uid]);
+ var ownerUid;
+ async.waterfall([
+ function (next) {
+ async.parallel({
+ username: async.apply(user.getUserField, fromuid, 'username'),
+ topicData: async.apply(topics.getTopicFields, tid, ['uid', 'slug', 'title']),
+ }, next);
+ },
+ function (results, next) {
+ if (fromuid === parseInt(results.topicData.uid, 10)) {
+ return;
}
- });
+ ownerUid = results.topicData.uid;
+ var title = S(results.topicData.title).decodeHTMLEntities().s;
+ var titleEscaped = title.replace(/%/g, '%').replace(/,/g, ',');
+
+ notifications.create({
+ bodyShort: '[[' + notification + ', ' + results.username + ', ' + titleEscaped + ']]',
+ path: '/topic/' + results.topicData.slug,
+ nid: 'tid:' + tid + ':uid:' + fromuid,
+ from: fromuid
+ }, next);
+ }
+ ], function(err, notification) {
+ if (err) {
+ return winston.error(err);
+ }
+ if (notification && parseInt(ownerUid, 10)) {
+ notifications.push(notification, [ownerUid]);
+ }
});
};
diff --git a/src/socket.io/index.js b/src/socket.io/index.js
index 8f6f3c4c5a..fc97f7cc87 100644
--- a/src/socket.io/index.js
+++ b/src/socket.io/index.js
@@ -10,209 +10,224 @@ var winston = require('winston');
var db = require('../database');
var logger = require('../logger');
var ratelimit = require('../middleware/ratelimit');
+var cls = require('../middleware/cls');
-var Sockets = {};
-var Namespaces = {};
+(function(Sockets) {
+ var Namespaces = {};
+ var io;
-var io;
+ Sockets.init = function (server) {
+ requireModules();
-Sockets.init = function(server) {
- requireModules();
+ io = new SocketIO({
+ path: nconf.get('relative_path') + '/socket.io'
+ });
- io = new SocketIO({
- path: nconf.get('relative_path') + '/socket.io'
- });
+ addRedisAdapter(io);
- addRedisAdapter(io);
+ io.use(socketioWildcard);
+ io.use(authorize);
- io.use(socketioWildcard);
- io.use(authorize);
+ io.on('connection', onConnection);
+ io.on('disconnect', onDisconnect);
- io.on('connection', onConnection);
+ io.listen(server, {
+ transports: nconf.get('socket.io:transports')
+ });
- io.listen(server, {
- transports: nconf.get('socket.io:transports')
- });
-
- Sockets.server = io;
-};
-
-function onConnection(socket) {
- socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
-
- logger.io_one(socket, socket.uid);
-
- onConnect(socket);
-
- socket.on('*', function(payload) {
- onMessage(socket, payload);
- });
-}
-
-function onConnect(socket) {
- if (socket.uid) {
- socket.join('uid_' + socket.uid);
- socket.join('online_users');
- } else {
- socket.join('online_guests');
- }
-}
-
-
-function onMessage(socket, payload) {
- if (!payload.data.length) {
- return winston.warn('[socket.io] Empty payload');
- }
-
- var eventName = payload.data[0];
- var params = payload.data[1];
- var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function() {};
-
- if (!eventName) {
- return winston.warn('[socket.io] Empty method name');
- }
-
- var parts = eventName.toString().split('.');
- var namespace = parts[0];
- var methodToCall = parts.reduce(function(prev, cur) {
- if (prev !== null && prev[cur]) {
- return prev[cur];
- } else {
- return null;
- }
- }, Namespaces);
-
- if(!methodToCall) {
- if (process.env.NODE_ENV === 'development') {
- winston.warn('[socket.io] Unrecognized message: ' + eventName);
- }
- return;
- }
-
- socket.previousEvents = socket.previousEvents || [];
- socket.previousEvents.push(eventName);
- if (socket.previousEvents.length > 20) {
- socket.previousEvents.shift();
- }
-
- if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
- winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents);
- return socket.disconnect();
- }
-
- async.waterfall([
- function (next) {
- validateSession(socket, next);
- },
- function (next) {
- if (Namespaces[namespace].before) {
- Namespaces[namespace].before(socket, eventName, params, next);
- } else {
- next();
- }
- },
- function (next) {
- methodToCall(socket, params, next);
- }
- ], function(err, result) {
- callback(err ? {message: err.message} : null, result);
- });
-}
-
-function requireModules() {
- var modules = ['admin', 'categories', 'groups', 'meta', 'modules',
- 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist'
- ];
-
- modules.forEach(function(module) {
- Namespaces[module] = require('./' + module);
- });
-}
-
-function validateSession(socket, callback) {
- var req = socket.request;
- if (!req.signedCookies || !req.signedCookies['express.sid']) {
- return callback(new Error('[[error:invalid-session]]'));
- }
- db.sessionStore.get(req.signedCookies['express.sid'], function(err, sessionData) {
- if (err || !sessionData) {
- return callback(err || new Error('[[error:invalid-session]]'));
- }
-
- callback();
- });
-}
-
-function authorize(socket, callback) {
- var request = socket.request;
-
- if (!request) {
- return callback(new Error('[[error:not-authorized]]'));
- }
-
- async.waterfall([
- function(next) {
- cookieParser(request, {}, next);
- },
- function(next) {
- db.sessionStore.get(request.signedCookies['express.sid'], function(err, sessionData) {
- if (err) {
- return next(err);
- }
- if (sessionData && sessionData.passport && sessionData.passport.user) {
- request.session = sessionData;
- socket.uid = parseInt(sessionData.passport.user, 10);
- } else {
- socket.uid = 0;
- }
- next();
- });
- }
- ], callback);
-}
-
-function addRedisAdapter(io) {
- if (nconf.get('redis')) {
- var redisAdapter = require('socket.io-redis');
- var redis = require('../database/redis');
- var pub = redis.connect({return_buffers: true});
- var sub = redis.connect({return_buffers: true});
-
- io.adapter(redisAdapter({pubClient: pub, subClient: sub}));
- } else if (nconf.get('isCluster') === 'true') {
- winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.');
- }
-}
-
-Sockets.in = function(room) {
- return io.in(room);
-};
-
-Sockets.getUserSocketCount = function(uid) {
- if (!io) {
- return 0;
- }
-
- var room = io.sockets.adapter.rooms['uid_' + uid];
- return room ? room.length : 0;
-};
-
-
-Sockets.reqFromSocket = function(socket) {
- var headers = socket.request.headers;
- var host = headers.host;
- var referer = headers.referer || '';
-
- return {
- ip: headers['x-forwarded-for'] || socket.ip,
- host: host,
- protocol: socket.request.connection.encrypted ? 'https' : 'http',
- secure: !!socket.request.connection.encrypted,
- url: referer,
- path: referer.substr(referer.indexOf(host) + host.length),
- headers: headers
+ Sockets.server = io;
};
-};
+
+ function onConnection(socket) {
+ socket.ip = socket.request.headers['x-forwarded-for'] || socket.request.connection.remoteAddress;
+
+ logger.io_one(socket, socket.uid);
+
+ cls.socket(socket, null, 'connection', function () {
+ onConnect(socket);
+ });
+
+ socket.on('*', function (payload) {
+ cls.socket(socket, payload, null, function () {
+ onMessage(socket, payload);
+ });
+ });
+ }
+
+ function onConnect(socket) {
+ if (socket.uid) {
+ socket.join('uid_' + socket.uid);
+ socket.join('online_users');
+ } else {
+ socket.join('online_guests');
+ }
+ }
+
+ function onDisconnect(socket) {
+ cls.socket(socket, null, 'disconnect', function () {
+ });
+ }
-module.exports = Sockets;
+ function onMessage(socket, payload) {
+ if (!payload.data.length) {
+ return winston.warn('[socket.io] Empty payload');
+ }
+
+ var eventName = payload.data[0];
+ var params = payload.data[1];
+ var callback = typeof payload.data[payload.data.length - 1] === 'function' ? payload.data[payload.data.length - 1] : function () {
+ };
+
+ if (!eventName) {
+ return winston.warn('[socket.io] Empty method name');
+ }
+
+ var parts = eventName.toString().split('.');
+ var namespace = parts[0];
+ var methodToCall = parts.reduce(function (prev, cur) {
+ if (prev !== null && prev[cur]) {
+ return prev[cur];
+ } else {
+ return null;
+ }
+ }, Namespaces);
+
+ if (!methodToCall) {
+ if (process.env.NODE_ENV === 'development') {
+ winston.warn('[socket.io] Unrecognized message: ' + eventName);
+ }
+ return;
+ }
+
+ socket.previousEvents = socket.previousEvents || [];
+ socket.previousEvents.push(eventName);
+ if (socket.previousEvents.length > 20) {
+ socket.previousEvents.shift();
+ }
+
+ if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) {
+ winston.warn('[socket.io] Too many emits! Disconnecting uid : ' + socket.uid + '. Events : ' + socket.previousEvents);
+ return socket.disconnect();
+ }
+
+ async.waterfall([
+ function (next) {
+ validateSession(socket, next);
+ },
+ function (next) {
+ if (Namespaces[namespace].before) {
+ Namespaces[namespace].before(socket, eventName, params, next);
+ } else {
+ next();
+ }
+ },
+ function (next) {
+ methodToCall(socket, params, next);
+ }
+ ], function (err, result) {
+ callback(err ? {message: err.message} : null, result);
+ });
+ }
+
+ function requireModules() {
+ var modules = ['admin', 'categories', 'groups', 'meta', 'modules',
+ 'notifications', 'plugins', 'posts', 'topics', 'user', 'blacklist'
+ ];
+
+ modules.forEach(function (module) {
+ Namespaces[module] = require('./' + module);
+ });
+ }
+
+ function validateSession(socket, callback) {
+ var req = socket.request;
+ if (!req.signedCookies || !req.signedCookies['express.sid']) {
+ return callback(new Error('[[error:invalid-session]]'));
+ }
+ db.sessionStore.get(req.signedCookies['express.sid'], function (err, sessionData) {
+ if (err || !sessionData) {
+ return callback(err || new Error('[[error:invalid-session]]'));
+ }
+
+ callback();
+ });
+ }
+
+ function authorize(socket, callback) {
+ var request = socket.request;
+
+ if (!request) {
+ return callback(new Error('[[error:not-authorized]]'));
+ }
+
+ async.waterfall([
+ function (next) {
+ cookieParser(request, {}, next);
+ },
+ function (next) {
+ db.sessionStore.get(request.signedCookies['express.sid'], function (err, sessionData) {
+ if (err) {
+ return next(err);
+ }
+ if (sessionData && sessionData.passport && sessionData.passport.user) {
+ request.session = sessionData;
+ socket.uid = parseInt(sessionData.passport.user, 10);
+ } else {
+ socket.uid = 0;
+ }
+ next();
+ });
+ }
+ ], callback);
+ }
+
+ function addRedisAdapter(io) {
+ if (nconf.get('redis')) {
+ var redisAdapter = require('socket.io-redis');
+ var redis = require('../database/redis');
+ var pub = redis.connect({return_buffers: true});
+ var sub = redis.connect({return_buffers: true});
+
+ io.adapter(redisAdapter({pubClient: pub, subClient: sub}));
+ } else if (nconf.get('isCluster') === 'true') {
+ winston.warn('[socket.io] Clustering detected, you are advised to configure Redis as a websocket store.');
+ }
+ }
+
+ Sockets.in = function (room) {
+ return io.in(room);
+ };
+
+ Sockets.getUserSocketCount = function (uid) {
+ if (!io) {
+ return 0;
+ }
+
+ var room = io.sockets.adapter.rooms['uid_' + uid];
+ return room ? room.length : 0;
+ };
+
+
+ Sockets.reqFromSocket = function (socket, payload, event) {
+ var headers = socket.request.headers;
+ var host = headers.host;
+ var referer = headers.referer || '';
+ var data = ((payload || {}).data || []);
+
+ return {
+ uid: socket.uid,
+ params: data[1],
+ method: event || data[0],
+ body: payload,
+ ip: headers['x-forwarded-for'] || socket.ip,
+ host: host,
+ protocol: socket.request.connection.encrypted ? 'https' : 'http',
+ secure: !!socket.request.connection.encrypted,
+ url: referer,
+ path: referer.substr(referer.indexOf(host) + host.length),
+ headers: headers
+ };
+ };
+
+})(exports);
\ No newline at end of file
diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js
index 121ede2a96..db6753bc40 100644
--- a/src/socket.io/notifications.js
+++ b/src/socket.io/notifications.js
@@ -56,28 +56,4 @@ SocketNotifs.markAllRead = function(socket, data, callback) {
notifications.markAllRead(socket.uid, callback);
};
-SocketNotifs.generatePath = function(socket, nid, callback) {
- if (!socket.uid) {
- return callback(new Error('[[error:no-privileges]]'));;
- }
- async.waterfall([
- function (next) {
- notifications.get(nid, next);
- },
- function (notification, next) {
- if (!notification) {
- return next(null, '');
- }
- user.notifications.generateNotificationPaths([notification], socket.uid, next);
- },
- function (notificationsData, next) {
- if (notificationsData && notificationsData.length) {
- next(null, notificationsData[0].path);
- } else {
- next();
- }
- }
- ], callback);
-};
-
module.exports = SocketNotifs;
diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js
index bbf7a4721c..dbb3f7f551 100644
--- a/src/socket.io/posts/flag.js
+++ b/src/socket.io/posts/flag.js
@@ -90,6 +90,7 @@ module.exports = function(SocketPosts) {
bodyShort: '[[notifications:user_flagged_post_in, ' + flaggingUser.username + ', ' + titleEscaped + ']]',
bodyLong: post.content,
pid: data.pid,
+ path: '/post/' + data.pid,
nid: 'post_flag:' + data.pid + ':uid:' + socket.uid,
from: socket.uid,
mergeId: 'notifications:user_flagged_post_in|' + data.pid,
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index c96a77265e..6f5ab296d9 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -69,7 +69,7 @@ SocketTopics.createTopicFromPosts = function(socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
- topics.createTopicFromPosts(socket.uid, data.title, data.pids, callback);
+ topics.createTopicFromPosts(socket.uid, data.title, data.pids, data.fromTid, callback);
};
SocketTopics.toggleFollow = function(socket, tid, callback) {
diff --git a/src/socket.io/user.js b/src/socket.io/user.js
index 75ecd949fd..c38bcc5bbf 100644
--- a/src/socket.io/user.js
+++ b/src/socket.io/user.js
@@ -155,7 +155,7 @@ SocketUser.follow = function(socket, data, callback) {
bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]',
nid: 'follow:' + data.uid + ':uid:' + socket.uid,
from: socket.uid,
- path: '/user/' + userData.userslug,
+ path: '/uid/' + socket.uid,
mergeId: 'notifications:user_started_following_you'
}, next);
},
diff --git a/src/topics.js b/src/topics.js
index 385d3ae558..fbdd507d9a 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -328,4 +328,88 @@ var social = require('./social');
}
};
+ Topics.getTopicBookmarks = function( tid, callback ){
+ db.getSortedSetRangeWithScores(['tid:' + tid + ':bookmarks'], 0, -1, callback );
+ };
+
+ Topics.updateTopicBookmarks = function( tid, pids, callback ){
+ var maxIndex;
+ var Posts = posts;
+ async.waterfall([
+ function(next){
+ Topics.getPostCount( tid, next );
+ },
+ function(postcount, next){
+ maxIndex = postcount;
+ Topics.getTopicBookmarks( tid, next );
+ },
+ function(bookmarks, next){
+ var uids = bookmarks.map( function( bookmark ){return bookmark.value});
+ var forkedPosts = pids.map( function( pid ){ return { pid: pid, tid: tid }; } );
+ var uidBookmark = new Object();
+ var uidData = bookmarks.map(
+ function( bookmark ){
+ var u = new Object();
+ u.uid = bookmark.value;
+ u.bookmark = bookmark.score;
+ return u;
+ } );
+ async.map(
+ uidData,
+ function( data, mapCallback ){
+ Posts.getPostIndices(
+ forkedPosts,
+ data.uid,
+ function( err, indices ){
+ if( err ){
+ callback( err );
+ }
+ data.postIndices = indices;
+ mapCallback( null, data );
+ } )
+ },
+ function( err, results ){
+ if( err ){
+ return callback();
+ }
+ async.map(
+ results,
+ function( data, mapCallback ){
+ var uid = data.uid;
+ var bookmark = data.bookmark;
+ bookmark = bookmark < maxIndex ? bookmark : maxIndex;
+ var postIndices = data.postIndices;
+ var i;
+ for( i = 0; i < postIndices.length && postIndices[i] < data.bookmark; ++i ){
+ --bookmark;
+ }
+
+ if( bookmark != data.bookmark ){
+ mapCallback( null, { uid: uid, bookmark: bookmark } );
+ }
+ else{
+ mapCallback( null, null );
+ }
+ },
+ function( err, results ){
+ async.map( results,
+ function(ui, cb ){
+ if( ui && ui.bookmark){
+ Topics.setUserBookmark( tid, ui.uid, ui.bookmark, cb );
+ }
+ else{
+ return cb( null, null );
+ }
+ },
+ function( err, results ){
+ next();
+ }
+ );
+ }
+ );
+ }
+ );
+ }],
+ function( err, result ){ callback();} );
+ };
}(exports));
diff --git a/src/topics/follow.js b/src/topics/follow.js
index c05a796741..90708ba50a 100644
--- a/src/topics/follow.js
+++ b/src/topics/follow.js
@@ -12,6 +12,7 @@ var notifications = require('../notifications');
var privileges = require('../privileges');
var meta = require('../meta');
var emailer = require('../emailer');
+var plugins = require('../plugins');
module.exports = function(Topics) {
@@ -57,6 +58,7 @@ module.exports = function(Topics) {
}
db.setAdd('tid:' + tid + ':followers', uid, next);
},
+ async.apply(plugins.fireHook, 'action:topic.follow', { uid: uid, tid: tid }),
function(next) {
db.sortedSetAdd('uid:' + uid + ':followed_tids', Date.now(), tid, next);
}
@@ -75,6 +77,7 @@ module.exports = function(Topics) {
}
db.setRemove('tid:' + tid + ':followers', uid, next);
},
+ async.apply(plugins.fireHook, 'action:topic.unfollow', { uid: uid, tid: tid }),
function(next) {
db.sortedSetRemove('uid:' + uid + ':followed_tids', tid, next);
}
@@ -138,6 +141,7 @@ module.exports = function(Topics) {
bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
bodyLong: postData.content,
pid: postData.pid,
+ path: '/post/' + postData.pid,
nid: 'new_post:tid:' + postData.topic.tid + ':pid:' + postData.pid + ':uid:' + exceptUid,
tid: postData.topic.tid,
from: exceptUid,
diff --git a/src/topics/fork.js b/src/topics/fork.js
index 80143e4d46..768b656d2a 100644
--- a/src/topics/fork.js
+++ b/src/topics/fork.js
@@ -13,7 +13,7 @@ var meta = require('../meta');
module.exports = function(Topics) {
- Topics.createTopicFromPosts = function(uid, title, pids, callback) {
+ Topics.createTopicFromPosts = function(uid, title, pids, fromTid, callback) {
if (title) {
title = title.trim();
}
@@ -55,6 +55,9 @@ module.exports = function(Topics) {
}
Topics.create({uid: results.postData.uid, title: title, cid: cid}, next);
},
+ function( results, next) {
+ Topics.updateTopicBookmarks(fromTid, pids, function(){ next( null, results );} );
+ },
function(_tid, next) {
function move(pid, next) {
privileges.posts.canEdit(pid, uid, function(err, canEdit) {
diff --git a/src/upgrade.js b/src/upgrade.js
index 87d2dc7c78..1874db129e 100644
--- a/src/upgrade.js
+++ b/src/upgrade.js
@@ -6,7 +6,7 @@ var db = require('./database'),
Upgrade = {},
- minSchemaDate = Date.UTC(2015, 7, 18), // This value gets updated every new MINOR version
+ minSchemaDate = Date.UTC(2015, 10, 6), // This value gets updated every new MAJOR version
schemaDate, thisSchemaDate,
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
@@ -62,88 +62,6 @@ Upgrade.upgrade = function(callback) {
}
});
},
- function(next) {
- thisSchemaDate = Date.UTC(2015, 8, 30);
- if (schemaDate < thisSchemaDate) {
- updatesMade = true;
- winston.info('[2015/09/30] Converting default Gravatar image to default User Avatar');
-
- async.waterfall([
- async.apply(db.isObjectField, 'config', 'customGravatarDefaultImage'),
- function(keyExists, _next) {
- if (keyExists) {
- _next();
- } else {
- winston.info('[2015/09/30] Converting default Gravatar image to default User Avatar skipped');
- Upgrade.update(thisSchemaDate, next);
- next();
- }
- },
- async.apply(db.getObjectField, 'config', 'customGravatarDefaultImage'),
- async.apply(db.setObjectField, 'config', 'defaultAvatar'),
- async.apply(db.deleteObjectField, 'config', 'customGravatarDefaultImage')
- ], function(err) {
- if (err) {
- return next(err);
- }
-
- winston.info('[2015/09/30] Converting default Gravatar image to default User Avatar done');
- Upgrade.update(thisSchemaDate, next);
- });
- } else {
- winston.info('[2015/09/30] Converting default Gravatar image to default User Avatar skipped');
- next();
- }
- },
- function(next) {
- thisSchemaDate = Date.UTC(2015, 10, 6);
- if (schemaDate < thisSchemaDate) {
- updatesMade = true;
- winston.info('[2015/11/06] Removing gravatar');
-
- db.getSortedSetRange('users:joindate', 0, -1, function(err, uids) {
- if (err) {
- return next(err);
- }
-
- async.eachLimit(uids, 500, function(uid, next) {
- db.getObjectFields('user:' + uid, ['picture', 'gravatarpicture'], function(err, userData) {
- if (err) {
- return next(err);
- }
-
- if (!userData.picture || !userData.gravatarpicture) {
- return next();
- }
-
- if (userData.gravatarpicture === userData.picture) {
- async.series([
- function (next) {
- db.setObjectField('user:' + uid, 'picture', '', next);
- },
- function (next) {
- db.deleteObjectField('user:' + uid, 'gravatarpicture', next);
- }
- ], next);
- } else {
- db.deleteObjectField('user:' + uid, 'gravatarpicture', next);
- }
- });
- }, function(err) {
- if (err) {
- return next(err);
- }
-
- winston.info('[2015/11/06] Gravatar pictures removed!');
- Upgrade.update(thisSchemaDate, next);
- });
- });
-
- } else {
- winston.info('[2015/11/06] Gravatar removal skipped');
- next();
- }
- },
function(next) {
thisSchemaDate = Date.UTC(2015, 11, 15);
diff --git a/src/user/delete.js b/src/user/delete.js
index 3ab5176725..011ed0aa12 100644
--- a/src/user/delete.js
+++ b/src/user/delete.js
@@ -121,10 +121,6 @@ module.exports = function(User) {
},
function(next) {
groups.leaveAllGroups(uid, next);
- },
- function(next) {
- // Deprecated as of v0.7.4, remove in v1.0.0
- plugins.fireHook('filter:user.delete', uid, next);
}
], next);
},
diff --git a/src/user/notifications.js b/src/user/notifications.js
index 2f766a72de..b08cb6aaff 100644
--- a/src/user/notifications.js
+++ b/src/user/notifications.js
@@ -1,19 +1,14 @@
'use strict';
-var async = require('async'),
- nconf = require('nconf'),
- winston = require('winston'),
- S = require('string'),
+var async = require('async');
+var winston = require('winston');
+var S = require('string');
- user = require('../user'),
- db = require('../database'),
- meta = require('../meta'),
- notifications = require('../notifications'),
- posts = require('../posts'),
- topics = require('../topics'),
- privileges = require('../privileges'),
- utils = require('../../public/src/utils');
+var db = require('../database');
+var meta = require('../meta');
+var notifications = require('../notifications');
+var privileges = require('../privileges');
(function(UserNotifications) {
@@ -103,89 +98,13 @@ var async = require('async'),
if (err) {
return callback(err);
}
-
- UserNotifications.generateNotificationPaths(notifications, uid, callback);
- });
- };
-
- UserNotifications.generateNotificationPaths = function (notifications, uid, callback) {
- var pids = notifications.map(function(notification) {
- return notification ? notification.pid : null;
- });
-
- generatePostPaths(pids, uid, function(err, pidToPaths) {
- if (err) {
- return callback(err);
- }
-
- notifications = notifications.map(function(notification, index) {
- if (!notification) {
- return null;
- }
-
- notification.path = pidToPaths[notification.pid] || notification.path || null;
-
- if (notification.nid.startsWith('follow')) {
- notification.path = '/user/' + notification.user.userslug;
- }
-
- notification.datetimeISO = utils.toISOString(notification.datetime);
- return notification;
- }).filter(function(notification) {
- // Remove notifications that do not resolve to a path
- return notification && notification.path !== null;
+ notifications = notifications.filter(function(notification) {
+ return notification && notification.path;
});
-
callback(null, notifications);
});
};
- function generatePostPaths(pids, uid, callback) {
- pids = pids.filter(Boolean);
- var postKeys = pids.map(function(pid) {
- return 'post:' + pid;
- });
-
- db.getObjectsFields(postKeys, ['pid', 'tid'], function(err, postData) {
- if (err) {
- return callback(err);
- }
-
- var topicKeys = postData.map(function(post) {
- return post ? 'topic:' + post.tid : null;
- });
-
- async.parallel({
- indices: function(next) {
- posts.getPostIndices(postData, uid, next);
- },
- topics: function(next) {
- db.getObjectsFields(topicKeys, ['slug', 'deleted'], next);
- }
- }, function(err, results) {
- if (err) {
- return callback(err);
- }
-
- var pidToPaths = {};
- pids.forEach(function(pid, index) {
- if (parseInt(results.topics[index].deleted, 10) === 1) {
- pidToPaths[pid] = null;
- return;
- }
-
- var slug = results.topics[index] ? results.topics[index].slug : null;
- var postIndex = utils.isNumber(results.indices[index]) ? parseInt(results.indices[index], 10) + 1 : null;
-
- if (slug && postIndex) {
- pidToPaths[pid] = '/topic/' + slug + '/' + postIndex;
- }
- });
-
- callback(null, pidToPaths);
- });
- });
- }
UserNotifications.getDailyUnread = function(uid, callback) {
var yesterday = Date.now() - (1000 * 60 * 60 * 24); // Approximate, can be more or less depending on time changes, makes no difference really.
@@ -281,13 +200,20 @@ var async = require('async'),
};
UserNotifications.sendTopicNotificationToFollowers = function(uid, topicData, postData) {
- db.getSortedSetRange('followers:' + uid, 0, -1, function(err, followers) {
- if (err || !Array.isArray(followers) || !followers.length) {
- return;
- }
-
- privileges.categories.filterUids('read', topicData.cid, followers, function(err, followers) {
- if (err || !followers.length) {
+ var followers;
+ async.waterfall([
+ function (next) {
+ db.getSortedSetRange('followers:' + uid, 0, -1, next);
+ },
+ function (followers, next) {
+ if (!Array.isArray(followers) || !followers.length) {
+ return;
+ }
+ privileges.categories.filterUids('read', topicData.cid, followers, next);
+ },
+ function (_followers, next) {
+ followers = _followers;
+ if (!followers.length) {
return;
}
@@ -300,15 +226,20 @@ var async = require('async'),
bodyShort: '[[notifications:user_posted_topic, ' + postData.user.username + ', ' + title + ']]',
bodyLong: postData.content,
pid: postData.pid,
+ path: '/post/' + postData.pid,
nid: 'tid:' + postData.tid + ':uid:' + uid,
tid: postData.tid,
from: uid
- }, function(err, notification) {
- if (!err && notification) {
- notifications.push(notification, followers);
- }
- });
- });
+ }, next);
+ }
+ ], function(err, notification) {
+ if (err) {
+ return winston.error(err);
+ }
+
+ if (notification) {
+ notifications.push(notification, followers);
+ }
});
};
diff --git a/src/user/picture.js b/src/user/picture.js
index a55eee2975..385050fcf2 100644
--- a/src/user/picture.js
+++ b/src/user/picture.js
@@ -63,7 +63,9 @@ module.exports = function(User) {
async.series([
async.apply(image.normalise, picture.path, extension),
async.apply(fs.rename, picture.path + '.png', picture.path)
- ], next);
+ ], function(err) {
+ next(err);
+ });
},
function(next) {
User.getUserField(updateUid, 'uploadedpicture', next);
diff --git a/src/views/admin/general/sounds.tpl b/src/views/admin/general/sounds.tpl
index dfdbae512d..d227ab6292 100644
--- a/src/views/admin/general/sounds.tpl
+++ b/src/views/admin/general/sounds.tpl
@@ -1,5 +1,5 @@
-
+
@@ -50,25 +50,20 @@
-
+
+
+
+
+
+
+
-