Files
NodeBB/src/controllers/helpers.js

347 lines
9.4 KiB
JavaScript
Raw Normal View History

2014-11-15 23:22:57 -05:00
'use strict';
2019-08-19 23:17:43 -04:00
const nconf = require('nconf');
const validator = require('validator');
const querystring = require('querystring');
const _ = require('lodash');
2019-08-19 23:17:43 -04:00
const user = require('../user');
const privileges = require('../privileges');
const categories = require('../categories');
const plugins = require('../plugins');
const meta = require('../meta');
const middleware = require('../middleware');
const helpers = module.exports;
2014-11-15 23:22:57 -05:00
helpers.noScriptErrors = async function (req, res, error, httpStatus) {
Squashed commit of the following: commit 9c86d9b2904e14927cd7e9679b92aec0951d1063 Merge: ebfa63a 5a7f811 Author: Julian Lam <julian@nodebb.org> Date: Thu Jul 20 08:41:39 2017 -0400 Merge branch 'noscript-login' of https://github.com/An-dz/NodeBB into noscript commit 5a7f81185e8f9bd7d2d011c3d495988be7e437a3 Author: André Zanghelini <an_dz@simutrans-forum> Date: Mon Jul 17 23:07:14 2017 -0300 Rename clashing variable 'next' commit ebfa63a984073a58c17aa408c363cdb03ef89985 Merge: c1801cd f159d0d Author: Julian Lam <julian@nodebb.org> Date: Mon Jul 17 16:30:40 2017 -0400 Merge branch 'noscript-logout' of https://github.com/An-dz/NodeBB into noscript commit c1801cda14e6363491e30b659902e2ae71f7e1f7 Merge: 7a5f9f3 9fd542d Author: Julian Lam <julian@nodebb.org> Date: Mon Jul 17 16:30:31 2017 -0400 Merge branch 'noscript-register' of https://github.com/An-dz/NodeBB into noscript commit 7a5f9f35abc834bb72ddddc9ca07d34f2fde8353 Merge: 44851f9 d37b95c Author: Julian Lam <julian@nodebb.org> Date: Mon Jul 17 16:30:10 2017 -0400 Merge branch 'noscript-compose' of https://github.com/An-dz/NodeBB into noscript commit f159d0d9ef1b7f600e830a96fdb4b9c87c79bb4a Author: André Zanghelini <an_dz@simutrans-forum> Date: Thu Jul 6 12:16:38 2017 -0300 Prevent form submit Required for theme change commit d37b95cb71d32d4483190609798e244c331db165 Author: André Zanghelini <an_dz@simutrans-forum> Date: Thu Jul 6 01:49:52 2017 -0300 Prevent link action with scripts Required for the theme change that changes the buttons to `a` tags. commit 9fd542d8970b7d1a4126f4edc4b44eab7d708fb0 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 19:57:56 2017 -0300 Fix tests commit cdad5bf8c2891ad76f7441fd4d8a74b058a14e6d Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 19:09:17 2017 -0300 Update error handling commit 4ff11cd136a4fb98483f837e2cebc741380dfe76 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 17:29:08 2017 -0300 Remove async waterfall commit df01d44e821a70c984b89e9585a325c3e02c6e37 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 16:59:43 2017 -0300 Set noscript compose as noscript at start commit 4bcc380da72239b8315cc849a77a3036e06e4a12 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 16:59:12 2017 -0300 Remove last useless next commit b5eac6fea11e209934c0648a7e75ad07a2167123 Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 18:35:08 2017 -0300 Last function requires no next commit 20a5cce6e6e32a454c304c448383707ec44c75a8 Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 18:06:58 2017 -0300 Remove more useless next calls commit 85ee22a79bcbbb1995106f43d4c74d6ba9206cab Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 17:46:07 2017 -0300 Remove useless next calls commit 7d984c47ad24faac1fe537dee4a5a7d697e8634c Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 15:45:31 2017 -0300 Support old themes commit 4a09dfbd08253115c342a9e829c4e6940cecb8cc Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 15:37:23 2017 -0300 Moved all error handling into helpers function commit 391aa6e67ef9ab67304005e14ac0633cdb630713 Author: André Zanghelini <an_dz@simutrans-forum> Date: Thu Jun 8 15:37:37 2017 -0300 ESLint - Fix mixed conditionals commit 80ccc6fd581d791f31e7ab62de8de611837bfc3c Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 18:08:15 2017 -0300 Compose without scripts commit 2aca811256721238ca0cede4954213d369009885 Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 18:00:44 2017 -0300 Register without scripts commit 097bb51577fb26f8e22f86dc274cb670ab606a8a Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 16:42:15 2017 -0300 Logout without scripts commit d497e08109891079656fee1c145043a9c0e55f2e Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 16:27:10 2017 -0300 Login without script
2017-07-20 08:51:04 -04:00
if (req.body.noscript !== 'true') {
return res.status(httpStatus).send(error);
}
2019-08-19 23:17:43 -04:00
const httpStatusString = httpStatus.toString();
await middleware.buildHeaderAsync(req, res);
res.status(httpStatus).render(httpStatusString, {
path: req.path,
loggedIn: req.loggedIn,
error: error,
returnLink: true,
title: '[[global:' + httpStatusString + '.title]]',
Squashed commit of the following: commit 9c86d9b2904e14927cd7e9679b92aec0951d1063 Merge: ebfa63a 5a7f811 Author: Julian Lam <julian@nodebb.org> Date: Thu Jul 20 08:41:39 2017 -0400 Merge branch 'noscript-login' of https://github.com/An-dz/NodeBB into noscript commit 5a7f81185e8f9bd7d2d011c3d495988be7e437a3 Author: André Zanghelini <an_dz@simutrans-forum> Date: Mon Jul 17 23:07:14 2017 -0300 Rename clashing variable 'next' commit ebfa63a984073a58c17aa408c363cdb03ef89985 Merge: c1801cd f159d0d Author: Julian Lam <julian@nodebb.org> Date: Mon Jul 17 16:30:40 2017 -0400 Merge branch 'noscript-logout' of https://github.com/An-dz/NodeBB into noscript commit c1801cda14e6363491e30b659902e2ae71f7e1f7 Merge: 7a5f9f3 9fd542d Author: Julian Lam <julian@nodebb.org> Date: Mon Jul 17 16:30:31 2017 -0400 Merge branch 'noscript-register' of https://github.com/An-dz/NodeBB into noscript commit 7a5f9f35abc834bb72ddddc9ca07d34f2fde8353 Merge: 44851f9 d37b95c Author: Julian Lam <julian@nodebb.org> Date: Mon Jul 17 16:30:10 2017 -0400 Merge branch 'noscript-compose' of https://github.com/An-dz/NodeBB into noscript commit f159d0d9ef1b7f600e830a96fdb4b9c87c79bb4a Author: André Zanghelini <an_dz@simutrans-forum> Date: Thu Jul 6 12:16:38 2017 -0300 Prevent form submit Required for theme change commit d37b95cb71d32d4483190609798e244c331db165 Author: André Zanghelini <an_dz@simutrans-forum> Date: Thu Jul 6 01:49:52 2017 -0300 Prevent link action with scripts Required for the theme change that changes the buttons to `a` tags. commit 9fd542d8970b7d1a4126f4edc4b44eab7d708fb0 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 19:57:56 2017 -0300 Fix tests commit cdad5bf8c2891ad76f7441fd4d8a74b058a14e6d Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 19:09:17 2017 -0300 Update error handling commit 4ff11cd136a4fb98483f837e2cebc741380dfe76 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 17:29:08 2017 -0300 Remove async waterfall commit df01d44e821a70c984b89e9585a325c3e02c6e37 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 16:59:43 2017 -0300 Set noscript compose as noscript at start commit 4bcc380da72239b8315cc849a77a3036e06e4a12 Author: André Zanghelini <an_dz@simutrans-forum> Date: Wed Jul 5 16:59:12 2017 -0300 Remove last useless next commit b5eac6fea11e209934c0648a7e75ad07a2167123 Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 18:35:08 2017 -0300 Last function requires no next commit 20a5cce6e6e32a454c304c448383707ec44c75a8 Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 18:06:58 2017 -0300 Remove more useless next calls commit 85ee22a79bcbbb1995106f43d4c74d6ba9206cab Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 17:46:07 2017 -0300 Remove useless next calls commit 7d984c47ad24faac1fe537dee4a5a7d697e8634c Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 15:45:31 2017 -0300 Support old themes commit 4a09dfbd08253115c342a9e829c4e6940cecb8cc Author: André Zanghelini <an_dz@simutrans-forum> Date: Sun Jul 2 15:37:23 2017 -0300 Moved all error handling into helpers function commit 391aa6e67ef9ab67304005e14ac0633cdb630713 Author: André Zanghelini <an_dz@simutrans-forum> Date: Thu Jun 8 15:37:37 2017 -0300 ESLint - Fix mixed conditionals commit 80ccc6fd581d791f31e7ab62de8de611837bfc3c Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 18:08:15 2017 -0300 Compose without scripts commit 2aca811256721238ca0cede4954213d369009885 Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 18:00:44 2017 -0300 Register without scripts commit 097bb51577fb26f8e22f86dc274cb670ab606a8a Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 16:42:15 2017 -0300 Logout without scripts commit d497e08109891079656fee1c145043a9c0e55f2e Author: André Zanghelini <an_dz@simutrans-forum> Date: Sat Jun 3 16:27:10 2017 -0300 Login without script
2017-07-20 08:51:04 -04:00
});
};
2017-10-19 13:53:05 -04:00
helpers.validFilters = { '': true, new: true, watched: true, unreplied: true };
2018-06-18 14:37:32 -04:00
helpers.terms = {
daily: 'day',
weekly: 'week',
monthly: 'month',
};
helpers.buildQueryString = function (cid, filter, term) {
2019-08-19 23:17:43 -04:00
const qs = {};
2018-06-18 14:37:32 -04:00
if (cid) {
qs.cid = cid;
}
if (filter) {
qs.filter = filter;
}
if (term) {
qs.term = term;
}
2019-08-19 23:17:43 -04:00
return Object.keys(qs).length ? '?' + querystring.stringify(qs) : '';
2018-06-18 14:37:32 -04:00
};
helpers.addLinkTags = function (params) {
params.res.locals.linkTags = params.res.locals.linkTags || [];
params.res.locals.linkTags.push({
rel: 'canonical',
href: nconf.get('url') + '/' + params.url,
});
params.tags.forEach(function (rel) {
rel.href = nconf.get('url') + '/' + params.url + rel.href;
params.res.locals.linkTags.push(rel);
});
};
2018-06-18 14:37:32 -04:00
helpers.buildFilters = function (url, filter, query) {
2017-10-19 13:53:05 -04:00
return [{
name: '[[unread:all-topics]]',
2018-06-18 14:37:32 -04:00
url: url + helpers.buildQueryString(query.cid, '', query.term),
2017-10-19 13:53:05 -04:00
selected: filter === '',
filter: '',
icon: 'fa-book',
2017-10-19 13:53:05 -04:00
}, {
name: '[[unread:new-topics]]',
2018-06-18 14:37:32 -04:00
url: url + helpers.buildQueryString(query.cid, 'new', query.term),
2017-10-19 13:53:05 -04:00
selected: filter === 'new',
filter: 'new',
icon: 'fa-clock-o',
2017-10-19 13:53:05 -04:00
}, {
name: '[[unread:watched-topics]]',
2018-06-18 14:37:32 -04:00
url: url + helpers.buildQueryString(query.cid, 'watched', query.term),
2017-10-19 13:53:05 -04:00
selected: filter === 'watched',
filter: 'watched',
icon: 'fa-bell-o',
2017-10-19 13:53:05 -04:00
}, {
name: '[[unread:unreplied-topics]]',
2018-06-18 14:37:32 -04:00
url: url + helpers.buildQueryString(query.cid, 'unreplied', query.term),
2017-10-19 13:53:05 -04:00
selected: filter === 'unreplied',
filter: 'unreplied',
icon: 'fa-reply',
2017-10-19 13:53:05 -04:00
}];
};
2018-06-18 14:37:32 -04:00
helpers.buildTerms = function (url, term, query) {
return [{
name: '[[recent:alltime]]',
url: url + helpers.buildQueryString(query.cid, query.filter, ''),
selected: term === 'alltime',
term: 'alltime',
}, {
name: '[[recent:day]]',
url: url + helpers.buildQueryString(query.cid, query.filter, 'daily'),
selected: term === 'day',
term: 'day',
}, {
name: '[[recent:week]]',
url: url + helpers.buildQueryString(query.cid, query.filter, 'weekly'),
selected: term === 'week',
term: 'week',
}, {
name: '[[recent:month]]',
url: url + helpers.buildQueryString(query.cid, query.filter, 'monthly'),
selected: term === 'month',
term: 'month',
}];
};
helpers.notAllowed = async function (req, res, error) {
const data = await plugins.fireHook('filter:helpers.notAllowed', {
2016-06-22 14:58:46 -04:00
req: req,
res: res,
2017-02-17 19:31:21 -07:00
error: error,
});
if (req.loggedIn || req.uid === -1) {
if (res.locals.isAPI) {
res.status(403).json({
path: req.path.replace(/^\/api/, ''),
loggedIn: req.loggedIn,
error: data.error,
title: '[[global:403.title]]',
});
2014-11-15 23:22:57 -05:00
} else {
await middleware.buildHeaderAsync(req, res);
res.status(403).render('403', {
path: req.path,
loggedIn: req.loggedIn,
error: data.error,
title: '[[global:403.title]]',
});
2014-11-15 23:22:57 -05:00
}
} else if (res.locals.isAPI) {
req.session.returnTo = req.url.replace(/^\/api/, '');
res.status(401).json('not-authorized');
} else {
req.session.returnTo = req.url;
res.redirect(nconf.get('relative_path') + '/login');
}
2014-11-15 23:22:57 -05:00
};
helpers.redirect = function (res, url) {
2015-03-09 18:22:44 -04:00
if (res.locals.isAPI) {
2017-07-05 11:36:35 -04:00
res.set('X-Redirect', encodeURI(url)).status(200).json(url);
2015-03-09 18:22:44 -04:00
} else {
2016-03-20 15:11:32 -05:00
res.redirect(nconf.get('relative_path') + encodeURI(url));
2015-03-09 18:22:44 -04:00
}
};
2019-08-19 23:17:43 -04:00
helpers.buildCategoryBreadcrumbs = async function (cid) {
const breadcrumbs = [];
2019-08-19 23:17:43 -04:00
while (parseInt(cid, 10)) {
/* eslint-disable no-await-in-loop */
const data = await categories.getCategoryFields(cid, ['name', 'slug', 'parentCid', 'disabled', 'isSection']);
if (!data.disabled && !data.isSection) {
2016-04-21 11:40:40 -04:00
breadcrumbs.unshift({
2019-08-19 23:17:43 -04:00
text: String(data.name),
url: nconf.get('relative_path') + '/category/' + data.slug,
2020-03-26 12:04:04 -04:00
cid: cid,
2016-04-21 11:40:40 -04:00
});
}
2019-08-19 23:17:43 -04:00
cid = data.parentCid;
}
if (meta.config.homePageRoute && meta.config.homePageRoute !== 'categories') {
2015-01-29 01:06:48 -05:00
breadcrumbs.unshift({
2019-08-19 23:17:43 -04:00
text: '[[global:header.categories]]',
url: nconf.get('relative_path') + '/categories',
});
2019-08-19 23:17:43 -04:00
}
2015-01-29 01:06:48 -05:00
2019-08-19 23:17:43 -04:00
breadcrumbs.unshift({
text: '[[global:home]]',
url: nconf.get('relative_path') + '/',
2015-01-29 01:06:48 -05:00
});
2019-08-19 23:17:43 -04:00
return breadcrumbs;
2015-01-29 01:06:48 -05:00
};
helpers.buildBreadcrumbs = function (crumbs) {
2019-08-19 23:17:43 -04:00
const breadcrumbs = [
2015-01-29 01:06:48 -05:00
{
text: '[[global:home]]',
2017-02-17 19:31:21 -07:00
url: nconf.get('relative_path') + '/',
},
2015-01-29 01:06:48 -05:00
];
crumbs.forEach(function (crumb) {
2015-01-29 01:06:48 -05:00
if (crumb) {
if (crumb.url) {
crumb.url = nconf.get('relative_path') + crumb.url;
}
breadcrumbs.push(crumb);
}
});
2015-01-29 01:06:48 -05:00
return breadcrumbs;
};
2014-11-15 23:22:57 -05:00
helpers.buildTitle = function (pageTitle) {
2019-08-19 23:17:43 -04:00
const titleLayout = meta.config.titleLayout || '{pageTitle} | {browserTitle}';
2015-09-23 01:59:13 -04:00
2019-08-19 23:17:43 -04:00
const browserTitle = validator.escape(String(meta.config.browserTitle || meta.config.title || 'NodeBB'));
2015-09-23 01:59:13 -04:00
pageTitle = pageTitle || '';
2019-08-19 23:17:43 -04:00
const title = titleLayout.replace('{pageTitle}', () => pageTitle).replace('{browserTitle}', () => browserTitle);
2015-09-23 01:59:13 -04:00
return title;
};
2019-08-19 23:17:43 -04:00
helpers.getCategories = async function (set, uid, privilege, selectedCid) {
const cids = await categories.getCidsByPrivilege(set, uid, privilege);
return await getCategoryData(cids, uid, selectedCid);
2018-08-17 16:39:50 -04:00
};
2019-08-19 23:17:43 -04:00
helpers.getCategoriesByStates = async function (uid, selectedCid, states) {
const cids = await categories.getAllCidsFromSet('categories:cid');
return await getCategoryData(cids, uid, selectedCid, states);
2018-08-17 16:39:50 -04:00
};
async function getCategoryData(cids, uid, selectedCid, states) {
2018-08-17 16:39:50 -04:00
if (selectedCid && !Array.isArray(selectedCid)) {
selectedCid = [selectedCid];
}
selectedCid = selectedCid && selectedCid.map(String);
states = states || [categories.watchStates.watching, categories.watchStates.notwatching];
const [allowed, watchState, categoryData, isAdmin] = await Promise.all([
privileges.categories.isUserAllowedTo('topics:read', cids, uid),
categories.getWatchState(cids, uid),
categories.getCategoriesData(cids),
user.isAdministrator(uid),
]);
2019-08-19 23:17:43 -04:00
2019-09-20 19:04:47 -04:00
categories.getTree(categoryData);
const cidToAllowed = _.zipObject(cids, allowed.map(allowed => isAdmin || allowed));
const cidToCategory = _.zipObject(cids, categoryData);
const cidToWatchState = _.zipObject(cids, watchState);
const visibleCategories = categoryData.filter(function (c) {
2019-10-30 12:47:01 -04:00
const hasVisibleChildren = checkVisibleChildren(c, cidToAllowed, cidToWatchState, states);
const isCategoryVisible = c && cidToAllowed[c.cid] && !c.link && !c.disabled && states.includes(cidToWatchState[c.cid]);
const shouldBeRemoved = !hasVisibleChildren && !isCategoryVisible;
if (shouldBeRemoved && c && c.parent && c.parent.cid && cidToCategory[c.parent.cid]) {
cidToCategory[c.parent.cid].children = cidToCategory[c.parent.cid].children.filter(child => child.cid !== c.cid);
}
return c && !shouldBeRemoved;
});
const categoriesData = categories.buildForSelectCategories(visibleCategories);
2019-09-20 19:04:47 -04:00
2019-08-19 23:17:43 -04:00
let selectedCategory = [];
const selectedCids = [];
2019-09-20 19:04:47 -04:00
categoriesData.forEach(function (category) {
2019-08-19 23:17:43 -04:00
category.selected = selectedCid ? selectedCid.includes(String(category.cid)) : false;
if (category.selected) {
selectedCategory.push(category);
selectedCids.push(category.cid);
}
});
selectedCids.sort((a, b) => a - b);
if (selectedCategory.length > 1) {
selectedCategory = {
icon: 'fa-plus',
name: '[[unread:multiple-categories-selected]]',
bgColor: '#ddd',
};
} else if (selectedCategory.length === 1) {
selectedCategory = selectedCategory[0];
} else {
selectedCategory = undefined;
}
2016-11-03 13:06:21 +03:00
return {
2019-09-20 19:04:47 -04:00
categories: categoriesData,
selectedCategory: selectedCategory,
selectedCids: selectedCids,
};
2016-11-03 13:06:21 +03:00
}
2019-10-30 12:47:01 -04:00
function checkVisibleChildren(c, cidToAllowed, cidToWatchState, states) {
if (!c || !Array.isArray(c.children)) {
return false;
}
return c.children.some(c => c && !c.disabled && (
2019-10-30 12:47:01 -04:00
(cidToAllowed[c.cid] && states.includes(cidToWatchState[c.cid])) || checkVisibleChildren(c, cidToAllowed, cidToWatchState, states)
));
}
helpers.getHomePageRoutes = async function (uid) {
let cids = await categories.getAllCidsFromSet('categories:cid');
cids = await privileges.categories.filterCids('find', cids, uid);
const categoryData = await categories.getCategoriesFields(cids, ['name', 'slug']);
const categoryRoutes = categoryData.map(function (category) {
return {
route: 'category/' + category.slug,
name: 'Category: ' + category.name,
};
});
const routes = [
{
route: 'categories',
name: 'Categories',
},
{
route: 'unread',
name: 'Unread',
},
{
route: 'recent',
name: 'Recent',
},
{
route: 'top',
name: 'Top',
},
{
route: 'popular',
name: 'Popular',
},
].concat(categoryRoutes, [
{
route: 'custom',
name: 'Custom',
},
]);
const data = await plugins.fireHook('filter:homepage.get', { routes: routes });
return data.routes;
};
2019-08-19 23:17:43 -04:00
require('../promisify')(helpers);