dev/core#2568 Enotice fix + test
[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 protected $_individualId;
22
23 protected $_docTypes;
24
25 protected $_contactIds;
26
27 /**
28 * Count how many times the hookTokens is called.
29 *
30 * This only needs to be called once, check refactoring doesn't change this.
31 *
32 * @var int
33 */
34 protected $hookTokensCalled = 0;
35
36 protected function setUp(): void {
37 parent::setUp();
38 $this->_individualId = $this->individualCreate(['first_name' => 'Anthony', 'last_name' => 'Collins']);
39 $this->_docTypes = CRM_Core_SelectValues::documentApplicationType();
40 }
41
42 /**
43 * Clean up after each test.
44 *
45 * @throws \CRM_Core_Exception
46 */
47 public function tearDown(): void {
48 $this->quickCleanUpFinancialEntities();
49 $this->quickCleanup(['civicrm_uf_match']);
50 CRM_Utils_Hook::singleton()->reset();
51 }
52
53 /**
54 * Test thank you send with grouping.
55 *
56 * @throws \CRM_Core_Exception
57 * @throws \CiviCRM_API3_Exception
58 */
59 public function testGroupedThankYous(): void {
60 $this->ids['Contact'][0] = $this->individualCreate();
61 $this->createLoggedInUser();
62 $contribution1ID = $this->callAPISuccess('Contribution', 'create', [
63 'contact_id' => $this->ids['Contact'][0],
64 'total_amount' => '60',
65 'financial_type_id' => 'Donation',
66 'currency' => 'USD',
67 'receive_date' => '2021-01-01 13:21',
68 ])['id'];
69 $contribution2ID = $this->callAPISuccess('Contribution', 'create', [
70 'contact_id' => $this->ids['Contact'][0],
71 'total_amount' => '70',
72 'financial_type_id' => 'Donation',
73 'receive_date' => '2021-02-01 2:21',
74 'currency' => 'USD',
75 ])['id'];
76 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', [
77 'campaign_id' => '',
78 'subject' => '',
79 'format_id' => '',
80 'paper_size' => 'letter',
81 'orientation' => 'portrait',
82 'metric' => 'in',
83 'margin_left' => '0.75',
84 'margin_right' => '0.75',
85 'margin_top' => '0.75',
86 'margin_bottom' => '0.75',
87 'document_type' => 'pdf',
88 'html_message' => '{contribution.currency} * {contribution.total_amount} * {contribution.receive_date}',
89 'template' => '',
90 'saveTemplateName' => '',
91 'from_email_address' => '185',
92 'thankyou_update' => '1',
93 'group_by' => 'contact_id',
94 'group_by_separator' => 'comma',
95 'email_options' => '',
96 ]);
97 $this->setSearchSelection([$contribution1ID, $contribution2ID], $form);
98 $form->preProcess();
99 try {
100 $form->postProcess();
101 }
102 catch (CRM_Core_Exception_PrematureExitException $e) {
103 $this->assertContains('USD, USD * $ 60.00, $ 70.00 * January 1st, 2021 1:21 PM, February 1st, 2021 2:21 AM', $e->errorData['html']);
104 }
105 }
106
107 /**
108 * Test the buildContributionArray function.
109 *
110 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
111 */
112 public function testBuildContributionArray(): void {
113 $this->_individualId = $this->individualCreate();
114
115 $customGroup = $this->callAPISuccess('CustomGroup', 'create', [
116 'title' => 'Test Custom Set for Contribution',
117 'extends' => 'Contribution',
118 'is_active' => TRUE,
119 ]);
120 $params = [
121 'custom_group_id' => $customGroup['id'],
122 'label' => 'Text field',
123 'html_type' => 'Text',
124 'data_type' => 'String',
125 'weight' => 1,
126 'is_active' => 1,
127 ];
128 $customField = $this->callAPISuccess('CustomField', 'create', $params);
129 $customFieldKey = 'custom_' . $customField['id'];
130 $campaignTitle = 'Test Campaign ';
131
132 $params = [
133 'contact_id' => $this->_individualId,
134 'total_amount' => 6,
135 'campaign_id' => $this->campaignCreate(['title' => $campaignTitle], FALSE),
136 'financial_type_id' => 'Donation',
137 $customFieldKey => 'Text_' . substr(sha1(rand()), 0, 7),
138 ];
139 $contributionIDs = $returnProperties = [];
140 $result = $this->callAPISuccess('Contribution', 'create', $params);
141 $contributionIDs[] = $result['id'];
142 $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hookTokenValues']);
143
144 // assume that there are two token {contribution.financial_type} and
145 // {contribution.custom_N} in message content
146 $messageToken = [
147 'contribution' => [
148 'financial_type',
149 'payment_instrument',
150 'campaign',
151 $customFieldKey,
152 ],
153 ];
154
155 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter');
156 [$contributions, $contacts] = $form->buildContributionArray('contact_id', $contributionIDs, $returnProperties, TRUE, TRUE, $messageToken, 'test', '**', FALSE);
157
158 $this->assertEquals('Anthony', $contacts[$this->_individualId]['first_name']);
159 $this->assertEquals('emo', $contacts[$this->_individualId]['favourite_emoticon']);
160 $this->assertEquals('Donation', $contributions[$result['id']]['financial_type']);
161 $this->assertEquals($campaignTitle, $contributions[$result['id']]['campaign']);
162 $this->assertEquals('Check', $contributions[$result['id']]['payment_instrument']);
163 // CRM-20359: assert that contribution custom field token is rightfully replaced by its value
164 $this->assertEquals($params[$customFieldKey], $contributions[$result['id']][$customFieldKey]);
165
166 $this->customFieldDelete($customField['id']);
167 $this->customGroupDelete($customGroup['id']);
168 }
169
170 /**
171 * Implement token values hook.
172 *
173 * @param array $details
174 * @param array $contactIDs
175 * @param int $jobID
176 * @param array $tokens
177 * @param string $className
178 */
179 public function hookTokenValues(&$details, $contactIDs, $jobID, $tokens, $className): void {
180 foreach ($details as $index => $detail) {
181 $details[$index]['favourite_emoticon'] = 'emo';
182 }
183 }
184
185 /**
186 * Test contribution token replacement in
187 * html returned by postProcess function.
188 *
189 * @throws \CiviCRM_API3_Exception
190 * @throws \CRM_Core_Exception
191 */
192 public function testPostProcess(): void {
193 $this->createLoggedInUser();;
194 foreach (['docx', 'odt'] as $docType) {
195 $formValues = [
196 'is_unit_test' => TRUE,
197 'group_by' => NULL,
198 'document_file' => [
199 'name' => __DIR__ . "/sample_documents/Template.$docType",
200 'type' => $this->_docTypes[$docType],
201 ],
202 ];
203
204 $contributionId = $this->createContribution();
205 /* @var $form CRM_Contribute_Form_Task_PDFLetter */
206 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
207 $form->setContributionIds([$contributionId]);
208 $format = Civi::settings()->get('dateformatFull');
209 $date = CRM_Utils_Date::getToday();
210 $displayDate = CRM_Utils_Date::customFormat($date, $format);
211
212 $html = $form->postProcess();
213 $expectedValues = [
214 'Hello Anthony',
215 '$ 100.00',
216 $displayDate,
217 'Donation',
218 'Domain Name - Default Domain Name',
219 ];
220
221 foreach ($expectedValues as $val) {
222 $this->assertTrue(strpos($html[$contributionId], $val) !== 0);
223 }
224 }
225 }
226
227 /**
228 * Test that no notice or errors occur if no contribution tokens are requested.
229 *
230 * @throws \CRM_Core_Exception
231 * @throws \CiviCRM_API3_Exception
232 */
233 public function testNoContributionTokens(): void {
234 $this->createLoggedInUser();
235 $formValues = [
236 'html_message' => '{contact.display_name}',
237 'document_type' => 'pdf',
238 ];
239 /* @var $form CRM_Contribute_Form_Task_PDFLetter */
240 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
241 $form->setContributionIds([$this->createContribution()]);
242 try {
243 $form->postProcess();
244 }
245 catch (CRM_Core_Exception_PrematureExitException $e) {
246 $html = $e->errorData['html'];
247 }
248 $this->assertStringContainsString('Mr. Anthony Anderson II', $html);
249 }
250
251 /**
252 * Test assignment of variables when using the group by function.
253 *
254 * We are looking to see that the contribution aggregate and contributions
255 * arrays reflect the most recent contact rather than a total aggregate,
256 * since we are using group by.
257 *
258 * @throws \CiviCRM_API3_Exception
259 * @throws \CRM_Core_Exception
260 */
261 public function testPostProcessGroupByContact(): void {
262 $this->createLoggedInUser();
263 $this->hookClass->setHook('civicrm_tokenValues', [$this, 'hook_aggregateTokenValues']);
264 $this->hookClass->setHook('civicrm_tokens', [$this, 'hook_tokens']);
265 $this->mut = new CiviMailUtils($this, TRUE);
266 $this->_individualId = $this->individualCreate();
267 $this->_individualId2 = $this->individualCreate();
268 $htmlMessage = "{aggregate.rendered_token}";
269 $formValues = [
270 'is_unit_test' => TRUE,
271 'group_by' => 'contact_id',
272 'html_message' => $htmlMessage,
273 'email_options' => 'both',
274 'subject' => 'Testy test test',
275 'from' => 'info@example.com',
276 ];
277
278 $contributionIDs = [];
279 $contribution = $this->callAPISuccess('Contribution', 'create', [
280 'contact_id' => $this->_individualId,
281 'total_amount' => 100,
282 'financial_type_id' => 'Donation',
283 'receive_date' => '2016-12-25',
284 ]);
285 $contributionIDs[] = $contribution['id'];
286 $contribution = $this->callAPISuccess('Contribution', 'create', [
287 'contact_id' => $this->_individualId2,
288 'total_amount' => 10,
289 'financial_type_id' => 'Donation',
290 'receive_date' => '2016-12-25',
291 ]);
292 $contributionIDs[] = $contribution['id'];
293
294 $contribution = $this->callAPISuccess('Contribution', 'create', [
295 'contact_id' => $this->_individualId2,
296 'total_amount' => 1,
297 'financial_type_id' => 'Donation',
298 'receive_date' => '2016-12-25',
299 ]);
300 $contributionIDs[] = $contribution['id'];
301
302 /* @var \CRM_Contribute_Form_Task_PDFLetter $form */
303 $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', $formValues);
304 $form->setContributionIds($contributionIDs);
305
306 $html = $form->postProcess();
307 $this->assertEquals("<table border='1' cellpadding='2' cellspacing='0' class='table'>
308 <tbody>
309 <tr>
310 <th>Date</th>
311 <th>Amount</th>
312 <th>Financial Type</th>
313 <th>Source</th>
314 </tr>
315 <!--
316 -->
317 <tr>
318 <td>25 December 2016</td>
319 <td>$ 100.00</td>
320 <td>Donation</td>
321 <td></td>
322 </tr>
323 <!--
324 -->
325 <tr>
326 <td><strong>Total</strong></td>
327 <td><strong>$ 100.00</strong></td>
328 <td></td>
329 <td></td>
330 </tr>
331 </tbody>
332 </table>", $html[1]);
333 $this->assertEquals("<table border='1' cellpadding='2' cellspacing='0' class='table'>
334 <tbody>
335 <tr>
336 <th>Date</th>
337 <th>Amount</th>
338 <th>Financial Type</th>
339 <th>Source</th>
340 </tr>
341 <!--
342 -->
343 <tr>
344 <td>25 December 2016</td>
345 <td>$ 10.00</td>
346 <td>Donation</td>
347 <td></td>
348 </tr>
349 <!--
350 -->
351 <tr>
352 <td>25 December 2016</td>
353 <td>$ 1.00</td>
354 <td>Donation</td>
355 <td></td>
356 </tr>
357 <!--
358 -->
359 <tr>
360 <td><strong>Total</strong></td>
361 <td><strong>$ 11.00</strong></td>
362 <td></td>
363 <td></td>
364 </tr>
365 </tbody>
366 </table>", $html[2]);
367
368 $activities = $this->callAPISuccess('Activity', 'get', ['activity_type_id' => 'Print PDF Letter', 'sequential' => 1]);
369 $this->assertEquals(2, $activities['count']);
370 $this->assertEquals($html[1], $activities['values'][0]['details']);
371 $this->assertEquals($html[2], $activities['values'][1]['details']);
372 // Checking it is not called multiple times.
373 // once for each contact create + once for the activities.
374 // & once for the token processor, for now.
375 // By calling the cached function we can get this down to 1
376 $this->assertEquals(4, $this->hookTokensCalled);
377 $this->mut->checkAllMailLog($html);
378
379 }
380
381 /**
382 * Implements civicrm_tokens().
383 */
384 public function hook_tokens(&$tokens) {
385 $this->hookTokensCalled++;
386 $tokens['aggregate'] = ['rendered_token' => 'rendered_token'];
387 }
388
389 /**
390 * Get the html message.
391 *
392 * @return string
393 */
394 public function getHtmlMessage() {
395 return '{assign var=\'contact_aggregate\' value=0}
396 <table border=\'1\' cellpadding=\'2\' cellspacing=\'0\' class=\'table\'>
397 <tbody>
398 <tr>
399 <th>Date</th>
400 <th>Amount</th>
401 <th>Financial Type</th>
402 <th>Source</th>
403 </tr>
404 <!--
405 {foreach from=$contributions item=contribution}
406 {if $contribution.contact_id == $messageContactID}
407 {assign var=\'date\' value=$contribution.receive_date|date_format:\'%d %B %Y\'}
408 {assign var=contact_aggregate
409 value=$contact_aggregate+$contribution.total_amount}
410 -->
411 <tr>
412 <td>{$date}</td>
413 <td>{$contribution.total_amount|crmMoney}</td>
414 <td>{$contribution.financial_type}</td>
415 <td></td>
416 </tr>
417 <!--
418 {/if}
419 {/foreach}
420 -->
421 <tr>
422 <td><strong>Total</strong></td>
423 <td><strong>{$contact_aggregate|crmMoney}</strong></td>
424 <td></td>
425 <td></td>
426 </tr>
427 </tbody>
428 </table>';
429 }
430
431 /**
432 * Implements CiviCRM hook.
433 *
434 * @param array $values
435 * @param array $contactIDs
436 * @param null $job
437 * @param array $tokens
438 * @param null $context
439 */
440 public function hook_aggregateTokenValues(&$values, $contactIDs, $job = NULL, $tokens = [], $context = NULL) {
441 foreach ($contactIDs as $contactID) {
442 CRM_Core_Smarty::singleton()->assign('messageContactID', $contactID);
443 $values[$contactID]['aggregate.rendered_token'] = CRM_Core_Smarty::singleton()
444 ->fetch('string:' . $this->getHtmlMessage());
445 }
446 }
447
448 /**
449 * @param string $token
450 * @param string $entity
451 * @param string $textToSearch
452 * @param bool $expected
453 *
454 * @dataProvider isHtmlTokenInTableCellProvider
455 */
456 public function testIsHtmlTokenInTableCell($token, $entity, $textToSearch, $expected) {
457 $this->assertEquals($expected,
458 CRM_Contribute_Form_Task_PDFLetter::isHtmlTokenInTableCell($token, $entity, $textToSearch)
459 );
460 }
461
462 public function isHtmlTokenInTableCellProvider() {
463 return [
464
465 'simplest TRUE' => [
466 'token',
467 'entity',
468 '<td>{entity.token}</td>',
469 TRUE,
470 ],
471
472 'simplest FALSE' => [
473 'token',
474 'entity',
475 '{entity.token}',
476 FALSE,
477 ],
478
479 'token between two tables' => [
480 'token',
481 'entity',
482 ' <table><tr><td>Top</td></tr></table>
483 {entity.token}
484 <table><tr><td>Bottom</td></tr></table>',
485 FALSE,
486 ],
487
488 'token in two tables' => [
489 'token',
490 'entity',
491 ' <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>
492 <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>',
493 TRUE,
494 ],
495
496 'token outside of table and inside of table' => [
497 'token',
498 'entity',
499 ' {entity.token}
500 <table><tr><td>{entity.token}</td></tr><tr><td>foo</td></tr></table>',
501 FALSE,
502 ],
503
504 'token inside more complicated table' => [
505 'token',
506 'entity',
507 ' <table><tr><td class="foo"><em>{entity.token}</em></td></tr></table>',
508 TRUE,
509 ],
510
511 'token inside something that looks like table cell' => [
512 'token',
513 'entity',
514 ' <tdata>{entity.token}</tdata>
515 <table><tr><td>Bottom</td></tr></table>',
516 FALSE,
517 ],
518
519 ];
520 }
521
522 /**
523 * @param array $entities
524 * @param \CRM_Core_Form $form
525 */
526 protected function setSearchSelection(array $entities, CRM_Core_Form $form): void {
527 $_SESSION['_' . $form->controller->_name . '_container']['values']['Search'] = [
528 'radio_ts' => 'ts_sel',
529 ];
530 foreach ($entities as $entityID) {
531 $_SESSION['_' . $form->controller->_name . '_container']['values']['Search']['mark_x_' . $entityID] = TRUE;
532 }
533 }
534
535 /**
536 * @return mixed
537 * @throws \CRM_Core_Exception
538 * @throws \CiviCRM_API3_Exception
539 */
540 protected function createContribution() {
541 $contributionParams = [
542 'contact_id' => $this->individualCreate(),
543 'total_amount' => 100,
544 'financial_type_id' => 'Donation',
545 ];
546 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
547 $contributionId = $contribution['id'];
548 return $contributionId;
549 }
550
551 }