Skip to content

Commit

Permalink
Fix activity tokens
Browse files Browse the repository at this point in the history
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}
```
  • Loading branch information
eileenmcnaughton committed Sep 15, 2021
1 parent 979232c commit d491ed0
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 88 deletions.
169 changes: 91 additions & 78 deletions CRM/Activity/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@
*
* 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';
}

/**
Expand Down Expand Up @@ -75,7 +77,7 @@ private function getEntityContextSchema(): string {
/**
* @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;
}
Expand All @@ -87,42 +89,6 @@ public function alterActionScheduleQuery(\Civi\ActionSchedule\Event\MailingQuery
$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.
*
Expand All @@ -138,75 +104,122 @@ public function prefetch(TokenValueEvent $e) {
* @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;
}

/**
* Get the basic tokens provided.
*
* @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',
];
}

}
10 changes: 4 additions & 6 deletions CRM/Core/TokenTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,15 @@ public function getActiveTokens(TokenValueEvent $e) {

/**
* 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]);
Expand Down
48 changes: 44 additions & 4 deletions tests/phpunit/CRM/Activity/Form/Task/PDFLetterCommonTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Civi\Token\TokenProcessor;

/**
* Class CRM_Activity_Form_Task_PDFLetterCommonTest
* @group headless
Expand All @@ -21,19 +23,29 @@ public function setUp(): void {
* @throws \CiviCRM_API3_Exception
*/
public function testCreateDocumentBasicTokens(): void {
$activity = $this->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'],
['Duration: {activity.duration}', 'Duration: 90'],
['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]);
Expand All @@ -44,6 +56,34 @@ public function testCreateDocumentBasicTokens(): void {
}
}

/**
* 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
Expand Down

0 comments on commit d491ed0

Please sign in to comment.