Skip to content

Commit

Permalink
Split mechanism to derive token metadata from display
Browse files Browse the repository at this point in the history
  • Loading branch information
eileenmcnaughton committed Oct 5, 2021
1 parent 2df7d0c commit 889b061
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 82 deletions.
41 changes: 40 additions & 1 deletion CRM/Activity/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ class CRM_Activity_Tokens extends CRM_Core_EntityTokens {

use CRM_Core_TokenTrait;

/**
* Class constructor.
*
* Overriding because the trait needs this to happen & trying to
* leave any changes that affect the trait out of scope here.
*/
public function __construct() {
$this->entity = 'activity';
$this->tokenNames = array_merge(
$this->getBasicTokens(),
$this->getCustomFieldTokens());
}

/**
* Get the entity name for api v4 calls.
*
Expand Down Expand Up @@ -171,6 +184,32 @@ protected function getBespokeTokens(): array {
return $tokens;
}

/**
* Get fields Fieldshistorically not advertised for tokens.
*
* @return string[]
*/
protected function getSkippedFields(): array {
return array_merge(parent::getSkippedFields(), [
'source_record_id',
'phone_id',
'phone_number',
'priority_id',
'parent_id',
'is_test',
'medium_id',
'is_auto',
'relationship_id',
'is_current_revision',
'original_id',
'result',
'is_deleted',
'engagement_level',
'weight',
'is_star',
]);
}

/**
* @inheritDoc
*/
Expand All @@ -183,7 +222,7 @@ public function getActiveTokens(TokenValueEvent $e) {
$activeTokens = [];
// if message token contains '_\d+_', then treat as '_N_'
foreach ($messageTokens[$this->entity] as $msgToken) {
if (array_key_exists($msgToken, $this->tokenNames)) {
if (array_key_exists($msgToken, $this->tokensMetadata)) {
$activeTokens[] = $msgToken;
}
elseif (in_array($msgToken, ['campaign', 'activity_id', 'status', 'activity_type', 'case_id'])) {
Expand Down
153 changes: 119 additions & 34 deletions CRM/Core/EntityTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,24 @@
*/
class CRM_Core_EntityTokens extends AbstractTokenSubscriber {

/**
* Metadata about all tokens.
*
* @var array
*/
protected $tokensMetadata = [];
/**
* @var array
*/
protected $prefetch = [];

/**
* Should permissions be checked when loading tokens.
*
* @var bool
*/
protected $checkPermissions = FALSE;

/**
* Register the declared tokens.
*
Expand All @@ -44,17 +57,88 @@ public function registerTokens(TokenRegisterEvent $e) {
if (!$this->checkActive($e->getTokenProcessor())) {
return;
}
foreach ($this->getAllTokens() as $name => $label) {
if (!in_array($name, $this->getHiddenTokens(), TRUE)) {
foreach ($this->getTokenMetadata() as $field) {
if ($field['audience'] === 'user') {
$e->register([
'entity' => $this->entity,
'field' => $name,
'label' => $label,
'field' => $field['name'],
'label' => $field['title'],
]);
}
}
}

/**
* Get the metadata about the available tokens
*
* @return array
*/
protected function getTokenMetadata(): array {
if (empty($this->tokensMetadata)) {
$cacheKey = __CLASS__ . 'token_metadata' . $this->getApiEntityName() . CRM_Core_Config::domainID() . '_' . CRM_Core_I18n::getLocale();
if ($this->checkPermissions) {
$cacheKey .= '__' . CRM_Core_Session::getLoggedInContactID();
}
if (Civi::cache('metadata')->has($cacheKey)) {
$this->tokensMetadata = Civi::cache('metadata')->get($cacheKey);
}
else {
foreach (array_merge($this->getFieldMetadata(), $this->getBespokeTokens()) as $field) {
if (
$field['type'] === 'Custom'
|| !empty($this->getBespokeTokens()[$field['name']])
|| in_array($field['name'], $this->getExposedFields(), TRUE)
) {
$field['audience'] = 'user';
if ($field['name'] === 'contact_id') {
// Since {contact.id} is almost always present don't confuse users
// by also adding (e.g {participant.contact_id)
$field['audience'] = 'sysadmin';
}
if (!empty($this->getTokenMetadataOverrides()[$field['name']])) {
$field = array_merge($field, $this->getTokenMetadataOverrides()[$field['name']]);
}
if ($field['type'] === 'Custom') {
// Convert to apiv3 style for now. Later we can add v4 with
// portable naming & support for labels/ dates etc so let's leave
// the space open for that.
// Not the existing quickform widget has handling for the custom field
// format based on the title using this syntax.
$field['name'] = 'custom_' . $field['custom_field_id'];
$parts = explode(': ', $field['label']);
$field['title'] = "{$parts[1]} :: {$parts[0]}";
}
if (
($field['options'] || !empty($field['suffixes']))
// At the time of writing currency didn't have a label option - this may have changed.
&& !in_array($field['name'], $this->getCurrencyFieldName(), TRUE)
) {
$this->tokensMetadata[$field['name'] . ':label'] = $this->tokensMetadata[$field['name'] . ':name'] = $field;
$fieldLabel = $field['input_attrs']['label'] ?? $field['label'];
$this->tokensMetadata[$field['name'] . ':label']['name'] = $field['name'] . ':label';
$this->tokensMetadata[$field['name'] . ':name']['name'] = $field['name'] . ':name';
$this->tokensMetadata[$field['name'] . ':name']['audience'] = 'sysadmin';
$this->tokensMetadata[$field['name'] . ':label']['title'] = $fieldLabel;
$this->tokensMetadata[$field['name'] . ':name']['title'] = ts('Machine name') . ': ' . $fieldLabel;
$field['audience'] = 'sysadmin';
}
if ($field['data_type'] === 'Boolean') {
$this->tokensMetadata[$field['name'] . ':label'] = $field;
$this->tokensMetadata[$field['name'] . ':label']['name'] = $field['name'] . ':label';
$field['audience'] = 'sysadmin';
}
$this->tokensMetadata[$field['name']] = $field;
}
}
foreach ($this->getHiddenTokens() as $name) {
$this->tokensMetadata[$name]['audience'] = 'hidden';
}
Civi::cache('metadata')->set($cacheKey, $this->tokensMetadata);
}
}
return $this->tokensMetadata;
}

/**
* @inheritDoc
* @throws \CRM_Core_Exception
Expand Down Expand Up @@ -155,31 +239,6 @@ public function getReturnFields(): array {
return array_keys($this->getBasicTokens());
}

/**
* Get all the tokens supported by this processor.
*
* @return array|string[]
* @throws \API_Exception
*/
protected function getAllTokens(): array {
$basicTokens = $this->getBasicTokens();
foreach (array_keys($basicTokens) as $fieldName) {
// The goal is to be able to render more complete tokens
// (eg. actual booleans, field names, raw ids) for a more
// advanced audiences - ie those using conditionals
// and to specify that audience in the api that retrieves.
// But, for now, let's not advertise, given that most of these fields
// aren't really needed even once...
if ($this->isBooleanField($fieldName)) {
unset($basicTokens[$fieldName]);
}
}
foreach ($this->getBespokeTokens() as $token) {
$basicTokens[$token['name']] = $token['title'];
}
return array_merge($basicTokens, $this->getPseudoTokens(), CRM_Utils_Token::getCustomFieldTokens($this->getApiEntityName()));
}

/**
* Is the given field a boolean field.
*
Expand Down Expand Up @@ -360,8 +419,7 @@ protected function getFieldValue(TokenRow $row, string $field) {
* Class constructor.
*/
public function __construct() {
$tokens = $this->getAllTokens();
parent::__construct($this->getEntityName(), $tokens);
parent::__construct($this->getEntityName(), []);
}

/**
Expand Down Expand Up @@ -449,11 +507,11 @@ protected function getExposedFields(): array {
*
* @return string[]
*/
public function getSkippedFields(): array {
protected function getSkippedFields(): array {
// tags is offered in 'case' & is one of the only fields that is
// 'not a real field' offered up by case - seems like an oddity
// we should skip at the top level for now.
$fields = ['contact_id', 'tags'];
$fields = ['tags'];
if (!CRM_Campaign_BAO_Campaign::isCampaignEnable()) {
$fields[] = 'campaign_id';
}
Expand Down Expand Up @@ -513,7 +571,7 @@ public function getCurrency($row): string {
* @throws \API_Exception
*/
public function getPrefetchFields(TokenValueEvent $e): array {
$allTokens = array_keys($this->getAllTokens());
$allTokens = array_keys($this->getTokenMetadata());
$requiredFields = array_intersect($this->getActiveTokens($e), $allTokens);
if (empty($requiredFields)) {
return [];
Expand Down Expand Up @@ -569,4 +627,31 @@ protected function getCustomFieldValue($entityID, string $field) {
}
}

/**
* Get any overrides for token metadata.
*
* This is most obviously used for setting the audience, which
* will affect widget-presence.
*
* @return \string[][]
*/
protected function getTokenMetadataOverrides(): array {
return [];
}

/**
* To handle variable tokens, override this function and return the active tokens.
*
* @param \Civi\Token\Event\TokenValueEvent $e
*
* @return mixed
*/
public function getActiveTokens(TokenValueEvent $e) {
$messageTokens = $e->getTokenProcessor()->getMessageTokens();
if (!isset($messageTokens[$this->entity])) {
return FALSE;
}
return array_intersect($messageTokens[$this->entity], array_keys($this->getTokenMetadata()));
}

}
2 changes: 1 addition & 1 deletion CRM/Event/ParticipantTokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ protected function getHiddenTokens(): array {
*
* @return string[]
*/
public function getSkippedFields(): array {
protected function getSkippedFields(): array {
$fields = parent::getSkippedFields();
// Never add these 2 fields - may not be a stable part of the schema.
// This field is on it's way out of core.
Expand Down
7 changes: 5 additions & 2 deletions CRM/Event/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ public function evaluateToken(TokenRow $row, $entity, $field, $prefetch = NULL)
*/
protected function getEventTokenValues(int $eventID = NULL): array {
$cacheKey = __CLASS__ . 'event_tokens' . $eventID . '_' . CRM_Core_I18n::getLocale();
if ($this->checkPermissions) {
$cacheKey .= '__' . CRM_Core_Session::getLoggedInContactID();
}
if (!Civi::cache('metadata')->has($cacheKey)) {
$event = Event::get(FALSE)->addWhere('id', '=', $eventID)
$event = Event::get($this->checkPermissions)->addWhere('id', '=', $eventID)
->setSelect(array_merge([
'loc_block_id.address_id.street_address',
'loc_block_id.address_id.city',
Expand All @@ -140,7 +143,7 @@ protected function getEventTokenValues(int $eventID = NULL): array {
$tokens['contact_phone']['text/html'] = $event['loc_block_id.phone_id.phone'];
$tokens['contact_email']['text/html'] = $event['loc_block_id.email_id.email'];

foreach (array_keys($this->getAllTokens()) as $field) {
foreach (array_keys($this->getTokenMetadata()) as $field) {
if (!isset($tokens[$field])) {
if ($this->isCustomField($field)) {
$this->prefetch[$eventID] = $event;
Expand Down
26 changes: 26 additions & 0 deletions CRM/Member/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ protected function getExposedFields(): array {
'end_date',
'status_id',
'membership_type_id',
'source',
'status_override_end_date',
];
}

Expand All @@ -67,6 +69,30 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe
}
}

/**
* Get any overrides for token metadata.
*
* This is most obviously used for setting the audience, which
* will affect widget-presence.
*
* Changing the audience is done in order to simplify the
* UI for more general users.
*
* @return \string[][]
*/
protected function getTokenMetadataOverrides(): array {
return [
'owner_membership_id' => ['audience' => 'sysadmin'],
'max_related' => ['audience' => 'sysadmin'],
'contribution_recur_id' => ['audience' => 'sysadmin'],
'is_override' => ['audience' => 'sysadmin'],
'is_test' => ['audience' => 'sysadmin'],
// Pay later is considered to be unreliable in the schema
// and will eventually be removed.
'is_pay_later' => ['audience' => 'deprecated'],
];
}

/**
* Get fields which need to be returned to render another token.
*
Expand Down
20 changes: 7 additions & 13 deletions tests/phpunit/CRM/Activity/Form/Task/PDFLetterCommonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,16 @@ public function testCreateDocumentBasicTokens(): void {
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.subject}' => 'Subject',
'{activity.details}' => 'Details',
'{activity.activity_date_time}' => 'Activity Date',
'{activity.created_date}' => 'Created Date',
'{activity.modified_date}' => 'Modified Date',
'{activity.location}' => 'Location',
'{activity.duration}' => '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',
];
}
Expand Down
Loading

0 comments on commit 889b061

Please sign in to comment.