4 * This class provides the common functionality for creating PDF letter for
5 * one or a group of contact ids.
7 class CRM_Contribute_Form_Task_PDFLetterCommon
extends CRM_Contact_Form_Task_PDFLetterCommon
{
10 * Process the form after the input has been submitted and validated.
12 * @param CRM_Contribute_Form_Task $form
14 public static function postProcess(&$form) {
15 list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self
::processMessageTemplate($form);
17 $emailParams = array();
18 if (!empty($formValues['email_options'])) {
19 $returnProperties['email'] = $returnProperties['on_hold'] = $returnProperties['is_deceased'] = $returnProperties['do_not_email'] = 1;
21 'subject' => $formValues['subject'],
23 // We need display_name for emailLetter() so add to returnProperties here
24 $returnProperties['display_name'] = 1;
25 if (stristr($formValues['email_options'], 'pdfemail')) {
30 $receipt_update = isset($formValues['receipt_update']) ?
$formValues['receipt_update'] : FALSE;
31 $thankyou_update = isset($formValues['thankyou_update']) ?
$formValues['thankyou_update'] : FALSE;
32 $nowDate = date('YmdHis');
33 $receipts = $thanks = $emailed = 0;
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
39 if (isset($formValues['group_by_separator'])) {
40 if ($formValues['group_by_separator'] == 'td') {
41 $realSeparator = "</td><td>";
44 $separator = '****~~~~';// a placeholder in case the separator is common in the string - e.g ', '
47 $groupBy = $formValues['group_by'];
49 // skip some contacts ?
50 $skipOnHold = isset($form->skipOnHold
) ?
$form->skipOnHold
: FALSE;
51 $skipDeceased = isset($form->skipDeceased
) ?
$form->skipDeceased
: TRUE;
52 $contributionIDs = $form->getVar('_contributionIds');
53 if ($form->_includesSoftCredits
) {
54 //@todo - comment on what is stored there
55 $contributionIDs = $form->getVar('_contributionContactIds');
57 list($contributions, $contacts) = self
::buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $form->_includesSoftCredits
);
59 foreach ($contributions as $contributionId => $contribution) {
60 $contact = &$contacts[$contribution['contact_id']];
61 $grouped = $groupByID = 0;
63 $groupByID = empty($contribution[$groupBy]) ?
0 : $contribution[$groupBy];
64 $contribution = $contact['combined'][$groupBy][$groupByID];
68 self
::assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID);
70 if (empty($groupBy) ||
empty($contact['is_sent'][$groupBy][$groupByID])) {
71 if (!$validated && $realSeparator == '</td><td>' && !self
::isValidHTMLWithTableSeparator($messageToken, $html_message)) {
72 $realSeparator = ', ';
73 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.'));
76 $html[$contributionId] = str_replace($separator, $realSeparator, self
::resolveTokens($html_message, $contact, $contribution, $messageToken, $categories, $grouped, $separator));
77 $contact['is_sent'][$groupBy][$groupByID] = TRUE;
78 if (!empty($formValues['email_options'])) {
79 if (self
::emailLetter($contact, $html[$contributionId], $isPDF, $formValues, $emailParams)) {
81 if (!stristr($formValues['email_options'], 'both')) {
82 unset($html[$contributionId]);
88 // update dates (do it for each contribution including grouped recurring contribution)
89 //@todo - the 2 calls below bypass all hooks. Using the api would possibly be slower than one call but not than 2
90 if ($receipt_update) {
91 $result = CRM_Core_DAO
::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'receipt_date', $nowDate);
96 if ($thankyou_update) {
97 $result = CRM_Core_DAO
::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'thankyou_date', $nowDate);
103 //createActivities requires both $form->_contactIds and $contacts -
104 //@todo - figure out why
105 $form->_contactIds
= array_keys($contacts);
106 self
::createActivities($form, $html_message, $form->_contactIds
);
108 CRM_Utils_PDF_Utils
::html2pdf($html, "CiviLetter.pdf", FALSE, $formValues);
111 $form->postProcessHook();
114 $updateStatus = ts('Receipts have been emailed to %1 contributions.', array(1 => $emailed));
117 $updateStatus = ts('Receipt date has been updated for %1 contributions.', array(1 => $receipts));
120 $updateStatus .= ' ' . ts('Thank-you date has been updated for %1 contributions.', array(1 => $thanks));
124 CRM_Core_Session
::setStatus($updateStatus);
127 // ie. we have only sent emails - lets no show a white screen
128 CRM_Utils_System
::civiExit(1);
133 * Check whether any of the tokens exist in the html outside a table cell.
134 * If they do the table cell separator is not supported (return false)
135 * At this stage we are only anticipating contributions passed in this way but
136 * it would be easy to add others
142 public static function isValidHTMLWithTableSeparator($tokens, $html) {
143 $relevantEntities = array('contribution');
144 foreach ($relevantEntities as $entity) {
145 if (isset($tokens[$entity]) && is_array($tokens[$entity])) {
146 foreach ($tokens[$entity] as $token) {
147 if (!self
::isHtmlTokenInTableCell($token, $entity, $html)) {
157 * Check that the token only appears in a table cell. The '</td><td>' separator cannot otherwise work
158 * Calculate the number of times it appears IN the cell & the number of times it appears - should be the same!
162 * @param $textToSearch
166 public static function isHtmlTokenInTableCell($token, $entity, $textToSearch) {
167 $tokenToMatch = $entity . '.' . $token;
169 $within = preg_match_all("|<td.+?{" . $tokenToMatch . "}.+?</td|si", $textToSearch, $dontCare);
170 $total = preg_match_all("|{" . $tokenToMatch . "}|", $textToSearch, $dontCare);
171 return ($within == $total);
176 * @param string $html_message
177 * @param array $contact
178 * @param array $contribution
179 * @param array $messageToken
180 * @param array $categories
181 * @param bool $grouped
182 * Does this letter represent more than one contribution.
183 * @param string $separator
184 * What is the preferred letter separator.
187 private static function resolveTokens($html_message, $contact, $contribution, $messageToken, $categories, $grouped, $separator) {
188 $tokenHtml = CRM_Utils_Token
::replaceContactTokens($html_message, $contact, TRUE, $messageToken);
190 $tokenHtml = CRM_Utils_Token
::replaceMultipleContributionTokens($separator, $tokenHtml, $contribution, TRUE, $messageToken);
193 // no change to normal behaviour to avoid risk of breakage
194 $tokenHtml = CRM_Utils_Token
::replaceContributionTokens($tokenHtml, $contribution, TRUE, $messageToken);
196 $tokenHtml = CRM_Utils_Token
::replaceHookTokens($tokenHtml, $contact, $categories, TRUE);
197 if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY
) {
198 $smarty = CRM_Core_Smarty
::singleton();
199 // also add the tokens to the template
200 $smarty->assign_by_ref('contact', $contact);
201 $tokenHtml = $smarty->fetch("string:$tokenHtml");
207 * Generate the contribution array from the form, we fill in the contact details and determine any aggregation
208 * around contact_id of contribution_recur_id
210 * @param string $groupBy
211 * @param array $contributionIDs
212 * @param array $returnProperties
213 * @param bool $skipOnHold
214 * @param bool $skipDeceased
215 * @param array $messageToken
216 * @param string $task
217 * @param string $separator
218 * @param bool $isIncludeSoftCredits
222 public static function buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $isIncludeSoftCredits) {
223 $contributions = $contacts = $notSent = array();
224 foreach ($contributionIDs as $item => $contributionId) {
225 // get contribution information
226 $contribution = CRM_Utils_Token
::getContributionTokenDetails(array('contribution_id' => $contributionId),
232 $contribution = $contributions[$contributionId] = $contribution[$contributionId];
234 if ($isIncludeSoftCredits) {
235 //@todo find out why this happens & add comments
236 list($contactID) = explode('-', $item);
237 $contactID = (int) $contactID;
240 $contactID = $contribution['contact_id'];
242 if (!isset($contacts[$contactID])) {
243 $contacts[$contactID] = array();
244 $contacts[$contactID]['contact_aggregate'] = 0;
245 $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = array();
248 $contacts[$contactID]['contact_aggregate'] +
= $contribution['total_amount'];
249 $groupByID = empty($contribution[$groupBy]) ?
0 : $contribution[$groupBy];
251 $contacts[$contactID]['contribution_ids'][$groupBy][$groupByID][$contributionId] = TRUE;
252 if (!isset($contacts[$contactID]['combined'][$groupBy]) ||
!isset($contacts[$contactID]['combined'][$groupBy][$groupByID])) {
253 $contacts[$contactID]['combined'][$groupBy][$groupByID] = $contribution;
254 $contacts[$contactID]['aggregates'][$groupBy][$groupByID] = $contribution['total_amount'];
257 $contacts[$contactID]['combined'][$groupBy][$groupByID] = self
::combineContributions($contacts[$contactID]['combined'][$groupBy][$groupByID], $contribution, $separator);
258 $contacts[$contactID]['aggregates'][$groupBy][$groupByID] +
= $contribution['total_amount'];
261 // Assign the available contributions before calling tokens so hooks parsing smarty can access it.
262 // Note that in core code you can only use smarty here if enable if for the whole site, incl
263 // CiviMail, with a big performance impact.
264 // Hooks allow more nuanced smarty usage here.
265 CRM_Core_Smarty
::singleton()->assign('contributions', $contributions);
266 foreach ($contacts as $contactID => $contact) {
267 $tokenResolvedContacts = CRM_Utils_Token
::getTokenDetails(array('contact_id' => $contactID),
275 $contacts[$contactID] = array_merge($tokenResolvedContacts[0][$contactID], $contact);
277 return array($contributions, $contacts);
281 * We combine the contributions by adding the contribution to each field with the separator in
282 * between the existing value and the new one. We put the separator there even if empty so it is clear what the
283 * value for previous contributions was
285 * @param array $existing
286 * @param array $contribution
287 * @param string $separator
291 public static function combineContributions($existing, $contribution, $separator) {
292 foreach ($contribution as $field => $value) {
293 $existing[$field] = isset($existing[$field]) ?
$existing[$field] . $separator : '';
294 $existing[$field] .= $value;
300 * We are going to retrieve the combined contribution and if smarty mail is enabled we
301 * will also assign an array of contributions for this contact to the smarty template
303 * @param array $contact
304 * @param array $contributions
306 * @param int $groupByID
308 public static function assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID) {
309 if (!defined('CIVICRM_MAIL_SMARTY') ||
!CIVICRM_MAIL_SMARTY
) {
312 CRM_Core_Smarty
::singleton()->assign('contact_aggregate', $contact['contact_aggregate']);
313 CRM_Core_Smarty
::singleton()
314 ->assign('contributions', array_intersect_key($contributions, $contact['contribution_ids'][$groupBy][$groupByID]));
315 CRM_Core_Smarty
::singleton()->assign('contribution_aggregate', $contact['aggregates'][$groupBy][$groupByID]);
322 * @param array $contact
323 * @param string $html
326 * @param array $format
327 * @param array $params
331 public static function emailLetter($contact, $html, $is_pdf, $format = array(), $params = array()) {
333 if (empty($contact['email'])) {
336 $mustBeEmpty = array('do_not_email', 'is_deceased', 'on_hold');
337 foreach ($mustBeEmpty as $emptyField) {
338 if (!empty($contact[$emptyField])) {
344 'toName' => $contact['display_name'],
345 'toEmail' => $contact['email'],
349 if (empty($params['from'])) {
350 $emails = CRM_Core_BAO_Email
::getFromEmail();
351 $emails = array_keys($emails);
352 $defaults['from'] = array_pop($emails);
354 if (!empty($params['subject'])) {
355 $defaults['subject'] = $params['subject'];
358 $defaults['subject'] = ts('Thank you for your contribution/s');
361 $defaults['html'] = ts('Please see attached');
362 $defaults['attachments'] = array(CRM_Utils_Mail
::appendPDF('ThankYou.pdf', $html, $format));
364 $params = array_merge($defaults);
365 return CRM_Utils_Mail
::send($params);
367 catch (CRM_Core_Exception
$e) {