Skip to content
Snippets Groups Projects
Commit 2478a7f2 authored by Eileen McNaughton's avatar Eileen McNaughton
Browse files

#2814 Add support for preferred syntax for contact tokens

This maintains support for all currently advertised tokens. But it
switches the advertisement to our preferred style (matching the db field,
specifically denoting if the label is desired.) Tests for both styles are added.

This is a step towards switching to apiv4. An important difference with v4 is
that we can distinquish between 'not fetched' and 'empty' - saving us
from extra queries when it is not clear - which will in turn
allow us to switch greeting
processing over & still support the performance tweaks via
that method
parent 3ed153be
Branches
Tags
No related merge requests found
......@@ -158,28 +158,28 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
'display_name' => 'Display Name',
'nick_name' => 'Nickname',
'image_URL' => 'Image Url',
'preferred_communication_method' => 'Preferred Communication Method',
'preferred_language' => 'Preferred Language',
'preferred_mail_format' => 'Preferred Mail Format',
'preferred_communication_method:label' => 'Preferred Communication Method',
'preferred_language:label' => 'Preferred Language',
'preferred_mail_format:label' => 'Preferred Mail Format',
'hash' => 'Contact Hash',
'contact_source' => 'Contact Source',
'source' => 'Contact Source',
'first_name' => 'First Name',
'middle_name' => 'Middle Name',
'last_name' => 'Last Name',
'individual_prefix' => 'Individual Prefix',
'individual_suffix' => 'Individual Suffix',
'prefix_id:label' => 'Individual Prefix',
'suffix_id:label' => 'Individual Suffix',
'formal_title' => 'Formal Title',
'communication_style' => 'Communication Style',
'communication_style_id:label' => 'Communication Style',
'job_title' => 'Job Title',
'gender' => 'Gender ID',
'gender_id:label' => 'Gender ID',
'birth_date' => 'Birth Date',
'current_employer_id' => 'Current Employer ID',
'contact_is_deleted' => 'Contact is in Trash',
'is_deleted:label' => 'Contact is in Trash',
'created_date' => 'Created Date',
'modified_date' => 'Modified Date',
'addressee' => 'Addressee',
'email_greeting' => 'Email Greeting',
'postal_greeting' => 'Postal Greeting',
'addressee_display' => 'Addressee',
'email_greeting_display' => 'Email Greeting',
'postal_greeting_display' => 'Postal Greeting',
'current_employer' => 'Current Employer',
'location_type' => 'Location Type',
'address_id' => 'Address ID',
......@@ -216,7 +216,7 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
'world_region' => 'World Region',
'url' => 'Website',
'checksum' => 'Checksum',
'contact_id' => 'Internal Contact ID',
'id' => 'Internal Contact ID',
];
}
......@@ -348,12 +348,41 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
}
else {
$row->format('text/html')
->tokens('contact', $token, $row->context['contact'][$token] ?? '');
->tokens('contact', $token, $this->getFieldValue($row, $token));
}
}
}
}
/**
* Get the field value.
*
* @param \Civi\Token\TokenRow $row
* @param string $field
* @return string|int
*/
protected function getFieldValue(TokenRow $row, string $field) {
$entityName = 'contact';
if (isset($this->getDeprecatedTokens()[$field])) {
// Check the non-deprecated location first, fall back to deprecated
// this is important for the greetings because - they are weird in the query object.
$possibilities = [$this->getDeprecatedTokens()[$field], $field];
}
else {
$possibilities = [$field];
if (in_array($field, $this->getDeprecatedTokens(), TRUE)) {
$possibilities[] = array_search($field, $this->getDeprecatedTokens(), TRUE);
}
}
foreach ($possibilities as $possibility) {
if (isset($row->context[$entityName][$possibility])) {
return $row->context[$entityName][$possibility];
}
}
return '';
}
/**
* Is the given field a boolean field.
*
......@@ -458,18 +487,15 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
*/
protected function getContact(int $contactId, array $requiredFields, bool $getAll = FALSE): array {
$returnProperties = array_fill_keys($requiredFields, 1);
$mappedFields = [
'email_greeting' => 'email_greeting_display',
'postal_greeting' => 'postal_greeting_display',
'addressee' => 'addressee_display',
];
$mappedFields = array_flip($this->getDeprecatedTokens());
if (!empty($returnProperties['checksum'])) {
$returnProperties['hash'] = 1;
}
foreach ($mappedFields as $tokenName => $realName) {
foreach ($mappedFields as $tokenName => $api3Name) {
if (in_array($tokenName, $requiredFields, TRUE)) {
$returnProperties[$realName] = 1;
$returnProperties[$api3Name] = 1;
}
}
if ($getAll) {
......@@ -483,8 +509,14 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
[$contact] = \CRM_Contact_BAO_Query::apiQuery($params, $returnProperties ?? NULL);
//CRM-4524
$contact = reset($contact);
foreach ($mappedFields as $tokenName => $realName) {
$contact[$tokenName] = $contact[$realName] ?? '';
foreach ($mappedFields as $tokenName => $apiv3Name) {
// it would be set already with the right value for a greeting token
// the query object returns the db value for email_greeting_display
// and a numeric value for email_greeting if you put email_greeting
// in the return properties.
if (!isset($contact[$tokenName])) {
$contact[$tokenName] = $contact[$apiv3Name] ?? '';
}
}
//update value of custom field token
......@@ -562,4 +594,34 @@ class TokenCompatSubscriber implements EventSubscriberInterface {
];
}
/**
* These tokens still work but we don't advertise them.
*
* We can remove from the following places
* - scheduled reminders
* - add to 'blocked' on pdf letter & email
*
* & then at some point start issuing warnings for them
* but contact tokens are pretty central so it might be
* a bit drawn out.
*
* @return string[]
* Keys are deprecated tokens and values are their replacements.
*/
protected function getDeprecatedTokens(): array {
return [
'individual_prefix' => 'prefix_id:label',
'individual_suffix' => 'suffix_id:label',
'gender' => 'gender_id:label',
'communication_style' => 'communication_style_id:label',
'preferred_communication_method' => 'preferred_communication_method:label',
'email_greeting' => 'email_greeting_display',
'postal_greeting' => 'postal_greeting_display',
'addressee' => 'addressee_display',
'contact_id' => 'id',
'contact_source' => 'source',
'contact_is_deleted' => 'is_deleted:label',
];
}
}
......@@ -2,6 +2,7 @@
use Civi\Api4\Address;
use Civi\Api4\Contact;
use Civi\Api4\MessageTemplate;
use Civi\Token\TokenProcessor;
/**
......@@ -110,7 +111,7 @@ class CRM_Core_BAO_MessageTemplateTest extends CiviUnitTestCase {
CRM_Core_Transaction::create(TRUE)->run(function(CRM_Core_Transaction $tx) {
$tx->rollback();
\Civi\Api4\MessageTemplate::update()
MessageTemplate::update()
->addWhere('workflow_name', '=', 'case_activity')
->addWhere('is_reserved', '=', 0)
->setValues([
......@@ -147,7 +148,7 @@ class CRM_Core_BAO_MessageTemplateTest extends CiviUnitTestCase {
CRM_Core_Transaction::create(TRUE)->run(function(CRM_Core_Transaction $tx) {
$tx->rollback();
\Civi\Api4\MessageTemplate::update()
MessageTemplate::update()
->addWhere('workflow_name', '=', 'case_activity')
->addWhere('is_reserved', '=', 0)
->setValues([
......@@ -327,12 +328,6 @@ London, 90210
$advertisedTokens = CRM_Core_SelectValues::contactTokens();
$this->assertEquals($this->getAdvertisedTokens(), $advertisedTokens);
// Compare with our token data.
unset($advertisedTokens['{important_stuff.favourite_emoticon}']);
foreach (array_keys($advertisedTokens) as $token) {
$this->assertArrayKeyExists(substr($token, 9, -1), $tokenData);
}
CRM_Core_Smarty::singleton()->assign('pre_assigned_smarty', 'wee');
// This string contains the 4 types of possible replaces just to be sure they
// work in combination.
......@@ -386,6 +381,36 @@ emo
CRM_Utils_Time::resetTime();
}
/**
* Test that old contact tokens still work, as we add new-style support.
*/
public function testLegacyTokens(): void {
$contactID = $this->individualCreate(['gender_id' => 'Female', 'communication_style' => 1, 'preferred_communication_method' => 'Phone']);
$mappings = [
['old' => '{contact.individual_prefix}', 'new' => '{contact.prefix_id:label}', 'output' => 'Mr.'],
['old' => '{contact.individual_suffix}', 'new' => '{contact.suffix_id:label}', 'output' => 'II'],
['old' => '{contact.gender}', 'new' => '{contact.gender_id:label}', 'output' => 'Female'],
['old' => '{contact.communication_style}', 'new' => '{contact.communication_style_id:label}', 'output' => 'Formal'],
['old' => '{contact.preferred_communication_method}', 'new' => '{contact.preferred_communication_method:label}', 'output' => 'Phone'],
['old' => '{contact.contact_id}', 'new' => '{contact.id}', 'output' => $contactID],
['old' => '{contact.email_greeting}', 'new' => '{contact.email_greeting_display}', 'output' => 'Dear Anthony'],
['old' => '{contact.postal_greeting}', 'new' => '{contact.postal_greeting_display}', 'output' => 'Dear Anthony'],
['old' => '{contact.addressee}', 'new' => '{contact.addressee_display}', 'output' => 'Mr. Anthony J. Anderson II'],
];
foreach ($mappings as $mapping) {
foreach (['old', 'new'] as $type) {
$messageContent = CRM_Core_BAO_MessageTemplate::renderTemplate([
'contactId' => $contactID,
'messageTemplate' => [
'msg_text' => $mapping[$type],
],
])['text'];
$this->assertEquals($mapping['output'], $messageContent, 'could not resolve ' . $mapping[$type]);
}
}
}
/**
* Implement token values hook.
*
......@@ -485,28 +510,28 @@ emo
'{contact.display_name}' => 'Display Name',
'{contact.nick_name}' => 'Nickname',
'{contact.image_URL}' => 'Image Url',
'{contact.preferred_communication_method}' => 'Preferred Communication Method',
'{contact.preferred_language}' => 'Preferred Language',
'{contact.preferred_mail_format}' => 'Preferred Mail Format',
'{contact.preferred_communication_method:label}' => 'Preferred Communication Method',
'{contact.preferred_language:label}' => 'Preferred Language',
'{contact.preferred_mail_format:label}' => 'Preferred Mail Format',
'{contact.hash}' => 'Contact Hash',
'{contact.contact_source}' => 'Contact Source',
'{contact.source}' => 'Contact Source',
'{contact.first_name}' => 'First Name',
'{contact.middle_name}' => 'Middle Name',
'{contact.last_name}' => 'Last Name',
'{contact.individual_prefix}' => 'Individual Prefix',
'{contact.individual_suffix}' => 'Individual Suffix',
'{contact.prefix_id:label}' => 'Individual Prefix',
'{contact.suffix_id:label}' => 'Individual Suffix',
'{contact.formal_title}' => 'Formal Title',
'{contact.communication_style}' => 'Communication Style',
'{contact.communication_style_id:label}' => 'Communication Style',
'{contact.job_title}' => 'Job Title',
'{contact.gender}' => 'Gender ID',
'{contact.gender_id:label}' => 'Gender ID',
'{contact.birth_date}' => 'Birth Date',
'{contact.current_employer_id}' => 'Current Employer ID',
'{contact.contact_is_deleted}' => 'Contact is in Trash',
'{contact.is_deleted:label}' => 'Contact is in Trash',
'{contact.created_date}' => 'Created Date',
'{contact.modified_date}' => 'Modified Date',
'{contact.addressee}' => 'Addressee',
'{contact.email_greeting}' => 'Email Greeting',
'{contact.postal_greeting}' => 'Postal Greeting',
'{contact.addressee_display}' => 'Addressee',
'{contact.email_greeting_display}' => 'Email Greeting',
'{contact.postal_greeting_display}' => 'Postal Greeting',
'{contact.current_employer}' => 'Current Employer',
'{contact.location_type}' => 'Location Type',
'{contact.address_id}' => 'Address ID',
......@@ -556,7 +581,7 @@ emo
'{contact.custom_12}' => 'Yes No :: Custom Group',
'{contact.custom_3}' => 'Test Date :: Custom Group',
'{contact.checksum}' => 'Checksum',
'{contact.contact_id}' => 'Internal Contact ID',
'{contact.id}' => 'Internal Contact ID',
'{important_stuff.favourite_emoticon}' => 'Best coolest emoticon',
];
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment