Merge branch '4.6' into 'master'
[civicrm-core.git] / CRM / Contribute / Form / Task / PDFLetterCommon.php
CommitLineData
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 */
7class 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}