3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2014
37 * This is class to handle address related functions
39 class CRM_Core_BAO_Address
extends CRM_Core_DAO_Address
{
42 * Takes an associative array and creates a address.
44 * @param array $params
45 * (reference ) an assoc array of name/value pairs.
46 * @param bool $fixAddress
47 * True if you need to fix (format) address values.
48 * before inserting in db
53 * array of created address
55 public static function create(&$params, $fixAddress = TRUE, $entity = NULL) {
56 if (!isset($params['address']) ||
!is_array($params['address'])) {
59 CRM_Core_BAO_Block
::sortPrimaryFirst($params['address']);
63 $updateBlankLocInfo = CRM_Utils_Array
::value('updateBlankLocInfo', $params, FALSE);
65 $contactId = $params['contact_id'];
66 //get all the addresses for this contact
67 $addresses = self
::allAddress($contactId, $updateBlankLocInfo);
70 // get all address from location block
71 $entityElements = array(
72 'entity_table' => $params['entity_table'],
73 'entity_id' => $params['entity_id'],
75 $addresses = self
::allEntityAddress($entityElements);
78 $isPrimary = $isBilling = TRUE;
80 foreach ($params['address'] as $key => $value) {
81 if (!is_array($value)) {
85 $addressExists = self
::dataExists($value);
86 if (empty($value['id'])) {
87 if ($updateBlankLocInfo) {
88 if ((!empty($addresses) ||
!$addressExists) && array_key_exists($key, $addresses)) {
89 $value['id'] = $addresses[$key];
93 if (!empty($addresses) && array_key_exists(CRM_Utils_Array
::value('location_type_id', $value), $addresses)) {
94 $value['id'] = $addresses[CRM_Utils_Array
::value('location_type_id', $value)];
99 // Note there could be cases when address info already exist ($value[id] is set) for a contact/entity
100 // BUT info is not present at this time, and therefore we should be really careful when deleting the block.
101 // $updateBlankLocInfo will help take appropriate decision. CRM-5969
102 if (isset($value['id']) && !$addressExists && $updateBlankLocInfo) {
103 //delete the existing record
104 CRM_Core_BAO_Block
::blockDelete('Address', array('id' => $value['id']));
107 elseif (!$addressExists) {
111 if ($isPrimary && !empty($value['is_primary'])) {
115 $value['is_primary'] = 0;
118 if ($isBilling && !empty($value['is_billing'])) {
122 $value['is_billing'] = 0;
125 if (empty($value['manual_geo_code'])) {
126 $value['manual_geo_code'] = 0;
128 $value['contact_id'] = $contactId;
129 $blocks[] = self
::add($value, $fixAddress);
136 * Takes an associative array and adds address.
138 * @param array $params
139 * (reference ) an assoc array of name/value pairs.
140 * @param bool $fixAddress
141 * True if you need to fix (format) address values.
142 * before inserting in db
144 * @return CRM_Core_BAO_Address|null
146 public static function add(&$params, $fixAddress) {
147 static $customFields = NULL;
148 $address = new CRM_Core_DAO_Address();
150 // fixAddress mode to be done
152 CRM_Core_BAO_Address
::fixAddress($params);
155 $hook = empty($params['id']) ?
'create' : 'edit';
156 CRM_Utils_Hook
::pre($hook, 'Address', CRM_Utils_Array
::value('id', $params), $params);
158 // if id is set & is_primary isn't we can assume no change
159 if (is_numeric(CRM_Utils_Array
::value('is_primary', $params)) ||
empty($params['id'])) {
160 CRM_Core_BAO_Block
::handlePrimary($params, get_class());
162 $config = CRM_Core_Config
::singleton();
163 $address->copyValues($params);
168 if (!$customFields) {
169 $customFields = CRM_Core_BAO_CustomField
::getFields('Address', FALSE, TRUE);
171 if (!empty($customFields)) {
172 $addressCustom = CRM_Core_BAO_CustomField
::postProcess($params,
179 if (!empty($addressCustom)) {
180 CRM_Core_BAO_CustomValueTable
::store($addressCustom, 'civicrm_address', $address->id
);
183 //call the function to sync shared address
184 self
::processSharedAddress($address->id
, $params);
186 // call the function to create shared relationships
187 // we only create create relationship if address is shared by Individual
188 if ($address->master_id
!= 'null') {
189 self
::processSharedAddressRelationship($address->master_id
, $params);
192 // lets call the post hook only after we've done all the follow on processing
193 CRM_Utils_Hook
::post($hook, 'Address', $address->id
, $address);
200 * Format the address params to have reasonable values.
202 * @param array $params
203 * (reference ) an assoc array of name/value pairs.
207 public static function fixAddress(&$params) {
208 if (!empty($params['billing_street_address'])) {
209 //Check address is comming from online contribution / registration page
212 'street_address' => 'billing_street_address',
213 'city' => 'billing_city',
214 'postal_code' => 'billing_postal_code',
215 'state_province' => 'billing_state_province',
216 'state_province_id' => 'billing_state_province_id',
217 'country' => 'billing_country',
218 'country_id' => 'billing_country_id',
221 foreach ($billing as $key => $val) {
222 if ($value = CRM_Utils_Array
::value($val, $params)) {
223 if (!empty($params[$key])) {
224 unset($params[$val]);
227 //add new key and removed old
228 $params[$key] = $value;
229 unset($params[$val]);
235 /* Split the zip and +4, if it's in US format */
236 if (!empty($params['postal_code']) &&
237 preg_match('/^(\d{4,5})[+-](\d{4})$/',
238 $params['postal_code'],
242 $params['postal_code'] = $match[1];
243 $params['postal_code_suffix'] = $match[2];
246 // add country id if not set
247 if ((!isset($params['country_id']) ||
!is_numeric($params['country_id'])) &&
248 isset($params['country'])
250 $country = new CRM_Core_DAO_Country();
251 $country->name
= $params['country'];
252 if (!$country->find(TRUE)) {
253 $country->name
= NULL;
254 $country->iso_code
= $params['country'];
255 $country->find(TRUE);
257 $params['country_id'] = $country->id
;
260 // add state_id if state is set
261 if ((!isset($params['state_province_id']) ||
!is_numeric($params['state_province_id']))
262 && isset($params['state_province'])
264 if (!empty($params['state_province'])) {
265 $state_province = new CRM_Core_DAO_StateProvince();
266 $state_province->name
= $params['state_province'];
268 // add country id if present
269 if (!empty($params['country_id'])) {
270 $state_province->country_id
= $params['country_id'];
273 if (!$state_province->find(TRUE)) {
274 unset($state_province->name
);
275 $state_province->abbreviation
= $params['state_province'];
276 $state_province->find(TRUE);
278 $params['state_province_id'] = $state_province->id
;
279 if (empty($params['country_id'])) {
280 // set this here since we have it
281 $params['country_id'] = $state_province->country_id
;
285 $params['state_province_id'] = 'null';
289 // add county id if county is set
291 if ((!isset($params['county_id']) ||
!is_numeric($params['county_id']))
292 && isset($params['county']) && !empty($params['county'])
294 $county = new CRM_Core_DAO_County();
295 $county->name
= $params['county'];
297 if (isset($params['state_province_id'])) {
298 $county->state_province_id
= $params['state_province_id'];
301 if ($county->find(TRUE)) {
302 $params['county_id'] = $county->id
;
306 // currently copy values populates empty fields with the string "null"
307 // and hence need to check for the string null
308 if (isset($params['state_province_id']) &&
309 is_numeric($params['state_province_id']) &&
310 (!isset($params['country_id']) ||
empty($params['country_id']))
312 // since state id present and country id not present, hence lets populate it
313 // jira issue http://issues.civicrm.org/jira/browse/CRM-56
314 $params['country_id'] = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_StateProvince',
315 $params['state_province_id'],
320 //special check to ignore non numeric values if they are not
321 //detected by formRule(sometimes happens due to internet latency), also allow user to unselect state/country
322 if (isset($params['state_province_id'])) {
323 if (empty($params['state_province_id'])) {
324 $params['state_province_id'] = 'null';
326 elseif (!is_numeric($params['state_province_id']) ||
327 ((int ) $params['state_province_id'] < 1000)
329 // CRM-3393 ( the hacky 1000 check)
330 $params['state_province_id'] = 'null';
334 if (isset($params['country_id'])) {
335 if (empty($params['country_id'])) {
336 $params['country_id'] = 'null';
338 elseif (!is_numeric($params['country_id']) ||
339 ((int ) $params['country_id'] < 1000)
341 // CRM-3393 ( the hacky 1000 check)
342 $params['country_id'] = 'null';
346 // add state and country names from the ids
347 if (isset($params['state_province_id']) && is_numeric($params['state_province_id'])) {
348 $params['state_province'] = CRM_Core_PseudoConstant
::stateProvinceAbbreviation($params['state_province_id']);
351 if (isset($params['country_id']) && is_numeric($params['country_id'])) {
352 $params['country'] = CRM_Core_PseudoConstant
::country($params['country_id']);
355 $config = CRM_Core_Config
::singleton();
357 $asp = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::ADDRESS_STANDARDIZATION_PREFERENCES_NAME
,
358 'address_standardization_provider'
360 // clean up the address via USPS web services if enabled
361 if ($asp === 'USPS' &&
362 $params['country_id'] == 1228
364 CRM_Utils_Address_USPS
::checkAddress($params);
366 // do street parsing again if enabled, since street address might have changed
367 $parseStreetAddress = CRM_Utils_Array
::value(
368 'street_address_parsing',
369 CRM_Core_BAO_Setting
::valueOptions(
370 CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
376 if ($parseStreetAddress && !empty($params['street_address'])) {
381 'street_number_suffix',
383 unset($params[$fld]);
385 // main parse string.
386 $parseString = CRM_Utils_Array
::value('street_address', $params);
387 $parsedFields = CRM_Core_BAO_Address
::parseStreetAddress($parseString);
389 // merge parse address in to main address block.
390 $params = array_merge($params, $parsedFields);
394 // add latitude and longitude and format address if needed
395 if (!empty($config->geocodeMethod
) && ($config->geocodeMethod
!= 'CRM_Utils_Geocode_OpenStreetMaps') && empty($params['manual_geo_code'])) {
396 $class = $config->geocodeMethod
;
397 $class::format($params);
402 * Check if there is data to create the object.
404 * @param array $params
405 * (reference ) an assoc array of name/value pairs.
409 public static function dataExists(&$params) {
410 //check if location type is set if not return false
411 if (!isset($params['location_type_id'])) {
415 $config = CRM_Core_Config
::singleton();
416 foreach ($params as $name => $value) {
417 if (in_array($name, array(
428 elseif (!CRM_Utils_System
::isNull($value)) {
429 // name could be country or country id
430 if (substr($name, 0, 7) == 'country') {
431 // make sure its different from the default country
433 $defaultCountry = $config->defaultContactCountry();
435 $defaultCountryName = $config->defaultContactCountryName();
437 if ($defaultCountry) {
438 if ($value == $defaultCountry ||
439 $value == $defaultCountryName ||
440 $value == $config->defaultContactCountry
449 // return if null default
463 * Given the list of params in the params array, fetch the object
464 * and store the values in the values array
466 * @param array $entityBlock
467 * Associated array of fields.
468 * @param bool $microformat
469 * If microformat output is required.
470 * @param int|string $fieldName conditional field name
473 * array with address fields
475 public static function &getValues($entityBlock, $microformat = FALSE, $fieldName = 'contact_id') {
476 if (empty($entityBlock)) {
479 $addresses = array();
480 $address = new CRM_Core_BAO_Address();
482 if (empty($entityBlock['entity_table'])) {
483 $address->$fieldName = CRM_Utils_Array
::value($fieldName, $entityBlock);
486 $addressIds = array();
487 $addressIds = self
::allEntityAddress($entityBlock);
489 if (!empty($addressIds[1])) {
490 $address->id
= $addressIds[1];
496 //get primary address as a first block.
497 $address->orderBy('is_primary desc, id');
501 $locationTypes = CRM_Core_PseudoConstant
::get('CRM_Core_DAO_Address', 'location_type_id');
503 while ($address->fetch()) {
504 // deprecate reference.
512 if (isset($address->$fld)) {
513 unset($address->$fld);
517 $stree = $address->street_address
;
519 CRM_Core_DAO
::storeValues($address, $values);
521 // add state and country information: CRM-369
522 if (!empty($address->location_type_id
)) {
523 $values['location_type'] = CRM_Utils_Array
::value($address->location_type_id
, $locationTypes);
525 if (!empty($address->state_province_id
)) {
526 $address->state
= CRM_Core_PseudoConstant
::stateProvinceAbbreviation($address->state_province_id
, FALSE);
527 $address->state_name
= CRM_Core_PseudoConstant
::stateProvince($address->state_province_id
, FALSE);
530 if (!empty($address->country_id
)) {
531 $address->country
= CRM_Core_PseudoConstant
::country($address->country_id
);
534 $regionId = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_Country', $address->country_id
, 'region_id');
536 $address->world_region
= CRM_Core_PseudoConstant
::worldregion($regionId);
539 $address->addDisplay($microformat);
541 $values['display'] = $address->display
;
542 $values['display_text'] = $address->display_text
;
544 if (is_numeric($address->master_id
)) {
545 $values['use_shared_address'] = 1;
548 $addresses[$count] = $values;
550 //unset is_primary after first block. Due to some bug in earlier version
551 //there might be more than one primary blocks, hence unset is_primary other than first
553 unset($addresses[$count]['is_primary']);
563 * Add the formatted address to $this-> display
565 * @param bool $microformat
569 public function addDisplay($microformat = FALSE) {
571 // added this for CRM 1200
572 'address_id' => $this->id
,
574 'address_name' => str_replace('\ 1', ' ', $this->name
),
575 'street_address' => $this->street_address
,
576 'supplemental_address_1' => $this->supplemental_address_1
,
577 'supplemental_address_2' => $this->supplemental_address_2
,
578 'city' => $this->city
,
579 'state_province_name' => isset($this->state_name
) ?
$this->state_name
: "",
580 'state_province' => isset($this->state
) ?
$this->state
: "",
581 'postal_code' => isset($this->postal_code
) ?
$this->postal_code
: "",
582 'postal_code_suffix' => isset($this->postal_code_suffix
) ?
$this->postal_code_suffix
: "",
583 'country' => isset($this->country
) ?
$this->country
: "",
584 'world_region' => isset($this->world_region
) ?
$this->world_region
: "",
587 if (isset($this->county_id
) && $this->county_id
) {
588 $fields['county'] = CRM_Core_PseudoConstant
::county($this->county_id
);
591 $fields['county'] = NULL;
594 $this->display
= CRM_Utils_Address
::format($fields, NULL, $microformat);
595 $this->display_text
= CRM_Utils_Address
::format($fields);
599 * Get all the addresses for a specified contact_id, with the primary address being first
604 * @param bool $updateBlankLocInfo
607 * the array of adrress data
609 public static function allAddress($id, $updateBlankLocInfo = FALSE) {
615 SELECT civicrm_address.id as address_id, civicrm_address.location_type_id as location_type_id
616 FROM civicrm_contact, civicrm_address
617 WHERE civicrm_address.contact_id = civicrm_contact.id AND civicrm_contact.id = %1
618 ORDER BY civicrm_address.is_primary DESC, address_id ASC";
619 $params = array(1 => array($id, 'Integer'));
621 $addresses = array();
622 $dao = CRM_Core_DAO
::executeQuery($query, $params);
624 while ($dao->fetch()) {
625 if ($updateBlankLocInfo) {
626 $addresses[$count++
] = $dao->address_id
;
629 $addresses[$dao->location_type_id
] = $dao->address_id
;
636 * Get all the addresses for a specified location_block id, with the primary address being first
638 * @param array $entityElements
639 * The array containing entity_id and.
643 * the array of adrress data
645 public static function allEntityAddress(&$entityElements) {
646 $addresses = array();
647 if (empty($entityElements)) {
651 $entityId = $entityElements['entity_id'];
652 $entityTable = $entityElements['entity_table'];
655 SELECT civicrm_address.id as address_id
656 FROM civicrm_loc_block loc, civicrm_location_type ltype, civicrm_address, {$entityTable} ev
658 AND loc.id = ev.loc_block_id
659 AND civicrm_address.id IN (loc.address_id, loc.address_2_id)
660 AND ltype.id = civicrm_address.location_type_id
661 ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC, address_id ASC ";
663 $params = array(1 => array($entityId, 'Integer'));
664 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
666 while ($dao->fetch()) {
667 $addresses[$locationCount] = $dao->address_id
;
674 * Get address sequence.
677 * Array of address sequence.
679 public static function addressSequence() {
680 $config = CRM_Core_Config
::singleton();
681 $addressSequence = $config->addressSequence();
683 $countryState = $cityPostal = FALSE;
684 foreach ($addressSequence as $key => $field) {
686 in_array($field, array('country', 'state_province')) &&
689 $countryState = TRUE;
690 $addressSequence[$key] = 'country_state_province';
693 in_array($field, array('city', 'postal_code')) &&
697 $addressSequence[$key] = 'city_postal_code';
700 in_array($field, array('country', 'state_province', 'city', 'postal_code'))
702 unset($addressSequence[$key]);
706 return $addressSequence;
710 * Parse given street address string in to street_name,
711 * street_unit, street_number and street_number_suffix
712 * eg "54A Excelsior Ave. Apt 1C", or "917 1/2 Elm Street"
714 * NB: civic street formats for en_CA and fr_CA used by default if those locales are active
715 * otherwise en_US format is default action
717 * @param string $streetAddress
718 * Street address including number and apt.
719 * @param string $locale
720 * Locale used to parse address.
723 * parsed fields values.
725 public static function parseStreetAddress($streetAddress, $locale = NULL) {
726 $config = CRM_Core_Config
::singleton();
728 /* locales supported include:
729 * en_US - http://pe.usps.com/cpim/ftp/pubs/pub28/pub28.pdf
730 * en_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp
731 * fr_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-f.asp
732 * NB: common use of comma after street number also supported
736 $supportedLocalesForParsing = array('en_US', 'en_CA', 'fr_CA');
738 $locale = $config->lcMessages
;
740 // as different locale explicitly requested but is not available, display warning message and set $locale = 'en_US'
741 if (!in_array($locale, $supportedLocalesForParsing)) {
742 CRM_Core_Session
::setStatus(ts('Unsupported locale specified to parseStreetAddress: %1. Proceeding with en_US locale.', array(1 => $locale)), ts('Unsupported Locale'), 'alert');
745 $emptyParseFields = $parseFields = array(
748 'street_number' => '',
749 'street_number_suffix' => '',
752 if (empty($streetAddress)) {
756 $streetAddress = trim($streetAddress);
759 if (in_array($locale, array(
762 )) && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches)
764 $parseFields['street_unit'] = $matches[1];
765 // unset from rest of street address
766 $streetAddress = preg_replace('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', '', $streetAddress);
769 // get street number and suffix.
771 //alter street number/suffix handling so that we accept -digit
772 if (preg_match('/^[A-Za-z0-9]+([\S]+)/', $streetAddress, $matches)) {
773 // check that $matches[0] is numeric, else assume no street number
774 if (preg_match('/^(\d+)/', $matches[0])) {
775 $streetNumAndSuffix = $matches[0];
777 // get street number.
779 if (preg_match('/^(\d+)/', $streetNumAndSuffix, $matches)) {
780 $parseFields['street_number'] = $matches[0];
781 $suffix = preg_replace('/^(\d+)/', '', $streetNumAndSuffix);
782 $parseFields['street_number_suffix'] = trim($suffix);
785 // unset from main street address.
786 $streetAddress = preg_replace('/^[A-Za-z0-9]+([\S]+)/', '', $streetAddress);
787 $streetAddress = trim($streetAddress);
790 elseif (preg_match('/^(\d+)/', $streetAddress, $matches)) {
791 $parseFields['street_number'] = $matches[0];
792 // unset from main street address.
793 $streetAddress = preg_replace('/^(\d+)/', '', $streetAddress);
794 $streetAddress = trim($streetAddress);
797 // suffix might be like 1/2
799 if (preg_match('/^\d\/\d/', $streetAddress, $matches)) {
800 $parseFields['street_number_suffix'] .= $matches[0];
802 // unset from main street address.
803 $streetAddress = preg_replace('/^\d+\/\d+/', '', $streetAddress);
804 $streetAddress = trim($streetAddress);
807 // now get the street unit.
808 // supportable street unit formats.
809 $streetUnitFormats = array(
852 // overwriting $streetUnitFormats for 'en_CA' and 'fr_CA' locale
853 if (in_array($locale, array(
857 $streetUnitFormats = array('APT', 'APP', 'SUITE', 'BUREAU', 'UNIT');
859 //@todo per CRM-14459 this regex picks up words with the string in them - e.g APT picks up
860 //Captain - presuming fixing regex (& adding test) to ensure a-z does not preced string will fix
861 $streetUnitPreg = '/(' . implode('|\s', $streetUnitFormats) . ')(.+)?/i';
863 if (preg_match($streetUnitPreg, $streetAddress, $matches)) {
864 $parseFields['street_unit'] = trim($matches[0]);
865 $streetAddress = str_replace($matches[0], '', $streetAddress);
866 $streetAddress = trim($streetAddress);
869 // consider remaining string as street name.
870 $parseFields['street_name'] = $streetAddress;
872 //run parsed fields through stripSpaces to clean
873 foreach ($parseFields as $parseField => $value) {
874 $parseFields[$parseField] = CRM_Utils_String
::stripSpaces($value);
876 //CRM-14459 if the field is too long we should assume it didn't get it right & skip rather than allow
878 $fields = CRM_Core_BAO_Address
::fields();
879 foreach ($fields as $fieldname => $field) {
880 if (!empty($field['maxlength']) && strlen(CRM_Utils_Array
::value($fieldname, $parseFields)) > $field['maxlength']) {
881 return $emptyParseFields;
889 * Validate the address fields based on the address options enabled.
890 * in the Address Settings
892 * @param array $fields
893 * An array of importable/exportable contact fields.
896 * an array of contact fields and only the enabled address options
898 public static function validateAddressOptions($fields) {
899 static $addressOptions = NULL;
900 if (!$addressOptions) {
901 $addressOptions = CRM_Core_BAO_Setting
::valueOptions(
902 CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
907 if (is_array($fields) && !empty($fields)) {
908 foreach ($addressOptions as $key => $value) {
909 if (!$value && isset($fields[$key])) {
910 unset($fields[$key]);
918 * Check if current address is used by any other contacts.
920 * @param int $addressId
924 * count of contacts that use this shared address
926 public static function checkContactSharedAddress($addressId) {
927 $query = 'SELECT count(id) FROM civicrm_address WHERE master_id = %1';
928 return CRM_Core_DAO
::singleValueQuery($query, array(1 => array($addressId, 'Integer')));
932 * Check if current address fields are shared with any other address.
934 * @param array $fields
935 * Address fields in profile.
936 * @param int $contactId
940 public static function checkContactSharedAddressFields(&$fields, $contactId) {
941 if (!$contactId ||
!is_array($fields) ||
empty($fields)) {
945 $sharedLocations = array();
951 WHERE contact_id = %1
952 AND master_id IS NOT NULL";
954 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($contactId, 'Positive')));
955 while ($dao->fetch()) {
956 $sharedLocations[$dao->location_type_id
] = $dao->location_type_id
;
957 if ($dao->is_primary
) {
958 $sharedLocations['Primary'] = 'Primary';
962 //no need to process further.
963 if (empty($sharedLocations)) {
967 $addressFields = array(
977 'postal_code_suffix',
978 'supplemental_address_1',
979 'supplemental_address_2',
982 foreach ($fields as $name => & $values) {
983 if (!is_array($values) ||
empty($values)) {
987 $nameVal = explode('-', $values['name']);
988 $fldName = CRM_Utils_Array
::value(0, $nameVal);
989 $locType = CRM_Utils_Array
::value(1, $nameVal);
990 if (!empty($values['location_type_id'])) {
991 $locType = $values['location_type_id'];
994 if (in_array($fldName, $addressFields) &&
995 in_array($locType, $sharedLocations)
997 $values['is_shared'] = TRUE;
1003 * Update the shared addresses if master address is modified.
1005 * @param int $addressId
1007 * @param array $params
1008 * Associated array of address params.
1012 public static function processSharedAddress($addressId, $params) {
1013 $query = 'SELECT id FROM civicrm_address WHERE master_id = %1';
1014 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($addressId, 'Integer')));
1017 $skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id', 'contact_id');
1018 foreach ($skipFields as $value) {
1019 unset($params[$value]);
1022 $addressDAO = new CRM_Core_DAO_Address();
1023 while ($dao->fetch()) {
1024 $addressDAO->copyValues($params);
1025 $addressDAO->id
= $dao->id
;
1026 $addressDAO->save();
1027 $addressDAO->free();
1032 * Merge contacts with the Same address to get one shared label.
1033 * @param array $rows
1034 * Array[contact_id][contactDetails].
1036 public static function mergeSameAddress(&$rows) {
1037 $uniqueAddress = array();
1038 foreach (array_keys($rows) as $rowID) {
1039 // load complete address as array key
1040 $address = trim($rows[$rowID]['street_address'])
1041 . trim($rows[$rowID]['city'])
1042 . trim($rows[$rowID]['state_province'])
1043 . trim($rows[$rowID]['postal_code'])
1044 . trim($rows[$rowID]['country']);
1045 if (isset($rows[$rowID]['last_name'])) {
1046 $name = $rows[$rowID]['last_name'];
1049 $name = $rows[$rowID]['display_name'];
1054 'first_name' => $rows[$rowID]['first_name'],
1055 'individual_prefix' => $rows[$rowID]['individual_prefix'],
1057 $format = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'display_name_format');
1058 $firstNameWithPrefix = CRM_Utils_Address
::format($formatted, $format, FALSE, FALSE, TRUE);
1059 $firstNameWithPrefix = trim($firstNameWithPrefix);
1061 // fill uniqueAddress array with last/first name tree
1062 if (isset($uniqueAddress[$address])) {
1063 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
1064 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
1065 // drop unnecessary rows
1066 unset($rows[$rowID]);
1067 // this is the first listing at this address
1070 $uniqueAddress[$address]['ID'] = $rowID;
1071 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
1072 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
1075 foreach ($uniqueAddress as $address => $data) {
1076 // copy data back to $rows
1078 // one last name list per row
1079 foreach ($data['names'] as $last_name => $first_names) {
1084 if (count($first_names) == 1) {
1085 $family = $first_names[current(array_keys($first_names))]['addressee_display'];
1088 // collapse the tree to summarize
1089 $family = trim(implode(" & ", array_keys($first_names)) . " " . $last_name);
1092 $processedNames .= "\n" . $family;
1095 // build display_name string
1096 $processedNames = $family;
1100 $rows[$data['ID']]['addressee'] = $rows[$data['ID']]['addressee_display'] = $rows[$data['ID']]['display_name'] = $processedNames;
1105 * Create relationship between contacts who share an address.
1107 * Note that currently we create relationship only for Individual contacts
1108 * Individual + Household and Individual + Orgnization
1110 * @param int $masterAddressId
1111 * Master address id.
1112 * @param array $params
1113 * Associated array of submitted values.
1117 public static function processSharedAddressRelationship($masterAddressId, $params) {
1118 if (!$masterAddressId) {
1121 // get the contact type of contact being edited / created
1122 $currentContactType = CRM_Contact_BAO_Contact
::getContactType($params['contact_id']);
1123 $currentContactId = $params['contact_id'];
1125 // if current contact is not of type individual return
1126 if ($currentContactType != 'Individual') {
1130 // get the contact id and contact type of shared contact
1131 // check the contact type of shared contact, return if it is of type Individual
1133 $query = 'SELECT cc.id, cc.contact_type
1134 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1137 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($masterAddressId, 'Integer')));
1141 // if current contact is not of type individual return, since we don't create relationship between
1143 if ($dao->contact_type
== 'Individual') {
1146 $sharedContactType = $dao->contact_type
;
1147 $sharedContactId = $dao->id
;
1149 // create relationship between ontacts who share an address
1150 if ($sharedContactType == 'Organization') {
1151 return CRM_Contact_BAO_Contact_Utils
::createCurrentEmployerRelationship($currentContactId, $sharedContactId);
1154 // get the relationship type id of "Household Member of"
1155 $relationshipType = 'Household Member of';
1158 $cid = array('contact' => $currentContactId);
1160 $relTypeId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', $relationshipType, 'id', 'name_a_b');
1163 CRM_Core_Error
::fatal(ts("You seem to have deleted the relationship type '%1'", array(1 => $relationshipType)));
1166 // create relationship
1167 $relationshipParams = array(
1168 'is_active' => TRUE,
1169 'relationship_type_id' => $relTypeId . '_a_b',
1170 'contact_check' => array($sharedContactId => TRUE),
1173 list($valid, $invalid, $duplicate,
1174 $saved, $relationshipIds
1175 ) = CRM_Contact_BAO_Relationship
::createMultiple($relationshipParams, $cid);
1179 * Check and set the status for shared address delete.
1181 * @param int $addressId
1183 * @param int $contactId
1185 * @param bool $returnStatus
1190 public static function setSharedAddressDeleteStatus($addressId = NULL, $contactId = NULL, $returnStatus = FALSE) {
1191 // check if address that is being deleted has any shared
1193 $entityId = $addressId;
1194 $query = 'SELECT cc.id, cc.display_name
1195 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1196 WHERE ca.master_id = %1';
1199 $entityId = $contactId;
1200 $query = 'SELECT cc.id, cc.display_name
1201 FROM civicrm_address ca1
1202 INNER JOIN civicrm_address ca2 ON ca1.id = ca2.master_id
1203 INNER JOIN civicrm_contact cc ON ca2.contact_id = cc.id
1204 WHERE ca1.contact_id = %1';
1207 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($entityId, 'Integer')));
1209 $deleteStatus = array();
1210 $sharedContactList = array();
1211 $statusMessage = NULL;
1213 while ($dao->fetch()) {
1214 if (empty($deleteStatus)) {
1215 $deleteStatus[] = ts('The following contact(s) have address records which were shared with the address you removed from this contact. These address records are no longer shared - but they have not been removed or altered.');
1218 $contactViewUrl = CRM_Utils_System
::url('civicrm/contact/view', "reset=1&cid={$dao->id}");
1219 $sharedContactList[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1220 $deleteStatus[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1225 if (!empty($deleteStatus)) {
1226 $statusMessage = implode('<br/>', $deleteStatus) . '<br/>';
1229 if (!$returnStatus) {
1230 CRM_Core_Session
::setStatus($statusMessage, '', 'info');
1234 'contactList' => $sharedContactList,
1235 'count' => $addressCount,
1241 * Call common delete function.
1243 public static function del($id) {
1244 return CRM_Contact_BAO_Contact
::deleteObjectWithPrimary('Address', $id);
1248 * Get options for a given address field.
1249 * @see CRM_Core_DAO::buildOptions
1251 * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow?
1252 * TODO: In context of chainselect, what to return if e.g. a country has no states?
1254 * @param string $fieldName
1255 * @param string $context
1256 * @see CRM_Core_DAO::buildOptionsContext
1257 * @param array $props
1258 * whatever is known about this dao object.
1260 * @return array|bool
1262 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
1264 // Special logic for fields whose options depend on context or properties
1265 switch ($fieldName) {
1266 // Filter state_province list based on chosen country or site defaults
1267 case 'state_province_id':
1268 if (empty($props['country_id'])) {
1269 $config = CRM_Core_Config
::singleton();
1270 if (!empty($config->provinceLimit
)) {
1271 $props['country_id'] = $config->provinceLimit
;
1274 $props['country_id'] = $config->defaultContactCountry
;
1277 if (!empty($props['country_id']) && $context !== 'validate') {
1278 $params['condition'] = 'country_id IN (' . implode(',', (array) $props['country_id']) . ')';
1282 // Filter country list based on site defaults
1284 if ($context != 'get' && $context != 'validate') {
1285 $config = CRM_Core_Config
::singleton();
1286 if (!empty($config->countryLimit
) && is_array($config->countryLimit
)) {
1287 $params['condition'] = 'id IN (' . implode(',', $config->countryLimit
) . ')';
1292 // Filter county list based on chosen state
1294 if (!empty($props['state_province_id'])) {
1295 $params['condition'] = 'state_province_id IN (' . implode(',', (array) $props['state_province_id']) . ')';
1299 // Not a real field in this entity
1300 case 'world_region':
1301 return CRM_Core_PseudoConstant
::worldRegion();
1303 return CRM_Core_PseudoConstant
::get(__CLASS__
, $fieldName, $params, $context);