CRM-15578 - Add crmMailingAB2 module (based on crmMailingAB). Add skeletal list/edit...
[civicrm-core.git] / js / angular-crmMailing2-services.js
index f8307a75e67a3a52b7a168ac9d335731a0346d9f..340608a38ac731e0cca4304b02b1ac9b9fcb73a4 100644 (file)
@@ -3,9 +3,125 @@
     return CRM.resourceUrls['civicrm'] + '/partials/crmMailing2/' + relPath;
   };
 
+  // FIXME: surely there's already some helper which can do this in one line?
+  // @return string "YYYY-MM-DD hh:mm:ss"
+  var createNow = function () {
+    var currentdate = new Date();
+    var yyyy = currentdate.getFullYear();
+    var mm = currentdate.getMonth() + 1;
+    mm = mm < 10 ? '0' + mm : mm;
+    var dd = currentdate.getDate();
+    dd = dd < 10 ? '0' + dd : dd;
+    var hh = currentdate.getHours();
+    hh = hh < 10 ? '0' + hh : hh;
+    var min = currentdate.getMinutes();
+    min = min < 10 ? '0' + min : min;
+    var sec = currentdate.getSeconds();
+    sec = sec < 10 ? '0' + sec : sec;
+    return yyyy + "-" + mm + "-" + dd + " " + hh + ":" + min + ":" + sec;
+  };
+
+  // FIXME: Load status ids from DB
+  var APPROVAL_STATUSES = { Approved: "1", Rejected: "2", None: "3" };
+
   var crmMailing2 = angular.module('crmMailing2');
 
