[REF] dev/core#2790 move preProcess static to the trait
[civicrm-core.git] / CRM / Contact / Form / Task / PDFLetterCommon.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class provides the common functionality for creating PDF letter for one or a group of contact ids.
20 */
21 class CRM_Contact_Form_Task_PDFLetterCommon extends CRM_Core_Form_Task_PDFLetterCommon {
22
23 protected static $tokenCategories;
24
25 /**
26 * @return array
27 * Array(string $machineName => string $label).
28 */
29 public static function getLoggingOptions() {
30 return [
31 'none' => ts('Do not record'),
32 'multiple' => ts('Multiple activities (one per contact)'),
33 'combined' => ts('One combined activity'),
34 'combined-attached' => ts('One combined activity plus one file attachment'),
35 // 'multiple-attached' <== not worth the work
36 ];
37 }
38
39 /**
40 * Build all the data structures needed to build the form.
41 *
42 * @deprecated
43 *
44 * @param CRM_Core_Form $form
45 */
46 public static function preProcess(&$form) {
47 CRM_Core_Error::deprecatedFunctionWarning('no alternative');
48 $defaults = [];
49 $form->_fromEmails = CRM_Core_BAO_Email::getFromEmail();
50 if (is_numeric(key($form->_fromEmails))) {
51 $emailID = (int) key($form->_fromEmails);
52 $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID);
53 }
54 if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) {
55 $defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE));
56 }
57 $form->setDefaults($defaults);
58 $form->setTitle('Print/Merge Document');
59 }
60
61 /**
62 * @param CRM_Core_Form $form
63 * @param int $cid
64 */
65 public static function preProcessSingle(&$form, $cid) {
66 $form->_contactIds = explode(',', $cid);
67 // put contact display name in title for single contact mode
68 if (count($form->_contactIds) === 1) {
69 CRM_Utils_System::setTitle(ts('Print/Merge Document for %1', [1 => CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $cid, 'display_name')]));
70 }
71 }
72
73 /**
74 * Part of the post process which prepare and extract information from the template.
75 *
76 *
77 * @param array $formValues
78 *
79 * @return array
80 * [$categories, $html_message, $messageToken, $returnProperties]
81 */
82 public static function processMessageTemplate($formValues) {
83 $html_message = self::processTemplate($formValues);
84
85 $categories = self::getTokenCategories();
86
87 //time being hack to strip '&nbsp;'
88 //from particular letter line, CRM-6798
89 self::formatMessage($html_message);
90
91 $messageToken = CRM_Utils_Token::getTokens($html_message);
92
93 $returnProperties = [];
94 if (isset($messageToken['contact'])) {
95 foreach ($messageToken['contact'] as $key => $value) {
96 $returnProperties[$value] = 1;
97 }
98 }
99
100 return [$formValues, $categories, $html_message, $messageToken, $returnProperties];
101 }
102
103 /**
104 * Process the form after the input has been submitted and validated.
105 *
106 * @param CRM_Core_Form $form
107 * @throws \CRM_Core_Exception
108 * @throws \CiviCRM_API3_Exception
109 */
110 public static function postProcess(&$form) {
111 $formValues = $form->controller->exportValues($form->getName());
112 list($formValues, $categories, $html_message, $messageToken, $returnProperties) = self::processMessageTemplate($formValues);
113 $skipOnHold = $form->skipOnHold ?? FALSE;
114 $skipDeceased = $form->skipDeceased ?? TRUE;
115 $html = $activityIds = [];
116
117 // CRM-16725 Skip creation of activities if user is previewing their PDF letter(s)
118 if (self::isLiveMode($form)) {
119 $activityIds = self::createActivities($form, $html_message, $form->_contactIds, $formValues['subject'], CRM_Utils_Array::value('campaign_id', $formValues));
120 }
121
122 if (!empty($formValues['document_file_path'])) {
123 list($html_message, $zip) = CRM_Utils_PDF_Document::unzipDoc($formValues['document_file_path'], $formValues['document_type']);
124 }
125
126 foreach ($form->_contactIds as $item => $contactId) {
127 $caseId = NULL;
128 $params = ['contact_id' => $contactId];
129
130 $caseId = $form->getVar('_caseId');
131 if (empty($caseId) && !empty($form->_caseIds[$item])) {
132 $caseId = $form->_caseIds[$item];
133 }
134 if ($caseId) {
135 $params['case_id'] = $caseId;
136 }
137
138 list($contact) = CRM_Utils_Token::getTokenDetails($params,
139 $returnProperties,
140 $skipOnHold,
141 $skipDeceased,
142 NULL,
143 $messageToken,
144 'CRM_Contact_Form_Task_PDFLetterCommon'
145 );
146
147 if (civicrm_error($contact)) {
148 $notSent[] = $contactId;
149 continue;
150 }
151
152 $tokenHtml = CRM_Utils_Token::replaceContactTokens($html_message, $contact[$contactId], TRUE, $messageToken);
153
154 if ($caseId) {
155 $tokenHtml = CRM_Utils_Token::replaceCaseTokens($caseId, $tokenHtml, $messageToken);
156 }
157 $tokenHtml = CRM_Utils_Token::replaceHookTokens($tokenHtml, $contact[$contactId], $categories, TRUE);
158
159 if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
160 $smarty = CRM_Core_Smarty::singleton();
161 // also add the contact tokens to the template
162 $smarty->assign_by_ref('contact', $contact);
163 $tokenHtml = $smarty->fetch("string:$tokenHtml");
164 }
165
166 $html[] = $tokenHtml;
167 }
168
169 $tee = NULL;
170 if (self::isLiveMode($form) && Civi::settings()->get('recordGeneratedLetters') === 'combined-attached') {
171 if (count($activityIds) !== 1) {
172 throw new CRM_Core_Exception("When recordGeneratedLetters=combined-attached, there should only be one activity.");
173 }
174 $tee = CRM_Utils_ConsoleTee::create()->start();
175 }
176
177 $type = $formValues['document_type'];
178 $mimeType = self::getMimeType($type);
179 // ^^ Useful side-effect: consistently throws error for unrecognized types.
180
181 $fileName = self::getFileName($form);
182 $fileName = "$fileName.$type";
183
184 if ($type == 'pdf') {
185 CRM_Utils_PDF_Utils::html2pdf($html, $fileName, FALSE, $formValues);
186 }
187 elseif (!empty($formValues['document_file_path'])) {
188 $fileName = pathinfo($formValues['document_file_path'], PATHINFO_FILENAME) . '.' . $type;
189 CRM_Utils_PDF_Document::printDocuments($html, $fileName, $type, $zip);
190 }
191 else {
192 CRM_Utils_PDF_Document::html2doc($html, $fileName, $formValues);
193 }
194
195 if ($tee) {
196 $tee->stop();
197 $content = file_get_contents($tee->getFileName(), NULL, NULL, NULL, 5);
198 if (empty($content)) {
199 throw new \CRM_Core_Exception("Failed to capture document content (type=$type)!");
200 }
201 foreach ($activityIds as $activityId) {
202 civicrm_api3('Attachment', 'create', [
203 'entity_table' => 'civicrm_activity',
204 'entity_id' => $activityId,
205 'name' => $fileName,
206 'mime_type' => $mimeType,
207 'options' => [
208 'move-file' => $tee->getFileName(),
209 ],
210 ]);
211 }
212 }
213
214 $form->postProcessHook();
215
216 CRM_Utils_System::civiExit();
217 }
218
219 /**
220 * Returns the filename for the pdf by striping off unwanted characters and limits the length to 200 characters.
221 *
222 * @param CRM_Core_Form $form
223 *
224 * @return string
225 * The name of the file.
226 */
227 private static function getFileName(CRM_Core_Form $form) {
228 if (!empty($form->getSubmittedValue('pdf_file_name'))) {
229 $fileName = CRM_Utils_File::makeFilenameWithUnicode($form->getSubmittedValue('pdf_file_name'), '_', 200);
230 }
231 elseif (!empty($form->getSubmittedValue('subject'))) {
232 $fileName = CRM_Utils_File::makeFilenameWithUnicode($form->getSubmittedValue('subject'), '_', 200);
233 }
234 else {
235 $fileName = 'CiviLetter';
236 }
237 $fileName = self::isLiveMode($form) ? $fileName : $fileName . '_preview';
238
239 return $fileName;
240 }
241
242 /**
243 * @param CRM_Core_Form $form
244 * @param string $html_message
245 * @param array $contactIds
246 * @param string $subject
247 * @param int $campaign_id
248 * @param array $perContactHtml
249 *
250 * @return array
251 * List of activity IDs.
252 * There may be 1 or more, depending on the system-settings
253 * and use-case.
254 *
255 * @throws CRM_Core_Exception
256 */
257 public static function createActivities($form, $html_message, $contactIds, $subject, $campaign_id, $perContactHtml = []) {
258
259 $activityParams = [
260 'subject' => $subject,
261 'campaign_id' => $campaign_id,
262 'source_contact_id' => CRM_Core_Session::getLoggedInContactID(),
263 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Print PDF Letter'),
264 'activity_date_time' => date('YmdHis'),
265 'details' => $html_message,
266 ];
267 if (!empty($form->_activityId)) {
268 $activityParams += ['id' => $form->_activityId];
269 }
270
271 $activityIds = [];
272 switch (Civi::settings()->get('recordGeneratedLetters')) {
273 case 'none':
274 return [];
275
276 case 'multiple':
277 // One activity per contact.
278 foreach ($contactIds as $i => $contactId) {
279 $fullParams = [
280 'target_contact_id' => $contactId,
281 ] + $activityParams;
282 if (!empty($form->_caseId)) {
283 $fullParams['case_id'] = $form->_caseId;
284 }
285 elseif (!empty($form->_caseIds[$i])) {
286 $fullParams['case_id'] = $form->_caseIds[$i];
287 }
288
289 if (isset($perContactHtml[$contactId])) {
290 $fullParams['details'] = implode('<hr>', $perContactHtml[$contactId]);
291 }
292 $activity = civicrm_api3('Activity', 'create', $fullParams);
293 $activityIds[$contactId] = $activity['id'];
294 }
295
296 break;
297
298 case 'combined':
299 case 'combined-attached':
300 // One activity with all contacts.
301 $fullParams = [
302 'target_contact_id' => $contactIds,
303 ] + $activityParams;
304 if (!empty($form->_caseId)) {
305 $fullParams['case_id'] = $form->_caseId;
306 }
307 elseif (!empty($form->_caseIds)) {
308 $fullParams['case_id'] = $form->_caseIds;
309 }
310 $activity = civicrm_api3('Activity', 'create', $fullParams);
311 $activityIds[] = $activity['id'];
312 break;
313
314 default:
315 throw new CRM_Core_Exception("Unrecognized option in recordGeneratedLetters: " . Civi::settings()->get('recordGeneratedLetters'));
316 }
317
318 return $activityIds;
319 }
320
321 /**
322 * Convert from a vague-type/file-extension to mime-type.
323 *
324 * @param string $type
325 * @return string
326 * @throws \CRM_Core_Exception
327 */
328 private static function getMimeType($type) {
329 $mimeTypes = [
330 'pdf' => 'application/pdf',
331 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
332 'odt' => 'application/vnd.oasis.opendocument.text',
333 'html' => 'text/html',
334 ];
335 if (isset($mimeTypes[$type])) {
336 return $mimeTypes[$type];
337 }
338 else {
339 throw new \CRM_Core_Exception("Cannot determine mime type");
340 }
341 }
342
343 /**
344 * Get the categories required for rendering tokens.
345 *
346 * @return array
347 */
348 protected static function getTokenCategories() {
349 if (!isset(Civi::$statics[__CLASS__]['token_categories'])) {
350 $tokens = [];
351 CRM_Utils_Hook::tokens($tokens);
352 Civi::$statics[__CLASS__]['token_categories'] = array_keys($tokens);
353 }
354 return Civi::$statics[__CLASS__]['token_categories'];
355 }
356
357 /**
358 * Is the form in live mode (as opposed to being run as a preview).
359 *
360 * Returns true if the user has clicked the Download Document button on a
361 * Print/Merge Document (PDF Letter) search task form, or false if the Preview
362 * button was clicked.
363 *
364 * @param CRM_Core_Form $form
365 *
366 * @return bool
367 * TRUE if the Download Document button was clicked (also defaults to TRUE
368 * if the form controller does not exist), else FALSE
369 */
370 protected static function isLiveMode($form) {
371 // CRM-21255 - Hrm, CiviCase 4+5 seem to report buttons differently...
372 $buttonName = $form->controller->getButtonName();
373 $c = $form->controller->container();
374 $isLiveMode = ($buttonName == '_qf_PDF_upload') || isset($c['values']['PDF']['buttons']['_qf_PDF_upload']);
375 return $isLiveMode;
376 }
377
378 }