Fix test to manage statics within the test
[civicrm-core.git] / tests / phpunit / Civi / Token / TokenProcessorTest.php
1 <?php
2 namespace Civi\Token;
3
4 use Civi\Token\Event\TokenRegisterEvent;
5 use Civi\Token\Event\TokenValueEvent;
6 use Symfony\Component\EventDispatcher\EventDispatcher;
7
8 class TokenProcessorTest extends \CiviUnitTestCase {
9
10 /**
11 * @var \Symfony\Component\EventDispatcher\EventDispatcher
12 */
13 protected $dispatcher;
14
15 /**
16 * @var array
17 * Array(string $funcName => int $invocationCount).
18 */
19 protected $counts;
20
21 protected function setUp(): void {
22 $this->useTransaction(TRUE);
23 parent::setUp();
24 $this->dispatcher = new EventDispatcher();
25 $this->dispatcher->addListener('civi.token.list', [$this, 'onListTokens']);
26 $this->dispatcher->addListener('civi.token.eval', [$this, 'onEvalTokens']);
27 $this->counts = [
28 'onListTokens' => 0,
29 'onEvalTokens' => 0,
30 ];
31 }
32
33 /**
34 * The visitTokens() method is internal - but it is important basis for other
35 * methods. Specifically, it parses all token expressions and invokes a
36 * callback for each.
37 *
38 * Ensure these callbacks get the expected data (with various quirky
39 * notations).
40 *
41 * @throws \CRM_Core_Exception
42 */
43 public function testVisitTokens(): void {
44 $p = new TokenProcessor($this->dispatcher, [
45 'controller' => __CLASS__,
46 ]);
47 $examples = [
48 '{foo.bar}' => ['foo', 'bar', NULL],
49 '{foo.bar|whiz}' => ['foo', 'bar', ['whiz']],
50 '{foo.bar|whiz:"bang"}' => ['foo', 'bar', ['whiz', 'bang']],
51 '{FoO.bAr|whiz:"bang"}' => ['FoO', 'bAr', ['whiz', 'bang']],
52 '{oo_f.ra_b|b_52:"bang":"b@ng, on +he/([do0r])?!"}' => ['oo_f', 'ra_b', ['b_52', 'bang', 'b@ng, on +he/([do0r])?!']],
53 '{foo.bar.whiz}' => ['foo', 'bar.whiz', NULL],
54 '{foo.bar.whiz|bang}' => ['foo', 'bar.whiz', ['bang']],
55 '{foo.bar:label}' => ['foo', 'bar:label', NULL],
56 '{foo.bar:label|truncate:"10"}' => ['foo', 'bar:label', ['truncate', '10']],
57 ];
58 foreach ($examples as $input => $expected) {
59 array_unshift($expected, $input);
60 $log = [];
61 $filtered = $p->visitTokens($input, function (?string $fullToken, ?string $entity, ?string $field, ?array $modifier) use (&$log) {
62 $log[] = [$fullToken, $entity, $field, $modifier];
63 return 'Replaced!';
64 });
65 $this->assertCount(1, $log, "Should receive one callback on expression: $input");
66 $this->assertEquals($expected, $log[0]);
67 $this->assertEquals('Replaced!', $filtered);
68 }
69 }
70
71 /**
72 * Test that a row can be added via "addRow(array $context)".
73 */
74 public function testAddRow(): void {
75 $p = new TokenProcessor($this->dispatcher, [
76 'controller' => __CLASS__,
77 ]);
78 $createdRow = $p->addRow(['one' => 'Apple'])
79 ->context('two', 'Banana');
80 $gotRow = $p->getRow(0);
81 foreach ([$createdRow, $gotRow] as $row) {
82 $this->assertEquals('Apple', $row->context['one']);
83 $this->assertEquals('Banana', $row->context['two']);
84 }
85 }
86
87 /**
88 * Test that multiple rows can be added via "addRows(array $contexts)".
89 */
90 public function testAddRows(): void {
91 $p = new TokenProcessor($this->dispatcher, [
92 'controller' => __CLASS__,
93 ]);
94 $createdRows = $p->addRows([
95 ['one' => 'Apple', 'two' => 'Banana'],
96 ['one' => 'Pomme', 'two' => 'Banane'],
97 ]);
98 $gotRow0 = $p->getRow(0);
99 foreach ([$createdRows[0], $gotRow0] as $row) {
100 $this->assertEquals('Apple', $row->context['one']);
101 $this->assertEquals('Banana', $row->context['two']);
102 }
103 $gotRow1 = $p->getRow(1);
104 foreach ([$createdRows[1], $gotRow1] as $row) {
105 $this->assertEquals('Pomme', $row->context['one']);
106 $this->assertEquals('Banane', $row->context['two']);
107 }
108 }
109
110 /**
111 * Check that the TokenRow helper can correctly read/update context
112 * values.
113 */
114 public function testRowContext(): void {
115 $p = new TokenProcessor($this->dispatcher, [
116 'controller' => __CLASS__,
117 'omega' => '99',
118 ]);
119 $createdRow = $p->addRow()
120 ->context('one', 1)
121 ->context('two', [2 => 3])
122 ->context([
123 'two' => [4 => 5],
124 'three' => [6 => 7],
125 'omega' => '98',
126 ]);
127 $gotRow = $p->getRow(0);
128 foreach ([$createdRow, $gotRow] as $row) {
129 $this->assertEquals(1, $row->context['one']);
130 $this->assertEquals(3, $row->context['two'][2]);
131 $this->assertEquals(5, $row->context['two'][4]);
132 $this->assertEquals(7, $row->context['three'][6]);
133 $this->assertEquals(98, $row->context['omega']);
134 $this->assertEquals(__CLASS__, $row->context['controller']);
135 }
136 }
137
138 /**
139 * Check that getContextValues() returns the correct data
140 */
141 public function testGetContextValues(): void {
142 $p = new TokenProcessor($this->dispatcher, [
143 'controller' => __CLASS__,
144 'omega' => '99',
145 ]);
146 $p->addRow()->context('id', 10)->context('omega', '98');
147 $p->addRow()->context('id', 10)->context('contact', (object) ['cid' => 10]);
148 $p->addRow()->context('id', 11)->context('contact', (object) ['cid' => 11]);
149 $this->assertArrayValuesEqual([10, 11], $p->getContextValues('id'));
150 $this->assertArrayValuesEqual(['99', '98'], $p->getContextValues('omega'));
151 $this->assertArrayValuesEqual([10, 11], $p->getContextValues('contact', 'cid'));
152 }
153
154 /**
155 * Check that the TokenRow helper can correctly read/update token
156 * values.
157 */
158 public function testRowTokens(): void {
159 $p = new TokenProcessor($this->dispatcher, [
160 'controller' => __CLASS__,
161 ]);
162 $createdRow = $p->addRow()
163 ->tokens('one', 1)
164 ->tokens('two', [2 => 3])
165 ->tokens([
166 'two' => [4 => 5],
167 'three' => [6 => 7],
168 ])
169 ->tokens('four', 8, 9);
170 $gotRow = $p->getRow(0);
171 foreach ([$createdRow, $gotRow] as $row) {
172 $this->assertEquals(1, $row->tokens['one']);
173 $this->assertEquals(3, $row->tokens['two'][2]);
174 $this->assertEquals(5, $row->tokens['two'][4]);
175 $this->assertEquals(7, $row->tokens['three'][6]);
176 $this->assertEquals(9, $row->tokens['four'][8]);
177 }
178 }
179
180 public function testRenderLocalizedSmarty() {
181 \CRM_Utils_Time::setTime('2022-04-08 16:32:04');
182 $resetTime = \CRM_Utils_AutoClean::with(['CRM_Utils_Time', 'resetTime']);
183 $this->dispatcher->addSubscriber(new \CRM_Core_DomainTokens());
184 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
185 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
186 $p = new TokenProcessor($this->dispatcher, [
187 'controller' => __CLASS__,
188 'smarty' => TRUE,
189 ]);
190 $p->addMessage('text', '{ts}Yes{/ts} {ts}No{/ts} {domain.now|crmDate:"%B"}', 'text/plain');
191 $p->addRow([]);
192 $p->addRow(['locale' => 'fr_FR']);
193 $p->addRow(['locale' => 'es_MX']);
194
195 $expectText = [
196 'Yes No April',
197 'Oui Non Avril',
198 'Sí No Abril',
199 ];
200
201 $rowCount = 0;
202 foreach ($p->evaluate()->getRows() as $key => $row) {
203 /** @var TokenRow */
204 $this->assertTrue($row instanceof TokenRow);
205 $this->assertEquals($expectText[$key], $row->render('text'));
206 $rowCount++;
207 }
208 $this->assertEquals(3, $rowCount);
209 }
210
211 public function testRenderLocalizedHookToken(): void {
212 $cid = $this->individualCreate();
213
214 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
215 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
216 \Civi::dispatcher()->addListener('hook_civicrm_tokens', function($e) {
217 $e->tokens['trans'] = [
218 'trans.affirm' => ts('Translated affirmation'),
219 ];
220 });
221 \Civi::dispatcher()->addListener('hook_civicrm_tokenValues', function($e) {
222 if (in_array('affirm', $e->tokens['trans'], TRUE)) {
223 foreach ($e->contactIDs as $cid) {
224 $e->details[$cid]['trans.affirm'] = ts('Yes');
225 }
226 }
227 });
228
229 unset(\Civi::$statics['CRM_Contact_Tokens']['hook_tokens']);
230 $tokenProcessor = new TokenProcessor($this->dispatcher, [
231 'controller' => __CLASS__,
232 'smarty' => FALSE,
233 ]);
234 $tokenProcessor->addMessage('text', '!!{trans.affirm}!!', 'text/plain');
235 $tokenProcessor->addRow(['contactId' => $cid]);
236 $tokenProcessor->addRow(['contactId' => $cid, 'locale' => 'fr_FR']);
237 $tokenProcessor->addRow(['contactId' => $cid, 'locale' => 'es_MX']);
238
239 $expectText = [
240 '!!Yes!!',
241 '!!Oui!!',
242 '!!Sí!!',
243 ];
244
245 $rowCount = 0;
246 foreach ($tokenProcessor->evaluate()->getRows() as $key => $row) {
247 /** @var TokenRow */
248 $this->assertTrue($row instanceof TokenRow);
249 $this->assertEquals($expectText[$key], $row->render('text'));
250 $rowCount++;
251 }
252 $this->assertEquals(3, $rowCount);
253 }
254
255 public function testGetMessageTokens() {
256 $p = new TokenProcessor($this->dispatcher, [
257 'controller' => __CLASS__,
258 ]);
259 $p->addMessage('greeting_html', 'Good morning, <p>{contact.display_name}</p>. {custom.foobar}!', 'text/html');
260 $p->addMessage('greeting_text', 'Good morning, {contact.display_name}. {custom.whizbang}, {contact.first_name}!', 'text/plain');
261 $expected = [
262 'contact' => ['display_name', 'first_name'],
263 'custom' => ['foobar', 'whizbang'],
264 ];
265 $this->assertEquals($expected, $p->getMessageTokens());
266 }
267
268 public function testListTokens(): void {
269 $p = new TokenProcessor($this->dispatcher, [
270 'controller' => __CLASS__,
271 ]);
272 $p->addToken(['entity' => 'MyEntity', 'field' => 'myField', 'label' => 'My Label']);
273 $this->assertEquals(['{MyEntity.myField}' => 'My Label'], $p->listTokens());
274 }
275
276 /**
277 * Perform a full mail-merge, substituting multiple tokens for multiple
278 * contacts in multiple messages.
279 */
280 public function testFull() {
281 $p = new TokenProcessor($this->dispatcher, [
282 'controller' => __CLASS__,
283 ]);
284 $p->addMessage('greeting_html', 'Good morning, <p>{contact.display_name}</p>. {custom.foobar} Bye!', 'text/html');
285 $p->addMessage('greeting_text', 'Good morning, {contact.display_name}. {custom.foobar} Bye!', 'text/plain');
286 $p->addRow()
287 ->context(['contact_id' => 123])
288 ->format('text/plain')->tokens([
289 'contact' => ['display_name' => 'What'],
290 ]);
291 $p->addRow()
292 ->context(['contact_id' => 4])
293 ->format('text/plain')->tokens([
294 'contact' => ['display_name' => 'Who'],
295 ]);
296 $p->addRow()
297 ->context(['contact_id' => 10])
298 ->format('text/plain')->tokens([
299 'contact' => ['display_name' => 'Darth Vader'],
300 ]);
301
302 $expectHtml = [
303 0 => 'Good morning, <p>What</p>. #0123 is a good number. Trickster {contact.display_name}. Bye!',
304 1 => 'Good morning, <p>Who</p>. #0004 is a good number. Trickster {contact.display_name}. Bye!',
305 2 => 'Good morning, <p>Darth Vader</p>. #0010 is a good number. Trickster {contact.display_name}. Bye!',
306 ];
307
308 $expectText = [
309 0 => 'Good morning, What. #0123 is a good number. Trickster {contact.display_name}. Bye!',
310 1 => 'Good morning, Who. #0004 is a good number. Trickster {contact.display_name}. Bye!',
311 2 => 'Good morning, Darth Vader. #0010 is a good number. Trickster {contact.display_name}. Bye!',
312 ];
313
314 $rowCount = 0;
315 foreach ($p->evaluate()->getRows() as $key => $row) {
316 /** @var TokenRow */
317 $this->assertTrue($row instanceof TokenRow);
318 $this->assertEquals($expectHtml[$key], $row->render('greeting_html'));
319 $this->assertEquals($expectText[$key], $row->render('greeting_text'));
320 $rowCount++;
321 }
322 $this->assertEquals(3, $rowCount);
323 // This may change in the future.
324 $this->assertEquals(0, $this->counts['onListTokens']);
325 $this->assertEquals(1, $this->counts['onEvalTokens']);
326 }
327
328 public function testFilter() {
329 $exampleTokens['foo_bar']['whiz_bang'] = 'Some Text';
330 $exampleMessages = [
331 'This is {foo_bar.whiz_bang}.' => 'This is Some Text.',
332 'This is {foo_bar.whiz_bang|lower}...' => 'This is some text...',
333 'This is {foo_bar.whiz_bang|upper}!' => 'This is SOME TEXT!',
334 ];
335 $expectExampleCount = /* {#msgs} x {smarty:on,off} */ 6;
336 $actualExampleCount = 0;
337
338 foreach ($exampleMessages as $inputMessage => $expectOutput) {
339 foreach ([TRUE, FALSE] as $useSmarty) {
340 $p = new TokenProcessor($this->dispatcher, [
341 'controller' => __CLASS__,
342 'smarty' => $useSmarty,
343 ]);
344 $p->addMessage('example', $inputMessage, 'text/plain');
345 $p->addRow()
346 ->format('text/plain')->tokens($exampleTokens);
347 foreach ($p->evaluate()->getRows() as $key => $row) {
348 $this->assertEquals($expectOutput, $row->render('example'));
349 $actualExampleCount++;
350 }
351 }
352 }
353
354 $this->assertEquals($expectExampleCount, $actualExampleCount);
355 }
356
357 public function onListTokens(TokenRegisterEvent $e) {
358 $this->counts[__FUNCTION__]++;
359 $e->register('custom', [
360 'foobar' => 'A special message about foobar',
361 ]);
362 }
363
364 public function onEvalTokens(TokenValueEvent $e) {
365 $this->counts[__FUNCTION__]++;
366 foreach ($e->getRows() as $row) {
367 /** @var TokenRow $row */
368 $row->format('text/html');
369 $row->tokens['custom']['foobar'] = sprintf("#%04d is a good number. Trickster {contact.display_name}.", $row->context['contact_id']);
370 }
371 }
372
373 /**
374 * Inspired by dev/core#2673. This creates three custom tokens and uses each
375 * of them in a different template (subject/body_text/body_html). Ensure
376 * that all 3 tokens are properly evaluated.
377 *
378 * This is not literally the same as dev/core#2673. But that class of problem
379 * could arise in different code-paths. This just ensures that it arise in
380 * TokenProcessor.
381 *
382 * It also improves test-coverage of hooks and TokenProcessor.
383 *
384 * @link https://lab.civicrm.org/dev/core/-/issues/2673
385 */
386 public function testHookTokenDiagonal() {
387 $cid = $this->individualCreate();
388
389 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
390 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
391
392 \Civi::dispatcher()->addListener('hook_civicrm_tokens', function($e) {
393 $e->tokens['fruit'] = [
394 'fruit.apple' => ts('Apple'),
395 'fruit.banana' => ts('Banana'),
396 'fruit.cherry' => ts('Cherry'),
397 ];
398 });
399 \Civi::dispatcher()->addListener('hook_civicrm_tokenValues', function($e) {
400 $fruits = array_intersect($e->tokens['fruit'], ['apple', 'banana', 'cherry']);
401 foreach ($fruits as $fruit) {
402 foreach ($e->contactIDs as $cid) {
403 $e->details[$cid]['fruit.' . $fruit] = 'Nomnomnom' . $fruit;
404 }
405 }
406 });
407
408 unset(\Civi::$statics['CRM_Contact_Tokens']['hook_tokens']);
409 $tokenProcessor = new TokenProcessor($this->dispatcher, [
410 'controller' => __CLASS__,
411 'smarty' => FALSE,
412 ]);
413 $tokenProcessor->addMessage('subject', '!!{fruit.apple}!!', 'text/plain');
414 $tokenProcessor->addMessage('body_html', '!!{fruit.banana}!!', 'text/html');
415 $tokenProcessor->addMessage('body_text', '!!{fruit.cherry}!!', 'text/plain');
416 $tokenProcessor->addMessage('other', 'No fruit :(', 'text/plain');
417 $tokenProcessor->addRow(['contactId' => $cid]);
418 $tokenProcessor->evaluate();
419
420 foreach ($tokenProcessor->getRows() as $row) {
421 $this->assertEquals('!!Nomnomnomapple!!', $row->render('subject'));
422 $this->assertEquals('!!Nomnomnombanana!!', $row->render('body_html'));
423 $this->assertEquals('!!Nomnomnomcherry!!', $row->render('body_text'));
424 $this->assertEquals('No fruit :(', $row->render('other'));
425 $looped = TRUE;
426 }
427 $this->assertTrue(isset($looped));
428 }
429
430 /**
431 * Define extended tokens with funny symbols
432 */
433 public function testHookTokenExtraChar(): void {
434 $cid = $this->individualCreate();
435
436 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
437 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
438 \Civi::dispatcher()->addListener('hook_civicrm_tokens', function ($e) {
439 $e->tokens['food'] = [
440 'food.fruit.apple' => ts('Apple'),
441 'food.fruit:summary' => ts('Fruit summary'),
442 ];
443 });
444 \Civi::dispatcher()->addListener('hook_civicrm_tokenValues', function ($e) {
445 foreach ($e->tokens['food'] ?? [] as $subtoken) {
446 foreach ($e->contactIDs as $cid) {
447 switch ($subtoken) {
448 case 'fruit.apple':
449 $e->details[$cid]['food.fruit.apple'] = 'Fruit of the Tree';
450 break;
451
452 case 'fruit:summary':
453 $e->details[$cid]['food.fruit:summary'] = 'Apples, Bananas, and Cherries Oh My';
454 break;
455 }
456 }
457 }
458 });
459 unset(\Civi::$statics['CRM_Contact_Tokens']['hook_tokens']);
460 $expectRealSmartyOutputs = [
461 TRUE => 'Fruit of the Tree yes',
462 FALSE => 'Fruit of the Tree {if 1}yes{else}no{/if}',
463 ];
464
465 $loops = 0;
466 foreach ([TRUE, FALSE] as $smarty) {
467 $tokenProcessor = new TokenProcessor($this->dispatcher, [
468 'controller' => __CLASS__,
469 'smarty' => $smarty,
470 ]);
471 $tokenProcessor->addMessage('real_dot', '!!{food.fruit.apple}!!', 'text/plain');
472 $tokenProcessor->addMessage('real_dot_smarty', '{food.fruit.apple} {if 1}yes{else}no{/if}', 'text/plain');
473 $tokenProcessor->addMessage('real_colon', 'Summary of fruits: {food.fruit:summary}!', 'text/plain');
474 $tokenProcessor->addMessage('not_real_1', '!!{food.fruit}!!', 'text/plain');
475 $tokenProcessor->addMessage('not_real_2', '!!{food.apple}!!', 'text/plain');
476 $tokenProcessor->addMessage('not_real_3', '!!{fruit.apple}!!', 'text/plain');
477 $tokenProcessor->addMessage('not_real_4', '!!{food.fruit:apple}!!', 'text/plain');
478 $tokenProcessor->addRow(['contactId' => $cid]);
479 $tokenProcessor->evaluate();
480
481 foreach ($tokenProcessor->getRows() as $row) {
482 $loops++;
483 $this->assertEquals('!!Fruit of the Tree!!', $row->render('real_dot'));
484 $this->assertEquals($expectRealSmartyOutputs[$smarty], $row->render('real_dot_smarty'));
485 $this->assertEquals('Summary of fruits: Apples, Bananas, and Cherries Oh My!', $row->render('real_colon'));
486 $this->assertEquals('!!!!', $row->render('not_real_1'));
487 $this->assertEquals('!!!!', $row->render('not_real_2'));
488 $this->assertEquals('!!!!', $row->render('not_real_3'));
489 $this->assertEquals('!!!!', $row->render('not_real_4'));
490 }
491 }
492 $this->assertEquals(2, $loops);
493 }
494
495 /**
496 * Process a message using mocked data.
497 */
498 public function testMockData_ContactContribution(): void {
499 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
500 $this->dispatcher->addSubscriber(new \CRM_Contribute_Tokens());
501 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
502 $p = new TokenProcessor($this->dispatcher, [
503 'controller' => __CLASS__,
504 'schema' => ['contributionId', 'contactId'],
505 ]);
506 $p->addMessage('example', 'Invoice #{contribution.invoice_id} for {contact.display_name}!', 'text/plain');
507 $p->addRow([
508 'contactId' => 11,
509 'contact' => [
510 'display_name' => 'The Override',
511 ],
512 'contributionId' => 111,
513 'contribution' => [
514 'id' => 111,
515 'receive_date' => '2012-01-02',
516 'invoice_id' => 11111,
517 ],
518 ]);
519 $p->addRow([
520 'contactId' => 22,
521 'contact' => [
522 'display_name' => 'Another Override',
523 ],
524 'contributionId' => 222,
525 'contribution' => [
526 'id' => 111,
527 'receive_date' => '2012-01-02',
528 'invoice_id' => 22222,
529 ],
530 ]);
531 $p->evaluate();
532
533 $outputs = [];
534 foreach ($p->getRows() as $row) {
535 $outputs[] = $row->render('example');
536 }
537 $this->assertEquals('Invoice #11111 for The Override!', $outputs[0]);
538 $this->assertEquals('Invoice #22222 for Another Override!', $outputs[1]);
539 }
540
541 /**
542 * Process a message using mocked data, accessed through a Smarty alias.
543 */
544 public function testMockData_SmartyAlias_Contribution(): void {
545 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
546 $this->dispatcher->addSubscriber(new \CRM_Contribute_Tokens());
547 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
548
549 $p = new TokenProcessor($this->dispatcher, [
550 'controller' => __CLASS__,
551 'schema' => ['contributionId'],
552 'smarty' => TRUE,
553 'smartyTokenAlias' => [
554 'theInvoiceId' => 'contribution.invoice_id',
555 ],
556 ]);
557 $p->addMessage('example', 'Invoice #{$theInvoiceId}!', 'text/plain');
558 $p->addRow([
559 'contributionId' => 333,
560 'contribution' => [
561 'id' => 333,
562 'receive_date' => '2012-01-02',
563 'invoice_id' => 33333,
564 ],
565 ]);
566 $p->addRow([
567 'contributionId' => 444,
568 'contribution' => [
569 'id' => 444,
570 'receive_date' => '2012-01-02',
571 'invoice_id' => 44444,
572 ],
573 ]);
574 $p->evaluate();
575
576 $outputs = [];
577 foreach ($p->getRows() as $row) {
578 $outputs[] = $row->render('example');
579 }
580 $this->assertEquals('Invoice #33333!', $outputs[0]);
581 $this->assertEquals('Invoice #44444!', $outputs[1]);
582
583 }
584
585 /**
586 * This defines a compatibility mechanism wherein an old Smarty expression can
587 * be evaluated based on a newer token expression.
588 *
589 * Ex: $tokenContext['oldSmartyVar'] = 'new_entity.new_field';
590 */
591 public function testSmartyTokenAlias_Contribution(): void {
592 $first = $this->contributionCreate(['contact_id' => $this->individualCreate(), 'receive_date' => '2010-01-01', 'invoice_id' => 100, 'trxn_id' => 1000]);
593 $second = $this->contributionCreate(['contact_id' => $this->individualCreate(), 'receive_date' => '2011-02-02', 'invoice_id' => 200, 'trxn_id' => 1]);
594 $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
595 $this->dispatcher->addSubscriber(new \CRM_Contribute_Tokens());
596 $this->dispatcher->addSubscriber(new \CRM_Contact_Tokens());
597
598 $p = new TokenProcessor($this->dispatcher, [
599 'controller' => __CLASS__,
600 'schema' => ['contributionId'],
601 'smarty' => TRUE,
602 'smartyTokenAlias' => [
603 'theInvoiceId' => 'contribution.invoice_id',
604 ],
605 ]);
606 $p->addMessage('example', 'Invoice #{$theInvoiceId}!', 'text/plain');
607 $p->addRow(['contributionId' => $first]);
608 $p->addRow(['contributionId' => $second]);
609 $p->evaluate();
610
611 $outputs = [];
612 foreach ($p->getRows() as $row) {
613 $outputs[] = $row->render('example');
614 }
615 $this->assertEquals('Invoice #100!', $outputs[0]);
616 $this->assertEquals('Invoice #200!', $outputs[1]);
617 }
618
619 ///**
620 // * This defines a compatibility mechanism wherein an old Smarty expression can
621 // * be evaluated based on a newer token expression.
622 // *
623 // * The following example doesn't work because the handling of greeting+contact
624 // * tokens still use a special override (TokenCompatSubscriber::onRender).
625 // *
626 // * Ex: $tokenContext['oldSmartyVar'] = 'new_entity.new_field';
627 // */
628 // public function testSmartyTokenAlias_Contact() {
629 // $alice = $this->individualCreate(['first_name' => 'Alice']);
630 // $bob = $this->individualCreate(['first_name' => 'Bob']);
631 // $this->dispatcher->addSubscriber(new TokenCompatSubscriber());
632 //
633 // $p = new TokenProcessor($this->dispatcher, [
634 // 'controller' => __CLASS__,
635 // 'schema' => ['contactId'],
636 // 'smarty' => TRUE,
637 // 'smartyTokenAlias' => [
638 // 'myFirstName' => 'contact.first_name',
639 // ],
640 // ]);
641 // $p->addMessage('example', 'Hello {$myFirstName}!', 'text/plain');
642 // $p->addRow(['contactId' => $alice]);
643 // $p->addRow(['contactId' => $bob]);
644 // $p->evaluate();
645 //
646 // $outputs = [];
647 // foreach ($p->getRows() as $row) {
648 // $outputs[] = $row->render('example');
649 // }
650 // $this->assertEquals('Hello Alice!', $outputs[0]);
651 // $this->assertEquals('Hello Bob!', $outputs[1]);
652 // }
653
654 }