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