1 (function (angular
, $, _
) {
2 var partialUrl = function (relPath
) {
3 return CRM
.resourceUrls
['civicrm'] + '/partials/crmMailing2/' + relPath
;
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
;
24 // FIXME: Load status ids from DB
25 var APPROVAL_STATUSES
= { Approved
: "1", Rejected
: "2", None
: "3" };
27 var crmMailing2
= angular
.module('crmMailing2');
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"
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)'
42 function first(array
) {
43 return (array
.length
== 0) ? null : array
[0];
47 getAll
: function getAll() {
50 getByAuthorEmail
: function getByAuthorEmail(author
, email
, autocreate
) {
52 _
.each(addrs
, function(addr
){
53 if (addr
.author
== author
&& addr
.email
== email
) {
57 if (!result
&& autocreate
) {
59 label
: '(INVALID) "' + author
+ '" <' + email
+ '>',
67 getByEmail
: function getByEmail(email
) {
68 return first(_
.where(addrs
, {email
: email
}));
70 getByLabel: function(label
) {
71 return first(_
.where(addrs
, {label
: label
}));
73 getDefault
: function getDefault() {
74 return first(_
.where(addrs
, {is_default
: "1"}));
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)
86 var lastModifiedTpl
= null;
88 // @return Promise MessageTemplate (per APIv3)
89 get: function get(id
) {
90 id
= ''+id
; // parseInt(id);
92 var tpl
= _
.where(tpls
, {id
: id
});
93 if (id
&& tpl
&& tpl
[0]) {
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
){
106 tpl
.id
= ''+response
.id
; //parseInt(response.id);
109 lastModifiedTpl
= tpl
113 // @return Object MessageTemplate (per APIv3)
114 getLastModifiedTpl: function() {
115 return lastModifiedTpl
;
117 getAll
: function getAll() {
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
, {
130 return (mcs
.length
>= 1) ? mcs
[0].id
: null;
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
);
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
);
154 // @return Object Mailing (per APIv3)
155 create
: function create() {
157 name
: "revert this", // fixme
159 from_name
: crmFromAddresses
.getDefault().author
,
160 from_email
: crmFromAddresses
.getDefault().email
,
162 subject
: "For {contact.display_name}", // fixme
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",
173 forward_replies
: "0",
177 optout_id
: pickDefaultMailComponent('OptOut'),
178 reply_id
: pickDefaultMailComponent('Reply'),
179 resubscribe_id
: pickDefaultMailComponent('Resubscribe'),
180 unsubscribe_id
: pickDefaultMailComponent('Unsubscribe')
184 // @param mailing Object (per APIv3)
186 'delete': function(mailing
) {
188 return crmApi('Mailing', 'delete', {id
: mailing
.id
});
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'
229 _
.each(MAILING_FIELDS
, function (field
) {
230 if (!_
.contains(excludes
, field
)) {
231 mailingTgt
[field
] = mailingFrom
[field
];
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': {
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
;
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'}
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
;
273 // Save a (draft) mailing
274 // @param mailing Object (per APIv3)
276 save: function(mailing
) {
277 var params
= _
.extend({}, mailing
, {
278 'api.mailing_job.create': 0 // note: exact match to API default
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
;
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
];
293 // Schedule/send the mailing
294 // @param mailing Object (per APIv3)
296 submit: function (mailing
) {
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
304 var params
= _
.extend({}, mailing
, changes
, {
305 'api.mailing_job.create': 0 // note: exact match to API default
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
];
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
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
;
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
;
341 })(angular
, CRM
.$, CRM
._
);