Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
bc77d7c0 | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
6a488035 | 5 | | | |
bc77d7c0 TO |
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 | | |
6a488035 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
6a488035 | 11 | |
31f2ebac EM |
12 | use Civi\Token\TokenProcessor; |
13 | ||
6a488035 TO |
14 | /** |
15 | * | |
16 | * @package CRM | |
ca5cec67 | 17 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
18 | */ |
19 | ||
20 | /** | |
07f8d162 | 21 | * This class provides the functionality to create PDF letter for a group of contacts or a single contact. |
6a488035 TO |
22 | */ |
23 | class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task { | |
24 | ||
fc34a273 EM |
25 | use CRM_Contact_Form_Task_PDFTrait; |
26 | ||
6a488035 | 27 | /** |
fe482240 | 28 | * All the existing templates in the system. |
6a488035 TO |
29 | * |
30 | * @var array | |
31 | */ | |
32 | public $_templates = NULL; | |
33 | ||
34 | public $_single = NULL; | |
35 | ||
36 | public $_cid = NULL; | |
37 | ||
38 | /** | |
fe482240 | 39 | * Build all the data structures needed to build the form. |
ed106721 | 40 | */ |
00be9182 | 41 | public function preProcess() { |
6a488035 | 42 | $this->skipOnHold = $this->skipDeceased = FALSE; |
c97bfeff | 43 | $this->preProcessPDF(); |
1a90603e | 44 | parent::preProcess(); |
45 | $this->assign('single', $this->isSingle()); | |
6a488035 TO |
46 | } |
47 | ||
186c9c17 EM |
48 | /** |
49 | * This virtual function is used to set the default values of | |
50 | * various form elements | |
51 | * | |
52 | * access public | |
53 | * | |
a6c01b45 CW |
54 | * @return array |
55 | * reference to the array of default values | |
186c9c17 | 56 | */ |
1330f57a | 57 | |
186c9c17 EM |
58 | /** |
59 | * @return array | |
60 | */ | |
00be9182 | 61 | public function setDefaultValues() { |
fc34a273 | 62 | $defaults = $this->getPDFDefaultValues(); |
6a488035 | 63 | if (isset($this->_activityId)) { |
be2fb01f | 64 | $params = ['id' => $this->_activityId]; |
6a488035 | 65 | CRM_Activity_BAO_Activity::retrieve($params, $defaults); |
9c1bc317 | 66 | $defaults['html_message'] = $defaults['details'] ?? NULL; |
6a488035 | 67 | } |
b5fd2bef CW |
68 | else { |
69 | $defaults['thankyou_update'] = 1; | |
70 | } | |
6a488035 TO |
71 | return $defaults; |
72 | } | |
73 | ||
74 | /** | |
fe482240 | 75 | * Build the form object. |
053c1a4b EM |
76 | * |
77 | * @throws \CRM_Core_Exception | |
6a488035 TO |
78 | */ |
79 | public function buildQuickForm() { | |
80 | //enable form element | |
81 | $this->assign('suppressForm', FALSE); | |
82 | ||
b701d2e5 | 83 | // Contribute PDF tasks allow you to email as well, so we need to add email address to those forms |
84 | $this->add('select', 'from_email_address', ts('From Email Address'), $this->_fromEmails, TRUE); | |
053c1a4b | 85 | $this->addPDFElementsToForm(); |
6a488035 TO |
86 | |
87 | // specific need for contributions | |
383c047b | 88 | $this->add('static', 'more_options_header', NULL, ts('Thank-you Letter Options')); |
6a488035 TO |
89 | $this->add('checkbox', 'receipt_update', ts('Update receipt dates for these contributions'), FALSE); |
90 | $this->add('checkbox', 'thankyou_update', ts('Update thank-you dates for these contributions'), FALSE); | |
6a488035 TO |
91 | |
92 | // Group options for tokens are not yet implemented. dgg | |
be2fb01f | 93 | $options = [ |
383c047b DG |
94 | '' => ts('- no grouping -'), |
95 | 'contact_id' => ts('Contact'), | |
96 | 'contribution_recur_id' => ts('Contact and Recurring'), | |
97 | 'financial_type_id' => ts('Contact and Financial Type'), | |
98 | 'campaign_id' => ts('Contact and Campaign'), | |
536f0e02 | 99 | 'payment_instrument_id' => ts('Contact and Payment Method'), |
be2fb01f CW |
100 | ]; |
101 | $this->addElement('select', 'group_by', ts('Group contributions by'), $options, [], "<br/>", FALSE); | |
383c047b | 102 | // this was going to be free-text but I opted for radio options in case there was a script injection risk |
be2fb01f | 103 | $separatorOptions = ['comma' => 'Comma', 'td' => 'Horizontal Table Cell', 'tr' => 'Vertical Table Cell', 'br' => 'Line Break']; |
698253db | 104 | |
383c047b | 105 | $this->addElement('select', 'group_by_separator', ts('Separator (grouped contributions)'), $separatorOptions); |
be2fb01f | 106 | $emailOptions = [ |
383c047b DG |
107 | '' => ts('Generate PDFs for printing (only)'), |
108 | 'email' => ts('Send emails where possible. Generate printable PDFs for contacts who cannot receive email.'), | |
109 | 'both' => ts('Send emails where possible. Generate printable PDFs for all contacts.'), | |
be2fb01f | 110 | ]; |
353ffa53 | 111 | if (CRM_Core_Config::singleton()->doNotAttachPDFReceipt) { |
97eaa51b EM |
112 | $emailOptions['pdfemail'] = ts('Send emails with an attached PDF where possible. Generate printable PDFs for contacts who cannot receive email.'); |
113 | $emailOptions['pdfemail_both'] = ts('Send emails with an attached PDF where possible. Generate printable PDFs for all contacts.'); | |
114 | } | |
be2fb01f | 115 | $this->addElement('select', 'email_options', ts('Print and email options'), $emailOptions, [], "<br/>", FALSE); |
ed106721 | 116 | |
f336046d | 117 | $this->addButtons($this->getButtons()); |
6a488035 TO |
118 | |
119 | } | |
120 | ||
f336046d EM |
121 | /** |
122 | * Get the name for the main submit button. | |
123 | * | |
124 | * @return string | |
125 | */ | |
126 | protected function getMainSubmitButtonName(): string { | |
127 | return ts('Make Thank-you Letters'); | |
128 | } | |
129 | ||
6a488035 | 130 | /** |
fe482240 | 131 | * Process the form after the input has been submitted and validated. |
9da59513 | 132 | * |
133 | * @throws \CRM_Core_Exception | |
cf56c730 | 134 | * @throws \CiviCRM_API3_Exception |
6a488035 TO |
135 | */ |
136 | public function postProcess() { | |
9be8686d | 137 | $formValues = $this->controller->exportValues($this->getName()); |
31f2ebac EM |
138 | [$formValues, $html_message] = $this->processMessageTemplate($formValues); |
139 | ||
140 | $messageToken = CRM_Utils_Token::getTokens($html_message); | |
141 | ||
142 | $returnProperties = []; | |
143 | if (isset($messageToken['contact'])) { | |
144 | foreach ($messageToken['contact'] as $key => $value) { | |
145 | $returnProperties[$value] = 1; | |
146 | } | |
147 | } | |
148 | ||
b701d2e5 | 149 | $isPDF = FALSE; |
150 | $emailParams = []; | |
151 | if (!empty($formValues['email_options'])) { | |
152 | $returnProperties['email'] = $returnProperties['on_hold'] = $returnProperties['is_deceased'] = $returnProperties['do_not_email'] = 1; | |
153 | $emailParams = [ | |
154 | 'subject' => $formValues['subject'] ?? NULL, | |
155 | 'from' => $formValues['from_email_address'] ?? NULL, | |
156 | ]; | |
157 | ||
158 | $emailParams['from'] = CRM_Utils_Mail::formatFromAddress($emailParams['from']); | |
159 | ||
160 | // We need display_name for emailLetter() so add to returnProperties here | |
161 | $returnProperties['display_name'] = 1; | |
162 | if (stristr($formValues['email_options'], 'pdfemail')) { | |
163 | $isPDF = TRUE; | |
164 | } | |
165 | } | |
166 | // update dates ? | |
167 | $receipt_update = $formValues['receipt_update'] ?? FALSE; | |
168 | $thankyou_update = $formValues['thankyou_update'] ?? FALSE; | |
169 | $nowDate = date('YmdHis'); | |
170 | $receipts = $thanks = $emailed = 0; | |
171 | $updateStatus = ''; | |
172 | $task = 'CRM_Contribution_Form_Task_PDFLetterCommon'; | |
173 | $realSeparator = ', '; | |
174 | $tableSeparators = [ | |
175 | 'td' => '</td><td>', | |
176 | 'tr' => '</td></tr><tr><td>', | |
177 | ]; | |
178 | //the original thinking was mutliple options - but we are going with only 2 (comma & td) for now in case | |
179 | // there are security (& UI) issues we need to think through | |
180 | if (isset($formValues['group_by_separator'])) { | |
181 | if (in_array($formValues['group_by_separator'], ['td', 'tr'])) { | |
182 | $realSeparator = $tableSeparators[$formValues['group_by_separator']]; | |
183 | } | |
184 | elseif ($formValues['group_by_separator'] == 'br') { | |
185 | $realSeparator = "<br />"; | |
186 | } | |
187 | } | |
188 | // a placeholder in case the separator is common in the string - e.g ', ' | |
189 | $separator = '****~~~~'; | |
9da59513 | 190 | $groupBy = $this->getSubmittedValue('group_by'); |
b701d2e5 | 191 | |
192 | // skip some contacts ? | |
3467497b | 193 | $skipOnHold = $this->skipOnHold ?? FALSE; |
194 | $skipDeceased = $this->skipDeceased ?? TRUE; | |
9da59513 | 195 | $contributionIDs = $this->getIDs(); |
3467497b | 196 | if ($this->isQueryIncludesSoftCredits()) { |
b701d2e5 | 197 | $contributionIDs = []; |
3467497b | 198 | $result = $this->getSearchQueryResults(); |
b701d2e5 | 199 | while ($result->fetch()) { |
3467497b | 200 | $this->_contactIds[$result->contact_id] = $result->contact_id; |
b701d2e5 | 201 | $contributionIDs["{$result->contact_id}-{$result->contribution_id}"] = $result->contribution_id; |
202 | } | |
203 | } | |
3467497b | 204 | [$contributions, $contacts] = $this->buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $this->isQueryIncludesSoftCredits()); |
b701d2e5 | 205 | $html = []; |
206 | $contactHtml = $emailedHtml = []; | |
207 | foreach ($contributions as $contributionId => $contribution) { | |
208 | $contact = &$contacts[$contribution['contact_id']]; | |
209 | $grouped = FALSE; | |
210 | $groupByID = 0; | |
211 | if ($groupBy) { | |
212 | $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; | |
213 | $contribution = $contact['combined'][$groupBy][$groupByID]; | |
214 | $grouped = TRUE; | |
215 | } | |
216 | ||
217 | if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { | |
3467497b | 218 | $html[$contributionId] = $this->generateHtml($contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID); |
b701d2e5 | 219 | $contactHtml[$contact['contact_id']][] = $html[$contributionId]; |
f336046d | 220 | if ($this->isSendEmails()) { |
3467497b | 221 | if ($this->emailLetter($contact, $html[$contributionId], $isPDF, $formValues, $emailParams)) { |
b701d2e5 | 222 | $emailed++; |
223 | if (!stristr($formValues['email_options'], 'both')) { | |
224 | $emailedHtml[$contributionId] = TRUE; | |
225 | } | |
226 | } | |
227 | } | |
228 | $contact['is_sent'][$groupBy][$groupByID] = TRUE; | |
229 | } | |
f336046d EM |
230 | if ($this->isLiveMode()) { |
231 | // Update receipt/thankyou dates | |
232 | $contributionParams = ['id' => $contributionId]; | |
233 | if ($receipt_update) { | |
234 | $contributionParams['receipt_date'] = $nowDate; | |
235 | } | |
236 | if ($thankyou_update) { | |
237 | $contributionParams['thankyou_date'] = $nowDate; | |
238 | } | |
239 | if ($receipt_update || $thankyou_update) { | |
240 | civicrm_api3('Contribution', 'create', $contributionParams); | |
241 | $receipts = ($receipt_update ? $receipts + 1 : $receipts); | |
242 | $thanks = ($thankyou_update ? $thanks + 1 : $thanks); | |
243 | } | |
b701d2e5 | 244 | } |
245 | } | |
246 | ||
247 | $contactIds = array_keys($contacts); | |
f336046d EM |
248 | // CRM-16725 Skip creation of activities if user is previewing their PDF letter(s) |
249 | if ($this->isLiveMode()) { | |
2537798f | 250 | $this->createActivities($html_message, $contactIds, CRM_Utils_Array::value('subject', $formValues, ts('Thank you letter')), CRM_Utils_Array::value('campaign_id', $formValues), $contactHtml); |
f336046d | 251 | } |
b701d2e5 | 252 | $html = array_diff_key($html, $emailedHtml); |
253 | ||
b701d2e5 | 254 | //CRM-19761 |
255 | if (!empty($html)) { | |
7c0d6f7a | 256 | $fileName = $this->getFileName(); |
ba7ea395 JF |
257 | if ($this->getSubmittedValue('document_type') === 'pdf') { |
258 | CRM_Utils_PDF_Utils::html2pdf($html, $fileName . '.pdf', FALSE, $formValues); | |
b701d2e5 | 259 | } |
260 | else { | |
ba7ea395 | 261 | CRM_Utils_PDF_Document::html2doc($html, $fileName . '.' . $this->getSubmittedValue('document_type'), $formValues); |
b701d2e5 | 262 | } |
263 | } | |
264 | ||
3467497b | 265 | $this->postProcessHook(); |
b701d2e5 | 266 | |
267 | if ($emailed) { | |
268 | $updateStatus = ts('Receipts have been emailed to %1 contributions.', [1 => $emailed]); | |
269 | } | |
270 | if ($receipts) { | |
271 | $updateStatus = ts('Receipt date has been updated for %1 contributions.', [1 => $receipts]); | |
272 | } | |
273 | if ($thanks) { | |
274 | $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', [1 => $thanks]); | |
275 | } | |
276 | ||
277 | if ($updateStatus) { | |
278 | CRM_Core_Session::setStatus($updateStatus); | |
279 | } | |
280 | if (!empty($html)) { | |
281 | // ie. we have only sent emails - lets no show a white screen | |
282 | CRM_Utils_System::civiExit(); | |
283 | } | |
6a488035 | 284 | } |
96025800 | 285 | |
f336046d EM |
286 | /** |
287 | * Are emails to be sent out? | |
288 | * | |
289 | * @return bool | |
290 | */ | |
291 | protected function isSendEmails(): bool { | |
292 | return $this->isLiveMode() && $this->getSubmittedValue('email_options'); | |
293 | } | |
294 | ||
5ec6b0ad | 295 | /** |
601c941f | 296 | * Get the token processor schema required to list any tokens for this task. |
5ec6b0ad TM |
297 | * |
298 | * @return array | |
299 | */ | |
601c941f EM |
300 | public function getTokenSchema(): array { |
301 | return ['contributionId', 'contactId']; | |
5ec6b0ad TM |
302 | } |
303 | ||
01a5461d | 304 | /** |
305 | * Generate the contribution array from the form, we fill in the contact details and determine any aggregation | |
306 | * around contact_id of contribution_recur_id | |
307 | * | |
308 | * @param string $groupBy | |
309 | * @param array $contributionIDs | |
310 | * @param array $returnProperties | |
311 | * @param bool $skipOnHold | |
312 | * @param bool $skipDeceased | |
313 | * @param array $messageToken | |
314 | * @param string $task | |
315 | * @param string $separator | |
316 | * @param bool $isIncludeSoftCredits | |
317 | * | |
318 | * @return array | |
14145505 | 319 | * @throws \CiviCRM_API3_Exception |
01a5461d | 320 | */ |
3467497b | 321 | public function buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $isIncludeSoftCredits) { |
01a5461d | 322 | $contributions = $contacts = []; |
323 | foreach ($contributionIDs as $item => $contributionId) { | |
324 | $contribution = CRM_Contribute_BAO_Contribution::getContributionTokenValues($contributionId, $messageToken)['values'][$contributionId]; | |
325 | $contribution['campaign'] = $contribution['contribution_campaign_title'] ?? NULL; | |
326 | $contributions[$contributionId] = $contribution; | |
327 | ||
328 | if ($isIncludeSoftCredits) { | |
329 | //@todo find out why this happens & add comments | |
330 | [$contactID] = explode('-', $item); | |
331 | $contactID = (int) $contactID; | |
332 | } | |
333 | else { | |
334 | $contactID = $contribution['contact_id']; | |
335 | } | |
336 | if (!isset($contacts[$contactID])) { | |
337 | $contacts[$contactID] = []; | |
338 | $contacts[$contactID]['contact_aggregate'] = 0; | |
339 | $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = []; | |
340 | } | |
341 | ||
342 | $contacts[$contactID]['contact_aggregate'] += $contribution['total_amount']; | |
343 | $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; | |
344 | ||
345 | $contacts[$contactID]['contribution_ids'][$groupBy][$groupByID][$contributionId] = TRUE; | |
346 | if (!isset($contacts[$contactID]['combined'][$groupBy]) || !isset($contacts[$contactID]['combined'][$groupBy][$groupByID])) { | |
347 | $contacts[$contactID]['combined'][$groupBy][$groupByID] = $contribution; | |
348 | $contacts[$contactID]['aggregates'][$groupBy][$groupByID] = $contribution['total_amount']; | |
349 | } | |
350 | else { | |
351 | $contacts[$contactID]['combined'][$groupBy][$groupByID] = self::combineContributions($contacts[$contactID]['combined'][$groupBy][$groupByID], $contribution, $separator); | |
352 | $contacts[$contactID]['aggregates'][$groupBy][$groupByID] += $contribution['total_amount']; | |
353 | } | |
354 | } | |
355 | // Assign the available contributions before calling tokens so hooks parsing smarty can access it. | |
356 | // Note that in core code you can only use smarty here if enable if for the whole site, incl | |
357 | // CiviMail, with a big performance impact. | |
358 | // Hooks allow more nuanced smarty usage here. | |
359 | CRM_Core_Smarty::singleton()->assign('contributions', $contributions); | |
14145505 EM |
360 | $resolvedContacts = civicrm_api3('Contact', 'get', [ |
361 | 'return' => array_keys($returnProperties), | |
362 | 'id' => ['IN' => array_keys($contacts)], | |
a5ba7508 | 363 | 'options' => ['limit' => 0], |
14145505 | 364 | ])['values']; |
01a5461d | 365 | foreach ($contacts as $contactID => $contact) { |
14145505 | 366 | $contacts[$contactID] = array_merge($resolvedContacts[$contactID], $contact); |
01a5461d | 367 | } |
368 | return [$contributions, $contacts]; | |
369 | } | |
370 | ||
371 | /** | |
372 | * We combine the contributions by adding the contribution to each field with the separator in | |
373 | * between the existing value and the new one. We put the separator there even if empty so it is clear what the | |
374 | * value for previous contributions was | |
375 | * | |
376 | * @param array $existing | |
377 | * @param array $contribution | |
378 | * @param string $separator | |
379 | * | |
380 | * @return array | |
381 | */ | |
382 | public static function combineContributions($existing, $contribution, $separator) { | |
383 | foreach ($contribution as $field => $value) { | |
384 | $existing[$field] = isset($existing[$field]) ? $existing[$field] . $separator : ''; | |
385 | $existing[$field] .= $value; | |
386 | } | |
387 | return $existing; | |
388 | } | |
389 | ||
390 | /** | |
391 | * We are going to retrieve the combined contribution and if smarty mail is enabled we | |
392 | * will also assign an array of contributions for this contact to the smarty template | |
393 | * | |
394 | * @param array $contact | |
395 | * @param array $contributions | |
396 | * @param $groupBy | |
397 | * @param int $groupByID | |
398 | */ | |
399 | public static function assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID) { | |
400 | CRM_Core_Smarty::singleton()->assign('contact_aggregate', $contact['contact_aggregate']); | |
401 | CRM_Core_Smarty::singleton() | |
402 | ->assign('contributions', $contributions); | |
403 | CRM_Core_Smarty::singleton()->assign('contribution_aggregate', $contact['aggregates'][$groupBy][$groupByID]); | |
404 | } | |
405 | ||
406 | /** | |
407 | * @param $contact | |
408 | * @param $formValues | |
409 | * @param $contribution | |
410 | * @param $groupBy | |
411 | * @param $contributions | |
412 | * @param $realSeparator | |
413 | * @param $tableSeparators | |
414 | * @param $messageToken | |
415 | * @param $html_message | |
416 | * @param $separator | |
417 | * @param $categories | |
418 | * @param bool $grouped | |
419 | * @param int $groupByID | |
420 | * | |
421 | * @return string | |
422 | * @throws \CRM_Core_Exception | |
423 | */ | |
9292a691 | 424 | public function generateHtml($contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID) { |
01a5461d | 425 | static $validated = FALSE; |
426 | $html = NULL; | |
427 | ||
428 | $groupedContributions = array_intersect_key($contributions, $contact['contribution_ids'][$groupBy][$groupByID]); | |
429 | CRM_Contribute_Form_Task_PDFLetter::assignCombinedContributionValues($contact, $groupedContributions, $groupBy, $groupByID); | |
430 | ||
431 | if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { | |
b701d2e5 | 432 | if (!$validated && in_array($realSeparator, $tableSeparators) && !CRM_Contribute_Form_Task_PDFLetter::isValidHTMLWithTableSeparator($messageToken, $html_message)) { |
01a5461d | 433 | $realSeparator = ', '; |
434 | CRM_Core_Session::setStatus(ts('You have selected the table cell separator, but one or more token fields are not placed inside a table cell. This would result in invalid HTML, so comma separators have been used instead.')); | |
435 | } | |
436 | $validated = TRUE; | |
9292a691 | 437 | $html = str_replace($separator, $realSeparator, $this->resolveTokens($html_message, $contact['contact_id'], $contribution['id'], $grouped, $separator, $groupedContributions)); |
01a5461d | 438 | } |
439 | ||
440 | return $html; | |
441 | } | |
442 | ||
9be8686d | 443 | /** |
444 | * Send pdf by email. | |
445 | * | |
446 | * @param array $contact | |
447 | * @param string $html | |
448 | * | |
449 | * @param $is_pdf | |
450 | * @param array $format | |
451 | * @param array $params | |
452 | * | |
453 | * @return bool | |
454 | */ | |
3467497b | 455 | public function emailLetter($contact, $html, $is_pdf, $format = [], $params = []) { |
9be8686d | 456 | try { |
457 | if (empty($contact['email'])) { | |
458 | return FALSE; | |
459 | } | |
460 | $mustBeEmpty = ['do_not_email', 'is_deceased', 'on_hold']; | |
461 | foreach ($mustBeEmpty as $emptyField) { | |
462 | if (!empty($contact[$emptyField])) { | |
463 | return FALSE; | |
464 | } | |
465 | } | |
466 | ||
467 | $defaults = [ | |
52dd4e08 | 468 | 'contactId' => $contact['id'], |
9be8686d | 469 | 'toName' => $contact['display_name'], |
470 | 'toEmail' => $contact['email'], | |
471 | 'text' => '', | |
472 | 'html' => $html, | |
473 | ]; | |
474 | if (empty($params['from'])) { | |
475 | $emails = CRM_Core_BAO_Email::getFromEmail(); | |
476 | $emails = array_keys($emails); | |
477 | $defaults['from'] = array_pop($emails); | |
478 | } | |
479 | else { | |
480 | $defaults['from'] = $params['from']; | |
481 | } | |
482 | if (!empty($params['subject'])) { | |
483 | $defaults['subject'] = $params['subject']; | |
484 | } | |
485 | else { | |
486 | $defaults['subject'] = ts('Thank you for your contribution/s'); | |
487 | } | |
488 | if ($is_pdf) { | |
489 | $defaults['html'] = ts('Please see attached'); | |
490 | $defaults['attachments'] = [CRM_Utils_Mail::appendPDF('ThankYou.pdf', $html, $format)]; | |
491 | } | |
8c1008e0 | 492 | return CRM_Utils_Mail::send($defaults); |
9be8686d | 493 | } |
494 | catch (CRM_Core_Exception $e) { | |
495 | return FALSE; | |
496 | } | |
497 | } | |
498 | ||
499 | /** | |
500 | * Check that the token only appears in a table cell. The '</td><td>' separator cannot otherwise work | |
501 | * Calculate the number of times it appears IN the cell & the number of times it appears - should be the same! | |
502 | * | |
503 | * @param string $token | |
504 | * @param string $entity | |
505 | * @param string $textToSearch | |
506 | * | |
507 | * @return bool | |
508 | */ | |
509 | public static function isHtmlTokenInTableCell($token, $entity, $textToSearch) { | |
510 | $tokenToMatch = $entity . '\.' . $token; | |
511 | $pattern = '|<td(?![\w-])((?!</td>).)*\{' . $tokenToMatch . '\}.*?</td>|si'; | |
512 | $within = preg_match_all($pattern, $textToSearch); | |
513 | $total = preg_match_all("|{" . $tokenToMatch . "}|", $textToSearch); | |
514 | return ($within == $total); | |
515 | } | |
516 | ||
b701d2e5 | 517 | /** |
518 | * Check whether any of the tokens exist in the html outside a table cell. | |
519 | * If they do the table cell separator is not supported (return false) | |
520 | * At this stage we are only anticipating contributions passed in this way but | |
521 | * it would be easy to add others | |
522 | * @param $tokens | |
523 | * @param $html | |
524 | * | |
525 | * @return bool | |
526 | */ | |
527 | public static function isValidHTMLWithTableSeparator($tokens, $html) { | |
528 | $relevantEntities = ['contribution']; | |
529 | foreach ($relevantEntities as $entity) { | |
530 | if (isset($tokens[$entity]) && is_array($tokens[$entity])) { | |
531 | foreach ($tokens[$entity] as $token) { | |
532 | if (!CRM_Contribute_Form_Task_PDFLetter::isHtmlTokenInTableCell($token, $entity, $html)) { | |
533 | return FALSE; | |
534 | } | |
535 | } | |
536 | } | |
537 | } | |
538 | return TRUE; | |
539 | } | |
540 | ||
12d88807 | 541 | /** |
542 | * | |
543 | * @param string $html_message | |
9292a691 | 544 | * @param int $contactID |
31f2ebac | 545 | * @param int $contributionID |
12d88807 | 546 | * @param bool $grouped |
547 | * Does this letter represent more than one contribution. | |
548 | * @param string $separator | |
549 | * What is the preferred letter separator. | |
550 | * @param array $contributions | |
551 | * | |
552 | * @return string | |
553 | */ | |
9292a691 | 554 | protected function resolveTokens(string $html_message, int $contactID, $contributionID, $grouped, $separator, $contributions): string { |
82430f99 TO |
555 | $tokenContext = [ |
556 | 'smarty' => (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY), | |
9292a691 | 557 | 'contactId' => $contactID, |
31f2ebac | 558 | 'schema' => ['contributionId'], |
82430f99 | 559 | ]; |
9a88bc66 | 560 | if ($grouped) { |
31f2ebac EM |
561 | // First replace the contribution tokens. These are pretty ... special. |
562 | // if the text looks like `<td>{contribution.currency} {contribution.total_amount}</td>' | |
563 | // and there are 2 rows with a currency separator of | |
564 | // you wind up with a string like | |
565 | // '<td>USD</td><td>USD></td> <td>$50</td><td>$89</td> | |
566 | // see https://docs.civicrm.org/user/en/latest/contributions/manual-receipts-and-thank-yous/#grouped-contribution-thank-you-letters | |
567 | $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), $tokenContext); | |
568 | $contributionTokens = CRM_Utils_Token::getTokens($html_message)['contribution'] ?? []; | |
569 | foreach ($contributionTokens as $token) { | |
570 | $tokenProcessor->addMessage($token, '{contribution.' . $token . '}', 'text/html'); | |
571 | } | |
572 | ||
573 | foreach ($contributions as $contribution) { | |
574 | $tokenProcessor->addRow([ | |
575 | 'contributionId' => $contribution['id'], | |
576 | 'contribution' => $contribution, | |
577 | ]); | |
578 | } | |
579 | $tokenProcessor->evaluate(); | |
580 | $resolvedTokens = []; | |
581 | foreach ($contributionTokens as $token) { | |
582 | foreach ($tokenProcessor->getRows() as $row) { | |
583 | $resolvedTokens[$token][$row->context['contributionId']] = $row->render($token); | |
584 | } | |
f70a513f | 585 | $html_message = str_replace('{contribution.' . $token . '}', implode($separator, $resolvedTokens[$token]), $html_message); |
31f2ebac | 586 | } |
9a88bc66 | 587 | } |
31f2ebac EM |
588 | $tokenContext['contributionId'] = $contributionID; |
589 | return CRM_Core_TokenSmarty::render(['html' => $html_message], $tokenContext)['html']; | |
12d88807 | 590 | } |
591 | ||
6a488035 | 592 | } |