array(
'forward',
'optOut',
'optOutUrl',
'reply',
'unsubscribe',
'unsubscribeUrl',
'resubscribe',
'resubscribeUrl'
),
'mailing' => array(
'name',
'group'
),
'contact' => null, // populate this dynamically
'domain' => array(
'name',
'phone',
'address',
'email'
),
'subscribe' => array(
'group'
),
'unsubscribe' => array(
'group'
),
'resubscribe' => array(
'group'
),
'welcome' => array(
'group'
),
);
/**
* Check a string (mailing body) for required tokens.
*
* @param string $str The message
* @return true|array true if all required tokens are found,
* else an array of the missing tokens
* @access public
* @static
*/
public static function requiredTokens(&$str)
{
if (self::$_requiredTokens == null) {
self::$_requiredTokens = array (
'domain.address' => ts("Displays your organization's postal address."),
'action.optOut' =>
array(
'action.optOut' => ts("Creates a link for recipients to opt out of receiving emails from your organization."),
'action.optOutUrl' => ts("Creates a link for recipients to opt out of receiving emails from your organization."),
),
'action.unsubscribe' =>
array(
'action.unsubscribe' => ts("Creates a link for recipients to unsubscribe from the group(s) to which this mailing is being sent."),
'action.unsubscribeUrl' => ts("Creates a link for recipients to unsubscribe from the group(s) to which this mailing is being sent."),
),
);
}
$missing = array( );
foreach (self::$_requiredTokens as $token => $value) {
if ( ! is_array( $value ) ) {
if (! preg_match('/(^|[^\{])'.preg_quote('{' . $token . '}').'/', $str ) ) {
$missing[$token] = $value;
}
} else {
$present = false;
$desc = null;
foreach ( $value as $t => $d ) {
$desc = $d;
if ( preg_match('/(^|[^\{])'.preg_quote('{' . $t . '}').'/', $str ) ) {
$present = true;
}
}
if ( ! $present ) {
$missing[$token] = $desc;
}
}
}
if (empty($missing)) {
return true;
}
return $missing;
}
/**
* Wrapper for token matching
*
* @param string $type The token type (domain,mailing,contact,action)
* @param string $var The token variable
* @param string $str The string to search
* @return boolean Was there a match
* @access public
* @static
*/
public static function token_match($type, $var, &$str)
{
$token = preg_quote('{' . "$type.$var")
. '(\|.+?)?' . preg_quote('}');
return preg_match("/(^|[^\{])$token/", $str);
}
/**
* Wrapper for token replacing
*
* @param string $type The token type
* @param string $var The token variable
* @param string $value The value to substitute for the token
* @param string (reference) $str The string to replace in
* @return string The processed string
* @access public
* @static
*/
public static function &token_replace($type, $var, $value, &$str)
{
$token = preg_quote('{' . "$type.$var")
. '(\|([^\}]+?))?' . preg_quote('}');
if ( !$value ) {
$value = '$3';
}
$str = preg_replace("/([^\{])?$token/", "\${1}$value", $str);
return $str;
}
/**
* get the regex for token replacement
*
* @param string $key a string indicating the the type of token to be used in the expression
* @return string regular expression sutiable for using in preg_replace
* @access private
* @static
*/
private static function tokenRegex($token_type)
{
return '/(?getLocationValues();
if ( !in_array($token, self::$_tokens['domain']) ) {
$value = "{domain.$token}";
} else if ($token == 'address') {
static $addressCache = array();
$cache_key = $html ? 'address-html' : 'address-text';
if ( array_key_exists($cache_key, $addressCache) ) {
return $addressCache[$cache_key];
}
require_once 'CRM/Utils/Address.php';
$value = null;
/* Construct the address token */
if ( CRM_Utils_Array::value( $token, $loc ) ) {
$value = CRM_Utils_Address::format($loc[$token]);
if ($html) $value = str_replace("\n", '
', $value);
$addressCache[$cache_key] = $value;
}
} else if ( $token == 'name') {
$value = $domain->name;
} else if($token == 'phone' || $token == 'email'){
/* Construct the phone and email tokens */
$value = null;
if ( CRM_Utils_Array::value( $token, $loc ) ) {
foreach ($loc[$token] as $index => $entity) {
$value = $entity[$token];
break;
}
}
}
return $value;
}
/**
* Replace all the org-level tokens in $str
*
* @param string $str The string with tokens to be replaced
* @param object $org Associative array of org properties
* @param boolean $html Replace tokens with HTML or plain text
* @return string The processed string
* @access public
* @static
*/
public static function &replaceOrgTokens($str, &$org, $html = false) {
self::$_tokens['org'] =
array_merge( array_keys( CRM_Contact_BAO_Contact::importableFields( 'Organization' ) ),
array( 'address', 'display_name', 'checksum', 'contact_id' ) );
/*
print "org tokens:
";
print_r( $_tokens['org'] );
print "";
*/
$cv = null;
foreach (self::$_tokens['org'] as $token) {
// print "Getting token value for $token";
print_r( $org );
print "";
*/
$value = CRM_Contact_BAO_Contact::retrieveValue( $org, $token );
/*
print "\$value: ";
print_r( $value );
print "";
*/
}
self::token_replace('org', $token, $value, $str);
}
return $str;
}
/**
* Replace all mailing tokens in $str
*
* @param string $str The string with tokens to be replaced
* @param object $mailing The mailing BAO, or null for validation
* @param boolean $html Replace tokens with HTML or plain text
* @return string The processed sstring
* @access public
* @static
*/
public static function &replaceMailingTokens($str, &$mailing, $html = false, $knownTokens = null)
{
$key = 'mailing';
if ( ! $knownTokens ||
! isset( $knownTokens[$key] ) ) {
return $str;
}
$str = preg_replace(self::tokenRegex($key),'self::getMailingTokenReplacement(\'\\1\',$mailing)',$str);
return $str;
}
public static function getMailingTokenReplacement($token, &$mailing)
{
$value = '';
// 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
if (!in_array($token,self::$_tokens['mailing'])) {
$value = "{mailing.$token}";
} else if ($token == 'name') {
$value = $mailing ? $mailing->name : 'Mailing Name';
} else if ($token == 'group') {
$groups = $mailing ? $mailing->getGroupNames() : array('Mailing Groups');
$value = implode(', ', $groups);
}
return $value;
}
/**
* Replace all action tokens in $str
*
* @param string $str The string with tokens to be replaced
* @param array $addresses Assoc. array of VERP event addresses
* @param array $urls Assoc. array of action URLs
* @param boolean $html Replace tokens with HTML or plain text
* @param array $knownTokens A list of tokens that are known to exist in the email body
* @return string The processed string
* @access public
* @static
*/
public static function &replaceActionTokens($str, &$addresses, &$urls, $html = false, $knownTokens = null)
{
$key = 'action';
// here we intersect with the list of pre-configured valid tokens
// so that we remove anything we do not recognize
// I hope to move this step out of here soon and
// then we will just iterate on a list of tokens that are passed to us
if ( ! $knownTokens || ! $knownTokens[$key] ) {
return $str;
}
$str = preg_replace( self::tokenRegex($key),
'self::getActionTokenReplacement(\'\\1\',$addresses,$urls)',
$str);
return $str;
}
public static function getActionTokenReplacement($token, &$addresses, &$urls, $html = false)
{
/* If the token is an email action, use it. Otherwise, find the
* appropriate URL */
if ( !in_array( $token, self::$_tokens['action']) ) {
$value = "{action.$token}";
} else {
$value = CRM_Utils_Array::value($token, $addresses);
if ( $value == null ) {
$value = CRM_Utils_Array::value($token, $urls);
}
if ( $value && $html ) {
//fix for CRM-2318
if ( (substr( $token, -3 ) != 'Url') && ($token != 'forward') ) {
$value = "mailto:$value";
}
} else if ($value && !$html) {
$value = str_replace('&', '&', $value);
}
}
return $value;
}
/**
* Replace all the contact-level tokens in $str with information from
* $contact.
*
* @param string $str The string with tokens to be replaced
* @param array $contact Associative array of contact properties
* @param boolean $html Replace tokens with HTML or plain text
* @param boolean $html Replace tokens with HTML or plain text
* @param array $knownTokens A list of tokens that are known to exist in the email body
* @return string The processed string
* @access public
* @static
*/
public static function &replaceContactTokens($str, &$contact, $html = false, $knownTokens = null)
{
$key = 'contact';
if (self::$_tokens[$key] == null) {
/* This should come from UF */
self::$_tokens[$key] =
array_merge( array_keys(CRM_Contact_BAO_Contact::importableFields( ) ),
array( 'display_name', 'checksum', 'contact_id' ) );
}
// here we intersect with the list of pre-configured valid tokens
// so that we remove anything we do not recognize
// I hope to move this step out of here soon and
// then we will just iterate on a list of tokens that are passed to us
if(!$knownTokens || !$knownTokens[$key]) return $str;
$str = preg_replace(self::tokenRegex($key),'self::getContactTokenReplacement(\'\\1\', $contact, $html)',$str);
return $str;
}
public function getContactTokenReplacement($token, &$contact, $html = false)
{
if (self::$_tokens['contact'] == null) {
/* This should come from UF */
self::$_tokens['contact'] =
array_merge( array_keys(CRM_Contact_BAO_Contact::importableFields( ) ),
array( 'display_name', 'checksum', 'contact_id' ) );
}
/* Construct value from $token and $contact */
$value = null;
// 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
if (!in_array($token,self::$_tokens['contact'])) {
$value = "{contact.$token}";
} else if ( $token == 'checksum' ) {
$cs = CRM_Contact_BAO_Contact::generateChecksum( $contact['contact_id'] );
$value = "cs={$cs}";
} else {
$value = CRM_Contact_BAO_Contact::retrieveValue($contact, $token);
}
if (!$html) {
$value = str_replace('&', '&', $value);
}
return $value;
}
/**
* unescapeTokens removes any characters that caused the replacement routines to skip token replacement
* for example {{token}} or \{token} will result in {token} in the final email
*
* this routine will remove the extra backslashes and braces
*
* @param $str ref to the string that will be scanned and modified
* @return void this function works directly on the string that is passed
* @access public
* @static
*/
public static function unescapeTokens(&$str)
{
$str = preg_replace('/\\\\|\{(\{\w+\.\w+\})\}/','\\1',$str);
}
/**
* Replace unsubscribe tokens
*
* @param string $str the string with tokens to be replaced
* @param object $domain The domain BAO
* @param array $groups The groups (if any) being unsubscribed
* @param boolean $html Replace tokens with html or plain text
* @param int $contact_id The contact ID
* @param string hash The security hash of the unsub event
* @return string The processed string
* @access public
* @static
*/
public static function &replaceUnsubscribeTokens($str, &$domain, &$groups, $html,
$contact_id, $hash)
{
if (self::token_match('unsubscribe', 'group', $str)) {
if ( !empty($groups) ) {
$config =& CRM_Core_Config::singleton();
$base = CRM_Utils_System::baseURL();
// FIXME: an ugly hack for CRM-2035, to be dropped once CRM-1799 is implemented
require_once 'CRM/Contact/DAO/Group.php';
$dao =& new CRM_Contact_DAO_Group();
$dao->domain_id = $config->domainID();
$dao->find();
while ($dao->fetch()) {
if (substr($dao->visibility, 0, 6) == 'Public') {
$visibleGroups[] = $dao->id;
}
}
$value = implode(', ', $groups);
self::token_replace('unsubscribe', 'group', $value, $str);
}
}
return $str;
}
/**
* Replace resubscribe tokens
*
* @param string $str the string with tokens to be replaced
* @param object $domain The domain BAO
* @param array $groups The groups (if any) being resubscribed
* @param boolean $html Replace tokens with html or plain text
* @param int $contact_id The contact ID
* @param string hash The security hash of the resub event
* @return string The processed string
* @access public
* @static
*/
public static function &replaceResubscribeTokens($str, &$domain, &$groups, $html,
$contact_id, $hash)
{
if (self::token_match('resubscribe', 'group', $str)) {
if (! empty($groups)) {
$value = implode(', ', $groups);
self::token_replace('resubscribe', 'group', $value, $str);
}
}
return $str;
}
/**
* Replace subscription-confirmation-request tokens
*
* @param string $str The string with tokens to be replaced
* @param string $group The name of the group being subscribed
* @param boolean $html Replace tokens with html or plain text
* @return string The processed string
* @access public
* @static
*/
public static function &replaceSubscribeTokens($str, $group, $url, $html)
{
if (self::token_match('subscribe', 'group', $str)) {
self::token_replace('subscribe', 'group', $group, $str);
}
if (self::token_match('subscribe', 'url', $str)) {
self::token_replace('subscribe', 'url', $url, $str);
}
return $str;
}
/**
* Replace subscription-invitation tokens
*
* @param string $str The string with tokens to be replaced
* @return string The processed string
* @access public
* @static
*/
public static function &replaceSubscribeInviteTokens($str)
{
if (preg_match('/\{action\.subscribeUrl\}/', $str )) {
$url = CRM_Utils_System::url( 'civicrm/mailing/subscribe',
'reset=1',
true, null, true, true );
$str = preg_replace('/\{action\.subscribeUrl\}/', $url, $str );
}
if ( preg_match('/\{action\.subscribeUrl.\d+\}/', $str, $matches) ) {
foreach ( $matches as $key => $value ) {
$gid = substr($value, 21, -1);
$url = CRM_Utils_System::url( 'civicrm/mailing/subscribe',
"reset=1&gid={$gid}",
true, null, true, true );
$url = str_replace('&', '&', $url);
$str = preg_replace('/'.preg_quote($value).'/', $url, $str );
}
}
if ( preg_match('/\{action\.subscribe.\d+\}/', $str, $matches) ) {
foreach ( $matches as $key => $value ) {
$gid = substr($value, 18, -1);
$config =& CRM_Core_Config::singleton();
require_once 'CRM/Core/BAO/Domain.php';
$domain = CRM_Core_BAO_Domain::getDomainByID($config->domainID());
$str = preg_replace('/'.preg_quote($value).'/','mailto:subscribe.'.$domain->id.'.'.$gid.'@'.$domain->email_domain, $str);
}
}
return $str;
}
/**
* Replace welcome/confirmation tokens
*
* @param string $str The string with tokens to be replaced
* @param string $group The name of the group being subscribed
* @param boolean $html Replace tokens with html or plain text
* @return string The processed string
* @access public
* @static
*/
public static function &replaceWelcomeTokens($str, $group, $html)
{
if (self::token_match('welcome', 'group', $str)) {
self::token_replace('welcome', 'group', $group, $str);
}
return $str;
}
/**
* Find unprocessed tokens (call this last)
*
* @param string $str The string to search
* @return array Array of tokens that weren't replaced
* @access public
* @static
*/
public static function &unmatchedTokens(&$str)
{
//preg_match_all('/[^\{\\\\]\{(\w+\.\w+)\}[^\}]/', $str, $match);
preg_match_all('/\{(\w+\.\w+)\}/', $str, $match);
return $match[1];
}
}
?>