Work in progress on tag language
Fix - message sent to a list not associated with a campaign couldn't be shown in archive - to know which message to show even if the list is not at the campaign, we store test messages in table test_messages
This commit is contained in:
parent
00e328a914
commit
4113cb8476
17 changed files with 312 additions and 172 deletions
|
@ -229,7 +229,8 @@ export default class CustomContent extends Component {
|
|||
|
||||
return {
|
||||
html: exportedData.data_sourceCustom_html,
|
||||
text: this.getFormValue('data_sourceCustom_text')
|
||||
text: this.getFormValue('data_sourceCustom_text'),
|
||||
tagLanguage: this.getFormValue('data_sourceCustom_tag_language')
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ class SendControls extends Component {
|
|||
const entity = this.props.entity;
|
||||
|
||||
if (entity.scheduled) {
|
||||
const date = moment(entity.scheduled);
|
||||
const date = moment.utc(entity.scheduled);
|
||||
this.populateFormValues({
|
||||
sendLater: true,
|
||||
date: date.format('YYYY-MM-DD'),
|
||||
|
|
|
@ -97,6 +97,7 @@ export class TestSendModalDialog extends Component {
|
|||
const contentData = await this.props.getDataAsync();
|
||||
data.html = contentData.html;
|
||||
data.text = contentData.text;
|
||||
data.tagLanguage = contentData.tagLanguage;
|
||||
}
|
||||
|
||||
if (mode === TestSendModalDialogMode.TEMPLATE) {
|
||||
|
@ -202,7 +203,7 @@ export class TestSendModalDialog extends Component {
|
|||
const target = this.getFormValue('target');
|
||||
const mode = this.props.mode;
|
||||
|
||||
if (mode === TestSendModalDialogMode.CAMPAIGN_STATUS) {
|
||||
if (mode === TestSendModalDialogMode.CAMPAIGN_CONTENT || mode === TestSendModalDialogMode.CAMPAIGN_STATUS) {
|
||||
const targetOpts = [
|
||||
{key: Target.CAMPAIGN_ONE, label: t('Single test user of the campaign')},
|
||||
{key: Target.CAMPAIGN_ALL, label: t('All test users of the campaign')},
|
||||
|
|
|
@ -262,7 +262,8 @@ export default class CUD extends Component {
|
|||
|
||||
return {
|
||||
html: exportedData.html,
|
||||
text: this.getFormValue('text')
|
||||
text: this.getFormValue('text'),
|
||||
tagLanguage: this.getFormValue('tag_language')
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import {getSandboxUrl} from "../lib/urls";
|
|||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import {ActionLink, Button} from "../lib/bootstrap-components";
|
||||
import {Trans} from "react-i18next";
|
||||
import {TagLanguages} from "../../../shared/templates";
|
||||
import {TagLanguages, renderTag} from "../../../shared/templates";
|
||||
|
||||
import styles from "../lib/styles.scss";
|
||||
|
||||
|
@ -507,6 +507,39 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
|
|||
export function getEditForm(owner, typeKey, prefix = '') {
|
||||
const t = owner.props.t;
|
||||
|
||||
const tagLanguage = owner.getFormValue(prefix + 'tag_language');
|
||||
|
||||
const tg = tag => renderTag(tagLanguage, tag);
|
||||
|
||||
let instructions = null;
|
||||
if (tagLanguage === TagLanguages.SIMPLE) {
|
||||
instructions = (
|
||||
<>
|
||||
<Trans i18nKey="mergeTagsAreTagsThatAreReplacedBefore">
|
||||
<p>Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: <code>{tg('TAG_NAME')}</code> or <code>[TAG_NAME/fallback]</code> where <code>fallback</code> is an optional text value used when <code>TAG_NAME</code> is empty.</p>
|
||||
</Trans>
|
||||
<Trans i18nKey="youCanUseAnyOfTheStandardMergeTagsBelow">
|
||||
<p>You can use any of the standard merge tags below. In addition to that every custom field has its own merge tag. Check the fields of the list you are going to send to.</p>
|
||||
</Trans>
|
||||
</>
|
||||
);
|
||||
} else if (tagLanguage === TagLanguages.HBS) {
|
||||
instructions = (
|
||||
<>
|
||||
<Trans>
|
||||
<p>Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: <code>{tg('TAG_NAME')}</code>. </p>
|
||||
</Trans>
|
||||
<Trans i18nKey="youCanUseAnyOfTheStandardMergeTagsBelow">
|
||||
<p>You can use any of the standard merge tags below. In addition to that every custom field has its own merge tag. Check the fields of the list you are going to send to.</p>
|
||||
</Trans>
|
||||
<Trans>
|
||||
<p>The whole message is interpreted as Handlebars template (see <a href="http://handlebarsjs.com/">http://handlebarsjs.com/</a>). You can use any Handlebars blocks and expressions
|
||||
in the template. The merge tags form the root context of the Handlebars template.</p>
|
||||
</Trans>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AlignedRow>
|
||||
|
@ -516,12 +549,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
label={t('mergeTagReference')}/>
|
||||
{owner.state.showMergeTagReference &&
|
||||
<div style={{marginTop: '15px'}}>
|
||||
<Trans i18nKey="mergeTagsAreTagsThatAreReplacedBefore">
|
||||
<p>Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: <code>[TAG_NAME]</code> or <code>[TAG_NAME/fallback]</code> where <code>fallback</code> is an optional text value used when <code>TAG_NAME</code> is empty.</p>
|
||||
</Trans>
|
||||
<Trans i18nKey="youCanUseAnyOfTheStandardMergeTagsBelow">
|
||||
<p>You can use any of the standard merge tags below. In addition to that every custom field has its own merge tag. Check the fields of the list you are going to send to.</p>
|
||||
</Trans>
|
||||
{instructions}
|
||||
<table className="table table-bordered table-condensed table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -536,7 +564,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[LINK_UNSUBSCRIBE]
|
||||
{tg('LINK_UNSUBSCRIBE')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="urlThatPointsToTheUnsubscribePage">URL that points to the unsubscribe page</Trans>
|
||||
|
@ -544,7 +572,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[LINK_PREFERENCES]
|
||||
{tg('LINK_PREFERENCES')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="urlThatPointsToThePreferencesPageOfThe">URL that points to the preferences page of the subscriber</Trans>
|
||||
|
@ -552,7 +580,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[LINK_BROWSER]
|
||||
{tg('LINK_BROWSER')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="urlToPreviewTheMessageInABrowser">URL to preview the message in a browser</Trans>
|
||||
|
@ -560,7 +588,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[EMAIL]
|
||||
{tg('EMAIL')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="emailAddress-1">Email address</Trans>
|
||||
|
@ -568,7 +596,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[SUBSCRIPTION_ID]
|
||||
{tg('SUBSCRIPTION_ID')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="uniqueIdThatIdentifiesTheRecipient">Unique ID that identifies the recipient</Trans>
|
||||
|
@ -576,7 +604,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[LIST_ID]
|
||||
{tg('LIST_ID')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="uniqueIdThatIdentifiesTheListUsedForThis">Unique ID that identifies the list used for this campaign</Trans>
|
||||
|
@ -584,7 +612,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[CAMPAIGN_ID]
|
||||
{tg('CAMPAIGN_ID')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="uniqueIdThatIdentifiesCurrentCampaign">Unique ID that identifies current campaign</Trans>
|
||||
|
@ -609,7 +637,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[RSS_ENTRY_TITLE]
|
||||
{tg('RSS_ENTRY_TITLE')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="rssEntryTitle">RSS entry title</Trans>
|
||||
|
@ -617,7 +645,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[RSS_ENTRY_DATE]
|
||||
{tg('RSS_ENTRY_DATE')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="rssEntryDate">RSS entry date</Trans>
|
||||
|
@ -625,7 +653,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[RSS_ENTRY_LINK]
|
||||
{tg('RSS_ENTRY_LINK')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="rssEntryLink">RSS entry link</Trans>
|
||||
|
@ -633,7 +661,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[RSS_ENTRY_CONTENT]
|
||||
{tg('RSS_ENTRY_CONTENT')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="contentOfAnRssEntry">Content of an RSS entry</Trans>
|
||||
|
@ -641,7 +669,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[RSS_ENTRY_SUMMARY]
|
||||
{tg('RSS_ENTRY_SUMMARY')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="rssEntrySummary">RSS entry summary</Trans>
|
||||
|
@ -649,7 +677,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
|
|||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
[RSS_ENTRY_IMAGE_URL]
|
||||
{tg('RSS_ENTRY_IMAGE_URL')}
|
||||
</th>
|
||||
<td>
|
||||
<Trans i18nKey="rssEntryImageUrl">RSS entry image URL</Trans>
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit ec89af43120f95dcf7f14dc92a2b3811bf50d7e8
|
||||
Subproject commit fca588ecbe7df717d2219bac82acdc3da1bd6f40
|
|
@ -13,7 +13,7 @@ const fields = require('../models/fields');
|
|||
const sendConfigurations = require('../models/send-configurations');
|
||||
const links = require('../models/links');
|
||||
const {CampaignSource, CampaignType} = require('../../shared/campaigns');
|
||||
const {SubscriptionStatus} = require('../../shared/lists');
|
||||
const {SubscriptionStatus, toNameTagLangauge} = require('../../shared/lists');
|
||||
const tools = require('./tools');
|
||||
const htmlToText = require('html-to-text');
|
||||
const request = require('request-promise');
|
||||
|
@ -39,7 +39,7 @@ class MessageSender {
|
|||
settings is one of:
|
||||
- campaignCid / campaignId
|
||||
or
|
||||
- sendConfiguration, listId, attachments, html, text, subject
|
||||
- sendConfiguration, listId, attachments, html, text, subject, tagLanguage
|
||||
*/
|
||||
async _init(settings) {
|
||||
this.type = settings.type;
|
||||
|
@ -58,14 +58,6 @@ class MessageSender {
|
|||
this.isMassMail = true;
|
||||
|
||||
} else if (this.type === MessageType.TEST) {
|
||||
// We are not within scope of a campaign (i.e. templates in MessageType.TEST message)
|
||||
// This is to fake the campaign for getMessageLinks, which is called inside formatMessage
|
||||
this.campaign = {
|
||||
cid: '[CAMPAIGN_ID]',
|
||||
from_name_override: null,
|
||||
from_email_override: null,
|
||||
reply_to_override: null
|
||||
};
|
||||
this.isMassMail = true;
|
||||
|
||||
} else {
|
||||
|
@ -89,6 +81,12 @@ class MessageSender {
|
|||
this.listsByCid.set(list.cid, list);
|
||||
this.listsFieldsGrouped.set(list.id, await fields.listGroupedTx(tx, list.id));
|
||||
|
||||
} else if (settings.listCid) {
|
||||
const list = await lists.getByCidTx(tx, contextHelpers.getAdminContext(), settings.listCid);
|
||||
this.listsById.set(list.id, list);
|
||||
this.listsByCid.set(list.cid, list);
|
||||
this.listsFieldsGrouped.set(list.id, await fields.listGroupedTx(tx, list.id));
|
||||
|
||||
} else if (this.campaign && this.campaign.lists) {
|
||||
for (const listSpec of this.campaign.lists) {
|
||||
const list = await lists.getByIdTx(tx, contextHelpers.getAdminContext(), listSpec.list);
|
||||
|
@ -123,11 +121,22 @@ class MessageSender {
|
|||
} else if (settings.html !== undefined) {
|
||||
this.html = settings.html;
|
||||
this.text = settings.text;
|
||||
this.tagLanguage = settings.tagLanguage;
|
||||
|
||||
} else if (this.campaign && this.campaign.source === CampaignSource.TEMPLATE) {
|
||||
this.template = await templates.getByIdTx(tx, contextHelpers.getAdminContext(), this.campaign.data.sourceTemplate, false);
|
||||
this.html = this.template.html;
|
||||
this.text = this.template.text;
|
||||
this.tagLanguage = this.template.tag_language;
|
||||
|
||||
} else if (this.campaign && (this.campaign.source === CampaignSource.CUSTOM || this.campaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE || this.campaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN)) {
|
||||
this.html = this.campaign.data.sourceCustom.html;
|
||||
this.text = this.campaign.data.sourceCustom.text;
|
||||
this.tagLanguage = this.campaign.data.sourceCustom.tag_language;
|
||||
}
|
||||
|
||||
enforce(this.renderedHtml || (this.campaign && this.campaign.source === CampaignSource.URL) || this.tagLanguage);
|
||||
|
||||
if (settings.subject !== undefined) {
|
||||
this.subject = settings.subject;
|
||||
} else if (this.campaign && this.campaign.subject !== undefined) {
|
||||
|
@ -155,40 +164,25 @@ class MessageSender {
|
|||
text = this.text;
|
||||
renderTags = true;
|
||||
|
||||
} else if (campaign) {
|
||||
if (campaign.source === CampaignSource.URL) {
|
||||
const form = tools.getMessageLinks(campaign, list, subscriptionGrouped);
|
||||
for (const key in mergeTags) {
|
||||
form[key] = mergeTags[key];
|
||||
}
|
||||
|
||||
const response = await request.post({
|
||||
uri: campaign.sourceUrl,
|
||||
form,
|
||||
resolveWithFullResponse: true
|
||||
});
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`Received status code ${httpResponse.statusCode} from ${campaign.sourceUrl}`);
|
||||
}
|
||||
|
||||
html = response.body;
|
||||
text = '';
|
||||
renderTags = false;
|
||||
|
||||
} else if (campaign.source === CampaignSource.CUSTOM || campaign.source === CampaignSource.CUSTOM_FROM_CAMPAIGN || campaign.source === CampaignSource.CUSTOM_FROM_TEMPLATE) {
|
||||
html = campaign.data.sourceCustom.html;
|
||||
text = campaign.data.sourceCustom.text;
|
||||
renderTags = true;
|
||||
|
||||
} else if (campaign.source === CampaignSource.TEMPLATE) {
|
||||
const template = this.template;
|
||||
html = template.html;
|
||||
text = template.text;
|
||||
renderTags = true;
|
||||
} else if (campaign && campaign.source === CampaignSource.URL) {
|
||||
const form = tools.getMessageLinks(campaign, list, subscriptionGrouped);
|
||||
for (const key in mergeTags) {
|
||||
form[key] = mergeTags[key];
|
||||
}
|
||||
|
||||
html = await links.updateLinks(campaign, list, subscriptionGrouped, mergeTags, html);
|
||||
const response = await request.post({
|
||||
uri: campaign.sourceUrl,
|
||||
form,
|
||||
resolveWithFullResponse: true
|
||||
});
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`Received status code ${httpResponse.statusCode} from ${campaign.sourceUrl}`);
|
||||
}
|
||||
|
||||
html = response.body;
|
||||
text = '';
|
||||
renderTags = false;
|
||||
}
|
||||
|
||||
const attachments = this.attachments.slice();
|
||||
|
@ -204,11 +198,23 @@ class MessageSender {
|
|||
});
|
||||
}
|
||||
|
||||
html = renderTags ? tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, html, true) : html;
|
||||
|
||||
text = (text || '').trim()
|
||||
? (renderTags ? tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, text) : text)
|
||||
: htmlToText.fromString(html, {wordwrap: 130});
|
||||
if (renderTags) {
|
||||
if (this.campaign) {
|
||||
html = await links.updateLinks(html, this.tagLanguage, mergeTags, campaign, list, subscriptionGrouped);
|
||||
}
|
||||
|
||||
html = tools.formatCampaignTemplate(html, this.tagLanguage, mergeTags, true, campaign, list, subscriptionGrouped);
|
||||
}
|
||||
|
||||
const generateText = !!(text || '').trim();
|
||||
if (generateText) {
|
||||
text = htmlToText.fromString(html, {wordwrap: 130});
|
||||
} else {
|
||||
if (renderTags) {
|
||||
text = tools.formatCampaignTemplate(text, this.tagLanguage, mergeTags, false, campaign, list, subscriptionGrouped)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
html,
|
||||
|
@ -220,7 +226,7 @@ class MessageSender {
|
|||
_getExtraTags(campaign) {
|
||||
const tags = {};
|
||||
|
||||
if (campaign.type === CampaignType.RSS_ENTRY) {
|
||||
if (campaign && campaign.type === CampaignType.RSS_ENTRY) {
|
||||
const rssEntry = campaign.data.rssEntry;
|
||||
tags['RSS_ENTRY_TITLE'] = rssEntry.title;
|
||||
tags['RSS_ENTRY_DATE'] = rssEntry.date;
|
||||
|
@ -234,26 +240,10 @@ class MessageSender {
|
|||
return tags;
|
||||
}
|
||||
|
||||
async initByCampaignCid(campaignCid) {
|
||||
await this._init({type: MessageType.REGULAR, campaignCid});
|
||||
}
|
||||
|
||||
async initByCampaignId(campaignId) {
|
||||
await this._init({type: MessageType.REGULAR, campaignId});
|
||||
}
|
||||
|
||||
async getMessage(listCid, subscriptionCid) {
|
||||
enforce(this.type === MessageType.REGULAR);
|
||||
|
||||
const list = this.listsByCid.get(listCid);
|
||||
const subscriptionGrouped = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, subscriptionCid);
|
||||
const flds = this.listsFieldsGrouped.get(list.id);
|
||||
const campaign = this.campaign;
|
||||
const mergeTags = fields.getMergeTags(flds, subscriptionGrouped, this._getExtraTags(campaign));
|
||||
|
||||
return await this._getMessage(list, subscriptionGrouped, mergeTags, false);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
subData is one of:
|
||||
|
@ -304,55 +294,59 @@ class MessageSender {
|
|||
|
||||
message = await this._getMessage(list, subscriptionGrouped, mergeTags, true);
|
||||
|
||||
const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.');
|
||||
|
||||
let listUnsubscribe = null;
|
||||
if (!list.listunsubscribe_disabled) {
|
||||
listUnsubscribe = campaign.unsubscribe_url
|
||||
? tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, campaign.unsubscribe_url)
|
||||
listUnsubscribe = campaign && campaign.unsubscribe_url
|
||||
? tools.formatCampaignTemplate(campaign.unsubscribe_url, this.tagLanguage, mergeTags, false, campaign, list, subscriptionGrouped)
|
||||
: getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscriptionGrouped.cid);
|
||||
}
|
||||
|
||||
to = {
|
||||
name: list.to_name === null ? undefined : tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, list.to_name, false),
|
||||
name: list.to_name === null ? undefined : tools.formatCampaignTemplate(list.to_name, toNameTagLangauge, mergeTags, false, campaign, list, subscriptionGrouped),
|
||||
address: subscriptionGrouped.email
|
||||
};
|
||||
|
||||
subject = tools.formatMessage(campaign, list, subscriptionGrouped, mergeTags, this.subject, false);
|
||||
subject = this.subject;
|
||||
|
||||
if (this.useVerp) {
|
||||
envelope = {
|
||||
from: campaignAddress + '@' + sendConfiguration.verp_hostname,
|
||||
to: subscriptionGrouped.email
|
||||
};
|
||||
}
|
||||
|
||||
if (this.useVerpSenderHeader) {
|
||||
sender = campaignAddress + '@' + sendConfiguration.verp_hostname;
|
||||
if (this.tagLanguage) {
|
||||
subject = tools.formatCampaignTemplate(this.subject, this.tagLanguage, mergeTags, false, campaign, list, subscriptionGrouped);
|
||||
}
|
||||
|
||||
headers = {
|
||||
'x-fbl': campaignAddress,
|
||||
// custom header for SparkPost
|
||||
'x-msys-api': JSON.stringify({
|
||||
campaign_id: campaignAddress
|
||||
}),
|
||||
// custom header for SendGrid
|
||||
'x-smtpapi': JSON.stringify({
|
||||
unique_args: {
|
||||
campaign_id: campaignAddress
|
||||
}
|
||||
}),
|
||||
// custom header for Mailgun
|
||||
'x-mailgun-variables': JSON.stringify({
|
||||
campaign_id: campaignAddress
|
||||
}),
|
||||
'List-ID': {
|
||||
prepared: true,
|
||||
value: libmime.encodeWords(list.name) + ' <' + list.cid + '.' + getPublicUrl() + '>'
|
||||
}
|
||||
};
|
||||
|
||||
if (campaign) {
|
||||
const campaignAddress = [campaign.cid, list.cid, subscriptionGrouped.cid].join('.');
|
||||
|
||||
if (this.useVerp) {
|
||||
envelope = {
|
||||
from: campaignAddress + '@' + sendConfiguration.verp_hostname,
|
||||
to: subscriptionGrouped.email
|
||||
};
|
||||
}
|
||||
|
||||
if (this.useVerpSenderHeader) {
|
||||
sender = campaignAddress + '@' + sendConfiguration.verp_hostname;
|
||||
}
|
||||
|
||||
headers['x-fbl'] = campaignAddress;
|
||||
headers['x-msys-api'] = JSON.stringify({
|
||||
campaign_id: campaignAddress
|
||||
});
|
||||
headers['x-smtpapi'] = JSON.stringify({
|
||||
unique_args: {
|
||||
campaign_id: campaignAddress
|
||||
}
|
||||
});
|
||||
headers['x-mailgun-variables'] = JSON.stringify({
|
||||
campaign_id: campaignAddress
|
||||
});
|
||||
}
|
||||
|
||||
listHeader = {
|
||||
unsubscribe: listUnsubscribe
|
||||
};
|
||||
|
@ -375,7 +369,7 @@ class MessageSender {
|
|||
await mailer.throttleWait();
|
||||
|
||||
const getOverridable = key => {
|
||||
if (campaign && sendConfiguration[key + '_overridable'] && this.campaign[key + '_override'] !== null) {
|
||||
if (campaign && sendConfiguration[key + '_overridable'] && campaign[key + '_override'] !== null) {
|
||||
return campaign[key + '_override'] || '';
|
||||
} else {
|
||||
return sendConfiguration[key] || '';
|
||||
|
@ -458,7 +452,7 @@ class MessageSender {
|
|||
enforce(subscriptionGrouped);
|
||||
|
||||
await knex('campaign_messages').insert({
|
||||
campaign: this.campaign.id,
|
||||
campaign: campaign.id,
|
||||
list: list.id,
|
||||
subscription: subscriptionGrouped.id,
|
||||
send_configuration: sendConfiguration.id,
|
||||
|
@ -468,7 +462,31 @@ class MessageSender {
|
|||
updated: now
|
||||
});
|
||||
|
||||
} else if (msgType === MessageType.TRIGGERED || msgType === MessageType.TEST || msgType === MessageType.SUBSCRIPTION) {
|
||||
}
|
||||
|
||||
if (campaign && msgType === MessageType.TEST) {
|
||||
enforce(list);
|
||||
enforce(subscriptionGrouped);
|
||||
|
||||
try {
|
||||
// Insert an entry to test_messages. This allows us to remember test sends to lists that are not
|
||||
// listed in the campaign - see the check in getMessage
|
||||
await knex('test_messages').insert({
|
||||
campaign: campaign.id,
|
||||
list: list.id,
|
||||
subscription: subscriptionGrouped.id
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
// The entry is already there, so we can ignore this error
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msgType === MessageType.TRIGGERED || msgType === MessageType.TEST || msgType === MessageType.SUBSCRIPTION) {
|
||||
|
||||
if (subData.attachments) {
|
||||
for (const attachment of subData.attachments) {
|
||||
try {
|
||||
|
@ -515,6 +533,7 @@ async function sendQueuedMessage(queuedMessage) {
|
|||
html: msgData.html,
|
||||
text: msgData.text,
|
||||
subject: msgData.subject,
|
||||
tagLanguage: msgData.tagLanguage,
|
||||
renderedHtml: msgData.renderedHtml,
|
||||
renderedText: msgData.renderedText
|
||||
});
|
||||
|
@ -588,9 +607,51 @@ async function queueSubscriptionMessage(sendConfigurationId, to, subject, encryp
|
|||
senders.scheduleCheck();
|
||||
}
|
||||
|
||||
async function getMessage(campaignCid, listCid, subscriptionCid) {
|
||||
const cs = new MessageSender();
|
||||
await cs._init({type: MessageType.REGULAR, campaignCid, listCid});
|
||||
|
||||
const campaign = cs.campaign;
|
||||
const list = cs.listsByCid.get(listCid);
|
||||
|
||||
const subscriptionGrouped = await subscriptions.getByCid(contextHelpers.getAdminContext(), list.id, subscriptionCid);
|
||||
|
||||
let listOk = false;
|
||||
|
||||
for (const listSpec of campaign.lists) {
|
||||
if (list.id === listSpec.list) {
|
||||
// This means we send to a list that is associated with the campaign
|
||||
listOk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!listOk) {
|
||||
const row = await knex('test_messages').where({
|
||||
campaign: campaign.id,
|
||||
list: list.id,
|
||||
subscription: subscriptionGrouped.id
|
||||
}).first();
|
||||
|
||||
if (row) {
|
||||
listOk = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!listOk) {
|
||||
throw new Error('Message not found');
|
||||
}
|
||||
|
||||
const flds = cs.listsFieldsGrouped.get(list.id);
|
||||
const mergeTags = fields.getMergeTags(flds, subscriptionGrouped, cs._getExtraTags(campaign));
|
||||
|
||||
return await cs._getMessage(list, subscriptionGrouped, mergeTags, false);
|
||||
}
|
||||
|
||||
module.exports.MessageSender = MessageSender;
|
||||
module.exports.MessageType = MessageType;
|
||||
module.exports.sendQueuedMessage = sendQueuedMessage;
|
||||
module.exports.queueCampaignMessageTx = queueCampaignMessageTx;
|
||||
module.exports.queueSubscriptionMessage = queueSubscriptionMessage;
|
||||
module.exports.dropQueuedMessage = dropQueuedMessage;
|
||||
module.exports.dropQueuedMessage = dropQueuedMessage;
|
||||
module.exports.getMessage = getMessage;
|
|
@ -6,7 +6,7 @@ const settings = require('../models/settings');
|
|||
const {getTrustedUrl, getPublicUrl} = require('./urls');
|
||||
const { tUI, tMark } = require('./translate');
|
||||
const contextHelpers = require('./context-helpers');
|
||||
const {getFieldColumn} = require('../../shared/lists');
|
||||
const {getFieldColumn, toNameTagLangauge} = require('../../shared/lists');
|
||||
const forms = require('../models/forms');
|
||||
const messageSender = require('./message-sender');
|
||||
const tools = require('./tools');
|
||||
|
@ -118,7 +118,7 @@ async function _sendMail(list, email, template, locale, subjectKey, relativeUrls
|
|||
await messageSender.queueSubscriptionMessage(
|
||||
list.send_configuration,
|
||||
{
|
||||
name: list.to_name === null ? undefined : tools.formatTemplate(list.to_name, {}, mergeTags, false),
|
||||
name: list.to_name === null ? undefined : tools.formatTemplate(list.to_name, toNameTagLangauge, mergeTags, false),
|
||||
address: email
|
||||
},
|
||||
tUI(subjectKey, locale, { list: list.name }),
|
||||
|
|
|
@ -18,6 +18,8 @@ const fs = require('fs-extra');
|
|||
const { JSDOM } = require('jsdom');
|
||||
const { tUI, tLog, getLangCodeFromExpressLocale } = require('./translate');
|
||||
|
||||
const {TagLanguages} = require('../../shared/templates');
|
||||
|
||||
|
||||
const templates = new Map();
|
||||
|
||||
|
@ -142,23 +144,18 @@ function validateEmailGetMessage(result, address, language) {
|
|||
}
|
||||
}
|
||||
|
||||
function formatMessage(campaign, list, subscription, mergeTags, message, isHTML) {
|
||||
const links = campaign && list && subscription ? getMessageLinks(campaign, list, subscription) : {};
|
||||
return formatTemplate(message, links, mergeTags, isHTML);
|
||||
function formatCampaignTemplate(source, tagLanguage, mergeTags, isHTML, campaign, list, subscription) {
|
||||
const links = getMessageLinks(campaign, list, subscription);
|
||||
mergeTags = {...mergeTags, ...links};
|
||||
return formatTemplate(source, tagLanguage, mergeTags, isHTML);
|
||||
}
|
||||
|
||||
function formatTemplate(template, links, mergeTags, isHTML) {
|
||||
if (!links && !mergeTags) { return template; }
|
||||
function _formatTemplateSimple(source, mergeTags, isHTML) {
|
||||
if (!mergeTags) { return source; }
|
||||
|
||||
const getValue = fullKey => {
|
||||
const keys = (fullKey || '').split('.');
|
||||
|
||||
if (links && links.hasOwnProperty(keys[0])) {
|
||||
return links[keys[0]];
|
||||
}
|
||||
|
||||
if (!mergeTags) { return false; }
|
||||
|
||||
let value = mergeTags;
|
||||
while (keys.length > 0) {
|
||||
let key = keys.shift();
|
||||
|
@ -176,7 +173,7 @@ function formatTemplate(template, links, mergeTags, isHTML) {
|
|||
}) : (containsHTML ? htmlToText.fromString(value) : value);
|
||||
};
|
||||
|
||||
return template.replace(/\[([a-z0-9_.]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
|
||||
return source.replace(/\[([a-z0-9_.]+)(?:\/([^\]]+))?\]/ig, (match, identifier, fallback) => {
|
||||
let value = getValue(identifier);
|
||||
if (value === false) {
|
||||
return match;
|
||||
|
@ -186,6 +183,21 @@ function formatTemplate(template, links, mergeTags, isHTML) {
|
|||
});
|
||||
}
|
||||
|
||||
function _formatTemplateHbs(source, mergeTags, isHTML) {
|
||||
const renderer = hbs.handlebars.compile(source);
|
||||
const options = {};
|
||||
|
||||
return renderer(mergeTags, options);
|
||||
}
|
||||
|
||||
function formatTemplate(source, tagLanguage, mergeTags, isHTML) {
|
||||
if (tagLanguage === TagLanguages.SIMPLE) {
|
||||
return _formatTemplateSimple(source, mergeTags, isHTML)
|
||||
} else if (tagLanguage === TagLanguages.HBS) {
|
||||
return _formatTemplateHbs(source, mergeTags, isHTML)
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareHtml(html) {
|
||||
const { window } = new JSDOM(html);
|
||||
|
||||
|
@ -212,14 +224,26 @@ async function prepareHtml(html) {
|
|||
}
|
||||
|
||||
function getMessageLinks(campaign, list, subscription) {
|
||||
return {
|
||||
LINK_UNSUBSCRIBE: getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?c=' + campaign.cid),
|
||||
LINK_PREFERENCES: getPublicUrl('/subscription/' + list.cid + '/manage/' + subscription.cid),
|
||||
LINK_BROWSER: getPublicUrl('/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid),
|
||||
CAMPAIGN_ID: campaign.cid,
|
||||
LIST_ID: list.cid,
|
||||
SUBSCRIPTION_ID: subscription.cid
|
||||
};
|
||||
const result = {};
|
||||
|
||||
if (list && subscription) {
|
||||
if (campaign) {
|
||||
result.LINK_UNSUBSCRIBE = getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?c=' + campaign.cid);
|
||||
result.LINK_BROWSER = getPublicUrl('/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid);
|
||||
} else {
|
||||
result.LINK_UNSUBSCRIBE = getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid);
|
||||
}
|
||||
|
||||
result.LINK_PREFERENCES = getPublicUrl('/subscription/' + list.cid + '/manage/' + subscription.cid);
|
||||
result.LIST_ID = list.cid;
|
||||
result.SUBSCRIPTION_ID = subscription.cid;
|
||||
}
|
||||
|
||||
if (campaign) {
|
||||
result.CAMPAIGN_ID = campaign.cid;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -228,7 +252,7 @@ module.exports = {
|
|||
getTemplate,
|
||||
prepareHtml,
|
||||
getMessageLinks,
|
||||
formatMessage,
|
||||
formatCampaignTemplate,
|
||||
formatTemplate
|
||||
};
|
||||
|
||||
|
|
|
@ -888,7 +888,7 @@ async function _changeStatus(context, campaignId, permittedCurrentStates, newSta
|
|||
|
||||
|
||||
async function start(context, campaignId, startAt) {
|
||||
await _changeStatus(context, campaignId, [CampaignStatus.IDLE, CampaignStatus.PAUSED, CampaignStatus.FINISHED], CampaignStatus.SCHEDULED, 'Cannot start campaign until it is in IDLE, PAUSED, or FINISHED state', startAt);
|
||||
await _changeStatus(context, campaignId, [CampaignStatus.IDLE, CampaignStatus.SCHEDULED, CampaignStatus.PAUSED, CampaignStatus.FINISHED], CampaignStatus.SCHEDULED, 'Cannot start campaign until it is in IDLE, PAUSED, or FINISHED state', startAt);
|
||||
}
|
||||
|
||||
async function stop(context, campaignId) {
|
||||
|
@ -990,8 +990,9 @@ async function testSend(context, data) {
|
|||
const messageData = {
|
||||
campaignId: campaignId,
|
||||
subject: data.subjectPrepend + campaign.subject + data.subjectAppend,
|
||||
html: data.html, // The html and text may be undefined
|
||||
html: data.html, // The html, text and tagLanguage may be undefined
|
||||
text: data.text,
|
||||
tagLanguage: data.tagLanguage,
|
||||
attachments: []
|
||||
};
|
||||
|
||||
|
@ -1048,7 +1049,8 @@ async function testSend(context, data) {
|
|||
const messageData = {
|
||||
subject: 'Test',
|
||||
html: data.html,
|
||||
text: data.text
|
||||
text: data.text,
|
||||
tagLanguage: data.tagLanguage
|
||||
};
|
||||
|
||||
const list = await lists.getByCidTx(tx, context, data.listCid);
|
||||
|
|
|
@ -140,10 +140,10 @@ async function addOrGet(campaignId, url) {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateLinks(campaign, list, subscription, mergeTags, message) {
|
||||
if ((campaign.open_tracking_disabled && campaign.click_tracking_disabled) || !message || !message.trim()) {
|
||||
async function updateLinks(source, tagLanguage, mergeTags, campaign, list, subscription) {
|
||||
if ((campaign.open_tracking_disabled && campaign.click_tracking_disabled) || !source || !source.trim()) {
|
||||
// tracking is disabled, do not modify the message
|
||||
return message;
|
||||
return source;
|
||||
}
|
||||
|
||||
// insert tracking image
|
||||
|
@ -151,12 +151,12 @@ async function updateLinks(campaign, list, subscription, mergeTags, message) {
|
|||
let inserted = false;
|
||||
const imgUrl = getPublicUrl(`/links/${campaign.cid}/${list.cid}/${subscription.cid}`);
|
||||
const img = '<img src="' + imgUrl + '" width="1" height="1" alt="mt">';
|
||||
message = message.replace(/<\/body\b/i, match => {
|
||||
source = source.replace(/<\/body\b/i, match => {
|
||||
inserted = true;
|
||||
return img + match;
|
||||
});
|
||||
if (!inserted) {
|
||||
message = message + img;
|
||||
source = source + img;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ async function updateLinks(campaign, list, subscription, mergeTags, message) {
|
|||
|
||||
const urlsToBeReplaced = new Set();
|
||||
|
||||
message.replace(re, (match, prefix, encodedUrl) => {
|
||||
source.replace(re, (match, prefix, encodedUrl) => {
|
||||
const url = he.decode(encodedUrl, {isAttributeValue: true});
|
||||
urlsToBeReplaced.add(url);
|
||||
});
|
||||
|
@ -173,19 +173,19 @@ async function updateLinks(campaign, list, subscription, mergeTags, message) {
|
|||
const urls = new Map(); // url -> {id, cid} (as returned by add)
|
||||
for (const url of urlsToBeReplaced) {
|
||||
// url might include variables, need to rewrite those just as we do with message content
|
||||
const expanedUrl = tools.formatMessage(campaign, list, subscription, mergeTags, url);
|
||||
const expanedUrl = tools.formatCampaignTemplate(url, tagLanguage, mergeTags, false, campaign, list, subscription);
|
||||
const link = await addOrGet(campaign.id, expanedUrl);
|
||||
urls.set(url, link);
|
||||
}
|
||||
|
||||
message = message.replace(re, (match, prefix, encodedUrl) => {
|
||||
source = source.replace(re, (match, prefix, encodedUrl) => {
|
||||
const url = he.decode(encodedUrl, {isAttributeValue: true});
|
||||
const link = urls.get(url);
|
||||
return prefix + (link ? getPublicUrl(`/links/${campaign.cid}/${list.cid}/${subscription.cid}/${link.cid}`) : url);
|
||||
});
|
||||
}
|
||||
|
||||
return message;
|
||||
return source;
|
||||
}
|
||||
|
||||
module.exports.LinkId = LinkId;
|
||||
|
|
|
@ -158,6 +158,7 @@ const MAX_EMAIL_COUNT = 100;
|
|||
async function sendAsTransactionalEmail(context, templateId, sendConfigurationId, emails, subject, mergeTags) {
|
||||
// TODO - Update this to use MessageSender.queueMessageTx (with renderedHtml and renderedText)
|
||||
|
||||
/*
|
||||
if (emails.length > MAX_EMAIL_COUNT) {
|
||||
throw new Error(`Cannot send more than ${MAX_EMAIL_COUNT} emails at once`);
|
||||
}
|
||||
|
@ -182,6 +183,7 @@ async function sendAsTransactionalEmail(context, templateId, sendConfigurationId
|
|||
};
|
||||
|
||||
const html = tools.formatTemplate(
|
||||
TODO - tag langauge
|
||||
template.html,
|
||||
null,
|
||||
variables,
|
||||
|
@ -190,6 +192,7 @@ async function sendAsTransactionalEmail(context, templateId, sendConfigurationId
|
|||
|
||||
const text = (template.text || '').trim()
|
||||
? tools.formatTemplate(
|
||||
TODO - tag langauge
|
||||
template.text,
|
||||
null,
|
||||
variables,
|
||||
|
@ -210,6 +213,7 @@ async function sendAsTransactionalEmail(context, templateId, sendConfigurationId
|
|||
);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
'use strict';
|
||||
|
||||
const router = require('../lib/router-async').create();
|
||||
const {MessageSender} = require('../lib/message-sender');
|
||||
const messageSender = require('../lib/message-sender');
|
||||
|
||||
|
||||
router.get('/:campaign/:list/:subscription', (req, res, next) => {
|
||||
const cs = new MessageSender();
|
||||
cs.initByCampaignCid(req.params.campaign)
|
||||
.then(() => cs.getMessage(req.params.list, req.params.subscription))
|
||||
messageSender.getMessage(req.params.campaign, req.params.list, req.params.subscription)
|
||||
.then(result => {
|
||||
const {html} = result;
|
||||
|
||||
|
|
14
server/setup/knex/migrations/20190705220000_test_messages.js
Normal file
14
server/setup/knex/migrations/20190705220000_test_messages.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
exports.up = (knex, Promise) => (async() => {
|
||||
await knex.schema.raw('CREATE TABLE `test_messages` (\n' +
|
||||
' `id` int(10) unsigned NOT NULL AUTO_INCREMENT,\n' +
|
||||
' `campaign` int(10) unsigned NOT NULL,\n' +
|
||||
' `list` int(10) unsigned NOT NULL,\n' +
|
||||
' `subscription` int(10) unsigned NOT NULL,\n' +
|
||||
' `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n' +
|
||||
' PRIMARY KEY (`id`),\n' +
|
||||
' UNIQUE KEY `cls` (`campaign`, `list`, `subscription`)\n' +
|
||||
') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n');
|
||||
})();
|
||||
|
||||
exports.down = (knex, Promise) => (async() => {
|
||||
})();
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const {TagLanguages} = require('./templates');
|
||||
|
||||
const UnsubscriptionMode = {
|
||||
MIN: 0,
|
||||
|
||||
|
@ -42,10 +44,13 @@ function getFieldColumn(field) {
|
|||
return field.column || 'grouped_' + field.id;
|
||||
}
|
||||
|
||||
const toNameTagLangauge = TagLanguages.SIMPLE;
|
||||
|
||||
module.exports = {
|
||||
UnsubscriptionMode,
|
||||
SubscriptionStatus,
|
||||
SubscriptionSource,
|
||||
FieldWizard,
|
||||
getFieldColumn
|
||||
getFieldColumn,
|
||||
toNameTagLangauge
|
||||
};
|
|
@ -1,14 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const {TagLanguages} = require('./templates');
|
||||
|
||||
function renderTag(tagLanguage, tag) {
|
||||
if (tagLanguage === TagLanguages.SIMPLE) {
|
||||
return `[${tag}]`;
|
||||
} else if (tagLanguage === TagLanguages.HBS) {
|
||||
return `{{${tag}}}`;
|
||||
}
|
||||
}
|
||||
const {renderTag} = require('./templates');
|
||||
|
||||
function getVersafix(tagLanguage) {
|
||||
const tg = tag => renderTag(tagLanguage, tag);
|
||||
|
|
|
@ -7,6 +7,14 @@ const TagLanguages = {
|
|||
|
||||
const allTagLanguages = [TagLanguages.SIMPLE, TagLanguages.HBS];
|
||||
|
||||
function renderTag(tagLanguage, tag) {
|
||||
if (tagLanguage === TagLanguages.SIMPLE) {
|
||||
return `[${tag}]`;
|
||||
} else if (tagLanguage === TagLanguages.HBS) {
|
||||
return `{{${tag}}}`;
|
||||
}
|
||||
}
|
||||
|
||||
function _getBases(trustedBaseUrl, sandboxBaseUrl, publicBaseUrl) {
|
||||
if (trustedBaseUrl.endsWith('/')) {
|
||||
trustedBaseUrl = trustedBaseUrl.substring(0, trustedBaseUrl.length - 1);
|
||||
|
@ -67,5 +75,6 @@ module.exports = {
|
|||
unbase,
|
||||
getMergeTagsForBases,
|
||||
TagLanguages,
|
||||
allTagLanguages
|
||||
allTagLanguages,
|
||||
renderTag
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue