'use strict'; const winston = require('winston'); const _ = require('lodash'); const Benchpress = require('benchpressjs'); const util = require('util'); const plugins = require('../plugins'); const groups = require('../groups'); const translator = require('../translator'); const db = require('../database'); const apiController = require('../controllers/api'); const loadConfigAsync = util.promisify(apiController.loadConfig); const meta = require('../meta'); const widgets = module.exports; widgets.render = async function (uid, options) { if (!options.template) { throw new Error('[[error:invalid-data]]'); } const data = await widgets.getWidgetDataForTemplates(['global', options.template]); delete data.global.drafts; const locations = _.uniq(Object.keys(data.global).concat(Object.keys(data[options.template]))); const widgetData = await Promise.all(locations.map(location => renderLocation(location, data, uid, options))); const returnData = {}; locations.forEach(function (location, i) { if (Array.isArray(widgetData[i]) && widgetData[i].length) { returnData[location] = widgetData[i].filter(Boolean); } }); return returnData; }; async function renderLocation(location, data, uid, options) { const widgetsAtLocation = (data[options.template][location] || []).concat(data.global[location] || []); if (!widgetsAtLocation.length) { return []; } const renderedWidgets = await Promise.all(widgetsAtLocation.map(widget => renderWidget(widget, uid, options))); return renderedWidgets; } async function renderWidget(widget, uid, options) { if (!widget || !widget.data || (!!widget.data['hide-mobile'] && options.req.useragent.isMobile)) { return; } const isVisible = await checkVisibility(widget, uid); if (!isVisible) { return; } let config = options.res.locals.config || {}; if (options.res.locals.isAPI) { config = await loadConfigAsync(options.req); } const userLang = config.userLang || meta.config.defaultLang || 'en-GB'; const templateData = _.assign({ }, options.templateData, { config: config }); const data = await plugins.fireHook('filter:widget.render:' + widget.widget, { uid: uid, area: options, templateData: templateData, data: widget.data, req: options.req, res: options.res, }); if (!data) { return; } let html = data; if (typeof html !== 'string') { html = data.html; } else { winston.warn('[widgets.render] passing a string is deprecated!, filter:widget.render:' + widget.widget + '. Please set hookData.html in your plugin.'); } if (widget.data.container && widget.data.container.match('{body}')) { html = await Benchpress.compileRender(widget.data.container, { title: widget.data.title, body: html, template: data.templateData && data.templateData.template, }); } if (html !== undefined) { html = await translator.translate(html, userLang); } return { html: html }; } async function checkVisibility(widget, uid) { let isVisible = true; let isHidden = false; if (widget.data.groups.length) { isVisible = await groups.isMemberOfAny(uid, widget.data.groups); } if (widget.data.groupsHideFrom.length) { isHidden = await groups.isMemberOfAny(uid, widget.data.groupsHideFrom); } return isVisible && !isHidden; } widgets.getWidgetDataForTemplates = async function (templates) { const keys = templates.map(tpl => 'widgets:' + tpl); const data = await db.getObjects(keys); const returnData = {}; templates.forEach(function (template, index) { returnData[template] = returnData[template] || {}; const templateWidgetData = data[index] || {}; const locations = Object.keys(templateWidgetData); locations.forEach(function (location) { if (templateWidgetData && templateWidgetData[location]) { try { returnData[template][location] = parseWidgetData(templateWidgetData[location]); } catch (err) { winston.error('can not parse widget data. template: ' + template + ' location: ' + location); returnData[template][location] = []; } } else { returnData[template][location] = []; } }); }); return returnData; }; widgets.getArea = async function (template, location) { const result = await db.getObjectField('widgets:' + template, location); if (!result) { return []; } return parseWidgetData(result); }; function parseWidgetData(data) { const widgets = JSON.parse(data); widgets.forEach(function (widget) { if (widget) { widget.data.groups = widget.data.groups || []; if (widget.data.groups && !Array.isArray(widget.data.groups)) { widget.data.groups = [widget.data.groups]; } widget.data.groupsHideFrom = widget.data.groupsHideFrom || []; if (widget.data.groupsHideFrom && !Array.isArray(widget.data.groupsHideFrom)) { widget.data.groupsHideFrom = [widget.data.groupsHideFrom]; } } }); return widgets; } widgets.setArea = async function (area) { if (!area.location || !area.template) { throw new Error('Missing location and template data'); } await db.setObjectField('widgets:' + area.template, area.location, JSON.stringify(area.widgets)); }; widgets.reset = async function () { const defaultAreas = [ { name: 'Draft Zone', template: 'global', location: 'header' }, { name: 'Draft Zone', template: 'global', location: 'footer' }, { name: 'Draft Zone', template: 'global', location: 'sidebar' }, ]; const [areas, drafts] = await Promise.all([ plugins.fireHook('filter:widgets.getAreas', defaultAreas), widgets.getArea('global', 'drafts'), ]); let saveDrafts = drafts || []; for (const area of areas) { /* eslint-disable no-await-in-loop */ const areaData = await widgets.getArea(area.template, area.location); saveDrafts = saveDrafts.concat(areaData); area.widgets = []; await widgets.setArea(area); } await widgets.setArea({ template: 'global', location: 'drafts', widgets: saveDrafts, }); }; widgets.resetTemplate = async function (template) { let toBeDrafted = []; const area = await db.getObject('widgets:' + template + '.tpl'); for (var location in area) { if (area.hasOwnProperty(location)) { toBeDrafted = toBeDrafted.concat(JSON.parse(area[location])); } } await db.delete('widgets:' + template + '.tpl'); let draftWidgets = await db.getObjectField('widgets:global', 'drafts'); draftWidgets = JSON.parse(draftWidgets).concat(toBeDrafted); await db.setObjectField('widgets:global', 'drafts', JSON.stringify(draftWidgets)); }; widgets.resetTemplates = async function (templates) { for (const template of templates) { /* eslint-disable no-await-in-loop */ await widgets.resetTemplate(template); } }; require('../promisify')(widgets);