3 use Civi\Api4\Activity
;
6 * Class CRM_Activity_BAO_ActivityTest
9 class CRM_Activity_BAO_ActivityTest
extends CiviUnitTestCase
{
11 private $allowedContactsACL = [];
13 private $loggedInUserId = NULL;
15 private $someContacts = [];
20 * @throws \CiviCRM_API3_Exception
22 public function setUp():void
{
24 $this->prepareForACLs();
25 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['view all contacts', 'access CiviCRM'];
26 $this->setupForSmsTests();
30 * Clean up after tests.
32 * @throws \API_Exception
33 * @throws \CRM_Core_Exception
34 * @throws \CiviCRM_API3_Exception
36 public function tearDown(): void
{
39 'civicrm_activity_contact',
44 $this->quickCleanup($tablesToTruncate);
45 $this->cleanUpAfterACLs();
46 $this->setupForSmsTests(TRUE);
51 * Test case for create() method.
53 * @throws \CRM_Core_Exception
55 public function testCreate() {
56 $contactId = $this->individualCreate();
59 'source_contact_id' => $contactId,
60 'subject' => 'Scheduling Meeting',
61 'activity_type_id' => 2,
64 CRM_Activity_BAO_Activity
::create($params);
66 $activityId = $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting', 'id',
67 'subject', 'Database check for created activity.'
70 // Now call create() to modify an existing Activity.
73 'source_contact_id' => $contactId,
74 'subject' => 'Scheduling Interview',
75 'activity_type_id' => 3,
78 CRM_Activity_BAO_Activity
::create($params);
80 $activityTypeId = $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Interview',
82 'subject', 'Database check on updated activity record.'
84 $this->assertEquals($activityTypeId, 3, 'Verify activity type id is 3.');
86 $this->contactDelete($contactId);
90 * Test case for getContactActivity() method.
92 * getContactActivity() method get activities detail for given target contact
95 * @throws \CRM_Core_Exception
96 * @throws \CiviCRM_API3_Exception
98 public function testGetContactActivity() {
99 $contactId = $this->individualCreate();
101 'first_name' => 'liz',
102 'last_name' => 'hurleey',
104 $targetContactId = $this->individualCreate($params);
107 'source_contact_id' => $contactId,
108 'subject' => 'Scheduling Meeting',
109 'activity_type_id' => 2,
110 'target_contact_id' => [$targetContactId],
111 'activity_date_time' => date('Ymd'),
114 $this->callAPISuccess('Activity', 'create', $params);
116 $activityId = $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting',
118 'subject', 'Database check for created activity.'
121 // @todo - remove this deprecated functions
122 $activities = CRM_Activity_BAO_Activity
::getContactActivity($targetContactId);
124 $this->assertEquals($activities[$activityId]['subject'], 'Scheduling Meeting', 'Verify activity subject is correct.');
126 $this->contactDelete($contactId);
127 $this->contactDelete($targetContactId);
131 * Test case for retrieve() method.
133 * Retrieve($params, $defaults) method return activity detail for given params
136 public function testRetrieve(): void
{
137 $contactId = $this->individualCreate();
139 'first_name' => 'liz',
140 'last_name' => 'hurleey',
142 $targetContactId = $this->individualCreate($params);
145 'source_contact_id' => $contactId,
146 'subject' => 'Scheduling Meeting',
147 'activity_type_id' => 2,
148 'target_contact_id' => [$targetContactId],
149 'activity_date_time' => date('Ymd'),
152 CRM_Activity_BAO_Activity
::create($params);
154 $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting', 'id',
155 'subject', 'Database check for created activity.'
158 $this->assertDBNotNull('CRM_Activity_DAO_ActivityContact', $targetContactId,
160 'Database check for created activity target.'
164 $activity = CRM_Activity_BAO_Activity
::retrieve($params, $defaults);
166 $this->assertEquals($activity->subject
, 'Scheduling Meeting', 'Verify activity subject is correct.');
167 $this->assertEquals($activity->activity_type_id
, 2, 'Verify activity type id is correct.');
168 $this->assertEquals($defaults['source_contact_id'], $contactId, 'Verify source contact id is correct.');
170 $this->assertEquals($defaults['subject'], 'Scheduling Meeting', 'Verify activity subject is correct.');
171 $this->assertEquals($defaults['activity_type_id'], 2, 'Verify activity type id is correct.');
173 $this->assertEquals($defaults['target_contact'][0], $targetContactId, 'Verify target contact id is correct.');
175 $this->contactDelete($contactId);
176 $this->contactDelete($targetContactId);
180 * Test Assigning a target contact but then the logged in user cannot see the contact
182 public function testTargetContactNotavaliable() {
183 $contactId = $this->individualCreate();
185 'first_name' => 'liz',
186 'last_name' => 'hurleey',
188 $targetContactId = $this->individualCreate($params);
191 'source_contact_id' => $contactId,
192 'subject' => 'Scheduling Meeting',
193 'activity_type_id' => 2,
194 'target_contact_id' => [$targetContactId],
195 'activity_date_time' => date('Ymd'),
198 CRM_Activity_BAO_Activity
::create($params);
201 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'hook_civicrm_aclWhereClause']);
203 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
205 $this->allowedContactsACL
= [$contactId];
207 // get logged in user
208 $user_id = $this->createLoggedInUser();
209 $activityGetParams = CRM_Core_Page_AJAX
::defaultSortAndPagerParams();
210 $activityGetParams +
= ['contact_id' => $contactId];
211 $activities = CRM_Activity_BAO_Activity
::getContactActivitySelector($activityGetParams);
212 // Aseert that we have sensible data to display in the contact tab
213 $this->assertEquals('Anderson, Anthony', $activities['data'][0]['source_contact_name']);
214 // Note that becasue there is a target contact but it is not accessable the output is an empty string not n/a
215 $this->assertEquals('', $activities['data'][0]['target_contact_name']);
216 // verify that doing the underlying query shows we get a target contact_id
217 $this->assertEquals(1, CRM_Activity_BAO_Activity
::getActivities(['contact_id' => $contactId])[1]['target_contact_count']);
218 $this->cleanUpAfterACLs();
222 * Check for errors when viewing a contact's activity tab when there
223 * is an activity that doesn't have a target (With Contact).
225 public function testActivitySelectorNoTargets() {
226 $contact_id = $this->individualCreate([], 0, TRUE);
227 $activity = $this->callAPISuccess('activity', 'create', [
228 'source_contact_id' => $contact_id,
229 'activity_type_id' => 'Meeting',
230 'subject' => 'Lonely Meeting',
231 'details' => 'Here at this meeting all by myself and no other contacts.',
238 'context' => 'activity',
239 'contact_id' => $contact_id,
241 $output = CRM_Activity_BAO_Activity
::getContactActivitySelector($input);
242 $this->assertEquals($activity['id'], $output['data'][0]['DT_RowId']);
243 $this->assertEquals('<em>n/a</em>', $output['data'][0]['target_contact_name']);
244 $this->assertEquals('Lonely Meeting', $output['data'][0]['subject']);
246 $this->callAPISuccess('activity', 'delete', ['id' => $activity['id']]);
247 $this->callAPISuccess('contact', 'delete', ['id' => $contact_id]);
251 * Test case for deleteActivity() method.
253 * deleteActivity($params) method deletes activity for given params.
255 * @throws \CRM_Core_Exception
256 * @throws \CiviCRM_API3_Exception
258 public function testDeleteActivity() {
259 $contactId = $this->individualCreate();
261 'first_name' => 'liz',
262 'last_name' => 'hurleey',
264 $targetContactId = $this->individualCreate($params);
267 'source_contact_id' => $contactId,
268 'source_record_id' => $contactId,
269 'subject' => 'Scheduling Meeting',
270 'activity_type_id' => 2,
271 'target_contact_id' => [$targetContactId],
272 'activity_date_time' => date('Ymd'),
275 CRM_Activity_BAO_Activity
::create($params);
277 $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting', 'id',
278 'subject', 'Database check for created activity.'
281 $this->assertDBNotNull('CRM_Activity_DAO_ActivityContact', $targetContactId,
283 'Database check for created activity target.'
286 $paramOptions = ['0))+and+0+--+-f', ['0))+and+0+--+-f']];
287 $paramField = ['source_record_id', 'activity_type_id'];
288 foreach ($paramField as $field) {
289 foreach ($paramOptions as $paramOption) {
291 $field => $paramOption,
294 CRM_Activity_BAO_Activity
::deleteActivity($params);
296 catch (Exception
$e) {
297 if ($e->getMessage() === 'DB Error: syntax error') {
298 $this->fail('Delete Activity function did not validate field: ' . $field);
304 'source_contact_id' => $contactId,
305 'source_record_id' => $contactId,
306 'subject' => 'Scheduling Meeting',
307 'activity_type_id' => 2,
310 CRM_Activity_BAO_Activity
::deleteActivity($params);
312 $this->assertDBNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting', 'id',
313 'subject', 'Database check for deleted activity.'
315 $this->contactDelete($contactId);
316 $this->contactDelete($targetContactId);
320 * Test case for deleteActivityContact() method.
322 public function testDeleteActivityTarget() {
323 $contactId = $this->individualCreate();
325 'first_name' => 'liz',
326 'last_name' => 'hurleey',
328 $targetContactId = $this->individualCreate($params);
331 'source_contact_id' => $contactId,
332 'subject' => 'Scheduling Meeting',
333 'activity_type_id' => 2,
334 'target_contact_id' => [$targetContactId],
335 'activity_date_time' => date('Ymd'),
338 CRM_Activity_BAO_Activity
::create($params);
340 $activityId = $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting', 'id',
341 'subject', 'Database check for created activity.'
344 $this->assertDBNotNull('CRM_Activity_DAO_ActivityContact', $targetContactId,
346 'Database check for created activity target.'
349 CRM_Activity_BAO_Activity
::deleteActivityContact($activityId, 3);
351 $this->assertDBNull('CRM_Activity_DAO_ActivityContact', $targetContactId, 'id',
352 'contact_id', 'Database check for deleted activity target.'
355 $this->contactDelete($contactId);
356 $this->contactDelete($targetContactId);
360 * Test case for deleteActivityAssignment() method.
362 * deleteActivityAssignment($activityId) method deletes activity assignment
363 * for given activity id.
365 * @throws \CRM_Core_Exception
366 * @throws \CiviCRM_API3_Exception
368 public function testDeleteActivityAssignment(): void
{
369 $contactId = $this->individualCreate();
371 'first_name' => 'liz',
372 'last_name' => 'hurleey',
374 $assigneeContactId = $this->individualCreate($params);
377 'source_contact_id' => $contactId,
378 'subject' => 'Scheduling Meeting',
379 'activity_type_id' => 2,
380 'assignee_contact_id' => [$assigneeContactId],
381 'activity_date_time' => date('Ymd'),
384 CRM_Activity_BAO_Activity
::create($params);
386 $activityId = $this->assertDBNotNull('CRM_Activity_DAO_Activity', 'Scheduling Meeting', 'id',
387 'subject', 'Database check for created activity.'
390 $this->assertDBNotNull('CRM_Activity_DAO_ActivityContact',
391 $assigneeContactId, 'id', 'contact_id',
392 'Database check for created activity assignment.'
395 CRM_Activity_BAO_Activity
::deleteActivityContact($activityId, 1);
397 $this->assertDBNull('CRM_Activity_DAO_ActivityContact', $assigneeContactId, 'id',
398 'contact_id', 'Database check for deleted activity assignment.'
401 $this->contactDelete($contactId);
402 $this->contactDelete($assigneeContactId);
406 * Test getActivities BAO method for getting count.
409 public function testGetActivitiesCountForAdminDashboard() {
411 $this->setShowCaseActivitiesInCore(FALSE);
412 $this->setUpForActivityDashboardTests();
413 $this->addCaseWithActivity();
414 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
[] = 'access all cases and activities';
416 $activityCount = CRM_Activity_BAO_Activity
::getActivitiesCount($this->_params
);
417 $this->assertEquals(8, $activityCount);
419 // If we're showing case activities, we exepct to see one more (the scheduled meeting)...
420 $this->setShowCaseActivitiesInCore(TRUE);
421 $activityCount = CRM_Activity_BAO_Activity
::getActivitiesCount($this->_params
);
422 $this->assertEquals(9, $activityCount);
424 $this->setShowCaseActivitiesInCore(FALSE);
428 * Test getActivities BAO method for getting count
431 public function testGetActivitiesCountforNonAdminDashboard() {
433 $this->setShowCaseActivitiesInCore(FALSE);
434 $this->createTestActivities();
435 $this->addCaseWithActivity();
436 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
[] = 'access all cases and activities';
443 'activity_type_id' => NULL,
444 // for dashlet the Scheduled status is set by default
445 'activity_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled'),
451 //since we are loading activities from dataset, we know total number of activities for this contact
452 // 5 activities ( 2 scheduled, 3 Completed, 1 Scheduled Case activity ),
453 // note that dashboard shows only scheduled activities
454 $this->assertEquals(2, CRM_Activity_BAO_Activity
::getActivitiesCount($params));
456 // If we're showing case activities, we expect to see one more (the scheduled meeting)...
457 $this->setShowCaseActivitiesInCore(TRUE);
458 $this->assertEquals(3, CRM_Activity_BAO_Activity
::getActivitiesCount($params));
460 $this->setShowCaseActivitiesInCore(FALSE);
464 * Test getActivities BAO method for getting count
467 public function testGetActivitiesCountforContactSummary() {
469 $this->setShowCaseActivitiesInCore(FALSE);
470 $this->createTestActivities();
471 $this->addCaseWithActivity();
472 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
[] = 'access all cases and activities';
478 'context' => 'activity',
479 'activity_type_id' => NULL,
485 //since we are loading activities from dataset, we know total number of activities for this contact
487 $this->assertEquals(5, CRM_Activity_BAO_Activity
::getActivitiesCount($params));
489 // If we're showing case activities, we exepct to see one more (the scheduled meeting)...
490 $this->setShowCaseActivitiesInCore(TRUE);
491 $this->assertEquals(6, CRM_Activity_BAO_Activity
::getActivitiesCount($params));
493 $this->setShowCaseActivitiesInCore(FALSE);
497 * CRM-18706 - Test Include/Exclude Activity Filters
499 public function testActivityFilters() {
500 $this->createTestActivities();
501 Civi
::settings()->set('preserve_activity_tab_filter', 1);
502 $this->createLoggedInUser();
507 'context' => 'activity',
508 'activity_type_id' => 1,
511 'activity_type_filter_id' => 1,
515 CRM_Activity_Page_AJAX
::getContactActivity();
517 catch (CRM_Core_Exception_PrematureExitException
$e) {
518 $activityFilter = Civi
::contactSettings()->get('activity_tab_filter');
519 $activities = $e->errorData
;
521 //Assert whether filters are correctly set.
522 $this->checkArrayEquals($expectedFilters, $activityFilter);
523 // This should include activities of type Meeting only.
524 foreach ($activities['data'] as $value) {
525 $this->assertStringContainsString('Meeting', $value['activity_type']);
527 unset($_GET['activity_type_id']);
529 $_GET['activity_type_exclude_id'] = $expectedFilters['activity_type_exclude_filter_id'] = 1;
531 CRM_Activity_Page_AJAX
::getContactActivity();
533 catch (CRM_Core_Exception_PrematureExitException
$e) {
534 $activityFilter = Civi
::contactSettings()->get('activity_tab_filter');
535 $activities = $e->errorData
;
537 $this->assertEquals(['activity_type_exclude_filter_id' => 1], $activityFilter);
538 // None of the activities should be of type Meeting.
539 foreach ($activities['data'] as $value) {
540 $this->assertStringNotContainsString('Meeting', $value['activity_type']);
545 * Test getActivities BAO method for getting count
547 public function testGetActivitiesCountforContactSummaryWithNoActivities() {
548 $this->createTestActivities();
555 'activity_type_id' => NULL,
561 //since we are loading activities from dataset, we know total number of activities for this contact
562 // this contact does not have any activity
563 $this->assertEquals(0, CRM_Activity_BAO_Activity
::getActivitiesCount($params));
567 * Test getActivities BAO method.
569 public function testGetActivitiesForAdminDashboard() {
570 $this->setShowCaseActivitiesInCore(FALSE);
571 $this->setUpForActivityDashboardTests();
572 $this->addCaseWithActivity();
573 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
[] = 'access all cases and activities';
575 $activitiesNew = CRM_Activity_BAO_Activity
::getActivities($this->_params
);
576 // $this->assertEquals($activities, $activitiesDeprecatedFn);
578 //since we are loading activities from dataset, we know total number of activities
579 // with no contact ID and there should be 8 schedule activities shown on dashboard
581 foreach ([$activitiesNew] as $activities) {
582 $this->assertCount($count, $activities);
584 foreach ($activities as $key => $value) {
585 $this->assertEquals($value['subject'], "subject {$key}", 'Verify activity subject is correct.');
586 $this->assertEquals($value['activity_type_id'], 2, 'Verify activity type is correct.');
587 $this->assertEquals($value['status_id'], 1, 'Verify all activities are scheduled.');
591 // Now check that we get the scheduled meeting, if civicaseShowCaseActivities is set.
592 $this->setShowCaseActivitiesInCore(TRUE);
593 $activitiesNew = CRM_Activity_BAO_Activity
::getActivities($this->_params
);
594 $this->assertCount(9, $activitiesNew);
595 // Scan through to find the meeting.
596 $this->assertContains('test meeting activity', array_column($activitiesNew, 'subject'), "failed to find scheduled case Meeting activity");
598 $this->setShowCaseActivitiesInCore(FALSE);
602 * Test getActivities BAO method.
604 public function testGetActivitiesForAdminDashboardNoViewContacts() {
605 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
606 $this->setUpForActivityDashboardTests();
607 foreach ([CRM_Activity_BAO_Activity
::getActivities($this->_params
)] as $activities) {
608 // Skipped until we get back to the upgraded version properly.
609 $this->assertCount(0, $activities);
614 * Test getActivities BAO method.
616 public function testGetActivitiesForAdminDashboardAclLimitedViewContacts() {
617 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
618 $this->allowedContacts
= [1, 3, 4, 5];
619 $this->hookClass
->setHook('civicrm_aclWhereClause', [$this, 'aclWhereMultipleContacts']);
620 $this->setUpForActivityDashboardTests();
621 $this->assertEquals(7, count(CRM_Activity_BAO_Activity
::getActivities($this->_params
)));
625 * Test getActivities BAO method.
627 public function testGetActivitiesforNonAdminDashboard() {
628 $this->setShowCaseActivitiesInCore(FALSE);
629 $this->createTestActivities();
630 $this->addCaseWithActivity();
631 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
[] = 'access all cases and activities';
635 'contact_id' => $contactID,
639 'activity_type_id' => NULL,
640 // for dashlet the Scheduled status is set by default
641 'activity_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled'),
647 foreach ([CRM_Activity_BAO_Activity
::getActivities($params)] as $activities) {
648 //since we are loading activities from dataset, we know total number of activities for this contact
649 // 5 activities ( 2 scheduled, 3 Completed ), note that dashboard shows only scheduled activities
651 $this->assertEquals($count, count($activities));
653 foreach ($activities as $key => $value) {
654 $this->assertEquals($value['subject'], "subject {$key}", 'Verify activity subject is correct.');
655 $this->assertEquals($value['activity_type_id'], 2, 'Verify activity type is correct.');
656 $this->assertEquals($value['status_id'], 1, 'Verify all activities are scheduled.');
659 $this->assertArrayHasKey($contactID, $value['target_contact_name']);
662 $this->assertArrayHasKey($contactID, $value['assignee_contact_name']);
667 // Now check that we get the scheduled meeting, if civicaseShowCaseActivities is set.
668 $this->setShowCaseActivitiesInCore(TRUE);
669 $activities = CRM_Activity_BAO_Activity
::getActivities($params);
670 $this->assertEquals(3, count($activities));
671 // Scan through to find the meeting.
672 $this->assertTrue(in_array('test meeting activity', array_column($activities, 'subject')),
673 "failed to find scheduled case Meeting activity");
676 $this->setShowCaseActivitiesInCore(FALSE);
680 * Test target contact count.
682 public function testTargetCountforContactSummary() {
684 $contactId = $this->individualCreate();
685 $targetContactIDs = [];
686 for ($i = 0; $i < $targetCount; $i++
) {
687 $targetContactIDs[] = $this->individualCreate([], $i);
689 // Create activities with 5 target contacts.
691 'source_contact_id' => $contactId,
692 'target_contact_id' => $targetContactIDs,
694 $this->activityCreate($activityParams);
697 'contact_id' => $contactId,
698 'context' => 'activity',
700 $activities = CRM_Activity_BAO_Activity
::getActivities($params);
701 //verify target count
702 $this->assertEquals($targetCount, $activities[1]['target_contact_count']);
703 $this->assertEquals([$targetContactIDs[0] => 'Anderson, Anthony'], $activities[1]['target_contact_name']);
704 $this->assertEquals('Anderson, Anthony', $activities[1]['source_contact_name']);
705 $this->assertEquals('Anderson, Anthony', $activities[1]['assignee_contact_name'][4]);
709 * Test getActivities BAO method.
711 public function testGetActivitiesforContactSummaryWithSortOptions() {
712 $this->createTestActivities();
717 'context' => 'activity',
718 'activity_type_id' => NULL,
721 'sort' => 'source_contact_name desc',
724 $activities = CRM_Activity_BAO_Activity
::getActivities($params);
725 $alphaOrder = ['Test Contact 11', 'Test Contact 12', 'Test Contact 3', 'Test Contact 4', 'Test Contact 9'];
726 foreach ($activities as $activity) {
727 $this->assertEquals(array_pop($alphaOrder), $activity['source_contact_name']);
733 * Test getActivities BAO method.
735 public function testGetActivitiesForContactSummary() {
737 $this->setShowCaseActivitiesInCore(FALSE);
738 $this->createTestActivities();
739 $this->addCaseWithActivity();
740 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
[] = 'access all cases and activities';
744 'contact_id' => $contactID,
747 'context' => 'activity',
748 'activity_type_id' => NULL,
753 //since we are loading activities from dataset, we know total number of activities for this contact
754 // 5 activities, Contact Summary should show all activities
756 $activities = CRM_Activity_BAO_Activity
::getActivities($params);
757 $this->assertEquals($count, count($activities));
758 foreach ($activities as $key => $value) {
759 $this->assertEquals($value['subject'], "subject {$key}", 'Verify activity subject is correct.');
762 $this->assertEquals($value['status_id'], 2, 'Verify all activities are scheduled.');
765 $this->assertEquals($value['status_id'], 1, 'Verify all activities are scheduled.');
769 $this->assertEquals($value['activity_type'], 'Bulk Email', 'Verify activity type is correct.');
770 $this->assertEquals('(2 recipients)', $value['recipients']);
771 $targetContactID = key($value['target_contact_name']);
772 // The 2 targets have ids 10 & 11. Since they are not sorted it could be either on some systems.
773 $this->assertTrue(in_array($targetContactID, [10, 11]));
776 $this->assertEquals($value['activity_type_id'], 1, 'Verify activity type is correct.');
779 $this->assertEquals($value['activity_type_id'], 2, 'Verify activity type is correct.');
783 $this->assertEquals([$contactID => 'Test Contact ' . $contactID], $value['target_contact_name']);
786 $this->assertArrayHasKey($contactID, $value['assignee_contact_name']);
790 // Now check that we get the scheduled meeting, if civicaseShowCaseActivities is set.
791 $this->setShowCaseActivitiesInCore(TRUE);
792 $activities = CRM_Activity_BAO_Activity
::getActivities($params);
793 $this->assertEquals(6, count($activities));
794 // Scan through to find the meeting.
795 $this->assertTrue(in_array('test meeting activity', array_column($activities, 'subject')),
796 "failed to find scheduled case Meeting activity");
798 $this->setShowCaseActivitiesInCore(FALSE);
802 * Test getActivities BAO method.
804 public function testGetActivitiesforContactSummaryWithActivities() {
806 $this->setShowCaseActivitiesInCore(FALSE);
807 $this->createTestActivities();
809 // parameters for different test cases, check each array key for the specific test-case
811 'with-no-activity' => [
817 'activity_type_id' => NULL,
829 'activity_type_id' => NULL,
835 'with-activity_type' => [
841 'activity_type_id' => 2,
847 'exclude-all-activity_type' => [
853 'activity_type_exclude_id' => [1, 2],
859 'sort-by-subject' => [
865 'activity_type_id' => NULL,
868 'sort' => 'subject DESC',
873 foreach ($testCases as $caseName => $testCase) {
874 $activityCount = CRM_Activity_BAO_Activity
::getActivitiesCount($testCase['params']);
875 $activitiesNew = CRM_Activity_BAO_Activity
::getActivities($testCase['params']);
877 foreach ([$activitiesNew] as $activities) {
878 //$this->assertEquals($activityCount, CRM_Activity_BAO_Activity::getActivitiesCount($testCase['params']));
879 if ($caseName === 'with-no-activity') {
880 $this->assertEquals(0, count($activities));
881 $this->assertEquals(0, $activityCount);
883 elseif ($caseName === 'with-activity') {
884 // contact id 1 is assigned as source, target and assignee for activity id 1, 7 and 8 respectively
885 $this->assertEquals(3, count($activities));
886 $this->assertEquals(3, $activityCount);
887 $this->assertEquals(1, $activities[1]['source_contact_id']);
888 // @todo - this is a discrepancy between old & new - review.
889 //$this->assertEquals(TRUE, array_key_exists(1, $activities[7]['target_contact_name']));
890 $this->assertEquals(TRUE, array_key_exists(1, $activities[8]['assignee_contact_name']));
892 elseif ($caseName === 'with-activity_type') {
893 // contact id 3 for activity type 2 is assigned as assignee, source and target for
894 // activity id 1, 3 and 8 respectively
895 $this->assertEquals(3, count($activities));
896 $this->assertEquals(3, $activityCount);
897 // ensure activity type id is 2
898 $this->assertEquals(2, $activities[1]['activity_type_id']);
899 $this->assertEquals(3, $activities[3]['source_contact_id']);
900 // @todo review inconsistency between 2 versions.
901 // $this->assertEquals(TRUE, array_key_exists(3, $activities[8]['target_contact_name']));
902 $this->assertEquals(TRUE, array_key_exists(3, $activities[1]['assignee_contact_name']));
904 if ($caseName === 'exclude-all-activity_type') {
905 $this->assertEquals(0, count($activities));
906 $this->assertEquals(0, $activityCount);
908 if ($caseName === 'sort-by-subject') {
909 $this->assertEquals(3, count($activities));
910 $this->assertEquals(3, $activityCount);
911 // activities should be order by 'subject DESC'
918 foreach ($activities as $activity) {
919 $this->assertEquals($subjectOrder[$count], $activity['subject']);
928 * CRM-20793 : Test getActivities by using activity date and status filter
930 * @throws \CRM_Core_Exception
932 public function testByActivityDateAndStatus(): void
{
933 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['view all contacts', 'access CiviCRM'];
934 $this->createTestActivities();
936 // activity IDs catagorised by date
937 $lastWeekActivities = [1, 2, 3];
938 $todayActivities = [4, 5, 6, 7];
939 $lastTwoMonthsActivities = [8, 9, 10, 11];
940 $lastOrNextYearActivities = [12, 13, 14, 15, 16];
942 // date values later used to set activity date value
943 $lastWeekDate = date('YmdHis', strtotime('1 week ago'));
944 $today = date('YmdHis');
945 $lastTwoMonthAgoDate = date('YmdHis', strtotime('2 months ago'));
946 // if current month is Jan then choose next year date otherwise the search result will include
947 // the previous week and last two months activities which are still in previous year and hence leads to improper result
948 $lastOrNextYearDate = (date('M') === 'Jan') ?
date('YmdHis', strtotime('+1 year')) : date('YmdHis', strtotime('1 year ago'));
949 for ($i = 1; $i <= 16; $i++
) {
950 if (in_array($i, $lastWeekActivities)) {
951 $date = $lastWeekDate;
953 elseif (in_array($i, $lastTwoMonthsActivities)) {
954 $date = $lastTwoMonthAgoDate;
956 elseif (in_array($i, $lastOrNextYearActivities)) {
957 $date = $lastOrNextYearDate;
959 elseif (in_array($i, $todayActivities)) {
962 $this->callAPISuccess('Activity', 'create', [
964 'activity_date_time' => $date,
968 // parameters for different test cases, check each array key for the specific test-case
970 'todays-activity' => [
975 'context' => 'activity',
976 'activity_date_time_relative' => 'this.day',
977 'activity_type_id' => NULL,
983 'todays-activity-filtered-by-range' => [
988 'context' => 'activity',
989 'activity_date_time_low' => date('Y/m/d', strtotime('yesterday')),
990 'activity_date_time_high' => date('Y/m/d'),
991 'activity_type_id' => NULL,
997 'last-week-activity' => [
1002 'context' => 'activity',
1003 'activity_date_time_relative' => 'previous.week',
1004 'activity_type_id' => NULL,
1010 'this-quarter-activity' => [
1015 'context' => 'activity',
1016 'activity_date_time_relative' => 'this.quarter',
1017 'activity_type_id' => NULL,
1023 'activity-of-all-statuses' => [
1028 'context' => 'activity',
1029 'activity_status_id' => '1,2',
1030 'activity_type_id' => NULL,
1038 foreach ($testCases as $caseName => $testCase) {
1039 CRM_Utils_Date
::convertFormDateToApiFormat($testCase['params'], 'activity_date_time', FALSE);
1040 $activities = CRM_Activity_BAO_Activity
::getActivities($testCase['params']);
1041 $activityCount = CRM_Activity_BAO_Activity
::getActivitiesCount($testCase['params']);
1043 $activityIDs = array_keys($activities);
1045 if ($caseName === 'todays-activity' ||
$caseName === 'todays-activity-filtered-by-range') {
1046 // Only one of the 4 activities today relates to contact id 1.
1047 $this->assertEquals(1, $activityCount);
1048 $this->assertEquals(1, count($activities));
1049 $this->assertEquals([7], array_keys($activities));
1051 elseif ($caseName === 'last-week-activity') {
1052 // Only one of the 3 activities today relates to contact id 1.
1053 $this->assertEquals(1, $activityCount);
1054 $this->assertEquals(1, count($activities));
1055 $this->assertEquals([1], $activityIDs);
1057 elseif ($caseName === 'lhis-quarter-activity') {
1058 $this->assertEquals(count($lastTwoMonthsActivities), $activityCount);
1059 $this->assertEquals(count($lastTwoMonthsActivities), count($activities));
1060 $this->checkArrayEquals($lastTwoMonthsActivities, $activityIDs);
1062 elseif ($caseName === 'last-or-next-year-activity') {
1063 $this->assertEquals(count($lastOrNextYearActivities), $activityCount);
1064 $this->assertEquals(count($lastOrNextYearActivities), count($activities));
1065 $this->checkArrayEquals($lastOrNextYearActivities, $activityIDs);
1067 elseif ($caseName === 'activity-of-all-statuses') {
1068 $this->assertEquals(3, $activityCount);
1069 $this->assertEquals(3, count($activities));
1075 * @dataProvider getActivityDateData
1080 * @throws \CRM_Core_Exception
1081 * @throws \CiviCRM_API3_Exception
1083 public function testActivityRelativeDateFilter($params, $expected): void
{
1084 $thisYear = date('Y');
1086 date('Y-m-d', strtotime(($thisYear - 1) . '-01-01')),
1087 date('Y-m-d', strtotime(($thisYear - 1) . '-12-31')),
1088 date('Y-m-d', strtotime($thisYear . '-01-01')),
1089 date('Y-m-d', strtotime($thisYear . '-12-31')),
1090 date('Y-m-d', strtotime(($thisYear +
1) . '-01-01')),
1091 date('Y-m-d', strtotime(($thisYear +
1) . '-12-31')),
1093 foreach ($dates as $date) {
1094 $this->activityCreate(['activity_date_time' => $date]);
1096 $activitiesDep = CRM_Activity_BAO_Activity
::getActivities($params);
1097 $activityCount = CRM_Activity_BAO_Activity
::getActivitiesCount($params);
1098 $this->assertEquals(count($activitiesDep), $activityCount);
1099 foreach ($activitiesDep as $activity) {
1100 $this->assertTrue(strtotime($activity['activity_date_time']) >= $expected['earliest'], $activity['activity_date_time'] . ' should be no earlier than ' . date('Y-m-d H:i:s', $expected['earliest']));
1101 $this->assertTrue(strtotime($activity['activity_date_time']) < $expected['latest'], $activity['activity_date_time'] . ' should be before ' . date('Y-m-d H:i:s', $expected['latest']));
1107 * Get activity date data.
1109 * Later we might migrate rework the rest of
1110 * testByActivityDateAndStatus
1111 * to use data provider methodology as it's way complex!
1115 public function getActivityDateData() {
1117 'last-year-activity' => [
1122 'context' => 'activity',
1123 'activity_date_relative' => 'previous.year',
1124 'activity_type_id' => NULL,
1131 'earliest' => strtotime('first day of january last year'),
1132 'latest' => strtotime('first day of january this year'),
1139 * CRM-20308: Test from email address when a 'copy of Activity' event occur
1141 * @throws \CRM_Core_Exception
1142 * @throws \CiviCRM_API3_Exception
1144 public function testEmailAddressOfActivityCopy() {
1145 // Case 1: assert the 'From' Email Address of source Actvity Contact ID
1146 // create activity with source contact ID which has email address
1147 $assigneeContactId = $this->individualCreate();
1148 $sourceContactParams = [
1149 'first_name' => 'liz',
1150 'last_name' => 'hurleey',
1151 'email' => 'liz@testemail.com',
1153 $sourceContactID = $this->individualCreate($sourceContactParams);
1154 $sourceDisplayName = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact', $sourceContactID, 'display_name');
1156 // create an activity using API
1158 'source_contact_id' => $sourceContactID,
1159 'subject' => 'Scheduling Meeting',
1160 'activity_type_id' => 'Meeting',
1161 'assignee_contact_id' => [$assigneeContactId],
1162 'activity_date_time' => 'now',
1164 $activity = $this->callAPISuccess('Activity', 'create', $params);
1166 // Check that from address is in "Source-Display-Name <source-email>"
1167 $formAddress = CRM_Case_BAO_Case
::getReceiptFrom($activity['id']);
1168 $expectedFromAddress = sprintf('%s <%s>', $sourceDisplayName, $sourceContactParams['email']);
1169 $this->assertEquals($expectedFromAddress, $formAddress);
1171 // Case 2: System Default From Address
1172 // but first erase the email address of existing source contact ID
1173 $withoutEmailParams = [
1176 $sourceContactID = $this->individualCreate($withoutEmailParams);
1178 'source_contact_id' => $sourceContactID,
1179 'subject' => 'Scheduling Meeting 2',
1180 'activity_type_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Meeting'),
1181 'activity_date_time' => date('Ymd'),
1183 $activity = $this->callAPISuccess('Activity', 'create', $params);
1184 // fetch domain info
1185 $domainInfo = $this->callAPISuccess('Domain', 'getsingle', ['id' => CRM_Core_Config
::domainID()]);
1187 $formAddress = CRM_Case_BAO_Case
::getReceiptFrom($activity['id']);
1188 if (!empty($domainInfo['from_email'])) {
1189 $expectedFromAddress = sprintf('%s <%s>', $domainInfo['from_name'], $domainInfo['from_email']);
1191 // Case 3: fetch default Organization Contact email address
1192 elseif (!empty($domainInfo['domain_email'])) {
1193 $expectedFromAddress = sprintf('%s <%s>', $domainInfo['name'], $domainInfo['domain_email']);
1195 $this->assertEquals($expectedFromAddress, $formAddress);
1197 // TODO: Case 4 about checking the $formAddress on basis of logged contact ID respectively needs,
1198 // to change the domain setting, which isn't straight forward in test environment
1202 * Set up for testing activity queries.
1204 protected function setUpForActivityDashboardTests() {
1205 $this->createTestActivities();
1208 'contact_id' => NULL,
1211 'context' => 'home',
1212 'activity_type_id' => NULL,
1213 // for dashlet the Scheduled status is set by default
1214 'activity_status_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled'),
1222 * @throws \CRM_Core_Exception
1223 * @throws \CiviCRM_API3_Exception
1225 public function testSendEmailBasic(): void
{
1226 $contactId = $this->individualCreate();
1228 // create a logged in USER since the code references it for sendEmail user.
1229 $loggedInUser = $this->createLoggedInUser();
1231 $contactDetailsIntersectKeys = [
1234 'display_name' => '',
1235 'do_not_email' => '',
1236 'preferred_mail_format' => '',
1237 'is_deceased' => '',
1242 $contact = $this->callAPISuccess('Contact', 'getsingle', ['id' => $contactId, 'return' => array_keys($contactDetailsIntersectKeys)]);
1243 $contactDetailsIntersectKeys = [
1246 'display_name' => '',
1247 'do_not_email' => '',
1248 'preferred_mail_format' => '',
1249 'is_deceased' => '',
1254 array_intersect_key($contact, $contactDetailsIntersectKeys),
1257 $subject = __FUNCTION__
. ' subject';
1258 $html = __FUNCTION__
. ' html';
1259 $text = __FUNCTION__
. ' text';
1260 $userID = $loggedInUser;
1262 [$sent, $activity_ids] = CRM_Activity_BAO_Activity
::sendEmail(
1269 $from = __FUNCTION__
. '@example.com'
1272 $activity = $this->callAPISuccessGetSingle('Activity', ['id' => $activity_ids[0], 'return' => ['details', 'subject']]);
1273 $details = "-ALTERNATIVE ITEM 0-
1275 -ALTERNATIVE ITEM 1-
1279 $this->assertEquals($activity['details'], $details, 'Activity details does not match.');
1280 $this->assertEquals($activity['subject'], $subject, 'Activity subject does not match.');
1283 public function testSendEmailWithCampaign() {
1284 // Create a contact and contactDetails array.
1285 $contactId = $this->individualCreate();
1287 // create a logged in USER since the code references it for sendEmail user.
1288 $this->createLoggedInUser();
1289 $session = CRM_Core_Session
::singleton();
1290 $loggedInUser = $session->get('userID');
1292 $contact = $this->civicrm_api('contact', 'getsingle', ['id' => $contactId, 'version' => $this->_apiversion
]);
1293 $contactDetailsIntersectKeys = [
1296 'display_name' => '',
1297 'do_not_email' => '',
1298 'preferred_mail_format' => '',
1299 'is_deceased' => '',
1304 array_intersect_key($contact, $contactDetailsIntersectKeys),
1307 // Create a campaign.
1308 $result = $this->civicrm_api('Campaign', 'create', [
1309 'version' => $this->_apiversion
,
1310 'title' => __FUNCTION__
. ' campaign',
1312 $campaign_id = $result['id'];
1314 $subject = __FUNCTION__
. ' subject';
1315 $html = __FUNCTION__
. ' html';
1316 $text = __FUNCTION__
. ' text';
1317 $userID = $loggedInUser;
1319 [$sent, $activity_ids] = $email_result = CRM_Activity_BAO_Activity
::sendEmail(
1326 $from = __FUNCTION__
. '@example.com',
1327 $attachments = NULL,
1331 $additionalDetails = NULL,
1335 $activity = $this->civicrm_api('activity', 'getsingle', ['id' => $activity_ids[0], 'version' => $this->_apiversion
]);
1336 $this->assertEquals($activity['campaign_id'], $campaign_id, 'Activity campaign_id does not match.');
1341 public function testSendSMSWithoutPermission() {
1343 $session = CRM_Core_Session
::singleton();
1344 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM'];
1345 $this->expectException(CRM_Core_Exception
::class);
1346 $this->expectExceptionMessage('You do not have the \'send SMS\' permission');
1347 CRM_Activity_BAO_Activity
::sendSMS(
1352 $session->get('userID')
1357 * Test that a sms does not send when a phone number is not available.
1359 * @throws \API_Exception
1360 * @throws \CRM_Core_Exception
1361 * @throws \CiviCRM_API3_Exception
1362 * @throws \Civi\API\Exception\UnauthorizedException
1364 public function testSendSmsNoPhoneNumber(): void
{
1365 $sent = $this->createSendSmsTest(FALSE);
1366 $this->assertEquals('Recipient phone number is invalid or recipient does not want to receive SMS', $sent[0], "Expected error doesn't match");
1370 * @throws \API_Exception
1371 * @throws \CRM_Core_Exception
1372 * @throws \CiviCRM_API3_Exception
1373 * @throws \Civi\API\Exception\UnauthorizedException
1375 public function testSendSmsLandLinePhoneNumber(): void
{
1376 $sent = $this->createSendSmsTest(FALSE, 1);
1377 $this->assertEquals('Recipient phone number is invalid or recipient does not want to receive SMS', $sent[0], "Expected error doesn't match");
1381 * @throws \API_Exception
1382 * @throws \CRM_Core_Exception
1383 * @throws \CiviCRM_API3_Exception
1384 * @throws \Civi\API\Exception\UnauthorizedException
1386 public function testSendSmsMobilePhoneNumber(): void
{
1387 $sent = $this->createSendSmsTest(TRUE, 2);
1388 $this->assertEquals(TRUE, $sent[0], "Expected sent should be true");
1392 * Test that when a number is specified in the To Param of the SMS provider parameters that an SMS is sent
1393 * @see dev/core/#273
1395 public function testSendSMSMobileInToProviderParam(): void
{
1396 $sent = $this->createSendSmsTest(TRUE, 2, TRUE);
1397 $this->assertEquals(TRUE, $sent[0], 'Expected sent should be true');
1401 * Test that when a numbe ris specified in the To Param of the SMS provider parameters that an SMS is sent
1402 * @see dev/core/#273
1404 public function testSendSMSMobileInToProviderParamWithDoNotSMS(): void
{
1405 $sent = $this->createSendSmsTest(FALSE, 2, TRUE, ['do_not_sms' => 1]);
1406 foreach ($sent as $error) {
1407 $this->assertEquals('Contact Does not accept SMS', $error);
1409 $this->assertCount(1, $sent, 'Expected sent should a PEAR Error');
1413 * @param bool $expectSuccess
1414 * @param int $phoneType (0=no phone, phone_type option group (1=fixed,
1416 * @param bool $passPhoneTypeInContactDetails
1417 * @param array $additionalContactParams additional contact creation params
1420 * @throws \API_Exception
1421 * @throws \CRM_Core_Exception
1422 * @throws \CiviCRM_API3_Exception
1423 * @throws \Civi\API\Exception\UnauthorizedException
1425 public function createSendSmsTest(bool $expectSuccess = TRUE, int $phoneType = 0, bool $passPhoneTypeInContactDetails = FALSE, array $additionalContactParams = []): array {
1426 $provider = civicrm_api3('SmsProvider', 'create', [
1427 'name' => 'CiviTestSMSProvider',
1432 'api_params' => 'a=1',
1438 $smsProviderParams['provider_id'] = $provider['id'];
1441 $contactId = $this->individualCreate();
1442 if (!empty($additionalContactParams)) {
1443 $this->callAPISuccess('contact', 'create', ['id' => $contactId] +
$additionalContactParams);
1445 $contactsResult = $this->callApiSuccess('Contact', 'get', ['id' => $contactId, 'return' => ['id', 'phone_type_id', 'do_not_sms']]);
1446 $contactDetails = $contactsResult['values'];
1448 // Get contactIds from contact details
1449 foreach ($contactDetails as $contact) {
1450 $contactIds[] = $contact['contact_id'];
1453 $activityParams['sms_text_message'] = 'text';
1454 $activityParams['activity_subject'] = 'subject';
1456 // Get a "logged in" user to set as source of Sms.
1457 $session = CRM_Core_Session
::singleton();
1458 $sourceContactId = $session->get('userID');
1460 $this->createLoggedInUser();
1462 // Give user permission to 'send SMS'
1463 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['access CiviCRM', 'send SMS'];
1465 // Create a phone number
1466 switch ($phoneType) {
1472 // Create a mobile phone number
1473 $contactDetails = $this->createMobilePhone($contactId, $passPhoneTypeInContactDetails, $contactDetails);
1477 // Create a fixed phone number
1478 $phone = civicrm_api3('Phone', 'create', [
1479 'contact_id' => $contactId,
1481 'phone_type_id' => 'Phone',
1483 if ($passPhoneTypeInContactDetails) {
1484 $contactDetails[$contactId]['phone'] = $phone['values'][$phone['id']]['phone'];
1485 $contactDetails[$contactId]['phone_type_id'] = $phone['values'][$phone['id']]['phone_type_id'];
1490 // Now run the actual test
1491 [$sent, $activityId, $success] = CRM_Activity_BAO_Activity
::sendSms(
1498 $this->validateActivity($activityId);
1499 $this->assertEquals($expectSuccess, $success);
1500 return (array) $sent;
1504 * @throws \CRM_Core_Exception
1506 protected function createTestActivities(): void
{
1507 $this->loadXMLDataSet(__DIR__
. '/activities_for_dashboard_count.xml');
1508 // Make changes to improve variation in php since the xml method is brittle & relies on option values being unchanged.
1509 $this->callAPISuccess('Activity', 'create', ['id' => 12, 'activity_type_id' => 'Bulk Email']);
1513 * ACL HOOK implementation for various tests
1515 public function hook_civicrm_aclWhereClause($type, &$tables, &$whereTables, &$contactID, &$where): void
{
1516 if (!empty($this->allowedContactsACL
)) {
1517 $contact_id_list = implode(',', $this->allowedContactsACL
);
1518 $where = " contact_a.id IN ($contact_id_list)";
1522 public function testCaseTokens() {
1523 $caseTest = new CiviCaseTestCase();
1525 // Create a contact and contactDetails array.
1526 $contactId = $this->individualCreate();
1528 // create a case for this user
1529 $result = $this->callAPISuccess('Case', 'create', [
1530 'contact_id' => $contactId,
1531 'case_type_id' => '1',
1532 'subject' => "my case",
1533 'status_id' => "Open",
1536 $caseId = $result['id'];
1537 $html_message = "<p>This is a test case with id: {case.id} and subject: {case.subject}</p>";
1538 $html_message = CRM_Utils_Token
::replaceCaseTokens($caseId, $html_message);
1540 $this->assertTrue(strpos($html_message, 'id: ' . $caseId) !== 0);
1541 $this->assertTrue(strpos($html_message, 'subject: my case') !== 0);
1542 $caseTest->tearDown();
1545 public function testSendEmailWithCaseId() {
1546 $caseTest = new CiviCaseTestCase();
1548 // Create a contact and contactDetails array.
1549 $contactId = $this->individualCreate();
1550 $contact = $this->callAPISuccess('Contact', 'get', ['id' => $contactId]);
1552 // create a logged in USER since the code references it for sendEmail user.
1553 $this->createLoggedInUser();
1554 CRM_Core_Config
::singleton()->userPermissionClass
->permissions
= ['view all contacts', 'access CiviCRM', 'access all cases and activities', 'administer CiviCase'];
1555 $session = CRM_Core_Session
::singleton();
1556 $loggedInUser = $session->get('userID');
1558 // create a case for this user
1559 $result = $this->callAPISuccess('Case', 'create', [
1560 'contact_id' => $contactId,
1561 'case_type_id' => 1,
1562 'subject' => "my case",
1563 'status_id' => "Open",
1566 $caseId = $result['id'];
1568 $subject = __FUNCTION__
. ' subject {case.subject}';
1569 $html = __FUNCTION__
. ' html {case.subject}';
1570 $text = __FUNCTION__
. ' text';
1572 $mut = new CiviMailUtils($this, TRUE);
1573 [$sent, $activity_ids] = $email_result = CRM_Activity_BAO_Activity
::sendEmail(
1578 $contact['values'][$contactId]['email'],
1580 $from = __FUNCTION__
. '@example.com',
1590 $activity = $this->callAPISuccess('Activity', 'getsingle', ['id' => $activity_ids[0], 'return' => ['case_id']]);
1591 $this->assertEquals($caseId, $activity['case_id'][0], 'Activity case_id does not match.');
1592 $mut->checkMailLog(['subject my case']);
1597 * Checks that tokens are uniquely replaced for contacts.
1599 public function testSendEmailWillReplaceTokensUniquelyForEachContact() {
1600 $contactId1 = $this->individualCreate(['last_name' => 'Red']);
1601 $contactId2 = $this->individualCreate(['last_name' => 'Pink']);
1603 // create a logged in USER since the code references it for sendEmail user.
1604 $this->createLoggedInUser();
1605 $session = CRM_Core_Session
::singleton();
1606 $loggedInUser = $session->get('userID');
1607 $contact = $this->callAPISuccess('Contact', 'get', ['sequential' => 1, 'id' => ['IN' => [$contactId1, $contactId2]]]);
1609 // Create a campaign.
1610 $result = $this->callAPISuccess('Campaign', 'create', [
1611 'version' => $this->_apiversion
,
1612 'title' => __FUNCTION__
. ' campaign',
1614 $campaign_id = $result['id'];
1616 // Add contact tokens in subject, html , text.
1617 $subject = __FUNCTION__
. ' subject' . '{contact.display_name}';
1618 $html = __FUNCTION__
. ' html' . '{contact.display_name}';
1619 $text = __FUNCTION__
. ' text' . '{contact.display_name}';
1620 $userID = $loggedInUser;
1622 CRM_Activity_BAO_Activity
::sendEmail(
1627 $contact['values'][0]['email'],
1629 $from = __FUNCTION__
. '@example.com',
1630 $attachments = NULL,
1633 $contactIds = array_column($contact['values'], 'id'),
1634 $additionalDetails = NULL,
1638 $result = $this->callAPISuccess('activity', 'get', ['campaign_id' => $campaign_id]);
1639 // An activity created for each of the two contacts
1640 $this->assertEquals(2, $result['count']);
1642 foreach ($result['values'] as $activity) {
1643 $htmlValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $html);
1644 $textValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $text);
1645 $subjectValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $subject);
1646 $details = "-ALTERNATIVE ITEM 0-
1648 -ALTERNATIVE ITEM 1-
1652 $this->assertEquals($activity['details'], $details, 'Activity details does not match.');
1653 $this->assertEquals($activity['subject'], $subjectValue, 'Activity subject does not match.');
1659 * Test that smarty is rendered, if enabled.
1661 * @throws \CRM_Core_Exception
1662 * @throws \CiviCRM_API3_Exception
1664 public function testSmartyEnabled(): void
{
1665 putenv('CIVICRM_MAIL_SMARTY=1');
1666 $this->createLoggedInUser();
1667 $contactID = $this->individualCreate(['last_name' => 'Red']);
1668 CRM_Activity_BAO_Activity
::sendEmail(
1671 'preferred_mail_format' => 'Both',
1672 'contact_id' => $contactID,
1673 'email' => 'a@example.com',
1676 '{contact.first_name} {$contact.first_name}',
1677 '{contact.first_name} {$contact.first_name}',
1678 '{contact.first_name} {$contact.first_name}',
1687 $activity = $this->callAPISuccessGetValue('Activity', ['return' => 'details']);
1688 putenv('CIVICRM_MAIL_SMARTY=0');
1692 * Same as testSendEmailWillReplaceTokensUniquelyForEachContact but with
1693 * 3 recipients and an attachment.
1695 * @throws \CRM_Core_Exception
1696 * @throws \CiviCRM_API3_Exception
1698 public function testSendEmailWillReplaceTokensUniquelyForEachContact3(): void
{
1699 $contactId1 = $this->individualCreate(['last_name' => 'Red']);
1700 $contactId2 = $this->individualCreate(['last_name' => 'Pink']);
1701 $contactId3 = $this->individualCreate(['last_name' => 'Ochre']);
1703 // create a logged in USER since the code references it for sendEmail user.
1704 $loggedInUser = $this->createLoggedInUser();
1705 $contact = $this->callAPISuccess('Contact', 'get', ['sequential' => 1, 'id' => ['IN' => [$contactId1, $contactId2, $contactId3]]]);
1707 // Add contact tokens in subject, html , text.
1708 $subject = __FUNCTION__
. ' subject' . '{contact.display_name}';
1709 $html = __FUNCTION__
. ' html' . '{contact.display_name}';
1710 // Check the smarty doesn't mess stuff up.
1711 $text = ' text' . '{contact.display_name} {$contact.first_name}';
1713 $filepath = Civi
::paths()->getPath('[civicrm.files]/custom');
1714 $fileName = 'test_email_create.txt';
1715 $fileUri = "{$filepath}/{$fileName}";
1717 CRM_Utils_File
::createFakeFile($filepath, 'aaaaaa', $fileName);
1722 'type' => 'text/plain',
1723 'location' => $fileUri,
1727 CRM_Activity_BAO_Activity
::sendEmail(
1732 $contact['values'][0]['email'],
1734 __FUNCTION__
. '@example.com',
1738 array_column($contact['values'], 'id'),
1741 $this->getCampaignID()
1743 $result = $this->callAPISuccess('Activity', 'get', ['campaign_id' => $this->getCampaignID()]);
1744 // An activity created for each of the two contacts
1745 $this->assertEquals(3, $result['count']);
1747 foreach ($result['values'] as $activity) {
1748 $htmlValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $html);
1749 $textValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $text);
1750 $subjectValue = str_replace('{contact.display_name}', $contact['values'][$id]['display_name'], $subject);
1751 $details = "-ALTERNATIVE ITEM 0-
1753 -ALTERNATIVE ITEM 1-
1757 $this->assertEquals($activity['details'], $details, 'Activity details does not match.');
1758 $this->assertEquals($activity['subject'], $subjectValue, 'Activity subject does not match.');
1766 * Checks that attachments are not duplicated for activities.
1768 public function testSendEmailDoesNotDuplicateAttachmentFileIdsForActivitiesCreated() {
1769 $contactId1 = $this->individualCreate(['last_name' => 'Red']);
1770 $contactId2 = $this->individualCreate(['last_name' => 'Pink']);
1772 // create a logged in USER since the code references it for sendEmail user.
1773 $this->createLoggedInUser();
1774 $session = CRM_Core_Session
::singleton();
1775 $loggedInUser = $session->get('userID');
1776 $contact = $this->callAPISuccess('Contact', 'get', ['sequential' => 1, 'id' => ['IN' => [$contactId1, $contactId2]]]);
1778 // Create a campaign.
1779 $result = $this->callAPISuccess('Campaign', 'create', [
1780 'version' => $this->_apiversion
,
1781 'title' => __FUNCTION__
. ' campaign',
1783 $campaign_id = $result['id'];
1785 $subject = __FUNCTION__
. ' subject';
1786 $html = __FUNCTION__
. ' html';
1787 $text = __FUNCTION__
. ' text';
1788 $userID = $loggedInUser;
1790 $filepath = Civi
::paths()->getPath('[civicrm.files]/custom');
1791 $fileName = "test_email_create.txt";
1792 $fileUri = "{$filepath}/{$fileName}";
1794 CRM_Utils_File
::createFakeFile($filepath, 'Bananas do not bend themselves without a little help.', $fileName);
1799 'type' => 'text/plain',
1800 'location' => $fileUri,
1804 CRM_Activity_BAO_Activity
::sendEmail(
1809 $contact['values'][0]['email'],
1811 $from = __FUNCTION__
. '@example.com',
1815 $contactIds = array_column($contact['values'], 'id'),
1816 $additionalDetails = NULL,
1820 $result = $this->callAPISuccess('activity', 'get', ['campaign_id' => $campaign_id]);
1821 // An activity created for each of the two contacts, i.e two activities.
1822 $this->assertEquals(2, $result['count']);
1823 $activityIds = array_column($result['values'], 'id');
1824 $result = $this->callAPISuccess('Activity', 'get', [
1825 'return' => ['file_id'],
1826 'id' => ['IN' => $activityIds],
1830 // Verify that the that both activities are linked to the same File Id.
1831 $this->assertEquals($result['values'][0]['file_id'], $result['values'][1]['file_id']);
1835 * Adds a case with one activity.
1838 protected function addCaseWithActivity() {
1839 // case is not enabled by default do that now.
1840 $enableResult = CRM_Core_BAO_ConfigSetting
::enableComponent('CiviCase');
1841 $this->assertTrue($enableResult, 'Cannot enable CiviCase in line ' . __LINE__
);
1843 // We need a minimal case setup.
1844 $case_type_id = civicrm_api3('CaseType', 'get', ['return' => 'id', 'name' => 'test_case_type'])['id'] ??
NULL;
1845 if (!$case_type_id) {
1847 'name' => 'test_case_type',
1848 'title' => 'test_case_type',
1851 "activityTypes" => [
1852 ["name" => "Open Case", "max_instances" => "1"],
1853 ["name" => "Meeting"],
1857 "name" => "standard_timeline",
1858 "label" => "Standard Timeline",
1860 "activityTypes" => [
1862 "name" => "Open Case",
1863 "status" => "Completed",
1864 "label" => "Open Case",
1865 "default_assignee_type" => "1",
1870 "timelineActivityTypes" => [
1872 "name" => 'Open Case',
1873 "status" => 'Completed',
1874 "label" => 'Open Case',
1875 "default_assignee_type" => "1",
1880 "name" => "Case Coordinator",
1887 $case_type_id = $this->callAPISuccess('CaseType', 'create', $params)['id'];
1890 // Create a case with Contact #3 as the client.
1891 $case_id = civicrm_api3('case', 'get', ['subject' => 'test case 1'])['id'] ??
NULL;
1895 'subject' => 'test case 1',
1897 'status_id' => 'Open',
1898 'case_type_id' => $case_type_id,
1901 $case_id = $this->callAPISuccess('case', 'create', $params)['id'];
1904 // Create a scheduled 'Meeting' activity that belongs to this case, but is
1905 // assigned to contact #9
1906 $activity_id = $this->callAPISuccess('Activity', 'create', [
1907 'activity_type_id' => 'Meeting',
1908 'status_id' => 'Scheduled',
1909 'case_id' => $case_id,
1910 'source_contact_id' => 3,
1911 'assignee_id' => [9],
1912 'subject' => 'test meeting activity',
1917 * Change setting, and the cache of it.
1919 protected function setShowCaseActivitiesInCore(bool $val) {
1920 Civi
::settings()->set('civicaseShowCaseActivities', $val ?
1 : 0);
1921 CRM_Core_Component
::getEnabledComponents();
1922 Civi
::$statics['CRM_Core_Component']['info']['CiviCase'] = new CRM_Case_Info('CiviCase', 'CRM_Case', 7);
1923 Civi
::$statics['CRM_Core_Component']['info']['CiviCase']->info
['showActivitiesInCore'] = $val;
1927 * Test multiple variations of target and assignee contacts in create
1930 * @dataProvider targetAndAssigneeProvider
1932 * @param array $do_first
1933 * @param array $do_second
1935 * @throws \CiviCRM_API3_Exception
1937 public function testTargetAssigneeVariations(array $do_first, array $do_second) {
1938 // Originally wanted to put this in setUp() but it broke other tests.
1939 $this->loggedInUserId
= $this->createLoggedInUser();
1940 for ($i = 1; $i <= 4; $i++
) {
1941 $this->someContacts
[$i] = $this->individualCreate([], $i - 1, TRUE);
1945 'activity_type_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Meeting'),
1946 'subject' => 'Test Meeting',
1947 'source_contact_id' => $this->loggedInUserId
,
1950 // Create an activity first if specified.
1952 if (!empty($do_first)) {
1953 if (!empty($do_first['targets'])) {
1954 // e.g. if it is [1], then pick $someContacts[1]. If it's [1,2], then
1955 // pick $someContacts[1] and $someContacts[2].
1956 $params['target_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_first['targets'])));
1958 if (!empty($do_first['assignees'])) {
1959 $params['assignee_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_first['assignees'])));
1962 $activity = CRM_Activity_BAO_Activity
::create($params);
1963 $this->assertNotEmpty($activity->id
);
1965 $params['id'] = $activity->id
;
1968 // Now do the second one, which will either create or update depending what
1970 $params['target_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_second['targets'])));
1971 $params['assignee_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_second['assignees'])));
1972 $activity = CRM_Activity_BAO_Activity
::create($params);
1976 1 => [$activity->id
, 'Integer'],
1977 2 => [CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'), 'Integer'],
1979 $this->assertEquals($params['target_contact_id'], array_column(CRM_Core_DAO
::executeQuery('SELECT contact_id FROM civicrm_activity_contact WHERE activity_id = %1 AND record_type_id = %2', $queryParams)->fetchAll(), 'contact_id'));
1983 1 => [$activity->id
, 'Integer'],
1984 2 => [CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Assignees'), 'Integer'],
1986 $this->assertEquals($params['assignee_contact_id'], array_column(CRM_Core_DAO
::executeQuery('SELECT contact_id FROM civicrm_activity_contact WHERE activity_id = %1 AND record_type_id = %2', $queryParams)->fetchAll(), 'contact_id'));
1989 foreach ($this->someContacts
as $cid) {
1990 $this->callAPISuccess('Contact', 'delete', ['id' => $cid]);
1995 * Same as testTargetAssigneeVariations but passes the target/assignee
1996 * in as a scalar when there's only one of them.
1998 * @dataProvider targetAndAssigneeProvider
2000 * @param array $do_first
2001 * @param array $do_second
2003 * @throws \CRM_Core_Exception
2004 * @throws \CiviCRM_API3_Exception
2006 public function testTargetAssigneeVariationsWithScalars(array $do_first, array $do_second) {
2007 // Originally wanted to put this in setUp() but it broke other tests.
2008 $this->loggedInUserId
= $this->createLoggedInUser();
2009 for ($i = 1; $i <= 4; $i++
) {
2010 $this->someContacts
[$i] = $this->individualCreate([], $i - 1, TRUE);
2014 'activity_type_id' => CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Meeting'),
2015 'subject' => 'Test Meeting',
2016 'source_contact_id' => $this->loggedInUserId
,
2019 // Create an activity first if specified.
2021 if (!empty($do_first)) {
2022 if (!empty($do_first['targets'])) {
2023 // e.g. if it is [1], then pick $someContacts[1]. If it's [1,2], then
2024 // pick $someContacts[1] and $someContacts[2].
2025 $params['target_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_first['targets'])));
2026 if (count($params['target_contact_id']) == 1) {
2027 $params['target_contact_id'] = $params['target_contact_id'][0];
2030 if (!empty($do_first['assignees'])) {
2031 $params['assignee_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_first['assignees'])));
2032 if (count($params['assignee_contact_id']) == 1) {
2033 $params['assignee_contact_id'] = $params['assignee_contact_id'][0];
2037 $activity = CRM_Activity_BAO_Activity
::create($params);
2038 $this->assertNotEmpty($activity->id
);
2040 $params['id'] = $activity->id
;
2043 // Now do the second one, which will either create or update depending what
2045 $params['target_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_second['targets'])));
2046 if (count($params['target_contact_id']) == 1) {
2047 $params['target_contact_id'] = $params['target_contact_id'][0];
2049 $params['assignee_contact_id'] = array_values(array_intersect_key($this->someContacts
, array_flip($do_second['assignees'])));
2050 if (count($params['assignee_contact_id']) == 1) {
2051 $params['assignee_contact_id'] = $params['assignee_contact_id'][0];
2053 $activity = CRM_Activity_BAO_Activity
::create($params);
2057 1 => [$activity->id
, 'Integer'],
2058 2 => [CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Targets'), 'Integer'],
2060 $this->assertEquals((array) $params['target_contact_id'], array_column(CRM_Core_DAO
::executeQuery('SELECT contact_id FROM civicrm_activity_contact WHERE activity_id = %1 AND record_type_id = %2', $queryParams)->fetchAll(), 'contact_id'));
2064 1 => [$activity->id
, 'Integer'],
2065 2 => [CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_ActivityContact', 'record_type_id', 'Activity Assignees'), 'Integer'],
2067 $this->assertEquals((array) $params['assignee_contact_id'], array_column(CRM_Core_DAO
::executeQuery('SELECT contact_id FROM civicrm_activity_contact WHERE activity_id = %1 AND record_type_id = %2', $queryParams)->fetchAll(), 'contact_id'));
2070 foreach ($this->someContacts
as $cid) {
2071 $this->callAPISuccess('Contact', 'delete', ['id' => $cid]);
2076 * Dataprovider for testTargetAssigneeVariations
2079 public function targetAndAssigneeProvider():array {
2081 // Explicit index so that it's easy to see which one has failed without
2082 // having to finger count.
2085 // Completely empty array means don't create any activity first,
2086 // as opposed to the ones we do later where "do first" has member
2087 // elements but those are empty, which means create an activity first
2088 // but with no contacts.
2105 'targets' => [1, 2],
2120 'assignees' => [3, 4],
2133 'targets' => [1, 2],
2140 'targets' => [1, 2],
2141 'assignees' => [3, 4],
2144 // The next sets test the same thing again but updating an activity
2145 // that has no contacts
2172 'targets' => [1, 2],
2193 'assignees' => [3, 4],
2212 'targets' => [1, 2],
2222 'targets' => [1, 2],
2223 'assignees' => [3, 4],
2226 // And again but updating an activity with 1 contact
2253 'targets' => [1, 2],
2274 'assignees' => [3, 4],
2293 'targets' => [1, 2],
2303 'targets' => [1, 2],
2304 'assignees' => [3, 4],
2313 // a little different variation where we're changing the target as
2314 // opposed to adding one or deleting
2319 // And again but updating an activity with 2 contacts
2322 'targets' => [1, 2],
2332 'targets' => [1, 2],
2342 'targets' => [1, 2],
2346 'targets' => [1, 2],
2352 'targets' => [1, 2],
2362 'targets' => [1, 2],
2367 'assignees' => [3, 4],
2372 'targets' => [1, 2],
2382 'targets' => [1, 2],
2386 'targets' => [1, 2],
2392 'targets' => [1, 2],
2396 'targets' => [1, 2],
2397 'assignees' => [3, 4],
2402 'targets' => [1, 2],
2410 // And again but now start with assignees
2437 'targets' => [1, 2],
2458 'assignees' => [3, 4],
2477 'targets' => [1, 2],
2487 'targets' => [1, 2],
2488 'assignees' => [3, 4],
2501 // And again but now start with 2 assignees
2505 'assignees' => [3, 4],
2515 'assignees' => [3, 4],
2525 'assignees' => [3, 4],
2528 'targets' => [1, 2],
2535 'assignees' => [3, 4],
2545 'assignees' => [3, 4],
2549 'assignees' => [3, 4],
2555 'assignees' => [3, 4],
2565 'assignees' => [3, 4],
2568 'targets' => [1, 2],
2575 'assignees' => [3, 4],
2578 'targets' => [1, 2],
2579 'assignees' => [3, 4],
2585 'assignees' => [3, 4],
2596 * Test the returned activity ids when there are multiple "To" recipients.
2597 * Similar to testSendEmailWillReplaceTokensUniquelyForEachContact but we're
2598 * checking the activity ids returned from sendEmail.
2600 public function testSendEmailWithMultipleToRecipients(): void
{
2601 $contactId1 = $this->individualCreate(['first_name' => 'Aaaa', 'last_name' => 'Bbbb']);
2602 $contactId2 = $this->individualCreate(['first_name' => 'Cccc', 'last_name' => 'Dddd']);
2604 // create a logged in USER since the code references it for sendEmail user.
2605 $loggedInUser = $this->createLoggedInUser();
2606 $contacts = $this->callAPISuccess('Contact', 'get', [
2608 'id' => ['IN' => [$contactId1, $contactId2]],
2611 [$sent, $activityIds] = CRM_Activity_BAO_Activity
::sendEmail(
2612 $contacts['values'],
2614 'here is some text',
2615 '<p>here is some html</p>',
2616 $contacts['values'][0]['email'],
2618 $from = __FUNCTION__
. '@example.com',
2619 $attachments = NULL,
2622 array_column($contacts['values'], 'id')
2625 // Get all activities for these contacts
2626 $result = $this->callAPISuccess('activity', 'get', [
2628 'return' => ['target_contact_id'],
2629 'target_contact_id' => ['IN' => [$contactId1, $contactId2]],
2632 // There should be one activity created for each of the two contacts
2633 $this->assertEquals(2, $result['count']);
2635 // Activity ids returned from sendEmail should match the ones returned from api call.
2636 $this->assertEquals($activityIds, array_column($result['values'], 'id'));
2638 // Is it the right contacts?
2639 $this->assertEquals(
2640 [0 => [0 => $contactId1], 1 => [0 => $contactId2]],
2641 array_column($result['values'], 'target_contact_id')
2643 $this->assertEquals(
2644 [0 => [$contactId1 => 'Bbbb, Aaaa'], 1 => [$contactId2 => 'Dddd, Cccc']],
2645 array_column($result['values'], 'target_contact_sort_name')
2650 * @param $activityId
2652 * @throws \API_Exception
2653 * @throws \Civi\API\Exception\UnauthorizedException
2655 protected function validateActivity($activityId): void
{
2656 $activity = Activity
::get(FALSE)
2657 ->addSelect('activity_type_id', 'status_id', 'subject', 'details')
2658 ->addWhere('id', '=', $activityId)
2659 ->execute()->first();
2661 $outBoundSmsActivityId = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS');
2662 $activityStatusCompleted = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_status_id', 'Completed');
2663 $this->assertEquals($outBoundSmsActivityId, $activity['activity_type_id'], 'Wrong activity type is set.');
2664 $this->assertEquals($activityStatusCompleted, $activity['status_id'], 'Expected activity status Completed.');
2665 $this->assertEquals('subject', $activity['subject'], 'Activity subject does not match.');
2666 $this->assertEquals('text', $activity['details'], 'Activity details does not match.');
2670 * @param int $contactId
2671 * @param bool $passPhoneTypeInContactDetails
2672 * @param $contactDetails
2675 * @throws \CiviCRM_API3_Exception
2677 protected function createMobilePhone(int $contactId, bool $passPhoneTypeInContactDetails, $contactDetails): array {
2678 $phone = civicrm_api3('Phone', 'create', [
2679 'contact_id' => $contactId,
2681 'phone_type_id' => 'Mobile',
2683 if ($passPhoneTypeInContactDetails) {
2684 $contactDetails[$contactId]['phone'] = $phone['values'][$phone['id']]['phone'];
2685 $contactDetails[$contactId]['phone_type_id'] = $phone['values'][$phone['id']]['phone_type_id'];
2687 return $contactDetails;
2691 * Get a campaign id - creating one if need be.
2695 protected function getCampaignID() {
2696 if (!isset($this->ids
['Campaign'][0])) {
2697 $this->ids
['Campaign'][0] = $this->callAPISuccess('Campaign', 'create', [
2698 'title' => 'campaign',
2701 return $this->ids
['Campaign'][0];