Merge pull request #21555 from eileenmcnaughton/money
authorMatthew Wire <devel@mrwire.co.uk>
Tue, 21 Sep 2021 22:07:43 +0000 (23:07 +0100)
committerGitHub <noreply@github.com>
Tue, 21 Sep 2021 22:07:43 +0000 (23:07 +0100)
dev/core#2493 Add support for money laundry in `getSubmittedValue`

27 files changed:
CRM/Badge/BAO/Badge.php
CRM/Contact/BAO/GroupContactCache.php
CRM/Contact/Form/Task/EmailTrait.php
CRM/Core/EntityTokens.php
CRM/Core/SelectValues.php
CRM/Event/ParticipantTokens.php [new file with mode: 0644]
CRM/Event/Tokens.php
CRM/Report/Form.php
CRM/Upgrade/Incremental/Base.php
CRM/Upgrade/Incremental/MessageTemplates.php
CRM/Upgrade/Incremental/php/FiveFortyThree.php
Civi/Core/Container.php
Civi/Token/TokenCompatSubscriber.php
Civi/Token/TokenProcessor.php
tests/phpunit/CRM/Batch/BAO/BatchTest.php
tests/phpunit/CRM/Batch/Form/EntryTest.php
tests/phpunit/CRM/Contact/Form/Task/EmailCommonTest.php
tests/phpunit/CRM/Contribute/Form/Contribution/ThankYouTest.php
tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php [new file with mode: 0644]
tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php
tests/phpunit/CRM/Core/BAO/CustomFieldTest.php
tests/phpunit/CRM/Core/BAO/MessageTemplateTest.php
tests/phpunit/CRM/Core/FormTest.php
tests/phpunit/CRM/Event/Form/Task/BadgeTest.php
tests/phpunit/CRM/Utils/TokenConsistencyTest.php
tests/phpunit/CiviTest/CiviUnitTestCase.php
tests/phpunit/api/v3/ContactTest.php

index e501e95ff7515f35933e7b46b94769b61760276c..adb8dd791c220a5bfb6a33b94a7d0f596916b08c 100644 (file)
@@ -15,6 +15,8 @@
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
+use Civi\Token\TokenProcessor;
+
 /**
  * Class CRM_Badge_Format_Badge.
  *
@@ -87,7 +89,7 @@ class CRM_Badge_BAO_Badge {
         if ($element) {
           $value = $row[$element];
           // hack to fix date field display format
-          if (strpos($element, '_date')) {
+          if (in_array($element, ['event_start_date', 'event_end_date'], TRUE)) {
             $value = CRM_Utils_Date::customFormat($value, "%B %E%f");
           }
         }
@@ -369,7 +371,7 @@ class CRM_Badge_BAO_Badge {
     $this->imgRes = 300;
 
     if ($img) {
-      list($w, $h) = self::getImageProperties($img, $this->imgRes, $w, $h);
+      [$w, $h] = self::getImageProperties($img, $this->imgRes, $w, $h);
       $this->pdf->Image($img, $x, $y, $w, $h, '', '', '', FALSE, 72, '', FALSE,
         FALSE, $this->debug, FALSE, FALSE, FALSE);
     }
@@ -402,39 +404,36 @@ class CRM_Badge_BAO_Badge {
   public static function buildBadges(&$params, &$form) {
     // get name badge layout info
     $layoutInfo = CRM_Badge_BAO_Layout::buildLayout($params);
-
+    $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), ['schema' => ['participantId'], 'smarty' => FALSE]);
     // split/get actual field names from token and individual contact image URLs
-    $returnProperties = [];
+    $returnProperties = $processorTokens = [];
     if (!empty($layoutInfo['data']['token'])) {
       foreach ($layoutInfo['data']['token'] as $index => $value) {
-        $element = '';
         if ($value) {
           $token = CRM_Utils_Token::getTokens($value);
-          if (key($token) == 'contact') {
-            $element = $token['contact'][0];
-          }
-          elseif (key($token) == 'event') {
+          if (strpos($value, '{event.') === 0) {
             $element = $token['event'][0];
             //FIX ME - we need to standardize event token names
             if (substr($element, 0, 6) != 'event_') {
+              // legacy style.
               $element = 'event_' . $element;
+              $returnProperties[$element] = 1;
+              // add actual field name to row element
+              $layoutInfo['data']['rowElements'][$index] = $element;
             }
           }
-          elseif (key($token) == 'participant') {
-            $element = $token['participant'][0];
+          else {
+            $tokenName = str_replace(['}', '{contact.', '{participant.'], '', $value);
+            $tokenProcessor->addMessage($tokenName, $value, 'text/plain');
+            $processorTokens[] = $tokenName;
+            $layoutInfo['data']['rowElements'][$index] = $tokenName;
           }
-
-          // build returnproperties for query
-          $returnProperties[$element] = 1;
         }
-
-        // add actual field name to row element
-        $layoutInfo['data']['rowElements'][$index] = $element;
       }
     }
 
     // add additional required fields for query execution
-    $additionalFields = ['participant_register_date', 'participant_id', 'event_id', 'contact_id', 'image_URL'];
+    $additionalFields = ['participant_id', 'event_id', 'contact_id'];
     foreach ($additionalFields as $field) {
       $returnProperties[$field] = 1;
     }
@@ -450,7 +449,7 @@ class CRM_Badge_BAO_Badge {
       CRM_Contact_BAO_Query::MODE_EVENT
     );
 
-    list($select, $from, $where, $having) = $query->query();
+    [$select, $from, $where, $having] = $query->query();
     if (empty($where)) {
       $where = "WHERE {$form->_componentClause}";
     }
@@ -469,16 +468,19 @@ class CRM_Badge_BAO_Badge {
 
     $dao = CRM_Core_DAO::executeQuery($queryString);
     $rows = [];
+
     while ($dao->fetch()) {
-      $query->convertToPseudoNames($dao);
+      $tokenProcessor->addRow(['contactId' => $dao->contact_id, 'participantId' => $dao->participant_id]);
       $rows[$dao->participant_id] = [];
       foreach ($returnProperties as $key => $dontCare) {
-        $value = $dao->$key ?? NULL;
-        // Format custom fields
-        if (strstr($key, 'custom_') && isset($value)) {
-          $value = CRM_Core_BAO_CustomField::displayValue($value, substr($key, 7), $dao->contact_id);
-        }
-        $rows[$dao->participant_id][$key] = $value;
+        // we are now only resolving the 4 event tokens here.
+        $rows[$dao->participant_id][$key] = $dao->$key ?? NULL;
+      }
+    }
+    $tokenProcessor->evaluate();
+    foreach ($tokenProcessor->getRows() as $row) {
+      foreach ($processorTokens as $processorToken) {
+        $rows[$row->context['participantId']][$processorToken] = $row->render($processorToken);
       }
     }
 
index 32dc446c4cefa67bd608dbae1c6c1d20685d4231..43088ff058a4e0ad7bbbeca0949c2b72721570e2 100644 (file)
@@ -246,19 +246,32 @@ WHERE  id IN ( $groupIDs )
       // Someone else is kindly doing the refresh for us right now.
       return;
     }
+
+    // Get the list of expired smart groups that may need flushing
     $params = [1 => [self::getCacheInvalidDateTime(), 'String']];
-    $groupsDAO = CRM_Core_DAO::executeQuery("SELECT id FROM civicrm_group WHERE cache_date <= %1", $params);
+    $groupsThatMayNeedToBeFlushedSQL = "SELECT id FROM civicrm_group WHERE (saved_search_id IS NOT NULL OR children <> '') AND (cache_date <= %1 OR cache_date IS NULL)";
+    $groupsDAO = CRM_Core_DAO::executeQuery($groupsThatMayNeedToBeFlushedSQL, $params);
     $expiredGroups = [];
     while ($groupsDAO->fetch()) {
       $expiredGroups[] = $groupsDAO->id;
     }
-    if (!empty($expiredGroups)) {
-      $expiredGroups = implode(',', $expiredGroups);
-      CRM_Core_DAO::executeQuery("DELETE FROM civicrm_group_contact_cache WHERE group_id IN ({$expiredGroups})");
+    if (empty($expiredGroups)) {
+      // There are no expired smart groups to flush
+      return;
+    }
+
+    $expiredGroupsCSV = implode(',', $expiredGroups);
+    $flushSQLParams = [1 => [$expiredGroupsCSV, 'CommaSeparatedIntegers']];
+    // Now check if we actually have any entries in the smart groups to flush
+    $groupsHaveEntriesToFlushSQL = 'SELECT group_id FROM civicrm_group_contact_cache gc WHERE group_id IN (%1) LIMIT 1';
+    $groupsHaveEntriesToFlush = (bool) CRM_Core_DAO::singleValueQuery($groupsHaveEntriesToFlushSQL, $flushSQLParams);
+
+    if ($groupsHaveEntriesToFlush) {
+      CRM_Core_DAO::executeQuery("DELETE FROM civicrm_group_contact_cache WHERE group_id IN (%1)", [1 => [$expiredGroupsCSV, 'CommaSeparatedIntegers']]);
 
       // Clear these out without resetting them because we are not building caches here, only clearing them,
       // so the state is 'as if they had never been built'.
-      CRM_Core_DAO::executeQuery("UPDATE civicrm_group SET cache_date = NULL WHERE id IN ({$expiredGroups})");
+      CRM_Core_DAO::executeQuery("UPDATE civicrm_group SET cache_date = NULL WHERE id IN (%1)", [1 => [$expiredGroupsCSV, 'CommaSeparatedIntegers']]);
     }
     $lock->release();
   }
index 4bfa01b407fe008767727a9b2f7381d852793caf..db6af3198c38e170fbe36c93db02bbf4a96d6b4e 100644 (file)
@@ -37,6 +37,13 @@ trait CRM_Contact_Form_Task_EmailTrait {
    */
   public $_templates;
 
