Merge pull request #21780 from eileenmcnaughton/api4load
[civicrm-core.git] / CRM / Contact / Tokens.php
CommitLineData
a7f29bf4
EM
1<?php
2
3/*
4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
6 | |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
11 */
12
12ea84cf 13use Civi\Api4\Contact;
a7f29bf4 14use Civi\Token\Event\TokenRegisterEvent;
a7f29bf4
EM
15use Civi\Token\Event\TokenValueEvent;
16use Civi\Token\TokenProcessor;
17use Civi\Token\TokenRow;
18
19/**
20 * Class CRM_Contact_Tokens
21 *
22 * Generate "contact.*" tokens.
23 */
24class CRM_Contact_Tokens extends CRM_Core_EntityTokens {
25
26 /**
27 * Get the entity name for api v4 calls.
28 *
29 * @return string
30 */
31 protected function getApiEntityName(): string {
32 return 'Contact';
33 }
34
35 /**
36 * @inheritDoc
37 */
38 public static function getSubscribedEvents(): array {
39 return [
40 'civi.token.eval' => [
41 ['evaluateLegacyHookTokens', 500],
42 ['onEvaluate'],
43 ],
44 'civi.token.list' => 'registerTokens',
45 ];
46 }
47
48 /**
49 * Register the declared tokens.
50 *
51 * @param \Civi\Token\Event\TokenRegisterEvent $e
52 * The registration event. Add new tokens using register().
53 *
54 * @throws \CRM_Core_Exception
55 */
56 public function registerTokens(TokenRegisterEvent $e): void {
57 if (!$this->checkActive($e->getTokenProcessor())) {
58 return;
59 }
60 foreach (array_merge($this->getContactTokens(), $this->getCustomFieldTokens()) as $name => $label) {
61 $e->register([
62 'entity' => $this->entity,
63 'field' => $name,
64 'label' => $label,
65 ]);
66 }
67 foreach ($this->getLegacyHookTokens() as $legacyHookToken) {
68 $e->register([
69 'entity' => $legacyHookToken['category'],
70 'field' => $legacyHookToken['name'],
71 'label' => $legacyHookToken['label'],
72 ]);
73 }
74 }
75
76 /**
77 * Determine whether this token-handler should be used with
78 * the given processor.
79 *
80 * To short-circuit token-processing in irrelevant contexts,
81 * override this.
82 *
83 * @param \Civi\Token\TokenProcessor $processor
84 * @return bool
85 */
86 public function checkActive(TokenProcessor $processor): bool {
87 return in_array($this->getEntityIDField(), $processor->context['schema'], TRUE);
88 }
89
90 /**
91 * @return string
92 */
93 public function getEntityIDField(): string {
94 return 'contactId';
95 }
96
97 /**
98 * Get functions declared using the legacy hook.
99 *
100 * Note that these only extend the contact entity (
101 * ie they are based on having a contact ID which they.
102 * may or may not use, but they don't have other
103 * entity IDs.)
104 *
105 * @return array
106 */
107 public function getLegacyHookTokens(): array {
108 $tokens = [];
109 $hookTokens = [];
110 \CRM_Utils_Hook::tokens($hookTokens);
111 foreach ($hookTokens as $tokenValues) {
112 foreach ($tokenValues as $key => $value) {
113 if (is_numeric($key)) {
114 // This appears to be an attempt to compensate for
115 // inconsistencies described in https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_tokenValues/#example
116 // in effect there is a suggestion that
117 // Send an Email" and "CiviMail" send different parameters to the tokenValues hook
118 // As of now 'send an email' renders hooks through this class.
119 // CiviMail it depends on the use or otherwise of flexmailer.
120 $key = $value;
121 }
122 if (preg_match('/^\{([^\}]+)\}$/', $value, $matches)) {
123 $value = $matches[1];
124 }
125 $keyParts = explode('.', $key);
126 $tokens[$key] = [
127 'category' => $keyParts[0],
128 'name' => $keyParts[1],
129 'label' => $value,
130 ];
131 }
132 }
133 return $tokens;
134 }
135
136 /**
137 * @return array
138 * @throws \CRM_Core_Exception
139 */
140 public function getCustomFieldTokens(): array {
141 $tokens = [];
142 $customFields = \CRM_Core_BAO_CustomField::getFields(['Individual', 'Address', 'Contact']);
143 foreach ($customFields as $customField) {
144 $tokens['custom_' . $customField['id']] = $customField['label'] . " :: " . $customField['groupTitle'];
145 }
146 return $tokens;
147 }
148
149 /**
150 * Get all tokens advertised as contact tokens.
151 *
152 * @return string[]
153 */
154 public function getContactTokens(): array {
155 return [
156 'contact_type' => 'Contact Type',
157 'do_not_email' => 'Do Not Email',
158 'do_not_phone' => 'Do Not Phone',
159 'do_not_mail' => 'Do Not Mail',
160 'do_not_sms' => 'Do Not Sms',
161 'do_not_trade' => 'Do Not Trade',
162 'is_opt_out' => 'No Bulk Emails (User Opt Out)',
163 'external_identifier' => 'External Identifier',
164 'sort_name' => 'Sort Name',
165 'display_name' => 'Display Name',
166 'nick_name' => 'Nickname',
167 'image_URL' => 'Image Url',
168 'preferred_communication_method:label' => 'Preferred Communication Method',
169 'preferred_language:label' => 'Preferred Language',
170 'preferred_mail_format:label' => 'Preferred Mail Format',
171 'hash' => 'Contact Hash',
172 'source' => 'Contact Source',
173 'first_name' => 'First Name',
174 'middle_name' => 'Middle Name',
175 'last_name' => 'Last Name',
176 'prefix_id:label' => 'Individual Prefix',
177 'suffix_id:label' => 'Individual Suffix',
178 'formal_title' => 'Formal Title',
179 'communication_style_id:label' => 'Communication Style',
180 'job_title' => 'Job Title',
181 'gender_id:label' => 'Gender ID',
182 'birth_date' => 'Birth Date',
183 'current_employer_id' => 'Current Employer ID',
184 'is_deleted:label' => 'Contact is in Trash',
185 'created_date' => 'Created Date',
186 'modified_date' => 'Modified Date',
187 'addressee_display' => 'Addressee',
188 'email_greeting_display' => 'Email Greeting',
189 'postal_greeting_display' => 'Postal Greeting',
190 'current_employer' => 'Current Employer',
191 'location_type' => 'Location Type',
192 'address_id' => 'Address ID',
193 'street_address' => 'Street Address',
194 'street_number' => 'Street Number',
195 'street_number_suffix' => 'Street Number Suffix',
196 'street_name' => 'Street Name',
197 'street_unit' => 'Street Unit',
198 'supplemental_address_1' => 'Supplemental Address 1',
199 'supplemental_address_2' => 'Supplemental Address 2',
200 'supplemental_address_3' => 'Supplemental Address 3',
201 'city' => 'City',
202 'postal_code_suffix' => 'Postal Code Suffix',
203 'postal_code' => 'Postal Code',
204 'geo_code_1' => 'Latitude',
205 'geo_code_2' => 'Longitude',
206 'manual_geo_code' => 'Is Manually Geocoded',
207 'address_name' => 'Address Name',
208 'master_id' => 'Master Address ID',
209 'county' => 'County',
210 'state_province' => 'State',
211 'country' => 'Country',
212 'phone' => 'Phone',
213 'phone_ext' => 'Phone Extension',
214 'phone_type_id' => 'Phone Type ID',
215 'phone_type' => 'Phone Type',
216 'email' => 'Email',
217 'on_hold' => 'On Hold',
218 'signature_text' => 'Signature Text',
219 'signature_html' => 'Signature Html',
220 'im_provider' => 'IM Provider',
221 'im' => 'IM Screen Name',
222 'openid' => 'OpenID',
223 'world_region' => 'World Region',
224 'url' => 'Website',
225 'checksum' => 'Checksum',
226 'id' => 'Internal Contact ID',
227 ];
228 }
229
12ea84cf
EM
230 /**
231 * Get all tokens advertised as contact tokens.
232 *
233 * @return string[]
234 */
235 public function getExposedFields(): array {
236 return [
237 'contact_type',
238 'do_not_email',
239 'do_not_phone',
240 'do_not_mail',
241 'do_not_sms',
242 'do_not_trade',
243 'is_opt_out',
244 'external_identifier',
245 'sort_name',
246 'display_name',
247 'nick_name',
248 'image_URL',
249 'preferred_communication_method',
250 'preferred_language',
251 'preferred_mail_format',
252 'hash',
253 'source',
254 'first_name',
255 'middle_name',
256 'last_name',
257 'prefix_id',
258 'suffix_id',
259 'formal_title',
260 'communication_style_id',
261 'job_title',
262 'gender_id',
263 'birth_date',
264 'employer_id',
265 'is_deleted',
266 'created_date',
267 'modified_date',
268 'addressee_display',
269 'email_greeting_display',
270 'postal_greeting_display',
271 'id',
272 ];
273 }
274
275 /**
276 * Get the fields exposed from related entities.
277 *
278 * @return \string[][]
279 */
280 protected function getRelatedEntityTokenMetadata(): array {
281 return [
282 'address' => [
283 'location_type_id',
284 'id',
285 'street_address',
286 'street_number',
287 'street_number_suffix',
288 'street_name',
289 'street_unit',
290 'supplemental_address_1',
291 'supplemental_address_2',
292 'supplemental_address_3',
293 'city',
294 'postal_code_suffix',
295 'postal_code',
296 'manual_geo_code',
297 'geo_code_1',
298 'geo_code_2',
299 'name',
300 'master_id',
301 'county_id',
302 'state_province_id',
303 'country_id',
304 ],
305 'phone' => ['phone', 'phone_ext', 'phone_type_id'],
306 'email' => ['email', 'signature_html', 'signature_text', 'on_hold'],
307 'website' => ['url'],
308 'openid' => ['openid'],
309 'im' => ['name', 'provider_id'],
310 ];
311 }
312
a7f29bf4
EM
313 /**
314 * Load token data from legacy hooks.
315 *
316 * While our goal is for people to move towards implementing
317 * toke processors the old-style hooks can extend contact
318 * token data.
319 *
320 * When that is happening we need to load the full contact record
321 * to send to the hooks (not great for performance but the
322 * fix is to move away from implementing legacy style hooks).
323 *
324 * Consistent with prior behaviour we only load the contact it it
325 * is already loaded. In that scenario we also load any extra fields
326 * that might be wanted for the contact tokens.
327 *
328 * @param \Civi\Token\Event\TokenValueEvent $e
12ea84cf
EM
329 *
330 * @throws \CRM_Core_Exception
a7f29bf4
EM
331 */
332 public function evaluateLegacyHookTokens(TokenValueEvent $e): void {
333 $messageTokens = $e->getTokenProcessor()->getMessageTokens();
334 $hookTokens = array_intersect(\CRM_Utils_Token::getTokenCategories(), array_keys($messageTokens));
335 if (empty($hookTokens)) {
336 return;
337 }
338 foreach ($e->getRows() as $row) {
339 if (empty($row->context['contactId'])) {
340 continue;
341 }
342 unset($swapLocale);
343 $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']);
344 if (empty($row->context['contact'])) {
345 // If we don't have the contact already load it now, getting full
346 // details for hooks and anything the contact token resolution might
347 // want later.
348 $row->context['contact'] = $this->getContact($row->context['contactId'], $messageTokens['contact'] ?? [], TRUE);
349 }
350 $contactArray = [$row->context['contactId'] => $row->context['contact']];
351 \CRM_Utils_Hook::tokenValues($contactArray,
352 [$row->context['contactId']],
353 empty($row->context['mailingJobId']) ? NULL : $row->context['mailingJobId'],
354 $messageTokens,
355 $row->context['controller']
356 );
357 foreach ($hookTokens as $hookToken) {
358 foreach ($messageTokens[$hookToken] as $tokenName) {
359 $row->format('text/html')->tokens($hookToken, $tokenName, $contactArray[$row->context['contactId']]["{$hookToken}.{$tokenName}"] ?? '');
360 }
361 }
362 }
363 }
364
365 /**
366 * Load token data.
367 *
368 * @param \Civi\Token\Event\TokenValueEvent $e
369 *
370 * @throws TokenException
371 * @throws \CRM_Core_Exception
372 */
373 public function onEvaluate(TokenValueEvent $e) {
374 $messageTokens = $e->getTokenProcessor()->getMessageTokens()['contact'] ?? [];
375 if (empty($messageTokens)) {
376 return;
377 }
378 $this->fieldMetadata = (array) civicrm_api4('Contact', 'getfields', ['checkPermissions' => FALSE], 'name');
379
380 foreach ($e->getRows() as $row) {
381 if (empty($row->context['contactId']) && empty($row->context['contact'])) {
382 continue;
383 }
384
385 unset($swapLocale);
386 $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']);
387
388 if (empty($row->context['contact'])) {
389 $row->context['contact'] = $this->getContact($row->context['contactId'], $messageTokens);
390 }
391
392 foreach ($messageTokens as $token) {
393 if ($token === 'checksum') {
394 $cs = \CRM_Contact_BAO_Contact_Utils::generateChecksum($row->context['contactId'],
395 NULL,
396 NULL,
397 $row->context['hash'] ?? NULL
398 );
399 $row->format('text/html')
400 ->tokens('contact', $token, "cs={$cs}");
401 }
402 elseif (!empty($row->context['contact'][$token]) &&
403 $this->isDateField($token)
404 ) {
405 // Handle dates here, for now. Standardise with other token entities next round
406 $row->format('text/plain')->tokens('contact', $token, \CRM_Utils_Date::customFormat($row->context['contact'][$token]));
407 }
408 elseif (
409 ($row->context['contact'][$token] ?? '') == 0
410 && $this->isBooleanField($token)) {
411 // Note this will be the default behaviour once we fetch with apiv4.
412 $row->format('text/plain')->tokens('contact', $token, '');
413 }
414 elseif ($token === 'signature_html') {
415 $row->format('text/html')->tokens('contact', $token, html_entity_decode($row->context['contact'][$token]));
416 }
417 else {
12ea84cf
EM
418 $value = $this->getFieldValue($row, $token);
419 if (is_array($value)) {
420 $value = implode(',', $value);
421 }
a7f29bf4 422 $row->format('text/html')
12ea84cf 423 ->tokens('contact', $token, $value);
a7f29bf4
EM
424 }
425 }
426 }
427 }
428
429 /**
430 * Get the field value.
431 *
432 * @param \Civi\Token\TokenRow $row
433 * @param string $field
434 * @return string|int
435 */
436 protected function getFieldValue(TokenRow $row, string $field) {
437 $entityName = 'contact';
438 if (isset($this->getDeprecatedTokens()[$field])) {
439 // Check the non-deprecated location first, fall back to deprecated
440 // this is important for the greetings because - they are weird in the query object.
441 $possibilities = [$this->getDeprecatedTokens()[$field], $field];
442 }
443 else {
444 $possibilities = [$field];
445 if (in_array($field, $this->getDeprecatedTokens(), TRUE)) {
446 $possibilities[] = array_search($field, $this->getDeprecatedTokens(), TRUE);
447 }
448 }
449
450 foreach ($possibilities as $possibility) {
451 if (isset($row->context[$entityName][$possibility])) {
452 return $row->context[$entityName][$possibility];
453 }
454 }
455 return '';
456 }
457
458 /**
459 * Is the given field a boolean field.
460 *
461 * @param string $fieldName
462 *
463 * @return bool
464 */
465 public function isBooleanField(string $fieldName): bool {
466 // no metadata for these 2 non-standard fields
467 // @todo - fix to api v4 & have metadata for all fields. Migrate contact_is_deleted
468 // to {contact.is_deleted}. on hold feels like a token that exists by
469 // accident & could go.... since it's not from the main entity.
470 if (in_array($fieldName, ['contact_is_deleted', 'on_hold'])) {
471 return TRUE;
472 }
473 if (empty($this->getFieldMetadata()[$fieldName])) {
474 return FALSE;
475 }
476 return $this->getFieldMetadata()[$fieldName]['data_type'] === 'Boolean';
477 }
478
479 /**
480 * Is the given field a date field.
481 *
482 * @param string $fieldName
483 *
484 * @return bool
485 */
486 public function isDateField(string $fieldName): bool {
487 if (empty($this->getFieldMetadata()[$fieldName])) {
488 return FALSE;
489 }
490 return in_array($this->getFieldMetadata()[$fieldName]['data_type'], ['Timestamp', 'Date'], TRUE);
491 }
492
493 /**
494 * Get the metadata for the available fields.
495 *
496 * @return array
12ea84cf
EM
497 * @noinspection PhpDocMissingThrowsInspection
498 * @noinspection PhpUnhandledExceptionInspection
a7f29bf4 499 */
12ea84cf
EM
500 protected function getTokenMetadata(): array {
501 if ($this->tokensMetadata) {
502 return $this->tokensMetadata;
503 }
504 if (Civi::cache('metadata')->has($this->getCacheKey())) {
505 return Civi::cache('metadata')->get($this->getCacheKey());
506 }
507 $this->fieldMetadata = (array) civicrm_api4('Contact', 'getfields', ['checkPermissions' => FALSE], 'name');
508 $this->tokensMetadata = $this->getBespokeTokens();
509 foreach ($this->fieldMetadata as $field) {
510 $this->addFieldToTokenMetadata($field, $this->getExposedFields());
511 }
512
513 foreach ($this->getRelatedEntityTokenMetadata() as $entity => $exposedFields) {
514 $apiEntity = ($entity === 'openid') ? 'OpenID' : $entity;
515 $metadata = (array) civicrm_api4($apiEntity, 'getfields', ['checkPermissions' => FALSE], 'name');
516 foreach ($metadata as $field) {
517 $this->addFieldToTokenMetadata($field, $exposedFields, 'primary_' . $entity);
a7f29bf4 518 }
12ea84cf
EM
519 // Manually add in the abbreviated state province as that maps to
520 // what has traditionally been delivered.
521 $this->tokensMetadata['primary_address.state_province_id:abbr'] = $this->tokensMetadata['primary_address.state_province_id:label'];
a7f29bf4 522 }
12ea84cf
EM
523 Civi::cache('metadata')->set($this->getCacheKey(), $this->tokensMetadata);
524 return $this->tokensMetadata;
a7f29bf4
EM
525 }
526
a7f29bf4
EM
527 /**
528 * Get the contact for the row.
529 *
530 * @param int $contactId
531 * @param array $requiredFields
532 * @param bool $getAll
533 *
534 * @return array
535 * @throws \CRM_Core_Exception
536 */
537 protected function getContact(int $contactId, array $requiredFields, bool $getAll = FALSE): array {
12ea84cf
EM
538 $returnProperties = [];
539 if (in_array('checksum', $requiredFields, TRUE)) {
540 $returnProperties[] = 'hash';
541 }
542 foreach ($this->getTokenMappingsForRelatedEntities() as $oldName => $newName) {
543 if (in_array($oldName, $requiredFields, TRUE)) {
544 $returnProperties[] = $newName;
545 }
a7f29bf4 546 }
12ea84cf
EM
547 $joins = [];
548 $customFields = [];
549 foreach ($requiredFields as $field) {
550 $fieldSpec = $this->getMetadataForField($field);
551 $prefix = '';
552 if (isset($fieldSpec['table_name']) && $fieldSpec['table_name'] !== 'civicrm_contact') {
553 $tableAlias = str_replace('civicrm_', 'primary_', $fieldSpec['table_name']);
554 $joins[$tableAlias] = $fieldSpec['entity'];
a7f29bf4 555
12ea84cf 556 $prefix = $tableAlias . '.';
a7f29bf4 557 }
12ea84cf
EM
558 if ($fieldSpec['type'] === 'Custom') {
559 $customFields['custom_' . $fieldSpec['custom_field_id']] = $fieldSpec['name'];
560 }
561 $returnProperties[] = $prefix . $this->getMetadataForField($field)['name'];
a7f29bf4 562 }
12ea84cf 563
a7f29bf4 564 if ($getAll) {
12ea84cf 565 $returnProperties = array_merge(['*', 'custom.*'], $this->getDeprecatedTokens(), $this->getTokenMappingsForRelatedEntities());
a7f29bf4
EM
566 }
567
12ea84cf
EM
568 $contactApi = Contact::get($this->checkPermissions)
569 ->setSelect($returnProperties)->addWhere('id', '=', $contactId);
570 foreach ($joins as $alias => $joinEntity) {
571 $contactApi->addJoin($joinEntity . ' AS ' . $alias,
572 'LEFT',
573 ['id', '=', $alias . '.contact_id'],
574 // For website the fact we use 'first' is the deduplication.
575 ($joinEntity !== 'Website' ? [$alias . '.is_primary', '=', 1] : []));
576 }
577 $contact = $contactApi->execute()->first();
578
579 foreach ($this->getDeprecatedTokens() as $apiv3Name => $fieldName) {
a7f29bf4
EM
580 // it would be set already with the right value for a greeting token
581 // the query object returns the db value for email_greeting_display
582 // and a numeric value for email_greeting if you put email_greeting
583 // in the return properties.
12ea84cf
EM
584 if (!isset($contact[$apiv3Name]) && array_key_exists($fieldName, $contact)) {
585 $contact[$apiv3Name] = $contact[$fieldName];
586 }
587 }
588 foreach ($this->getTokenMappingsForRelatedEntities() as $oldName => $newName) {
589 if (isset($contact[$newName])) {
590 $contact[$oldName] = $contact[$newName];
a7f29bf4
EM
591 }
592 }
593
594 //update value of custom field token
12ea84cf
EM
595 foreach ($customFields as $apiv3Name => $fieldName) {
596 $value = $contact[$fieldName];
597 if ($this->getMetadataForField($apiv3Name)['data_type'] === 'Boolean') {
598 $value = (int) $value;
a7f29bf4 599 }
12ea84cf 600 $contact[$apiv3Name] = \CRM_Core_BAO_CustomField::displayValue($value, \CRM_Core_BAO_CustomField::getKeyID($apiv3Name));
a7f29bf4
EM
601 }
602
603 return $contact;
604 }
605
606 /**
607 * Get the array of the return fields from 'get all'.
608 *
609 * This is the list from the BAO_Query object but copied
610 * here to be 'frozen in time'. The goal is to map to apiv4
611 * and stop using the legacy call to load the contact.
612 *
613 * @return array
614 */
615 protected function getAllContactReturnFields(): array {
616 return [
617 'image_URL' => 1,
618 'legal_identifier' => 1,
619 'external_identifier' => 1,
620 'contact_type' => 1,
621 'contact_sub_type' => 1,
622 'sort_name' => 1,
623 'display_name' => 1,
624 'preferred_mail_format' => 1,
625 'nick_name' => 1,
626 'first_name' => 1,
627 'middle_name' => 1,
628 'last_name' => 1,
629 'prefix_id' => 1,
630 'suffix_id' => 1,
631 'formal_title' => 1,
632 'communication_style_id' => 1,
633 'birth_date' => 1,
634 'gender_id' => 1,
635 'street_address' => 1,
636 'supplemental_address_1' => 1,
637 'supplemental_address_2' => 1,
638 'supplemental_address_3' => 1,
639 'city' => 1,
640 'postal_code' => 1,
641 'postal_code_suffix' => 1,
642 'state_province' => 1,
643 'country' => 1,
644 'world_region' => 1,
645 'geo_code_1' => 1,
646 'geo_code_2' => 1,
647 'email' => 1,
648 'on_hold' => 1,
649 'phone' => 1,
650 'im' => 1,
651 'household_name' => 1,
652 'organization_name' => 1,
653 'deceased_date' => 1,
654 'is_deceased' => 1,
655 'job_title' => 1,
656 'legal_name' => 1,
657 'sic_code' => 1,
658 'current_employer' => 1,
659 'do_not_email' => 1,
660 'do_not_mail' => 1,
661 'do_not_sms' => 1,
662 'do_not_phone' => 1,
663 'do_not_trade' => 1,
664 'is_opt_out' => 1,
665 'contact_is_deleted' => 1,
666 'preferred_communication_method' => 1,
667 'preferred_language' => 1,
668 ];
669 }
670
671 /**
672 * These tokens still work but we don't advertise them.
673 *
674 * We can remove from the following places
675 * - scheduled reminders
676 * - add to 'blocked' on pdf letter & email
677 *
678 * & then at some point start issuing warnings for them
679 * but contact tokens are pretty central so it might be
680 * a bit drawn out.
681 *
682 * @return string[]
683 * Keys are deprecated tokens and values are their replacements.
684 */
685 protected function getDeprecatedTokens(): array {
686 return [
687 'individual_prefix' => 'prefix_id:label',
688 'individual_suffix' => 'suffix_id:label',
689 'gender' => 'gender_id:label',
690 'communication_style' => 'communication_style_id:label',
691 'preferred_communication_method' => 'preferred_communication_method:label',
692 'email_greeting' => 'email_greeting_display',
693 'postal_greeting' => 'postal_greeting_display',
694 'addressee' => 'addressee_display',
695 'contact_id' => 'id',
696 'contact_source' => 'source',
697 'contact_is_deleted' => 'is_deleted:label',
12ea84cf
EM
698 'current_employer_id' => 'employer_id',
699 ];
700 }
701
702 /**
703 * Get the tokens that are accessed by joining onto a related entity.
704 *
705 * Note the original thinking was to migrate to advertising the tokens
706 * that more accurately reflect the schema & also add support for e.g
707 * billing_address.street_address - which would be hugely useful for workflow
708 * message templates.
709 *
710 * However that feels like a bridge too far for this round
711 * since we haven't quite hit the goal of all token processing going through
712 * the token processor & we risk advertising tokens that don't work if we get
713 * ahead of that process.
714 *
715 * @return string[]
716 */
717 protected function getTokenMappingsForRelatedEntities(): array {
718 return [
719 'on_hold' => 'primary_email.on_hold:label',
720 'on_hold:label' => 'primary_email.on_hold:label',
721 'phone_type_id' => 'primary_phone.phone_type_id',
722 'phone_type_id:label' => 'primary_phone.phone_type_id:label',
723 'current_employer' => 'employer_id.display_name',
724 'location_type_id' => 'primary_address.location_type_id',
725 'location_type' => 'primary_address.location_type_id:label',
726 'location_type_id:label' => 'primary_address.location_type_id:label',
727 'street_address' => 'primary_address.street_address',
728 'address_id' => 'primary_address.id',
729 'address_name' => 'primary_address.name',
730 'street_number' => 'primary_address.street_number',
731 'street_number_suffix' => 'primary_address.street_number_suffix',
732 'street_name' => 'primary_address.street_name',
733 'street_unit' => 'primary_address.street_unit',
734 'supplemental_address_1' => 'primary_address.supplemental_address_1',
735 'supplemental_address_2' => 'primary_address.supplemental_address_2',
736 'supplemental_address_3' => 'primary_address.supplemental_address_3',
737 'city' => 'primary_address.city',
738 'postal_code' => 'primary_address.postal_code',
739 'postal_code_suffix' => 'primary_address.postal_code_suffix',
740 'geo_code_1' => 'primary_address.geo_code_1',
741 'geo_code_2' => 'primary_address.geo_code_2',
742 'manual_geo_code' => 'primary_address.manual_geo_code',
743 'master_id' => 'primary_address.master_id',
744 'county' => 'primary_address.county_id:label',
745 'county_id' => 'primary_address.county_id',
746 'county_id:label' => 'primary_address.county_id:label',
747 'state_province' => 'primary_address.state_province_id:abbr',
748 'state_province_id' => 'primary_address.state_province_id',
749 'country' => 'primary_address.country_id:label',
750 'country_id' => 'primary_address.country_id',
751 'country_id:label' => 'primary_address.country_id:label',
752 'world_region' => 'primary_address.country_id.region_id:name',
753 'phone_type' => 'primary_phone.phone_type_id:label',
754 'phone' => 'primary_phone.phone',
755 'phone_ext' => 'primary_phone.phone_ext',
756 'email' => 'primary_email.email',
757 'signature_text' => 'primary_email.signature_text',
758 'signature_html' => 'primary_email.signature_html',
759 'im' => 'primary_im.name',
760 'im_provider' => 'primary_im.provider_id',
761 'provider_id:label' => 'primary_im.provider_id:label',
762 'provider_id' => 'primary_im.provider_id',
763 'openid' => 'primary_openid.openid',
764 'url' => 'primary_website.url',
765 ];
766 }
767
768 /**
769 * Get calculated or otherwise 'special', tokens.
770 *
771 * @return array[]
772 */
773 protected function getBespokeTokens(): array {
774 return [
775 'checksum' => [
776 'title' => ts('Checksum'),
777 'name' => 'checksum',
778 'type' => 'calculated',
779 'options' => NULL,
780 'data_type' => 'String',
781 'audience' => 'user',
782 ],
783 'employer_id.display_name' => [
784 'title' => ts('Current Employer'),
785 'name' => 'employer_id.display_name',
786 'type' => 'mapped',
787 'api_v3' => 'current_employer',
788 'options' => NULL,
789 'data_type' => 'String',
790 'audience' => 'user',
791 ],
792 'primary_address.country_id.region_id:name' => [
793 'title' => ts('World Region'),
794 'name' => 'country_id.region_id.name',
795 'type' => 'mapped',
796 'api_v3' => 'world_region',
797 'options' => NULL,
798 'data_type' => 'String',
799 'advertised_name' => 'world_region',
800 'audience' => 'user',
801 ],
802 // this gets forced out if we specify individual fields
803 'organization_name' => [
804 'title' => ts('Organization name'),
805 'name' => 'organization_name',
806 'type' => 'Field',
807 'options' => NULL,
808 'data_type' => 'String',
809 'audience' => 'sysadmin',
810 ],
a7f29bf4
EM
811 ];
812 }
813
814}