mailtrain/lib/models/fields.js

524 lines
19 KiB
JavaScript
Raw Normal View History

2016-04-04 12:36:30 +00:00
'use strict';
let db = require('../db');
let tools = require('../tools');
let slugify = require('slugify');
let lists = require('./lists');
let shortid = require('shortid');
let allowedKeys = ['name', 'key', 'default_value', 'group', 'visible'];
module.exports.grouped = ['radio', 'checkbox', 'dropdown'];
module.exports.types = {
text: 'Text',
website: 'Website',
2016-04-16 05:27:45 +00:00
longtext: 'Multi-line text',
gpg: 'GPG Public Key',
2016-04-04 12:36:30 +00:00
number: 'Number',
radio: 'Radio Buttons',
checkbox: 'Checkboxes',
dropdown: 'Drop Down',
'date-us': 'Date (MM/DD/YYY)',
'date-eur': 'Date (DD/MM/YYYY)',
'birthday-us': 'Birthday (MM/DD)',
'birthday-eur': 'Birthday (DD/MM)',
option: 'Option'
};
module.exports.genericTypes = {
text: 'string',
website: 'string',
2016-04-16 05:27:45 +00:00
longtext: 'textarea',
gpg: 'textarea',
2016-04-04 12:36:30 +00:00
number: 'number',
'date-us': 'date',
'date-eur': 'date',
'birthday-us': 'birthday',
'birthday-eur': 'birthday',
option: 'boolean'
};
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);
}
let query = 'SELECT * FROM custom_fields WHERE list=?';
connection.query(query, [listId], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
let fieldList = rows && rows.map(row => tools.convertKeys(row)) || [];
let groups = new Map();
// remove grouped rows
for (let i = fieldList.length - 1; i >= 0; i--) {
let field = fieldList[i];
if (module.exports.grouped.indexOf(field.type) >= 0) {
if (!groups.has(field.id)) {
groups.set(field.id, []);
}
field.options = groups.get(field.id);
} else if (field.group && field.type === 'option') {
if (!groups.has(field.group)) {
groups.set(field.group, [field]);
} else {
groups.get(field.group).unshift(field);
}
fieldList.splice(i, 1);
}
}
return callback(null, fieldList);
});
});
};
module.exports.get = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing List ID'));
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT * FROM custom_fields WHERE id=? LIMIT 1';
connection.query(query, [id], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, rows && rows[0] && tools.convertKeys(rows[0]) || false);
});
});
};
module.exports.create = (listId, field, callback) => {
listId = Number(listId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
}
field = tools.convertKeys(field);
if (field.type === 'option' && !field.group) {
return callback(new Error('Option field requires a group to be selected'));
}
if (field.type !== 'option') {
field.group = null;
}
addCustomField(listId, field.name, field.defaultValue, field.type, field.group, field.visible, callback);
};
module.exports.update = (id, updates, callback) => {
updates = updates || {};
id = Number(id) || 0;
updates = tools.convertKeys(updates);
if (id < 1) {
return callback(new Error('Missing Field ID'));
}
if (!(updates.name || '').toString().trim()) {
return callback(new Error('Field Name must be set'));
}
if (updates.key) {
updates.key = slugify(updates.key, '_').toUpperCase();
}
updates.visible = updates.visible ? 1 : 0;
let name = (updates.name || '').toString().trim();
let keys = ['name'];
let values = [name];
Object.keys(updates).forEach(key => {
let value = typeof updates[key] === 'string' ? updates[key].trim() : updates[key];
key = tools.toDbKey(key);
if (allowedKeys.indexOf(key) >= 0) {
keys.push(key);
values.push(value);
}
});
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
values.push(id);
connection.query('UPDATE custom_fields SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, result && result.affectedRows || false);
});
});
};
module.exports.delete = (fieldId, callback) => {
fieldId = Number(fieldId) || 0;
if (fieldId < 1) {
return callback(new Error('Missing Field ID'));
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT * FROM custom_fields WHERE id=? LIMIT 1';
connection.query(query, [fieldId], (err, rows) => {
if (err) {
connection.release();
return callback(err);
}
if (!rows || !rows.length) {
connection.release();
return callback(new Error('Custom field not found'));
}
let field = tools.convertKeys(rows[0]);
if (field.column) {
connection.query('ALTER TABLE `subscription__' + field.list + '` DROP COLUMN `' + field.column + '`', err => {
if (err && err.code !== 'ER_CANT_DROP_FIELD_OR_KEY') {
connection.release();
return callback(err);
}
connection.query('DELETE FROM custom_fields WHERE id=? LIMIT 1', [fieldId], err => {
if (err) {
connection.release();
return callback(err);
}
connection.query('DELETE FROM segment_rules WHERE column=? LIMIT 1', [field.column], err => {
connection.release();
if (err) {
// ignore
}
return callback(null, true);
});
});
});
} else {
// delete all subfields in this group
let query = 'SELECT id FROM custom_fields WHERE `group`=?';
connection.query(query, [fieldId], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
rows = [];
}
let pos = 0;
let deleteNext = () => {
if (pos >= rows.length) {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('DELETE FROM custom_fields WHERE id=? LIMIT 1', [fieldId], err => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, true);
});
});
return;
}
module.exports.delete(rows[pos++].id, deleteNext);
};
deleteNext();
});
}
});
});
};
function addCustomField(listId, name, defaultValue, type, group, visible, callback) {
type = (type || '').toString().trim().toLowerCase();
group = Number(group) || null;
listId = Number(listId) || 0;
let column = null;
let key = slugify('merge ' + name, '_').toUpperCase();
2016-04-16 05:27:45 +00:00
let allowedTypes = ['text', 'number', 'radio', 'checkbox', 'dropdown', 'date-us', 'date-eur', 'birthday-us', 'birthday-eur', 'website', 'option', 'gpg', 'longtext'];
2016-04-04 12:36:30 +00:00
if (allowedTypes.indexOf(type) < 0) {
return callback(new Error('Unknown column type ' + type));
}
if (!name) {
return callback(new Error('Missing column name'));
}
if (listId <= 0) {
return callback(new Error('Missing list ID'));
}
lists.get(listId, (err, list) => {
if (err) {
return callback(err);
}
if (!list) {
return callback('Provided List ID not found');
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
if (module.exports.grouped.indexOf(type) < 0) {
column = ('custom_' + slugify(name, '_') + '_' + shortid.generate()).toLowerCase().replace(/[^a-z0-9\_]/g, '');
}
let query = 'INSERT INTO custom_fields (`list`, `name`, `key`,`default_value`, `type`, `group`, `column`, `visible`) VALUES(?,?,?,?,?,?,?,?)';
connection.query(query, [listId, name, key, defaultValue, type, group, column, visible ? 1 : 0], (err, result) => {
if (err) {
connection.release();
return callback(err);
}
let fieldId = result && result.insertId;
switch (type) {
case 'text':
case 'website':
2016-04-16 05:27:45 +00:00
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` VARCHAR(255) DEFAULT NULL, ADD INDEX (' + column + ')';
break;
case 'gpg':
case 'longtext':
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` TEXT DEFAULT NULL';
2016-04-04 12:36:30 +00:00
break;
case 'number':
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` INT(11) DEFAULT NULL, ADD INDEX (' + column + ')';
break;
case 'option':
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` TINYINT(4) UNSIGNED NOT NULL DEFAULT \'0\', ADD INDEX (' + column + ')';
break;
case 'date-us':
case 'date-eur':
case 'birthday-us':
case 'birthday-eur':
query = 'ALTER TABLE `subscription__' + listId + '` ADD COLUMN `' + column + '` TIMESTAMP NULL DEFAULT NULL, ADD INDEX (' + column + ')';
break;
default:
connection.release();
return callback(null, true);
}
connection.query(query, err => {
if (err) {
connection.query('DELETE FROM custom_fields WHERE id=? LIMIT 1', [fieldId], () => connection.release());
2016-04-04 12:36:30 +00:00
return callback(err);
}
connection.release();
2016-04-04 12:36:30 +00:00
return callback(null, fieldId);
});
});
});
});
}
module.exports.getRow = (fieldList, values, useDate, showAll, onlyExisting) => {
2016-04-04 12:36:30 +00:00
let valueList = {};
let row = [];
Object.keys(values || {}).forEach(key => {
let value = values[key];
key = tools.toDbKey(key);
if (key.indexOf('custom_') === 0) {
valueList[key] = value;
} else if (key.indexOf('group_g') === 0 && value.indexOf('custom_') === 0) {
valueList[tools.toDbKey(value)] = 1;
}
});
fieldList.filter(field => showAll || field.visible).forEach(field => {
if (onlyExisting && field.column && !valueList.hasOwnProperty(field.column)) {
// ignore missing values
return;
}
2016-04-04 12:36:30 +00:00
switch (field.type) {
case 'text':
case 'website':
2016-04-16 05:27:45 +00:00
case 'gpg':
case 'longtext':
2016-04-04 12:36:30 +00:00
{
let item = {
type: field.type,
name: field.name,
column: field.column,
value: (valueList[field.column] || '').toString().trim(),
visible: !!field.visible,
mergeTag: field.key,
mergeValue: (valueList[field.column] || '').toString().trim() || field.defaultValue,
['type' + (field.type || '').toString().trim().replace(/(?:^|\-)([a-z])/g, (m, c) => c.toUpperCase())]: true
};
row.push(item);
break;
}
case 'number':
{
let item = {
type: field.type,
name: field.name,
column: field.column,
value: Number(valueList[field.column]) || 0,
visible: !!field.visible,
mergeTag: field.key,
mergeValue: Number(valueList[field.column]) || field.defaultValue || 0,
['type' + (field.type || '').toString().trim().replace(/(?:^|\-)([a-z])/g, (m, c) => c.toUpperCase())]: true
};
row.push(item);
break;
}
case 'dropdown':
case 'radio':
case 'checkbox':
{
let item = {
type: field.type,
name: field.name,
visible: !!field.visible,
key: 'group-g' + field.id,
mergeTag: field.key,
mergeValue: field.defaultValue,
['type' + (field.type || '').toString().trim().replace(/(?:^|\-)([a-z])/g, (m, c) => c.toUpperCase())]: true,
options: (field.options || []).map(subField => {
if (onlyExisting && subField.column && !valueList.hasOwnProperty(subField.column)) {
// ignore missing values
return false;
}
return {
type: subField.type,
name: subField.name,
column: subField.column,
value: valueList[subField.column] ? 1 : 0,
visible: !!subField.visible,
mergeTag: subField.key,
mergeValue: valueList[subField.column] ? subField.name : subField.defaultValue
};
}).filter(subField => subField)
2016-04-04 12:36:30 +00:00
};
2016-05-13 12:32:29 +00:00
item.value = item.options.filter(subField => (showAll || subField.visible) && subField.value).map(subField => subField.name).join(', ');
2016-04-04 12:36:30 +00:00
item.mergeValue = item.value || field.defaultValue;
row.push(item);
break;
}
case 'date-eur':
case 'birthday-eur':
case 'date-us':
case 'birthday-us':
{
let isUs = /\-us$/.test(field.type);
let isYear = field.type.indexOf('date-') === 0;
let value = valueList[field.column];
let day, month, year;
let formatted;
if (value && typeof value.getUTCFullYear === 'function') {
day = value.getUTCDate();
month = value.getUTCMonth() + 1;
year = value.getUTCFullYear();
} else {
value = (value || '').toString().trim();
let parts = value.match(/(\d+)\D+(\d+)(?:\D+(\d+))?/);
if (!parts) {
value = null;
} else {
day = Number(parts[isUs ? 2 : 1]) || 0;
month = Number(parts[isUs ? 1 : 2]) || 0;
year = Number(parts[3]) || 2000;
if (!day || !month) {
value = null;
} else {
value = new Date(Date.UTC(year, month - 1, day));
}
}
}
if (day && month) {
if (isUs) {
formatted = (month < 10 ? '0' : '') + month + '/' + (day < 10 ? '0' : '') + day;
} else {
formatted = (day < 10 ? '0' : '') + day + '/' + (month < 10 ? '0' : '') + month;
}
if (isYear) {
formatted += '/' + year;
}
} else {
formatted = null;
}
let item = {
type: field.type,
name: field.name,
column: field.column,
value: useDate ? value : formatted,
visible: !!field.visible,
mergeTag: field.key,
mergeValue: (useDate ? value : formatted) || field.defaultValue,
['type' + (field.type || '').toString().trim().replace(/(?:^|\-)([a-z])/g, (m, c) => c.toUpperCase())]: true
};
row.push(item);
break;
}
}
});
return row;
};
module.exports.getValues = (row, showAll) => {
let result = [];
row.filter(field => showAll || field.visible).forEach(field => {
if (field.column) {
result.push({
key: field.column,
value: field.value
});
} else if (field.options) {
field.options.filter(field => showAll || field.visible).forEach(subField => {
result.push({
key: subField.column,
value: subField.value
});
});
}
});
return result;
};