SearchKit - Add CiviMail integration
authorColeman Watts <coleman@civicrm.org>
Sun, 20 Feb 2022 22:36:19 +0000 (17:36 -0500)
committerColeman Watts <coleman@civicrm.org>
Mon, 21 Feb 2022 18:06:47 +0000 (13:06 -0500)
ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php
ext/search_kit/ang/crmSearchTasks/crmSearchBatchRunner.component.js
ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.ctrl.js [new file with mode: 0644]
ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.html [new file with mode: 0644]

index da0c5d61d1353a2b56fed3e6d004d7bb8e6ab734..b0ded1f45eeecf966080b0e74463cb9e47202e22 100644 (file)
@@ -120,6 +120,16 @@ class GetSearchTasks extends \Civi\Api4\Generic\AbstractAction {
           ],
         ];
       }
+      if (\CRM_Core_Component::isEnabled('CiviMail') && (
+        \CRM_Core_Permission::access('CiviMail') || !$this->checkPermissions ||
+        (\CRM_Mailing_Info::workflowEnabled() && \CRM_Core_Permission::check('create mailings'))
+      )) {
+        $tasks[$entity['name']]['contact.mailing'] = [
+          'title' => E::ts('Email - schedule/send via CiviMail'),
+          'uiDialog' => ['templateUrl' => '~/crmSearchTasks/crmSearchTaskMailing.html'],
+          'icon' => 'fa-paper-plane',
+        ];
+      }
     }
 
     if ($entity['name'] === 'Contribution') {
index 6b910d825af1cf2a186fbf4289398642dc41aae3..88c90f7f4b325275a75c29d5f407e17670cd5912 100644 (file)
@@ -27,6 +27,9 @@
         EST_BATCH_TIME = 5;
 
       this.$onInit = function() {
+        if (ctrl.action === 'create') {
+          ctrl.ids = [0];
+        }
         totalBatches = Math.ceil(ctrl.ids.length / BATCH_SIZE);
         runBatch();
       };
@@ -50,7 +53,7 @@
               records.push(record);
             });
           });
