From b441a50438a16e9bf923b1db47ce7e0c50fd6016 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Thu, 16 Sep 2021 10:03:56 +1200 Subject: [PATCH] Fix activity tokens I wasn't gonna touch these until the end but there seems to be some active breakage so this does enough to get the following to work - but leaves a full cleanup pass out of scope as it does the reconcilliation between the trait & parent class and any decisions about any finalised interface. Tests cover the changes to tokens and testActivityDateTimeMatchRepeatableSchedule covers the schedule rendering Existing tokens still work but new-style are advertised with this ``` Subject: {activity.subject} Date: {activity.activity_date_time} Duration: {activity.duration} Location: {activity.location} Details: {activity.details} Status ID: {activity.status_id} (legacy) Status: {activity.status} Status: {activity.status_id:label} Activity Type ID: {activity.activity_type_id} (legacy) Activity Type: {activity.activity_type} Activity Type: {activity.activity_type_id:label} Activity ID: {activity.activity_id} (legacy) Activity ID: {activity.id} (just weird) Case ID: {activity.case_id} ``` --- CRM/Activity/Tokens.php | 170 ++++++++++-------- CRM/Core/TokenTrait.php | 10 +- .../Form/Task/PDFLetterCommonTest.php | 48 ++++- 3 files changed, 139 insertions(+), 89 deletions(-) diff --git a/CRM/Activity/Tokens.php b/CRM/Activity/Tokens.php index bfcb4e76e5..86b9da6b8d 100644 --- a/CRM/Activity/Tokens.php +++ b/CRM/Activity/Tokens.php @@ -15,7 +15,6 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ -use Civi\Token\AbstractTokenSubscriber; use Civi\Token\Event\TokenValueEvent; use Civi\Token\TokenRow; @@ -33,15 +32,17 @@ use Civi\Token\TokenRow; * * This has been enhanced to work with PDF/letter merge */ -class CRM_Activity_Tokens extends AbstractTokenSubscriber { +class CRM_Activity_Tokens extends CRM_Core_EntityTokens { use CRM_Core_TokenTrait; /** + * Get the entity name for api v4 calls. + * * @return string */ - private function getEntityName(): string { - return 'activity'; + protected function getApiEntityName(): string { + return 'Activity'; } /** @@ -75,7 +76,7 @@ class CRM_Activity_Tokens extends AbstractTokenSubscriber { /** * @inheritDoc */ - public function alterActionScheduleQuery(\Civi\ActionSchedule\Event\MailingQueryEvent $e) { + public function alterActionScheduleQuery(\Civi\ActionSchedule\Event\MailingQueryEvent $e): void { if ($e->mapping->getEntity() !== $this->getEntityTableName()) { return; } @@ -87,42 +88,6 @@ class CRM_Activity_Tokens extends AbstractTokenSubscriber { $e->query->select('e.id AS tokenContext_' . $this->getEntityContextSchema()); } - /** - * @inheritDoc - */ - public function prefetch(TokenValueEvent $e) { - // Find all the entity IDs - $entityIds = $e->getTokenProcessor()->getContextValues($this->getEntityContextSchema()); - - if (!$entityIds) { - return NULL; - } - - // Get data on all activities for basic and customfield tokens - $prefetch['activity'] = civicrm_api3('Activity', 'get', [ - 'id' => ['IN' => $entityIds], - 'options' => ['limit' => 0], - 'return' => self::getReturnFields($this->activeTokens), - ])['values']; - - // Store the activity types if needed - if (in_array('activity_type', $this->activeTokens, TRUE)) { - $this->activityTypes = \CRM_Core_OptionGroup::values('activity_type'); - } - - // Store the activity statuses if needed - if (in_array('status', $this->activeTokens, TRUE)) { - $this->activityStatuses = \CRM_Core_OptionGroup::values('activity_status'); - } - - // Store the campaigns if needed - if (in_array('campaign', $this->activeTokens, TRUE)) { - $this->campaigns = \CRM_Campaign_BAO_Campaign::getCampaigns(); - } - - return $prefetch; - } - /** * Evaluate the content of a single token. * @@ -138,45 +103,37 @@ class CRM_Activity_Tokens extends AbstractTokenSubscriber { * @throws \CRM_Core_Exception */ public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL) { - // maps token name to api field - $mapping = [ - 'activity_id' => 'id', - ]; - $activityId = $row->context[$this->getEntityContextSchema()]; - $activity = $prefetch['activity'][$activityId]; - - if (in_array($field, ['activity_date_time', 'created_date', 'modified_date'])) { - $row->tokens($entity, $field, \CRM_Utils_Date::customFormat($activity[$field])); - } - elseif (isset($mapping[$field]) and (isset($activity[$mapping[$field]]))) { - $row->tokens($entity, $field, $activity[$mapping[$field]]); - } - elseif (in_array($field, ['activity_type'])) { - $row->tokens($entity, $field, $this->activityTypes[$activity['activity_type_id']]); - } - elseif (in_array($field, ['status'])) { - $row->tokens($entity, $field, $this->activityStatuses[$activity['status_id']]); - } - elseif (in_array($field, ['campaign'])) { - $row->tokens($entity, $field, $this->campaigns[$activity['campaign_id']]); + if (!empty($this->getDeprecatedTokens()[$field])) { + $realField = $this->getDeprecatedTokens()[$field]; + parent::evaluateToken($row, $entity, $realField, $prefetch); + $row->format('text/plain')->tokens($entity, $field, $row->tokens['activity'][$realField]); } elseif (in_array($field, ['case_id'])) { // An activity can be linked to multiple cases so case_id is always an array. // We just return the first case ID for the token. - $row->tokens($entity, $field, is_array($activity['case_id']) ? reset($activity['case_id']) : $activity['case_id']); + // this weird hack might exist because apiv3 is weird & + $caseID = CRM_Core_DAO::singleValueQuery('SELECT case_id FROM civicrm_case_activity WHERE activity_id = %1 LIMIT 1', [1 => [$activityId, 'Integer']]); + $row->tokens($entity, $field, $caseID ?? ''); } - elseif (array_key_exists($field, $this->customFieldTokens)) { - $row->tokens($entity, $field, - isset($activity[$field]) - ? \CRM_Core_BAO_CustomField::displayValue($activity[$field], $field) - : '' - ); + else { + parent::evaluateToken($row, $entity, $field, $prefetch); } - elseif (isset($activity[$field])) { - $row->tokens($entity, $field, $activity[$field]); + } + + /** + * Get all the tokens supported by this processor. + * + * @return array|string[] + * @throws \API_Exception + */ + protected function getAllTokens(): array { + $tokens = parent::getAllTokens(); + if (array_key_exists('CiviCase', CRM_Core_Component::getEnabledComponents())) { + $tokens['case_id'] = ts('Activity Case ID'); } + return $tokens; } /** @@ -184,29 +141,84 @@ class CRM_Activity_Tokens extends AbstractTokenSubscriber { * * @return array token name => token label */ - protected function getBasicTokens(): array { + public function getBasicTokens(): array { if (!isset($this->basicTokens)) { $this->basicTokens = [ - 'activity_id' => ts('Activity ID'), - 'activity_type' => ts('Activity Type'), + 'id' => ts('Activity ID'), 'subject' => ts('Activity Subject'), 'details' => ts('Activity Details'), 'activity_date_time' => ts('Activity Date-Time'), 'created_date' => ts('Activity Created Date'), 'modified_date' => ts('Activity Modified Date'), 'activity_type_id' => ts('Activity Type ID'), - 'status' => ts('Activity Status'), 'status_id' => ts('Activity Status ID'), 'location' => ts('Activity Location'), 'duration' => ts('Activity Duration'), - 'campaign' => ts('Activity Campaign'), - 'campaign_id' => ts('Activity Campaign ID'), ]; - if (array_key_exists('CiviCase', CRM_Core_Component::getEnabledComponents())) { - $this->basicTokens['case_id'] = ts('Activity Case ID'); + if (CRM_Campaign_BAO_Campaign::isCampaignEnable()) { + $this->basicTokens['campaign_id'] = ts('Campaign ID'); } } return $this->basicTokens; } + /** + * @inheritDoc + */ + public function getActiveTokens(TokenValueEvent $e) { + $messageTokens = $e->getTokenProcessor()->getMessageTokens(); + if (!isset($messageTokens[$this->entity])) { + return NULL; + } + + $activeTokens = []; + // if message token contains '_\d+_', then treat as '_N_' + foreach ($messageTokens[$this->entity] as $msgToken) { + if (array_key_exists($msgToken, $this->tokenNames)) { + $activeTokens[] = $msgToken; + } + elseif (in_array($msgToken, ['campaign', 'activity_id', 'status', 'activity_type', 'case_id'])) { + $activeTokens[] = $msgToken; + } + else { + $altToken = preg_replace('/_\d+_/', '_N_', $msgToken); + if (array_key_exists($altToken, $this->tokenNames)) { + $activeTokens[] = $msgToken; + } + } + } + return array_unique($activeTokens); + } + + public function getPrefetchFields(TokenValueEvent $e): array { + $tokens = parent::getPrefetchFields($e); + $active = $this->getActiveTokens($e); + foreach ($this->getDeprecatedTokens() as $old => $new) { + if (in_array($old, $active, TRUE) && !in_array($new, $active, TRUE)) { + $tokens[] = $new; + } + } + return $tokens; + } + + /** + * These tokens still work but we don't advertise them. + * + * We will actively remove from the following places + * - scheduled reminders + * - add to 'blocked' on pdf letter & email + * + * & then at some point start issuing warnings for them. + * + * @return string[] + */ + protected function getDeprecatedTokens(): array { + return [ + 'activity_id' => 'id', + 'activity_type' => 'activity_type_id:label', + 'status' => 'status_id:label', + 'campaign' => 'campaign_id:label', + ]; + } + } diff --git a/CRM/Core/TokenTrait.php b/CRM/Core/TokenTrait.php index 356bdc7fd0..bb236232de 100644 --- a/CRM/Core/TokenTrait.php +++ b/CRM/Core/TokenTrait.php @@ -58,17 +58,15 @@ trait CRM_Core_TokenTrait { /** * Find the fields that we need to get to construct the tokens requested. - * @param array $activeTokens list of active tokens + * * @return array list of fields needed to generate those tokens */ - public function getReturnFields($activeTokens) { + public function getReturnFields(): array { // Make sure we always return something $fields = ['id']; - $tokensInUse = array_intersect( - $activeTokens, - array_merge(array_keys(self::getBasicTokens()), array_keys(self::getCustomFieldTokens())) - ); + $tokensInUse = + array_merge(array_keys(self::getBasicTokens()), array_keys(self::getCustomFieldTokens())); foreach ($tokensInUse as $token) { if (isset(self::$fieldMapping[$token])) { $fields = array_merge($fields, self::$fieldMapping[$token]); diff --git a/tests/phpunit/CRM/Activity/Form/Task/PDFLetterCommonTest.php b/tests/phpunit/CRM/Activity/Form/Task/PDFLetterCommonTest.php index 9244074268..6f45f35912 100644 --- a/tests/phpunit/CRM/Activity/Form/Task/PDFLetterCommonTest.php +++ b/tests/phpunit/CRM/Activity/Form/Task/PDFLetterCommonTest.php @@ -1,5 +1,7 @@ activityCreate(); + CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase'); + $this->enableCiviCampaign(); + + $activity = $this->activityCreate(['campaign_id' => $this->campaignCreate()]); $data = [ ['Subject: {activity.subject}', 'Subject: Discussion on warm beer'], ['Date: {activity.activity_date_time}', 'Date: ' . CRM_Utils_Date::customFormat(date('Ymd')) . ' 12:00 AM'], @@ -29,11 +34,18 @@ class CRM_Activity_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase { ['Location: {activity.location}', 'Location: Baker Street'], ['Details: {activity.details}', 'Details: Lets schedule a meeting'], ['Status ID: {activity.status_id}', 'Status ID: 1'], - ['Status: {activity.status}', 'Status: Scheduled'], + ['(legacy) Status: {activity.status}', '(legacy) Status: Scheduled'], + ['Status: {activity.status_id:label}', 'Status: Scheduled'], ['Activity Type ID: {activity.activity_type_id}', 'Activity Type ID: 1'], - ['Activity Type: {activity.activity_type}', 'Activity Type: Meeting'], - ['Activity ID: {activity.activity_id}', 'Activity ID: ' . $activity['id']], + ['(legacy) Activity Type: {activity.activity_type}', '(legacy) Activity Type: Meeting'], + ['Activity Type: {activity.activity_type_id:label}', 'Activity Type: Meeting'], + ['(legacy) Activity ID: {activity.activity_id}', '(legacy) Activity ID: ' . $activity['id']], + ['Activity ID: {activity.id}', 'Activity ID: ' . $activity['id']], + ['(just weird) Case ID: {activity.case_id}', '(just weird) Case ID: ' . ''], ]; + $tokenProcessor = new TokenProcessor(Civi::dispatcher(), ['schema' => ['activityId']]); + + $this->assertEquals($this->getActivityTokens(), $tokenProcessor->listTokens()); $html_message = "\n" . implode("\n", CRM_Utils_Array::collect('0', $data)) . "\n"; $form = $this->getFormObject('CRM_Activity_Form_Task_PDF'); $output = $form->createDocument([$activity['id']], $html_message, ['is_unit_test' => TRUE]); @@ -44,6 +56,34 @@ class CRM_Activity_Form_Task_PDFLetterCommonTest extends CiviUnitTestCase { } } + /** + * Get expected activity Tokens. + * + * @return string[] + */ + protected function getActivityTokens(): array { + return [ + '{activity.id}' => 'Activity ID', + '{activity.subject}' => 'Activity Subject', + '{activity.details}' => 'Activity Details', + '{activity.activity_date_time}' => 'Activity Date-Time', + '{activity.created_date}' => 'Activity Created Date', + '{activity.modified_date}' => 'Activity Modified Date', + '{activity.activity_type_id}' => 'Activity Type ID', + '{activity.status_id}' => 'Activity Status ID', + '{activity.location}' => 'Activity Location', + '{activity.duration}' => 'Activity Duration', + '{activity.activity_type_id:label}' => 'Activity Type', + '{activity.activity_type_id:name}' => 'Machine name: Activity Type', + '{activity.status_id:label}' => 'Activity Status', + '{activity.status_id:name}' => 'Machine name: Activity Status', + '{activity.campaign_id:label}' => 'Campaign', + '{activity.campaign_id:name}' => 'Machine name: Campaign', + '{activity.campaign_id}' => 'Campaign ID', + '{activity.case_id}' => 'Activity Case ID', + ]; + } + public function testCreateDocumentCustomFieldTokens() { // Set up custom group, and field // returns custom_group_id, custom_field_id, custom_field_option_group_id, custom_field_group_options -- 2.25.1