3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
18 require_once 'Mail/mime.php';
21 * Class CRM_Core_BAO_MessageTemplate.
23 class CRM_Core_BAO_MessageTemplate
extends CRM_Core_DAO_MessageTemplate
{
26 * Fetch object based on array of properties.
28 * @param array $params
29 * (reference ) an assoc array of name/value pairs.
30 * @param array $defaults
31 * (reference ) an assoc array to hold the flattened values.
33 * @return CRM_Core_BAO_MessageTemplate
35 public static function retrieve(&$params, &$defaults) {
36 $messageTemplates = new CRM_Core_DAO_MessageTemplate();
37 $messageTemplates->copyValues($params);
38 if ($messageTemplates->find(TRUE)) {
39 CRM_Core_DAO
::storeValues($messageTemplates, $defaults);
40 return $messageTemplates;
46 * Update the is_active flag in the db.
49 * Id of the database record.
50 * @param bool $is_active
51 * Value we want to set the is_active field.
54 * true if we found and updated the object, else false
56 public static function setIsActive($id, $is_active) {
57 return CRM_Core_DAO
::setFieldValue('CRM_Core_DAO_MessageTemplate', $id, 'is_active', $is_active);
61 * Add the Message Templates.
63 * @param array $params
64 * Reference array contains the values submitted by the form.
69 public static function add(&$params) {
70 // System Workflow Templates have a specific wodkflow_id in them but normal user end message templates don't
71 // If we have an id check to see if we are update, and need to check if original is a system workflow or not.
72 $systemWorkflowPermissionDeniedMessage = 'Editing or creating system workflow messages requires edit system workflow message templates permission or the edit message templates permission';
73 $userWorkflowPermissionDeniedMessage = 'Editing or creating user driven workflow messages requires edit user-driven message templates or the edit message templates permission';
74 if (!empty($params['check_permissions'])) {
75 if (!CRM_Core_Permission
::check('edit message templates')) {
76 if (!empty($params['id'])) {
77 $details = civicrm_api3('MessageTemplate', 'getSingle', ['id' => $params['id']]);
78 if (!empty($details['workflow_id'])) {
79 if (!CRM_Core_Permission
::check('edit system workflow message templates')) {
80 throw new \Civi\API\Exception\
UnauthorizedException(ts('%1', [1 => $systemWorkflowPermissionDeniedMessage]));
83 elseif (!CRM_Core_Permission
::check('edit user-driven message templates')) {
84 throw new \Civi\API\Exception\
UnauthorizedException(ts('%1', [1 => $userWorkflowPermissionDeniedMessage]));
88 if (!empty($params['workflow_id']) && !CRM_Core_Permission
::check('edit system workflow message templates')) {
89 throw new \Civi\API\Exception\
UnauthorizedException(ts('%1', [1 => $systemWorkflowPermissionDeniedMessage]));
91 elseif (!CRM_Core_Permission
::check('edit user-driven message templates')) {
92 throw new \Civi\API\Exception\
UnauthorizedException(ts('%1', [1 => $userWorkflowPermissionDeniedMessage]));
97 $hook = empty($params['id']) ?
'create' : 'edit';
98 CRM_Utils_Hook
::pre($hook, 'MessageTemplate', CRM_Utils_Array
::value('id', $params), $params);
100 if (!empty($params['file_id']) && is_array($params['file_id']) && count($params['file_id'])) {
101 $fileParams = $params['file_id'];
102 unset($params['file_id']);
105 $messageTemplates = new CRM_Core_DAO_MessageTemplate();
106 $messageTemplates->copyValues($params);
107 $messageTemplates->save();
109 if (!empty($fileParams)) {
110 $params['file_id'] = $fileParams;
111 CRM_Core_BAO_File
::filePostProcess(
112 $params['file_id']['location'],
114 'civicrm_msg_template',
115 $messageTemplates->id
,
120 $params['file_id']['type']
124 CRM_Utils_Hook
::post($hook, 'MessageTemplate', $messageTemplates->id
, $messageTemplates);
125 return $messageTemplates;
129 * Delete the Message Templates.
131 * @param int $messageTemplatesID
133 public static function del($messageTemplatesID) {
134 // make sure messageTemplatesID is an integer
135 if (!CRM_Utils_Rule
::positiveInteger($messageTemplatesID)) {
136 CRM_Core_Error
::fatal(ts('Invalid Message template'));
139 // Set mailing msg template col to NULL
140 $query = "UPDATE civicrm_mailing
141 SET msg_template_id = NULL
142 WHERE msg_template_id = %1";
144 $params = [1 => [$messageTemplatesID, 'Integer']];
145 CRM_Core_DAO
::executeQuery($query, $params);
147 $messageTemplates = new CRM_Core_DAO_MessageTemplate();
148 $messageTemplates->id
= $messageTemplatesID;
149 $messageTemplates->delete();
150 CRM_Core_Session
::setStatus(ts('Selected message template has been deleted.'), ts('Deleted'), 'success');
154 * Get the Message Templates.
163 public static function getMessageTemplates($all = TRUE, $isSMS = FALSE) {
166 $messageTemplates = new CRM_Core_DAO_MessageTemplate();
167 $messageTemplates->is_active
= 1;
168 $messageTemplates->is_sms
= $isSMS;
171 $messageTemplates->workflow_id
= 'NULL';
173 $messageTemplates->find();
174 while ($messageTemplates->fetch()) {
175 $msgTpls[$messageTemplates->id
] = $messageTemplates->msg_title
;
182 * @param int $contactId
184 * @param int $messageTemplateID
189 public static function sendReminder($contactId, $email, $messageTemplateID, $from) {
191 $messageTemplates = new CRM_Core_DAO_MessageTemplate();
192 $messageTemplates->id
= $messageTemplateID;
194 $domain = CRM_Core_BAO_Domain
::getDomain();
198 if ($messageTemplates->find(TRUE)) {
199 $body_text = $messageTemplates->msg_text
;
200 $body_html = $messageTemplates->msg_html
;
201 $body_subject = $messageTemplates->msg_subject
;
203 $body_text = CRM_Utils_String
::htmlToText($body_html);
206 $params = [['contact_id', '=', $contactId, 0, 0]];
207 list($contact, $_) = CRM_Contact_BAO_Query
::apiQuery($params);
210 $contact = reset($contact);
212 if (!$contact ||
is_a($contact, 'CRM_Core_Error')) {
218 // get tokens to be replaced
219 $tokens = array_merge(CRM_Utils_Token
::getTokens($body_text),
220 CRM_Utils_Token
::getTokens($body_html),
221 CRM_Utils_Token
::getTokens($body_subject));
223 // get replacement text for these tokens
224 $returnProperties = ["preferred_mail_format" => 1];
225 if (isset($tokens['contact'])) {
226 foreach ($tokens['contact'] as $key => $value) {
227 $returnProperties[$value] = 1;
230 list($details) = CRM_Utils_Token
::getTokenDetails([$contactId],
234 'CRM_Core_BAO_MessageTemplate');
235 $contact = reset($details);
239 CRM_Utils_Hook
::tokens($hookTokens);
240 $categories = array_keys($hookTokens);
242 // do replacements in text and html body
243 $type = ['html', 'text'];
244 foreach ($type as $key => $value) {
245 $bodyType = "body_{$value}";
247 CRM_Utils_Token
::replaceGreetingTokens($
$bodyType, NULL, $contact['contact_id']);
248 $
$bodyType = CRM_Utils_Token
::replaceDomainTokens($
$bodyType, $domain, TRUE, $tokens, TRUE);
249 $
$bodyType = CRM_Utils_Token
::replaceContactTokens($
$bodyType, $contact, FALSE, $tokens, FALSE, TRUE);
250 $
$bodyType = CRM_Utils_Token
::replaceComponentTokens($
$bodyType, $contact, $tokens, TRUE);
251 $
$bodyType = CRM_Utils_Token
::replaceHookTokens($
$bodyType, $contact, $categories, TRUE);
257 $smarty = CRM_Core_Smarty
::singleton();
262 $
$elem = $smarty->fetch("string:{$$elem}");
265 // do replacements in message subject
266 $messageSubject = CRM_Utils_Token
::replaceContactTokens($body_subject, $contact, FALSE, $tokens);
267 $messageSubject = CRM_Utils_Token
::replaceDomainTokens($messageSubject, $domain, TRUE, $tokens);
268 $messageSubject = CRM_Utils_Token
::replaceComponentTokens($messageSubject, $contact, $tokens, TRUE);
269 $messageSubject = CRM_Utils_Token
::replaceHookTokens($messageSubject, $contact, $categories, TRUE);
271 $messageSubject = $smarty->fetch("string:{$messageSubject}");
273 // set up the parameters for CRM_Utils_Mail::send
275 'groupName' => 'Scheduled Reminder Sender',
277 'toName' => $contact['display_name'],
279 'subject' => $messageSubject,
281 if (!$html ||
$contact['preferred_mail_format'] == 'Text' ||
282 $contact['preferred_mail_format'] == 'Both'
284 // render the & entities in text mode, so that the links work
285 $mailParams['text'] = str_replace('&', '&', $text);
287 if ($html && ($contact['preferred_mail_format'] == 'HTML' ||
288 $contact['preferred_mail_format'] == 'Both'
291 $mailParams['html'] = $html;
294 $result = CRM_Utils_Mail
::send($mailParams);
301 * Revert a message template to its default subject+text+HTML state.
303 * @param int $id id of the template
305 public static function revert($id) {
306 $diverted = new CRM_Core_BAO_MessageTemplate();
307 $diverted->id
= (int) $id;
310 if ($diverted->N
!= 1) {
311 CRM_Core_Error
::fatal(ts('Did not find a message template with id of %1.', [1 => $id]));
314 $orig = new CRM_Core_BAO_MessageTemplate();
315 $orig->workflow_id
= $diverted->workflow_id
;
316 $orig->is_reserved
= 1;
320 CRM_Core_Error
::fatal(ts('Message template with id of %1 does not have a default to revert to.', [1 => $id]));
323 $diverted->msg_subject
= $orig->msg_subject
;
324 $diverted->msg_text
= $orig->msg_text
;
325 $diverted->msg_html
= $orig->msg_html
;
326 $diverted->pdf_format_id
= is_null($orig->pdf_format_id
) ?
'null' : $orig->pdf_format_id
;
331 * Send an email from the specified template based on an array of params.
333 * @param array $params
334 * A string-keyed array of function params, see function body for details.
337 * Array of four parameters: a boolean whether the email was sent, and the subject, text and HTML templates
339 public static function sendTemplate($params) {
341 // option group name of the template
343 // option value name of the template
345 // ID of the template
346 'messageTemplateID' => NULL,
347 // contact id if contact tokens are to be replaced
349 // additional template params (other than the ones already set in the template singleton)
353 // the recipient’s name
355 // the recipient’s email - mail is sent only if set
361 // the Reply-To: header
364 'attachments' => NULL,
365 // whether this is a test email (and hence should include the test banner)
367 // filename of optional PDF version to add as attachment (do not include path)
368 'PDFFilename' => NULL,
370 $params = array_merge($defaults, $params);
372 // Core#644 - handle Email ID passed as "From".
373 if (isset($params['from'])) {
374 $params['from'] = CRM_Utils_Mail
::formatFromAddress($params['from']);
377 CRM_Utils_Hook
::alterMailParams($params, 'messageTemplate');
379 if ((!$params['groupName'] ||
380 !$params['valueName']
382 !$params['messageTemplateID']
384 CRM_Core_Error
::fatal(ts("Message template's option group and/or option value or ID missing."));
387 if ($params['messageTemplateID']) {
388 // fetch the three elements from the db based on id
389 $query = 'SELECT msg_subject subject, msg_text text, msg_html html, pdf_format_id format
390 FROM civicrm_msg_template mt
391 WHERE mt.id = %1 AND mt.is_default = 1';
392 $sqlParams = [1 => [$params['messageTemplateID'], 'String']];
395 // fetch the three elements from the db based on option_group and option_value names
396 $query = 'SELECT msg_subject subject, msg_text text, msg_html html, pdf_format_id format
397 FROM civicrm_msg_template mt
398 JOIN civicrm_option_value ov ON workflow_id = ov.id
399 JOIN civicrm_option_group og ON ov.option_group_id = og.id
400 WHERE og.name = %1 AND ov.name = %2 AND mt.is_default = 1';
401 $sqlParams = [1 => [$params['groupName'], 'String'], 2 => [$params['valueName'], 'String']];
403 $dao = CRM_Core_DAO
::executeQuery($query, $sqlParams);
407 if ($params['messageTemplateID']) {
408 CRM_Core_Error
::fatal(ts('No such message template: id=%1.', [1 => $params['messageTemplateID']]));
411 CRM_Core_Error
::fatal(ts('No such message template: option group %1, option value %2.', [
412 1 => $params['groupName'],
413 2 => $params['valueName'],
419 'subject' => $dao->subject
,
420 'text' => $dao->text
,
421 'html' => $dao->html
,
422 'format' => $dao->format
,
423 'groupName' => $params['groupName'],
424 'valueName' => $params['valueName'],
425 'messageTemplateID' => $params['messageTemplateID'],
428 CRM_Utils_Hook
::alterMailContent($mailContent);
430 // add the test banner (if requested)
431 if ($params['isTest']) {
432 $query = "SELECT msg_subject subject, msg_text text, msg_html html
433 FROM civicrm_msg_template mt
434 JOIN civicrm_option_value ov ON workflow_id = ov.id
435 JOIN civicrm_option_group og ON ov.option_group_id = og.id
436 WHERE og.name = 'msg_tpl_workflow_meta' AND ov.name = 'test_preview' AND mt.is_default = 1";
437 $testDao = CRM_Core_DAO
::executeQuery($query);
440 $mailContent['subject'] = $testDao->subject
. $mailContent['subject'];
441 $mailContent['text'] = $testDao->text
. $mailContent['text'];
442 $mailContent['html'] = preg_replace('/<body(.*)$/im', "<body\\1\n{$testDao->html}", $mailContent['html']);
445 // replace tokens in the three elements (in subject as if it was the text body)
446 $domain = CRM_Core_BAO_Domain
::getDomain();
448 $mailing = new CRM_Mailing_BAO_Mailing();
449 $mailing->subject
= $mailContent['subject'];
450 $mailing->body_text
= $mailContent['text'];
451 $mailing->body_html
= $mailContent['html'];
452 $tokens = $mailing->getTokens();
453 CRM_Utils_Hook
::tokens($hookTokens);
454 $categories = array_keys($hookTokens);
456 $contactID = CRM_Utils_Array
::value('contactId', $params);
459 $contactParams = ['contact_id' => $contactID];
460 $returnProperties = [];
462 if (isset($tokens['subject']['contact'])) {
463 foreach ($tokens['subject']['contact'] as $name) {
464 $returnProperties[$name] = 1;
468 if (isset($tokens['text']['contact'])) {
469 foreach ($tokens['text']['contact'] as $name) {
470 $returnProperties[$name] = 1;
474 if (isset($tokens['html']['contact'])) {
475 foreach ($tokens['html']['contact'] as $name) {
476 $returnProperties[$name] = 1;
480 // @todo CRM-17253 don't resolve contact details if there are no tokens
481 // effectively comment out this next (performance-expensive) line
482 // but unfortunately testing is a bit think on the ground to that needs to
484 list($contact) = CRM_Utils_Token
::getTokenDetails($contactParams,
487 CRM_Utils_Token
::flattenTokens($tokens),
488 // we should consider adding groupName and valueName here
489 'CRM_Core_BAO_MessageTemplate'
491 $contact = $contact[$contactID];
494 $mailContent['subject'] = CRM_Utils_Token
::replaceDomainTokens($mailContent['subject'], $domain, FALSE, $tokens['subject'], TRUE);
495 $mailContent['text'] = CRM_Utils_Token
::replaceDomainTokens($mailContent['text'], $domain, FALSE, $tokens['text'], TRUE);
496 $mailContent['html'] = CRM_Utils_Token
::replaceDomainTokens($mailContent['html'], $domain, TRUE, $tokens['html'], TRUE);
499 $mailContent['subject'] = CRM_Utils_Token
::replaceContactTokens($mailContent['subject'], $contact, FALSE, $tokens['subject'], FALSE, TRUE);
500 $mailContent['text'] = CRM_Utils_Token
::replaceContactTokens($mailContent['text'], $contact, FALSE, $tokens['text'], FALSE, TRUE);
501 $mailContent['html'] = CRM_Utils_Token
::replaceContactTokens($mailContent['html'], $contact, FALSE, $tokens['html'], FALSE, TRUE);
503 $contactArray = [$contactID => $contact];
504 CRM_Utils_Hook
::tokenValues($contactArray,
507 CRM_Utils_Token
::flattenTokens($tokens),
508 // we should consider adding groupName and valueName here
509 'CRM_Core_BAO_MessageTemplate'
511 $contact = $contactArray[$contactID];
513 $mailContent['subject'] = CRM_Utils_Token
::replaceHookTokens($mailContent['subject'], $contact, $categories, TRUE);
514 $mailContent['text'] = CRM_Utils_Token
::replaceHookTokens($mailContent['text'], $contact, $categories, TRUE);
515 $mailContent['html'] = CRM_Utils_Token
::replaceHookTokens($mailContent['html'], $contact, $categories, TRUE);
518 // strip whitespace from ends and turn into a single line
519 $mailContent['subject'] = "{strip}{$mailContent['subject']}{/strip}";
521 // parse the three elements with Smarty
522 $smarty = CRM_Core_Smarty
::singleton();
523 foreach ($params['tplParams'] as $name => $value) {
524 $smarty->assign($name, $value);
531 $mailContent[$elem] = $smarty->fetch("string:{$mailContent[$elem]}");
534 // send the template, honouring the target user’s preferences (if any)
537 // create the params array
538 $params['subject'] = $mailContent['subject'];
539 $params['text'] = $mailContent['text'];
540 $params['html'] = $mailContent['html'];
542 if ($params['toEmail']) {
543 $contactParams = [['email', 'LIKE', $params['toEmail'], 0, 1]];
544 list($contact, $_) = CRM_Contact_BAO_Query
::apiQuery($contactParams);
546 $prefs = array_pop($contact);
548 if (isset($prefs['preferred_mail_format']) and $prefs['preferred_mail_format'] == 'HTML') {
549 $params['text'] = NULL;
552 if (isset($prefs['preferred_mail_format']) and $prefs['preferred_mail_format'] == 'Text') {
553 $params['html'] = NULL;
556 $config = CRM_Core_Config
::singleton();
557 if (isset($params['isEmailPdf']) && $params['isEmailPdf'] == 1) {
558 $pdfHtml = CRM_Contribute_BAO_ContributionPage
::addInvoicePdfToEmail($params['contributionId'], $params['contactId']);
559 if (empty($params['attachments'])) {
560 $params['attachments'] = [];
562 $params['attachments'][] = CRM_Utils_Mail
::appendPDF('Invoice.pdf', $pdfHtml, $mailContent['format']);
565 if ($config->doNotAttachPDFReceipt
&&
566 $params['PDFFilename'] &&
569 if (empty($params['attachments'])) {
570 $params['attachments'] = [];
572 $params['attachments'][] = CRM_Utils_Mail
::appendPDF($params['PDFFilename'], $params['html'], $mailContent['format']);
573 if (isset($params['tplParams']['email_comment'])) {
574 $params['html'] = $params['tplParams']['email_comment'];
575 $params['text'] = strip_tags($params['tplParams']['email_comment']);
579 $sent = CRM_Utils_Mail
::send($params);
582 unlink($pdf_filename);
586 return [$sent, $mailContent['subject'], $mailContent['text'], $mailContent['html']];