From 63b7d4427ad0e1967032693cf2a733f9b581cf1b Mon Sep 17 00:00:00 2001 From: Allen Shaw Date: Wed, 8 Nov 2017 03:46:50 -0600 Subject: [PATCH] Fix for CRM-21180: Inline changes to custom fields aren't reflected in custom greetings. Includes unit test. Toward CRM-21180: Better static var handling. Toward CRM-21180: removed static vars; removed unused method parameters. CRM-21180 add unit test for custom field being set in address CRM-21180 Inline changes to custom fields aren't reflected in custom greetings This incorporates Allan's work to cause custom fields to be updated when a custom value is updated. These have been reconciled with the changes to allow greeting fields to be set to null per CRM-21474 m --- CRM/Contact/BAO/Contact.php | 91 ++++++++++++++++--- CRM/Contact/BAO/Contact/Utils.php | 28 ++++++ CRM/Core/BAO/CustomQuery.php | 1 + CRM/Core/BAO/CustomValueTable.php | 19 ++++ .../Contact/Page/View/CustomDataFieldView.tpl | 2 +- tests/phpunit/api/v3/CustomValueTest.php | 43 +++++++++ 6 files changed, 170 insertions(+), 14 deletions(-) diff --git a/CRM/Contact/BAO/Contact.php b/CRM/Contact/BAO/Contact.php index 39de40240b..866c823702 100644 --- a/CRM/Contact/BAO/Contact.php +++ b/CRM/Contact/BAO/Contact.php @@ -287,7 +287,7 @@ class CRM_Contact_BAO_Contact extends CRM_Contact_DAO_Contact { } } - $config = CRM_Core_Config::singleton(); + self::ensureGreetingParamsAreSet($params); // CRM-6942: set preferred language to the current language if it’s unset (and we’re creating a contact). if (empty($params['contact_id'])) { @@ -296,17 +296,6 @@ class CRM_Contact_BAO_Contact extends CRM_Contact_DAO_Contact { $params['preferred_language'] = $language; } - // CRM-9739: set greeting & addressee if unset and we’re creating a contact. - foreach (self::$_greetingTypes as $greeting) { - if (empty($params[$greeting . '_id'])) { - if ($defaultGreetingTypeId - = CRM_Contact_BAO_Contact_Utils::defaultGreeting($params['contact_type'], $greeting) - ) { - $params[$greeting . '_id'] = $defaultGreetingTypeId; - } - } - } - // CRM-21041: set default 'Communication Style' if unset when creating a contact. if (empty($params['communication_style_id'])) { $defaultCommunicationStyleId = CRM_Core_OptionGroup::values('communication_style', TRUE, NULL, NULL, 'AND is_default = 1'); @@ -448,12 +437,58 @@ class CRM_Contact_BAO_Contact extends CRM_Contact_DAO_Contact { } } - // process greetings CRM-4575, cache greetings self::processGreetings($contact); return $contact; } + /** + * Ensure greeting parameters are set. + * + * By always populating greetings here we can be sure they are set if required & avoid a call later. + * (ie. knowing we have definitely tried disambiguates between NULL & not loaded.) + * + * @param array $params + */ + public static function ensureGreetingParamsAreSet(&$params) { + $allGreetingParams = array('addressee' => 'addressee_id', 'postal_greeting' => 'postal_greeting_id', 'email_greeting' => 'email_greeting_id'); + $missingGreetingParams = array(); + + foreach ($allGreetingParams as $greetingIndex => $greetingParam) { + if (empty($params[$greetingParam])) { + $missingGreetingParams[$greetingIndex] = $greetingParam; + } + } + + if (!empty($params['contact_id']) && !empty($missingGreetingParams)) { + $savedGreetings = civicrm_api3('Contact', 'getsingle', array( + 'id' => $params['contact_id'], + 'return' => array_keys($missingGreetingParams)) + ); + + foreach (array_keys($missingGreetingParams) as $missingGreetingParam) { + if (!empty($savedGreetings[$missingGreetingParam . '_custom'])) { + $missingGreetingParams[$missingGreetingParam . '_custom'] = $missingGreetingParam . '_custom'; + } + } + // Filter out other fields. + $savedGreetings = array_intersect_key($savedGreetings, array_flip($missingGreetingParams)); + $params = array_merge($params, $savedGreetings); + } + else { + foreach ($missingGreetingParams as $greetingName => $greeting) { + $params[$greeting] = CRM_Contact_BAO_Contact_Utils::defaultGreeting($params['contact_type'], $greetingName); + } + } + + foreach ($allGreetingParams as $greetingIndex => $greetingParam) { + if ($params[$greetingParam] === 'null') { + // If we are setting it to null then null out the display field. + $params[$greetingIndex . '_display'] = 'null'; + } + } + } + /** * Get the display name and image of a contact. * @@ -2689,6 +2724,31 @@ AND civicrm_openid.is_primary = 1"; } } + /** + * Update contact greetings if an update has resulted in a custom field change. + * + * @param array $updatedFields + * Array of fields that have been updated e.g array('first_name', 'prefix_id', 'custom_2'); + * @param array $contactParams + * Parameters known about the contact. At minimum array('contact_id' => x). + * Fields in this array will take precedence over DB fields (so far only + * in the case of greeting id fields). + */ + public static function updateGreetingsOnTokenFieldChange($updatedFields, $contactParams) { + $contactID = $contactParams['contact_id']; + CRM_Contact_BAO_Contact::ensureGreetingParamsAreSet($contactParams); + $tokens = CRM_Contact_BAO_Contact_Utils::getTokensRequiredForContactGreetings($contactParams); + if (!empty($tokens['all']['contact'])) { + $affectedTokens = array_intersect_key($updatedFields[$contactID], array_flip($tokens['all']['contact'])); + if (!empty($affectedTokens)) { + // @todo this is still reloading the whole contact -fix to be more selective & use pre-loaded. + $contact = new CRM_Contact_BAO_Contact(); + $contact->id = $contactID; + CRM_Contact_BAO_Contact::processGreetings($contact); + } + } + } + /** * Process greetings and cache. * @@ -2697,6 +2757,11 @@ AND civicrm_openid.is_primary = 1"; */ public static function processGreetings(&$contact) { + //@todo this function does a lot of unnecessary loading. + // ensureGreetingParamsAreSet now makes sure that the contact is + // loaded and using updateGreetingsOnTokenFieldChange + // allows us the possibility of only doing an update if required. + // The contact object has not always required the // fields that are required to calculate greetings // so we need to retrieve it again. diff --git a/CRM/Contact/BAO/Contact/Utils.php b/CRM/Contact/BAO/Contact/Utils.php index 83ceefc3ec..662e866227 100644 --- a/CRM/Contact/BAO/Contact/Utils.php +++ b/CRM/Contact/BAO/Contact/Utils.php @@ -1096,6 +1096,34 @@ WHERE id IN (" . implode(',', $contactIds) . ")"; } } + /** + * Get the tokens that will need to be resolved to populate the contact's greetings. + * + * @param array $contactParams + * + * @return array + * Array of tokens. The ALL ke + */ + public static function getTokensRequiredForContactGreetings($contactParams) { + $tokens = array(); + foreach (array('addressee', 'email_greeting', 'postal_greeting') as $greeting) { + $string = ''; + if (!empty($contactParams[$greeting . '_id'])) { + $string = CRM_Core_PseudoConstant::getLabel('CRM_Contact_BAO_Contact', $greeting . '_id', $contactParams[$greeting . '_id']); + } + $string = isset($contactParams[$greeting . '_custom']) ? $contactParams[$greeting . '_custom'] : $string; + if (empty($string)) { + $tokens[$greeting] = array(); + } + else { + $tokens[$greeting] = CRM_Utils_Token::getTokens($string); + } + } + $allTokens = array_merge_recursive($tokens['addressee'], $tokens['email_greeting'], $tokens['postal_greeting']); + $tokens['all'] = $allTokens; + return $tokens; + } + /** * Process a greeting template string to produce the individualised greeting text. * diff --git a/CRM/Core/BAO/CustomQuery.php b/CRM/Core/BAO/CustomQuery.php index 81e5eb876f..975926e154 100644 --- a/CRM/Core/BAO/CustomQuery.php +++ b/CRM/Core/BAO/CustomQuery.php @@ -179,6 +179,7 @@ SELECT f.id, f.label, f.data_type, while ($dao->fetch()) { // get the group dao to figure which class this custom field extends $extends = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $dao->custom_group_id, 'extends'); + $extendsTable = ''; if (array_key_exists($extends, self::$extendsMap)) { $extendsTable = self::$extendsMap[$extends]; } diff --git a/CRM/Core/BAO/CustomValueTable.php b/CRM/Core/BAO/CustomValueTable.php index 9d8245380c..caeaa600d3 100644 --- a/CRM/Core/BAO/CustomValueTable.php +++ b/CRM/Core/BAO/CustomValueTable.php @@ -46,6 +46,8 @@ class CRM_Core_BAO_CustomValueTable { return; } + $paramFieldsExtendContactForEntities = array(); + foreach ($customParams as $tableName => $tables) { foreach ($tables as $index => $fields) { $sqlOP = NULL; @@ -227,6 +229,17 @@ class CRM_Core_BAO_CustomValueTable { $params[$count] = array($value, $type); $count++; } + + $fieldExtends = CRM_Utils_Array::value('extends', $field); + if ( + CRM_Utils_Array::value('entity_table', $field) == 'civicrm_contact' + || $fieldExtends == 'Contact' + || $fieldExtends == 'Individual' + || $fieldExtends == 'Organization' + || $fieldExtends == 'Household' + ) { + $paramFieldsExtendContactForEntities[$entityID]['custom_' . CRM_Utils_Array::value('custom_field_id', $field)] = CRM_Utils_Array::value('custom_field_id', $field); + } } if (!empty($set)) { @@ -262,6 +275,10 @@ class CRM_Core_BAO_CustomValueTable { } } } + + if (!empty($paramFieldsExtendContactForEntities)) { + CRM_Contact_BAO_Contact::updateGreetingsOnTokenFieldChange($paramFieldsExtendContactForEntities, array('contact_id' => $entityID)); + } } /** @@ -560,6 +577,7 @@ AND $cond SELECT cg.table_name as table_name , cg.id as cg_id , cg.is_multiple as is_multiple, + cg.extends as extends, cf.column_name as column_name, cf.id as cf_id , cf.data_type as data_type @@ -616,6 +634,7 @@ AND cf.id IN ( $fieldIDList ) 'table_name' => $dao->table_name, 'column_name' => $dao->column_name, 'is_multiple' => $dao->is_multiple, + 'extends' => $dao->extends, ); if ($cvParam['type'] == 'File') { diff --git a/templates/CRM/Contact/Page/View/CustomDataFieldView.tpl b/templates/CRM/Contact/Page/View/CustomDataFieldView.tpl index 3460e84cd3..ad3cc5637c 100644 --- a/templates/CRM/Contact/Page/View/CustomDataFieldView.tpl +++ b/templates/CRM/Contact/Page/View/CustomDataFieldView.tpl @@ -23,7 +23,7 @@ | see the CiviCRM license FAQ at http://civicrm.org/licensing | +--------------------------------------------------------------------+ *} -
+
{if $permission EQ 'edit'}
diff --git a/tests/phpunit/api/v3/CustomValueTest.php b/tests/phpunit/api/v3/CustomValueTest.php index c3360f6c4d..a3d425bd67 100644 --- a/tests/phpunit/api/v3/CustomValueTest.php +++ b/tests/phpunit/api/v3/CustomValueTest.php @@ -475,4 +475,47 @@ class api_v3_CustomValueTest extends CiviUnitTestCase { $this->assertEquals('custom_value.id', $fields['custom_value.id']['name']); } + /** + * Test that custom fields in greeting strings are updated. + */ + public function testUpdateCustomGreetings() { + // Create a custom group with one field. + $customGroupResult = $this->callAPISuccess('CustomGroup', 'create', array( + 'sequential' => 1, + 'title' => "test custom group", + 'extends' => "Individual", + )); + $customFieldResult = $this->callAPISuccess('CustomField', 'create', array( + 'custom_group_id' => $customGroupResult['id'], + 'label' => "greeting test", + 'data_type' => "String", + 'html_type' => "Text", + )); + $customFieldId = $customFieldResult['id']; + + // Create a contact with an email greeting format that includes the new custom field. + $contactResult = $this->callAPISuccess('Contact', 'create', array( + 'contact_type' => 'Individual', + 'email' => substr(sha1(rand()), 0, 7) . '@yahoo.com', + 'email_greeting_id' => "Customized", + 'email_greeting_custom' => "Dear {contact.custom_{$customFieldId}}", + )); + $cid = $contactResult['id']; + + // Define testing values. + $uniq = uniqid(); + $testGreetingValue = "Dear $uniq"; + + // Update contact's custom field with CustomValue.create + $customValueResult = $this->callAPISuccess('CustomValue', 'create', array( + 'entity_id' => $cid, + "custom_{$customFieldId}" => $uniq, + 'entity_table' => "civicrm_contact", + )); + + $contact = $this->callAPISuccessGetSingle('Contact', array('id' => $cid, 'return' => 'email_greeting')); + $this->assertEquals($testGreetingValue, $contact['email_greeting_display']); + + } + } -- 2.25.1