Merge pull request #11726 from scardinius/crm-21808
[civicrm-core.git] / tests / phpunit / CRM / Mailing / BAO / MailingTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Class CRM_Mailing_BAO_MailingTest
30 */
31 class CRM_Mailing_BAO_MailingTest extends CiviUnitTestCase {
32
33 protected $allowedContactId = 0;
34
35 public function setUp() {
36 parent::setUp();
37 }
38
39 public function tearDown() {
40 global $dbLocale;
41 if ($dbLocale) {
42 CRM_Core_I18n_Schema::makeSinglelingual('en_US');
43 }
44 parent::tearDown();
45 }
46
47 /**
48 * Helper function to assert whether the calculated recipients of a mailing
49 * match the expected list
50 *
51 * @param $mailingID
52 * @param $expectedRecipients array
53 * Array of contact ID that should be in the recipient list.
54 */
55 private function assertRecipientsCorrect($mailingID, $expectedRecipients) {
56
57 // Reset keys to ensure match
58 $expectedRecipients = array_values($expectedRecipients);
59
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'];
66 }
67
68 // Check the lists match
69 $this->assertTreeEquals($expectedRecipients, $contactIDs);
70 }
71
72 /**
73 * Helper function to create a mailing include/exclude group.
74 *
75 * @param $mailingID
76 * @param $groupID
77 * @param string $type
78 * @return array|int
79 */
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,
86 ));
87 }
88
89 /**
90 * Test to ensure that using ACL permitted contacts are correctly fetched for bulk mailing
91 */
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');
98
99 // Create dummy group and assign 2 contacts
100 $name = 'Test static group ' . substr(sha1(rand()), 0, 7);
101 $groupID = $this->groupCreate([
102 'name' => $name,
103 'title' => $name,
104 'is_active' => 1,
105 ]);
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,
112 ));
113 $this->callAPISuccess('GroupContact', 'Create', array(
114 'group_id' => $groupID,
115 'contact_id' => $this->allowedContactId,
116 ));
117
118 // Create dummy mailing
119 $mailingID = $this->callAPISuccess('Mailing', 'create', array())['id'];
120 $this->createMailingGroup($mailingID, $groupID);
121
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);
126
127 $this->cleanUpAfterACLs();
128 $this->callAPISuccess('Group', 'Delete', ['id' => $groupID]);
129 $this->contactDelete($contactID1);
130 $this->contactDelete($this->allowedContactId);
131 }
132
133 /**
134 * Test verify that a disabled mailing group doesn't prvent access to the mailing generated with the group.
135 */
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([
146 'name' => $name,
147 'title' => $name,
148 'is_active' => 1,
149 ]);
150 $contactID = $this->individualCreate(array(), 0);
151 $this->callAPISuccess('GroupContact', 'Create', array(
152 'group_id' => $groupID,
153 'contact_id' => $contactID,
154 ));
155
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', [
161 'id' => $groupID,
162 'is_active' => 0,
163 ]);
164 $groups = CRM_Mailing_BAO_Mailing::mailingACLIDs();
165 $this->assertTrue(in_array($groupID, $groups));
166 $this->cleanUpAfterACLs();
167 $this->contactDelete($contactID);
168 }
169
170 /**
171 * Build ACL where clause
172 *
173 * @implements CRM_Utils_Hook::aclWhereClause
174 *
175 * @param string $type
176 * @param array $tables
177 * @param array $whereTables
178 * @param int $contactID
179 * @param string $where
180 */
181 public function aclWhereAllowedOnlyOne($type, &$tables, &$whereTables, &$contactID, &$where) {
182 $where = " contact_a.id = " . $this->allowedContactId;
183 }
184
185 /**
186 * Implements ACLGroup hook.
187 *
188 * @implements CRM_Utils_Hook::aclGroup
189 *
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
196 */
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";
200 $groups = array();
201 $dao = CRM_Core_DAO::executeQuery($sql);
202 while ($dao->fetch()) {
203 $groups[] = $dao->id;
204 }
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));
209 }
210 else {
211 $currentGroups = $groups;
212 }
213 }
214
215
216 /**
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')
222 * - ...
223 */
224
225 /**
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.
229 *
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)
239 *
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
242 */
243 public function testgetRecipientsEmailGroupIncludeExclude() {
244 // Set up groups; 3 standard, 4 smart
245 $groupIDs = array();
246 for ($i = 0; $i < 7; $i++) {
247 $params = array(
248 'name' => 'Test static group ' . $i,
249 'title' => 'Test static group ' . $i,
250 'is_active' => 1,
251 );
252 if ($i < 3) {
253 $groupIDs[$i] = $this->groupCreate($params);
254 }
255 else {
256 $groupIDs[$i] = $this->smartGroupCreate(array(
257 'formValues' => ['last_name' => (($i == 6) ? 'smart5' : 'smart' . $i)],
258 ), $params);
259 }
260 }
261
262 // Create contacts
263 $contactIDs = array(
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),
273 );
274
275 // Add contacts to static groups
276 $this->callAPISuccess('GroupContact', 'Create', array(
277 'group_id' => $groupIDs[0],
278 'contact_id' => $contactIDs[0],
279 ));
280 $this->callAPISuccess('GroupContact', 'Create', array(
281 'group_id' => $groupIDs[0],
282 'contact_id' => $contactIDs[1],
283 ));
284 $this->callAPISuccess('GroupContact', 'Create', array(
285 'group_id' => $groupIDs[1],
286 'contact_id' => $contactIDs[2],
287 ));
288 $this->callAPISuccess('GroupContact', 'Create', array(
289 'group_id' => $groupIDs[1],
290 'contact_id' => $contactIDs[3],
291 ));
292 $this->callAPISuccess('GroupContact', 'Create', array(
293 'group_id' => $groupIDs[2],
294 'contact_id' => $contactIDs[4],
295 ));
296
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];
301 $group->find(TRUE);
302 CRM_Contact_BAO_GroupContactCache::load($group, TRUE);
303 }
304
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);
314
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);
327
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;
332 unset($expected[4]);
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);
337
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);
346
347 // Tear down: delete mailing, groups, contacts
348 $this->deleteMailing($mailing['id']);
349 foreach ($groupIDs as $groupID) {
350 $this->groupDelete($groupID);
351 }
352 foreach ($contactIDs as $contactID) {
353 $this->contactDelete($contactID);
354 }
355 }
356
357 /**
358 * Test CRM_Mailing_BAO_Mailing::getRecipients() on sms mode
359 */
360 public function testgetRecipientsSMS() {
361 // Tests for SMS bulk mailing recipients
362 // +CRM-21320 Ensure primary mobile number is selected over non-primary
363
364 // Setup
365 $smartGroupParams = array(
366 'formValues' => array('contact_type' => array('IN' => array('Individual'))),
367 );
368 $group = $this->smartGroupCreate($smartGroupParams);
369 $sms_provider = $this->callAPISuccess('SmsProvider', 'create', array(
370 'sequential' => 1,
371 'name' => 1,
372 'title' => "Test",
373 'username' => "Test",
374 'password' => "Test",
375 'api_type' => 1,
376 'is_active' => 1,
377 ));
378
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,
384 ));
385
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,
391 ));
392
393 $contactIDPhoneRecords = array(
394 $contactID1 => array(
395 'primary_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
396 'contact_id' => $contactID1,
397 'phone' => "01 01",
398 'location_type_id' => "Home",
399 'phone_type_id' => "Mobile",
400 'is_primary' => 1,
401 ))),
402 'other_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
403 'contact_id' => $contactID1,
404 'phone' => "01 02",
405 'location_type_id' => "Work",
406 'phone_type_id' => "Mobile",
407 'is_primary' => 0,
408 ))),
409 ),
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,
414 'phone' => "02 01",
415 'location_type_id' => "Home",
416 'phone_type_id' => "Mobile",
417 'is_primary' => 0,
418 ))),
419 'primary_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
420 'contact_id' => $contactID2,
421 'phone' => "02 02",
422 'location_type_id' => "Work",
423 'phone_type_id' => "Mobile",
424 'is_primary' => 1,
425 ))),
426 ),
427 );
428
429 // Prepare expected results
430 $checkPhoneIDs = array(
431 $contactID1 => $contactIDPhoneRecords[$contactID1]['primary_phone_id'],
432 $contactID2 => $contactIDPhoneRecords[$contactID2]['primary_phone_id'],
433 );
434
435 // Create mailing
436 $mailing = $this->callAPISuccess('Mailing', 'create', array('sms_provider_id' => $sms_provider['id']));
437 $mailingInclude = $this->createMailingGroup($mailing['id'], $group);
438
439 // Get recipients
440 CRM_Mailing_BAO_Mailing::getRecipients($mailing['id']);
441 $recipients = $this->callAPISuccess('MailingRecipients', 'get', array('mailing_id' => $mailing['id']));
442
443 // Check the count is correct
444 $this->assertEquals(2, $recipients['count'], 'Check recipient count');
445
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']);
449 }
450
451 // Tidy up
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);
457 }
458
459 }