From 73d64eb695adbf17d25e1714226e519b4a6e155b Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 15 Jul 2013 08:12:39 +0200 Subject: [PATCH] Process greeting templates with Smarty When expanding `email_greeting`, `postal_greeting`, and `addressee` templates, after substituting the CiviCRM tokens, pass the result through Smarty. This way it becomes possible to include conditionals (or other types of fancy processing) in greeting templates. Example: {capture assign=style}{contact.communication_style}{/capture} {capture assign=prefix}{contact.individual_prefix}{/capture} {if $style=="Familiar"} {if $prefix=="Frau"} Liebe {elseif $prefix=="Herr"} Lieber {else} Liebe/r {/if} {contact.first_name} {else} {if $prefix=="Frau"} Liebe Frau {elseif $prefix=="Herr"} Lieber Herr {else} Liebe/r Herr oder Frau {/if} {contact.formal_title} {contact.last_name} {/if} The major limitation of this approach is that the `label` field -- where the actual greeting templates are stored -- is limited to 255 characters. (So the above example wouldn't actually work without some trickery to make it shorter...) Not sure how to address this problem. We are wondering whether we should hide this feature behind a config option, like it is done for Mailings? I'm not sure this is really necessary here, as the Smarty processing shouldn't be in anyone's way when unused. The only possible downside is that templates with syntax errors might produce somewhat obscure warnings or errors in some cases. However, the greeting templates are generally only set up during the implementation phase, and never touched by mortal users -- just like system workflow message templates, which use Smarty unconditionally as well. Also, it feels wrong to hide such useful functionality behind some obscure option... To allow for proper Smarty escaping, this patch needs to introduce some small changes to the way contact tokens are processed in the greetings. These shouldn't affect other callers. To avoid code duplication, a new function is introduced for the template processing (tokens+Smarty), adding another layer of indirection. This could be avoided, if replaceGreetingTokens() wasn't used in other contexts too. (Which I believe is a misunderstanding: I'm pretty sure the function was only meant for *generating* the greeting texts, by replacing tokens in the greeting templates when saving a contact record; and not for replacing greeting tokens in message templates -- the latter should be handled just fine along with the other contact tokens...) --- CRM/Contact/BAO/Contact.php | 6 +++--- CRM/Contact/BAO/Contact/Utils.php | 28 +++++++++++++++++++++++++++- CRM/Utils/Token.php | 18 +++++++++++++----- bin/deprecated/UpdateGreeting.php | 2 +- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/CRM/Contact/BAO/Contact.php b/CRM/Contact/BAO/Contact.php index c6dc298c0d..5306c580f1 100644 --- a/CRM/Contact/BAO/Contact.php +++ b/CRM/Contact/BAO/Contact.php @@ -2551,7 +2551,7 @@ AND civicrm_openid.is_primary = 1"; } if ($emailGreetingString) { - CRM_Utils_Token::replaceGreetingTokens($emailGreetingString, + CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($emailGreetingString, $contactDetails, $contact->id, 'CRM_Contact_BAO_Contact' @@ -2587,7 +2587,7 @@ AND civicrm_openid.is_primary = 1"; } if ($postalGreetingString) { - CRM_Utils_Token::replaceGreetingTokens($postalGreetingString, + CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($postalGreetingString, $contactDetails, $contact->id, 'CRM_Contact_BAO_Contact' @@ -2624,7 +2624,7 @@ AND civicrm_openid.is_primary = 1"; } if ($addresseeString) { - CRM_Utils_Token::replaceGreetingTokens($addresseeString, + CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($addresseeString, $contactDetails, $contact->id, 'CRM_Contact_BAO_Contact' diff --git a/CRM/Contact/BAO/Contact/Utils.php b/CRM/Contact/BAO/Contact/Utils.php index 3e82aafeee..97eb060c32 100644 --- a/CRM/Contact/BAO/Contact/Utils.php +++ b/CRM/Contact/BAO/Contact/Utils.php @@ -1035,7 +1035,7 @@ Group By componentId"; } } - CRM_Utils_Token::replaceGreetingTokens($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting'); + self::processGreetingTemplate($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting'); $greetingString = CRM_Core_DAO::escapeString($greetingString); $cacheFieldQuery .= " WHEN {$contactID} THEN '{$greetingString}' "; @@ -1089,5 +1089,31 @@ WHERE id IN (" . implode(',', $contactIds) . ")"; return current($id); } } + + /** + * Process a greeting template string to produce the individualised greeting text. + * + * This works just like message templates for mailings: + * the template is processed with the token substitution mechanism, + * to supply the individual contact data; + * and it is also processed with Smarty, + * to allow for conditionals etc. based on the contact data. + * + * Note: We don't pass any variables to Smarty -- + * all variable data is inserted into the input string + * by the token substitution mechanism, + * before Smarty is invoked. + * + * @param string $templateString the greeting template string with contact tokens + Smarty syntax + * + * @return void + * @static + */ + static function processGreetingTemplate(&$templateString, $contactDetails, $contactID, $className) { + CRM_Utils_Token::replaceGreetingTokens($templateString, $contactDetails, $contactID, $className, TRUE); + + $smarty = CRM_Core_Smarty::singleton(); + $templateString = $smarty->fetch("string:$templateString"); + } } diff --git a/CRM/Utils/Token.php b/CRM/Utils/Token.php index 4d459f93b7..ed5e490bbe 100644 --- a/CRM/Utils/Token.php +++ b/CRM/Utils/Token.php @@ -643,6 +643,7 @@ class CRM_Utils_Token { /* Construct value from $token and $contact */ $value = NULL; + $noReplace = FALSE; // Support legacy tokens $token = CRM_Utils_Array::value($token, self::legacyContactTokens(), $token); @@ -652,7 +653,7 @@ class CRM_Utils_Token { // called only when we find a token in the string if (!in_array($token, self::$_tokens['contact'])) { - $value = "{contact.$token}"; + $noReplace = TRUE; } elseif ($token == 'checksum') { $hash = CRM_Utils_Array::value('hash', $contact); @@ -686,10 +687,15 @@ class CRM_Utils_Token { // if null then return actual token if ($returnBlankToken && !$value) { + $noReplace = TRUE; + } + + if ($noReplace) { $value = "{contact.$token}"; } - if ($escapeSmarty) { + if ($escapeSmarty + && !($returnBlankToken && $noReplace)) { // $returnBlankToken means the caller wants to do further attempts at processing unreplaced tokens -- so don't escape them yet in this case. $value = self::tokenEscapeSmarty($value); } @@ -1212,7 +1218,7 @@ class CRM_Utils_Token { * * @access public */ - static function replaceGreetingTokens(&$tokenString, $contactDetails = NULL, $contactId = NULL, $className = NULL) { + static function replaceGreetingTokens(&$tokenString, $contactDetails = NULL, $contactId = NULL, $className = NULL, $escapeSmarty = FALSE) { if (!$contactDetails && !$contactId) { return; @@ -1224,7 +1230,7 @@ class CRM_Utils_Token { if (!empty($greetingTokens)) { // first use the existing contact object for token replacement if (!empty($contactDetails)) { - $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString, $contactDetails, TRUE, $greetingTokens, TRUE); + $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString, $contactDetails, TRUE, $greetingTokens, TRUE, $escapeSmarty); } // check if there are any unevaluated tokens @@ -1248,7 +1254,9 @@ class CRM_Utils_Token { $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString, $greetingDetails, TRUE, - $greetingTokens + $greetingTokens, + FALSE, + $escapeSmarty ); } } diff --git a/bin/deprecated/UpdateGreeting.php b/bin/deprecated/UpdateGreeting.php index 5f35d2f053..c47027de25 100644 --- a/bin/deprecated/UpdateGreeting.php +++ b/bin/deprecated/UpdateGreeting.php @@ -202,7 +202,7 @@ SELECT DISTINCT id, $idFldName $contactIds[] = $contactID; } } - CRM_Utils_Token::replaceGreetingTokens($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting'); + CRM_Contact_BAO_Contact_Utils::processGreetingTemplate($greetingString, $contactDetails, $contactID, 'CRM_UpdateGreeting'); $greetingString = CRM_Core_DAO::escapeString($greetingString); $cacheFieldQuery .= " WHEN {$contactID} THEN '{$greetingString}' "; -- 2.25.1