+  /**
+   * Email addresses to send to.
+   *
+   * @var array
+   */
+  protected $emails = [];
+
   /**
    * Store "to" contact details.
    * @var array
@@ -138,24 +145,6 @@ trait CRM_Contact_Form_Task_EmailTrait {
     // are having to re-write contactIds afterwards due to this inappropriate variable setting
     // If we don't have any contact IDs, use the logged in contact ID
     $form->_contactIds = $form->_contactIds ?: [CRM_Core_Session::getLoggedInContactID()];
-
-    $fromEmailValues = CRM_Core_BAO_Email::getFromEmail();
-
-    if (empty($fromEmailValues)) {
-      CRM_Core_Error::statusBounce(ts('Your user record does not have a valid email address and no from addresses have been configured.'));
-    }
-
-    $form->_emails = $fromEmailValues;
-    $defaults = [];
-    $form->_fromEmails = $fromEmailValues;
-    if (is_numeric(key($form->_fromEmails))) {
-      $emailID = (int) key($form->_fromEmails);
-      $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID);
-    }
-    if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) {
-      $defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE));
-    }
-    $form->setDefaults($defaults);
   }
 
   /**
@@ -208,10 +197,8 @@ trait CRM_Contact_Form_Task_EmailTrait {
     if ($to->getValue()) {
       foreach ($this->getEmails($to) as $value) {
         $contactId = $value['contact_id'];
-        $email = $value['email'];
         if ($contactId) {
           $this->_contactIds[] = $this->_toContactIds[] = $contactId;
-          $this->_toContactEmails[] = $email;
           $this->_allContactIds[] = $contactId;
         }
       }
@@ -269,7 +256,7 @@ trait CRM_Contact_Form_Task_EmailTrait {
 
     $this->add('text', 'subject', ts('Subject'), ['size' => 50, 'maxlength' => 254], TRUE);
 
-    $this->add('select', 'from_email_address', ts('From'), $this->_fromEmails, TRUE);
+    $this->add('select', 'from_email_address', ts('From'), $this->getFromEmails(), TRUE);
 
     CRM_Mailing_BAO_Mailing::commonCompose($this);
 
@@ -353,6 +340,27 @@ trait CRM_Contact_Form_Task_EmailTrait {
     CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'templates/CRM/Contact/Form/Task/EmailCommon.js', 0, 'html-header');
   }
 
+  /**
+   * Set relevant default values.
+   *
+   * @return array
+   *
+   * @throws \API_Exception
+   * @throws \CRM_Core_Exception
+   */
+  public function setDefaultValues(): array {
+    $defaults = parent::setDefaultValues();
+    $fromEmails = $this->getFromEmails();
+    if (is_numeric(key($fromEmails))) {
+      $emailID = (int) key($fromEmails);
+      $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID);
+    }
+    if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) {
+      $defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE));
+    }
+    return $defaults;
+  }
+
   /**
    * Process the form after the input has been submitted and validated.
    *
@@ -363,8 +371,6 @@ trait CRM_Contact_Form_Task_EmailTrait {
    */
   public function postProcess() {
     $this->bounceIfSimpleMailLimitExceeded(count($this->_contactIds));
-
-    // check and ensure that
     $formValues = $this->controller->exportValues($this->getName());
     $this->submit($formValues);
   }
