diff --git a/src/middleware/admin.js b/src/middleware/admin.js index ed49408be8..3a49f2d729 100644 --- a/src/middleware/admin.js +++ b/src/middleware/admin.js @@ -1,16 +1,12 @@ 'use strict'; -const winston = require('winston'); -const jsesc = require('jsesc'); + const nconf = require('nconf'); -const semver = require('semver'); const user = require('../user'); const meta = require('../meta'); const plugins = require('../plugins'); const privileges = require('../privileges'); -const utils = require('../utils'); -const versions = require('../admin/versions'); const helpers = require('./helpers'); const controllers = { @@ -30,90 +26,6 @@ middleware.buildHeader = helpers.try(async (req, res, next) => { next(); }); -middleware.renderHeader = async (req, res, data) => { - const custom_header = { - plugins: [], - authentication: [], - }; - res.locals.config = res.locals.config || {}; - - const results = await utils.promiseParallel({ - userData: user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed']), - scripts: getAdminScripts(), - custom_header: plugins.hooks.fire('filter:admin.header.build', custom_header), - configs: meta.configs.list(), - latestVersion: getLatestVersion(), - privileges: privileges.admin.get(req.uid), - tags: meta.tags.parse(req, {}, [], []), - }); - - const { userData } = results; - userData.uid = req.uid; - userData['email:confirmed'] = userData['email:confirmed'] === 1; - userData.privileges = results.privileges; - - let acpPath = req.path.slice(1).split('/'); - acpPath.forEach((path, i) => { - acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); - }); - acpPath = acpPath.join(' > '); - - const version = nconf.get('version'); - - res.locals.config.userLang = res.locals.config.acpLang || res.locals.config.userLang; - let templateValues = { - config: res.locals.config, - configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }), - relative_path: res.locals.config.relative_path, - adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), - metaTags: results.tags.meta, - linkTags: results.tags.link, - user: userData, - userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }), - plugins: results.custom_header.plugins, - authentication: results.custom_header.authentication, - scripts: results.scripts, - 'cache-buster': meta.config['cache-buster'] || '', - env: !!process.env.NODE_ENV, - title: `${acpPath || 'Dashboard'} | NodeBB Admin Control Panel`, - bodyClass: data.bodyClass, - version: version, - latestVersion: results.latestVersion, - upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version), - showManageMenu: results.privileges.superadmin || ['categories', 'privileges', 'users', 'admins-mods', 'groups', 'tags', 'settings'].some(priv => results.privileges[`admin:${priv}`]), - }; - - templateValues.template = { name: res.locals.template }; - templateValues.template[res.locals.template] = true; - ({ templateData: templateValues } = await plugins.hooks.fire('filter:middleware.renderAdminHeader', { - req, - res, - templateData: templateValues, - data, - })); - - return await req.app.renderAsync('admin/header', templateValues); -}; - -async function getAdminScripts() { - const scripts = await plugins.hooks.fire('filter:admin.scripts.get', []); - return scripts.map(script => ({ src: script })); -} - -async function getLatestVersion() { - try { - const result = await versions.getLatestVersion(); - return result; - } catch (err) { - winston.error(`[acp] Failed to fetch latest version${err.stack}`); - } - return null; -} - -middleware.renderFooter = async function (req, res, data) { - return await req.app.renderAsync('admin/footer', data); -}; - middleware.checkPrivileges = helpers.try(async (req, res, next) => { // Kick out guests, obviously if (req.uid <= 0) { diff --git a/src/middleware/header.js b/src/middleware/header.js index 8da7f157a0..d8645d6471 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -1,33 +1,17 @@ 'use strict'; -const nconf = require('nconf'); -const jsesc = require('jsesc'); -const _ = require('lodash'); -const validator = require('validator'); const util = require('util'); const user = require('../user'); -const topics = require('../topics'); -const messaging = require('../messaging'); -const flags = require('../flags'); -const meta = require('../meta'); const plugins = require('../plugins'); -const navigation = require('../navigation'); -const translator = require('../translator'); -const privileges = require('../privileges'); -const languages = require('../languages'); -const utils = require('../utils'); const helpers = require('./helpers'); const controllers = { api: require('../controllers/api'), - helpers: require('../controllers/helpers'), }; const middleware = module.exports; -const relative_path = nconf.get('relative_path'); - middleware.buildHeader = helpers.try(async (req, res, next) => { res.locals.renderHeader = true; res.locals.isAPI = false; @@ -52,215 +36,3 @@ middleware.buildHeader = helpers.try(async (req, res, next) => { }); middleware.buildHeaderAsync = util.promisify(middleware.buildHeader); - -middleware.renderHeader = async function renderHeader(req, res, data) { - const registrationType = meta.config.registrationType || 'normal'; - res.locals.config = res.locals.config || {}; - const templateValues = { - title: meta.config.title || '', - 'title:url': meta.config['title:url'] || '', - description: meta.config.description || '', - 'cache-buster': meta.config['cache-buster'] || '', - 'brand:logo': meta.config['brand:logo'] || '', - 'brand:logo:url': meta.config['brand:logo:url'] || '', - 'brand:logo:alt': meta.config['brand:logo:alt'] || '', - 'brand:logo:display': meta.config['brand:logo'] ? '' : 'hide', - allowRegistration: registrationType === 'normal', - searchEnabled: plugins.hooks.hasListeners('filter:search.query'), - postQueueEnabled: !!meta.config.postQueue, - config: res.locals.config, - relative_path, - bodyClass: data.bodyClass, - }; - - templateValues.configJSON = jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }); - - const results = await utils.promiseParallel({ - isAdmin: user.isAdministrator(req.uid), - isGlobalMod: user.isGlobalModerator(req.uid), - isModerator: user.isModeratorOfAnyCategory(req.uid), - privileges: privileges.global.get(req.uid), - user: user.getUserData(req.uid), - isEmailConfirmSent: req.uid <= 0 ? false : await user.email.isValidationPending(req.uid), - languageDirection: translator.translate('[[language:dir]]', res.locals.config.userLang), - timeagoCode: languages.userTimeagoCode(res.locals.config.userLang), - browserTitle: translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title))), - navigation: navigation.get(req.uid), - }); - - const unreadData = { - '': {}, - new: {}, - watched: {}, - unreplied: {}, - }; - - results.user.unreadData = unreadData; - results.user.isAdmin = results.isAdmin; - results.user.isGlobalMod = results.isGlobalMod; - results.user.isMod = !!results.isModerator; - results.user.privileges = results.privileges; - results.user.timeagoCode = results.timeagoCode; - results.user[results.user.status] = true; - - results.user.email = String(results.user.email); - results.user['email:confirmed'] = results.user['email:confirmed'] === 1; - results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; - - templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; - templateValues.browserTitle = results.browserTitle; - ({ - navigation: templateValues.navigation, - unreadCount: templateValues.unreadCount, - } = await appendUnreadCounts({ - uid: req.uid, - query: req.query, - navigation: results.navigation, - unreadData, - })); - templateValues.isAdmin = results.user.isAdmin; - templateValues.isGlobalMod = results.user.isGlobalMod; - templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod; - templateValues.canChat = results.privileges.chat && meta.config.disableChat !== 1; - templateValues.user = results.user; - templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true }); - templateValues.useCustomCSS = meta.config.useCustomCSS && meta.config.customCSS; - templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : ''; - templateValues.useCustomHTML = meta.config.useCustomHTML; - templateValues.customHTML = templateValues.useCustomHTML ? meta.config.customHTML : ''; - templateValues.maintenanceHeader = meta.config.maintenanceMode && !results.isAdmin; - templateValues.defaultLang = meta.config.defaultLang || 'en-GB'; - templateValues.userLang = res.locals.config.userLang; - templateValues.languageDirection = results.languageDirection; - if (req.query.noScriptMessage) { - templateValues.noScriptMessage = validator.escape(String(req.query.noScriptMessage)); - } - - templateValues.template = { name: res.locals.template }; - templateValues.template[res.locals.template] = true; - - if (data.hasOwnProperty('_header')) { - templateValues.metaTags = data._header.tags.meta; - templateValues.linkTags = data._header.tags.link; - } - - if (req.route && req.route.path === '/') { - modifyTitle(templateValues); - } - - const hookReturn = await plugins.hooks.fire('filter:middleware.renderHeader', { - req: req, - res: res, - templateValues: templateValues, // TODO: deprecate - templateData: templateValues, - data: data, - }); - - return await req.app.renderAsync('header', hookReturn.templateValues); -}; - -async function appendUnreadCounts({ uid, navigation, unreadData, query }) { - const originalRoutes = navigation.map(nav => nav.originalRoute); - const calls = { - unreadData: topics.getUnreadData({ uid: uid, query: query }), - unreadChatCount: messaging.getUnreadCount(uid), - unreadNotificationCount: user.notifications.getUnreadCount(uid), - unreadFlagCount: (async function () { - if (originalRoutes.includes('/flags') && await user.isPrivileged(uid)) { - return flags.getCount({ - uid, - query, - filters: { - quick: 'unresolved', - cid: (await user.isAdminOrGlobalMod(uid)) ? [] : (await user.getModeratedCids(uid)), - }, - }); - } - return 0; - }()), - }; - const results = await utils.promiseParallel(calls); - - const unreadCounts = results.unreadData.counts; - const unreadCount = { - topic: unreadCounts[''] || 0, - newTopic: unreadCounts.new || 0, - watchedTopic: unreadCounts.watched || 0, - unrepliedTopic: unreadCounts.unreplied || 0, - mobileUnread: 0, - unreadUrl: '/unread', - chat: results.unreadChatCount || 0, - notification: results.unreadNotificationCount || 0, - flags: results.unreadFlagCount || 0, - }; - - Object.keys(unreadCount).forEach((key) => { - if (unreadCount[key] > 99) { - unreadCount[key] = '99+'; - } - }); - - const { tidsByFilter } = results.unreadData; - navigation = navigation.map((item) => { - function modifyNavItem(item, route, filter, content) { - if (item && item.originalRoute === route) { - unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); - item.content = content; - unreadCount.mobileUnread = content; - unreadCount.unreadUrl = route; - if (unreadCounts[filter] > 0) { - item.iconClass += ' unread-count'; - } - } - } - modifyNavItem(item, '/unread', '', unreadCount.topic); - modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); - modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); - modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); - - ['flags'].forEach((prop) => { - if (item && item.originalRoute === `/${prop}` && unreadCount[prop] > 0) { - item.iconClass += ' unread-count'; - item.content = unreadCount.flags; - } - }); - - return item; - }); - - return { navigation, unreadCount }; -} - -middleware.renderFooter = async function renderFooter(req, res, templateValues) { - const data = await plugins.hooks.fire('filter:middleware.renderFooter', { - req: req, - res: res, - templateValues: templateValues, // TODO: deprecate - templateData: templateValues, - }); - - const scripts = await plugins.hooks.fire('filter:scripts.get', []); - - data.templateValues.scripts = scripts.map(script => ({ src: script })); - - data.templateValues.useCustomJS = meta.config.useCustomJS; - data.templateValues.customJS = data.templateValues.useCustomJS ? meta.config.customJS : ''; - data.templateValues.isSpider = req.uid === -1; - - return await req.app.renderAsync('footer', data.templateValues); -}; - -function modifyTitle(obj) { - const title = controllers.helpers.buildTitle(meta.config.homePageTitle || '[[pages:home]]'); - obj.browserTitle = title; - - if (obj.metaTags) { - obj.metaTags.forEach((tag, i) => { - if (tag.property === 'og:title') { - obj.metaTags[i].content = title; - } - }); - } - - return title; -} diff --git a/src/middleware/render.js b/src/middleware/render.js index 8e76d19f10..e3cf642996 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -1,15 +1,27 @@ 'use strict'; +const _ = require('lodash'); const nconf = require('nconf'); const validator = require('validator'); +const jsesc = require('jsesc'); +const winston = require('winston'); +const semver = require('semver'); - -const plugins = require('../plugins'); -const meta = require('../meta'); +const navigation = require('../navigation'); const translator = require('../translator'); +const privileges = require('../privileges'); +const languages = require('../languages'); +const plugins = require('../plugins'); +const user = require('../user'); +const topics = require('../topics'); +const messaging = require('../messaging'); +const flags = require('../flags'); +const meta = require('../meta'); const widgets = require('../widgets'); const utils = require('../utils'); const helpers = require('./helpers'); +const versions = require('../admin/versions'); +const controllersHelpers = require('../controllers/helpers'); const relative_path = nconf.get('relative_path'); @@ -70,10 +82,11 @@ module.exports = function (middleware) { return res.json(options); } const optionsString = JSON.stringify(options).replace(/<\//g, '<\\/'); + const headerFooterData = await loadHeaderFooterData(req, res, options); const results = await utils.promiseParallel({ - header: renderHeaderFooter('renderHeader', req, res, options), + header: renderHeaderFooter('renderHeader', req, res, options, headerFooterData), content: renderContent(render, templateToRender, req, res, options), - footer: renderHeaderFooter('renderFooter', req, res, options), + footer: renderHeaderFooter('renderFooter', req, res, options, headerFooterData), }); const str = `${results.header + @@ -102,6 +115,170 @@ module.exports = function (middleware) { next(); }; + async function loadHeaderFooterData(req, res, options) { + if (res.locals.renderHeader) { + return await loadClientHeaderFooterData(req, res, options); + } else if (res.locals.renderAdminHeader) { + return await loadAdminHeaderFooterData(req, res, options); + } + return null; + } + + async function loadClientHeaderFooterData(req, res, options) { + const registrationType = meta.config.registrationType || 'normal'; + res.locals.config = res.locals.config || {}; + const templateValues = { + title: meta.config.title || '', + 'title:url': meta.config['title:url'] || '', + description: meta.config.description || '', + 'cache-buster': meta.config['cache-buster'] || '', + 'brand:logo': meta.config['brand:logo'] || '', + 'brand:logo:url': meta.config['brand:logo:url'] || '', + 'brand:logo:alt': meta.config['brand:logo:alt'] || '', + 'brand:logo:display': meta.config['brand:logo'] ? '' : 'hide', + allowRegistration: registrationType === 'normal', + searchEnabled: plugins.hooks.hasListeners('filter:search.query'), + postQueueEnabled: !!meta.config.postQueue, + config: res.locals.config, + relative_path, + bodyClass: options.bodyClass, + }; + + templateValues.configJSON = jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }); + + const results = await utils.promiseParallel({ + isAdmin: user.isAdministrator(req.uid), + isGlobalMod: user.isGlobalModerator(req.uid), + isModerator: user.isModeratorOfAnyCategory(req.uid), + privileges: privileges.global.get(req.uid), + user: user.getUserData(req.uid), + isEmailConfirmSent: req.uid <= 0 ? false : await user.email.isValidationPending(req.uid), + languageDirection: translator.translate('[[language:dir]]', res.locals.config.userLang), + timeagoCode: languages.userTimeagoCode(res.locals.config.userLang), + browserTitle: translator.translate(controllersHelpers.buildTitle(translator.unescape(options.title))), + navigation: navigation.get(req.uid), + }); + + const unreadData = { + '': {}, + new: {}, + watched: {}, + unreplied: {}, + }; + + results.user.unreadData = unreadData; + results.user.isAdmin = results.isAdmin; + results.user.isGlobalMod = results.isGlobalMod; + results.user.isMod = !!results.isModerator; + results.user.privileges = results.privileges; + results.user.timeagoCode = results.timeagoCode; + results.user[results.user.status] = true; + + results.user.email = String(results.user.email); + results.user['email:confirmed'] = results.user['email:confirmed'] === 1; + results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; + + templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; + templateValues.browserTitle = results.browserTitle; + ({ + navigation: templateValues.navigation, + unreadCount: templateValues.unreadCount, + } = await appendUnreadCounts({ + uid: req.uid, + query: req.query, + navigation: results.navigation, + unreadData, + })); + templateValues.isAdmin = results.user.isAdmin; + templateValues.isGlobalMod = results.user.isGlobalMod; + templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod; + templateValues.canChat = results.privileges.chat && meta.config.disableChat !== 1; + templateValues.user = results.user; + templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true }); + templateValues.useCustomCSS = meta.config.useCustomCSS && meta.config.customCSS; + templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : ''; + templateValues.useCustomHTML = meta.config.useCustomHTML; + templateValues.customHTML = templateValues.useCustomHTML ? meta.config.customHTML : ''; + templateValues.maintenanceHeader = meta.config.maintenanceMode && !results.isAdmin; + templateValues.defaultLang = meta.config.defaultLang || 'en-GB'; + templateValues.userLang = res.locals.config.userLang; + templateValues.languageDirection = results.languageDirection; + if (req.query.noScriptMessage) { + templateValues.noScriptMessage = validator.escape(String(req.query.noScriptMessage)); + } + + templateValues.template = { name: res.locals.template }; + templateValues.template[res.locals.template] = true; + + if (options.hasOwnProperty('_header')) { + templateValues.metaTags = options._header.tags.meta; + templateValues.linkTags = options._header.tags.link; + } + + if (req.route && req.route.path === '/') { + modifyTitle(templateValues); + } + return templateValues; + } + + async function loadAdminHeaderFooterData(req, res, options) { + const custom_header = { + plugins: [], + authentication: [], + }; + res.locals.config = res.locals.config || {}; + + const results = await utils.promiseParallel({ + userData: user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed']), + scripts: getAdminScripts(), + custom_header: plugins.hooks.fire('filter:admin.header.build', custom_header), + configs: meta.configs.list(), + latestVersion: getLatestVersion(), + privileges: privileges.admin.get(req.uid), + tags: meta.tags.parse(req, {}, [], []), + }); + + const { userData } = results; + userData.uid = req.uid; + userData['email:confirmed'] = userData['email:confirmed'] === 1; + userData.privileges = results.privileges; + + let acpPath = req.path.slice(1).split('/'); + acpPath.forEach((path, i) => { + acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); + }); + acpPath = acpPath.join(' > '); + + const version = nconf.get('version'); + + res.locals.config.userLang = res.locals.config.acpLang || res.locals.config.userLang; + const templateValues = { + config: res.locals.config, + configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }), + relative_path: res.locals.config.relative_path, + adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), + metaTags: results.tags.meta, + linkTags: results.tags.link, + user: userData, + userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }), + plugins: results.custom_header.plugins, + authentication: results.custom_header.authentication, + scripts: results.scripts, + 'cache-buster': meta.config['cache-buster'] || '', + env: !!process.env.NODE_ENV, + title: `${acpPath || 'Dashboard'} | NodeBB Admin Control Panel`, + bodyClass: options.bodyClass, + version: version, + latestVersion: results.latestVersion, + upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version), + showManageMenu: results.privileges.superadmin || ['categories', 'privileges', 'users', 'admins-mods', 'groups', 'tags', 'settings'].some(priv => results.privileges[`admin:${priv}`]), + }; + + templateValues.template = { name: res.locals.template }; + templateValues.template[res.locals.template] = true; + return templateValues; + } + function renderContent(render, tpl, req, res, options) { return new Promise((resolve, reject) => { render.call(res, tpl, options, async (err, str) => { @@ -111,14 +288,76 @@ module.exports = function (middleware) { }); } - async function renderHeaderFooter(method, req, res, options) { + async function renderHeader(req, res, options, headerFooterData) { + const hookReturn = await plugins.hooks.fire('filter:middleware.renderHeader', { + req: req, + res: res, + templateValues: headerFooterData, // TODO: deprecate + templateData: headerFooterData, + data: options, + }); + + return await req.app.renderAsync('header', hookReturn.templateData); + } + + async function renderFooter(req, res, options, headerFooterData) { + const hookReturn = await plugins.hooks.fire('filter:middleware.renderFooter', { + req, + res, + templateValues: headerFooterData, // TODO: deprecate + templateData: headerFooterData, + data: options, + }); + + const scripts = await plugins.hooks.fire('filter:scripts.get', []); + + hookReturn.templateData.scripts = scripts.map(script => ({ src: script })); + + hookReturn.templateData.useCustomJS = meta.config.useCustomJS; + hookReturn.templateData.customJS = hookReturn.templateData.useCustomJS ? meta.config.customJS : ''; + hookReturn.templateData.isSpider = req.uid === -1; + + return await req.app.renderAsync('footer', hookReturn.templateData); + } + + async function renderAdminHeader(req, res, options, headerFooterData) { + const hookReturn = await plugins.hooks.fire('filter:middleware.renderAdminHeader', { + req, + res, + templateValues: headerFooterData, // TODO: deprecate + templateData: headerFooterData, + data: options, + }); + + return await req.app.renderAsync('admin/header', hookReturn.templateData); + } + + async function renderAdminFooter(req, res, options, headerFooterData) { + const hookReturn = await plugins.hooks.fire('filter:middleware.renderAdminFooter', { + req, + res, + templateValues: headerFooterData, // TODO: deprecate + templateData: headerFooterData, + data: options, + }); + + return await req.app.renderAsync('admin/footer', hookReturn.templateData); + } + + async function renderHeaderFooter(method, req, res, options, headerFooterData) { let str = ''; if (res.locals.renderHeader) { - str = await middleware[method](req, res, options); + if (method === 'renderHeader') { + str = await renderHeader(req, res, options, headerFooterData); + } else if (method === 'renderFooter') { + str = await renderFooter(req, res, options, headerFooterData); + } } else if (res.locals.renderAdminHeader) { - str = await middleware.admin[method](req, res, options); - } else { - str = ''; + if (method === 'renderHeader') { + str = await renderAdminHeader(req, res, options, headerFooterData); + } else if (method === 'renderFooter') { + str = await renderAdminFooter(req, res, options, headerFooterData); + } } return await translate(str, getLang(req, res)); } @@ -135,4 +374,106 @@ module.exports = function (middleware) { const translated = await translator.translate(str, language); return translator.unescape(translated); } + + async function appendUnreadCounts({ uid, navigation, unreadData, query }) { + const originalRoutes = navigation.map(nav => nav.originalRoute); + const calls = { + unreadData: topics.getUnreadData({ uid: uid, query: query }), + unreadChatCount: messaging.getUnreadCount(uid), + unreadNotificationCount: user.notifications.getUnreadCount(uid), + unreadFlagCount: (async function () { + if (originalRoutes.includes('/flags') && await user.isPrivileged(uid)) { + return flags.getCount({ + uid, + query, + filters: { + quick: 'unresolved', + cid: (await user.isAdminOrGlobalMod(uid)) ? [] : (await user.getModeratedCids(uid)), + }, + }); + } + return 0; + }()), + }; + const results = await utils.promiseParallel(calls); + + const unreadCounts = results.unreadData.counts; + const unreadCount = { + topic: unreadCounts[''] || 0, + newTopic: unreadCounts.new || 0, + watchedTopic: unreadCounts.watched || 0, + unrepliedTopic: unreadCounts.unreplied || 0, + mobileUnread: 0, + unreadUrl: '/unread', + chat: results.unreadChatCount || 0, + notification: results.unreadNotificationCount || 0, + flags: results.unreadFlagCount || 0, + }; + + Object.keys(unreadCount).forEach((key) => { + if (unreadCount[key] > 99) { + unreadCount[key] = '99+'; + } + }); + + const { tidsByFilter } = results.unreadData; + navigation = navigation.map((item) => { + function modifyNavItem(item, route, filter, content) { + if (item && item.originalRoute === route) { + unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); + item.content = content; + unreadCount.mobileUnread = content; + unreadCount.unreadUrl = route; + if (unreadCounts[filter] > 0) { + item.iconClass += ' unread-count'; + } + } + } + modifyNavItem(item, '/unread', '', unreadCount.topic); + modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); + modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); + modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); + + ['flags'].forEach((prop) => { + if (item && item.originalRoute === `/${prop}` && unreadCount[prop] > 0) { + item.iconClass += ' unread-count'; + item.content = unreadCount.flags; + } + }); + + return item; + }); + + return { navigation, unreadCount }; + } + + + function modifyTitle(obj) { + const title = controllersHelpers.buildTitle(meta.config.homePageTitle || '[[pages:home]]'); + obj.browserTitle = title; + + if (obj.metaTags) { + obj.metaTags.forEach((tag, i) => { + if (tag.property === 'og:title') { + obj.metaTags[i].content = title; + } + }); + } + + return title; + } + + async function getAdminScripts() { + const scripts = await plugins.hooks.fire('filter:admin.scripts.get', []); + return scripts.map(script => ({ src: script })); + } + + async function getLatestVersion() { + try { + return await versions.getLatestVersion(); + } catch (err) { + winston.error(`[acp] Failed to fetch latest version${err.stack}`); + } + return null; + } };