Merge remote-tracking branch 'upstream/master' into clicks-by-device-type
This commit is contained in:
commit
3152ecb228
68 changed files with 3472 additions and 34855 deletions
187
lib/helpers.js
187
lib/helpers.js
|
@ -1,12 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
let config = require('config');
|
||||
let path = require('path');
|
||||
let fs = require('fs');
|
||||
let tools = require('./tools');
|
||||
let settings = require('./models/settings');
|
||||
let lists = require('./models/lists');
|
||||
let fields = require('./models/fields');
|
||||
let forms = require('./models/forms');
|
||||
let _ = require('./translate')._;
|
||||
let objectHash = require('object-hash');
|
||||
let mjml = require('mjml');
|
||||
let mjmlTemplates = new Map();
|
||||
let hbs = require('hbs');
|
||||
|
||||
module.exports = {
|
||||
getDefaultMergeTags,
|
||||
getListMergeTags
|
||||
getListMergeTags,
|
||||
captureFlashMessages,
|
||||
injectCustomFormData,
|
||||
injectCustomFormTemplates,
|
||||
filterCustomFields,
|
||||
getMjmlTemplate
|
||||
};
|
||||
|
||||
function getDefaultMergeTags(callback) {
|
||||
|
@ -73,3 +88,173 @@ function getListMergeTags(listId, callback) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
function filterCustomFields(customFieldsIn = [], fieldIds = [], method = 'include') {
|
||||
let customFields = customFieldsIn.slice();
|
||||
fieldIds = typeof fieldIds === 'string' ? fieldIds.split(',') : fieldIds;
|
||||
|
||||
customFields.unshift({
|
||||
id: 'email',
|
||||
name: 'Email Address',
|
||||
type: 'Email',
|
||||
typeSubsciptionEmail: true
|
||||
}, {
|
||||
id: 'firstname',
|
||||
name: 'First Name',
|
||||
type: 'Text',
|
||||
typeFirstName: true
|
||||
}, {
|
||||
id: 'lastname',
|
||||
name: 'Last Name',
|
||||
type: 'Text',
|
||||
typeLastName: true
|
||||
});
|
||||
|
||||
let filtered = [];
|
||||
|
||||
if (method === 'include') {
|
||||
fieldIds.forEach(id => {
|
||||
let field = customFields.find(f => f.id.toString() === id);
|
||||
field && filtered.push(field);
|
||||
});
|
||||
} else {
|
||||
customFields.forEach(field => {
|
||||
!fieldIds.includes(field.id.toString()) && filtered.push(field);
|
||||
});
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function injectCustomFormData(customFormId, viewPath, data, callback) {
|
||||
|
||||
let injectDefaultData = data => {
|
||||
data.customFields = filterCustomFields(data.customFields, [], 'exclude');
|
||||
data.formInputStyle = '@import url(/subscription/form-input-style.css);';
|
||||
return data;
|
||||
};
|
||||
|
||||
if (Number(customFormId) < 1) {
|
||||
return callback(null, injectDefaultData(data));
|
||||
}
|
||||
|
||||
forms.get(customFormId, (err, form) => {
|
||||
if (err) {
|
||||
return callback(null, injectDefaultData(data));
|
||||
}
|
||||
|
||||
let view = viewPath.split('/')[1];
|
||||
|
||||
if (view === 'web-subscribe') {
|
||||
data.customFields = form.fieldsShownOnSubscribe
|
||||
? filterCustomFields(data.customFields, form.fieldsShownOnSubscribe)
|
||||
: filterCustomFields(data.customFields, [], 'exclude');
|
||||
} else if (view === 'web-manage') {
|
||||
data.customFields = form.fieldsShownOnManage
|
||||
? filterCustomFields(data.customFields, form.fieldsShownOnManage)
|
||||
: filterCustomFields(data.customFields, [], 'exclude');
|
||||
}
|
||||
|
||||
let key = tools.fromDbKey(view);
|
||||
data.template.template = form[key] || data.template.template;
|
||||
data.template.layout = form.layout || data.template.layout;
|
||||
data.formInputStyle = form.formInputStyle || '@import url(/subscription/form-input-style.css);';
|
||||
|
||||
settings.list(['ua_code'], (err, configItems) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
data.uaCode = configItems.uaCode;
|
||||
data.customSubscriptionScripts = config.customsubscriptionscripts || [];
|
||||
callback(null, data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function injectCustomFormTemplates(customFormId, templates, callback) {
|
||||
if (Number(customFormId) < 1) {
|
||||
return callback(null, templates);
|
||||
}
|
||||
|
||||
forms.get(customFormId, (err, form) => {
|
||||
if (err) {
|
||||
return callback(null, templates);
|
||||
}
|
||||
|
||||
let lookUp = name => {
|
||||
let key = tools.fromDbKey(
|
||||
/subscription\/([^.]*)/.exec(name)[1]
|
||||
);
|
||||
return form[key] || name;
|
||||
};
|
||||
|
||||
Object.keys(templates).forEach(key => {
|
||||
let value = templates[key];
|
||||
|
||||
if (typeof value === 'string') {
|
||||
templates[key] = lookUp(value);
|
||||
}
|
||||
if (typeof value === 'object' && value.template) {
|
||||
templates[key].template = lookUp(value.template);
|
||||
}
|
||||
if (typeof value === 'object' && value.layout) {
|
||||
templates[key].layout = lookUp(value.layout);
|
||||
}
|
||||
});
|
||||
|
||||
callback(null, templates);
|
||||
});
|
||||
}
|
||||
|
||||
function getMjmlTemplate(template, callback) {
|
||||
if (!template) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
let key = (typeof template === 'object') ? objectHash(template) : template;
|
||||
|
||||
if (mjmlTemplates.has(key)) {
|
||||
return callback(null, mjmlTemplates.get(key));
|
||||
}
|
||||
|
||||
let done = source => {
|
||||
let compiled;
|
||||
try {
|
||||
compiled = mjml.mjml2html(source);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (compiled.errors.length) {
|
||||
return callback(compiled.errors[0].message || compiled.errors[0]);
|
||||
}
|
||||
let renderer = hbs.handlebars.compile(compiled.html);
|
||||
mjmlTemplates.set(key, renderer);
|
||||
callback(null, renderer);
|
||||
};
|
||||
|
||||
if (typeof template === 'object') {
|
||||
tools.mergeTemplateIntoLayout(template.template, template.layout, (err, source) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
done(source);
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
done(source);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function captureFlashMessages(req, res, callback) {
|
||||
res.render('subscription/capture-flash-messages', { layout: null }, (err, flash) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, flash);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ let path = require('path');
|
|||
let templates = new Map();
|
||||
let htmlToText = require('html-to-text');
|
||||
let aws = require('aws-sdk');
|
||||
let objectHash = require('object-hash');
|
||||
let mjml = require('mjml');
|
||||
|
||||
let _ = require('./translate')._;
|
||||
let util = require('util');
|
||||
|
@ -124,18 +126,46 @@ function getTemplate(template, callback) {
|
|||
return callback(null, false);
|
||||
}
|
||||
|
||||
if (templates.has(template)) {
|
||||
return callback(null, templates.get(template));
|
||||
let key = (typeof template === 'object') ? objectHash(template) : template;
|
||||
|
||||
if (templates.has(key)) {
|
||||
return callback(null, templates.get(key));
|
||||
}
|
||||
|
||||
fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
let done = (source, isMjml = false) => {
|
||||
if (isMjml) {
|
||||
let compiled;
|
||||
try {
|
||||
compiled = mjml.mjml2html(source);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (compiled.errors.length) {
|
||||
return callback(compiled.errors[0].message || compiled.errors[0]);
|
||||
}
|
||||
source = compiled.html;
|
||||
}
|
||||
let renderer = Handlebars.compile(source);
|
||||
templates.set(template, renderer);
|
||||
return callback(null, renderer);
|
||||
});
|
||||
templates.set(key, renderer);
|
||||
callback(null, renderer);
|
||||
};
|
||||
|
||||
if (typeof template === 'object') {
|
||||
tools.mergeTemplateIntoLayout(template.template, template.layout, (err, source) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
let isMjml = template.type === 'mjml';
|
||||
done(source, isMjml);
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8', (err, source) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
done(source);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createMailer(callback) {
|
||||
|
|
|
@ -405,6 +405,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
|
|||
case 'longtext':
|
||||
{
|
||||
let item = {
|
||||
id: field.id,
|
||||
type: field.type,
|
||||
name: field.name,
|
||||
column: field.column,
|
||||
|
@ -434,6 +435,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
|
|||
}
|
||||
|
||||
let item = {
|
||||
id: field.id,
|
||||
type: field.type,
|
||||
name: field.name,
|
||||
column: field.column,
|
||||
|
@ -449,6 +451,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
|
|||
case 'number':
|
||||
{
|
||||
let item = {
|
||||
id: field.id,
|
||||
type: field.type,
|
||||
name: field.name,
|
||||
column: field.column,
|
||||
|
@ -466,6 +469,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
|
|||
case 'checkbox':
|
||||
{
|
||||
let item = {
|
||||
id: field.id,
|
||||
type: field.type,
|
||||
name: field.name,
|
||||
visible: !!field.visible,
|
||||
|
@ -556,6 +560,7 @@ module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
|
|||
}
|
||||
|
||||
let item = {
|
||||
id: field.id,
|
||||
type: field.type,
|
||||
name: field.name,
|
||||
column: field.column,
|
||||
|
|
409
lib/models/forms.js
Normal file
409
lib/models/forms.js
Normal file
|
@ -0,0 +1,409 @@
|
|||
'use strict';
|
||||
|
||||
let db = require('../db');
|
||||
let fs = require('fs');
|
||||
let path = require('path');
|
||||
let tools = require('../tools');
|
||||
let mjml = require('mjml');
|
||||
let _ = require('../translate')._;
|
||||
|
||||
let allowedKeys = [
|
||||
'name',
|
||||
'description',
|
||||
'fields_shown_on_subscribe',
|
||||
'fields_shown_on_manage',
|
||||
'layout',
|
||||
'form_input_style',
|
||||
'mail_confirm_html',
|
||||
'mail_confirm_text',
|
||||
'mail_subscription_confirmed_html',
|
||||
'mail_subscription_confirmed_text',
|
||||
'mail_unsubscribe_confirmed_html',
|
||||
'mail_unsubscribe_confirmed_text',
|
||||
'web_confirm_notice',
|
||||
'web_manage_address',
|
||||
'web_manage',
|
||||
'web_subscribe',
|
||||
'web_subscribed',
|
||||
'web_unsubscribe_notice',
|
||||
'web_unsubscribe',
|
||||
'web_updated_notice'
|
||||
];
|
||||
|
||||
module.exports.list = (listId, callback) => {
|
||||
listId = Number(listId) || 0;
|
||||
|
||||
if (listId < 1) {
|
||||
return callback(new Error(_('Missing List ID')));
|
||||
}
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
connection.query('SELECT * FROM custom_forms WHERE list=? ORDER BY id', [listId], (err, rows) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let formList = rows && rows.map(row => tools.convertKeys(row)) || [];
|
||||
return callback(null, formList);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.get = (id, callback) => {
|
||||
id = Number(id) || 0;
|
||||
|
||||
if (id < 1) {
|
||||
return callback(new Error(_('Missing Form ID')));
|
||||
}
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
connection.query('SELECT * FROM custom_forms WHERE id=? LIMIT 1', [id], (err, rows) => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let form = rows && rows[0] && tools.convertKeys(rows[0]) || false;
|
||||
|
||||
if (!form) {
|
||||
connection.release();
|
||||
return callback(new Error('Selected form not found'));
|
||||
}
|
||||
|
||||
connection.query('SELECT * FROM custom_forms_data WHERE form=?', [id], (err, data_rows = []) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
data_rows.forEach(data_row => {
|
||||
let modelKey = tools.fromDbKey(data_row.data_key);
|
||||
form[modelKey] = data_row.data_value;
|
||||
});
|
||||
|
||||
return callback(null, form);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
module.exports.create = (listId, form, callback) => {
|
||||
listId = Number(listId) || 0;
|
||||
|
||||
if (listId < 1) {
|
||||
return callback(new Error(_('Missing Form ID')));
|
||||
}
|
||||
|
||||
form = tools.convertKeys(form);
|
||||
form = setDefaultValues(form);
|
||||
form.name = (form.name || '').toString().trim();
|
||||
|
||||
if (!form.name) {
|
||||
return callback(new Error(_('Form Name must be set')));
|
||||
}
|
||||
|
||||
let keys = ['list'];
|
||||
let values = [listId];
|
||||
|
||||
Object.keys(form).forEach(key => {
|
||||
let value = form[key].trim();
|
||||
key = tools.toDbKey(key);
|
||||
if (key === 'description') {
|
||||
value = tools.purifyHTML(value);
|
||||
}
|
||||
if (allowedKeys.indexOf(key) >= 0) {
|
||||
keys.push(key);
|
||||
values.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let filtered = filterKeysAndValues(keys, values, 'exclude', ['mail_', 'web_']);
|
||||
let query = 'INSERT INTO custom_forms (' + filtered.keys.join(', ') + ') VALUES (' + filtered.values.map(() => '?').join(',') + ')';
|
||||
|
||||
connection.query(query, filtered.values, (err, result) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let formId = result && result.insertId;
|
||||
|
||||
if (!formId) {
|
||||
return callback(new Error('Invalid custom_forms insertId'));
|
||||
}
|
||||
|
||||
let jobs = 1;
|
||||
let error = null;
|
||||
|
||||
let done = err => {
|
||||
jobs--;
|
||||
error = err ? err : error; // One's enough
|
||||
jobs === 0 && callback(error, formId);
|
||||
};
|
||||
|
||||
filtered = filterKeysAndValues(keys, values, 'include', ['mail_', 'web_']);
|
||||
|
||||
filtered.keys.forEach((key, index) => {
|
||||
jobs++;
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
connection.query('INSERT INTO custom_forms_data (form, data_key, data_value) VALUES (?, ?, ?)', [formId, key, filtered.values[index]], err => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
return done(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
done(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.update = (id, updates, callback) => {
|
||||
updates = updates || {};
|
||||
id = Number(id) || 0;
|
||||
|
||||
updates = tools.convertKeys(updates);
|
||||
|
||||
if (id < 1) {
|
||||
return callback(new Error(_('Missing Form ID')));
|
||||
}
|
||||
|
||||
if (!(updates.name || '').toString().trim()) {
|
||||
return callback(new Error(_('Form Name must be set')));
|
||||
}
|
||||
|
||||
let keys = [];
|
||||
let values = [];
|
||||
|
||||
Object.keys(updates).forEach(key => {
|
||||
let value = typeof updates[key] === 'string' ? updates[key].trim() : updates[key];
|
||||
key = tools.toDbKey(key);
|
||||
if (key === 'description') {
|
||||
value = tools.purifyHTML(value);
|
||||
}
|
||||
if (allowedKeys.indexOf(key) >= 0) {
|
||||
keys.push(key);
|
||||
values.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let filtered = filterKeysAndValues(keys, values, 'exclude', ['mail_', 'web_']);
|
||||
let query = 'UPDATE custom_forms SET ' + filtered.keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1';
|
||||
|
||||
connection.query(query, filtered.values.concat(id), (err, result) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let affectedRows = result && result.affectedRows;
|
||||
|
||||
let jobs = 1;
|
||||
let error = null;
|
||||
|
||||
let done = err => {
|
||||
jobs--;
|
||||
error = err ? err : error; // One's enough
|
||||
|
||||
if (jobs === 0) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
// Save then validate, as otherwise their work get's lost ...
|
||||
err = testForMjmlErrors(keys, values);
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
return callback(null, affectedRows);
|
||||
}
|
||||
};
|
||||
|
||||
filtered = filterKeysAndValues(keys, values, 'include', ['mail_', 'web_']);
|
||||
|
||||
filtered.keys.forEach((key, index) => {
|
||||
jobs++;
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
connection.query('UPDATE custom_forms_data SET data_value=? WHERE data_key=? AND form=?', [filtered.values[index], key, id], err => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
return done(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
done(null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.delete = (formId, callback) => {
|
||||
formId = Number(formId) || 0;
|
||||
|
||||
if (formId < 1) {
|
||||
return callback(new Error(_('Missing Form ID')));
|
||||
}
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
connection.query('SELECT * FROM custom_forms WHERE id=? LIMIT 1', [formId], (err, rows) => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!rows || !rows.length) {
|
||||
connection.release();
|
||||
return callback(new Error(_('Custom form not found')));
|
||||
}
|
||||
|
||||
connection.query('DELETE FROM custom_forms WHERE id=? LIMIT 1', [formId], err => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function setDefaultValues(form) {
|
||||
let getContents = fileName => {
|
||||
try {
|
||||
let basePath = path.join(__dirname, '..', '..');
|
||||
let template = fs.readFileSync(path.join(basePath, fileName), 'utf8');
|
||||
return template.replace(/\{\{#translate\}\}(.*?)\{\{\/translate\}\}/g, (m, s) => _(s));
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
allowedKeys.forEach(key => {
|
||||
let modelKey = tools.fromDbKey(key);
|
||||
let base = 'views/subscription/' + key.replace(/_/g, '-');
|
||||
|
||||
if (key.startsWith('mail') || key.startsWith('web')) {
|
||||
form[modelKey] = getContents(base + '.mjml.hbs') || getContents(base + '.hbs') || '';
|
||||
}
|
||||
});
|
||||
|
||||
form.layout = getContents('views/subscription/layout.mjml.hbs') || '';
|
||||
form.formInputStyle = getContents('public/subscription/form-input-style.css') || '@import url(/subscription/form-input-style.css);';
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
function filterKeysAndValues(keysIn, valuesIn, method = 'include', prefixes = []) {
|
||||
let values = [];
|
||||
|
||||
let prefixMatch = key => (
|
||||
prefixes.some(prefix => key.startsWith(prefix))
|
||||
);
|
||||
|
||||
let keys = keysIn.filter((key, index) => {
|
||||
if ((method === 'include' && prefixMatch(key)) || (method === 'exclude' && !prefixMatch(key))) {
|
||||
values.push(valuesIn[index]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return {
|
||||
keys,
|
||||
values
|
||||
};
|
||||
}
|
||||
|
||||
function testForMjmlErrors(keys, values) {
|
||||
|
||||
let errors = [];
|
||||
let testLayout = '<mjml><mj-body><mj-container>{{{body}}}</mj-container></mj-body></mjml>';
|
||||
|
||||
let hasMjmlError = (template, layout = testLayout) => {
|
||||
let source = layout.replace(/\{\{\{body\}\}\}/g, template);
|
||||
let compiled;
|
||||
|
||||
try {
|
||||
compiled = mjml.mjml2html(source);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (compiled.errors.length) {
|
||||
return compiled.errors[0].message || compiled.errors[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
keys.forEach((key, index) => {
|
||||
if (key.startsWith('mail_') || key.startsWith('web_')) {
|
||||
|
||||
let template = values[index];
|
||||
let err = hasMjmlError(template);
|
||||
|
||||
err && errors.push(key + ': ' + (err.message || err));
|
||||
key === 'mail_confirm_html' && !template.includes('{{confirmUrl}}') && errors.push(key + ': Missing {{confirmUrl}}');
|
||||
|
||||
} else if (key === 'layout') {
|
||||
|
||||
let layout = values[index];
|
||||
let err = hasMjmlError('', layout);
|
||||
|
||||
err && errors.push('layout: ' + (err.message || err));
|
||||
!layout.includes('{{{body}}}') && errors.push('layout: {{{body}}} not found');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (errors.length) {
|
||||
errors.forEach((err, index) => {
|
||||
errors[index] = (index + 1) + ') ' + err;
|
||||
});
|
||||
return 'Please fix these MJML errors:\n\n' + errors.join('\n');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -6,7 +6,7 @@ let shortid = require('shortid');
|
|||
let segments = require('./segments');
|
||||
let _ = require('../translate')._;
|
||||
|
||||
let allowedKeys = ['description'];
|
||||
let allowedKeys = ['description', 'default_form'];
|
||||
|
||||
module.exports.list = (start, limit, callback) => {
|
||||
db.getConnection((err, connection) => {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
let db = require('../db');
|
||||
let shortid = require('shortid');
|
||||
let tools = require('../tools');
|
||||
let helpers = require('../helpers');
|
||||
let fields = require('./fields');
|
||||
let geoip = require('geoip-ultralight');
|
||||
let segments = require('./segments');
|
||||
|
@ -210,7 +211,7 @@ module.exports.addConfirmation = (list, email, optInIp, data, callback) => {
|
|||
}
|
||||
});
|
||||
|
||||
settings.list(['defaultHomepage', 'defaultFrom', 'defaultAddress', 'serviceUrl'], (err, configItems) => {
|
||||
settings.list(['defaultHomepage', 'defaultFrom', 'defaultAddress', 'defaultPostaddress', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -221,35 +222,55 @@ module.exports.addConfirmation = (list, email, optInIp, data, callback) => {
|
|||
return;
|
||||
}
|
||||
|
||||
mailer.sendMail({
|
||||
from: {
|
||||
name: configItems.defaultFrom,
|
||||
address: configItems.defaultAddress
|
||||
},
|
||||
to: {
|
||||
name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '),
|
||||
address: email
|
||||
},
|
||||
subject: util.format(_('%s: Please Confirm Subscription'), list.name),
|
||||
encryptionKeys
|
||||
}, {
|
||||
html: 'emails/confirm-html.hbs',
|
||||
text: 'emails/confirm-text.hbs',
|
||||
data: {
|
||||
title: list.name,
|
||||
contactAddress: configItems.defaultAddress,
|
||||
confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + cid)
|
||||
}
|
||||
}, err => {
|
||||
let sendMail = (html, text) => {
|
||||
mailer.sendMail({
|
||||
from: {
|
||||
name: configItems.defaultFrom,
|
||||
address: configItems.defaultAddress
|
||||
},
|
||||
to: {
|
||||
name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '),
|
||||
address: email
|
||||
},
|
||||
subject: util.format(_('%s: Please Confirm Subscription'), list.name),
|
||||
encryptionKeys
|
||||
}, {
|
||||
html,
|
||||
text,
|
||||
data: {
|
||||
title: list.name,
|
||||
contactAddress: configItems.defaultAddress,
|
||||
defaultPostaddress: configItems.defaultPostaddress,
|
||||
confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + cid)
|
||||
}
|
||||
}, err => {
|
||||
if (err) {
|
||||
log.error('Subscription', err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let text = {
|
||||
template: 'subscription/mail-confirm-text.hbs'
|
||||
};
|
||||
|
||||
let html = {
|
||||
template: 'subscription/mail-confirm-html.mjml.hbs',
|
||||
layout: 'subscription/layout.mjml.hbs',
|
||||
type: 'mjml'
|
||||
};
|
||||
|
||||
helpers.injectCustomFormTemplates(list.defaultForm, { text, html }, (err, tmpl) => {
|
||||
if (err) {
|
||||
log.error('Subscription', err.stack);
|
||||
return sendMail(html, text);
|
||||
}
|
||||
|
||||
sendMail(tmpl.html, tmpl.text);
|
||||
});
|
||||
});
|
||||
return callback(null, cid);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
51
lib/tools.js
51
lib/tools.js
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
let fs = require('fs');
|
||||
let path = require('path');
|
||||
let db = require('./db');
|
||||
let slugify = require('slugify');
|
||||
let Isemail = require('isemail');
|
||||
|
@ -25,6 +27,7 @@ module.exports = {
|
|||
getMessageLinks,
|
||||
prepareHtml,
|
||||
purifyHTML,
|
||||
mergeTemplateIntoLayout,
|
||||
workers: new Set()
|
||||
};
|
||||
|
||||
|
@ -245,3 +248,51 @@ function purifyHTML(html) {
|
|||
let DOMPurify = createDOMPurify(win);
|
||||
return DOMPurify.sanitize(html);
|
||||
}
|
||||
|
||||
// TODO Simplify!
|
||||
function mergeTemplateIntoLayout(template, layout, callback) {
|
||||
|
||||
layout = layout || '{{{body}}}';
|
||||
|
||||
let readFile = (relPath, callback) => {
|
||||
fs.readFile(path.join(__dirname, '..', 'views', relPath), 'utf-8', (err, source) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, source);
|
||||
});
|
||||
};
|
||||
|
||||
let done = (template, layout) => {
|
||||
let source = layout.replace(/\{\{\{body\}\}\}/g, template);
|
||||
return callback(null, source);
|
||||
};
|
||||
|
||||
if (layout.endsWith('.hbs')) {
|
||||
readFile(layout, (err, layout) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
// Please dont end your custom messages with .hbs ...
|
||||
if (template.endsWith('.hbs')) {
|
||||
readFile(template, (err, template) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return done(template, layout);
|
||||
});
|
||||
} else {
|
||||
return done(template, layout);
|
||||
}
|
||||
});
|
||||
} else if (template.endsWith('.hbs')) {
|
||||
readFile(template, (err, template) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return done(template, layout);
|
||||
});
|
||||
} else {
|
||||
return done(template, layout);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue