Merge pull request #21523 from eileenmcnaughton/dep
[civicrm-core.git] / CRM / Contact / Form / Task / PDFTrait.php
CommitLineData
fc34a273
EM
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 tasks that send emails.
20 */
21trait CRM_Contact_Form_Task_PDFTrait {
22
23 /**
24 * Set defaults for the pdf.
25 *
26 * @return array
27 */
28 public function setDefaultValues(): array {
29 return $this->getPDFDefaultValues();
30 }
31
32 /**
33 * Set default values.
34 */
35 protected function getPDFDefaultValues(): array {
36 $defaultFormat = CRM_Core_BAO_PdfFormat::getDefaultValues();
37 $defaultFormat['format_id'] = $defaultFormat['id'];
38 return $defaultFormat;
39 }
40
053c1a4b
EM
41 /**
42 * Build the form object.
43 *
44 * @throws \CRM_Core_Exception
45 */
46 public function buildQuickForm(): void {
47 $this->addPDFElementsToForm();
48 }
49
50 /**
51 * Build the form object.
52 *
53 * @throws \CRM_Core_Exception
54 */
55 public function addPDFElementsToForm(): void {
56 $form = $this;
57 // This form outputs a file so should never be submitted via ajax
58 $form->preventAjaxSubmit();
59
60 //Added for CRM-12682: Add activity subject and campaign fields
61 CRM_Campaign_BAO_Campaign::addCampaign($form);
62 $form->add(
63 'text',
64 'subject',
65 ts('Activity Subject'),
66 ['size' => 45, 'maxlength' => 255],
67 FALSE
68 );
69
70 // Added for core#2121,
71 // To support sending a custom pdf filename before downloading.
72 $form->addElement('hidden', 'pdf_file_name');
73
74 $form->addSelect('format_id', [
75 'label' => ts('Select Format'),
76 'placeholder' => ts('Default'),
77 'entity' => 'message_template',
78 'field' => 'pdf_format_id',
79 'option_url' => 'civicrm/admin/pdfFormats',
80 ]);
81 $form->add(
82 'select',
83 'paper_size',
84 ts('Paper Size'),
85 [0 => ts('- default -')] + CRM_Core_BAO_PaperSize::getList(TRUE),
86 FALSE,
87 ['onChange' => "selectPaper( this.value ); showUpdateFormatChkBox();"]
88 );
89 $form->add(
90 'select',
91 'orientation',
92 ts('Orientation'),
93 CRM_Core_BAO_PdfFormat::getPageOrientations(),
94 FALSE,
95 ['onChange' => "updatePaperDimensions(); showUpdateFormatChkBox();"]
96 );
97 $form->add(
98 'select',
99 'metric',
100 ts('Unit of Measure'),
101 CRM_Core_BAO_PdfFormat::getUnits(),
102 FALSE,
103 ['onChange' => "selectMetric( this.value );"]
104 );
105 $form->add(
106 'text',
107 'margin_left',
108 ts('Left Margin'),
109 ['size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"],
110 TRUE
111 );
112 $form->add(
113 'text',
114 'margin_right',
115 ts('Right Margin'),
116 ['size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"],
117 TRUE
118 );
119 $form->add(
120 'text',
121 'margin_top',
122 ts('Top Margin'),
123 ['size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"],
124 TRUE
125 );
126 $form->add(
127 'text',
128 'margin_bottom',
129 ts('Bottom Margin'),
130 ['size' => 8, 'maxlength' => 8, 'onkeyup' => "showUpdateFormatChkBox();"],
131 TRUE
132 );
133
134 $config = CRM_Core_Config::singleton();
135 /** CRM-15883 Suppressing Stationery path field until we switch from DOMPDF to a library that supports it.
136 * if ($config->wkhtmltopdfPath == FALSE) {
137 * $form->add(
138 * 'text',
139 * 'stationery',
140 * ts('Stationery (relative path to PDF you wish to use as the background)'),
141 * array('size' => 25, 'maxlength' => 900, 'onkeyup' => "showUpdateFormatChkBox();"),
142 * FALSE
143 * );
144 * }
145 */
146 $form->add('checkbox', 'bind_format', ts('Always use this Page Format with the selected Template'));
147 $form->add('checkbox', 'update_format', ts('Update Page Format (this will affect all templates that use this format)'));
148
149 $form->assign('useThisPageFormat', ts('Always use this Page Format with the new template?'));
150 $form->assign('useSelectedPageFormat', ts('Should the new template always use the selected Page Format?'));
151 $form->assign('totalSelectedContacts', !is_null($form->_contactIds) ? count($form->_contactIds) : 0);
152
153 $form->add('select', 'document_type', ts('Document Type'), CRM_Core_SelectValues::documentFormat());
154
155 $documentTypes = implode(',', CRM_Core_SelectValues::documentApplicationType());
156 $form->addElement('file', "document_file", 'Upload Document', 'size=30 maxlength=255 accept="' . $documentTypes . '"');
157 $form->addUploadElement("document_file");
158
159 CRM_Mailing_BAO_Mailing::commonCompose($form);
160
161 $buttons = [];
162 if ($form->get('action') != CRM_Core_Action::VIEW) {
163 $buttons[] = [
164 'type' => 'upload',
165 'name' => ts('Download Document'),
166 'isDefault' => TRUE,
167 'icon' => 'fa-download',
168 ];
169 $buttons[] = [
170 'type' => 'submit',
171 'name' => ts('Preview'),
172 'subName' => 'preview',
173 'icon' => 'fa-search',
174 'isDefault' => FALSE,
175 ];
176 }
177 $buttons[] = [
178 'type' => 'cancel',
179 'name' => $form->get('action') == CRM_Core_Action::VIEW ? ts('Done') : ts('Cancel'),
180 ];
181 $form->addButtons($buttons);
182
183 $form->addFormRule(['CRM_Core_Form_Task_PDFLetterCommon', 'formRule'], $form);
184 }
185
c97bfeff
EM
186 /**
187 * Prepare form.
188 */
189 public function preProcessPDF(): void {
190 $form = $this;
191 $defaults = [];
192 $form->_fromEmails = CRM_Core_BAO_Email::getFromEmail();
193 if (is_numeric(key($form->_fromEmails))) {
194 $emailID = (int) key($form->_fromEmails);
195 $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID);
196 }
197 if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) {
198 $defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE));
199 }
200 $form->setDefaults($defaults);
201 $form->setTitle('Print/Merge Document');
202 }
203
7c0d6f7a
EM
204 /**
205 * Returns the filename for the pdf by striping off unwanted characters and limits the length to 200 characters.
206 *
207 * @return string
208 * The name of the file.
209 */
210 public function getFileName(): string {
211 if (!empty($this->getSubmittedValue('pdf_file_name'))) {
212 $fileName = CRM_Utils_File::makeFilenameWithUnicode($this->getSubmittedValue('pdf_file_name'), '_', 200);
213 }
214 elseif (!empty($this->getSubmittedValue('subject'))) {
215 $fileName = CRM_Utils_File::makeFilenameWithUnicode($this->getSubmittedValue('subject'), '_', 200);
216 }
217 else {
218 $fileName = 'CiviLetter';
219 }
220 return $this->isLiveMode() ? $fileName : $fileName . '_preview';
221 }
222
223 /**
224 * Is the form in live mode (as opposed to being run as a preview).
225 *
226 * Returns true if the user has clicked the Download Document button on a
227 * Print/Merge Document (PDF Letter) search task form, or false if the Preview
228 * button was clicked.
229 *
230 * @return bool
231 * TRUE if the Download Document button was clicked (also defaults to TRUE
232 * if the form controller does not exist), else FALSE
233 */
234 protected function isLiveMode(): bool {
235 // CRM-21255 - Hrm, CiviCase 4+5 seem to report buttons differently...
236 $buttonName = $this->controller->getButtonName();
237 $c = $this->controller->container();
238 return ($buttonName === '_qf_PDF_upload') || isset($c['values']['PDF']['buttons']['_qf_PDF_upload']);
239 }
240
fb4f4e89
EM
241 /**
242 * Process the form after the input has been submitted and validated.
243 *
244 * @throws \CRM_Core_Exception
245 * @throws \CiviCRM_API3_Exception
246 * @throws \API_Exception
247 */
248 public function postProcess(): void {
249 $form = $this;
250 $formValues = $form->controller->exportValues($form->getName());
0ceb63d9 251 [$formValues, $html_message, $messageToken, $returnProperties] = $this->processMessageTemplate($formValues);
fb4f4e89
EM
252 $html = $activityIds = [];
253
254 // CRM-16725 Skip creation of activities if user is previewing their PDF letter(s)
255 if ($this->isLiveMode()) {
256 $activityIds = $this->createActivities($form, $html_message, $form->_contactIds, $formValues['subject'], CRM_Utils_Array::value('campaign_id', $formValues));
257 }
258
259 if (!empty($formValues['document_file_path'])) {
260 [$html_message, $zip] = CRM_Utils_PDF_Document::unzipDoc($formValues['document_file_path'], $formValues['document_type']);
261 }
262
263 foreach ($form->_contactIds as $item => $contactId) {
264 $caseId = $form->getVar('_caseId');
265 if (empty($caseId) && !empty($form->_caseIds[$item])) {
266 $caseId = $form->_caseIds[$item];
267 }
268
269 $tokenHtml = CRM_Core_BAO_MessageTemplate::renderTemplate([
270 'contactId' => $contactId,
271 'messageTemplate' => ['msg_html' => $html_message],
272 'tokenContext' => $caseId ? ['caseId' => $caseId] : [],
273 'disableSmarty' => (!defined('CIVICRM_MAIL_SMARTY') || !CIVICRM_MAIL_SMARTY),
274 ])['html'];
275
276 $html[] = $tokenHtml;
277 }
278
279 $tee = NULL;
280 if ($this->isLiveMode() && Civi::settings()->get('recordGeneratedLetters') === 'combined-attached') {
281 if (count($activityIds) !== 1) {
282 throw new CRM_Core_Exception("When recordGeneratedLetters=combined-attached, there should only be one activity.");
283 }
284 $tee = CRM_Utils_ConsoleTee::create()->start();
285 }
286
287 $type = $formValues['document_type'];
288 $mimeType = $this->getMimeType($type);
289 // ^^ Useful side-effect: consistently throws error for unrecognized types.
290
291 $fileName = method_exists($form, 'getFileName') ? ($form->getFileName() . '.' . $type) : 'CiviLetter.' . $type;
292
293 if ($type === 'pdf') {
294 CRM_Utils_PDF_Utils::html2pdf($html, $fileName, FALSE, $formValues);
295 }
296 elseif (!empty($formValues['document_file_path'])) {
297 $fileName = pathinfo($formValues['document_file_path'], PATHINFO_FILENAME) . '.' . $type;
298 CRM_Utils_PDF_Document::printDocuments($html, $fileName, $type, $zip);
299 }
300 else {
301 CRM_Utils_PDF_Document::html2doc($html, $fileName, $formValues);
302 }
303
304 if ($tee) {
305 $tee->stop();
306 $content = file_get_contents($tee->getFileName(), NULL, NULL, NULL, 5);
307 if (empty($content)) {
308 throw new \CRM_Core_Exception("Failed to capture document content (type=$type)!");
309 }
310 foreach ($activityIds as $activityId) {
311 civicrm_api3('Attachment', 'create', [
312 'entity_table' => 'civicrm_activity',
313 'entity_id' => $activityId,
314 'name' => $fileName,
315 'mime_type' => $mimeType,
316 'options' => [
317 'move-file' => $tee->getFileName(),
318 ],
319 ]);
320 }
321 }
322
323 $form->postProcessHook();
324
325 CRM_Utils_System::civiExit();
326 }
327
328 /**
329 * Convert from a vague-type/file-extension to mime-type.
330 *
331 * @param string $type
332 * @return string
333 * @throws \CRM_Core_Exception
334 */
335 protected function getMimeType($type) {
336 $mimeTypes = [
337 'pdf' => 'application/pdf',
338 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
339 'odt' => 'application/vnd.oasis.opendocument.text',
340 'html' => 'text/html',
341 ];
342 if (isset($mimeTypes[$type])) {
343 return $mimeTypes[$type];
344 }
345 else {
346 throw new \CRM_Core_Exception("Cannot determine mime type");
347 }
348 }
349
350 /**
351 * @param CRM_Core_Form $form
352 * @param string $html_message
353 * @param array $contactIds
354 * @param string $subject
355 * @param int $campaign_id
356 * @param array $perContactHtml
357 *
358 * @return array
359 * List of activity IDs.
360 * There may be 1 or more, depending on the system-settings
361 * and use-case.
362 *
363 * @throws CRM_Core_Exception
364 */
365 public function createActivities($form, $html_message, $contactIds, $subject, $campaign_id, $perContactHtml = []) {
366
367 $activityParams = [
368 'subject' => $subject,
369 'campaign_id' => $campaign_id,
370 'source_contact_id' => CRM_Core_Session::getLoggedInContactID(),
371 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Print PDF Letter'),
372 'activity_date_time' => date('YmdHis'),
373 'details' => $html_message,
374 ];
375 if (!empty($form->_activityId)) {
376 $activityParams += ['id' => $form->_activityId];
377 }
378
379 $activityIds = [];
380 switch (Civi::settings()->get('recordGeneratedLetters')) {
381 case 'none':
382 return [];
383
384 case 'multiple':
385 // One activity per contact.
386 foreach ($contactIds as $i => $contactId) {
387 $fullParams = ['target_contact_id' => $contactId] + $activityParams;
388 if (!empty($form->_caseId)) {
389 $fullParams['case_id'] = $form->_caseId;
390 }
391 elseif (!empty($form->_caseIds[$i])) {
392 $fullParams['case_id'] = $form->_caseIds[$i];
393 }
394
395 if (isset($perContactHtml[$contactId])) {
396 $fullParams['details'] = implode('<hr>', $perContactHtml[$contactId]);
397 }
398 $activity = civicrm_api3('Activity', 'create', $fullParams);
399 $activityIds[$contactId] = $activity['id'];
400 }
401
402 break;
403
404 case 'combined':
405 case 'combined-attached':
406 // One activity with all contacts.
407 $fullParams = ['target_contact_id' => $contactIds] + $activityParams;
408 if (!empty($form->_caseId)) {
409 $fullParams['case_id'] = $form->_caseId;
410 }
411 elseif (!empty($form->_caseIds)) {
412 $fullParams['case_id'] = $form->_caseIds;
413 }
414 $activity = civicrm_api3('Activity', 'create', $fullParams);
415 $activityIds[] = $activity['id'];
416 break;
417
418 default:
419 throw new CRM_Core_Exception("Unrecognized option in recordGeneratedLetters: " . Civi::settings()->get('recordGeneratedLetters'));
420 }
421
422 return $activityIds;
423 }
424
0ceb63d9
EM
425 /**
426 * Handle the template processing part of the form
427 *
428 * @param array $formValues
429 *
430 * @return string $html_message
431 *
432 * @throws \CRM_Core_Exception
433 * @throws \CiviCRM_API3_Exception
434 * @throws \Civi\API\Exception\UnauthorizedException
435 */
436 public function processTemplate(&$formValues) {
437 $html_message = $formValues['html_message'] ?? NULL;
438
439 // process message template
440 if (!empty($formValues['saveTemplate']) || !empty($formValues['updateTemplate'])) {
441 $messageTemplate = [
442 'msg_text' => NULL,
443 'msg_html' => $formValues['html_message'],
444 'msg_subject' => NULL,
445 'is_active' => TRUE,
446 ];
447
448 $messageTemplate['pdf_format_id'] = 'null';
449 if (!empty($formValues['bind_format']) && $formValues['format_id']) {
450 $messageTemplate['pdf_format_id'] = $formValues['format_id'];
451 }
452 if (!empty($formValues['saveTemplate'])) {
453 $messageTemplate['msg_title'] = $formValues['saveTemplateName'];
454 CRM_Core_BAO_MessageTemplate::add($messageTemplate);
455 }
456
457 if ($formValues['template'] && !empty($formValues['updateTemplate'])) {
458 $messageTemplate['id'] = $formValues['template'];
459
460 unset($messageTemplate['msg_title']);
461 CRM_Core_BAO_MessageTemplate::add($messageTemplate);
462 }
463 }
464 elseif (CRM_Utils_Array::value('template', $formValues) > 0) {
465 if (!empty($formValues['bind_format']) && $formValues['format_id']) {
466 $query = "UPDATE civicrm_msg_template SET pdf_format_id = {$formValues['format_id']} WHERE id = {$formValues['template']}";
467 }
468 else {
469 $query = "UPDATE civicrm_msg_template SET pdf_format_id = NULL WHERE id = {$formValues['template']}";
470 }
471 CRM_Core_DAO::executeQuery($query);
472
473 $documentInfo = CRM_Core_BAO_File::getEntityFile('civicrm_msg_template', $formValues['template']);
474 foreach ((array) $documentInfo as $info) {
475 [$html_message, $formValues['document_type']] = CRM_Utils_PDF_Document::docReader($info['fullPath'], $info['mime_type']);
476 $formValues['document_file_path'] = $info['fullPath'];
477 }
478 }
479 // extract the content of uploaded document file
480 elseif (!empty($formValues['document_file'])) {
481 [$html_message, $formValues['document_type']] = CRM_Utils_PDF_Document::docReader($formValues['document_file']['name'], $formValues['document_file']['type']);
482 $formValues['document_file_path'] = $formValues['document_file']['name'];
483 }
484
485 if (!empty($formValues['update_format'])) {
486 $bao = new CRM_Core_BAO_PdfFormat();
487 $bao->savePdfFormat($formValues, $formValues['format_id']);
488 }
489
490 return $html_message;
491 }
492
493 /**
494 * Part of the post process which prepare and extract information from the template.
495 *
496 *
497 * @param array $formValues
498 *
499 * @return array
500 * [$categories, $html_message, $messageToken, $returnProperties]
501 */
502 public function processMessageTemplate($formValues) {
503 $html_message = $this->processTemplate($formValues);
504
505 //time being hack to strip '&nbsp;'
506 //from particular letter line, CRM-6798
507 $this->formatMessage($html_message);
508
509 $messageToken = CRM_Utils_Token::getTokens($html_message);
510
511 $returnProperties = [];
512 if (isset($messageToken['contact'])) {
513 foreach ($messageToken['contact'] as $key => $value) {
514 $returnProperties[$value] = 1;
515 }
516 }
517
518 return [$formValues, $html_message, $messageToken, $returnProperties];
519 }
520
521 /**
522 * @param $message
523 */
524 public function formatMessage(&$message) {
525 $newLineOperators = [
526 'p' => [
527 'oper' => '<p>',
528 'pattern' => '/<(\s+)?p(\s+)?>/m',
529 ],
530 'br' => [
531 'oper' => '<br />',
532 'pattern' => '/<(\s+)?br(\s+)?\/>/m',
533 ],
534 ];
535
536 $htmlMsg = preg_split($newLineOperators['p']['pattern'], $message);
537 foreach ($htmlMsg as $k => & $m) {
538 $messages = preg_split($newLineOperators['br']['pattern'], $m);
539 foreach ($messages as $key => & $msg) {
540 $msg = trim($msg);
541 $matches = [];
542 if (preg_match('/^(&nbsp;)+/', $msg, $matches)) {
543 $spaceLen = strlen($matches[0]) / 6;
544 $trimMsg = ltrim($msg, '&nbsp; ');
545 $charLen = strlen($trimMsg);
546 $totalLen = $charLen + $spaceLen;
547 if ($totalLen > 100) {
548 $spacesCount = 10;
549 if ($spaceLen > 50) {
550 $spacesCount = 20;
551 }
552 if ($charLen > 100) {
553 $spacesCount = 1;
554 }
555 $msg = str_repeat('&nbsp;', $spacesCount) . $trimMsg;
556 }
557 }
558 }
559 $m = implode($newLineOperators['br']['oper'], $messages);
560 }
561 $message = implode($newLineOperators['p']['oper'], $htmlMsg);
562 }
563
fc34a273 564}