Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FOSFA-283: Resolve issue with case tokens not resolved via webform #1012

Merged
merged 3 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 48 additions & 87 deletions CRM/Civicase/Hook/Tokens/AddCaseCustomFieldsTokenValues.php
Original file line number Diff line number Diff line change
@@ -1,113 +1,74 @@
<?php

use CRM_Civicase_Hook_Tokens_Helper_CaseTokenValues as CaseTokenValuesHelper;
use Civi\Token\Event\TokenValueEvent;

/**
* Class for adding case custom fields token values.
*/
class CRM_Civicase_Hook_Tokens_AddCaseCustomFieldsTokenValues {

/**
* Case Id.
* Evaluate custom fields and case role tokens.
*
* @var int|null
* Case Id.
* @param \Civi\Token\Event\TokenValueEvent $e
* TokenValue Event.
*/
private $caseId;
public static function evaluateCaseCustomFieldsTokens(TokenValueEvent $e) {
$context = $e->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_') !== FALSE;
}

}
21 changes: 20 additions & 1 deletion CRM/Civicase/Hook/Tokens/AddCaseTokenCategory.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
<?php

use CRM_Civicase_Hook_Helper_CaseTypeCategory as CaseTypeCategoryHelper;

/**
* Add Case token category class.
*/
class CRM_Civicase_Hook_Tokens_AddCaseTokenCategory {

const TOKEN_KEY = 'case_cf';

/**
* CRM_Civicase_Hook_Tokens_AddCaseTokenCategory constructor.
*
* @param CRM_Civicase_Service_CaseCustomFieldsProvider $caseCustomFieldsService
* Service for fetching contact custom fields.
*/
public function __construct(
private CRM_Civicase_Service_CaseCustomFieldsProvider $caseCustomFieldsService,
) {
$this->caseCustomFieldsService = $caseCustomFieldsService;
}

/**
* Sets Case Token Category.
*
Expand Down Expand Up @@ -40,7 +56,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)));
}
}

/**
Expand Down
48 changes: 48 additions & 0 deletions CRM/Civicase/Service/CaseCustomFieldsProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/**
* Provides contacts custom fields.
*/
class CRM_Civicase_Service_CaseCustomFieldsProvider {

/**
* Provides contacts custom fields.
*
* @return array
* List of custom fields that extends contacts.
*/
public function get() {
$fields = [];
$customFields = $this->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;
}

}
13 changes: 10 additions & 3 deletions civicase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]
);
}

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down

This file was deleted.

Loading