diff --git a/CRM/Core/BAO/MessageTemplate.php b/CRM/Core/BAO/MessageTemplate.php
index 9ba60e2003c9..2b217a834bcc 100644
--- a/CRM/Core/BAO/MessageTemplate.php
+++ b/CRM/Core/BAO/MessageTemplate.php
@@ -594,7 +594,7 @@ protected static function resolveDomainTokens(array $mailContent, array $tokens,
$domain = CRM_Core_BAO_Domain::getDomain();
$mailContent['subject'] = CRM_Utils_Token::replaceDomainTokens($mailContent['subject'], $domain, FALSE, $tokens['subject'], $escapeSmarty);
$mailContent['text'] = CRM_Utils_Token::replaceDomainTokens($mailContent['text'], $domain, FALSE, $tokens['text'], $escapeSmarty);
- $mailContent['html'] = CRM_Utils_Token::replaceDomainTokens($mailContent['html'], $domain, TRUE, $tokens, $escapeSmarty);
+ $mailContent['html'] = CRM_Utils_Token::replaceDomainTokens($mailContent['html'], $domain, TRUE, $tokens['html'], $escapeSmarty);
return $mailContent;
}
@@ -687,6 +687,9 @@ protected static function parseThroughSmarty(array $mailContent, $tplParams): ar
/**
* Render the message template, resolving tokens and smarty tokens.
*
+ * As with all BAO methods this should not be called directly outside
+ * of tested core code and is highly likely to change.
+ *
* @param array $mailContent
* @param bool $disableSmarty
* @param int $contactID
@@ -695,7 +698,7 @@ protected static function parseThroughSmarty(array $mailContent, $tplParams): ar
* @return array
* @throws \CRM_Core_Exception
*/
- protected static function renderMessageTemplate(array $mailContent, $disableSmarty, $contactID, $smartyAssigns): array {
+ public static function renderMessageTemplate(array $mailContent, $disableSmarty, $contactID, $smartyAssigns): array {
$tokens = self::getTokensToResolve($mailContent);
// When using Smarty we need to pass the $escapeSmarty parameter.
diff --git a/CRM/Utils/Token.php b/CRM/Utils/Token.php
index 1b62b706f764..038c191543b3 100644
--- a/CRM/Utils/Token.php
+++ b/CRM/Utils/Token.php
@@ -249,14 +249,14 @@ function ($matches) use ($domain, $html, $escapeSmarty) {
}
/**
- * @param $token
+ * @param string $token
* @param CRM_Core_BAO_Domain $domain
* @param bool $html
* @param bool $escapeSmarty
*
- * @return mixed|null|string
+ * @return null|string
*/
- public static function getDomainTokenReplacement($token, $domain, $html = FALSE, $escapeSmarty = FALSE) {
+ public static function getDomainTokenReplacement($token, $domain, $html = FALSE, $escapeSmarty = FALSE): ?string {
// check if the token we were passed is valid
// we have to do this because this function is
// called only when we find a token in the string
@@ -266,7 +266,7 @@ public static function getDomainTokenReplacement($token, $domain, $html = FALSE,
if (!in_array($token, self::$_tokens['domain'])) {
$value = "{domain.$token}";
}
- elseif ($token == 'address') {
+ elseif ($token === 'address') {
static $addressCache = [];
$cache_key = $html ? 'address-html' : 'address-text';
@@ -288,10 +288,10 @@ public static function getDomainTokenReplacement($token, $domain, $html = FALSE,
$addressCache[$cache_key] = $value;
}
}
- elseif ($token == 'name' || $token == 'id' || $token == 'description') {
+ elseif ($token === 'name' || $token === 'id' || $token === 'description') {
$value = $domain->$token;
}
- elseif ($token == 'phone' || $token == 'email') {
+ elseif ($token === 'phone' || $token === 'email') {
// Construct the phone and email tokens
$value = NULL;
@@ -1234,7 +1234,7 @@ public static function getTokenDetails(
if (!empty($contactDetails[$contactID]['preferred_communication_method'])
) {
$communicationPreferences = [];
- foreach ($contactDetails[$contactID]['preferred_communication_method'] as $val) {
+ foreach ((array) $contactDetails[$contactID]['preferred_communication_method'] as $val) {
if ($val) {
$communicationPreferences[$val] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_DAO_Contact', 'preferred_communication_method', $val);
}
diff --git a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
index 05f7e3e6d77a..4ab571b5c58b 100644
--- a/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
+++ b/tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
@@ -1,25 +1,34 @@
quickCleanup(['civicrm_address', 'civicrm_phone', 'civicrm_im', 'civicrm_website', 'civicrm_openid', 'civicrm_email']);
parent::tearDown();
}
/**
* Test message template send.
*
+ * @throws \API_Exception
* @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
*/
- public function testCaseActivityCopyTemplate() {
+ public function testCaseActivityCopyTemplate():void {
$client_id = $this->individualCreate();
$contact_id = $this->individualCreate();
@@ -41,7 +50,7 @@ public function testCaseActivityCopyTemplate() {
'idHash' => substr(sha1(CIVICRM_SITE_KEY . '1234'), 0, 7),
];
- list($sent, $subject, $message) = CRM_Core_BAO_MessageTemplate::sendTemplate(
+ [$sent, $subject, $message] = CRM_Core_BAO_MessageTemplate::sendTemplate(
[
'valueName' => 'case_activity',
'contactId' => $contact_id,
@@ -58,4 +67,322 @@ public function testCaseActivityCopyTemplate() {
$this->assertContains('Case ID : 1234', $message);
}
+ /**
+ * Test rendering of domain tokens.
+ *
+ * @throws \CRM_Core_Exception
+ */
+ public function testDomainTokens(): void {
+ $values = $this->getDomainTokenData();
+ $this->callAPISuccess('Domain', 'create', [
+ 'id' => CRM_Core_Config::domainID(),
+ 'description' => $values['description'],
+ ]);
+ $this->callAPISuccess('Address', 'create', array_merge($values['address'], ['contact_id' => 1]));
+ $this->callAPISuccess('Email', 'create', array_merge(['email' => $values['email']], ['contact_id' => 1, 'is_primary' => 1]));
+ $tokenString = '{domain.' . implode('} ~ {domain.', array_keys($values)) . '}';
+ $messageContent = CRM_Core_BAO_MessageTemplate::renderMessageTemplate([
+ 'html' => $tokenString,
+ // Check the space is stripped.
+ 'subject' => $tokenString . ' ',
+ 'text' => $tokenString,
+ ], FALSE, FALSE, []);
+ $this->assertEquals('Default Domain Name ~ ~
Buckingham palace
Up the road
London, 90210
~ crown@example.com ~ 1 ~ rather nice', $messageContent['html']);
+ $this->assertEquals('Default Domain Name ~ ~ Buckingham palace
+Up the road
+London, 90210
+ ~ crown@example.com ~ 1 ~ rather nice', $messageContent['text']);
+ $this->assertEquals('Default Domain Name ~ ~ Buckingham palaceUp the roadLondon, 90210~ crown@example.com ~ 1 ~ rather nice', $messageContent['subject']);
+ }
+
+ /**
+ * Test rendering of smarty tokens.
+ *
+ * @throws \CRM_Core_Exception
+ */
+ public function testRenderMessageTemplateSmarty(): void {
+ $messageContent = CRM_Core_BAO_MessageTemplate::renderMessageTemplate([
+ 'html' => '{$tokenString}',
+ // Check the space is stripped.
+ 'subject' => '{$tokenString} ',
+ 'text' => '{$tokenString}',
+ ], FALSE, FALSE, ['tokenString' => 'Something really witty']);
+
+ $this->assertEquals('Something really witty', $messageContent['text']);
+ $this->assertEquals('Something really witty', $messageContent['html']);
+ $this->assertEquals('Something really witty', $messageContent['subject']);
+ }
+
+ /**
+ * Test rendering of smarty tokens.
+ *
+ * @throws \CRM_Core_Exception
+ */
+ public function testRenderMessageTemplateIgnoreSmarty(): void {
+ $messageContent = CRM_Core_BAO_MessageTemplate::renderMessageTemplate([
+ 'html' => '{$tokenString}',
+ // Check the space is stripped.
+ 'subject' => '{$tokenString} ',
+ 'text' => '{$tokenString}',
+ ], TRUE, FALSE, ['tokenString' => 'Something really witty']);
+
+ $this->assertEquals('{$tokenString}', $messageContent['text']);
+ $this->assertEquals('{$tokenString}', $messageContent['html']);
+ $this->assertEquals('{$tokenString}', $messageContent['subject']);
+ }
+
+ /**
+ * Test rendering of domain tokens.
+ *
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
+ */
+ public function testContactTokens(): void {
+ $this->createCustomGroupWithFieldOfType([], 'contact_reference');
+ $tokenData = $this->getAllContactTokens();
+ $this->callAPISuccess('Contact', 'create', $tokenData);
+ $address = $this->callAPISuccess('Address', 'create', array_merge($tokenData, ['is_primary' => TRUE]));
+ $this->callAPISuccess('Phone', 'create', array_merge($tokenData, ['is_primary' => TRUE]));
+ $this->callAPISuccess('Email', 'create', array_merge($tokenData, ['is_primary' => TRUE]));
+ $this->callAPISuccess('Website', 'create', array_merge($tokenData, ['is_primary' => TRUE]));
+ $this->callAPISuccess('Im', 'create', ['is_primary' => TRUE, 'name' => $tokenData['im'], 'provider_id' => $tokenData['im_provider'], 'contact_id' => $tokenData['contact_id']]);
+ $this->callAPISuccess('OpenID', 'create', array_merge($tokenData, ['is_primary' => TRUE, 'contact_id' => $tokenData['contact_id'], 'openid' => $tokenData['openid']]));
+
+ CRM_Core_Smarty::singleton()->assign('pre_assigned_smarty', 'wee');
+ // This string contains the 4 types of possible replaces just to be sure they
+ // work in combination.
+ $tokenString = '{$pre_assigned_smarty}{$passed_smarty}
+{domain.name}
+';
+ foreach (array_keys($tokenData) as $key) {
+ $tokenString .= "{$key}:{contact.{$key}}\n";
+ }
+ $messageContent = CRM_Core_BAO_MessageTemplate::renderMessageTemplate([
+ 'html' => $tokenString,
+ // Check the space is stripped.
+ 'subject' => $tokenString . ' ',
+ 'text' => $tokenString,
+ ], FALSE, $tokenData['contact_id'], ['passed_smarty' => 'whoa']);
+ $checksum = substr($messageContent['html'], (strpos($messageContent['html'], 'cs=') + 3), 47);
+ $contact = Contact::get(FALSE)->addWhere('id', '=', $tokenData['contact_id'])->setSelect(['modified_date', 'employer_id'])->execute()->first();
+ $expected = 'weewhoa
+Default Domain Name
+contact_type:Individual
+do_not_email:1
+do_not_phone:
+do_not_mail:1
+do_not_sms:1
+do_not_trade:1
+is_opt_out:1
+external_identifier:blah
+sort_name:Smith, Robert
+display_name:Mr. Robert Smith II
+nick_name:Bob
+image_URL:https://example.com
+preferred_communication_method:
+preferred_language:fr_CA
+preferred_mail_format:Both
+hash:xyz
+contact_source:Contact Source
+first_name:Robert
+middle_name:Frank
+last_name:Smith
+individual_prefix:Mr.
+individual_suffix:II
+formal_title:Dogsbody
+communication_style:Formal
+email_greeting_id:Dear {contact.first_name}
+postal_greeting_id:Dear {contact.first_name}
+addressee_id:{contact.individual_prefix}{ } {contact.first_name}{ }{contact.middle_name}{ }{contact.last_name}{ }{contact.individual_suffix}
+job_title:Busy person
+gender:Female
+birth_date:December 31st, 1998
+current_employer_id:' . $contact['employer_id'] . '
+contact_is_deleted:
+created_date:January 1st, 2020 12:00 AM
+modified_date:' . CRM_Utils_Date::customFormat($contact['modified_date']) . '
+addressee:Mr. Robert Frank Smith II
+email_greeting:Dear Robert
+postal_greeting:Dear Robert
+current_employer:Unit Test Organization
+location_type:Home
+address_id:' . $address['id'] . '
+street_address:Street Address
+street_number:123
+street_number_suffix:S
+street_name:Main St
+street_unit:45B
+supplemental_address_1:Round the corner
+supplemental_address_2:Up the road
+supplemental_address_3:By the big tree
+city:New York
+postal_code_suffix:4578
+postal_code:90210
+geo_code_1:48.858093
+geo_code_2:2.294694
+manual_geo_code:1
+address_name:The white house
+master_id:' . $tokenData['master_id'] . '
+county:
+state_province:TX
+country:United States
+phone:123-456
+phone_ext:77
+phone_type_id:
+phone_type:Mobile
+email:anthony_anderson@civicrm.org
+on_hold:
+signature_text:Yours sincerely
+signature_html:<p>Yours</p>
+im_provider:1
+im:IM Screen Name
+openid:OpenID
+world_region:America South, Central, North and Caribbean
+url:http://civicrm.org
+custom_1:Mr. Spider Man II
+checksum:cs=' . $checksum . '
+contact_id:' . $tokenData['contact_id'] . '
+';
+ $this->assertEquals($expected, $messageContent['html']);
+ $this->assertEquals($expected, $messageContent['text']);
+ $this->assertEquals(str_replace("\n", '', $expected), $messageContent['subject']);
+ }
+
+ /**
+ * Gets the values needed to render domain tokens.
+ *
+ * This is keyed by all the available tokens and fills
+ * them with sample data.
+ *
+ * @return array
+ */
+ protected function getDomainTokenData(): array {
+ return [
+ 'name' => 'Default Domain Name',
+ 'phone' => 123,
+ 'address' => [
+ 'street_address' => 'Buckingham palace',
+ 'supplemental_address_1' => 'Up the road',
+ 'postal_code' => 90210,
+ 'geocode_1' => 789,
+ 'geocode_2' => 890,
+ 'city' => 'London',
+ ],
+ 'email' => 'crown@example.com',
+ 'id' => CRM_Core_Config::domainID(),
+ 'description' => 'rather nice',
+ ];
+ }
+
+ /**
+ * Get all the available tokens with usable data.
+ *
+ * This is the result rendered from CRM_Core_SelectValues::contactTokens();
+ * and has been gathered by calling that function.
+ *
+ * I have hard-coded them so we have a record of what we have
+ * seemingly committed to support.
+ *
+ * Note it will render additional custom fields if they exist.
+ *
+ * @return array
+ * @throws \API_Exception
+ * @throws \CRM_Core_Exception
+ * @throws \CiviCRM_API3_Exception
+ */
+ public function getAllContactTokens(): array {
+ return [
+ 'contact_type' => 'Individual',
+ 'do_not_email' => 1,
+ 'do_not_phone' => 0,
+ 'do_not_mail' => 1,
+ 'do_not_sms' => 1,
+ 'do_not_trade' => 1,
+ 'is_opt_out' => 1,
+ 'external_identifier' => 'blah',
+ 'sort_name' => 'Smith, Robert',
+ 'display_name' => 'Robert Smith',
+ 'nick_name' => 'Bob',
+ 'image_URL' => 'https://example.com',
+ 'preferred_communication_method' => 'Phone',
+ 'preferred_language' => 'fr_CA',
+ 'preferred_mail_format' => 'Both',
+ 'hash' => 'xyz',
+ 'contact_source' => 'Contact Source',
+ 'first_name' => 'Robert',
+ 'middle_name' => 'Frank',
+ 'last_name' => 'Smith',
+ 'individual_prefix' => 'Mr.',
+ 'individual_suffix' => 'II',
+ 'formal_title' => 'Dogsbody',
+ 'communication_style' => 'Formal',
+ 'email_greeting_id' => 1,
+ 'postal_greeting_id' => 1,
+ 'addressee_id' => 1,
+ 'job_title' => 'Busy person',
+ 'gender' => 'Female',
+ 'birth_date' => '1998-12-31',
+ 'current_employer_id' => $this->organizationCreate(),
+ 'contact_is_deleted' => 0,
+ 'created_date' => '2020-01-01',
+ 'modified_date' => '2020-01-01',
+ 'addressee' => '{contact.individual_prefix}{ } {contact.first_name}{ }{contact.middle_name}{ }{contact.last_name}{ }{contact.individual_suffix}',
+ 'email_greeting' => 'Dear {contact.first_name}',
+ 'postal_greeting' => 'Dear {contact.first_name}',
+ 'current_employer' => 'Unit Test Organization',
+ 'location_type' => 'Main',
+ 'address_id' => Address::create(FALSE)->setValues(['street_address' => 'Street Address'])->execute()->first()['id'],
+ 'street_address' => 'Street Address',
+ 'street_number' => '123',
+ 'street_number_suffix' => 'S',
+ 'street_name' => 'Main St',
+ 'street_unit' => '45B',
+ 'supplemental_address_1' => 'Round the corner',
+ 'supplemental_address_2' => 'Up the road',
+ 'supplemental_address_3' => 'By the big tree',
+ 'city' => 'New York',
+ 'postal_code_suffix' => '4578',
+ 'postal_code' => '90210',
+ 'geo_code_1' => '48.858093',
+ 'geo_code_2' => '2.294694',
+ 'manual_geo_code' => TRUE,
+ 'address_name' => 'The white house',
+ 'master_id' => $this->callAPISuccess('Address', 'create', [
+ 'contact_id' => $this->individualCreate(),
+ 'street_address' => 'Street Address',
+ 'street_number' => '123',
+ 'street_number_suffix' => 'S',
+ 'street_name' => 'Main St',
+ 'street_unit' => '45B',
+ 'supplemental_address_1' => 'Round the corner',
+ 'supplemental_address_2' => 'Up the road',
+ 'supplemental_address_3' => 'By the big tree',
+ 'city' => 'New York',
+ 'postal_code_suffix' => '4578',
+ 'postal_code' => '90210',
+ 'location_type' => 'Main',
+ ])['id'],
+ 'county' => 'Harris County',
+ 'state_province' => 'Texas',
+ 'country' => 'United States',
+ 'phone' => '123-456',
+ 'phone_ext' => '77',
+ 'phone_type_id' => 'Mobile',
+ 'phone_type' => 'Mobile',
+ 'email' => 'anthony_anderson@civicrm.org',
+ 'on_hold' => FALSE,
+ 'signature_text' => 'Yours sincerely',
+ 'signature_html' => 'Yours
',
+ 'im_provider' => 'Yahoo',
+ 'im' => 'IM Screen Name',
+ 'openid' => 'OpenID',
+ 'world_region' => 'World Region',
+ 'url' => 'http://civicrm.org',
+ $this->getCustomFieldName('contact_reference') => $this->individualCreate(['first_name' => 'Spider', 'last_name' => 'Man']),
+ 'checksum' => 'Checksum',
+ 'contact_id' => $this->individualCreate(['first_name' => 'Peter', 'last_name' => 'Parker']),
+ ];
+ }
+
}