<?php/* +--------------------------------------------------------------------+ | CiviCRM version 2.0 | +--------------------------------------------------------------------+ | Copyright CiviCRM LLC (c) 2004-2007 | +--------------------------------------------------------------------+ | This file is a part of CiviCRM. | | | | CiviCRM is free software; you can copy, modify, and distribute it | | under the terms of the GNU Affero General Public License | | Version 3, 19 November 2007. | | | | CiviCRM is distributed in the hope that it will be useful, but | | WITHOUT ANY WARRANTY; without even the implied warranty of | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | | See the GNU Affero General Public License for more details. | | | | You should have received a copy of the GNU Affero General Public | | License along with this program; if not, contact CiviCRM LLC | | at info[AT]civicrm[DOT]org. If you have questions about the | | GNU Affero General Public License or the licensing of CiviCRM, | | see the CiviCRM license FAQ at http://civicrm.org/licensing | +--------------------------------------------------------------------+*//** * * @package CRM * @copyright CiviCRM LLC (c) 2004-2007 * $Id$ * *//** * Class to abstract token replacement */class CRM_Utils_Token{ static $_requiredTokens = null; static $_tokens = array( 'action' => 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 '/(?<!\{|\\\\)\{'.$token_type.'\.(\w+)\}(?!\})/e'; } /** * Replace all the domain-level tokens in $str * * @param string $str The string with tokens to be replaced * @param object $domain The domain BAO * @param boolean $html Replace tokens with HTML or plain text * @return string The processed string * @access public * @static */ public static function &replaceDomainTokens($str, &$domain, $html = false, $knownTokens = null) { $key = 'domain'; if ( ! $knownTokens || ! CRM_Utils_Array::value( $key, $knownTokens ) ) { return $str; } $str = preg_replace(self::tokenRegex($key),'self::getDomainTokenReplacement(\'\\1\',$domain,$html)',$str); return $str; } public static function getDomainTokenReplacement($token, &$domain, $html = false) { // 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 $loc =& $domain->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", '<br />', $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: <pre>"; print_r( $_tokens['org'] ); print "</pre>"; */ $cv = null; foreach (self::$_tokens['org'] as $token) { // print "Getting token value for $token<br/><br/>"; if ($token == '') { continue; } /* If the string doesn't contain this token, skip it. */ if (! self::token_match('org', $token, $str)) { continue; } /* Construct value from $token and $contact */ $value = null; if ($cfID = CRM_Core_BAO_CustomField::getKeyID($token)) { // only generate cv if we need it if ( $cv === null ) { $cv =& CRM_Core_BAO_CustomValue::getContactValues($org['contact_id']); } foreach ($cv as $cvFieldID => $value ) { if ($cvFieldID == $cfID) { $value = CRM_Core_BAO_CustomOption::getOptionLabel($cfID, $value ); break; } } } else if ( $token == 'checksum' ) { $cs = CRM_Contact_BAO_Contact::generateChecksum( $org['contact_id'] ); $value = "cs={$cs}"; } else if ( $token == 'address' ) { /* Build the location values array */ $loc = array( ); $loc['display_name'] = CRM_Contact_BAO_Contact::retrieveValue( $org, 'display_name' ); $loc['street_address'] = CRM_Contact_BAO_Contact::retrieveValue( $org, 'street_address' ); $loc['city'] = CRM_Contact_BAO_Contact::retrieveValue( $org, 'city' ); $loc['state_province'] = CRM_Contact_BAO_Contact::retrieveValue( $org, 'state_province' ); $loc['postal_code'] = CRM_Contact_BAO_Contact::retrieveValue( $org, 'postal_code' ); /* Construct the address token */ $value = CRM_Utils_Address::format( $loc ); if ($html) $value = str_replace( "\n", '<br />', $value ); } else { /* print "\$org: <pre>"; print_r( $org ); print "</pre>"; */ $value = CRM_Contact_BAO_Contact::retrieveValue( $org, $token ); /* print "\$value: <pre>"; print_r( $value ); print "</pre>"; */ } 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]; }}?>