Add effective end and start date, extend unit test
[civicrm-core.git] / tests / phpunit / CRM / Core / BAO / ActionScheduleTest.php
... / ...
CommitLineData
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
12use Civi\Api4\Activity;
13use Civi\Api4\ActivityContact;
14use 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 */
26class 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 $manyTokensTmpl = implode(';;', [
890 $someTokensTmpl,
891 '{contact.email_greeting}',
892 $this->fixtures['contact_custom_token']['token'],
893 ]);
894 // Note: The behavior of domain-tokens on a scheduled reminder is undefined. All we
895 // can really do is check that it has something.
896 $someTokensExpected = 'Churmondleia Ōtākou;;Female;;Female;;[a-zA-Z0-9 ]+;;Phone Call';
897 $manyTokensExpected = sprintf('%s;;Dear Churmondleia;;%s', $someTokensExpected, $this->fixtures['contact_custom_token']['value']);
898
899 // In this example, we use a lot of tokens cutting across multiple components.
900 $cases[0] = [
901 // Schedule definition.
902 [
903 'subject' => "subj $someTokensTmpl",
904 'body_html' => "html $manyTokensTmpl",
905 'body_text' => "text $manyTokensTmpl",
906 ],
907 // Assertions (regex).
908 [
909 'from_name' => '/^FIXME$/',
910 'from_email' => '/^info@EXAMPLE.ORG$/',
911 'subject' => "/^subj $someTokensExpected\$/",
912 'body_html' => "/^html $manyTokensExpected\$/",
913 'body_text' => "/^text $manyTokensExpected\$/",
914 ],
915 ];
916
917 // In this example, we customize the from address.
918 $cases[1] = [
919 // Schedule definition.
920 [
921 'from_name' => 'Bob',
922 'from_email' => 'bob@example.org',
923 ],
924 // Assertions (regex).
925 [
926 'from_name' => '/^Bob$/',
927 'from_email' => '/^bob@example.org$/',
928 ],
929 ];
930
931 // In this example, we auto-convert HTML to text
932 $cases[2] = [
933 // Schedule definition.
934 [
935 'body_html' => '<p>Hello &amp; stuff.</p>',
936 'body_text' => '',
937 ],
938 // Assertions (regex).
939 [
940 'body_html' => '/^' . preg_quote('<p>Hello &amp; stuff.</p>', '/') . '/',
941 'body_text' => '/^' . preg_quote('Hello & stuff.', '/') . '/',
942 ],
943 ];
944
945 // In this example, we autoconvert HTML to text
946 $cases[3] = [
947 // Schedule definition.
948 [
949 'body_html' => '',
950 'body_text' => 'Hello world',
951 ],
952 // Assertions (regex).
953 [
954 'body_html' => '/^--UNDEFINED--$/',
955 'body_text' => '/^Hello world$/',
956 ],
957 ];
958
959 return $cases;
960 }
961
962 /**
963 * This generates a single mailing through the scheduled-reminder
964 * system (using an activity-reminder as a baseline) and
965 * checks that the resulting message satisfies various
966 * regular expressions.
967 *
968 * @param array $schedule
969 * Values to set/override in the schedule.
970 * Ex: array('subject' => 'Hello, {contact.first_name}!').
971 * @param array $patterns
972 * A list of regexes to compare with the actual email.
973 * Ex: array('subject' => '/^Hello, Alice!/').
974 * Keys: subject, body_text, body_html, from_name, from_email.
975 *
976 * @throws \API_Exception
977 * @throws \CRM_Core_Exception
978 * @throws \Civi\API\Exception\UnauthorizedException
979 * @dataProvider mailerExamples
980 */
981 public function testMailer(array $schedule, array $patterns): void {
982 $this->createScheduleFromFixtures('sched_activity_1day', $schedule);
983 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phone_call']);
984 $contact = $this->callAPISuccess('contact', 'create', array_merge(
985 $this->fixtures['contact'],
986 [
987 $this->fixtures['contact_custom_token']['name'] => $this->fixtures['contact_custom_token']['value'],
988 ]
989 ));
990 $activity->save();
991
992 ActivityContact::create(FALSE)->setValues([
993 'contact_id' => $contact['id'],
994 'activity_id' => $activity->id,
995 'record_type_id:name' => 'Activity Source',
996 ])->execute();
997
998 CRM_Utils_Time::setTime('2012-06-14 15:00:00');
999 $this->callAPISuccess('job', 'send_reminder');
1000 $this->mut->assertRecipients([['test-member@example.com']]);
1001 foreach ($this->mut->getAllMessages('ezc') as $message) {
1002 /** @var ezcMail $message */
1003
1004 $messageArray = [];
1005 $messageArray['subject'] = $message->subject;
1006 $messageArray['from_name'] = $message->from->name;
1007 $messageArray['from_email'] = $message->from->email;
1008 $messageArray['body_text'] = '--UNDEFINED--';
1009 $messageArray['body_html'] = '--UNDEFINED--';
1010
1011 foreach ($message->fetchParts() as $part) {
1012 /** @var ezcMailText ezcMailText */
1013 if ($part instanceof ezcMailText && $part->subType === 'html') {
1014 $messageArray['body_html'] = $part->text;
1015 }
1016 if ($part instanceof ezcMailText && $part->subType === 'plain') {
1017 $messageArray['body_text'] = $part->text;
1018 }
1019 }
1020
1021 foreach ($patterns as $field => $pattern) {
1022 $this->assertRegExp($pattern, $messageArray[$field],
1023 "Check that '$field'' matches regex. " . print_r(['expected' => $patterns, 'actual' => $messageArray], 1));
1024 }
1025 }
1026 $this->mut->clearMessages();
1027 }
1028
1029 /**
1030 * Send reminder 1 hour before custom date field
1031 *
1032 * @throws \CRM_Core_Exception
1033 */
1034 public function testReminderWithCustomDateField(): void {
1035 $this->createScheduleFromFixtures('sched_on_custom_date');
1036 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], [$this->fixtures['sched_on_custom_date']['custom_field_name'] => '04/06/2021']));
1037 $this->assertCronRuns([
1038 [
1039 // Before the 24-hour mark, no email
1040 'time' => '2021-04-02 04:00:00',
1041 'recipients' => [],
1042 'subjects' => [],
1043 ],
1044 [
1045 // After the 24-hour mark, an email
1046 'time' => '2021-04-05 23:00:00',
1047 'recipients' => [['test-member@example.com']],
1048 'subjects' => ['Send reminder on custom date field'],
1049 ],
1050 [
1051 // Run cron again; message already sent
1052 'time' => '',
1053 'recipients' => [],
1054 ],
1055 ]);
1056 }
1057
1058 /**
1059 * Test calculated activity schedule.
1060 *
1061 * @throws \API_Exception
1062 * @throws \CRM_Core_Exception
1063 */
1064 public function testActivityDateTimeMatchNonRepeatableSchedule(): void {
1065 $this->createScheduleFromFixtures('sched_activity_1day');
1066
1067 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phone_call']);
1068 $contact = $this->callAPISuccess('contact', 'create', $this->fixtures['contact']);
1069 $activity->subject = 'Test subject for phone_call';
1070 $activity->save();
1071
1072 $source['contact_id'] = $contact['id'];
1073 $source['activity_id'] = $activity->id;
1074 $source['record_type_id'] = 2;
1075 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
1076 $activityContact->save();
1077
1078 $this->assertCronRuns([
1079 [
1080 // Before the 24-hour mark, no email
1081 'time' => '2012-06-14 04:00:00',
1082 'recipients' => [],
1083 'subjects' => [],
1084 ],
1085 [
1086 // After the 24-hour mark, an email
1087 'time' => '2012-06-14 15:00:00',
1088 'recipients' => [['test-member@example.com']],
1089 'subjects' => ['1-Day (non-repeating) (about Phone Call)'],
1090 ],
1091 [
1092 // Run cron again; message already sent
1093 'time' => '',
1094 'recipients' => [],
1095 ],
1096 ]);
1097 $activities = Activity::get(FALSE)
1098 ->setSelect(['details'])
1099 ->addWhere('activity_type_id:name', '=', 'Reminder Sent')
1100 ->addWhere('source_record_id', '=', $activity->id)
1101 ->execute();
1102 foreach ($activities as $activityDetails) {
1103 $this->assertStringContainsString($activity->subject, $activityDetails['details']);
1104 }
1105 }
1106
1107 /**
1108 * Test schedule creation on repeatable schedule.
1109 *
1110 * @throws \CRM_Core_Exception
1111 */
1112 public function testActivityDateTimeMatchRepeatableSchedule(): void {
1113 $this->createScheduleFromFixtures('sched_activity_1day_r');
1114 $this->createActivityAndContactFromFixtures();
1115
1116 $this->assertCronRuns([
1117 [
1118 // Before the 24-hour mark, no email
1119 'time' => '2012-06-14 04:00:00',
1120 'recipients' => [],
1121 'subjects' => [],
1122 ],
1123 [
1124 // After the 24-hour mark, an email
1125 'time' => '2012-06-14 15:00:00',
1126 'recipients' => [['test-member@example.com']],
1127 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1128 ],
1129 [
1130 // Run cron 4 hours later; first message already sent
1131 'time' => '2012-06-14 20:00:00',
1132 'recipients' => [],
1133 'subjects' => [],
1134 ],
1135 [
1136 // Run cron 6 hours later; send second message.
1137 'time' => '2012-06-14 21:00:01',
1138 'recipients' => [['test-member@example.com']],
1139 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1140 ],
1141 ]);
1142 }
1143
1144 /**
1145 * @throws \CRM_Core_Exception
1146 */
1147 public function testActivityDateTimeMatchRepeatableScheduleOnAbsDate(): void {
1148 $this->createScheduleFromFixtures('sched_activity_1day_r_on_abs_date');
1149 $this->createActivityAndContactFromFixtures();
1150
1151 $this->assertCronRuns([
1152 [
1153 // Before the 24-hour mark, no email
1154 'time' => '2012-06-13 04:00:00',
1155 'recipients' => [],
1156 'subjects' => [],
1157 ],
1158 [
1159 // On absolute date set on 2012-06-14
1160 'time' => '2012-06-14 00:00:00',
1161 'recipients' => [['test-member@example.com']],
1162 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1163 ],
1164 [
1165 // Run cron 4 hours later; first message already sent
1166 'time' => '2012-06-14 04:00:00',
1167 'recipients' => [],
1168 'subjects' => [],
1169 ],
1170 [
1171 // Run cron 6 hours later; send second message.
1172 'time' => '2012-06-14 06:00:01',
1173 'recipients' => [['test-member@example.com']],
1174 'subjects' => ['1-Day (repeating) (about Phone Call)'],
1175 ],
1176 ]);
1177 }
1178
1179 /**
1180 * Test event with only an absolute date.
1181 *
1182 * @throws \CRM_Core_Exception
1183 */
1184 public function testEventNameWithAbsoluteDateAndNothingElse(): void {
1185 $participant = $this->createTestObject('CRM_Event_DAO_Participant', array_merge($this->fixtures['participant'], ['status_id' => 1]));
1186 $this->callAPISuccess('Email', 'create', [
1187 'contact_id' => $participant->contact_id,
1188 'email' => 'test-event@example.com',
1189 ]);
1190 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $participant->contact_id]));
1191
1192 $actionSchedule = $this->fixtures['sched_event_name_1day_on_abs_date'];
1193 $actionSchedule['entity_value'] = $participant->event_id;
1194 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
1195
1196 $this->assertCronRuns([
1197 [
1198 // Before the 24-hour mark, no email
1199 'time' => '2012-06-13 04:00:00',
1200 'recipients' => [],
1201 'subjects' => [],
1202 ],
1203 [
1204 // On absolute date set on 2012-06-14
1205 'time' => '2012-06-14 00:00:00',
1206 'recipients' => [['test-event@example.com']],
1207 'subjects' => ['sched_event_name_1day_on_abs_date'],
1208 ],
1209 [
1210 // Run cron 4 hours later; first message already sent
1211 'time' => '2012-06-14 04:00:00',
1212 'recipients' => [],
1213 'subjects' => [],
1214 ],
1215 ]);
1216 }
1217
1218 /**
1219 * For contacts/members which match schedule based on join/start date,
1220 * an email should be sent.
1221 *
1222 * @throws \API_Exception
1223 * @throws \CRM_Core_Exception
1224 * @throws \CiviCRM_API3_Exception
1225 */
1226 public function testMembershipDateMatch(): void {
1227 $contactID = $this->individualCreate(array_merge($this->fixtures['contact'], ['email' => 'test-member@example.com']));
1228 $membershipTypeID = $this->getMembershipTypeID();
1229 $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];
1230 $this->createScheduleFromFixtures('sched_membership_join_2week', ['entity_value' => $membershipTypeID]);
1231
1232 // start_date=2012-03-15 ; schedule is 2 weeks after join_date
1233 $this->assertCronRuns([
1234 [
1235 // Before the 2-week mark, no email.
1236 'time' => '2012-03-28 01:00:00',
1237 'recipients' => [],
1238 'subjects' => [],
1239 ],
1240 [
1241 // After the 2-week mark, send an email.
1242 'time' => '2012-03-29 01:00:00',
1243 'recipients' => [['test-member@example.com']],
1244 'subjects' => ['subject sched_membership_join_2week (joined March 15th, 2012)'],
1245 ],
1246 ]);
1247
1248 $this->createScheduleFromFixtures('sched_membership_start_1week', ['entity_value' => $membership['membership_type_id']]);
1249
1250 // start_date=2012-03-15 ; schedule is 1 weeks after start_date
1251 $this->assertCronRuns([
1252 [
1253 // Before the 2-week mark, no email.
1254 'time' => '2012-03-21 01:00:00',
1255 'recipients' => [],
1256 'subjects' => [],
1257 ],
1258 [
1259 // After the 2-week mark, send an email.
1260 'time' => '2012-03-22 01:00:00',
1261 'recipients' => [['test-member@example.com']],
1262 'subjects' => ['subject sched_membership_start_1week (joined March 15th, 2012)'],
1263 ],
1264 ]);
1265 }
1266
1267 /**
1268 * CRM-21675: Support parent and smart group in 'Limit to' field
1269 *
1270 * @throws \CRM_Core_Exception
1271 * @throws \CiviCRM_API3_Exception
1272 */
1273 public function testScheduleReminderWithParentGroup(): void {
1274 // Contact A with birth-date at '07-07-2005' and gender - Male, later got added in smart group
1275 $this->individualCreate(['birth_date' => '20050707', 'gender_id' => 1, 'email' => 'abc@test.com']);
1276 // Contact B with birth-date at '07-07-2005', later got added in regular group
1277 $contactID2 = $this->individualCreate(['birth_date' => '20050707', 'email' => 'def@test.com'], 1);
1278 // Contact C with birth-date at '07-07-2005', but not included in any group
1279 $this->individualCreate(['birth_date' => '20050707', 'email' => 'ghi@test.com'], 2);
1280
1281 // create regular group and add Contact B to it
1282 $groupID = $this->groupCreate();
1283 $this->callAPISuccess('GroupContact', 'Create', [
1284 'group_id' => $groupID,
1285 'contact_id' => $contactID2,
1286 ]);
1287
1288 // create smart group which will contain all Male contacts
1289 $smartGroupParams = ['form_values' => ['gender_id' => 1]];
1290 $smartGroupID = $this->smartGroupCreate(
1291 $smartGroupParams,
1292 [
1293 'name' => 'new_smart_group',
1294 'title' => 'New Smart Group',
1295 'parents' => [$groupID => 1],
1296 ]
1297 );
1298
1299 $actionScheduleParams = [
1300 'name' => 'sched_contact_birth_day_yesterday',
1301 'title' => 'sched_contact_birth_day_yesterday',
1302 'absolute_date' => '',
1303 'body_html' => '<p>you look like you were born yesterday!</p>',
1304 'body_text' => 'you look like you were born yesterday!',
1305 'end_action' => '',
1306 'end_date' => '',
1307 'end_frequency_interval' => '',
1308 'end_frequency_unit' => '',
1309 'entity_status' => 1,
1310 'entity_value' => 'birth_date',
1311 'limit_to' => 1,
1312 'group_id' => $groupID,
1313 'is_active' => 1,
1314 'is_repeat' => '0',
1315 'mapping_id' => 6,
1316 'msg_template_id' => '',
1317 'recipient' => '2',
1318 'recipient_listing' => '',
1319 'recipient_manual' => '',
1320 'record_activity' => 1,
1321 'repetition_frequency_interval' => '',
1322 'repetition_frequency_unit' => '',
1323 'start_action_condition' => 'after',
1324 'start_action_date' => 'date_field',
1325 'start_action_offset' => '1',
1326 'start_action_unit' => 'day',
1327 'subject' => 'subject sched_contact_birth_day_yesterday',
1328 ];
1329
1330 // Create schedule reminder where parent group ($groupID) is selected to limit recipients,
1331 // which contain a individual contact - $contactID2 and is parent to smart group.
1332 $this->callAPISuccess('ActionSchedule', 'create', $actionScheduleParams);
1333 $this->assertCronRuns([
1334 [
1335 // On the birthday, no email.
1336 'time' => '2005-07-07 01:00:00',
1337 'recipients' => [],
1338 ],
1339 [
1340 // The next day, send an email.
1341 'time' => '2005-07-08 20:00:00',
1342 'recipients' => [
1343 [
1344 'def@test.com',
1345 ],
1346 [
1347 'abc@test.com',
1348 ],
1349 ],
1350 ],
1351 ]);
1352 $this->groupDelete($smartGroupID);
1353 $this->groupDelete($groupID);
1354 }
1355
1356 /**
1357 * Test end date email sent.
1358 *
1359 * For contacts/members which match schedule based on join date,
1360 * an email should be sent.
1361 *
1362 * @throws \API_Exception
1363 * @throws \CRM_Core_Exception
1364 * @throws \CiviCRM_API3_Exception
1365 */
1366 public function testMembershipJoinDateNonMatch(): void {
1367 $this->createMembershipFromFixture('rolling_membership', '', ['email' => 'test-member@example.com']);
1368 // Add an alternative membership type, and only send messages for that type
1369 $extraMembershipType = $this->createTestObject('CRM_Member_DAO_MembershipType', []);
1370 $this->createScheduleFromFixtures('sched_membership_join_2week', ['entity_value' => $extraMembershipType->id]);
1371
1372 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
1373 $this->assertCronRuns([
1374 [
1375 // After the 2-week mark, don't send email because we have different membership type.
1376 'time' => '2012-03-29 01:00:00',
1377 'recipients' => [],
1378 ],
1379 ]);
1380 }
1381
1382 /**
1383 * Test that the first and SECOND notifications are sent out.
1384 *
1385 * @throws \API_Exception
1386 * @throws \CRM_Core_Exception
1387 * @throws \CiviCRM_API3_Exception
1388 */
1389 public function testMembershipEndDateRepeat(): void {
1390 // creates membership with end_date = 20120615
1391 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1392 $this->callAPISuccess('Email', 'create', [
1393 'contact_id' => $membership['contact_id'],
1394 'email' => 'test-member@example.com',
1395 ]);
1396 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1397
1398 $this->createScheduleFromFixtures('sched_membership_end_2month_repeat_twice_4_weeks', ['entity_value' => $membership['membership_type_id']]);
1399
1400 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1401 $this->assertCronRuns([
1402 [
1403 // After the 1-month mark, no email
1404 'time' => '2012-07-15 01:00:00',
1405 'recipients' => [],
1406 ],
1407 [
1408 // After the 2-month mark, send an email.
1409 'time' => '2012-08-15 01:00:00',
1410 'recipients' => [['test-member@example.com']],
1411 ],
1412 [
1413 // 4 weeks after first email send first repeat
1414 'time' => '2012-09-12 01:00:00',
1415 'recipients' => [['test-member@example.com']],
1416 ],
1417 [
1418 // 1 week after first repeat send nothing
1419 // There was a bug where the first repeat went out and then
1420 // it would keep going out every cron run. This is to check that's
1421 // not happening.
1422 'time' => '2012-09-19 01:00:00',
1423 'recipients' => [],
1424 ],
1425 [
1426 // 4 weeks after first repeat send second repeat
1427 'time' => '2012-10-10 01:00:00',
1428 'recipients' => [['test-member@example.com']],
1429 ],
1430 [
1431 // 4 months after membership end, send nothing
1432 'time' => '2012-10-15 01:00:00',
1433 'recipients' => [],
1434 ],
1435 [
1436 // 5 months after membership end, send nothing
1437 'time' => '2012-11-15 01:00:00',
1438 'recipients' => [],
1439 ],
1440 ]);
1441 }
1442
1443 /**
1444 * Test behaviour when date changes.
1445 *
1446 * Test that the first notification is sent but the second is NOT sent if the end date changes in
1447 * between
1448 * see CRM-15376
1449 *
1450 * @throws \API_Exception
1451 * @throws \CRM_Core_Exception
1452 * @throws \CiviCRM_API3_Exception
1453 */
1454 public function testMembershipEndDateRepeatChangedEndDate_CRM_15376(): void {
1455 // creates membership with end_date = 20120615
1456 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1457 $this->callAPISuccess('Email', 'create', [
1458 'contact_id' => $membership['contact_id'],
1459 'email' => 'test-member@example.com',
1460 ]);
1461 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1462
1463 $this->createScheduleFromFixtures('sched_membership_end_2month_repeat_twice_4_weeks', ['entity_value' => $membership['membership_type_id']]);
1464 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1465 $this->assertCronRuns([
1466 [
1467 // After the 2-week mark, send an email.
1468 'time' => '2012-08-15 01:00:00',
1469 'recipients' => [['test-member@example.com']],
1470 ],
1471 ]);
1472
1473 // Extend membership - reminder should NOT go out.
1474 $this->callAPISuccess('membership', 'create', ['id' => $membership['id'], 'end_date' => '2014-01-01']);
1475 $this->assertCronRuns([
1476 [
1477 // After the 2-week mark, send an email.
1478 'time' => '2012-09-12 01:00:00',
1479 'recipients' => [],
1480 ],
1481 ]);
1482 }
1483
1484 /**
1485 * Test membership end date email sends.
1486 *
1487 * For contacts/members which match schedule based on end date,
1488 * an email should be sent.
1489 *
1490 * @throws \API_Exception
1491 * @throws \CRM_Core_Exception
1492 * @throws \CiviCRM_API3_Exception
1493 */
1494 public function testMembershipEndDateMatch(): void {
1495 // creates membership with end_date = 20120615
1496 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1497 $this->callAPISuccess('Email', 'create', [
1498 'contact_id' => $membership['contact_id'],
1499 'email' => 'test-member@example.com',
1500 ]);
1501 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership->contact_id]));
1502 $actionScheduleOn['effective_start_date'] = '2012-06-14 00:00:00';
1503 $actionScheduleAfter['effective_end_date'] = '2012-06-15 01:00:00';
1504
1505 $this->createScheduleFromFixtures('sched_membership_end_2week', [
1506 'entity_value' => $membership->membership_type_id,
1507 'effective_start_date' => '2012-06-14 00:00:00',
1508 'effective_end_date' => '2012-06-15 01:00:00',
1509 ]);
1510
1511 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1512 $this->assertCronRuns([
1513 [
1514 // Before the 2-week mark, no email.
1515 'time' => '2012-05-31 01:00:00',
1516 'recipients' => [],
1517 ],
1518 [
1519 // After the 2-week mark, send an email.
1520 'time' => '2012-06-01 01:00:00',
1521 'recipients' => [['test-member@example.com']],
1522 ],
1523 [
1524 // After the email is sent, another one is not sent
1525 'time' => '2012-06-01 02:00:00',
1526 'recipients' => [],
1527 ],
1528 ]);
1529
1530 // Now suppose user has renewed for rolling membership after 3 months, so upcoming assertion is written
1531 // to ensure that new reminder is sent 2 week before the new end_date i.e. '2012-09-15'
1532 $membershipBAO = new CRM_Member_BAO_Membership();
1533 $membershipBAO->id = $membership['id'];
1534 $membershipBAO->end_date = '2012-09-15';
1535 $membershipBAO->save();
1536
1537 //change the email id of chosen membership contact to assert
1538 //recipient of not the previously sent mail but the new one
1539 $result = $this->callAPISuccess('Email', 'create', [
1540 'is_primary' => 1,
1541 'contact_id' => $membership['contact_id'],
1542 'email' => 'member2@example.com',
1543 ]);
1544 $this->assertAPISuccess($result);
1545
1546 // end_date=2012-09-15 ; schedule is 2 weeks before end_date
1547 $this->assertCronRuns([
1548 [
1549 // Before the 2-week mark, no email
1550 'time' => '2012-08-31 01:00:00',
1551 'recipients' => [],
1552 ],
1553 [
1554 // After the 2-week mark, send an email
1555 'time' => '2012-09-01 01:00:00',
1556 'recipients' => [['member2@example.com']],
1557 ],
1558 [
1559 // After the email is sent, another one is not sent
1560 'time' => '2012-09-01 02:00:00',
1561 'recipients' => [],
1562 ],
1563 ]);
1564 $membershipBAO = new CRM_Member_BAO_Membership();
1565 $membershipBAO->id = $membership['id'];
1566 $membershipBAO->end_date = '2012-12-15';
1567 $membershipBAO->save();
1568 // end_date=2012-12-15 ; schedule is 2 weeks before end_date
1569 $this->assertCronRuns([
1570 [
1571 // Before the 2-week mark, no email
1572 'time' => '2012-11-30 01:00:00',
1573 'recipients' => [],
1574 ],
1575 [
1576 // After the 2-week mark, send an email
1577 'time' => '2012-12-01 01:00:00',
1578 'recipients' => [['member2@example.com']],
1579 ],
1580 [
1581 // After the email is sent, another one is not sent
1582 'time' => '2012-12-01 02:00:00',
1583 'recipients' => [],
1584 ],
1585 ]);
1586
1587 }
1588
1589 /**
1590 * This test is very similar to testMembershipEndDateMatch, but it adds
1591 * another contact because there was a bug in
1592 * RecipientBuilder::buildRelFirstPass where it was only sending the
1593 * reminder for the first contact returned in a query for renewed
1594 * memberships. Other contacts wouldn't get the mail.
1595 *
1596 * @throws \API_Exception
1597 * @throws \CRM_Core_Exception
1598 * @throws \CiviCRM_API3_Exception
1599 */
1600 public function testMultipleMembershipEndDateMatch(): void {
1601 $contactID = $this->callAPISuccess('Contact', 'create', array_merge($this->fixtures['contact'], ['email' => 'test-member@example.com']))['id'];
1602 $contactID2 = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_2'])['id'];
1603 $membershipOne = $this->createMembershipFromFixture('rolling_membership', 2, [], ['contact_id' => $contactID]);
1604 $membershipTypeId = $membershipOne['membership_type_id'];
1605 $membershipTwo = $this->createMembershipFromFixture('rolling_membership', 2, [], ['contact_id' => $contactID2, 'membership_type_id' => $membershipTypeId]);
1606 // We are using dates that 'should' be expired but the test expects them not to be
1607 CRM_Core_DAO::executeQuery('UPDATE civicrm_membership SET status_id = 2 WHERE 1');
1608 $this->createScheduleFromFixtures('sched_membership_end_2week', ['entity_value' => $membershipTypeId]);
1609
1610 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1611 $this->assertCronRuns([
1612 [
1613 // Before the 2-week mark, no email.
1614 'time' => '2012-05-31 01:00:00',
1615 'recipients' => [],
1616 ],
1617 [
1618 // After the 2-week mark, send emails.
1619 'time' => '2012-06-01 01:00:00',
1620 'recipients' => [
1621 ['test-member@example.com'],
1622 ['test-contact-2@example.com'],
1623 ],
1624 ],
1625 [
1626 // After the email is sent, another one is not sent
1627 'time' => '2012-06-01 02:00:00',
1628 'recipients' => [],
1629 ],
1630 ]);
1631
1632 // Now suppose user has renewed for rolling membership after 3 months, so upcoming assertion is written
1633 // to ensure that new reminder is sent 2 week before the new end_date i.e. '2012-09-15'
1634 $membershipOneBAO = new CRM_Member_BAO_Membership();
1635 $membershipOneBAO->id = $membershipOne['id'];
1636 $membershipOneBAO->end_date = '2012-09-15';
1637 $membershipOneBAO->save();
1638 $membershipTwoBAO = new CRM_Member_BAO_Membership();
1639 $membershipTwoBAO->id = $membershipTwo['id'];
1640 $membershipTwoBAO->end_date = '2012-09-15';
1641 $membershipTwoBAO->save();
1642
1643 // end_date=2012-09-15 ; schedule is 2 weeks before end_date
1644 $this->assertCronRuns([
1645 [
1646 // Before the 2-week mark, no email
1647 'time' => '2012-08-31 01:00:00',
1648 'recipients' => [],
1649 ],
1650 [
1651 // After the 2-week mark, send an email
1652 'time' => '2012-09-01 01:00:00',
1653 'recipients' => [
1654 ['test-member@example.com'],
1655 ['test-contact-2@example.com'],
1656 ],
1657 ],
1658 [
1659 // After the email is sent, another one is not sent
1660 'time' => '2012-06-01 02:00:00',
1661 'recipients' => [],
1662 ],
1663 ]);
1664 }
1665
1666 /**
1667 * Test membership end date email.
1668 *
1669 * For contacts/members which match schedule based on end date,
1670 * an email should be sent.
1671 *
1672 * @throws \API_Exception
1673 * @throws \CRM_Core_Exception
1674 * @throws \CiviCRM_API3_Exception
1675 */
1676 public function testMembershipEndDateNoMatch(): void {
1677 // creates membership with end_date = 20120615
1678 $membership = $this->createMembershipFromFixture('rolling_membership', 'Grace');
1679 $this->callAPISuccess('Email', 'create', [
1680 'contact_id' => $membership['contact_id'],
1681 'email' => 'test-member@example.com',
1682 ]);
1683 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1684 $this->createScheduleFromFixtures('sched_membership_end_2month', ['entity_value' => $membership['membership_type_id']]);
1685
1686 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1687 $this->assertCronRuns([
1688 [
1689 // Before the 2-week mark, no email.
1690 'time' => '2012-05-31 01:00:00',
1691 'recipients' => [],
1692 ],
1693 [
1694 // After the 2-week mark, no email
1695 'time' => '2013-05-01 01:00:00',
1696 'recipients' => [],
1697 ],
1698 ]);
1699 }
1700
1701 /**
1702 * @throws \CRM_Core_Exception
1703 */
1704 public function testContactBirthDateNoAnniversary(): void {
1705 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1706 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1707 $this->createScheduleFromFixtures('sched_contact_birth_day_yesterday');
1708 $this->assertCronRuns([
1709 [
1710 // On the birthday, no email.
1711 'time' => '2005-07-07 01:00:00',
1712 'recipients' => [],
1713 ],
1714 [
1715 // The next day, send an email.
1716 'time' => '2005-07-08 20:00:00',
1717 'recipients' => [['test-birth_day@example.com']],
1718 ],
1719 ]);
1720 }
1721
1722 /**
1723 * @throws \CRM_Core_Exception
1724 */
1725 public function testContactBirthDateAnniversary(): void {
1726 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1727 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1728 $this->createScheduleFromFixtures('sched_contact_birth_day_anniversary');
1729 $this->assertCronRuns([
1730 [
1731 // On some random day, no email.
1732 'time' => '2014-03-07 01:00:00',
1733 'recipients' => [],
1734 ],
1735 [
1736 // On the eve of their 9th birthday, send an email.
1737 'time' => '2014-07-06 20:00:00',
1738 'recipients' => [['test-birth_day@example.com']],
1739 ],
1740 ]);
1741 }
1742
1743 /**
1744 * @throws \CRM_Core_Exception
1745 */
1746 public function testContactCustomDateNoAnniversary(): void {
1747 $group = [
1748 'title' => 'Test_Group',
1749 'name' => 'test_group',
1750 'extends' => ['Individual'],
1751 'style' => 'Inline',
1752 'is_multiple' => FALSE,
1753 'is_active' => 1,
1754 ];
1755 $createGroup = $this->callAPISuccess('custom_group', 'create', $group);
1756 $field = [
1757 'label' => 'Graduation',
1758 'data_type' => 'Date',
1759 'html_type' => 'Select Date',
1760 'custom_group_id' => $createGroup['id'],
1761 ];
1762 $createField = $this->callAPISuccess('custom_field', 'create', $field);
1763 $contactParams = $this->fixtures['contact'];
1764 $contactParams["custom_{$createField['id']}"] = '2013-12-16';
1765 $contact = $this->callAPISuccess('Contact', 'create', $contactParams);
1766 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1767 $this->createScheduleFromFixtures('sched_contact_grad_tomorrow', ['entity_value' => "custom_{$createField['id']}"]);
1768 $this->assertCronRuns([
1769 [
1770 // On some random day, no email.
1771 'time' => '2014-03-07 01:00:00',
1772 'recipients' => [],
1773 ],
1774 [
1775 // On the eve of their graduation, send an email.
1776 'time' => '2013-12-15 20:00:00',
1777 'recipients' => [['test-member@example.com']],
1778 ],
1779 ]);
1780 $this->callAPISuccess('custom_group', 'delete', ['id' => $createGroup['id']]);
1781 }
1782
1783 /**
1784 * @throws \CRM_Core_Exception
1785 */
1786 public function testContactCreatedNoAnniversary(): void {
1787 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1788 $this->createScheduleFromFixtures('sched_contact_created_yesterday');
1789 $this->assertCronRuns([
1790 [
1791 // On the date created, no email.
1792 'time' => $contact['values'][$contact['id']]['created_date'],
1793 'recipients' => [],
1794 ],
1795 [
1796 // The next day, send an email.
1797 'time' => date('Y-m-d H:i:s', strtotime($contact['values'][$contact['id']]['created_date'] . ' +1 day')),
1798 'recipients' => [['test-birth_day@example.com'], ['fixme.domainemail@example.org'], ['domainemail2@example.org']],
1799 ],
1800 ]);
1801 }
1802
1803 /**
1804 * Test the impact of changing the anniversary.
1805 *
1806 * @throws \CRM_Core_Exception
1807 */
1808 public function testContactModifiedAnniversary(): void {
1809 $contact = $this->callAPISuccess('Contact', 'create', $this->fixtures['contact_birthdate']);
1810 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
1811 $modifiedDate = $this->callAPISuccess('Contact', 'getvalue', ['id' => $contact['id'], 'return' => 'modified_date']);
1812 $actionSchedule = $this->createScheduleFromFixtures('sched_contact_mod_anniversary');
1813 $actionSchedule['effective_start_date'] = date('Y-m-d H:i:s', strtotime($contact['values'][$contact['id']]['modified_date']));
1814 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule);
1815 $this->assertCronRuns([
1816 [
1817 // On some random day, no email.
1818 'time' => date('Y-m-d H:i:s', strtotime($contact['values'][$contact['id']]['modified_date'] . ' -60 days')),
1819 'recipients' => [],
1820 ],
1821 [
1822 // On the eve of 3 years after they were modified, send an email.
1823 'time' => date('Y-m-d H:i:s', strtotime($modifiedDate . ' +3 years -1 day')),
1824 'recipients' => [['test-birth_day@example.com'], ['fixme.domainemail@example.org'], ['domainemail2@example.org']],
1825 ],
1826 ]);
1827 }
1828
1829 /**
1830 * Check that limit_to + an empty recipients doesn't sent to multiple
1831 * contacts.
1832 *
1833 * @throws \API_Exception
1834 * @throws \CRM_Core_Exception
1835 * @throws \CiviCRM_API3_Exception
1836 */
1837 public function testMembershipLimitToNone(): void {
1838 // creates membership with end_date = 20120615
1839 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current');
1840 $result = $this->callAPISuccess('Email', 'create', [
1841 'contact_id' => $membership['contact_id'],
1842 'email' => 'member@example.com',
1843 ]);
1844 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1845 $this->callAPISuccess('contact', 'create', ['email' => 'b@c.com', 'contact_type' => 'Individual']);
1846
1847 $this->assertAPISuccess($result);
1848
1849 $this->createScheduleFromFixtures('sched_membership_end_limit_to_none', ['entity_value' => $membership['membership_type_id']]);
1850
1851 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
1852 $this->assertCronRuns([
1853 [
1854 // Before the 2-week mark, no email.
1855 'time' => '2012-05-31 01:00:00',
1856 'recipients' => [],
1857 ],
1858 ]);
1859 }
1860
1861 /**
1862 * Test handling of reference date for memberships.
1863 *
1864 * @throws \API_Exception
1865 * @throws \CRM_Core_Exception
1866 * @throws \CiviCRM_API3_Exception
1867 */
1868 public function testMembershipWithReferenceDate(): void {
1869 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current', ['email' => 'member@example.com']);
1870 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1871
1872 $this->createScheduleFromFixtures('sched_membership_join_2week', ['entity_value' => $membership['membership_type_id']]);
1873
1874 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
1875 $this->assertCronRuns([
1876 [
1877 // After the 2-week mark, send an email
1878 'time' => '2012-03-29 01:00:00',
1879 'recipients' => [['member@example.com']],
1880 ],
1881 [
1882 // After the 2-week 1day mark, don't send an email
1883 'time' => '2012-03-30 01:00:00',
1884 'recipients' => [],
1885 ],
1886 ]);
1887
1888 //check if reference date is set to membership's join date
1889 //as per the action_start_date chosen for current schedule reminder
1890 $this->assertEquals('2012-03-15 00:00:00',
1891 CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $membership['contact_id'], 'reference_date', 'contact_id')
1892 );
1893
1894 //change current membership join date that may signifies as membership renewal activity
1895 $membershipBAO = new CRM_Member_BAO_Membership();
1896 $membershipBAO->id = $membership['id'];
1897 $membershipBAO->join_date = '2012-03-29';
1898 $membershipBAO->save();
1899
1900 $this->assertCronRuns([
1901 [
1902 // After the 13 days of the changed join date 2012-03-29, don't send an email
1903 'time' => '2012-04-11 01:00:00',
1904 'recipients' => [],
1905 ],
1906 [
1907 // After the 2-week of the changed join date 2012-03-29, send an email
1908 'time' => '2012-04-12 01:00:00',
1909 'recipients' => [['member@example.com']],
1910 ],
1911 ]);
1912 $this->assertCronRuns([
1913 [
1914 // It should not re-send on the same day
1915 'time' => '2012-04-12 01:00:00',
1916 'recipients' => [],
1917 ],
1918 ]);
1919 }
1920
1921 /**
1922 * Test multiple membership reminder.
1923 *
1924 * @throws \API_Exception
1925 * @throws \CRM_Core_Exception
1926 * @throws \CiviCRM_API3_Exception
1927 */
1928 public function testMembershipOnMultipleReminder(): void {
1929 $membership = $this->createMembershipFromFixture('rolling_membership', 'Current', ['email' => 'member@example.com']);
1930 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
1931
1932 // Send email 2 weeks before end_date
1933 $actionScheduleBefore = $this->fixtures['sched_membership_end_2week'];
1934 // Send email on end_date/expiry date
1935 $actionScheduleOn = $this->fixtures['sched_on_membership_end_date'];
1936 $actionScheduleOn['effective_start_date'] = '2012-06-14 00:00:00';
1937 $actionScheduleAfter['effective_end_date'] = '2012-06-15 01:00:00';
1938 // Send email 1 day after end_date/grace period
1939 $actionScheduleAfter = $this->fixtures['sched_after_1day_membership_end_date'];
1940 $actionScheduleAfter['effective_start_date'] = '2012-06-15 01:00:00';
1941 $actionScheduleAfter['effective_end_date'] = '2012-06-16 02:00:00';
1942 $actionScheduleBefore['entity_value'] = $actionScheduleOn['entity_value'] = $actionScheduleAfter['entity_value'] = $membership->membership_type_id;
1943 foreach (['actionScheduleBefore', 'actionScheduleOn', 'actionScheduleAfter'] as $value) {
1944 $$value = CRM_Core_BAO_ActionSchedule::add($$value);
1945 }
1946
1947 $this->assertCronRuns(
1948 [
1949 [
1950 // 1day 2weeks before membership end date(MED), don't send mail
1951 'time' => '2012-05-31 01:00:00',
1952 'recipients' => [],
1953 ],
1954 [
1955 // 2 weeks before MED, send an email
1956 'time' => '2012-06-01 01:00:00',
1957 'recipients' => [['member@example.com']],
1958 ],
1959 [
1960 // 1day before MED, don't send mail
1961 'time' => '2012-06-14 01:00:00',
1962 'recipients' => [],
1963 ],
1964 [
1965 // On MED, send an email
1966 'time' => '2012-06-15 00:00:00',
1967 'recipients' => [['member@example.com']],
1968 ],
1969 [
1970 // After 1day of MED, send an email
1971 'time' => '2012-06-16 01:00:00',
1972 'recipients' => [['member@example.com']],
1973 ],
1974 [
1975 // After 1day 1min of MED, don't send an email
1976 'time' => '2012-06-17 00:01:00',
1977 'recipients' => [],
1978 ],
1979 ]
1980 );
1981
1982 // Assert the timestamp as of when the emails of respective three reminders as configured
1983 // 2 weeks before, on and 1 day after MED, are sent
1984 $this->assertApproxEquals(
1985 strtotime('2012-06-01 01:00:00'),
1986 strtotime(CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleBefore->id, 'action_date_time', 'action_schedule_id', TRUE)),
1987 // Variation in test execution time.
1988 3
1989 );
1990 $this->assertApproxEquals(
1991 strtotime('2012-06-15 00:00:00'),
1992 strtotime(CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleOn->id, 'action_date_time', 'action_schedule_id', TRUE)),
1993 // Variation in test execution time.
1994 3
1995 );
1996 $this->assertApproxEquals(
1997 strtotime('2012-06-16 01:00:00'),
1998 strtotime(CRM_Core_DAO::getFieldValue('CRM_Core_DAO_ActionLog', $actionScheduleAfter->id, 'action_date_time', 'action_schedule_id', TRUE)),
1999 // Variation in test execution time.
2000 3
2001 );
2002
2003 //extend MED to 2 weeks after the current MED (that may signifies as membership renewal activity)
2004 // and lets assert as of when the new set of reminders will be sent against their respective Schedule Reminders(SR)
2005 $membershipBAO = new CRM_Member_BAO_Membership();
2006 $membershipBAO->id = $membership['id'];
2007 $membershipBAO->end_date = '2012-06-20';
2008 $membershipBAO->save();
2009
2010 $this->callAPISuccess('Contact', 'get', ['id' => $membership['contact_id']]);
2011 $this->assertCronRuns(
2012 [
2013 [
2014 // 1day 2weeks before membership end date(MED), don't send mail
2015 'time' => '2012-06-05 01:00:00',
2016 'recipients' => [],
2017 ],
2018 [
2019 // 2 weeks before MED, send an email
2020 'time' => '2012-06-06 01:00:00',
2021 'recipients' => [['member@example.com']],
2022 ],
2023 [
2024 // 1day before MED, don't send mail
2025 'time' => '2012-06-19 01:00:00',
2026 'recipients' => [],
2027 ],
2028 [
2029 // On MED, send an email
2030 'time' => '2012-06-20 00:00:00',
2031 'recipients' => [['member@example.com']],
2032 ],
2033 [
2034 // After 1day of MED, send an email
2035 'time' => '2012-06-21 01:00:00',
2036 'recipients' => [['member@example.com']],
2037 ],
2038 [
2039 // After 1day 1min of MED, don't send an email
2040 'time' => '2012-07-21 00:01:00',
2041 'recipients' => [],
2042 ],
2043 ]);
2044 }
2045
2046 /**
2047 * Test reminders sent on custom data anniversary.
2048 *
2049 * @throws \API_Exception
2050 * @throws \CRM_Core_Exception
2051 */
2052 public function testContactCustomDate_Anniversary(): void {
2053 $this->createCustomGroupWithFieldOfType([], 'date');
2054 $contactParams = $this->fixtures['contact'];
2055 $contactParams[$this->getCustomFieldName('date')] = '2013-12-16';
2056 $contact = $this->callAPISuccess('Contact', 'create', $contactParams);
2057 $this->_testObjects['CRM_Contact_DAO_Contact'][] = $contact['id'];
2058 $this->fixtures['sched_contact_grad_anniversary']['entity_value'] = $this->getCustomFieldName('date');
2059 $this->createScheduleFromFixtures('sched_contact_grad_anniversary');
2060
2061 $this->assertCronRuns([
2062 [
2063 // On some random day, no email.
2064 'time' => '2014-03-07 01:00:00',
2065 'recipients' => [],
2066 ],
2067 [
2068 // A week after their 5th anniversary of graduation, send an email.
2069 'time' => '2018-12-23 20:00:00',
2070 'recipients' => [['test-member@example.com']],
2071 ],
2072 ]);
2073 }
2074
2075 /**
2076 * Test sched reminder set via registration date.
2077 *
2078 * @throws \CRM_Core_Exception
2079 * @throws \CiviCRM_API3_Exception
2080 */
2081 public function testEventTypeRegistrationDate(): void {
2082 $contact = $this->individualCreate(['email' => 'test-event@example.com']);
2083 //Add it as a participant to an event ending registration - 7 days from now.
2084 $params = [
2085 'start_date' => date('Ymd', strtotime('-5 day')),
2086 'end_date' => date('Ymd', strtotime('+7 day')),
2087 'registration_start_date' => date('Ymd', strtotime('-5 day')),
2088 'registration_end_date' => date('Ymd', strtotime('+7 day')),
2089 ];
2090 $event = $this->eventCreate($params);
2091 $this->participantCreate(['contact_id' => $contact, 'event_id' => $event['id']]);
2092
2093 //Create a scheduled reminder to send email 7 days before registration date.
2094 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2095 $actionSchedule['start_action_offset'] = 7;
2096 $actionSchedule['start_action_unit'] = 'day';
2097 $actionSchedule['start_action_date'] = 'registration_end_date';
2098 $actionSchedule['entity_value'] = $event['values'][$event['id']]['event_type_id'];
2099 $actionSchedule['entity_status'] = $this->callAPISuccessGetValue('ParticipantStatusType', [
2100 'return' => 'id',
2101 'name' => 'Attended',
2102 ]);
2103 $actionSched = $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2104 //Run the cron and verify if an email was sent.
2105 $this->assertCronRuns([
2106 [
2107 'time' => date('Y-m-d'),
2108 'recipients' => [['test-event@example.com']],
2109 ],
2110 ]);
2111
2112 //Create contact 2
2113 $contactParams = [
2114 'email' => 'test-event2@example.com',
2115 ];
2116 $contact2 = $this->individualCreate($contactParams);
2117 //Create an event with registration end date = 2 week from now.
2118 $params['end_date'] = date('Ymd', strtotime('+2 week'));
2119 $params['registration_end_date'] = date('Ymd', strtotime('+2 week'));
2120 $event2 = $this->eventCreate($params);
2121 $this->participantCreate(['contact_id' => $contact2, 'event_id' => $event2['id']]);
2122
2123 //Assert there is no reminder sent to the contact.
2124 $this->assertCronRuns([
2125 [
2126 'time' => date('Y-m-d'),
2127 'recipients' => [],
2128 ],
2129 ]);
2130
2131 //Modify the sched reminder to be sent 2 week from registration end date.
2132 $this->callAPISuccess('action_schedule', 'create', [
2133 'id' => $actionSched['id'],
2134 'start_action_offset' => 2,
2135 'start_action_unit' => 'week',
2136 ]);
2137
2138 //Contact should receive the reminder now.
2139 $this->assertCronRuns([
2140 [
2141 'time' => date('Y-m-d'),
2142 'recipients' => [['test-event2@example.com']],
2143 ],
2144 ]);
2145 }
2146
2147 /**
2148 * Test sched reminder set via start date.
2149 *
2150 * @throws \CRM_Core_Exception
2151 * @throws \CiviCRM_API3_Exception
2152 */
2153 public function testEventTypeStartDate(): void {
2154 // Create event+participant with start_date = 20120315, end_date = 20120615.
2155 $params = $this->fixtures['participant'];
2156 $params['event_id'] = $this->callAPISuccess('Event', 'create', array_merge($this->fixtures['participant']['event_id'], ['event_type_id' => 1]))['id'];
2157 $params['status_id'] = 2;
2158 $params['contact_id'] = $this->individualCreate(array_merge($this->fixtures['contact'], ['email' => 'test-event@example.com']));
2159 $this->callAPISuccess('Participant', 'create', $params);
2160
2161 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2162 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $params['event_id'], 'event_type_id');
2163 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2164
2165 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
2166 $this->assertCronRuns([
2167 [
2168 // 2 weeks before
2169 'time' => '2012-03-02 01:00:00',
2170 'recipients' => [],
2171 ],
2172 [
2173 // 1 week before
2174 'time' => '2012-03-08 01:00:00',
2175 'recipients' => [['test-event@example.com']],
2176 ],
2177 [
2178 // And then nothing else
2179 'time' => '2012-03-16 01:00:00',
2180 'recipients' => [],
2181 ],
2182 ]);
2183
2184 // CASE 2: Create a schedule reminder which was created 1 day after the schdule day,
2185 // so it shouldn't deliver reminders schedule to send 1 week before the event start date
2186 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2187 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $participant->event_id, 'event_type_id');
2188 $actionSchedule['effective_start_date'] = '20120309000000';
2189 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2190 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
2191 $this->assertCronRuns([
2192 [
2193 // 2 weeks before
2194 'time' => '2012-03-02 01:00:00',
2195 'recipients' => [],
2196 ],
2197 [
2198 // 1 week before
2199 'time' => '2012-03-08 01:00:00',
2200 'recipients' => [],
2201 ],
2202 [
2203 // And then nothing else
2204 'time' => '2012-03-16 01:00:00',
2205 'recipients' => [],
2206 ],
2207 ]);
2208
2209 // CASE 3: Create a schedule reminder which is created less then a week before the event start date,
2210 // 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
2211 $actionSchedule = $this->fixtures['sched_event_type_start_1week_before'];
2212 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $participant->event_id, 'event_type_id');
2213 $actionSchedule['effective_end_date'] = '20120309010000';
2214 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2215 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
2216 $this->assertCronRuns([
2217 [
2218 // 2 weeks before
2219 'time' => '2012-03-02 01:00:00',
2220 'recipients' => [],
2221 ],
2222 [
2223 // 1 week before
2224 'time' => '2012-03-08 01:00:00',
2225 'recipients' => [['test-event@example.com']],
2226 ],
2227 [
2228 // And then nothing else
2229 'time' => '2012-03-16 01:00:00',
2230 'recipients' => [],
2231 ],
2232 ]);
2233 }
2234
2235 /**
2236 * Test schedule on event end date.
2237 *
2238 * @throws \CRM_Core_Exception
2239 */
2240 public function testEventTypeEndDateRepeat(): void {
2241 // Create event+participant with start_date = 20120315, end_date = 20120615.
2242 $participant = $this->createTestObject('CRM_Event_DAO_Participant', array_merge($this->fixtures['participant'], ['status_id' => 2]));
2243 $this->callAPISuccess('Email', 'create', [
2244 'contact_id' => $participant->contact_id,
2245 'email' => 'test-event@example.com',
2246 ]);
2247 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $participant->contact_id]));
2248
2249 $actionSchedule = $this->fixtures['sched_event_type_end_2month_repeat_twice_2_weeks'];
2250 $actionSchedule['entity_value'] = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $participant->event_id, 'event_type_id');
2251 $this->callAPISuccess('action_schedule', 'create', $actionSchedule);
2252
2253 $this->assertCronRuns([
2254 [
2255 // Almost 2 months.
2256 'time' => '2012-08-13 01:00:00',
2257 'recipients' => [],
2258 ],
2259 [
2260 // After the 2-month mark, send an email.
2261 'time' => '2012-08-16 01:00:00',
2262 'recipients' => [['test-event@example.com']],
2263 ],
2264 [
2265 // After 2 months and 1 week, don't repeat yet.
2266 'time' => '2012-08-23 02:00:00',
2267 'recipients' => [],
2268 ],
2269 [
2270 // After 2 months and 2 weeks
2271 'time' => '2012-08-30 02:00:00',
2272 'recipients' => [['test-event@example.com']],
2273 ],
2274 [
2275 // After 2 months and 4 week
2276 'time' => '2012-09-13 02:00:00',
2277 'recipients' => [['test-event@example.com']],
2278 ],
2279 [
2280 // After 2 months and 6 weeks
2281 'time' => '2012-09-27 01:00:00',
2282 'recipients' => [],
2283 ],
2284 ]);
2285 }
2286
2287 /**
2288 * Run a series of cron jobs and make an assertion about email deliveries.
2289 *
2290 * @param array $cronRuns
2291 * array specifying when to run cron and what messages to expect; each item is an array with keys:
2292 * - time: string, e.g. '2012-06-15 21:00:01'
2293 * - recipients: array(array(string)), list of email addresses which should receive messages
2294 *
2295 * @throws \CRM_Core_Exception
2296 * @noinspection DisconnectedForeachInstructionInspection
2297 */
2298 public function assertCronRuns(array $cronRuns): void {
2299 foreach ($cronRuns as $cronRun) {
2300 CRM_Utils_Time::setTime($cronRun['time']);
2301 $this->callAPISuccess('job', 'send_reminder', []);
2302 $this->mut->assertRecipients($cronRun['recipients']);
2303 if (array_key_exists('subjects', $cronRun)) {
2304 $this->mut->assertSubjects($cronRun['subjects']);
2305 }
2306 $this->mut->clearMessages();
2307 }
2308 }
2309
2310 /**
2311 * @var array
2312 *
2313 * (DAO_Name => array(int)) List of items to garbage-collect during tearDown
2314 */
2315 private $_testObjects = [];
2316
2317 /**
2318 * This is a wrapper for CRM_Core_DAO::createTestObject which tracks
2319 * created entities and provides for brainless cleanup.
2320 *
2321 * However, it is only really brainless when initially writing the code.
2322 * It 'steals and deletes entities that are part of the 'stock build'.
2323 *
2324 * In general this causes weird stuff.
2325 *
2326 * @param $daoName
2327 * @param array $params
2328 * @param int $numObjects
2329 * @param bool $createOnly
2330 *
2331 * @return array|NULL|object
2332 * @see CRM_Core_DAO::createTestObject
2333 */
2334 public function createTestObject($daoName, array $params = [], int $numObjects = 1, bool $createOnly = FALSE) {
2335 $objects = CRM_Core_DAO::createTestObject($daoName, $params, $numObjects, $createOnly);
2336 if (is_array($objects)) {
2337 $this->registerTestObjects($objects);
2338 }
2339 else {
2340 $this->registerTestObjects([$objects]);
2341 }
2342 return $objects;
2343 }
2344
2345 /**
2346 * @param array $objects
2347 * DAO or BAO objects.
2348 */
2349 public function registerTestObjects(array $objects): void {
2350 //if (is_object($objects)) {
2351 // $objects = array($objects);
2352 //}
2353 foreach ($objects as $object) {
2354 $daoName = str_replace('_BAO_', '_DAO_', get_class($object));
2355 $this->_testObjects[$daoName][] = $object->id;
2356 }
2357 }
2358
2359 public function deleteTestObjects(): void {
2360 // Note: You might argue that the FK relations between test
2361 // objects could make this problematic; however, it should
2362 // behave intuitively as long as we mentally split our
2363 // test-objects between the "manual/primary records"
2364 // and the "automatic/secondary records"
2365 foreach ($this->_testObjects as $daoName => $daoIds) {
2366 foreach ($daoIds as $daoId) {
2367 CRM_Core_DAO::deleteTestObjects($daoName, ['id' => $daoId]);
2368 }
2369 }
2370 $this->_testObjects = [];
2371 }
2372
2373 /**
2374 * Test that the various repetition units work correctly.
2375 *
2376 * @see https://issues.civicrm.org/jira/browse/CRM-17028
2377 * @throws \CRM_Core_Exception
2378 */
2379 public function testRepetitionFrequencyUnit(): void {
2380 $membershipTypeParams = [
2381 'duration_interval' => '1',
2382 'duration_unit' => 'year',
2383 'is_active' => 1,
2384 'period_type' => 'rolling',
2385 ];
2386 $membershipType = $this->createTestObject('CRM_Member_DAO_MembershipType', $membershipTypeParams);
2387 $interval_units = ['hour', 'day', 'week', 'month', 'year'];
2388 foreach ($interval_units as $interval_unit) {
2389 $membershipEndDate = DateTime::createFromFormat('Y-m-d H:i:s', '2013-03-15 00:00:00');
2390 $contactParams = [
2391 'contact_type' => 'Individual',
2392 'first_name' => 'Test',
2393 'last_name' => "Interval $interval_unit",
2394 'is_deceased' => 0,
2395 ];
2396 $contact = $this->createTestObject('CRM_Contact_DAO_Contact', $contactParams);
2397 $emailParams = [
2398 'contact_id' => $contact->id,
2399 'is_primary' => 1,
2400 'email' => "test-member-$interval_unit@example.com",
2401 'location_type_id' => 1,
2402 ];
2403 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2404 $membershipParams = [
2405 'membership_type_id' => $membershipType->id,
2406 'contact_id' => $contact->id,
2407 'join_date' => '20120315',
2408 'start_date' => '20120315',
2409 'end_date' => '20130315',
2410 'is_override' => 0,
2411 'status_id' => 2,
2412 ];
2413 $membershipParams['status-id'] = 1;
2414 $membership = $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2415 $actionScheduleParams = $this->fixtures['sched_on_membership_end_date_repeat_interval'];
2416 $actionScheduleParams['entity_value'] = $membershipType->id;
2417 $actionScheduleParams['repetition_frequency_unit'] = $interval_unit;
2418 $actionScheduleParams['repetition_frequency_interval'] = 2;
2419 $actionSchedule = CRM_Core_BAO_ActionSchedule::add($actionScheduleParams);
2420 $beforeEndDate = $this->createModifiedDateTime($membershipEndDate, '-1 day');
2421 $beforeFirstUnit = $this->createModifiedDateTime($membershipEndDate, "+1 $interval_unit");
2422 $afterFirstUnit = $this->createModifiedDateTime($membershipEndDate, "+2 $interval_unit");
2423 $cronRuns = [
2424 [
2425 'time' => $beforeEndDate->format('Y-m-d H:i:s'),
2426 'recipients' => [],
2427 ],
2428 [
2429 'time' => $membershipEndDate->format('Y-m-d H:i:s'),
2430 'recipients' => [["test-member-$interval_unit@example.com"]],
2431 ],
2432 [
2433 'time' => $beforeFirstUnit->format('Y-m-d H:i:s'),
2434 'recipients' => [],
2435 ],
2436 [
2437 'time' => $afterFirstUnit->format('Y-m-d H:i:s'),
2438 'recipients' => [["test-member-$interval_unit@example.com"]],
2439 ],
2440 ];
2441 $this->assertCronRuns($cronRuns);
2442 $actionSchedule->delete();
2443 $membership->delete();
2444 }
2445 }
2446
2447 /**
2448 * Inherited members without permission to edit the main member contact should
2449 * not get reminders.
2450 *
2451 * However, just because a contact inherits one membership doesn't mean
2452 * reminders for other memberships should be suppressed.
2453 *
2454 * See CRM-14098
2455 *
2456 * @throws \CRM_Core_Exception
2457 */
2458 public function testInheritedMembershipPermissions(): void {
2459 // Set up common parameters for memberships.
2460 $membershipParams = $this->fixtures['rolling_membership'];
2461 $membershipParams['status_id'] = 1;
2462
2463 $membershipParams['membership_type_id']['relationship_type_id'] = 1;
2464 $membershipParams['membership_type_id']['relationship_direction'] = 'b_a';
2465 $membershipType1 = $this->createTestObject('CRM_Member_DAO_MembershipType', $membershipParams['membership_type_id']);
2466
2467 // We'll create a new membership type that can be held at the same time as
2468 // the first one.
2469 $membershipParams['membership_type_id']['relationship_type_id'] = 'NULL';
2470 $membershipParams['membership_type_id']['relationship_direction'] = 'NULL';
2471 $membershipType2 = $this->createTestObject('CRM_Member_DAO_MembershipType', $membershipParams['membership_type_id']);
2472
2473 // Create the parent membership and contact
2474 $membershipParams['membership_type_id'] = $membershipType1->id;
2475 $mainMembership = $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2476
2477 $contactParams = [
2478 'contact_type' => 'Individual',
2479 'first_name' => 'Mom',
2480 'last_name' => 'Rel',
2481 'is_deceased' => 0,
2482 ];
2483 $this->createTestObject('CRM_Contact_DAO_Contact', array_merge($contactParams, ['id' => $mainMembership->contact_id]));
2484
2485 $emailParams = [
2486 'contact_id' => $mainMembership->contact_id,
2487 'email' => 'test-member@example.com',
2488 'location_type_id' => 1,
2489 'is_primary' => 1,
2490 ];
2491 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2492
2493 // Set up contacts and emails for the two children
2494 $contactParams['first_name'] = 'Favorite';
2495 $permChild = $this->createTestObject('CRM_Contact_DAO_Contact', $contactParams);
2496 $emailParams['email'] = 'favorite@example.com';
2497 $emailParams['contact_id'] = $permChild->id;
2498 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2499
2500 $contactParams['first_name'] = 'Black Sheep';
2501 $nonPermChild = $this->createTestObject('CRM_Contact_DAO_Contact', $contactParams);
2502 $emailParams['email'] = 'black.sheep@example.com';
2503 $emailParams['contact_id'] = $nonPermChild->id;
2504 $this->createTestObject('CRM_Core_DAO_Email', $emailParams);
2505
2506 // Each child gets a relationship, one with permission to edit the parent. This
2507 // will trigger inherited memberships for the first membership type
2508 $relParams = [
2509 'relationship_type_id' => 1,
2510 'contact_id_a' => $nonPermChild->id,
2511 'contact_id_b' => $mainMembership->contact_id,
2512 'is_active' => 1,
2513 ];
2514 $this->callAPISuccess('relationship', 'create', $relParams);
2515
2516 $relParams['contact_id_a'] = $permChild->id;
2517 $relParams['is_permission_a_b'] = CRM_Contact_BAO_Relationship::EDIT;
2518 $this->callAPISuccess('relationship', 'create', $relParams);
2519
2520 // Mom and Black Sheep get their own memberships of the second type.
2521 $membershipParams['membership_type_id'] = $membershipType2->id;
2522 $membershipParams['owner_membership_id'] = 'NULL';
2523 $membershipParams['contact_id'] = $mainMembership->contact_id;
2524 $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2525
2526 $membershipParams['contact_id'] = $nonPermChild->id;
2527 $this->createTestObject('CRM_Member_DAO_Membership', $membershipParams);
2528
2529 // Test a reminder for the first membership type - that should exclude Black
2530 // Sheep.
2531 $this->fixtures['sched_membership_join_2week']['entity_value'] = $membershipType1->id;
2532 $this->createScheduleFromFixtures('sched_membership_join_2week');
2533
2534 $this->assertCronRuns([
2535 [
2536 'time' => '2012-03-29 01:00:00',
2537 'recipients' => [['test-member@example.com'], ['favorite@example.com']],
2538 'subjects' => [
2539 'subject sched_membership_join_2week (joined March 15th, 2012)',
2540 'subject sched_membership_join_2week (joined March 15th, 2012)',
2541 ],
2542 ],
2543 ]);
2544
2545 // Test a reminder for the second membership type - that should include
2546 // Black Sheep.
2547 $this->fixtures['sched_membership_start_1week']['entity_value'] = $membershipType2->id;
2548 $this->createScheduleFromFixtures('sched_membership_start_1week');
2549
2550 $this->assertCronRuns([
2551 [
2552 'time' => '2012-03-22 01:00:00',
2553 'recipients' => [['test-member@example.com'], ['black.sheep@example.com']],
2554 'subjects' => [
2555 'subject sched_membership_start_1week (joined March 15th, 2012)',
2556 'subject sched_membership_start_1week (joined March 15th, 2012)',
2557 ],
2558 ],
2559 ]);
2560 }
2561
2562 /**
2563 * Modify the date time by the modify rule.
2564 *
2565 * @param DateTime $origDateTime
2566 * @param string $modifyRule
2567 *
2568 * @return DateTime
2569 */
2570 public function createModifiedDateTime(DateTime $origDateTime, string $modifyRule): DateTime {
2571 $newDateTime = clone($origDateTime);
2572 $newDateTime->modify($modifyRule);
2573 return $newDateTime;
2574 }
2575
2576 /**
2577 * Test absolute date handling for membership.
2578 *
2579 * @throws \API_Exception
2580 * @throws \CRM_Core_Exception
2581 * @throws \CiviCRM_API3_Exception
2582 * @throws \Civi\API\Exception\UnauthorizedException
2583 */
2584 public function testMembershipScheduleWithAbsoluteDate(): void {
2585 $membership = $this->createMembershipFromFixture('rolling_membership', 'New', [
2586 'email' => 'test-member@example.com',
2587 'location_type_id' => 1,
2588 ]);
2589
2590 $this->callAPISuccess('contact', 'create', array_merge($this->fixtures['contact'], ['contact_id' => $membership['contact_id']]));
2591 $this->fixtures['sched_membership_absolute_date']['entity_value'] = $membership['membership_type_id'];
2592 $this->createScheduleFromFixtures('sched_membership_absolute_date');
2593
2594 $this->assertCronRuns([
2595 [
2596 // Before the 24-hour mark, no email
2597 'time' => '2012-06-13 04:00:00',
2598 'recipients' => [],
2599 'subjects' => [],
2600 ],
2601 [
2602 // On absolute date set on 2012-06-14
2603 'time' => '2012-06-14 00:00:00',
2604 'recipients' => [['test-member@example.com']],
2605 'subjects' => ['subject sched_membership_absolute_date'],
2606 ],
2607 [
2608 // Run cron 4 hours later; first message already sent
2609 'time' => '2012-06-14 04:00:00',
2610 'recipients' => [],
2611 'subjects' => [],
2612 ],
2613 ]);
2614 }
2615
2616 /**
2617 * @param string $fixture
2618 * Key from $this->fixtures
2619 * @param string $status
2620 * Membership status
2621 * @param array $emailParams
2622 * @param array $membershipOverrides
2623 *
2624 * @return array
2625 * @throws \API_Exception
2626 * @throws \CRM_Core_Exception
2627 * @throws \CiviCRM_API3_Exception
2628 * @throws \Civi\API\Exception\UnauthorizedException
2629 */
2630 protected function createMembershipFromFixture(string $fixture, string $status, array $emailParams = [], array $membershipOverrides = []): array {
2631 $membershipTypeID = $membershipOverrides['membership_type_id'] ?? $this->fixtures[$fixture]['membership_type_id'];
2632 if (is_array($membershipTypeID)) {
2633 $membershipTypeID = MembershipType::create()->setValues(array_merge([
2634 'member_of_contact_id' => 1,
2635 'financial_type_id:name' => 'Member Dues',
2636 'name' => 'fixture-created-type',
2637 ], $this->fixtures[$fixture]['membership_type_id']))->execute()->first()['id'];
2638 }
2639 $params = array_merge($this->fixtures[$fixture], [
2640 'sequential' => 1,
2641 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', $status),
2642 'membership_type_id' => $membershipTypeID,
2643 ], $membershipOverrides);
2644 if (empty($params['contact_id'])) {
2645 $params['contact_id'] = $this->individualCreate(['email' => '']);
2646 }
2647 $membership = (array) $this->callAPISuccess('Membership', 'create', $params)['values'][0];
2648 if ($emailParams) {
2649 Civi\Api4\Email::create(FALSE)->setValues(array_merge([
2650 'contact_id' => $membership['contact_id'],
2651 'location_type_id' => 1,
2652 ], $emailParams))->execute();
2653 }
2654 return $membership;
2655 }
2656
2657 /**
2658 * Create action schedule from defined fixtures.
2659 *
2660 * @param string $fixture
2661 * @param array $extraParams
2662 *
2663 * @throws \CRM_Core_Exception
2664 */
2665 protected function createScheduleFromFixtures(string $fixture, array $extraParams = []): void {
2666 $id = $this->callAPISuccess('ActionSchedule', 'create', array_merge($this->fixtures[$fixture], $extraParams))['id'];
2667 $this->fixtures[$fixture]['action_schedule_id'] = (int) $id;
2668 }
2669
2670 /**
2671 * @param string $activityKey
2672 * @param string $contactKey
2673 *
2674 * @throws \CRM_Core_Exception
2675 */
2676 protected function createActivityAndContactFromFixtures(string $activityKey = 'phone_call', string $contactKey = 'contact'): void {
2677 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures[$activityKey]);
2678 $contact = $this->callAPISuccess('contact', 'create', $this->fixtures[$contactKey]);
2679 $activity->save();
2680
2681 $source = [];
2682 $source['contact_id'] = $contact['id'];
2683 $source['activity_id'] = $activity->id;
2684 $source['record_type_id'] = 2;
2685 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
2686 $activityContact->save();
2687 }
2688
2689}