diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php index da0c5d61d135..b0ded1f45eee 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php @@ -120,6 +120,16 @@ public function _run(\Civi\Api4\Generic\Result $result) { ], ]; } + 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') { diff --git a/ext/search_kit/ang/crmSearchTasks/crmSearchBatchRunner.component.js b/ext/search_kit/ang/crmSearchTasks/crmSearchBatchRunner.component.js index 6b910d825af1..88c90f7f4b32 100644 --- a/ext/search_kit/ang/crmSearchTasks/crmSearchBatchRunner.component.js +++ b/ext/search_kit/ang/crmSearchTasks/crmSearchBatchRunner.component.js @@ -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 index 000000000000..a5f14bb30373 --- /dev/null +++ b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.ctrl.js @@ -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 index 000000000000..f03ae5d90965 --- /dev/null +++ b/ext/search_kit/ang/crmSearchTasks/crmSearchTaskMailing.html @@ -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 < $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 < $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>