3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
13 * Test APIv3 civicrm_contribute_* functions
15 * @package CiviCRM_APIv3
16 * @subpackage API_Contribution
19 class CRM_Contribute_Form_Task_PDFLetterCommonTest
extends CiviUnitTestCase
{
21 use CRMTraits_Custom_CustomDataTrait
;
23 protected $_individualId;
27 protected $_contactIds;
30 * Count how many times the hookTokens is called.
32 * This only needs to be called once, check refactoring doesn't change this.
36 protected $hookTokensCalled = 0;
38 protected function setUp(): void
{
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'));
48 * Clean up after each test.
50 * @throws \API_Exception
51 * @throws \CRM_Core_Exception
53 public function tearDown(): void
{
54 $this->quickCleanUpFinancialEntities();
55 $this->quickCleanup(['civicrm_uf_match', 'civicrm_campaign'], TRUE);
56 CRM_Utils_Hook
::singleton()->reset();
61 * Test thank you send with grouping.
63 * @throws \CRM_Core_Exception
64 * @throws \CiviCRM_API3_Exception
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',
74 'receive_date' => '2021-01-01 13:21',
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',
83 /* @var CRM_Contribute_Form_Task_PDFLetter $form */
84 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', [
88 'paper_size' => 'letter',
89 'orientation' => 'portrait',
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}',
98 'saveTemplateName' => '',
99 'from_email_address' => '185',
100 'thankyou_update' => '1',
101 'group_by' => 'contact_id',
102 'group_by_separator' => 'comma',
103 'email_options' => '',
105 $this->setSearchSelection([$contribution1ID, $contribution2ID], $form);
108 $form->postProcess();
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']);
116 * Test the buildContributionArray function.
118 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
120 public function testBuildContributionArray(): void
{
121 $this->_individualId
= $this->individualCreate();
123 $customGroup = $this->callAPISuccess('CustomGroup', 'create', [
124 'title' => 'Test Custom Set for Contribution',
125 'extends' => 'Contribution',
129 'custom_group_id' => $customGroup['id'],
130 'label' => 'Text field',
131 'html_type' => 'Text',
132 'data_type' => 'String',
136 $customField = $this->callAPISuccess('CustomField', 'create', $params);
137 $customFieldKey = 'custom_' . $customField['id'];
138 $campaignTitle = 'Test Campaign ';
141 'contact_id' => $this->_individualId
,
143 'campaign_id' => $this->campaignCreate(['title' => $campaignTitle], FALSE),
144 'financial_type_id' => 'Donation',
145 $customFieldKey => 'Text_',
147 $contributionIDs = $returnProperties = [];
148 $result = $this->callAPISuccess('Contribution', 'create', $params);
149 $contributionIDs[] = $result['id'];
150 $this->hookClass
->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
152 // assume that there are two token {contribution.financial_type} and
153 // {contribution.custom_N} in message content
157 'payment_instrument',
163 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter');
164 [$contributions, $contacts] = $form->buildContributionArray('contact_id', $contributionIDs, $returnProperties, TRUE, TRUE, $messageToken, 'test', '**', FALSE);
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]);
173 $this->customFieldDelete($customField['id']);
174 $this->customGroupDelete($customGroup['id']);
178 * Implement token values hook.
180 * @param array $details
181 * @param array $contactIDs
183 * @param array $tokens
184 * @param string $className
186 public function hookTokenValues(&$details, $contactIDs, $jobID, $tokens, $className): void
{
187 foreach ($details as $index => $detail) {
188 $details[$index]['favourite_emoticon'] = 'emo';
193 * Test contribution token replacement in
194 * html returned by postProcess function.
196 * @throws \CiviCRM_API3_Exception
197 * @throws \CRM_Core_Exception
199 public function testPostProcess(): void
{
200 $this->createLoggedInUser();
201 foreach (['docx', 'odt'] as $docType) {
205 'name' => __DIR__
. "/sample_documents/Template.$docType",
206 'type' => $this->_docTypes
[$docType],
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);
219 $form->postProcess();
220 $this->fail('Exception expected');
222 catch (CRM_Core_Exception_PrematureExitException
$e) {
223 $html = $e->errorData
['html'];
230 'Domain Name - Default Domain Name',
233 foreach ($expectedValues as $val) {
234 $this->assertNotSame(strpos($html[$contributionId], $val), 0);
240 * Test that no notice or errors occur if no contribution tokens are requested.
242 * @throws \CRM_Core_Exception
243 * @throws \CiviCRM_API3_Exception
245 public function testNoContributionTokens(): void
{
246 $this->createLoggedInUser();
248 'html_message' => '{contact.display_name}',
249 'document_type' => 'pdf',
251 /* @var $form CRM_Contribute_Form_Task_PDFLetter */
252 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
253 $form->setContributionIds([$this->createContribution()]);
255 $form->postProcess();
257 catch (CRM_Core_Exception_PrematureExitException
$e) {
258 $html = $e->errorData
['html'];
260 $this->assertStringContainsString('Mr. Anthony Anderson II', $html);
264 * Test all contribution tokens.
266 * @throws \CRM_Core_Exception
267 * @throws \CiviCRM_API3_Exception
269 public function testAllContributionTokens(): void
{
270 $this->hookClass
->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
271 $this->hookClass
->setHook('civicrm_tokens', [$this, 'hook_tokens']);
273 $this->createLoggedInUser();
274 $this->createCustomGroupWithFieldsOfAllTypes(['extends' => 'Contribution']);
275 $this->campaignCreate(['name' => 'Big one', 'title' => 'Big one'], FALSE);
276 $tokens = $this->getAllContributionTokens();
278 'document_type' => 'pdf',
279 'html_message' => '',
281 foreach (array_keys($this->getAllContributionTokens()) as $token) {
282 $formValues['html_message'] .= "$token : {contribution.$token}\n";
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))]);
289 $form->postProcess();
291 catch (CRM_Core_Exception_PrematureExitException
$e) {
292 $html = $e->errorData
['html'];
294 $this->assertEquals('
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>
302 <div id="crm-container">
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
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
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
341 * Get all the tokens available to contributions.
345 public function getAllContributionTokens(): array {
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',
355 'invoice_id' => '568',
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']),
383 * Test assignment of variables when using the group by function.
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.
389 * @throws \CiviCRM_API3_Exception
390 * @throws \CRM_Core_Exception
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}';
401 'group_by' => 'contact_id',
402 'html_message' => $htmlMessage,
403 'email_options' => 'both',
404 'subject' => 'Testy test test',
405 'from' => 'info@example.com',
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',
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',
422 $contributionIDs[] = $contribution['id'];
424 $contribution = $this->callAPISuccess('Contribution', 'create', [
425 'contact_id' => $this->_individualId2
,
427 'financial_type_id' => 'Donation',
428 'receive_date' => '2016-12-25',
430 $contributionIDs[] = $contribution['id'];
432 /* @var \CRM_Contribute_Form_Task_PDFLetter $form */
433 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
434 $form->setContributionIds($contributionIDs);
437 $form->postProcess();
438 $this->fail('exception expected.');
440 catch (CRM_Core_Exception_PrematureExitException
$e) {
441 $html = $e->errorData
['html'];
443 $this->assertEquals("<table border='1' cellpadding='2' cellspacing='0' class='table'>
448 <th>Financial Type</th>
454 <td>25 December 2016</td>
462 <td><strong>Total</strong></td>
463 <td><strong>$100.00</strong></td>
468 </table>", $html[1]);
469 $this->assertEquals("<table border='1' cellpadding='2' cellspacing='0' class='table'>
474 <th>Financial Type</th>
480 <td>25 December 2016</td>
488 <td>25 December 2016</td>
496 <td><strong>Total</strong></td>
497 <td><strong>$11.00</strong></td>
502 </table>", $html[2]);
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);
517 * Implements civicrm_tokens().
519 public function hook_tokens(&$tokens): void
{
520 $this->hookTokensCalled++
;
521 $tokens['aggregate'] = ['rendered_token' => 'rendered_token'];
522 $tokens['emoji'] = ['favourite_emoticon' => 'favourite_emoticon'];
526 * Get the html message.
530 public function getHtmlMessage() {
531 return '{assign var=\'contact_aggregate\' value=0}
532 <table border=\'1\' cellpadding=\'2\' cellspacing=\'0\' class=\'table\'>
537 <th>Financial Type</th>
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}
549 <td>{$contribution.total_amount|crmMoney}</td>
550 <td>{$contribution.financial_type}</td>
558 <td><strong>Total</strong></td>
559 <td><strong>{$contact_aggregate|crmMoney}</strong></td>
568 * Implements CiviCRM hook.
570 * @param array $values
571 * @param array $contactIDs
573 * @param array $tokens
574 * @param null $context
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());
585 * @param string $token
586 * @param string $entity
587 * @param string $textToSearch
588 * @param bool $expected
590 * @dataProvider isHtmlTokenInTableCellProvider
592 public function testIsHtmlTokenInTableCell($token, $entity, $textToSearch, $expected): void
{
593 $this->assertEquals($expected,
594 CRM_Contribute_Form_Task_PDFLetter
::isHtmlTokenInTableCell($token, $entity, $textToSearch)
598 public function isHtmlTokenInTableCellProvider() {
604 '<td>{entity.token}</td>',
608 'simplest FALSE' => [
615 'token between two tables' => [
618 ' <table><tr><td>Top</td></tr></table>
620 <table><tr><td>Bottom</td></tr></table>',
624 'token in two tables' => [
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>',
632 'token outside of table and inside of table' => [
636 <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>',
640 'token inside more complicated table' => [
643 ' <table><tr><td class="foo"><em>{entity.token}</em></td></tr></table>',
647 'token inside something that looks like table cell' => [
650 ' <tdata>{entity.token}</tdata>
651 <table><tr><td>Bottom</td></tr></table>',
659 * @param array $entities
660 * @param \CRM_Core_Form $form
662 protected function setSearchSelection(array $entities, CRM_Core_Form
$form): void
{
663 $_SESSION['_' . $form->controller
->_name
. '_container']['values']['Search'] = [
664 'radio_ts' => 'ts_sel',
666 foreach ($entities as $entityID) {
667 $_SESSION['_' . $form->controller
->_name
. '_container']['values']['Search']['mark_x_' . $entityID] = TRUE;
672 * @param array $contributionParams
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'];
687 * @see CRM_Utils_Hook::alterMailParams
689 public function hook_alterMailParams(&$params, $context = NULL) {
690 $this->assertTrue(isset($params['contactId']));