-        } else {
+        } else if (ctrl.action !== 'create') {
           // For other batch actions (update, delete), add supplied ids to the where clause
           params.where = params.where || [];
           params.where.push([ctrl.idField || 'id', 'IN', ctrl.ids.slice(ctrl.first, ctrl.last)]);
@@ -60,7 +63,9 @@
             stopIncrementer();
             ctrl.progress = Math.floor(100 * ++currentBatch / totalBatches);
             if (ctrl.last >= ctrl.ids.length) {
-              $timeout(ctrl.success, 500);
+              $timeout(function() {
+                ctrl.success({result: result});
+              }, 500);
             } else {
               runBatch();
             }
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.ctrl.js b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.ctrl.js
new file mode 100644 (file)
index 0000000..a5f14bb
--- /dev/null
@@ -0,0 +1,73 @@
+(function(angular, $, _) {
+  "use strict";
+
+  angular.module('crmSearchTasks').controller('crmSearchTaskMailing', function($scope, crmApi4, searchTaskBaseTrait) {
+    var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
+      // Combine this controller with model properties (ids, entity, entityInfo) and searchTaskBaseTrait
+      ctrl = angular.extend(this, $scope.model, searchTaskBaseTrait),
+      templateTypes;
+
+    this.entityTitle = this.getEntityTitle();
+
+    // This option is needed to determine whether the mailing will be handled by CiviMail or Mosaico
+    crmApi4({
+      templateTypes: ['Mailing', 'getFields', {
+        loadOptions: ['id'],
+        where: [['name', '=', 'template_type']]
+      }, ['options']],
+      recipientCount: ['Contact', 'get', {
+        select: ['row_count'],
+        join: [['Email AS email', 'INNER', ['id', '=', 'email.contact_id']]],
+        where: [['id', 'IN', ctrl.ids], ['do_not_email', '!=', true], ['is_opt_out', '!=', true], ['email.on_hold', '=', 0]],
+        groupBy: ['id']
+      }]
+    }).then(function(results) {
+      templateTypes = results.templateTypes[0];
+      ctrl.recipientCount = results.recipientCount.count;
+    });
+
+    this.submit = function() {
+      var contacts = _.transform(ctrl.ids, function(records, cid) {
+        records.push({contact_id: cid});
+      });
+      ctrl.start({
+        values: {
+          title: 'Hidden Group ' + Date.now(),
+          is_hidden: true,
+          'group_type:name': ['Mailing List'],
+        },
+        chain: {
+          contacts: ['GroupContact', 'save', {
+            defaults: {group_id: '$id'},
+            records: contacts
+          }],
+          mailing: ['Mailing', 'create', {
+            values: {
+              name: ctrl.name,
+              template_type: templateTypes[0].id
+            }
+          }, 0],
+          mailingGroup: ['MailingGroup', 'create', {
+            values: {
+              group_type: 'Include',
+              'entity_table:name': 'Group',
+              entity_id: '$id',
+              mailing_id: '$mailing.id'
+            },
+          }, 0]
+        }
+      });
+    };
+
+
+    this.onSuccess = function(result) {
+      window.location = CRM.url('civicrm/a#/mailing/' + result[0].mailing.id);
+    };
+
+    this.onError = function() {
+      CRM.alert(ts('An error occurred while attempting to create mailing.'), ts('Error'), 'error');
+      this.cancel();
+    };
+
+  });
+})(angular, CRM.$, CRM._);
diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.html b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.html
new file mode 100644 (file)
index 0000000..f03ae5d
--- /dev/null
@@ -0,0 +1,34 @@
+<div id="bootstrap-theme" crm-dialog="crmSearchTask">
+  <form name="crmSearchTaskMailingForm" ng-controller="crmSearchTaskMailing as $ctrl">
+    <div class="alert alert-info">
+      <p>{{:: ts('Compose and send a mass-mailing to the %1 selected contacts (you will be able to add or exclude additional groups of contacts in the next step).', {1: $ctrl.ids.length}) }}</p>
+    </div>
+    <label for="crm-search-task-mailing-name">{{:: ts('Mailing Name') }} <span class="crm-marker">*</span></label>
+    <input required class="form-control" id="crm-search-task-mailing-name" ng-model="$ctrl.name">
+    <br>
+    <div ng-if="!$ctrl.run" class="alert" ng-class="{'alert-success': $ctrl.recipientCount === $ctrl.ids.length, 'alert-danger': $ctrl.recipientCount === 0, 'alert-warning': $ctrl.recipientCount !== 0 && $ctrl.recipientCount &lt; $ctrl.ids.length}">
+      <div ng-if="!$ctrl.recipientCount && $ctrl.recipientCount !== 0">
+        <i class="crm-i fa-spinner fa-spin"></i>
+        {{:: ts('Checking recipients...') }}
+      </div>
+      <div ng-if="$ctrl.recipientCount === 0">
+        <i class="crm-i fa-exclamation-triangle"></i>
+        {{:: ts('None of the selected contacts are eligible to receive mailings (due to lack of email address or unsubscribe status).') }}
+      </div>
+      <div ng-if="$ctrl.recipientCount && $ctrl.recipientCount &lt; $ctrl.ids.length">
+        <i class="crm-i fa-exclamation-triangle"></i>
+        {{:: ts('%1 of the selected contacts cannot receive mailings (due to lack of email address or unsubscribe status).', {1: $ctrl.ids.length - $ctrl.recipientCount}) }}
+      </div>
+      <div ng-if="$ctrl.recipientCount === $ctrl.ids.length">
+        <i class="crm-i fa-check-circle"></i>
+        {{:: ts('All of the selected contacts have active email addresses.') }}
+      </div>
+    </div>
+    <div ng-if="$ctrl.run" class="crm-search-task-progress">
+      <h5>{{:: ts('Creating mailing...') }}</h5>
+      <crm-search-batch-runner entity="'Group'" action="create" params="$ctrl.run" success="$ctrl.onSuccess(result)" error="$ctrl.onError()" ></crm-search-batch-runner>
+    </div>
+    <crm-dialog-button text="ts('Cancel')" icons="{primary: 'fa-times'}" on-click="$ctrl.cancel()" disabled="$ctrl.run" />
+    <crm-dialog-button text="ts('Create Mailing')" icons="{primary: 'fa-paper-plane'}" on-click="$ctrl.submit()" disabled="!$ctrl.recipientCount || $ctrl.run || !crmSearchTaskMailingForm.$valid" />
+  </form>
+</div>