CRM-15578 - Add crmMailingAB2 module (based on crmMailingAB). Add skeletal list/edit...
[civicrm-core.git] / js / angular-crmMailing2-services.js
1 (function (angular, $, _) {
2 var partialUrl = function (relPath) {
3 return CRM.resourceUrls['civicrm'] + '/partials/crmMailing2/' + relPath;
4 };
5
6 // FIXME: surely there's already some helper which can do this in one line?
7 // @return string "YYYY-MM-DD hh:mm:ss"
8 var createNow = function () {
9 var currentdate = new Date();
10 var yyyy = currentdate.getFullYear();
11 var mm = currentdate.getMonth() + 1;
12 mm = mm < 10 ? '0' + mm : mm;
13 var dd = currentdate.getDate();
14 dd = dd < 10 ? '0' + dd : dd;
15 var hh = currentdate.getHours();
16 hh = hh < 10 ? '0' + hh : hh;
17 var min = currentdate.getMinutes();
18 min = min < 10 ? '0' + min : min;
19 var sec = currentdate.getSeconds();
20 sec = sec < 10 ? '0' + sec : sec;
21 return yyyy + "-" + mm + "-" + dd + " " + hh + ":" + min + ":" + sec;
22 };
23
24 // FIXME: Load status ids from DB
25 var APPROVAL_STATUSES = { Approved: "1", Rejected: "2", None: "3" };
26
27 var crmMailing2 = angular.module('crmMailing2');
28
29 // The representation of from/reply-to addresses is inconsistent in the mailing data-model,
30 // so the UI must do some adaptation. The crmFromAddresses provides a richer way to slice/dice
31 // the available "From:" addrs. Records are like the underlying OptionValues -- but add "email"
32 // and "author".
33 crmMailing2.factory('crmFromAddresses', function($q, crmApi) {
34 var emailRegex = /^"(.*)" \<([^@\>]*@[^@\>]*)\>$/;
35 var addrs = _.map(CRM.crmMailing.fromAddress, function(addr){
36 var match = emailRegex.exec(addr.label);
37 return _.extend({}, addr, {
38 email: match ? match[2] : '(INVALID)',
39 author: match ? match[1] : '(INVALID)'
40 });
41 });
42 function first(array) {
43 return (array.length == 0) ? null : array[0];
44 }
45
46 return {
47 getAll: function getAll() {
48 return addrs;
49 },
50 getByAuthorEmail: function getByAuthorEmail(author, email, autocreate) {
51 var result = null;
52 _.each(addrs, function(addr){
53 if (addr.author == author && addr.email == email) {
54 result = addr;
55 }
56 });
57 if (!result && autocreate) {
58 result = {
59 label: '(INVALID) "' + author + '" <' + email + '>',
60 author: author,
61 email: email
62 };
63 addrs.push(result);
64 }
65 return result;
66 },
67 getByEmail: function getByEmail(email) {
68 return first(_.where(addrs, {email: email}));
69 },
70 getByLabel: function(label) {
71 return first(_.where(addrs, {label: label}));
72 },
73 getDefault: function getDefault() {
74 return first(_.where(addrs, {is_default: "1"}));
75 }
76 };
77 });
78
79 crmMailing2.factory('crmMsgTemplates', function($q, crmApi) {
80 var tpls = _.map(CRM.crmMailing.mesTemplate, function(tpl){
81 return _.extend({}, tpl, {
82 //id: tpl parseInt(tpl.id)
83 });
84 });
85 window.tpls = tpls;
86 var lastModifiedTpl = null;
87 return {
88 // @return Promise MessageTemplate (per APIv3)
89 get: function get(id) {
90 id = ''+id; // parseInt(id);
91 var dfr = $q.defer();
92 var tpl = _.where(tpls, {id: id});
93 if (id && tpl && tpl[0]) {
94 dfr.resolve(tpl[0]);
95 } else {
96 dfr.reject(id);
97 }
98 return dfr.promise;
99 },
100 // Save a template
101 // @param tpl MessageTemplate (per APIv3) For new templates, omit "id"
102 // @return Promise MessageTemplate (per APIv3)
103 save: function(tpl) {
104 return crmApi('MessageTemplate', 'create', tpl).then(function(response){
105 if (!tpl.id) {
106 tpl.id = ''+response.id; //parseInt(response.id);
107 tpls.push(tpl);
108 }
109 lastModifiedTpl = tpl
110 return tpl;
111 });
112 },
113 // @return Object MessageTemplate (per APIv3)
114 getLastModifiedTpl: function() {
115 return lastModifiedTpl;
116 },
117 getAll: function getAll() {
118 return tpls;
119 }
120 };
121 });
122
123 // The crmMailingMgr service provides business logic for loading, saving, previewing, etc
124 crmMailing2.factory('crmMailingMgr', function($q, crmApi, crmFromAddresses) {
125 var pickDefaultMailComponent = function pickDefaultMailComponent(type) {
126 var mcs = _.where(CRM.crmMailing.headerfooterList, {
127 component_type:type,
128 is_default: "1"
129 });
130 return (mcs.length >= 1) ? mcs[0].id : null;
131 };
132
133 return {
134 // @param scalar idExpr a number or the literal string 'new'
135 // @return Promise|Object Mailing (per APIv3)
136 getOrCreate: function getOrCreate(idExpr) {
137 return (idExpr == 'new') ? this.create() : this.get(idExpr);
138 },
139 // @return Promise Mailing (per APIv3)
140 get: function get(id) {
141 return crmApi('Mailing', 'getsingle', {id: id}).then(function(mailing){
142 return crmApi('MailingGroup', 'get', {mailing_id: id}).then(function(groupResult){
143 mailing.groups = {include: [], exclude: []};
144 mailing.mailings = {include: [], exclude: []};
145 _.each(groupResult.values, function(mailingGroup) {
146 var bucket = (mailingGroup.entity_table == 'civicrm_group') ? 'groups' : 'mailings';
147 var entityId = parseInt(mailingGroup.entity_id);
148 mailing[bucket][mailingGroup.group_type].push(entityId);
149 });
150 return mailing;
151 });
152 });
153 },
154 // @return Object Mailing (per APIv3)
155 create: function create() {
156 return {
157 name: "revert this", // fixme
158 campaign_id: null,
159 from_name: crmFromAddresses.getDefault().author,
160 from_email: crmFromAddresses.getDefault().email,
161 replyto_email: "",
162 subject: "For {contact.display_name}", // fixme
163 dedupe_email: "1",
164 groups: {include: [2], exclude: [4]}, // fixme
165 mailings: {include: [], exclude: []},
166 body_html: "<b>Hello</b> {contact.display_name}", // fixme
167 body_text: "Hello {contact.display_name}", // fixme
168 footer_id: null, // pickDefaultMailComponent('Footer'),
169 header_id: null, // pickDefaultMailComponent('Header'),
170 visibility: "Public Pages",
171 url_tracking: "1",
172 dedupe_email: "1",
173 forward_replies: "0",
174 auto_responder: "0",
175 open_tracking: "1",
176 override_verp: "1",
177 optout_id: pickDefaultMailComponent('OptOut'),
178 reply_id: pickDefaultMailComponent('Reply'),
179 resubscribe_id: pickDefaultMailComponent('Resubscribe'),
180 unsubscribe_id: pickDefaultMailComponent('Unsubscribe')
181 };
182 },
183
184 // @param mailing Object (per APIv3)
185 // @return Promise
186 'delete': function(mailing) {
187 if (mailing.id) {
188 return crmApi('Mailing', 'delete', {id: mailing.id});
189 } else {
190 var d = $q.defer();
191 d.resolve();
192 return d.promise;
193 }
194 },
195
196 // Copy all data fields in (mailingFrom) to (mailingTgt) -- except for (excludes)
197 // ex: crmMailingMgr.mergeInto(newMailing, mailingTemplate, ['subject']);
198 mergeInto: function mergeInto(mailingTgt, mailingFrom, excludes) {
199 var MAILING_FIELDS = [
200 // always exclude: 'id'
201 'name',
202 'campaign_id',
203 'from_name',
204 'from_email',
205 'replyto_email',
206 'subject',
207 'dedupe_email',
208 'groups',
209 'mailings',
210 'body_html',
211 'body_text',
212 'footer_id',
213 'header_id',
214 'visibility',
215 'url_tracking',
216 'dedupe_email',
217 'forward_replies',
218 'auto_responder',
219 'open_tracking',
220 'override_verp',
221 'optout_id',
222 'reply_id',
223 'resubscribe_id',
224 'unsubscribe_id'
225 ];
226 if (!excludes) {
227 excludes = [];
228 }
229 _.each(MAILING_FIELDS, function (field) {
230 if (!_.contains(excludes, field)) {
231 mailingTgt[field] = mailingFrom[field];
232 }
233 })
234 },
235
236 // @param mailing Object (per APIv3)
237 // @return Promise an object with "subject", "body_text", "body_html"
238 preview: function preview(mailing) {
239 var params = _.extend({}, mailing, {
240 options: {force_rollback: 1},
241 'api.Mailing.preview': {
242 id: '$value.id'
243 }
244 });
245 return crmApi('Mailing', 'create', params).then(function(result){
246 // changes rolled back, so we don't care about updating mailing
247 return result.values[result.id]['api.Mailing.preview'].values;
248 });
249 },
250
251 // @param mailing Object (per APIv3)
252 // @param int previewLimit
253 // @return Promise for a list of recipients (mailing_id, contact_id, api.contact.getvalue, api.email.getvalue)
254 previewRecipients: function previewRecipients(mailing, previewLimit) {
255 // To get list of recipients, we tentatively save the mailing and
256 // get the resulting recipients -- then rollback any changes.
257 var params = _.extend({}, mailing, {
258 options: {force_rollback: 1},
259 'api.mailing_job.create': 1, // note: exact match to API default
260 'api.MailingRecipients.get': {
261 mailing_id: '$value.id',
262 options: { limit: previewLimit },
263 'api.contact.getvalue': {'return': 'display_name'},
264 'api.email.getvalue': {'return': 'email'}
265 }
266 });
267 return crmApi('Mailing', 'create', params).then(function(recipResult){
268 // changes rolled back, so we don't care about updating mailing
269 return recipResult.values[recipResult.id]['api.MailingRecipients.get'].values;
270 });
271 },
272
273 // Save a (draft) mailing
274 // @param mailing Object (per APIv3)
275 // @return Promise
276 save: function(mailing) {
277 var params = _.extend({}, mailing, {
278 'api.mailing_job.create': 0 // note: exact match to API default
279 });
280
281 // WORKAROUND: Mailing.create (aka CRM_Mailing_BAO_Mailing::create()) interprets scheduled_date
282 // as an *intent* to schedule and creates tertiary records. Saving a draft with a scheduled_date
283 // is therefore not allowed. Remove this after fixing Mailing.create's contract.
284 delete params.scheduled_date;
285
286 return crmApi('Mailing', 'create', params).then(function(result){
287 if (result.id && !mailing.id) mailing.id = result.id; // no rollback, so update mailing.id
288 // Perhaps we should reload mailing based on result?
289 return result.values[result.id];
290 });
291 },
292
293 // Schedule/send the mailing
294 // @param mailing Object (per APIv3)
295 // @return Promise
296 submit: function (mailing) {
297 var changes = {
298 approval_date: createNow(),
299 approver_id: CRM.crmMailing.contactid,
300 approval_status_id: APPROVAL_STATUSES.Approved,
301 scheduled_date: mailing.scheduled_date ? mailing.scheduled_date : createNow(),
302 scheduled_id: CRM.crmMailing.contactid
303 };
304 var params = _.extend({}, mailing, changes, {
305 'api.mailing_job.create': 0 // note: exact match to API default
306 });
307 return crmApi('Mailing', 'create', params).then(function (result) {
308 if (result.id && !mailing.id) mailing.id = result.id; // no rollback, so update mailing.id
309 _.extend(mailing, changes); // Perhaps we should reload mailing based on result?
310 return result.values[result.id];
311 });
312 },
313
314 // Immediately send a test message
315 // @param mailing Object (per APIv3)
316 // @param testEmail string
317 // @param testGroup int (id#)
318 // @return Promise for a list of delivery reports
319 sendTest: function(mailing, testEmail, testGroup) {
320 var params = _.extend({}, mailing, {
321 // options: {force_rollback: 1}, // Test mailings include tracking features, so the mailing must be persistent
322 'api.Mailing.send_test': {
323 mailing_id: '$value.id',
324 test_email: testEmail,
325 test_group: testGroup
326 }
327 });
328
329 // WORKAROUND: Mailing.create (aka CRM_Mailing_BAO_Mailing::create()) interprets scheduled_date
330 // as an *intent* to schedule and creates tertiary records. Saving a draft with a scheduled_date
331 // is therefore not allowed. Remove this after fixing Mailing.create's contract.
332 delete params.scheduled_date;
333
334 return crmApi('Mailing', 'create', params).then(function(result){
335 if (result.id && !mailing.id) mailing.id = result.id; // no rollback, so update mailing.id
336 return result.values[result.id]['api.Mailing.send_test'].values;
337 });
338 }
339 };
340 });
341 })(angular, CRM.$, CRM._);