Remove call to the dreaded replaceMultipleContributionTokens
authorEileen McNaughton <emcnaughton@wikimedia.org>
Tue, 28 Sep 2021 07:17:57 +0000 (20:17 +1300)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Wed, 29 Sep 2021 01:54:09 +0000 (14:54 +1300)
CRM/Contribute/BAO/Contribution.php
CRM/Contribute/Form/Task/PDFLetter.php
CRM/Utils/Token.php
tests/phpunit/CRM/Contribute/Form/Task/PDFLetterCommonTest.php

index 4b400a8b69428aebc93204da2dd4e58496b7312f..f0f84f988d58a243d36672c6024864fde4bd1659 100644 (file)
@@ -4858,6 +4858,8 @@ LIMIT 1;";
   }
 
   /**
+   * Do not use - unused in core.
+   *
    * Function to replace contribution tokens.
    *
    * @param array $contributionIds
@@ -4874,6 +4876,8 @@ LIMIT 1;";
    *
    * @param bool $escapeSmarty
    *
+   * @deprecated
+   *
    * @return array
    * @throws \CiviCRM_API3_Exception
    */
index 1d2f9b845db2313928a7bb0c790c79852fd9981a..ce5839b7123ac4b5b57c9627ce9b13bc96c49fd4 100644 (file)
@@ -9,6 +9,8 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\Token\TokenProcessor;
+
 /**
  *
  * @package CRM
@@ -133,7 +135,17 @@ class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task {
    */
   public function postProcess() {
     $formValues = $this->controller->exportValues($this->getName());
-    [$formValues, $html_message, $messageToken, $returnProperties] = $this->processMessageTemplate($formValues);
+    [$formValues, $html_message] = $this->processMessageTemplate($formValues);
+
+    $messageToken = CRM_Utils_Token::getTokens($html_message);
+
+    $returnProperties = [];
+    if (isset($messageToken['contact'])) {
+      foreach ($messageToken['contact'] as $key => $value) {
+        $returnProperties[$value] = 1;
+      }
+    }
+
     $isPDF = FALSE;
     $emailParams = [];
     if (!empty($formValues['email_options'])) {
@@ -427,7 +439,7 @@ class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task {
         CRM_Core_Session::setStatus(ts('You have selected the table cell separator, but one or more token fields are not placed inside a table cell. This would result in invalid HTML, so comma separators have been used instead.'));
       }
       $validated = TRUE;
-      $html = str_replace($separator, $realSeparator, $this->resolveTokens($html_message, $contact, $contribution, $messageToken, $grouped, $separator, $groupedContributions));
+      $html = str_replace($separator, $realSeparator, $this->resolveTokens($html_message, $contact, $contribution['id'], $grouped, $separator, $groupedContributions));
     }
 
     return $html;
@@ -535,8 +547,7 @@ class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task {
    *
    * @param string $html_message
    * @param array $contact
-   * @param array $contribution
-   * @param array $messageToken
+   * @param int $contributionID
    * @param bool $grouped
    *   Does this letter represent more than one contribution.
    * @param string $separator
@@ -545,20 +556,44 @@ class CRM_Contribute_Form_Task_PDFLetter extends CRM_Contribute_Form_Task {
    *
    * @return string
    */
-  protected function resolveTokens(string $html_message, $contact, $contribution, $messageToken, $grouped, $separator, $contributions): string {
+  protected function resolveTokens(string $html_message, $contact, $contributionID, $grouped, $separator, $contributions): string {
     $tokenContext = [
       'smarty' => (defined('CIVICRM_MAIL_SMARTY') && CIVICRM_MAIL_SMARTY),
       'contactId' => $contact['contact_id'],
+      'schema' => ['contributionId'],
     ];
     if ($grouped) {
-      $html_message = CRM_Utils_Token::replaceMultipleContributionTokens($separator, $html_message, $contributions, $messageToken);
-    }
-    else {
-      $tokenContext['schema'] = ['contributionId'];
-      $tokenContext['contributionId'] = $contribution['id'];
+      // First replace the contribution tokens. These are pretty ... special.
+      // if the text looks like `<td>{contribution.currency} {contribution.total_amount}</td>'
+      // and there are 2 rows with a currency separator of
+      // you wind up with a string like
+      // '<td>USD</td><td>USD></td> <td>$50</td><td>$89</td>
+      // see https://docs.civicrm.org/user/en/latest/contributions/manual-receipts-and-thank-yous/#grouped-contribution-thank-you-letters
+      $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), $tokenContext);
+      $contributionTokens = CRM_Utils_Token::getTokens($html_message)['contribution'] ?? [];
+      foreach ($contributionTokens as $token) {
+        $tokenProcessor->addMessage($token, '{contribution.' . $token . '}', 'text/html');
+      }
+
+      foreach ($contributions as $contribution) {
+        $tokenProcessor->addRow([
+          'contributionId' => $contribution['id'],
+          'contribution' => $contribution,
+        ]);
+      }
+      $tokenProcessor->evaluate();
+      $resolvedTokens = [];
+      foreach ($contributionTokens as $token) {
+        foreach ($tokenProcessor->getRows() as $row) {
+          $resolvedTokens[$token][$row->context['contributionId']] = $row->render($token);
+        }
+        // We've resolved the value for each row - resorting to swapping them out
+        // with the old function.
+        $html_message = CRM_Utils_Token::token_replace('contribution', $token, implode($separator, $resolvedTokens[$token]), $html_message);
+      }
     }
-    $smarty = ['contact' => $contact];
-    return CRM_Core_TokenSmarty::render(['html' => $html_message], $tokenContext, $smarty)['html'];
+    $tokenContext['contributionId'] = $contributionID;
+    return CRM_Core_TokenSmarty::render(['html' => $html_message], $tokenContext)['html'];
   }
 
 }
index 992578e2f5d690a054850328caae1095bb4b86e2..71a199c193adb57824325c382f18631bf3a49221 100644 (file)
@@ -1636,6 +1636,8 @@ class CRM_Utils_Token {
   }
 
   /**
+   * Do not use - unused in core.
+   *
    * Replace Contribution tokens in html.
    *
    * @param string $str
@@ -1644,6 +1646,8 @@ class CRM_Utils_Token {
    * @param string $knownTokens
    * @param bool|string $escapeSmarty
    *
+   * @deprecated
+   *
    * @return mixed
    */
   public static function replaceContributionTokens($str, &$contribution, $html = FALSE, $knownTokens = NULL, $escapeSmarty = FALSE) {
@@ -1685,9 +1689,12 @@ class CRM_Utils_Token {
    * @param array $contributions
    * @param array $knownTokens
    *
+   * @deprecated
+   *
    * @return string
    */
   public static function replaceMultipleContributionTokens(string $separator, string $str, array $contributions, array $knownTokens): string {
+    CRM_Core_Error::deprecatedFunctionWarning('no alternative');
     foreach ($knownTokens['contribution'] ?? [] as $token) {
       $resolvedTokens = [];
       foreach ($contributions as $contribution) {
@@ -1778,11 +1785,15 @@ class CRM_Utils_Token {
   }
 
   /**
+   * Do not use - unused in core.
+   *
    * @param $token
    * @param $contribution
    * @param bool $html
    * @param bool $escapeSmarty
    *
+   * @deprecated
+   *
    * @return mixed|string
    * @throws \CRM_Core_Exception
    */
index 6cbc71de8ee1aef22629782a73bfcae4f96226ee..54250b806d731f3e268642b7cea8f4b8b0725934 100644 (file)
@@ -48,7 +48,6 @@ class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
    * @throws \CRM_Core_Exception
    */
   public function tearDown(): void {
-    CRM_Utils_Token::$_tokens['contribution'] = NULL;
     $this->quickCleanUpFinancialEntities();
     $this->quickCleanup(['civicrm_uf_match', 'civicrm_campaign'], TRUE);
     CRM_Utils_Hook::singleton()->reset();
@@ -78,6 +77,7 @@ class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
       'receive_date' => '2021-02-01 2:21',
       'currency' => 'USD',
     ])['id'];
+    /* @var CRM_Contribute_Form_Task_PDFLetter $form */
     $form = $this->getFormObject('CRM_Contribute_Form_Task_PDFLetter', [
       'campaign_id' => '',
       'subject' => '',
@@ -139,7 +139,7 @@ class CRM_Contribute_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase {
       'total_amount' => 6,
       'campaign_id' => $this->campaignCreate(['title' => $campaignTitle], FALSE),
       'financial_type_id' => 'Donation',
-      $customFieldKey => 'Text_' . substr(sha1(rand()), 0, 7),
+      $customFieldKey => 'Text_',
     ];
     $contributionIDs = $returnProperties = [];
     $result = $this->callAPISuccess('Contribution', 'create', $params);
@@ -301,15 +301,15 @@ total_amount : &euro; 9,999.99
 fee_amount : &euro; 1,111.11
 net_amount : &euro; 7,777.78
 non_deductible_amount : &euro; 2,222.22
-receive_date : July 20th, 2018 12:00 AM
+receive_date : July 20th, 2018
 payment_instrument_id:label : Check
 trxn_id : 1234
 invoice_id : 568
 currency : EUR
-cancel_date : December 30th, 2019 12:00 AM
+cancel_date : December 30th, 2019
 cancel_reason : Contribution Cancel Reason
-receipt_date : October 30th, 2019 12:00 AM
-thankyou_date : November 30th, 2019 12:00 AM
+receipt_date : October 30th, 2019
+thankyou_date : November 30th, 2019
 source : Contribution Source
 amount_level : Amount Level
 contribution_status_id : 2