3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 * Class CRM_Mailing_BAO_MailingTest
31 class CRM_Mailing_BAO_MailingTest
extends CiviUnitTestCase
{
33 protected $allowedContactId = 0;
35 public function setUp() {
39 public function tearDown() {
42 CRM_Core_I18n_Schema
::makeSinglelingual('en_US');
48 * Helper function to assert whether the calculated recipients of a mailing
49 * match the expected list
52 * @param $expectedRecipients array
53 * Array of contact ID that should be in the recipient list.
55 private function assertRecipientsCorrect($mailingID, $expectedRecipients) {
57 // Reset keys to ensure match
58 $expectedRecipients = array_values($expectedRecipients);
60 // Load the recipients as a list of contact IDs
61 CRM_Mailing_BAO_Mailing
::getRecipients($mailingID);
62 $recipients = $this->callAPISuccess('MailingRecipients', 'get', array('mailing_id' => $mailingID));
63 $contactIDs = array();
64 foreach ($recipients['values'] as $recipient) {
65 $contactIDs[] = $recipient['contact_id'];
68 // Check the lists match
69 $this->assertTreeEquals($expectedRecipients, $contactIDs);
73 * Helper function to create a mailing include/exclude group.
80 private function createMailingGroup($mailingID, $groupID, $type = 'Include') {
81 return $this->callAPISuccess('MailingGroup', 'create', array(
82 'mailing_id' => $mailingID,
83 'group_type' => $type,
84 'entity_table' => CRM_Contact_BAO_Group
::getTableName(),
85 'entity_id' => $groupID,
90 * Test to ensure that using ACL permitted contacts are correctly fetched for bulk mailing
92 public function testgetRecipientsUsingACL() {
93 $this->prepareForACLs();
94 $this->createLoggedInUser();
95 // create hook to build ACL where clause which choses $this->allowedContactId as the only contact to be considered as mail recipient
96 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereAllowedOnlyOne'));
97 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array('access CiviCRM', 'view my contact');
99 // Create dummy group and assign 2 contacts
100 $name = 'Test static group ' . substr(sha1(rand()), 0, 7);
101 $groupID = $this->groupCreate([
106 // Create 2 contacts where one of them identified as $this->allowedContactId will be used in ACL where clause
107 $contactID1 = $this->individualCreate(array(), 0);
108 $this->allowedContactId
= $this->individualCreate(array(), 1);
109 $this->callAPISuccess('GroupContact', 'Create', array(
110 'group_id' => $groupID,
111 'contact_id' => $contactID1,
113 $this->callAPISuccess('GroupContact', 'Create', array(
114 'group_id' => $groupID,
115 'contact_id' => $this->allowedContactId
,
118 // Create dummy mailing
119 $mailingID = $this->callAPISuccess('Mailing', 'create', array())['id'];
120 $this->createMailingGroup($mailingID, $groupID);
122 // Check that the desired contact (identified as Contact ID - $this->allowedContactId) is the only
123 // contact chosen as mail recipient
124 $expectedContactIDs = [$this->allowedContactId
];
125 $this->assertRecipientsCorrect($mailingID, $expectedContactIDs);
127 $this->cleanUpAfterACLs();
128 $this->callAPISuccess('Group', 'Delete', ['id' => $groupID]);
129 $this->contactDelete($contactID1);
130 $this->contactDelete($this->allowedContactId
);
134 * Test verify that a disabled mailing group doesn't prvent access to the mailing generated with the group.
136 public function testGetMailingDisabledGroup() {
137 $this->prepareForACLs();
138 $this->createLoggedInUser();
139 // create hook to build ACL where clause which choses $this->allowedContactId as the only contact to be considered as mail recipient
140 $this->hookClass
->setHook('civicrm_aclWhereClause', array($this, 'aclWhereAllowedOnlyOne'));
141 $this->hookClass
->setHook('civicrm_aclGroup', array($this, 'hook_civicrm_aclGroup'));
142 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= array('access CiviCRM', 'edit groups');
143 // Create dummy group and assign 2 contacts
144 $name = 'Test static group ' . substr(sha1(rand()), 0, 7);
145 $groupID = $this->groupCreate([
150 $contactID = $this->individualCreate(array(), 0);
151 $this->callAPISuccess('GroupContact', 'Create', array(
152 'group_id' => $groupID,
153 'contact_id' => $contactID,
156 // Create dummy mailing
157 $mailingID = $this->callAPISuccess('Mailing', 'create', array())['id'];
158 $this->createMailingGroup($mailingID, $groupID);
159 // Now disable the group.
160 $this->callAPISuccess('group', 'create', [
164 $groups = CRM_Mailing_BAO_Mailing
::mailingACLIDs();
165 $this->assertTrue(in_array($groupID, $groups));
166 $this->cleanUpAfterACLs();
167 $this->contactDelete($contactID);
171 * Build ACL where clause
173 * @implements CRM_Utils_Hook::aclWhereClause
175 * @param string $type
176 * @param array $tables
177 * @param array $whereTables
178 * @param int $contactID
179 * @param string $where
181 public function aclWhereAllowedOnlyOne($type, &$tables, &$whereTables, &$contactID, &$where) {
182 $where = " contact_a.id = " . $this->allowedContactId
;
186 * Implements ACLGroup hook.
188 * @implements CRM_Utils_Hook::aclGroup
190 * aclGroup function returns a list of permitted groups
191 * @param string $type
192 * @param int $contactID
193 * @param string $tableName
194 * @param array $allGroups
195 * @param array $currentGroups
197 public function hook_civicrm_aclGroup($type, $contactID, $tableName, &$allGroups, &$currentGroups) {
198 //don't use api - you will get a loop
199 $sql = " SELECT * FROM civicrm_group";
201 $dao = CRM_Core_DAO
::executeQuery($sql);
202 while ($dao->fetch()) {
203 $groups[] = $dao->id
;
205 if (!empty($allGroups)) {
206 //all groups is empty if we really mean all groups but if a filter like 'is_disabled' is already applied
207 // it is populated, ajax calls from Manage Groups will leave empty but calls from New Mailing pass in a filtered list
208 $currentGroups = array_intersect($groups, array_flip($allGroups));
211 $currentGroups = $groups;
217 * @todo Missing tests:
218 * - Ensure opt out emails are not mailed
219 * - Ensure 'stop' emails are not mailed
220 * - Ensure the deceased are not mailed
221 * - Tests for getLocationFilterAndOrderBy (selecting correct 'type')
226 * Test to ensure that static and smart mailing groups can be added to an
227 * email mailing as 'include' or 'exclude' groups - and the members are
228 * included or excluded appropriately.
230 * contact 0 : static 0 (inc) + smart 5 (exc)
231 * contact 1 : static 0 (inc)
232 * contact 2 : static 1 (inc)
233 * contact 3 : static 1 (inc)
234 * contact 4 : static 2 (exc) + smart 3 (inc)
235 * contact 5 : smart 3 (inc)
236 * contact 6 : smart 4 (inc)
237 * contact 7 : smart 4 (inc)
238 * contact 8 : smart 5 (base)
240 * here 'contact 1 : static 0 (inc)' identified as static group $groupIDs[0]
241 * that has 'contact 1' identified as $contactIDs[0] and Included in the mailing recipient list
243 public function testgetRecipientsEmailGroupIncludeExclude() {
244 // Set up groups; 3 standard, 4 smart
246 for ($i = 0; $i < 7; $i++
) {
248 'name' => 'Test static group ' . $i,
249 'title' => 'Test static group ' . $i,
253 $groupIDs[$i] = $this->groupCreate($params);
256 $groupIDs[$i] = $this->smartGroupCreate(array(
257 'formValues' => ['last_name' => (($i == 6) ?
'smart5' : 'smart' . $i)],
264 $this->individualCreate(array('last_name' => 'smart5'), 0),
265 $this->individualCreate(array(), 1),
266 $this->individualCreate(array(), 2),
267 $this->individualCreate(array(), 3),
268 $this->individualCreate(array('last_name' => 'smart3'), 4),
269 $this->individualCreate(array('last_name' => 'smart3'), 5),
270 $this->individualCreate(array('last_name' => 'smart4'), 6),
271 $this->individualCreate(array('last_name' => 'smart4'), 7),
272 $this->individualCreate(array('last_name' => 'smart5'), 8),
275 // Add contacts to static groups
276 $this->callAPISuccess('GroupContact', 'Create', array(
277 'group_id' => $groupIDs[0],
278 'contact_id' => $contactIDs[0],
280 $this->callAPISuccess('GroupContact', 'Create', array(
281 'group_id' => $groupIDs[0],
282 'contact_id' => $contactIDs[1],
284 $this->callAPISuccess('GroupContact', 'Create', array(
285 'group_id' => $groupIDs[1],
286 'contact_id' => $contactIDs[2],
288 $this->callAPISuccess('GroupContact', 'Create', array(
289 'group_id' => $groupIDs[1],
290 'contact_id' => $contactIDs[3],
292 $this->callAPISuccess('GroupContact', 'Create', array(
293 'group_id' => $groupIDs[2],
294 'contact_id' => $contactIDs[4],
297 // Force rebuild the smart groups
298 for ($i = 3; $i < 7; $i++
) {
299 $group = new CRM_Contact_DAO_Group();
300 $group->id
= $groupIDs[$i];
302 CRM_Contact_BAO_GroupContactCache
::load($group, TRUE);
305 // Check that we can include static groups in the mailing.
306 // Expected: Contacts [0-3] should be included.
307 $mailing = $this->callAPISuccess('Mailing', 'create', array());
308 $this->createMailingGroup($mailing['id'], $groupIDs[0]);
309 $this->createMailingGroup($mailing['id'], $groupIDs[1]);
310 $this->createMailingGroup($mailing['id'], $groupIDs[6], 'Base');
311 $expected = $contactIDs;
312 unset($expected[4], $expected[5], $expected[6], $expected[7], $expected[8]);
313 $this->assertRecipientsCorrect($mailing['id'], $expected);
315 // Check that we can include smart groups in the mailing too.
316 // Expected: All contacts should be included.
317 // Also (dev/mail/6): Enable multilingual mode to check that restructing group doesn't affect recipient rebuilding
318 $this->enableMultilingual();
319 $this->createMailingGroup($mailing['id'], $groupIDs[3]);
320 $this->createMailingGroup($mailing['id'], $groupIDs[4]);
321 $this->createMailingGroup($mailing['id'], $groupIDs[5]);
322 // Check that all the contacts whould be present is recipient list as static group [0], [1] and [2] and
323 // smart groups [3], [4] and [5] is included in the recipient listing.
324 // NOTE: that contact[8] is present in both included smart group[5] and base smart group [6] so it will be
325 // present in recipient list as contact(s) from Base smart groups are not excluded the list as per (dev/mail/13)
326 $this->assertRecipientsCorrect($mailing['id'], $contactIDs);
328 // Check we can exclude static groups from the mailing.
329 // Expected: All contacts except [4]
330 $this->createMailingGroup($mailing['id'], $groupIDs[2], 'Exclude');
331 $expected = $contactIDs;
333 // NOTE: as per (dev/mail/13) if a contact A is present in smartGroup [5] which is Included in the mailing AND
334 // also present in another smartGroup [6] which is considered as Base group, then contact A should not be excluded from
335 // the recipient list due to later
336 $this->assertRecipientsCorrect($mailing['id'], $expected);
338 // Check we can exclude smart groups from the mailing too.
339 // Expected: All contacts except [0], [4] and [8]
340 $this->createMailingGroup($mailing['id'], $groupIDs[5], 'Exclude');
341 $expected = $contactIDs;
342 // As contact [0] and [8] belongs to excluded smart group[5] and base smart group[6] respectively,
343 // both these contacts should not be present in the mailing list
344 unset($expected[0], $expected[4], $expected[8]);
345 $this->assertRecipientsCorrect($mailing['id'], $expected);
347 // Tear down: delete mailing, groups, contacts
348 $this->deleteMailing($mailing['id']);
349 foreach ($groupIDs as $groupID) {
350 $this->groupDelete($groupID);
352 foreach ($contactIDs as $contactID) {
353 $this->contactDelete($contactID);
358 * Test CRM_Mailing_BAO_Mailing::getRecipients() on sms mode
360 public function testgetRecipientsSMS() {
361 // Tests for SMS bulk mailing recipients
362 // +CRM-21320 Ensure primary mobile number is selected over non-primary
365 $smartGroupParams = array(
366 'formValues' => array('contact_type' => array('IN' => array('Individual'))),
368 $group = $this->smartGroupCreate($smartGroupParams);
369 $sms_provider = $this->callAPISuccess('SmsProvider', 'create', array(
373 'username' => "Test",
374 'password' => "Test",
379 // Create Contact 1 and add in group
380 $contactID1 = $this->individualCreate(array(), 0);
381 $this->callAPISuccess('GroupContact', 'Create', array(
382 'group_id' => $group,
383 'contact_id' => $contactID1,
386 // Create contact 2 and add in group
387 $contactID2 = $this->individualCreate(array(), 1);
388 $this->callAPISuccess('GroupContact', 'Create', array(
389 'group_id' => $group,
390 'contact_id' => $contactID2,
393 $contactIDPhoneRecords = array(
394 $contactID1 => array(
395 'primary_phone_id' => CRM_Utils_Array
::value('id', $this->callAPISuccess('Phone', 'create', array(
396 'contact_id' => $contactID1,
398 'location_type_id' => "Home",
399 'phone_type_id' => "Mobile",
402 'other_phone_id' => CRM_Utils_Array
::value('id', $this->callAPISuccess('Phone', 'create', array(
403 'contact_id' => $contactID1,
405 'location_type_id' => "Work",
406 'phone_type_id' => "Mobile",
410 // Create the non-primary with a lower ID than the primary, to test CRM-21320
411 $contactID2 => array(
412 'other_phone_id' => CRM_Utils_Array
::value('id', $this->callAPISuccess('Phone', 'create', array(
413 'contact_id' => $contactID2,
415 'location_type_id' => "Home",
416 'phone_type_id' => "Mobile",
419 'primary_phone_id' => CRM_Utils_Array
::value('id', $this->callAPISuccess('Phone', 'create', array(
420 'contact_id' => $contactID2,
422 'location_type_id' => "Work",
423 'phone_type_id' => "Mobile",
429 // Prepare expected results
430 $checkPhoneIDs = array(
431 $contactID1 => $contactIDPhoneRecords[$contactID1]['primary_phone_id'],
432 $contactID2 => $contactIDPhoneRecords[$contactID2]['primary_phone_id'],
436 $mailing = $this->callAPISuccess('Mailing', 'create', array('sms_provider_id' => $sms_provider['id']));
437 $mailingInclude = $this->createMailingGroup($mailing['id'], $group);
440 CRM_Mailing_BAO_Mailing
::getRecipients($mailing['id']);
441 $recipients = $this->callAPISuccess('MailingRecipients', 'get', array('mailing_id' => $mailing['id']));
443 // Check the count is correct
444 $this->assertEquals(2, $recipients['count'], 'Check recipient count');
446 // Check we got the 'primary' mobile for both contacts
447 foreach ($recipients['values'] as $value) {
448 $this->assertEquals($value['phone_id'], $checkPhoneIDs[$value['contact_id']], 'Check correct phone number for contact ' . $value['contact_id']);
452 $this->deleteMailing($mailing['id']);
453 $this->callAPISuccess('SmsProvider', 'Delete', array('id' => $sms_provider['id']));
454 $this->groupDelete($group);
455 $this->contactDelete($contactID1);
456 $this->contactDelete($contactID2);
460 * Test alterMailingRecipients Hook which is called twice when we create a Mailing,
461 * 1. In the first call we will modify the mailing filter to include only deceased recipients
462 * 2. In the second call we will check if only deceased recipient is populated in MailingRecipient table
464 public function testAlterMailingRecipientsHook() {
465 $groupID = $this->groupCreate();
466 $this->tagCreate(array('name' => 'Tagged'));
468 // Create deseased Contact 1 and add in group
469 $contactID1 = $this->individualCreate(array('email' => 'abc@test.com', 'is_deceased' => 1), 0);
470 // Create deseased Contact 2 and add in group
471 $contactID2 = $this->individualCreate(array('email' => 'def@test.com'), 1);
472 // Create deseased Contact 3 and add in group
473 $contactID3 = $this->individualCreate(array('email' => 'ghi@test.com', 'is_deceased' => 1), 2);
475 // Add both the created contacts in group
476 $this->callAPISuccess('GroupContact', 'Create', array(
477 'group_id' => $groupID,
478 'contact_id' => $contactID1,
480 $this->callAPISuccess('GroupContact', 'Create', array(
481 'group_id' => $groupID,
482 'contact_id' => $contactID2,
484 $this->callAPISuccess('GroupContact', 'Create', array(
485 'group_id' => $groupID,
486 'contact_id' => $contactID3,
488 $this->entityTagAdd(array('contact_id' => $contactID3, 'tag_id' => 'Tagged'));
490 // trigger the alterMailingRecipients hook
491 $this->hookClass
->setHook('civicrm_alterMailingRecipients', array($this, 'alterMailingRecipients'));
493 // create mailing that will trigger alterMailingRecipients hook
495 'name' => 'mailing name',
496 'subject' => 'Test Subject',
497 'body_html' => '<p>HTML Body</p>',
498 'text_html' => 'Text Body',
500 'groups' => array('include' => array($groupID)),
501 'scheduled_date' => 'now',
503 $this->callAPISuccess('Mailing', 'create', $params);
507 * @implements CRM_Utils_Hook::alterMailingRecipients
509 * @param object $mailingObject
510 * @param array $criteria
511 * @param string $context
513 public function alterMailingRecipients(&$mailingObject, &$criteria, $context) {
514 if ($context == 'pre') {
515 // modify the filter to include only deceased recipient(s) that is Tagged
516 $criteria['is_deceased'] = CRM_Utils_SQL_Select
::fragment()->where("civicrm_contact.is_deceased = 1");
517 $criteria['tagged_contact'] = CRM_Utils_SQL_Select
::fragment()
518 ->join('civicrm_entity_tag', "INNER JOIN civicrm_entity_tag et ON et.entity_id = civicrm_contact.id AND et.entity_table = 'civicrm_contact'")
519 ->join('civicrm_tag', "INNER JOIN civicrm_tag t ON t.id = et.tag_id")
520 ->where("t.name = 'Tagged'");
523 $mailingRecipients = $this->callAPISuccess('MailingRecipients', 'get', array(
524 'mailing_id' => $mailingObject->id
,
525 'api.Email.getvalue' => array(
526 'id' => '$value.email_id',
530 $this->assertEquals(1, $mailingRecipients['count'], 'Check recipient count');
531 $this->assertEquals('ghi@test.com', $mailingRecipients['values'][$mailingRecipients['id']]['api.Email.getvalue'], 'Check if recipient email belong to deceased contact');