Merge pull request #23698 from seamuslee001/fix_price_set_entity_since
[civicrm-core.git] / CRM / Utils / Token.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035 11
58b954cf
EM
12use Civi\Token\TokenProcessor;
13
6a488035
TO
14/**
15 *
16 * @package CRM
ca5cec67 17 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
18 */
19
20/**
b8c71ffa 21 * Class to abstract token replacement.
6a488035
TO
22 */
23class CRM_Utils_Token {
6714d8d2 24 public static $_requiredTokens = NULL;
6a488035 25
6714d8d2 26 public static $_tokens = [
be2fb01f 27 'action' => [
6a488035
TO
28 'forward',
29 'optOut',
30 'optOutUrl',
31 'reply',
32 'unsubscribe',
33 'unsubscribeUrl',
34 'resubscribe',
35 'resubscribeUrl',
36 'subscribeUrl',
be2fb01f
CW
37 ],
38 'mailing' => [
6a488035 39 'id',
b56b8b0e 40 'key',
6a488035
TO
41 'name',
42 'group',
43 'subject',
44 'viewUrl',
45 'editUrl',
46 'scheduleUrl',
47 'approvalStatus',
48 'approvalNote',
49 'approveUrl',
50 'creator',
51 'creatorEmail',
be2fb01f
CW
52 ],
53 'user' => [
6a488035
TO
54 // we extract the stuff after the role / permission and return the
55 // civicrm email addresses of all users with that role / permission
56 // useful with rules integration
57 'permission:',
58 'role:',
be2fb01f 59 ],
6a488035
TO
60 // populate this dynamically
61 'contact' => NULL,
62 // populate this dynamically
63 'contribution' => NULL,
be2fb01f 64 'domain' => [
6a488035
TO
65 'name',
66 'phone',
67 'address',
68 'email',
e3470b79 69 'id',
70 'description',
be2fb01f
CW
71 ],
72 'subscribe' => ['group'],
73 'unsubscribe' => ['group'],
74 'resubscribe' => ['group'],
75 'welcome' => ['group'],
76 ];
6a488035
TO
77
78 /**
dfbda5e5
TO
79 * @deprecated
80 * This is used by CiviMail but will be made redundant by FlexMailer.
5f56e085 81 * @return array
6a488035 82 */
5f56e085 83 public static function getRequiredTokens() {
6a488035 84 if (self::$_requiredTokens == NULL) {
be2fb01f 85 self::$_requiredTokens = [
6a488035 86 'domain.address' => ts("Domain address - displays your organization's postal address."),
be2fb01f 87 'action.optOutUrl or action.unsubscribeUrl' => [
6a488035
TO
88 'action.optOut' => ts("'Opt out via email' - displays an email address for recipients to opt out of receiving emails from your organization."),
89 '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."),
90 'action.unsubscribe' => ts("'Unsubscribe via email' - displays an email address for recipients to unsubscribe from the specific mailing list used to send this message."),
d72434db 91 'action.unsubscribeUrl' => 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."),
be2fb01f
CW
92 ],
93 ];
6a488035 94 }
5f56e085
TO
95 return self::$_requiredTokens;
96 }
97
98 /**
99 * Check a string (mailing body) for required tokens.
100 *
101 * @param string $str
102 * The message.
103 *
104 * @return bool|array
6714d8d2 105 * true if all required tokens are found,
5f56e085
TO
106 * else an array of the missing tokens
107 */
108 public static function requiredTokens(&$str) {
b845ba46 109 // FlexMailer is a refactoring of CiviMail which provides new hooks/APIs/docs. If the sysadmin has opted to enable it, then use that instead of CiviMail.
be2fb01f 110 $requiredTokens = defined('CIVICRM_FLEXMAILER_HACK_REQUIRED_TOKENS') ? Civi\Core\Resolver::singleton()->call(CIVICRM_FLEXMAILER_HACK_REQUIRED_TOKENS, []) : CRM_Utils_Token::getRequiredTokens();
6a488035 111
be2fb01f 112 $missing = [];
5f56e085 113 foreach ($requiredTokens as $token => $value) {
6a488035
TO
114 if (!is_array($value)) {
115 if (!preg_match('/(^|[^\{])' . preg_quote('{' . $token . '}') . '/', $str)) {
116 $missing[$token] = $value;
117 }
118 }
119 else {
120 $present = FALSE;
121 $desc = NULL;
122 foreach ($value as $t => $d) {
123 $desc = $d;
124 if (preg_match('/(^|[^\{])' . preg_quote('{' . $t . '}') . '/', $str)) {
125 $present = TRUE;
126 }
127 }
128 if (!$present) {
129 $missing[$token] = $desc;
130 }
131 }
132 }
133
134 if (empty($missing)) {
135 return TRUE;
136 }
137 return $missing;
138 }
139
140 /**
fe482240 141 * Wrapper for token matching.
6a488035 142 *
77855840
TO
143 * @param string $type
144 * The token type (domain,mailing,contact,action).
145 * @param string $var
146 * The token variable.
147 * @param string $str
148 * The string to search.
6a488035 149 *
e7483cbe 150 * @return bool
a6c01b45 151 * Was there a match
6a488035
TO
152 */
153 public static function token_match($type, $var, &$str) {
154 $token = preg_quote('{' . "$type.$var") . '(\|.+?)?' . preg_quote('}');
93f087c7 155 return preg_match("/(^|[^\{])$token/", $str);
6a488035
TO
156 }
157
158 /**
fe482240 159 * Wrapper for token replacing.
6a488035 160 *
77855840
TO
161 * @param string $type
162 * The token type.
163 * @param string $var
164 * The token variable.
165 * @param string $value
166 * The value to substitute for the token.
6714d8d2 167 * @param string $str (reference) The string to replace in
e39893f5
EM
168 *
169 * @param bool $escapeSmarty
6a488035 170 *
a6c01b45
CW
171 * @return string
172 * The processed string
6a488035 173 */
40022216 174 public static function token_replace($type, $var, $value, &$str, $escapeSmarty = FALSE) {
6a488035
TO
175 $token = preg_quote('{' . "$type.$var") . '(\|([^\}]+?))?' . preg_quote('}');
176 if (!$value) {
177 $value = '$3';
178 }
179 if ($escapeSmarty) {
180 $value = self::tokenEscapeSmarty($value);
181 }
182 $str = preg_replace("/([^\{])?$token/", "\${1}$value", $str);
183 return $str;
184 }
185
186 /**
ad06f98f 187 * Get the regex for token replacement
6a488035 188 *
77855840
TO
189 * @param string $token_type
190 * A string indicating the the type of token to be used in the expression.
6a488035 191 *
a6c01b45 192 * @return string
ad06f98f 193 * regular expression suitable for using in preg_replace
6a488035 194 */
ad06f98f
EM
195 private static function tokenRegex(string $token_type) {
196 return '/(?<!\{|\\\\)\{' . $token_type . '\.([\w]+(:|\.)?\w*(\-[\w\s]+)?)\}(?!\})/';
6a488035
TO
197 }
198
199 /**
fe482240 200 * Escape the string so a malicious user cannot inject smarty code into the template.
6a488035 201 *
77855840
TO
202 * @param string $string
203 * A string that needs to be escaped from smarty parsing.
6a488035 204 *
a6c01b45
CW
205 * @return string
206 * the escaped string
6a488035 207 */
43ceab3f 208 public static function tokenEscapeSmarty($string) {
6a488035 209 // need to use negative look-behind, as both str_replace() and preg_replace() are sequential
be2fb01f 210 return preg_replace(['/{/', '/(?<!{ldelim)}/'], ['{ldelim}', '{rdelim}'], $string);
6a488035
TO
211 }
212
e7292422 213 /**
6a488035
TO
214 * Replace all the domain-level tokens in $str
215 *
15111972
EM
216 * @deprecated
217 *
77855840
TO
218 * @param string $str
219 * The string with tokens to be replaced.
220 * @param object $domain
221 * The domain BAO.
222 * @param bool $html
223 * Replace tokens with HTML or plain text.
e39893f5
EM
224 *
225 * @param null $knownTokens
226 * @param bool $escapeSmarty
6a488035 227 *
a6c01b45
CW
228 * @return string
229 * The processed string
6a488035 230 */
17aa5355 231 public static function replaceDomainTokens(
21bb6c7b 232 $str,
17aa5355 233 $domain,
21bb6c7b
DL
234 $html = FALSE,
235 $knownTokens = NULL,
236 $escapeSmarty = FALSE
237 ) {
6a488035 238 $key = 'domain';
21bb6c7b 239 if (
353ffa53
TO
240 !$knownTokens || empty($knownTokens[$key])
241 ) {
6a488035
TO
242 return $str;
243 }
244
8bab0eb0 245 $str = preg_replace_callback(
21bb6c7b 246 self::tokenRegex($key),
17aa5355 247 function ($matches) use ($domain, $html, $escapeSmarty) {
353ffa53 248 return CRM_Utils_Token::getDomainTokenReplacement($matches[1], $domain, $html, $escapeSmarty);
8bab0eb0 249 },
21bb6c7b
DL
250 $str
251 );
6a488035
TO
252 return $str;
253 }
254
e39893f5 255 /**
15111972
EM
256 * Do not use.
257 *
258 * @deprecated
259 *
160e7328 260 * @param string $token
d357f225 261 * @param CRM_Core_BAO_Domain $domain
e39893f5
EM
262 * @param bool $html
263 * @param bool $escapeSmarty
264 *
160e7328 265 * @return null|string
e39893f5 266 */
160e7328 267 public static function getDomainTokenReplacement($token, $domain, $html = FALSE, $escapeSmarty = FALSE): ?string {
3c78698e 268 $tokens = CRM_Core_DomainTokens::getDomainTokenValues($domain->id, $html);
60299771 269 $value = $tokens[$token] ?? "{domain.$token}";
6a488035
TO
270 if ($escapeSmarty) {
271 $value = self::tokenEscapeSmarty($value);
272 }
6a488035
TO
273 return $value;
274 }
275
276 /**
277 * Replace all the org-level tokens in $str
278 *
fc0ac495 279 * @fixme: This function appears to be broken, as it depended on
c21a9b7f 280 * nonexistant method: CRM_Core_BAO_CustomValue::getContactValues()
fc0ac495 281 * Marking as deprecated until this is clarified.
282 *
c21a9b7f 283 * @deprecated
fc0ac495 284 * - the above hard-breakage was there from 2015 to 2021 and
285 * no error was ever reported on it -does that mean
286 * 1) the code is never hit because the only function that
287 * calls this function is never called or
288 * 2) it was called but never required to resolve any tokens
289 * or more specifically custom field tokens
290 *
291 * The handling for custom fields with the removed token has
292 * now been removed.
c21a9b7f 293 *
77855840
TO
294 * @param string $str
295 * The string with tokens to be replaced.
296 * @param object $org
297 * Associative array of org properties.
298 * @param bool $html
299 * Replace tokens with HTML or plain text.
e39893f5
EM
300 *
301 * @param bool $escapeSmarty
6a488035 302 *
a6c01b45
CW
303 * @return string
304 * The processed string
6a488035 305 */
fc0ac495 306 public static function replaceOrgTokens($str, &$org, $html = FALSE, $escapeSmarty = FALSE) {
79594ce7 307 CRM_Core_Error::deprecatedFunctionWarning('token processor');
e7483cbe
J
308 self::$_tokens['org']
309 = array_merge(
21bb6c7b 310 array_keys(CRM_Contact_BAO_Contact::importableFields('Organization')),
be2fb01f 311 ['address', 'display_name', 'checksum', 'contact_id']
21bb6c7b 312 );
6a488035 313
6a488035
TO
314 foreach (self::$_tokens['org'] as $token) {
315 // print "Getting token value for $token<br/><br/>";
fc0ac495 316 if ($token === '') {
6a488035
TO
317 continue;
318 }
319
50bfb460 320 // If the string doesn't contain this token, skip it.
6a488035
TO
321
322 if (!self::token_match('org', $token, $str)) {
323 continue;
324 }
325
50bfb460 326 // Construct value from $token and $contact
6a488035
TO
327
328 $value = NULL;
329
fc0ac495 330 if ($token === 'checksum') {
6a488035
TO
331 $cs = CRM_Contact_BAO_Contact_Utils::generateChecksum($org['contact_id']);
332 $value = "cs={$cs}";
333 }
fc0ac495 334 elseif ($token === 'address') {
50bfb460 335 // Build the location values array
6a488035 336
be2fb01f 337 $loc = [];
6a488035
TO
338 $loc['display_name'] = CRM_Utils_Array::retrieveValueRecursive($org, 'display_name');
339 $loc['street_address'] = CRM_Utils_Array::retrieveValueRecursive($org, 'street_address');
340 $loc['city'] = CRM_Utils_Array::retrieveValueRecursive($org, 'city');
341 $loc['state_province'] = CRM_Utils_Array::retrieveValueRecursive($org, 'state_province');
342 $loc['postal_code'] = CRM_Utils_Array::retrieveValueRecursive($org, 'postal_code');
343
50bfb460 344 // Construct the address token
6a488035
TO
345
346 $value = CRM_Utils_Address::format($loc);
347 if ($html) {
348 $value = str_replace("\n", '<br />', $value);
349 }
350 }
351 else {
352 $value = CRM_Utils_Array::retrieveValueRecursive($org, $token);
353 }
354
355 self::token_replace('org', $token, $value, $str, $escapeSmarty);
356 }
357
358 return $str;
359 }
360
361 /**
362 * Replace all mailing tokens in $str
363 *
77855840
TO
364 * @param string $str
365 * The string with tokens to be replaced.
366 * @param object $mailing
367 * The mailing BAO, or null for validation.
368 * @param bool $html
369 * Replace tokens with HTML or plain text.
e39893f5
EM
370 *
371 * @param null $knownTokens
372 * @param bool $escapeSmarty
6a488035 373 *
a6c01b45 374 * @return string
50bfb460 375 * The processed string
15111972
EM
376 *
377 * @deprecated
6a488035 378 */
21bb6c7b
DL
379 public static function &replaceMailingTokens(
380 $str,
381 &$mailing,
382 $html = FALSE,
383 $knownTokens = NULL,
384 $escapeSmarty = FALSE
385 ) {
6a488035
TO
386 $key = 'mailing';
387 if (!$knownTokens || !isset($knownTokens[$key])) {
388 return $str;
389 }
390
8bab0eb0 391 $str = preg_replace_callback(
6a488035 392 self::tokenRegex($key),
353ffa53
TO
393 function ($matches) use (&$mailing, $escapeSmarty) {
394 return CRM_Utils_Token::getMailingTokenReplacement($matches[1], $mailing, $escapeSmarty);
8bab0eb0
DL
395 },
396 $str
6a488035
TO
397 );
398 return $str;
399 }
400
e39893f5
EM
401 /**
402 * @param $token
403 * @param $mailing
404 * @param bool $escapeSmarty
405 *
406 * @return string
407 */
6a488035
TO
408 public static function getMailingTokenReplacement($token, &$mailing, $escapeSmarty = FALSE) {
409 $value = '';
410 switch ($token) {
411 // CRM-7663
412
413 case 'id':
414 $value = $mailing ? $mailing->id : 'undefined';
415 break;
416
b56b8b0e
J
417 // Key is the ID, or the hash when the hash URLs setting is enabled
418 case 'key':
419 $value = $mailing->id;
420 if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($value)) {
421 $value = $hash;
422 }
423 break;
6a488035
TO
424
425 case 'name':
426 $value = $mailing ? $mailing->name : 'Mailing Name';
427 break;
428
429 case 'group':
be2fb01f 430 $groups = $mailing ? $mailing->getGroupNames() : ['Mailing Groups'];
6a488035
TO
431 $value = implode(', ', $groups);
432 break;
433
434 case 'subject':
435 $value = $mailing->subject;
436 break;
437
438 case 'viewUrl':
c57f36a1
PJ
439 $mailingKey = $mailing->id;
440 if ($hash = CRM_Mailing_BAO_Mailing::getMailingHash($mailingKey)) {
441 $mailingKey = $hash;
442 }
6a488035 443 $value = CRM_Utils_System::url('civicrm/mailing/view',
c57f36a1 444 "reset=1&id={$mailingKey}",
6a488035
TO
445 TRUE, NULL, FALSE, TRUE
446 );
447 break;
448
449 case 'editUrl':
0083c73f
TO
450 case 'scheduleUrl':
451 // Note: editUrl and scheduleUrl used to be different, but now there's
452 // one screen which can adapt based on permissions (in workflow mode).
6a488035
TO
453 $value = CRM_Utils_System::url('civicrm/mailing/send',
454 "reset=1&mid={$mailing->id}&continue=true",
455 TRUE, NULL, FALSE, TRUE
456 );
457 break;
458
6a488035
TO
459 case 'html':
460 $page = new CRM_Mailing_Page_View();
c57f36a1 461 $value = $page->run($mailing->id, NULL, FALSE, TRUE);
6a488035
TO
462 break;
463
464 case 'approvalStatus':
a8c23526 465 $value = CRM_Core_PseudoConstant::getLabel('CRM_Mailing_DAO_Mailing', 'approval_status_id', $mailing->approval_status_id);
6a488035
TO
466 break;
467
468 case 'approvalNote':
469 $value = $mailing->approval_note;
470 break;
471
472 case 'approveUrl':
473 $value = CRM_Utils_System::url('civicrm/mailing/approve',
474 "reset=1&mid={$mailing->id}",
475 TRUE, NULL, FALSE, TRUE
476 );
477 break;
478
479 case 'creator':
480 $value = CRM_Contact_BAO_Contact::displayName($mailing->created_id);
481 break;
482
483 case 'creatorEmail':
484 $value = CRM_Contact_BAO_Contact::getPrimaryEmail($mailing->created_id);
485 break;
486
487 default:
488 $value = "{mailing.$token}";
489 break;
490 }
491
492 if ($escapeSmarty) {
493 $value = self::tokenEscapeSmarty($value);
494 }
495 return $value;
496 }
497
498 /**
499 * Replace all action tokens in $str
500 *
77855840
TO
501 * @param string $str
502 * The string with tokens to be replaced.
503 * @param array $addresses
504 * Assoc. array of VERP event addresses.
505 * @param array $urls
506 * Assoc. array of action URLs.
507 * @param bool $html
508 * Replace tokens with HTML or plain text.
509 * @param array $knownTokens
510 * A list of tokens that are known to exist in the email body.
e39893f5
EM
511 *
512 * @param bool $escapeSmarty
6a488035 513 *
a6c01b45
CW
514 * @return string
515 * The processed string
6a488035
TO
516 */
517 public static function &replaceActionTokens(
518 $str,
519 &$addresses,
520 &$urls,
521 $html = FALSE,
522 $knownTokens = NULL,
523 $escapeSmarty = FALSE
524 ) {
525 $key = 'action';
526 // here we intersect with the list of pre-configured valid tokens
527 // so that we remove anything we do not recognize
528 // I hope to move this step out of here soon and
529 // then we will just iterate on a list of tokens that are passed to us
8cc574cf 530 if (!$knownTokens || empty($knownTokens[$key])) {
6a488035
TO
531 return $str;
532 }
533
8bab0eb0
DL
534 $str = preg_replace_callback(
535 self::tokenRegex($key),
353ffa53
TO
536 function ($matches) use (&$addresses, &$urls, $html, $escapeSmarty) {
537 return CRM_Utils_Token::getActionTokenReplacement($matches[1], $addresses, $urls, $html, $escapeSmarty);
8bab0eb0 538 },
6a488035
TO
539 $str
540 );
541 return $str;
542 }
543
e39893f5 544 /**
15111972
EM
545 * @deprecated
546 *
e39893f5
EM
547 * @param $token
548 * @param $addresses
549 * @param $urls
550 * @param bool $html
551 * @param bool $escapeSmarty
552 *
553 * @return mixed|string
554 */
21bb6c7b
DL
555 public static function getActionTokenReplacement(
556 $token,
557 &$addresses,
558 &$urls,
559 $html = FALSE,
560 $escapeSmarty = FALSE
561 ) {
50bfb460
SB
562 // If the token is an email action, use it. Otherwise, find the
563 // appropriate URL.
6a488035
TO
564
565 if (!in_array($token, self::$_tokens['action'])) {
566 $value = "{action.$token}";
567 }
568 else {
9c1bc317 569 $value = $addresses[$token] ?? NULL;
6a488035
TO
570
571 if ($value == NULL) {
9c1bc317 572 $value = $urls[$token] ?? NULL;
6a488035
TO
573 }
574
575 if ($value && $html) {
50bfb460 576 // fix for CRM-2318
6a488035
TO
577 if ((substr($token, -3) != 'Url') && ($token != 'forward')) {
578 $value = "mailto:$value";
579 }
580 }
581 elseif ($value && !$html) {
582 $value = str_replace('&amp;', '&', $value);
583 }
584 }
585
586 if ($escapeSmarty) {
587 $value = self::tokenEscapeSmarty($value);
588 }
589 return $value;
590 }
591
592 /**
593 * Replace all the contact-level tokens in $str with information from
594 * $contact.
595 *
77855840
TO
596 * @param string $str
597 * The string with tokens to be replaced.
598 * @param array $contact
599 * Associative array of contact properties.
600 * @param bool $html
601 * Replace tokens with HTML or plain text.
602 * @param array $knownTokens
603 * A list of tokens that are known to exist in the email body.
604 * @param bool $returnBlankToken
605 * Return unevaluated token if value is null.
e39893f5 606 *
58b954cf
EM
607 * @deprecated
608 *
e39893f5 609 * @param bool $escapeSmarty
6a488035 610 *
a6c01b45
CW
611 * @return string
612 * The processed string
6a488035 613 */
17aa5355 614 public static function replaceContactTokens(
21bb6c7b
DL
615 $str,
616 &$contact,
617 $html = FALSE,
618 $knownTokens = NULL,
619 $returnBlankToken = FALSE,
620 $escapeSmarty = FALSE
6a488035 621 ) {
7c990617 622 // Refresh contact tokens in case they have changed. There is heavy caching
623 // in exportable fields so there is no benefit in doing this conditionally.
624 self::$_tokens['contact'] = array_merge(
625 array_keys(CRM_Contact_BAO_Contact::exportableFields('All')),
be2fb01f 626 ['checksum', 'contact_id']
7c990617 627 );
6a488035 628
7c990617 629 $key = 'contact';
6a488035
TO
630 // here we intersect with the list of pre-configured valid tokens
631 // so that we remove anything we do not recognize
632 // I hope to move this step out of here soon and
633 // then we will just iterate on a list of tokens that are passed to us
8cc574cf 634 if (!$knownTokens || empty($knownTokens[$key])) {
6a488035
TO
635 return $str;
636 }
637
8bab0eb0 638 $str = preg_replace_callback(
21bb6c7b 639 self::tokenRegex($key),
353ffa53
TO
640 function ($matches) use (&$contact, $html, $returnBlankToken, $escapeSmarty) {
641 return CRM_Utils_Token::getContactTokenReplacement($matches[1], $contact, $html, $returnBlankToken, $escapeSmarty);
8bab0eb0 642 },
6a488035
TO
643 $str
644 );
645
646 $str = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
647 return $str;
648 }
649
e39893f5 650 /**
58b954cf
EM
651 * Do Not use.
652 *
653 * Only core usage is from a deprecated unused function and
654 * from deprecated BAO_Mailing code (to be replaced by flexmailer).
655 *
656 * @deprecated
657 *
e39893f5
EM
658 * @param $token
659 * @param $contact
660 * @param bool $html
661 * @param bool $returnBlankToken
662 * @param bool $escapeSmarty
663 *
664 * @return bool|mixed|null|string
665 */
21bb6c7b
DL
666 public static function getContactTokenReplacement(
667 $token,
668 &$contact,
669 $html = FALSE,
670 $returnBlankToken = FALSE,
671 $escapeSmarty = FALSE
6a488035
TO
672 ) {
673 if (self::$_tokens['contact'] == NULL) {
674 /* This should come from UF */
675
e7483cbe
J
676 self::$_tokens['contact']
677 = array_merge(
21bb6c7b 678 array_keys(CRM_Contact_BAO_Contact::exportableFields('All')),
be2fb01f 679 ['checksum', 'contact_id']
21bb6c7b 680 );
6a488035
TO
681 }
682
50bfb460 683 // Construct value from $token and $contact
6a488035
TO
684
685 $value = NULL;
73d64eb6 686 $noReplace = FALSE;
6a488035 687
091133bb
CW
688 // Support legacy tokens
689 $token = CRM_Utils_Array::value($token, self::legacyContactTokens(), $token);
690
6a488035
TO
691 // check if the token we were passed is valid
692 // we have to do this because this function is
693 // called only when we find a token in the string
694
2f7db718 695 if (!in_array(str_replace(':label', '', $token), self::$_tokens['contact'])) {
73d64eb6 696 $noReplace = TRUE;
6a488035
TO
697 }
698 elseif ($token == 'checksum') {
9c1bc317 699 $hash = $contact['hash'] ?? NULL;
6a488035
TO
700 $contactID = CRM_Utils_Array::retrieveValueRecursive($contact, 'contact_id');
701 $cs = CRM_Contact_BAO_Contact_Utils::generateChecksum($contactID,
702 NULL,
703 NULL,
704 $hash
705 );
706 $value = "cs={$cs}";
707 }
708 else {
2f7db718 709 $value = (array) CRM_Utils_Array::retrieveValueRecursive($contact, str_replace(':label', '', $token));
ee70f332 710
ea8be131 711 foreach ($value as $index => $item) {
2f7db718 712 $value[$index] = self::convertPseudoConstantsUsingMetadata($value[$index], str_replace(':label', '', $token));
7ab8b45b 713 }
ea8be131 714 $value = implode(', ', $value);
6a488035
TO
715 }
716
717 if (!$html) {
718 $value = str_replace('&amp;', '&', $value);
719 }
720
721 // if null then return actual token
722 if ($returnBlankToken && !$value) {
73d64eb6
OB
723 $noReplace = TRUE;
724 }
725
726 if ($noReplace) {
6a488035
TO
727 $value = "{contact.$token}";
728 }
729
73d64eb6 730 if ($escapeSmarty
353ffa53 731 && !($returnBlankToken && $noReplace)
e7483cbe
J
732 ) {
733 // $returnBlankToken means the caller wants to do further attempts at
734 // processing unreplaced tokens -- so don't escape them yet in this case.
6a488035
TO
735 $value = self::tokenEscapeSmarty($value);
736 }
737
738 return $value;
739 }
740
741 /**
58b954cf
EM
742 * Do not use - unused in core.
743 *
6a488035
TO
744 * Replace all the hook tokens in $str with information from
745 * $contact.
746 *
58b954cf
EM
747 * @deprecated
748 *
77855840
TO
749 * @param string $str
750 * The string with tokens to be replaced.
751 * @param array $contact
752 * Associative array of contact properties (including hook token values).
e39893f5 753 * @param $categories
77855840
TO
754 * @param bool $html
755 * Replace tokens with HTML or plain text.
e39893f5
EM
756 *
757 * @param bool $escapeSmarty
6a488035 758 *
a6c01b45
CW
759 * @return string
760 * The processed string
6a488035 761 */
21bb6c7b
DL
762 public static function &replaceHookTokens(
763 $str,
764 &$contact,
12d88807 765 $categories = NULL,
21bb6c7b
DL
766 $html = FALSE,
767 $escapeSmarty = FALSE
768 ) {
12d88807 769 if (!$categories) {
770 $categories = self::getTokenCategories();
771 }
6a488035 772 foreach ($categories as $key) {
8bab0eb0 773 $str = preg_replace_callback(
21bb6c7b 774 self::tokenRegex($key),
353ffa53
TO
775 function ($matches) use (&$contact, $key, $html, $escapeSmarty) {
776 return CRM_Utils_Token::getHookTokenReplacement($matches[1], $contact, $key, $html, $escapeSmarty);
8bab0eb0 777 },
6a488035
TO
778 $str
779 );
780 }
781 return $str;
782 }
783
12d88807 784 /**
785 * Get the categories required for rendering tokens.
786 *
787 * @return array
788 */
789 public static function getTokenCategories(): array {
790 if (!isset(\Civi::$statics[__CLASS__]['token_categories'])) {
791 $tokens = [];
792 \CRM_Utils_Hook::tokens($tokens);
793 \Civi::$statics[__CLASS__]['token_categories'] = array_keys($tokens);
794 }
795 return \Civi::$statics[__CLASS__]['token_categories'];
796 }
797
2d3e3c7b 798 /**
fe482240 799 * Parse html through Smarty resolving any smarty functions.
2d3e3c7b 800 * @param string $tokenHtml
801 * @param array $entity
802 * @param string $entityType
a6c01b45
CW
803 * @return string
804 * html parsed through smarty
79594ce7 805 * @deprecated
2d3e3c7b 806 */
807 public static function parseThroughSmarty($tokenHtml, $entity, $entityType = 'contact') {
808 if (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY) {
809 $smarty = CRM_Core_Smarty::singleton();
810 // also add the tokens to the template
811 $smarty->assign_by_ref($entityType, $entity);
812 $tokenHtml = $smarty->fetch("string:$tokenHtml");
813 }
814 return $tokenHtml;
815 }
5bc392e6
EM
816
817 /**
58b954cf
EM
818 * Do not use, unused in core.
819 *
820 * @deprecated
821 *
5bc392e6
EM
822 * @param $token
823 * @param $contact
824 * @param $category
825 * @param bool $html
826 * @param bool $escapeSmarty
827 *
828 * @return mixed|string
95ea96be
EM
829 */
830 public static function getHookTokenReplacement(
21bb6c7b
DL
831 $token,
832 &$contact,
833 $category,
834 $html = FALSE,
835 $escapeSmarty = FALSE
836 ) {
9c1bc317 837 $value = $contact["{$category}.{$token}"] ?? NULL;
6a488035 838
21bb6c7b 839 if ($value && !$html) {
6a488035
TO
840 $value = str_replace('&amp;', '&', $value);
841 }
842
843 if ($escapeSmarty) {
844 $value = self::tokenEscapeSmarty($value);
845 }
846
847 return $value;
848 }
849
850 /**
50bfb460
SB
851 * unescapeTokens removes any characters that caused the replacement routines to skip token replacement
852 * for example {{token}} or \{token} will result in {token} in the final email
6a488035 853 *
50bfb460 854 * this routine will remove the extra backslashes and braces
6a488035 855 *
79594ce7
EM
856 * @deprecated
857 *
a2f24340 858 * @param string $str ref to the string that will be scanned and modified
6a488035
TO
859 */
860 public static function unescapeTokens(&$str) {
861 $str = preg_replace('/\\\\|\{(\{\w+\.\w+\})\}/', '\\1', $str);
862 }
863
864 /**
fe482240 865 * Replace unsubscribe tokens.
6a488035 866 *
77855840
TO
867 * @param string $str
868 * The string with tokens to be replaced.
869 * @param object $domain
870 * The domain BAO.
871 * @param array $groups
872 * The groups (if any) being unsubscribed.
873 * @param bool $html
874 * Replace tokens with html or plain text.
875 * @param int $contact_id
876 * The contact ID.
e7483cbe 877 * @param string $hash The security hash of the unsub event
6a488035 878 *
a6c01b45
CW
879 * @return string
880 * The processed string
6a488035
TO
881 */
882 public static function &replaceUnsubscribeTokens(
883 $str,
884 &$domain,
885 &$groups,
886 $html,
887 $contact_id,
888 $hash
889 ) {
890 if (self::token_match('unsubscribe', 'group', $str)) {
891 if (!empty($groups)) {
892 $config = CRM_Core_Config::singleton();
893 $base = CRM_Utils_System::baseURL();
894
895 // FIXME: an ugly hack for CRM-2035, to be dropped once CRM-1799 is implemented
896 $dao = new CRM_Contact_DAO_Group();
897 $dao->find();
898 while ($dao->fetch()) {
899 if (substr($dao->visibility, 0, 6) == 'Public') {
900 $visibleGroups[] = $dao->id;
901 }
902 }
903 $value = implode(', ', $groups);
904 self::token_replace('unsubscribe', 'group', $value, $str);
905 }
906 }
907 return $str;
908 }
909
910 /**
fe482240 911 * Replace resubscribe tokens.
6a488035 912 *
77855840
TO
913 * @param string $str
914 * The string with tokens to be replaced.
915 * @param object $domain
916 * The domain BAO.
917 * @param array $groups
918 * The groups (if any) being resubscribed.
919 * @param bool $html
920 * Replace tokens with html or plain text.
921 * @param int $contact_id
922 * The contact ID.
e7483cbe 923 * @param string $hash The security hash of the resub event
6a488035 924 *
a6c01b45
CW
925 * @return string
926 * The processed string
6a488035 927 */
a3e55d9c
TO
928 public static function &replaceResubscribeTokens(
929 $str, &$domain, &$groups, $html,
6a488035
TO
930 $contact_id, $hash
931 ) {
932 if (self::token_match('resubscribe', 'group', $str)) {
933 if (!empty($groups)) {
934 $value = implode(', ', $groups);
935 self::token_replace('resubscribe', 'group', $value, $str);
936 }
937 }
938 return $str;
939 }
940
941 /**
942 * Replace subscription-confirmation-request tokens
943 *
77855840
TO
944 * @param string $str
945 * The string with tokens to be replaced.
946 * @param string $group
947 * The name of the group being subscribed.
e39893f5 948 * @param $url
77855840
TO
949 * @param bool $html
950 * Replace tokens with html or plain text.
6a488035 951 *
a6c01b45
CW
952 * @return string
953 * The processed string
6a488035
TO
954 */
955 public static function &replaceSubscribeTokens($str, $group, $url, $html) {
956 if (self::token_match('subscribe', 'group', $str)) {
957 self::token_replace('subscribe', 'group', $group, $str);
958 }
959 if (self::token_match('subscribe', 'url', $str)) {
960 self::token_replace('subscribe', 'url', $url, $str);
961 }
962 return $str;
963 }
964
965 /**
966 * Replace subscription-invitation tokens
967 *
77855840
TO
968 * @param string $str
969 * The string with tokens to be replaced.
6a488035 970 *
a6c01b45
CW
971 * @return string
972 * The processed string
6a488035
TO
973 */
974 public static function &replaceSubscribeInviteTokens($str) {
975 if (preg_match('/\{action\.subscribeUrl\}/', $str)) {
976 $url = CRM_Utils_System::url('civicrm/mailing/subscribe',
977 'reset=1',
3f390aa6 978 TRUE, NULL, FALSE, TRUE
6a488035
TO
979 );
980 $str = preg_replace('/\{action\.subscribeUrl\}/', $url, $str);
981 }
982
983 if (preg_match('/\{action\.subscribeUrl.\d+\}/', $str, $matches)) {
984 foreach ($matches as $key => $value) {
985 $gid = substr($value, 21, -1);
986 $url = CRM_Utils_System::url('civicrm/mailing/subscribe',
987 "reset=1&gid={$gid}",
3f390aa6 988 TRUE, NULL, FALSE, TRUE
6a488035 989 );
6a488035
TO
990 $str = preg_replace('/' . preg_quote($value) . '/', $url, $str);
991 }
992 }
993
994 if (preg_match('/\{action\.subscribe.\d+\}/', $str, $matches)) {
995 foreach ($matches as $key => $value) {
353ffa53
TO
996 $gid = substr($value, 18, -1);
997 $config = CRM_Core_Config::singleton();
998 $domain = CRM_Core_BAO_MailSettings::defaultDomain();
6a488035
TO
999 $localpart = CRM_Core_BAO_MailSettings::defaultLocalpart();
1000 // we add the 0.0000000000000000 part to make this match the other email patterns (with action, two ids and a hash)
1001 $str = preg_replace('/' . preg_quote($value) . '/', "mailto:{$localpart}s.{$gid}.0.0000000000000000@$domain", $str);
1002 }
1003 }
1004 return $str;
1005 }
1006
1007 /**
1008 * Replace welcome/confirmation tokens
1009 *
77855840
TO
1010 * @param string $str
1011 * The string with tokens to be replaced.
1012 * @param string $group
1013 * The name of the group being subscribed.
1014 * @param bool $html
1015 * Replace tokens with html or plain text.
6a488035 1016 *
a6c01b45
CW
1017 * @return string
1018 * The processed string
6a488035
TO
1019 */
1020 public static function &replaceWelcomeTokens($str, $group, $html) {
1021 if (self::token_match('welcome', 'group', $str)) {
1022 self::token_replace('welcome', 'group', $group, $str);
1023 }
1024 return $str;
1025 }
1026
1027 /**
1028 * Find unprocessed tokens (call this last)
1029 *
77855840
TO
1030 * @param string $str
1031 * The string to search.
6a488035 1032 *
a6c01b45
CW
1033 * @return array
1034 * Array of tokens that weren't replaced
6a488035
TO
1035 */
1036 public static function &unmatchedTokens(&$str) {
1037 //preg_match_all('/[^\{\\\\]\{(\w+\.\w+)\}[^\}]/', $str, $match);
1038 preg_match_all('/\{(\w+\.\w+)\}/', $str, $match);
1039 return $match[1];
1040 }
1041
1042 /**
fe482240 1043 * Find and replace tokens for each component.
6a488035 1044 *
77855840
TO
1045 * @param string $str
1046 * The string to search.
1047 * @param array $contact
1048 * Associative array of contact properties.
1049 * @param array $components
1050 * A list of tokens that are known to exist in the email body.
6a488035 1051 *
e39893f5
EM
1052 * @param bool $escapeSmarty
1053 * @param bool $returnEmptyToken
1054 *
a6c01b45
CW
1055 * @return string
1056 * The processed string
79594ce7
EM
1057 *
1058 * @deprecated
6a488035 1059 */
79594ce7
EM
1060 public static function replaceComponentTokens(&$str, $contact, $components, $escapeSmarty = FALSE, $returnEmptyToken = TRUE) {
1061 CRM_Core_Error::deprecatedFunctionWarning('use the token processor');
6a488035
TO
1062 if (!is_array($components) || empty($contact)) {
1063 return $str;
1064 }
1065
1066 foreach ($components as $name => $tokens) {
1067 if (!is_array($tokens) || empty($tokens)) {
1068 continue;
1069 }
1070
1071 foreach ($tokens as $token) {
1072 if (self::token_match($name, $token, $str) && isset($contact[$name . '.' . $token])) {
1073 self::token_replace($name, $token, $contact[$name . '.' . $token], $str, $escapeSmarty);
1074 }
1075 elseif (!$returnEmptyToken) {
1076 //replacing empty token
1077 self::token_replace($name, $token, "", $str, $escapeSmarty);
1078 }
1079 }
1080 }
1081 return $str;
1082 }
1083
1084 /**
fe482240 1085 * Get array of string tokens.
6a488035 1086 *
5a4f6742 1087 * @param string $string
77855840 1088 * The input string to parse for tokens.
6a488035 1089 *
a6c01b45
CW
1090 * @return array
1091 * array of tokens mentioned in field
6a488035 1092 */
00be9182 1093 public static function getTokens($string) {
be2fb01f
CW
1094 $matches = [];
1095 $tokens = [];
ad06f98f 1096 preg_match_all('/(?<!\{|\\\\)\{(\w+\.\w+(:|.)?\w*)\}(?!\})/',
6a488035
TO
1097 $string,
1098 $matches,
1099 PREG_PATTERN_ORDER
1100 );
1101
1102 if ($matches[1]) {
1103 foreach ($matches[1] as $token) {
ad06f98f
EM
1104 $parts = explode('.', $token, 3);
1105 $type = $parts[0];
1106 $name = $parts[1];
1107 $suffix = !empty($parts[2]) ? ('.' . $parts[2]) : '';
6a488035
TO
1108 if ($name && $type) {
1109 if (!isset($tokens[$type])) {
be2fb01f 1110 $tokens[$type] = [];
6a488035 1111 }
ad06f98f 1112 $tokens[$type][] = $name . $suffix;
6a488035
TO
1113 }
1114 }
1115 }
1116 return $tokens;
1117 }
1118
bdd49e38
EM
1119 /**
1120 * Function to determine which values to retrieve to insert into tokens. The heavy resemblance between this function
1121 * and getTokens appears to be historical rather than intentional and should be reviewed
1122 * @param $string
a6c01b45
CW
1123 * @return array
1124 * fields to pass in as return properties when populating token
bdd49e38
EM
1125 */
1126 public static function getReturnProperties(&$string) {
be2fb01f
CW
1127 $returnProperties = [];
1128 $matches = [];
e7292422 1129 preg_match_all('/(?<!\{|\\\\)\{(\w+\.\w+)\}(?!\})/',
353ffa53
TO
1130 $string,
1131 $matches,
1132 PREG_PATTERN_ORDER
1133 );
e7292422
TO
1134 if ($matches[1]) {
1135 foreach ($matches[1] as $token) {
3017ca78 1136 [$type, $name] = preg_split('/\./', $token, 2);
e7292422
TO
1137 if ($name) {
1138 $returnProperties["{$name}"] = 1;
bdd49e38
EM
1139 }
1140 }
e7292422 1141 }
bdd49e38 1142
e7292422 1143 return $returnProperties;
bdd49e38
EM
1144 }
1145
6a488035 1146 /**
943c6d0c
EM
1147 * Do not use this function.
1148 *
100fef9d 1149 * Gives required details of contacts in an indexed array format so we
6a488035
TO
1150 * can iterate in a nice loop and do token evaluation
1151 *
36f5faa3 1152 * @param array $contactIDs
77855840
TO
1153 * @param array $returnProperties
1154 * Of required properties.
e7483cbe 1155 * @param bool $skipOnHold Don't return on_hold contact info also.
77855840 1156 * Don't return on_hold contact info also.
e7483cbe 1157 * @param bool $skipDeceased Don't return deceased contact info.
77855840
TO
1158 * Don't return deceased contact info.
1159 * @param array $extraParams
bf02bdac 1160 * Extra params - DEPRECATED
77855840
TO
1161 * @param array $tokens
1162 * The list of tokens we've extracted from the content.
4d385723 1163 * @param string|null $className
1164 * @param int|null $jobID
77855840 1165 * The mailing list jobID - this is a legacy param.
6a488035 1166 *
943c6d0c
EM
1167 * @deprecated
1168 *
4d385723 1169 * @return array - e.g [[1 => ['first_name' => 'bob'...], 34 => ['first_name' => 'fred'...]]]
6a488035 1170 */
e7483cbe 1171 public static function getTokenDetails(
a3e55d9c 1172 $contactIDs,
6a488035 1173 $returnProperties = NULL,
e7292422
TO
1174 $skipOnHold = TRUE,
1175 $skipDeceased = TRUE,
1176 $extraParams = NULL,
be2fb01f 1177 $tokens = [],
e7292422
TO
1178 $className = NULL,
1179 $jobID = NULL
6a488035 1180 ) {
6d5a7ee7 1181 CRM_Core_Error::deprecatedFunctionWarning('If you hit this in mailing code you should use flexmailer - otherwise use the token processor');
be2fb01f 1182 $params = [];
36f5faa3 1183 foreach ($contactIDs as $contactID) {
be2fb01f 1184 $params[] = [
6a488035 1185 CRM_Core_Form::CB_PREFIX . $contactID,
353ffa53
TO
1186 '=',
1187 1,
1188 0,
1189 0,
be2fb01f 1190 ];
6a488035
TO
1191 }
1192
1193 // fix for CRM-2613
1194 if ($skipDeceased) {
be2fb01f 1195 $params[] = ['is_deceased', '=', 0, 0, 0];
6a488035
TO
1196 }
1197
1198 //fix for CRM-3798
1199 if ($skipOnHold) {
be2fb01f 1200 $params[] = ['on_hold', '=', 0, 0, 0];
6a488035
TO
1201 }
1202
1203 if ($extraParams) {
bf02bdac 1204 CRM_Core_Error::deprecatedWarning('Passing $extraParams to getTokenDetails() is not supported and will be removed in a future version');
6a488035
TO
1205 $params = array_merge($params, $extraParams);
1206 }
1207
1208 // if return properties are not passed then get all return properties
1209 if (empty($returnProperties)) {
1210 $fields = array_merge(array_keys(CRM_Contact_BAO_Contact::exportableFields()),
be2fb01f 1211 ['display_name', 'checksum', 'contact_id']
6a488035 1212 );
36f5faa3 1213 foreach ($fields as $val) {
b8a4ead8 1214 // The unavailable fields are not available as tokens, do not have a one-2-one relationship
1215 // with contacts and are expensive to resolve.
1216 // @todo see CRM-17253 - there are some other fields (e.g note) that should be excluded
1217 // and upstream calls to this should populate return properties.
be2fb01f 1218 $unavailableFields = ['group', 'tag'];
b8a4ead8 1219 if (!in_array($val, $unavailableFields)) {
1220 $returnProperties[$val] = 1;
1221 }
6a488035
TO
1222 }
1223 }
1224
be2fb01f 1225 $custom = [];
6a488035
TO
1226 foreach ($returnProperties as $name => $dontCare) {
1227 $cfID = CRM_Core_BAO_CustomField::getKeyID($name);
1228 if ($cfID) {
1229 $custom[] = $cfID;
1230 }
1231 }
1232
4d385723 1233 [$contactDetails] = CRM_Contact_BAO_Query::apiQuery($params, $returnProperties, NULL, NULL, 0, count($contactIDs), TRUE, FALSE, TRUE, CRM_Contact_BAO_Query::MODE_CONTACTS, NULL, TRUE);
6a488035 1234
36f5faa3 1235 foreach ($contactIDs as $contactID) {
6a488035 1236 if (array_key_exists($contactID, $contactDetails)) {
5973d2e1 1237 if (!empty($contactDetails[$contactID]['preferred_communication_method'])
6a488035 1238 ) {
be2fb01f 1239 $communicationPreferences = [];
160e7328 1240 foreach ((array) $contactDetails[$contactID]['preferred_communication_method'] as $val) {
6a488035 1241 if ($val) {
5973d2e1 1242 $communicationPreferences[$val] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_DAO_Contact', 'preferred_communication_method', $val);
6a488035
TO
1243 }
1244 }
5973d2e1 1245 $contactDetails[$contactID]['preferred_communication_method'] = implode(', ', $communicationPreferences);
6a488035
TO
1246 }
1247
1248 foreach ($custom as $cfID) {
1249 if (isset($contactDetails[$contactID]["custom_{$cfID}"])) {
8cee0c70 1250 $contactDetails[$contactID]["custom_{$cfID}"] = CRM_Core_BAO_CustomField::displayValue($contactDetails[$contactID]["custom_{$cfID}"], $cfID);
6a488035
TO
1251 }
1252 }
1253
50bfb460 1254 // special case for greeting replacement
be2fb01f 1255 foreach ([
6714d8d2
SL
1256 'email_greeting',
1257 'postal_greeting',
1258 'addressee',
1259 ] as $val) {
a7488080 1260 if (!empty($contactDetails[$contactID][$val])) {
6a488035
TO
1261 $contactDetails[$contactID][$val] = $contactDetails[$contactID]["{$val}_display"];
1262 }
1263 }
1264 }
1265 }
1266
590111ef 1267 // $contactDetails = &$details[0] = is an array of [ contactID => contactDetails ]
6a488035 1268 // also call a hook and get token details
590111ef 1269 CRM_Utils_Hook::tokenValues($contactDetails,
6a488035
TO
1270 $contactIDs,
1271 $jobID,
1272 $tokens,
1273 $className
1274 );
4d385723 1275 return [$contactDetails];
6a488035
TO
1276 }
1277
d20c4dad
EM
1278 /**
1279 * Call hooks on tokens for anonymous users - contact id is set to 0 - this allows non-contact
1280 * specific tokens to be rendered
1281 *
77855840
TO
1282 * @param array $contactIDs
1283 * This should always be array(0) or its not anonymous - left to keep signature same.
16b10e64 1284 * as main fn
d20c4dad 1285 * @param string $returnProperties
77855840
TO
1286 * @param bool $skipOnHold
1287 * @param bool $skipDeceased
d20c4dad
EM
1288 * @param string $extraParams
1289 * @param array $tokens
77855840
TO
1290 * @param string $className
1291 * Sent as context to the hook.
d20c4dad 1292 * @param string $jobID
a6c01b45
CW
1293 * @return array
1294 * contactDetails with hooks swapped out
15111972
EM
1295 *
1296 * @deprecated
d20c4dad 1297 */
590111ef 1298 public static function getAnonymousTokenDetails($contactIDs = [0],
353ffa53
TO
1299 $returnProperties = NULL,
1300 $skipOnHold = TRUE,
1301 $skipDeceased = TRUE,
1302 $extraParams = NULL,
be2fb01f 1303 $tokens = [],
353ffa53
TO
1304 $className = NULL,
1305 $jobID = NULL) {
be2fb01f 1306 $details = [0 => []];
e7292422
TO
1307 // also call a hook and get token details
1308 CRM_Utils_Hook::tokenValues($details[0],
d20c4dad
EM
1309 $contactIDs,
1310 $jobID,
1311 $tokens,
1312 $className
1313 );
1314 return $details;
1315 }
e39893f5 1316
2d3e3c7b 1317 /**
fe482240 1318 * Get Membership Token Details.
77855840
TO
1319 * @param array $membershipIDs
1320 * Array of membership IDS.
a95f041b
EM
1321 *
1322 * @deprecated
2d3e3c7b 1323 */
00be9182 1324 public static function getMembershipTokenDetails($membershipIDs) {
be2fb01f
CW
1325 $memberships = civicrm_api3('membership', 'get', [
1326 'options' => ['limit' => 0],
1327 'membership_id' => ['IN' => (array) $membershipIDs],
1328 ]);
2d3e3c7b 1329 return $memberships['values'];
1330 }
353ffa53 1331
6a488035 1332 /**
50bfb460 1333 * Replace existing greeting tokens in message/subject.
54957108 1334 *
206f0198
CB
1335 * This function operates by reference, modifying the first parameter. Other
1336 * methods for token replacement in this class return the modified string.
1337 * This leads to inconsistency in how these methods must be applied.
1338 *
1339 * @TODO Remove that inconsistency in usage.
1340 *
54957108 1341 * @param string $tokenString
1342 * @param array $contactDetails
1343 * @param int $contactId
1344 * @param string $className
1345 * @param bool $escapeSmarty
6a488035 1346 */
00be9182 1347 public static function replaceGreetingTokens(&$tokenString, $contactDetails = NULL, $contactId = NULL, $className = NULL, $escapeSmarty = FALSE) {
6a488035
TO
1348
1349 if (!$contactDetails && !$contactId) {
1350 return;
1351 }
6a488035
TO
1352 // check if there are any tokens
1353 $greetingTokens = self::getTokens($tokenString);
58b954cf
EM
1354 $context = $contactId ? ['contactId' => $contactId] : [];
1355 if ($contactDetails) {
4c820cf9 1356 $context['contact'] = isset($contactDetails[0]) ? reset($contactDetails[0]) : $contactDetails;
6a488035 1357 }
58b954cf
EM
1358 $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
1359 'smarty' => FALSE,
1360 'class' => $className,
1361 ]);
1362 $tokenProcessor->addRow($context);
1363 $tokenProcessor->addMessage('greeting', $tokenString, 'text/plain');
1364 $tokenProcessor->evaluate();
1365 foreach ($tokenProcessor->getRows() as $row) {
1366 $tokenString = $row->render('greeting');
6a488035
TO
1367 }
1368 }
1369
663cc0b4
J
1370 /**
1371 * At this point, $contactDetails has loaded the contact from the DAO. Any
1372 * (non-custom) missing fields are null. By removing them, we can avoid
1373 * expensive calls to CRM_Contact_BAO_Query.
1374 *
58b954cf
EM
1375 * @deprecated unused in core
1376 *
663cc0b4
J
1377 * @param string $tokenString
1378 * @param array $contactDetails
6714d8d2 1379 * @param array $greetingTokens
663cc0b4
J
1380 */
1381 private static function removeNullContactTokens(&$tokenString, $contactDetails, &$greetingTokens) {
dcdff6e6 1382
a2a7ed37
JK
1383 // Only applies to contact tokens
1384 if (!array_key_exists('contact', $greetingTokens)) {
1385 return;
1386 }
dcdff6e6 1387
663cc0b4
J
1388 $greetingTokensOriginal = $greetingTokens;
1389 $contactFieldList = CRM_Contact_DAO_Contact::fields();
578c7a3a
J
1390 // Sometimes contactDetails are in a multidemensional array, sometimes a
1391 // single-dimension array.
1392 if (array_key_exists(0, $contactDetails) && is_array($contactDetails[0])) {
1393 $contactDetails = current($contactDetails[0]);
1394 }
663cc0b4
J
1395 $nullFields = array_keys(array_diff_key($contactFieldList, $contactDetails));
1396
1397 // Handle legacy tokens
1398 foreach (self::legacyContactTokens() as $oldToken => $newToken) {
1399 if (CRM_Utils_Array::key($newToken, $nullFields)) {
1400 $nullFields[] = $oldToken;
1401 }
1402 }
1403
1404 // Remove null contact fields from $greetingTokens
1405 $greetingTokens['contact'] = array_diff($greetingTokens['contact'], $nullFields);
1406
1407 // Also remove them from $tokenString
1408 $removedTokens = array_diff($greetingTokensOriginal['contact'], $greetingTokens['contact']);
1409 // Handle legacy tokens again, sigh
1410 if (!empty($removedTokens)) {
1411 foreach ($removedTokens as $token) {
1412 if (CRM_Utils_Array::value($token, self::legacyContactTokens()) !== NULL) {
1413 $removedTokens[] = CRM_Utils_Array::value($token, self::legacyContactTokens());
1414 }
1415 }
1416 foreach ($removedTokens as $token) {
1417 $tokenString = str_replace("{contact.$token}", '', $tokenString);
1418 }
1419 }
1420 }
1421
5bc392e6
EM
1422 /**
1423 * @param $tokens
1424 *
1425 * @return array
1426 */
00be9182 1427 public static function flattenTokens(&$tokens) {
be2fb01f 1428 $flattenTokens = [];
6a488035 1429
be2fb01f 1430 foreach ([
6714d8d2
SL
1431 'html',
1432 'text',
1433 'subject',
1434 ] as $prop) {
6a488035
TO
1435 if (!isset($tokens[$prop])) {
1436 continue;
1437 }
1438 foreach ($tokens[$prop] as $type => $names) {
1439 if (!isset($flattenTokens[$type])) {
be2fb01f 1440 $flattenTokens[$type] = [];
6a488035
TO
1441 }
1442 foreach ($names as $name) {
1443 $flattenTokens[$type][$name] = 1;
1444 }
1445 }
1446 }
1447
1448 return $flattenTokens;
1449 }
1450
1451 /**
1452 * Replace all user tokens in $str
1453 *
77855840
TO
1454 * @param string $str
1455 * The string with tokens to be replaced.
e39893f5
EM
1456 *
1457 * @param null $knownTokens
1458 * @param bool $escapeSmarty
6a488035 1459 *
a6c01b45
CW
1460 * @return string
1461 * The processed string
6a488035
TO
1462 */
1463 public static function &replaceUserTokens($str, $knownTokens = NULL, $escapeSmarty = FALSE) {
1464 $key = 'user';
1465 if (!$knownTokens ||
1466 !isset($knownTokens[$key])
1467 ) {
1468 return $str;
1469 }
1470
8bab0eb0
DL
1471 $str = preg_replace_callback(
1472 self::tokenRegex($key),
353ffa53
TO
1473 function ($matches) use ($escapeSmarty) {
1474 return CRM_Utils_Token::getUserTokenReplacement($matches[1], $escapeSmarty);
8bab0eb0
DL
1475 },
1476 $str
6a488035
TO
1477 );
1478 return $str;
1479 }
1480
e39893f5
EM
1481 /**
1482 * @param $token
1483 * @param bool $escapeSmarty
1484 *
1485 * @return string
1486 */
6a488035
TO
1487 public static function getUserTokenReplacement($token, $escapeSmarty = FALSE) {
1488 $value = '';
1489
3017ca78 1490 [$objectName, $objectValue] = explode('-', $token, 2);
6a488035
TO
1491
1492 switch ($objectName) {
1493 case 'permission':
1494 $value = CRM_Core_Permission::permissionEmails($objectValue);
1495 break;
1496
1497 case 'role':
1498 $value = CRM_Core_Permission::roleEmails($objectValue);
1499 break;
1500 }
1501
1502 if ($escapeSmarty) {
1503 $value = self::tokenEscapeSmarty($value);
1504 }
1505
1506 return $value;
1507 }
1508
7a39d045
EM
1509 /**
1510 * @deprecated
1511 *
1512 * Do not use this function - it still needs full removal from active code
1513 * in CRM_Contribute_Form_Task_PDFLetter.
1514 */
6a488035
TO
1515 protected static function _buildContributionTokens() {
1516 $key = 'contribution';
9b3cb77d 1517
17cca51e 1518 if (!isset(Civi::$statics[__CLASS__][__FUNCTION__][$key])) {
da3ea3d0
SL
1519 $tokens = array_merge(CRM_Contribute_BAO_Contribution::exportableFields('All'),
1520 ['campaign' => [], 'financial_type' => [], 'payment_instrument' => []],
9b3cb77d 1521 self::getCustomFieldTokens('Contribution'),
7a39d045
EM
1522 [
1523 'financial_type_id:label',
1524 'financial_type_id:name',
1525 'contribution_page_id:label',
1526 'contribution_page_id:name',
1527 'payment_instrument_id:label',
1528 'payment_instrument_id:name',
1529 'is_test:label',
1530 'is_pay_later:label',
1531 'contribution_status_id:label',
1532 'contribution_status_id:name',
1533 'is_template:label',
2601e26d
SL
1534 'campaign_id:label',
1535 'campaign_id:name',
7a39d045 1536 ]
da3ea3d0
SL
1537 );
1538 foreach ($tokens as $token) {
1539 if (!empty($token['name'])) {
1540 $tokens[$token['name']] = [];
1541 }
2601e26d
SL
1542 elseif (is_string($token) && strpos($token, ':') !== FALSE) {
1543 $tokens[$token] = [];
1544 }
da3ea3d0 1545 }
17cca51e 1546 Civi::$statics[__CLASS__][__FUNCTION__][$key] = array_keys($tokens);
6a488035 1547 }
17cca51e 1548 self::$_tokens[$key] = Civi::$statics[__CLASS__][__FUNCTION__][$key];
6a488035
TO
1549 }
1550
2d3e3c7b 1551 /**
3232ec13
EM
1552 * Do not use.
1553 *
1554 * @deprecated
1555 *
fe482240 1556 * Replace tokens for an entity.
2d3e3c7b 1557 * @param string $entity
77855840
TO
1558 * @param array $entityArray
1559 * (e.g. in format from api).
1560 * @param string $str
1561 * String to replace in.
1562 * @param array $knownTokens
1563 * Array of tokens present.
1564 * @param bool $escapeSmarty
a6c01b45
CW
1565 * @return string
1566 * string with replacements made
2d3e3c7b 1567 */
be2fb01f 1568 public static function replaceEntityTokens($entity, $entityArray, $str, $knownTokens = [], $escapeSmarty = FALSE) {
8cc574cf 1569 if (!$knownTokens || empty($knownTokens[$entity])) {
2d3e3c7b 1570 return $str;
1571 }
1572
07945b3c 1573 $fn = 'get' . ucfirst($entity) . 'TokenReplacement';
be2fb01f 1574 $fn = is_callable(['CRM_Utils_Token', $fn]) ? $fn : 'getApiTokenReplacement';
50bfb460 1575 // since we already know the tokens lets just use them & do str_replace which is faster & simpler than preg_replace
2d3e3c7b 1576 foreach ($knownTokens[$entity] as $token) {
2f5c0408
EM
1577 // We are now supporting the syntax case_type_id:label
1578 // so strip anything after the ':'
1579 // (we aren't supporting 'name' at this stage, so we can assume 'label'
1580 // test cover in TokenConsistencyTest.
1581 $parts = explode(':', $token);
1582 $replacement = self::$fn($entity, $parts[0], $entityArray);
07945b3c
CW
1583 if ($escapeSmarty) {
1584 $replacement = self::tokenEscapeSmarty($replacement);
1585 }
1586 $str = str_replace('{' . $entity . '.' . $token . '}', $replacement, $str);
2d3e3c7b 1587 }
07945b3c
CW
1588 return preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
1589 }
1590
1591 /**
662bee85
EM
1592 * @deprecated
1593 *
07945b3c 1594 * @param int $caseId
f274450a 1595 * @param string $str
07945b3c
CW
1596 * @param array $knownTokens
1597 * @param bool $escapeSmarty
1598 * @return string
1599 * @throws \CiviCRM_API3_Exception
1600 */
d0ce76fd
EM
1601 public static function replaceCaseTokens($caseId, $str, $knownTokens = NULL, $escapeSmarty = FALSE): string {
1602 if (strpos($str, '{case.') === FALSE) {
07945b3c
CW
1603 return $str;
1604 }
d0ce76fd
EM
1605 if (!$knownTokens) {
1606 $knownTokens = self::getTokens($str);
1607 }
be2fb01f 1608 $case = civicrm_api3('case', 'getsingle', ['id' => $caseId]);
07945b3c
CW
1609 return self::replaceEntityTokens('case', $case, $str, $knownTokens, $escapeSmarty);
1610 }
1611
1612 /**
1613 * Generic function for formatting token replacement for an api field
1614 *
79594ce7
EM
1615 * @deprecated
1616 *
07945b3c
CW
1617 * @param string $entity
1618 * @param string $token
1619 * @param array $entityArray
1620 * @return string
1621 * @throws \CiviCRM_API3_Exception
1622 */
1623 public static function getApiTokenReplacement($entity, $token, $entityArray) {
1624 if (!isset($entityArray[$token])) {
1625 return '';
1626 }
be2fb01f 1627 $field = civicrm_api3($entity, 'getfield', ['action' => 'get', 'name' => $token, 'get_options' => 'get']);
07945b3c 1628 $field = $field['values'];
9c1bc317 1629 $fieldType = $field['type'] ?? NULL;
6f9518ec
CW
1630 // Boolean fields
1631 if ($fieldType == CRM_Utils_Type::T_BOOLEAN && empty($field['options'])) {
be2fb01f 1632 $field['options'] = [ts('No'), ts('Yes')];
6f9518ec 1633 }
07945b3c
CW
1634 // Match pseudoconstants
1635 if (!empty($field['options'])) {
be2fb01f 1636 $ret = [];
07945b3c
CW
1637 foreach ((array) $entityArray[$token] as $val) {
1638 $ret[] = $field['options'][$val];
1639 }
1640 return implode(', ', $ret);
1641 }
6f9518ec 1642 // Format date fields
78ffc4d7 1643 elseif ($entityArray[$token] && in_array($fieldType, [CRM_Utils_Type::T_DATE, CRM_Utils_Type::T_TIMESTAMP, (CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME)])) {
07945b3c
CW
1644 return CRM_Utils_Date::customFormat($entityArray[$token]);
1645 }
6f9518ec 1646 return implode(', ', (array) $entityArray[$token]);
2d3e3c7b 1647 }
1648
383c047b 1649 /**
31f2ebac
EM
1650 * Do not use - unused in core.
1651 *
fe482240 1652 * Replace Contribution tokens in html.
e39893f5 1653 *
115dba92
EM
1654 * @param string $str
1655 * @param array $contribution
e39893f5 1656 * @param bool|string $html
383c047b 1657 * @param string $knownTokens
e39893f5
EM
1658 * @param bool|string $escapeSmarty
1659 *
31f2ebac
EM
1660 * @deprecated
1661 *
72b3a70c 1662 * @return mixed
383c047b
DG
1663 */
1664 public static function replaceContributionTokens($str, &$contribution, $html = FALSE, $knownTokens = NULL, $escapeSmarty = FALSE) {
1665 $key = 'contribution';
b99f3e96 1666 if (!$knownTokens || empty($knownTokens[$key])) {
6714d8d2
SL
1667 //early return
1668 return $str;
383c047b 1669 }
6a488035
TO
1670
1671 // here we intersect with the list of pre-configured valid tokens
1672 // so that we remove anything we do not recognize
1673 // I hope to move this step out of here soon and
1674 // then we will just iterate on a list of tokens that are passed to us
6a488035 1675
8bab0eb0
DL
1676 $str = preg_replace_callback(
1677 self::tokenRegex($key),
353ffa53
TO
1678 function ($matches) use (&$contribution, $html, $escapeSmarty) {
1679 return CRM_Utils_Token::getContributionTokenReplacement($matches[1], $contribution, $html, $escapeSmarty);
8bab0eb0 1680 },
6a488035
TO
1681 $str
1682 );
1683
1684 $str = preg_replace('/\\\\|\{(\s*)?\}/', ' ', $str);
1685 return $str;
1686 }
1687
383c047b
DG
1688 /**
1689 * We have a situation where we are rendering more than one token in each field because we are combining
1690 * tokens from more than one contribution when pdf thank you letters are grouped (CRM-14367)
1691 *
1692 * The replaceContributionToken doesn't handle receive_date correctly in this scenario because of the formatting
1693 * it applies (other tokens are OK including date fields)
1694 *
1695 * So we sort this out & then call the main function. Note that we are not escaping smarty on this fields like the main function
1696 * does - but the fields is already being formatted through a date function
1697 *
1698 * @param string $separator
1699 * @param string $str
2f5a20da 1700 * @param array $contributions
1701 * @param array $knownTokens
e39893f5 1702 *
31f2ebac
EM
1703 * @deprecated
1704 *
6f9518ec 1705 * @return string
383c047b 1706 */
2f5a20da 1707 public static function replaceMultipleContributionTokens(string $separator, string $str, array $contributions, array $knownTokens): string {
31f2ebac 1708 CRM_Core_Error::deprecatedFunctionWarning('no alternative');
2f5a20da 1709 foreach ($knownTokens['contribution'] ?? [] as $token) {
1710 $resolvedTokens = [];
1711 foreach ($contributions as $contribution) {
1712 $resolvedTokens[] = self::replaceContributionTokens('{contribution.' . $token . '}', $contribution, FALSE, $knownTokens);
383c047b 1713 }
2f5a20da 1714 $str = self::token_replace('contribution', $token, implode($separator, $resolvedTokens), $str);
383c047b 1715 }
2f5a20da 1716 return $str;
383c047b
DG
1717 }
1718
2d3e3c7b 1719 /**
1720 * Get replacement strings for any membership tokens (only a small number of tokens are implemnted in the first instance
1721 * - this is used by the pdfLetter task from membership search
d41a5d53
EM
1722 *
1723 * This is called via replaceEntityTokens.
1724 *
1725 * In the near term it will not be called at all from core as
1726 * the pdf letter task is updated to use the processor.
1727 *
1728 * @deprecated
1729 *
6f9518ec
CW
1730 * @param string $entity
1731 * should always be "membership"
2d3e3c7b 1732 * @param string $token
6f9518ec 1733 * field name
77855840
TO
1734 * @param array $membership
1735 * An api result array for a single membership.
6f9518ec 1736 * @return string token replacement
2d3e3c7b 1737 */
07945b3c 1738 public static function getMembershipTokenReplacement($entity, $token, $membership) {
d41a5d53
EM
1739 $supportedTokens = [
1740 'id',
1741 'status',
1742 'status_id',
1743 'type',
1744 'membership_type_id',
1745 'start_date',
1746 'join_date',
1747 'end_date',
1748 'fee',
1749 ];
e7292422
TO
1750 switch ($token) {
1751 case 'type':
d41a5d53
EM
1752 // membership_type_id would only be requested if the calling
1753 // class is mapping it to '{membership:membership_type_id:label'}
1754 case 'membership_type_id':
e7292422
TO
1755 $value = $membership['membership_name'];
1756 break;
1757
1758 case 'status':
d41a5d53
EM
1759 // status_id would only be requested if the calling
1760 // class is mapping it to '{membership:status_id:label'}
1761 case 'status_id':
e7292422
TO
1762 $statuses = CRM_Member_BAO_Membership::buildOptions('status_id');
1763 $value = $statuses[$membership['status_id']];
1764 break;
1765
1766 case 'fee':
92e4c2a5 1767 try {
be2fb01f 1768 $value = civicrm_api3('membership_type', 'getvalue', [
6714d8d2
SL
1769 'id' => $membership['membership_type_id'],
1770 'return' => 'minimum_fee',
1771 ]);
daec3f08 1772 $value = CRM_Utils_Money::format($value, NULL, NULL, TRUE);
e7292422
TO
1773 }
1774 catch (CiviCRM_API3_Exception $e) {
1775 // we can anticipate we will get an error if the minimum fee is set to 'NULL' because of the way the
1776 // api handles NULL (4.4)
1777 $value = 0;
1778 }
1779 break;
1780
1781 default:
d41a5d53 1782 if (in_array($token, $supportedTokens)) {
e7292422 1783 $value = $membership[$token];
7ab8b45b 1784 if (CRM_Utils_String::endsWith($token, '_date')) {
1785 $value = CRM_Utils_Date::customFormat($value);
1786 }
e7292422
TO
1787 }
1788 else {
50bfb460 1789 // ie unchanged
e7292422
TO
1790 $value = "{$entity}.{$token}";
1791 }
1792 break;
2d3e3c7b 1793 }
1794
2d3e3c7b 1795 return $value;
1796 }
1797
e39893f5 1798 /**
31f2ebac
EM
1799 * Do not use - unused in core.
1800 *
e39893f5
EM
1801 * @param $token
1802 * @param $contribution
1803 * @param bool $html
1804 * @param bool $escapeSmarty
1805 *
31f2ebac
EM
1806 * @deprecated
1807 *
e39893f5 1808 * @return mixed|string
3017ca78 1809 * @throws \CRM_Core_Exception
e39893f5 1810 */
ec20b780 1811 public static function getContributionTokenReplacement($token, $contribution, $html = FALSE, $escapeSmarty = FALSE) {
6a488035
TO
1812 self::_buildContributionTokens();
1813
1814 switch ($token) {
1815 case 'total_amount':
1816 case 'net_amount':
1817 case 'fee_amount':
1818 case 'non_deductible_amount':
2aa64508
JG
1819 // FIXME: Is this ever a multi-dimensional array? Why use retrieveValueRecursive()?
1820 $amount = CRM_Utils_Array::retrieveValueRecursive($contribution, $token);
1821 $currency = CRM_Utils_Array::retrieveValueRecursive($contribution, 'currency');
1822 $value = CRM_Utils_Money::format($amount, $currency);
6a488035
TO
1823 break;
1824
1825 case 'receive_date':
9a32c3e5 1826 case 'receipt_date':
6a488035 1827 $value = CRM_Utils_Array::retrieveValueRecursive($contribution, $token);
9a32c3e5
JF
1828 $config = CRM_Core_Config::singleton();
1829 $value = CRM_Utils_Date::customFormat($value, $config->dateformatDatetime);
6a488035
TO
1830 break;
1831
ec20b780
EM
1832 case 'source':
1833 $value = CRM_Utils_Array::retrieveValueRecursive($contribution, 'contribution_source');
1834 break;
1835
6a488035
TO
1836 default:
1837 if (!in_array($token, self::$_tokens['contribution'])) {
1838 $value = "{contribution.$token}";
1839 }
1840 else {
1841 $value = CRM_Utils_Array::retrieveValueRecursive($contribution, $token);
1842 }
1843 break;
1844 }
1845
6a488035
TO
1846 if ($escapeSmarty) {
1847 $value = self::tokenEscapeSmarty($value);
1848 }
1849 return $value;
1850 }
1851
091133bb 1852 /**
58b954cf
EM
1853 * @deprecated
1854 *
1855 * Only used from deprecated functions not called by core.
1856 *
a6c01b45 1857 * @return array
6f9518ec 1858 * [legacy_token => new_token]
091133bb 1859 */
00be9182 1860 public static function legacyContactTokens() {
be2fb01f 1861 return [
091133bb
CW
1862 'individual_prefix' => 'prefix_id',
1863 'individual_suffix' => 'suffix_id',
1864 'gender' => 'gender_id',
aa62b355 1865 'communication_style' => 'communication_style_id',
be2fb01f 1866 ];
091133bb
CW
1867 }
1868
18c017c8 1869 /**
1870 * Get all custom field tokens of $entity
1871 *
3232ec13
EM
1872 * @deprecated
1873 *
18c017c8 1874 * @param string $entity
6714d8d2 1875 * @return array
18c017c8 1876 * return custom field tokens in array('custom_N' => 'label') format
1877 */
075c519b 1878 public static function getCustomFieldTokens($entity) {
be2fb01f 1879 $customTokens = [];
18c017c8 1880 foreach (CRM_Core_BAO_CustomField::getFields($entity) as $id => $info) {
075c519b 1881 $customTokens['custom_' . $id] = $info['label'] . ' :: ' . $info['groupTitle'];
18c017c8 1882 }
18c017c8 1883 return $customTokens;
1884 }
1885
ac0a3db5
CW
1886 /**
1887 * Formats a token list for the select2 widget
9950a1c9 1888 *
ac0a3db5
CW
1889 * @param $tokens
1890 * @return array
1891 */
00be9182 1892 public static function formatTokensForDisplay($tokens) {
be2fb01f 1893 $sorted = $output = [];
ac0a3db5
CW
1894
1895 // Sort in ascending order by ignoring word case
1896 natcasesort($tokens);
1897
1898 // Attempt to place tokens into optgroups
50bfb460 1899 // @todo These groupings could be better and less hackish. Getting them pre-grouped from upstream would be nice.
ac0a3db5
CW
1900 foreach ($tokens as $k => $v) {
1901 // Check to see if this token is already in a group e.g. for custom fields
1902 $split = explode(' :: ', $v);
1903 if (!empty($split[1])) {
be2fb01f 1904 $sorted[$split[1]][] = ['id' => $k, 'text' => $split[0]];
ac0a3db5
CW
1905 }
1906 // Group by entity
1907 else {
1908 $split = explode('.', trim($k, '{}'));
cc666210 1909 if (isset($split[1])) {
f274450a 1910 $entity = array_key_exists($split[1], CRM_Core_DAO_Address::export()) ? 'Address' : ucwords(str_replace('_', ' ', $split[0]));
cc666210
CW
1911 }
1912 else {
1913 $entity = 'Contact';
1914 }
be2fb01f 1915 $sorted[ts($entity)][] = ['id' => $k, 'text' => $v];
ac0a3db5
CW
1916 }
1917 }
1918
1919 ksort($sorted);
1920 foreach ($sorted as $k => $v) {
be2fb01f 1921 $output[] = ['text' => $k, 'children' => $v];
ac0a3db5
CW
1922 }
1923
1924 return $output;
1925 }
96025800 1926
ea8be131 1927 /**
1928 * @param $value
1929 * @param $token
1930 *
1931 * @return bool|int|mixed|string|null
1932 */
1933 protected static function convertPseudoConstantsUsingMetadata($value, $token) {
1934 // Convert pseudoconstants using metadata
1935 if ($value && is_numeric($value)) {
1936 $allFields = CRM_Contact_BAO_Contact::exportableFields('All');
1937 if (!empty($allFields[$token]['pseudoconstant'])) {
1938 $value = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $token, $value);
1939 }
1940 }
1941 elseif ($value && CRM_Utils_String::endsWith($token, '_date')) {
1942 $value = CRM_Utils_Date::customFormat($value);
1943 }
1944 return $value;
1945 }
1946
58169187
EM
1947 /**
1948 * Get token deprecation information.
1949 *
1950 * @return array
1951 */
1952 public static function getTokenDeprecations(): array {
1953 return [
1954 'WorkFlowMessageTemplates' => [
1955 'contribution_invoice_receipt' => [
1956 '$display_name' => 'contact.display_name',
1957 ],
1f7cb15c
EM
1958 'contribution_online_receipt' => [
1959 '$contributeMode' => 'no longer available / relevant',
1960 '$first_name' => 'contact.first_name',
1961 '$last_name' => 'contact.last_name',
1962 '$displayName' => 'contact.display_name',
1963 ],
7b2d6751
EM
1964 'membership_offline_receipt' => [
1965 // receipt_text_renewal appears to be long gone.
1966 'receipt_text_renewal' => 'receipt_text',
1967 ],
aa7dd30f
EM
1968 'pledge_acknowledgement' => [
1969 '$domain' => 'no longer available / relevant',
1970 '$contact' => 'no longer available / relevant',
1971 ],
21585345
EM
1972 'pledge_reminder' => [
1973 '$domain' => 'no longer available / relevant',
1974 '$contact' => 'no longer available / relevant',
1975 ],
58169187
EM
1976 ],
1977 ];
1978 }
1979
6a488035 1980}