Skip to content

Commit 0fbeed4

Browse files
committed
Fixed small bugs connected with changing Mosaico template for an existing campaign/template.
Added new merge tags: - LINK_PUBLIC_SUBSCRIBE - LIST_ID_<index> - PUBLIC_LIST_ID_<index> Among others, this allows mixing private and public lists in a campaign and have the subscribe link to always point to the first/n-th public list even for subscribers in private lists.
1 parent 9a70387 commit 0fbeed4

File tree

8 files changed

+81
-24
lines changed

8 files changed

+81
-24
lines changed

client/src/Home.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export default class List extends Component {
2525
return (
2626
<div>
2727
<h2>{t('mailtrain2')}</h2>
28-
<div>{t('build') + ' 2021-05-24-1621'}</div>
28+
<div>{t('build') + ' 2021-05-25-0915'}</div>
2929
<p>{mailtrainConfig.shoutout}</p>
3030
</div>
3131
);

client/src/campaigns/Content.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,12 @@ export default class CustomContent extends Component {
139139
localValidateFormValues(state) {
140140
const t = this.props.t;
141141

142+
for (const key of state.keys()) {
143+
state.setIn([key, 'error'], null);
144+
}
145+
142146
if (!state.getIn(['data_sourceCustom_tag_language', 'value'])) {
143147
state.setIn(['data_sourceCustom_tag_language', 'error'], t('tagLanguageMustBeSelected'));
144-
} else {
145-
state.setIn(['data_sourceCustom_tag_language', 'error'], null);
146148
}
147149

148150
const customTemplateTypeKey = state.getIn(['data_sourceCustom_type', 'value']);

client/src/lib/sandboxed-mosaico-root.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class MosaicoSandbox extends Component {
3535
}
3636

3737
async exportState(method, params) {
38+
if (!this.viewModel) {
39+
// If something fails (e.g. the mosaico template does not exists, the viewModel is not defined
40+
return null;
41+
}
42+
3843
const trustedUrlBase = getTrustedUrl();
3944
const sandboxUrlBase = getSandboxUrl();
4045
const publicUrlBase = getPublicUrl();

client/src/templates/helpers.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,14 @@ export function getEditForm(owner, typeKey, prefix = '') {
713713
</tr>
714714
</thead>
715715
<tbody>
716+
<tr>
717+
<th scope="row">
718+
{tg('LINK_PUBLIC_SUBSCRIBE')}
719+
</th>
720+
<td>
721+
<Trans>URL that points to the subscribe page of the first list in the campaign that allows public subscriptions</Trans>
722+
</td>
723+
</tr>
716724
<tr>
717725
<th scope="row">
718726
{tg('LINK_UNSUBSCRIBE')}
@@ -758,7 +766,23 @@ export function getEditForm(owner, typeKey, prefix = '') {
758766
{tg('LIST_ID')}
759767
</th>
760768
<td>
761-
<Trans i18nKey="uniqueIdThatIdentifiesTheListUsedForThis">Unique ID that identifies the list used for this campaign</Trans>
769+
<Trans i18nKey="uniqueIdThatIdentifiesTheListUsedForThis">Unique ID that identifies the list to which the target subscriber belongs</Trans>
770+
</td>
771+
</tr>
772+
<tr>
773+
<th scope="row">
774+
{tg('LIST_ID_<index>')}
775+
</th>
776+
<td>
777+
<Trans>Unique ID that identifies the list. The <code>index</code> is 0-based index of the list in the campaign. <code>LIST_ID_0</code> thus means the first list in the campaign.</Trans>
778+
</td>
779+
</tr>
780+
<tr>
781+
<th scope="row">
782+
{tg('PUBLIC_LIST_ID_<index>')}
783+
</th>
784+
<td>
785+
<Trans>The same as above, but only taking into account the lists for which public subscribe is enabled. <code>PUBLIC_LIST_ID_0</code> thus means the first public list in the campaign.</Trans>
762786
</td>
763787
</tr>
764788
<tr>

mvis/ivis-core

Submodule ivis-core updated 235 files

server/lib/message-sender.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,24 +89,31 @@ class MessageSender {
8989
this.useVerpSenderHeader = this.useVerp && !this.sendConfiguration.verp_disable_sender_header;
9090

9191

92+
// These IFs are not mutually exclusive because there are situations when listId is provided, but we want to collect all lists of the campaign
93+
// in order to support tags like LINK_PUBLIC_SUBSCRIBE, LIST_ID_<index>, PUBLIC_LIST_ID_<index>
9294
if (settings.listId) {
9395
const list = await lists.getByIdTx(tx, contextHelpers.getAdminContext(), settings.listId);
9496
this.listsById.set(list.id, list);
9597
this.listsByCid.set(list.cid, list);
9698
this.listsFieldsGrouped.set(list.id, await fields.listGroupedTx(tx, list.id));
99+
}
97100

98-
} else if (settings.listCid) {
101+
if (settings.listCid && !this.listsByCid.has(settings.listCid)) {
99102
const list = await lists.getByCidTx(tx, contextHelpers.getAdminContext(), settings.listCid);
100103
this.listsById.set(list.id, list);
101104
this.listsByCid.set(list.cid, list);
102105
this.listsFieldsGrouped.set(list.id, await fields.listGroupedTx(tx, list.id));
103106

104-
} else if (this.campaign && this.campaign.lists) {
107+
}
108+
109+
if (this.campaign && this.campaign.lists) {
105110
for (const listSpec of this.campaign.lists) {
106-
const list = await lists.getByIdTx(tx, contextHelpers.getAdminContext(), listSpec.list);
107-
this.listsById.set(list.id, list);
108-
this.listsByCid.set(list.cid, list);
109-
this.listsFieldsGrouped.set(list.id, await fields.listGroupedTx(tx, list.id));
111+
if (!this.listsById.has(listSpec.list)) {
112+
const list = await lists.getByIdTx(tx, contextHelpers.getAdminContext(), listSpec.list);
113+
this.listsById.set(list.id, list);
114+
this.listsByCid.set(list.cid, list);
115+
this.listsFieldsGrouped.set(list.id, await fields.listGroupedTx(tx, list.id));
116+
}
110117
}
111118
}
112119

@@ -196,7 +203,7 @@ class MessageSender {
196203
renderTags = true;
197204

198205
} else if (campaign && campaign.source === CampaignSource.URL) {
199-
const form = tools.getMessageLinks(campaign, list, subscriptionGrouped);
206+
const form = tools.getMessageLinks(campaign, this.listsById, list, subscriptionGrouped);
200207
for (const key in mergeTags) {
201208
form[key] = mergeTags[key];
202209
}
@@ -245,20 +252,20 @@ class MessageSender {
245252

246253

247254
if (renderTags) {
248-
if (this.campaign) {
249-
html = await links.updateLinks(html, this.tagLanguage, mergeTags, campaign, list, subscriptionGrouped);
255+
if (campaign) {
256+
html = await links.updateLinks(html, this.tagLanguage, mergeTags, campaign, this.listsById, list, subscriptionGrouped);
250257
}
251258

252259
// When no list and subscriptionGrouped is provided, formatCampaignTemplate works the same way as formatTemplate
253-
html = tools.formatCampaignTemplate(html, this.tagLanguage, mergeTags, true, campaign, list, subscriptionGrouped);
260+
html = tools.formatCampaignTemplate(html, this.tagLanguage, mergeTags, true, campaign, this.listsById, list, subscriptionGrouped);
254261
}
255262

256263
const generateText = !(text || '').trim();
257264
if (generateText) {
258265
text = htmlToText.fromString(html, {wordwrap: 130});
259266
} else {
260267
// When no list and subscriptionGrouped is provided, formatCampaignTemplate works the same way as formatTemplate
261-
text = tools.formatCampaignTemplate(text, this.tagLanguage, mergeTags, false, campaign, list, subscriptionGrouped)
268+
text = tools.formatCampaignTemplate(text, this.tagLanguage, mergeTags, false, campaign, this.listsById, list, subscriptionGrouped)
262269
}
263270

264271
return {
@@ -348,19 +355,19 @@ class MessageSender {
348355
let listUnsubscribe = null;
349356
if (!list.listunsubscribe_disabled) {
350357
listUnsubscribe = campaign && campaign.unsubscribe_url
351-
? tools.formatCampaignTemplate(campaign.unsubscribe_url, this.tagLanguage, mergeTags, false, campaign, list, subscriptionGrouped)
358+
? tools.formatCampaignTemplate(campaign.unsubscribe_url, this.tagLanguage, mergeTags, false, campaign, this.listsById, list, subscriptionGrouped)
352359
: getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscriptionGrouped.cid);
353360
}
354361

355362
to = {
356-
name: list.to_name === null ? undefined : tools.formatCampaignTemplate(list.to_name, toNameTagLangauge, mergeTags, false, campaign, list, subscriptionGrouped),
363+
name: list.to_name === null ? undefined : tools.formatCampaignTemplate(list.to_name, toNameTagLangauge, mergeTags, false, campaign, this.listsById, list, subscriptionGrouped),
357364
address: subscriptionGrouped.email
358365
};
359366

360367
subject = this.subject;
361368

362369
if (this.tagLanguage) {
363-
subject = tools.formatCampaignTemplate(this.subject, this.tagLanguage, mergeTags, false, campaign, list, subscriptionGrouped);
370+
subject = tools.formatCampaignTemplate(this.subject, this.tagLanguage, mergeTags, false, campaign, this.listsById, list, subscriptionGrouped);
364371
}
365372

366373
headers = {

server/lib/tools.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ function validateEmailGetMessage(result, address, language) {
144144
}
145145
}
146146

147-
function formatCampaignTemplate(source, tagLanguage, mergeTags, isHTML, campaign, list, subscription) {
148-
const links = getMessageLinks(campaign, list, subscription);
147+
function formatCampaignTemplate(source, tagLanguage, mergeTags, isHTML, campaign, campaignListsById, list, subscription) {
148+
const links = getMessageLinks(campaign, campaignListsById, list, subscription);
149149
mergeTags = {...mergeTags, ...links};
150150
return formatTemplate(source, tagLanguage, mergeTags, isHTML);
151151
}
@@ -227,13 +227,32 @@ async function prepareHtml(html) {
227227
return juice(preparedHtml);
228228
}
229229

230-
function getMessageLinks(campaign, list, subscription) {
230+
function getMessageLinks(campaign, campaignListsById, list, subscription) {
231231
const result = {};
232232

233233
if (list && subscription) {
234234
if (campaign) {
235235
result.LINK_UNSUBSCRIBE = getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?c=' + campaign.cid);
236236
result.LINK_BROWSER = getPublicUrl('/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid);
237+
238+
if (campaign.lists) {
239+
let cpgListIdx = 0;
240+
let cpgPublicListIdx = 0;
241+
for (const cpgListSpec of campaign.lists) {
242+
const cpgList = campaignListsById.get(cpgListSpec.list);
243+
244+
result[`LIST_ID_${cpgListIdx}`] = cpgList.cid;
245+
cpgListIdx += 1;
246+
247+
if (cpgList.public_subscribe) {
248+
if (!result.LINK_PUBLIC_SUBSCRIBE) {
249+
result.LINK_PUBLIC_SUBSCRIBE = getPublicUrl('/subscription/' + cpgList.cid)
250+
}
251+
result[`PUBLIC_LIST_ID_${cpgPublicListIdx}`] = cpgList.cid;
252+
cpgPublicListIdx += 1;
253+
}
254+
}
255+
}
237256
} else {
238257
result.LINK_UNSUBSCRIBE = getPublicUrl('/subscription/' + list.cid + '/unsubscribe/' + subscription.cid);
239258
}

server/models/links.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ async function addOrGet(campaignId, url) {
140140
}
141141
}
142142

143-
async function updateLinks(source, tagLanguage, mergeTags, campaign, list, subscription) {
143+
async function updateLinks(source, tagLanguage, mergeTags, campaign, campaignListsById, list, subscription) {
144144
if ((campaign.open_tracking_disabled && campaign.click_tracking_disabled) || !source || !source.trim()) {
145145
// tracking is disabled, do not modify the message
146146
return source;
@@ -173,7 +173,7 @@ async function updateLinks(source, tagLanguage, mergeTags, campaign, list, subsc
173173
const urls = new Map(); // url -> {id, cid} (as returned by add)
174174
for (const url of urlsToBeReplaced) {
175175
// url might include variables, need to rewrite those just as we do with message content
176-
const expanedUrl = encodeURI(tools.formatCampaignTemplate(url, tagLanguage, mergeTags, false, campaign, list, subscription));
176+
const expanedUrl = encodeURI(tools.formatCampaignTemplate(url, tagLanguage, mergeTags, false, campaign, campaignListsById, list, subscription));
177177
const link = await addOrGet(campaign.id, expanedUrl);
178178
urls.set(url, link);
179179
}

0 commit comments

Comments
 (0)