Merge pull request #247 from pradpnayak/CRM-12192
[civicrm-core.git] / CRM / Utils / Token.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2013
32 * $Id: $
33 *
34 */
35
36/**
37 * Class to abstract token replacement
38 */
39class CRM_Utils_Token {
40 static $_requiredTokens = NULL;
41
42 static $_tokens = array(
43 'action' => array(
44 'forward',
45 'optOut',
46 'optOutUrl',
47 'reply',
48 'unsubscribe',
49 'unsubscribeUrl',
50 'resubscribe',
51 'resubscribeUrl',
52 'subscribeUrl',
53 ),
54 'mailing' => array(
55 'id',
56 'name',
57 'group',
58 'subject',
59 'viewUrl',
60 'editUrl',
61 'scheduleUrl',
62 'approvalStatus',
63 'approvalNote',
64 'approveUrl',
65 'creator',
66 'creatorEmail',
67 ),
68 'user' => array(
69 // we extract the stuff after the role / permission and return the
70 // civicrm email addresses of all users with that role / permission
71 // useful with rules integration
72 'permission:',
73 'role:',
74 ),
75 // populate this dynamically
76 'contact' => NULL,
77 // populate this dynamically
78 'contribution' => NULL,
79 'domain' => array(
80 'name',
81 'phone',
82 'address',
83 'email',
84 ),
85 'subscribe' => array( 'group' ),
86 'unsubscribe' => array( 'group' ),
87 'resubscribe' => array( 'group' ),
88 'welcome' => array( 'group' ),
89 );
90
91 /**
92 * Check a string (mailing body) for required tokens.
93 *
94 * @param string $str The message
95 *
96 * @return true|array true if all required tokens are found,
97 * else an array of the missing tokens
98 * @access public
99 * @static
100 */
101 public static function requiredTokens(&$str) {
102 if (self::$_requiredTokens == NULL) {
103 self::$_requiredTokens = array(
104 'domain.address' => ts("Domain address - displays your organization's postal address."),
105 'action.optOutUrl or action.subscribeUrl' =>
106 array(
107 'action.optOut' => ts("'Opt out via email' - displays an email address for recipients to opt out of receiving emails from your organization."),
108 'action.optOutUrl' => ts("'Opt out via web page' - creates a link for recipients to click if they want to opt out of receiving emails from your organization. Alternatively, you can include the 'Opt out via email' token."),
109 'action.unsubscribe' => ts("'Unsubscribe via email' - displays an email address for recipients to unsubscribe from the specific mailing list used to send this message."),
110 'action.unsubscribe' => ts("'Unsubscribe via web page' - creates a link for recipients to unsubscribe from the specific mailing list used to send this message. Alternatively, you can include the 'Unsubscribe via email' token or one of the Opt-out tokens."),
111 ),
112 );
113 }
114
115 $missing = array();
116 foreach (self::$_requiredTokens as $token => $value) {
117 if (!is_array($value)) {
118 if (!preg_match('/(^|[^\{])' . preg_quote('{' . $token . '}') . '/', $str)) {
119 $missing[$token] = $value;
120 }
121 }
122 else {
123 $present = FALSE;
124 $desc = NULL;
125 foreach ($value as $t => $d) {
126 $desc = $d;
127 if (preg_match('/(^|[^\{])' . preg_quote('{' . $t . '}') . '/', $str)) {
128 $present = TRUE;
129 }
130 }
131 if (!$present) {
132 $missing[$token] = $desc;
133 }
134 }
135 }
136
137 if (empty($missing)) {
138 return TRUE;
139 }
140 return $missing;
141 }
142
143 /**
144 * Wrapper for token matching
145 *
146 * @param string $type The token type (domain,mailing,contact,action)
147 * @param string $var The token variable
148 * @param string $str The string to search
149 *
150 * @return boolean Was there a match
151 * @access public
152 * @static
153 */
154 public static function token_match($type, $var, &$str) {
155 $token = preg_quote('{' . "$type.$var") . '(\|.+?)?' . preg_quote('}');
156 return preg_match("/(^|[^\{]) $token/", $str);
157 }
158
159 /**
160 * Wrapper for token replacing
161 *
162 * @param string $type The token type
163 * @param string $var The token variable
164 * @param string $value The value to substitute for the token
165 * @param string (reference) $str The string to replace in
166 *
167 * @return string The processed string
168 * @access public
169 * @static
170 */
171 public static function &token_replace($type, $var, $value, &$str, $escapeSmarty = FALSE) {
172 $token = preg_quote('{' . "$type.$var") . '(\|([^\}]+?))?' . preg_quote('}');
173 if (!$value) {
174 $value = '$3';
175 }
176 if ($escapeSmarty) {
177 $value = self::tokenEscapeSmarty($value);
178 }
179 $str = preg_replace("/([^\{])?$token/", "\${1}$value", $str);
180 return $str;
181 }
182
183 /**
184 * get< the regex for token replacement
185 *
186 * @param string $key a string indicating the the type of token to be used in the expression
187 *
188 * @return string regular expression sutiable for using in preg_replace
189 * @access private
190 * @static
191 */
192 private static function tokenRegex($token_type) {
193 return '/(?<!\{|\\\\)\{' . $token_type . '\.([\w]+(\-[\w\s]+)?)\}(?!\})/e';
194 }
195
196 /**
197 * escape the string so a malicious user cannot inject smarty code into the template
198 *
199 * @param string $string a string that needs to be escaped from smarty parsing
200 *
201 * @return string the escaped string
202 * @access private
203 * @static
204 */
205 private static function tokenEscapeSmarty($string) {
206 // need to use negative look-behind, as both str_replace() and preg_replace() are sequential
207 return preg_replace(array('/{/', '/(?<!{ldelim)}/'), array('{ldelim}', '{rdelim}'), $string);
208 }
209
210 /**
211 /**
212 * Replace all the domain-level tokens in $str
213 *
214 * @param string $str The string with tokens to be replaced
215 * @param object $domain The domain BAO
216 * @param boolean $html Replace tokens with HTML or plain text
217 *
218 * @return string The processed string
219 * @access public
220 * @static
221 */
222 public static function &replaceDomainTokens($str, &$domain, $html = FALSE, $knownTokens = NULL, $escapeSmarty = FALSE) {
223 $key = 'domain';
224 if (!$knownTokens ||
225 !CRM_Utils_Array::value($key, $knownTokens)
226 ) {
227 return $str;
228 }
229
230 $str = preg_replace(self::tokenRegex($key), 'self::getDomainTokenReplacement(\'\\1\',$domain,$html)', $str);
231 return $str;
232 }
233
234 public static function getDomainTokenReplacement($token, &$domain, $html = FALSE, $escapeSmarty = FALSE) {
235 // check if the token we were passed is valid
236 // we have to do this because this function is
237 // called only when we find a token in the string
238
239 $loc = &$domain->getLocationValues();
240
241 if (!in_array($token, self::$_tokens['domain'])) {
242 $value = "{domain.$token}";
243 }
244 elseif ($token == 'address') {
245 static $addressCache = array();
246
247 $cache_key = $html ? 'address-html' : 'address-text';
248 if (array_key_exists($cache_key, $addressCache)) {
249 return $addressCache[$cache_key];
250 }
251
252 $value = NULL;
253 /* Construct the address token */
254
255 if (CRM_Utils_Array::value($token, $loc)) {
256 if ($html) {
257 $value = $loc[$token][1]['display'];
258 $value = str_replace("\n", '<br />', $value);
259 }
260 else {
261 $value = $loc[$token][1]['display_text'];
262 }
263 $addressCache[$cache_key] = $value;
264 }
265 }
266 elseif ($token == 'name' || $token == 'id') {
267 $value = $domain->$token;
268 }
269 elseif ($token == 'phone' || $token == 'email') {
270 /* Construct the phone and email tokens */
271
272 $value = NULL;
273 if (CRM_Utils_Array::value($token, $loc)) {
274 foreach ($loc[$token] as $index => $entity) {
275 $value = $entity[$token];
276 break;
277 }
278 }
279 }
280
281 if ($escapeSmarty) {
282 $value = self::tokenEscapeSmarty($value);
283 }
284
285 return $value;
286 }
287
288 /**
289 * Replace all the org-level tokens in $str
290 *
291 * @param string $str The string with tokens to be replaced
292 * @param object $org Associative array of org properties
293 * @param boolean $html Replace tokens with HTML or plain text
294 *
295 * @return string The processed string
296 * @access public
297 * @static
298 */
299 public static function &replaceOrgTokens($str, &$org, $html = FALSE, $escapeSmarty = FALSE) {
300 self::$_tokens['org'] = array_merge(array_keys(CRM_Contact_BAO_Contact::importableFields('Organization')),
301 array('address', 'display_name', 'checksum', 'contact_id')
302 );
303
304 $cv = NULL;
305 foreach (self::$_tokens['org'] as $token) {
306 // print "Getting token value for $token<br/><br/>";
307 if ($token == '') {
308 continue;
309 }
310
311 /* If the string doesn't contain this token, skip it. */
312
313 if (!self::token_match('org', $token, $str)) {
314 continue;
315 }
316
317 /* Construct value from $token and $contact */
318
319 $value = NULL;
320
321 if ($cfID = CRM_Core_BAO_CustomField::getKeyID($token)) {
322 // only generate cv if we need it
323 if ($cv === NULL) {
324 $cv = CRM_Core_BAO_CustomValue::getContactValues($org['contact_id']);
325 }
326 foreach ($cv as $cvFieldID => $value) {
327 if ($cvFieldID == $cfID) {
328 $value = CRM_Core_BAO_CustomOption::getOptionLabel($cfID, $value);
329 break;
330 }
331 }
332 }
333 elseif ($token == 'checksum') {
334 $cs = CRM_Contact_BAO_Contact_Utils::generateChecksum($org['contact_id']);
335 $value = "cs={$cs}";
336 }
337 elseif ($token == 'address') {
338 /* Build the location values array */
339
340 $loc = array();
341 $loc['display_name'] = CRM_Utils_Array::retrieveValueRecursive($org, 'display_name');
342 $loc['street_address'] = CRM_Utils_Array::retrieveValueRecursive($org, 'street_address');
343 $loc['city'] = CRM_Utils_Array::retrieveValueRecursive($org, 'city');
344 $loc['state_province'] = CRM_Utils_Array::retrieveValueRecursive($org, 'state_province');
345 $loc['postal_code'] = CRM_Utils_Array::retrieveValueRecursive($org, 'postal_code');
346
347 /* Construct the address token */
348
349 $value = CRM_Utils_Address::format($loc);
350 if ($html) {
351 $value = str_replace("\n", '<br />', $value);
352 }
353 }
354 else {
355 $value = CRM_Utils_Array::retrieveValueRecursive($org, $token);
356 }
357
358 self::token_replace('org', $token, $value, $str, $escapeSmarty);
359 }
360
361 return $str;
362 }
363
364 /**
365 * Replace all mailing tokens in $str
366 *
367 * @param string $str The string with tokens to be replaced
368 * @param object $mailing The mailing BAO, or null for validation
369 * @param boolean $html Replace tokens with HTML or plain text
370 *
371 * @return string The processed sstring
372 * @access public
373 * @static
374 */
375 public static function &replaceMailingTokens($str, &$mailing, $html = FALSE, $knownTokens = NULL, $escapeSmarty = FALSE) {
376 $key = 'mailing';
377 if (!$knownTokens || !isset($knownTokens[$key])) {
378 return $str;
379 }
380
381 $str = preg_replace(
382 self::tokenRegex($key),
383 'self::getMailingTokenReplacement(\'\\1\',$mailing,$escapeSmarty)', $str
384 );
385 return $str;
386 }
387
388 public static function getMailingTokenReplacement($token, &$mailing, $escapeSmarty = FALSE) {
389 $value = '';
390 switch ($token) {
391 // CRM-7663
392
393 case 'id':
394 $value = $mailing ? $mailing->id : 'undefined';
395 break;
396
397 case 'name':
398 $value = $mailing ? $mailing->name : 'Mailing Name';
399 break;
400
401 case 'group':
402 $groups = $mailing ? $mailing->getGroupNames() : array('Mailing Groups');
403 $value = implode(', ', $groups);
404 break;
405
406 case 'subject':
407 $value = $mailing->subject;
408 break;
409
410 case 'viewUrl':
411 $value = CRM_Utils_System::url('civicrm/mailing/view',
412 "reset=1&id={$mailing->id}",
413 TRUE, NULL, FALSE, TRUE
414 );
415 break;
416
417 case 'editUrl':
418 $value = CRM_Utils_System::url('civicrm/mailing/send',
419 "reset=1&mid={$mailing->id}&continue=true",
420 TRUE, NULL, FALSE, TRUE
421 );
422 break;
423
424 case 'scheduleUrl':
425 $value = CRM_Utils_System::url('civicrm/mailing/schedule',
426 "reset=1&mid={$mailing->id}",
427 TRUE, NULL, FALSE, TRUE
428 );
429 break;
430
431 case 'html':
432 $page = new CRM_Mailing_Page_View();
433 $value = $page->run($mailing->id, NULL, FALSE);
434 break;
435
436 case 'approvalStatus':
437 $mailApprovalStatus = CRM_Mailing_PseudoConstant::approvalStatus();
438 $value = $mailApprovalStatus[$mailing->approval_status_id];
439 break;
440
441 case 'approvalNote':
442 $value = $mailing->approval_note;
443 break;
444
445 case 'approveUrl':
446 $value = CRM_Utils_System::url('civicrm/mailing/approve',
447 "reset=1&mid={$mailing->id}",
448 TRUE, NULL, FALSE, TRUE
449 );
450 break;
451
452 case 'creator':
453 $value = CRM_Contact_BAO_Contact::displayName($mailing->created_id);
454 break;
455
456 case 'creatorEmail':
457 $value = CRM_Contact_BAO_Contact::getPrimaryEmail($mailing->created_id);
458 break;
459
460 default:
461 $value = "{mailing.$token}";
462 break;
463 }
464
465 if ($escapeSmarty) {
466 $value = self::tokenEscapeSmarty($value);
467 }
468 return $value;
469 }
470
471 /**
472 * Replace all action tokens in $str
473 *
474 * @param string $str The string with tokens to be replaced
475 * @param array $addresses Assoc. array of VERP event addresses
476 * @param array $urls Assoc. array of action URLs
477 * @param boolean $html Replace tokens with HTML or plain text
478 * @param array $knownTokens A list of tokens that are known to exist in the email body
479 *
480 * @return string The processed string
481 * @access public
482 * @static
483 */
484 public static function &replaceActionTokens(
485 $str,
486 &$addresses,
487 &$urls,
488 $html = FALSE,
489 $knownTokens = NULL,
490 $escapeSmarty = FALSE
491 ) {
492 $key = 'action';
493 // here we intersect with the list of pre-configured valid tokens
494 // so that we remove anything we do not recognize
495 // I hope to move this step out of here soon and
496 // then we will just iterate on a list of tokens that are passed to us
497 if (!$knownTokens || !CRM_Utils_Array::value($key, $knownTokens)) {
498 return $str;
499 }
500
501 $str = preg_replace(self::tokenRegex($key),
502 'self::getActionTokenReplacement(\'\\1\',$addresses,$urls,$escapeSmarty)',
503 $str
504 );
505 return $str;
506 }
507
508 public static function getActionTokenReplacement($token, &$addresses, &$urls, $html = FALSE, $escapeSmarty = FALSE) {
509 /* If the token is an email action, use it. Otherwise, find the
510 * appropriate URL */
511
512 if (!in_array($token, self::$_tokens['action'])) {
513 $value = "{action.$token}";
514 }
515 else {
516 $value = CRM_Utils_Array::value($token, $addresses);
517
518 if ($value == NULL) {
519 $value = CRM_Utils_Array::value($token, $urls);
520 }
521
522 if ($value && $html) {
523 //fix for CRM-2318
524 if ((substr($token, -3) != 'Url') && ($token != 'forward')) {
525 $value = "mailto:$value";
526 }
527 }
528 elseif ($value && !$html) {
529 $value = str_replace('&amp;', '&', $value);
530 }
531 }
532
533 if ($escapeSmarty) {
534 $value = self::tokenEscapeSmarty($value);
535 }
536 return $value;
537 }
538
539 /**
540 * Replace all the contact-level tokens in $str with information from
541 * $contact.
542 *
543 * @param string $str The string with tokens to be replaced
544 * @param array $contact Associative array of contact properties
545 * @param boolean $html Replace tokens with HTML or plain text
546 * @param array $knownTokens A list of tokens that are known to exist in the email body
547 * @param boolean $returnBlankToken return unevaluated token if value is null
548 *
549 * @return string The processed string
550 * @access public
551 * @static
552 */
553 public static function &replaceContactTokens($str, &$contact, $html = FALSE, $knownTokens = NULL,
554 $returnBlankToken = FALSE, $escapeSmarty = FALSE
555 ) {
556 $key = 'contact';
557 if (self::$_tokens[$key] == NULL) {
558 /* This should come from UF */
559
560 self::$_tokens[$key] = array_merge(array_keys(CRM_Contact_BAO_Contact::exportableFields('All')),
561 array('checksum', 'contact_id')
562 );
563 }
564
565 // here we intersect with the list of pre-configured valid tokens
566 // so that we remove anything we do not recognize
567 // I hope to move this step out of here soon and
568 // then we will just iterate on a list of tokens that are passed to us
569 if (!$knownTokens || !CRM_Utils_Array::value($key, $knownTokens)) {
570 return $str;
571 }
572
573 $str = preg_replace(self::tokenRegex($key),
574 'self::getContactTokenReplacement(\'\\1\', $contact, $html, $returnBlankToken, $escapeSmarty)',
575 $str
576 );
577
578 $str = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
579 return $str;
580 }
581
582 public static function getContactTokenReplacement($token, &$contact, $html = FALSE,
583 $returnBlankToken = FALSE, $escapeSmarty = FALSE
584 ) {
585 if (self::$_tokens['contact'] == NULL) {
586 /* This should come from UF */
587
588 self::$_tokens['contact'] = array_merge(array_keys(CRM_Contact_BAO_Contact::exportableFields('All')),
589 array('checksum', 'contact_id')
590 );
591 }
592
593 /* Construct value from $token and $contact */
594
595 $value = NULL;
596
597 // check if the token we were passed is valid
598 // we have to do this because this function is
599 // called only when we find a token in the string
600
601 if (!in_array($token, self::$_tokens['contact'])) {
602 $value = "{contact.$token}";
603 }
604 elseif ($token == 'checksum') {
605 $hash = CRM_Utils_Array::value('hash', $contact);
606 $contactID = CRM_Utils_Array::retrieveValueRecursive($contact, 'contact_id');
607 $cs = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID,
608 NULL,
609 NULL,
610 $hash
611 );
612 $value = "cs={$cs}";
613 }
614 else {
615 $value = CRM_Utils_Array::retrieveValueRecursive($contact, $token);
616 }
617
618 if (!$html) {
619 $value = str_replace('&amp;', '&', $value);
620 }
621
622 // if null then return actual token
623 if ($returnBlankToken && !$value) {
624 $value = "{contact.$token}";
625 }
626
627 if ($escapeSmarty) {
628 $value = self::tokenEscapeSmarty($value);
629 }
630
631 return $value;
632 }
633
634 /**
635 * Replace all the hook tokens in $str with information from
636 * $contact.
637 *
638 * @param string $str The string with tokens to be replaced
639 * @param array $contact Associative array of contact properties (including hook token values)
640 * @param boolean $html Replace tokens with HTML or plain text
641 *
642 * @return string The processed string
643 * @access public
644 * @static
645 */
646 public static function &replaceHookTokens($str, &$contact, &$categories, $html = FALSE, $escapeSmarty = FALSE) {
647
648 foreach ($categories as $key) {
649 $str = preg_replace(self::tokenRegex($key),
650 'self::getHookTokenReplacement(\'\\1\', $contact, $key, $html, $escapeSmarty)',
651 $str
652 );
653 }
654 return $str;
655 }
656
657 public static function getHookTokenReplacement($token, &$contact, $category, $html = FALSE, $escapeSmarty = FALSE) {
658 $value = CRM_Utils_Array::value("{$category}.{$token}", $contact);
659
660 if ($value &&
661 !$html
662 ) {
663 $value = str_replace('&amp;', '&', $value);
664 }
665
666 if ($escapeSmarty) {
667 $value = self::tokenEscapeSmarty($value);
668 }
669
670 return $value;
671 }
672
673 /**
674 * unescapeTokens removes any characters that caused the replacement routines to skip token replacement
675 * for example {{token}} or \{token} will result in {token} in the final email
676 *
677 * this routine will remove the extra backslashes and braces
678 *
679 * @param $str ref to the string that will be scanned and modified
680 * @return void this function works directly on the string that is passed
681 * @access public
682 * @static
683 */
684 public static function unescapeTokens(&$str) {
685 $str = preg_replace('/\\\\|\{(\{\w+\.\w+\})\}/', '\\1', $str);
686 }
687
688 /**
689 * Replace unsubscribe tokens
690 *
691 * @param string $str the string with tokens to be replaced
692 * @param object $domain The domain BAO
693 * @param array $groups The groups (if any) being unsubscribed
694 * @param boolean $html Replace tokens with html or plain text
695 * @param int $contact_id The contact ID
696 * @param string hash The security hash of the unsub event
697 *
698 * @return string The processed string
699 * @access public
700 * @static
701 */
702 public static function &replaceUnsubscribeTokens(
703 $str,
704 &$domain,
705 &$groups,
706 $html,
707 $contact_id,
708 $hash
709 ) {
710 if (self::token_match('unsubscribe', 'group', $str)) {
711 if (!empty($groups)) {
712 $config = CRM_Core_Config::singleton();
713 $base = CRM_Utils_System::baseURL();
714
715 // FIXME: an ugly hack for CRM-2035, to be dropped once CRM-1799 is implemented
716 $dao = new CRM_Contact_DAO_Group();
717 $dao->find();
718 while ($dao->fetch()) {
719 if (substr($dao->visibility, 0, 6) == 'Public') {
720 $visibleGroups[] = $dao->id;
721 }
722 }
723 $value = implode(', ', $groups);
724 self::token_replace('unsubscribe', 'group', $value, $str);
725 }
726 }
727 return $str;
728 }
729
730 /**
731 * Replace resubscribe tokens
732 *
733 * @param string $str the string with tokens to be replaced
734 * @param object $domain The domain BAO
735 * @param array $groups The groups (if any) being resubscribed
736 * @param boolean $html Replace tokens with html or plain text
737 * @param int $contact_id The contact ID
738 * @param string hash The security hash of the resub event
739 *
740 * @return string The processed string
741 * @access public
742 * @static
743 */
744 public static function &replaceResubscribeTokens($str, &$domain, &$groups, $html,
745 $contact_id, $hash
746 ) {
747 if (self::token_match('resubscribe', 'group', $str)) {
748 if (!empty($groups)) {
749 $value = implode(', ', $groups);
750 self::token_replace('resubscribe', 'group', $value, $str);
751 }
752 }
753 return $str;
754 }
755
756 /**
757 * Replace subscription-confirmation-request tokens
758 *
759 * @param string $str The string with tokens to be replaced
760 * @param string $group The name of the group being subscribed
761 * @param boolean $html Replace tokens with html or plain text
762 *
763 * @return string The processed string
764 * @access public
765 * @static
766 */
767 public static function &replaceSubscribeTokens($str, $group, $url, $html) {
768 if (self::token_match('subscribe', 'group', $str)) {
769 self::token_replace('subscribe', 'group', $group, $str);
770 }
771 if (self::token_match('subscribe', 'url', $str)) {
772 self::token_replace('subscribe', 'url', $url, $str);
773 }
774 return $str;
775 }
776
777 /**
778 * Replace subscription-invitation tokens
779 *
780 * @param string $str The string with tokens to be replaced
781 *
782 * @return string The processed string
783 * @access public
784 * @static
785 */
786 public static function &replaceSubscribeInviteTokens($str) {
787 if (preg_match('/\{action\.subscribeUrl\}/', $str)) {
788 $url = CRM_Utils_System::url('civicrm/mailing/subscribe',
789 'reset=1',
790 TRUE, NULL, TRUE, TRUE
791 );
792 $str = preg_replace('/\{action\.subscribeUrl\}/', $url, $str);
793 }
794
795 if (preg_match('/\{action\.subscribeUrl.\d+\}/', $str, $matches)) {
796 foreach ($matches as $key => $value) {
797 $gid = substr($value, 21, -1);
798 $url = CRM_Utils_System::url('civicrm/mailing/subscribe',
799 "reset=1&gid={$gid}",
800 TRUE, NULL, TRUE, TRUE
801 );
802 $url = str_replace('&amp;', '&', $url);
803 $str = preg_replace('/' . preg_quote($value) . '/', $url, $str);
804 }
805 }
806
807 if (preg_match('/\{action\.subscribe.\d+\}/', $str, $matches)) {
808 foreach ($matches as $key => $value) {
809 $gid = substr($value, 18, -1);
810 $config = CRM_Core_Config::singleton();
811 $domain = CRM_Core_BAO_MailSettings::defaultDomain();
812 $localpart = CRM_Core_BAO_MailSettings::defaultLocalpart();
813 // we add the 0.0000000000000000 part to make this match the other email patterns (with action, two ids and a hash)
814 $str = preg_replace('/' . preg_quote($value) . '/', "mailto:{$localpart}s.{$gid}.0.0000000000000000@$domain", $str);
815 }
816 }
817 return $str;
818 }
819
820 /**
821 * Replace welcome/confirmation tokens
822 *
823 * @param string $str The string with tokens to be replaced
824 * @param string $group The name of the group being subscribed
825 * @param boolean $html Replace tokens with html or plain text
826 *
827 * @return string The processed string
828 * @access public
829 * @static
830 */
831 public static function &replaceWelcomeTokens($str, $group, $html) {
832 if (self::token_match('welcome', 'group', $str)) {
833 self::token_replace('welcome', 'group', $group, $str);
834 }
835 return $str;
836 }
837
838 /**
839 * Find unprocessed tokens (call this last)
840 *
841 * @param string $str The string to search
842 *
843 * @return array Array of tokens that weren't replaced
844 * @access public
845 * @static
846 */
847 public static function &unmatchedTokens(&$str) {
848 //preg_match_all('/[^\{\\\\]\{(\w+\.\w+)\}[^\}]/', $str, $match);
849 preg_match_all('/\{(\w+\.\w+)\}/', $str, $match);
850 return $match[1];
851 }
852
853 /**
854 * Find and replace tokens for each component
855 *
856 * @param string $str The string to search
857 * @param array $contact Associative array of contact properties
858 * @param array $components A list of tokens that are known to exist in the email body
859 *
860 * @return string The processed string
861 * @access public
862 * @static
863 */
864 public static function &replaceComponentTokens(&$str, $contact, $components, $escapeSmarty = FALSE, $returnEmptyToken = TRUE) {
865 if (!is_array($components) || empty($contact)) {
866 return $str;
867 }
868
869 foreach ($components as $name => $tokens) {
870 if (!is_array($tokens) || empty($tokens)) {
871 continue;
872 }
873
874 foreach ($tokens as $token) {
875 if (self::token_match($name, $token, $str) && isset($contact[$name . '.' . $token])) {
876 self::token_replace($name, $token, $contact[$name . '.' . $token], $str, $escapeSmarty);
877 }
878 elseif (!$returnEmptyToken) {
879 //replacing empty token
880 self::token_replace($name, $token, "", $str, $escapeSmarty);
881 }
882 }
883 }
884 return $str;
885 }
886
887 /**
888 * Get array of string tokens
889 *
890 * @param $string the input string to parse for tokens
891 *
892 * @return $tokens array of tokens mentioned in field
893 * @access public
894 * @static
895 */
896 static function getTokens($string) {
897 $matches = array();
898 $tokens = array();
899 preg_match_all('/(?<!\{|\\\\)\{(\w+\.\w+)\}(?!\})/',
900 $string,
901 $matches,
902 PREG_PATTERN_ORDER
903 );
904
905 if ($matches[1]) {
906 foreach ($matches[1] as $token) {
907 list($type, $name) = preg_split('/\./', $token, 2);
908 if ($name && $type) {
909 if (!isset($tokens[$type])) {
910 $tokens[$type] = array();
911 }
912 $tokens[$type][] = $name;
913 }
914 }
915 }
916 return $tokens;
917 }
918
919 /**
920 * gives required details of contacts in an indexed array format so we
921 * can iterate in a nice loop and do token evaluation
922 *
923 * @param array $contactIds of contacts
924 * @param array $returnProperties of required properties
925 * @param boolean $skipOnHold don't return on_hold contact info also.
926 * @param boolean $skipDeceased don't return deceased contact info.
927 * @param array $extraParams extra params
928 * @param array $tokens the list of tokens we've extracted from the content
929 * @param int $jobID the mailing list jobID - this is a legacy param
930 *
931 * @return array
932 * @access public
933 * @static
934 */
935 static function getTokenDetails($contactIDs,
936 $returnProperties = NULL,
937 $skipOnHold = TRUE,
938 $skipDeceased = TRUE,
939 $extraParams = NULL,
940 $tokens = array(),
941 $className = NULL,
942 $jobID = NULL
943 ) {
944 if (empty($contactIDs)) {
945 // putting a fatal here so we can track if/when this happens
946 CRM_Core_Error::fatal();
947 }
948
949 $params = array();
950 foreach ($contactIDs as $key => $contactID) {
951 $params[] = array(
952 CRM_Core_Form::CB_PREFIX . $contactID,
953 '=', 1, 0, 0,
954 );
955 }
956
957 // fix for CRM-2613
958 if ($skipDeceased) {
959 $params[] = array('is_deceased', '=', 0, 0, 0);
960 }
961
962 //fix for CRM-3798
963 if ($skipOnHold) {
964 $params[] = array('on_hold', '=', 0, 0, 0);
965 }
966
967 if ($extraParams) {
968 $params = array_merge($params, $extraParams);
969 }
970
971 // if return properties are not passed then get all return properties
972 if (empty($returnProperties)) {
973 $fields = array_merge(array_keys(CRM_Contact_BAO_Contact::exportableFields()),
974 array('display_name', 'checksum', 'contact_id')
975 );
976 foreach ($fields as $key => $val) {
977 $returnProperties[$val] = 1;
978 }
979 }
980
981 $custom = array();
982 foreach ($returnProperties as $name => $dontCare) {
983 $cfID = CRM_Core_BAO_CustomField::getKeyID($name);
984 if ($cfID) {
985 $custom[] = $cfID;
986 }
987 }
988
989 //get the total number of contacts to fetch from database.
990 $numberofContacts = count($contactIDs);
991 $query = new CRM_Contact_BAO_Query($params, $returnProperties);
992
993 $details = $query->apiQuery($params, $returnProperties, NULL, NULL, 0, $numberofContacts);
994
995 $contactDetails = &$details[0];
996
997 foreach ($contactIDs as $key => $contactID) {
998 if (array_key_exists($contactID, $contactDetails)) {
999 if (CRM_Utils_Array::value('preferred_communication_method', $returnProperties) == 1
1000 && array_key_exists('preferred_communication_method', $contactDetails[$contactID])
1001 ) {
1002 $pcm = CRM_Core_PseudoConstant::pcm();
1003
1004 // communication Prefferance
1005 $contactPcm = explode(CRM_Core_DAO::VALUE_SEPARATOR,
1006 $contactDetails[$contactID]['preferred_communication_method']
1007 );
1008 $result = array();
1009 foreach ($contactPcm as $key => $val) {
1010 if ($val) {
1011 $result[$val] = $pcm[$val];
1012 }
1013 }
1014 $contactDetails[$contactID]['preferred_communication_method'] = implode(', ', $result);
1015 }
1016
1017 foreach ($custom as $cfID) {
1018 if (isset($contactDetails[$contactID]["custom_{$cfID}"])) {
1019 $contactDetails[$contactID]["custom_{$cfID}"] = CRM_Core_BAO_CustomField::getDisplayValue($contactDetails[$contactID]["custom_{$cfID}"],
1020 $cfID, $details[1]
1021 );
1022 }
1023 }
1024
1025 //special case for greeting replacement
1026 foreach (array(
1027 'email_greeting', 'postal_greeting', 'addressee') as $val) {
1028 if (CRM_Utils_Array::value($val, $contactDetails[$contactID])) {
1029 $contactDetails[$contactID][$val] = $contactDetails[$contactID]["{$val}_display"];
1030 }
1031 }
1032 }
1033 }
1034
1035 // also call a hook and get token details
1036 CRM_Utils_Hook::tokenValues($details[0],
1037 $contactIDs,
1038 $jobID,
1039 $tokens,
1040 $className
1041 );
1042 return $details;
1043 }
1044
1045 /**
1046 * gives required details of contribuion in an indexed array format so we
1047 * can iterate in a nice loop and do token evaluation
1048 *
1049 * @param array $contributionId one contribution id
1050 * @param array $returnProperties of required properties
1051 * @param boolean $skipOnHold don't return on_hold contact info.
1052 * @param boolean $skipDeceased don't return deceased contact info.
1053 * @param array $extraParams extra params
1054 * @param array $tokens the list of tokens we've extracted from the content
1055 *
1056 * @return array
1057 * @access public
1058 * @static
1059 */
1060 static function getContributionTokenDetails($contributionIDs,
1061 $returnProperties = NULL,
1062 $extraParams = NULL,
1063 $tokens = array(),
1064 $className = NULL
1065 ) {
1066 if (empty($contributionIDs)) {
1067 // putting a fatal here so we can track if/when this happens
1068 CRM_Core_Error::fatal();
1069 }
1070
1071 $details = array();
1072
1073 // no apiQuery helper yet, so do a loop and find contribution by id
1074 foreach ($contributionIDs as $contributionID) {
1075
1076 $dao = new CRM_Contribute_DAO_Contribution();
1077 $dao->id = $contributionID;
1078
1079 if ($dao->find(TRUE)) {
1080
1081 $details[$dao->id] = array();
1082 CRM_Core_DAO::storeValues($dao, $details[$dao->id]);
1083
1084 // do the necessary transformation
1085 if (CRM_Utils_Array::value('payment_instrument_id', $details[$dao->id])) {
1086 $piId = $details[$dao->id]['payment_instrument_id'];
1087 $pis = CRM_Contribute_PseudoConstant::paymentInstrument();
1088 $details[$dao->id]['payment_instrument'] = $pis[$piId];
1089 }
1090 if (CRM_Utils_Array::value('campaign_id', $details[$dao->id])) {
1091 $campaignId = $details[$dao->id]['campaign_id'];
1092 $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns($campaignId);
1093 $details[$dao->id]['campaign'] = $campaigns[$campaignId];
1094 }
1095
1096 // TODO: call a hook to get token contribution details
1097 }
1098 }
1099
1100 return $details;
1101 }
1102
1103 /**
1104 * replace greeting tokens exists in message/subject
1105 *
1106 * @access public
1107 */
1108 static function replaceGreetingTokens(&$tokenString, $contactDetails = NULL, $contactId = NULL, $className = NULL) {
1109
1110 if (!$contactDetails && !$contactId) {
1111 return;
1112 }
1113
1114 // check if there are any tokens
1115 $greetingTokens = self::getTokens($tokenString);
1116
1117 if (!empty($greetingTokens)) {
1118 // first use the existing contact object for token replacement
1119 if (!empty($contactDetails)) {
1120 $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString, $contactDetails, TRUE, $greetingTokens, TRUE);
1121 }
1122
1123 // check if there are any unevaluated tokens
1124 $greetingTokens = self::getTokens($tokenString);
1125
1126 // $greetingTokens not empty, means there are few tokens which are not evaluated, like custom data etc
1127 // so retrieve it from database
1128 if (!empty($greetingTokens) && array_key_exists('contact', $greetingTokens)) {
1129 $greetingsReturnProperties = array_flip(CRM_Utils_Array::value('contact', $greetingTokens));
1130 $greetingsReturnProperties = array_fill_keys(array_keys($greetingsReturnProperties), 1);
1131 $contactParams = array('contact_id' => $contactId);
1132
1133 $greetingDetails = self::getTokenDetails($contactParams,
1134 $greetingsReturnProperties,
1135 FALSE, FALSE, NULL,
1136 $greetingTokens,
1137 $className
1138 );
1139
1140 // again replace tokens
1141 $tokenString = CRM_Utils_Token::replaceContactTokens($tokenString,
1142 $greetingDetails,
1143 TRUE,
1144 $greetingTokens
1145 );
1146 }
1147 }
1148 }
1149
1150 static function flattenTokens(&$tokens) {
1151 $flattenTokens = array();
1152
1153 foreach (array(
1154 'html', 'text', 'subject') as $prop) {
1155 if (!isset($tokens[$prop])) {
1156 continue;
1157 }
1158 foreach ($tokens[$prop] as $type => $names) {
1159 if (!isset($flattenTokens[$type])) {
1160 $flattenTokens[$type] = array();
1161 }
1162 foreach ($names as $name) {
1163 $flattenTokens[$type][$name] = 1;
1164 }
1165 }
1166 }
1167
1168 return $flattenTokens;
1169 }
1170
1171 /**
1172 * Replace all user tokens in $str
1173 *
1174 * @param string $str The string with tokens to be replaced
1175 *
1176 * @return string The processed string
1177 * @access public
1178 * @static
1179 */
1180 public static function &replaceUserTokens($str, $knownTokens = NULL, $escapeSmarty = FALSE) {
1181 $key = 'user';
1182 if (!$knownTokens ||
1183 !isset($knownTokens[$key])
1184 ) {
1185 return $str;
1186 }
1187
1188 $str = preg_replace(self::tokenRegex($key),
1189 'self::getUserTokenReplacement(\'\\1\',$escapeSmarty)', $str
1190 );
1191 return $str;
1192 }
1193
1194 public static function getUserTokenReplacement($token, $escapeSmarty = FALSE) {
1195 $value = '';
1196
1197 list($objectName, $objectValue) = explode('-', $token, 2);
1198
1199 switch ($objectName) {
1200 case 'permission':
1201 $value = CRM_Core_Permission::permissionEmails($objectValue);
1202 break;
1203
1204 case 'role':
1205 $value = CRM_Core_Permission::roleEmails($objectValue);
1206 break;
1207 }
1208
1209 if ($escapeSmarty) {
1210 $value = self::tokenEscapeSmarty($value);
1211 }
1212
1213 return $value;
1214 }
1215
1216
1217 protected static function _buildContributionTokens() {
1218 $key = 'contribution';
1219 if (self::$_tokens[$key] == NULL) {
1220 self::$_tokens[$key] = array_keys(array_merge(CRM_Contribute_BAO_Contribution::exportableFields('All'),
1221 array('campaign', 'financial_type')
1222 ));
1223 }
1224 }
1225
1226 public static function &replaceContributionTokens($str, &$contribution, $html = FALSE, $knownTokens = NULL, $escapeSmarty = FALSE) {
1227 self::_buildContributionTokens();
1228
1229 // here we intersect with the list of pre-configured valid tokens
1230 // so that we remove anything we do not recognize
1231 // I hope to move this step out of here soon and
1232 // then we will just iterate on a list of tokens that are passed to us
1233 $key = 'contribution';
1234 if (!$knownTokens || !CRM_Utils_Array::value($key, $knownTokens)) {
1235 return $str;
1236 }
1237
1238 $str = preg_replace(self::tokenRegex($key),
1239 'self::getContributionTokenReplacement(\'\\1\', $contribution, $html, $escapeSmarty)',
1240 $str
1241 );
1242
1243 $str = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
1244 return $str;
1245 }
1246
1247 public static function getContributionTokenReplacement($token, &$contribution, $html = FALSE, $escapeSmarty = FALSE) {
1248 self::_buildContributionTokens();
1249
1250 switch ($token) {
1251 case 'total_amount':
1252 case 'net_amount':
1253 case 'fee_amount':
1254 case 'non_deductible_amount':
1255 $value = CRM_Utils_Money::format(CRM_Utils_Array::retrieveValueRecursive($contribution, $token));
1256 break;
1257
1258 case 'receive_date':
1259 $value = CRM_Utils_Array::retrieveValueRecursive($contribution, $token);
1260 $value = CRM_Utils_Date::customFormat($value, NULL, array('j', 'm', 'Y'));
1261 break;
1262
1263 default:
1264 if (!in_array($token, self::$_tokens['contribution'])) {
1265 $value = "{contribution.$token}";
1266 }
1267 else {
1268 $value = CRM_Utils_Array::retrieveValueRecursive($contribution, $token);
1269 }
1270 break;
1271 }
1272
1273
1274 if ($escapeSmarty) {
1275 $value = self::tokenEscapeSmarty($value);
1276 }
1277 return $value;
1278 }
1279
1280 function getPermissionEmails($permissionName) {}
1281
1282 function getRoleEmails($roleName) {}
1283}
1284