diff --git a/CRM/Activity/Form/Task.php b/CRM/Activity/Form/Task.php index 23afcfab94b2..ee6e1ebd2b8a 100644 --- a/CRM/Activity/Form/Task.php +++ b/CRM/Activity/Form/Task.php @@ -127,4 +127,13 @@ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back' ]); } + /** + * Get the token processor schema required to list any tokens for this task. + * + * @return array + */ + public function getTokenSchema(): array { + return ['activityId']; + } + } diff --git a/CRM/Activity/Form/Task/PDF.php b/CRM/Activity/Form/Task/PDF.php index 760bde39eadc..8a7cd7abb0dd 100644 --- a/CRM/Activity/Form/Task/PDF.php +++ b/CRM/Activity/Form/Task/PDF.php @@ -56,15 +56,6 @@ public function postProcess() { CRM_Utils_System::civiExit(0); } - /** - * List available tokens for this form. - * - * @return array - */ - public function listTokens() { - return $this->createTokenProcessor()->listTokens(); - } - /** * Create a token processor * diff --git a/CRM/Case/Form/Task.php b/CRM/Case/Form/Task.php index d1f318ac4322..0734653c2f3b 100644 --- a/CRM/Case/Form/Task.php +++ b/CRM/Case/Form/Task.php @@ -68,6 +68,23 @@ public function orderBy() { return 'ORDER BY ' . implode(',', $order_array); } + /** + * Get the rows from the results to be pdf-d. + * + * @return array + */ + protected function getRows(): array { + $rows = []; + foreach ($this->_contactIds as $index => $contactID) { + $caseID = $this->getVar('_caseId'); + if (empty($caseID) && !empty($this->_caseIds[$index])) { + $caseID = $this->_caseIds[$index]; + } + $rows[] = ['contactId' => $contactID, 'caseId' => $caseID]; + } + return $rows; + } + /** * Get the name of the table for the relevant entity. * diff --git a/CRM/Contact/Form/Task.php b/CRM/Contact/Form/Task.php index 67b7f2be125f..2a2ad62c635d 100644 --- a/CRM/Contact/Form/Task.php +++ b/CRM/Contact/Form/Task.php @@ -137,7 +137,7 @@ public static function preProcessCommon(&$form) { // since we don't store all contacts in prevnextcache, when user selects "all" use query to retrieve contacts // rather than prevnext cache table for most of the task actions except export where we rebuild query to fetch // final result set - $allCids[$cacheKey] = self::getContactIds($form); + $allCids[$cacheKey] = self::legacyGetContactIds($form); $form->_contactIds = []; if (empty($form->_contactIds)) { @@ -222,11 +222,16 @@ public static function preProcessCommon(&$form) { * - custom search (FIXME: does this still apply to custom search?). * When we call this function we are not using the prev/next cache * + * We've started to try to move away from these functions + * being static. Probably we need to convert the export forms + * to use a trait based approach. For now this is renamed to + * permit the use of a non-static function with this name + * * @param $form CRM_Core_Form * * @return array $contactIds */ - public static function getContactIds($form) { + protected static function legacyGetContactIds($form) { // need to perform action on all contacts // fire the query again and get the contact id's + display name $sortID = NULL; diff --git a/CRM/Contact/Form/Task/PDF.php b/CRM/Contact/Form/Task/PDF.php index 9688e912e3e6..66f1138eb594 100644 --- a/CRM/Contact/Form/Task/PDF.php +++ b/CRM/Contact/Form/Task/PDF.php @@ -110,4 +110,26 @@ public function listTokens() { return $tokens; } + /** + * Get the rows from the results to be pdf-d. + * + * @todo the case handling should be in the case pdf task. + * It needs fixing to support standalone & some url fixes + * + * similar to https://github.com/civicrm/civicrm-core/pull/21688 + * + * @return array + */ + protected function getRows(): array { + $rows = []; + foreach ($this->_contactIds as $index => $contactID) { + $caseID = $this->getVar('_caseId'); + if (empty($caseID) && !empty($this->_caseIds[$index])) { + $caseID = $this->_caseIds[$index]; + } + $rows[] = ['contactId' => $contactID, 'caseId' => $caseID]; + } + return $rows; + } + } diff --git a/CRM/Contact/Form/Task/PDFTrait.php b/CRM/Contact/Form/Task/PDFTrait.php index e0877493ce37..2cfa43c87bb8 100644 --- a/CRM/Contact/Form/Task/PDFTrait.php +++ b/CRM/Contact/Form/Task/PDFTrait.php @@ -237,16 +237,11 @@ public function postProcess(): void { [$html_message, $zip] = CRM_Utils_PDF_Document::unzipDoc($formValues['document_file_path'], $formValues['document_type']); } - foreach ($this->_contactIds as $item => $contactId) { - $caseId = $this->getVar('_caseId'); - if (empty($caseId) && !empty($this->_caseIds[$item])) { - $caseId = $this->_caseIds[$item]; - } - + foreach ($this->getRows() as $row) { $tokenHtml = CRM_Core_BAO_MessageTemplate::renderTemplate([ - 'contactId' => $contactId, + 'contactId' => $row['contactId'], 'messageTemplate' => ['msg_html' => $html_message], - 'tokenContext' => $caseId ? ['caseId' => $caseId] : [], + 'tokenContext' => array_merge($row, ['schema' => $this->getTokenSchema()]), 'disableSmarty' => (!defined('CIVICRM_MAIL_SMARTY') || !CIVICRM_MAIL_SMARTY), ])['html']; diff --git a/CRM/Contribute/Form/Task.php b/CRM/Contribute/Form/Task.php index b15aa0e23894..9a3b30871fe3 100644 --- a/CRM/Contribute/Form/Task.php +++ b/CRM/Contribute/Form/Task.php @@ -104,4 +104,13 @@ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back' ]); } + /** + * Get the token processor schema required to list any tokens for this task. + * + * @return array + */ + public function getTokenSchema(): array { + return ['contributionId', 'contactId']; + } + } diff --git a/CRM/Contribute/Form/Task/Email.php b/CRM/Contribute/Form/Task/Email.php index db83653ea539..7fb87e4c97f7 100644 --- a/CRM/Contribute/Form/Task/Email.php +++ b/CRM/Contribute/Form/Task/Email.php @@ -32,15 +32,4 @@ protected function getContributionIDs(): array { return $this->getIDs(); } - /** - * List available tokens for this form. - * - * @return array - */ - public function listTokens() { - $tokens = CRM_Core_SelectValues::contactTokens(); - $tokens = array_merge(CRM_Core_SelectValues::contributionTokens(), $tokens); - return $tokens; - } - } diff --git a/CRM/Contribute/Form/Task/PDFLetter.php b/CRM/Contribute/Form/Task/PDFLetter.php index ce5839b7123a..e6b639f09e7c 100644 --- a/CRM/Contribute/Form/Task/PDFLetter.php +++ b/CRM/Contribute/Form/Task/PDFLetter.php @@ -293,15 +293,12 @@ protected function isSendEmails(): bool { } /** - * List available tokens for this form. + * Get the token processor schema required to list any tokens for this task. * * @return array */ - public function listTokens() { - $tokens = CRM_Core_SelectValues::contactTokens(); - $tokens = array_merge(CRM_Core_SelectValues::contributionTokens(), $tokens); - $tokens = array_merge(CRM_Core_SelectValues::domainTokens(), $tokens); - return $tokens; + public function getTokenSchema(): array { + return ['contributionId', 'contactId']; } /** diff --git a/CRM/Core/Form/Task.php b/CRM/Core/Form/Task.php index cb4c7349c9da..81ed62d15caf 100644 --- a/CRM/Core/Form/Task.php +++ b/CRM/Core/Form/Task.php @@ -9,6 +9,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Token\TokenProcessor; + /** * @package CRM * @copyright CiviCRM LLC https://civicrm.org/licensing @@ -338,4 +340,45 @@ public function getEntityAliasField() { return $this::$entityShortname . '_id'; } + /** + * List available tokens for this form. + * + * @return array + */ + public function listTokens() { + $tokenProcessor = new TokenProcessor(Civi::dispatcher(), ['schema' => $this->getTokenSchema()]); + return $tokenProcessor->listTokens(); + } + + /** + * Get the token processor schema required to list any tokens for this task. + * + * @return array + */ + protected function getTokenSchema(): array { + return ['contactId']; + } + + /** + * Get the rows from the results. + * + * @return array + */ + protected function getRows(): array { + $rows = []; + foreach ($this->getContactIDs() as $contactID) { + $rows[] = ['contactId' => $contactID]; + } + return $rows; + } + + /** + * Get the relevant contact IDs. + * + * @return array + */ + protected function getContactIDs(): array { + return $this->_contactIds ?? []; + } + } diff --git a/CRM/Event/Form/Task.php b/CRM/Event/Form/Task.php index e3bbae330dac..20704e0e9392 100644 --- a/CRM/Event/Form/Task.php +++ b/CRM/Event/Form/Task.php @@ -15,6 +15,8 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\Participant; + /** * Class for event form task actions. * FIXME: This needs refactoring to properly inherit from CRM_Core_Form_Task and share more functions. @@ -28,6 +30,16 @@ class CRM_Event_Form_Task extends CRM_Core_Form_Task { */ protected $_participantIds; + /** + * Rows to act on. + * + * Each row will have a participant ID & a contact ID using + * the keys the token processor expects. + * + * @var array + */ + protected $rows = []; + /** * Build all the data structures needed to build the form. * @@ -83,14 +95,36 @@ public static function preProcessCommon(&$form) { $form->setNextUrl('event'); } + /** + * Get the participant IDs. + * + * @return array + */ + public function getIDs(): array { + return $this->_participantIds; + } + /** * Given the participant id, compute the contact id * since its used for things like send email */ - public function setContactIDs() { - $this->_contactIds = CRM_Core_DAO::getContactIDsFromComponent($this->_participantIds, - 'civicrm_participant' - ); + public function setContactIDs(): void { + $this->_contactIds = $this->getContactIDs(); + } + + /** + * Get the relevant contact IDs. + * + * @return array + */ + protected function getContactIDs(): array { + if (isset($this->_contactIds)) { + return $this->_contactIds; + } + foreach ($this->getRows() as $row) { + $this->_contactIds[] = $row['contactId']; + } + return $this->_contactIds; } /** @@ -119,4 +153,35 @@ public function addDefaultButtons($title, $nextType = 'next', $backType = 'back' ]); } + /** + * Get the rows form the search, keyed to make the token processor happy. + * + * @throws \API_Exception + */ + public function getRows(): array { + if (empty($this->rows)) { + // checkPermissions set to false - in case form is bypassing in some way. + $participants = Participant::get(FALSE) + ->addWhere('id', 'IN', $this->getIDs()) + ->setSelect(['id', 'contact_id'])->execute(); + foreach ($participants as $participant) { + $this->rows[] = [ + // We map to this funky format for the token processor :-( + 'contactId' => $participant['contact_id'], + 'participantId' => $participant['id'], + ]; + } + } + return $this->rows; + } + + /** + * Get the token processor schema required to list any tokens for this task. + * + * @return array + */ + public function getTokenSchema(): array { + return ['participantId', 'contactId', 'eventId']; + } + } diff --git a/CRM/Event/Form/Task/PDF.php b/CRM/Event/Form/Task/PDF.php index 778a195f2bab..0f0d25436f4f 100644 --- a/CRM/Event/Form/Task/PDF.php +++ b/CRM/Event/Form/Task/PDF.php @@ -48,21 +48,7 @@ class CRM_Event_Form_Task_PDF extends CRM_Event_Form_Task { public function preProcess() { $this->preProcessPDF(); parent::preProcess(); - - // we have all the participant ids, so now we get the contact ids - parent::setContactIDs(); - $this->assign('single', $this->_single); } - /** - * List available tokens for this form. - * - * @return array - */ - public function listTokens() { - $tokens = CRM_Core_SelectValues::contactTokens(); - return $tokens; - } - } diff --git a/CRM/Event/ParticipantTokens.php b/CRM/Event/ParticipantTokens.php index 5733e10af9e2..f33af5d2b545 100644 --- a/CRM/Event/ParticipantTokens.php +++ b/CRM/Event/ParticipantTokens.php @@ -10,6 +10,7 @@ +--------------------------------------------------------------------+ */ +use Civi\Token\Event\TokenValueEvent; use Civi\Token\TokenRow; /** @@ -42,6 +43,21 @@ public function getCurrencyFieldName(): array { return ['fee_currency']; } + /** + * To handle variable tokens, override this function and return the active tokens. + * + * @param \Civi\Token\Event\TokenValueEvent $e + * + * @return mixed + */ + public function getActiveTokens(TokenValueEvent $e) { + $messageTokens = $e->getTokenProcessor()->getMessageTokens(); + if (!isset($messageTokens[$this->entity])) { + return isset($messageTokens['event']) ? ['event_id'] : FALSE; + } + return parent::getActiveTokens($e); + } + /** * Get any tokens with custom calculation. */ diff --git a/CRM/Member/Form/Task.php b/CRM/Member/Form/Task.php index 8d5d042ab8b2..9dd37aa1b90f 100644 --- a/CRM/Member/Form/Task.php +++ b/CRM/Member/Form/Task.php @@ -95,4 +95,13 @@ public function setContactIDs() { ); } + /** + * Get the token processor schema required to list any tokens for this task. + * + * @return array + */ + public function getTokenSchema(): array { + return ['membershipId', 'contactId']; + } + } diff --git a/CRM/Member/Form/Task/PDFLetter.php b/CRM/Member/Form/Task/PDFLetter.php index 1d2354e3b4b1..f7464b1b705f 100644 --- a/CRM/Member/Form/Task/PDFLetter.php +++ b/CRM/Member/Form/Task/PDFLetter.php @@ -138,14 +138,12 @@ public function generateHTML($membershipIDs, $messageToken, $html_message): arra } /** - * List available tokens for this form. + * Get the token processor schema required to list any tokens for this task. * * @return array */ - public function listTokens() { - $tokens = CRM_Core_SelectValues::contactTokens(); - $tokens = array_merge(CRM_Core_SelectValues::membershipTokens(), $tokens); - return $tokens; + public function getTokenSchema(): array { + return ['membershipId', 'contactId']; } } diff --git a/Civi/Token/TokenRow.php b/Civi/Token/TokenRow.php index 96ea17816656..680e7c0e3693 100644 --- a/Civi/Token/TokenRow.php +++ b/Civi/Token/TokenRow.php @@ -174,8 +174,8 @@ public function tokens($a = NULL, $b = NULL, $c = NULL) { * @return TokenRow */ public function customToken($entity, $customFieldID, $entityID) { - $customFieldName = "custom_" . $customFieldID; - $record = civicrm_api3($entity, "getSingle", [ + $customFieldName = 'custom_' . $customFieldID; + $record = civicrm_api3($entity, 'getSingle', [ 'return' => $customFieldName, 'id' => $entityID, ]); @@ -183,7 +183,7 @@ public function customToken($entity, $customFieldID, $entityID) { // format the raw custom field value into proper display value if (isset($fieldValue)) { - $fieldValue = \CRM_Core_BAO_CustomField::displayValue($fieldValue, $customFieldID); + $fieldValue = (string) \CRM_Core_BAO_CustomField::displayValue($fieldValue, $customFieldID); } return $this->format('text/html')->tokens($entity, $customFieldName, $fieldValue); diff --git a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php index 92f1048c25fa..33612e5778dd 100644 --- a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php +++ b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php @@ -569,6 +569,24 @@ public function testParticipantTokenConsistency(): void { } + /** + * Test that membership tokens are consistently rendered. + * + * @throws \API_Exception + * @throws \CRM_Core_Exception + */ + public function testParticipantCustomDateToken(): void { + $this->createEventAndParticipant(); + $dateFieldID = $this->createDateCustomField(['custom_group_id' => $this->ids['CustomGroup']['participant_'], 'default_value' => ''])['id']; + $input = '{participant.custom_' . $dateFieldID . '}'; + $input .= '{participant.' . $this->getCustomFieldName('participant_int') . '}'; + $tokenHtml = CRM_Core_BAO_MessageTemplate::renderTemplate([ + 'messageTemplate' => ['msg_html' => $input], + 'tokenContext' => array_merge(['participantId' => $this->ids['participant'][0]], ['schema' => ['participantId', 'eventId']]), + ])['html']; + $this->assertEquals(99999, $tokenHtml); + } + /** * Get declared participant tokens. * @@ -745,45 +763,12 @@ public function testEventTokenConsistencyNoParticipantTokens(): void { * @throws \API_Exception */ public function setupParticipantScheduledReminder($includeParticipant = TRUE): void { - $this->createCustomGroupWithFieldOfType(['extends' => 'Event']); - $this->createCustomGroupWithFieldOfType(['extends' => 'Participant'], 'int', 'participant_'); - $html = ''; + $this->createEventAndParticipant(); $tokens = array_keys($this->getEventTokens()); if ($includeParticipant) { $tokens = array_keys(array_merge($this->getEventTokens(), $this->getParticipantTokens())); } $html = $this->getTokenString($tokens); - $emailID = Email::create()->setValues(['email' => 'event@example.com'])->execute()->first()['id']; - $addressID = Address::create()->setValues([ - 'street_address' => '15 Walton St', - 'supplemental_address_1' => 'up the road', - 'city' => 'Emerald City', - 'state_province_id:label' => 'Maine', - 'postal_code' => 90210, - ])->execute()->first()['id']; - $phoneID = Phone::create()->setValues(['phone' => '456 789'])->execute()->first()['id']; - - $locationBlockID = LocBlock::save(FALSE)->setRecords([ - [ - 'email_id' => $emailID, - 'address_id' => $addressID, - 'phone_id' => $phoneID, - ], - ])->execute()->first()['id']; - $this->ids['event'][0] = $this->eventCreate([ - 'description' => 'event description', - $this->getCustomFieldName('text') => 'my field', - 'loc_block_id' => $locationBlockID, - ])['id']; - // Create an unrelated participant record so that the ids don't match. - // this prevents things working just because the id 'happens to be valid' - $this->participantCreate(['register_date' => '2020-01-01', 'event_id' => $this->ids['event'][0]]); - $this->ids['participant'][0] = $this->participantCreate([ - 'event_id' => $this->ids['event'][0], - 'fee_amount' => 50, - 'fee_level' => 'steep', - $this->getCustomFieldName('participant_int') => '99999', - ]); CRM_Utils_Time::setTime('2007-02-20 15:00:00'); $this->callAPISuccess('action_schedule', 'create', [ 'title' => 'job', @@ -834,4 +819,54 @@ protected function getTokenString(array $tokens): string { return $html; } + /** + * Create an event with a participant. + * + * @throws \API_Exception + */ + protected function createEventAndParticipant(): void { + $this->createCustomGroupWithFieldOfType(['extends' => 'Event']); + $this->createCustomGroupWithFieldOfType(['extends' => 'Participant'], 'int', 'participant_'); + $emailID = Email::create() + ->setValues(['email' => 'event@example.com']) + ->execute() + ->first()['id']; + $addressID = Address::create()->setValues([ + 'street_address' => '15 Walton St', + 'supplemental_address_1' => 'up the road', + 'city' => 'Emerald City', + 'state_province_id:label' => 'Maine', + 'postal_code' => 90210, + ])->execute()->first()['id']; + $phoneID = Phone::create() + ->setValues(['phone' => '456 789']) + ->execute() + ->first()['id']; + + $locationBlockID = LocBlock::save(FALSE)->setRecords([ + [ + 'email_id' => $emailID, + 'address_id' => $addressID, + 'phone_id' => $phoneID, + ], + ])->execute()->first()['id']; + $this->ids['event'][0] = $this->eventCreate([ + 'description' => 'event description', + $this->getCustomFieldName('text') => 'my field', + 'loc_block_id' => $locationBlockID, + ])['id']; + // Create an unrelated participant record so that the ids don't match. + // this prevents things working just because the id 'happens to be valid' + $this->participantCreate([ + 'register_date' => '2020-01-01', + 'event_id' => $this->ids['event'][0], + ]); + $this->ids['participant'][0] = $this->participantCreate([ + 'event_id' => $this->ids['event'][0], + 'fee_amount' => 50, + 'fee_level' => 'steep', + $this->getCustomFieldName('participant_int') => '99999', + ]); + } + }