From f030a44d497d02d514312430a603ebce879d227a Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Mon, 13 Feb 2023 10:20:33 +1100 Subject: [PATCH] dev/core#4123 Support contribution recur tokens when accessing from a contribution or membership Use getRelatedTokens function name instead and also fix tests Hide some unnecessary tokens and fix tests --- CRM/Contribute/Tokens.php | 24 ++++++ CRM/Core/EntityTokens.php | 8 ++ CRM/Member/Tokens.php | 22 ++++++ .../Contribute/ActionMapping/ByTypeTest.php | 12 +++ .../CRM/Utils/TokenConsistencyTest.php | 23 +++++- .../phpunit/Civi/Token/TokenProcessorTest.php | 78 +++++++++++++++++++ 6 files changed, 166 insertions(+), 1 deletion(-) diff --git a/CRM/Contribute/Tokens.php b/CRM/Contribute/Tokens.php index 6569c6a72e..7436b0b239 100644 --- a/CRM/Contribute/Tokens.php +++ b/CRM/Contribute/Tokens.php @@ -10,6 +10,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\ContributionRecur; + /** * Class CRM_Contribute_Tokens * @@ -46,4 +48,26 @@ class CRM_Contribute_Tokens extends CRM_Core_EntityTokens { return ['currency']; } + /** + * Get Related Entity tokens. + * + * @return array[] + */ + protected function getRelatedTokens(): array { + $tokens = []; + $hiddenTokens = ['modified_date', 'create_date', 'trxn_id', 'invoice_id', 'is_test', 'payment_token_id', 'payment_processor_id', 'payment_instrument_id', 'cycle_day', 'installments', 'processor_id', 'next_sched_contribution_date', 'failure_count', 'failure_retry_date', 'auto_renew', 'is_email_receipt', 'contribution_status_id']; + $contributionRecurFields = ContributionRecur::getFields(FALSE)->setLoadOptions(TRUE)->execute(); + foreach ($contributionRecurFields as $contributionRecurField) { + $tokens['contribution_recur_id.' . $contributionRecurField['name']] = [ + 'title' => $contributionRecurField['title'], + 'name' => 'contribution_recur_id.' . $contributionRecurField['name'], + 'type' => 'mapped', + 'options' => $contributionRecurField['options'] ?? NULL, + 'data_type' => $contributionRecurField['data_type'], + 'audience' => in_array($contributionRecurField['name'], $hiddenTokens) ? 'hidden' : 'user', + ]; + } + return $tokens; + } + } diff --git a/CRM/Core/EntityTokens.php b/CRM/Core/EntityTokens.php index 9d33cf1359..061f964c8a 100644 --- a/CRM/Core/EntityTokens.php +++ b/CRM/Core/EntityTokens.php @@ -78,6 +78,7 @@ class CRM_Core_EntityTokens extends AbstractTokenSubscriber { $cacheKey = $this->getCacheKey(); if (!Civi::cache('metadata')->has($cacheKey)) { $tokensMetadata = $this->getBespokeTokens(); + $tokensMetadata = array_merge($tokensMetadata, $this->getRelatedTokens()); foreach ($this->getFieldMetadata() as $field) { $this->addFieldToTokenMetadata($tokensMetadata, $field, $this->getExposedFields()); } @@ -275,6 +276,13 @@ class CRM_Core_EntityTokens extends AbstractTokenSubscriber { return []; } + /** + * Get related entity tokens. + */ + protected function getRelatedTokens(): array { + return []; + } + /** * Get the value for the relevant pseudo field. * diff --git a/CRM/Member/Tokens.php b/CRM/Member/Tokens.php index 0b6916f383..6f78449b48 100644 --- a/CRM/Member/Tokens.php +++ b/CRM/Member/Tokens.php @@ -10,6 +10,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\ContributionRecur; + /** * Class CRM_Member_Tokens * @@ -122,4 +124,24 @@ class CRM_Member_Tokens extends CRM_Core_EntityTokens { ]; } + /** + * Get related tokens related to membership e.g. recurring contribution tokens + */ + protected function getRelatedTokens(): array { + $tokens = []; + $hiddenTokens = ['modified_date', 'create_date', 'trxn_id', 'invoice_id', 'is_test', 'payment_token_id', 'payment_processor_id', 'payment_instrument_id', 'cycle_day', 'installments', 'processor_id', 'next_sched_contribution_date', 'failure_count', 'failure_retry_date', 'auto_renew', 'is_email_receipt', 'contribution_status_id']; + $contributionRecurFields = ContributionRecur::getFields(FALSE)->setLoadOptions(TRUE)->execute(); + foreach ($contributionRecurFields as $contributionRecurField) { + $tokens['contribution_recur_id.' . $contributionRecurField['name']] = [ + 'title' => $contributionRecurField['title'], + 'name' => 'contribution_recur_id.' . $contributionRecurField['name'], + 'type' => 'mapped', + 'options' => $contributionRecurField['options'] ?? NULL, + 'data_type' => $contributionRecurField['data_type'], + 'audience' => in_array($contributionRecurField['name'], $hiddenTokens) ? 'hidden' : 'user', + ]; + } + return $tokens; + } + } diff --git a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php index b978f64594..08d751b188 100644 --- a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php +++ b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php @@ -426,6 +426,18 @@ class CRM_Contribute_ActionMapping_ByTypeTest extends \Civi\ActionSchedule\Abstr 'paid_amount' => 'Amount Paid', 'balance_amount' => 'Balance', 'tax_exclusive_amount' => 'Tax Exclusive Amount', + 'contribution_recur_id.id' => 'Recurring Contribution ID', + 'contribution_recur_id.contact_id' => 'Contact ID', + 'contribution_recur_id.amount' => 'Amount', + 'contribution_recur_id.currency' => 'Currency', + 'contribution_recur_id.frequency_unit' => 'Frequency Unit', + 'contribution_recur_id.frequency_interval' => 'Interval (number of units)', + 'contribution_recur_id.start_date' => 'Start Date', + 'contribution_recur_id.cancel_date' => 'Cancel Date', + 'contribution_recur_id.cancel_reason' => 'Cancellation Reason', + 'contribution_recur_id.end_date' => 'Recurring Contribution End Date', + 'contribution_recur_id.financial_type_id' => 'Financial Type ID', + 'contribution_recur_id.campaign_id' => 'Campaign ID', ], $comparison); } diff --git a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php index c87775d79d..b6514403dc 100644 --- a/tests/phpunit/CRM/Utils/TokenConsistencyTest.php +++ b/tests/phpunit/CRM/Utils/TokenConsistencyTest.php @@ -520,7 +520,7 @@ contribution_recur.payment_instrument_id:name :Check $tokens = $tokenProcessor->listTokens(); // Add in custom tokens as token processor supports these. $expectedTokens = array_merge($expectedTokens, $this->getTokensAdvertisedByTokenProcessorButNotLegacy()); - $this->assertEquals(array_merge($expectedTokens, $this->getDomainTokens()), $tokens); + $this->assertEquals(array_merge($expectedTokens, $this->getDomainTokens(), $this->getRecurEntityTokens('membership')), $tokens); $tokenProcessor->addMessage('html', $tokenString, 'text/plain'); $tokenProcessor->addRow(['membershipId' => $this->getMembershipID()]); $tokenProcessor->evaluate(); @@ -957,6 +957,27 @@ United States', $tokenProcessor->getRow(0)->render('message')); ]; } + /** + * @param string $entity + * + * @return string[] + */ + protected function getRecurEntityTokens($entity): array { + return [ + '{' . $entity . '.contribution_recur_id.id}' => 'Recurring Contribution ID', + '{' . $entity . '.contribution_recur_id.contact_id}' => 'Contact ID', + '{' . $entity . '.contribution_recur_id.amount}' => 'Amount', + '{' . $entity . '.contribution_recur_id.currency}' => 'Currency', + '{' . $entity . '.contribution_recur_id.frequency_unit}' => 'Frequency Unit', + '{' . $entity . '.contribution_recur_id.frequency_interval}' => 'Interval (number of units)', + '{' . $entity . '.contribution_recur_id.start_date}' => 'Start Date', + '{' . $entity . '.contribution_recur_id.cancel_date}' => 'Cancel Date', + '{' . $entity . '.contribution_recur_id.cancel_reason}' => 'Cancellation Reason', + '{' . $entity . '.contribution_recur_id.end_date}' => 'Recurring Contribution End Date', + '{' . $entity . '.contribution_recur_id.financial_type_id}' => 'Financial Type ID', + ]; + } + /** * @param array $tokens * diff --git a/tests/phpunit/Civi/Token/TokenProcessorTest.php b/tests/phpunit/Civi/Token/TokenProcessorTest.php index f6a4317f4f..07c93dc701 100644 --- a/tests/phpunit/Civi/Token/TokenProcessorTest.php +++ b/tests/phpunit/Civi/Token/TokenProcessorTest.php @@ -311,6 +311,84 @@ class TokenProcessorTest extends \CiviUnitTestCase { return $tokenProcessor->evaluate()->getRow(0); } + /** + * Check that we can render contribution and contribution_recur tokens when passing a contribution ID. + * This checks Bestspoke tokens + * + * @return void + * @throws \API_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + public function testRenderContributionRecurTokenFromContribution(): void { + $cid = $this->individualCreate(); + $crid = \Civi\Api4\ContributionRecur::create(FALSE) + ->addValue('contact_id', $cid) + ->addValue('amount', 5) + ->execute() + ->first()['id']; + $coid = $this->contributionCreate(['contact_id' => $cid, 'contribution_recur_id' => $crid]); + + $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [ + 'controller' => __CLASS__, + 'schema' => ['contactId', 'contributionId'], + 'smarty' => FALSE, + ]); + $tokenProcessor->addMessage('text', '!!{contribution.id}{contribution.contribution_recur_id.id}{contribution.contribution_recur_id.amount}!!', 'text/plain'); + $tokenProcessor->addRow()->context(['contactId' => $cid, 'contributionId' => $coid]); + + $expectText = [ + "!!{$coid}{$crid}$5.00!!", + ]; + + $rowCount = 0; + foreach ($tokenProcessor->evaluate()->getRows() as $key => $row) { + /** @var TokenRow */ + $this->assertTrue($row instanceof TokenRow); + $this->assertEquals($expectText[$key], $row->render('text')); + $rowCount++; + } + $this->assertEquals(1, $rowCount); + } + + /** + * Check that we can render membership and contribution_recur tokens when passing a membership ID. + * This checks Bestspoke Tokens work correctly + * + * @return void + * @throws \API_Exception + * @throws \Civi\API\Exception\UnauthorizedException + */ + public function testRenderContributionRecurTokenFromMembership(): void { + $cid = $this->individualCreate(); + $crid = \Civi\Api4\ContributionRecur::create(FALSE) + ->addValue('contact_id', $cid) + ->addValue('amount', 5) + ->execute() + ->first()['id']; + $mid = $this->contactMembershipCreate(['contribution_recur_id' => $crid, 'contact_id' => $cid]); + + $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [ + 'controller' => __CLASS__, + 'schema' => ['contactId', 'membershipId'], + 'smarty' => FALSE, + ]); + $tokenProcessor->addMessage('text', '!!{membership.id}{membership.contribution_recur_id.id}{membership.contribution_recur_id.amount}!!', 'text/plain'); + $tokenProcessor->addRow()->context(['contactId' => $cid, 'membershipId' => $mid]); + + $expectText = [ + "!!{$mid}{$crid}$5.00!!", + ]; + + $rowCount = 0; + foreach ($tokenProcessor->evaluate()->getRows() as $key => $row) { + /** @var TokenRow */ + $this->assertTrue($row instanceof TokenRow); + $this->assertEquals($expectText[$key], $row->render('text')); + $rowCount++; + } + $this->assertEquals(1, $rowCount); + } + public function testGetMessageTokens(): void { $tokenProcessor = $this->getTokenProcessor(); $tokenProcessor->addMessage('greeting_html', 'Good morning,

{contact.display_name}

. {custom.foobar}!', 'text/html'); -- 2.25.1