Merge pull request #12262 from JMAConsulting/dev_mail_13-rc
[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->contactDelete($contactID1);
129 $this->contactDelete($this->allowedContactId);
130 }
131
132 /**
133 * Build ACL where clause
134 *
135 * @implements CRM_Utils_Hook::aclWhereClause
136 *
137 * @param string $type
138 * @param array $tables
139 * @param array $whereTables
140 * @param int $contactID
141 * @param string $where
142 */
143 public function aclWhereAllowedOnlyOne($type, &$tables, &$whereTables, &$contactID, &$where) {
144 $where = " contact_a.id = " . $this->allowedContactId;
145 }
146
147 /**
148 * @todo Missing tests:
149 * - Ensure opt out emails are not mailed
150 * - Ensure 'stop' emails are not mailed
151 * - Ensure the deceased are not mailed
152 * - Tests for getLocationFilterAndOrderBy (selecting correct 'type')
153 * - ...
154 */
155
156 /**
157 * Test to ensure that static and smart mailing groups can be added to an
158 * email mailing as 'include' or 'exclude' groups - and the members are
159 * included or excluded appropriately.
160 *
161 * contact 0 : static 0 (inc) + smart 5 (exc)
162 * contact 1 : static 0 (inc)
163 * contact 2 : static 1 (inc)
164 * contact 3 : static 1 (inc)
165 * contact 4 : static 2 (exc) + smart 3 (inc)
166 * contact 5 : smart 3 (inc)
167 * contact 6 : smart 4 (inc)
168 * contact 7 : smart 4 (inc)
169 * contact 8 : smart 5 (base)
170 *
171 * here 'contact 1 : static 0 (inc)' identified as static group $groupIDs[0]
172 * that has 'contact 1' identified as $contactIDs[0] and Included in the mailing recipient list
173 */
174 public function testgetRecipientsEmailGroupIncludeExclude() {
175 // Set up groups; 3 standard, 4 smart
176 $groupIDs = array();
177 for ($i = 0; $i < 7; $i++) {
178 $params = array(
179 'name' => 'Test static group ' . $i,
180 'title' => 'Test static group ' . $i,
181 'is_active' => 1,
182 );
183 if ($i < 3) {
184 $groupIDs[$i] = $this->groupCreate($params);
185 }
186 else {
187 $groupIDs[$i] = $this->smartGroupCreate(array(
188 'formValues' => ['last_name' => (($i == 6) ? 'smart5' : 'smart' . $i)],
189 ), $params);
190 }
191 }
192
193 // Create contacts
194 $contactIDs = array(
195 $this->individualCreate(array('last_name' => 'smart5'), 0),
196 $this->individualCreate(array(), 1),
197 $this->individualCreate(array(), 2),
198 $this->individualCreate(array(), 3),
199 $this->individualCreate(array('last_name' => 'smart3'), 4),
200 $this->individualCreate(array('last_name' => 'smart3'), 5),
201 $this->individualCreate(array('last_name' => 'smart4'), 6),
202 $this->individualCreate(array('last_name' => 'smart4'), 7),
203 $this->individualCreate(array('last_name' => 'smart5'), 8),
204 );
205
206 // Add contacts to static groups
207 $this->callAPISuccess('GroupContact', 'Create', array(
208 'group_id' => $groupIDs[0],
209 'contact_id' => $contactIDs[0],
210 ));
211 $this->callAPISuccess('GroupContact', 'Create', array(
212 'group_id' => $groupIDs[0],
213 'contact_id' => $contactIDs[1],
214 ));
215 $this->callAPISuccess('GroupContact', 'Create', array(
216 'group_id' => $groupIDs[1],
217 'contact_id' => $contactIDs[2],
218 ));
219 $this->callAPISuccess('GroupContact', 'Create', array(
220 'group_id' => $groupIDs[1],
221 'contact_id' => $contactIDs[3],
222 ));
223 $this->callAPISuccess('GroupContact', 'Create', array(
224 'group_id' => $groupIDs[2],
225 'contact_id' => $contactIDs[4],
226 ));
227
228 // Force rebuild the smart groups
229 for ($i = 3; $i < 7; $i++) {
230 $group = new CRM_Contact_DAO_Group();
231 $group->id = $groupIDs[$i];
232 $group->find(TRUE);
233 CRM_Contact_BAO_GroupContactCache::load($group, TRUE);
234 }
235
236 // Check that we can include static groups in the mailing.
237 // Expected: Contacts [0-3] should be included.
238 $mailing = $this->callAPISuccess('Mailing', 'create', array());
239 $this->createMailingGroup($mailing['id'], $groupIDs[0]);
240 $this->createMailingGroup($mailing['id'], $groupIDs[1]);
241 $this->createMailingGroup($mailing['id'], $groupIDs[6], 'Base');
242 $expected = $contactIDs;
243 unset($expected[4], $expected[5], $expected[6], $expected[7], $expected[8]);
244 $this->assertRecipientsCorrect($mailing['id'], $expected);
245
246 // Check that we can include smart groups in the mailing too.
247 // Expected: All contacts should be included.
248 // Also (dev/mail/6): Enable multilingual mode to check that restructing group doesn't affect recipient rebuilding
249 $this->enableMultilingual();
250 $this->createMailingGroup($mailing['id'], $groupIDs[3]);
251 $this->createMailingGroup($mailing['id'], $groupIDs[4]);
252 $this->createMailingGroup($mailing['id'], $groupIDs[5]);
253 // Check that all the contacts whould be present is recipient list as static group [0], [1] and [2] and
254 // smart groups [3], [4] and [5] is included in the recipient listing.
255 // NOTE: that contact[8] is present in both included smart group[5] and base smart group [6] so it will be
256 // present in recipient list as contact(s) from Base smart groups are not excluded the list as per (dev/mail/13)
257 $this->assertRecipientsCorrect($mailing['id'], $contactIDs);
258
259 // Check we can exclude static groups from the mailing.
260 // Expected: All contacts except [4]
261 $this->createMailingGroup($mailing['id'], $groupIDs[2], 'Exclude');
262 $expected = $contactIDs;
263 unset($expected[4]);
264 // NOTE: as per (dev/mail/13) if a contact A is present in smartGroup [5] which is Included in the mailing AND
265 // also present in another smartGroup [6] which is considered as Base group, then contact A should not be excluded from
266 // the recipient list due to later
267 $this->assertRecipientsCorrect($mailing['id'], $expected);
268
269 // Check we can exclude smart groups from the mailing too.
270 // Expected: All contacts except [0], [4] and [8]
271 $this->createMailingGroup($mailing['id'], $groupIDs[5], 'Exclude');
272 $expected = $contactIDs;
273 // As contact [0] and [8] belongs to excluded smart group[5] and base smart group[6] respectively,
274 // both these contacts should not be present in the mailing list
275 unset($expected[0], $expected[4], $expected[8]);
276 $this->assertRecipientsCorrect($mailing['id'], $expected);
277
278 // Tear down: delete mailing, groups, contacts
279 $this->deleteMailing($mailing['id']);
280 foreach ($groupIDs as $groupID) {
281 $this->groupDelete($groupID);
282 }
283 foreach ($contactIDs as $contactID) {
284 $this->contactDelete($contactID);
285 }
286 }
287
288 /**
289 * Test CRM_Mailing_BAO_Mailing::getRecipients() on sms mode
290 */
291 public function testgetRecipientsSMS() {
292 // Tests for SMS bulk mailing recipients
293 // +CRM-21320 Ensure primary mobile number is selected over non-primary
294
295 // Setup
296 $smartGroupParams = array(
297 'formValues' => array('contact_type' => array('IN' => array('Individual'))),
298 );
299 $group = $this->smartGroupCreate($smartGroupParams);
300 $sms_provider = $this->callAPISuccess('SmsProvider', 'create', array(
301 'sequential' => 1,
302 'name' => 1,
303 'title' => "Test",
304 'username' => "Test",
305 'password' => "Test",
306 'api_type' => 1,
307 'is_active' => 1,
308 ));
309
310 // Create Contact 1 and add in group
311 $contactID1 = $this->individualCreate(array(), 0);
312 $this->callAPISuccess('GroupContact', 'Create', array(
313 'group_id' => $group,
314 'contact_id' => $contactID1,
315 ));
316
317 // Create contact 2 and add in group
318 $contactID2 = $this->individualCreate(array(), 1);
319 $this->callAPISuccess('GroupContact', 'Create', array(
320 'group_id' => $group,
321 'contact_id' => $contactID2,
322 ));
323
324 $contactIDPhoneRecords = array(
325 $contactID1 => array(
326 'primary_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
327 'contact_id' => $contactID1,
328 'phone' => "01 01",
329 'location_type_id' => "Home",
330 'phone_type_id' => "Mobile",
331 'is_primary' => 1,
332 ))),
333 'other_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
334 'contact_id' => $contactID1,
335 'phone' => "01 02",
336 'location_type_id' => "Work",
337 'phone_type_id' => "Mobile",
338 'is_primary' => 0,
339 ))),
340 ),
341 // Create the non-primary with a lower ID than the primary, to test CRM-21320
342 $contactID2 => array(
343 'other_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
344 'contact_id' => $contactID2,
345 'phone' => "02 01",
346 'location_type_id' => "Home",
347 'phone_type_id' => "Mobile",
348 'is_primary' => 0,
349 ))),
350 'primary_phone_id' => CRM_Utils_Array::value('id', $this->callAPISuccess('Phone', 'create', array(
351 'contact_id' => $contactID2,
352 'phone' => "02 02",
353 'location_type_id' => "Work",
354 'phone_type_id' => "Mobile",
355 'is_primary' => 1,
356 ))),
357 ),
358 );
359
360 // Prepare expected results
361 $checkPhoneIDs = array(
362 $contactID1 => $contactIDPhoneRecords[$contactID1]['primary_phone_id'],
363 $contactID2 => $contactIDPhoneRecords[$contactID2]['primary_phone_id'],
364 );
365
366 // Create mailing
367 $mailing = $this->callAPISuccess('Mailing', 'create', array('sms_provider_id' => $sms_provider['id']));
368 $mailingInclude = $this->createMailingGroup($mailing['id'], $group);
369
370 // Get recipients
371 CRM_Mailing_BAO_Mailing::getRecipients($mailing['id']);
372 $recipients = $this->callAPISuccess('MailingRecipients', 'get', array('mailing_id' => $mailing['id']));
373
374 // Check the count is correct
375 $this->assertEquals(2, $recipients['count'], 'Check recipient count');
376
377 // Check we got the 'primary' mobile for both contacts
378 foreach ($recipients['values'] as $value) {
379 $this->assertEquals($value['phone_id'], $checkPhoneIDs[$value['contact_id']], 'Check correct phone number for contact ' . $value['contact_id']);
380 }
381
382 // Tidy up
383 $this->deleteMailing($mailing['id']);
384 $this->callAPISuccess('SmsProvider', 'Delete', array('id' => $sms_provider['id']));
385 $this->groupDelete($group);
386 $this->contactDelete($contactID1);
387 $this->contactDelete($contactID2);
388 }
389
390 }