From bd6b5299296f34e7908af33bdf2e25de61bbb060 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Fri, 13 Aug 2021 10:53:44 +1200 Subject: [PATCH 1/2] dev/core#2745 Expose all field to token processor for contribution This just excludes contact id - see https://lab.civicrm.org/dev/core/-/issues/2745 for extra list --- CRM/Contribute/Tokens.php | 65 ------------------- CRM/Core/EntityTokens.php | 65 +++++++++++++++++++ .../Contribute/ActionMapping/ByTypeTest.php | 5 +- .../CRM/Contribute/BAO/ContributionTest.php | 3 +- 4 files changed, 71 insertions(+), 67 deletions(-) diff --git a/CRM/Contribute/Tokens.php b/CRM/Contribute/Tokens.php index ca1b952cc7fb..6a8c7754f968 100644 --- a/CRM/Contribute/Tokens.php +++ b/CRM/Contribute/Tokens.php @@ -20,13 +20,6 @@ */ class CRM_Contribute_Tokens extends CRM_Core_EntityTokens { - /** - * @return string - */ - protected function getEntityName(): string { - return 'contribution'; - } - /** * @return string */ @@ -46,62 +39,4 @@ protected function getApiEntityName(): string { return 'Contribution'; } - /** - * Get a list of tokens for the entity for which access is permitted to. - * - * This list is historical and we need to question whether we - * should filter out any fields (other than those fields, like api_key - * on the contact entity) with permissions defined. - * - * @return array - */ - protected function getExposedFields(): array { - $fields = [ - 'contribution_page_id', - 'source', - 'id', - 'receive_date', - 'total_amount', - 'fee_amount', - 'net_amount', - 'non_deductible_amount', - 'trxn_id', - 'invoice_id', - 'currency', - 'cancel_date', - 'receipt_date', - 'thankyou_date', - 'tax_amount', - 'contribution_status_id', - 'financial_type_id', - 'payment_instrument_id', - 'cancel_reason', - 'amount_level', - 'check_number', - ]; - if (CRM_Campaign_BAO_Campaign::isCampaignEnable()) { - $fields[] = 'campaign_id'; - } - return $fields; - } - - /** - * Get tokens supporting the syntax we are migrating to. - * - * In general these are tokens that were not previously supported - * so we can add them in the preferred way or that we have - * undertaken some, as yet to be written, db update. - * - * See https://lab.civicrm.org/dev/core/-/issues/2650 - * - * @return string[] - */ - public function getBasicTokens(): array { - $return = []; - foreach ($this->getExposedFields() as $fieldName) { - $return[$fieldName] = $this->getFieldMetadata()[$fieldName]['title']; - } - return $return; - } - } diff --git a/CRM/Core/EntityTokens.php b/CRM/Core/EntityTokens.php index 542ff711ac75..c967815ac561 100644 --- a/CRM/Core/EntityTokens.php +++ b/CRM/Core/EntityTokens.php @@ -214,6 +214,15 @@ public function isAddPseudoTokens($fieldName): bool { // from the metadata as yet. return FALSE; } + if ($this->getFieldMetadata()[$fieldName]['type'] === 'Custom') { + // If we remove this early return then we get that extra nuanced goodness + // and support for the more portable v4 style field names + // on custom fields - where labels or names can be returned. + // At present the gap is that the metadata for the label is not accessed + // and tests failed on the enotice and we don't have a clear plan about + // v4 style custom tokens - but medium term this IF will probably go. + return FALSE; + } return (bool) $this->getFieldMetadata()[$fieldName]['options']; } @@ -284,4 +293,60 @@ public function alterActionScheduleQuery(MailingQueryEvent $e): void { } } + /** + * Get tokens supporting the syntax we are migrating to. + * + * In general these are tokens that were not previously supported + * so we can add them in the preferred way or that we have + * undertaken some, as yet to be written, db update. + * + * See https://lab.civicrm.org/dev/core/-/issues/2650 + * + * @return string[] + * @throws \API_Exception + */ + public function getBasicTokens(): array { + $return = []; + foreach ($this->getExposedFields() as $fieldName) { + $return[$fieldName] = $this->getFieldMetadata()[$fieldName]['title']; + } + return $return; + } + + /** + * Get entity fields that should be exposed as tokens. + * + * @return string[] + * + */ + public function getExposedFields(): array { + $return = []; + foreach ($this->getFieldMetadata() as $field) { + if (!in_array($field['name'], $this->getSkippedFields(), TRUE)) { + $return[] = $field['name']; + } + } + return $return; + } + + /** + * Get entity fields that should not be exposed as tokens. + * + * @return string[] + */ + public function getSkippedFields(): array { + $fields = ['contact_id']; + if (!CRM_Campaign_BAO_Campaign::isCampaignEnable()) { + $fields[] = 'campaign_id'; + } + return $fields; + } + + /** + * @return string + */ + protected function getEntityName(): string { + return CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($this->getApiEntityName()); + } + } diff --git a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php index d10f687bd719..2b2c9cc2892a 100644 --- a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php +++ b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php @@ -255,6 +255,7 @@ public function useHelloFirstNameStatus(): void { * legacy processor function. Once this is true we can expose the listener on the * token processor for contribution and call it internally from the legacy code. * + * @throws \API_Exception * @throws \CiviCRM_API3_Exception */ public function testTokenRendering(): void { @@ -381,7 +382,9 @@ public function testTokenRendering(): void { foreach ($fields as $field) { $allFields[$field['name']] = $field['title']; } - // $this->assertEquals($realLegacyTokens, $allFields); + // contact ID is skipped. + unset($allFields['contact_id']); + $this->assertEquals($allFields, $realLegacyTokens); $this->assertEquals($legacyTokens, $processor->tokenNames); foreach ($tokens as $token) { $this->assertEquals(CRM_Core_SelectValues::contributionTokens()['{contribution.' . $token . '}'], $processor->tokenNames[$token]); diff --git a/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php b/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php index 1eada5848275..f74d3624c4f5 100644 --- a/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php +++ b/tests/phpunit/CRM/Contribute/BAO/ContributionTest.php @@ -1304,7 +1304,8 @@ public function testProcessOnBehalfOrganization() { /** * Test for replaceContributionTokens. - * This function tests whether the contribution tokens are replaced with + * + * This function tests whether the contribution tokens are replaced with * values from contribution. * * @throws \CiviCRM_API3_Exception From eba1573201913e1ed4c808068321089d4c7599e2 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Sat, 7 Aug 2021 18:19:34 +1200 Subject: [PATCH 2/2] dev/core#2747 Add listening & test for contribution tokens processor --- CRM/Contribute/Tokens.php | 7 +++ CRM/Core/EntityTokens.php | 61 +++++++++++++++++-- .../Contribute/ActionMapping/ByTypeTest.php | 17 ++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/CRM/Contribute/Tokens.php b/CRM/Contribute/Tokens.php index 6a8c7754f968..6569c6a72ee3 100644 --- a/CRM/Contribute/Tokens.php +++ b/CRM/Contribute/Tokens.php @@ -39,4 +39,11 @@ protected function getApiEntityName(): string { return 'Contribution'; } + /** + * @return array + */ + public function getCurrencyFieldName() { + return ['currency']; + } + } diff --git a/CRM/Core/EntityTokens.php b/CRM/Core/EntityTokens.php index c967815ac561..438cb3d007b0 100644 --- a/CRM/Core/EntityTokens.php +++ b/CRM/Core/EntityTokens.php @@ -27,11 +27,17 @@ */ class CRM_Core_EntityTokens extends AbstractTokenSubscriber { + /** + * @var array + */ + protected $prefetch = []; + /** * @inheritDoc * @throws \CRM_Core_Exception */ public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) { + $this->prefetch = (array) $prefetch; $fieldValue = $this->getFieldValue($row, $field); if ($this->isPseudoField($field)) { @@ -40,7 +46,7 @@ public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) } if ($this->isMoneyField($field)) { return $row->format('text/plain')->tokens($entity, $field, - \CRM_Utils_Money::format($fieldValue, $this->getFieldValue($row, 'currency'))); + \CRM_Utils_Money::format($fieldValue, $this->getCurrency($row))); } if ($this->isDateField($field)) { return $row->format('text/plain')->tokens($entity, $field, \CRM_Utils_Date::customFormat($fieldValue)); @@ -256,7 +262,11 @@ public function getPseudoValue(string $realField, string $pseudoKey, $fieldValue protected function getFieldValue(TokenRow $row, string $field) { $actionSearchResult = $row->context['actionSearchResult']; $aliasedField = $this->getEntityAlias() . $field; - return $actionSearchResult->{$aliasedField} ?? NULL; + if (isset($actionSearchResult->{$aliasedField})) { + return $actionSearchResult->{$aliasedField}; + } + $entityID = $row->context[$this->getEntityIDField()]; + return $this->prefetch[$entityID][$field] ?? ''; } /** @@ -275,8 +285,10 @@ public function __construct() { * @return bool */ public function checkActive(TokenProcessor $processor) { - return !empty($processor->context['actionMapping']) - && $processor->context['actionMapping']->getEntity() === $this->getExtendableTableName(); + return (!empty($processor->context['actionMapping']) + // This makes the 'schema context compulsory - which feels accidental + // since recent discu + && $processor->context['actionMapping']->getEntity()) || in_array($this->getEntityIDField(), $processor->context['schema']); } /** @@ -349,4 +361,45 @@ protected function getEntityName(): string { return CRM_Core_DAO_AllCoreTables::convertEntityNameToLower($this->getApiEntityName()); } + public function getEntityIDField() { + return $this->getEntityName() . 'Id'; + } + + public function prefetch(\Civi\Token\Event\TokenValueEvent $e): ?array { + $entityIDs = $e->getTokenProcessor()->getContextValues($this->getEntityIDField()); + if (empty($entityIDs)) { + return []; + } + $select = $this->getPrefetchFields($e); + $result = (array) civicrm_api4($this->getApiEntityName(), 'get', [ + 'checkPermissions' => FALSE, + // Note custom fields are not yet added - I need to + // re-do the unit tests to support custom fields first. + 'select' => $select, + 'where' => [['id', 'IN', $entityIDs]], + ], 'id'); + return $result; + } + + public function getCurrencyFieldName() { + return []; + } + + /** + * Get the currency to use for formatting money. + * @param $row + * + * @return string + */ + public function getCurrency($row): string { + if (!empty($this->getCurrencyFieldName())) { + return $this->getFieldValue($row, $this->getCurrencyFieldName()[0]); + } + return CRM_Core_Config::singleton()->defaultCurrency; + } + + public function getPrefetchFields(\Civi\Token\Event\TokenValueEvent $e): array { + return array_intersect($this->getActiveTokens($e), $this->getCurrencyFieldName(), array_keys($this->getAllTokens())); + } + } diff --git a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php index 2b2c9cc2892a..14da2ef4251f 100644 --- a/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php +++ b/tests/phpunit/CRM/Contribute/ActionMapping/ByTypeTest.php @@ -10,6 +10,7 @@ */ use Civi\Api4\Contribution; +use Civi\Token\TokenProcessor; /** * Class CRM_Contribute_ActionMapping_ByTypeTest @@ -320,6 +321,22 @@ public function testTokenRendering(): void { ]; $this->mut->checkMailLog($expected); + $tokenProcessor = new TokenProcessor(\Civi::dispatcher(), [ + 'controller' => get_class(), + 'smarty' => FALSE, + 'schema' => ['contributionId'], + 'contributionId' => $this->ids['Contribution']['alice'], + 'contactId' => $this->contacts['alice']['id'], + ]); + $tokenProcessor->addRow([]); + $tokenProcessor->addMessage('html', $this->schedule->body_text, 'text/plain'); + $tokenProcessor->evaluate(); + foreach ($tokenProcessor->getRows() as $row) { + foreach ($expected as $value) { + $this->assertStringContainsString($value, $row->render('html')); + } + } + $messageToken = CRM_Utils_Token::getTokens($this->schedule->body_text); $contributionDetails = CRM_Contribute_BAO_Contribution::replaceContributionTokens(