8bcc1826bfbef12be2aac44eaa4cfb6c85c59772
[civicrm-core.git] / tests / phpunit / CRM / Contribute / Form / Task / PDFLetterCommonTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Test APIv3 civicrm_contribute_* functions
14 *
15 * @package CiviCRM_APIv3
16 * @subpackage API_Contribution
17 * @group headless
18 */
19 class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
20
21 use CRMTraits_Custom_CustomDataTrait;
22
23 protected $_individualId;
24
25 protected $_docTypes;
26
27 protected $_contactIds;
28
29 /**
30 * Count how many times the hookTokens is called.
31 *
32 * This only needs to be called once, check refactoring doesn't change this.
33 *
34 * @var int
35 */
36 protected $hookTokensCalled = 0;
37
38 protected function setUp(): void {
39 parent::setUp();
40 $this->_individualId = $this->individualCreate(['first_name' => 'Anthony', 'last_name' => 'Collins']);
41 $this->_docTypes = CRM_Core_SelectValues::documentApplicationType();
42 $hooks = \CRM_Utils_Hook::singleton();
43 $hooks->setHook('civicrm_alterMailParams',
44 array($this, 'hook_alterMailParams'));
45 }
46
47 /**
48 * Clean up after each test.
49 *
50 * @throws \API_Exception
51 * @throws \CRM_Core_Exception
52 */
53 public function tearDown(): void {
54 $this->quickCleanUpFinancialEntities();
55 $this->quickCleanup(['civicrm_uf_match', 'civicrm_campaign'], TRUE);
56 CRM_Utils_Hook::singleton()->reset();
57 parent::tearDown();
58 }
59
60 /**
61 * Test thank you send with grouping.
62 *
63 * @throws \CRM_Core_Exception
64 * @throws \CiviCRM_API3_Exception
65 */
66 public function testGroupedThankYous(): void {
67 $this->ids['Contact'][0] = $this->individualCreate();
68 $this->createLoggedInUser();
69 $contribution1ID = $this->callAPISuccess('Contribution', 'create', [
70 'contact_id' => $this->ids['Contact'][0],
71 'total_amount' => '60',
72 'financial_type_id' => 'Donation',
73 'currency' => 'USD',
74 'receive_date' => '2021-01-01 13:21',
75 ])['id'];
76 $contribution2ID = $this->callAPISuccess('Contribution', 'create', [
77 'contact_id' => $this->ids['Contact'][0],
78 'total_amount' => '70',
79 'financial_type_id' => 'Donation',
80 'receive_date' => '2021-02-01 2:21',
81 'currency' => 'USD',
82 ])['id'];
83 /* @var CRM_Contribute_Form_Task_PDFLetter $form */
84 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', [
85 'campaign_id' => '',
86 'subject' => '',
87 'format_id' => '',
88 'paper_size' => 'letter',
89 'orientation' => 'portrait',
90 'metric' => 'in',
91 'margin_left' => '0.75',
92 'margin_right' => '0.75',
93 'margin_top' => '0.75',
94 'margin_bottom' => '0.75',
95 'document_type' => 'pdf',
96 'html_message' => '{contribution.currency} * {contribution.total_amount} * {contribution.receive_date}',
97 'template' => '',
98 'saveTemplateName' => '',
99 'from_email_address' => '185',
100 'thankyou_update' => '1',
101 'group_by' => 'contact_id',
102 'group_by_separator' => 'comma',
103 'email_options' => '',
104 ]);
105 $this->setSearchSelection([$contribution1ID, $contribution2ID], $form);
106 $form->preProcess();
107 try {
108 $form->postProcess();
109 }
110 catch (CRM_Core_Exception_PrematureExitException $e) {
111 $this->assertStringContainsString('USD, USD * $60.00, $70.00 * January 1st, 2021 1:21 PM, February 1st, 2021 2:21 AM', $e->errorData['html']);
112 }
113 }
114
115 /**
116 * Test the buildContributionArray function.
117 *
118 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
119 */
120 public function testBuildContributionArray(): void {
121 $this->_individualId = $this->individualCreate();
122
123 $customGroup = $this->callAPISuccess('CustomGroup', 'create', [
124 'title' => 'Test Custom Set for Contribution',
125 'extends' => 'Contribution',
126 'is_active' => TRUE,
127 ]);
128 $params = [
129 'custom_group_id' => $customGroup['id'],
130 'label' => 'Text field',
131 'html_type' => 'Text',
132 'data_type' => 'String',
133 'weight' => 1,
134 'is_active' => 1,
135 ];
136 $customField = $this->callAPISuccess('CustomField', 'create', $params);
137 $customFieldKey = 'custom_' . $customField['id'];
138 $campaignTitle = 'Test Campaign ';
139
140 $params = [
141 'contact_id' => $this->_individualId,
142 'total_amount' => 6,
143 'campaign_id' => $this->campaignCreate(['title' => $campaignTitle], FALSE),
144 'financial_type_id' => 'Donation',
145 $customFieldKey => 'Text_',
146 ];
147 $contributionIDs = $returnProperties = [];
148 $result = $this->callAPISuccess('Contribution', 'create', $params);
149 $contributionIDs[] = $result['id'];
150 $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
151
152 // assume that there are two token {contribution.financial_type} and
153 // {contribution.custom_N} in message content
154 $messageToken = [
155 'contribution' => [
156 'financial_type',
157 'payment_instrument',
158 'campaign',
159 $customFieldKey,
160 ],
161 ];
162
163 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter');
164 [$contributions, $contacts] = $form->buildContributionArray('contact_id', $contributionIDs, $returnProperties, TRUE, TRUE, $messageToken, 'test', '**', FALSE);
165
166 $this->assertEquals('Anthony', $contacts[$this->_individualId]['first_name']);
167 $this->assertEquals('Donation', $contributions[$result['id']]['financial_type']);
168 $this->assertEquals($campaignTitle, $contributions[$result['id']]['campaign']);
169 $this->assertEquals('Check', $contributions[$result['id']]['payment_instrument']);
170 // CRM-20359: assert that contribution custom field token is rightfully replaced by its value
171 $this->assertEquals($params[$customFieldKey], $contributions[$result['id']][$customFieldKey]);
172
173 $this->customFieldDelete($customField['id']);
174 $this->customGroupDelete($customGroup['id']);
175 }
176
177 /**
178 * Implement token values hook.
179 *
180 * @param array $details
181 * @param array $contactIDs
182 * @param int $jobID
183 * @param array $tokens
184 * @param string $className
185 */
186 public function hookTokenValues(&$details, $contactIDs, $jobID, $tokens, $className): void {
187 foreach ($details as $index => $detail) {
188 $details[$index]['favourite_emoticon'] = 'emo';
189 }
190 }
191
192 /**
193 * Test contribution token replacement in
194 * html returned by postProcess function.
195 *
196 * @throws \CiviCRM_API3_Exception
197 * @throws \CRM_Core_Exception
198 */
199 public function testPostProcess(): void {
200 $this->createLoggedInUser();
201 foreach (['docx', 'odt'] as $docType) {
202 $formValues = [
203 'group_by' => NULL,
204 'document_file' => [
205 'name' => __DIR__ . "/sample_documents/Template.$docType",
206 'type' => $this->_docTypes[$docType],
207 ],
208 ];
209
210 $contributionId = $this->createContribution();
211 /* @var $form CRM_Contribute_Form_Task_PDFLetter */
212 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
213 $form->setContributionIds([$contributionId]);
214 $format = Civi::settings()->get('dateformatFull');
215 $date = CRM_Utils_Date::getToday();
216 $displayDate = CRM_Utils_Date::customFormat($date, $format);
217
218 try {
219 $form->postProcess();
220 $this->fail('Exception expected');
221 }
222 catch (CRM_Core_Exception_PrematureExitException $e) {
223 $html = $e->errorData['html'];
224 }
225 $expectedValues = [
226 'Hello Anthony',
227 '$ 100.00',
228 $displayDate,
229 'Donation',
230 'Domain Name - Default Domain Name',
231 ];
232
233 foreach ($expectedValues as $val) {
234 $this->assertNotSame(strpos($html[$contributionId], $val), 0);
235 }
236 }
237 }
238
239 /**
240 * Test that no notice or errors occur if no contribution tokens are requested.
241 *
242 * @throws \CRM_Core_Exception
243 * @throws \CiviCRM_API3_Exception
244 */
245 public function testNoContributionTokens(): void {
246 $this->createLoggedInUser();
247 $formValues = [
248 'html_message' => '{contact.display_name}',
249 'document_type' => 'pdf',
250 ];
251 /* @var $form CRM_Contribute_Form_Task_PDFLetter */
252 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
253 $form->setContributionIds([$this->createContribution()]);
254 try {
255 $form->postProcess();
256 }
257 catch (CRM_Core_Exception_PrematureExitException $e) {
258 $html = $e->errorData['html'];
259 }
260 $this->assertStringContainsString('Mr. Anthony Anderson II', $html);
261 }
262
263 /**
264 * Test all contribution tokens.
265 *
266 * @throws \CRM_Core_Exception
267 * @throws \CiviCRM_API3_Exception
268 */
269 public function testAllContributionTokens(): void {
270 $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
271 $this->hookClass->setHook('civicrm_tokens', [$this, 'hook_tokens']);
272
273 $this->createLoggedInUser();
274 $this->createCustomGroupWithFieldsOfAllTypes(['extends' => 'Contribution']);
275 $this->campaignCreate(['name' => 'Big one', 'title' => 'Big one'], FALSE);
276 $tokens = $this->getAllContributionTokens();
277 $formValues = [
278 'document_type' => 'pdf',
279 'html_message' => '',
280 ];
281 foreach (array_keys($this->getAllContributionTokens()) as $token) {
282 $formValues['html_message'] .= "$token : {contribution.$token}\n";
283 }
284 $formValues['html_message'] .= '{emoji.favourite_emoticon}';
285 /* @var $form CRM_Contribute_Form_Task_PDFLetter */
286 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
287 $form->setContributionIds([$this->createContribution(array_merge(['campaign_id' => $tokens['campaign_id:label']], $tokens))]);
288 try {
289 $form->postProcess();
290 }
291 catch (CRM_Core_Exception_PrematureExitException $e) {
292 $html = $e->errorData['html'];
293 }
294 $this->assertEquals('
295 <html>
296 <head>
297 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
298 <style>@page { margin: 0.75in 0.75in 0.75in 0.75in; }</style>
299 <style type="text/css">@import url(' . CRM_Core_Config::singleton()->userFrameworkResourceURL . 'css/print.css);</style>
300 ' . " \n" . ' </head>
301 <body>
302 <div id="crm-container">
303 id : 1
304 total_amount : €9,999.99
305 fee_amount : €1,111.11
306 net_amount : €7,777.78
307 non_deductible_amount : €2,222.22
308 receive_date : July 20th, 2018
309 payment_instrument_id:label : Check
310 trxn_id : 1234
311 invoice_id : 568
312 currency : EUR
313 cancel_date : December 30th, 2019
314 cancel_reason : Contribution Cancel Reason
315 receipt_date : October 30th, 2019
316 thankyou_date : November 30th, 2019
317 source : Contribution Source
318 amount_level : Amount Level
319 contribution_status_id : 2
320 check_number : 6789
321 campaign_id:label : Big one
322 ' . $this->getCustomFieldName('text') . ' : Bobsled
323 ' . $this->getCustomFieldName('select_string') . ' : Red
324 ' . $this->getCustomFieldName('select_date') . ' : 01/20/2021 12:00AM
325 ' . $this->getCustomFieldName('int') . ' : 999
326 ' . $this->getCustomFieldName('link') . ' : <a href="http://civicrm.org" target="_blank">http://civicrm.org</a>
327 ' . $this->getCustomFieldName('country') . ' : New Zealand
328 ' . $this->getCustomFieldName('multi_country') . ' : France, Canada
329 ' . $this->getCustomFieldName('contact_reference') . ' : Mr. Spider Man II
330 ' . $this->getCustomFieldName('state') . ' : Queensland
331 ' . $this->getCustomFieldName('multi_state') . ' : Victoria, New South Wales
332 ' . $this->getCustomFieldName('boolean') . ' : Yes
333 ' . $this->getCustomFieldName('checkbox') . ' : Purple
334 emo
335 </div>
336 </body>
337 </html>', $html);
338 }
339
340 /**
341 * Get all the tokens available to contributions.
342 *
343 * @return array
344 */
345 public function getAllContributionTokens(): array {
346 return [
347 'id' => '',
348 'total_amount' => '9999.99',
349 'fee_amount' => '1111.11',
350 'net_amount' => '7777.78',
351 'non_deductible_amount' => '2222.22',
352 'receive_date' => '2018-07-20',
353 'payment_instrument_id:label' => 'Check',
354 'trxn_id' => '1234',
355 'invoice_id' => '568',
356 'currency' => 'EUR',
357 'cancel_date' => '2019-12-30',
358 'cancel_reason' => 'Contribution Cancel Reason',
359 'receipt_date' => '2019-10-30',
360 'thankyou_date' => '2019-11-30',
361 'source' => 'Contribution Source',
362 'amount_level' => 'Amount Level',
363 'contribution_status_id' => 'Pending',
364 'check_number' => '6789',
365 'campaign_id:label' => 'Big one',
366 $this->getCustomFieldName('text') => 'Bobsled',
367 $this->getCustomFieldName('select_string') => 'R',
368 $this->getCustomFieldName('select_date') => '2021-01-20',
369 $this->getCustomFieldName('int') => 999,
370 $this->getCustomFieldName('link') => 'http://civicrm.org',
371 $this->getCustomFieldName('country') => 'New Zealand',
372 $this->getCustomFieldName('multi_country') => ['France', 'Canada'],
373 $this->getCustomFieldName('contact_reference') => $this->individualCreate(['first_name' => 'Spider', 'last_name' => 'Man']),
374 $this->getCustomFieldName('state') => 'Queensland',
375 $this->getCustomFieldName('multi_state') => ['Victoria', 'New South Wales'],
376 $this->getCustomFieldName('boolean') => TRUE,
377 $this->getCustomFieldName('checkbox') => 'P',
378 $this->getCustomFieldName('contact_reference') => $this->individualCreate(['first_name' => 'Spider', 'last_name' => 'Man']),
379 ];
380 }
381
382 /**
383 * Test assignment of variables when using the group by function.
384 *
385 * We are looking to see that the contribution aggregate and contributions
386 * arrays reflect the most recent contact rather than a total aggregate,
387 * since we are using group by.
388 *
389 * @throws \CiviCRM_API3_Exception
390 * @throws \CRM_Core_Exception
391 */
392 public function testPostProcessGroupByContact(): void {
393 $this->createLoggedInUser();
394 $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hook_aggregateTokenValues']);
395 $this->hookClass->setHook('civicrm_tokens', [$this, 'hook_tokens']);
396 $this->mut = new CiviMailUtils($this, TRUE);
397 $this->_individualId = $this->individualCreate();
398 $this->_individualId2 = $this->individualCreate();
399 $htmlMessage = '{aggregate.rendered_token}';
400 $formValues = [
401 'group_by' => 'contact_id',
402 'html_message' => $htmlMessage,
403 'email_options' => 'both',
404 'subject' => 'Testy test test',
405 'from' => 'info@example.com',
406 ];
407
408 $contributionIDs = [];
409 $contribution = $this->callAPISuccess('Contribution', 'create', [
410 'contact_id' => $this->_individualId,
411 'total_amount' => 100,
412 'financial_type_id' => 'Donation',
413 'receive_date' => '2016-12-25',
414 ]);
415 $contributionIDs[] = $contribution['id'];
416 $contribution = $this->callAPISuccess('Contribution', 'create', [
417 'contact_id' => $this->_individualId2,
418 'total_amount' => 10,
419 'financial_type_id' => 'Donation',
420 'receive_date' => '2016-12-25',
421 ]);
422 $contributionIDs[] = $contribution['id'];
423
424 $contribution = $this->callAPISuccess('Contribution', 'create', [
425 'contact_id' => $this->_individualId2,
426 'total_amount' => 1,
427 'financial_type_id' => 'Donation',
428 'receive_date' => '2016-12-25',
429 ]);
430 $contributionIDs[] = $contribution['id'];
431
432 /* @var \CRM_Contribute_Form_Task_PDFLetter $form */
433 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
434 $form->setContributionIds($contributionIDs);
435
436 try {
437 $form->postProcess();
438 $this->fail('exception expected.');
439 }
440 catch (CRM_Core_Exception_PrematureExitException $e) {
441 $html = $e->errorData['html'];
442 }
443 $this->assertEquals("<table border='1' cellpadding='2' cellspacing='0' class='table'>
444 <tbody>
445 <tr>
446 <th>Date</th>
447 <th>Amount</th>
448 <th>Financial Type</th>
449 <th>Source</th>
450 </tr>
451 <!--
452 -->
453 <tr>
454 <td>25 December 2016</td>
455 <td>$100.00</td>
456 <td>Donation</td>
457 <td></td>
458 </tr>
459 <!--
460 -->
461 <tr>
462 <td><strong>Total</strong></td>
463 <td><strong>$100.00</strong></td>
464 <td></td>
465 <td></td>
466 </tr>
467 </tbody>
468 </table>", $html[1]);
469 $this->assertEquals("<table border='1' cellpadding='2' cellspacing='0' class='table'>
470 <tbody>
471 <tr>
472 <th>Date</th>
473 <th>Amount</th>
474 <th>Financial Type</th>
475 <th>Source</th>
476 </tr>
477 <!--
478 -->
479 <tr>
480 <td>25 December 2016</td>
481 <td>$10.00</td>
482 <td>Donation</td>
483 <td></td>
484 </tr>
485 <!--
486 -->
487 <tr>
488 <td>25 December 2016</td>
489 <td>$1.00</td>
490 <td>Donation</td>
491 <td></td>
492 </tr>
493 <!--
494 -->
495 <tr>
496 <td><strong>Total</strong></td>
497 <td><strong>$11.00</strong></td>
498 <td></td>
499 <td></td>
500 </tr>
501 </tbody>
502 </table>", $html[2]);
503
504 $activities = $this->callAPISuccess('Activity', 'get', ['activity_type_id' => 'Print PDF Letter', 'sequential' => 1]);
505 $this->assertEquals(2, $activities['count']);
506 $this->assertEquals($html[1], $activities['values'][0]['details']);
507 $this->assertEquals($html[2], $activities['values'][1]['details']);
508 // Checking it is not called multiple times.
509 // once for each contact create + once for the activities.
510 // By calling the cached function we can get this down to 1
511 $this->assertEquals(3, $this->hookTokensCalled);
512 $this->mut->checkAllMailLog($html);
513
514 }
515
516 /**
517 * Implements civicrm_tokens().
518 */
519 public function hook_tokens(&$tokens): void {
520 $this->hookTokensCalled++;
521 $tokens['aggregate'] = ['rendered_token' => 'rendered_token'];
522 $tokens['emoji'] = ['favourite_emoticon' => 'favourite_emoticon'];
523 }
524
525 /**
526 * Get the html message.
527 *
528 * @return string
529 */
530 public function getHtmlMessage() {
531 return '{assign var=\'contact_aggregate\' value=0}
532 <table border=\'1\' cellpadding=\'2\' cellspacing=\'0\' class=\'table\'>
533 <tbody>
534 <tr>
535 <th>Date</th>
536 <th>Amount</th>
537 <th>Financial Type</th>
538 <th>Source</th>
539 </tr>
540 <!--
541 {foreach from=$contributions item=contribution}
542 {if $contribution.contact_id == $messageContactID}
543 {assign var=\'date\' value=$contribution.receive_date|date_format:\'%d %B %Y\'}
544 {assign var=contact_aggregate
545 value=$contact_aggregate+$contribution.total_amount}
546 -->
547 <tr>
548 <td>{$date}</td>
549 <td>{$contribution.total_amount|crmMoney}</td>
550 <td>{$contribution.financial_type}</td>
551 <td></td>
552 </tr>
553 <!--
554 {/if}
555 {/foreach}
556 -->
557 <tr>
558 <td><strong>Total</strong></td>
559 <td><strong>{$contact_aggregate|crmMoney}</strong></td>
560 <td></td>
561 <td></td>
562 </tr>
563 </tbody>
564 </table>';
565 }
566
567 /**
568 * Implements CiviCRM hook.
569 *
570 * @param array $values
571 * @param array $contactIDs
572 * @param null $job
573 * @param array $tokens
574 * @param null $context
575 */
576 public function hook_aggregateTokenValues(array &$values, $contactIDs, $job = NULL, $tokens = [], $context = NULL) {
577 foreach ($contactIDs as $contactID) {
578 CRM_Core_Smarty::singleton()->assign('messageContactID', $contactID);
579 $values[$contactID]['aggregate.rendered_token'] = CRM_Core_Smarty::singleton()
580 ->fetch('string:' . $this->getHtmlMessage());
581 }
582 }
583
584 /**
585 * @param string $token
586 * @param string $entity
587 * @param string $textToSearch
588 * @param bool $expected
589 *
590 * @dataProvider isHtmlTokenInTableCellProvider
591 */
592 public function testIsHtmlTokenInTableCell($token, $entity, $textToSearch, $expected): void {
593 $this->assertEquals($expected,
594 CRM_Contribute_Form_Task_PDFLetter::isHtmlTokenInTableCell($token, $entity, $textToSearch)
595 );
596 }
597
598 public function isHtmlTokenInTableCellProvider() {
599 return [
600
601 'simplest TRUE' => [
602 'token',
603 'entity',
604 '<td>{entity.token}</td>',
605 TRUE,
606 ],
607
608 'simplest FALSE' => [
609 'token',
610 'entity',
611 '{entity.token}',
612 FALSE,
613 ],
614
615 'token between two tables' => [
616 'token',
617 'entity',
618 ' <table><tr><td>Top</td></tr></table>
619 {entity.token}
620 <table><tr><td>Bottom</td></tr></table>',
621 FALSE,
622 ],
623
624 'token in two tables' => [
625 'token',
626 'entity',
627 ' <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>
628 <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>',
629 TRUE,
630 ],
631
632 'token outside of table and inside of table' => [
633 'token',
634 'entity',
635 ' {entity.token}
636 <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>',
637 FALSE,
638 ],
639
640 'token inside more complicated table' => [
641 'token',
642 'entity',
643 ' <table><tr><td class="foo"><em>{entity.token}</em></td></tr></table>',
644 TRUE,
645 ],
646
647 'token inside something that looks like table cell' => [
648 'token',
649 'entity',
650 ' <tdata>{entity.token}</tdata>
651 <table><tr><td>Bottom</td></tr></table>',
652 FALSE,
653 ],
654
655 ];
656 }
657
658 /**
659 * @param array $entities
660 * @param \CRM_Core_Form $form
661 */
662 protected function setSearchSelection(array $entities, CRM_Core_Form $form): void {
663 $_SESSION['_' . $form->controller->_name . '_container']['values']['Search'] = [
664 'radio_ts' => 'ts_sel',
665 ];
666 foreach ($entities as $entityID) {
667 $_SESSION['_' . $form->controller->_name . '_container']['values']['Search']['mark_x_' . $entityID] = TRUE;
668 }
669 }
670
671 /**
672 * @param array $contributionParams
673 *
674 * @return mixed
675 */
676 protected function createContribution(array $contributionParams = []) {
677 $contributionParams = array_merge([
678 'contact_id' => $this->individualCreate(),
679 'total_amount' => 100,
680 'financial_type_id' => 'Donation',
681 'source' => 'Contribution Source',
682 ], $contributionParams);
683 return $this->callAPISuccess('Contribution', 'create', $contributionParams)['id'];
684 }
685
686 /**
687 * @see CRM_Utils_Hook::alterMailParams
688 */
689 public function hook_alterMailParams(&$params, $context = NULL) {
690 $this->assertTrue(isset($params['contactId']));
691 }
692
693 }