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
52 * @return array $blocks 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 object CRM_Core_BAO_Address object on success, null otherwise
147 public static function add(&$params, $fixAddress) {
148 static $customFields = NULL;
149 $address = new CRM_Core_DAO_Address();
151 // fixAddress mode to be done
153 CRM_Core_BAO_Address
::fixAddress($params);
156 $hook = empty($params['id']) ?
'create' : 'edit';
157 CRM_Utils_Hook
::pre($hook, 'Address', CRM_Utils_Array
::value('id', $params), $params);
159 // if id is set & is_primary isn't we can assume no change
160 if (is_numeric(CRM_Utils_Array
::value('is_primary', $params)) ||
empty($params['id'])) {
161 CRM_Core_BAO_Block
::handlePrimary($params, get_class());
163 $config = CRM_Core_Config
::singleton();
164 $address->copyValues($params);
169 if (!$customFields) {
170 $customFields = CRM_Core_BAO_CustomField
::getFields('Address', FALSE, TRUE);
172 if (!empty($customFields)) {
173 $addressCustom = CRM_Core_BAO_CustomField
::postProcess($params,
180 if (!empty($addressCustom)) {
181 CRM_Core_BAO_CustomValueTable
::store($addressCustom, 'civicrm_address', $address->id
);
184 //call the function to sync shared address
185 self
::processSharedAddress($address->id
, $params);
187 // call the function to create shared relationships
188 // we only create create relationship if address is shared by Individual
189 if ($address->master_id
!= 'null') {
190 self
::processSharedAddressRelationship($address->master_id
, $params);
193 // lets call the post hook only after we've done all the follow on processing
194 CRM_Utils_Hook
::post($hook, 'Address', $address->id
, $address);
201 * Format the address params to have reasonable values
203 * @param array $params
204 * (reference ) an assoc array of name/value pairs.
209 public static function fixAddress(&$params) {
210 if (!empty($params['billing_street_address'])) {
211 //Check address is comming from online contribution / registration page
214 'street_address' => 'billing_street_address',
215 'city' => 'billing_city',
216 'postal_code' => 'billing_postal_code',
217 'state_province' => 'billing_state_province',
218 'state_province_id' => 'billing_state_province_id',
219 'country' => 'billing_country',
220 'country_id' => 'billing_country_id',
223 foreach ($billing as $key => $val) {
224 if ($value = CRM_Utils_Array
::value($val, $params)) {
225 if (!empty($params[$key])) {
226 unset($params[$val]);
229 //add new key and removed old
230 $params[$key] = $value;
231 unset($params[$val]);
237 /* Split the zip and +4, if it's in US format */
238 if (!empty($params['postal_code']) &&
239 preg_match('/^(\d{4,5})[+-](\d{4})$/',
240 $params['postal_code'],
244 $params['postal_code'] = $match[1];
245 $params['postal_code_suffix'] = $match[2];
248 // add country id if not set
249 if ((!isset($params['country_id']) ||
!is_numeric($params['country_id'])) &&
250 isset($params['country'])
252 $country = new CRM_Core_DAO_Country();
253 $country->name
= $params['country'];
254 if (!$country->find(TRUE)) {
255 $country->name
= NULL;
256 $country->iso_code
= $params['country'];
257 $country->find(TRUE);
259 $params['country_id'] = $country->id
;
262 // add state_id if state is set
263 if ((!isset($params['state_province_id']) ||
!is_numeric($params['state_province_id']))
264 && isset($params['state_province'])
266 if (!empty($params['state_province'])) {
267 $state_province = new CRM_Core_DAO_StateProvince();
268 $state_province->name
= $params['state_province'];
270 // add country id if present
271 if (!empty($params['country_id'])) {
272 $state_province->country_id
= $params['country_id'];
275 if (!$state_province->find(TRUE)) {
276 unset($state_province->name
);
277 $state_province->abbreviation
= $params['state_province'];
278 $state_province->find(TRUE);
280 $params['state_province_id'] = $state_province->id
;
281 if (empty($params['country_id'])) {
282 // set this here since we have it
283 $params['country_id'] = $state_province->country_id
;
287 $params['state_province_id'] = 'null';
291 // add county id if county is set
293 if ((!isset($params['county_id']) ||
!is_numeric($params['county_id']))
294 && isset($params['county']) && !empty($params['county'])
296 $county = new CRM_Core_DAO_County();
297 $county->name
= $params['county'];
299 if (isset($params['state_province_id'])) {
300 $county->state_province_id
= $params['state_province_id'];
303 if ($county->find(TRUE)) {
304 $params['county_id'] = $county->id
;
308 // currently copy values populates empty fields with the string "null"
309 // and hence need to check for the string null
310 if (isset($params['state_province_id']) &&
311 is_numeric($params['state_province_id']) &&
312 (!isset($params['country_id']) ||
empty($params['country_id']))
314 // since state id present and country id not present, hence lets populate it
315 // jira issue http://issues.civicrm.org/jira/browse/CRM-56
316 $params['country_id'] = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_StateProvince',
317 $params['state_province_id'],
322 //special check to ignore non numeric values if they are not
323 //detected by formRule(sometimes happens due to internet latency), also allow user to unselect state/country
324 if (isset($params['state_province_id'])) {
325 if (empty($params['state_province_id'])) {
326 $params['state_province_id'] = 'null';
328 elseif (!is_numeric($params['state_province_id']) ||
329 ((int ) $params['state_province_id'] < 1000)
331 // CRM-3393 ( the hacky 1000 check)
332 $params['state_province_id'] = 'null';
336 if (isset($params['country_id'])) {
337 if (empty($params['country_id'])) {
338 $params['country_id'] = 'null';
340 elseif (!is_numeric($params['country_id']) ||
341 ((int ) $params['country_id'] < 1000)
343 // CRM-3393 ( the hacky 1000 check)
344 $params['country_id'] = 'null';
348 // add state and country names from the ids
349 if (isset($params['state_province_id']) && is_numeric($params['state_province_id'])) {
350 $params['state_province'] = CRM_Core_PseudoConstant
::stateProvinceAbbreviation($params['state_province_id']);
353 if (isset($params['country_id']) && is_numeric($params['country_id'])) {
354 $params['country'] = CRM_Core_PseudoConstant
::country($params['country_id']);
357 $config = CRM_Core_Config
::singleton();
359 $asp = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::ADDRESS_STANDARDIZATION_PREFERENCES_NAME
,
360 'address_standardization_provider'
362 // clean up the address via USPS web services if enabled
363 if ($asp === 'USPS' &&
364 $params['country_id'] == 1228
366 CRM_Utils_Address_USPS
::checkAddress($params);
368 // do street parsing again if enabled, since street address might have changed
369 $parseStreetAddress =
370 CRM_Utils_Array
::value(
371 'street_address_parsing',
372 CRM_Core_BAO_Setting
::valueOptions(
373 CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
379 if ($parseStreetAddress && !empty($params['street_address'])) {
381 'street_number', 'street_name', 'street_unit', 'street_number_suffix') as $fld) {
382 unset($params[$fld]);
384 // main parse string.
385 $parseString = CRM_Utils_Array
::value('street_address', $params);
386 $parsedFields = CRM_Core_BAO_Address
::parseStreetAddress($parseString);
388 // merge parse address in to main address block.
389 $params = array_merge($params, $parsedFields);
393 // add latitude and longitude and format address if needed
394 if (!empty($config->geocodeMethod
) && ($config->geocodeMethod
!= 'CRM_Utils_Geocode_OpenStreetMaps') && empty($params['manual_geo_code'])) {
395 $class = $config->geocodeMethod
;
396 $class::format($params);
401 * Check if there is data to create the object
403 * @param array $params
404 * (reference ) an assoc array of name/value pairs.
410 public static function dataExists(&$params) {
411 //check if location type is set if not return false
412 if (!isset($params['location_type_id'])) {
416 $config = CRM_Core_Config
::singleton();
417 foreach ($params as $name => $value) {
418 if (in_array($name, array(
419 'is_primary', 'location_type_id', 'id', 'contact_id', 'is_billing', 'display', 'master_id'))) {
422 elseif (!CRM_Utils_System
::isNull($value)) {
423 // name could be country or country id
424 if (substr($name, 0, 7) == 'country') {
425 // make sure its different from the default country
427 $defaultCountry = $config->defaultContactCountry();
429 $defaultCountryName = $config->defaultContactCountryName();
431 if ($defaultCountry) {
432 if ($value == $defaultCountry ||
433 $value == $defaultCountryName ||
434 $value == $config->defaultContactCountry
443 // return if null default
457 * Given the list of params in the params array, fetch the object
458 * and store the values in the values array
460 * @param array $entityBlock
461 * Associated array of fields.
462 * @param bool $microformat
463 * If microformat output is required.
464 * @param int|string $fieldName conditional field name
466 * @return array $addresses array with address fields
469 public static function &getValues($entityBlock, $microformat = FALSE, $fieldName = 'contact_id') {
470 if (empty($entityBlock)) {
473 $addresses = array();
474 $address = new CRM_Core_BAO_Address();
476 if (empty($entityBlock['entity_table'])) {
477 $address->$fieldName = CRM_Utils_Array
::value($fieldName, $entityBlock);
480 $addressIds = array();
481 $addressIds = self
::allEntityAddress($entityBlock);
483 if (!empty($addressIds[1])) {
484 $address->id
= $addressIds[1];
490 //get primary address as a first block.
491 $address->orderBy('is_primary desc, id');
496 while ($address->fetch()) {
497 // deprecate reference.
500 'state', 'state_name', 'country', 'world_region') as $fld) {
501 if (isset($address->$fld)) { unset($address->$fld);
505 $stree = $address->street_address
;
507 CRM_Core_DAO
::storeValues($address, $values);
509 // add state and country information: CRM-369
510 if (!empty($address->state_province_id
)) {
511 $address->state
= CRM_Core_PseudoConstant
::stateProvinceAbbreviation($address->state_province_id
, FALSE);
512 $address->state_name
= CRM_Core_PseudoConstant
::stateProvince($address->state_province_id
, FALSE);
515 if (!empty($address->country_id
)) {
516 $address->country
= CRM_Core_PseudoConstant
::country($address->country_id
);
519 $regionId = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_Country', $address->country_id
, 'region_id');
521 $address->world_region
= CRM_Core_PseudoConstant
::worldregion($regionId);
524 $address->addDisplay($microformat);
526 $values['display'] = $address->display
;
527 $values['display_text'] = $address->display_text
;
529 if (is_numeric($address->master_id
)) {
530 $values['use_shared_address'] = 1;
533 $addresses[$count] = $values;
535 //unset is_primary after first block. Due to some bug in earlier version
536 //there might be more than one primary blocks, hence unset is_primary other than first
538 unset($addresses[$count]['is_primary']);
548 * Add the formatted address to $this-> display
550 * @param bool $microformat
554 public function addDisplay($microformat = FALSE) {
556 // added this for CRM 1200
557 'address_id' => $this->id
,
559 'address_name' => str_replace('\ 1', ' ', $this->name
),
560 'street_address' => $this->street_address
,
561 'supplemental_address_1' => $this->supplemental_address_1
,
562 'supplemental_address_2' => $this->supplemental_address_2
,
563 'city' => $this->city
,
564 'state_province_name' => isset($this->state_name
) ?
$this->state_name
: "",
565 'state_province' => isset($this->state
) ?
$this->state
: "",
566 'postal_code' => isset($this->postal_code
) ?
$this->postal_code
: "",
567 'postal_code_suffix' => isset($this->postal_code_suffix
) ?
$this->postal_code_suffix
: "",
568 'country' => isset($this->country
) ?
$this->country
: "",
569 'world_region' => isset($this->world_region
) ?
$this->world_region
: "",
572 if (isset($this->county_id
) && $this->county_id
) {
573 $fields['county'] = CRM_Core_PseudoConstant
::county($this->county_id
);
576 $fields['county'] = NULL;
579 $this->display
= CRM_Utils_Address
::format($fields, NULL, $microformat);
580 $this->display_text
= CRM_Utils_Address
::format($fields);
584 * Get all the addresses for a specified contact_id, with the primary address being first
589 * @param bool $updateBlankLocInfo
591 * @return array the array of adrress data
594 public static function allAddress($id, $updateBlankLocInfo = FALSE) {
600 SELECT civicrm_address.id as address_id, civicrm_address.location_type_id as location_type_id
601 FROM civicrm_contact, civicrm_address
602 WHERE civicrm_address.contact_id = civicrm_contact.id AND civicrm_contact.id = %1
603 ORDER BY civicrm_address.is_primary DESC, address_id ASC";
604 $params = array(1 => array($id, 'Integer'));
606 $addresses = array();
607 $dao = CRM_Core_DAO
::executeQuery($query, $params);
609 while ($dao->fetch()) {
610 if ($updateBlankLocInfo) {
611 $addresses[$count++
] = $dao->address_id
;
614 $addresses[$dao->location_type_id
] = $dao->address_id
;
621 * Get all the addresses for a specified location_block id, with the primary address being first
623 * @param array $entityElements
624 * The array containing entity_id and.
627 * @return array the array of adrress data
630 public static function allEntityAddress(&$entityElements) {
631 $addresses = array();
632 if (empty($entityElements)) {
636 $entityId = $entityElements['entity_id'];
637 $entityTable = $entityElements['entity_table'];
640 SELECT civicrm_address.id as address_id
641 FROM civicrm_loc_block loc, civicrm_location_type ltype, civicrm_address, {$entityTable} ev
643 AND loc.id = ev.loc_block_id
644 AND civicrm_address.id IN (loc.address_id, loc.address_2_id)
645 AND ltype.id = civicrm_address.location_type_id
646 ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC, address_id ASC ";
648 $params = array(1 => array($entityId, 'Integer'));
649 $dao = CRM_Core_DAO
::executeQuery($sql, $params);
651 while ($dao->fetch()) {
652 $addresses[$locationCount] = $dao->address_id
;
659 * Get address sequence
661 * @return array of address sequence.
663 public static function addressSequence() {
664 $config = CRM_Core_Config
::singleton();
665 $addressSequence = $config->addressSequence();
667 $countryState = $cityPostal = FALSE;
668 foreach ($addressSequence as $key => $field) {
670 in_array($field, array('country', 'state_province')) &&
673 $countryState = TRUE;
674 $addressSequence[$key] = 'country_state_province';
677 in_array($field, array('city', 'postal_code')) &&
681 $addressSequence[$key] = 'city_postal_code';
684 in_array($field, array('country', 'state_province', 'city', 'postal_code'))
686 unset($addressSequence[$key]);
690 return $addressSequence;
694 * Parse given street address string in to street_name,
695 * street_unit, street_number and street_number_suffix
696 * eg "54A Excelsior Ave. Apt 1C", or "917 1/2 Elm Street"
698 * NB: civic street formats for en_CA and fr_CA used by default if those locales are active
699 * otherwise en_US format is default action
701 * @param string Street address including number and apt
702 * @param string Locale - to set locale used to parse address
704 * @return array $parseFields parsed fields values.
707 public static function parseStreetAddress($streetAddress, $locale = NULL) {
708 $config = CRM_Core_Config
::singleton();
710 /* locales supported include:
711 * en_US - http://pe.usps.com/cpim/ftp/pubs/pub28/pub28.pdf
712 * en_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp
713 * fr_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-f.asp
714 * NB: common use of comma after street number also supported
718 $supportedLocalesForParsing = array('en_US', 'en_CA', 'fr_CA');
720 $locale = $config->lcMessages
;
722 // as different locale explicitly requested but is not available, display warning message and set $locale = 'en_US'
723 if (!in_array($locale, $supportedLocalesForParsing)) {
724 CRM_Core_Session
::setStatus(ts('Unsupported locale specified to parseStreetAddress: %1. Proceeding with en_US locale.', array(1 => $locale)), ts('Unsupported Locale'), 'alert');
727 $emptyParseFields = $parseFields = array(
730 'street_number' => '',
731 'street_number_suffix' => '',
734 if (empty($streetAddress)) {
738 $streetAddress = trim($streetAddress);
741 if (in_array($locale, array(
742 'en_CA', 'fr_CA')) && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches)) {
743 $parseFields['street_unit'] = $matches[1];
744 // unset from rest of street address
745 $streetAddress = preg_replace('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', '', $streetAddress);
748 // get street number and suffix.
750 //alter street number/suffix handling so that we accept -digit
751 if (preg_match('/^[A-Za-z0-9]+([\S]+)/', $streetAddress, $matches)) {
752 // check that $matches[0] is numeric, else assume no street number
753 if (preg_match('/^(\d+)/', $matches[0])) {
754 $streetNumAndSuffix = $matches[0];
756 // get street number.
758 if (preg_match('/^(\d+)/', $streetNumAndSuffix, $matches)) {
759 $parseFields['street_number'] = $matches[0];
760 $suffix = preg_replace('/^(\d+)/', '', $streetNumAndSuffix);
761 $parseFields['street_number_suffix'] = trim($suffix);
764 // unset from main street address.
765 $streetAddress = preg_replace('/^[A-Za-z0-9]+([\S]+)/', '', $streetAddress);
766 $streetAddress = trim($streetAddress);
769 elseif (preg_match('/^(\d+)/', $streetAddress, $matches)) {
770 $parseFields['street_number'] = $matches[0];
771 // unset from main street address.
772 $streetAddress = preg_replace('/^(\d+)/', '', $streetAddress);
773 $streetAddress = trim($streetAddress);
776 // suffix might be like 1/2
778 if (preg_match('/^\d\/\d/', $streetAddress, $matches)) {
779 $parseFields['street_number_suffix'] .= $matches[0];
781 // unset from main street address.
782 $streetAddress = preg_replace('/^\d+\/\d+/', '', $streetAddress);
783 $streetAddress = trim($streetAddress);
786 // now get the street unit.
787 // supportable street unit formats.
788 $streetUnitFormats = array(
789 'APT', 'APARTMENT', 'BSMT', 'BASEMENT', 'BLDG', 'BUILDING',
790 'DEPT', 'DEPARTMENT', 'FL', 'FLOOR', 'FRNT', 'FRONT',
791 'HNGR', 'HANGER', 'LBBY', 'LOBBY', 'LOWR', 'LOWER',
792 'OFC', 'OFFICE', 'PH', 'PENTHOUSE', 'TRLR', 'TRAILER',
793 'UPPR', 'RM', 'ROOM', 'SIDE', 'SLIP', 'KEY',
794 'LOT', 'PIER', 'REAR', 'SPC', 'SPACE',
795 'STOP', 'STE', 'SUITE', 'UNIT', '#',
798 // overwriting $streetUnitFormats for 'en_CA' and 'fr_CA' locale
799 if (in_array($locale, array(
800 'en_CA', 'fr_CA'))) {
801 $streetUnitFormats = array('APT', 'APP', 'SUITE', 'BUREAU', 'UNIT');
803 //@todo per CRM-14459 this regex picks up words with the string in them - e.g APT picks up
804 //Captain - presuming fixing regex (& adding test) to ensure a-z does not preced string will fix
805 $streetUnitPreg = '/(' . implode('|\s', $streetUnitFormats) . ')(.+)?/i';
807 if (preg_match($streetUnitPreg, $streetAddress, $matches)) {
808 $parseFields['street_unit'] = trim($matches[0]);
809 $streetAddress = str_replace($matches[0], '', $streetAddress);
810 $streetAddress = trim($streetAddress);
813 // consider remaining string as street name.
814 $parseFields['street_name'] = $streetAddress;
816 //run parsed fields through stripSpaces to clean
817 foreach ($parseFields as $parseField => $value) {
818 $parseFields[$parseField] = CRM_Utils_String
::stripSpaces($value);
820 //CRM-14459 if the field is too long we should assume it didn't get it right & skip rather than allow
822 $fields = CRM_Core_BAO_Address
::fields();
823 foreach ($fields as $fieldname => $field) {
824 if (!empty($field['maxlength']) && strlen(CRM_Utils_Array
::value($fieldname, $parseFields)) > $field['maxlength']) {
825 return $emptyParseFields;
833 * Validate the address fields based on the address options enabled
834 * in the Address Settings
836 * @param array $fields
837 * An array of importable/exportable contact fields.
839 * @return array $fields an array of contact fields and only the enabled address options
842 public static function validateAddressOptions($fields) {
843 static $addressOptions = NULL;
844 if (!$addressOptions) {
846 CRM_Core_BAO_Setting
::valueOptions(
847 CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
,
852 if (is_array($fields) && !empty($fields)) {
853 foreach ($addressOptions as $key => $value) {
854 if (!$value && isset($fields[$key])) {
855 unset($fields[$key]);
863 * Check if current address is used by any other contacts
865 * @param int $addressId
868 * @return count of contacts that use this shared address
871 public static function checkContactSharedAddress($addressId) {
872 $query = 'SELECT count(id) FROM civicrm_address WHERE master_id = %1';
873 return CRM_Core_DAO
::singleValueQuery($query, array(1 => array($addressId, 'Integer')));
877 * Check if current address fields are shared with any other address
879 * @param array $fields
880 * Address fields in profile.
881 * @param int $contactId
886 public static function checkContactSharedAddressFields(&$fields, $contactId) {
887 if (!$contactId ||
!is_array($fields) ||
empty($fields)) {
891 $sharedLocations = array();
897 WHERE contact_id = %1
898 AND master_id IS NOT NULL";
900 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($contactId, 'Positive')));
901 while ($dao->fetch()) {
902 $sharedLocations[$dao->location_type_id
] = $dao->location_type_id
;
903 if ($dao->is_primary
) {
904 $sharedLocations['Primary'] = 'Primary';
908 //no need to process further.
909 if (empty($sharedLocations)) {
913 $addressFields = array(
923 'postal_code_suffix',
924 'supplemental_address_1',
925 'supplemental_address_2',
928 foreach ($fields as $name => & $values) {
929 if (!is_array($values) ||
empty($values)) {
933 $nameVal = explode('-', $values['name']);
934 $fldName = CRM_Utils_Array
::value(0, $nameVal);
935 $locType = CRM_Utils_Array
::value(1, $nameVal);
936 if (!empty($values['location_type_id'])) {
937 $locType = $values['location_type_id'];
940 if (in_array($fldName, $addressFields) &&
941 in_array($locType, $sharedLocations)
943 $values['is_shared'] = TRUE;
949 * Update the shared addresses if master address is modified
951 * @param int $addressId
953 * @param array $params
954 * Associated array of address params.
959 public static function processSharedAddress($addressId, $params) {
960 $query = 'SELECT id FROM civicrm_address WHERE master_id = %1';
961 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($addressId, 'Integer')));
964 $skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id', 'contact_id');
965 foreach ($skipFields as $value) {
966 unset($params[$value]);
969 $addressDAO = new CRM_Core_DAO_Address();
970 while ($dao->fetch()) {
971 $addressDAO->copyValues($params);
972 $addressDAO->id
= $dao->id
;
979 * Merge contacts with the Same address to get one shared label
981 * Array[contact_id][contactDetails].
983 public static function mergeSameAddress(&$rows) {
984 $uniqueAddress = array();
985 foreach (array_keys($rows) as $rowID) {
986 // load complete address as array key
988 trim($rows[$rowID]['street_address']) . trim($rows[$rowID]['city']) . trim($rows[$rowID]['state_province']) . trim($rows[$rowID]['postal_code']) . trim($rows[$rowID]['country']);
989 if (isset($rows[$rowID]['last_name'])) {
990 $name = $rows[$rowID]['last_name'];
993 $name = $rows[$rowID]['display_name'];
998 'first_name' => $rows[$rowID]['first_name'],
999 'individual_prefix' => $rows[$rowID]['individual_prefix']
1001 $format = CRM_Core_BAO_Setting
::getItem(CRM_Core_BAO_Setting
::SYSTEM_PREFERENCES_NAME
, 'display_name_format');
1002 $firstNameWithPrefix = CRM_Utils_Address
::format($formatted, $format, FALSE, FALSE, TRUE);
1003 $firstNameWithPrefix = trim($firstNameWithPrefix);
1005 // fill uniqueAddress array with last/first name tree
1006 if (isset($uniqueAddress[$address])) {
1007 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
1008 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
1009 // drop unnecessary rows
1010 unset($rows[$rowID]);
1011 // this is the first listing at this address
1014 $uniqueAddress[$address]['ID'] = $rowID;
1015 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
1016 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
1019 foreach ($uniqueAddress as $address => $data) {
1020 // copy data back to $rows
1022 // one last name list per row
1023 foreach ($data['names'] as $last_name => $first_names) {
1028 if (count($first_names) == 1) {
1029 $family = $first_names[current(array_keys($first_names))]['addressee_display'];
1032 // collapse the tree to summarize
1033 $family = trim(implode(" & ", array_keys($first_names)) . " " . $last_name);
1036 $processedNames .= "\n" . $family;
1039 // build display_name string
1040 $processedNames = $family;
1044 $rows[$data['ID']]['addressee'] = $rows[$data['ID']]['addressee_display'] = $rows[$data['ID']]['display_name'] = $processedNames;
1049 * Create relationship between contacts who share an address
1051 * Note that currently we create relationship only for Individual contacts
1052 * Individual + Household and Individual + Orgnization
1054 * @param int $masterAddressId
1055 * Master address id.
1056 * @param array $params
1057 * Associated array of submitted values.
1062 public static function processSharedAddressRelationship($masterAddressId, $params) {
1063 if (!$masterAddressId) {
1066 // get the contact type of contact being edited / created
1067 $currentContactType = CRM_Contact_BAO_Contact
::getContactType($params['contact_id']);
1068 $currentContactId = $params['contact_id'];
1070 // if current contact is not of type individual return
1071 if ($currentContactType != 'Individual') {
1075 // get the contact id and contact type of shared contact
1076 // check the contact type of shared contact, return if it is of type Individual
1078 $query = 'SELECT cc.id, cc.contact_type
1079 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1082 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($masterAddressId, 'Integer')));
1086 // if current contact is not of type individual return, since we don't create relationship between
1088 if ($dao->contact_type
== 'Individual') {
1091 $sharedContactType = $dao->contact_type
;
1092 $sharedContactId = $dao->id
;
1094 // create relationship between ontacts who share an address
1095 if ($sharedContactType == 'Organization') {
1096 return CRM_Contact_BAO_Contact_Utils
::createCurrentEmployerRelationship($currentContactId, $sharedContactId);
1099 // get the relationship type id of "Household Member of"
1100 $relationshipType = 'Household Member of';
1103 $cid = array('contact' => $currentContactId);
1105 $relTypeId = CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_RelationshipType', $relationshipType, 'id', 'name_a_b');
1108 CRM_Core_Error
::fatal(ts("You seem to have deleted the relationship type '%1'", array(1 => $relationshipType)));
1111 // create relationship
1112 $relationshipParams = array(
1113 'is_active' => TRUE,
1114 'relationship_type_id' => $relTypeId . '_a_b',
1115 'contact_check' => array($sharedContactId => TRUE),
1118 list($valid, $invalid, $duplicate,
1119 $saved, $relationshipIds
1120 ) = CRM_Contact_BAO_Relationship
::createMultiple($relationshipParams, $cid);
1124 * Check and set the status for shared address delete
1126 * @param int $addressId
1128 * @param int $contactId
1130 * @param bool $returnStatus
1133 * @return string $statusMessage
1136 public static function setSharedAddressDeleteStatus($addressId = NULL, $contactId = NULL, $returnStatus = FALSE) {
1137 // check if address that is being deleted has any shared
1139 $entityId = $addressId;
1140 $query = 'SELECT cc.id, cc.display_name
1141 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1142 WHERE ca.master_id = %1';
1145 $entityId = $contactId;
1146 $query = 'SELECT cc.id, cc.display_name
1147 FROM civicrm_address ca1
1148 INNER JOIN civicrm_address ca2 ON ca1.id = ca2.master_id
1149 INNER JOIN civicrm_contact cc ON ca2.contact_id = cc.id
1150 WHERE ca1.contact_id = %1';
1153 $dao = CRM_Core_DAO
::executeQuery($query, array(1 => array($entityId, 'Integer')));
1155 $deleteStatus = array();
1156 $sharedContactList = array();
1157 $statusMessage = NULL;
1159 while ($dao->fetch()) {
1160 if (empty($deleteStatus)) {
1161 $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.');
1164 $contactViewUrl = CRM_Utils_System
::url('civicrm/contact/view', "reset=1&cid={$dao->id}");
1165 $sharedContactList[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1166 $deleteStatus[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1171 if (!empty($deleteStatus)) {
1172 $statusMessage = implode('<br/>', $deleteStatus) . '<br/>';
1175 if (!$returnStatus) {
1176 CRM_Core_Session
::setStatus($statusMessage, '', 'info');
1180 'contactList' => $sharedContactList,
1181 'count' => $addressCount,
1187 * Call common delete function
1189 public static function del($id) {
1190 return CRM_Contact_BAO_Contact
::deleteObjectWithPrimary('Address', $id);
1194 * Get options for a given address field.
1195 * @see CRM_Core_DAO::buildOptions
1197 * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow?
1198 * TODO: In context of chainselect, what to return if e.g. a country has no states?
1200 * @param string $fieldName
1201 * @param string $context
1202 * : @see CRM_Core_DAO::buildOptionsContext.
1203 * @param array $props
1204 * : whatever is known about this dao object.
1206 * @return Array|bool
1208 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
1210 // Special logic for fields whose options depend on context or properties
1211 switch ($fieldName) {
1212 // Filter state_province list based on chosen country or site defaults
1213 case 'state_province_id':
1214 if (empty($props['country_id'])) {
1215 $config = CRM_Core_Config
::singleton();
1216 if (!empty($config->provinceLimit
)) {
1217 $props['country_id'] = $config->provinceLimit
;
1220 $props['country_id'] = $config->defaultContactCountry
;
1223 if (!empty($props['country_id']) && $context !== 'validate') {
1224 $params['condition'] = 'country_id IN (' . implode(',', (array) $props['country_id']) . ')';
1228 // Filter country list based on site defaults
1230 if ($context != 'get' && $context != 'validate') {
1231 $config = CRM_Core_Config
::singleton();
1232 if (!empty($config->countryLimit
) && is_array($config->countryLimit
)) {
1233 $params['condition'] = 'id IN (' . implode(',', $config->countryLimit
) . ')';
1238 // Filter county list based on chosen state
1240 if (!empty($props['state_province_id'])) {
1241 $params['condition'] = 'state_province_id IN (' . implode(',', (array) $props['state_province_id']) . ')';
1245 // Not a real field in this entity
1246 case 'world_region':
1247 return CRM_Core_PseudoConstant
::worldRegion();
1251 return CRM_Core_PseudoConstant
::get(__CLASS__
, $fieldName, $params, $context);