_loadGroups: function (mailing) {
return crmApi('MailingGroup', 'get', {mailing_id: mailing.id})
.then(function (groupResult) {
- mailing.groups = {include: [], exclude: []};
- mailing.mailings = {include: [], exclude: []};
+ mailing.recipients = {};
+ mailing.recipients.groups = {include: [], exclude: [], base: []};
+ mailing.recipients.mailings = {include: [], exclude: []};
_.each(groupResult.values, function (mailingGroup) {
var bucket = (/^civicrm_group/.test(mailingGroup.entity_table)) ? 'groups' : 'mailings';
var entityId = parseInt(mailingGroup.entity_id);
- mailing[bucket][mailingGroup.group_type].push(entityId);
+ mailing.recipients[bucket][mailingGroup.group_type.toLowerCase()].push(entityId);
});
});
},
});
},
// @return Object Mailing (per APIv3)
- create: function create() {
- return {
+ create: function create(params) {
+ var defaults = {
jobs: {}, // {jobId: JobRecord}
+ recipients: {
+ groups: {include: [], exclude: [], base: []},
+ mailings: {include: [], exclude: []}
+ },
name: "",
campaign_id: null,
- from_name: crmFromAddresses.getDefault().author,
- from_email: crmFromAddresses.getDefault().email,
replyto_email: "",
subject: "",
- groups: {include: [], exclude: []},
- mailings: {include: [], exclude: []},
body_html: "",
- body_text: "",
- footer_id: null, // pickDefaultMailComponent('Footer'),
- header_id: null, // pickDefaultMailComponent('Header'),
- visibility: "Public Pages",
- url_tracking: "1",
- dedupe_email: "1",
- forward_replies: "0",
- auto_responder: "0",
- open_tracking: "1",
- override_verp: "1",
- optout_id: pickDefaultMailComponent('OptOut'),
- reply_id: pickDefaultMailComponent('Reply'),
- resubscribe_id: pickDefaultMailComponent('Resubscribe'),
- unsubscribe_id: pickDefaultMailComponent('Unsubscribe')
+ body_text: ""
};
+ return angular.extend({}, defaults, params);
},
// @param mailing Object (per APIv3)
}
},
+ // Search the body, header, and footer for required tokens.
+ // ex: var msgs = findMissingTokens(mailing, 'body_html');
+ findMissingTokens: function(mailing, field) {
+ var missing = {};
+ if (!_.isEmpty(mailing[field])) {
+ var body = '';
+ if (mailing.footer_id) {
+ var footer = _.where(CRM.crmMailing.headerfooterList, {id: mailing.footer_id});
+ body = body + footer[0][field];
+
+ }
+ body = body + mailing[field];
+ if (mailing.header_id) {
+ var header = _.where(CRM.crmMailing.headerfooterList, {id: mailing.header_id});
+ body = body + header[0][field];
+ }
+
+ angular.forEach(CRM.crmMailing.requiredTokens, function(value, token) {
+ if (!_.isObject(value)) {
+ if (body.indexOf('{' + token + '}') < 0) {
+ missing[token] = value;
+ }
+ }
+ else {
+ var count = 0;
+ angular.forEach(value, function(nestedValue, nestedToken) {
+ if (body.indexOf('{' + nestedToken + '}') >= 0) {
+ count++;
+ }
+ });
+ if (count === 0) {
+ angular.extend(missing, value);
+ }
+ }
+ });
+ }
+ return missing;
+ },
+
// Copy all data fields in (mailingFrom) to (mailingTgt) -- except for (excludes)
// ex: crmMailingMgr.mergeInto(newMailing, mailingTemplate, ['subject']);
mergeInto: function mergeInto(mailingTgt, mailingFrom, excludes) {
'replyto_email',
'subject',
'dedupe_email',
- 'groups',
- 'mailings',
+ 'recipients',
'body_html',
'body_text',
'footer_id',
// @param mailing Object (per APIv3)
// @return Promise an object with "subject", "body_text", "body_html"
preview: function preview(mailing) {
- var params = angular.extend({}, mailing, {
+ var params = angular.extend({}, mailing, mailing.recipients, {
options: {force_rollback: 1},
'api.Mailing.preview': {
id: '$value.id'
}
});
+ delete params.recipients; // the content was merged in
return crmApi('Mailing', 'create', params).then(function (result) {
// changes rolled back, so we don't care about updating mailing
return result.values[result.id]['api.Mailing.preview'].values;
previewRecipients: function previewRecipients(mailing, previewLimit) {
// To get list of recipients, we tentatively save the mailing and
// get the resulting recipients -- then rollback any changes.
- var params = angular.extend({}, mailing, {
+ var params = angular.extend({}, mailing, mailing.recipients, {
name: 'placeholder', // for previewing recipients on new, incomplete mailing
subject: 'placeholder', // for previewing recipients on new, incomplete mailing
options: {force_rollback: 1},
'api.email.getvalue': {'return': 'email'}
}
});
+ delete params.recipients; // the content was merged in
return crmApi('Mailing', 'create', params).then(function (recipResult) {
// changes rolled back, so we don't care about updating mailing
return recipResult.values[recipResult.id]['api.MailingRecipients.get'].values;
// @param mailing Object (per APIv3)
// @return Promise
save: function(mailing) {
- var params = angular.extend({}, mailing, {
- 'api.mailing_job.create': 0 // note: exact match to API default
- });
+ var params = angular.extend({}, mailing, mailing.recipients);
// Angular ngModel sometimes treats blank fields as undefined.
angular.forEach(mailing, function(value, key) {
- if (value === undefined) {
+ if (value === undefined || value === null) {
mailing[key] = '';
}
});
delete params.jobs;
+ delete params.recipients; // the content was merged in
+
return crmApi('Mailing', 'create', params).then(function(result) {
if (result.id && !mailing.id) {
mailing.id = result.id;
// @param to Object with either key "email" (string) or "gid" (int)
// @return Promise for a list of delivery reports
sendTest: function (mailing, recipient) {
- var params = angular.extend({}, mailing, {
+ var params = angular.extend({}, mailing, mailing.recipients, {
// options: {force_rollback: 1}, // Test mailings include tracking features, so the mailing must be persistent
'api.Mailing.send_test': {
mailing_id: '$value.id',
delete params.jobs;
+ delete params.recipients; // the content was merged in
+
return crmApi('Mailing', 'create', params).then(function (result) {
if (result.id && !mailing.id) {
mailing.id = result.id;
var p = crmMailingMgr
.preview(mailing)
.then(function (content) {
- var options = {
+ var options = CRM.utils.adjustDialogDefaults({
autoOpen: false,
- modal: true,
title: ts('Subject: %1', {
1: content.subject
})
- };
+ });
result = dialogService.open('previewDialog', templates[mode], content, options);
});
crmStatus({start: ts('Previewing'), success: ''}, p);
};
});
+ angular.module('crmMailing').factory('crmMailingStats', function (crmApi, crmLegacy) {
+ var statTypes = [
+ // {name: 'Recipients', title: ts('Intended Recipients'), searchFilter: '', eventsFilter: '&event=queue'},
+ {name: 'Delivered', title: ts('Successful Deliveries'), searchFilter: '&mailing_delivery_status=Y', eventsFilter: '&event=delivered'},
+ {name: 'Opened', title: ts('Tracked Opens'), searchFilter: '&mailing_open_status=Y', eventsFilter: '&event=opened'},
+ {name: 'Unique Clicks', title: ts('Click-throughs'), searchFilter: '&mailing_click_status=Y', eventsFilter: '&event=click&distinct=1'},
+ // {name: 'Forward', title: ts('Forwards'), searchFilter: '&mailing_forward=1', eventsFilter: '&event=forward'},
+ // {name: 'Replies', title: ts('Replies'), searchFilter: '&mailing_reply_status=Y', eventsFilter: '&event=reply'},
+ {name: 'Bounces', title: ts('Bounces'), searchFilter: '&mailing_delivery_status=N', eventsFilter: '&event=bounce'},
+ {name: 'Unsubscribers', title: ts('Unsubscribes'), searchFilter: '&mailing_unsubscribe=1', eventsFilter: '&event=unsubscribe'}
+ // {name: 'OptOuts', title: ts('Opt-Outs'), searchFilter: '&mailing_optout=1', eventsFilter: '&event=optout'}
+ ];
+
+ return {
+ getStatTypes: function() {
+ return statTypes;
+ },
+
+ /**
+ * @param mailingIds object
+ * List of mailing IDs ({a: 123, b: 456})
+ * @return Promise
+ * List of stats for each mailing
+ * ({a: ...object..., b: ...object...})
+ */
+ getStats: function(mailingIds) {
+ var params = {};
+ angular.forEach(mailingIds, function(mailingId, name) {
+ params[name] = ['Mailing', 'stats', {mailing_id: mailingId}];
+ });
+ return crmApi(params).then(function(result) {
+ var stats = {};
+ angular.forEach(mailingIds, function(mailingId, name) {
+ stats[name] = result[name].values[mailingId];
+ });
+ return stats;
+ });
+ },
+
+ /**
+ * Determine the legacy URL for a report about a given mailing and stat.
+ *
+ * @param mailing object
+ * @param statType object (see statTypes above)
+ * @param view string ('search', 'event', 'report')
+ * @return string|null
+ */
+ getUrl: function getUrl(mailing, statType, view) {
+ switch (view) {
+ case 'events':
+ return crmLegacy.url('civicrm/mailing/report/event',
+ 'reset=1&mid=' + mailing.id + statType.eventsFilter);
+
+ case 'search':
+ return crmLegacy.url('civicrm/contact/search/advanced',
+ 'force=1&mailing_id=' + mailing.id + statType.searchFilter);
+
+ // TODO: case 'report':
+ default:
+ return null;
+ }
+ }
+ };
+ });
+
})(angular, CRM.$, CRM._);