New in 2.2: Token hooks and Smarty templates for CiviMail

Published
2008-11-23 18:03
Written by
CiviMail is getting another major upgrade in 2.2. Some of the features coming in 2.2 include:
  • PHP return channel solution. This feature should enable more folks to install and run CiviMail. This will obsolete our amavisd return channel solution. No perl or sys admin / root access expertise needed any more :)
  • Adding CiviMail as an "action" to search results. This feature will allow folks to send a mailing to a "dynamic smart group" and alleviate the need to create multiple groups just for civimail purposes. Thus if you want to send a targeted mailing to all the folks in the 94135 zip code in your "we want more green trees" group, you enter the relevant search parameters (check the group and type the zip code), do the search and invoke the "Send Bulk Email" action. You can read more about this issue here
  • Adding token hooks and ability to send "templatized" emails. This is described in more detail below

Token hooks

A common request for the past few releases has been the ability to expose a diverse amount of contact information to the mailing text via tokens. We've exposed most of the fields in the contact table via tokens and also some of the primary address / email / phone information. We've stayed away from exposing any tables that have a many -> one relationship with the contact (i.e. contribution, membership, participant, relationship, activity tables etc). Querying and exposing this data is quite expensive from a sql perspective. Different users want different views of the data that should be exposed. For e.g. some folks might want just the latest contribution amount and date, while other folks would want the total amount and latest contribution date, while a third group might want the total amount contributed for that fiscal year. Similar to contribution data, users have also requested that membership data be exposed that they can use in renewal reminder and other emails. As with many of our other complex issues, the hook system once again comes to our rescue. We've introduced two new hooks: tokens and tokenValues. Each token is represented as a tokenCategory.tokenName (e.g: contact.display_name). We've retained the same protocol for external tokens. Having a category allows the use of the same token name (e.g. contact.display_name and event.display_name). The tokens hook modifies the tokens array and can append to the current category list. It also lists all the tokens associated with that category A sample implementation of this hook is:
function civicrm_civicrm_tokens( &$tokens ) {
    $tokens['contribution'] = array( 'contribution.amount', 'contribution.date' );
}
The tokenValues hook returns the values of all the tokens for the set of contactIDs passed in. This allows the code to be fairly efficient and do one query for a groups of ids rather than one query / contactID. The tokenValues function needs to distinguish when it is called with one contact ID vs an array of contact IDs. A sample tokenValues implementation for the contribution category which returns the total amount and the last contribution date is:
function civicrm_civicrm_tokenValues( &$values, &$contactIDs ) {
    if ( is_array( $contactIDs ) ) {
        $contactIDString = implode( ',', array_values( $contactIDs ) );
        $single = false;
    } else {
        $contactIDString = "( $contactIDs )";
        $single = true;
    }
    $query = "
SELECT sum( total_amount ) as total_amount,
       contact_id,
       max( receive_date ) as receive_date
FROM   civicrm_contribution 
WHERE  contact_id IN ( $contactIDString )
AND    is_test = 0
GROUP BY contact_id
";
    $dao = CRM_Core_DAO::executeQuery( $query );
    while ( $dao->fetch( ) ) {
        if ( $single ) {
            $value =& $values;
        } else {
            if ( ! array_key_exists( $dao->contact_id, $values ) ) {
                $values[$dao->contact_id] = array( );
            }
            $value =& $values[$dao->contact_id];
        }
        $value['contribution.amount'] = $dao->total_amount;
        $value['contribution.date'  ] = $dao->receive_date;
    }
}

Using smarty templates for sending email

Another common request is the ability to customize email based on the recipient. This is in addition to tokenization of the email. Users have requested the use of conditionals and other programming constructs in the email. For e.g. you might want to send a generic fundraising email, but have some parts different based on whether the contact has never contributed, has contributed in prior years but not this year, or is a consistent contributor. In v2.3 we will allow uploaded files to be smarty templates. We will also expose all the contact tokens as a smarty variable ($contact). This gives the power of smarty to the organization the ability to tailor the mail based on token values. Some example code that uses the above token hooks to illustrate the above ideas:
{if {contribution.amount} > 1000.00}
Thank you for being a major supporter of our organization. Our records indicate that over the last year you have contributed ${contribution.amount}. Your last contribution was on {contribution.date}.
{else  if {contribution.amount} > 0)
Thank you for supporting our organization. Our records indicate that over the last year you have contributed ${contribution.amount}. Your last contribution was on {contribution.date}.
{else}
Please make a contribution to our organization here.
{/if}
To enable the Smarty evaluation of a mail, you need to add the following line to civicrm.settings.php
  define( 'CIVICRM_MAIL_SMARTY', 1 );
The above features along with the PHP return channel solution feature, and the ability to CiviMail all the contacts from a search result make CiviMail an incredibly powerful solution :) A big thank you and tip of the hat to the great folks at U.S. PIRG and especially to Wes Morgan for sponsoring these features and being huge advocates of CiviCRM
Filed under

Comments

This is great, with these two features all the functional requirements that I am facing (at present) will be taken care of. Bravo and many thanks.

In my case smarty templates will serve for conditional formatting of addresses.

Now, questions... If I understand correctly the hooks will allow using of the tokens for the mailing text - which is great, but I can't help to think that immediately after making it available in the mailing it will become necessary to search by it...

In my example - this will allow me to easily expose a custom fields on the relationships to the mailings, but will not allow me to search by this field, AFAIK, in the advanced search.

the token hooks have nothing to do with search. However you can create your own custom search and expose those custom fields in your search which should give you sufficient power to do the things you need :)

lobo

Excellent feature. I found that the syntax shown above did not work in 3.1.3; instead of e.g.:

{if {contact.custom_277} ne ''}

I needed:

{if $contact.custom_277 ne ''}

Also the WYSIWYG editor will mess this up, even if using the HTML Source tool, so you need to use upload rather than compose on screen.

Dave J

Anonymous (not verified)
2013-02-19 - 20:02

Apologies for my ignorance but i am only learning the developer side of things. I have used your code and placed it in /www/civicrm_custom_code/plugins/rtla/joomla/rtla.php file

the code i place was

function civicrm_civicrm_tokens( &$tokens ){ .....

and

function civicrm_civicrm_tokenValues( &$values, &$contactIDs ) {.....
 

I then searched a contribution name, selected the checkbox, and Thank-you Letters for Contribtions > GO

I input to the Letter as a test

<p>
 Dear {contact.first_name},</p>
<p>
 Thank you for your recent contribution of ${$contribution.amount}.</p>

RESULT

The PDF that comes up does not return the contribution details.

i.e. Dear Michelle,

Thank you for your recent contribution of $.

 

Any help or advice would be very much appreciated.

Thanks for your time.