-  crmMailing2.factory('crmMailingMgr', function($q, crmApi) {
+  // The representation of from/reply-to addresses is inconsistent in the mailing data-model,
+  // so the UI must do some adaptation. The crmFromAddresses provides a richer way to slice/dice
+  // the available "From:" addrs. Records are like the underlying OptionValues -- but add "email"
+  // and "author".
+  crmMailing2.factory('crmFromAddresses', function($q, crmApi) {
+    var emailRegex = /^"(.*)" \<([^@\>]*@[^@\>]*)\>$/;
+    var addrs = _.map(CRM.crmMailing.fromAddress, function(addr){
+      var match = emailRegex.exec(addr.label);
+      return _.extend({}, addr, {
+        email: match ? match[2] : '(INVALID)',
+        author: match ? match[1] : '(INVALID)'
+      });
+    });
+    function first(array) {
+      return (array.length == 0) ? null : array[0];
+    }
+
+    return {
+      getAll: function getAll() {
+        return addrs;
+      },
+      getByAuthorEmail: function getByAuthorEmail(author, email, autocreate) {
+        var result = null;
+        _.each(addrs, function(addr){
+          if (addr.author == author && addr.email == email) {
+            result = addr;
+          }
+        });
+        if (!result && autocreate) {
+          result = {
+            label: '(INVALID) "' + author + '" <' + email + '>',
+            author: author,
+            email: email
+          };
+          addrs.push(result);
+        }
+        return result;
+      },
+      getByEmail: function getByEmail(email) {
+        return first(_.where(addrs, {email: email}));
+      },
+      getByLabel: function(label) {
+        return first(_.where(addrs, {label: label}));
+      },
+      getDefault: function getDefault() {
+        return first(_.where(addrs, {is_default: "1"}));
+      }
+    };
+  });
+
+  crmMailing2.factory('crmMsgTemplates', function($q, crmApi) {
+    var tpls = _.map(CRM.crmMailing.mesTemplate, function(tpl){
+      return _.extend({}, tpl, {
+        //id: tpl parseInt(tpl.id)
+      });
+    });
+    window.tpls = tpls;
+    var lastModifiedTpl = null;
+    return {
+      // @return Promise MessageTemplate (per APIv3)
+      get: function get(id) {
+        id = ''+id; // parseInt(id);
+        var dfr = $q.defer();
+        var tpl = _.where(tpls, {id: id});
+        if (id && tpl && tpl[0]) {
+          dfr.resolve(tpl[0]);
+        } else {
+          dfr.reject(id);
+        }
+        return dfr.promise;
+      },
+      // Save a template
+      // @param tpl MessageTemplate (per APIv3) For new templates, omit "id"
+      // @return Promise MessageTemplate (per APIv3)
+      save: function(tpl) {
+        return crmApi('MessageTemplate', 'create', tpl).then(function(response){
+          if (!tpl.id) {
+            tpl.id = ''+response.id; //parseInt(response.id);
+            tpls.push(tpl);
+          }
+          lastModifiedTpl = tpl
+          return tpl;
+        });
+      },
+      // @return Object MessageTemplate (per APIv3)
+      getLastModifiedTpl: function() {
+        return lastModifiedTpl;
+      },
+      getAll: function getAll() {
+        return tpls;
+      }
+    };
+  });
+
+  // The crmMailingMgr service provides business logic for loading, saving, previewing, etc
+  crmMailing2.factory('crmMailingMgr', function($q, crmApi, crmFromAddresses) {
     var pickDefaultMailComponent = function pickDefaultMailComponent(type) {
       var mcs = _.where(CRM.crmMailing.headerfooterList, {
         component_type:type,
             mailing.mailings = {include: [], exclude: []};
             _.each(groupResult.values, function(mailingGroup) {
               var bucket = (mailingGroup.entity_table == 'civicrm_group') ? 'groups' : 'mailings';
-              mailing[bucket][mailingGroup.group_type].push(mailingGroup.entity_id);
+              var entityId = parseInt(mailingGroup.entity_id);
+              mailing[bucket][mailingGroup.group_type].push(entityId);
             });
             return mailing;
           });
         return {
           name: "revert this", // fixme
           campaign_id: null,
-          from: _.where(CRM.crmMailing.fromAddress, {is_default: "1"})[0].label,
+          from_name: crmFromAddresses.getDefault().author,
+          from_email: crmFromAddresses.getDefault().email,
           replyto_email: "",
           subject: "For {contact.display_name}", // fixme
           dedupe_email: "1",
         };
       },
 
+      // @param mailing Object (per APIv3)
+      // @return Promise
+      'delete': function(mailing) {
+        if (mailing.id) {
+          return crmApi('Mailing', 'delete', {id: mailing.id});
+        } else {
+          var d = $q.defer();
+          d.resolve();
+          return d.promise;
+        }
+      },
+
+      // Copy all data fields in (mailingFrom) to (mailingTgt) -- except for (excludes)
+      // ex: crmMailingMgr.mergeInto(newMailing, mailingTemplate, ['subject']);
+      mergeInto: function mergeInto(mailingTgt, mailingFrom, excludes) {
+        var MAILING_FIELDS = [
+          // always exclude: 'id'
+          'name',
+          'campaign_id',
+          'from_name',
+          'from_email',
+          'replyto_email',
+          'subject',
+          'dedupe_email',
+          'groups',
+          'mailings',
+          'body_html',
+          'body_text',
+          'footer_id',
+          'header_id',
+          'visibility',
+          'url_tracking',
+          'dedupe_email',
+          'forward_replies',
+          'auto_responder',
+          'open_tracking',
+          'override_verp',
+          'optout_id',
+          'reply_id',
+          'resubscribe_id',
+          'unsubscribe_id'
+        ];
+        if (!excludes) {
+          excludes = [];
+        }
+        _.each(MAILING_FIELDS, function (field) {
+          if (!_.contains(excludes, field)) {
+            mailingTgt[field] = mailingFrom[field];
+          }
+        })
+      },
+
       // @param mailing Object (per APIv3)
       // @return Promise an object with "subject", "body_text", "body_html"
       preview: function preview(mailing) {
         var params = _.extend({}, mailing, {
           options:  {force_rollback: 1},
           'api.Mailing.preview': {
-            id: '$value.id',
+            id: '$value.id'
           }
         });
         return crmApi('Mailing', 'create', params).then(function(result){
         // get the resulting recipients -- then rollback any changes.
         var params = _.extend({}, mailing, {
           options:  {force_rollback: 1},
+          'api.mailing_job.create': 1, // note: exact match to API default
           'api.MailingRecipients.get': {
             mailing_id: '$value.id',
             options: { limit: previewLimit },
         });
       },
 
+      // Save a (draft) mailing
+      // @param mailing Object (per APIv3)
+      // @return Promise
+      save: function(mailing) {
+        var params = _.extend({}, mailing, {
+          'api.mailing_job.create': 0 // note: exact match to API default
+        });
+
+        // WORKAROUND: Mailing.create (aka CRM_Mailing_BAO_Mailing::create()) interprets scheduled_date
+        // as an *intent* to schedule and creates tertiary records. Saving a draft with a scheduled_date
+        // is therefore not allowed. Remove this after fixing Mailing.create's contract.
+        delete params.scheduled_date;
+
+        return crmApi('Mailing', 'create', params).then(function(result){
+          if (result.id && !mailing.id) mailing.id = result.id;  // no rollback, so update mailing.id
+          // Perhaps we should reload mailing based on result?
+          return result.values[result.id];
+        });
+      },
+
+      // Schedule/send the mailing
+      // @param mailing Object (per APIv3)
+      // @return Promise
+      submit: function (mailing) {
+        var changes = {
+          approval_date: createNow(),
+          approver_id: CRM.crmMailing.contactid,
+          approval_status_id: APPROVAL_STATUSES.Approved,
+          scheduled_date: mailing.scheduled_date ? mailing.scheduled_date : createNow(),
+          scheduled_id: CRM.crmMailing.contactid
+        };
+        var params = _.extend({}, mailing, changes, {
+          'api.mailing_job.create': 0 // note: exact match to API default
+        });
+        return crmApi('Mailing', 'create', params).then(function (result) {
+          if (result.id && !mailing.id) mailing.id = result.id; // no rollback, so update mailing.id
+          _.extend(mailing, changes); // Perhaps we should reload mailing based on result?
+          return result.values[result.id];
+        });
+      },
+
+      // Immediately send a test message
       // @param mailing Object (per APIv3)
       // @param testEmail string
       // @param testGroup int (id#)
       // @return Promise for a list of delivery reports
       sendTest: function(mailing, testEmail, testGroup) {
         var params = _.extend({}, mailing, {
-          // options:  {force_rollback: 1},
+          // options:  {force_rollback: 1}, // Test mailings include tracking features, so the mailing must be persistent
           'api.Mailing.send_test': {
             mailing_id: '$value.id',
             test_email: testEmail,
             test_group: testGroup
           }
         });
+
+        // WORKAROUND: Mailing.create (aka CRM_Mailing_BAO_Mailing::create()) interprets scheduled_date
+        // as an *intent* to schedule and creates tertiary records. Saving a draft with a scheduled_date
+        // is therefore not allowed. Remove this after fixing Mailing.create's contract.
+        delete params.scheduled_date;
+
         return crmApi('Mailing', 'create', params).then(function(result){
           if (result.id && !mailing.id) mailing.id = result.id;  // no rollback, so update mailing.id
           return result.values[result.id]['api.Mailing.send_test'].values;
       }
     };
   });
-
 })(angular, CRM.$, CRM._);