Merge pull request #23500 from civicrm/5.50
[civicrm-core.git] / tests / phpunit / CRM / Core / BAO / ActionScheduleTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 use Civi\Api4\Activity;
13 use Civi\Api4\ActivityContact;
14 use Civi\Api4\MembershipType;
15
16 /**
17 * Class CRM_Core_BAO_ActionScheduleTest.
18 *
19 * @group ActionSchedule
20 * @group headless
21 *
22 * There are additional tests for some specific entities in other classes:
23 * @see CRM_Activity_ActionMappingTest
24 * @see CRM_Contribute_ActionMapping_ByTypeTest
25 */
26 class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase {
27
28 use CRMTraits_Custom_CustomDataTrait;
29
30 /**
31 * @var CiviMailUtils
32 */
33 public $mut;
34
35 /**
36 * Entities set up for the test.
37 *
38 * @var array
39 */
40 private $fixtures = [];
41
42 /**
43 * Generic usable membership type id.
44 *
45 * These should pre-exist but something is deleting them.
46 *
47 * @var int
48 */
49 protected $membershipTypeID;
50
51 /**
52 * Setup for tests.
53 *
54 * @throws CRM_Core_Exception
55 */
56 public function setUp(): void {
57 parent::setUp();
58
59 $this->mut = new CiviMailUtils($this, TRUE);
60
61 $this->fixtures['rolling_membership_type'] = [
62 'period_type' => 'rolling',
63 'duration_unit' => 'month',
64 'duration_interval' => '3',
65 'is_active' => 1,
66 'domain_id' => 1,
67 'financial_type_id' => 2,
68 ];
69
70 $this->fixtures['rolling_membership'] = [
71 'membership_type_id' => [
72 'period_type' => 'rolling',
73 'duration_unit' => 'month',
74 'duration_interval' => '3',
75 'is_active' => 1,
76 ],
77 'join_date' => '20120315',
78 'start_date' => '20120315',
79 'end_date' => '20120615',
80 'is_override' => 0,
81 ];
82
83 $this->fixtures['rolling_membership_past'] = [
84 'membership_type_id' => [
85 'period_type' => 'rolling',
86 'duration_unit' => 'month',
87 'duration_interval' => '3',
88 'is_active' => 1,
89 ],
90 'join_date' => '20100310',
91 'start_date' => '20100310',
92 'end_date' => '20100610',
93 'is_override' => 'NULL',
94 ];
95 $this->fixtures['participant'] = [
96 'event_id' => [
97 'is_active' => 1,
98 'is_template' => 0,
99 'title' => 'Example Event',
100 'start_date' => '20120315',
101 'end_date' => '20120615',
102 ],
103 // Attendee.
104 'role_id' => '1',
105 // No-show.
106 'status_id' => '8',
107 ];
108
109 $this->fixtures['phone_call'] = [
110 'status_id' => 1,
111 'activity_type_id' => 2,
112 'activity_date_time' => '20120615100000',
113 'is_current_revision' => 1,
114 'is_deleted' => 0,
115 'subject' => 'Phone call',
116 'details' => 'A phone call about a bear',
117 ];
118 $this->fixtures['contact'] = [
119 'is_deceased' => 0,
120 'contact_type' => 'Individual',
121 'email' => 'test-member@example.com',
122 'gender_id' => 'Female',
123 'first_name' => 'Churmondleia',
124 'last_name' => 'Ōtākou',
125 ];
126 $this->fixtures['contact_2'] = [
127 'is_deceased' => 0,
128 'contact_type' => 'Individual',
129 'email' => 'test-contact-2@example.com',
130 'gender_id' => 'Male',
131 'first_name' => 'Fabio',
132 'last_name' => 'Fi',
133 ];
134 $this->fixtures['contact_birthdate'] = [
135 'is_deceased' => 0,
136 'contact_type' => 'Individual',
137 'email' => 'test-birth_day@example.com',
138 'birth_date' => '20050707',
139 ];
140 $this->fixtures['sched_activity_1day'] = [
141 'name' => 'One_Day_Phone_Call_Notice',
142 'title' => 'One Day Phone Call Notice',
143 'limit_to' => '1',
144 'absolute_date' => NULL,
145 'body_html' => '<p>1-Day (non-repeating) (for {activity.subject})</p>',
146 'body_text' => '1-Day (non-repeating) (for {activity.subject})',
147 'end_action' => NULL,
148 'end_date' => NULL,
149 'end_frequency_interval' => NULL,
150 'end_frequency_unit' => NULL,
151 'entity_status' => '1',
152 'entity_value' => '2',
153 'group_id' => NULL,
154 'is_active' => '1',
155 'is_repeat' => '0',
156 'mapping_id' => '1',
157 'msg_template_id' => NULL,
158 'recipient' => '2',
159 'recipient_listing' => NULL,
160 'recipient_manual' => NULL,
161 'record_activity' => 1,
162 'repetition_frequency_interval' => NULL,
163 'repetition_frequency_unit' => NULL,
164 'start_action_condition' => 'before',
165 'start_action_date' => 'activity_date_time',
166 'start_action_offset' => '1',
167 'start_action_unit' => 'day',
168 'subject' => '1-Day (non-repeating) (about {activity.activity_type})',
169 'effective_start_date' => '2012-06-14 00:00:00',
170 'effective_end_date' => '2012-06-15 00:00:00',
171 ];
172 $this->fixtures['sched_activity_5minute'] = [
173 'name' => 'Five_Minute_Phone_Call_Notice',
174 'title' => 'Five Minute Phone Call Notice',
175 'limit_to' => '1',
176 'absolute_date' => NULL,
177 'body_html' => '<p>5 minutes (for {activity.subject})</p>',
178 'body_text' => '5 minutes (non-repeating) (for {activity.subject})',
179 'end_action' => NULL,
180 'end_date' => NULL,
181 'end_frequency_interval' => NULL,
182 'end_frequency_unit' => NULL,
183 'entity_status' => '1',
184 'entity_value' => '2',
185 'group_id' => NULL,
186 'is_active' => '1',
187 'is_repeat' => '0',
188 'mapping_id' => '1',
189 'msg_template_id' => NULL,
190 'recipient' => '2',
191 'recipient_listing' => NULL,
192 'recipient_manual' => NULL,
193 'record_activity' => 1,
194 'repetition_frequency_interval' => NULL,
195 'repetition_frequency_unit' => NULL,
196 'start_action_condition' => 'before',
197 'start_action_date' => 'activity_date_time',
198 'start_action_offset' => '5',
199 'start_action_unit' => 'minute',
200 'subject' => '5 minutes (about {activity.activity_type})',
201 ];
202 $this->fixtures['sched_activity_1day_r'] = [
203 'name' => 'One_Day_Phone_Call_Notice_R',
204 'title' => 'One Day Phone Call Notice R',
205 'limit_to' => 1,
206 'absolute_date' => NULL,
207 'body_html' => '<p>1-Day (repeating)</p>',
208 'body_text' => '1-Day (repeating)',
209 'end_action' => 'after',
210 'end_date' => 'activity_date_time',
211 'end_frequency_interval' => '2',
212 'end_frequency_unit' => 'day',
213 'entity_status' => '1',
214 'entity_value' => '2',
215 'group_id' => NULL,
216 'is_active' => '1',
217 'is_repeat' => '1',
218 'mapping_id' => '1',
219 'msg_template_id' => NULL,
220 'recipient' => '2',
221 'recipient_listing' => NULL,
222 'recipient_manual' => NULL,
223 'record_activity' => NULL,
224 'repetition_frequency_interval' => '6',
225 'repetition_frequency_unit' => 'hour',
226 'start_action_condition' => 'before',
227 'start_action_date' => 'activity_date_time',
228 'start_action_offset' => '1',
229 'start_action_unit' => 'day',
230 'subject' => '1-Day (repeating) (about {activity.activity_type})',
231 'effective_end_date' => '2012-06-14 16:00:00',
232 ];
233 $this->fixtures['sched_activity_1day_r_on_abs_date'] = [
234 'name' => 'One_Day_Phone_Call_Notice_R',
235 'title' => 'One Day Phone Call Notice R',
236 'limit_to' => 1,
237 'absolute_date' => CRM_Utils_Date::processDate('20120614100000'),
238 'body_html' => '<p>1-Day (repeating)</p>',
239 'body_text' => '1-Day (repeating)',
240 'entity_status' => '1',
241 'entity_value' => '2',
242 'group_id' => NULL,
243 'is_active' => '1',
244 'is_repeat' => '1',
245 'mapping_id' => '1',
246 'msg_template_id' => NULL,
247 'recipient' => '2',
248 'recipient_listing' => NULL,
249 'recipient_manual' => NULL,
250 'record_activity' => NULL,
251 'repetition_frequency_interval' => '6',
252 'repetition_frequency_unit' => 'hour',
253 'end_action' => 'after',
254 'end_date' => 'activity_date_time',
255 'end_frequency_interval' => '2',
256 'end_frequency_unit' => 'day',
257 'start_action_condition' => '',
258 'start_action_date' => '',
259 'start_action_offset' => '',
260 'start_action_unit' => '',
261 'subject' => '1-Day (repeating) (about {activity.activity_type})',
262 ];
263 $this->fixtures['sched_event_name_1day_on_abs_date'] = [
264 'name' => 'sched_event_name_1day_on_abs_date',
265 'title' => 'sched_event_name_1day_on_abs_date',
266 'limit_to' => 1,
267 'absolute_date' => CRM_Utils_Date::processDate('20120614100000'),
268 'body_html' => '<p>sched_event_name_1day_on_abs_date</p>',
269 'body_text' => 'sched_event_name_1day_on_abs_date',
270 'entity_status' => '1',
271 'entity_value' => '2',
272 'group_id' => NULL,
273 'is_active' => '1',
274 'is_repeat' => '0',
275 'mapping_id' => '3',
276 'msg_template_id' => NULL,
277 'recipient' => '2',
278 'recipient_listing' => NULL,
279 'recipient_manual' => NULL,
280 'record_activity' => NULL,
281 'repetition_frequency_interval' => NULL,
282 'repetition_frequency_unit' => NULL,
283 'end_action' => NULL,
284 'end_date' => NULL,
285 'end_frequency_interval' => NULL,
286 'end_frequency_unit' => NULL,
287 'start_action_condition' => NULL,
288 'start_action_date' => NULL,
289 'start_action_offset' => NULL,
290 'start_action_unit' => NULL,
291 'subject' => 'sched_event_name_1day_on_abs_date',
292 ];
293 $this->fixtures['sched_membership_join_2week'] = [
294 'name' => 'sched_membership_join_2week',
295 'title' => 'sched_membership_join_2week',
296 'absolute_date' => '',
297 'body_html' => '<p>body sched_membership_join_2week</p>',
298 'body_text' => 'body sched_membership_join_2week',
299 'end_action' => '',
300 'end_date' => '',
301 'end_frequency_interval' => '',
302 'end_frequency_unit' => '',
303 'entity_status' => '',
304 'entity_value' => '',
305 'group_id' => '',
306 'is_active' => 1,
307 'is_repeat' => '0',
308 'mapping_id' => 4,
309 'msg_template_id' => '',
310 'recipient' => '',
311 'recipient_listing' => '',
312 'recipient_manual' => '',
313 'record_activity' => 1,
314 'repetition_frequency_interval' => '',
315 'repetition_frequency_unit' => '',
316 'start_action_condition' => 'after',
317 'start_action_date' => 'membership_join_date',
318 'start_action_offset' => '2',
319 'start_action_unit' => 'week',
320 'subject' => 'subject sched_membership_join_2week (joined {membership.join_date})',
321 ];
322 $this->fixtures['sched_membership_start_1week'] = [
323 'name' => 'sched_membership_start_1week',
324 'title' => 'sched_membership_start_1week',
325 'absolute_date' => '',
326 'body_html' => '<p>body sched_membership_start_1week</p>',
327 'body_text' => 'body sched_membership_start_1week',
328 'end_action' => '',
329 'end_date' => '',
330 'end_frequency_interval' => '',
331 'end_frequency_unit' => '',
332 'entity_status' => '',
333 'entity_value' => '',
334 'group_id' => '',
335 'is_active' => 1,
336 'is_repeat' => '0',
337 'mapping_id' => 4,
338 'msg_template_id' => '',
339 'recipient' => '',
340 'recipient_listing' => '',
341 'recipient_manual' => '',
342 'record_activity' => 1,
343 'repetition_frequency_interval' => '',
344 'repetition_frequency_unit' => '',
345 'start_action_condition' => 'after',
346 'start_action_date' => 'membership_start_date',
347 'start_action_offset' => '1',
348 'start_action_unit' => 'week',
349 'subject' => 'subject sched_membership_start_1week (joined {membership.start_date})',
350 ];
351 $this->fixtures['sched_membership_end_2week'] = [
352 'name' => 'sched_membership_end_2week',
353 'title' => 'sched_membership_end_2week',
354 'absolute_date' => '',
355 'body_html' => '<p>body sched_membership_end_2week</p>',
356 'body_text' => 'body sched_membership_end_2week',
357 'end_action' => '',
358 'end_date' => '',
359 'end_frequency_interval' => '',
360 'end_frequency_unit' => '',
361 'entity_status' => '',
362 'entity_value' => '',
363 'group_id' => '',
364 'is_active' => 1,
365 'is_repeat' => '0',
366 'mapping_id' => 4,
367 'msg_template_id' => '',
368 'recipient' => '',
369 'recipient_listing' => '',
370 'recipient_manual' => '',
371 'record_activity' => 1,
372 'repetition_frequency_interval' => '',
373 'repetition_frequency_unit' => '',
374 'start_action_condition' => 'before',
375 'start_action_date' => 'membership_end_date',
376 'start_action_offset' => '2',
377 'start_action_unit' => 'week',
378 'subject' => 'subject sched_membership_end_2week',
379 'effective_start_date' => '2012-05-01 01:00:00',
380 ];
381 $this->fixtures['sched_on_membership_end_date'] = [
382 'name' => 'sched_on_membership_end_date',
383 'title' => 'sched_on_membership_end_date',
384 'body_html' => '<p>Your membership expired today</p>',
385 'body_text' => 'Your membership expired today',
386 'is_active' => 1,
387 'mapping_id' => 4,
388 'record_activity' => 1,
389 'start_action_condition' => 'after',
390 'start_action_date' => 'membership_end_date',
391 'start_action_offset' => '0',
392 'start_action_unit' => 'hour',
393 'subject' => 'subject send reminder on membership_end_date',
394 ];
395 $this->fixtures['sched_after_1day_membership_end_date'] = [
396 'name' => 'sched_after_1day_membership_end_date',
397 'title' => 'sched_after_1day_membership_end_date',
398 'body_html' => '<p>Your membership expired yesterday</p>',
399 'body_text' => 'Your membership expired yesterday',
400 'is_active' => 1,
401 'mapping_id' => 4,
402 'record_activity' => 1,
403 'start_action_condition' => 'after',
404 'start_action_date' => 'membership_end_date',
405 'start_action_offset' => '1',
406 'start_action_unit' => 'day',
407 'subject' => 'subject send reminder on membership_end_date',
408 ];
409
410 $this->fixtures['sched_membership_end_2month'] = [
411 'name' => 'sched_membership_end_2month',
412 'title' => 'sched_membership_end_2month',
413 'absolute_date' => '',
414 'body_html' => '<p>body sched_membership_end_2month</p>',
415 'body_text' => 'body sched_membership_end_2month',
416 'end_action' => '',
417 'end_date' => '',
418 'end_frequency_interval' => '',
419 'end_frequency_unit' => '',
420 'entity_status' => '',
421 'entity_value' => '',
422 'group_id' => '',
423 'is_active' => 1,
424 'is_repeat' => '0',
425 'mapping_id' => 4,
426 'msg_template_id' => '',
427 'recipient' => '',
428 'recipient_listing' => '',
429 'recipient_manual' => '',
430 'record_activity' => 1,
431 'repetition_frequency_interval' => '',
432 'repetition_frequency_unit' => '',
433 'start_action_condition' => 'after',
434 'start_action_date' => 'membership_end_date',
435 'start_action_offset' => '2',
436 'start_action_unit' => 'month',
437 'subject' => 'subject sched_membership_end_2month',
438 ];
439
440 $this->fixtures['sched_membership_absolute_date'] = [
441 'name' => 'sched_membership_absolute_date',
442 'title' => 'sched_membership_absolute_date',
443 'absolute_date' => CRM_Utils_Date::processDate('20120614100000'),
444 'body_html' => '<p>body sched_membership_absolute_date</p>',
445 'body_text' => 'body sched_membership_absolute_date',
446 'end_action' => '',
447 'end_date' => '',
448 'end_frequency_interval' => '',
449 'end_frequency_unit' => '',
450 'entity_status' => '',
451 'entity_value' => '',
452 'group_id' => '',
453 'is_active' => 1,
454 'is_repeat' => '0',
455 'mapping_id' => 4,
456 'msg_template_id' => '',
457 'recipient' => '',
458 'recipient_listing' => '',
459 'recipient_manual' => '',
460 'record_activity' => 1,
461 'repetition_frequency_interval' => '',
462 'repetition_frequency_unit' => '',
463 'start_action_condition' => '',
464 'start_action_date' => '',
465 'start_action_offset' => '',
466 'start_action_unit' => '',
467 'subject' => 'subject sched_membership_absolute_date',
468 ];
469
470 $this->fixtures['sched_contact_birth_day_yesterday'] = [
471 'name' => 'sched_contact_birth_day_yesterday',
472 'title' => 'sched_contact_birth_day_yesterday',
473 'absolute_date' => '',
474 'body_html' => '<p>you look like you were born yesterday!</p>',
475 'body_text' => 'you look like you were born yesterday!',
476 'end_action' => '',
477 'end_date' => '',
478 'end_frequency_interval' => '',
479 'end_frequency_unit' => '',
480 'entity_status' => 1,
481 'entity_value' => 'birth_date',
482 'group_id' => '',
483 'is_active' => 1,
484 'is_repeat' => '0',
485 'mapping_id' => 6,
486 'msg_template_id' => '',
487 'recipient' => '',
488 'recipient_listing' => '',
489 'recipient_manual' => '',
490 'record_activity' => 1,
491 'repetition_frequency_interval' => '',
492 'repetition_frequency_unit' => '',
493 'start_action_condition' => 'after',
494 'start_action_date' => 'date_field',
495 'start_action_offset' => '1',
496 'start_action_unit' => 'day',
497 'subject' => 'subject sched_contact_birth_day_yesterday',
498 ];
499
500 $this->fixtures['sched_contact_birth_day_anniversary'] = [
501 'name' => 'sched_contact_birth_day_anniversary',
502 'title' => 'sched_contact_birth_day_anniversary',
503 'absolute_date' => '',
504 'body_html' => '<p>happy birthday!</p>',
505 'body_text' => 'happy birthday!',
506 'end_action' => '',
507 'end_date' => '',
508 'end_frequency_interval' => '',
509 'end_frequency_unit' => '',
510 'entity_status' => 2,
511 'entity_value' => 'birth_date',
512 'group_id' => '',
513 'is_active' => 1,
514 'is_repeat' => '0',
515 'mapping_id' => 6,
516 'msg_template_id' => '',
517 'recipient' => '',
518 'recipient_listing' => '',
519 'recipient_manual' => '',
520 'record_activity' => 1,
521 'repetition_frequency_interval' => '',
522 'repetition_frequency_unit' => '',
523 'start_action_condition' => 'before',
524 'start_action_date' => 'date_field',
525 'start_action_offset' => '1',
526 'start_action_unit' => 'day',
527 'subject' => 'subject sched_contact_birth_day_anniversary',
528 ];
529
530 $this->fixtures['sched_contact_grad_tomorrow'] = [
531 'name' => 'sched_contact_grad_tomorrow',
532 'title' => 'sched_contact_grad_tomorrow',
533 'absolute_date' => '',
534 'body_html' => '<p>congratulations on your graduation!</p>',
535 'body_text' => 'congratulations on your graduation!',
536 'end_action' => '',
537 'end_date' => '',
538 'end_frequency_interval' => '',
539 'end_frequency_unit' => '',
540 'entity_status' => 1,
541 'group_id' => '',
542 'is_active' => 1,
543 'is_repeat' => '0',
544 'mapping_id' => 6,
545 'msg_template_id' => '',
546 'recipient' => '',
547 'recipient_listing' => '',
548 'recipient_manual' => '',
549 'record_activity' => 1,
550 'repetition_frequency_interval' => '',
551 'repetition_frequency_unit' => '',
552 'start_action_condition' => 'before',
553 'start_action_date' => 'date_field',
554 'start_action_offset' => '1',
555 'start_action_unit' => 'day',
556 'subject' => 'subject sched_contact_grad_tomorrow',
557 'effective_start_date' => '2013-10-15 20:00:00',
558 ];
559
560 $this->fixtures['sched_contact_grad_anniversary'] = [
561 'name' => 'sched_contact_grad_anniversary',
562 'title' => 'sched_contact_grad_anniversary',
563 'absolute_date' => '',
564 'body_html' => '<p>dear alum, please send us money.</p>',
565 'body_text' => 'dear alum, please send us money.',
566 'end_action' => '',
567 'end_date' => '',
568 'end_frequency_interval' => '',
569 'end_frequency_unit' => '',
570 'entity_status' => 2,
571 'group_id' => '',
572 'is_active' => 1,
573 'is_repeat' => '0',
574 'mapping_id' => 6,
575 'msg_template_id' => '',
576 'recipient' => '',
577 'recipient_listing' => '',
578 'recipient_manual' => '',
579 'record_activity' => 1,
580 'repetition_frequency_interval' => '',
581 'repetition_frequency_unit' => '',
582 'start_action_condition' => 'after',
583 'start_action_date' => 'date_field',
584 'start_action_offset' => '1',
585 'start_action_unit' => 'week',
586 'subject' => 'subject sched_contact_grad_anniversary',
587 ];
588
589 $this->fixtures['sched_contact_created_yesterday'] = [
590 'name' => 'sched_contact_created_yesterday',
591 'title' => 'sched_contact_created_yesterday',
592 'absolute_date' => '',
593 'body_html' => '<p>Your contact was created yesterday</p>',
594 'body_text' => 'Your contact was created yesterday!',
595 'end_action' => '',
596 'end_date' => '',
597 'end_frequency_interval' => '',
598 'end_frequency_unit' => '',
599 'entity_status' => 1,
600 'entity_value' => 'created_date',
601 'group_id' => '',
602 'is_active' => 1,
603 'is_repeat' => '0',
604 'mapping_id' => 6,
605 'msg_template_id' => '',
606 'recipient' => '',
607 'recipient_listing' => '',
608 'recipient_manual' => '',
609 'record_activity' => 1,
610 'repetition_frequency_interval' => '',
611 'repetition_frequency_unit' => '',
612 'start_action_condition' => 'after',
613 'start_action_date' => 'date_field',
614 'start_action_offset' => '1',
615 'start_action_unit' => 'day',
616 'subject' => 'subject sched_contact_created_yesterday',
617 ];
618
619 $this->fixtures['sched_contact_mod_anniversary'] = [
620 'name' => 'sched_contact_mod_anniversary',
621 'title' => 'sched_contact_mod_anniversary',
622 'absolute_date' => '',
623 'body_html' => '<p>You last updated your data last year</p>',
624 'body_text' => 'Go update your stuff!',
625 'end_action' => '',
626 'end_date' => '',
627 'end_frequency_interval' => '',
628 'end_frequency_unit' => '',
629 'entity_status' => 2,
630 'entity_value' => 'modified_date',
631 'group_id' => '',
632 'is_active' => 1,
633 'is_repeat' => '0',
634 'mapping_id' => 6,
635 'msg_template_id' => '',
636 'recipient' => '',
637 'recipient_listing' => '',
638 'recipient_manual' => '',
639 'record_activity' => 1,
640 'repetition_frequency_interval' => '',
641 'repetition_frequency_unit' => '',
642 'start_action_condition' => 'before',
643 'start_action_date' => 'date_field',
644 'start_action_offset' => '1',
645 'start_action_unit' => 'day',
646 'subject' => 'subject sched_contact_mod_anniversary',
647 ];
648
649 $this->fixtures['sched_event_type_start_1week_before'] = [
650 'name' => 'sched_event_type_start_1week_before',
651 'title' => 'sched_event_type_start_1week_before',
652 'absolute_date' => '',
653 'body_html' => '<p>body sched_event_type_start_1week_before ({event.title})</p>',
654 'body_text' => 'body sched_event_type_start_1week_before ({event.title})',
655 'end_action' => '',
656 'end_date' => '',
657 'end_frequency_interval' => '',
658 'end_frequency_unit' => '',
659 // participant status id
660 'entity_status' => '',
661 // event type id
662 'entity_value' => '',
663 'group_id' => '',
664 'is_active' => 1,
665 'is_repeat' => '0',
666 // event type
667 'mapping_id' => 2,
668 'msg_template_id' => '',
669 'recipient' => '',
670 'recipient_listing' => '',
671 'recipient_manual' => '',
672 'record_activity' => 1,
673 'repetition_frequency_interval' => '',
674 'repetition_frequency_unit' => '',
675 'start_action_condition' => 'before',
676 'start_action_date' => 'event_start_date',
677 'start_action_offset' => '1',
678 'start_action_unit' => 'week',
679 'subject' => 'subject sched_event_type_start_1week_before ({event.title})',
680 ];
681 $this->fixtures['sched_event_type_end_2month_repeat_twice_2_weeks'] = [
682 'name' => 'sched_event_type_end_2month_repeat_twice_2_weeks',
683 'title' => 'sched_event_type_end_2month_repeat_twice_2_weeks',
684 'absolute_date' => '',
685 'body_html' => '<p>body sched_event_type_end_2month_repeat_twice_2_weeks {event.title}</p>',
686 'body_text' => 'body sched_event_type_end_2month_repeat_twice_2_weeks {event.title}',
687 'end_action' => 'after',
688 'end_date' => 'event_end_date',
689 'end_frequency_interval' => '3',
690 'end_frequency_unit' => 'month',
691 // participant status id
692 'entity_status' => '',
693 // event type id
694 'entity_value' => '',
695 'group_id' => '',
696 'is_active' => 1,
697 'is_repeat' => '1',
698 // event type
699 'mapping_id' => 2,
700 'msg_template_id' => '',
701 'recipient' => '',
702 'recipient_listing' => '',
703 'recipient_manual' => '',
704 'record_activity' => 1,
705 'repetition_frequency_interval' => '2',
706 'repetition_frequency_unit' => 'week',
707 'start_action_condition' => 'after',
708 'start_action_date' => 'event_end_date',
709 'start_action_offset' => '2',
710 'start_action_unit' => 'month',
711 'subject' => 'subject sched_event_type_end_2month_repeat_twice_2_weeks {event.title}',
712 ];
713
714 $this->fixtures['sched_membership_end_2month_repeat_twice_4_weeks'] = [
715 'name' => 'sched_membership_end_2month',
716 'title' => 'sched_membership_end_2month',
717 'absolute_date' => '',
718 'body_html' => '<p>body sched_membership_end_2month</p>',
719 'body_text' => 'body sched_membership_end_2month',
720 'end_action' => '',
721 'end_date' => 'membership_end_date',
722 'end_frequency_interval' => '4',
723 'end_frequency_unit' => 'month',
724 'entity_status' => '',
725 'entity_value' => '',
726 'group_id' => '',
727 'is_active' => 1,
728 'is_repeat' => '1',
729 'mapping_id' => 4,
730 'msg_template_id' => '',
731 'recipient' => '',
732 'recipient_listing' => '',
733 'recipient_manual' => '',
734 'record_activity' => 1,
735 'repetition_frequency_interval' => '4',
736 'repetition_frequency_unit' => 'week',
737 'start_action_condition' => 'after',
738 'start_action_date' => 'membership_end_date',
739 'start_action_offset' => '2',
740 'start_action_unit' => 'month',
741 'subject' => 'subject sched_membership_end_2month',
742 ];
743 $this->fixtures['sched_membership_end_limit_to_none'] = [
744 'name' => 'limit to none',
745 'title' => 'limit to none',
746 'absolute_date' => '',
747 'body_html' => '<p>body sched_membership_end_2month</p>',
748 'body_text' => 'body sched_membership_end_2month',
749 'end_action' => '',
750 'end_date' => '',
751 'end_frequency_interval' => '4',
752 'end_frequency_unit' => 'month',
753 'entity_status' => '',
754 'entity_value' => '',
755 'limit_to' => 0,
756 'group_id' => '',
757 'is_active' => 1,
758 'is_repeat' => '1',
759 'mapping_id' => 4,
760 'msg_template_id' => '',
761 'recipient' => '',
762 'recipient_listing' => '',
763 'recipient_manual' => '',
764 'record_activity' => 1,
765 'repetition_frequency_interval' => '4',
766 'repetition_frequency_unit' => 'week',
767 'start_action_condition' => 'after',
768 'start_action_date' => 'membership_end_date',
769 'start_action_offset' => '2',
770 'start_action_unit' => 'month',
771 'subject' => 'limit to none',
772 ];
773 $this->fixtures['sched_on_membership_end_date_repeat_interval'] = [
774 'name' => 'sched_on_membership_end_date',
775 'title' => 'sched_on_membership_end_date',
776 'body_html' => '<p>Your membership expired 1 unit ago</p>',
777 'body_text' => 'Your membership expired 1 unit ago',
778 'end_frequency_interval' => 10,
779 'end_frequency_unit' => 'year',
780 'is_active' => 1,
781 'is_repeat' => TRUE,
782 'mapping_id' => 4,
783 'record_activity' => 1,
784 'start_action_condition' => 'after',
785 'start_action_date' => 'membership_end_date',
786 'start_action_offset' => '0',
787 'start_action_unit' => 'hour',
788 'subject' => 'subject send reminder every unit after membership_end_date',
789 ];
790
791 $customGroup = $this->callAPISuccess('CustomGroup', 'create', [
792 'title' => ts('Test Contact Custom group'),
793 'name' => 'test_contact_cg',
794 'extends' => 'Contact',
795 'domain_id' => CRM_Core_Config::domainID(),
796 'is_active' => 1,
797 'collapse_adv_display' => 0,
798 'collapse_display' => 0,
799 ]);
800 $customField = $this->callAPISuccess('CustomField', 'create', [
801 'label' => 'Test Text',
802 'data_type' => 'String',
803 'html_type' => 'Text',
804 'custom_group_id' => $customGroup['id'],
805 ]);
806 $customDateField = $this->callAPISuccess('CustomField', 'create', [
807 'label' => 'Test Date Field',
808 'data_type' => 'Date',
809 'html_type' => 'Select Date',
810 'date_format' => 'mm/dd/yy',
811 'custom_group_id' => $customGroup['id'],
812 ]);
813
814 $this->fixtures['contact_custom_token'] = [
815 'id' => $customField['id'],
816 'token' => sprintf('{contact.custom_%s}', $customField['id']),
817 'name' => sprintf('custom_%s', $customField['id']),
818 'value' => 'text ' . substr(sha1(mt_rand()), 0, 7),
819 ];
820
821 $this->fixtures['sched_on_custom_date'] = [
822 'name' => 'sched_on_custom_date',
823 'title' => 'sched_on_custom_date',
824 'body_html' => '<p>Send reminder before 1 hour of custom date field</p>',
825 'body_text' => 'Send reminder on custom date field',
826 'subject' => 'Send reminder on custom date field',
827 'mapping_id' => 6,
828 'entity_value' => 'custom_' . $customDateField['id'],
829 'entity_status' => 2,
830 'entity' => [
831 6,
832 ['custom_' . $customDateField['id']],
833 [1],
834 ],
835 'start_action_offset' => 1,
836 'start_action_unit' => 'hour',
837 'start_action_condition' => 'before',
838 'start_action_date' => 'date_field',
839 'record_activity' => 1,
840 'repetition_frequency_unit' => 'hour',
841 'end_frequency_unit' => 'hour',
842 'end_action' => 'before',
843 'end_date' => 'date_field',
844 'custom_field_name' => 'custom_' . $customDateField['id'],
845 ];
846 }
847
848 /**
849 * Tears down the fixture, for example, closes a network connection.
850 *
851 * This method is called after a test is executed.
852 *
853 * @throws \CRM_Core_Exception
854 * @throws \CiviCRM_API3_Exception
855 * @throws \API_Exception
856 */
857 public function tearDown(): void {
858 $this->deleteTestObjects();
859 MembershipType::delete()->addWhere('name', 'NOT IN', ['General', 'Student', 'Lifetime'])->execute();
860 $this->quickCleanup([
861 'civicrm_action_schedule',
862 'civicrm_action_log',
863 'civicrm_membership',
864 'civicrm_line_item',
865 'civicrm_participant',
866 'civicrm_event',
867 'civicrm_email',
868 ], TRUE);
869 $this->quickCleanUpFinancialEntities();
870 parent::tearDown();
871 }
872
873 /**
874 * Get a usable membership type id - creating one if none exists.
875 *
876 * It should exist but this class over-deletes in not-fully-diagnosed places.
877 *
878 * @throws \API_Exception
879 */
880 protected function getMembershipTypeID(): int {
881 $generalTypeID = CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'membership_type_id', 'General');
882 if ($generalTypeID) {
883 $this->membershipTypeID = $generalTypeID;
884 }
885 else {
886 $this->membershipTypeID = (int) MembershipType::create()
887 ->setValues([
888 'name' => 'General',
889 'period_type' => 'rolling',
890 'member_of_contact_id' => 1,
891 'financial_type_id:name' => 'Member Dues',
892 'duration_unit' => 'month',
893 ]
894 )->execute()->first()['id'];
895 }
896 return $this->membershipTypeID;
897 }
898
899 /**
900 * Get mailer examples.
901 *
902 * @return array
903 */
904 public function mailerExamples(): array {
905 $cases = [];
906
907 // Some tokens - short as subject has 128char limit in DB.
908 $someTokensTmpl = implode(';;', [
909 // basic contact token
910 '{contact.display_name}',
911 // funny legacy contact token
912 '{contact.gender}',
913 // domain token
914 '{domain.name}',
915 // action-scheduler token
916 '{activity.activity_type}',
917 ]);
918 // Further tokens can be tested in the body text/html.
919 // We use a dummy string to represent the custom token as this is done in setUp which is run after this function is called.
920 $manyTokensTmpl = implode(';;', [
921 $someTokensTmpl,
922 '{contact.email_greeting}',
923 '{contactCustomToken}',
924 ]);
925 // Note: The behavior of domain-tokens on a scheduled reminder is undefined. All we
926 // can really do is check that it has something.
927 $someTokensExpected = 'Churmondleia Ōtākou;;Female;;[a-zA-Z0-9 ]+;;Phone Call';
928 $manyTokensExpected = sprintf('%s;;Dear Churmondleia;;%s', $someTokensExpected, '{contactCustomTokenValue}');
929
930 // In this example, we use a lot of tokens cutting across multiple components.
931 $cases[0] = [
932 // Schedule definition.
933 [
934 'subject' => "subj $someTokensTmpl",
935 'body_html' => "html $manyTokensTmpl",
936 'body_text' => "text $manyTokensTmpl",
937 ],
938 // Assertions (regex).
939 [
940 'from_name' => '/^FIXME$/',
941 'from_email' => '/^info@EXAMPLE.ORG$/',
942 'subject' => "/^subj $someTokensExpected\$/",
943 'body_html' => "/^html $manyTokensExpected\$/",
944 'body_text' => "/^text $manyTokensExpected\$/",
945 ],
946 ];
947
948 // In this example, we customize the from address.
949 $cases[1] = [
950 // Schedule definition.
951 [
952 'from_name' => 'Bob',
953 'from_email' => 'bob@example.org',
954 ],
955 // Assertions (regex).
956 [
957 'from_name' => '/^Bob$/',
958 'from_email' => '/^bob@example.org$/',
959 ],
960 ];
961
962 // In this example, we auto-convert HTML to text
963 $cases[2] = [
964 // Schedule definition.
965 [
966 'body_html' => '<p>Hello &amp; stuff.</p>',
967 'body_text' => '',
968 ],
969 // Assertions (regex).
970 [
971 'body_html' => '/^' . preg_quote('<p>Hello &amp; stuff.</p>', '/') . '/',
972 'body_text' => '/^' . preg_quote('Hello & stuff.', '/') . '/',
973 ],
974 ];
975
976 // In this example, we autoconvert HTML to text
977 $cases[3] = [
978 // Schedule definition.
979 [
980 'body_html' => '',
981 'body_text' => 'Hello world',
982 ],
983 // Assertions (regex).
984 [
985 'body_html' => '/^--UNDEFINED--$/',
986 'body_text' => '/^Hello world$/',
987 ],
988 ];
989
990 // In this example, we test activity tokens
991 $activityTokens = '{activity.subject};;{activity.details};;{activity.activity_date_time}';
992 $activity = [
993 'status_id' => 1,
994 'activity_type_id' => 2,
995 'activity_date_time' => '20120615100000',
996 'is_current_revision' => 1,
997 'is_deleted' => 0,
998 'subject' => 'Phone call',
999 'details' => 'A phone call about a bear',
1000 ];
1001 $activityTokensExpected = 'Phone call;;A phone call about a bear;;June 15th, 2012 10:00 AM';
1002 $cases[4] = [
1003 // Schedule definition.
1004 [
1005 'subject' => "subj $someTokensTmpl",
1006 'body_html' => "html {$activityTokens}",
1007 'body_text' => "text {$activityTokens}",
1008 ],
1009 // Assertions (regex).
1010 [
1011 'from_name' => '/^FIXME$/',
1012 'from_email' => '/^info@EXAMPLE.ORG$/',
1013 'subject' => "/^subj $someTokensExpected\$/",
1014 'body_html' => "/^html $activityTokensExpected\$/",
1015 'body_text' => "/^text $activityTokensExpected\$/",
1016 ],
1017 ];
1018
1019 return $cases;
1020 }
1021
1022 /**
1023 * This generates a single mailing through the scheduled-reminder
1024 * system (using an activity-reminder as a baseline) and
1025 * checks that the resulting message satisfies various
1026 * regular expressions.
1027 *
1028 * @param array $schedule
1029 * Values to set/override in the schedule.
1030 * Ex: array('subject' => 'Hello, {contact.first_name}!').
1031 * @param array $patterns
1032 * A list of regexes to compare with the actual email.
1033 * Ex: array('subject' => '/^Hello, Alice!/').
1034 * Keys: subject, body_text, body_html, from_name, from_email.
1035 *
1036 * @throws \API_Exception
1037 * @throws \CRM_Core_Exception
1038 * @throws \Civi\API\Exception\UnauthorizedException
1039 * @dataProvider mailerExamples
1040 */
1041 public function testMailer(array $schedule, array $patterns): void {
1042 // Replace the dummy custom contact token referecnes in schedule and patterns that we had to insert because phpunit
1043 // evaluates dataProviders before running setUp
1044 foreach ($schedule as $type => $content) {
1045 $schedule[$type] = str_replace('{contactCustomToken}', $this->fixtures['contact_custom_token']['token'], $content);
1046 }
1047 foreach ($patterns as $type => $content) {
1048 $patterns[$type] = str_replace('{contactCustomTokenValue}', $this->fixtures['contact_custom_token']['value'], $content);
1049 }
1050 $this->createScheduleFromFixtures('sched_activity_1day', $schedule);
1051 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phone_call']);
1052 $contact = $this->callAPISuccess('contact', 'create', array_merge(
1053 $this->fixtures['contact'],
1054 [
1055 $this->fixtures['contact_custom_token']['name'] => $this->fixtures['contact_custom_token']['value'],
1056 ]
1057 ));
1058 $activity->save();
1059
1060 ActivityContact::create(FALSE)->setValues([
1061 'contact_id' => $contact['id'],
1062 'activity_id' => $activity->id,
1063 'record_type_id:name' => 'Activity Source',
1064 ])->execute();
1065
1066 CRM_Utils_Time::setTime('2012-06-14 15:00:00');
1067 $this->callAPISuccess('job', 'send_reminder');
1068 $this->mut->assertRecipients([['test-member@example.com']]);
1069 foreach ($this->mut->getAllMessages('ezc') as $message) {
1070 /** @var ezcMail $message */
1071
1072 $messageArray = [];
1073 $messageArray['subject'] = $message->subject;
1074 $messageArray['from_name'] = $message->from->name;
1075 $messageArray['from_email'] = $message->from->email;
1076 $messageArray['body_text'] = '--UNDEFINED--';
1077 $messageArray['body_html'] = '--UNDEFINED--';
1078
1079 foreach ($message->fetchParts() as $part) {
1080 /** @var ezcMailText ezcMailText */
1081 if ($part instanceof ezcMailText && $part->subType === 'html') {
1082 $messageArray['body_html'] = $part->text;
1083 }
1084 if ($part instanceof ezcMailText && $part->subType === 'plain') {
1085 $messageArray['body_text'] = $part->text;
1086 }
1087 }
1088
1089 foreach ($patterns as $field => $pattern) {
1090 $this->assertRegExp($pattern, $messageArray[$field],
1091 "Check that '$field'' matches regex. " . print_r(['expected' => $patterns, 'actual' => $messageArray], 1));
1092 }
1093 }
1094 $this->mut->clearMessages();
1095 }
1096
1097 /**
1098 * Send reminder 1 hour before custom date field
1099 *
1100 * @throws \CRM_Core_Exception
1101 */
1102 public function testReminderWithCustomDateField(): void {
1103 $this->createScheduleFromFixtures('sched_on_custom_date');
1104 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], [$this->fixtures['sched_on_custom_date']['custom_field_name'] => '04/06/2021']));
1105 $this->assertCronRuns([
1106 [
1107 // Before the 24-hour mark, no email
1108 'time' => '2021-04-02 04:00:00',
1109 'recipients' => [],
1110 'subjects' => [],
1111 ],
1112 [
1113 // After the 24-hour mark, an email
1114 'time' => '2021-04-05 23:00:00',
1115 'recipients' => [['test-member@example.com']],
1116 'subjects' => ['Send reminder on custom date field'],
1117 ],
1118 [
1119 // Run cron again; message already sent
1120 'time' => '',
1121 'recipients' => [],
1122 ],
1123 ]);
1124 }
1125
1126 /**
1127 * Test calculated activity schedule.
1128 *
1129 * @throws \API_Exception
1130 * @throws \CRM_Core_Exception
1131 */
1132 public function testActivityDateTimeMatchNonRepeatableSchedule(): void {
1133 $this->createScheduleFromFixtures('sched_activity_1day');
1134
1135 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phone_call']);
1136 $contact = $this->callAPISuccess('contact', 'create', $this->fixtures['contact']);
1137 $activity->subject = 'Test subject for phone_call';
1138 $activity->save();
1139
1140 $source['contact_id'] = $contact['id'];
1141 $source['activity_id'] = $activity->id;
1142 $source['record_type_id'] = 2;
1143 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
1144 $activityContact->save();
1145
1146 $this->assertCronRuns([
1147 [
1148 // Before the 24-hour mark, no email
1149 'time' => '2012-06-14 04:00:00',
1150 'recipients' => [],
1151 'subjects' => [],
1152 ],
1153 [
1154 // After the 24-hour mark, an email
1155 'time' => '2012-06-14 15:00:00',
1156 'recipients' => [['test-member@example.com']],
1157 'subjects' => ['1-Day (non-repeating) (about Phone Call)'],
1158 ],
1159 [
1160 // Run cron again; message already sent
1161 'time' => '',
1162 'recipients' => [],
1163 ],
1164 ]);
1165 $activities = Activity::get(FALSE)
1166 ->setSelect(['details'])
1167 ->addWhere('activity_type_id:name', '=', 'Reminder Sent')
1168 ->addWhere('source_record_id', '=', $activity->id)
1169 ->execute();
1170 foreach ($activities as $activityDetails) {
1171 $this->assertStringContainsString($activity->subject, $activityDetails['details']);
1172 }
1173 }
1174
1175 /**
1176 * Test "minute" as a valid unit.
1177 *
1178 * @throws \API_Exception
1179 * @throws \CRM_Core_Exception
1180 */
1181 public function testActivityDateTimeMinuteUnit(): void {
1182 $this->createScheduleFromFixtures('sched_activity_5minute');
1183
1184 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phone_call']);
1185 $contact = $this->callAPISuccess('contact', 'create', $this->fixtures['contact']);
1186 $activity->subject = 'Test subject for phone_call';
1187 $activity->save();
1188
1189 $source['contact_id'] = $contact['id'];
1190 $source['activity_id'] = $activity->id;
1191 $source['record_type_id'] = 2;
1192 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
1193 $activityContact->save();
1194
1195 $this->assertCronRuns([
1196 [
1197 // 15 minutes before phone call time, no email
1198 'time' => '2012-06-15 9:45:00',
1199 'recipients' => [],
1200 'subjects' => [],
1201 ],
1202 [
1203 // 3 minutes before, an email
1204 'time' => '2012-06-15 9:57:00',
1205 'recipients' => [['test-member@example.com']],
1206 ],
1207 [
1208 // Run cron again; message already sent
1209 'time' => '',
1210 'recipients' => [],
1211 ],
1212 ]);
1213 }
1214
1215 /**
1216 * Test schedule creation on repeatable schedule.
1217 *
1218 * @throws \CRM_Core_Exception
1219 */
1220 public function testActivityDateTimeMatchRepeatableSchedule(): void {
1221 $this->createScheduleFromFixtures('sched_activity_1day_r');
1222 $this->createActivityAndContactFromFixtures();
1223
1224 $this->assertCronRuns([
1225 [
1226 // Before the 24-hour mark, no email
1227 'time' => '2012-06-14 04:00:00',
1228 'recipients' => [],
1229 'subjects' => [],
1230 ],
1231 [
1232 // After the 24-hour mark, an email
1233 'time' => '2012-06-14 15:00:00',
1234 'recipients' => [['test-member@example.com']],
1235 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1236 ],
1237 [
1238 // Run cron 4 hours later; first message already sent
1239 'time' => '2012-06-14 20:00:00',
1240 'recipients' => [],
1241 'subjects' => [],
1242 ],
1243 [
1244 // Run cron 6 hours later; send second message.
1245 'time' => '2012-06-14 21:00:01',
1246 'recipients' => [['test-member@example.com']],
1247 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1248 ],
1249 ]);
1250 }
1251
1252 /**
1253 * @throws \CRM_Core_Exception
1254 */
1255 public function testActivityDateTimeMatchRepeatableScheduleOnAbsDate(): void {
1256 $this->createScheduleFromFixtures('sched_activity_1day_r_on_abs_date');
1257 $this->createActivityAndContactFromFixtures();
1258
1259 $this->assertCronRuns([
1260 [
1261 // Before the 24-hour mark, no email
1262 'time' => '2012-06-13 04:00:00',
1263 'recipients' => [],
1264 'subjects' => [],
1265 ],
1266 [
1267 // On absolute date set on 2012-06-14
1268 'time' => '2012-06-14 00:00:00',
1269 'recipients' => [['test-member@example.com']],
1270 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1271 ],
1272 [
1273 // Run cron 4 hours later; first message already sent
1274 'time' => '2012-06-14 04:00:00',
1275 'recipients' => [],
1276 'subjects' => [],
1277 ],
1278 [
1279 // Run cron 6 hours later; send second message.
1280 'time' => '2012-06-14 06:00:01',
1281 'recipients' => [['test-member@example.com']],
1282 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1283 ],
1284 ]);
1285 }
1286
1287 /**
1288 * Test event with only an absolute date.
1289 *
1290 * @throws \CRM_Core_Exception
1291 */
1292 public function testEventNameWithAbsoluteDateAndNothingElse(): void {
1293 $participant = $this->createTestObject('CRM_Event_DAO_Participant', array_merge($this->fixtures['participant'], ['status_id' => 1]));
1294 $this->callAPISuccess('Email', 'create', [
1295 'contact_id' => $participant->contact_id,
1296 'email' => 'test-event@example.com',
1297 ]);
1298 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $participant->contact_id]));
1299
1300 $actionSchedule = $this->fixtures['sched_event_name_1day_on_abs_date'];
1301 $actionSchedule['entity_value'] = $participant->event_id;
1302 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
1303
1304 $this->assertCronRuns([
1305 [
1306 // Before the 24-hour mark, no email
1307 'time' => '2012-06-13 04:00:00',
1308 'recipients' => [],
1309 'subjects' => [],
1310 ],
1311 [
1312 // On absolute date set on 2012-06-14
1313 'time' => '2012-06-14 00:00:00',
1314 'recipients' => [['test-event@example.com']],
1315 'subjects' => ['sched_event_name_1day_on_abs_date'],
1316 ],
1317 [
1318 // Run cron 4 hours later; first message already sent
1319 'time' => '2012-06-14 04:00:00',
1320 'recipients' => [],
1321 'subjects' => [],
1322 ],
1323 ]);
1324 }
1325
1326 /**
1327 * For contacts/members which match schedule based on join/start date,
1328 * an email should be sent.
1329 *
1330 * @throws \API_Exception
1331 * @throws \CRM_Core_Exception
1332 * @throws \CiviCRM_API3_Exception
1333 */
1334 public function testMembershipDateMatch(): void {
1335 $contactID = $this->individualCreate(array_merge($this->fixtures['contact'], ['email' => 'test-member@example.com']));
1336 $membershipTypeID = $this->getMembershipTypeID();
1337 $membership = (array) $this->callAPISuccess('Membership', 'create', array_merge($this->fixtures['rolling_membership'], ['status_id' => 1, 'contact_id' => $contactID, 'sequential' => 1, 'membership_type_id' => $membershipTypeID]))['values'][0];
1338 $this->createScheduleFromFixtures('sched_membership_join_2week', ['entity_value' => $membershipTypeID]);
1339
1340 // start_date=2012-03-15 ; schedule is 2 weeks after join_date
1341 $this->assertCronRuns([
1342 [
1343 // Before the 2-week mark, no email.
1344 'time' => '2012-03-28 01:00:00',
1345 'recipients' => [],
1346 'subjects' => [],
1347 ],
1348 [
1349 // After the 2-week mark, send an email.
1350 'time' => '2012-03-29 01:00:00',
1351 'recipients' => [['test-member@example.com']],
1352 'subjects' => ['subject sched_membership_join_2week (joined March 15th, 2012)'],
1353 ],
1354 ]);
1355
1356 $this->createScheduleFromFixtures('sched_membership_start_1week', ['entity_value' => $membership['membership_type_id']]);
1357
1358 // start_date=2012-03-15 ; schedule is 1 weeks after start_date
1359 $this->assertCronRuns([
1360 [
1361 // Before the 2-week mark, no email.
1362 'time' => '2012-03-21 01:00:00',
1363 'recipients' => [],
1364 'subjects' => [],
1365 ],
1366 [
1367 // After the 2-week mark, send an email.
1368 'time' => '2012-03-22 01:00:00',
1369 'recipients' => [['test-member@example.com']],
1370 'subjects' => ['subject sched_membership_start_1week (joined March 15th, 2012)'],
1371 ],
1372 ]);
1373 }
1374
1375 /**
1376 * CRM-21675: Support parent and smart group in 'Limit to' field
1377 *
1378 * @throws \CRM_Core_Exception
1379 * @throws \CiviCRM_API3_Exception
1380 */
1381 public function testScheduleReminderWithParentGroup(): void {
1382 // Contact A with birth-date at '07-07-2005' and gender - Male, later got added in smart group
1383 $this->individualCreate(['birth_date' => '20050707', 'gender_id' => 1, 'email' => 'abc@test.com']);
1384 // Contact B with birth-date at '07-07-2005', later got added in regular group
1385 $contactID2 = $this->individualCreate(['birth_date' => '20050707', 'email' => 'def@test.com'], 1);
1386 // Contact C with birth-date at '07-07-2005', but not included in any group
1387 $this->individualCreate(['birth_date' => '20050707', 'email' => 'ghi@test.com'], 2);
1388
1389 // create regular group and add Contact B to it
1390 $groupID = $this->groupCreate();
1391 $this->callAPISuccess('GroupContact', 'Create', [
1392 'group_id' => $groupID,
1393 'contact_id' => $contactID2,
1394 ]);
1395
1396 // create smart group which will contain all Male contacts
1397 $smartGroupParams = ['form_values' => ['gender_id' => 1]];
1398 $smartGroupID = $this->smartGroupCreate(
1399 $smartGroupParams,
1400 [
1401 'name' => 'new_smart_group',
1402 'title' => 'New Smart Group',
1403 'parents' => [$groupID => 1],
1404 ]
1405 );
1406
1407 $actionScheduleParams = [
1408 'name' => 'sched_contact_birth_day_yesterday',
1409 'title' => 'sched_contact_birth_day_yesterday',
1410 'absolute_date' => '',
1411 'body_html' => '<p>you look like you were born yesterday!</p>',
1412 'body_text' => 'you look like you were born yesterday!',
1413 'end_action' => '',
1414 'end_date' => '',
1415 'end_frequency_interval' => '',
1416 'end_frequency_unit' => '',
1417 'entity_status' => 1,
1418 'entity_value' => 'birth_date',
1419 'limit_to' => 1,
1420 'group_id' => $groupID,
1421 'is_active' => 1,
1422 'is_repeat' => '0',
1423 'mapping_id' => 6,
1424 'msg_template_id' => '',
1425 'recipient' => '2',
1426 'recipient_listing' => '',
1427 'recipient_manual' => '',
1428 'record_activity' => 1,
1429 'repetition_frequency_interval' => '',
1430 'repetition_frequency_unit' => '',
1431 'start_action_condition' => 'after',
1432 'start_action_date' => 'date_field',
1433 'start_action_offset' => '1',
1434 'start_action_unit' => 'day',
1435 'subject' => 'subject sched_contact_birth_day_yesterday',
1436 ];
1437
1438 // Create schedule reminder where parent group ($groupID) is selected to limit recipients,
1439 // which contain a individual contact - $contactID2 and is parent to smart group.
1440 $this->callAPISuccess('ActionSchedule', 'create', $actionScheduleParams);
1441 $this->assertCronRuns([
1442 [
1443 // On the birthday, no email.
1444 'time' => '2005-07-07 01:00:00',
1445 'recipients' => [],
1446 ],
1447 [
1448 // The next day, send an email.
1449 'time' => '2005-07-08 20:00:00',
1450 'recipients' => [
1451 [
1452 'def@test.com',
1453 ],
1454 [
1455 'abc@test.com',
1456 ],
1457 ],
1458 ],
1459 ]);
1460 $this->groupDelete($smartGroupID);
1461 $this->groupDelete($groupID);
1462 }
1463
1464 /**
1465 * Test end date email sent.
1466 *
1467 * For contacts/members which match schedule based on join date,
1468 * an email should be sent.
1469 *
1470 * @throws \API_Exception
1471 * @throws \CRM_Core_Exception
1472 * @throws \CiviCRM_API3_Exception
1473 */
1474 public function testMembershipJoinDateNonMatch(): void {
1475 $this->createMembershipFromFixture('rolling_membership', '', ['email' => 'test-member@example.com']);
1476 // Add an alternative membership type, and only send messages for that type
1477 $extraMembershipType = $this->createTestObject('CRM_Member_DAO_MembershipType', []);
1478 $this->createScheduleFromFixtures('sched_membership_join_2week', ['entity_value' => $extraMembershipType->id]);
1479
1480 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
1481 $this->assertCronRuns([
1482 [
1483 // After the 2-week mark, don't send email because we have different membership type.
1484 'time' => '2012-03-29 01:00:00',
1485 'recipients' => [],
1486 ],
1487 ]);
1488 }
1489
1490 /**
1491 * Test that the first and SECOND notifications are sent out.
1492 *
1493 * @throws \API_Exception
1494 * @throws \CRM_Core_Exception
1495 * @throws \CiviCRM_API3_Exception
1496 */
1497 public function testMembershipEndDateRepeat(): void {
1498 // creates membership with end_date = 20120615
1499 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1500 $this->callAPISuccess('Email', 'create', [
1501 'contact_id' => $membership['contact_id'],
1502 'email' => 'test-member@example.com',
1503 ]);
1504 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1505
1506 $this->createScheduleFromFixtures('sched_membership_end_2month_repeat_twice_4_weeks', ['entity_value' => $membership['membership_type_id']]);
1507
1508 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1509 $this->assertCronRuns([
1510 [
1511 // After the 1-month mark, no email
1512 'time' => '2012-07-15 01:00:00',
1513 'recipients' => [],
1514 ],
1515 [
1516 // After the 2-month mark, send an email.
1517 'time' => '2012-08-15 01:00:00',
1518 'recipients' => [['test-member@example.com']],
1519 ],
1520 [
1521 // 4 weeks after first email send first repeat
1522 'time' => '2012-09-12 01:00:00',
1523 'recipients' => [['test-member@example.com']],
1524 ],
1525 [
1526 // 1 week after first repeat send nothing
1527 // There was a bug where the first repeat went out and then
1528 // it would keep going out every cron run. This is to check that's
1529 // not happening.
1530 'time' => '2012-09-19 01:00:00',
1531 'recipients' => [],
1532 ],
1533 [
1534 // 4 weeks after first repeat send second repeat
1535 'time' => '2012-10-10 01:00:00',
1536 'recipients' => [['test-member@example.com']],
1537 ],
1538 [
1539 // 4 months after membership end, send nothing
1540 'time' => '2012-10-15 01:00:00',
1541 'recipients' => [],
1542 ],
1543 [
1544 // 5 months after membership end, send nothing
1545 'time' => '2012-11-15 01:00:00',
1546 'recipients' => [],
1547 ],
1548 ]);
1549 }
1550
1551 /**
1552 * Test behaviour when date changes.
1553 *
1554 * Test that the first notification is sent but the second is NOT sent if the end date changes in
1555 * between
1556 * see CRM-15376
1557 *
1558 * @throws \API_Exception
1559 * @throws \CRM_Core_Exception
1560 * @throws \CiviCRM_API3_Exception
1561 */
1562 public function testMembershipEndDateRepeatChangedEndDate_CRM_15376(): void {
1563 // creates membership with end_date = 20120615
1564 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1565 $this->callAPISuccess('Email', 'create', [
1566 'contact_id' => $membership['contact_id'],
1567 'email' => 'test-member@example.com',
1568 ]);
1569 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1570
1571 $this->createScheduleFromFixtures('sched_membership_end_2month_repeat_twice_4_weeks', ['entity_value' => $membership['membership_type_id']]);
1572 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1573 $this->assertCronRuns([
1574 [
1575 // After the 2-week mark, send an email.
1576 'time' => '2012-08-15 01:00:00',
1577 'recipients' => [['test-member@example.com']],
1578 ],
1579 ]);
1580
1581 // Extend membership - reminder should NOT go out.
1582 $this->callAPISuccess('membership', 'create', ['id' => $membership['id'], 'end_date' => '2014-01-01']);
1583 $this->assertCronRuns([
1584 [
1585 // After the 2-week mark, send an email.
1586 'time' => '2012-09-12 01:00:00',
1587 'recipients' => [],
1588 ],
1589 ]);
1590 }
1591
1592 /**
1593 * Test membership end date email sends.
1594 *
1595 * For contacts/members which match schedule based on end date,
1596 * an email should be sent.
1597 *
1598 * @throws \API_Exception
1599 * @throws \CRM_Core_Exception
1600 * @throws \CiviCRM_API3_Exception
1601 */
1602 public function testMembershipEndDateMatch(): void {
1603 // creates membership with end_date = 20120615
1604 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1605 $this->callAPISuccess('Email', 'create', [
1606 'contact_id' => $membership['contact_id'],
1607 'email' => 'test-member@example.com',
1608 ]);
1609 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1610
1611 $this->createScheduleFromFixtures('sched_membership_end_2week', [
1612 'entity_value' => $membership['membership_type_id'],
1613 'effective_start_date' => '2012-06-01 00:00:00',
1614 ]);
1615
1616 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1617 $this->assertCronRuns([
1618 [
1619 // Before the 2-week mark, no email.
1620 'time' => '2012-05-31 01:00:00',
1621 'recipients' => [],
1622 ],
1623 [
1624 // After the 2-week mark, send an email.
1625 'time' => '2012-06-01 01:00:00',
1626 'recipients' => [['test-member@example.com']],
1627 ],
1628 [
1629 // After the email is sent, another one is not sent
1630 'time' => '2012-06-01 02:00:00',
1631 'recipients' => [],
1632 ],
1633 ]);
1634
1635 // Now suppose user has renewed for rolling membership after 3 months, so upcoming assertion is written
1636 // to ensure that new reminder is sent 2 week before the new end_date i.e. '2012-09-15'
1637 $membershipBAO = new CRM_Member_BAO_Membership();
1638 $membershipBAO->id = $membership['id'];
1639 $membershipBAO->end_date = '2012-09-15';
1640 $membershipBAO->save();
1641
1642 //change the email id of chosen membership contact to assert
1643 //recipient of not the previously sent mail but the new one
1644 $result = $this->callAPISuccess('Email', 'create', [
1645 'is_primary' => 1,
1646 'contact_id' => $membership['contact_id'],
1647 'email' => 'member2@example.com',
1648 ]);
1649 $this->assertAPISuccess($result);
1650
1651 // end_date=2012-09-15 ; schedule is 2 weeks before end_date
1652 $this->assertCronRuns([
1653 [
1654 // Before the 2-week mark, no email
1655 'time' => '2012-08-31 01:00:00',
1656 'recipients' => [],
1657 ],
1658 [
1659 // After the 2-week mark, send an email
1660 'time' => '2012-09-01 01:00:00',
1661 'recipients' => [['member2@example.com']],
1662 ],
1663 [
1664 // After the email is sent, another one is not sent
1665 'time' => '2012-09-01 02:00:00',
1666 'recipients' => [],
1667 ],
1668 ]);
1669 $membershipBAO = new CRM_Member_BAO_Membership();
1670 $membershipBAO->id = $membership['id'];
1671 $membershipBAO->end_date = '2012-12-15';
1672 $membershipBAO->save();
1673 // end_date=2012-12-15 ; schedule is 2 weeks before end_date
1674 $this->assertCronRuns([
1675 [
1676 // Before the 2-week mark, no email
1677 'time' => '2012-11-30 01:00:00',
1678 'recipients' => [],
1679 ],
1680 [
1681 // After the 2-week mark, send an email
1682 'time' => '2012-12-01 01:00:00',
1683 'recipients' => [['member2@example.com']],
1684 ],
1685 [
1686 // After the email is sent, another one is not sent
1687 'time' => '2012-12-01 02:00:00',
1688 'recipients' => [],
1689 ],
1690 ]);
1691
1692 }
1693
1694 /**
1695 * This test is very similar to testMembershipEndDateMatch, but it adds
1696 * another contact because there was a bug in
1697 * RecipientBuilder::buildRelFirstPass where it was only sending the
1698 * reminder for the first contact returned in a query for renewed
1699 * memberships. Other contacts wouldn't get the mail.
1700 *
1701 * @throws \API_Exception
1702 * @throws \CRM_Core_Exception
1703 * @throws \CiviCRM_API3_Exception
1704 */
1705 public function testMultipleMembershipEndDateMatch(): void {
1706 $contactID = $this->callAPISuccess('Contact', 'create', array_merge($this->fixtures['contact'], ['email' => 'test-member@example.com']))['id'];
1707 $contactID2 = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_2'])['id'];
1708 $membershipOne = $this->createMembershipFromFixture('rolling_membership', 2, [], ['contact_id' => $contactID]);
1709 $membershipTypeId = $membershipOne['membership_type_id'];
1710 $membershipTwo = $this->createMembershipFromFixture('rolling_membership', 2, [], ['contact_id' => $contactID2, 'membership_type_id' => $membershipTypeId]);
1711 // We are using dates that 'should' be expired but the test expects them not to be
1712 CRM_Core_DAO::executeQuery('UPDATE civicrm_membership SET status_id = 2 WHERE 1');
1713 $this->createScheduleFromFixtures('sched_membership_end_2week', ['entity_value' => $membershipTypeId]);
1714
1715 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1716 $this->assertCronRuns([
1717 [
1718 // Before the 2-week mark, no email.
1719 'time' => '2012-05-31 01:00:00',
1720 'recipients' => [],
1721 ],
1722 [
1723 // After the 2-week mark, send emails.
1724 'time' => '2012-06-01 01:00:00',
1725 'recipients' => [
1726 ['test-member@example.com'],
1727 ['test-contact-2@example.com'],
1728 ],
1729 ],
1730 [
1731 // After the email is sent, another one is not sent
1732 'time' => '2012-06-01 02:00:00',
1733 'recipients' => [],
1734 ],
1735 ]);
1736
1737 // Now suppose user has renewed for rolling membership after 3 months, so upcoming assertion is written
1738 // to ensure that new reminder is sent 2 week before the new end_date i.e. '2012-09-15'
1739 $membershipOneBAO = new CRM_Member_BAO_Membership();
1740 $membershipOneBAO->id = $membershipOne['id'];
1741 $membershipOneBAO->end_date = '2012-09-15';
1742 $membershipOneBAO->save();
1743 $membershipTwoBAO = new CRM_Member_BAO_Membership();
1744 $membershipTwoBAO->id = $membershipTwo['id'];
1745 $membershipTwoBAO->end_date = '2012-09-15';
1746 $membershipTwoBAO->save();
1747
1748 // end_date=2012-09-15 ; schedule is 2 weeks before end_date
1749 $this->assertCronRuns([
1750 [
1751 // Before the 2-week mark, no email
1752 'time' => '2012-08-31 01:00:00',
1753 'recipients' => [],
1754 ],
1755 [
1756 // After the 2-week mark, send an email
1757 'time' => '2012-09-01 01:00:00',
1758 'recipients' => [
1759 ['test-member@example.com'],
1760 ['test-contact-2@example.com'],
1761 ],
1762 ],
1763 [
1764 // After the email is sent, another one is not sent
1765 'time' => '2012-06-01 02:00:00',
1766 'recipients' => [],
1767 ],
1768 ]);
1769 }
1770
1771 /**
1772 * Test membership end date email.
1773 *
1774 * For contacts/members which match schedule based on end date,
1775 * an email should be sent.
1776 *
1777 * @throws \API_Exception
1778 * @throws \CRM_Core_Exception
1779 * @throws \CiviCRM_API3_Exception
1780 */
1781 public function testMembershipEndDateNoMatch(): void {
1782 // creates membership with end_date = 20120615
1783 $membership = $this->createMembershipFromFixture('rolling_membership', 'Grace');
1784 $this->callAPISuccess('Email', 'create', [
1785 'contact_id' => $membership['contact_id'],
1786 'email' => 'test-member@example.com',
1787 ]);
1788 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1789 $this->createScheduleFromFixtures('sched_membership_end_2month', ['entity_value' => $membership['membership_type_id']]);
1790
1791 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1792 $this->assertCronRuns([
1793 [
1794 // Before the 2-week mark, no email.
1795 'time' => '2012-05-31 01:00:00',
1796 'recipients' => [],
1797 ],
1798 [
1799 // After the 2-week mark, no email
1800 'time' => '2013-05-01 01:00:00',
1801 'recipients' => [],
1802 ],
1803 ]);
1804 }
1805
1806 /**
1807 * @throws \CRM_Core_Exception
1808 */
1809 public function testContactBirthDateNoAnniversary(): void {
1810 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1811 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1812 $this->createScheduleFromFixtures('sched_contact_birth_day_yesterday');
1813 $this->assertCronRuns([
1814 [
1815 // On the birthday, no email.
1816 'time' => '2005-07-07 01:00:00',
1817 'recipients' => [],
1818 ],
1819 [
1820 // The next day, send an email.
1821 'time' => '2005-07-08 20:00:00',
1822 'recipients' => [['test-birth_day@example.com']],
1823 ],
1824 ]);
1825 }
1826
1827 /**
1828 * @throws \CRM_Core_Exception
1829 */
1830 public function testContactBirthDateAnniversary(): void {
1831 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1832 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1833 $this->createScheduleFromFixtures('sched_contact_birth_day_anniversary');
1834 $this->assertCronRuns([
1835 [
1836 // On some random day, no email.
1837 'time' => '2014-03-07 01:00:00',
1838 'recipients' => [],
1839 ],
1840 [
1841 // On the eve of their 9th birthday, send an email.
1842 'time' => '2014-07-06 20:00:00',
1843 'recipients' => [['test-birth_day@example.com']],
1844 ],
1845 ]);
1846 }
1847
1848 /**
1849 * @throws \CRM_Core_Exception
1850 */
1851 public function testContactCustomDateNoAnniversary(): void {
1852 $group = [
1853 'title' => 'Test_Group',
1854 'name' => 'test_group',
1855 'extends' => ['Individual'],
1856 'style' => 'Inline',
1857 'is_multiple' => FALSE,
1858 'is_active' => 1,
1859 ];
1860 $createGroup = $this->callAPISuccess('custom_group', 'create', $group);
1861 $field = [
1862 'label' => 'Graduation',
1863 'data_type' => 'Date',
1864 'html_type' => 'Select Date',
1865 'custom_group_id' => $createGroup['id'],
1866 ];
1867 $createField = $this->callAPISuccess('custom_field', 'create', $field);
1868 $contactParams = $this->fixtures['contact'];
1869 $contactParams["custom_{$createField['id']}"] = '2013-12-16';
1870 $contact = $this->callAPISuccess('Contact', 'create', $contactParams);
1871 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1872 $this->createScheduleFromFixtures('sched_contact_grad_tomorrow', ['entity_value' => "custom_{$createField['id']}"]);
1873 $this->assertCronRuns([
1874 [
1875 // On some random day, no email.
1876 'time' => '2014-03-07 01:00:00',
1877 'recipients' => [],
1878 ],
1879 [
1880 // On the eve of their graduation, send an email.
1881 'time' => '2013-12-15 20:00:00',
1882 'recipients' => [['test-member@example.com']],
1883 ],
1884 ]);
1885 $this->callAPISuccess('custom_group', 'delete', ['id' => $createGroup['id']]);
1886 }
1887
1888 /**
1889 * @throws \CRM_Core_Exception
1890 */
1891 public function testContactCreatedNoAnniversary(): void {
1892 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1893 $this->createScheduleFromFixtures('sched_contact_created_yesterday');
1894 $this->assertCronRuns([
1895 [
1896 // On the date created, no email.
1897 'time' => $contact['values'][$contact['id']]['created_date'],
1898 'recipients' => [],
1899 ],
1900 [
1901 // The next day, send an email.
1902 'time' => date('Y-m-d H:i:s', strtotime($contact['values'][$contact['id']]['created_date'] . ' +1 day')),
1903 'recipients' => [['test-birth_day@example.com'], ['fixme.domainemail@example.org'], ['domainemail2@example.org']],
1904 ],
1905 ]);
1906 }
1907
1908 /**
1909 * Test the impact of changing the anniversary.
1910 *
1911 * @throws \CRM_Core_Exception
1912 */
1913 public function testContactModifiedAnniversary(): void {
1914 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1915 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1916 $modifiedDate = $this->callAPISuccess('Contact', 'getvalue', ['id' => $contact['id'], 'return' => 'modified_date']);
1917 $actionSchedule = $this->createScheduleFromFixtures('sched_contact_mod_anniversary');
1918 $actionSchedule['effective_start_date'] = date('Y-m-d H:i:s', strtotime($contact['values'][$contact['id']]['modified_date']));
1919 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule);
1920 $this->assertCronRuns([
1921 [
1922 // On some random day, no email.
1923 'time' => date('Y-m-d H:i:s', strtotime($contact['values'][$contact['id']]['modified_date'] . ' -60 days')),
1924 'recipients' => [],
1925 ],
1926 [
1927 // On the eve of 3 years after they were modified, send an email.
1928 'time' => date('Y-m-d H:i:s', strtotime($modifiedDate . ' +3 years -1 day')),
1929 'recipients' => [['test-birth_day@example.com'], ['fixme.domainemail@example.org'], ['domainemail2@example.org']],
1930 ],
1931 ]);
1932 }
1933
1934 /**
1935 * Check that limit_to + an empty recipients doesn't sent to multiple
1936 * contacts.
1937 *
1938 * @throws \API_Exception
1939 * @throws \CRM_Core_Exception
1940 * @throws \CiviCRM_API3_Exception
1941 */
1942 public function testMembershipLimitToNone(): void {
1943 // creates membership with end_date = 20120615
1944 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1945 $result = $this->callAPISuccess('Email', 'create', [
1946 'contact_id' => $membership['contact_id'],
1947 'email' => 'member@example.com',
1948 ]);
1949 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1950 $this->callAPISuccess('contact', 'create', ['email' => 'b@c.com', 'contact_type' => 'Individual']);
1951
1952 $this->assertAPISuccess($result);
1953
1954 $this->createScheduleFromFixtures('sched_membership_end_limit_to_none', ['entity_value' => $membership['membership_type_id']]);
1955
1956 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1957 $this->assertCronRuns([
1958 [
1959 // Before the 2-week mark, no email.
1960 'time' => '2012-05-31 01:00:00',
1961 'recipients' => [],
1962 ],
1963 ]);
1964 }
1965
1966 /**
1967 * Test handling of reference date for memberships.
1968 *
1969 * @throws \API_Exception
1970 * @throws \CRM_Core_Exception
1971 * @throws \CiviCRM_API3_Exception
1972 */
1973 public function testMembershipWithReferenceDate(): void {
1974 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current', ['email' => 'member@example.com']);
1975 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1976
1977 $this->createScheduleFromFixtures('sched_membership_join_2week', ['entity_value' => $membership['membership_type_id']]);
1978
1979 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
1980 $this->assertCronRuns([
1981 [
1982 // After the 2-week mark, send an email
1983 'time' => '2012-03-29 01:00:00',
1984 'recipients' => [['member@example.com']],
1985 ],
1986 [
1987 // After the 2-week 1day mark, don't send an email
1988 'time' => '2012-03-30 01:00:00',
1989 'recipients' => [],
1990 ],
1991 ]);
1992
1993 //check if reference date is set to membership's join date
1994 //as per the action_start_date chosen for current schedule reminder
1995 $this->assertEquals('2012-03-15 00:00:00',
1996 CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $membership['contact_id'], 'reference_date', 'contact_id')
1997 );
1998
1999 //change current membership join date that may signifies as membership renewal activity
2000 $membershipBAO = new CRM_Member_BAO_Membership();
2001 $membershipBAO->id = $membership['id'];
2002 $membershipBAO->join_date = '2012-03-29';
2003 $membershipBAO->save();
2004
2005 $this->assertCronRuns([
2006 [
2007 // After the 13 days of the changed join date 2012-03-29, don't send an email
2008 'time' => '2012-04-11 01:00:00',
2009 'recipients' => [],
2010 ],
2011 [
2012 // After the 2-week of the changed join date 2012-03-29, send an email
2013 'time' => '2012-04-12 01:00:00',
2014 'recipients' => [['member@example.com']],
2015 ],
2016 ]);
2017 $this->assertCronRuns([
2018 [
2019 // It should not re-send on the same day
2020 'time' => '2012-04-12 01:00:00',
2021 'recipients' => [],
2022 ],
2023 ]);
2024 }
2025
2026 /**
2027 * Test multiple membership reminder.
2028 *
2029 * @throws \API_Exception
2030 * @throws \CRM_Core_Exception
2031 * @throws \CiviCRM_API3_Exception
2032 */
2033 public function testMembershipOnMultipleReminder(): void {
2034 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current', ['email' => 'member@example.com']);
2035 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
2036
2037 // Send email 2 weeks before end_date
2038 $actionScheduleBefore = $this->fixtures['sched_membership_end_2week'];
2039 // Send email on end_date/expiry date
2040 $actionScheduleOn = $this->fixtures['sched_on_membership_end_date'];
2041 $actionScheduleOn['effective_start_date'] = '2012-06-14 00:00:00';
2042 $actionScheduleAfter['effective_end_date'] = '2012-06-15 01:00:00';
2043 // Send email 1 day after end_date/grace period
2044 $actionScheduleAfter = $this->fixtures['sched_after_1day_membership_end_date'];
2045 $actionScheduleAfter['effective_start_date'] = '2012-06-15 01:00:00';
2046 $actionScheduleAfter['effective_end_date'] = '2012-06-16 02:00:00';
2047 $actionScheduleBefore['entity_value'] = $actionScheduleOn['entity_value'] = $actionScheduleAfter['entity_value'] = $membership['membership_type_id'];
2048 foreach (['actionScheduleBefore', 'actionScheduleOn', 'actionScheduleAfter'] as $value) {
2049 $$value = CRM_Core_BAO_ActionSchedule::add($$value);
2050 }
2051
2052 $this->assertCronRuns(
2053 [
2054 [
2055 // 1day 2weeks before membership end date(MED), don't send mail
2056 'time' => '2012-05-31 01:00:00',
2057 'recipients' => [],
2058 ],
2059 [
2060 // 2 weeks before MED, send an email
2061 'time' => '2012-06-01 01:00:00',
2062 'recipients' => [['member@example.com']],
2063 ],
2064 [
2065 // 1day before MED, don't send mail
2066 'time' => '2012-06-14 01:00:00',
2067 'recipients' => [],
2068 ],
2069 [
2070 // On MED, send an email
2071 'time' => '2012-06-15 00:00:00',
2072 'recipients' => [['member@example.com']],
2073 ],
2074 [
2075 // After 1day of MED, send an email
2076 'time' => '2012-06-16 01:00:00',
2077 'recipients' => [['member@example.com']],
2078 ],
2079 [
2080 // After 1day 1min of MED, don't send an email
2081 'time' => '2012-06-17 00:01:00',
2082 'recipients' => [],
2083 ],
2084 ]
2085 );
2086
2087 // Assert the timestamp as of when the emails of respective three reminders as configured
2088 // 2 weeks before, on and 1 day after MED, are sent
2089 $this->assertApproxEquals(
2090 strtotime('2012-06-01 01:00:00'),
2091 strtotime(CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleBefore->id, 'action_date_time', 'action_schedule_id', TRUE)),
2092 // Variation in test execution time.
2093 3
2094 );
2095 $this->assertApproxEquals(
2096 strtotime('2012-06-15 00:00:00'),
2097 strtotime(CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleOn->id, 'action_date_time', 'action_schedule_id', TRUE)),
2098 // Variation in test execution time.
2099 3
2100 );
2101 $this->assertApproxEquals(
2102 strtotime('2012-06-16 01:00:00'),
2103 strtotime(CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleAfter->id, 'action_date_time', 'action_schedule_id', TRUE)),
2104 // Variation in test execution time.
2105 3
2106 );
2107
2108 //extend MED to 2 weeks after the current MED (that may signifies as membership renewal activity)
2109 // and lets assert as of when the new set of reminders will be sent against their respective Schedule Reminders(SR)
2110 $membershipBAO = new CRM_Member_BAO_Membership();
2111 $membershipBAO->id = $membership['id'];
2112 $membershipBAO->end_date = '2012-06-20';
2113 $membershipBAO->save();
2114
2115 // increase the effective end date to future
2116 $actionScheduleAfter->effective_end_date = '2012-07-22 00:00:00';
2117 $actionScheduleAfter->save();
2118
2119 $this->callAPISuccess('Contact', 'get', ['id' => $membership['contact_id']]);
2120 $this->assertCronRuns(
2121 [
2122 [
2123 // 1day 2weeks before membership end date(MED), don't send mail
2124 'time' => '2012-06-05 01:00:00',
2125 'recipients' => [],
2126 ],
2127 [
2128 // 2 weeks before MED, send an email
2129 'time' => '2012-06-06 01:00:00',
2130 'recipients' => [['member@example.com']],
2131 ],
2132 [
2133 // 1day before MED, don't send mail
2134 'time' => '2012-06-19 01:00:00',
2135 'recipients' => [],
2136 ],
2137 [
2138 // On MED, send an email
2139 'time' => '2012-06-20 00:00:00',
2140 'recipients' => [['member@example.com']],
2141 ],
2142 [
2143 // After 1day of MED, send an email
2144 'time' => '2012-06-21 01:00:00',
2145 'recipients' => [['member@example.com']],
2146 ],
2147 [
2148 // After 1day 1min of MED, don't send an email
2149 'time' => '2012-07-21 00:01:00',
2150 'recipients' => [],
2151 ],
2152 ]);
2153 }
2154
2155 /**
2156 * Test reminders sent on custom data anniversary.
2157 *
2158 * @throws \API_Exception
2159 * @throws \CRM_Core_Exception
2160 */
2161 public function testContactCustomDate_Anniversary(): void {
2162 $this->createCustomGroupWithFieldOfType([], 'date');
2163 $contactParams = $this->fixtures['contact'];
2164 $contactParams[$this->getCustomFieldName('date')] = '2013-12-16';
2165 $contact = $this->callAPISuccess('Contact', 'create', $contactParams);
2166 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
2167 $this->fixtures['sched_contact_grad_anniversary']['entity_value'] = $this->getCustomFieldName('date');
2168 $this->createScheduleFromFixtures('sched_contact_grad_anniversary');
2169
2170 $this->assertCronRuns([
2171 [
2172 // On some random day, no email.
2173 'time' => '2014-03-07 01:00:00',
2174 'recipients' => [],
2175 ],
2176 [
2177 // A week after their 5th anniversary of graduation, send an email.
2178 'time' => '2018-12-23 20:00:00',
2179 'recipients' => [['test-member@example.com']],
2180 ],
2181 ]);
2182 }
2183
2184 /**
2185 * Test sched reminder set via registration date.
2186 *
2187 * @throws \CRM_Core_Exception
2188 * @throws \CiviCRM_API3_Exception
2189 */
2190 public function testEventTypeRegistrationDate(): void {
2191 $contact = $this->individualCreate(['email' => 'test-event@example.com']);
2192 //Add it as a participant to an event ending registration - 7 days from now.
2193 $params = [
2194 'start_date' => date('Ymd', strtotime('-5 day')),
2195 'end_date' => date('Ymd', strtotime('+7 day')),
2196 'registration_start_date' => date('Ymd', strtotime('-5 day')),
2197 'registration_end_date' => date('Ymd', strtotime('+7 day')),
2198 ];
2199 $event = $this->eventCreate($params);
2200 $this->participantCreate(['contact_id' => $contact, 'event_id' => $event['id']]);
2201
2202 //Create a scheduled reminder to send email 7 days before registration date.
2203 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2204 $actionSchedule['start_action_offset'] = 7;
2205 $actionSchedule['start_action_unit'] = 'day';
2206 $actionSchedule['start_action_date'] = 'registration_end_date';
2207 $actionSchedule['entity_value'] = $event['values'][$event['id']]['event_type_id'];
2208 $actionSchedule['entity_status'] = $this->callAPISuccessGetValue('ParticipantStatusType', [
2209 'return' => 'id',
2210 'name' => 'Attended',
2211 ]);
2212 $actionSched = $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2213 //Run the cron and verify if an email was sent.
2214 $this->assertCronRuns([
2215 [
2216 'time' => date('Y-m-d'),
2217 'recipients' => [['test-event@example.com']],
2218 ],
2219 ]);
2220
2221 //Create contact 2
2222 $contactParams = [
2223 'email' => 'test-event2@example.com',
2224 ];
2225 $contact2 = $this->individualCreate($contactParams);
2226 //Create an event with registration end date = 2 week from now.
2227 $params['end_date'] = date('Ymd', strtotime('+2 week'));
2228 $params['registration_end_date'] = date('Ymd', strtotime('+2 week'));
2229 $event2 = $this->eventCreate($params);
2230 $this->participantCreate(['contact_id' => $contact2, 'event_id' => $event2['id']]);
2231
2232 //Assert there is no reminder sent to the contact.
2233 $this->assertCronRuns([
2234 [
2235 'time' => date('Y-m-d'),
2236 'recipients' => [],
2237 ],
2238 ]);
2239
2240 //Modify the sched reminder to be sent 2 week from registration end date.
2241 $this->callAPISuccess('action_schedule', 'create', [
2242 'id' => $actionSched['id'],
2243 'start_action_offset' => 2,
2244 'start_action_unit' => 'week',
2245 ]);
2246
2247 //Contact should receive the reminder now.
2248 $this->assertCronRuns([
2249 [
2250 'time' => date('Y-m-d'),
2251 'recipients' => [['test-event2@example.com']],
2252 ],
2253 ]);
2254 }
2255
2256 /**
2257 * Test sched reminder set via start date.
2258 *
2259 * @throws \CRM_Core_Exception
2260 * @throws \CiviCRM_API3_Exception
2261 */
2262 public function testEventTypeStartDate(): void {
2263 // Create event+participant with start_date = 20120315, end_date = 20120615.
2264 $params = $this->fixtures['participant'];
2265 $params['event_id'] = $this->callAPISuccess('Event', 'create', array_merge($this->fixtures['participant']['event_id'], ['event_type_id' => 1]))['id'];
2266 $params['status_id'] = 2;
2267 $params['contact_id'] = $this->individualCreate(array_merge($this->fixtures['contact'], ['email' => 'test-event@example.com']));
2268 $this->callAPISuccess('Participant', 'create', $params);
2269
2270 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2271 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $params['event_id'], 'event_type_id');
2272 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2273
2274 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
2275 $this->assertCronRuns([
2276 [
2277 // 2 weeks before
2278 'time' => '2012-03-02 01:00:00',
2279 'recipients' => [],
2280 ],
2281 [
2282 // 1 week before
2283 'time' => '2012-03-08 01:00:00',
2284 'recipients' => [['test-event@example.com']],
2285 ],
2286 [
2287 // And then nothing else
2288 'time' => '2012-03-16 01:00:00',
2289 'recipients' => [],
2290 ],
2291 ]);
2292
2293 // CASE 2: Create a schedule reminder which was created 1 day after the schdule day,
2294 // so it shouldn't deliver reminders schedule to send 1 week before the event start date
2295 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2296 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $params['event_id'], 'event_type_id');
2297 $actionSchedule['effective_start_date'] = '20120309000000';
2298 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2299 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
2300 $this->assertCronRuns([
2301 [
2302 // 2 weeks before
2303 'time' => '2012-03-02 01:00:00',
2304 'recipients' => [],
2305 ],
2306 [
2307 // 1 week before
2308 'time' => '2012-03-08 01:00:00',
2309 'recipients' => [],
2310 ],
2311 [
2312 // And then nothing else
2313 'time' => '2012-03-16 01:00:00',
2314 'recipients' => [],
2315 ],
2316 ]);
2317
2318 // CASE 3: Create a schedule reminder which is created less then a week before the event start date,
2319 // so it should deliver reminders schedule to send 1 week before the event start date, set the effective end date just an hour later the reminder delivery date
2320 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2321 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $params['event_id'], 'event_type_id');
2322 $actionSchedule['effective_end_date'] = '20120309010000';
2323 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2324 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
2325 $this->assertCronRuns([
2326 [
2327 // 2 weeks before
2328 'time' => '2012-03-02 01:00:00',
2329 'recipients' => [],
2330 ],
2331 [
2332 // 1 week before
2333 'time' => '2012-03-08 01:00:00',
2334 'recipients' => [['test-event@example.com']],
2335 ],
2336 [
2337 // And then nothing else
2338 'time' => '2012-03-16 01:00:00',
2339 'recipients' => [],
2340 ],
2341 ]);
2342 }
2343
2344 /**
2345 * Test schedule on event end date.
2346 *
2347 * @throws \CRM_Core_Exception
2348 */
2349 public function testEventTypeEndDateRepeat(): void {
2350 // Create event+participant with start_date = 20120315, end_date = 20120615.
2351 $participant = $this->createTestObject('CRM_Event_DAO_Participant', array_merge($this->fixtures['participant'], ['status_id' => 2]));
2352 $this->callAPISuccess('Email', 'create', [
2353 'contact_id' => $participant->contact_id,
2354 'email' => 'test-event@example.com',
2355 ]);
2356 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $participant->contact_id]));
2357
2358 $actionSchedule = $this->fixtures['sched_event_type_end_2month_repeat_twice_2_weeks'];
2359 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $participant->event_id, 'event_type_id');
2360 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2361
2362 $this->assertCronRuns([
2363 [
2364 // Almost 2 months.
2365 'time' => '2012-08-13 01:00:00',
2366 'recipients' => [],
2367 ],
2368 [
2369 // After the 2-month mark, send an email.
2370 'time' => '2012-08-16 01:00:00',
2371 'recipients' => [['test-event@example.com']],
2372 ],
2373 [
2374 // After 2 months and 1 week, don't repeat yet.
2375 'time' => '2012-08-23 02:00:00',
2376 'recipients' => [],
2377 ],
2378 [
2379 // After 2 months and 2 weeks
2380 'time' => '2012-08-30 02:00:00',
2381 'recipients' => [['test-event@example.com']],
2382 ],
2383 [
2384 // After 2 months and 4 week
2385 'time' => '2012-09-13 02:00:00',
2386 'recipients' => [['test-event@example.com']],
2387 ],
2388 [
2389 // After 2 months and 6 weeks
2390 'time' => '2012-09-27 01:00:00',
2391 'recipients' => [],
2392 ],
2393 ]);
2394 }
2395
2396 /**
2397 * Run a series of cron jobs and make an assertion about email deliveries.
2398 *
2399 * @param array $cronRuns
2400 * array specifying when to run cron and what messages to expect; each item is an array with keys:
2401 * - time: string, e.g. '2012-06-15 21:00:01'
2402 * - recipients: array(array(string)), list of email addresses which should receive messages
2403 *
2404 * @throws \CRM_Core_Exception
2405 * @noinspection DisconnectedForeachInstructionInspection
2406 */
2407 public function assertCronRuns(array $cronRuns): void {
2408 foreach ($cronRuns as $cronRun) {
2409 CRM_Utils_Time::setTime($cronRun['time']);
2410 $this->callAPISuccess('job', 'send_reminder', []);
2411 $this->mut->assertRecipients($cronRun['recipients']);
2412 if (array_key_exists('subjects', $cronRun)) {
2413 $this->mut->assertSubjects($cronRun['subjects']);
2414 }
2415 $this->mut->clearMessages();
2416 }
2417 }
2418
2419 /**
2420 * @var array
2421 *
2422 * (DAO_Name => array(int)) List of items to garbage-collect during tearDown
2423 */
2424 private $_testObjects = [];
2425
2426 /**
2427 * This is a wrapper for CRM_Core_DAO::createTestObject which tracks
2428 * created entities and provides for brainless cleanup.
2429 *
2430 * However, it is only really brainless when initially writing the code.
2431 * It 'steals and deletes entities that are part of the 'stock build'.
2432 *
2433 * In general this causes weird stuff.
2434 *
2435 * @param $daoName
2436 * @param array $params
2437 * @param int $numObjects
2438 * @param bool $createOnly
2439 *
2440 * @return array|NULL|object
2441 * @see CRM_Core_DAO::createTestObject
2442 */
2443 public function createTestObject($daoName, array $params = [], int $numObjects = 1, bool $createOnly = FALSE) {
2444 $objects = CRM_Core_DAO::createTestObject($daoName, $params, $numObjects, $createOnly);
2445 if (is_array($objects)) {
2446 $this->registerTestObjects($objects);
2447 }
2448 else {
2449 $this->registerTestObjects([$objects]);
2450 }
2451 return $objects;
2452 }
2453
2454 /**
2455 * @param array $objects
2456 * DAO or BAO objects.
2457 */
2458 public function registerTestObjects(array $objects): void {
2459 //if (is_object($objects)) {
2460 // $objects = array($objects);
2461 //}
2462 foreach ($objects as $object) {
2463 $daoName = str_replace('_BAO_', '_DAO_', get_class($object));
2464 $this->_testObjects[$daoName][] = $object->id;
2465 }
2466 }
2467
2468 public function deleteTestObjects(): void {
2469 // Note: You might argue that the FK relations between test
2470 // objects could make this problematic; however, it should
2471 // behave intuitively as long as we mentally split our
2472 // test-objects between the "manual/primary records"
2473 // and the "automatic/secondary records"
2474 foreach ($this->_testObjects as $daoName => $daoIds) {
2475 foreach ($daoIds as $daoId) {
2476 CRM_Core_DAO::deleteTestObjects($daoName, ['id' => $daoId]);
2477 }
2478 }
2479 $this->_testObjects = [];
2480 }
2481
2482 /**
2483 * Test that the various repetition units work correctly.
2484 *
2485 * @see https://issues.civicrm.org/jira/browse/CRM-17028
2486 * @throws \CRM_Core_Exception
2487 */
2488 public function testRepetitionFrequencyUnit(): void {
2489 $membershipTypeParams = [
2490 'duration_interval' => '1',
2491 'duration_unit' => 'year',
2492 'is_active' => 1,
2493 'period_type' => 'rolling',
2494 ];
2495 $membershipType = $this->createTestObject('CRM_Member_DAO_MembershipType', $membershipTypeParams);
2496 $interval_units = ['hour', 'day', 'week', 'month', 'year'];
2497 foreach ($interval_units as $interval_unit) {
2498 $membershipEndDate = DateTime::createFromFormat('Y-m-d H:i:s', '2013-03-15 00:00:00');
2499 $contactParams = [
2500 'contact_type' => 'Individual',
2501 'first_name' => 'Test',
2502 'last_name' => "Interval $interval_unit",
2503 'is_deceased' => 0,
2504 ];
2505 $contact = $this->createTestObject('CRM_Contact_DAO_Contact', $contactParams);
2506 $emailParams = [
2507 'contact_id' => $contact->id,
2508 'is_primary' => 1,
2509 'email' => "test-member-$interval_unit@example.com",
2510 'location_type_id' => 1,
2511 ];
2512 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2513 $membershipParams = [
2514 'membership_type_id' => $membershipType->id,
2515 'contact_id' => $contact->id,
2516 'join_date' => '20120315',
2517 'start_date' => '20120315',
2518 'end_date' => '20130315',
2519 'is_override' => 0,
2520 'status_id' => 2,
2521 ];
2522 $membershipParams['status-id'] = 1;
2523 $membership = $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2524 $actionScheduleParams = $this->fixtures['sched_on_membership_end_date_repeat_interval'];
2525 $actionScheduleParams['entity_value'] = $membershipType->id;
2526 $actionScheduleParams['repetition_frequency_unit'] = $interval_unit;
2527 $actionScheduleParams['repetition_frequency_interval'] = 2;
2528 $actionSchedule = CRM_Core_BAO_ActionSchedule::add($actionScheduleParams);
2529 $beforeEndDate = $this->createModifiedDateTime($membershipEndDate, '-1 day');
2530 $beforeFirstUnit = $this->createModifiedDateTime($membershipEndDate, "+1 $interval_unit");
2531 $afterFirstUnit = $this->createModifiedDateTime($membershipEndDate, "+2 $interval_unit");
2532 $cronRuns = [
2533 [
2534 'time' => $beforeEndDate->format('Y-m-d H:i:s'),
2535 'recipients' => [],
2536 ],
2537 [
2538 'time' => $membershipEndDate->format('Y-m-d H:i:s'),
2539 'recipients' => [["test-member-$interval_unit@example.com"]],
2540 ],
2541 [
2542 'time' => $beforeFirstUnit->format('Y-m-d H:i:s'),
2543 'recipients' => [],
2544 ],
2545 [
2546 'time' => $afterFirstUnit->format('Y-m-d H:i:s'),
2547 'recipients' => [["test-member-$interval_unit@example.com"]],
2548 ],
2549 ];
2550 $this->assertCronRuns($cronRuns);
2551 $actionSchedule->delete();
2552 $membership->delete();
2553 }
2554 }
2555
2556 /**
2557 * Inherited members without permission to edit the main member contact should
2558 * not get reminders.
2559 *
2560 * However, just because a contact inherits one membership doesn't mean
2561 * reminders for other memberships should be suppressed.
2562 *
2563 * See CRM-14098
2564 *
2565 * @throws \CRM_Core_Exception
2566 */
2567 public function testInheritedMembershipPermissions(): void {
2568 // Set up common parameters for memberships.
2569 $membershipParams = $this->fixtures['rolling_membership'];
2570 $membershipParams['status_id'] = 1;
2571
2572 $membershipParams['membership_type_id']['relationship_type_id'] = 1;
2573 $membershipParams['membership_type_id']['relationship_direction'] = 'b_a';
2574 $membershipType1 = $this->createTestObject('CRM_Member_DAO_MembershipType', $membershipParams['membership_type_id']);
2575
2576 // We'll create a new membership type that can be held at the same time as
2577 // the first one.
2578 $membershipParams['membership_type_id']['relationship_type_id'] = 'NULL';
2579 $membershipParams['membership_type_id']['relationship_direction'] = 'NULL';
2580 $membershipType2 = $this->createTestObject('CRM_Member_DAO_MembershipType', $membershipParams['membership_type_id']);
2581
2582 // Create the parent membership and contact
2583 $membershipParams['membership_type_id'] = $membershipType1->id;
2584 $mainMembership = $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2585
2586 $contactParams = [
2587 'contact_type' => 'Individual',
2588 'first_name' => 'Mom',
2589 'last_name' => 'Rel',
2590 'is_deceased' => 0,
2591 ];
2592 $this->createTestObject('CRM_Contact_DAO_Contact', array_merge($contactParams, ['id' => $mainMembership->contact_id]));
2593
2594 $emailParams = [
2595 'contact_id' => $mainMembership->contact_id,
2596 'email' => 'test-member@example.com',
2597 'location_type_id' => 1,
2598 'is_primary' => 1,
2599 ];
2600 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2601
2602 // Set up contacts and emails for the two children
2603 $contactParams['first_name'] = 'Favorite';
2604 $permChild = $this->createTestObject('CRM_Contact_DAO_Contact', $contactParams);
2605 $emailParams['email'] = 'favorite@example.com';
2606 $emailParams['contact_id'] = $permChild->id;
2607 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2608
2609 $contactParams['first_name'] = 'Black Sheep';
2610 $nonPermChild = $this->createTestObject('CRM_Contact_DAO_Contact', $contactParams);
2611 $emailParams['email'] = 'black.sheep@example.com';
2612 $emailParams['contact_id'] = $nonPermChild->id;
2613 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2614
2615 // Each child gets a relationship, one with permission to edit the parent. This
2616 // will trigger inherited memberships for the first membership type
2617 $relParams = [
2618 'relationship_type_id' => 1,
2619 'contact_id_a' => $nonPermChild->id,
2620 'contact_id_b' => $mainMembership->contact_id,
2621 'is_active' => 1,
2622 ];
2623 $this->callAPISuccess('relationship', 'create', $relParams);
2624
2625 $relParams['contact_id_a'] = $permChild->id;
2626 $relParams['is_permission_a_b'] = CRM_Contact_BAO_Relationship::EDIT;
2627 $this->callAPISuccess('relationship', 'create', $relParams);
2628
2629 // Mom and Black Sheep get their own memberships of the second type.
2630 $membershipParams['membership_type_id'] = $membershipType2->id;
2631 $membershipParams['owner_membership_id'] = 'NULL';
2632 $membershipParams['contact_id'] = $mainMembership->contact_id;
2633 $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2634
2635 $membershipParams['contact_id'] = $nonPermChild->id;
2636 $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2637
2638 // Test a reminder for the first membership type - that should exclude Black
2639 // Sheep.
2640 $this->fixtures['sched_membership_join_2week']['entity_value'] = $membershipType1->id;
2641 $this->createScheduleFromFixtures('sched_membership_join_2week');
2642
2643 $this->assertCronRuns([
2644 [
2645 'time' => '2012-03-29 01:00:00',
2646 'recipients' => [['test-member@example.com'], ['favorite@example.com']],
2647 'subjects' => [
2648 'subject sched_membership_join_2week (joined March 15th, 2012)',
2649 'subject sched_membership_join_2week (joined March 15th, 2012)',
2650 ],
2651 ],
2652 ]);
2653
2654 // Test a reminder for the second membership type - that should include
2655 // Black Sheep.
2656 $this->fixtures['sched_membership_start_1week']['entity_value'] = $membershipType2->id;
2657 $this->createScheduleFromFixtures('sched_membership_start_1week');
2658
2659 $this->assertCronRuns([
2660 [
2661 'time' => '2012-03-22 01:00:00',
2662 'recipients' => [['test-member@example.com'], ['black.sheep@example.com']],
2663 'subjects' => [
2664 'subject sched_membership_start_1week (joined March 15th, 2012)',
2665 'subject sched_membership_start_1week (joined March 15th, 2012)',
2666 ],
2667 ],
2668 ]);
2669 }
2670
2671 /**
2672 * Modify the date time by the modify rule.
2673 *
2674 * @param DateTime $origDateTime
2675 * @param string $modifyRule
2676 *
2677 * @return DateTime
2678 */
2679 public function createModifiedDateTime(DateTime $origDateTime, string $modifyRule): DateTime {
2680 $newDateTime = clone($origDateTime);
2681 $newDateTime->modify($modifyRule);
2682 return $newDateTime;
2683 }
2684
2685 /**
2686 * Test absolute date handling for membership.
2687 *
2688 * @throws \API_Exception
2689 * @throws \CRM_Core_Exception
2690 * @throws \CiviCRM_API3_Exception
2691 * @throws \Civi\API\Exception\UnauthorizedException
2692 */
2693 public function testMembershipScheduleWithAbsoluteDate(): void {
2694 $membership = $this->createMembershipFromFixture('rolling_membership', 'New', [
2695 'email' => 'test-member@example.com',
2696 'location_type_id' => 1,
2697 ]);
2698
2699 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
2700 $this->fixtures['sched_membership_absolute_date']['entity_value'] = $membership['membership_type_id'];
2701 $this->createScheduleFromFixtures('sched_membership_absolute_date');
2702
2703 $this->assertCronRuns([
2704 [
2705 // Before the 24-hour mark, no email
2706 'time' => '2012-06-13 04:00:00',
2707 'recipients' => [],
2708 'subjects' => [],
2709 ],
2710 [
2711 // On absolute date set on 2012-06-14
2712 'time' => '2012-06-14 00:00:00',
2713 'recipients' => [['test-member@example.com']],
2714 'subjects' => ['subject sched_membership_absolute_date'],
2715 ],
2716 [
2717 // Run cron 4 hours later; first message already sent
2718 'time' => '2012-06-14 04:00:00',
2719 'recipients' => [],
2720 'subjects' => [],
2721 ],
2722 ]);
2723 }
2724
2725 /**
2726 * @param string $fixture
2727 * Key from $this->fixtures
2728 * @param string $status
2729 * Membership status
2730 * @param array $emailParams
2731 * @param array $membershipOverrides
2732 *
2733 * @return array
2734 * @throws \API_Exception
2735 * @throws \CRM_Core_Exception
2736 * @throws \CiviCRM_API3_Exception
2737 * @throws \Civi\API\Exception\UnauthorizedException
2738 */
2739 protected function createMembershipFromFixture(string $fixture, string $status, array $emailParams = [], array $membershipOverrides = []): array {
2740 $membershipTypeID = $membershipOverrides['membership_type_id'] ?? $this->fixtures[$fixture]['membership_type_id'];
2741 if (is_array($membershipTypeID)) {
2742 $membershipTypeID = MembershipType::create()->setValues(array_merge([
2743 'member_of_contact_id' => 1,
2744 'financial_type_id:name' => 'Member Dues',
2745 'name' => 'fixture-created-type',
2746 ], $this->fixtures[$fixture]['membership_type_id']))->execute()->first()['id'];
2747 }
2748 $params = array_merge($this->fixtures[$fixture], [
2749 'sequential' => 1,
2750 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', $status),
2751 'membership_type_id' => $membershipTypeID,
2752 ], $membershipOverrides);
2753 if (empty($params['contact_id'])) {
2754 $params['contact_id'] = $this->individualCreate(['email' => '']);
2755 }
2756 $membership = (array) $this->callAPISuccess('Membership', 'create', $params)['values'][0];
2757 if ($emailParams) {
2758 Civi\Api4\Email::create(FALSE)->setValues(array_merge([
2759 'contact_id' => $membership['contact_id'],
2760 'location_type_id' => 1,
2761 ], $emailParams))->execute();
2762 }
2763 return $membership;
2764 }
2765
2766 /**
2767 * Create action schedule from defined fixtures.
2768 *
2769 * @param string $fixture
2770 * @param array $extraParams
2771 *
2772 * @throws \CRM_Core_Exception
2773 */
2774 protected function createScheduleFromFixtures(string $fixture, array $extraParams = []): void {
2775 $id = $this->callAPISuccess('ActionSchedule', 'create', array_merge($this->fixtures[$fixture], $extraParams))['id'];
2776 $this->fixtures[$fixture]['action_schedule_id'] = (int) $id;
2777 }
2778
2779 /**
2780 * @param string $activityKey
2781 * @param string $contactKey
2782 *
2783 * @throws \CRM_Core_Exception
2784 */
2785 protected function createActivityAndContactFromFixtures(string $activityKey = 'phone_call', string $contactKey = 'contact'): void {
2786 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures[$activityKey]);
2787 $contact = $this->callAPISuccess('contact', 'create', $this->fixtures[$contactKey]);
2788 $activity->save();
2789
2790 $source = [];
2791 $source['contact_id'] = $contact['id'];
2792 $source['activity_id'] = $activity->id;
2793 $source['record_type_id'] = 2;
2794 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
2795 $activityContact->save();
2796 }
2797
2798 }