Merge pull request #20136 from eileenmcnaughton/dom
[civicrm-core.git] / CRM / Contribute / Form / Task / PDFLetter.php
CommitLineData
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
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
07f8d162 19 * This class provides the functionality to create PDF letter for a group of contacts or a single contact.
6a488035
TO
20 */
21class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task {
22
23 /**
fe482240 24 * All the existing templates in the system.
6a488035
TO
25 *
26 * @var array
27 */
28 public $_templates = NULL;
29
30 public $_single = NULL;
31
32 public $_cid = NULL;
33
34 /**
fe482240 35 * Build all the data structures needed to build the form.
ed106721 36 */
00be9182 37 public function preProcess() {
6a488035
TO
38 $this->skipOnHold = $this->skipDeceased = FALSE;
39 CRM_Contact_Form_Task_PDFLetterCommon::preProcess($this);
6a488035 40 // store case id if present
fe61faf3
CW
41 $this->_caseId = CRM_Utils_Request::retrieve('caseid', 'CommaSeparatedIntegers', $this, FALSE);
42 if (!empty($this->_caseId) && strpos($this->_caseId, ',')) {
43 $this->_caseIds = explode(',', $this->_caseId);
44 unset($this->_caseId);
45 }
6a488035
TO
46
47 // retrieve contact ID if this is 'single' mode
fe61faf3 48 $cid = CRM_Utils_Request::retrieve('cid', 'CommaSeparatedIntegers', $this, FALSE);
6a488035
TO
49
50 $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
51
52 if ($cid) {
53 CRM_Contact_Form_Task_PDFLetterCommon::preProcessSingle($this, $cid);
54 $this->_single = TRUE;
6a488035
TO
55 }
56 else {
57 parent::preProcess();
58 }
59 $this->assign('single', $this->_single);
60 }
61
186c9c17
EM
62 /**
63 * This virtual function is used to set the default values of
64 * various form elements
65 *
66 * access public
67 *
a6c01b45
CW
68 * @return array
69 * reference to the array of default values
186c9c17 70 */
1330f57a 71
186c9c17
EM
72 /**
73 * @return array
74 */
00be9182 75 public function setDefaultValues() {
be2fb01f 76 $defaults = [];
6a488035 77 if (isset($this->_activityId)) {
be2fb01f 78 $params = ['id' => $this->_activityId];
6a488035 79 CRM_Activity_BAO_Activity::retrieve($params, $defaults);
9c1bc317 80 $defaults['html_message'] = $defaults['details'] ?? NULL;
6a488035 81 }
b5fd2bef
CW
82 else {
83 $defaults['thankyou_update'] = 1;
84 }
6a488035
TO
85 $defaults = $defaults + CRM_Contact_Form_Task_PDFLetterCommon::setDefaultValues();
86 return $defaults;
87 }
88
89 /**
fe482240 90 * Build the form object.
6a488035
TO
91 */
92 public function buildQuickForm() {
93 //enable form element
94 $this->assign('suppressForm', FALSE);
95
99ad38df
MW
96 // Build common form elements
97 CRM_Contribute_Form_Task_PDFLetterCommon::buildQuickForm($this);
6a488035
TO
98
99 // specific need for contributions
383c047b 100 $this->add('static', 'more_options_header', NULL, ts('Thank-you Letter Options'));
6a488035
TO
101 $this->add('checkbox', 'receipt_update', ts('Update receipt dates for these contributions'), FALSE);
102 $this->add('checkbox', 'thankyou_update', ts('Update thank-you dates for these contributions'), FALSE);
6a488035
TO
103
104 // Group options for tokens are not yet implemented. dgg
be2fb01f 105 $options = [
383c047b
DG
106 '' => ts('- no grouping -'),
107 'contact_id' => ts('Contact'),
108 'contribution_recur_id' => ts('Contact and Recurring'),
109 'financial_type_id' => ts('Contact and Financial Type'),
110 'campaign_id' => ts('Contact and Campaign'),
536f0e02 111 'payment_instrument_id' => ts('Contact and Payment Method'),
be2fb01f
CW
112 ];
113 $this->addElement('select', 'group_by', ts('Group contributions by'), $options, [], "<br/>", FALSE);
383c047b 114 // this was going to be free-text but I opted for radio options in case there was a script injection risk
be2fb01f 115 $separatorOptions = ['comma' => 'Comma', 'td' => 'Horizontal Table Cell', 'tr' => 'Vertical Table Cell', 'br' => 'Line Break'];
698253db 116
383c047b 117 $this->addElement('select', 'group_by_separator', ts('Separator (grouped contributions)'), $separatorOptions);
be2fb01f 118 $emailOptions = [
383c047b
DG
119 '' => ts('Generate PDFs for printing (only)'),
120 'email' => ts('Send emails where possible. Generate printable PDFs for contacts who cannot receive email.'),
121 'both' => ts('Send emails where possible. Generate printable PDFs for all contacts.'),
be2fb01f 122 ];
353ffa53 123 if (CRM_Core_Config::singleton()->doNotAttachPDFReceipt) {
97eaa51b
EM
124 $emailOptions['pdfemail'] = ts('Send emails with an attached PDF where possible. Generate printable PDFs for contacts who cannot receive email.');
125 $emailOptions['pdfemail_both'] = ts('Send emails with an attached PDF where possible. Generate printable PDFs for all contacts.');
126 }
be2fb01f 127 $this->addElement('select', 'email_options', ts('Print and email options'), $emailOptions, [], "<br/>", FALSE);
ed106721 128
be2fb01f 129 $this->addButtons([
1330f57a
SL
130 [
131 'type' => 'upload',
132 'name' => ts('Make Thank-you Letters'),
133 'isDefault' => TRUE,
134 ],
135 [
136 'type' => 'cancel',
137 'name' => ts('Done'),
138 ],
139 ]);
6a488035
TO
140
141 }
142
143 /**
fe482240 144 * Process the form after the input has been submitted and validated.
6a488035
TO
145 */
146 public function postProcess() {
6a488035
TO
147 CRM_Contribute_Form_Task_PDFLetterCommon::postProcess($this);
148 }
96025800 149
5ec6b0ad
TM
150 /**
151 * List available tokens for this form.
152 *
153 * @return array
154 */
155 public function listTokens() {
156 $tokens = CRM_Core_SelectValues::contactTokens();
8ee05f69 157 $tokens = array_merge(CRM_Core_SelectValues::contributionTokens(), $tokens);
cb9b8644 158 $tokens = array_merge(CRM_Core_SelectValues::domainTokens(), $tokens);
5ec6b0ad
TM
159 return $tokens;
160 }
161
01a5461d 162 /**
163 * Generate the contribution array from the form, we fill in the contact details and determine any aggregation
164 * around contact_id of contribution_recur_id
165 *
166 * @param string $groupBy
167 * @param array $contributionIDs
168 * @param array $returnProperties
169 * @param bool $skipOnHold
170 * @param bool $skipDeceased
171 * @param array $messageToken
172 * @param string $task
173 * @param string $separator
174 * @param bool $isIncludeSoftCredits
175 *
176 * @return array
177 */
178 public static function buildContributionArray($groupBy, $contributionIDs, $returnProperties, $skipOnHold, $skipDeceased, $messageToken, $task, $separator, $isIncludeSoftCredits) {
179 $contributions = $contacts = [];
180 foreach ($contributionIDs as $item => $contributionId) {
181 $contribution = CRM_Contribute_BAO_Contribution::getContributionTokenValues($contributionId, $messageToken)['values'][$contributionId];
182 $contribution['campaign'] = $contribution['contribution_campaign_title'] ?? NULL;
183 $contributions[$contributionId] = $contribution;
184
185 if ($isIncludeSoftCredits) {
186 //@todo find out why this happens & add comments
187 [$contactID] = explode('-', $item);
188 $contactID = (int) $contactID;
189 }
190 else {
191 $contactID = $contribution['contact_id'];
192 }
193 if (!isset($contacts[$contactID])) {
194 $contacts[$contactID] = [];
195 $contacts[$contactID]['contact_aggregate'] = 0;
196 $contacts[$contactID]['combined'] = $contacts[$contactID]['contribution_ids'] = [];
197 }
198
199 $contacts[$contactID]['contact_aggregate'] += $contribution['total_amount'];
200 $groupByID = empty($contribution[$groupBy]) ? 0 : $contribution[$groupBy];
201
202 $contacts[$contactID]['contribution_ids'][$groupBy][$groupByID][$contributionId] = TRUE;
203 if (!isset($contacts[$contactID]['combined'][$groupBy]) || !isset($contacts[$contactID]['combined'][$groupBy][$groupByID])) {
204 $contacts[$contactID]['combined'][$groupBy][$groupByID] = $contribution;
205 $contacts[$contactID]['aggregates'][$groupBy][$groupByID] = $contribution['total_amount'];
206 }
207 else {
208 $contacts[$contactID]['combined'][$groupBy][$groupByID] = self::combineContributions($contacts[$contactID]['combined'][$groupBy][$groupByID], $contribution, $separator);
209 $contacts[$contactID]['aggregates'][$groupBy][$groupByID] += $contribution['total_amount'];
210 }
211 }
212 // Assign the available contributions before calling tokens so hooks parsing smarty can access it.
213 // Note that in core code you can only use smarty here if enable if for the whole site, incl
214 // CiviMail, with a big performance impact.
215 // Hooks allow more nuanced smarty usage here.
216 CRM_Core_Smarty::singleton()->assign('contributions', $contributions);
217 foreach ($contacts as $contactID => $contact) {
218 [$tokenResolvedContacts] = CRM_Utils_Token::getTokenDetails(['contact_id' => $contactID],
219 $returnProperties,
220 $skipOnHold,
221 $skipDeceased,
222 NULL,
223 $messageToken,
224 $task
225 );
226 $contacts[$contactID] = array_merge($tokenResolvedContacts[$contactID], $contact);
227 }
228 return [$contributions, $contacts];
229 }
230
231 /**
232 * We combine the contributions by adding the contribution to each field with the separator in
233 * between the existing value and the new one. We put the separator there even if empty so it is clear what the
234 * value for previous contributions was
235 *
236 * @param array $existing
237 * @param array $contribution
238 * @param string $separator
239 *
240 * @return array
241 */
242 public static function combineContributions($existing, $contribution, $separator) {
243 foreach ($contribution as $field => $value) {
244 $existing[$field] = isset($existing[$field]) ? $existing[$field] . $separator : '';
245 $existing[$field] .= $value;
246 }
247 return $existing;
248 }
249
250 /**
251 * We are going to retrieve the combined contribution and if smarty mail is enabled we
252 * will also assign an array of contributions for this contact to the smarty template
253 *
254 * @param array $contact
255 * @param array $contributions
256 * @param $groupBy
257 * @param int $groupByID
258 */
259 public static function assignCombinedContributionValues($contact, $contributions, $groupBy, $groupByID) {
260 CRM_Core_Smarty::singleton()->assign('contact_aggregate', $contact['contact_aggregate']);
261 CRM_Core_Smarty::singleton()
262 ->assign('contributions', $contributions);
263 CRM_Core_Smarty::singleton()->assign('contribution_aggregate', $contact['aggregates'][$groupBy][$groupByID]);
264 }
265
266 /**
267 * @param $contact
268 * @param $formValues
269 * @param $contribution
270 * @param $groupBy
271 * @param $contributions
272 * @param $realSeparator
273 * @param $tableSeparators
274 * @param $messageToken
275 * @param $html_message
276 * @param $separator
277 * @param $categories
278 * @param bool $grouped
279 * @param int $groupByID
280 *
281 * @return string
282 * @throws \CRM_Core_Exception
283 */
284 public static function generateHtml(&$contact, $contribution, $groupBy, $contributions, $realSeparator, $tableSeparators, $messageToken, $html_message, $separator, $grouped, $groupByID) {
285 static $validated = FALSE;
286 $html = NULL;
287
288 $groupedContributions = array_intersect_key($contributions, $contact['contribution_ids'][$groupBy][$groupByID]);
289 CRM_Contribute_Form_Task_PDFLetter::assignCombinedContributionValues($contact, $groupedContributions, $groupBy, $groupByID);
290
291 if (empty($groupBy) || empty($contact['is_sent'][$groupBy][$groupByID])) {
292 if (!$validated && in_array($realSeparator, $tableSeparators) && !CRM_Contribute_Form_Task_PDFLetterCommon::isValidHTMLWithTableSeparator($messageToken, $html_message)) {
293 $realSeparator = ', ';
294 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.'));
295 }
296 $validated = TRUE;
297 $html = str_replace($separator, $realSeparator, CRM_Contribute_Form_Task_PDFLetterCommon::resolveTokens($html_message, $contact, $contribution, $messageToken, $grouped, $separator, $groupedContributions));
298 }
299
300 return $html;
301 }
302
6a488035 303}