CRM-16055 migrate single create call away from multiple create and use create api
[civicrm-core.git] / CRM / Core / BAO / Address.php
index 2df8a1235c3c9cd7394cfafc255156f46c32f4ba..14aaa5664e0338478f8aba32e98972dced9d7a29 100644 (file)
@@ -23,7 +23,7 @@
  | GNU Affero General Public License or the licensing of CiviCRM,     |
  | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
  +--------------------------------------------------------------------+
-*/
+ */
 
 /**
  *
 class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
 
   /**
-   * Takes an associative array and creates a address
+   * Takes an associative array and creates a address.
    *
-   * @param array $params (reference ) an assoc array of name/value pairs
-   * @param boolean $fixAddress true if you need to fix (format) address values
+   * @param array $params
+   *   (reference ) an assoc array of name/value pairs.
+   * @param bool $fixAddress
+   *   True if you need to fix (format) address values.
    *                               before inserting in db
    *
    * @param null $entity
    *
-   * @return array $blocks array of created address
-   * @static
+   * @return array|NULL
+   *   array of created address
    */
   public static function create(&$params, $fixAddress = TRUE, $entity = NULL) {
     if (!isset($params['address']) || !is_array($params['address'])) {
-      return;
+      return NULL;
     }
     CRM_Core_BAO_Block::sortPrimaryFirst($params['address']);
     $addresses = array();
@@ -131,14 +133,15 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
   }
 
   /**
-   * Takes an associative array and adds address
+   * Takes an associative array and adds address.
    *
-   * @param array  $params         (reference ) an assoc array of name/value pairs
-   * @param boolean  $fixAddress   true if you need to fix (format) address values
+   * @param array $params
+   *   (reference ) an assoc array of name/value pairs.
+   * @param bool $fixAddress
+   *   True if you need to fix (format) address values.
    *                               before inserting in db
    *
-   * @return object       CRM_Core_BAO_Address object on success, null otherwise
-   * @static
+   * @return CRM_Core_BAO_Address|null
    */
   public static function add(&$params, $fixAddress) {
     static $customFields = NULL;
@@ -194,12 +197,12 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
   }
 
   /**
-   * Format the address params to have reasonable values
+   * Format the address params to have reasonable values.
    *
-   * @param array  $params         (reference ) an assoc array of name/value pairs
+   * @param array $params
+   *   (reference ) an assoc array of name/value pairs.
    *
    * @return void
-   * @static
    */
   public static function fixAddress(&$params) {
     if (!empty($params['billing_street_address'])) {
@@ -361,19 +364,22 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
       CRM_Utils_Address_USPS::checkAddress($params);
 
       // do street parsing again if enabled, since street address might have changed
-      $parseStreetAddress =
-        CRM_Utils_Array::value(
-          'street_address_parsing',
-          CRM_Core_BAO_Setting::valueOptions(
-            CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
-            'address_options'
-          ),
-          FALSE
-        );
+      $parseStreetAddress = CRM_Utils_Array::value(
+        'street_address_parsing',
+        CRM_Core_BAO_Setting::valueOptions(
+          CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
+          'address_options'
+        ),
+        FALSE
+      );
 
       if ($parseStreetAddress && !empty($params['street_address'])) {
         foreach (array(
-          'street_number', 'street_name', 'street_unit', 'street_number_suffix') as $fld) {
+                   'street_number',
+                   'street_name',
+                   'street_unit',
+                   'street_number_suffix',
+                 ) as $fld) {
           unset($params[$fld]);
         }
         // main parse string.
@@ -393,13 +399,12 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
   }
 
   /**
-   * Check if there is data to create the object
-   *
-   * @param array  $params    (reference ) an assoc array of name/value pairs
+   * Check if there is data to create the object.
    *
-   * @return boolean
+   * @param array $params
+   *   (reference ) an assoc array of name/value pairs.
    *
-   * @static
+   * @return bool
    */
   public static function dataExists(&$params) {
     //check if location type is set if not return false
@@ -410,7 +415,14 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
     $config = CRM_Core_Config::singleton();
     foreach ($params as $name => $value) {
       if (in_array($name, array(
-        'is_primary', 'location_type_id', 'id', 'contact_id', 'is_billing', 'display', 'master_id'))) {
+        'is_primary',
+        'location_type_id',
+        'id',
+        'contact_id',
+        'is_billing',
+        'display',
+        'master_id',
+      ))) {
         continue;
       }
       elseif (!CRM_Utils_System::isNull($value)) {
@@ -451,12 +463,14 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
    * Given the list of params in the params array, fetch the object
    * and store the values in the values array
    *
-   * @param array $entityBlock associated array of fields
-   * @param boolean $microformat if microformat output is required
+   * @param array $entityBlock
+   *   Associated array of fields.
+   * @param bool $microformat
+   *   If microformat output is required.
    * @param int|string $fieldName conditional field name
    *
-   * @return array  $addresses     array with address fields
-   * @static
+   * @return array
+   *   array with address fields
    */
   public static function &getValues($entityBlock, $microformat = FALSE, $fieldName = 'contact_id') {
     if (empty($entityBlock)) {
@@ -484,13 +498,20 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
 
     $address->find();
 
+    $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
     $count = 1;
     while ($address->fetch()) {
       // deprecate reference.
       if ($count > 1) {
         foreach (array(
-          'state', 'state_name', 'country', 'world_region') as $fld) {
-          if (isset($address->$fld))unset($address->$fld);
+                   'state',
+                   'state_name',
+                   'country',
+                   'world_region',
+                 ) as $fld) {
+          if (isset($address->$fld)) {
+            unset($address->$fld);
+          }
         }
       }
       $stree = $address->street_address;
@@ -498,6 +519,9 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
       CRM_Core_DAO::storeValues($address, $values);
 
       // add state and country information: CRM-369
+      if (!empty($address->location_type_id)) {
+        $values['location_type'] = CRM_Utils_Array::value($address->location_type_id, $locationTypes);
+      }
       if (!empty($address->state_province_id)) {
         $address->state = CRM_Core_PseudoConstant::stateProvinceAbbreviation($address->state_province_id, FALSE);
         $address->state_name = CRM_Core_PseudoConstant::stateProvince($address->state_province_id, FALSE);
@@ -574,12 +598,13 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
   /**
    * Get all the addresses for a specified contact_id, with the primary address being first
    *
-   * @param int $id the contact id
+   * @param int $id
+   *   The contact id.
    *
    * @param bool $updateBlankLocInfo
    *
-   * @return array  the array of adrress data
-   * @static
+   * @return array
+   *   the array of adrress data
    */
   public static function allAddress($id, $updateBlankLocInfo = FALSE) {
     if (!$id) {
@@ -610,11 +635,12 @@ ORDER BY civicrm_address.is_primary DESC, address_id ASC";
   /**
    * Get all the addresses for a specified location_block id, with the primary address being first
    *
-   * @param array $entityElements the array containing entity_id and
-   * entity_table name
+   * @param array $entityElements
+   *   The array containing entity_id and.
+   *   entity_table name
    *
-   * @return array  the array of adrress data
-   * @static
+   * @return array
+   *   the array of adrress data
    */
   public static function allEntityAddress(&$entityElements) {
     $addresses = array();
@@ -645,9 +671,10 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
   }
 
   /**
-   * Get address sequence
+   * Get address sequence.
    *
-   * @return  array of address sequence.
+   * @return array
+   *   Array of address sequence.
    */
   public static function addressSequence() {
     $config = CRM_Core_Config::singleton();
@@ -670,7 +697,7 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
         $addressSequence[$key] = 'city_postal_code';
       }
       elseif (
-        in_array($field, array('country', 'state_province', 'city', 'postal_code'))
+      in_array($field, array('country', 'state_province', 'city', 'postal_code'))
       ) {
         unset($addressSequence[$key]);
       }
@@ -687,11 +714,13 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
    * NB: civic street formats for en_CA and fr_CA used by default if those locales are active
    *     otherwise en_US format is default action
    *
-   * @param  string   Street address including number and apt
-   * @param  string   Locale - to set locale used to parse address
+   * @param string $streetAddress
+   *   Street address including number and apt.
+   * @param string $locale
+   *   Locale used to parse address.
    *
-   * @return array    $parseFields    parsed fields values.
-   * @static
+   * @return array
+   *   parsed fields values.
    */
   public static function parseStreetAddress($streetAddress, $locale = NULL) {
     $config = CRM_Core_Config::singleton();
@@ -728,7 +757,10 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
 
     $matches = array();
     if (in_array($locale, array(
-      'en_CA', 'fr_CA')) && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches)) {
+        'en_CA',
+        'fr_CA',
+      )) && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches)
+    ) {
       $parseFields['street_unit'] = $matches[1];
       // unset from rest of street address
       $streetAddress = preg_replace('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', '', $streetAddress);
@@ -775,18 +807,53 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
     // now get the street unit.
     // supportable street unit formats.
     $streetUnitFormats = array(
-      'APT', 'APARTMENT', 'BSMT', 'BASEMENT', 'BLDG', 'BUILDING',
-      'DEPT', 'DEPARTMENT', 'FL', 'FLOOR', 'FRNT', 'FRONT',
-      'HNGR', 'HANGER', 'LBBY', 'LOBBY', 'LOWR', 'LOWER',
-      'OFC', 'OFFICE', 'PH', 'PENTHOUSE', 'TRLR', 'TRAILER',
-      'UPPR', 'RM', 'ROOM', 'SIDE', 'SLIP', 'KEY',
-      'LOT', 'PIER', 'REAR', 'SPC', 'SPACE',
-      'STOP', 'STE', 'SUITE', 'UNIT', '#',
+      'APT',
+      'APARTMENT',
+      'BSMT',
+      'BASEMENT',
+      'BLDG',
+      'BUILDING',
+      'DEPT',
+      'DEPARTMENT',
+      'FL',
+      'FLOOR',
+      'FRNT',
+      'FRONT',
+      'HNGR',
+      'HANGER',
+      'LBBY',
+      'LOBBY',
+      'LOWR',
+      'LOWER',
+      'OFC',
+      'OFFICE',
+      'PH',
+      'PENTHOUSE',
+      'TRLR',
+      'TRAILER',
+      'UPPR',
+      'RM',
+      'ROOM',
+      'SIDE',
+      'SLIP',
+      'KEY',
+      'LOT',
+      'PIER',
+      'REAR',
+      'SPC',
+      'SPACE',
+      'STOP',
+      'STE',
+      'SUITE',
+      'UNIT',
+      '#',
     );
 
     // overwriting $streetUnitFormats for 'en_CA' and 'fr_CA' locale
     if (in_array($locale, array(
-      'en_CA', 'fr_CA'))) {
+      'en_CA',
+      'fr_CA',
+    ))) {
       $streetUnitFormats = array('APT', 'APP', 'SUITE', 'BUREAU', 'UNIT');
     }
     //@todo per CRM-14459 this regex picks up words with the string in them - e.g APT picks up
@@ -810,7 +877,7 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
     // the DB to fatal
     $fields = CRM_Core_BAO_Address::fields();
     foreach ($fields as $fieldname => $field) {
-      if(!empty($field['maxlength']) && strlen(CRM_Utils_Array::value($fieldname, $parseFields)) > $field['maxlength']) {
+      if (!empty($field['maxlength']) && strlen(CRM_Utils_Array::value($fieldname, $parseFields)) > $field['maxlength']) {
         return $emptyParseFields;
       }
     }
@@ -819,22 +886,22 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
   }
 
   /**
-   * Validate the address fields based on the address options enabled
+   * Validate the address fields based on the address options enabled.
    * in the Address Settings
    *
-   * @param  array   $fields an array of importable/exportable contact fields
+   * @param array $fields
+   *   An array of importable/exportable contact fields.
    *
-   * @return array   $fields an array of contact fields and only the enabled address options
-   * @static
+   * @return array
+   *   an array of contact fields and only the enabled address options
    */
   public static function validateAddressOptions($fields) {
     static $addressOptions = NULL;
     if (!$addressOptions) {
-      $addressOptions =
-        CRM_Core_BAO_Setting::valueOptions(
-          CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
-          'address_options'
-        );
+      $addressOptions = CRM_Core_BAO_Setting::valueOptions(
+        CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
+        'address_options'
+      );
     }
 
     if (is_array($fields) && !empty($fields)) {
@@ -848,12 +915,13 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
   }
 
   /**
-   * Check if current address is used by any other contacts
+   * Check if current address is used by any other contacts.
    *
-   * @param int $addressId address id
+   * @param int $addressId
+   *   Address id.
    *
-   * @return count of contacts that use this shared address
-   * @static
+   * @return int
+   *   count of contacts that use this shared address
    */
   public static function checkContactSharedAddress($addressId) {
     $query = 'SELECT count(id) FROM civicrm_address WHERE master_id = %1';
@@ -861,12 +929,13 @@ ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC,
   }
 
   /**
-   * Check if current address fields are shared with any other address
+   * Check if current address fields are shared with any other address.
    *
-   * @param array    $fields    address fields in profile
-   * @param int      $contactId contact id
+   * @param array $fields
+   *   Address fields in profile.
+   * @param int $contactId
+   *   Contact id.
    *
-   * @static
    */
   public static function checkContactSharedAddressFields(&$fields, $contactId) {
     if (!$contactId || !is_array($fields) || empty($fields)) {
@@ -931,13 +1000,14 @@ SELECT is_primary,
   }
 
   /**
-   * Update the shared addresses if master address is modified
+   * Update the shared addresses if master address is modified.
    *
-   * @param int    $addressId address id
-   * @param array  $params    associated array of address params
+   * @param int $addressId
+   *   Address id.
+   * @param array $params
+   *   Associated array of address params.
    *
    * @return void
-   * @static
    */
   public static function processSharedAddress($addressId, $params) {
     $query = 'SELECT id FROM civicrm_address WHERE master_id = %1';
@@ -959,16 +1029,90 @@ SELECT is_primary,
   }
 
   /**
-   * Create relationship between contacts who share an address
+   * Merge contacts with the Same address to get one shared label.
+   * @param array $rows
+   *   Array[contact_id][contactDetails].
+   */
+  public static function mergeSameAddress(&$rows) {
+    $uniqueAddress = array();
+    foreach (array_keys($rows) as $rowID) {
+      // load complete address as array key
+      $address = trim($rows[$rowID]['street_address'])
+        . trim($rows[$rowID]['city'])
+        . trim($rows[$rowID]['state_province'])
+        . trim($rows[$rowID]['postal_code'])
+        . trim($rows[$rowID]['country']);
+      if (isset($rows[$rowID]['last_name'])) {
+        $name = $rows[$rowID]['last_name'];
+      }
+      else {
+        $name = $rows[$rowID]['display_name'];
+      }
+
+      // CRM-15120
+      $formatted = array(
+        'first_name' => $rows[$rowID]['first_name'],
+        'individual_prefix' => $rows[$rowID]['individual_prefix'],
+      );
+      $format = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME, 'display_name_format');
+      $firstNameWithPrefix = CRM_Utils_Address::format($formatted, $format, FALSE, FALSE, TRUE);
+      $firstNameWithPrefix = trim($firstNameWithPrefix);
+
+      // fill uniqueAddress array with last/first name tree
+      if (isset($uniqueAddress[$address])) {
+        $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
+        $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
+        // drop unnecessary rows
+        unset($rows[$rowID]);
+        // this is the first listing at this address
+      }
+      else {
+        $uniqueAddress[$address]['ID'] = $rowID;
+        $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
+        $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
+      }
+    }
+    foreach ($uniqueAddress as $address => $data) {
+      // copy data back to $rows
+      $count = 0;
+      // one last name list per row
+      foreach ($data['names'] as $last_name => $first_names) {
+        // too many to list
+        if ($count > 2) {
+          break;
+        }
+        if (count($first_names) == 1) {
+          $family = $first_names[current(array_keys($first_names))]['addressee_display'];
+        }
+        else {
+          // collapse the tree to summarize
+          $family = trim(implode(" & ", array_keys($first_names)) . " " . $last_name);
+        }
+        if ($count) {
+          $processedNames .= "\n" . $family;
+        }
+        else {
+          // build display_name string
+          $processedNames = $family;
+        }
+        $count++;
+      }
+      $rows[$data['ID']]['addressee'] = $rows[$data['ID']]['addressee_display'] = $rows[$data['ID']]['display_name'] = $processedNames;
+    }
+  }
+
+  /**
+   * Create relationship between contacts who share an address.
    *
    * Note that currently we create relationship only for Individual contacts
    * Individual + Household and Individual + Orgnization
    *
-   * @param int    $masterAddressId master address id
-   * @param array  $params          associated array of submitted values
+   * @param int $masterAddressId
+   *   Master address id.
+   * @param array $params
+   *   Associated array of submitted values.
    *
    * @return void
-   * @static
    */
   public static function processSharedAddressRelationship($masterAddressId, $params) {
     if (!$masterAddressId) {
@@ -1019,27 +1163,32 @@ SELECT is_primary,
       CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type '%1'", array(1 => $relationshipType)));
     }
 
-    // create relationship
-    $relationshipParams = array(
-      'is_active' => TRUE,
-      'relationship_type_id' => $relTypeId . '_a_b',
-      'contact_check' => array($sharedContactId => TRUE),
-    );
-
-    list($valid, $invalid, $duplicate,
-      $saved, $relationshipIds
-    ) = CRM_Contact_BAO_Relationship::create($relationshipParams, $cid);
+    try {
+      // create relationship
+      civicrm_api3('relationship', 'create', array(
+        'is_active' => TRUE,
+        'relationship_type_id' => $relTypeId,
+        'contact_id_a' => $cid,
+        'contact_id_b' => $sharedContactId,
+      ));
+    }
+    catch (CiviCRM_API3_Exception $e) {
+      // We catch and ignore here because this has historically been a best-effort relationship create call.
+      // presumably it could refuse due to duplication or similar and we would ignore that.
+    }
   }
 
   /**
-   * Check and set the status for shared address delete
+   * Check and set the status for shared address delete.
    *
-   * @param int     $addressId address id
-   * @param int     $contactId contact id
-   * @param boolean $returnStatus by default false
+   * @param int $addressId
+   *   Address id.
+   * @param int $contactId
+   *   Contact id.
+   * @param bool $returnStatus
+   *   By default false.
    *
-   * @return string $statusMessage
-   * @static
+   * @return string
    */
   public static function setSharedAddressDeleteStatus($addressId = NULL, $contactId = NULL, $returnStatus = FALSE) {
     // check if address that is being deleted has any shared
@@ -1092,7 +1241,7 @@ SELECT is_primary,
   }
 
   /**
-   * Call common delete function
+   * Call common delete function.
    */
   public static function del($id) {
     return CRM_Contact_BAO_Contact::deleteObjectWithPrimary('Address', $id);
@@ -1105,11 +1254,13 @@ SELECT is_primary,
    * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow?
    * TODO: In context of chainselect, what to return if e.g. a country has no states?
    *
-   * @param String $fieldName
-   * @param String $context : @see CRM_Core_DAO::buildOptionsContext
-   * @param Array $props : whatever is known about this dao object
+   * @param string $fieldName
+   * @param string $context
+   * @see CRM_Core_DAO::buildOptionsContext
+   * @param array $props
+   *   whatever is known about this dao object.
    *
-   * @return Array|bool
+   * @return array|bool
    */
   public static function buildOptions($fieldName, $context = NULL, $props = array()) {
     $params = array();
@@ -1130,6 +1281,7 @@ SELECT is_primary,
           $params['condition'] = 'country_id IN (' . implode(',', (array) $props['country_id']) . ')';
         }
         break;
+
       // Filter country list based on site defaults
       case 'country_id':
         if ($context != 'get' && $context != 'validate') {
@@ -1139,17 +1291,19 @@ SELECT is_primary,
           }
         }
         break;
+
       // Filter county list based on chosen state
       case 'county_id':
         if (!empty($props['state_province_id'])) {
           $params['condition'] = 'state_province_id IN (' . implode(',', (array) $props['state_province_id']) . ')';
         }
         break;
+
       // Not a real field in this entity
       case 'world_region':
         return CRM_Core_PseudoConstant::worldRegion();
-        break;
     }
     return CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context);
   }
+
 }