dev/core#2832 Add test for legacy membership tokens, add support for preferred tokens
[civicrm-core.git] / CRM / Utils / Token.php
index 6e9ea5b403e9e9f8a7e56bf6ba08f81ce3bc6041..c3aa61b9294c5ac4f23ffb48f2eb30e298348906 100644 (file)
@@ -182,16 +182,16 @@ class CRM_Utils_Token {
   }
 
   /**
-   * Get< the regex for token replacement
+   * Get the regex for token replacement
    *
    * @param string $token_type
    *   A string indicating the the type of token to be used in the expression.
    *
    * @return string
-   *   regular expression sutiable for using in preg_replace
+   *   regular expression suitable for using in preg_replace
    */
-  private static function tokenRegex($token_type) {
-    return '/(?<!\{|\\\\)\{' . $token_type . '\.([\w]+:?\w*(\-[\w\s]+)?)\}(?!\})/';
+  private static function tokenRegex(string $token_type) {
+    return '/(?<!\{|\\\\)\{' . $token_type . '\.([\w]+(:|\.)?\w*(\-[\w\s]+)?)\}(?!\})/';
   }
 
   /**
@@ -257,56 +257,11 @@ class CRM_Utils_Token {
    * @return null|string
    */
   public static function getDomainTokenReplacement($token, $domain, $html = FALSE, $escapeSmarty = FALSE): ?string {
-    // check if the token we were passed is valid
-    // we have to do this because this function is
-    // called only when we find a token in the string
-
-    $loc = $domain->getLocationValues();
-
-    if (!in_array($token, self::$_tokens['domain'])) {
-      $value = "{domain.$token}";
-    }
-    elseif ($token === 'address') {
-      $cacheKey = __CLASS__ . 'address_token_cache' . CRM_Core_Config::domainID();
-      $addressCache = Civi::cache()->has($cacheKey) ? Civi::cache()->get($cacheKey) : [];
-
-      $fieldKey = $html ? 'address-html' : 'address-text';
-      if (array_key_exists($fieldKey, $addressCache)) {
-        return $addressCache[$fieldKey];
-      }
-
-      $value = NULL;
-      // Construct the address token
-
-      if (!empty($loc[$token])) {
-        if ($html) {
-          $value = str_replace("\n", '<br />', $loc[$token][1]['display']);
-        }
-        else {
-          $value = $loc[$token][1]['display_text'];
-        }
-        Civi::cache()->set($cacheKey, $addressCache);
-      }
-    }
-    elseif ($token === 'name' || $token === 'id' || $token === 'description') {
-      $value = $domain->$token;
-    }
-    elseif ($token === 'phone' || $token === 'email') {
-      // Construct the phone and email tokens
-
-      $value = NULL;
-      if (!empty($loc[$token])) {
-        foreach ($loc[$token] as $index => $entity) {
-          $value = $entity[$token];
-          break;
-        }
-      }
-    }
-
+    $tokens = self::getDomainTokens($domain->id, $html);
+    $value = $tokens[$token] ?? "{domain.$token}";
     if ($escapeSmarty) {
       $value = self::tokenEscapeSmarty($value);
     }
-
     return $value;
   }
 
@@ -1102,7 +1057,7 @@ class CRM_Utils_Token {
   public static function getTokens($string) {
     $matches = [];
     $tokens = [];
-    preg_match_all('/(?<!\{|\\\\)\{(\w+\.\w+:?\w*)\}(?!\})/',
+    preg_match_all('/(?<!\{|\\\\)\{(\w+\.\w+(:|.)?\w*)\}(?!\})/',
       $string,
       $matches,
       PREG_PATTERN_ORDER
@@ -1110,12 +1065,15 @@ class CRM_Utils_Token {
 
     if ($matches[1]) {
       foreach ($matches[1] as $token) {
-        [$type, $name] = preg_split('/\./', $token, 2);
+        $parts = explode('.', $token, 3);
+        $type = $parts[0];
+        $name = $parts[1];
+        $suffix = !empty($parts[2]) ? ('.' . $parts[2]) : '';
         if ($name && $type) {
           if (!isset($tokens[$type])) {
             $tokens[$type] = [];
           }
-          $tokens[$type][] = $name;
+          $tokens[$type][] = $name . $suffix;
         }
       }
     }
@@ -1576,21 +1534,6 @@ class CRM_Utils_Token {
     self::$_tokens[$key] = Civi::$statics[__CLASS__][__FUNCTION__][$key];
   }
 
-  /**
-   * Store membership tokens on the static _tokens array.
-   */
-  protected static function _buildMembershipTokens() {
-    $key = 'membership';
-    if (!isset(self::$_tokens[$key]) || self::$_tokens[$key] == NULL) {
-      $membershipTokens = [];
-      $tokens = CRM_Core_SelectValues::membershipTokens();
-      foreach ($tokens as $token => $dontCare) {
-        $membershipTokens[] = substr($token, (strpos($token, '.') + 1), -1);
-      }
-      self::$_tokens[$key] = $membershipTokens;
-    }
-  }
-
   /**
    * Replace tokens for an entity.
    * @param string $entity
@@ -1613,7 +1556,12 @@ class CRM_Utils_Token {
     $fn = is_callable(['CRM_Utils_Token', $fn]) ? $fn : 'getApiTokenReplacement';
     // since we already know the tokens lets just use them & do str_replace which is faster & simpler than preg_replace
     foreach ($knownTokens[$entity] as $token) {
-      $replacement = self::$fn($entity, $token, $entityArray);
+      // We are now supporting the syntax case_type_id:label
+      // so strip anything after the ':'
+      // (we aren't supporting 'name' at this stage, so we can assume 'label'
+      // test cover in TokenConsistencyTest.
+      $parts = explode(':', $token);
+      $replacement = self::$fn($entity, $parts[0], $entityArray);
       if ($escapeSmarty) {
         $replacement = self::tokenEscapeSmarty($replacement);
       }
@@ -1630,10 +1578,13 @@ class CRM_Utils_Token {
    * @return string
    * @throws \CiviCRM_API3_Exception
    */
-  public static function replaceCaseTokens($caseId, $str, $knownTokens = [], $escapeSmarty = FALSE) {
-    if (!$knownTokens || empty($knownTokens['case'])) {
+  public static function replaceCaseTokens($caseId, $str, $knownTokens = NULL, $escapeSmarty = FALSE): string {
+    if (strpos($str, '{case.') === FALSE) {
       return $str;
     }
+    if (!$knownTokens) {
+      $knownTokens = self::getTokens($str);
+    }
     $case = civicrm_api3('case', 'getsingle', ['id' => $caseId]);
     return self::replaceEntityTokens('case', $case, $str, $knownTokens, $escapeSmarty);
   }
@@ -1667,7 +1618,7 @@ class CRM_Utils_Token {
       return implode(', ', $ret);
     }
     // Format date fields
-    elseif ($entityArray[$token] && $fieldType == CRM_Utils_Type::T_DATE) {
+    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)])) {
       return CRM_Utils_Date::customFormat($entityArray[$token]);
     }
     return implode(', ', (array) $entityArray[$token]);
@@ -1739,6 +1690,14 @@ class CRM_Utils_Token {
   /**
    * Get replacement strings for any membership tokens (only a small number of tokens are implemnted in the first instance
    * - this is used by the pdfLetter task from membership search
+   *
+   * This is called via replaceEntityTokens.
+   *
+   * In the near term it will not be called at all from core as
+   * the pdf letter task is updated to use the processor.
+   *
+   * @deprecated
+   *
    * @param string $entity
    *   should always be "membership"
    * @param string $token
@@ -1748,13 +1707,29 @@ class CRM_Utils_Token {
    * @return string token replacement
    */
   public static function getMembershipTokenReplacement($entity, $token, $membership) {
-    self::_buildMembershipTokens();
+    $supportedTokens = [
+      'id',
+      'status',
+      'status_id',
+      'type',
+      'membership_type_id',
+      'start_date',
+      'join_date',
+      'end_date',
+      'fee',
+    ];
     switch ($token) {
       case 'type':
+        // membership_type_id would only be requested if the calling
+        // class is mapping it to '{membership:membership_type_id:label'}
+      case 'membership_type_id':
         $value = $membership['membership_name'];
         break;
 
       case 'status':
+        // status_id would only be requested if the calling
+        // class is mapping it to '{membership:status_id:label'}
+      case 'status_id':
         $statuses = CRM_Member_BAO_Membership::buildOptions('status_id');
         $value = $statuses[$membership['status_id']];
         break;
@@ -1775,7 +1750,7 @@ class CRM_Utils_Token {
         break;
 
       default:
-        if (in_array($token, self::$_tokens[$entity])) {
+        if (in_array($token, $supportedTokens)) {
           $value = $membership[$token];
           if (CRM_Utils_String::endsWith($token, '_date')) {
             $value = CRM_Utils_Date::customFormat($value);
@@ -1863,7 +1838,7 @@ class CRM_Utils_Token {
     $customTokens = [];
     $tokenName = $usedForTokenWidget ? "{contribution.custom_%d}" : "custom_%d";
     foreach (CRM_Core_BAO_CustomField::getFields($entity) as $id => $info) {
-      $customTokens[sprintf($tokenName, $id)] = $info['label'];
+      $customTokens[sprintf($tokenName, $id)] = $info['label'] . ' :: ' . $info['groupTitle'];
     }
 
     return $customTokens;
@@ -1930,4 +1905,65 @@ class CRM_Utils_Token {
     return $value;
   }
 
+  /**
+   * Get token deprecation information.
+   *
+   * @return array
+   */
+  public static function getTokenDeprecations(): array {
+    return [
+      'WorkFlowMessageTemplates' => [
+        'contribution_invoice_receipt' => [
+          '$display_name' => 'contact.display_name',
+        ],
+        'contribution_online_receipt' => [
+          '$contributeMode' => 'no longer available / relevant',
+          '$first_name' => 'contact.first_name',
+          '$last_name' => 'contact.last_name',
+          '$displayName' => 'contact.display_name',
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * Get the tokens available for the domain.
+   *
+   * @param int $domainID
+   * @param bool $html
+   *
+   * @return array
+   * @throws \CRM_Core_Exception
+   */
+  protected static function getDomainTokens(int $domainID, bool $html): array {
+    $cacheKey = __CLASS__ . 'domain_tokens' . $html . '_' . $domainID . '_' . CRM_Core_I18n::getLocale();
+    if (!Civi::cache('metadata')->has($cacheKey)) {
+      if (CRM_Core_Config::domainID() === $domainID) {
+        $domain = CRM_Core_BAO_Domain::getDomain();
+      }
+      else {
+        $domain = new CRM_Core_BAO_Domain();
+        $domain->find(TRUE);
+      }
+      $tokens = [
+        'name' => $domain->name,
+        'id' => $domain->id,
+        'description' => $domain->description,
+      ];
+      $loc = $domain->getLocationValues();
+      if ($html) {
+        $tokens['address'] = str_replace("\n", '<br />', ($loc['address'][1]['display'] ?? ''));
+      }
+      else {
+        $tokens['address'] = $loc['address'][1]['display_text'] ?? '';
+      }
+      $phone = reset($loc['phone']);
+      $email = reset($loc['email']);
+      $tokens['phone'] = $phone['phone'] ?? '';
+      $tokens['email'] = $email['email'] ?? '';
+      Civi::cache('metadata')->set($cacheKey, $tokens);
+    }
+    return Civi::cache('metadata')->get($cacheKey);
+  }
+
 }