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 | ||
9 | /** | |
fe482240 | 10 | * Process the form after the input has been submitted and validated. |
6a488035 | 11 | * |
493078cb | 12 | * @param CRM_Contribute_Form_Task $form |
6a488035 | 13 | */ |
00be9182 | 14 | public static function postProcess(&$form) { |
6a488035 | 15 | list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($form); |
493078cb EM |
16 | $isPDF = FALSE; |
17 | $emailParams = array(); | |
22e263ad | 18 | if (!empty($formValues['email_options'])) { |
383c047b DG |
19 | $returnProperties['email'] = $returnProperties['on_hold'] = $returnProperties['is_deceased'] = $returnProperties['do_not_email'] = 1; |
20 | $emailParams = array( | |
353ffa53 | 21 | 'subject' => $formValues['subject'], |
383c047b | 22 | ); |
f83f6352 DG |
23 | // We need display_name for emailLetter() so add to returnProperties here |
24 | $returnProperties['display_name'] = 1; | |
22e263ad | 25 | if (stristr($formValues['email_options'], 'pdfemail')) { |
383c047b DG |
26 | $isPDF = TRUE; |
27 | } | |
28 | } | |
6a488035 | 29 | // update dates ? |
353ffa53 | 30 | $receipt_update = isset($formValues['receipt_update']) ? $formValues['receipt_update'] : FALSE; |
6a488035 | 31 | $thankyou_update = isset($formValues['thankyou_update']) ? $formValues['thankyou_update'] : FALSE; |
353ffa53 | 32 | $nowDate = date('YmdHis'); |
383c047b | 33 | $receipts = $thanks = $emailed = 0; |
353ffa53 | 34 | $updateStatus = ''; |
383c047b DG |
35 | $task = 'CRM_Contribution_Form_Task_PDFLetterCommon'; |
36 | $realSeparator = ', '; | |
37 | //the original thinking was mutliple options - but we are going with only 2 (comma & td) for now in case | |
38 | // there are security (& UI) issues we need to think through | |
22e263ad TO |
39 | if (isset($formValues['group_by_separator'])) { |
40 | if ($formValues['group_by_separator'] == 'td') { | |
383c047b DG |
41 | $realSeparator = "</td><td>"; |
42 | } | |
43 | } | |
44 | $separator = '****~~~~';// a placeholder in case the separator is common in the string - e.g ', ' | |
8cb33f3b | 45 | $validated = FALSE; |
383c047b DG |
46 | |
47 | $groupBy = $formValues['group_by']; | |
6a488035 TO |
48 | |
49 | // skip some contacts ? | |
50 | $skipOnHold = isset($form->skipOnHold) ? $form->skipOnHold : FALSE; | |
51 | $skipDeceased = isset($form->skipDeceased) ? $form->skipDeceased : TRUE; | |
52 | ||
ff926d67 | 53 | list($contributions, $contacts) = self::buildContributionArray($groupBy, $form, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator); |
383c047b DG |
54 | $html = array(); |
55 | foreach ($contributions as $contributionId => $contribution) { | |
56 | $contact = &$contacts[$contribution['contact_id']]; | |
57 | $grouped = $groupByID = 0; | |
22e263ad | 58 | if ($groupBy) { |
383c047b DG |
59 | $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; |
60 | $contribution = $contact['combined'][$groupBy][$groupByID]; | |
61 | $grouped = TRUE; | |
6a488035 TO |
62 | } |
63 | ||
383c047b | 64 | self::assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID); |
6a488035 | 65 | |
22e263ad TO |
66 | if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) { |
67 | if (!$validated && $realSeparator == '</td><td>' && !self::isValidHTMLWithTableSeparator($messageToken, $html_message)) { | |
8cb33f3b | 68 | $realSeparator = ', '; |
6c690132 | 69 | 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.')); |
8cb33f3b EM |
70 | } |
71 | $validated = TRUE; | |
493078cb | 72 | $html[$contributionId] = str_replace($separator, $realSeparator, self::resolveTokens($html_message, $contact, $contribution, $messageToken, $categories, $grouped, $separator)); |
383c047b | 73 | $contact['is_sent'][$groupBy][$groupByID] = TRUE; |
22e263ad TO |
74 | if (!empty($formValues['email_options'])) { |
75 | if (self::emailLetter($contact, $html[$contributionId], $isPDF, $formValues, $emailParams)) { | |
d3e86119 | 76 | $emailed++; |
22e263ad | 77 | if (!stristr($formValues['email_options'], 'both')) { |
383c047b DG |
78 | unset($html[$contributionId]); |
79 | } | |
80 | } | |
81 | } | |
6a488035 TO |
82 | } |
83 | ||
6a488035 | 84 | // update dates (do it for each contribution including grouped recurring contribution) |
383c047b | 85 | //@todo - the 2 calls below bypass all hooks. Using the api would possibly be slower than one call but not than 2 |
6a488035 TO |
86 | if ($receipt_update) { |
87 | $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'receipt_date', $nowDate); | |
6a488035 TO |
88 | if ($result) { |
89 | $receipts++; | |
383c047b | 90 | } |
6a488035 TO |
91 | } |
92 | if ($thankyou_update) { | |
93 | $result = CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'thankyou_date', $nowDate); | |
6a488035 TO |
94 | if ($result) { |
95 | $thanks++; | |
383c047b | 96 | } |
6a488035 TO |
97 | } |
98 | } | |
383c047b DG |
99 | //createActivities requires both $form->_contactIds and $contacts - |
100 | //@todo - figure out why | |
101 | $form->_contactIds = array_keys($contacts); | |
6a488035 | 102 | self::createActivities($form, $html_message, $form->_contactIds); |
22e263ad | 103 | if (!empty($html)) { |
383c047b DG |
104 | CRM_Utils_PDF_Utils::html2pdf($html, "CiviLetter.pdf", FALSE, $formValues); |
105 | } | |
6a488035 TO |
106 | |
107 | $form->postProcessHook(); | |
108 | ||
383c047b DG |
109 | if ($emailed) { |
110 | $updateStatus = ts('Receipts have been emailed to %1 contributions.', array(1 => $emailed)); | |
111 | } | |
6a488035 TO |
112 | if ($receipts) { |
113 | $updateStatus = ts('Receipt date has been updated for %1 contributions.', array(1 => $receipts)); | |
114 | } | |
115 | if ($thanks) { | |
116 | $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', array(1 => $thanks)); | |
117 | } | |
118 | ||
119 | if ($updateStatus) { | |
120 | CRM_Core_Session::setStatus($updateStatus); | |
121 | } | |
22e263ad | 122 | if (!empty($html)) { |
383c047b DG |
123 | // ie. we have only sent emails - lets no show a white screen |
124 | CRM_Utils_System::civiExit(1); | |
125 | } | |
126 | } | |
665e5ec7 | 127 | |
8cb33f3b EM |
128 | /** |
129 | * Check whether any of the tokens exist in the html outside a table cell. | |
130 | * If they do the table cell separator is not supported (return false) | |
131 | * At this stage we are only anticipating contributions passed in this way but | |
132 | * it would be easy to add others | |
133 | * @param $tokens | |
134 | * @param $html | |
135 | * | |
136 | * @return bool | |
137 | */ | |
00be9182 | 138 | public static function isValidHTMLWithTableSeparator($tokens, $html) { |
8cb33f3b EM |
139 | $relevantEntities = array('contribution'); |
140 | foreach ($relevantEntities as $entity) { | |
045bdb93 | 141 | if (isset($tokens[$entity]) && is_array($tokens[$entity])) { |
8cb33f3b | 142 | foreach ($tokens[$entity] as $token) { |
353ffa53 | 143 | if (!self::isHtmlTokenInTableCell($token, $entity, $html)) { |
8cb33f3b EM |
144 | return FALSE; |
145 | } | |
146 | } | |
147 | } | |
148 | } | |
149 | return TRUE; | |
150 | } | |
151 | ||
152 | /** | |
100fef9d | 153 | * Check that the token only appears in a table cell. The '</td><td>' separator cannot otherwise work |
045bdb93 EM |
154 | * Calculate the number of times it appears IN the cell & the number of times it appears - should be the same! |
155 | * | |
8cb33f3b EM |
156 | * @param $token |
157 | * @param $entity | |
045bdb93 EM |
158 | * @param $textToSearch |
159 | * | |
045bdb93 | 160 | * @return bool |
8cb33f3b | 161 | */ |
00be9182 | 162 | public static function isHtmlTokenInTableCell($token, $entity, $textToSearch) { |
045bdb93 | 163 | $tokenToMatch = $entity . '.' . $token; |
6c690132 | 164 | $dontCare = array(); |
92fcb95f TO |
165 | $within = preg_match_all("|<td.+?{" . $tokenToMatch . "}.+?</td|si", $textToSearch, $dontCare); |
166 | $total = preg_match_all("|{" . $tokenToMatch . "}|", $textToSearch, $dontCare); | |
045bdb93 | 167 | return ($within == $total); |
8cb33f3b EM |
168 | } |
169 | ||
874c9be7 TO |
170 | /** |
171 | * | |
172 | * @param string $html_message | |
173 | * @param array $contact | |
174 | * @param array $contribution | |
175 | * @param array $messageToken | |
176 | * @param array $categories | |
177 | * @param bool $grouped | |
178 | * Does this letter represent more than one contribution. | |
179 | * @param string $separator | |
180 | * What is the preferred letter separator. | |
181 | * @return string | |
182 | */ | |
183 | private static function resolveTokens($html_message, $contact, $contribution, $messageToken, $categories, $grouped, $separator) { | |
184 | $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact, TRUE, $messageToken); | |
22e263ad | 185 | if ($grouped) { |
874c9be7 TO |
186 | $tokenHtml = CRM_Utils_Token::replaceMultipleContributionTokens($separator, $tokenHtml, $contribution, TRUE, $messageToken); |
187 | } | |
188 | else { | |
189 | // no change to normal behaviour to avoid risk of breakage | |
190 | $tokenHtml = CRM_Utils_Token::replaceContributionTokens($tokenHtml, $contribution, TRUE, $messageToken); | |
191 | } | |
192 | $tokenHtml = CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact, $categories, TRUE); | |
193 | if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) { | |
383c047b DG |
194 | $smarty = CRM_Core_Smarty::singleton(); |
195 | // also add the tokens to the template | |
196 | $smarty->assign_by_ref('contact', $contact); | |
197 | $tokenHtml = $smarty->fetch("string:$tokenHtml"); | |
198 | } | |
199 | return $tokenHtml; | |
200 | } | |
201 | ||
202 | /** | |
203 | * Generate the contribution array from the form, we fill in the contact details and determine any aggregation | |
204 | * around contact_id of contribution_recur_id | |
205 | * | |
045bdb93 EM |
206 | * @param string $groupBy |
207 | * @param CRM_Contribute_Form_Task $form | |
354bc6e1 | 208 | * @param array $returnProperties |
014c4014 TO |
209 | * @param bool $skipOnHold |
210 | * @param bool $skipDeceased | |
354bc6e1 | 211 | * @param array $messageToken |
8cb33f3b | 212 | * @param string $task |
045bdb93 | 213 | * @param string $separator |
fe7febc7 | 214 | * |
a6c01b45 | 215 | * @return array |
383c047b | 216 | */ |
00be9182 | 217 | public static function buildContributionArray($groupBy, $form, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator) { |
383c047b DG |
218 | $contributions = $contacts = $notSent = array(); |
219 | $contributionIDs = $form->getVar('_contributionIds'); | |
220 | if ($form->_includesSoftCredits) { | |
221 | //@todo - comment on what is stored there | |
222 | $contributionIDs = $form->getVar('_contributionContactIds'); | |
223 | } | |
224 | foreach ($contributionIDs as $item => $contributionId) { | |
ff926d67 EM |
225 | // get contribution information |
226 | $contribution = CRM_Utils_Token::getContributionTokenDetails(array('contribution_id' => $contributionId), | |
227 | $returnProperties, | |
228 | NULL, | |
229 | $messageToken, | |
230 | $task | |
231 | ); | |
232 | $contribution = $contributions[$contributionId] = $contribution[$contributionId]; | |
233 | if ($form->_includesSoftCredits) { | |
234 | //@todo find out why this happens & add comments | |
235 | list($contactID) = explode('-', $item); | |
236 | $contactID = (int) $contactID; | |
874c9be7 | 237 | } |
ff926d67 | 238 | else { |
874c9be7 | 239 | $contactID = $contribution['contact_id']; |
ff926d67 | 240 | } |
22e263ad | 241 | if (!isset($contacts[$contactID])) { |
ff926d67 | 242 | list($contact) = CRM_Utils_Token::getTokenDetails(array('contact_id' => $contactID), |
383c047b | 243 | $returnProperties, |
ff926d67 EM |
244 | $skipOnHold, |
245 | $skipDeceased, | |
383c047b DG |
246 | NULL, |
247 | $messageToken, | |
248 | $task | |
249 | ); | |
ff926d67 EM |
250 | $contacts[$contactID] = $contact[$contactID]; |
251 | $contacts[$contactID]['contact_aggregate'] = 0; | |
252 | $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = array(); | |
253 | } | |
383c047b | 254 | |
ff926d67 EM |
255 | $contacts[$contactID]['contact_aggregate'] += $contribution['total_amount']; |
256 | $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy]; | |
383c047b | 257 | |
ff926d67 | 258 | $contacts[$contactID]['contribution_ids'][$groupBy][$groupByID][$contributionId] = TRUE; |
22e263ad | 259 | if (!isset($contacts[$contactID]['combined'][$groupBy]) || !isset($contacts[$contactID]['combined'][$groupBy][$groupByID])) { |
ff926d67 EM |
260 | $contacts[$contactID]['combined'][$groupBy][$groupByID] = $contribution; |
261 | $contacts[$contactID]['aggregates'][$groupBy][$groupByID] = $contribution['total_amount']; | |
383c047b | 262 | } |
ff926d67 EM |
263 | else { |
264 | $contacts[$contactID]['combined'][$groupBy][$groupByID] = self::combineContributions($contacts[$contactID]['combined'][$groupBy][$groupByID], $contribution, $separator); | |
265 | $contacts[$contactID]['aggregates'][$groupBy][$groupByID] += $contribution['total_amount']; | |
383c047b DG |
266 | } |
267 | } | |
ff926d67 | 268 | return array($contributions, $contacts); |
383c047b DG |
269 | } |
270 | ||
271 | /** | |
272 | * We combine the contributions by adding the contribution to each field with the separator in | |
273 | * between the existing value and the new one. We put the separator there even if empty so it is clear what the | |
274 | * value for previous contributions was | |
fe7febc7 | 275 | * |
045bdb93 EM |
276 | * @param array $existing |
277 | * @param array $contribution | |
278 | * @param string $separator | |
fe7febc7 | 279 | * |
045bdb93 | 280 | * @return array |
383c047b | 281 | */ |
00be9182 | 282 | public static function combineContributions($existing, $contribution, $separator) { |
383c047b | 283 | foreach ($contribution as $field => $value) { |
7eebfcb9 | 284 | $existing[$field] = isset($existing[$field]) ? $existing[$field] . $separator : ''; |
353ffa53 | 285 | $existing[$field] .= $value; |
383c047b DG |
286 | } |
287 | return $existing; | |
288 | } | |
289 | ||
290 | /** | |
291 | * We are going to retrieve the combined contribution and if smarty mail is enabled we | |
292 | * will also assign an array of contributions for this contact to the smarty template | |
fe7febc7 | 293 | * |
383c047b DG |
294 | * @param array $contact |
295 | * @param array $contributions | |
fe7febc7 | 296 | * @param $groupBy |
100fef9d | 297 | * @param int $groupByID |
383c047b | 298 | */ |
00be9182 | 299 | public static function assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID) { |
383c047b DG |
300 | if (!defined('CIVICRM_MAIL_SMARTY') || !CIVICRM_MAIL_SMARTY) { |
301 | return; | |
302 | } | |
303 | CRM_Core_Smarty::singleton()->assign('contact_aggregate', $contact['contact_aggregate']); | |
353ffa53 TO |
304 | CRM_Core_Smarty::singleton() |
305 | ->assign('contributions', array_intersect_key($contributions, $contact['contribution_ids'][$groupBy][$groupByID])); | |
383c047b DG |
306 | CRM_Core_Smarty::singleton()->assign('contribution_aggregate', $contact['aggregates'][$groupBy][$groupByID]); |
307 | ||
308 | } | |
309 | ||
310 | /** | |
fe482240 | 311 | * Send pdf by email. |
fe7febc7 | 312 | * |
383c047b DG |
313 | * @param array $contact |
314 | * @param string $html | |
fe7febc7 EM |
315 | * |
316 | * @param $is_pdf | |
317 | * @param array $format | |
318 | * @param array $params | |
319 | * | |
320 | * @return bool | |
383c047b | 321 | */ |
00be9182 | 322 | public static function emailLetter($contact, $html, $is_pdf, $format = array(), $params = array()) { |
383c047b | 323 | try { |
22e263ad | 324 | if (empty($contact['email'])) { |
383c047b DG |
325 | return FALSE; |
326 | } | |
327 | $mustBeEmpty = array('do_not_email', 'is_deceased', 'on_hold'); | |
328 | foreach ($mustBeEmpty as $emptyField) { | |
22e263ad | 329 | if (!empty($contact[$emptyField])) { |
383c047b DG |
330 | return FALSE; |
331 | } | |
332 | } | |
333 | ||
334 | $defaults = array( | |
335 | 'toName' => $contact['display_name'], | |
336 | 'toEmail' => $contact['email'], | |
337 | 'subject' => ts('Thank you for your contribution/s'), | |
338 | 'text' => '', | |
339 | 'html' => $html, | |
340 | ); | |
22e263ad | 341 | if (empty($params['from'])) { |
383c047b DG |
342 | $emails = CRM_Core_BAO_Email::getFromEmail(); |
343 | $emails = array_keys($emails); | |
344 | $defaults['from'] = array_pop($emails); | |
345 | } | |
22e263ad | 346 | if ($is_pdf) { |
383c047b DG |
347 | $defaults['html'] = ts('Please see attached'); |
348 | $defaults['attachments'] = array(CRM_Utils_Mail::appendPDF('ThankYou.pdf', $html, $format)); | |
349 | } | |
350 | $params = array_merge($defaults); | |
351 | return CRM_Utils_Mail::send($params); | |
352 | } | |
353 | catch (CRM_Core_Exception $e) { | |
354 | return FALSE; | |
355 | } | |
6a488035 | 356 | } |
96025800 | 357 | |
6a488035 | 358 | } |