* @param CRM_Core_Form $form
* @param bool $bounce determine if we want to throw a status bounce.
*
- * @throws \CiviCRM_API3_Exception
+ * @throws \API_Exception
*/
public static function preProcessFromAddress(&$form, $bounce = TRUE) {
$form->_emails = [];
$form->_emails = $fromEmailValues;
$defaults = [];
$form->_fromEmails = $fromEmailValues;
+ if (is_numeric(key($form->_fromEmails))) {
+ $emailID = (int) key($form->_fromEmails);
+ $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID);
+ }
if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) {
$defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE));
}
- if (is_numeric(key($form->_fromEmails))) {
- // Add signature
- $defaultEmail = civicrm_api3('email', 'getsingle', ['id' => key($form->_fromEmails)]);
- $defaults = [];
- if (!empty($defaultEmail['signature_html'])) {
- $defaults['html_message'] = '<br/><br/>--' . $defaultEmail['signature_html'];
- }
- if (!empty($defaultEmail['signature_text'])) {
- $defaults['text_message'] = "\n\n--\n" . $defaultEmail['signature_text'];
- }
- }
$form->setDefaults($defaults);
}
* Call trait preProcess function.
*
* This function exists as a transitional arrangement so classes overriding
- * preProcess can still call it. Ideally it will be melded into preProcess later.
+ * preProcess can still call it. Ideally it will be melded into preProcess
+ * later.
*
- * @throws \CiviCRM_API3_Exception
* @throws \CRM_Core_Exception
+ * @throws \API_Exception
*/
protected function traitPreProcess() {
CRM_Contact_Form_Task_EmailCommon::preProcessFromAddress($this);
$contributionId = $params['contribution_id'];
$contributionStatusId = $params['contribution_status_id'];
- // if we already processed contribution object pass previous status id.
- $previousContriStatusId = $params['previous_contribution_status_id'];
-
// we process only ( Completed, Cancelled, or Failed ) contributions.
if (!$contributionId || $contributionStatus !== 'Completed') {
return;
if ($contributionStatus === 'Completed') {
// only pending contribution related object processed.
- if ($previousContriStatusId &&
- !in_array($previousStatus, [
- 'Pending',
- 'Partially paid',
- ])
- ) {
+ if (!in_array($previousStatus, ['Pending', 'Partially paid'])) {
// this is case when we already processed contribution object.
return;
}
- elseif (!$previousContriStatusId &&
- !in_array($contributionStatus, [
- 'Pending',
- 'Partially paid',
- ])
- ) {
- // this is case when we are going to process contribution object later.
- return;
- }
if (is_array($memberships)) {
foreach ($memberships as $membership) {
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/
+use Civi\Api4\Email;
+
/**
* This class contains functions for email handling.
*/
}
}
+ /**
+ * Get default text for a message with the signature from the email sender populated.
+ *
+ * @param int $emailID
+ *
+ * @return array
+ *
+ * @throws \API_Exception
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ public static function getEmailSignatureDefaults(int $emailID): array {
+ // Add signature
+ $defaultEmail = Email::get(FALSE)
+ ->addSelect('signature_html', 'signature_text')
+ ->addWhere('id', '=', $emailID)->execute()->first();
+ return [
+ 'html_message' => empty($defaultEmail['signature_html']) ? '' : '<br/><br/>--' . $defaultEmail['signature_html'],
+ 'text_message' => empty($defaultEmail['signature_text']) ? '' : "\n\n--\n" . $defaultEmail['signature_text'],
+ ];
+ }
+
}
'valueName' => NULL,
// ID of the template
'messageTemplateID' => NULL,
+ // content of the message template
+ // Ex: ['msg_subject' => 'Hello {contact.display_name}', 'msg_html' => '...', 'msg_text' => '...']
+ // INTERNAL: 'messageTemplate' is currently only intended for use within civicrm-core only. For downstream usage, future updates will provide comparable public APIs.
+ 'messageTemplate' => NULL,
// contact id if contact tokens are to be replaced
'contactId' => NULL,
// additional template params (other than the ones already set in the template singleton)
'tplParams' => [],
+ // additional token params (passed to the TokenProcessor)
+ // INTERNAL: 'tokenContext' is currently only intended for use within civicrm-core only. For downstream usage, future updates will provide comparable public APIs.
+ 'tokenContext' => [],
// the From: header
'from' => NULL,
// the recipient’s name
CRM_Core_Error::deprecatedWarning('message template id should be an integer');
$params['messageTemplateID'] = (int) $params['messageTemplateID'];
}
- $mailContent = self::loadTemplate((string) $params['valueName'], $params['isTest'], $params['messageTemplateID'] ?? NULL, $params['groupName'] ?? '');
+ $mailContent = self::loadTemplate((string) $params['valueName'], $params['isTest'], $params['messageTemplateID'] ?? NULL, $params['groupName'] ?? '', $params['messageTemplate'], $params['subject'] ?? NULL);
- // Overwrite subject from form field
- if (!empty($params['subject'])) {
- $mailContent['subject'] = $params['subject'];
- }
-
- $mailContent = self::renderMessageTemplate($mailContent, (bool) $params['disableSmarty'], $params['contactId'] ?? NULL, $params['tplParams']);
+ $mailContent = self::renderMessageTemplate($mailContent, (bool) $params['disableSmarty'], $params['contactId'] ?? NULL, $params['tplParams'], $params['tokenContext']);
// send the template, honouring the target user’s preferences (if any)
$sent = FALSE;
* @param bool $isTest
* @param int|null $messageTemplateID
* @param string $groupName
+ * @param array|null $messageTemplateOverride
+ * Optionally, record with msg_subject, msg_text, msg_html.
+ * If omitted, the record will be loaded from workflowName/messageTemplateID.
+ * @param string|null $subjectOverride
+ * This option is the older, wonkier version of $messageTemplate['msg_subject']...
*
* @return array
* @throws \API_Exception
* @throws \CRM_Core_Exception
*/
- protected static function loadTemplate(string $workflowName, bool $isTest, int $messageTemplateID = NULL, $groupName = NULL): array {
+ protected static function loadTemplate(string $workflowName, bool $isTest, int $messageTemplateID = NULL, $groupName = NULL, ?array $messageTemplateOverride = NULL, ?string $subjectOverride = NULL): array {
+ $base = ['msg_subject' => NULL, 'msg_text' => NULL, 'msg_html' => NULL, 'pdf_format_id' => NULL];
if (!$workflowName && !$messageTemplateID) {
throw new CRM_Core_Exception(ts("Message template's option value or ID missing."));
}
else {
$apiCall->addWhere('workflow_name', '=', $workflowName);
}
- $messageTemplate = $apiCall->execute()->first();
- if (empty($messageTemplate['id'])) {
+ $messageTemplate = array_merge($base, $apiCall->execute()->first() ?: [], $messageTemplateOverride ?: []);
+ if (empty($messageTemplate['id']) && empty($messageTemplateOverride)) {
if ($messageTemplateID) {
throw new CRM_Core_Exception(ts('No such message template: id=%1.', [1 => $messageTemplateID]));
}
- throw new CRM_Core_Exception(ts('No message template with workflow name %2.', [2 => $workflowName]));
+ throw new CRM_Core_Exception(ts('No message template with workflow name %1.', [1 => $workflowName]));
}
$mailContent = [
$mailContent['html'] = preg_replace('/<body(.*)$/im', "<body\\1\n{$testText['msg_html']}", $mailContent['html']);
}
+ if (!empty($subjectOverride)) {
+ CRM_Core_Error::deprecatedWarning('CRM_Core_BAO_MessageTemplate: $params[subject] is deprecated. Use $params[messageTemplate][msg_subject] instead.');
+ $mailContent['subject'] = $subjectOverride;
+ }
+
return $mailContent;
}
* @param bool $disableSmarty
* @param int|NULL $contactID
* @param array $smartyAssigns
+ * Data to pass through to Smarty.
+ * @param array $tokenContext
+ * Data to pass through to TokenProcessor.
*
* @return array
*/
- public static function renderMessageTemplate(array $mailContent, bool $disableSmarty, $contactID, array $smartyAssigns): array {
- $tokenContext = ['smarty' => !$disableSmarty];
- if ($contactID) {
+ public static function renderMessageTemplate(array $mailContent, bool $disableSmarty, $contactID, array $smartyAssigns, array $tokenContext = []): array {
+ $tokenContext['smarty'] = !$disableSmarty;
+ if ($contactID && !isset($tokenContext['contactId'])) {
$tokenContext['contactId'] = $contactID;
}
$result = CRM_Core_TokenSmarty::render(CRM_Utils_Array::subset($mailContent, ['text', 'html', 'subject']), $tokenContext, $smartyAssigns);
}
$ftAclSetting = Civi::settings()->get('acl_financial_type');
- $financialAclExtension = civicrm_api3('extension', 'get', ['key' => 'biz.jmaconsulting.financialaclreport']);
- if ($ftAclSetting && (($financialAclExtension['count'] == 1 && $financialAclExtension['status'] != 'Installed') || $financialAclExtension['count'] !== 1)) {
+ $financialAclExtension = civicrm_api3('extension', 'get', ['key' => 'biz.jmaconsulting.financialaclreport', 'sequential' => 1]);
+ if ($ftAclSetting && (($financialAclExtension['count'] == 1 && $financialAclExtension['values'][0]['status'] != 'Installed') || $financialAclExtension['count'] !== 1)) {
$preUpgradeMessage .= '<br />' . ts('CiviCRM will in the future require the extension %1 for CiviCRM Reports to work correctly with the Financial Type ACLs. The extension can be downloaded <a href="%2">here</a>', [
1 => 'biz.jmaconsulting.financialaclreport',
2 => 'https://github.com/JMAConsulting/biz.jmaconsulting.financialaclreport',
continue;
}
// Ensure alias is a safe string, and supply default if not given
- $alias = $alias ? \CRM_Utils_String::munge($alias, '_', 256) : strtolower($entity);
+ $alias = $alias ?: strtolower($entity);
+ if ($alias === self::MAIN_TABLE_ALIAS || !preg_match('/^[-\w]{1,256}$/', $alias)) {
+ throw new \API_Exception('Illegal join alias: "' . $alias . '"');
+ }
// First item in the array is a boolean indicating if the join is required (aka INNER or LEFT).
// The rest are join conditions.
$side = array_shift($join);
* @return mixed
*/
public static function getInfoItem(string $entityName, string $keyToReturn) {
- return self::getApiClass($entityName)::getInfo()[$keyToReturn] ?? NULL;
+ $className = self::getApiClass($entityName);
+ return $className ? $className::getInfo()[$keyToReturn] ?? NULL : NULL;
}
/**
}
{/literal}
- var toContact = {if $toContact}{$toContact}{else}''{/if},
- ccContact = {if $ccContact}{$ccContact}{else}''{/if};
+ var toContact = {if $toContact}{$toContact}{else}''{/if};
{literal}
emailSelect('#to', toContact);
});
parent::tearDown();
}
+ public function testSendTemplate_RenderMode_OpenTemplate() {
+ $contactId = $this->individualCreate([
+ 'first_name' => 'Abba',
+ 'last_name' => 'Baab',
+ 'prefix_id' => NULL,
+ 'suffix_id' => NULL,
+ ]);
+ [$sent, $subject, $messageText, $messageHtml] = CRM_Core_BAO_MessageTemplate::sendTemplate(
+ [
+ 'valueName' => 'case_activity',
+ 'contactId' => $contactId,
+ 'from' => 'admin@example.com',
+ // No 'toEmail'/'toName' address => not sendable, but still returns rendered value.
+ 'attachments' => NULL,
+ 'messageTemplate' => [
+ 'msg_subject' => 'Hello testSendTemplate_RenderMode_OpenTemplate {contact.display_name}!',
+ 'msg_text' => 'Hello testSendTemplate_RenderMode_OpenTemplate {contact.display_name}!',
+ 'msg_html' => '<p>Hello testSendTemplate_RenderMode_OpenTemplate {contact.display_name}!</p>',
+ ],
+ ]
+ );
+ $this->assertEquals(FALSE, $sent);
+ $this->assertEquals('Hello testSendTemplate_RenderMode_OpenTemplate Abba Baab!', $subject);
+ $this->assertEquals('Hello testSendTemplate_RenderMode_OpenTemplate Abba Baab!', $messageText);
+ $this->assertStringContainsString('<p>Hello testSendTemplate_RenderMode_OpenTemplate Abba Baab!</p>', $messageHtml);
+ }
+
+ public function testSendTemplate_RenderMode_DefaultTpl() {
+ CRM_Core_Transaction::create(TRUE)->run(function(CRM_Core_Transaction $tx) {
+ $tx->rollback();
+
+ \Civi\Api4\MessageTemplate::update()
+ ->addWhere('workflow_name', '=', 'case_activity')
+ ->addWhere('is_reserved', '=', 0)
+ ->setValues([
+ 'msg_subject' => 'Hello testSendTemplate_RenderMode_Default {contact.display_name}!',
+ 'msg_text' => 'Hello testSendTemplate_RenderMode_Default {contact.display_name}!',
+ 'msg_html' => '<p>Hello testSendTemplate_RenderMode_Default {contact.display_name}!</p>',
+ ])
+ ->execute();
+
+ $contactId = $this->individualCreate([
+ 'first_name' => 'Abba',
+ 'last_name' => 'Baab',
+ 'prefix_id' => NULL,
+ 'suffix_id' => NULL,
+ ]);
+
+ [$sent, $subject, $messageText, $messageHtml] = CRM_Core_BAO_MessageTemplate::sendTemplate(
+ [
+ 'valueName' => 'case_activity',
+ 'contactId' => $contactId,
+ 'from' => 'admin@example.com',
+ // No 'toEmail'/'toName' address => not sendable, but still returns rendered value.
+ 'attachments' => NULL,
+ ]
+ );
+ $this->assertEquals(FALSE, $sent);
+ $this->assertEquals('Hello testSendTemplate_RenderMode_Default Abba Baab!', $subject);
+ $this->assertEquals('Hello testSendTemplate_RenderMode_Default Abba Baab!', $messageText);
+ $this->assertStringContainsString('<p>Hello testSendTemplate_RenderMode_Default Abba Baab!</p>', $messageHtml);
+ });
+ }
+
+ public function testSendTemplate_RenderMode_TokenContext() {
+ CRM_Core_Transaction::create(TRUE)->run(function(CRM_Core_Transaction $tx) {
+ $tx->rollback();
+
+ \Civi\Api4\MessageTemplate::update()
+ ->addWhere('workflow_name', '=', 'case_activity')
+ ->addWhere('is_reserved', '=', 0)
+ ->setValues([
+ 'msg_subject' => 'Hello {contact.display_name} about {activity.subject}!',
+ 'msg_text' => 'Hello {contact.display_name} about {activity.subject}!',
+ 'msg_html' => '<p>Hello {contact.display_name} about {activity.subject}!</p>',
+ ])
+ ->execute();
+
+ $contactId = $this->individualCreate([
+ 'first_name' => 'Abba',
+ 'last_name' => 'Baab',
+ 'prefix_id' => NULL,
+ 'suffix_id' => NULL,
+ ]);
+ $activityId = $this->activityCreate(['subject' => 'Something Something'])['id'];
+
+ [$sent, $subject, $messageText, $messageHtml] = CRM_Core_BAO_MessageTemplate::sendTemplate(
+ [
+ 'valueName' => 'case_activity',
+ 'tokenContext' => [
+ 'contactId' => $contactId,
+ 'activityId' => $activityId,
+ ],
+ 'from' => 'admin@example.com',
+ // No 'toEmail'/'toName' address => not sendable, but still returns rendered value.
+ 'attachments' => NULL,
+ ]
+ );
+ $this->assertEquals(FALSE, $sent);
+ $this->assertEquals('Hello Abba Baab about Something Something!', $subject);
+ $this->assertEquals('Hello Abba Baab about Something Something!', $messageText);
+ $this->assertStringContainsString('<p>Hello Abba Baab about Something Something!</p>', $messageHtml);
+ });
+ }
+
/**
* Test message template send.
*
$this->assertNotContains($this->getReference('test_contact_1')['id'], $contacts);
}
+ public function testInvalidJoinAlias() {
+ // Not allowed to use same alias as the base table
+ try {
+ Contact::get(FALSE)->addJoin('Address AS a')->execute();
+ }
+ catch (\API_Exception $e) {
+ $message = $e->getMessage();
+ }
+ $this->assertEquals('Illegal join alias: "a"', $message);
+
+ // Not allowed to use dots in the alias
+ try {
+ Contact::get(FALSE)->addJoin('Address AS add.ress')->execute();
+ }
+ catch (\API_Exception $e) {
+ $message = $e->getMessage();
+ }
+ $this->assertEquals('Illegal join alias: "add.ress"', $message);
+
+ // Not allowed to use an alias > 256 characters
+ try {
+ $longAlias = str_repeat('z', 257);
+ Contact::get(FALSE)->addJoin("Address AS $longAlias")->execute();
+ }
+ catch (\API_Exception $e) {
+ $message = $e->getMessage();
+ }
+ $this->assertEquals("Illegal join alias: \"$longAlias\"", $message);
+
+ // Alpha-numeric with dashes 256 characters long - weird but allowed
+ $okAlias = str_repeat('-0_a-9Z_', 32);
+ Contact::get(FALSE)->addJoin("Address AS $okAlias")->execute();
+ }
+
public function testJoinToTheSameTableTwice() {
$cid1 = Contact::create(FALSE)
->addValue('first_name', 'Aaa')