diff --git a/CRM/Activity/Tokens.php b/CRM/Activity/Tokens.php index ed487e2f114e..7a0ba27d4096 100644 --- a/CRM/Activity/Tokens.php +++ b/CRM/Activity/Tokens.php @@ -49,12 +49,15 @@ class CRM_Activity_Tokens extends \Civi\Token\AbstractTokenSubscriber { * CRM_Activity_Tokens constructor. */ public function __construct() { - parent::__construct('activity', array( - 'activity_id' => ts('Activity ID'), - 'activity_type' => ts('Activity Type'), - 'subject' => ts('Activity Subject'), - 'details' => ts('Activity Details'), - 'activity_date_time' => ts('Activity Date-Time'), + parent::__construct('activity', array_merge( + array( + 'activity_id' => ts('Activity ID'), + 'activity_type' => ts('Activity Type'), + 'subject' => ts('Activity Subject'), + 'details' => ts('Activity Details'), + 'activity_date_time' => ts('Activity Date-Time'), + ), + $this->getCustomTokens('Activity') )); } @@ -107,6 +110,9 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe elseif (isset($actionSearchResult->$field)) { $row->tokens($entity, $field, $actionSearchResult->$field); } + elseif ($cfID = \CRM_Core_BAO_CustomField::getKeyID($field)) { + $row->customToken($entity, $cfID, $actionSearchResult->entity_id); + } else { $row->tokens($entity, $field, ''); } diff --git a/CRM/Contribute/Tokens.php b/CRM/Contribute/Tokens.php index c7fe1fdbda9a..40a5c832a9be 100644 --- a/CRM/Contribute/Tokens.php +++ b/CRM/Contribute/Tokens.php @@ -85,6 +85,7 @@ public function __construct() { $tokens['source'] = ts('Contribution Source'); $tokens['status'] = ts('Contribution Status'); $tokens['type'] = ts('Financial Type'); + $tokens = array_merge($tokens, $this->getCustomTokens('Contribution')); parent::__construct('contribution', $tokens); } @@ -135,6 +136,9 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe elseif (isset($aliasTokens[$field])) { $row->dbToken($entity, $field, 'CRM_Contribute_BAO_Contribution', $aliasTokens[$field], $fieldValue); } + elseif ($cfID = \CRM_Core_BAO_CustomField::getKeyID($field)) { + $row->customToken($entity, $cfID, $actionSearchResult->entity_id); + } else { $row->dbToken($entity, $field, 'CRM_Contribute_BAO_Contribution', $field, $fieldValue); } diff --git a/CRM/Event/Tokens.php b/CRM/Event/Tokens.php index 2a5025544058..e947ade45f79 100644 --- a/CRM/Event/Tokens.php +++ b/CRM/Event/Tokens.php @@ -44,21 +44,24 @@ class CRM_Event_Tokens extends \Civi\Token\AbstractTokenSubscriber { * Class constructor. */ public function __construct() { - parent::__construct('event', array( - 'event_type' => ts('Event Type'), - 'title' => ts('Event Title'), - 'event_id' => ts('Event ID'), - 'start_date' => ts('Event Start Date'), - 'end_date' => ts('Event End Date'), - 'summary' => ts('Event Summary'), - 'description' => ts('Event Description'), - 'location' => ts('Event Location'), - 'info_url' => ts('Event Info URL'), - 'registration_url' => ts('Event Registration URL'), - 'fee_amount' => ts('Event Fee'), - 'contact_email' => ts('Event Contact (Email)'), - 'contact_phone' => ts('Event Contact (Phone)'), - 'balance' => ts('Event Balance'), + parent::__construct('event', array_merge( + array( + 'event_type' => ts('Event Type'), + 'title' => ts('Event Title'), + 'event_id' => ts('Event ID'), + 'start_date' => ts('Event Start Date'), + 'end_date' => ts('Event End Date'), + 'summary' => ts('Event Summary'), + 'description' => ts('Event Description'), + 'location' => ts('Event Location'), + 'info_url' => ts('Event Info URL'), + 'registration_url' => ts('Event Registration URL'), + 'fee_amount' => ts('Event Fee'), + 'contact_email' => ts('Event Contact (Email)'), + 'contact_phone' => ts('Event Contact (Phone)'), + 'balance' => ts('Event Balance'), + ), + $this->getCustomTokens('Event') )); } @@ -139,6 +142,9 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe elseif (isset($actionSearchResult->$field)) { $row->tokens($entity, $field, $actionSearchResult->$field); } + elseif ($cfID = \CRM_Core_BAO_CustomField::getKeyID($field)) { + $row->customToken($entity, $cfID, $actionSearchResult->entity_id); + } else { $row->tokens($entity, $field, ''); } diff --git a/CRM/Member/Tokens.php b/CRM/Member/Tokens.php index 983ae100357f..7fe6496a292d 100644 --- a/CRM/Member/Tokens.php +++ b/CRM/Member/Tokens.php @@ -44,14 +44,17 @@ class CRM_Member_Tokens extends \Civi\Token\AbstractTokenSubscriber { * Class constructor. */ public function __construct() { - parent::__construct('membership', array( - 'fee' => ts('Membership Fee'), - 'id' => ts('Membership ID'), - 'join_date' => ts('Membership Join Date'), - 'start_date' => ts('Membership Start Date'), - 'end_date' => ts('Membership End Date'), - 'status' => ts('Membership Status'), - 'type' => ts('Membership Type'), + parent::__construct('membership', array_merge( + array( + 'fee' => ts('Membership Fee'), + 'id' => ts('Membership ID'), + 'join_date' => ts('Membership Join Date'), + 'start_date' => ts('Membership Start Date'), + 'end_date' => ts('Membership End Date'), + 'status' => ts('Membership Status'), + 'type' => ts('Membership Type'), + ), + $this->getCustomTokens('Membership') )); } @@ -95,6 +98,9 @@ public function evaluateToken(\Civi\Token\TokenRow $row, $entity, $field, $prefe elseif (isset($actionSearchResult->$field)) { $row->tokens($entity, $field, $actionSearchResult->$field); } + elseif ($cfID = \CRM_Core_BAO_CustomField::getKeyID($field)) { + $row->customToken($entity, $cfID, $actionSearchResult->entity_id); + } else { $row->tokens($entity, $field, ''); } diff --git a/Civi/Token/AbstractTokenSubscriber.php b/Civi/Token/AbstractTokenSubscriber.php index 81cfd91eb473..1164032de84c 100644 --- a/Civi/Token/AbstractTokenSubscriber.php +++ b/Civi/Token/AbstractTokenSubscriber.php @@ -117,6 +117,22 @@ public function registerTokens(TokenRegisterEvent $e) { } } + /** + * Get all custom field tokens of $entity + * + * @param string $entity + * @return array $customTokens + * return custom field tokens in array('custom_N' => 'label') format + */ + public function getCustomTokens($entity) { + $customTokens = array(); + foreach (\CRM_Core_BAO_CustomField::getFields($entity) as $id => $info) { + $customTokens["custom_$id"] = $info['label']; + } + + return $customTokens; + } + /** * Alter the query which prepopulates mailing data * for scheduled reminders. @@ -148,14 +164,11 @@ public function evaluateTokens(TokenValueEvent $e) { } $activeTokens = array_intersect($messageTokens[$this->entity], array_keys($this->tokenNames)); - if (empty($activeTokens)) { - return; - } $prefetch = $this->prefetch($e); foreach ($e->getRows() as $row) { - foreach ($activeTokens as $field) { + foreach ((array) $activeTokens as $field) { $this->evaluateToken($row, $this->entity, $field, $prefetch); } } diff --git a/Civi/Token/TokenCompatSubscriber.php b/Civi/Token/TokenCompatSubscriber.php index e6c2f72e65f2..ccd70813bf00 100644 --- a/Civi/Token/TokenCompatSubscriber.php +++ b/Civi/Token/TokenCompatSubscriber.php @@ -61,6 +61,18 @@ public function onEvaluate(TokenValueEvent $e) { // FIXME: Need to differentiate errors which kill the batch vs the individual row. throw new TokenException("Failed to generate token data. Invalid contact ID: " . $row->context['contactId']); } + + //update value of custom field token + if (!empty($messageTokens['contact'])) { + foreach ($messageTokens['contact'] as $token) { + if (\CRM_Core_BAO_CustomField::getKeyID($token)) { + $contact[$token] = civicrm_api3('Contact', 'getvalue', array( + 'return' => $token, + 'id' => $contactId, + )); + } + } + } } else { $contact = $row->context['contact']; @@ -76,7 +88,6 @@ public function onEvaluate(TokenValueEvent $e) { // Note: This is a small contract change from the past; data should be missing // less randomly. - //\CRM_Utils_Hook::tokenValues($contact, $row->context['contactId']); \CRM_Utils_Hook::tokenValues($contactArray, (array) $contactId, empty($row->context['mailingJob']) ? NULL : $row->context['mailingJob']->id, @@ -104,12 +115,12 @@ public function onRender(TokenRenderEvent $e) { $e->string = \CRM_Utils_Token::replaceDomainTokens($e->string, \CRM_Core_BAO_Domain::getDomain(), $isHtml, $e->message['tokens'], $useSmarty); if (!empty($e->context['contact'])) { - $e->string = \CRM_Utils_Token::replaceContactTokens($e->string, $e->context['contact'], $isHtml, $e->message['tokens'], FALSE, $useSmarty); + $e->string = \CRM_Utils_Token::replaceContactTokens($e->string, $e->context['contact'], $isHtml, $e->message['tokens'], TRUE, $useSmarty); // FIXME: This may depend on $contact being merged with hook values. $e->string = \CRM_Utils_Token::replaceHookTokens($e->string, $e->context['contact'], $e->context['hookTokenCategories'], $isHtml, $useSmarty); - \CRM_Utils_Token::replaceGreetingTokens($e->string, NULL, $e->context['contact']['contact_id'], NULL, $useSmarty); + \CRM_Utils_Token::replaceGreetingTokens($e->string, $e->context['contact'], $e->context['contact']['contact_id'], NULL, $useSmarty); } if ($useSmarty) { diff --git a/Civi/Token/TokenRow.php b/Civi/Token/TokenRow.php index 3c77f31d129a..4f186d3d5c1e 100644 --- a/Civi/Token/TokenRow.php +++ b/Civi/Token/TokenRow.php @@ -126,6 +126,29 @@ public function tokens($a = NULL, $b = NULL, $c = NULL) { return $this; } + /** + * Update the value of a custom field token. + * + * @param string $entity + * @param int $customFieldID + * @param int $entityID + * @return TokenRow + */ + public function customToken($entity, $customFieldID, $entityID) { + $customFieldName = "custom_" . $customFieldID; + $fieldValue = civicrm_api3($entity, 'getvalue', array( + 'return' => $customFieldName, + 'id' => $entityID, + )); + + // format the raw custom field value into proper display value + if ($fieldValue) { + $fieldValue = \CRM_Core_BAO_CustomField::displayValue($fieldValue, $customFieldID); + } + + return $this->tokens($entity, $customFieldName, $fieldValue); + } + /** * Update the value of a token. Apply formatting based on DB schema. * diff --git a/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php b/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php index b49bda4019b0..043b5846f57a 100644 --- a/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php +++ b/tests/phpunit/CRM/Core/BAO/ActionScheduleTest.php @@ -95,6 +95,8 @@ public function setUp() { 'contact_type' => 'Individual', 'email' => 'test-member@example.com', 'gender_id' => 'Female', + 'first_name' => 'Churmondleia', + 'last_name' => 'Ōtākou', ); $this->fixtures['contact_birthdate'] = array( 'is_deceased' => 0, @@ -593,6 +595,28 @@ public function setUp() { 'subject' => 'subject send reminder every unit after membership_end_date', ); + $customGroup = $this->callAPISuccess('CustomGroup', 'create', array( + 'title' => ts('Test Contact Custom group'), + 'name' => 'test_contact_cg', + 'extends' => 'Contact', + 'domain_id' => CRM_Core_Config::domainID(), + 'is_active' => 1, + 'collapse_adv_display' => 0, + 'collapse_display' => 0, + )); + $customField = $this->callAPISuccess('CustomField', 'create', array( + 'label' => 'Test Text', + 'data_type' => 'String', + 'html_type' => 'Text', + 'custom_group_id' => $customGroup['id'], + )); + $this->fixtures['contact_custom_token'] = array( + 'id' => $customField['id'], + 'token' => sprintf('{contact.custom_%s}', $customField['id']), + 'name' => sprintf('custom_%s', $customField['id']), + 'value' => 'text ' . substr(sha1(rand()), 0, 7), + ); + $this->_setUp(); } @@ -603,7 +627,6 @@ public function setUp() { */ public function tearDown() { parent::tearDown(); - $this->mut->clearMessages(); $this->mut->stop(); unset($this->mut); @@ -615,28 +638,40 @@ public function tearDown() { 'civicrm_event', 'civicrm_email', )); + $this->callAPISuccess('CustomField', 'delete', array('id' => $this->fixtures['contact_custom_token']['id'])); + $this->callAPISuccess('CustomGroup', 'delete', array( + 'id' => CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', 'test_contact_cg', 'id', 'name'), + )); $this->_tearDown(); } public function mailerExamples() { $cases = array(); - $manyTokensTmpl = implode(';;', array( + // Some tokens - short as subject has 128char limit in DB. + $someTokensTmpl = implode(';;', array( '{contact.display_name}', // basic contact token '{contact.gender}', // funny legacy contact token '{contact.gender_id}', // funny legacy contact token '{domain.name}', // domain token '{activity.activity_type}', // action-scheduler token )); + // Further tokens can be tested in the body text/html. + $manyTokensTmpl = implode(';;', array( + $someTokensTmpl, + '{contact.email_greeting}', + $this->fixture['contact_custom_token']['token'], + )); // Note: The behavior of domain-tokens on a scheduled reminder is undefined. All we // can really do is check that it has something. - $manyTokensExpected = 'test-member@example.com;;Female;;Female;;[a-zA-Z0-9 ]+;;Phone Call'; + $someTokensExpected = 'Churmondleia Ōtākou;;Female;;Female;;[a-zA-Z0-9 ]+;;Phone Call'; + $manyTokensExpected = sprintf('%s;;Dear Churmondleia;;%s', $someTokensExpected, $this->fixture['contact_custom_token']['value']); - // In this example, we use a lot of tokens cutting across multiple components.. + // In this example, we use a lot of tokens cutting across multiple components. $cases[0] = array( // Schedule definition. array( - 'subject' => "subj $manyTokensTmpl", + 'subject' => "subj $someTokensTmpl", 'body_html' => "html $manyTokensTmpl", 'body_text' => "text $manyTokensTmpl", ), @@ -644,7 +679,7 @@ public function mailerExamples() { array( 'from_name' => "/^FIXME\$/", 'from_email' => "/^info@EXAMPLE.ORG\$/", - 'subject' => "/^subj $manyTokensExpected\$/", + 'subject' => "/^subj $someTokensExpected\$/", 'body_html' => "/^html $manyTokensExpected\$/", 'body_text' => "/^text $manyTokensExpected\$/", ), @@ -717,7 +752,12 @@ public function testMailer($schedule, $patterns) { $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phonecall']); $this->assertTrue(is_numeric($activity->id)); - $contact = $this->callAPISuccess('contact', 'create', $this->fixtures['contact']); + $contact = $this->callAPISuccess('contact', 'create', array_merge( + $this->fixtures['contact'], + array( + $this->fixtures['contact_custom_token']['name'] => $this->fixtures['contact_custom_token']['value'], + ) + )); $activity->save(); $source['contact_id'] = $contact['id']; @@ -755,7 +795,6 @@ public function testMailer($schedule, $patterns) { } } $this->mut->clearMessages(); - } public function testActivityDateTimeMatchNonRepeatableSchedule() {