mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-04-14 00:18:03 +02:00
merge
This commit is contained in:
130
src/analytics.js
130
src/analytics.js
@@ -1,23 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var cronJob = require('cron').CronJob;
|
||||
var async = require('async');
|
||||
|
||||
var db = require('./database');
|
||||
|
||||
(function(Analytics) {
|
||||
var counters = {};
|
||||
|
||||
var pageViews = 0;
|
||||
var uniqueIPCount = 0;
|
||||
var uniquevisitors = 0;
|
||||
|
||||
var isCategory = /^(?:\/api)?\/category\/(\d+)/;
|
||||
|
||||
new cronJob('*/10 * * * *', function() {
|
||||
Analytics.writeData();
|
||||
}, null, true);
|
||||
|
||||
Analytics.pageView = function(ip) {
|
||||
Analytics.increment = function(keys) {
|
||||
keys = Array.isArray(keys) ? keys : [keys];
|
||||
|
||||
keys.forEach(function(key) {
|
||||
counters[key] = counters[key] || 0;
|
||||
++counters[key];
|
||||
});
|
||||
};
|
||||
|
||||
Analytics.pageView = function(payload) {
|
||||
++pageViews;
|
||||
|
||||
if (ip) {
|
||||
db.sortedSetScore('ip:recent', ip, function(err, score) {
|
||||
if (payload.ip) {
|
||||
db.sortedSetScore('ip:recent', payload.ip, function(err, score) {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
@@ -28,40 +42,115 @@ var db = require('./database');
|
||||
today.setHours(today.getHours(), 0, 0, 0);
|
||||
if (!score || score < today.getTime()) {
|
||||
++uniquevisitors;
|
||||
db.sortedSetAdd('ip:recent', Date.now(), ip);
|
||||
db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.path) {
|
||||
var categoryMatch = payload.path.match(isCategory),
|
||||
cid = categoryMatch ? parseInt(categoryMatch[1], 10) : null;
|
||||
|
||||
if (cid) {
|
||||
Analytics.increment(['pageviews:byCid:' + cid]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Analytics.writeData = function() {
|
||||
var today = new Date();
|
||||
var month = new Date();
|
||||
var dbQueue = [];
|
||||
|
||||
var today;
|
||||
if (pageViews > 0 || uniquevisitors > 0) {
|
||||
today = new Date();
|
||||
today.setHours(today.getHours(), 0, 0, 0);
|
||||
}
|
||||
today.setHours(today.getHours(), 0, 0, 0);
|
||||
month.setMonth(month.getMonth(), 1);
|
||||
month.setHours(0, 0, 0, 0);
|
||||
|
||||
if (pageViews > 0) {
|
||||
db.sortedSetIncrBy('analytics:pageviews', pageViews, today.getTime());
|
||||
var month = new Date();
|
||||
month.setMonth(month.getMonth(), 1);
|
||||
month.setHours(0, 0, 0, 0);
|
||||
db.sortedSetIncrBy('analytics:pageviews:month', pageViews, month.getTime());
|
||||
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews', pageViews, today.getTime()));
|
||||
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:pageviews:month', pageViews, month.getTime()));
|
||||
pageViews = 0;
|
||||
}
|
||||
|
||||
if (uniquevisitors > 0) {
|
||||
db.sortedSetIncrBy('analytics:uniquevisitors', uniquevisitors, today.getTime());
|
||||
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:uniquevisitors', uniquevisitors, today.getTime()));
|
||||
uniquevisitors = 0;
|
||||
}
|
||||
|
||||
if (uniqueIPCount > 0) {
|
||||
db.incrObjectFieldBy('global', 'uniqueIPCount', uniqueIPCount);
|
||||
dbQueue.push(async.apply(db.incrObjectFieldBy, 'global', 'uniqueIPCount', uniqueIPCount));
|
||||
uniqueIPCount = 0;
|
||||
}
|
||||
|
||||
if (Object.keys(counters).length > 0) {
|
||||
for(var key in counters) {
|
||||
dbQueue.push(async.apply(db.sortedSetIncrBy, 'analytics:' + key, counters[key], today.getTime()));
|
||||
delete counters[key];
|
||||
}
|
||||
}
|
||||
|
||||
async.parallel(dbQueue, function(err) {
|
||||
if (err) {
|
||||
winston.error('[analytics] Encountered error while writing analytics to data store: ' + err.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Analytics.getHourlyStatsForSet = function(set, hour, numHours, callback) {
|
||||
var terms = {},
|
||||
hoursArr = [];
|
||||
|
||||
hour = new Date(hour);
|
||||
hour.setHours(hour.getHours(), 0, 0, 0);
|
||||
|
||||
for (var i = 0, ii = numHours; i < ii; i++) {
|
||||
hoursArr.push(hour.getTime());
|
||||
hour.setHours(hour.getHours() - 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
db.sortedSetScores(set, hoursArr, function(err, counts) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
hoursArr.forEach(function(term, index) {
|
||||
terms[term] = parseInt(counts[index], 10) || 0;
|
||||
});
|
||||
|
||||
var termsArr = [];
|
||||
|
||||
hoursArr.reverse();
|
||||
hoursArr.forEach(function(hour) {
|
||||
termsArr.push(terms[hour]);
|
||||
});
|
||||
|
||||
callback(null, termsArr);
|
||||
});
|
||||
};
|
||||
|
||||
Analytics.getDailyStatsForSet = function(set, day, numDays, callback) {
|
||||
var daysArr = [];
|
||||
|
||||
day = new Date(day);
|
||||
day.setDate(day.getDate()+1); // set the date to tomorrow, because getHourlyStatsForSet steps *backwards* 24 hours to sum up the values
|
||||
day.setHours(0, 0, 0, 0);
|
||||
|
||||
async.whilst(function() {
|
||||
return numDays--;
|
||||
}, function(next) {
|
||||
Analytics.getHourlyStatsForSet(set, day.getTime()-(1000*60*60*24*numDays), 24, function(err, day) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
daysArr.push(day.reduce(function(cur, next) {
|
||||
return cur+next;
|
||||
}));
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
callback(err, daysArr);
|
||||
});
|
||||
};
|
||||
|
||||
Analytics.getUnwrittenPageviews = function() {
|
||||
@@ -86,4 +175,13 @@ var db = require('./database');
|
||||
});
|
||||
};
|
||||
|
||||
Analytics.getCategoryAnalytics = function(cid, callback) {
|
||||
async.parallel({
|
||||
'pageviews:hourly': async.apply(Analytics.getHourlyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 24),
|
||||
'pageviews:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:pageviews:byCid:' + cid, Date.now(), 30),
|
||||
'topics:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:topics:byCid:' + cid, Date.now(), 7),
|
||||
'posts:daily': async.apply(Analytics.getDailyStatsForSet, 'analytics:posts:byCid:' + cid, Date.now(), 7),
|
||||
}, callback);
|
||||
};
|
||||
|
||||
}(exports));
|
||||
@@ -1,15 +1,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
nconf = require('nconf'),
|
||||
var async = require('async');
|
||||
|
||||
db = require('./database'),
|
||||
user = require('./user'),
|
||||
Groups = require('./groups'),
|
||||
plugins = require('./plugins'),
|
||||
validator = require('validator'),
|
||||
privileges = require('./privileges');
|
||||
var db = require('./database');
|
||||
var user = require('./user');
|
||||
var Groups = require('./groups');
|
||||
var plugins = require('./plugins');
|
||||
var privileges = require('./privileges');
|
||||
|
||||
(function(Categories) {
|
||||
|
||||
@@ -27,37 +25,40 @@ var async = require('async'),
|
||||
};
|
||||
|
||||
Categories.getCategoryById = function(data, callback) {
|
||||
Categories.getCategories([data.cid], data.uid, function(err, categories) {
|
||||
if (err || !Array.isArray(categories) || !categories[0]) {
|
||||
return callback(err || new Error('[[error:invalid-cid]]'));
|
||||
}
|
||||
var category = categories[0];
|
||||
|
||||
if (parseInt(data.uid, 10)) {
|
||||
Categories.markAsRead([data.cid], data.uid);
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
topics: function(next) {
|
||||
Categories.getCategoryTopics(data, next);
|
||||
},
|
||||
isIgnored: function(next) {
|
||||
Categories.isIgnored([data.cid], data.uid, next);
|
||||
var category;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCategories([data.cid], data.uid, next);
|
||||
},
|
||||
function (categories, next) {
|
||||
if (!Array.isArray(categories) || !categories[0]) {
|
||||
return next(new Error('[[error:invalid-cid]]'));
|
||||
}
|
||||
}, function(err, results) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
category = categories[0];
|
||||
if (parseInt(data.uid, 10)) {
|
||||
Categories.markAsRead([data.cid], data.uid);
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
topics: function(next) {
|
||||
Categories.getCategoryTopics(data, next);
|
||||
},
|
||||
isIgnored: function(next) {
|
||||
Categories.isIgnored([data.cid], data.uid, next);
|
||||
}
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
category.topics = results.topics.topics;
|
||||
category.nextStart = results.topics.nextStart;
|
||||
category.isIgnored = results.isIgnored[0];
|
||||
|
||||
plugins.fireHook('filter:category.get', {category: category, uid: data.uid}, function(err, data) {
|
||||
callback(err, data ? data.category : null);
|
||||
});
|
||||
});
|
||||
});
|
||||
plugins.fireHook('filter:category.get', {category: category, uid: data.uid}, next);
|
||||
},
|
||||
function (data, next) {
|
||||
next(null, data.category);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.isIgnored = function(cids, uid, callback) {
|
||||
|
||||
@@ -8,10 +8,10 @@ var async = require('async'),
|
||||
|
||||
module.exports = function(Categories) {
|
||||
|
||||
Categories.purge = function(cid, callback) {
|
||||
Categories.purge = function(cid, uid, callback) {
|
||||
batch.processSortedSet('cid:' + cid + ':tids', function(tids, next) {
|
||||
async.eachLimit(tids, 10, function(tid, next) {
|
||||
topics.purgePostsAndTopic(tid, next);
|
||||
topics.purgePostsAndTopic(tid, uid, next);
|
||||
}, next);
|
||||
}, {alwaysStartAt: 0}, function(err) {
|
||||
if (err) {
|
||||
|
||||
@@ -47,6 +47,7 @@ module.exports = function(Categories) {
|
||||
topic.slug = topic.tid;
|
||||
topic.teaser = null;
|
||||
topic.noAnchor = true;
|
||||
topic.tags = [];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ var winston = require('winston');
|
||||
var db = require('../../database');
|
||||
var user = require('../../user');
|
||||
var meta = require('../../meta');
|
||||
var plugins = require('../../plugins');
|
||||
var helpers = require('../helpers');
|
||||
var accountHelpers = require('./helpers');
|
||||
|
||||
@@ -28,8 +29,15 @@ editController.get = function(req, res, callback) {
|
||||
|
||||
userData.title = '[[pages:account/edit, ' + userData.username + ']]';
|
||||
userData.breadcrumbs = helpers.buildBreadcrumbs([{text: userData.username, url: '/user/' + userData.userslug}, {text: '[[user:edit]]'}]);
|
||||
userData.editButtons = [];
|
||||
|
||||
res.render('account/edit', userData);
|
||||
plugins.fireHook('filter:user.account.edit', userData, function(err, userData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('account/edit', userData);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -36,8 +36,10 @@ profileController.get = function(req, res, callback) {
|
||||
}
|
||||
userData = _userData;
|
||||
|
||||
if (req.uid !== parseInt(userData.uid, 10)) {
|
||||
req.session.uids_viewed = req.session.uids_viewed || {};
|
||||
if (req.uid !== parseInt(userData.uid, 10) && (!req.session.uids_viewed[userData.uid] || req.session.uids_viewed[userData.uid] < Date.now() - 3600000)) {
|
||||
user.incrementUserFieldBy(userData.uid, 'profileviews', 1);
|
||||
req.session.uids_viewed[userData.uid] = Date.now();
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
var async = require('async');
|
||||
|
||||
user = require('../../user'),
|
||||
groups = require('../../groups'),
|
||||
languages = require('../../languages'),
|
||||
meta = require('../../meta'),
|
||||
plugins = require('../../plugins'),
|
||||
privileges = require('../../privileges'),
|
||||
categories = require('../../categories'),
|
||||
db = require('../../database'),
|
||||
helpers = require('../helpers'),
|
||||
accountHelpers = require('./helpers');
|
||||
var user = require('../../user');
|
||||
var groups = require('../../groups');
|
||||
var languages = require('../../languages');
|
||||
var meta = require('../../meta');
|
||||
var plugins = require('../../plugins');
|
||||
var privileges = require('../../privileges');
|
||||
var categories = require('../../categories');
|
||||
var db = require('../../database');
|
||||
var helpers = require('../helpers');
|
||||
var accountHelpers = require('./helpers');
|
||||
|
||||
|
||||
var settingsController = {};
|
||||
@@ -41,14 +41,18 @@ settingsController.get = function(req, res, callback) {
|
||||
homePageRoutes: function(next) {
|
||||
getHomePageRoutes(next);
|
||||
},
|
||||
ips: function (next) {
|
||||
user.getIPs(req.uid, 4, next);
|
||||
},
|
||||
sessions: async.apply(user.auth.getSessions, userData.uid, req.sessionID)
|
||||
}, next);
|
||||
},
|
||||
function(results, next) {
|
||||
userData.settings = results.settings;
|
||||
userData.languages = results.languages;
|
||||
userData.userGroups = results.userGroups[0];
|
||||
userData.languages = results.languages;
|
||||
userData.homePageRoutes = results.homePageRoutes;
|
||||
userData.ips = results.ips;
|
||||
userData.sessions = results.sessions;
|
||||
plugins.fireHook('filter:user.customSettings', {settings: results.settings, customSettings: [], uid: req.uid}, next);
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ var adminController = {
|
||||
sounds: require('./admin/sounds'),
|
||||
homepage: require('./admin/homepage'),
|
||||
navigation: require('./admin/navigation'),
|
||||
social: require('./admin/social'),
|
||||
themes: require('./admin/themes'),
|
||||
users: require('./admin/users'),
|
||||
uploads: require('./admin/uploads'),
|
||||
|
||||
@@ -4,6 +4,7 @@ var async = require('async'),
|
||||
|
||||
categories = require('../../categories'),
|
||||
privileges = require('../../privileges'),
|
||||
analytics = require('../../analytics'),
|
||||
plugins = require('../../plugins');
|
||||
|
||||
|
||||
@@ -12,20 +13,22 @@ var categoriesController = {};
|
||||
categoriesController.get = function(req, res, next) {
|
||||
async.parallel({
|
||||
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
|
||||
privileges: async.apply(privileges.categories.list, req.params.category_id)
|
||||
privileges: async.apply(privileges.categories.list, req.params.category_id),
|
||||
analytics: async.apply(analytics.getCategoryAnalytics, req.params.category_id)
|
||||
}, function(err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:admin.category.get', {req: req, res: res, category: data.category[0], privileges: data.privileges}, function(err, data) {
|
||||
plugins.fireHook('filter:admin.category.get', { req: req, res: res, category: data.category[0], privileges: data.privileges, analytics: data.analytics }, function(err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('admin/manage/category', {
|
||||
category: data.category,
|
||||
privileges: data.privileges
|
||||
privileges: data.privileges,
|
||||
analytics: data.analytics
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
20
src/controllers/admin/social.js
Normal file
20
src/controllers/admin/social.js
Normal file
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
var social = require('../../social');
|
||||
|
||||
var socialController = {};
|
||||
|
||||
|
||||
socialController.get = function(req, res, next) {
|
||||
social.getPostSharing(function(err, posts) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('admin/general/social', {
|
||||
posts: posts
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = socialController;
|
||||
@@ -93,6 +93,33 @@ uploadsController.uploadLogo = function(req, res, next) {
|
||||
upload('site-logo', req, res, next);
|
||||
};
|
||||
|
||||
uploadsController.uploadSound = function(req, res, next) {
|
||||
var uploadedFile = req.files.files[0];
|
||||
|
||||
file.saveFileToLocal(uploadedFile.name, 'sounds', uploadedFile.path, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var soundsPath = path.join(__dirname, '../../../public/sounds'),
|
||||
filePath = path.join(__dirname, '../../../public/uploads/sounds', uploadedFile.name);
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
fs.link(filePath, path.join(soundsPath, path.basename(filePath)));
|
||||
} else {
|
||||
fs.symlink(filePath, path.join(soundsPath, path.basename(filePath)), 'file');
|
||||
}
|
||||
|
||||
fs.unlink(uploadedFile.path, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json([{}]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
uploadsController.uploadDefaultAvatar = function(req, res, next) {
|
||||
upload('avatar-default', req, res, next);
|
||||
};
|
||||
@@ -131,6 +158,7 @@ function uploadImage(filename, folder, uploadedFile, req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.json([{name: uploadedFile.name, url: image.url.startsWith('http') ? image.url : nconf.get('relative_path') + image.url}]);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,7 @@ function registerAndLoginUser(req, res, userData, callback) {
|
||||
function(_uid, next) {
|
||||
uid = _uid;
|
||||
if (res.locals.processLogin === true) {
|
||||
user.logIP(uid, req.ip);
|
||||
req.login({uid: uid}, next);
|
||||
doLogin(req, uid, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -172,37 +171,11 @@ function continueLogin(req, res, next) {
|
||||
res.status(200).send(nconf.get('relative_path') + '/reset/' + code);
|
||||
});
|
||||
} else {
|
||||
req.login({
|
||||
uid: userData.uid
|
||||
}, function(err) {
|
||||
doLogin(req, userData.uid, function(err) {
|
||||
if (err) {
|
||||
return res.status(403).send(err.message);
|
||||
}
|
||||
|
||||
if (userData.uid) {
|
||||
var uuid = utils.generateUUID();
|
||||
req.session.meta = {};
|
||||
|
||||
// Associate IP used during login with user account
|
||||
user.logIP(userData.uid, req.ip);
|
||||
req.session.meta.ip = req.ip;
|
||||
|
||||
// Associate metadata retrieved via user-agent
|
||||
req.session.meta = _.extend(req.session.meta, {
|
||||
uuid: uuid,
|
||||
datetime: Date.now(),
|
||||
platform: req.useragent.platform,
|
||||
browser: req.useragent.browser,
|
||||
version: req.useragent.version
|
||||
});
|
||||
|
||||
// Associate login session with user
|
||||
user.auth.addSession(userData.uid, req.sessionID);
|
||||
db.setObjectField('uid:' + userData.uid + 'sessionUUID:sessionId', uuid, req.sessionID);
|
||||
|
||||
plugins.fireHook('action:user.loggedIn', userData.uid);
|
||||
}
|
||||
|
||||
if (!req.session.returnTo) {
|
||||
res.status(200).send(nconf.get('relative_path') + '/');
|
||||
} else {
|
||||
@@ -216,6 +189,40 @@ function continueLogin(req, res, next) {
|
||||
})(req, res, next);
|
||||
}
|
||||
|
||||
function doLogin(req, uid, callback) {
|
||||
req.login({uid: uid}, function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (uid) {
|
||||
var uuid = utils.generateUUID();
|
||||
req.session.meta = {};
|
||||
|
||||
// Associate IP used during login with user account
|
||||
user.logIP(uid, req.ip);
|
||||
req.session.meta.ip = req.ip;
|
||||
|
||||
// Associate metadata retrieved via user-agent
|
||||
req.session.meta = _.extend(req.session.meta, {
|
||||
uuid: uuid,
|
||||
datetime: Date.now(),
|
||||
platform: req.useragent.platform,
|
||||
browser: req.useragent.browser,
|
||||
version: req.useragent.version
|
||||
});
|
||||
|
||||
// Associate login session with user
|
||||
user.auth.addSession(uid, req.sessionID);
|
||||
db.setObjectField('uid:' + uid + 'sessionUUID:sessionId', uuid, req.sessionID);
|
||||
|
||||
plugins.fireHook('action:user.loggedIn', uid);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
authenticationController.localLogin = function(req, username, password, next) {
|
||||
if (!username) {
|
||||
return next(new Error('[[error:invalid-username]]'));
|
||||
|
||||
@@ -182,7 +182,8 @@ Controllers.compose = function(req, res, next) {
|
||||
Controllers.confirmEmail = function(req, res, next) {
|
||||
user.email.confirm(req.params.code, function (err) {
|
||||
res.render('confirm', {
|
||||
error: err ? err.message : ''
|
||||
error: err ? err.message : '',
|
||||
title: '[[pages:confirm]]',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,48 +1,44 @@
|
||||
"use strict";
|
||||
|
||||
var tagsController = {},
|
||||
async = require('async'),
|
||||
nconf = require('nconf'),
|
||||
validator = require('validator'),
|
||||
meta = require('../meta'),
|
||||
user = require('../user'),
|
||||
topics = require('../topics'),
|
||||
helpers = require('./helpers');
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var validator = require('validator');
|
||||
|
||||
var meta = require('../meta');
|
||||
var topics = require('../topics');
|
||||
var helpers = require('./helpers');
|
||||
|
||||
var tagsController = {};
|
||||
|
||||
tagsController.getTag = function(req, res, next) {
|
||||
var tag = validator.escape(req.params.tag);
|
||||
var stop = (parseInt(meta.config.topicsPerList, 10) || 20) - 1;
|
||||
|
||||
var templateData = {
|
||||
topics: [],
|
||||
tag: tag,
|
||||
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[tags:tags]]', url: '/tags'}, {text: tag}]),
|
||||
title: '[[pages:tag, ' + tag + ']]'
|
||||
};
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
function (next) {
|
||||
topics.getTagTids(req.params.tag, 0, stop, next);
|
||||
},
|
||||
function(tids, next) {
|
||||
function (tids, next) {
|
||||
if (Array.isArray(tids) && !tids.length) {
|
||||
topics.deleteTag(req.params.tag);
|
||||
return res.render('tag', {
|
||||
topics: [],
|
||||
tag: tag,
|
||||
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[tags:tags]]', url: '/tags'}, {text: tag}])
|
||||
});
|
||||
return res.render('tag', templateData);
|
||||
}
|
||||
|
||||
async.parallel({
|
||||
isAdmin: async.apply(user.isAdministrator, req.uid),
|
||||
topics: async.apply(topics.getTopics, tids, req.uid)
|
||||
}, next);
|
||||
topics.getTopics(tids, req.uid, next);
|
||||
}
|
||||
], function(err, results) {
|
||||
], function(err, topics) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!results.isAdmin) {
|
||||
results.topics = results.topics.filter(function(topic) {
|
||||
return topic && !topic.deleted;
|
||||
});
|
||||
}
|
||||
|
||||
res.locals.metaTags = [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -57,15 +53,10 @@ tagsController.getTag = function(req, res, next) {
|
||||
content: nconf.get('url') + '/tags/' + tag
|
||||
}
|
||||
];
|
||||
templateData.topics = topics;
|
||||
templateData.nextStart = stop + 1;
|
||||
|
||||
var data = {
|
||||
topics: results.topics,
|
||||
tag: tag,
|
||||
nextStart: stop + 1,
|
||||
breadcrumbs: helpers.buildBreadcrumbs([{text: '[[tags:tags]]', url: '/tags'}, {text: tag}]),
|
||||
title: '[[pages:tag, ' + tag + ']]'
|
||||
};
|
||||
res.render('tag', data);
|
||||
res.render('tag', templateData);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
var topicsController = {},
|
||||
async = require('async'),
|
||||
S = require('string'),
|
||||
nconf = require('nconf'),
|
||||
|
||||
user = require('../user'),
|
||||
meta = require('../meta'),
|
||||
topics = require('../topics'),
|
||||
posts = require('../posts'),
|
||||
privileges = require('../privileges'),
|
||||
plugins = require('../plugins'),
|
||||
helpers = require('./helpers'),
|
||||
pagination = require('../pagination'),
|
||||
utils = require('../../public/src/utils');
|
||||
var async = require('async');
|
||||
var S = require('string');
|
||||
var nconf = require('nconf');
|
||||
var validator = require('validator');
|
||||
|
||||
var user = require('../user');
|
||||
var meta = require('../meta');
|
||||
var topics = require('../topics');
|
||||
var posts = require('../posts');
|
||||
var privileges = require('../privileges');
|
||||
var plugins = require('../plugins');
|
||||
var helpers = require('./helpers');
|
||||
var pagination = require('../pagination');
|
||||
var utils = require('../../public/src/utils');
|
||||
|
||||
var topicsController = {};
|
||||
|
||||
topicsController.get = function(req, res, callback) {
|
||||
var tid = req.params.topic_id,
|
||||
sort = req.query.sort,
|
||||
currentPage = parseInt(req.query.page, 10) || 1,
|
||||
pageCount = 1,
|
||||
userPrivileges;
|
||||
var tid = req.params.topic_id;
|
||||
var sort = req.query.sort;
|
||||
var currentPage = parseInt(req.query.page, 10) || 1;
|
||||
var pageCount = 1;
|
||||
var userPrivileges;
|
||||
|
||||
if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) {
|
||||
return callback();
|
||||
@@ -67,8 +70,8 @@ topicsController.get = function(req, res, callback) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var set = 'tid:' + tid + ':posts',
|
||||
reverse = false;
|
||||
var set = 'tid:' + tid + ':posts';
|
||||
var reverse = false;
|
||||
|
||||
// `sort` qs has priority over user setting
|
||||
if (sort === 'newest_to_oldest') {
|
||||
@@ -90,6 +93,7 @@ topicsController.get = function(req, res, callback) {
|
||||
req.params.post_index = 0;
|
||||
}
|
||||
if (!settings.usePagination) {
|
||||
currentPage = 1;
|
||||
if (reverse) {
|
||||
postIndex = Math.max(0, postCount - (req.params.post_index || postCount) - Math.ceil(settings.postsPerPage / 2));
|
||||
} else {
|
||||
@@ -187,7 +191,7 @@ topicsController.get = function(req, res, callback) {
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: topicData.title.replace(/&/g, '&')
|
||||
content: topicData.title
|
||||
},
|
||||
{
|
||||
property: 'og:description',
|
||||
|
||||
@@ -17,14 +17,17 @@ usersController.getOnlineUsers = function(req, res, next) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
var hiddenCount = 0;
|
||||
if (!userData.isAdminOrGlobalMod) {
|
||||
userData.users = userData.users.filter(function(user) {
|
||||
if (user && user.status === 'offline') {
|
||||
hiddenCount ++;
|
||||
}
|
||||
return user && user.status !== 'offline';
|
||||
});
|
||||
}
|
||||
|
||||
userData.anonymousUserCount = require('../socket.io').getOnlineAnonCount();
|
||||
userData.anonymousUserCount = require('../socket.io').getOnlineAnonCount() + hiddenCount;
|
||||
|
||||
render(req, res, userData, next);
|
||||
});
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
"use strict";
|
||||
|
||||
var async = require('async'),
|
||||
winston = require('winston'),
|
||||
nconf = require('nconf'),
|
||||
templates = require('templates.js'),
|
||||
nodemailer = require('nodemailer'),
|
||||
sendmailTransport = require('nodemailer-sendmail-transport'),
|
||||
htmlToText = require('html-to-text'),
|
||||
url = require('url'),
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var nconf = require('nconf');
|
||||
var templates = require('templates.js');
|
||||
var nodemailer = require('nodemailer');
|
||||
var sendmailTransport = require('nodemailer-sendmail-transport');
|
||||
var smtpTransport = require('nodemailer-smtp-transport');
|
||||
var htmlToText = require('html-to-text');
|
||||
var url = require('url');
|
||||
|
||||
User = require('./user'),
|
||||
Plugins = require('./plugins'),
|
||||
meta = require('./meta'),
|
||||
translator = require('../public/src/modules/translator'),
|
||||
var User = require('./user');
|
||||
var Plugins = require('./plugins');
|
||||
var meta = require('./meta');
|
||||
var translator = require('../public/src/modules/translator');
|
||||
|
||||
transports = {
|
||||
sendmail: nodemailer.createTransport(sendmailTransport()),
|
||||
gmail: undefined
|
||||
},
|
||||
app, fallbackTransport;
|
||||
var transports = {
|
||||
sendmail: nodemailer.createTransport(sendmailTransport()),
|
||||
gmail: undefined
|
||||
};
|
||||
|
||||
var app;
|
||||
var fallbackTransport;
|
||||
|
||||
(function(Emailer) {
|
||||
Emailer.registerApp = function(expressApp) {
|
||||
@@ -26,13 +29,15 @@ var async = require('async'),
|
||||
|
||||
// Enable Gmail transport if enabled in ACP
|
||||
if (parseInt(meta.config['email:GmailTransport:enabled'], 10) === 1) {
|
||||
fallbackTransport = transports.gmail = nodemailer.createTransport('SMTP', {
|
||||
service: 'Gmail',
|
||||
fallbackTransport = transports.gmail = nodemailer.createTransport(smtpTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 465,
|
||||
secure: true,
|
||||
auth: {
|
||||
user: meta.config['email:GmailTransport:user'],
|
||||
pass: meta.config['email:GmailTransport:pass']
|
||||
}
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
fallbackTransport = transports.sendmail;
|
||||
}
|
||||
@@ -135,28 +140,19 @@ var async = require('async'),
|
||||
}
|
||||
|
||||
function renderAndTranslate(tpl, params, lang, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
render('emails/partials/footer' + (tpl.indexOf('_plaintext') !== -1 ? '_plaintext' : ''), params, next);
|
||||
},
|
||||
function(footer, next) {
|
||||
params.footer = footer;
|
||||
render(tpl, params, next);
|
||||
},
|
||||
function(html, next) {
|
||||
translator.translate(html, lang, function(translated) {
|
||||
next(null, translated);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
render(tpl, params, function(err, html) {
|
||||
translator.translate(html, lang, function(translated) {
|
||||
callback(err, translated);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getHostname() {
|
||||
var configUrl = nconf.get('url'),
|
||||
parsed = url.parse(configUrl);
|
||||
var configUrl = nconf.get('url');
|
||||
var parsed = url.parse(configUrl);
|
||||
|
||||
return parsed.hostname;
|
||||
};
|
||||
}
|
||||
|
||||
}(module.exports));
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ var utils = require('../public/src/utils');
|
||||
results.base.deleted = !!parseInt(results.base.deleted, 10);
|
||||
results.base.hidden = !!parseInt(results.base.hidden, 10);
|
||||
results.base.system = !!parseInt(results.base.system, 10);
|
||||
results.base.private = results.base.private ? !!parseInt(results.base.private, 10) : true;
|
||||
results.base.private = (results.base.private === null || results.base.private === undefined) ? true : !!parseInt(results.base.private, 10);
|
||||
results.base.disableJoinRequests = parseInt(results.base.disableJoinRequests, 10) === 1;
|
||||
results.base.isMember = results.isMember;
|
||||
results.base.isPending = results.isPending;
|
||||
@@ -409,7 +409,7 @@ var utils = require('../public/src/utils');
|
||||
group.createtimeISO = utils.toISOString(group.createtime);
|
||||
group.hidden = parseInt(group.hidden, 10) === 1;
|
||||
group.system = parseInt(group.system, 10) === 1;
|
||||
group.private = parseInt(group.private, 10) === 1;
|
||||
group.private = (group.private === null || group.private === undefined) ? true : !!parseInt(group.private, 10);
|
||||
group.disableJoinRequests = parseInt(group.disableJoinRequests) === 1;
|
||||
|
||||
group['cover:url'] = group['cover:url'] || require('./coverPhoto').getDefaultGroupCover(group.name);
|
||||
|
||||
@@ -15,19 +15,19 @@ module.exports = function(Groups) {
|
||||
function join() {
|
||||
var tasks = [
|
||||
async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', Date.now(), uid),
|
||||
async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount')
|
||||
async.apply(db.incrObjectField, 'group:' + groupName, 'memberCount')
|
||||
];
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
async.parallel({
|
||||
isAdmin: function(next) {
|
||||
user.isAdministrator(uid, next);
|
||||
user.isAdministrator(uid, next);
|
||||
},
|
||||
isHidden: function(next) {
|
||||
Groups.isHidden(groupName, next);
|
||||
}
|
||||
}, next);
|
||||
}, next);
|
||||
},
|
||||
function(results, next) {
|
||||
if (results.isAdmin) {
|
||||
@@ -213,10 +213,10 @@ module.exports = function(Groups) {
|
||||
Groups.destroy(groupName, callback);
|
||||
} else {
|
||||
if (parseInt(groupData.hidden, 10) !== 1) {
|
||||
db.sortedSetAdd('groups:visible:memberCount', groupData.memberCount, groupName, next);
|
||||
db.sortedSetAdd('groups:visible:memberCount', groupData.memberCount, groupName, callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -359,7 +359,7 @@ var async = require('async'),
|
||||
if (parseInt(meta.config.disableChat) === 1 || !uid) {
|
||||
return callback(new Error('[[error:chat-disabled]]'));
|
||||
}
|
||||
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Messaging.isUserInRoom(uid, roomId, next);
|
||||
@@ -368,6 +368,14 @@ var async = require('async'),
|
||||
if (!inRoom) {
|
||||
return next(new Error('[[error:not-in-room]]'));
|
||||
}
|
||||
|
||||
Messaging.getUserCountInRoom(roomId, next);
|
||||
},
|
||||
function(count, next) {
|
||||
if (count < 2) {
|
||||
return next(new Error('[[error:no-users-in-room]]'));
|
||||
}
|
||||
|
||||
user.getUserFields(uid, ['banned', 'email:confirmed'], next);
|
||||
},
|
||||
function (userData, next) {
|
||||
|
||||
@@ -17,48 +17,7 @@ module.exports = function(Meta) {
|
||||
|
||||
Meta.sounds.init = function(callback) {
|
||||
if (nconf.get('isPrimary') === 'true') {
|
||||
var soundsPath = path.join(__dirname, '../../public/sounds');
|
||||
|
||||
plugins.fireHook('filter:sounds.get', [], function(err, filePaths) {
|
||||
if (err) {
|
||||
winston.error('Could not initialise sound files:' + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the sounds directory
|
||||
async.series([
|
||||
function(next) {
|
||||
rimraf(soundsPath, next);
|
||||
},
|
||||
function(next) {
|
||||
mkdirp(soundsPath, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
winston.error('Could not initialise sound files:' + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Link paths
|
||||
async.each(filePaths, function(filePath, next) {
|
||||
if (process.platform === 'win32') {
|
||||
fs.link(filePath, path.join(soundsPath, path.basename(filePath)), next);
|
||||
} else {
|
||||
fs.symlink(filePath, path.join(soundsPath, path.basename(filePath)), 'file', next);
|
||||
}
|
||||
}, function(err) {
|
||||
if (!err) {
|
||||
winston.verbose('[sounds] Sounds OK');
|
||||
} else {
|
||||
winston.error('[sounds] Could not initialise sounds: ' + err.message);
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
setupSounds(callback);
|
||||
} else {
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
@@ -67,10 +26,23 @@ module.exports = function(Meta) {
|
||||
};
|
||||
|
||||
Meta.sounds.getFiles = function(callback) {
|
||||
// todo: Possibly move these into a bundled module?
|
||||
fs.readdir(path.join(__dirname, '../../public/sounds'), function(err, files) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
fs.readdir(path.join(__dirname, '../../public/sounds'), next);
|
||||
},
|
||||
function(sounds, next) {
|
||||
fs.readdir(path.join(__dirname, '../../public/uploads/sounds'), function(err, uploaded) {
|
||||
next(err, sounds.concat(uploaded));
|
||||
});
|
||||
}
|
||||
], function(err, files) {
|
||||
var localList = {};
|
||||
|
||||
// Filter out hidden files
|
||||
files = files.filter(function(filename) {
|
||||
return !filename.startsWith('.');
|
||||
});
|
||||
|
||||
if (err) {
|
||||
winston.error('Could not get local sound files:' + err.message);
|
||||
console.log(err.stack);
|
||||
@@ -102,4 +74,60 @@ module.exports = function(Meta) {
|
||||
callback(null, sounds);
|
||||
});
|
||||
};
|
||||
|
||||
function setupSounds(callback) {
|
||||
var soundsPath = path.join(__dirname, '../../public/sounds');
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
fs.readdir(path.join(__dirname, '../../public/uploads/sounds'), next);
|
||||
},
|
||||
function(uploaded, next) {
|
||||
uploaded = uploaded.map(function(filename) {
|
||||
return path.join(__dirname, '../../public/uploads/sounds', filename);
|
||||
});
|
||||
|
||||
plugins.fireHook('filter:sounds.get', uploaded, function(err, filePaths) {
|
||||
if (err) {
|
||||
winston.error('Could not initialise sound files:' + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the sounds directory
|
||||
async.series([
|
||||
function(next) {
|
||||
rimraf(soundsPath, next);
|
||||
},
|
||||
function(next) {
|
||||
mkdirp(soundsPath, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
winston.error('Could not initialise sound files:' + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Link paths
|
||||
async.each(filePaths, function(filePath, next) {
|
||||
if (process.platform === 'win32') {
|
||||
fs.link(filePath, path.join(soundsPath, path.basename(filePath)), next);
|
||||
} else {
|
||||
fs.symlink(filePath, path.join(soundsPath, path.basename(filePath)), 'file', next);
|
||||
}
|
||||
}, function(err) {
|
||||
if (!err) {
|
||||
winston.verbose('[sounds] Sounds OK');
|
||||
} else {
|
||||
winston.error('[sounds] Could not initialise sounds: ' + err.message);
|
||||
}
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,8 @@ var mkdirp = require('mkdirp'),
|
||||
plugins = require('../plugins'),
|
||||
utils = require('../../public/src/utils'),
|
||||
|
||||
Templates = {};
|
||||
Templates = {},
|
||||
searchIndex = {};
|
||||
|
||||
Templates.compile = function(callback) {
|
||||
callback = callback || function() {};
|
||||
@@ -27,25 +28,37 @@ Templates.compile = function(callback) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
var coreTemplatesPath = nconf.get('core_templates_path'),
|
||||
baseTemplatesPath = nconf.get('base_templates_path'),
|
||||
viewsPath = nconf.get('views_dir'),
|
||||
themeTemplatesPath = nconf.get('theme_templates_path'),
|
||||
themeConfig = require(nconf.get('theme_config'));
|
||||
compile(callback);
|
||||
};
|
||||
|
||||
if (themeConfig.baseTheme) {
|
||||
var pathToBaseTheme = path.join(nconf.get('themes_path'), themeConfig.baseTheme);
|
||||
baseTemplatesPath = require(path.join(pathToBaseTheme, 'theme.json')).templates;
|
||||
|
||||
if (!baseTemplatesPath){
|
||||
baseTemplatesPath = path.join(pathToBaseTheme, 'templates');
|
||||
}
|
||||
function getBaseTemplates(theme) {
|
||||
var baseTemplatesPaths = [],
|
||||
baseThemePath, baseThemeConfig;
|
||||
|
||||
while (theme) {
|
||||
baseThemePath = path.join(nconf.get('themes_path'), theme);
|
||||
baseThemeConfig = require(path.join(baseThemePath, 'theme.json'));
|
||||
|
||||
baseTemplatesPaths.push(path.join(baseThemePath, baseThemeConfig.templates || 'templates'));
|
||||
theme = baseThemeConfig.baseTheme;
|
||||
}
|
||||
|
||||
plugins.getTemplates(function(err, pluginTemplates) {
|
||||
return baseTemplatesPaths.reverse();
|
||||
}
|
||||
|
||||
function preparePaths(baseTemplatesPaths, callback) {
|
||||
var coreTemplatesPath = nconf.get('core_templates_path'),
|
||||
viewsPath = nconf.get('views_dir');
|
||||
|
||||
async.waterfall([
|
||||
async.apply(plugins.fireHook, 'static:templates.precompile', {}),
|
||||
async.apply(plugins.getTemplates)
|
||||
], function(err, pluginTemplates) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
winston.verbose('[meta/templates] Compiling templates');
|
||||
rimraf.sync(viewsPath);
|
||||
mkdirp.sync(viewsPath);
|
||||
@@ -54,27 +67,33 @@ Templates.compile = function(callback) {
|
||||
coreTpls: function(next) {
|
||||
utils.walk(coreTemplatesPath, next);
|
||||
},
|
||||
baseTpls: function(next) {
|
||||
utils.walk(baseTemplatesPath, next);
|
||||
baseThemes: function(next) {
|
||||
async.map(baseTemplatesPaths, function(baseTemplatePath, next) {
|
||||
utils.walk(baseTemplatePath, function(err, paths) {
|
||||
paths = paths.map(function(tpl) {
|
||||
return {
|
||||
base: baseTemplatePath,
|
||||
path: tpl.replace(baseTemplatePath, '')
|
||||
};
|
||||
});
|
||||
|
||||
next(err, paths);
|
||||
});
|
||||
}, next);
|
||||
}
|
||||
}, function(err, data) {
|
||||
var coreTpls = data.coreTpls,
|
||||
baseTpls = data.baseTpls,
|
||||
var baseThemes = data.baseThemes,
|
||||
coreTpls = data.coreTpls,
|
||||
paths = {};
|
||||
|
||||
if (!baseTpls) {
|
||||
winston.warn('[meta/templates] Could not find base template files at: ' + baseTemplatesPath);
|
||||
}
|
||||
|
||||
coreTpls = !coreTpls ? [] : coreTpls.map(function(tpl) { return tpl.replace(coreTemplatesPath, ''); });
|
||||
baseTpls = !baseTpls ? [] : baseTpls.map(function(tpl) { return tpl.replace(baseTemplatesPath, ''); });
|
||||
|
||||
coreTpls.forEach(function(el, i) {
|
||||
paths[coreTpls[i]] = path.join(coreTemplatesPath, coreTpls[i]);
|
||||
paths[coreTpls[i].replace(coreTemplatesPath, '')] = coreTpls[i];
|
||||
});
|
||||
|
||||
baseTpls.forEach(function(el, i) {
|
||||
paths[baseTpls[i]] = path.join(baseTemplatesPath, baseTpls[i]);
|
||||
baseThemes.forEach(function(baseTpls) {
|
||||
baseTpls.forEach(function(el, i) {
|
||||
paths[baseTpls[i].path] = path.join(baseTpls[i].base, baseTpls[i].path);
|
||||
});
|
||||
});
|
||||
|
||||
for (var tpl in pluginTemplates) {
|
||||
@@ -83,51 +102,65 @@ Templates.compile = function(callback) {
|
||||
}
|
||||
}
|
||||
|
||||
async.each(Object.keys(paths), function(relativePath, next) {
|
||||
var file = fs.readFileSync(paths[relativePath]).toString(),
|
||||
matches = null,
|
||||
regex = /[ \t]*<!-- IMPORT ([\s\S]*?)? -->[ \t]*/;
|
||||
callback(err, paths);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
while((matches = file.match(regex)) !== null) {
|
||||
var partial = "/" + matches[1];
|
||||
function compile(callback) {
|
||||
var themeConfig = require(nconf.get('theme_config')),
|
||||
baseTemplatesPaths = themeConfig.baseTheme ? getBaseTemplates(themeConfig.baseTheme) : [nconf.get('base_templates_path')],
|
||||
viewsPath = nconf.get('views_dir');
|
||||
|
||||
|
||||
if (paths[partial] && relativePath !== partial) {
|
||||
file = file.replace(regex, fs.readFileSync(paths[partial]).toString());
|
||||
} else {
|
||||
winston.warn('[meta/templates] Partial not loaded: ' + matches[1]);
|
||||
file = file.replace(regex, "");
|
||||
}
|
||||
preparePaths(baseTemplatesPaths, function(err, paths) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
async.each(Object.keys(paths), function(relativePath, next) {
|
||||
var file = fs.readFileSync(paths[relativePath]).toString(),
|
||||
matches = null,
|
||||
regex = /[ \t]*<!-- IMPORT ([\s\S]*?)? -->[ \t]*/;
|
||||
|
||||
while((matches = file.match(regex)) !== null) {
|
||||
var partial = "/" + matches[1];
|
||||
|
||||
if (paths[partial] && relativePath !== partial) {
|
||||
file = file.replace(regex, fs.readFileSync(paths[partial]).toString());
|
||||
} else {
|
||||
winston.warn('[meta/templates] Partial not loaded: ' + matches[1]);
|
||||
file = file.replace(regex, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (relativePath.match(/^\/admin\/[\s\S]*?/)) {
|
||||
addIndex(relativePath, file);
|
||||
if (relativePath.match(/^\/admin\/[\s\S]*?/)) {
|
||||
addIndex(relativePath, file);
|
||||
}
|
||||
|
||||
mkdirp.sync(path.join(viewsPath, relativePath.split('/').slice(0, -1).join('/')));
|
||||
fs.writeFile(path.join(viewsPath, relativePath), file, next);
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
winston.error('[meta/templates] ' + err.stack);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
compileIndex(viewsPath, function() {
|
||||
winston.verbose('[meta/templates] Successfully compiled templates.');
|
||||
|
||||
emitter.emit('templates:compiled');
|
||||
if (process.send) {
|
||||
process.send({
|
||||
action: 'templates:compiled'
|
||||
});
|
||||
}
|
||||
|
||||
mkdirp.sync(path.join(viewsPath, relativePath.split('/').slice(0, -1).join('/')));
|
||||
fs.writeFile(path.join(viewsPath, relativePath), file, next);
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
winston.error('[meta/templates] ' + err.stack);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
compileIndex(viewsPath, function() {
|
||||
winston.verbose('[meta/templates] Successfully compiled templates.');
|
||||
|
||||
emitter.emit('templates:compiled');
|
||||
if (process.send) {
|
||||
process.send({
|
||||
action: 'templates:compiled'
|
||||
});
|
||||
}
|
||||
callback();
|
||||
});
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var searchIndex = {};
|
||||
|
||||
function addIndex(path, file) {
|
||||
searchIndex[path] = file;
|
||||
|
||||
@@ -48,7 +48,11 @@ middleware.applyCSRF = csrf();
|
||||
middleware.ensureLoggedIn = ensureLoggedIn.ensureLoggedIn(nconf.get('relative_path') + '/login');
|
||||
|
||||
middleware.pageView = function(req, res, next) {
|
||||
analytics.pageView(req.ip);
|
||||
analytics.pageView({
|
||||
ip: req.ip,
|
||||
path: req.path,
|
||||
uid: req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') ? parseInt(req.user.uid, 10) : 0
|
||||
});
|
||||
|
||||
plugins.fireHook('action:middleware.pageView', {req: req});
|
||||
|
||||
|
||||
@@ -44,9 +44,6 @@ var async = require('async'),
|
||||
return next(null, null);
|
||||
}
|
||||
|
||||
if (notification.bodyShort) {
|
||||
notification.bodyShort = S(notification.bodyShort).escapeHTML().s;
|
||||
}
|
||||
if (notification.bodyLong) {
|
||||
notification.bodyLong = S(notification.bodyLong).escapeHTML().s;
|
||||
}
|
||||
@@ -58,6 +55,11 @@ var async = require('async'),
|
||||
}
|
||||
notification.image = userData.picture || null;
|
||||
notification.user = userData;
|
||||
|
||||
if (userData.username === '[[global:guest]]') {
|
||||
notification.bodyShort = notification.bodyShort.replace(/([\s\S]*?),[\s\S]*?,([\s\S]*?)/, '$1, [[global:guest]], $2');
|
||||
}
|
||||
|
||||
next(null, notification);
|
||||
});
|
||||
return;
|
||||
@@ -388,9 +390,9 @@ var async = require('async'),
|
||||
var numUsers = usernames.length;
|
||||
|
||||
if (numUsers === 2) {
|
||||
notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + ', ' + notifications[modifyIndex].topicTitle + ']]'
|
||||
notifications[modifyIndex].bodyShort = '[[' + mergeId + '_dual, ' + usernames.join(', ') + ', ' + notifications[modifyIndex].topicTitle + ']]';
|
||||
} else if (numUsers > 2) {
|
||||
notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers-1) + ', ' + notifications[modifyIndex].topicTitle + ']]'
|
||||
notifications[modifyIndex].bodyShort = '[[' + mergeId + '_multiple, ' + usernames[0] + ', ' + (numUsers-1) + ', ' + notifications[modifyIndex].topicTitle + ']]';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
_ = require('underscore'),
|
||||
var async = require('async');
|
||||
var _ = require('underscore');
|
||||
|
||||
db = require('../database'),
|
||||
topics = require('../topics'),
|
||||
user = require('../user'),
|
||||
plugins = require('../plugins');
|
||||
var db = require('../database');
|
||||
var topics = require('../topics');
|
||||
var user = require('../user');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
module.exports = function(Posts) {
|
||||
|
||||
Posts.delete = function(pid, callback) {
|
||||
Posts.delete = function(pid, uid, callback) {
|
||||
var postData;
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
function (next) {
|
||||
plugins.fireHook('filter:post.delete', {pid: pid, uid: uid}, next);
|
||||
},
|
||||
function (data, next) {
|
||||
Posts.setPostField(pid, 'deleted', 1, next);
|
||||
},
|
||||
function(next) {
|
||||
function (next) {
|
||||
Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'timestamp'], next);
|
||||
},
|
||||
function(_post, next) {
|
||||
function (_post, next) {
|
||||
postData = _post;
|
||||
topics.getTopicField(_post.tid, 'cid', next);
|
||||
},
|
||||
function(cid, next) {
|
||||
plugins.fireHook('action:post.delete', pid);
|
||||
|
||||
function (cid, next) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
updateTopicTimestamp(postData.tid, next);
|
||||
@@ -40,29 +41,31 @@ module.exports = function(Posts) {
|
||||
topics.updateTeaser(postData.tid, next);
|
||||
}
|
||||
], function(err) {
|
||||
plugins.fireHook('action:post.delete', pid);
|
||||
next(err, postData);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
Posts.restore = function(pid, callback) {
|
||||
Posts.restore = function(pid, uid, callback) {
|
||||
var postData;
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
function (next) {
|
||||
plugins.fireHook('filter:post.restore', {pid: pid, uid: uid}, next);
|
||||
},
|
||||
function (data, next) {
|
||||
Posts.setPostField(pid, 'deleted', 0, next);
|
||||
},
|
||||
function(next) {
|
||||
function (next) {
|
||||
Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp'], next);
|
||||
},
|
||||
function(_post, next) {
|
||||
function (_post, next) {
|
||||
postData = _post;
|
||||
topics.getTopicField(_post.tid, 'cid', next);
|
||||
},
|
||||
function(cid, next) {
|
||||
function (cid, next) {
|
||||
postData.cid = cid;
|
||||
plugins.fireHook('action:post.restore', _.clone(postData));
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
updateTopicTimestamp(postData.tid, next);
|
||||
@@ -74,6 +77,7 @@ module.exports = function(Posts) {
|
||||
topics.updateTeaser(postData.tid, next);
|
||||
}
|
||||
], function(err) {
|
||||
plugins.fireHook('action:post.restore', _.clone(postData));
|
||||
next(err, postData);
|
||||
});
|
||||
}
|
||||
@@ -99,40 +103,46 @@ module.exports = function(Posts) {
|
||||
});
|
||||
}
|
||||
|
||||
Posts.purge = function(pid, callback) {
|
||||
Posts.exists(pid, function(err, exists) {
|
||||
if (err || !exists) {
|
||||
return callback(err);
|
||||
Posts.purge = function(pid, uid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Posts.exists(pid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
if (!exists) {
|
||||
return callback();
|
||||
}
|
||||
plugins.fireHook('filter:post.purge', {pid: pid, uid: uid}, next);
|
||||
},
|
||||
function (data, next) {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
deletePostFromTopicAndUser(pid, next);
|
||||
},
|
||||
function (next) {
|
||||
deletePostFromCategoryRecentPosts(pid, next);
|
||||
},
|
||||
function (next) {
|
||||
deletePostFromUsersFavourites(pid, next);
|
||||
},
|
||||
function (next) {
|
||||
deletePostFromUsersVotes(pid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next);
|
||||
},
|
||||
function (next) {
|
||||
Posts.dismissFlag(pid, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
plugins.fireHook('action:post.purge', pid);
|
||||
db.delete('post:' + pid, next);
|
||||
});
|
||||
}
|
||||
|
||||
async.parallel([
|
||||
function(next) {
|
||||
deletePostFromTopicAndUser(pid, next);
|
||||
},
|
||||
function(next) {
|
||||
deletePostFromCategoryRecentPosts(pid, next);
|
||||
},
|
||||
function(next) {
|
||||
deletePostFromUsersFavourites(pid, next);
|
||||
},
|
||||
function(next) {
|
||||
deletePostFromUsersVotes(pid, next);
|
||||
},
|
||||
function(next) {
|
||||
db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next);
|
||||
},
|
||||
function(next) {
|
||||
Posts.dismissFlag(pid, next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
plugins.fireHook('action:post.purge', pid);
|
||||
db.delete('post:' + pid, callback);
|
||||
});
|
||||
});
|
||||
], callback);
|
||||
};
|
||||
|
||||
function deletePostFromTopicAndUser(pid, callback) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var cache = require('./cache'),
|
||||
plugins = require('../plugins');
|
||||
var cache = require('./cache');
|
||||
var plugins = require('../plugins');
|
||||
|
||||
module.exports = function(Posts) {
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
var async = require('async');
|
||||
|
||||
privileges = require('../privileges'),
|
||||
cache = require('./cache');
|
||||
var privileges = require('../privileges');
|
||||
var cache = require('./cache');
|
||||
|
||||
module.exports = function(Posts) {
|
||||
Posts.tools = {};
|
||||
@@ -40,25 +40,20 @@ module.exports = function(Posts) {
|
||||
if (!canEdit) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
next();
|
||||
}
|
||||
], function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (isDelete) {
|
||||
cache.del(pid);
|
||||
Posts.delete(pid, callback);
|
||||
} else {
|
||||
Posts.restore(pid, function(err, postData) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
Posts.parsePost(postData, callback);
|
||||
});
|
||||
if (isDelete) {
|
||||
cache.del(pid);
|
||||
Posts.delete(pid, uid, next);
|
||||
} else {
|
||||
Posts.restore(pid, uid, function(err, postData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
Posts.parsePost(postData, next);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
], callback);
|
||||
}
|
||||
|
||||
Posts.tools.purge = function(uid, pid, callback) {
|
||||
@@ -71,7 +66,7 @@ module.exports = function(Posts) {
|
||||
return next(new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
cache.del(pid);
|
||||
Posts.purge(pid, next);
|
||||
Posts.purge(pid, uid, next);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ function apiRoutes(router, middleware, controllers) {
|
||||
router.post('/uploadfavicon', middlewares, controllers.admin.uploads.uploadFavicon);
|
||||
router.post('/uploadTouchIcon', middlewares, controllers.admin.uploads.uploadTouchIcon);
|
||||
router.post('/uploadlogo', middlewares, controllers.admin.uploads.uploadLogo);
|
||||
router.post('/upload/sound', middlewares, controllers.admin.uploads.uploadSound);
|
||||
router.post('/uploadDefaultAvatar', middlewares, controllers.admin.uploads.uploadDefaultAvatar);
|
||||
}
|
||||
|
||||
@@ -47,6 +48,7 @@ function addRoutes(router, middleware, controllers) {
|
||||
router.get('/general/sounds', middlewares, controllers.admin.sounds.get);
|
||||
router.get('/general/navigation', middlewares, controllers.admin.navigation.get);
|
||||
router.get('/general/homepage', middlewares, controllers.admin.homepage.get);
|
||||
router.get('/general/social', middlewares, controllers.admin.social.get);
|
||||
|
||||
router.get('/manage/categories', middlewares, controllers.admin.categories.getAll);
|
||||
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
|
||||
|
||||
@@ -173,11 +173,11 @@ function handle404(app, middleware) {
|
||||
res.status(404);
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
return res.json({path: req.path.replace(/^\/api/, ''), title: '[[global:404.title]]'});
|
||||
return res.json({path: validator.escape(req.path.replace(/^\/api/, '') || ''), title: '[[global:404.title]]'});
|
||||
}
|
||||
|
||||
middleware.buildHeader(req, res, function() {
|
||||
res.render('404', {path: req.path, title: '[[global:404.title]]'});
|
||||
res.render('404', {path: validator.escape(req.path || ''), title: '[[global:404.title]]'});
|
||||
});
|
||||
} else {
|
||||
res.status(404).type('txt').send('Not found');
|
||||
@@ -201,10 +201,10 @@ function handleErrors(app, middleware) {
|
||||
res.status(err.status || 500);
|
||||
|
||||
if (res.locals.isAPI) {
|
||||
res.json({path: req.path, error: err.message});
|
||||
res.json({path: validator.escape(req.path || ''), error: err.message});
|
||||
} else {
|
||||
middleware.buildHeader(req, res, function() {
|
||||
res.render('500', {path: req.path, error: validator.escape(err.message)});
|
||||
res.render('500', {path: validator.escape(req.path || ''), error: validator.escape(err.message)});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
58
src/social.js
Normal file
58
src/social.js
Normal file
@@ -0,0 +1,58 @@
|
||||
"use strict";
|
||||
|
||||
var plugins = require('./plugins');
|
||||
var db = require('./database');
|
||||
var async = require('async');
|
||||
|
||||
var social = {};
|
||||
|
||||
social.getPostSharing = function(callback) {
|
||||
var networks = [
|
||||
{
|
||||
id: "facebook",
|
||||
name: "Facebook",
|
||||
class: "fa-facebook"
|
||||
},
|
||||
{
|
||||
id: "twitter",
|
||||
name: "Twitter",
|
||||
class: "fa-twitter"
|
||||
},
|
||||
{
|
||||
id: "google",
|
||||
name: "Google+",
|
||||
class: "fa-google-plus"
|
||||
}
|
||||
];
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
plugins.fireHook('filter:social.posts', networks, next);
|
||||
},
|
||||
function(networks, next) {
|
||||
db.getSetMembers('social:posts.activated', function(err, activated) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
networks.forEach(function(network, i) {
|
||||
networks[i].activated = (activated.indexOf(network.id) !== -1);
|
||||
});
|
||||
|
||||
next(null, networks);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
social.setActivePostSharingNetworks = function(networkIDs, callback) {
|
||||
db.delete('social:posts.activated', function(err) {
|
||||
if (!networkIDs.length) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
db.setAdd('social:posts.activated', networkIDs, callback);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = social;
|
||||
@@ -25,6 +25,7 @@ var async = require('async'),
|
||||
rewards: require('./admin/rewards'),
|
||||
navigation: require('./admin/navigation'),
|
||||
rooms: require('./admin/rooms'),
|
||||
social: require('./admin/social'),
|
||||
themes: {},
|
||||
plugins: {},
|
||||
widgets: {},
|
||||
@@ -216,16 +217,16 @@ SocketAdmin.analytics.get = function(socket, data, callback) {
|
||||
async.parallel({
|
||||
uniqueVisitors: function(next) {
|
||||
if (data.units === 'days') {
|
||||
getDailyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
||||
analytics.getDailyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
||||
} else {
|
||||
getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
||||
analytics.getHourlyStatsForSet('analytics:uniquevisitors', data.until || Date.now(), data.amount, next);
|
||||
}
|
||||
},
|
||||
pageviews: function(next) {
|
||||
if (data.units === 'days') {
|
||||
getDailyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
||||
analytics.getDailyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
||||
} else {
|
||||
getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
||||
analytics.getHourlyStatsForSet('analytics:pageviews', data.until || Date.now(), data.amount, next);
|
||||
}
|
||||
},
|
||||
monthlyPageViews: function(next) {
|
||||
@@ -250,62 +251,6 @@ SocketAdmin.logs.clear = function(socket, data, callback) {
|
||||
meta.logs.clear(callback);
|
||||
};
|
||||
|
||||
function getHourlyStatsForSet(set, hour, numHours, callback) {
|
||||
var terms = {},
|
||||
hoursArr = [];
|
||||
|
||||
hour = new Date(hour);
|
||||
hour.setHours(hour.getHours(), 0, 0, 0);
|
||||
|
||||
for (var i = 0, ii = numHours; i < ii; i++) {
|
||||
hoursArr.push(hour.getTime());
|
||||
hour.setHours(hour.getHours() - 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
db.sortedSetScores(set, hoursArr, function(err, counts) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
hoursArr.forEach(function(term, index) {
|
||||
terms[term] = parseInt(counts[index], 10) || 0;
|
||||
});
|
||||
|
||||
var termsArr = [];
|
||||
|
||||
hoursArr.reverse();
|
||||
hoursArr.forEach(function(hour) {
|
||||
termsArr.push(terms[hour]);
|
||||
});
|
||||
|
||||
callback(null, termsArr);
|
||||
});
|
||||
}
|
||||
|
||||
function getDailyStatsForSet(set, day, numDays, callback) {
|
||||
var daysArr = [];
|
||||
|
||||
day = new Date(day);
|
||||
day.setHours(0, 0, 0, 0);
|
||||
|
||||
async.whilst(function() {
|
||||
return numDays--;
|
||||
}, function(next) {
|
||||
getHourlyStatsForSet(set, day.getTime()-(1000*60*60*24*numDays), 24, function(err, day) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
daysArr.push(day.reduce(function(cur, next) {
|
||||
return cur+next;
|
||||
}));
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
callback(err, daysArr);
|
||||
});
|
||||
}
|
||||
|
||||
SocketAdmin.getMoreEvents = function(socket, next, callback) {
|
||||
var start = parseInt(next, 10);
|
||||
if (start < 0) {
|
||||
|
||||
@@ -42,7 +42,7 @@ Categories.getNames = function(socket, data, callback) {
|
||||
};
|
||||
|
||||
Categories.purge = function(socket, cid, callback) {
|
||||
categories.purge(cid, callback);
|
||||
categories.purge(cid, socket.uid, callback);
|
||||
};
|
||||
|
||||
Categories.update = function(socket, data, callback) {
|
||||
|
||||
10
src/socket.io/admin/social.js
Normal file
10
src/socket.io/admin/social.js
Normal file
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
var social = require('../../social'),
|
||||
SocketSocial = {};
|
||||
|
||||
SocketSocial.savePostSharingNetworks = function(socket, data, callback) {
|
||||
social.setActivePostSharingNetworks(data, callback);
|
||||
};
|
||||
|
||||
module.exports = SocketSocial;
|
||||
@@ -3,7 +3,6 @@
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var nconf = require('nconf');
|
||||
var validator = require('validator');
|
||||
|
||||
var websockets = require('./index');
|
||||
var user = require('../user');
|
||||
@@ -16,13 +15,12 @@ var plugins = require('../plugins');
|
||||
var SocketHelpers = {};
|
||||
|
||||
SocketHelpers.notifyOnlineUsers = function(uid, result) {
|
||||
var cid = result.posts[0].topic.cid;
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
user.getUidsFromSet('users:online', 0, -1, next);
|
||||
},
|
||||
function(uids, next) {
|
||||
privileges.categories.filterUids('read', cid, uids, next);
|
||||
privileges.topics.filterUids('read', result.posts[0].topic.tid, uids, next);
|
||||
},
|
||||
function(uids, next) {
|
||||
plugins.fireHook('filter:sockets.sendNewPostToUids', {uidsTo: uids, uidFrom: uid, type: 'newPost'}, next);
|
||||
|
||||
@@ -6,15 +6,20 @@ var SocketIO = require('socket.io'),
|
||||
nconf = require('nconf'),
|
||||
cookieParser = require('cookie-parser')(nconf.get('secret')),
|
||||
winston = require('winston'),
|
||||
var SocketIO = require('socket.io');
|
||||
var socketioWildcard = require('socketio-wildcard')();
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
var cookieParser = require('cookie-parser')(nconf.get('secret'));
|
||||
var winston = require('winston');
|
||||
|
||||
db = require('../database'),
|
||||
user = require('../user'),
|
||||
logger = require('../logger'),
|
||||
ratelimit = require('../middleware/ratelimit'),
|
||||
cls = require('../middleware/cls'),
|
||||
var db = require('../database');
|
||||
var user = require('../user');
|
||||
var logger = require('../logger');
|
||||
var ratelimit = require('../middleware/ratelimit');
|
||||
|
||||
Sockets = {},
|
||||
Namespaces = {};
|
||||
var Sockets = {};
|
||||
var Namespaces = {};
|
||||
|
||||
var io;
|
||||
|
||||
@@ -102,15 +107,15 @@ function onMessage(socket, payload) {
|
||||
return winston.warn('[socket.io] Empty method name');
|
||||
}
|
||||
|
||||
var parts = eventName.toString().split('.'),
|
||||
namespace = parts[0],
|
||||
methodToCall = parts.reduce(function(prev, cur) {
|
||||
if (prev !== null && prev[cur]) {
|
||||
return prev[cur];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, Namespaces);
|
||||
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') {
|
||||
@@ -130,16 +135,23 @@ function onMessage(socket, payload) {
|
||||
return socket.disconnect();
|
||||
}
|
||||
|
||||
if (Namespaces[namespace].before) {
|
||||
Namespaces[namespace].before(socket, eventName, params, function(err) {
|
||||
if (err) {
|
||||
return callback({message: err.message});
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
validateSession(socket, next);
|
||||
},
|
||||
function (next) {
|
||||
if (Namespaces[namespace].before) {
|
||||
Namespaces[namespace].before(socket, eventName, params, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
callMethod(methodToCall, socket, params, callback);
|
||||
});
|
||||
} else {
|
||||
callMethod(methodToCall, socket, params, callback);
|
||||
}
|
||||
},
|
||||
function (next) {
|
||||
methodToCall(socket, params, next);
|
||||
}
|
||||
], function(err, result) {
|
||||
callback(err ? {message: err.message} : null, result);
|
||||
});
|
||||
}
|
||||
|
||||
function requireModules() {
|
||||
@@ -152,19 +164,33 @@ function requireModules() {
|
||||
});
|
||||
}
|
||||
|
||||
function authorize(socket, callback) {
|
||||
var handshake = socket.request;
|
||||
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]]'));
|
||||
}
|
||||
|
||||
if (!handshake) {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function authorize(socket, callback) {
|
||||
var request = socket.request;
|
||||
|
||||
if (!request) {
|
||||
return callback(new Error('[[error:not-authorized]]'));
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
cookieParser(handshake, {}, next);
|
||||
cookieParser(request, {}, next);
|
||||
},
|
||||
function(next) {
|
||||
db.sessionStore.get(handshake.signedCookies['express.sid'], function(err, sessionData) {
|
||||
db.sessionStore.get(request.signedCookies['express.sid'], function(err, sessionData) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
@@ -192,12 +218,6 @@ function addRedisAdapter(io) {
|
||||
}
|
||||
}
|
||||
|
||||
function callMethod(method, socket, params, callback) {
|
||||
method(socket, params, function(err, result) {
|
||||
callback(err ? {message: err.message} : null, result);
|
||||
});
|
||||
}
|
||||
|
||||
Sockets.in = function(room) {
|
||||
return io.in(room);
|
||||
};
|
||||
@@ -235,9 +255,9 @@ Sockets.getOnlineAnonCount = function () {
|
||||
};
|
||||
|
||||
Sockets.reqFromSocket = function(socket) {
|
||||
var headers = socket.request.headers,
|
||||
host = headers.host,
|
||||
referer = headers.referer || '';
|
||||
var headers = socket.request.headers;
|
||||
var host = headers.host;
|
||||
var referer = headers.referer || '';
|
||||
|
||||
return {
|
||||
ip: headers['x-forwarded-for'] || socket.ip,
|
||||
|
||||
@@ -266,31 +266,6 @@ SocketModules.chats.renameRoom = function(socket, data, callback) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
SocketModules.chats.userStartTyping = function(socket, data, callback) {
|
||||
sendTypingNotification('event:chats.userStartTyping', socket, data, callback);
|
||||
};
|
||||
|
||||
SocketModules.chats.userStopTyping = function(socket, data, callback) {
|
||||
sendTypingNotification('event:chats.userStopTyping', socket, data, callback);
|
||||
};
|
||||
|
||||
function sendTypingNotification(event, socket, data, callback) {
|
||||
if (!socket.uid || !data || !data.roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
Messaging.getUidsInRoom(data.roomId, 0, -1, function(err, uids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
uids.forEach(function(uid) {
|
||||
if (socket.uid !== parseInt(uid, 10)) {
|
||||
server.in('uid_' + uid).emit(event, data.fromUid);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SocketModules.chats.getRecentChats = function(socket, data, callback) {
|
||||
if (!data || !utils.isNumber(data.after)) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
|
||||
@@ -8,6 +8,7 @@ var websockets = require('../index');
|
||||
var socketTopics = require('../topics');
|
||||
var privileges = require('../../privileges');
|
||||
var plugins = require('../../plugins');
|
||||
var social = require('../../social');
|
||||
var favourites = require('../../favourites');
|
||||
|
||||
module.exports = function(SocketPosts) {
|
||||
@@ -29,6 +30,9 @@ module.exports = function(SocketPosts) {
|
||||
},
|
||||
tools: function(next) {
|
||||
plugins.fireHook('filter:post.tools', {pid: data.pid, uid: socket.uid, tools: []}, next);
|
||||
},
|
||||
postSharing: function(next) {
|
||||
social.getPostSharing(next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var path = require('path');
|
||||
|
||||
var user = require('../../user');
|
||||
var plugins = require('../../plugins');
|
||||
@@ -73,20 +74,21 @@ module.exports = function(SocketUser) {
|
||||
user.isAdminOrSelf(socket.uid, data.uid, next);
|
||||
},
|
||||
function (next) {
|
||||
user.getUserField(data.uid, 'uploadedpicture', next);
|
||||
user.getUserFields(data.uid, ['uploadedpicture', 'picture'], next);
|
||||
},
|
||||
function(uploadedPicture, next) {
|
||||
if (!uploadedPicture.startsWith('http')) {
|
||||
require('fs').unlink(uploadedPicture, function(err) {
|
||||
function(userData, next) {
|
||||
if (!userData.uploadedpicture.startsWith('http')) {
|
||||
require('fs').unlink(path.join(__dirname, '../../../public', userData.uploadedpicture), function(err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
user.setUserField(data.uid, 'uploadedpicture', '', next);
|
||||
},
|
||||
function(next) {
|
||||
user.getUserField(data.uid, 'picture', next);
|
||||
|
||||
user.setUserFields(data.uid, {
|
||||
uploadedpicture: '',
|
||||
picture: userData.uploadedpicture === userData.picture ? '' : userData.picture // if current picture is uploaded picture, reset to user icon
|
||||
}, next);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ module.exports = function(SocketUser) {
|
||||
searchBy: data.searchBy,
|
||||
sortBy: data.sortBy,
|
||||
onlineOnly: data.onlineOnly,
|
||||
bannedOnly: data.bannedOnly,
|
||||
uid: socket.uid
|
||||
}, function(err, result) {
|
||||
if (err) {
|
||||
|
||||
@@ -6,6 +6,7 @@ var async = require('async'),
|
||||
db = require('../database'),
|
||||
utils = require('../../public/src/utils'),
|
||||
plugins = require('../plugins'),
|
||||
analytics = require('../analytics'),
|
||||
user = require('../user'),
|
||||
meta = require('../meta'),
|
||||
posts = require('../posts'),
|
||||
@@ -15,7 +16,7 @@ var async = require('async'),
|
||||
module.exports = function(Topics) {
|
||||
|
||||
Topics.create = function(data, callback) {
|
||||
// This is an interal method, consider using Topics.post instead
|
||||
// This is an internal method, consider using Topics.post instead
|
||||
var timestamp = data.timestamp || Date.now();
|
||||
var topicData;
|
||||
|
||||
@@ -171,6 +172,7 @@ module.exports = function(Topics) {
|
||||
data.topicData.mainPost = data.postData;
|
||||
data.postData.index = 0;
|
||||
|
||||
analytics.increment(['topics', 'topics:byCid:' + data.topicData.cid]);
|
||||
plugins.fireHook('action:topic.post', data.topicData);
|
||||
|
||||
if (parseInt(uid, 10)) {
|
||||
@@ -186,12 +188,12 @@ module.exports = function(Topics) {
|
||||
};
|
||||
|
||||
Topics.reply = function(data, callback) {
|
||||
var tid = data.tid,
|
||||
uid = data.uid,
|
||||
content = data.content,
|
||||
postData;
|
||||
|
||||
var tid = data.tid;
|
||||
var uid = data.uid;
|
||||
var content = data.content;
|
||||
var postData;
|
||||
var cid;
|
||||
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
Topics.getTopicField(tid, 'cid', next);
|
||||
@@ -256,6 +258,7 @@ module.exports = function(Topics) {
|
||||
}
|
||||
|
||||
Topics.notifyFollowers(postData, uid);
|
||||
analytics.increment(['posts', 'posts:byCid:' + cid]);
|
||||
plugins.fireHook('action:topic.reply', postData);
|
||||
|
||||
next(null, postData);
|
||||
|
||||
@@ -58,6 +58,7 @@ module.exports = function(Topics) {
|
||||
if (!topic) {
|
||||
return;
|
||||
}
|
||||
topic.titleRaw = topic.title;
|
||||
topic.title = validator.escape(topic.title);
|
||||
topic.relativeTime = utils.toISOString(topic.timestamp);
|
||||
topic.lastposttimeISO = utils.toISOString(topic.lastposttime);
|
||||
|
||||
@@ -85,7 +85,7 @@ module.exports = function(Topics) {
|
||||
});
|
||||
};
|
||||
|
||||
Topics.purgePostsAndTopic = function(tid, callback) {
|
||||
Topics.purgePostsAndTopic = function(tid, uid, callback) {
|
||||
var mainPid;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
@@ -94,11 +94,13 @@ module.exports = function(Topics) {
|
||||
function (_mainPid, next) {
|
||||
mainPid = _mainPid;
|
||||
batch.processSortedSet('tid:' + tid + ':posts', function(pids, next) {
|
||||
async.eachLimit(pids, 10, posts.purge, next);
|
||||
async.eachLimit(pids, 10, function(pid, next) {
|
||||
posts.purge(pid, uid, next);
|
||||
}, next);
|
||||
}, {alwaysStartAt: 0}, next);
|
||||
},
|
||||
function (next) {
|
||||
posts.purge(mainPid, next);
|
||||
posts.purge(mainPid, uid, next);
|
||||
},
|
||||
function (next) {
|
||||
Topics.purge(tid, next);
|
||||
|
||||
@@ -15,7 +15,7 @@ var async = require('async'),
|
||||
module.exports = function(Topics) {
|
||||
|
||||
Topics.onNewPostMade = function(postData, callback) {
|
||||
async.parallel([
|
||||
async.series([
|
||||
function(next) {
|
||||
Topics.increasePostCount(postData.tid, next);
|
||||
},
|
||||
@@ -143,6 +143,7 @@ module.exports = function(Topics) {
|
||||
if (post) {
|
||||
post.display_moderator_tools = topicPrivileges.isAdminOrMod || post.selfPost;
|
||||
post.display_move_tools = topicPrivileges.isAdminOrMod && post.index !== 0;
|
||||
post.display_post_menu = topicPrivileges.isAdminOrMod || post.selfPost || !post.deleted;
|
||||
if (post.deleted && !(topicPrivileges.isAdminOrMod || post.selfPost)) {
|
||||
post.content = '[[topic:post_is_deleted]]';
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
winston = require('winston'),
|
||||
db = require('../database');
|
||||
var async = require('async');
|
||||
var db = require('../database');
|
||||
|
||||
module.exports = function(Topics) {
|
||||
var terms = {
|
||||
@@ -42,12 +41,22 @@ module.exports = function(Topics) {
|
||||
Topics.updateTimestamp = function(tid, timestamp, callback) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
Topics.updateRecent(tid, timestamp, next);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Topics.getTopicField(tid, 'deleted', next);
|
||||
},
|
||||
function (deleted, next) {
|
||||
if (parseInt(deleted, 10) === 1) {
|
||||
return next();
|
||||
}
|
||||
Topics.updateRecent(tid, timestamp, next);
|
||||
}
|
||||
], next);
|
||||
},
|
||||
function(next) {
|
||||
Topics.setTopicField(tid, 'lastposttime', timestamp, next);
|
||||
}
|
||||
], function(err, results) {
|
||||
], function(err) {
|
||||
callback(err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@ module.exports = function(Topics) {
|
||||
},
|
||||
function (data, next) {
|
||||
tags = data.tags.slice(0, meta.config.maximumTagsPerTopic || 5);
|
||||
tags = tags.map(Topics.cleanUpTag).filter(function(tag) {
|
||||
return tag && tag.length >= (meta.config.minimumTagLength || 3);
|
||||
tags = tags.map(Topics.cleanUpTag).filter(function(tag, index, array) {
|
||||
return tag && tag.length >= (meta.config.minimumTagLength || 3) && array.indexOf(tag) === index;
|
||||
});
|
||||
|
||||
var keys = tags.map(function(tag) {
|
||||
|
||||
@@ -94,7 +94,7 @@ module.exports = function(Topics) {
|
||||
function (_cid, next) {
|
||||
cid = _cid;
|
||||
|
||||
Topics.purgePostsAndTopic(tid, next);
|
||||
Topics.purgePostsAndTopic(tid, uid, next);
|
||||
},
|
||||
function (next) {
|
||||
next(null, {tid: tid, cid: cid, uid: uid});
|
||||
|
||||
@@ -15,7 +15,7 @@ var utils = require('../../public/src/utils');
|
||||
module.exports = function(Topics) {
|
||||
|
||||
Topics.getTotalUnread = function(uid, callback) {
|
||||
Topics.getUnreadTids(0, uid, 0, 20, function(err, tids) {
|
||||
Topics.getUnreadTids(0, uid, 0, 99, function(err, tids) {
|
||||
callback(err, tids ? tids.length : 0);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ var db = require('./database'),
|
||||
schemaDate, thisSchemaDate,
|
||||
|
||||
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema
|
||||
latestSchema = Date.UTC(2016, 0, 23);
|
||||
latestSchema = Date.UTC(2016, 1, 25);
|
||||
|
||||
Upgrade.check = function(callback) {
|
||||
db.get('schemaDate', function(err, value) {
|
||||
@@ -410,6 +410,34 @@ Upgrade.upgrade = function(callback) {
|
||||
winston.info('[2016/01/23] Creating Global moderators group skipped!');
|
||||
next();
|
||||
}
|
||||
},
|
||||
function(next) {
|
||||
thisSchemaDate = Date.UTC(2016, 1, 25);
|
||||
|
||||
if (schemaDate < thisSchemaDate) {
|
||||
updatesMade = true;
|
||||
winston.info('[2016/02/25] Social: Post Sharing');
|
||||
|
||||
var social = require('./social');
|
||||
async.parallel([
|
||||
function (next) {
|
||||
social.setActivePostSharingNetworks(['facebook', 'google', 'twitter'], next);
|
||||
},
|
||||
function (next) {
|
||||
db.deleteObjectField('config', 'disableSocialButtons', next);
|
||||
}
|
||||
], function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
winston.info('[2016/02/25] Social: Post Sharing done!');
|
||||
Upgrade.update(thisSchemaDate, next);
|
||||
});
|
||||
} else {
|
||||
winston.info('[2016/02/25] Social: Post Sharing skipped!');
|
||||
next();
|
||||
}
|
||||
}
|
||||
// Add new schema updates here
|
||||
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!!
|
||||
|
||||
@@ -70,7 +70,7 @@ module.exports = function(User) {
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
async.apply(db.getSortedSetRange, 'uid:' + uid + ':sessions', 0, -1),
|
||||
async.apply(db.getSortedSetRevRange, 'uid:' + uid + ':sessions', 0, -1),
|
||||
function (sids, next) {
|
||||
_sids = sids;
|
||||
async.map(sids, db.sessionStore.get.bind(db.sessionStore), next);
|
||||
|
||||
@@ -123,7 +123,7 @@ var async = require('async'),
|
||||
return null;
|
||||
}
|
||||
|
||||
notification.path = pidToPaths[notification.pid] || notification.path || '';
|
||||
notification.path = pidToPaths[notification.pid] || notification.path || null;
|
||||
|
||||
if (notification.nid.startsWith('follow')) {
|
||||
notification.path = '/user/' + notification.user.userslug;
|
||||
@@ -131,6 +131,9 @@ var async = require('async'),
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
callback(null, notifications);
|
||||
@@ -157,7 +160,7 @@ var async = require('async'),
|
||||
posts.getPostIndices(postData, uid, next);
|
||||
},
|
||||
topics: function(next) {
|
||||
db.getObjectsFields(topicKeys, ['slug'], next);
|
||||
db.getObjectsFields(topicKeys, ['slug', 'deleted'], next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
@@ -166,6 +169,11 @@ var async = require('async'),
|
||||
|
||||
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;
|
||||
|
||||
@@ -200,7 +208,7 @@ var async = require('async'),
|
||||
if (!parseInt(uid, 10)) {
|
||||
return callback(null, 0);
|
||||
}
|
||||
db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 20, function(err, nids) {
|
||||
db.getSortedSetRevRange('uid:' + uid + ':notifications:unread', 0, 99, function(err, nids) {
|
||||
callback(err, Array.isArray(nids) ? nids.length : 0);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -19,17 +19,21 @@ module.exports = function(User) {
|
||||
User.isPasswordCorrect = function(uid, password, callback) {
|
||||
password = password || '';
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
User.isPasswordValid(password, next);
|
||||
},
|
||||
function (next) {
|
||||
db.getObjectField('user:' + uid, 'password', next);
|
||||
},
|
||||
function (hashedPassword, next) {
|
||||
if (!hashedPassword) {
|
||||
return callback();
|
||||
return callback(null, true);
|
||||
}
|
||||
Password.compare(password, hashedPassword, next);
|
||||
|
||||
User.isPasswordValid(password, function(err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
Password.compare(password, hashedPassword, next);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
@@ -63,7 +63,7 @@ module.exports = function(User) {
|
||||
};
|
||||
|
||||
User.onNewPostMade = function(postData, callback) {
|
||||
async.parallel([
|
||||
async.series([
|
||||
function(next) {
|
||||
User.addPostIdToUser(postData.uid, postData.pid, postData.timestamp, next);
|
||||
},
|
||||
|
||||
@@ -74,10 +74,6 @@ module.exports = function(User) {
|
||||
|
||||
var userslug = utils.slugify(data.username);
|
||||
|
||||
if (userslug === userData.userslug) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (data.username.length < meta.config.minimumUsernameLength) {
|
||||
return next(new Error('[[error:username-too-short]]'));
|
||||
}
|
||||
@@ -90,6 +86,10 @@ module.exports = function(User) {
|
||||
return next(new Error('[[error:invalid-username]]'));
|
||||
}
|
||||
|
||||
if (userslug === userData.userslug) {
|
||||
return next();
|
||||
}
|
||||
|
||||
User.existsBySlug(userslug, function(err, exists) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
@@ -110,7 +110,7 @@ module.exports = function(User) {
|
||||
return callback(err);
|
||||
}
|
||||
plugins.fireHook('action:user.updateProfile', {data: data, uid: uid});
|
||||
User.getUserFields(uid, ['email', 'username', 'userslug', 'picture'], callback);
|
||||
User.getUserFields(uid, ['email', 'username', 'userslug', 'picture', 'icon:text', 'icon:bgColor'], callback);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ module.exports = function(User) {
|
||||
function filterAndSortUids(uids, data, callback) {
|
||||
var sortBy = data.sortBy || 'joindate';
|
||||
|
||||
var fields = ['uid', 'status', 'lastonline', sortBy];
|
||||
var fields = ['uid', 'status', 'lastonline', 'banned', sortBy];
|
||||
|
||||
User.getUsersFields(uids, fields, function(err, userData) {
|
||||
if (err) {
|
||||
@@ -96,6 +96,12 @@ module.exports = function(User) {
|
||||
return user && user.status !== 'offline' && (Date.now() - parseInt(user.lastonline, 10) < 300000);
|
||||
});
|
||||
}
|
||||
|
||||
if(data.bannedOnly) {
|
||||
userData = userData.filter(function(user) {
|
||||
return user && user.banned;
|
||||
});
|
||||
}
|
||||
|
||||
sortUsers(userData, sortBy);
|
||||
|
||||
|
||||
@@ -58,14 +58,14 @@ module.exports = function(User) {
|
||||
var defaultTopicsPerPage = parseInt(meta.config.topicsPerPage, 10) || 20;
|
||||
var defaultPostsPerPage = parseInt(meta.config.postsPerPage, 10) || 20;
|
||||
|
||||
settings.showemail = parseInt(settings.showemail, 10) === 1;
|
||||
settings.showfullname = parseInt(settings.showfullname, 10) === 1;
|
||||
settings.showemail = parseInt(getSetting(settings, 'showemail', 0), 10) === 1;
|
||||
settings.showfullname = parseInt(getSetting(settings, 'showfullname', 0), 10) === 1;
|
||||
settings.openOutgoingLinksInNewTab = parseInt(getSetting(settings, 'openOutgoingLinksInNewTab', 0), 10) === 1;
|
||||
settings.dailyDigestFreq = getSetting(settings, 'dailyDigestFreq', 'off');
|
||||
settings.usePagination = parseInt(getSetting(settings, 'usePagination', 0), 10) === 1;
|
||||
settings.topicsPerPage = Math.min(settings.topicsPerPage ? parseInt(settings.topicsPerPage, 10) : defaultTopicsPerPage, defaultTopicsPerPage);
|
||||
settings.postsPerPage = Math.min(settings.postsPerPage ? parseInt(settings.postsPerPage, 10) : defaultPostsPerPage, defaultPostsPerPage);
|
||||
settings.notificationSounds = parseInt(settings.notificationSounds, 10) === 1;
|
||||
settings.notificationSounds = parseInt(getSetting(settings, 'notificationSounds', 0), 10) === 1;
|
||||
settings.userLang = settings.userLang || meta.config.defaultLang || 'en_GB';
|
||||
settings.topicPostSort = getSetting(settings, 'topicPostSort', 'oldest_to_newest');
|
||||
settings.categoryTopicSort = getSetting(settings, 'categoryTopicSort', 'newest_to_oldest');
|
||||
@@ -73,7 +73,7 @@ module.exports = function(User) {
|
||||
settings.followTopicsOnReply = parseInt(getSetting(settings, 'followTopicsOnReply', 0), 10) === 1;
|
||||
settings.sendChatNotifications = parseInt(getSetting(settings, 'sendChatNotifications', 0), 10) === 1;
|
||||
settings.sendPostNotifications = parseInt(getSetting(settings, 'sendPostNotifications', 0), 10) === 1;
|
||||
settings.restrictChat = parseInt(settings.restrictChat, 10) === 1;
|
||||
settings.restrictChat = parseInt(getSetting(settings, 'restrictChat', 0), 10) === 1;
|
||||
settings.topicSearchEnabled = parseInt(getSetting(settings, 'topicSearchEnabled', 0), 10) === 1;
|
||||
settings.bootswatchSkin = settings.bootswatchSkin || 'default';
|
||||
|
||||
@@ -91,8 +91,12 @@ module.exports = function(User) {
|
||||
}
|
||||
|
||||
User.saveSettings = function(uid, data, callback) {
|
||||
if (invalidPaginationSettings(data)) {
|
||||
return callback(new Error('[[error:invalid-pagination-value]]'));
|
||||
if (!data.postsPerPage || parseInt(data.postsPerPage, 10) <= 1 || parseInt(data.postsPerPage, 10) > meta.config.postsPerPage) {
|
||||
return callback(new Error('[[error:invalid-pagination-value, 2, ' + meta.config.postsPerPage + ']]'));
|
||||
}
|
||||
|
||||
if (!data.topicsPerPage || parseInt(data.topicsPerPage, 10) <= 1 || parseInt(data.topicsPerPage, 10) > meta.config.topicsPerPage) {
|
||||
return callback(new Error('[[error:invalid-pagination-value, 2, ' + meta.config.topicsPerPage + ']]'));
|
||||
}
|
||||
|
||||
data.userLang = data.userLang || meta.config.defaultLang;
|
||||
@@ -136,12 +140,6 @@ module.exports = function(User) {
|
||||
], callback);
|
||||
};
|
||||
|
||||
function invalidPaginationSettings(data) {
|
||||
return !data.topicsPerPage || !data.postsPerPage ||
|
||||
parseInt(data.topicsPerPage, 10) <= 0 || parseInt(data.postsPerPage, 10) <= 0 ||
|
||||
parseInt(data.topicsPerPage, 10) > meta.config.topicsPerPage || parseInt(data.postsPerPage, 10) > meta.config.postsPerPage;
|
||||
}
|
||||
|
||||
function updateDigestSetting(uid, dailyDigestFreq, callback) {
|
||||
async.waterfall([
|
||||
function(next) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<ul class="nav nav-pills">
|
||||
<li class="active"><a href="#installed" data-toggle="tab">Installed Plugins</a></li>
|
||||
<li><a href="#download" data-toggle="tab">Download Plugins</a></li>
|
||||
<li><a href="#upgrade" data-toggle="tab">Upgradable Plugins</a></li>
|
||||
</ul>
|
||||
<br />
|
||||
|
||||
@@ -15,13 +16,14 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="download">
|
||||
<div class="panel-body">
|
||||
<ul class="download">
|
||||
<!-- BEGIN download -->
|
||||
<!-- IMPORT admin/partials/download_plugin_item.tpl -->
|
||||
<!-- END download -->
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="download">
|
||||
<!-- BEGIN download -->
|
||||
<!-- IMPORT admin/partials/download_plugin_item.tpl -->
|
||||
<!-- END download -->
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="upgrade">
|
||||
<ul class="upgrade"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
24
src/views/admin/general/social.tpl
Normal file
24
src/views/admin/general/social.tpl
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="social settings" class="row">
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Post Sharing</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<div class="form-group" id="postSharingNetworks">
|
||||
<!-- BEGIN posts -->
|
||||
<div class="checkbox">
|
||||
<label for="{posts.id}" class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input type="checkbox" class="mdl-switch__input" id="{posts.id}" data-field="{posts.id}" name="{posts.id}" <!-- IF posts.activated -->checked<!-- ENDIF posts.activated --> />
|
||||
<span class="mdl-switch__label"><i class="fa {posts.class}"></i> {posts.name}</span>
|
||||
</label>
|
||||
</div>
|
||||
<!-- END posts -->
|
||||
<small>Plugins can add additional networks for sharing posts.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button id="save" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
@@ -1,62 +1,82 @@
|
||||
<div class="sounds settings" class="row">
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Notifications</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<label for="notification">Notifications</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-9">
|
||||
<select class="form-control" id="notification" name="notification">
|
||||
<option value=""></option>
|
||||
<!-- BEGIN sounds -->
|
||||
<option value="{sounds.name}">{sounds.name}</option>
|
||||
<!-- END sounds -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group col-xs-3">
|
||||
<button type="button" class="form-control btn btn-sm btn-default" data-action="play">Play <i class="fa fa-play"></i></button>
|
||||
<div class="col-xs-9">
|
||||
<form role="form">
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Notifications</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<label for="notification">Notifications</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-9">
|
||||
<select class="form-control" id="notification" name="notification">
|
||||
<option value=""></option>
|
||||
<!-- BEGIN sounds -->
|
||||
<option value="{sounds.name}">{sounds.name}</option>
|
||||
<!-- END sounds -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group col-xs-3">
|
||||
<button type="button" class="form-control btn btn-sm btn-default" data-action="play">Play <i class="fa fa-play"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Chat Messages</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<label for="chat-incoming">Incoming Message</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-9">
|
||||
<select class="form-control" id="chat-incoming" name="chat-incoming">
|
||||
<option value=""></option>
|
||||
<!-- BEGIN sounds -->
|
||||
<option value="{sounds.name}">{sounds.name}</option>
|
||||
<!-- END sounds -->
|
||||
</select>
|
||||
<div class="row">
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Chat Messages</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<label for="chat-incoming">Incoming Message</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-9">
|
||||
<select class="form-control" id="chat-incoming" name="chat-incoming">
|
||||
<option value=""></option>
|
||||
<!-- BEGIN sounds -->
|
||||
<option value="{sounds.name}">{sounds.name}</option>
|
||||
<!-- END sounds -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group col-xs-3">
|
||||
<button type="button" class="form-control btn btn-sm btn-default" data-action="play">Play <i class="fa fa-play"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group col-xs-3">
|
||||
<button type="button" class="form-control btn btn-sm btn-default" data-action="play">Play <i class="fa fa-play"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label for="chat-outgoing">Outgoing Message</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-9">
|
||||
<select class="form-control" id="chat-outgoing" name="chat-outgoing">
|
||||
<option value=""></option>
|
||||
<!-- BEGIN sounds -->
|
||||
<option value="{sounds.name}">{sounds.name}</option>
|
||||
<!-- END sounds -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group col-xs-3">
|
||||
<button type="button" class="form-control btn btn-sm btn-default" data-action="play">Play <i class="fa fa-play"></i></button>
|
||||
<label for="chat-outgoing">Outgoing Message</label>
|
||||
<div class="row">
|
||||
<div class="form-group col-xs-9">
|
||||
<select class="form-control" id="chat-outgoing" name="chat-outgoing">
|
||||
<option value=""></option>
|
||||
<!-- BEGIN sounds -->
|
||||
<option value="{sounds.name}">{sounds.name}</option>
|
||||
<!-- END sounds -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group col-xs-3">
|
||||
<button type="button" class="form-control btn btn-sm btn-default" data-action="play">Play <i class="fa fa-play"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-3">
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<input data-action="upload" data-target="logoUrl" data-route="{config.relative_path}/api/admin/upload/sound" type="button" class="btn btn-primary btn-block" value="Upload New Sound"></input>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="save" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
|
||||
<i class="material-icons">save</i>
|
||||
</button>
|
||||
</button>
|
||||
|
||||
<script>
|
||||
require(['admin/settings'], function(Settings) {
|
||||
Settings.init();
|
||||
});
|
||||
</script>
|
||||
@@ -3,6 +3,7 @@
|
||||
<ul class="nav nav-pills">
|
||||
<li class="active"><a href="#category-settings" data-toggle="tab">Category Settings</a></li>
|
||||
<li><a href="#privileges" data-toggle="tab">Privileges</a></li>
|
||||
<li><a href="#analytics" data-toggle="tab">Analytics</a></li>
|
||||
</ul>
|
||||
<br />
|
||||
<div class="tab-content">
|
||||
@@ -119,6 +120,37 @@
|
||||
<!-- IMPORT admin/partials/categories/privileges.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade col-xs-12" id="analytics">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 text-center">
|
||||
<div><canvas id="pageviews:hourly" height="250"></canvas></div>
|
||||
<p>
|
||||
<small><strong>Figure 1</strong> – Hourly page views for this category</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6 text-center">
|
||||
<div><canvas id="pageviews:daily" height="250"></canvas></div>
|
||||
<p>
|
||||
<small><strong>Figure 2</strong> – Daily page views for this category</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 text-center">
|
||||
<div><canvas id="topics:daily" height="250"></canvas></div>
|
||||
<p>
|
||||
<small><strong>Figure 3</strong> – Daily topics created in this category</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-sm-6 text-center">
|
||||
<div><canvas id="posts:daily" height="250"></canvas></div>
|
||||
<p>
|
||||
<small><strong>Figure 4</strong> – Daily posts made in this category</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<div class="groups">
|
||||
<div class="col-lg-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><i class="fa fa-group"></i> Groups List</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<input id="group-search" type="text" class="form-control" placeholder="Search" /><br/>
|
||||
|
||||
<table class="table table-striped groups-list">
|
||||
<tr>
|
||||
<th>Group Name</th>
|
||||
@@ -38,10 +34,9 @@
|
||||
|
||||
<div class="col-lg-3 acp-sidebar">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">Groups Control Panel</div>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<button class="btn btn-primary" id="create">New Group</button>
|
||||
<input id="group-search" type="text" class="form-control" placeholder="Search" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,4 +73,6 @@
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button id="create" class="floating-button mdl-button mdl-js-button mdl-button--fab mdl-js-ripple-effect mdl-button--colored">
|
||||
<i class="material-icons">add</i>
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<li><a href="{relative_path}/admin/general/navigation">Navigation</a></li>
|
||||
<li><a href="{relative_path}/admin/general/languages">Languages</a></li>
|
||||
<li><a href="{relative_path}/admin/general/sounds">Sounds</a></li>
|
||||
<li><a href="{relative_path}/admin/general/social">Social</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -160,6 +161,7 @@
|
||||
<li><a href="{relative_path}/admin/general/navigation">Navigation</a></li>
|
||||
<li><a href="{relative_path}/admin/general/languages">Languages</a></li>
|
||||
<li><a href="{relative_path}/admin/general/sounds">Sounds</a></li>
|
||||
<li><a href="{relative_path}/admin/general/social">Social</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown menu-item">
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="eventLoopLagThreshold">Event Loop Lag Threshold (in milliseconds)</label>
|
||||
<input class="form-control" id="eventLoopLagThreshold" type="number" data-field="eventLoopLagThreshold" placeholder="Default: 70" step="10" value="70" />
|
||||
<input class="form-control" id="eventLoopLagThreshold" type="number" data-field="eventLoopLagThreshold" placeholder="Default: 70" step="10" min="10" value="70" />
|
||||
<p class="help-block">
|
||||
Lowering this value decreases wait times for page loads, but will also show the
|
||||
"excessive load" message to more users. (Reload required)
|
||||
@@ -103,4 +103,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- IMPORT admin/settings/footer.tpl -->
|
||||
<!-- IMPORT admin/settings/footer.tpl -->
|
||||
|
||||
@@ -174,6 +174,28 @@
|
||||
<div class="col-sm-2 col-xs-12 settings-header">Default User Settings</div>
|
||||
<div class="col-sm-10 col-xs-12">
|
||||
<form>
|
||||
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="showemail">
|
||||
<span class="mdl-switch__label"><strong>[[user:show_email]]</strong></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="showfullname">
|
||||
<span class="mdl-switch__label"><strong>[[user:show_fullname]]</strong></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="restrictChat">
|
||||
<span class="mdl-switch__label"><strong>[[user:restrict_chats]]</strong></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="openOutgoingLinksInNewTab">
|
||||
@@ -226,6 +248,13 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
|
||||
<input class="mdl-switch__input" type="checkbox" data-field="notificationSounds" />
|
||||
<span class="mdl-switch__label">[[user:notification_sounds]]</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<a href="{url}">[[email:digest.cta, {site_title}]]</a>
|
||||
</p>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
|
||||
<hr />
|
||||
<p>
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
<a href="{registerLink}">[[email:invitation.ctr]]</a>
|
||||
</blockquote>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<a href="{url}/chats/{fromUserslug}">[[email:notif.chat.cta]]</a>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
|
||||
<hr />
|
||||
<p>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<a href="{url}">[[email:notif.post.cta]]</a>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
|
||||
<hr />
|
||||
<p>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
[[email:closing]]
|
||||
{site_title}
|
||||
@@ -6,4 +6,4 @@
|
||||
|
||||
<p>[[email:welcome.text3]]</p>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -8,4 +8,4 @@
|
||||
<a href="{reset_link}">[[email:reset.cta]]</a>
|
||||
</blockquote>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
<p>[[email:reset.notify.text2]]</p>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
<p>[[email:test.text1]]</p>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -10,4 +10,4 @@
|
||||
<a href="{confirm_link}">[[email:welcome.cta]]</a>
|
||||
</blockquote>
|
||||
|
||||
{footer}
|
||||
<!-- IMPORT emails/partials/footer.tpl -->
|
||||
@@ -128,7 +128,9 @@ widgets.setArea = function(area, callback) {
|
||||
|
||||
widgets.reset = function(callback) {
|
||||
var defaultAreas = [
|
||||
{ name: 'Draft Zone', template: 'global', location: 'drafts' }
|
||||
{ name: 'Draft Zone', template: 'global', location: 'header' },
|
||||
{ name: 'Draft Zone', template: 'global', location: 'footer' },
|
||||
{ name: 'Draft Zone', template: 'global', location: 'sidebar' }
|
||||
];
|
||||
|
||||
plugins.fireHook('filter:widgets.getAreas', defaultAreas, function(err, areas) {
|
||||
|
||||
Reference in New Issue
Block a user