CRM-15578 - Mailing API - Replace "preview_recipients" action with chained calls
authorTim Otten <totten@civicrm.org>
Fri, 21 Nov 2014 01:53:46 +0000 (17:53 -0800)
committerTim Otten <totten@civicrm.org>
Fri, 21 Nov 2014 02:14:31 +0000 (18:14 -0800)
The special-purpose API "Mailing.preview_recipients" is no longer necessary. Instead,
one can chain simpler APIs ("Mailing.create" + "MailingRecipients.get" + "force_rollback")

api/v3/Mailing.php
js/angular-crmMailing2.js
tests/phpunit/api/v3/MailingTest.php

index 013a3274835ad367eddb82ea958a5fd64ad1b004..af48f786ea6d2e37c94b78e6a3b45a085a7d0639 100755 (executable)
@@ -310,58 +310,6 @@ function civicrm_api3_mailing_event_open($params) {
   return civicrm_api3_create_success($params);
 }
 
-/**
- * Generate a list of likely recipients for a (hypothetical) mailing.
- *
- * "Mailing.preview_recipients" == "Mailing.create" + "MailingRecipients.get" + "rollback"
- *
- * Ideally, we could add a "force_rollback" option to the API; then downstream code could
- * combine the actions without needing this function.
- *
- * @param array $params
- * @return array list of recipients
- * @throws API_Exception
- */
-function civicrm_api3_mailing_preview_recipients($params) {
-  static $nextId = 0;
-  $savePoint = "civimail_preview_" . ++$nextId;
-  $tx = new CRM_Core_Transaction();
-  CRM_Core_DAO::executeQuery("SAVEPOINT $savePoint");
-
-  // We manually apply defaults (rather than using API defaults) because
-  // (a) these are temporary/non-sense values and (b) we want to
-  // apply defaults regardless of whether mailing has NULL/''/real-value.
-  $params['name'] = 'Placeholder (not saved)';
-  $params['subject'] = 'Placeholder (not saved)';
-  $params['scheduled_date'] = date('YmdHis', time() + 24*60*60);
-
-  // "Mailing.preview_recipients" == "Mailing.create"+"MailingRecipients.get"
-  $params['debug'] = 1;
-  $params['version'] = 3;
-  $params['options']['force_rollback'] = 1;
-  $params['api.MailingRecipients.get'] = array(
-    'options' => array(
-      'limit' => isset($params['options']['limit']) ? $params['options']['limit'] : 1000,
-    ),
-    'api.contact.getvalue' => array(
-      'return' => 'display_name',
-    ),
-    'api.email.getvalue' => array(
-      'return' => 'email',
-    ),
-  );
-
-  try {
-    $mailing = civicrm_api3('Mailing', 'create', $params);
-  } catch (Exception $ex) {
-    CRM_Core_DAO::executeQuery("ROLLBACK TO SAVEPOINT $savePoint");
-    throw $ex;
-  }
-
-  CRM_Core_DAO::executeQuery("ROLLBACK TO SAVEPOINT $savePoint");
-  return civicrm_api3_create_success($mailing['values'][$mailing['id']]['api.MailingRecipients.get']['values']);
-}
-
 function civicrm_api3_mailing_preview($params) {
   civicrm_api3_verify_mandatory($params,
     'CRM_Mailing_DAO_Mailing',
index 99d5d6dda211f438ef5400e0efb67a0d244e8586..542f3b9d01d6002e4e45fe733ee4b0b18e7e1c48 100644 (file)
@@ -7,6 +7,7 @@
 
   // Time to wait before triggering AJAX update to recipients list
   var RECIPIENTS_DEBOUNCE_MS = 100;
+  var RECIPIENTS_PREVIEW_LIMIT = 10000;
 
   /**
    * Initialize a new mailing
         return ts('No recipients');
       if ($scope.recipients.length == 1)
         return ts('~1 recipient');
+      if (RECIPIENTS_PREVIEW_LIMIT > 0 && $scope.recipients.length >= RECIPIENTS_PREVIEW_LIMIT)
+        return ts('>%1 recipients', {1: RECIPIENTS_PREVIEW_LIMIT});
       return ts('~%1 recipients', {1: $scope.recipients.length});
     };
     // We monitor four fields -- use debounce so that changes across the
     var refreshRecipients = _.debounce(function () {
       $scope.$apply(function () {
         $scope.recipients = null;
-        crmApi('Mailing', 'preview_recipients', $scope.mailing)
+        // To get list of recipients, we tentatively save the mailing and
+        // get the resulting recipients -- then rollback any changes.
+        var params = _.extend({}, $scope.mailing, {
+          options:  {force_rollback: 1},
+          'api.MailingRecipients.get': {
+            mailing_id: '$value.id',
+            options: {limit: RECIPIENTS_PREVIEW_LIMIT},
+            'api.contact.getvalue': {'return': 'display_name'},
+            'api.email.getvalue': {'return': 'email'}
+          }
+        });
+
+        crmApi('Mailing', 'create', params)
                 .then(function (recipResult) {
                   $scope.$apply(function () {
-                    $scope.recipients = recipResult.values;
+                    $scope.recipients = recipResult.values[recipResult.id]['api.MailingRecipients.get'].values;
                   });
                 });
       });
index aa282b20bbb38ee060b7b4bd68884a89167d1bc9..2ed1eb732ed9237e27070a71b843bc3ee01d0e82 100755 (executable)
@@ -108,8 +108,8 @@ class api_v3_MailingTest extends CiviUnitTestCase {
     // BEGIN SAMPLE DATA
     $this->groupIDs['inc'] = $this->groupCreate(array('name' => 'Example include group', 'title' => 'Example include group'));
     $this->groupIDs['exc'] = $this->groupCreate(array('name' => 'Example exclude group', 'title' => 'Example exclude group'));
-    $this->contactIDs['includeme'] = $this->individualCreate(array('include.me@example.org'));
-    $this->contactIDs['excludeme'] = $this->individualCreate(array('exclude.me@example.org'));
+    $this->contactIDs['includeme'] = $this->individualCreate(array('email' => 'include.me@example.org', 'first_name' => 'Includer', 'last_name' => 'Person'));
+    $this->contactIDs['excludeme'] = $this->individualCreate(array('email' => 'exclude.me@example.org', 'last_name' => 'Excluder', 'last_name' => 'Excluder'));
     $this->callAPISuccess('GroupContact', 'create', array('group_id' => $this->groupIDs['inc'], 'contact_id' => $this->contactIDs['includeme']));
     $this->callAPISuccess('GroupContact', 'create', array('group_id' => $this->groupIDs['inc'], 'contact_id' => $this->contactIDs['excludeme']));
     $this->callAPISuccess('GroupContact', 'create', array('group_id' => $this->groupIDs['exc'], 'contact_id' => $this->contactIDs['excludeme']));
@@ -119,6 +119,16 @@ class api_v3_MailingTest extends CiviUnitTestCase {
     $params['groups']['exclude'] = array($this->groupIDs['exc']);
     $params['mailings']['include'] = array();
     $params['mailings']['exclude'] = array();
+    $params['options']['force_rollback'] = 1;
+    $params['api.MailingRecipients.get'] = array(
+      'mailing_id' => '$value.id',
+      'api.contact.getvalue' => array(
+        'return' => 'display_name',
+      ),
+      'api.email.getvalue' => array(
+        'return' => 'email',
+      ),
+    );
     // END SAMPLE DATA
 
     $maxIDs =  array(
@@ -126,13 +136,18 @@ class api_v3_MailingTest extends CiviUnitTestCase {
       'job' => CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_mailing_job'),
       'group' => CRM_Core_DAO::singleValueQuery('SELECT MAX(id) FROM civicrm_mailing_group'),
     );
-    $preview = $this->callAPIAndDocument('Mailing', 'preview_recipients', $params, __FUNCTION__, __FILE__);
+    $create = $this->callAPIAndDocument('Mailing', 'create', $params, __FUNCTION__, __FILE__);
     $this->assertDBQuery($maxIDs['mailing'], 'SELECT MAX(id) FROM civicrm_mailing'); // 'Preview should not create any mailing records'
     $this->assertDBQuery($maxIDs['job'], 'SELECT MAX(id) FROM civicrm_mailing_job'); // 'Preview should not create any mailing_job record'
     $this->assertDBQuery($maxIDs['group'], 'SELECT MAX(id) FROM civicrm_mailing_group'); // 'Preview should not create any mailing_group records'
 
+    $preview = $create['values'][$create['id']]['api.MailingRecipients.get'];
     $previewIds = array_values(CRM_Utils_Array::collect('contact_id', $preview['values']));
     $this->assertEquals(array((string)$this->contactIDs['includeme']), $previewIds);
+    $previewEmails = array_values(CRM_Utils_Array::collect('api.email.getvalue', $preview['values']));
+    $this->assertEquals(array('include.me@example.org'), $previewEmails);
+    $previewNames = array_values(CRM_Utils_Array::collect('api.contact.getvalue', $preview['values']));
+    $this->assertTrue((bool)preg_match('/Includer Person/', $previewNames[0]), "Name 'Includer Person' should appear in '" . $previewNames[0] . '"');
   }
 
   public function testMailerSendTestMail() {