Commit | Line | Data |
---|---|---|
030dce01 | 1 | (function (angular, $, _) { |
030dce01 | 2 | |
88e9e883 | 3 | angular.module('crmMailing', [ |
8443385d | 4 | 'crmUtil', 'crmAttachment', 'crmAutosave', 'ngRoute', 'ui.utils', 'crmUi', 'dialogService' |
f4f103fa | 5 | ]); // TODO ngSanitize, unsavedChanges |
030dce01 | 6 | |
7801d9b5 TO |
7 | // Time to wait before triggering AJAX update to recipients list |
8 | var RECIPIENTS_DEBOUNCE_MS = 100; | |
b73e0c53 | 9 | var RECIPIENTS_PREVIEW_LIMIT = 10000; |
7801d9b5 | 10 | |
88e9e883 | 11 | angular.module('crmMailing').config([ |
f4f103fa | 12 | '$routeProvider', |
030dce01 | 13 | function ($routeProvider) { |
96ac27bd | 14 | $routeProvider.when('/mailing', { |
030dce01 TO |
15 | template: '<div></div>', |
16 | controller: 'ListMailingsCtrl' | |
17 | }); | |
b7c3beb2 TO |
18 | |
19 | var editorPaths = { | |
ef5d18a1 TO |
20 | '': '~/crmMailing/edit.html', |
21 | '/unified': '~/crmMailing/edit-unified.html', | |
22 | '/unified2': '~/crmMailing/edit-unified2.html', | |
23 | '/wizard': '~/crmMailing/edit-wizard.html' | |
b7c3beb2 TO |
24 | }; |
25 | angular.forEach(editorPaths, function(editTemplate, pathSuffix) { | |
26 | $routeProvider.when('/mailing/new' + pathSuffix, { | |
27 | template: '<p>' + ts('Initializing...') + '</p>', | |
28 | controller: 'CreateMailingCtrl', | |
29 | resolve: { | |
30 | selectedMail: function(crmMailingMgr) { | |
31 | var m = crmMailingMgr.create(); | |
32 | return crmMailingMgr.save(m); | |
33 | } | |
f4f103fa | 34 | } |
b7c3beb2 TO |
35 | }); |
36 | $routeProvider.when('/mailing/:id' + pathSuffix, { | |
37 | templateUrl: editTemplate, | |
38 | controller: 'EditMailingCtrl', | |
39 | resolve: { | |
40 | selectedMail: function($route, crmMailingMgr) { | |
41 | return crmMailingMgr.get($route.current.params.id); | |
f2cdd789 TO |
42 | }, |
43 | attachments: function($route, CrmAttachments) { | |
44 | var attachments = new CrmAttachments(function () { | |
45 | return {entity_table: 'civicrm_mailing', entity_id: $route.current.params.id}; | |
46 | }); | |
47 | return attachments.load(); | |
b7c3beb2 | 48 | } |
f4f103fa | 49 | } |
b7c3beb2 | 50 | }); |
030dce01 TO |
51 | }); |
52 | } | |
53 | ]); | |
54 | ||
416abe87 | 55 | angular.module('crmMailing').controller('ListMailingsCtrl', ['crmLegacy', 'crmNavigator', function ListMailingsCtrl(crmLegacy, crmNavigator) { |
030dce01 TO |
56 | // We haven't implemented this in Angular, but some users may get clever |
57 | // about typing URLs, so we'll provide a redirect. | |
416abe87 PH |
58 | var new_url = crmLegacy.url('civicrm/mailing/browse/unscheduled', {reset: 1, scheduled: 'false'}); |
59 | crmNavigator.redirect(new_url); | |
60 | }]); | |
030dce01 | 61 | |
b7c3beb2 TO |
62 | angular.module('crmMailing').controller('CreateMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location) { |
63 | // Transition URL "/mailing/new/foo" => "/mailing/123/foo" | |
64 | var parts = $location.path().split('/'); // e.g. "/mailing/new" or "/mailing/123/wizard" | |
65 | parts[2] = selectedMail.id; | |
66 | $location.path(parts.join('/')); | |
67 | $location.replace(); | |
68 | }); | |
69 | ||
f2cdd789 | 70 | angular.module('crmMailing').controller('EditMailingCtrl', function EditMailingCtrl($scope, selectedMail, $location, crmMailingMgr, crmStatus, attachments, crmMailingPreviewMgr, crmBlocker) { |
27e690c2 | 71 | $scope.mailing = selectedMail; |
f2cdd789 | 72 | $scope.attachments = attachments; |
27e690c2 TO |
73 | $scope.crmMailingConst = CRM.crmMailing; |
74 | ||
5d8901af | 75 | var ts = $scope.ts = CRM.ts(null); |
bdd3f781 | 76 | var block = $scope.block = crmBlocker(); |
27e690c2 | 77 | |
86c3a327 TO |
78 | $scope.isSubmitted = function isSubmitted() { |
79 | return _.size($scope.mailing.jobs) > 0; | |
80 | }; | |
81 | ||
58dfba8d TO |
82 | // @return Promise |
83 | $scope.previewMailing = function previewMailing(mailing, mode) { | |
84 | return crmMailingPreviewMgr.preview(mailing, mode); | |
85 | }; | |
86 | ||
87 | // @return Promise | |
88 | $scope.sendTest = function sendTest(mailing, attachments, recipient) { | |
89 | var savePromise = crmMailingMgr.save(mailing) | |
90 | .then(function () { | |
91 | return attachments.save(); | |
b7c3beb2 | 92 | }); |
bdd3f781 | 93 | return block(crmStatus({start: ts('Saving...'), success: ''}, savePromise) |
58dfba8d TO |
94 | .then(function () { |
95 | crmMailingPreviewMgr.sendTest(mailing, recipient); | |
bdd3f781 | 96 | })); |
58dfba8d TO |
97 | }; |
98 | ||
705c61e9 TO |
99 | // @return Promise |
100 | $scope.submit = function submit() { | |
c9ae63bb TO |
101 | if (block.check() || $scope.crmMailing.$invalid) { |
102 | return; | |
103 | } | |
104 | ||
db083bf0 | 105 | var promise = crmMailingMgr.save($scope.mailing) |
86c3a327 TO |
106 | .then(function () { |
107 | // pre-condition: the mailing exists *before* saving attachments to it | |
108 | return $scope.attachments.save(); | |
109 | }) | |
110 | .then(function () { | |
111 | return crmMailingMgr.submit($scope.mailing); | |
112 | }) | |
113 | .then(function () { | |
650a6ffc | 114 | $scope.leave('scheduled'); |
86c3a327 TO |
115 | }) |
116 | ; | |
bdd3f781 | 117 | return block(crmStatus({start: ts('Submitting...'), success: ts('Submitted')}, promise)); |
2d36e6bc | 118 | }; |
58dfba8d | 119 | |
705c61e9 TO |
120 | // @return Promise |
121 | $scope.save = function save() { | |
bdd3f781 | 122 | return block(crmStatus(null, |
db083bf0 TO |
123 | crmMailingMgr |
124 | .save($scope.mailing) | |
125 | .then(function () { | |
126 | // pre-condition: the mailing exists *before* saving attachments to it | |
127 | return $scope.attachments.save(); | |
128 | }) | |
bdd3f781 | 129 | )); |
2d36e6bc | 130 | }; |
58dfba8d | 131 | |
705c61e9 TO |
132 | // @return Promise |
133 | $scope.delete = function cancel() { | |
bdd3f781 | 134 | return block(crmStatus({start: ts('Deleting...'), success: ts('Deleted')}, |
f286acec | 135 | crmMailingMgr.delete($scope.mailing) |
b0797ac3 | 136 | .then(function () { |
650a6ffc | 137 | $scope.leave('unscheduled'); |
b0797ac3 | 138 | }) |
bdd3f781 | 139 | )); |
2d36e6bc | 140 | }; |
58dfba8d | 141 | |
b0797ac3 | 142 | // @param string listingScreen 'archive', 'scheduled', 'unscheduled' |
650a6ffc | 143 | $scope.leave = function leave(listingScreen) { |
b0797ac3 TO |
144 | switch (listingScreen) { |
145 | case 'archive': | |
146 | window.location = CRM.url('civicrm/mailing/browse/archived', { | |
147 | reset: 1 | |
148 | }); | |
149 | break; | |
150 | case 'scheduled': | |
151 | window.location = CRM.url('civicrm/mailing/browse/scheduled', { | |
152 | reset: 1, | |
153 | scheduled: 'true' | |
154 | }); | |
155 | break; | |
156 | case 'unscheduled': | |
8acf0123 | 157 | /* falls through */ |
b0797ac3 TO |
158 | default: |
159 | window.location = CRM.url('civicrm/mailing/browse/unscheduled', { | |
160 | reset: 1, | |
161 | scheduled: 'false' | |
162 | }); | |
163 | } | |
650a6ffc | 164 | }; |
030dce01 TO |
165 | }); |
166 | ||
7801d9b5 TO |
167 | // Controller for the edit-recipients fields ( |
168 | // WISHLIST: Move most of this to a (cache-enabled) service | |
169 | // Scope members: | |
170 | // - [input] mailing: object | |
171 | // - [output] recipients: array of recipient records | |
88e9e883 | 172 | angular.module('crmMailing').controller('EditRecipCtrl', function EditRecipCtrl($scope, dialogService, crmApi, crmMailingMgr) { |
5d8901af | 173 | var ts = $scope.ts = CRM.ts(null); |
7801d9b5 TO |
174 | $scope.recipients = null; |
175 | $scope.getRecipientsEstimate = function () { | |
176 | var ts = $scope.ts; | |
8acf0123 | 177 | if ($scope.recipients === null) { |
7801d9b5 | 178 | return ts('(Estimating)'); |
f4f103fa | 179 | } |
8acf0123 | 180 | if ($scope.recipients.length === 0) { |
7801d9b5 | 181 | return ts('No recipients'); |
f4f103fa | 182 | } |
8acf0123 | 183 | if ($scope.recipients.length === 1) { |
7801d9b5 | 184 | return ts('~1 recipient'); |
f4f103fa TO |
185 | } |
186 | if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT) { | |
b73e0c53 | 187 | return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT}); |
f4f103fa | 188 | } |
7801d9b5 TO |
189 | return ts('~%1 recipients', {1: $scope.recipients.length}); |
190 | }; | |
f4f103fa | 191 | $scope.getIncludesAsString = function () { |
47bacc20 TO |
192 | var first = true; |
193 | var names = ''; | |
9cd4f489 | 194 | _.each($scope.mailing.recipients.groups.include, function (id) { |
f4f103fa TO |
195 | if (!first) { |
196 | names = names + ', '; | |
197 | } | |
198 | var group = _.where(CRM.crmMailing.groupNames, {id: '' + id}); | |
47bacc20 TO |
199 | names = names + group[0].title; |
200 | first = false; | |
201 | }); | |
9cd4f489 | 202 | _.each($scope.mailing.recipients.mailings.include, function (id) { |
f4f103fa TO |
203 | if (!first) { |
204 | names = names + ', '; | |
205 | } | |
206 | var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id}); | |
47bacc20 TO |
207 | names = names + oldMailing[0].name; |
208 | first = false; | |
209 | }); | |
210 | return names; | |
211 | }; | |
f4f103fa | 212 | $scope.getExcludesAsString = function () { |
47bacc20 TO |
213 | var first = true; |
214 | var names = ''; | |
9cd4f489 | 215 | _.each($scope.mailing.recipients.groups.exclude, function (id) { |
f4f103fa TO |
216 | if (!first) { |
217 | names = names + ', '; | |
218 | } | |
219 | var group = _.where(CRM.crmMailing.groupNames, {id: '' + id}); | |
47bacc20 TO |
220 | names = names + group[0].title; |
221 | first = false; | |
222 | }); | |
9cd4f489 | 223 | _.each($scope.mailing.recipients.mailings.exclude, function (id) { |
f4f103fa TO |
224 | if (!first) { |
225 | names = names + ', '; | |
226 | } | |
227 | var oldMailing = _.where(CRM.crmMailing.civiMails, {id: '' + id}); | |
47bacc20 TO |
228 | names = names + oldMailing[0].name; |
229 | first = false; | |
230 | }); | |
231 | return names; | |
232 | }; | |
233 | ||
7801d9b5 TO |
234 | // We monitor four fields -- use debounce so that changes across the |
235 | // four fields can settle-down before AJAX. | |
236 | var refreshRecipients = _.debounce(function () { | |
237 | $scope.$apply(function () { | |
238 | $scope.recipients = null; | |
8dfd5110 TO |
239 | crmMailingMgr.previewRecipients($scope.mailing, RECIPIENTS_PREVIEW_LIMIT).then(function (recipients) { |
240 | $scope.recipients = recipients; | |
b73e0c53 | 241 | }); |
7801d9b5 TO |
242 | }); |
243 | }, RECIPIENTS_DEBOUNCE_MS); | |
9cd4f489 TO |
244 | $scope.$watchCollection("mailing.recipients.groups.include", refreshRecipients); |
245 | $scope.$watchCollection("mailing.recipients.groups.exclude", refreshRecipients); | |
246 | $scope.$watchCollection("mailing.recipients.mailings.include", refreshRecipients); | |
247 | $scope.$watchCollection("mailing.recipients.mailings.exclude", refreshRecipients); | |
7801d9b5 | 248 | |
4dd19229 | 249 | $scope.previewRecipients = function previewRecipients() { |
7801d9b5 TO |
250 | var model = { |
251 | recipients: $scope.recipients | |
252 | }; | |
253 | var options = { | |
254 | autoOpen: false, | |
255 | modal: true, | |
256 | title: ts('Preview (%1)', { | |
257 | 1: $scope.getRecipientsEstimate() | |
52f515c6 | 258 | }) |
7801d9b5 | 259 | }; |
ef5d18a1 | 260 | dialogService.open('recipDialog', '~/crmMailing/dialog/recipients.html', model, options); |
7801d9b5 TO |
261 | }; |
262 | }); | |
263 | ||
264 | // Controller for the "Preview Recipients" dialog | |
265 | // Note: Expects $scope.model to be an object with properties: | |
266 | // - recipients: array of contacts | |
88e9e883 | 267 | angular.module('crmMailing').controller('PreviewRecipCtrl', function ($scope) { |
5d8901af | 268 | $scope.ts = CRM.ts(null); |
7801d9b5 | 269 | }); |
493eb47a | 270 | |
493eb47a TO |
271 | // Controller for the "Preview Mailing" dialog |
272 | // Note: Expects $scope.model to be an object with properties: | |
273 | // - "subject" | |
274 | // - "body_html" | |
275 | // - "body_text" | |
870cbdbb | 276 | angular.module('crmMailing').controller('PreviewMailingDialogCtrl', function PreviewMailingDialogCtrl($scope) { |
5d8901af | 277 | $scope.ts = CRM.ts(null); |
493eb47a TO |
278 | }); |
279 | ||
47bacc20 TO |
280 | // Controller for the "Preview Mailing Component" segment |
281 | // which displays header/footer/auto-responder | |
d376f33e | 282 | angular.module('crmMailing').controller('PreviewComponentCtrl', function PreviewComponentCtrl($scope, dialogService) { |
5d8901af | 283 | var ts = $scope.ts = CRM.ts(null); |
52f515c6 | 284 | |
47bacc20 | 285 | $scope.previewComponent = function previewComponent(title, componentId) { |
f4f103fa | 286 | var component = _.where(CRM.crmMailing.headerfooterList, {id: "" + componentId}); |
47bacc20 TO |
287 | if (!component || !component[0]) { |
288 | CRM.alert(ts('Invalid component ID (%1)', { | |
289 | 1: componentId | |
290 | })); | |
291 | return; | |
292 | } | |
293 | var options = { | |
294 | autoOpen: false, | |
295 | modal: true, | |
296 | title: title // component[0].name | |
297 | }; | |
ef5d18a1 | 298 | dialogService.open('previewComponentDialog', '~/crmMailing/dialog/previewComponent.html', component[0], options); |
47bacc20 TO |
299 | }; |
300 | }); | |
301 | ||
d376f33e | 302 | // Controller for the "Preview Mailing Component" dialog |
47bacc20 TO |
303 | // Note: Expects $scope.model to be an object with properties: |
304 | // - "name" | |
305 | // - "subject" | |
306 | // - "body_html" | |
307 | // - "body_text" | |
d376f33e | 308 | angular.module('crmMailing').controller('PreviewComponentDialogCtrl', function PreviewComponentDialogCtrl($scope) { |
5d8901af | 309 | $scope.ts = CRM.ts(null); |
47bacc20 TO |
310 | }); |
311 | ||
744bebee | 312 | // Controller for the in-place msg-template management |
870cbdbb | 313 | angular.module('crmMailing').controller('MsgTemplateCtrl', function MsgTemplateCtrl($scope, crmMsgTemplates, dialogService) { |
5d8901af | 314 | var ts = $scope.ts = CRM.ts(null); |
744bebee TO |
315 | $scope.crmMsgTemplates = crmMsgTemplates; |
316 | ||
317 | // @return Promise MessageTemplate (per APIv3) | |
74263d6b | 318 | $scope.saveTemplate = function saveTemplate(mailing) { |
744bebee | 319 | var model = { |
74263d6b | 320 | selected_id: mailing.msg_template_id, |
744bebee TO |
321 | tpl: { |
322 | msg_title: '', | |
74263d6b TO |
323 | msg_subject: mailing.subject, |
324 | msg_text: mailing.body_text, | |
325 | msg_html: mailing.body_html | |
744bebee TO |
326 | } |
327 | }; | |
328 | var options = { | |
329 | autoOpen: false, | |
330 | modal: true, | |
331 | title: ts('Save Template') | |
332 | }; | |
ef5d18a1 | 333 | return dialogService.open('saveTemplateDialog', '~/crmMailing/dialog/saveTemplate.html', model, options) |
f4f103fa | 334 | .then(function (item) { |
74263d6b | 335 | mailing.msg_template_id = item.id; |
744bebee TO |
336 | return item; |
337 | }); | |
338 | }; | |
339 | ||
340 | // @param int id | |
341 | // @return Promise | |
74263d6b | 342 | $scope.loadTemplate = function loadTemplate(mailing, id) { |
744bebee | 343 | return crmMsgTemplates.get(id).then(function (tpl) { |
74263d6b TO |
344 | mailing.subject = tpl.msg_subject; |
345 | mailing.body_text = tpl.msg_text; | |
346 | mailing.body_html = tpl.msg_html; | |
744bebee TO |
347 | }); |
348 | }; | |
349 | }); | |
350 | ||
351 | // Controller for the "Save Message Template" dialog | |
352 | // Scope members: | |
353 | // - [input] "model": Object | |
354 | // - "selected_id": int | |
355 | // - "tpl": Object | |
356 | // - "msg_subject": string | |
357 | // - "msg_text": string | |
358 | // - "msg_html": string | |
88e9e883 | 359 | angular.module('crmMailing').controller('SaveMsgTemplateDialogCtrl', function SaveMsgTemplateDialogCtrl($scope, crmMsgTemplates, dialogService) { |
5d8901af | 360 | var ts = $scope.ts = CRM.ts(null); |
744bebee TO |
361 | $scope.saveOpt = {mode: '', newTitle: ''}; |
362 | $scope.selected = null; | |
363 | ||
364 | $scope.save = function save() { | |
365 | var tpl = _.extend({}, $scope.model.tpl); | |
366 | switch ($scope.saveOpt.mode) { | |
367 | case 'add': | |
368 | tpl.msg_title = $scope.saveOpt.newTitle; | |
369 | break; | |
370 | case 'update': | |
371 | tpl.id = $scope.selected.id; | |
372 | tpl.msg_title = $scope.selected.msg_title; | |
373 | break; | |
374 | default: | |
375 | throw 'SaveMsgTemplateDialogCtrl: Unrecognized mode: ' + $scope.saveOpt.mode; | |
376 | } | |
377 | return crmMsgTemplates.save(tpl) | |
378 | .then(function (item) { | |
379 | CRM.status(ts('Saved')); | |
380 | return item; | |
381 | }); | |
382 | }; | |
383 | ||
384 | function scopeApply(f) { | |
385 | return function () { | |
386 | var args = arguments; | |
387 | $scope.$apply(function () { | |
388 | f.apply(args); | |
389 | }); | |
390 | }; | |
391 | } | |
392 | ||
393 | function init() { | |
394 | crmMsgTemplates.get($scope.model.selected_id).then( | |
395 | function (tpl) { | |
396 | $scope.saveOpt.mode = 'update'; | |
397 | $scope.selected = tpl; | |
398 | }, | |
399 | function () { | |
400 | $scope.saveOpt.mode = 'add'; | |
401 | $scope.selected = null; | |
402 | } | |
403 | ); | |
404 | // When using dialogService with a button bar, the major button actions | |
405 | // need to be registered with the dialog widget (and not embedded in | |
406 | // the body of the dialog). | |
269d44f5 CW |
407 | var buttons = [ |
408 | { | |
409 | text: ts('Save'), | |
410 | icons: {primary: 'ui-icon-check'}, | |
411 | click: function () { | |
412 | $scope.save().then(function (item) { | |
413 | dialogService.close('saveTemplateDialog', item); | |
414 | }); | |
415 | } | |
416 | }, | |
417 | { | |
418 | text: ts('Cancel'), | |
419 | icons: {primary: 'ui-icon-close'}, | |
420 | click: function () { | |
421 | dialogService.cancel('saveTemplateDialog'); | |
422 | } | |
423 | } | |
424 | ]; | |
744bebee TO |
425 | dialogService.setButtons('saveTemplateDialog', buttons); |
426 | } | |
f4f103fa | 427 | |
744bebee TO |
428 | setTimeout(scopeApply(init), 0); |
429 | }); | |
2d06b3b6 | 430 | |
58dfba8d | 431 | angular.module('crmMailing').controller('EmailAddrCtrl', function EmailAddrCtrl($scope, crmFromAddresses) { |
0a993c89 TO |
432 | $scope.crmFromAddresses = crmFromAddresses; |
433 | }); | |
21fb26b8 TO |
434 | |
435 | var lastEmailTokenAlert = null; | |
436 | angular.module('crmMailing').controller('EmailBodyCtrl', function EmailBodyCtrl($scope, crmMailingMgr) { | |
437 | var ts = CRM.ts(null); | |
438 | ||
439 | $scope.hasAllTokens = function hasMissingTokens(mailing, field) { | |
440 | return _.isEmpty(crmMailingMgr.findMissingTokens(mailing, field)); | |
441 | }; | |
442 | ||
443 | $scope.checkTokens = function checkTokens(mailing) { | |
444 | if (lastEmailTokenAlert) { | |
445 | lastEmailTokenAlert.close(); | |
446 | } | |
447 | var missing = angular.extend( | |
448 | {}, | |
449 | crmMailingMgr.findMissingTokens(mailing, 'body_html'), | |
450 | crmMailingMgr.findMissingTokens(mailing, 'body_text') | |
451 | ); | |
d03e0d13 TO |
452 | if (! _.isEmpty(missing)) { |
453 | var buf = '<p>' + | |
454 | ts('Before submitting this mailing, you must include an address token and an action token as part of the mailing body, mailing header, or mailing footer.') + | |
455 | '</p><ul>'; | |
21fb26b8 TO |
456 | angular.forEach(missing, function(msg, token) { |
457 | // FIXME LTR RTL | |
d03e0d13 | 458 | buf = buf + '<li>{' + token + '} - <em>' + msg + '</em></li>'; |
21fb26b8 TO |
459 | }); |
460 | buf += '</ul>'; | |
461 | lastEmailTokenAlert = CRM.alert(buf, undefined, 'error'); | |
462 | } | |
d03e0d13 | 463 | }; |
21fb26b8 | 464 | }); |
030dce01 | 465 | })(angular, CRM.$, CRM._); |