Commit | Line | Data |
---|---|---|
9f0a25d7 J |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
2fe49090 | 4 | | CiviCRM version 5 | |
9f0a25d7 | 5 | +--------------------------------------------------------------------+ |
8c9251b3 | 6 | | Copyright CiviCRM LLC (c) 2004-2018 | |
9f0a25d7 J |
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 | ||
fae688ec | 33 | protected $allowedContactId = 0; |
34 | ||
9f0a25d7 J |
35 | public function setUp() { |
36 | parent::setUp(); | |
37 | } | |
38 | ||
df3320dc | 39 | public function tearDown() { |
40 | global $dbLocale; | |
41 | if ($dbLocale) { | |
42 | CRM_Core_I18n_Schema::makeSinglelingual('en_US'); | |
43 | } | |
44 | parent::tearDown(); | |
45 | } | |
46 | ||
6ff6f4f2 J |
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, | |
df3320dc | 84 | 'entity_table' => CRM_Contact_BAO_Group::getTableName(), |
6ff6f4f2 J |
85 | 'entity_id' => $groupID, |
86 | )); | |
87 | } | |
88 | ||
fae688ec | 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(); | |
2acf8c58 | 128 | $this->callAPISuccess('Group', 'Delete', ['id' => $groupID]); |
fae688ec | 129 | $this->contactDelete($contactID1); |
130 | $this->contactDelete($this->allowedContactId); | |
131 | } | |
132 | ||
2acf8c58 SL |
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 | ||
fae688ec | 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 | ||
2acf8c58 SL |
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 | ||
251a8849 | 216 | /** |
7ff80daf J |
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 | * - ... | |
251a8849 J |
223 | */ |
224 | ||
7ff80daf J |
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) | |
70170c4f | 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 | |
7ff80daf J |
242 | */ |
243 | public function testgetRecipientsEmailGroupIncludeExclude() { | |
70170c4f | 244 | // Set up groups; 3 standard, 4 smart |
7ff80daf | 245 | $groupIDs = array(); |
70170c4f | 246 | for ($i = 0; $i < 7; $i++) { |
7ff80daf J |
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( | |
70170c4f | 257 | 'formValues' => ['last_name' => (($i == 6) ? 'smart5' : 'smart' . $i)], |
7ff80daf J |
258 | ), $params); |
259 | } | |
260 | } | |
261 | ||
262 | // Create contacts | |
263 | $contactIDs = array( | |
70170c4f | 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), | |
7ff80daf J |
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 | |
70170c4f | 298 | for ($i = 3; $i < 7; $i++) { |
7ff80daf J |
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]); | |
70170c4f | 310 | $this->createMailingGroup($mailing['id'], $groupIDs[6], 'Base'); |
7ff80daf | 311 | $expected = $contactIDs; |
70170c4f | 312 | unset($expected[4], $expected[5], $expected[6], $expected[7], $expected[8]); |
7ff80daf J |
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. | |
df3320dc | 317 | // Also (dev/mail/6): Enable multilingual mode to check that restructing group doesn't affect recipient rebuilding |
318 | $this->enableMultilingual(); | |
7ff80daf J |
319 | $this->createMailingGroup($mailing['id'], $groupIDs[3]); |
320 | $this->createMailingGroup($mailing['id'], $groupIDs[4]); | |
70170c4f | 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) | |
7ff80daf J |
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]); | |
70170c4f | 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 | |
7ff80daf J |
336 | $this->assertRecipientsCorrect($mailing['id'], $expected); |
337 | ||
338 | // Check we can exclude smart groups from the mailing too. | |
70170c4f | 339 | // Expected: All contacts except [0], [4] and [8] |
7ff80daf J |
340 | $this->createMailingGroup($mailing['id'], $groupIDs[5], 'Exclude'); |
341 | $expected = $contactIDs; | |
70170c4f | 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]); | |
7ff80daf J |
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 | } | |
7ff80daf J |
355 | } |
356 | ||
9f0a25d7 | 357 | /** |
6f3a35e0 | 358 | * Test CRM_Mailing_BAO_Mailing::getRecipients() on sms mode |
9f0a25d7 | 359 | */ |
7ff80daf | 360 | public function testgetRecipientsSMS() { |
9f0a25d7 J |
361 | // Tests for SMS bulk mailing recipients |
362 | // +CRM-21320 Ensure primary mobile number is selected over non-primary | |
363 | ||
364 | // Setup | |
6f3a35e0 | 365 | $smartGroupParams = array( |
366 | 'formValues' => array('contact_type' => array('IN' => array('Individual'))), | |
367 | ); | |
368 | $group = $this->smartGroupCreate($smartGroupParams); | |
9f0a25d7 J |
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 | ||
6f3a35e0 | 379 | // Create Contact 1 and add in group |
380 | $contactID1 = $this->individualCreate(array(), 0); | |
9f0a25d7 J |
381 | $this->callAPISuccess('GroupContact', 'Create', array( |
382 | 'group_id' => $group, | |
6f3a35e0 | 383 | 'contact_id' => $contactID1, |
9f0a25d7 J |
384 | )); |
385 | ||
6f3a35e0 | 386 | // Create contact 2 and add in group |
387 | $contactID2 = $this->individualCreate(array(), 1); | |
9f0a25d7 J |
388 | $this->callAPISuccess('GroupContact', 'Create', array( |
389 | 'group_id' => $group, | |
6f3a35e0 | 390 | 'contact_id' => $contactID2, |
9f0a25d7 J |
391 | )); |
392 | ||
6f3a35e0 | 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 | ), | |
251a8849 | 410 | // Create the non-primary with a lower ID than the primary, to test CRM-21320 |
6f3a35e0 | 411 | $contactID2 => array( |
251a8849 | 412 | 'other_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array( |
6f3a35e0 | 413 | 'contact_id' => $contactID2, |
414 | 'phone' => "02 01", | |
415 | 'location_type_id' => "Home", | |
416 | 'phone_type_id' => "Mobile", | |
251a8849 | 417 | 'is_primary' => 0, |
6f3a35e0 | 418 | ))), |
251a8849 | 419 | 'primary_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array( |
6f3a35e0 | 420 | 'contact_id' => $contactID2, |
421 | 'phone' => "02 02", | |
422 | 'location_type_id' => "Work", | |
423 | 'phone_type_id' => "Mobile", | |
251a8849 | 424 | 'is_primary' => 1, |
6f3a35e0 | 425 | ))), |
426 | ), | |
427 | ); | |
428 | ||
9f0a25d7 | 429 | // Prepare expected results |
6f3a35e0 | 430 | $checkPhoneIDs = array( |
431 | $contactID1 => $contactIDPhoneRecords[$contactID1]['primary_phone_id'], | |
432 | $contactID2 => $contactIDPhoneRecords[$contactID2]['primary_phone_id'], | |
9f0a25d7 J |
433 | ); |
434 | ||
435 | // Create mailing | |
436 | $mailing = $this->callAPISuccess('Mailing', 'create', array('sms_provider_id' => $sms_provider['id'])); | |
6ff6f4f2 | 437 | $mailingInclude = $this->createMailingGroup($mailing['id'], $group); |
9f0a25d7 J |
438 | |
439 | // Get recipients | |
6ff6f4f2 | 440 | CRM_Mailing_BAO_Mailing::getRecipients($mailing['id']); |
9f0a25d7 J |
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) { | |
6f3a35e0 | 448 | $this->assertEquals($value['phone_id'], $checkPhoneIDs[$value['contact_id']], 'Check correct phone number for contact ' . $value['contact_id']); |
9f0a25d7 J |
449 | } |
450 | ||
451 | // Tidy up | |
452 | $this->deleteMailing($mailing['id']); | |
453 | $this->callAPISuccess('SmsProvider', 'Delete', array('id' => $sms_provider['id'])); | |
454 | $this->groupDelete($group); | |
6f3a35e0 | 455 | $this->contactDelete($contactID1); |
456 | $this->contactDelete($contactID2); | |
9f0a25d7 J |
457 | } |
458 | ||
459 | } |