From 08f9b274280564ed5b64cc95cffcf4891fa81fd0 Mon Sep 17 00:00:00 2001 From: Chris Burgess Date: Fri, 16 Dec 2016 13:59:54 +1300 Subject: [PATCH] Restore contact greeting tokens in scheduled reminders. CRM-19757. In 4.6, it was possible to use `{contact.email_greeting}` in a scheduled reminder. This was no longer possible in 4.7. * Add tests to ensure greeting tokens are processed in `CRM_Core_BAO_ActionScheduleTest` * Modify `CRM_Core_BAO_ActionScheduleTest` to test additional tokens (limited room in message subject due to 128char DB limit) * Modify function signature of `CRM_Contact_BAO_Contact::create()` to return updated contact. * Better handle case where contact preferred_mail_format is unset in `CRM_Core_BAO_ActionSchedule::sendReminderEmail()` * Ensure greeting tokens are loaded in `CRM_Utils_Token::replaceGreetingTokens()` and `\Civi\Token\TokenCompatSubscriber::onEvaluate()` * Re-order processing of tokens in `\Civi\Token\TokenCompatSubscriber::onRender()` - restores order used in 4.6, allowing greeting tokens which contain contact tokens to be fully processed. --- CRM/Contact/BAO/Contact.php | 6 +++++- CRM/Core/BAO/ActionSchedule.php | 7 +++++++ CRM/Utils/Token.php | 4 ++++ Civi/Token/TokenCompatSubscriber.php | 19 ++++++++++++++--- .../CRM/Core/BAO/ActionScheduleTest.php | 21 ++++++++++++------- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/CRM/Contact/BAO/Contact.php b/CRM/Contact/BAO/Contact.php index 4a8b1ae71575..9c69683b5730 100644 --- a/CRM/Contact/BAO/Contact.php +++ b/CRM/Contact/BAO/Contact.php @@ -450,7 +450,7 @@ public static function &create(&$params, $fixAddress = TRUE, $invokeHooks = TRUE } // process greetings CRM-4575, cache greetings - self::processGreetings($contact); + $contact = self::processGreetings($contact); return $contact; } @@ -2707,6 +2707,7 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { ); $emailGreetingString = CRM_Core_DAO::escapeString(CRM_Utils_String::stripSpaces($emailGreetingString)); $updateQueryString[] = " email_greeting_display = '{$emailGreetingString}'"; + $contact->email_greeting_display = $emailGreetingString; } //postal greetings @@ -2743,6 +2744,7 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { ); $postalGreetingString = CRM_Core_DAO::escapeString(CRM_Utils_String::stripSpaces($postalGreetingString)); $updateQueryString[] = " postal_greeting_display = '{$postalGreetingString}'"; + $contact->postal_greeting_display = $postalGreetingString; } // addressee @@ -2787,6 +2789,8 @@ public static function processGreetings(&$contact, $useDefaults = FALSE) { $queryString = "UPDATE civicrm_contact SET {$updateQueryString} WHERE id = {$contact->id}"; CRM_Core_DAO::executeQuery($queryString); } + + return $contact; } /** diff --git a/CRM/Core/BAO/ActionSchedule.php b/CRM/Core/BAO/ActionSchedule.php index 0c1a52dab462..eba1b1ef00b6 100644 --- a/CRM/Core/BAO/ActionSchedule.php +++ b/CRM/Core/BAO/ActionSchedule.php @@ -622,6 +622,13 @@ protected static function sendReminderEmail($tokenRow, $schedule, $toContactID) 'entity_id' => $schedule->id, ); + if (isset($tokenRow->context['contact'])) { + if (!isset($tokenRow->context['contact']['preferred_mail_format']) || + $tokenRow->context['contact']['preferred_mail_format'] === NULL) { + $tokenRow->context['contact']['preferred_mail_format'] = 'Both'; + } + } + if (!$body_html || $tokenRow->context['contact']['preferred_mail_format'] == 'Text' || $tokenRow->context['contact']['preferred_mail_format'] == 'Both' ) { diff --git a/CRM/Utils/Token.php b/CRM/Utils/Token.php index a23e9f3313d1..9cd21cb6b603 100644 --- a/CRM/Utils/Token.php +++ b/CRM/Utils/Token.php @@ -1443,6 +1443,10 @@ public static function replaceGreetingTokens(&$tokenString, $contactDetails = NU $greetingTokens = self::getTokens($tokenString); if (!empty($greetingTokens)) { + // Some greetings are required. + $greetingTokens[] = 'email_greeting'; + $greetingTokens[] = 'postal_greeting'; + // first use the existing contact object for token replacement if (!empty($contactDetails)) { $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString, $contactDetails, TRUE, $greetingTokens, TRUE, $escapeSmarty); diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php index 3eaf57d3b7cc..fe59caef0720 100644 --- a/Civi/Token/TokenCompatSubscriber.php +++ b/Civi/Token/TokenCompatSubscriber.php @@ -54,7 +54,17 @@ public function onEvaluate(TokenValueEvent $e) { $params = array( array('contact_id', '=', $contactId, 0, 0), ); - list($contact, $_) = \CRM_Contact_BAO_Query::apiQuery($params); + // Pass required contact tokens in. + if (isset($messageTokens['contact'])) { + $contactTokens = array(); + foreach ($messageTokens['contact'] as $name) { + $contactTokens[$name] = 1; + } + } + else { + $contactTokens = NULL; + } + list($contact, $_) = \CRM_Contact_BAO_Query::apiQuery($params, $contactTokens); $contact = reset($contact); //CRM-4524 if (!$contact || is_a($contact, 'CRM_Core_Error')) { // FIXME: Need to differentiate errors which kill the batch vs the individual row. @@ -103,12 +113,15 @@ public function onRender(TokenRenderEvent $e) { $e->string = \CRM_Utils_Token::replaceDomainTokens($e->string, \CRM_Core_BAO_Domain::getDomain(), $isHtml, $e->message['tokens'], $useSmarty); if (!empty($e->context['contact'])) { + // ::replaceGreetingTokens() may return "Dear {contact.first_name}" so + // must precede ::replaceContactTokens. + // + // Note also inconsistent function signatures - see CRM-19768. + \CRM_Utils_Token::replaceGreetingTokens($e->string, NULL, $e->context['contact']['contact_id'], NULL, $useSmarty); $e->string = \CRM_Utils_Token::replaceContactTokens($e->string, $e->context['contact'], $isHtml, $e->message['tokens'], FALSE, $useSmarty); // FIXME: This may depend on $contact being merged with hook values. $e->string = \CRM_Utils_Token::replaceHookTokens($e->string, $e->context['contact'], $e->context['hookTokenCategories'], $isHtml, $useSmarty); - - \CRM_Utils_Token::replaceGreetingTokens($e->string, NULL, $e->context['contact']['contact_id'], NULL, $useSmarty); } if ($useSmarty) { diff --git a/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php b/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php index 5d5df84fb468..8399a39919ba 100644 --- a/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php +++ b/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php @@ -95,6 +95,8 @@ public function setUp() { 'contact_type' => 'Individual', 'email' => 'test-member@example.com', 'gender_id' => 'Female', + 'first_name' => 'Churmondleia', + 'last_name' => 'Ōtākou', ); $this->fixtures['contact_birthdate'] = array( 'is_deceased' => 0, @@ -603,7 +605,6 @@ public function setUp() { */ public function tearDown() { parent::tearDown(); - $this->mut->clearMessages(); $this->mut->stop(); unset($this->mut); @@ -621,22 +622,29 @@ public function tearDown() { public function mailerExamples() { $cases = array(); - $manyTokensTmpl = implode(';;', array( + // Some tokens - short as subject has 128char limit in DB. + $someTokensTmpl = implode(';;', array( '{contact.display_name}', // basic contact token '{contact.gender}', // funny legacy contact token '{contact.gender_id}', // funny legacy contact token '{domain.name}', // domain token '{activity.activity_type}', // action-scheduler token )); + // Further tokens can be tested in the body text/html. + $manyTokensTmpl = implode(';;', array( + $someTokensTmpl, + '{contact.email_greeting}', + )); // Note: The behavior of domain-tokens on a scheduled reminder is undefined. All we // can really do is check that it has something. - $manyTokensExpected = 'test-member@example.com;;Female;;Female;;[a-zA-Z0-9 ]+;;Phone Call'; + $someTokensExpected = 'Churmondleia Ōtākou;;Female;;Female;;[a-zA-Z0-9 ]+;;Phone Call'; + $manyTokensExpected = "$someTokensExpected;;Dear Churmondleia"; - // In this example, we use a lot of tokens cutting across multiple components.. + // In this example, we use a lot of tokens cutting across multiple components. $cases[0] = array( // Schedule definition. array( - 'subject' => "subj $manyTokensTmpl", + 'subject' => "subj $someTokensTmpl", 'body_html' => "html $manyTokensTmpl", 'body_text' => "text $manyTokensTmpl", ), @@ -644,7 +652,7 @@ public function mailerExamples() { array( 'from_name' => "/^FIXME\$/", 'from_email' => "/^info@EXAMPLE.ORG\$/", - 'subject' => "/^subj $manyTokensExpected\$/", + 'subject' => "/^subj $someTokensExpected\$/", 'body_html' => "/^html $manyTokensExpected\$/", 'body_text' => "/^text $manyTokensExpected\$/", ), @@ -755,7 +763,6 @@ public function testMailer($schedule, $patterns) { } } $this->mut->clearMessages(); - } public function testActivityDateTimeMatchNonRepeatableSchedule() {