| 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 | * @param CRM_Core_Form $form |
| 43 | */ |
| 44 | public static function preProcess(&$form) { |
| 45 | CRM_Contact_Form_Task_EmailCommon::preProcessFromAddress($form); |
| 46 | $messageText = []; |
| 47 | $messageSubject = []; |
| 48 | $dao = new CRM_Core_BAO_MessageTemplate(); |
| 49 | $dao->is_active = 1; |
| 50 | $dao->find(); |
| 51 | while ($dao->fetch()) { |
| 52 | $messageText[$dao->id] = $dao->msg_text; |
| 53 | $messageSubject[$dao->id] = $dao->msg_subject; |
| 54 | } |
| 55 | |
| 56 | $form->assign('message', $messageText); |
| 57 | $form->assign('messageSubject', $messageSubject); |
| 58 | parent::preProcess($form); |
| 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 ' ' |
| 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 | if ($type == 'pdf') { |
| 182 | $fileName = "CiviLetter.$type"; |
| 183 | CRM_Utils_PDF_Utils::html2pdf($html, $fileName, FALSE, $formValues); |
| 184 | } |
| 185 | elseif (!empty($formValues['document_file_path'])) { |
| 186 | $fileName = pathinfo($formValues['document_file_path'], PATHINFO_FILENAME) . '.' . $type; |
| 187 | CRM_Utils_PDF_Document::printDocuments($html, $fileName, $type, $zip); |
| 188 | } |
| 189 | else { |
| 190 | $fileName = "CiviLetter.$type"; |
| 191 | CRM_Utils_PDF_Document::html2doc($html, $fileName, $formValues); |
| 192 | } |
| 193 | |
| 194 | if ($tee) { |
| 195 | $tee->stop(); |
| 196 | $content = file_get_contents($tee->getFileName(), NULL, NULL, NULL, 5); |
| 197 | if (empty($content)) { |
| 198 | throw new \CRM_Core_Exception("Failed to capture document content (type=$type)!"); |
| 199 | } |
| 200 | foreach ($activityIds as $activityId) { |
| 201 | civicrm_api3('Attachment', 'create', [ |
| 202 | 'entity_table' => 'civicrm_activity', |
| 203 | 'entity_id' => $activityId, |
| 204 | 'name' => $fileName, |
| 205 | 'mime_type' => $mimeType, |
| 206 | 'options' => [ |
| 207 | 'move-file' => $tee->getFileName(), |
| 208 | ], |
| 209 | ]); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | $form->postProcessHook(); |
| 214 | |
| 215 | CRM_Utils_System::civiExit(); |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * @param CRM_Core_Form $form |
| 220 | * @param string $html_message |
| 221 | * @param array $contactIds |
| 222 | * @param string $subject |
| 223 | * @param int $campaign_id |
| 224 | * @param array $perContactHtml |
| 225 | * |
| 226 | * @return array |
| 227 | * List of activity IDs. |
| 228 | * There may be 1 or more, depending on the system-settings |
| 229 | * and use-case. |
| 230 | * |
| 231 | * @throws CRM_Core_Exception |
| 232 | */ |
| 233 | public static function createActivities($form, $html_message, $contactIds, $subject, $campaign_id, $perContactHtml = []) { |
| 234 | |
| 235 | $activityParams = [ |
| 236 | 'subject' => $subject, |
| 237 | 'campaign_id' => $campaign_id, |
| 238 | 'source_contact_id' => CRM_Core_Session::getLoggedInContactID(), |
| 239 | 'activity_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Print PDF Letter'), |
| 240 | 'activity_date_time' => date('YmdHis'), |
| 241 | 'details' => $html_message, |
| 242 | ]; |
| 243 | if (!empty($form->_activityId)) { |
| 244 | $activityParams += ['id' => $form->_activityId]; |
| 245 | } |
| 246 | |
| 247 | $activityIds = []; |
| 248 | switch (Civi::settings()->get('recordGeneratedLetters')) { |
| 249 | case 'none': |
| 250 | return []; |
| 251 | |
| 252 | case 'multiple': |
| 253 | // One activity per contact. |
| 254 | foreach ($contactIds as $i => $contactId) { |
| 255 | $fullParams = [ |
| 256 | 'target_contact_id' => $contactId, |
| 257 | ] + $activityParams; |
| 258 | if (!empty($form->_caseId)) { |
| 259 | $fullParams['case_id'] = $form->_caseId; |
| 260 | } |
| 261 | elseif (!empty($form->_caseIds[$i])) { |
| 262 | $fullParams['case_id'] = $form->_caseIds[$i]; |
| 263 | } |
| 264 | |
| 265 | if (isset($perContactHtml[$contactId])) { |
| 266 | $fullParams['details'] = implode('<hr>', $perContactHtml[$contactId]); |
| 267 | } |
| 268 | $activity = civicrm_api3('Activity', 'create', $fullParams); |
| 269 | $activityIds[$contactId] = $activity['id']; |
| 270 | } |
| 271 | |
| 272 | break; |
| 273 | |
| 274 | case 'combined': |
| 275 | case 'combined-attached': |
| 276 | // One activity with all contacts. |
| 277 | $fullParams = [ |
| 278 | 'target_contact_id' => $contactIds, |
| 279 | ] + $activityParams; |
| 280 | if (!empty($form->_caseId)) { |
| 281 | $fullParams['case_id'] = $form->_caseId; |
| 282 | } |
| 283 | elseif (!empty($form->_caseIds)) { |
| 284 | $fullParams['case_id'] = $form->_caseIds; |
| 285 | } |
| 286 | $activity = civicrm_api3('Activity', 'create', $fullParams); |
| 287 | $activityIds[] = $activity['id']; |
| 288 | break; |
| 289 | |
| 290 | default: |
| 291 | throw new CRM_Core_Exception("Unrecognized option in recordGeneratedLetters: " . Civi::settings()->get('recordGeneratedLetters')); |
| 292 | } |
| 293 | |
| 294 | return $activityIds; |
| 295 | } |
| 296 | |
| 297 | /** |
| 298 | * Convert from a vague-type/file-extension to mime-type. |
| 299 | * |
| 300 | * @param string $type |
| 301 | * @return string |
| 302 | * @throws \CRM_Core_Exception |
| 303 | */ |
| 304 | private static function getMimeType($type) { |
| 305 | $mimeTypes = [ |
| 306 | 'pdf' => 'application/pdf', |
| 307 | 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
| 308 | 'odt' => 'application/vnd.oasis.opendocument.text', |
| 309 | 'html' => 'text/html', |
| 310 | ]; |
| 311 | if (isset($mimeTypes[$type])) { |
| 312 | return $mimeTypes[$type]; |
| 313 | } |
| 314 | else { |
| 315 | throw new \CRM_Core_Exception("Cannot determine mime type"); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | /** |
| 320 | * Get the categories required for rendering tokens. |
| 321 | * |
| 322 | * @return array |
| 323 | */ |
| 324 | protected static function getTokenCategories() { |
| 325 | if (!isset(Civi::$statics[__CLASS__]['token_categories'])) { |
| 326 | $tokens = []; |
| 327 | CRM_Utils_Hook::tokens($tokens); |
| 328 | Civi::$statics[__CLASS__]['token_categories'] = array_keys($tokens); |
| 329 | } |
| 330 | return Civi::$statics[__CLASS__]['token_categories']; |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * Is the form in live mode (as opposed to being run as a preview). |
| 335 | * |
| 336 | * Returns true if the user has clicked the Download Document button on a |
| 337 | * Print/Merge Document (PDF Letter) search task form, or false if the Preview |
| 338 | * button was clicked. |
| 339 | * |
| 340 | * @param CRM_Core_Form $form |
| 341 | * |
| 342 | * @return bool |
| 343 | * TRUE if the Download Document button was clicked (also defaults to TRUE |
| 344 | * if the form controller does not exist), else FALSE |
| 345 | */ |
| 346 | protected static function isLiveMode($form) { |
| 347 | // CRM-21255 - Hrm, CiviCase 4+5 seem to report buttons differently... |
| 348 | $buttonName = $form->controller->getButtonName(); |
| 349 | $c = $form->controller->container(); |
| 350 | $isLiveMode = ($buttonName == '_qf_PDF_upload') || isset($c['values']['PDF']['buttons']['_qf_PDF_upload']); |
| 351 | return $isLiveMode; |
| 352 | } |
| 353 | |
| 354 | } |