@@ -375,7 +381,7 @@ trait CRM_Contact_Form_Task_EmailTrait {
    * @param int $count
    *  The number of emails the user is attempting to send
    */
-  protected function bounceIfSimpleMailLimitExceeded($count) {
+  protected function bounceIfSimpleMailLimitExceeded($count): void {
     $limit = Civi::settings()->get('simple_mail_limit');
     if ($count > $limit) {
       CRM_Core_Error::statusBounce(ts('Please do not use this task to send a lot of emails (greater than %1). Many countries have legal requirements when sending bulk emails and the CiviMail framework has opt out functionality and domain tokens to help meet these.',
@@ -396,7 +402,7 @@ trait CRM_Contact_Form_Task_EmailTrait {
    * @throws \Civi\API\Exception\UnauthorizedException
    * @throws \API_Exception
    */
-  public function submit($formValues) {
+  public function submit($formValues): void {
     $this->saveMessageTemplate($formValues);
 
     $from = $formValues['from_email_address'] ?? NULL;
@@ -421,7 +427,7 @@ trait CRM_Contact_Form_Task_EmailTrait {
       if (!isset($this->_contactDetails[$contactId])) {
         continue;
       }
-      $email = $this->_toContactEmails[$key];
+      $email = $this->getEmail($key);
       // prevent duplicate emails if same email address is selected CRM-4067
       // we should allow same emails for different contacts
       $details = $this->_contactDetails[$contactId];
@@ -726,4 +732,34 @@ trait CRM_Contact_Form_Task_EmailTrait {
     return NULL;
   }
 
+  /**
+   * @return array
+   */
+  protected function getFromEmails(): array {
+    $fromEmailValues = CRM_Core_BAO_Email::getFromEmail();
+
+    if (empty($fromEmailValues)) {
+      CRM_Core_Error::statusBounce(ts('Your user record does not have a valid email address and no from addresses have been configured.'));
+    }
+    return $fromEmailValues;
+  }
+
+  /**
+   * Get the relevant emails.
+   *
+   * @param int $index
+   *
+   * @return string
+   */
+  protected function getEmail(int $index): string {
+    if (empty($this->emails)) {
+      $toEmails = explode(',', $this->getSubmittedValue('to'));
+      foreach ($toEmails as $value) {
+        $parts = explode('::', $value);
+        $this->emails[] = $parts[1];
+      }
+    }
+    return $this->emails[$index];
+  }
+
 }
index d879efdc73c711efa3eeea4917acd1b5a6bbe11f..cc9350bb1374a9f9a1021c230b1c2e80d080ac68 100644 (file)
@@ -40,6 +40,10 @@ class CRM_Core_EntityTokens extends AbstractTokenSubscriber {
   public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) {
     $this->prefetch = (array) $prefetch;
     $fieldValue = $this->getFieldValue($row, $field);
+    if (is_array($fieldValue)) {
+      // eg. role_id for participant would be an array here.
+      $fieldValue = implode(',', $fieldValue);
+    }
 
     if ($this->isPseudoField($field)) {
       if (!empty($fieldValue)) {
index f8aba561018f4475e688e777cf1fd86e2345de7a..b0e5f24d51ef024711c3d463180b1e2085a5c9d8 100644 (file)
@@ -536,25 +536,19 @@ class CRM_Core_SelectValues {
   /**
    * Different type of Event Tokens.
    *
+   * @deprecated
+   *
    * @return array
    */
-  public static function eventTokens() {
-    return [
-      '{event.event_id}' => ts('Event ID'),
-      '{event.title}' => ts('Event Title'),
-      '{event.start_date}' => ts('Event Start Date'),
-      '{event.end_date}' => ts('Event End Date'),
-      '{event.event_type}' => ts('Event Type'),
-      '{event.summary}' => ts('Event Summary'),
-      '{event.contact_email}' => ts('Event Contact Email'),
-      '{event.contact_phone}' => ts('Event Contact Phone'),
-      '{event.description}' => ts('Event Description'),
-      '{event.location}' => ts('Event Location'),
-      '{event.fee_amount}' => ts('Event Fees'),
-      '{event.info_url}' => ts('Event Info URL'),
-      '{event.registration_url}' => ts('Event Registration URL'),
-      '{event.balance}' => ts('Event Balance'),
-    ];
+  public static function eventTokens(): array {
+    $tokenProcessor = new TokenProcessor(Civi::dispatcher(), ['schema' => ['eventId']]);
+    $allTokens = $tokenProcessor->listTokens();
+    foreach (array_keys($allTokens) as $token) {
+      if (strpos($token, '{domain.') === 0) {
+        unset($allTokens[$token]);
+      }
+    }
+    return $allTokens;
   }
 
   /**
@@ -600,23 +594,16 @@ class CRM_Core_SelectValues {
    */
   public static function participantTokens(): array {
     $tokens = [
-      '{participant.participant_status_id}' => 'Status ID',
-      '{participant.participant_role_id}' => 'Participant Role (ID)',
-      '{participant.participant_register_date}' => 'Register date',
-      '{participant.participant_source}' => 'Participant Source',
-      '{participant.participant_fee_level}' => 'Fee level',
-      '{participant.participant_fee_amount}' => 'Fee Amount',
-      '{participant.participant_registered_by_id}' => 'Registered By Participant ID',
+      '{participant.status_id}' => 'Status ID',
+      '{participant.role_id}' => 'Participant Role (ID)',
+      '{participant.register_date}' => 'Register date',
+      '{participant.source}' => 'Participant Source',
+      '{participant.fee_level}' => 'Fee level',
+      '{participant.fee_amount}' => 'Fee Amount',
+      '{participant.registered_by_id}' => 'Registered By Participant ID',
       '{participant.transferred_to_contact_id}' => 'Transferred to Contact ID',
-      '{participant.participant_role}' => 'Participant Role (label)',
-      '{participant.event_title}' => 'Event Title',
-      '{participant.event_start_date}' => 'Event Start Date',
-      '{participant.event_end_date}' => 'Event End Date',
+      '{participant.role_id:label}' => 'Participant Role (label)',
       '{participant.fee_label}' => 'Fee Label',
-      '{participant.default_role_id}' => 'Default Role',
-      '{participant.template_title}' => 'Event Template Title',
-      '{participant.currency}' => 'Currency',
-      '{participant.participant_note}' => 'Participant Note',
     ];
     $customFields = CRM_Core_BAO_CustomField::getFields('Participant');
 
diff --git a/CRM/Event/ParticipantTokens.php b/CRM/Event/ParticipantTokens.php
new file mode 100644 (file)
index 0000000..717b117
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Event_ParticipantTokens
+ *
+ * Generate "participant.*" tokens.
+ */
+class CRM_Event_ParticipantTokens extends CRM_Core_EntityTokens {
+
+  /**
+   * Get the entity name for api v4 calls.
+   *
+   * @return string
+   */
+  protected function getApiEntityName(): string {
+    return 'Participant';
+  }
+
+  /**
+   * @return array
+   */
+  public function getCurrencyFieldName(): array {
+    return ['fee_currency'];
+  }
+
+}
index 15784cd87289b3ff72e1ee10b49559e24495c0b6..5de70c3080bf9616a4c5b00009e6994a31e765c0 100644 (file)
@@ -10,6 +10,8 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\ActionSchedule\Event\MailingQueryEvent;
+
 /**
  * Class CRM_Event_Tokens
  *
  * implementation which is not tied to scheduled reminders, although
  * that is outside the current scope.
  */
-class CRM_Event_Tokens extends \Civi\Token\AbstractTokenSubscriber {
+class CRM_Event_Tokens extends CRM_Core_EntityTokens {
 
   /**
-   * Class constructor.
+   * Get the entity name for api v4 calls.
+   *
+   * @return string
+   */
+  protected function getApiEntityName(): string {
+    return 'Event';
+  }
+
+  /**
+   * Get all tokens.
+   *
+   * This function will be removed once the parent class can determine it.
    */
-  public function __construct() {
-    parent::__construct('event', array_merge(
+  public function getAllTokens(): array {
+    return array_merge(
       [
         'event_type' => ts('Event Type'),
         'title' => ts('Event Title'),
@@ -41,12 +54,12 @@ class CRM_Event_Tokens extends \Civi\Token\AbstractTokenSubscriber {
         'info_url' => ts('Event Info URL'),
         'registration_url' => ts('Event Registration URL'),
         'fee_amount' => ts('Event Fee'),
-        'contact_email' => ts('Event Contact (Email)'),
-        'contact_phone' => ts('Event Contact (Phone)'),
+        'contact_email' => ts('Event Contact Email'),
+        'contact_phone' => ts('Event Contact Phone'),
         'balance' => ts('Event Balance'),
       ],
       CRM_Utils_Token::getCustomFieldTokens('Event')
-    ));
+    );
   }
 
   /**
@@ -54,8 +67,9 @@ class CRM_Event_Tokens extends \Civi\Token\AbstractTokenSubscriber {
    */
   public function checkActive(\Civi\Token\TokenProcessor $processor) {
     // Extracted from scheduled-reminders code. See the class description.
-    return !empty($processor->context['actionMapping'])
-      && $processor->context['actionMapping']->getEntity() === 'civicrm_participant';
+    return ((!empty($processor->context['actionMapping'])
+      && $processor->context['actionMapping']->getEntity() === 'civicrm_participant'))
+      || in_array($this->getEntityIDField(), $processor->context['schema'], TRUE);
   }
 
   /**
@@ -63,7 +77,7 @@ class CRM_Event_Tokens extends \Civi\Token\AbstractTokenSubscriber {
    *
    * @param \Civi\ActionSchedule\Event\MailingQueryEvent $e
    */
-  public function alterActionScheduleQuery(\Civi\ActionSchedule\Event\MailingQueryEvent $e) {
+  public function alterActionScheduleQuery(MailingQueryEvent $e): void {
     if ($e->mapping->getEntity() !== 'civicrm_participant') {
       return;
     }
@@ -127,7 +141,7 @@ LEFT JOIN civicrm_phone phone ON phone.id = lb.phone_id
       $row->tokens($entity, $field, $actionSearchResult->$field);
     }
     elseif ($cfID = \CRM_Core_BAO_CustomField::getKeyID($field)) {
-      $row->customToken($entity, $cfID, $actionSearchResult->entity_id);
+      $row->customToken($entity, $cfID, $actionSearchResult->event_id);
     }
     else {
       $row->tokens($entity, $field, '');
index 352bbf35eccf2cf47ccf7b57aff3f846c51dceaa..da4e6ba09d07968ffef4655648fdf6cdd931e13a 100644 (file)
@@ -626,6 +626,10 @@ class CRM_Report_Form extends CRM_Core_Form {
 
       // set qfkey so that pager picks it up and use it in the "Next > Last >>" links.
       // FIXME: Note setting it in $_GET doesn't work, since pager generates link based on QUERY_STRING
+      if (!isset($_SERVER['QUERY_STRING'])) {
+        // in php 7.4 can do this with less lines with ??=
+        $_SERVER['QUERY_STRING'] = '';
+      }
       $_SERVER['QUERY_STRING'] .= "&qfKey={$this->controller->_key}";
     }
 
index 8e2e8641f7471661bdf3c984ed0ae1bc905de931..355dbcb3def33df199928dcf0e823adc7d22b0e9 100644 (file)
@@ -269,7 +269,7 @@ class CRM_Upgrade_Incremental_Base {
   }
 
   /**
-   * Updated a message token within a template.
+   * Updated a message token within a scheduled reminder.
    *
    * @param CRM_Queue_TaskContext $ctx
    * @param string $old
@@ -284,6 +284,22 @@ class CRM_Upgrade_Incremental_Base {
     return TRUE;
   }
 
+  /**
+   * Updated a message token within a template.
+   *
+   * @param CRM_Queue_TaskContext $ctx
+   * @param string $old
+   * @param string $new
+   * @param $version
+   *
+   * @return bool
+   */
+  public static function updatePrintLabelToken($ctx, string $old, string $new, $version):bool {
+    $messageObj = new CRM_Upgrade_Incremental_MessageTemplates($version);
+    $messageObj->replaceTokenInPrintLabel($old, $new);
+    return TRUE;
+  }
+
   /**
    * Re-save any valid values from contribute settings into the normal setting
    * format.
index 52a69315b3bd5d8f4ef2c9a443353f70d4746131..2c044e32cb51df451d35e95dea22f5a569a3c323 100644 (file)
@@ -251,9 +251,12 @@ class CRM_Upgrade_Incremental_MessageTemplates {
       ],
       [
         'version' => '5.43.alpha1',
-        'upgrade_descriptor' => ts('Missed text version from 5.20'),
+        'upgrade_descriptor' => ts('Missed templates from earlier versions'),
         'templates' => [
           ['name' => 'contribution_online_receipt', 'type' => 'text'],
+          ['name' => 'case_activity', 'type' => 'html'],
+          ['name' => 'case_activity', 'type' => 'text'],
+          ['name' => 'case_activity', 'type' => 'subject'],
         ],
       ],
     ];
@@ -313,6 +316,21 @@ class CRM_Upgrade_Incremental_MessageTemplates {
     ");
   }
 
+  /**
+   * Replace a token with the new preferred option in a print label.
+   *
+   * @param string $old
+   * @param string $new
+   */
+  public function replaceTokenInPrintLabel(string $old, string $new): void {
+    $oldToken = '{' . $old . '}';
+    $newToken = '{' . $new . '}';
+    CRM_Core_DAO::executeQuery("UPDATE civicrm_print_label
+      SET
+        data = REPLACE(data, '$oldToken', '$newToken')
+    ");
+  }
+
   /**
    * Get the upgrade messages.
    */
index 2dfd87c610c5999d3b96f75d09346c5f4e77ffda..7d022f943738f17ec758c95f44a758d56056b1e0 100644 (file)
@@ -74,6 +74,40 @@ class CRM_Upgrade_Incremental_php_FiveFortyThree extends CRM_Upgrade_Incremental
     $this->addTask('Replace membership type token in action schedule',
       'updateActionScheduleToken', 'membership.type', 'membership.membership_type_id:label', $rev
     );
+    $this->addTask('Replace duplicate event title token in event badges',
+      'updatePrintLabelToken', 'participant.event_title', 'event.title', $rev
+    );
+    $this->addTask('Replace duplicate event start date token in event badges',
+      'updatePrintLabelToken', 'participant.event_start_date', 'event.start_date', $rev
+    );
+    $this->addTask('Replace duplicate event end date token in event badges',
+      'updatePrintLabelToken', 'participant.event_end_date', 'event.end_date', $rev
+    );
+    $this->addTask('Update participant status id token in event badges',
+      'updatePrintLabelToken', 'participant.participant_status_id', 'participant.status_id', $rev
+    );
+    $this->addTask('Update participant role id token in event badges',
+      'updatePrintLabelToken', 'participant.participant_role_id', 'participant.role_id', $rev
+    );
+    $this->addTask('Update participant role label token in event badges',
+      'updatePrintLabelToken', 'participant.participant_role', 'participant.role_id:label', $rev
+    );
+    $this->addTask('Update participant register date token in event badges',
+      'updatePrintLabelToken', 'participant.participant_register_date', 'participant.register_date', $rev
+    );
+    $this->addTask('Update participant source token in event badges',
+      'updatePrintLabelToken', 'participant.participant_source', 'participant.source', $rev
+    );
+    $this->addTask('Update participant fee level token in event badges',
+      'updatePrintLabelToken', 'participant.participant_fee_level', 'participant.fee_level', $rev
+    );
+    $this->addTask('Update participant fee amount token in event badges',
+      'updatePrintLabelToken', 'participant.participant_fee_amount', 'participant.fee_amount', $rev
+    );
+    $this->addTask('Update participant registered by id token in event badges',
+      'updatePrintLabelToken', 'participant.participant_registered_by_id', 'participant.registered_by_id', $rev
+    );
+
   }
 
   /**
index 5c74d402bc60f9c2d01da3a547d1b685721036aa..69f05d1bec1718f1ff4bc40633ce1bc5bba34f2b 100644 (file)
@@ -333,6 +333,10 @@ class Container {
         []
       ))->addTag('kernel.event_subscriber')->setPublic(TRUE);
     }
+    $container->setDefinition('crm_participant_tokens', new Definition(
+      'CRM_Event_ParticipantTokens',
+      []
+    ))->addTag('kernel.event_subscriber')->setPublic(TRUE);
     $container->setDefinition('crm_contribution_recur_tokens', new Definition(
       'CRM_Contribute_RecurTokens',
       []
index 3133df6eb3e4da10b1ada4f1136dd1d33f6f4139..92e20b780680f0d3c9ad2a39cca84cf1a9a4bb96 100644 (file)
@@ -298,48 +298,135 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
    * Load token data.
    *
    * @param \Civi\Token\Event\TokenValueEvent $e
+   *
    * @throws TokenException
+   * @throws \CRM_Core_Exception
    */
   public function onEvaluate(TokenValueEvent $e) {
-    // For reasons unknown, replaceHookTokens used to require a pre-computed list of
-    // hook *categories* (aka entities aka namespaces). We cache
-    // this in the TokenProcessor's context but can likely remove it now.
-
-    $e->getTokenProcessor()->context['hookTokenCategories'] = \CRM_Utils_Token::getTokenCategories();
-
     $messageTokens = $e->getTokenProcessor()->getMessageTokens()['contact'] ?? [];
+    if (empty($messageTokens)) {
+      return;
+    }
+    $this->fieldMetadata = (array) civicrm_api4('Contact', 'getfields', ['checkPermissions' => FALSE], 'name');
 
     foreach ($e->getRows() as $row) {
-      if (empty($row->context['contactId'])) {
+      if (empty($row->context['contactId']) && empty($row->context['contact'])) {
         continue;
       }
 
       unset($swapLocale);
       $swapLocale = empty($row->context['locale']) ? NULL : \CRM_Utils_AutoClean::swapLocale($row->context['locale']);
 
-      /** @var int $contactId */
-      $contactId = $row->context['contactId'];
       if (empty($row->context['contact'])) {
-        $contact = $this->getContact($contactId, $messageTokens);
+        $row->context['contact'] = $this->getContact($row->context['contactId'], $messageTokens);
       }
-      else {
-        $contact = $row->context['contact'];
+
+      foreach ($messageTokens as $token) {
+        if ($token === 'checksum') {
+          $cs = \CRM_Contact_BAO_Contact_Utils::generateChecksum($row->context['contactId'],
+            NULL,
+            NULL,
+            $row->context['hash'] ?? NULL
+          );
+          $row->format('text/html')
+            ->tokens('contact', $token, "cs={$cs}");
+        }
+        elseif (!empty($row->context['contact'][$token]) &&
+          $this->isDateField($token)
+        ) {
+          // Handle dates here, for now. Standardise with other token entities next round
+          $row->format('text/plain')->tokens('contact', $token, \CRM_Utils_Date::customFormat($row->context['contact'][$token]));
+        }
+        elseif (
+          ($row->context['contact'][$token] ?? '') == 0
+          && $this->isBooleanField($token)) {
+          // Note this will be the default behaviour once we fetch with apiv4.
+          $row->format('text/plain')->tokens('contact', $token, '');
+        }
+        elseif ($token === 'signature_html') {
+          $row->format('text/html')->tokens('contact', $token, html_entity_decode($row->context['contact'][$token]));
+        }
+        else {
+          $row->format('text/html')
+            ->tokens('contact', $token, $row->context['contact'][$token] ?? '');
+        }
       }
-      $row->context('contact', $contact);
     }
   }
 
+  /**
+   * Is the given field a boolean field.
+   *
+   * @param string $fieldName
+   *
+   * @return bool
+   */
+  public function isBooleanField(string $fieldName): bool {
+    // no metadata for these 2 non-standard fields
+    // @todo - fix to api v4 & have metadata for all fields. Migrate contact_is_deleted
+    // to {contact.is_deleted}. on hold feels like a token that exists by
+    // accident & could go.... since it's not from the main entity.
+    if (in_array($fieldName, ['contact_is_deleted', 'on_hold'])) {
+      return TRUE;
+    }
+    if (empty($this->getFieldMetadata()[$fieldName])) {
+      return FALSE;
+    }
+    return $this->getFieldMetadata()[$fieldName]['data_type'] === 'Boolean';
+  }
+
+  /**
+   * Is the given field a date field.
+   *
+   * @param string $fieldName
+   *
+   * @return bool
+   */
+  public function isDateField(string $fieldName): bool {
+    if (empty($this->getFieldMetadata()[$fieldName])) {
+      return FALSE;
+    }
+    return in_array($this->getFieldMetadata()[$fieldName]['data_type'], ['Timestamp', 'Date'], TRUE);
+  }
+
+  /**
+   * Get the metadata for the available fields.
+   *
+   * @return array
+   */
+  protected function getFieldMetadata(): array {
+    if (empty($this->fieldMetadata)) {
+      try {
+        // Tests fail without checkPermissions = FALSE
+        $this->fieldMetadata = (array) civicrm_api4('Contact', 'getfields', ['checkPermissions' => FALSE], 'name');
+      }
+      catch (\API_Exception $e) {
+        $this->fieldMetadata = [];
+      }
+    }
+    return $this->fieldMetadata;
+  }
+
   /**
    * Apply the various CRM_Utils_Token helpers.
    *
    * @param \Civi\Token\Event\TokenRenderEvent $e
+   *
+   * @throws \CRM_Core_Exception
    */
-  public function onRender(TokenRenderEvent $e) {
+  public function onRender(TokenRenderEvent $e): void {
     $isHtml = ($e->message['format'] === 'text/html');
     $useSmarty = !empty($e->context['smarty']);
 
     if (!empty($e->context['contact'])) {
-      \CRM_Utils_Token::replaceGreetingTokens($e->string, $e->context['contact'], $e->context['contact']['contact_id'] ?? $e->context['contactId'], NULL, $useSmarty);
+      // @todo - remove this - it simply removes the last unresolved tokens before
+      // they break smarty.
+      // historically it was only called when context['contact'] so that is
+      // retained but it only works because it's almost always true.
+      $remainingTokens = array_keys(\CRM_Utils_Token::getTokens($e->string));
+      if (!empty($remainingTokens)) {
+        $e->string = \CRM_Utils_Token::replaceHookTokens($e->string, $e->context['contact'], $remainingTokens);
+      }
     }
 
     if ($useSmarty) {
@@ -374,8 +461,12 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
     $mappedFields = [
       'email_greeting' => 'email_greeting_display',
       'postal_greeting' => 'postal_greeting_display',
-      'addressee' => 'address_display',
+      'addressee' => 'addressee_display',
     ];
+    if (!empty($returnProperties['checksum'])) {
+      $returnProperties['hash'] = 1;
+    }
+
     foreach ($mappedFields as $tokenName => $realName) {
       if (in_array($tokenName, $requiredFields, TRUE)) {
         $returnProperties[$realName] = 1;
index 5411a74fecc80a27a15da702904691b985c18fb1..3bad2d891878c5bf08c94870906c4d27f8f7d2ba 100644 (file)
@@ -383,7 +383,7 @@ class TokenProcessor {
     // Regex examples: '{foo.bar}', '{foo.bar|whiz}'
     // Regex counter-examples: '{foobar}', '{foo bar}', '{$foo.bar}', '{$foo.bar|whiz}', '{foo.bar|whiz{bang}}'
     // Key observations: Civi tokens MUST have a `.` and MUST NOT have a `$`. Civi filters MUST NOT have `{}`s or `$`s.
-    $tokRegex = '([\w]+)\.([\w:]+)';
+    $tokRegex = '([\w]+)\.([\w:\.]+)';
     $filterRegex = '(\w+)';
     $event->string = preg_replace_callback(";\{$tokRegex(?:\|$filterRegex)?\};", $getToken, $message['string']);
     $this->dispatcher->dispatch('civi.token.render', $event);
index 168c6ac3b3a03bfc1ebbf8cb87c8c356a523784e..edb435a7e5854dc7cea3aac410319d0f7adf6f4f 100644 (file)
@@ -34,11 +34,9 @@ class CRM_Batch_BAO_BatchTest extends CiviUnitTestCase {
   /**
    * Cleanup after test.
    *
-   * @throws \CRM_Core_Exception
-   * @throws \API_Exception
    */
   public function tearDown(): void {
-    $this->quickCleanup(['civicrm_batch']);
+    $this->quickCleanup(['civicrm_batch', 'civicrm_file', 'civicrm_entity_file']);
     parent::tearDown();
   }
 
@@ -56,7 +54,7 @@ class CRM_Batch_BAO_BatchTest extends CiviUnitTestCase {
    *
    * @throws \CRM_Core_Exception
    */
-  public function testGetBatchFinancialItems() {
+  public function testGetBatchFinancialItems(): void {
 
     // create two contributions: one check and one credit card
 
@@ -119,8 +117,10 @@ class CRM_Batch_BAO_BatchTest extends CiviUnitTestCase {
 
   /**
    * Test testExportFinancialBatch.
+   *
+   * @throws \CRM_Core_Exception
    */
-  public function testExportFinancialBatch() {
+  public function testExportFinancialBatch(): void {
     $this->createLoggedInUser();
     $batchParams = ['title' => 'Test Batch'];
     $batchParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Batch_BAO_Batch', 'status_id', 'Exported');
index 7f353c5c1c7d4ebb0fafe26a5cdfb6a98f356caa..d8d6fda2ca7d974db450eab338ca7452ea43cc3b 100644 (file)
@@ -165,12 +165,10 @@ class CRM_Batch_Form_EntryTest extends CiviUnitTestCase {
     if ($this->callAPISuccessGetCount('membership', ['id' => $this->_membershipTypeID])) {
       $this->membershipTypeDelete(['id' => $this->_membershipTypeID]);
     }
-    if ($this->callAPISuccessGetCount('MembershipStatus', ['id' => $this->_membershipStatusID])) {
-      $this->membershipStatusDelete($this->_membershipStatusID);
-    }
     $this->contactDelete($this->_contactID);
     $this->contactDelete($this->_contactID2);
     $this->contactDelete($this->_orgContactID);
+    parent::tearDown();
   }
 
   /**
index 589bcdf2e7e3d94e7616f5523c885af45796df50..930bfbebc2d39250227d00c74f1f06ccf3c02f79 100644 (file)
@@ -80,19 +80,6 @@ class CRM_Contact_Form_Task_EmailCommonTest extends CiviUnitTestCase {
 
     Civi::settings()->set('allow_mail_from_logged_in_contact', 1);
     $loggedInContactID = $this->createLoggedInUser();
-    /* @var CRM_Contact_Form_Task_Email $form*/
-    $form = $this->getFormObject('CRM_Contact_Form_Task_Email');
-
-    for ($i = 0; $i < 27; $i++) {
-      $email = 'spy' . $i . '@secretsquirrels.com';
-      $contactID = $this->individualCreate(['email' => $email]);
-      $form->_contactIds[$contactID] = $contactID;
-      $form->_toContactEmails[$this->callAPISuccessGetValue('Email', ['return' => 'id', 'email' => $email])] = $email;
-    }
-    $deceasedContactID = $this->individualCreate(['is_deceased' => 1, 'email' => 'dead@example.com']);
-    $form->_contactIds[$deceasedContactID] = $deceasedContactID;
-    $form->_toContactEmails[$this->callAPISuccessGetValue('Email', ['return' => 'id', 'email' => 'dead@example.com'])] = 'dead@example.com';
-
     $loggedInEmail = $this->callAPISuccess('Email', 'create', [
       'email' => 'mickey@mouse.com',
       'location_type_id' => 1,
@@ -101,15 +88,35 @@ class CRM_Contact_Form_Task_EmailCommonTest extends CiviUnitTestCase {
       'signature_text' => 'This is a test Signature',
       'signature_html' => '<p>This is a test Signature</p>',
     ]);
+
+    $to = $form_contactIds = $form_toContactEmails = [];
+    for ($i = 0; $i < 27; $i++) {
+      $email = 'spy' . $i . '@secretsquirrels.com';
+      $contactID = $this->individualCreate(['email' => $email]);
+      $form_contactIds[$contactID] = $contactID;
+      $to[] = $contactID . '::' . $email;
+    }
+    $deceasedContactID = $this->individualCreate(['is_deceased' => 1, 'email' => 'dead@example.com']);
+    $to[] = $deceasedContactID . '::' . 'email@example.com';
+    /* @var CRM_Contact_Form_Task_Email $form*/
+    $form = $this->getFormObject('CRM_Contact_Form_Task_Email', [
+      'to' => implode(',', $to),
+    ]);
+    $form->_contactIds = $form_contactIds;
+    $form->_contactIds[$deceasedContactID] = $deceasedContactID;
+
     $form->_allContactIds = $form->_toContactIds = $form->_contactIds;
-    $form->_emails = [$loggedInEmail['id'] => 'mickey@mouse.com'];
     $form->_fromEmails = [$loggedInEmail['id'] => 'mickey@mouse.com'];
     // This rule somehow disappears if there's a form-related test before us,
     // so register it again. See packages/HTML/QuickForm/file.php.
+    // update - actually - it's never registered. Even in form made
+    // I can see it missing - It's really weird.
     $form->registerRule('maxfilesize', 'callback', '_ruleCheckMaxFileSize', 'HTML_QuickForm_file');
     $form->isSearchContext = FALSE;
     $form->buildForm();
     $form->submit(array_merge($form->_defaultValues, [
+      // @todo - it's better to pass these into getForm
+      // and access them on the form using $this->getSubmittedValue().
       'from_email_address' => $loggedInEmail['id'],
       'subject' => 'Really interesting stuff',
       'bcc_id' => $bcc,
index 895cc03947151bfdfb68b8e29369dcd1c7a35aec..aa9b3e373f4372c63c53a496102c20d96b83524e 100644 (file)
@@ -20,8 +20,6 @@ class CRM_Contribute_Form_Contribution_ThankYouTest extends CiviUnitTestCase {
 
   /**
    * Clean up DB.
-   *
-   * @throws \CRM_Core_Exception|\API_Exception
    */
   public function tearDown(): void {
     $this->quickCleanUpFinancialEntities();
@@ -31,7 +29,7 @@ class CRM_Contribute_Form_Contribution_ThankYouTest extends CiviUnitTestCase {
   /**
    * Test that correct contribution status is fetched for both live and test contributions.
    */
-  public function testLiveAndTestContributionStatus() {
+  public function testLiveAndTestContributionStatus(): void {
     $paymentProcessorID = $this->paymentProcessorCreate(['payment_processor_type_id' => 'Dummy']);
 
     $form = $this->getThankYouFormWithContribution($paymentProcessorID, FALSE, FALSE);
diff --git a/tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php b/tests/phpunit/CRM/Contribute/Form/Task/EmailTest.php
new file mode 100644 (file)
index 0000000..f206163
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved.                        |
+ |                                                                    |
+ | This work is published under the GNU AGPLv3 license with some      |
+ | permitted exceptions and without any warranty. For full license    |
+ | and copyright information, see https://civicrm.org/licensing       |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *  Test Email task.
+ *
+ * @package CiviCRM_APIv3
+ * @subpackage API_Contribution
+ * @group headless
+ */
+class CRM_Contribute_Form_Task_EmailTest extends CiviUnitTestCase {
+
+  /**
+   * Clean up after each test.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \API_Exception
+   */
+  public function tearDown(): void {
+    $this->quickCleanUpFinancialEntities();
+    parent::tearDown();
+  }
+
+  /**
+   * Test that email tokens are rendered.
+   */
+  public function testEmailTokens(): void {
+    Civi::settings()->set('max_attachments', 0);
+    $contact1 = $this->individualCreate();
+    $contact2 = $this->individualCreate();
+    $userID = $this->createLoggedInUser();
+    Civi::settings()->set('allow_mail_from_logged_in_contact', TRUE);
+    $this->callAPISuccess('Email', 'create', [
+      'contact_id' => $userID,
+      'email' => 'benny_jetts@example.com',
+      'signature_html' => 'Benny, Benny',
+      'is_primary' => 1,
+    ]);
+    $contribution1 = $this->contributionCreate(['contact_id' => $contact2]);
+    $contribution2 = $this->contributionCreate(['total_amount' => 999, 'contact_id' => $contact1]);
+    $form = $this->getFormObject('CRM_Contribute_Form_Task_Email', [
+      'cc_id' => '',
+      'bcc_id' => '',
+      'to' => implode(',', [
+        $contact1 . '::teresajensen-nielsen65@spamalot.co.in',
+        $contact2 . '::bob@example.com',
+      ]),
+      'subject' => '{contact.display_name}',
+      'text_message' => '{contribution.total_amount}',
+      'html_message' => '{domain.name}',
+    ], [], [
+      'radio_ts' => 'ts_sel',
+      'task' => CRM_Core_Task::TASK_EMAIL,
+      'mark_x_' . $contribution1 => 1,
+      'mark_x_' . $contribution2 => 1,
+    ]);
+    $form->set('cid', $contact1 . ',' . $contact2);
+    $form->buildForm();
+    $this->assertEquals('<br/><br/>--Benny, Benny', $form->_defaultValues['html_message']);
+    $form->postProcess();
+  }
+
+}
index 5ff49af24f7217bcc543eb3ffa8a2ecc465cd65d..07244a71c76d5f5b546c6287a79fc5448bd58b61 100644 (file)
@@ -880,8 +880,6 @@ class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase {
       '{contact.display_name}',
       // funny legacy contact token
       '{contact.gender}',
-      // funny legacy contact token
-      '{contact.gender_id}',
       // domain token
       '{domain.name}',
       // action-scheduler token
@@ -896,7 +894,7 @@ class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase {
     ]);
     // Note: The behavior of domain-tokens on a scheduled reminder is undefined. All we
     // can really do is check that it has something.
-    $someTokensExpected = 'Churmondleia Ōtākou;;Female;;Female;;[a-zA-Z0-9 ]+;;Phone Call';
+    $someTokensExpected = 'Churmondleia Ōtākou;;Female;;[a-zA-Z0-9 ]+;;Phone Call';
     $manyTokensExpected = sprintf('%s;;Dear Churmondleia;;%s', $someTokensExpected, '{contactCustomTokenValue}');
 
     // In this example, we use a lot of tokens cutting across multiple components.
index c92b9df4715f8d26ae76099f5f040526e9592dcf..23968d527a868ae5c3e3e0e5c12397c42da6066b 100644 (file)
@@ -15,18 +15,16 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
 
   /**
    * Clean up after test.
-   *
-   * @throws \Exception
    */
   public function tearDown(): void {
-    $this->quickCleanup([], TRUE);
+    $this->quickCleanup(['civicrm_file', 'civicrm_entity_file'], TRUE);
     parent::tearDown();
   }
 
   /**
    * Test creating a custom field.
    */
-  public function testCreateCustomField() {
+  public function testCreateCustomField(): void {
     $customGroup = $this->createCustomField();
     $customFieldID = $this->assertDBNotNull('CRM_Core_DAO_CustomField', $customGroup['id'], 'id', 'custom_group_id',
       'Database check for created CustomField.'
@@ -984,7 +982,7 @@ class CRM_Core_BAO_CustomFieldTest extends CiviUnitTestCase {
   /**
    * Check that outputting the display value for a file field with No description doesn't generate error
    */
-  public function testFileDisplayValueNoDescription() {
+  public function testFileDisplayValueNoDescription(): void {
     $customGroup = $this->customGroupCreate([
       'extends' => 'Individual',
       'title' => 'Test Contact File Custom Group',
index 049653d0f4f401c66291fd7ee55feea57f7800b2..ed303f27341fbef855367105c481ad6838aec3fc 100644 (file)
@@ -360,6 +360,16 @@ emo
 ';
     $expected .= $this->getExpectedContactOutput($address['id'], $tokenData, $messageContent['html']);
     $this->assertEquals($expected, $messageContent['html']);
+    $textDifferences = [
+      '<p>',
+      '</p>',
+      '<a href="http://civicrm.org" ',
+      'target="_blank">',
+      '</a>',
+    ];
+    foreach ($textDifferences as $html) {
+      $expected = str_replace($html, '', $expected);
+    }
     $this->assertEquals($expected, $messageContent['text']);
     $checksum_position = strpos($messageContent['subject'], 'cs=');
     $this->assertTrue($checksum_position !== FALSE);
@@ -768,12 +778,12 @@ state_province:TX
 country:United States
 phone:123-456
 phone_ext:77
-phone_type_id:
+phone_type_id:2
 phone_type:Mobile
 email:anthony_anderson@civicrm.org
 on_hold:
 signature_text:Yours sincerely
-signature_html:&lt;p&gt;Yours&lt;/p&gt;
+signature_html:<p>Yours</p>
 im_provider:1
 im:IM Screen Name
 openid:OpenID
index d6f3fb2e85975a6ebd9a7d3595f77f7f48a6451b..0041d19ac39be487b4104f2b757fdba25fe3913f 100644 (file)
@@ -62,7 +62,7 @@ class CRM_Core_FormTest extends CiviUnitTestCase {
     ];
   }
 
-  public function testNewPriceField() {
+  public function testNewPriceField(): void {
     $this->createLoggedInUser();
 
     $priceSetId = $this->callAPISuccess('PriceSet', 'create', [
index 6ba914bb56ab907325094f4c2a73e79fd44119fb..8e0d4db5b25bd3e484b20f47f99876554a80993b 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use Civi\Api4\PrintLabel;
+
 /**
  *  Test CRM_Event_Form_Registration functions.
  *
@@ -10,13 +12,35 @@ class CRM_Event_Form_Task_BadgeTest extends CiviUnitTestCase {
 
   use CRMTraits_Custom_CustomDataTrait;
 
+  public function tearDown(): void {
+    $this->quickCleanup(['civicrm_participant', 'civicrm_print_label'], TRUE);
+    parent::tearDown();
+  }
+
   /**
    * Test the the submit function on the event participant submit function.
    */
   public function testSubmit(): void {
     $this->createCustomGroupWithFieldOfType(['extends' => 'Participant']);
-    $contactID = $this->individualCreate();
-    $participantID = $this->participantCreate(['contact_id' => $contactID]);
+    $contactID = $this->individualCreate(['employer_id' => 1]);
+    $participantID = $this->participantCreate([
+      'contact_id' => $contactID,
+      'fee_level' => 'low',
+    ]);
+
+    $badgeLayout = PrintLabel::get()->addSelect('data')->execute()->first();
+    $values = [
+      'data' => array_merge($badgeLayout['data'], ['token' => [], 'font_name' => [''], 'font_size' => [], 'text_alignment' => []]),
+    ];
+    foreach (array_keys($this->getAvailableTokens()) as $id => $token) {
+      $index = $id + 1;
+      $values['data']['token'][$index] = $token;
+      $values['data']['font_name'][$index] = 'dejavusans';
+      $values['data']['font_size'][$index] = '20';
+      $values['data']['font_style'][$index] = '';
+      $values['data']['text_alignment'][$index] = 'C';
+    }
+    PrintLabel::update()->addWhere('id', '=', 1)->setValues($values)->execute();
 
     $_REQUEST['context'] = 'view';
     $_REQUEST['id'] = $participantID;
@@ -39,45 +63,45 @@ class CRM_Event_Form_Task_BadgeTest extends CiviUnitTestCase {
     catch (CRM_Core_Exception_PrematureExitException $e) {
       $tokens = $e->errorData['formattedRow']['token'];
       $this->assertEquals([
-        1 => [
-          'value' => 'Annual CiviCRM meet',
-          'font_name' => 'dejavusans',
-          'font_size' => '9',
-          'font_style' => '',
-          'text_alignment' => 'L',
-          'token' => '{event.title}',
-        ],
-        2 =>
-          [
-            'value' => 'Mr. Anthony Anderson II',
-            'font_name' => 'dejavusans',
-            'font_size' => '20',
-            'font_style' => '',
-            'text_alignment' => 'C',
-            'token' => '{contact.display_name}',
-          ],
-        3 =>
-          [
-            'value' => NULL,
-            'font_name' => 'dejavusans',
-            'font_size' => '15',
-            'font_style' => '',
-            'text_alignment' => 'C',
-            'token' => '{contact.current_employer}',
-          ],
-        4 =>
-          [
-            'value' => 'October 21st',
-            'font_name' => 'dejavusans',
-            'font_size' => '9',
-            'font_style' => '',
-            'text_alignment' => 'R',
-            'token' => '{event.start_date}',
-          ],
-      ], $tokens);
+        'value' => 'Annual CiviCRM meet',
+        'font_name' => 'dejavusans',
+        'font_size' => '20',
+        'font_style' => '',
+        'text_alignment' => 'C',
+        'token' => '{event.title}',
+      ], $tokens[1]);
+      $index = 1;
+      foreach ($this->getAvailableTokens() as $token => $expected) {
+        $this->assertEquals($expected, $tokens[$index]['value'], 'failure in token ' . $token);
+        $index++;
+      }
       return;
     }
     $this->fail('Should not be reached');
   }
 
+  /**
+   * @return string[]
+   */
+  protected function getAvailableTokens(): array {
+    return [
+      '{event.title}' => 'Annual CiviCRM meet',
+      '{contact.display_name}' => 'Mr. Anthony Anderson II',
+      '{contact.current_employer}' => 'Default Organization',
+      '{event.start_date}' => 'October 21st',
+      '{participant.status_id}' => 2,
+      '{participant.role_id}' => 1,
+      '{participant.register_date}' => 'February 19th, 2007 12:00 AM',
+      '{participant.source}' => 'Wimbeldon',
+      '{participant.fee_level}' => 'low',
+      '{participant.fee_amount}' => NULL,
+      '{participant.registered_by_id}' => NULL,
+      '{participant.transferred_to_contact_id}' => NULL,
+      '{participant.role_id:label}' => 'Attendee',
+      '{participant.fee_label}' => NULL,
+      '{event.end_date}' => 'October 23rd',
+      '{event.id}' => 1,
+    ];
+  }
+
 }
index edc3bc43f1bf77ad0ee7882f9b30d43d6ad86b51..0a0ab45571112070dcb4499b322d9d4dc7ef8009 100644 (file)
@@ -483,23 +483,16 @@ December 21st, 2007
    */
   public function getParticipantTokens(): array {
     return [
-      '{participant.participant_status_id}' => 'Status ID',
-      '{participant.participant_role_id}' => 'Participant Role (ID)',
-      '{participant.participant_register_date}' => 'Register date',
-      '{participant.participant_source}' => 'Participant Source',
-      '{participant.participant_fee_level}' => 'Fee level',
-      '{participant.participant_fee_amount}' => 'Fee Amount',
-      '{participant.participant_registered_by_id}' => 'Registered By Participant ID',
+      '{participant.status_id}' => 'Status ID',
+      '{participant.role_id}' => 'Participant Role (ID)',
+      '{participant.register_date}' => 'Register date',
+      '{participant.source}' => 'Participant Source',
+      '{participant.fee_level}' => 'Fee level',
+      '{participant.fee_amount}' => 'Fee Amount',
+      '{participant.registered_by_id}' => 'Registered By Participant ID',
       '{participant.transferred_to_contact_id}' => 'Transferred to Contact ID',
-      '{participant.participant_role}' => 'Participant Role (label)',
-      '{participant.event_title}' => 'Event Title',
-      '{participant.event_start_date}' => 'Event Start Date',
-      '{participant.event_end_date}' => 'Event End Date',
+      '{participant.role_id:label}' => 'Participant Role (label)',
       '{participant.fee_label}' => 'Fee Label',
-      '{participant.default_role_id}' => 'Default Role',
-      '{participant.template_title}' => 'Event Template Title',
-      '{participant.currency}' => 'Currency',
-      '{participant.participant_note}' => 'Participant Note',
       '{participant.' . $this->getCustomFieldName('text') . '}' => 'Enter text here :: Group with field text',
     ];
   }
@@ -535,4 +528,42 @@ December 21st, 2007
     ];
   }
 
+  /**
+   * Test that domain tokens are consistently rendered.
+   */
+  public function testEventTokenConsistency(): void {
+    $tokens = CRM_Core_SelectValues::eventTokens();
+    $this->assertEquals($this->getEventTokens(), $tokens);
+    $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [
+      'controller' => __CLASS__,
+      'smarty' => FALSE,
+      'schema' => ['eventId'],
+    ]);
+    $this->assertEquals(array_merge($tokens, $this->getDomainTokens()), $tokenProcessor->listTokens());
+  }
+
+  /**
+   * Get expected event tokens.
+   *
+   * @return string[]
+   */
+  protected function getEventTokens(): array {
+    return [
+      '{event.event_id}' => 'Event ID',
+      '{event.title}' => 'Event Title',
+      '{event.start_date}' => 'Event Start Date',
+      '{event.end_date}' => 'Event End Date',
+      '{event.event_type}' => 'Event Type',
+      '{event.summary}' => 'Event Summary',
+      '{event.contact_email}' => 'Event Contact Email',
+      '{event.contact_phone}' => 'Event Contact Phone',
+      '{event.description}' => 'Event Description',
+      '{event.location}' => 'Event Location',
+      '{event.fee_amount}' => 'Event Fee',
+      '{event.info_url}' => 'Event Info URL',
+      '{event.registration_url}' => 'Event Registration URL',
+      '{event.balance}' => 'Event Balance',
+    ];
+  }
+
 }
index 652cd3f9decda251504657cfa02e3f3461e62af8..5a0bbb29a61c494696a0ce9e6250466fa84b513a 100644 (file)
@@ -487,36 +487,37 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
   /**
    * Create default domain contacts for the two domains added during test class.
    * database population.
-   *
-   * @throws \API_Exception
    */
   public function createDomainContacts(): void {
-    $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
-    $this->organizationCreate([
-      'organization_name' => 'Second Domain',
-      'api.Email.create' => ['email' => 'domainemail2@example.org'],
-      'api.Address.create' => [
-        'street_address' => '15 Main St',
-        'location_type_id' => 1,
-        'city' => 'Collinsville',
-        'country_id' => 1228,
-        'state_province_id' => 1003,
-        'postal_code' => 6022,
-      ],
-    ]);
-    OptionValue::replace(FALSE)->addWhere(
-      'option_group_id:name', '=', 'from_email_address'
-    )->setDefaults([
-      'is_default' => 1,
-      'name' => '"FIXME" <info@EXAMPLE.ORG>',
-      'label' => '"FIXME" <info@EXAMPLE.ORG>',
-    ])->setRecords([['domain_id' => 1], ['domain_id' => 2]])->execute();
+    try {
+      $this->organizationCreate(['api.Email.create' => ['email' => 'fixme.domainemail@example.org']]);
+      $this->organizationCreate([
+        'organization_name' => 'Second Domain',
+        'api.Email.create' => ['email' => 'domainemail2@example.org'],
+        'api.Address.create' => [
+          'street_address' => '15 Main St',
+          'location_type_id' => 1,
+          'city' => 'Collinsville',
+          'country_id' => 1228,
+          'state_province_id' => 1003,
+          'postal_code' => 6022,
+        ],
+      ]);
+      OptionValue::replace(FALSE)->addWhere(
+        'option_group_id:name', '=', 'from_email_address'
+      )->setDefaults([
+        'is_default' => 1,
+        'name' => '"FIXME" <info@EXAMPLE.ORG>',
+        'label' => '"FIXME" <info@EXAMPLE.ORG>',
+      ])->setRecords([['domain_id' => 1], ['domain_id' => 2]])->execute();
+    }
+    catch (API_Exception $e) {
+      $this->fail('failed to re-instate domain contacts ' . $e->getMessage());
+    }
   }
 
   /**
    *  Common teardown functions for all unit tests.
-   *
-   * @throws \API_Exception
    */
   protected function tearDown(): void {
     $this->_apiversion = 3;
index 616616c09ac877a5fe0e7d0fbd0bb0687d08a87c..b161260412fe4871080c11ba26a4258cbecdc412 100644 (file)
@@ -148,9 +148,11 @@ class api_v3_ContactTest extends CiviUnitTestCase {
    * @throws \CRM_Core_Exception
    */
   public function testCreateIndividualNoCacheClear(): void {
-
     $contact = $this->callAPISuccess('contact', 'create', $this->_params);
-    $groupID = $this->groupCreate();
+
+    $smartGroupParams = ['form_values' => ['contact_type' => ['IN' => ['Household']]]];
+    $savedSearch = CRM_Contact_BAO_SavedSearch::create($smartGroupParams);
+    $groupID = $this->groupCreate(['saved_search_id' => $savedSearch->id]);
 
     $this->putGroupContactCacheInClearableState($groupID, $contact);