Consolidate building of contact token list
authorEileen McNaughton <emcnaughton@wikimedia.org>
Fri, 10 Sep 2021 02:28:00 +0000 (14:28 +1200)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Fri, 10 Sep 2021 04:28:23 +0000 (16:28 +1200)
CRM/Core/SelectValues.php
Civi/Token/TokenCompatSubscriber.php

index 6bb4e60eca718d45f2ccb4390e5817416f099d0d..a0ffa0d81da8ff9abcb903a8ac6795d09e2c8f6c 100644 (file)
@@ -9,6 +9,8 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\Token\TokenProcessor;
+
 /**
  * One place to store frequently used values in Select Elements. Note that
  * some of the below elements will be dynamic, so we'll probably have a
@@ -576,77 +578,9 @@ class CRM_Core_SelectValues {
    *
    * @return array
    */
-  public static function contactTokens() {
-    if (!isset(Civi::$statics[__CLASS__ . __FUNCTION__])) {
-      $additionalFields = [
-        'checksum' => ['title' => ts('Checksum')],
-        'contact_id' => ['title' => ts('Internal Contact ID')],
-      ];
-      $exportFields = array_merge(CRM_Contact_BAO_Contact::exportableFields(), $additionalFields);
-
-      $values = array_merge(array_keys($exportFields));
-      unset($values[0]);
-
-      //FIXME:skipping some tokens for time being.
-      $skipTokens = [
-        'is_bulkmail',
-        'group',
-        'tag',
-        'contact_sub_type',
-        'note',
-        'is_deceased',
-        'deceased_date',
-        'legal_identifier',
-        'contact_sub_type',
-        'user_unique_id',
-        'addressee_id',
-        'email_greeting_id',
-        'postal_greeting_id',
-      ];
-
-      $customFields = CRM_Core_BAO_CustomField::getFields(['Individual', 'Address']);
-      $legacyTokenNames = array_flip(CRM_Utils_Token::legacyContactTokens());
-
-      foreach ($values as $val) {
-        if (in_array($val, $skipTokens)) {
-          continue;
-        }
-        //keys for $tokens should be constant. $token Values are changed for Custom Fields. CRM-3734
-        $customFieldId = CRM_Core_BAO_CustomField::getKeyID($val);
-        if ($customFieldId) {
-          // CRM-15191 - if key is not in $customFields then the field is disabled and should be ignored
-          if (!empty($customFields[$customFieldId])) {
-            $tokens["{contact.$val}"] = $customFields[$customFieldId]['label'] . " :: " . $customFields[$customFieldId]['groupTitle'];
-          }
-        }
-        else {
-          // Support legacy token names
-          $tokenName = CRM_Utils_Array::value($val, $legacyTokenNames, $val);
-          $tokens["{contact.$tokenName}"] = $exportFields[$val]['title'];
-        }
-      }
-
-      // Get all the hook tokens too
-      $hookTokens = [];
-      CRM_Utils_Hook::tokens($hookTokens);
-      foreach ($hookTokens as $tokenValues) {
-        foreach ($tokenValues as $key => $value) {
-          if (is_numeric($key)) {
-            $key = $value;
-          }
-          if (!preg_match('/^\{[^\}]+\}$/', $key)) {
-            $key = '{' . $key . '}';
-          }
-          if (preg_match('/^\{([^\}]+)\}$/', $value, $matches)) {
-            $value = $matches[1];
-          }
-          $tokens[$key] = $value;
-        }
-      }
-      Civi::$statics[__CLASS__ . __FUNCTION__] = $tokens;
-    }
-
-    return Civi::$statics[__CLASS__ . __FUNCTION__];
+  public static function contactTokens(): array {
+    $tokenProcessor = new TokenProcessor(Civi::dispatcher(), ['schema' => ['contactId']]);
+    return $tokenProcessor->listTokens();
   }
 
   /**
index 88c1221c36eb318f4a40b5ba556abc7a883e9156..f6abbfd8ae6442639fc335bb414995bffced9e11 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace Civi\Token;
 
+use Civi\Token\Event\TokenRegisterEvent;
 use Civi\Token\Event\TokenRenderEvent;
 use Civi\Token\Event\TokenValueEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -20,16 +21,199 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  */
 class TokenCompatSubscriber implements EventSubscriberInterface {
 
+  protected $entity = 'contact';
+
   /**
    * @inheritDoc
    */
-  public static function getSubscribedEvents() {
+  public static function getSubscribedEvents(): array {
     return [
       'civi.token.eval' => [
         ['setupSmartyAliases', 1000],
         ['onEvaluate'],
       ],
       'civi.token.render' => 'onRender',
+      'civi.token.list' => 'registerTokens',
+    ];
+  }
+
+  /**
+   * Register the declared tokens.
+   *
+   * @param \Civi\Token\Event\TokenRegisterEvent $e
+   *   The registration event. Add new tokens using register().
+   */
+  public function registerTokens(TokenRegisterEvent $e): void {
+    if (!$this->checkActive($e->getTokenProcessor())) {
+      return;
+    }
+    foreach (array_merge($this->getContactTokens(), $this->getCustomFieldTokens()) as $name => $label) {
+      $e->register([
+        'entity' => $this->entity,
+        'field' => $name,
+        'label' => $label,
+      ]);
+    }
+    foreach ($this->getLegacyHookTokens() as $legacyHookToken) {
+      $e->register([
+        'entity' => $legacyHookToken['category'],
+        'field' => $legacyHookToken['name'],
+        'label' => $legacyHookToken['label'],
+      ]);
+    }
+  }
+
+  /**
+   * Determine whether this token-handler should be used with
+   * the given processor.
+   *
+   * To short-circuit token-processing in irrelevant contexts,
+   * override this.
+   *
+   * @param \Civi\Token\TokenProcessor $processor
+   * @return bool
+   */
+  public function checkActive(\Civi\Token\TokenProcessor $processor) {
+    return in_array($this->getEntityIDField(), $processor->context['schema'], TRUE);
+  }
+
+  /**
+   * @return string
+   */
+  public function getEntityIDField(): string {
+    return 'contactId';
+  }
+
+  /**
+   * Get functions declared using the legacy hook.
+   *
+   * Note that these only extend the contact entity (
+   * ie they are based on having a contact ID which they.
+   * may or may not use, but they don't have other
+   * entity IDs.)
+   *
+   * @return array
+   */
+  public function getLegacyHookTokens(): array {
+    $tokens = [];
+    $hookTokens = [];
+    \CRM_Utils_Hook::tokens($hookTokens);
+    foreach ($hookTokens as $tokenValues) {
+      foreach ($tokenValues as $key => $value) {
+        if (is_numeric($key)) {
+          // This appears to be an attempt to compensate for
+          // inconsistencies described in https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_tokenValues/#example
+          // in effect there is a suggestion that
+          // Send an Email" and "CiviMail" send different parameters to the tokenValues hook
+          // As of now 'send an email' renders hooks through this class.
+          // CiviMail it depends on the use or otherwise of flexmailer.
+          $key = $value;
+        }
+        if (preg_match('/^\{([^\}]+)\}$/', $value, $matches)) {
+          $value = $matches[1];
+        }
+        $keyParts = explode('.', $key);
+        $tokens[$key] = [
+          'category' => $keyParts[0],
+          'name' => $keyParts[1],
+          'label' => $value,
+        ];
+      }
+    }
+    return $tokens;
+  }
+
+  /**
+   * @return array
+   * @throws \CRM_Core_Exception
+   */
+  public function getCustomFieldTokens(): array {
+    $tokens = [];
+    $customFields = \CRM_Core_BAO_CustomField::getFields(['Individual', 'Address', 'Contact']);
+    foreach ($customFields as $customField) {
+      $tokens['custom_' . $customField['id']] = $customField['label'] . " :: " . $customField['groupTitle'];
+    }
+    return $tokens;
+  }
+
+  /**
+   * Get all tokens advertised as contact tokens.
+   *
+   * @return string[]
+   */
+  public function getContactTokens(): array {
+    return [
+      'contact_type' => 'Contact Type',
+      'do_not_email' => 'Do Not Email',
+      'do_not_phone' => 'Do Not Phone',
+      'do_not_mail' => 'Do Not Mail',
+      'do_not_sms' => 'Do Not Sms',
+      'do_not_trade' => 'Do Not Trade',
+      'is_opt_out' => 'No Bulk Emails (User Opt Out)',
+      'external_identifier' => 'External Identifier',
+      'sort_name' => 'Sort Name',
+      'display_name' => 'Display Name',
+      'nick_name' => 'Nickname',
+      'image_URL' => 'Image Url',
+      'preferred_communication_method' => 'Preferred Communication Method',
+      'preferred_language' => 'Preferred Language',
+      'preferred_mail_format' => 'Preferred Mail Format',
+      'hash' => 'Contact Hash',
+      'contact_source' => 'Contact Source',
+      'first_name' => 'First Name',
+      'middle_name' => 'Middle Name',
+      'last_name' => 'Last Name',
+      'individual_prefix' => 'Individual Prefix',
+      'individual_suffix' => 'Individual Suffix',
+      'formal_title' => 'Formal Title',
+      'communication_style' => 'Communication Style',
+      'job_title' => 'Job Title',
+      'gender' => 'Gender ID',
+      'birth_date' => 'Birth Date',
+      'current_employer_id' => 'Current Employer ID',
+      'contact_is_deleted' => 'Contact is in Trash',
+      'created_date' => 'Created Date',
+      'modified_date' => 'Modified Date',
+      'addressee' => 'Addressee',
+      'email_greeting' => 'Email Greeting',
+      'postal_greeting' => 'Postal Greeting',
+      'current_employer' => 'Current Employer',
+      'location_type' => 'Location Type',
+      'address_id' => 'Address ID',
+      'street_address' => 'Street Address',
+      'street_number' => 'Street Number',
+      'street_number_suffix' => 'Street Number Suffix',
+      'street_name' => 'Street Name',
+      'street_unit' => 'Street Unit',
+      'supplemental_address_1' => 'Supplemental Address 1',
+      'supplemental_address_2' => 'Supplemental Address 2',
+      'supplemental_address_3' => 'Supplemental Address 3',
+      'city' => 'City',
+      'postal_code_suffix' => 'Postal Code Suffix',
+      'postal_code' => 'Postal Code',
+      'geo_code_1' => 'Latitude',
+      'geo_code_2' => 'Longitude',
+      'manual_geo_code' => 'Is Manually Geocoded',
+      'address_name' => 'Address Name',
+      'master_id' => 'Master Address ID',
+      'county' => 'County',
+      'state_province' => 'State',
+      'country' => 'Country',
+      'phone' => 'Phone',
+      'phone_ext' => 'Phone Extension',
+      'phone_type_id' => 'Phone Type ID',
+      'phone_type' => 'Phone Type',
+      'email' => 'Email',
+      'on_hold' => 'On Hold',
+      'signature_text' => 'Signature Text',
+      'signature_html' => 'Signature Html',
+      'im_provider' => 'IM Provider',
+      'im' => 'IM Screen Name',
+      'openid' => 'OpenID',
+      'world_region' => 'World Region',
+      'url' => 'Website',
+      'checksum' => 'Checksum',
+      'contact_id' => 'Internal Contact ID',
     ];
   }