Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | ||
3 | /** | |
4 | * This class provides the common functionality for creating PDF letter for | |
5 | * one or a group of contact ids. | |
6 | */ | |
7 | class CRM_Contribute_Form_Task_PDFLetterCommon extends CRM_Contact_Form_Task_PDFLetterCommon { | |
8 | ||
2cac4853 MW |
9 | /** |
10 | * Build the form object. | |
11 | * | |
12 | * @var CRM_Core_Form $form | |
13 | */ | |
14 | public static function buildQuickForm(&$form) { | |
99ad38df MW |
15 | // use contact form as a base |
16 | CRM_Contact_Form_Task_PDFLetterCommon::buildQuickForm($form); | |
17 | ||
2cac4853 MW |
18 | // Contribute PDF tasks allow you to email as well, so we need to add email address to those forms |
19 | $form->add('select', 'from_email_address', ts('From Email Address'), $form->_fromEmails, TRUE); | |
20 | parent::buildQuickForm($form); | |
21 | } | |
22 | ||
6a488035 | 23 | /** |
fe482240 | 24 | * Process the form after the input has been submitted and validated. |
6a488035 | 25 | * |
493078cb | 26 | * @param CRM_Contribute_Form_Task $form |
59d861cb | 27 | * @param array $formValues |
6a488035 | 28 | */ |
59d861cb | 29 | public static function postProcess(&$form, $formValues = NULL) { |
30 | if (empty($formValues)) { | |
31 | $formValues = $form->controller->exportValues($form->getName()); | |
32 | } | |
b0738a9b | 33 | list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($formValues); |
493078cb | 34 | $isPDF = FALSE; |
be2fb01f | 35 | $emailParams = []; |
22e263ad | 36 | if (!empty($formValues['email_options'])) { |
383c047b | 37 | $returnProperties['email'] = $returnProperties['on_hold'] = $returnProperties['is_deceased'] = $returnProperties['do_not_email'] = 1; |
be2fb01f | 38 | $emailParams = [ |
2cac4853 MW |
39 | 'subject' => CRM_Utils_Array::value('subject', $formValues), |
40 | 'from' => CRM_Utils_Array::value('from_email_address', $formValues), | |
be2fb01f | 41 | ]; |
80b646c0 ML |
42 | |
43 | $emailParams['from'] = CRM_Utils_Mail::formatFromAddress($emailParams['from']); | |
44 | ||
f83f6352 DG |
45 | // We need display_name for emailLetter() so add to returnProperties here |
46 | $returnProperties['display_name'] = 1; | |
22e263ad | 47 | if (stristr($formValues['email_options'], 'pdfemail')) { |
383c047b DG |
48 | $isPDF = TRUE; |
49 | } | |
50 | } | |
6a488035 | 51 | // update dates ? |
353ffa53 | 52 | $receipt_update = isset($formValues['receipt_update']) ? $formValues['receipt_update'] : FALSE; |
6a488035 | 53 | $thankyou_update = isset($formValues['thankyou_update']) ? $formValues['thankyou_update'] : FALSE; |
353ffa53 | 54 | $nowDate = date('YmdHis'); |
383c047b | 55 | $receipts = $thanks = $emailed = 0; |
353ffa53 | 56 | $updateStatus = ''; |
383c047b DG |
57 | $task = 'CRM_Contribution_Form_Task_PDFLetterCommon'; |
58 | $realSeparator = ', '; | |
be2fb01f | 59 | $tableSeparators = [ |
b26a113f | 60 | 'td' => '</td><td>', |
61 | 'tr' => '</td></tr><tr><td>', | |
be2fb01f | 62 | ]; |
383c047b DG |
63 | //the original thinking was mutliple options - but we are going with only 2 (comma & td) for now in case |
64 | // there are security (& UI) issues we need to think through | |
22e263ad | 65 | if (isset($formValues['group_by_separator'])) { |
be2fb01f | 66 | if (in_array($formValues['group_by_separator'], ['td', 'tr'])) { |
b26a113f | 67 | $realSeparator = $tableSeparators[$formValues['group_by_separator']]; |
6edcec04 YR |
68 | } |
69 | elseif ($formValues['group_by_separator'] == 'br') { | |
70 | $realSeparator = "<br />"; | |
71 | } | |
72 | } | |
1330f57a SL |
73 | // a placeholder in case the separator is common in the string - e.g ', ' |
74 | $separator = '****~~~~'; | |
383c047b | 75 | $groupBy = $formValues['group_by']; |
6a488035 TO |
76 | |
77 | // skip some contacts ? | |
78 | $skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE; | |
79 | $skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE; | |
e9379b58 | 80 | $contributionIDs = $form->getVar('_contributionIds'); |
81 | if ($form->_includesSoftCredits) { | |
82 | //@todo - comment on what is stored there | |
83 | $contributionIDs = $form->getVar('_contributionContactIds'); | |
84 | } | |
85 | list($contributions, $contacts) = self::buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $form->_includesSoftCredits); | |
be2fb01f CW |
86 | $html = []; |
87 | $contactHtml = $emailedHtml = []; | |
383c047b DG |
88 | foreach ($contributions as $contributionId => $contribution) { |
89 | $contact = &$contacts[$contribution['contact_id']]; | |
2fe8b920 | 90 | $grouped = FALSE; |
91 | $groupByID = 0; | |
22e263ad | 92 | if ($groupBy) { |
383c047b DG |
93 | $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; |
94 | $contribution = $contact['combined'][$groupBy][$groupByID]; | |
95 | $grouped = TRUE; | |
6a488035 TO |
96 | } |
97 | ||
22e263ad | 98 | if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { |
2fe8b920 | 99 | $html[$contributionId] = self::generateHtml($contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID); |
100 | $contactHtml[$contact['contact_id']][] = $html[$contributionId]; | |
22e263ad TO |
101 | if (!empty($formValues['email_options'])) { |
102 | if (self::emailLetter($contact, $html[$contributionId], $isPDF, $formValues, $emailParams)) { | |
d3e86119 | 103 | $emailed++; |
22e263ad | 104 | if (!stristr($formValues['email_options'], 'both')) { |
2fe8b920 | 105 | $emailedHtml[$contributionId] = TRUE; |
383c047b DG |
106 | } |
107 | } | |
108 | } | |
2fe8b920 | 109 | $contact['is_sent'][$groupBy][$groupByID] = TRUE; |
6a488035 | 110 | } |
fd578207 | 111 | // Update receipt/thankyou dates |
be2fb01f | 112 | $contributionParams = ['id' => $contributionId]; |
6a488035 | 113 | if ($receipt_update) { |
fd578207 | 114 | $contributionParams['receipt_date'] = $nowDate; |
6a488035 TO |
115 | } |
116 | if ($thankyou_update) { | |
fd578207 MW |
117 | $contributionParams['thankyou_date'] = $nowDate; |
118 | } | |
119 | if ($receipt_update || $thankyou_update) { | |
120 | civicrm_api3('Contribution', 'create', $contributionParams); | |
121 | $receipts = ($receipt_update ? $receipts + 1 : $receipts); | |
122 | $thanks = ($thankyou_update ? $thanks + 1 : $thanks); | |
6a488035 TO |
123 | } |
124 | } | |
59d861cb | 125 | |
fe61faf3 | 126 | $contactIds = array_keys($contacts); |
3280f327 | 127 | self::createActivities($form, $html_message, $contactIds, CRM_Utils_Array::value('subject', $formValues, ts('Thank you letter')), CRM_Utils_Array::value('campaign_id', $formValues), $contactHtml); |
128 | $html = array_diff_key($html, $emailedHtml); | |
2fe8b920 | 129 | |
59d861cb | 130 | if (!empty($formValues['is_unit_test'])) { |
131 | return $html; | |
132 | } | |
2fe8b920 | 133 | |
0f2a9bd2 | 134 | //CRM-19761 |
22e263ad | 135 | if (!empty($html)) { |
0f2a9bd2 BS |
136 | $type = $formValues['document_type']; |
137 | ||
138 | if ($type == 'pdf') { | |
139 | CRM_Utils_PDF_Utils::html2pdf($html, "CiviLetter.pdf", FALSE, $formValues); | |
140 | } | |
141 | else { | |
142 | CRM_Utils_PDF_Document::html2doc($html, "CiviLetter.$type", $formValues); | |
143 | } | |
383c047b | 144 | } |
6a488035 TO |
145 | |
146 | $form->postProcessHook(); | |
147 | ||
383c047b | 148 | if ($emailed) { |
be2fb01f | 149 | $updateStatus = ts('Receipts have been emailed to %1 contributions.', [1 => $emailed]); |
383c047b | 150 | } |
6a488035 | 151 | if ($receipts) { |
be2fb01f | 152 | $updateStatus = ts('Receipt date has been updated for %1 contributions.', [1 => $receipts]); |
6a488035 TO |
153 | } |
154 | if ($thanks) { | |
be2fb01f | 155 | $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', [1 => $thanks]); |
6a488035 TO |
156 | } |
157 | ||
158 | if ($updateStatus) { | |
159 | CRM_Core_Session::setStatus($updateStatus); | |
160 | } | |
22e263ad | 161 | if (!empty($html)) { |
383c047b | 162 | // ie. we have only sent emails - lets no show a white screen |
292c8687 | 163 | CRM_Utils_System::civiExit(); |
383c047b DG |
164 | } |
165 | } | |
665e5ec7 | 166 | |
8cb33f3b EM |
167 | /** |
168 | * Check whether any of the tokens exist in the html outside a table cell. | |
169 | * If they do the table cell separator is not supported (return false) | |
170 | * At this stage we are only anticipating contributions passed in this way but | |
171 | * it would be easy to add others | |
172 | * @param $tokens | |
173 | * @param $html | |
174 | * | |
175 | * @return bool | |
176 | */ | |
00be9182 | 177 | public static function isValidHTMLWithTableSeparator($tokens, $html) { |
be2fb01f | 178 | $relevantEntities = ['contribution']; |
8cb33f3b | 179 | foreach ($relevantEntities as $entity) { |
045bdb93 | 180 | if (isset($tokens[$entity]) && is_array($tokens[$entity])) { |
8cb33f3b | 181 | foreach ($tokens[$entity] as $token) { |
353ffa53 | 182 | if (!self::isHtmlTokenInTableCell($token, $entity, $html)) { |
8cb33f3b EM |
183 | return FALSE; |
184 | } | |
185 | } | |
186 | } | |
187 | } | |
188 | return TRUE; | |
189 | } | |
190 | ||
191 | /** | |
100fef9d | 192 | * Check that the token only appears in a table cell. The '</td><td>' separator cannot otherwise work |
045bdb93 EM |
193 | * Calculate the number of times it appears IN the cell & the number of times it appears - should be the same! |
194 | * | |
8dfeb798 SM |
195 | * @param string $token |
196 | * @param string $entity | |
197 | * @param string $textToSearch | |
045bdb93 | 198 | * |
045bdb93 | 199 | * @return bool |
8cb33f3b | 200 | */ |
00be9182 | 201 | public static function isHtmlTokenInTableCell($token, $entity, $textToSearch) { |
5aece37d SM |
202 | $tokenToMatch = $entity . '\.' . $token; |
203 | $pattern = '|<td(?![\w-])((?!</td>).)*\{' . $tokenToMatch . '\}.*?</td>|si'; | |
8dfeb798 SM |
204 | $within = preg_match_all($pattern, $textToSearch); |
205 | $total = preg_match_all("|{" . $tokenToMatch . "}|", $textToSearch); | |
045bdb93 | 206 | return ($within == $total); |
8cb33f3b EM |
207 | } |
208 | ||
874c9be7 TO |
209 | /** |
210 | * | |
211 | * @param string $html_message | |
212 | * @param array $contact | |
213 | * @param array $contribution | |
214 | * @param array $messageToken | |
874c9be7 TO |
215 | * @param bool $grouped |
216 | * Does this letter represent more than one contribution. | |
217 | * @param string $separator | |
218 | * What is the preferred letter separator. | |
219 | * @return string | |
220 | */ | |
3280f327 | 221 | private static function resolveTokens($html_message, $contact, $contribution, $messageToken, $grouped, $separator) { |
222 | $categories = self::getTokenCategories(); | |
874c9be7 | 223 | $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact, TRUE, $messageToken); |
22e263ad | 224 | if ($grouped) { |
874c9be7 TO |
225 | $tokenHtml = CRM_Utils_Token::replaceMultipleContributionTokens($separator, $tokenHtml, $contribution, TRUE, $messageToken); |
226 | } | |
227 | else { | |
228 | // no change to normal behaviour to avoid risk of breakage | |
229 | $tokenHtml = CRM_Utils_Token::replaceContributionTokens($tokenHtml, $contribution, TRUE, $messageToken); | |
230 | } | |
231 | $tokenHtml = CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact, $categories, TRUE); | |
232 | if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) { | |
383c047b DG |
233 | $smarty = CRM_Core_Smarty::singleton(); |
234 | // also add the tokens to the template | |
235 | $smarty->assign_by_ref('contact', $contact); | |
236 | $tokenHtml = $smarty->fetch("string:$tokenHtml"); | |
237 | } | |
238 | return $tokenHtml; | |
239 | } | |
240 | ||
241 | /** | |
242 | * Generate the contribution array from the form, we fill in the contact details and determine any aggregation | |
243 | * around contact_id of contribution_recur_id | |
244 | * | |
045bdb93 | 245 | * @param string $groupBy |
e9379b58 | 246 | * @param array $contributionIDs |
354bc6e1 | 247 | * @param array $returnProperties |
014c4014 TO |
248 | * @param bool $skipOnHold |
249 | * @param bool $skipDeceased | |
354bc6e1 | 250 | * @param array $messageToken |
8cb33f3b | 251 | * @param string $task |
045bdb93 | 252 | * @param string $separator |
e9379b58 | 253 | * @param bool $isIncludeSoftCredits |
fe7febc7 | 254 | * |
a6c01b45 | 255 | * @return array |
383c047b | 256 | */ |
e9379b58 | 257 | public static function buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $isIncludeSoftCredits) { |
be2fb01f | 258 | $contributions = $contacts = []; |
383c047b | 259 | foreach ($contributionIDs as $item => $contributionId) { |
fe7794b7 | 260 | $contribution = CRM_Contribute_BAO_Contribution::getContributionTokenValues($contributionId, $messageToken)['values'][$contributionId]; |
d31d0888 | 261 | $contribution['campaign'] = CRM_Utils_Array::value('contribution_campaign_title', $contribution); |
39b23159 | 262 | $contributions[$contributionId] = $contribution; |
8ea4b051 | 263 | |
e9379b58 | 264 | if ($isIncludeSoftCredits) { |
ff926d67 EM |
265 | //@todo find out why this happens & add comments |
266 | list($contactID) = explode('-', $item); | |
267 | $contactID = (int) $contactID; | |
874c9be7 | 268 | } |
ff926d67 | 269 | else { |
874c9be7 | 270 | $contactID = $contribution['contact_id']; |
ff926d67 | 271 | } |
22e263ad | 272 | if (!isset($contacts[$contactID])) { |
be2fb01f | 273 | $contacts[$contactID] = []; |
ff926d67 | 274 | $contacts[$contactID]['contact_aggregate'] = 0; |
be2fb01f | 275 | $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = []; |
ff926d67 | 276 | } |
383c047b | 277 | |
ff926d67 EM |
278 | $contacts[$contactID]['contact_aggregate'] += $contribution['total_amount']; |
279 | $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; | |
383c047b | 280 | |
ff926d67 | 281 | $contacts[$contactID]['contribution_ids'][$groupBy][$groupByID][$contributionId] = TRUE; |
22e263ad | 282 | if (!isset($contacts[$contactID]['combined'][$groupBy]) || !isset($contacts[$contactID]['combined'][$groupBy][$groupByID])) { |
ff926d67 EM |
283 | $contacts[$contactID]['combined'][$groupBy][$groupByID] = $contribution; |
284 | $contacts[$contactID]['aggregates'][$groupBy][$groupByID] = $contribution['total_amount']; | |
383c047b | 285 | } |
ff926d67 EM |
286 | else { |
287 | $contacts[$contactID]['combined'][$groupBy][$groupByID] = self::combineContributions($contacts[$contactID]['combined'][$groupBy][$groupByID], $contribution, $separator); | |
288 | $contacts[$contactID]['aggregates'][$groupBy][$groupByID] += $contribution['total_amount']; | |
383c047b DG |
289 | } |
290 | } | |
8ea4b051 | 291 | // Assign the available contributions before calling tokens so hooks parsing smarty can access it. |
292 | // Note that in core code you can only use smarty here if enable if for the whole site, incl | |
293 | // CiviMail, with a big performance impact. | |
294 | // Hooks allow more nuanced smarty usage here. | |
295 | CRM_Core_Smarty::singleton()->assign('contributions', $contributions); | |
296 | foreach ($contacts as $contactID => $contact) { | |
be2fb01f | 297 | $tokenResolvedContacts = CRM_Utils_Token::getTokenDetails(['contact_id' => $contactID], |
8ea4b051 | 298 | $returnProperties, |
299 | $skipOnHold, | |
300 | $skipDeceased, | |
301 | NULL, | |
302 | $messageToken, | |
303 | $task | |
304 | ); | |
305 | $contacts[$contactID] = array_merge($tokenResolvedContacts[0][$contactID], $contact); | |
306 | } | |
be2fb01f | 307 | return [$contributions, $contacts]; |
383c047b DG |
308 | } |
309 | ||
310 | /** | |
311 | * We combine the contributions by adding the contribution to each field with the separator in | |
312 | * between the existing value and the new one. We put the separator there even if empty so it is clear what the | |
313 | * value for previous contributions was | |
fe7febc7 | 314 | * |
045bdb93 EM |
315 | * @param array $existing |
316 | * @param array $contribution | |
317 | * @param string $separator | |
fe7febc7 | 318 | * |
045bdb93 | 319 | * @return array |
383c047b | 320 | */ |
00be9182 | 321 | public static function combineContributions($existing, $contribution, $separator) { |
383c047b | 322 | foreach ($contribution as $field => $value) { |
7eebfcb9 | 323 | $existing[$field] = isset($existing[$field]) ? $existing[$field] . $separator : ''; |
353ffa53 | 324 | $existing[$field] .= $value; |
383c047b DG |
325 | } |
326 | return $existing; | |
327 | } | |
328 | ||
329 | /** | |
330 | * We are going to retrieve the combined contribution and if smarty mail is enabled we | |
331 | * will also assign an array of contributions for this contact to the smarty template | |
fe7febc7 | 332 | * |
383c047b DG |
333 | * @param array $contact |
334 | * @param array $contributions | |
fe7febc7 | 335 | * @param $groupBy |
100fef9d | 336 | * @param int $groupByID |
383c047b | 337 | */ |
00be9182 | 338 | public static function assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID) { |
383c047b | 339 | CRM_Core_Smarty::singleton()->assign('contact_aggregate', $contact['contact_aggregate']); |
353ffa53 TO |
340 | CRM_Core_Smarty::singleton() |
341 | ->assign('contributions', array_intersect_key($contributions, $contact['contribution_ids'][$groupBy][$groupByID])); | |
383c047b DG |
342 | CRM_Core_Smarty::singleton()->assign('contribution_aggregate', $contact['aggregates'][$groupBy][$groupByID]); |
343 | ||
344 | } | |
345 | ||
346 | /** | |
fe482240 | 347 | * Send pdf by email. |
fe7febc7 | 348 | * |
383c047b DG |
349 | * @param array $contact |
350 | * @param string $html | |
fe7febc7 EM |
351 | * |
352 | * @param $is_pdf | |
353 | * @param array $format | |
354 | * @param array $params | |
355 | * | |
356 | * @return bool | |
383c047b | 357 | */ |
be2fb01f | 358 | public static function emailLetter($contact, $html, $is_pdf, $format = [], $params = []) { |
383c047b | 359 | try { |
22e263ad | 360 | if (empty($contact['email'])) { |
383c047b DG |
361 | return FALSE; |
362 | } | |
be2fb01f | 363 | $mustBeEmpty = ['do_not_email', 'is_deceased', 'on_hold']; |
383c047b | 364 | foreach ($mustBeEmpty as $emptyField) { |
22e263ad | 365 | if (!empty($contact[$emptyField])) { |
383c047b DG |
366 | return FALSE; |
367 | } | |
368 | } | |
369 | ||
be2fb01f | 370 | $defaults = [ |
383c047b DG |
371 | 'toName' => $contact['display_name'], |
372 | 'toEmail' => $contact['email'], | |
383c047b DG |
373 | 'text' => '', |
374 | 'html' => $html, | |
be2fb01f | 375 | ]; |
22e263ad | 376 | if (empty($params['from'])) { |
383c047b DG |
377 | $emails = CRM_Core_BAO_Email::getFromEmail(); |
378 | $emails = array_keys($emails); | |
379 | $defaults['from'] = array_pop($emails); | |
380 | } | |
beac1417 MW |
381 | else { |
382 | $defaults['from'] = $params['from']; | |
383 | } | |
ab0f580c | 384 | if (!empty($params['subject'])) { |
c285f292 | 385 | $defaults['subject'] = $params['subject']; |
ab0f580c BM |
386 | } |
387 | else { | |
c285f292 | 388 | $defaults['subject'] = ts('Thank you for your contribution/s'); |
ab0f580c | 389 | } |
22e263ad | 390 | if ($is_pdf) { |
383c047b | 391 | $defaults['html'] = ts('Please see attached'); |
be2fb01f | 392 | $defaults['attachments'] = [CRM_Utils_Mail::appendPDF('ThankYou.pdf', $html, $format)]; |
383c047b DG |
393 | } |
394 | $params = array_merge($defaults); | |
395 | return CRM_Utils_Mail::send($params); | |
396 | } | |
397 | catch (CRM_Core_Exception $e) { | |
398 | return FALSE; | |
399 | } | |
6a488035 | 400 | } |
96025800 | 401 | |
2fe8b920 | 402 | /** |
403 | * @param $contact | |
404 | * @param $formValues | |
405 | * @param $contribution | |
406 | * @param $groupBy | |
407 | * @param $contributions | |
408 | * @param $realSeparator | |
409 | * @param $tableSeparators | |
410 | * @param $messageToken | |
411 | * @param $html_message | |
412 | * @param $separator | |
413 | * @param $categories | |
414 | * @param bool $grouped | |
415 | * @param int $groupByID | |
416 | * | |
417 | * @return string | |
418 | */ | |
419 | protected static function generateHtml(&$contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID) { | |
420 | static $validated = FALSE; | |
421 | $html = NULL; | |
422 | ||
2fe8b920 | 423 | self::assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID); |
424 | ||
425 | if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { | |
426 | if (!$validated && in_array($realSeparator, $tableSeparators) && !self::isValidHTMLWithTableSeparator($messageToken, $html_message)) { | |
427 | $realSeparator = ', '; | |
428 | 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.')); | |
429 | } | |
430 | $validated = TRUE; | |
3280f327 | 431 | $html = str_replace($separator, $realSeparator, self::resolveTokens($html_message, $contact, $contribution, $messageToken, $grouped, $separator)); |
2fe8b920 | 432 | } |
433 | ||
434 | return $html; | |
435 | } | |
436 | ||
6a488035 | 437 | } |