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