Fix for CRM-21180: Inline changes to custom fields aren't reflected in custom greetings.
authorAllen Shaw <allen@JoineryHQ.com>
Wed, 8 Nov 2017 09:46:50 +0000 (03:46 -0600)
committereileen <emcnaughton@wikimedia.org>
Mon, 4 Dec 2017 07:16:35 +0000 (20:16 +1300)
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
CRM/Contact/BAO/Contact/Utils.php
CRM/Core/BAO/CustomQuery.php
CRM/Core/BAO/CustomValueTable.php
templates/CRM/Contact/Page/View/CustomDataFieldView.tpl
tests/phpunit/api/v3/CustomValueTest.php

index 39de40240b5c903f556a417a09f420a69f07e179..866c82370217222f9bad2f89d52bcbaa1c7dd6f9 100644 (file)
@@ -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.
index 83ceefc3ec6d44a08245c5cae9ce50264e2525a5..662e8662271bba347e1dc841dfe1194662cc70be 100644 (file)
@@ -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.
    *
index 81e5eb876f7a4232a217c8dbe124c06489ae152d..975926e15425ae65bfab5a9828a39fef21494c12 100644 (file)
@@ -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];
       }
index 9d8245380c5927383e3870742fe7646267e407b4..caeaa600d3a4d07e4d4508ae812a3a68c9a6911e 100644 (file)
@@ -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') {
index 3460e84cd3f00b97d69d0a4fff9db6156308ff06..ad3cc5637c0b52415b0354e045eafc0e3165a3aa 100644 (file)
@@ -23,7 +23,7 @@
  | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
  +--------------------------------------------------------------------+
 *}
-<div id="custom-set-content-{$customGroupId}" {if $permission EQ 'edit'} class="crm-inline-edit" data-edit-params='{ldelim}"cid": "{$contactId}", "class_name": "CRM_Contact_Form_Inline_CustomData", "groupID": "{$customGroupId}", "customRecId": "{$customRecId}", "cgcount" : "{$cgcount}"{rdelim}'{/if}>
+<div id="custom-set-content-{$customGroupId}" {if $permission EQ 'edit'} class="crm-inline-edit" data-edit-params='{ldelim}"cid": "{$contactId}", "class_name": "CRM_Contact_Form_Inline_CustomData", "groupID": "{$customGroupId}", "customRecId": "{$customRecId}", "cgcount" : "{$cgcount}"{rdelim}' data-dependent-fields='["#crm-communication-pref-content"]'{/if}>
   <div class="crm-clear crm-inline-block-content" {if $permission EQ 'edit'}title="{ts}Edit{/ts}"{/if}>
     {if $permission EQ 'edit'}
       <div class="crm-edit-help">
index c3360f6c4d86bc14457b250d4e9109b974afcb12..a3d425bd67324e13975021cc72ad3b3ff023fb85 100644 (file)
@@ -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']);
+
+  }
+
 }