Merge pull request #21708 from eileenmcnaughton/test_clean
[civicrm-core.git] / tests / phpunit / CRM / Utils / TokenConsistencyTest.php
CommitLineData
d0ce76fd
EM
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
0f4031da 12use Civi\Token\TokenProcessor;
f399fbd8
EM
13use Civi\Api4\LocBlock;
14use Civi\Api4\Email;
15use Civi\Api4\Phone;
16use Civi\Api4\Address;
0f4031da 17
d0ce76fd
EM
18/**
19 * CRM_Utils_TokenConsistencyTest
20 *
21 * Class for ensuring tokens have internal consistency.
22 *
23 * @group Tokens
24 *
25 * @group headless
26 */
27class CRM_Utils_TokenConsistencyTest extends CiviUnitTestCase {
28
29 use CRMTraits_Custom_CustomDataTrait;
30
31 /**
32 * Created case.
33 *
34 * @var array
35 */
36 protected $case;
37
0f4031da
EM
38 /**
39 * Recurring contribution.
40 *
41 * @var array
42 */
43 protected $contributionRecur;
44
d0ce76fd
EM
45 /**
46 * Post test cleanup.
d0ce76fd
EM
47 */
48 public function tearDown(): void {
e80f2261 49 $this->quickCleanup(['civicrm_case', 'civicrm_case_type', 'civicrm_participant', 'civicrm_event'], TRUE);
d0ce76fd
EM
50 parent::tearDown();
51 }
52
53 /**
54 * Test that case tokens are consistently rendered.
55 *
d0ce76fd
EM
56 * @throws \CiviCRM_API3_Exception
57 */
58 public function testCaseTokenConsistency(): void {
59 $this->createLoggedInUser();
60 CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
61 $this->createCustomGroupWithFieldOfType(['extends' => 'Case']);
62 $tokens = CRM_Core_SelectValues::caseTokens();
63 $this->assertEquals($this->getCaseTokens(), $tokens);
64 $caseID = $this->getCaseID();
044c0ad1 65 $tokenString = $this->getTokenString(array_keys($this->getCaseTokens()));
78ffc4d7 66 $tokenHtml = CRM_Utils_Token::replaceCaseTokens($caseID, $tokenString, ['case' => $this->getCaseTokenKeys()]);
d0ce76fd
EM
67 $this->assertEquals($this->getExpectedCaseTokenOutput(), $tokenHtml);
68 // Now do the same without passing in 'knownTokens'
78ffc4d7 69 $tokenHtml = CRM_Utils_Token::replaceCaseTokens($caseID, $tokenString);
d0ce76fd 70 $this->assertEquals($this->getExpectedCaseTokenOutput(), $tokenHtml);
2f5c0408
EM
71
72 // And check our deprecated tokens still work.
73 $tokenHtml = CRM_Utils_Token::replaceCaseTokens($caseID, '{case.case_type_id} {case.status_id}');
74 $this->assertEquals('Housing Support Ongoing', $tokenHtml);
78ffc4d7
EM
75
76 $additionalTokensFromProcessor = [
77 '{case.case_type_id}' => 'Case Type ID',
78 '{case.status_id}' => 'Case Status',
79 '{case.case_type_id:name}' => 'Machine name: Case Type',
80 '{case.status_id:name}' => 'Machine name: Case Status',
81 ];
82 $expectedTokens = array_merge($this->getCaseTokens(), $additionalTokensFromProcessor);
83
84 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
85 'controller' => __CLASS__,
86 'smarty' => FALSE,
87 'schema' => ['caseId'],
88 ]);
3c78698e 89 $this->assertEquals(array_merge($expectedTokens, $this->getDomainTokens()), $tokenProcessor->listTokens());
78ffc4d7
EM
90 $tokenProcessor->addRow([
91 'caseId' => $this->getCaseID(),
92 ]);
93 $tokenProcessor->addMessage('html', $tokenString, 'text/plain');
94
95 $tokenProcessor->evaluate();
96 foreach ($tokenProcessor->getRows() as $row) {
97 $text = $row->render('html');
98 }
99 $this->assertEquals($this->getExpectedCaseTokenOutput(), $text);
d0ce76fd
EM
100 }
101
102 /**
103 * Get expected output from token parsing.
104 *
105 * @return string
106 */
107 protected function getExpectedCaseTokenOutput(): string {
044c0ad1
EM
108 return 'case.id :1
109case.case_type_id:label :Housing Support
110case.subject :Case Subject
111case.start_date :July 23rd, 2021
112case.end_date :July 26th, 2021
113case.details :case details
114case.status_id:label :Ongoing
115case.is_deleted:label :No
116case.created_date :' . CRM_Utils_Date::customFormat($this->case['created_date']) . '
117case.modified_date :' . CRM_Utils_Date::customFormat($this->case['modified_date']) . '
118case.custom_1 :' . '
d0ce76fd
EM
119';
120 }
121
122 /**
123 * @return int
124 */
125 protected function getContactID(): int {
126 if (!isset($this->ids['Contact'][0])) {
127 $this->ids['Contact'][0] = $this->individualCreate();
128 }
129 return $this->ids['Contact'][0];
130 }
131
132 /**
133 * Get the keys for the case tokens.
134 *
135 * @return array
136 */
137 public function getCaseTokenKeys(): array {
138 $return = [];
139 foreach (array_keys($this->getCaseTokens()) as $key) {
140 $return[] = substr($key, 6, -1);
141 }
142 return $return;
143 }
144
145 /**
146 * Get declared tokens.
147 *
148 * @return string[]
149 */
150 public function getCaseTokens(): array {
151 return [
152 '{case.id}' => 'Case ID',
2f5c0408 153 '{case.case_type_id:label}' => 'Case Type',
d0ce76fd
EM
154 '{case.subject}' => 'Case Subject',
155 '{case.start_date}' => 'Case Start Date',
156 '{case.end_date}' => 'Case End Date',
157 '{case.details}' => 'Details',
2f5c0408 158 '{case.status_id:label}' => 'Case Status',
78ffc4d7 159 '{case.is_deleted:label}' => 'Case is in the Trash',
d0ce76fd
EM
160 '{case.created_date}' => 'Created Date',
161 '{case.modified_date}' => 'Modified Date',
162 '{case.custom_1}' => 'Enter text here :: Group with field text',
163 ];
164 }
165
166 /**
167 * Get case ID.
168 *
169 * @return int
170 */
171 protected function getCaseID(): int {
172 if (!isset($this->case)) {
a0fb3068 173 $case_id = $this->callAPISuccess('Case', 'create', [
d0ce76fd
EM
174 'case_type_id' => 'housing_support',
175 'activity_subject' => 'Case Subject',
176 'client_id' => $this->getContactID(),
177 'status_id' => 1,
178 'subject' => 'Case Subject',
179 'start_date' => '2021-07-23 15:39:20',
a0fb3068 180 // Note end_date is inconsistent with status Ongoing but for the
181 // purposes of testing tokens is ok. Creating it with status Resolved
182 // then ignores our known fixed end date.
d0ce76fd
EM
183 'end_date' => '2021-07-26 18:07:20',
184 'medium_id' => 2,
185 'details' => 'case details',
186 'activity_details' => 'blah blah',
187 'sequential' => 1,
a0fb3068 188 ])['id'];
189 // Need to retrieve the case again because modified date might be updated a
190 // split-second later than the original return value because of activity
191 // triggers when the timeline is populated. The returned array from create
192 // is determined before that happens.
193 $this->case = $this->callAPISuccess('Case', 'getsingle', ['id' => $case_id]);
d0ce76fd
EM
194 }
195 return $this->case['id'];
196 }
197
0f4031da
EM
198 /**
199 * Test that contribution recur tokens are consistently rendered.
200 */
201 public function testContributionRecurTokenConsistency(): void {
202 $this->createLoggedInUser();
203 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
204 'controller' => __CLASS__,
205 'smarty' => FALSE,
206 'schema' => ['contribution_recurId'],
207 ]);
3c78698e 208 $this->assertEquals(array_merge($this->getContributionRecurTokens(), $this->getDomainTokens()), $tokenProcessor->listTokens());
044c0ad1 209 $tokenString = $this->getTokenString(array_keys($this->getContributionRecurTokens()));
0f4031da
EM
210
211 $tokenProcessor->addMessage('html', $tokenString, 'text/plain');
212 $tokenProcessor->addRow(['contribution_recurId' => $this->getContributionRecurID()]);
213 $tokenProcessor->evaluate();
214 $this->assertEquals($this->getExpectedContributionRecurTokenOutPut(), $tokenProcessor->getRow(0)->render('html'));
215 }
216
217 /**
218 * Get the contribution recur tokens keyed by the token.
219 *
220 * e.g {contribution_recur.id}
221 *
222 * @return array
223 */
224 protected function getContributionRecurTokens(): array {
225 $return = [];
226 foreach ($this->getContributionRecurTokensByField() as $key => $value) {
227 $return['{contribution_recur.' . $key . '}'] = $value;
228 }
229 return $return;
230 }
231
232 protected function getContributionRecurTokensByField(): array {
233 return [
234 'id' => 'Recurring Contribution ID',
235 'amount' => 'Amount',
236 'currency' => 'Currency',
237 'frequency_unit' => 'Frequency Unit',
238 'frequency_interval' => 'Interval (number of units)',
239 'installments' => 'Number of Installments',
240 'start_date' => 'Start Date',
241 'create_date' => 'Created Date',
242 'modified_date' => 'Modified Date',
243 'cancel_date' => 'Cancel Date',
244 'cancel_reason' => 'Cancellation Reason',
245 'end_date' => 'Recurring Contribution End Date',
246 'processor_id' => 'Processor ID',
247 'payment_token_id' => 'Payment Token ID',
248 'trxn_id' => 'Transaction ID',
249 'invoice_id' => 'Invoice ID',
250 'contribution_status_id' => 'Status',
97ca72e4 251 'is_test:label' => 'Test',
0f4031da
EM
252 'cycle_day' => 'Cycle Day',
253 'next_sched_contribution_date' => 'Next Scheduled Contribution Date',
254 'failure_count' => 'Number of Failures',
255 'failure_retry_date' => 'Retry Failed Attempt Date',
97ca72e4 256 'auto_renew:label' => 'Auto Renew',
0f4031da
EM
257 'payment_processor_id' => 'Payment Processor ID',
258 'financial_type_id' => 'Financial Type ID',
259 'payment_instrument_id' => 'Payment Method',
97ca72e4 260 'is_email_receipt:label' => 'Send email Receipt?',
0f4031da
EM
261 'frequency_unit:label' => 'Frequency Unit',
262 'frequency_unit:name' => 'Machine name: Frequency Unit',
263 'contribution_status_id:label' => 'Status',
264 'contribution_status_id:name' => 'Machine name: Status',
265 'payment_processor_id:label' => 'Payment Processor',
266 'payment_processor_id:name' => 'Machine name: Payment Processor',
267 'financial_type_id:label' => 'Financial Type',
268 'financial_type_id:name' => 'Machine name: Financial Type',
269 'payment_instrument_id:label' => 'Payment Method',
270 'payment_instrument_id:name' => 'Machine name: Payment Method',
271 ];
272 }
273
274 /**
275 * Get contributionRecur ID.
276 *
277 * @return int
278 */
279 protected function getContributionRecurID(): int {
280 if (!isset($this->contributionRecur)) {
281 $paymentProcessorID = $this->processorCreate();
282 $this->contributionRecur = $this->callAPISuccess('ContributionRecur', 'create', [
283 'contact_id' => $this->getContactID(),
284 'status_id' => 1,
285 'is_email_receipt' => 1,
286 'start_date' => '2021-07-23 15:39:20',
287 'end_date' => '2021-07-26 18:07:20',
288 'cancel_date' => '2021-08-19 09:12:45',
34795e7a 289 'next_sched_contribution_date' => '2021-09-08',
0f4031da
EM
290 'cancel_reason' => 'Because',
291 'amount' => 5990.99,
292 'currency' => 'EUR',
293 'frequency_unit' => 'year',
294 'frequency_interval' => 2,
295 'installments' => 24,
296 'payment_instrument_id' => 'Check',
297 'financial_type_id' => 'Member dues',
298 'processor_id' => 'abc',
299 'payment_processor_id' => $paymentProcessorID,
300 'trxn_id' => 123,
301 'invoice_id' => 'inv123',
302 'sequential' => 1,
303 'failure_retry_date' => '2020-01-03',
304 'auto_renew' => 1,
305 'cycle_day' => '15',
306 'is_test' => TRUE,
307 'payment_token_id' => $this->callAPISuccess('PaymentToken', 'create', [
308 'contact_id' => $this->getContactID(),
309 'token' => 456,
310 'payment_processor_id' => $paymentProcessorID,
311 ])['id'],
312 ])['values'][0];
313 }
314 return $this->contributionRecur['id'];
315 }
316
317 /**
318 * Get rendered output for contribution tokens.
319 *
320 * @return string
321 */
322 protected function getExpectedContributionRecurTokenOutPut(): string {
044c0ad1
EM
323 return 'contribution_recur.id :' . $this->getContributionRecurID() . '
324contribution_recur.amount :€ 5,990.99
325contribution_recur.currency :EUR
326contribution_recur.frequency_unit :year
327contribution_recur.frequency_interval :2
328contribution_recur.installments :24
329contribution_recur.start_date :July 23rd, 2021 3:39 PM
330contribution_recur.create_date :' . CRM_Utils_Date::customFormat($this->contributionRecur['create_date']) . '
331contribution_recur.modified_date :' . CRM_Utils_Date::customFormat($this->contributionRecur['modified_date']) . '
332contribution_recur.cancel_date :August 19th, 2021 9:12 AM
333contribution_recur.cancel_reason :Because
334contribution_recur.end_date :July 26th, 2021 6:07 PM
335contribution_recur.processor_id :abc
336contribution_recur.payment_token_id :1
337contribution_recur.trxn_id :123
338contribution_recur.invoice_id :inv123
339contribution_recur.contribution_status_id :2
340contribution_recur.is_test:label :Yes
341contribution_recur.cycle_day :15
342contribution_recur.next_sched_contribution_date :September 8th, 2021
343contribution_recur.failure_count :0
344contribution_recur.failure_retry_date :January 3rd, 2020
345contribution_recur.auto_renew:label :Yes
346contribution_recur.payment_processor_id :1
347contribution_recur.financial_type_id :2
348contribution_recur.payment_instrument_id :4
349contribution_recur.is_email_receipt:label :Yes
350contribution_recur.frequency_unit:label :year
351contribution_recur.frequency_unit:name :year
352contribution_recur.contribution_status_id:label :Pending Label**
353contribution_recur.contribution_status_id:name :Pending
354contribution_recur.payment_processor_id:label :Dummy (test)
355contribution_recur.payment_processor_id:name :Dummy (test)
356contribution_recur.financial_type_id:label :Member Dues
357contribution_recur.financial_type_id:name :Member Dues
358contribution_recur.payment_instrument_id:label :Check
359contribution_recur.payment_instrument_id:name :Check
360';
361
0f4031da
EM
362 }
363
d41a5d53
EM
364 /**
365 * Test that membership tokens are consistently rendered.
366 *
367 * @throws \API_Exception
368 */
369 public function testMembershipTokenConsistency(): void {
370 $this->createLoggedInUser();
371 $this->restoreMembershipTypes();
372 $this->createCustomGroupWithFieldOfType(['extends' => 'Membership']);
373 $tokens = CRM_Core_SelectValues::membershipTokens();
d568dbe0
EM
374 $expectedTokens = $this->getMembershipTokens();
375 $this->assertEquals($expectedTokens, $tokens);
d41a5d53
EM
376 $newStyleTokens = "\n{membership.status_id:label}\n{membership.membership_type_id:label}\n";
377 $tokenString = $newStyleTokens . implode("\n", array_keys($this->getMembershipTokens()));
b024d6a1 378
d41a5d53
EM
379 $memberships = CRM_Utils_Token::getMembershipTokenDetails([$this->getMembershipID()]);
380 $messageToken = CRM_Utils_Token::getTokens($tokenString);
381 $tokenHtml = CRM_Utils_Token::replaceEntityTokens('membership', $memberships[$this->getMembershipID()], $tokenString, $messageToken);
382 $this->assertEquals($this->getExpectedMembershipTokenOutput(), $tokenHtml);
dd2f879a 383
b024d6a1
EM
384 // Custom fields work in the processor so test it....
385 $tokenString .= "\n{membership." . $this->getCustomFieldName('text') . '}';
dd2f879a
EM
386 // Now compare with scheduled reminder
387 $mut = new CiviMailUtils($this);
388 CRM_Utils_Time::setTime('2007-01-22 15:00:00');
389 $this->callAPISuccess('action_schedule', 'create', [
390 'title' => 'job',
391 'subject' => 'job',
392 'entity_value' => 1,
393 'mapping_id' => 4,
394 'start_action_date' => 'membership_join_date',
395 'start_action_offset' => 1,
396 'start_action_condition' => 'after',
397 'start_action_unit' => 'day',
398 'body_html' => $tokenString,
399 ]);
400 $this->callAPISuccess('job', 'send_reminder', []);
d568dbe0
EM
401 $expected = $this->getExpectedMembershipTokenOutput();
402 // Unlike the legacy method custom fields are resolved by the processor.
403 $expected .= "\nmy field";
404 $mut->checkMailLog([$expected]);
405
406 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
407 'controller' => __CLASS__,
408 'smarty' => FALSE,
409 'schema' => ['membershipId'],
410 ]);
411 $tokens = $tokenProcessor->listTokens();
412 // Add in custom tokens as token processor supports these.
413 $expectedTokens['{membership.custom_1}'] = 'Enter text here :: Group with field text';
3c78698e 414 $this->assertEquals(array_merge($expectedTokens, $this->getDomainTokens()), $tokens);
d568dbe0
EM
415 $tokenProcessor->addMessage('html', $tokenString, 'text/plain');
416 $tokenProcessor->addRow(['membershipId' => $this->getMembershipID()]);
417 $tokenProcessor->evaluate();
418 $this->assertEquals($expected, $tokenProcessor->getRow(0)->render('html'));
419
d41a5d53
EM
420 }
421
422 /**
423 * Get declared membership tokens.
424 *
425 * @return string[]
426 */
427 public function getMembershipTokens(): array {
428 return [
429 '{membership.id}' => 'Membership ID',
044c0ad1 430 '{membership.status_id:label}' => 'Status',
eac0a5bf 431 '{membership.membership_type_id:label}' => 'Membership Type',
d41a5d53 432 '{membership.start_date}' => 'Membership Start Date',
044c0ad1
EM
433 '{membership.join_date}' => 'Member Since',
434 '{membership.end_date}' => 'Membership Expiration Date',
d41a5d53
EM
435 '{membership.fee}' => 'Membership Fee',
436 ];
437 }
438
439 /**
440 * Get case ID.
441 *
442 * @return int
443 */
444 protected function getMembershipID(): int {
445 if (!isset($this->ids['Membership'][0])) {
b024d6a1
EM
446 $this->ids['Membership'][0] = $this->contactMembershipCreate([
447 'contact_id' => $this->getContactID(),
448 $this->getCustomFieldName('text') => 'my field',
449 ]);
d41a5d53
EM
450 }
451 return $this->ids['Membership'][0];
452 }
453
f399fbd8
EM
454 /**
455 * Get expected output from token parsing.
456 *
457 * @return string
458 */
e80f2261 459 protected function getExpectedParticipantTokenOutput(): string {
044c0ad1
EM
460 return 'participant.status_id :2
461participant.role_id :1
462participant.register_date :February 19th, 2007
463participant.source :Wimbeldon
464participant.fee_level :steep
465participant.fee_amount :$ 50.00
466participant.registered_by_id :
467participant.transferred_to_contact_id :
468participant.role_id:label :Attendee
469participant.balance :
470participant.custom_2 :99999
471participant.id :2
472participant.fee_currency :USD
473participant.discount_amount :
474participant.status_id:label :Attended
475participant.status_id:name :Attended
476participant.role_id:name :Attendee
477participant.is_test:label :No
478participant.must_wait :
e80f2261
EM
479';
480 }
481
482 /**
483 * Get expected output from token parsing.
484 *
485 * @return string
486 */
487 protected function getExpectedEventTokenOutput(): string {
044c0ad1
EM
488 return 'event.id :' . $this->ids['event'][0] . '
489event.title :Annual CiviCRM meet
490event.start_date :October 21st, 2008
491event.end_date :October 23rd, 2008
492event.event_type_id:label :Conference
493event.summary :If you have any CiviCRM related issues or want to track where CiviCRM is heading, Sign up now
494event.contact_email :event@example.com
495event.contact_phone :456 789
496event.description :event description
497event.location :15 Walton St
f399fbd8
EM
498Emerald City, Maine 90210
499
044c0ad1
EM
500event.info_url :' . CRM_Utils_System::url('civicrm/event/info', NULL, TRUE) . '&reset=1&id=1
501event.registration_url :' . CRM_Utils_System::url('civicrm/event/register', NULL, TRUE) . '&reset=1&id=1
502event.custom_1 :my field
503';
f399fbd8
EM
504 }
505
d41a5d53
EM
506 /**
507 * Get expected output from token parsing.
508 *
509 * @return string
510 */
511 protected function getExpectedMembershipTokenOutput(): string {
512 return '
513Expired
514General
5151
516Expired
517General
518January 21st, 2007
519January 21st, 2007
520December 21st, 2007
521100.00';
522 }
523
1ed50dc6
EM
524 /**
525 * Test that membership tokens are consistently rendered.
526 *
527 * @throws \API_Exception
528 */
529 public function testParticipantTokenConsistency(): void {
530 $this->createLoggedInUser();
e80f2261
EM
531 $this->setupParticipantScheduledReminder();
532
1ed50dc6
EM
533 $tokens = CRM_Core_SelectValues::participantTokens();
534 $this->assertEquals($this->getParticipantTokens(), $tokens);
e80f2261
EM
535
536 $mut = new CiviMailUtils($this);
537
538 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
539 'controller' => __CLASS__,
540 'smarty' => FALSE,
541 'schema' => ['participantId'],
542 ]);
543 $this->assertEquals(array_merge($tokens, $this->getDomainTokens()), $tokenProcessor->listTokens());
544
545 $this->callAPISuccess('job', 'send_reminder', []);
546 $expected = $this->getExpectedParticipantTokenOutput();
547 $mut->checkMailLog([$expected]);
548
044c0ad1 549 $tokenProcessor->addMessage('html', $this->getTokenString(array_keys($this->getParticipantTokens())), 'text/plain');
e80f2261
EM
550 $tokenProcessor->addRow(['participantId' => $this->ids['participant'][0]]);
551 $tokenProcessor->evaluate();
552 $this->assertEquals($expected, $tokenProcessor->getRow(0)->render('html'));
553
1ed50dc6
EM
554 }
555
556 /**
3c78698e 557 * Get declared participant tokens.
1ed50dc6
EM
558 *
559 * @return string[]
560 */
561 public function getParticipantTokens(): array {
562 return [
b7472bd6 563 '{participant.status_id}' => 'Status ID',
e80f2261 564 '{participant.role_id}' => 'Participant Role ID',
b7472bd6
EM
565 '{participant.register_date}' => 'Register date',
566 '{participant.source}' => 'Participant Source',
567 '{participant.fee_level}' => 'Fee level',
568 '{participant.fee_amount}' => 'Fee Amount',
569 '{participant.registered_by_id}' => 'Registered By Participant ID',
1ed50dc6 570 '{participant.transferred_to_contact_id}' => 'Transferred to Contact ID',
e80f2261
EM
571 '{participant.role_id:label}' => 'Participant Role',
572 '{participant.balance}' => 'Event Balance',
573 '{participant.' . $this->getCustomFieldName('participant_int') . '}' => 'Enter integer here :: participant_Group with field int',
574 '{participant.id}' => 'Participant ID',
575 '{participant.fee_currency}' => 'Fee Currency',
576 '{participant.discount_amount}' => 'Discount Amount',
577 '{participant.status_id:label}' => 'Status',
578 '{participant.status_id:name}' => 'Machine name: Status',
579 '{participant.role_id:name}' => 'Machine name: Participant Role',
580 '{participant.is_test:label}' => 'Test',
581 '{participant.must_wait}' => 'Must Wait on List',
1ed50dc6
EM
582 ];
583 }
584
3c78698e
EM
585 /**
586 * Test that domain tokens are consistently rendered.
587 */
588 public function testDomainTokenConsistency(): void {
589 $tokens = CRM_Core_SelectValues::domainTokens();
590 $this->assertEquals($this->getDomainTokens(), $tokens);
591 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
592 'controller' => __CLASS__,
593 'smarty' => FALSE,
594 ]);
595 $tokens['{domain.id}'] = 'Domain ID';
596 $tokens['{domain.description}'] = 'Domain Description';
dfe53edd 597 $tokens['{domain.now}'] = 'Current time/date';
3c78698e
EM
598 $this->assertEquals($tokens, $tokenProcessor->listTokens());
599 }
600
defba8ff
EM
601 /**
602 * @throws \API_Exception
603 * @throws \CRM_Core_Exception
604 */
605 public function testDomainNow(): void {
606 putenv('TIME_FUNC=frozen');
5e62af3d 607 CRM_Utils_Time::setTime('2021-09-18 23:58:00');
44dd64f0
EM
608 $modifiers = [
609 'shortdate' => '09/18/2021',
f85e1a4b 610 '%B %Y' => 'September 2021',
44dd64f0
EM
611 ];
612 foreach ($modifiers as $filter => $expected) {
613 $resolved = CRM_Core_BAO_MessageTemplate::renderTemplate([
614 'messageTemplate' => [
f85e1a4b 615 'msg_text' => '{domain.now|crmDate:"' . $filter . '"}',
44dd64f0
EM
616 ],
617 ])['text'];
f85e1a4b 618 $this->assertEquals($expected, $resolved);
44dd64f0 619 }
defba8ff
EM
620 $resolved = CRM_Core_BAO_MessageTemplate::renderTemplate([
621 'messageTemplate' => [
622 'msg_text' => '{domain.now}',
623 ],
624 ])['text'];
625 $this->assertEquals('September 18th, 2021 11:58 PM', $resolved);
f85e1a4b
TO
626
627 // This example is malformed - no quotes
628 try {
629 $resolved = CRM_Core_BAO_MessageTemplate::renderTemplate([
630 'messageTemplate' => [
631 'msg_text' => '{domain.now|crmDate:shortdate}',
632 ],
633 ])['text'];
e80f2261 634 $this->fail('Expected unquoted parameter to fail');
f85e1a4b
TO
635 }
636 catch (\CRM_Core_Exception $e) {
637 $this->assertRegExp(';Malformed token param;', $e->getMessage());
638 }
defba8ff
EM
639 }
640
3c78698e
EM
641 /**
642 * Get declared participant tokens.
643 *
644 * @return string[]
645 */
646 public function getDomainTokens(): array {
647 return [
648 '{domain.name}' => ts('Domain name'),
649 '{domain.address}' => ts('Domain (organization) address'),
650 '{domain.phone}' => ts('Domain (organization) phone'),
651 '{domain.email}' => 'Domain (organization) email',
652 '{domain.id}' => ts('Domain ID'),
653 '{domain.description}' => ts('Domain Description'),
defba8ff 654 '{domain.now}' => 'Current time/date',
3c78698e
EM
655 ];
656 }
657
ce971869 658 /**
e80f2261 659 * Test that event tokens are consistently rendered.
34795e7a
EM
660 *
661 * @throws \API_Exception
ce971869
EM
662 */
663 public function testEventTokenConsistency(): void {
f399fbd8
EM
664 $mut = new CiviMailUtils($this);
665 $this->setupParticipantScheduledReminder();
666
ce971869
EM
667 $tokens = CRM_Core_SelectValues::eventTokens();
668 $this->assertEquals($this->getEventTokens(), $tokens);
669 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
670 'controller' => __CLASS__,
671 'smarty' => FALSE,
672 'schema' => ['eventId'],
673 ]);
674 $this->assertEquals(array_merge($tokens, $this->getDomainTokens()), $tokenProcessor->listTokens());
f399fbd8 675
044c0ad1 676 $expectedEventString = $this->getExpectedEventTokenOutput();
e80f2261 677 $this->callAPISuccess('job', 'send_reminder', []);
044c0ad1
EM
678 $expectedParticipantString = $this->getExpectedParticipantTokenOutput();
679 $toCheck = array_merge(explode("\n", $expectedEventString), explode("\n", $expectedParticipantString));
680 $toCheck[] = $expectedEventString;
681 $toCheck[] = $expectedParticipantString;
682 $mut->checkMailLog($toCheck);
683 $tokens = array_keys($this->getEventTokens());
684 $html = $this->getTokenString($tokens);
685 $tokenProcessor->addMessage('html', $html, 'text/plain');
e80f2261
EM
686 $tokenProcessor->addRow(['eventId' => $this->ids['event'][0]]);
687 $tokenProcessor->evaluate();
044c0ad1 688 $this->assertEquals($expectedEventString, $tokenProcessor->getRow(0)->render('html'));
e80f2261
EM
689 }
690
691 /**
692 * Test that event tokens work absent participant tokens.
693 *
694 * @throws \API_Exception
695 */
696 public function testEventTokenConsistencyNoParticipantTokens(): void {
697 $mut = new CiviMailUtils($this);
698 $this->setupParticipantScheduledReminder(FALSE);
699
f399fbd8
EM
700 $this->callAPISuccess('job', 'send_reminder', []);
701 $expected = $this->getExpectedEventTokenOutput();
044c0ad1
EM
702 // Checking these individually is easier to decipher discrepancies
703 // but we also want to check in entirety.
704 $toCheck = explode("\n", $expected);
705 $toCheck[] = $expected;
706 $mut->checkMailLog($toCheck);
e80f2261
EM
707
708 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
709 'controller' => __CLASS__,
710 'smarty' => FALSE,
711 'schema' => ['eventId'],
712 ]);
044c0ad1 713 $html = $this->getTokenString(array_keys($this->getEventTokens()));
e80f2261 714
044c0ad1 715 $tokenProcessor->addMessage('html', $html, 'text/plain');
e80f2261
EM
716 $tokenProcessor->addRow(['eventId' => $this->ids['event'][0]]);
717 $tokenProcessor->evaluate();
718 $this->assertEquals($expected, $tokenProcessor->getRow(0)->render('html'));
719
f399fbd8
EM
720 }
721
722 /**
723 * Set up scheduled reminder for participants.
724 *
725 * @throws \API_Exception
726 */
e80f2261 727 public function setupParticipantScheduledReminder($includeParticipant = TRUE): void {
f399fbd8 728 $this->createCustomGroupWithFieldOfType(['extends' => 'Event']);
e80f2261 729 $this->createCustomGroupWithFieldOfType(['extends' => 'Participant'], 'int', 'participant_');
044c0ad1
EM
730 $html = '';
731 $tokens = array_keys($this->getEventTokens());
e80f2261 732 if ($includeParticipant) {
044c0ad1 733 $tokens = array_keys(array_merge($this->getEventTokens(), $this->getParticipantTokens()));
e80f2261 734 }
044c0ad1 735 $html = $this->getTokenString($tokens);
f399fbd8
EM
736 $emailID = Email::create()->setValues(['email' => 'event@example.com'])->execute()->first()['id'];
737 $addressID = Address::create()->setValues([
738 'street_address' => '15 Walton St',
739 'supplemental_address_1' => 'up the road',
740 'city' => 'Emerald City',
741 'state_province_id:label' => 'Maine',
742 'postal_code' => 90210,
743 ])->execute()->first()['id'];
744 $phoneID = Phone::create()->setValues(['phone' => '456 789'])->execute()->first()['id'];
745
746 $locationBlockID = LocBlock::save(FALSE)->setRecords([
747 [
748 'email_id' => $emailID,
749 'address_id' => $addressID,
750 'phone_id' => $phoneID,
751 ],
752 ])->execute()->first()['id'];
e80f2261 753 $this->ids['event'][0] = $this->eventCreate([
f399fbd8
EM
754 'description' => 'event description',
755 $this->getCustomFieldName('text') => 'my field',
756 'loc_block_id' => $locationBlockID,
e80f2261 757 ])['id'];
f399fbd8
EM
758 // Create an unrelated participant record so that the ids don't match.
759 // this prevents things working just because the id 'happens to be valid'
e80f2261
EM
760 $this->participantCreate(['register_date' => '2020-01-01', 'event_id' => $this->ids['event'][0]]);
761 $this->ids['participant'][0] = $this->participantCreate([
762 'event_id' => $this->ids['event'][0],
763 'fee_amount' => 50,
764 'fee_level' => 'steep',
765 $this->getCustomFieldName('participant_int') => '99999',
766 ]);
f399fbd8
EM
767 CRM_Utils_Time::setTime('2007-02-20 15:00:00');
768 $this->callAPISuccess('action_schedule', 'create', [
769 'title' => 'job',
770 'subject' => 'job',
771 'entity_value' => 1,
772 'mapping_id' => 2,
773 'start_action_date' => 'register_date',
774 'start_action_offset' => 1,
775 'start_action_condition' => 'after',
776 'start_action_unit' => 'day',
e80f2261 777 'body_html' => $html,
f399fbd8 778 ]);
ce971869
EM
779 }
780
781 /**
782 * Get expected event tokens.
783 *
784 * @return string[]
785 */
786 protected function getEventTokens(): array {
787 return [
e80f2261 788 '{event.id}' => 'Event ID',
ce971869
EM
789 '{event.title}' => 'Event Title',
790 '{event.start_date}' => 'Event Start Date',
791 '{event.end_date}' => 'Event End Date',
e80f2261 792 '{event.event_type_id:label}' => 'Event Type',
ce971869
EM
793 '{event.summary}' => 'Event Summary',
794 '{event.contact_email}' => 'Event Contact Email',
795 '{event.contact_phone}' => 'Event Contact Phone',
796 '{event.description}' => 'Event Description',
797 '{event.location}' => 'Event Location',
ce971869
EM
798 '{event.info_url}' => 'Event Info URL',
799 '{event.registration_url}' => 'Event Registration URL',
f399fbd8 800 '{event.' . $this->getCustomFieldName('text') . '}' => 'Enter text here :: Group with field text',
ce971869
EM
801 ];
802 }
803
044c0ad1
EM
804 /**
805 * @param array $tokens
806 *
807 * @return string
808 */
809 protected function getTokenString(array $tokens): string {
810 $html = '';
811 foreach ($tokens as $token) {
812 $html .= substr($token, 1, -1) . ' :' . $token . "\n";
813 }
814 return $html;
815 }
816
d0ce76fd 817}