From ff928f78262b6dcdacc49a90e5e53ca141d2b20e Mon Sep 17 00:00:00 2001 From: olayiwola-compucorp Date: Wed, 3 Apr 2024 15:46:04 +0100 Subject: [PATCH 1/3] FOSFA-283: Resolve issue with case tokens not resolved via webform We have rewritten the logic to evaluate case roles and custom fields tokens such that irrespective of how the case is creeated and email is triggered the tokens value are evaluated appropraitely --- .../Tokens/AddCaseCustomFieldsTokenValues.php | 135 +++++++----------- .../Hook/Tokens/AddCaseTokenCategory.php | 20 ++- .../Service/CaseCustomFieldsProvider.php | 48 +++++++ civicase.php | 13 +- 4 files changed, 125 insertions(+), 91 deletions(-) create mode 100644 CRM/Civicase/Service/CaseCustomFieldsProvider.php diff --git a/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php b/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php index 6056fbf37..405e9dbd0 100644 --- a/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php +++ b/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php @@ -1,6 +1,6 @@ getTokenProcessor()->context; + $caseTokenValuesHelper = new CRM_Civicase_Hook_Tokens_Helper_CaseTokenValues(); + $customTokens = $e->getTokenProcessor()->getMessageTokens()['case_cf'] ?? []; + $caseRoleTokens = $e->getTokenProcessor()->getMessageTokens()['case_roles'] ?? []; + $caseRoleValues = []; - /** - * Case Token Values helper. - * - * @var \CRM_Civicase_Hook_Tokens_Helper_CaseTokenValues - * Case Custom field helper. - */ - private $caseTokenValuesHelper; + if (array_key_exists('schema', $context) && in_array('caseId', $context['schema'])) { + foreach ($e->getRows() as $row) { + if (!empty($row->context['caseId'])) { + $caseId = $row->context['caseId']; + $contactId = $row->context['contactId']; + $customValues = $caseTokenValuesHelper->getCustomFieldValues($caseId, $customTokens); - /** - * Sets required class properties. - * - * @param \CRM_Civicase_Hook_Tokens_Helper_CaseTokenValues $caseTokenValuesHelper - * Case token values helper. - */ - public function __construct(CaseTokenValuesHelper $caseTokenValuesHelper) { - $this->caseTokenValuesHelper = $caseTokenValuesHelper; - } + // Replace custom field tokens with their values. + foreach ($customTokens as $token) { + $value = $caseTokenValuesHelper->getTokenReplacementValue($token, $customValues); + $row->format('text/plain')->tokens('case_cf', $token, $value); + $row->format('text/html')->tokens('case_cf', $token, $value); + } - /** - * Sets case custom field token values. - * - * @param array $values - * Token values. - * @param array $cids - * Contact ids. - * @param int $job - * Job id. - * @param array $tokens - * Available tokens. - * @param string $context - * Context name. - */ - public function run(array &$values, array $cids, $job, array $tokens, $context) { - $this->caseId = $this->caseTokenValuesHelper->getCaseId($values); - if (!$this->shouldRun($tokens)) { - return; - } + // If the token is being resolved through a webform-triggered + // activity, the case role token extension might fail + // to resolve the case role tokens + // due to its inability to locate the case ID. + // To address this, we manually reevaluate the token value here + // by extracting the case ID from the token event. + if (!self::isWebform()) { + continue; + } - $this->setCaseCustomFieldTokenValues($values, $cids, $tokens); - } + if (function_exists('casetokens_civicrm_tokenvalues') && !empty($caseRoleTokens)) { + Civi::$statics['casetokens']['case_id'] = $row->context['caseId']; + casetokens_civicrm_tokenvalues($caseRoleValues, [$contactId], NULL, ['case_roles' => $caseRoleTokens]); + } - /** - * Sets case custom field values. - * - * Normally we would not do this but there is an issue of a mysql max table - * join error on a site with a lot of case custom fields enabled. - * To fix that this class CRM_Civicase_Event_Listener_CaseCustomFields was - * created to prevent the loading of custom fields for cases without the - * specific custom fields being passed. However, when replacing tokens for - * sent emails, Civi expects the Case.get call to also return the case custom - * fields with the other case parameters when no return value is specified. - * - * This function replaces the case custom fields token values as civi is - * not able to do the values replacement because of the custom change we made - * to avoid the 61 max table join error. - * - * @param array $values - * Token values. - * @param array $cids - * Contact ids. - * @param array $tokens - * Available tokens. - */ - private function setCaseCustomFieldTokenValues(array &$values, array $cids, array $tokens) { - $customValues = $this->caseTokenValuesHelper->getCustomFieldValues($this->caseId, $tokens['case_cf']); - if (empty($customValues)) { - return; - } - unset($customValues['id']); + // Replace case role tokens with their values. + if (!empty($caseRoleValues)) { + foreach ($caseRoleTokens as $token) { + $row->format('text/plain')->tokens('case_roles', $token, $caseRoleValues[$contactId]['case_roles.' . $token] ?? ''); + $row->format('text/html')->tokens('case_roles', $token, $caseRoleValues[$contactId]['case_roles.' . $token] ?? ''); + } - // We need to prepend the token category 'case_cf' to the custom field key - // so it can be evaluated for the case_cf category when token is replaced. - $customValuesNew = []; - foreach ($customValues as $key => $customValue) { - $customValuesNew['case_cf.' . $key] = $this->caseTokenValuesHelper->getTokenReplacementValue($key, $customValues); - } + } + } + } - foreach ($cids as $cid) { - $values[$cid] = array_merge($values[$cid], $customValuesNew); } + } /** - * Decides whether the hook should run or not. - * - * @param array $tokens - * Available tokens. - * - * @return bool - * Whether this hook should run or not. + * Detects the token activity is triggered by webform. */ - private function shouldRun(array $tokens) { - return !empty($this->caseId) && !empty($tokens['case_cf']); + private static function isWebform() { + return isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_'); } } diff --git a/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php b/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php index e746edc25..110dd6378 100644 --- a/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php +++ b/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php @@ -1,10 +1,25 @@ caseCustomFieldsService = $caseCustomFieldsService; + } + /** * Sets Case Token Category. * @@ -40,7 +55,10 @@ private function setCaseTokenCategory(array &$tokens) { return $tokens['case_cf'] = []; } - $tokens['case_cf'][''] = ''; + foreach ($this->caseCustomFieldsService->get() as $key => $field) { + $tokens[self::TOKEN_KEY]['case_cf.' . $key] = + CaseTypeCategoryHelper::translate(ucwords(str_replace("_", " ", $field))); + } } /** diff --git a/CRM/Civicase/Service/CaseCustomFieldsProvider.php b/CRM/Civicase/Service/CaseCustomFieldsProvider.php new file mode 100644 index 000000000..0ee2c26d3 --- /dev/null +++ b/CRM/Civicase/Service/CaseCustomFieldsProvider.php @@ -0,0 +1,48 @@ +getCustomFields(); + if (!empty($customFields['values'])) { + foreach ($customFields['values'] as $id => $item) { + $fields['custom_' . $id] = $item['name']; + } + } + + return $fields; + } + + /** + * Provides contacts custom fields. + * + * @return array + * List of custom fields that extends contacts. + */ + public function getCustomFields() { + $customFields = []; + try { + $customFields = civicrm_api3('CustomField', 'get', [ + 'custom_group_id.extends' => [ + 'IN' => ['Case'], + ], + 'options' => ['limit' => 0], + ]); + } + catch (Throwable $ex) { + } + + return $customFields; + } + +} diff --git a/civicase.php b/civicase.php index 44e527c15..cbd9a0044 100644 --- a/civicase.php +++ b/civicase.php @@ -89,6 +89,14 @@ function civicase_civicrm_config(&$config) { 'civi.token.eval', ['CRM_Civicase_Hook_Tokens_SalesOrderTokens', 'evaluateSalesOrderTokens'] ); + + Civi::dispatcher()->addListener( + 'civi.token.eval', + [ + 'CRM_Civicase_Hook_Tokens_AddCaseCustomFieldsTokenValues', + 'evaluateCaseCustomFieldsTokens', + ] + ); } /** @@ -371,9 +379,10 @@ function civicase_civicrm_alterAPIPermissions($entity, $action, &$params, &$perm function civicase_civicrm_tokens(&$tokens) { $contactFieldsService = new CRM_Civicase_Service_ContactFieldsProvider(); $contactCustomFieldsService = new CRM_Civicase_Service_ContactCustomFieldsProvider(); + $caseCustomFieldsService = new CRM_Civicase_Service_CaseCustomFieldsProvider(); $hooks = [ new CRM_Civicase_Hook_Tokens_AddContactTokens($contactFieldsService, $contactCustomFieldsService), - new CRM_Civicase_Hook_Tokens_AddCaseTokenCategory(), + new CRM_Civicase_Hook_Tokens_AddCaseTokenCategory($caseCustomFieldsService), ]; foreach ($hooks as &$hook) { $hook->run($tokens); @@ -386,10 +395,8 @@ function civicase_civicrm_tokens(&$tokens) { function civicase_civicrm_tokenValues(&$values, $cids, $job = NULL, $tokens = [], $context = NULL) { $contactFieldsService = new CRM_Civicase_Service_ContactFieldsProvider(); $contactCustomFieldsService = new CRM_Civicase_Service_ContactCustomFieldsProvider(); - $caseTokenValuesHelper = new CRM_Civicase_Hook_Tokens_Helper_CaseTokenValues(); $hooks = [ new CRM_Civicase_Hook_Tokens_AddContactTokensValues($contactFieldsService, $contactCustomFieldsService), - new CRM_Civicase_Hook_Tokens_AddCaseCustomFieldsTokenValues($caseTokenValuesHelper), ]; foreach ($hooks as &$hook) { $hook->run($values, $cids, $job, $tokens, $context); From 23aecdb5bd3a065018ea4e6e4571b67d2d400eb2 Mon Sep 17 00:00:00 2001 From: olayiwola-compucorp Date: Thu, 4 Apr 2024 10:48:08 +0100 Subject: [PATCH 2/3] FOSFA-283: Remove case custom field token test We are removing this test given that the case custom field token resolve logic has changed --- .../AddCaseCustomFieldsTokenValuesTest.php | 108 ------------------ 1 file changed, 108 deletions(-) delete mode 100644 tests/phpunit/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValuesTest.php diff --git a/tests/phpunit/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValuesTest.php b/tests/phpunit/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValuesTest.php deleted file mode 100644 index faff61dd0..000000000 --- a/tests/phpunit/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValuesTest.php +++ /dev/null @@ -1,108 +0,0 @@ - 'This is it', - 'custom_99' => 123, - 'custom_50' => 'data', - ]; - $customValuesNew = []; - foreach ($caseCustomFieldValues as $key => $customValue) { - $customValuesNew['case_cf.' . $key] = $customValue; - } - - $caseId = 1; - $caseTokenValuesHelper = $this->getCaseTokenValuesHelperHelperMock($caseCustomFieldValues, $caseId); - $addCaseCustomFieldsTokenValueHook = new AddCaseCustomFieldsTokenValueHook($caseTokenValuesHelper); - - $cid = 1; - $values[$cid] = ['contact_id' => 5, 'display_name' => 'Sample']; - $tokens['case_cf'] = array_merge( - array_keys($caseCustomFieldValues), - ['subject', 'name'] - ); - $expectedResult = array_merge($values[$cid], $customValuesNew); - $addCaseCustomFieldsTokenValueHook->run($values, [$cid], 0, $tokens, NULL); - - $this->assertEquals($expectedResult, $values[$cid]); - } - - /** - * Test the values array is not tampered with. - */ - public function testValuesNotChangedWhenCaseIdNotFound() { - $caseCustomFieldValues = [ - 'custom_10' => 'This is it', - ]; - - $caseCustomFieldValuesHelper = $this->getCaseTokenValuesHelperHelperMock($caseCustomFieldValues, NULL); - $addCaseCustomFieldsTokenValueHook = new AddCaseCustomFieldsTokenValueHook($caseCustomFieldValuesHelper); - - $cid = 1; - $expected = ['contact_id' => 5, 'display_name' => 'Sample']; - $values[$cid] = $expected; - $tokens['case'] = array_merge( - array_keys($caseCustomFieldValues), - ['subject', 'name'] - ); - $addCaseCustomFieldsTokenValueHook->run($values, [$cid], 0, $tokens, NULL); - - $this->assertEquals($expected, $values[$cid]); - } - - /** - * Get mock object for case token values helper. - * - * @param array $customFieldValues - * Custom field values to return. - * @param int $caseId - * Case ID. - * - * @return \PHPUnit_Framework_MockObject_MockObject - * Mock object. - */ - private function getCaseTokenValuesHelperHelperMock(array $customFieldValues, $caseId) { - $caseCustomFieldValuesHelper = $this->getMockBuilder(CaseTokenValuesHelper::class) - ->setMethods( - [ - 'getCustomFieldValues', - 'getCaseId', - 'getTokenReplacementValue', - ] - ) - ->getMock(); - $caseCustomFieldValuesHelper->method('getCustomFieldValues')->willReturn($customFieldValues); - $caseCustomFieldValuesHelper->method('getCaseId')->willReturn($caseId); - $returnValueMap = []; - - foreach ($customFieldValues as $customField => $customFieldValue) { - $returnValueMap[] = [ - $customField, - $customFieldValues, - $customFieldValues[$customField], - ]; - } - - $caseCustomFieldValuesHelper->method('getTokenReplacementValue')->will( - $this->returnValueMap($returnValueMap) - ); - - return $caseCustomFieldValuesHelper; - } - -} From 0a2a72a6ca54a5ad21801c03e91e06d3489ca445 Mon Sep 17 00:00:00 2001 From: olayiwola-compucorp Date: Mon, 15 Apr 2024 16:56:14 +0100 Subject: [PATCH 3/3] FOSFA-283: Correct webform check --- CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php | 2 +- CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php b/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php index 405e9dbd0..57c2c563e 100644 --- a/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php +++ b/CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php @@ -68,7 +68,7 @@ public static function evaluateCaseCustomFieldsTokens(TokenValueEvent $e) { * Detects the token activity is triggered by webform. */ private static function isWebform() { - return isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_'); + return isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') !== FALSE; } } diff --git a/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php b/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php index 110dd6378..c00087b5a 100644 --- a/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php +++ b/CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php @@ -16,7 +16,8 @@ class CRM_Civicase_Hook_Tokens_AddCaseTokenCategory { * Service for fetching contact custom fields. */ public function __construct( - private CRM_Civicase_Service_CaseCustomFieldsProvider $caseCustomFieldsService) { + private CRM_Civicase_Service_CaseCustomFieldsProvider $caseCustomFieldsService, + ) { $this->caseCustomFieldsService = $caseCustomFieldsService; }