Merge pull request #360 from pradpnayak/CRM-12284
[civicrm-core.git] / CRM / Core / BAO / Address.php
CommitLineData
6a488035
TO
1<?php
2// $Id$
3
4/*
5 +--------------------------------------------------------------------+
6 | CiviCRM version 4.3 |
7 +--------------------------------------------------------------------+
8 | Copyright CiviCRM LLC (c) 2004-2013 |
9 +--------------------------------------------------------------------+
10 | This file is a part of CiviCRM. |
11 | |
12 | CiviCRM is free software; you can copy, modify, and distribute it |
13 | under the terms of the GNU Affero General Public License |
14 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
15 | |
16 | CiviCRM is distributed in the hope that it will be useful, but |
17 | WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
19 | See the GNU Affero General Public License for more details. |
20 | |
21 | You should have received a copy of the GNU Affero General Public |
22 | License and the CiviCRM Licensing Exception along |
23 | with this program; if not, contact CiviCRM LLC |
24 | at info[AT]civicrm[DOT]org. If you have questions about the |
25 | GNU Affero General Public License or the licensing of CiviCRM, |
26 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
27 +--------------------------------------------------------------------+
28*/
29
30/**
31 *
32 * @package CRM
33 * @copyright CiviCRM LLC (c) 2004-2013
34 * $Id$
35 *
36 */
37
38/**
39 * This is class to handle address related functions
40 */
41class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
42
43 /**
44 * takes an associative array and creates a address
45 *
46 * @param array $params (reference ) an assoc array of name/value pairs
47 * @param boolean $fixAddress true if you need to fix (format) address values
48 * before inserting in db
49 *
50 * @return array $blocks array of created address
51 * @access public
52 * @static
53 */
54 static function create(&$params, $fixAddress, $entity = NULL) {
55 if (!isset($params['address']) || !is_array($params['address'])) {
56 return;
57 }
58 CRM_Core_BAO_Block::sortPrimaryFirst($params['address']);
59 $addresses = array();
60 $contactId = NULL;
61
62 $updateBlankLocInfo = CRM_Utils_Array::value('updateBlankLocInfo', $params, FALSE);
63 if (!$entity) {
64 $contactId = $params['contact_id'];
65 //get all the addresses for this contact
66 $addresses = self::allAddress($contactId, $updateBlankLocInfo);
67 }
68 else {
69 // get all address from location block
70 $entityElements = array(
71 'entity_table' => $params['entity_table'],
72 'entity_id' => $params['entity_id'],
73 );
74 $addresses = self::allEntityAddress($entityElements);
75 }
76
77 $isPrimary = $isBilling = TRUE;
78 $blocks = array();
79 foreach ($params['address'] as $key => $value) {
80 if (!is_array($value)) {
81 continue;
82 }
83
84 $addressExists = self::dataExists($value);
85 if ( !CRM_Utils_Array::value('id', $value) ) {
86 if ($updateBlankLocInfo) {
87 if ((!empty($addresses) || !$addressExists) && array_key_exists($key, $addresses)) {
88 $value['id'] = $addresses[$key];
89 }
90 }
91 else {
92 if (!empty($addresses) && array_key_exists(CRM_Utils_Array::value('location_type_id', $value), $addresses)) {
93 $value['id'] = $addresses[CRM_Utils_Array::value('location_type_id', $value)];
94 }
95 }
96 }
97
98 // Note there could be cases when address info already exist ($value[id] is set) for a contact/entity
99 // BUT info is not present at this time, and therefore we should be really careful when deleting the block.
100 // $updateBlankLocInfo will help take appropriate decision. CRM-5969
101 if (isset($value['id']) && !$addressExists && $updateBlankLocInfo) {
102 //delete the existing record
103 CRM_Core_BAO_Block::blockDelete('Address', array('id' => $value['id']));
104 continue;
105 }
106 elseif (!$addressExists) {
107 continue;
108 }
109
110 if ($isPrimary && CRM_Utils_Array::value('is_primary', $value)) {
111 $isPrimary = FALSE;
112 }
113 else {
114 $value['is_primary'] = 0;
115 }
116
117 if ($isBilling && CRM_Utils_Array::value('is_billing', $value)) {
118 $isBilling = FALSE;
119 }
120 else {
121 $value['is_billing'] = 0;
122 }
123
124 if (!CRM_Utils_Array::value('manual_geo_code', $value)) {
125 $value['manual_geo_code'] = 0;
126 }
127 $value['contact_id'] = $contactId;
128 $blocks[] = self::add($value, $fixAddress);
129 }
130
131 return $blocks;
132 }
133
134 /**
135 * takes an associative array and adds address
136 *
137 * @param array $params (reference ) an assoc array of name/value pairs
138 * @param boolean $fixAddress true if you need to fix (format) address values
139 * before inserting in db
140 *
141 * @return object CRM_Core_BAO_Address object on success, null otherwise
142 * @access public
143 * @static
144 */
145 static function add(&$params, $fixAddress) {
146 static $customFields = NULL;
147 $address = new CRM_Core_DAO_Address();
148
149 // fixAddress mode to be done
150 if ($fixAddress) {
151 CRM_Core_BAO_Address::fixAddress($params);
152 }
153
154 $hook = empty($params['id']) ? 'create' : 'edit';
155 CRM_Utils_Hook::pre($hook, 'Address', CRM_Utils_Array::value('id', $params), $params);
156
157 // if id is set & is_primary isn't we can assume no change
158 if (is_numeric(CRM_Utils_Array::value('is_primary', $params)) || empty($params['id'])) {
159 CRM_Core_BAO_Block::handlePrimary($params, get_class());
160 }
161 $config = CRM_Core_Config::singleton();
162 $address->copyValues($params);
163
164 $address->save();
165
166 if ($address->id) {
167 if (!$customFields) {
168 $customFields = CRM_Core_BAO_CustomField::getFields('Address', FALSE, TRUE);
169 }
170 if (!empty($customFields)) {
171 $addressCustom = CRM_Core_BAO_CustomField::postProcess($params,
172 $customFields,
173 $address->id,
174 'Address',
175 TRUE
176 );
177 }
178 if (!empty($addressCustom)) {
179 CRM_Core_BAO_CustomValueTable::store($addressCustom, 'civicrm_address', $address->id);
180 }
181
182 //call the function to sync shared address
183 self::processSharedAddress($address->id, $params);
184
185 // call the function to create shared relationships
186 // we only create create relationship if address is shared by Individual
187 if ($address->master_id != 'null') {
188 self::processSharedAddressRelationship($address->master_id, $params);
189 }
190
191 // lets call the post hook only after we've done all the follow on processing
192 CRM_Utils_Hook::post($hook, 'Address', $address->id, $address);
193 }
194
195 return $address;
196 }
197
198 /**
199 * format the address params to have reasonable values
200 *
201 * @param array $params (reference ) an assoc array of name/value pairs
202 *
203 * @return void
204 * @access public
205 * @static
206 */
207 static function fixAddress(&$params) {
208 if (CRM_Utils_Array::value('billing_street_address', $params)) {
209 //Check address is comming from online contribution / registration page
210 //Fixed :CRM-5076
211 $billing = array(
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',
219 );
220
221 foreach ($billing as $key => $val) {
222 if ($value = CRM_Utils_Array::value($val, $params)) {
223 if (CRM_Utils_Array::value($key, $params)) {
224 unset($params[$val]);
225 }
226 else {
227 //add new key and removed old
228 $params[$key] = $value;
229 unset($params[$val]);
230 }
231 }
232 }
233 }
234
235 /* Split the zip and +4, if it's in US format */
236 if (CRM_Utils_Array::value('postal_code', $params) &&
237 preg_match('/^(\d{4,5})[+-](\d{4})$/',
238 $params['postal_code'],
239 $match
240 )
241 ) {
242 $params['postal_code'] = $match[1];
243 $params['postal_code_suffix'] = $match[2];
244 }
245
246 // add country id if not set
247 if ((!isset($params['country_id']) || !is_numeric($params['country_id'])) &&
248 isset($params['country'])
249 ) {
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);
256 }
257 $params['country_id'] = $country->id;
258 }
259
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'])
263 ) {
264 if (!empty($params['state_province'])) {
265 $state_province = new CRM_Core_DAO_StateProvince();
266 $state_province->name = $params['state_province'];
267
268 // add country id if present
269 if (!empty($params['country_id'])) {
270 $state_province->country_id = $params['country_id'];
271 }
272
273 if (!$state_province->find(TRUE)) {
274 unset($state_province->name);
275 $state_province->abbreviation = $params['state_province'];
276 $state_province->find(TRUE);
277 }
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;
282 }
283 }
284 else {
285 $params['state_province_id'] = 'null';
286 }
287 }
288
289 // add county id if county is set
290 // CRM-7837
291 if ((!isset($params['county_id']) || !is_numeric($params['county_id']))
292 && isset($params['county']) && !empty($params['county'])
293 ) {
294 $county = new CRM_Core_DAO_County();
295 $county->name = $params['county'];
296
297 if (isset($params['state_province_id'])) {
298 $county->state_province_id = $params['state_province_id'];
299 }
300
301 if ($county->find(TRUE)) {
302 $params['county_id'] = $county->id;
303 }
304 }
305
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']))
311 ) {
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'],
316 'country_id'
317 );
318 }
319
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';
325 }
326 elseif (!is_numeric($params['state_province_id']) ||
327 ((int ) $params['state_province_id'] < 1000)
328 ) {
329 // CRM-3393 ( the hacky 1000 check)
330 $params['state_province_id'] = 'null';
331 }
332 }
333
334 if (isset($params['country_id'])) {
335 if (empty($params['country_id'])) {
336 $params['country_id'] = 'null';
337 }
338 elseif (!is_numeric($params['country_id']) ||
339 ((int ) $params['country_id'] < 1000)
340 ) {
341 // CRM-3393 ( the hacky 1000 check)
342 $params['country_id'] = 'null';
343 }
344 }
345
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']);
349 }
350
351 if (isset($params['country_id']) && is_numeric($params['country_id'])) {
352 $params['country'] = CRM_Core_PseudoConstant::country($params['country_id']);
353 }
354
355 $config = CRM_Core_Config::singleton();
356
357 $asp = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::ADDRESS_STANDARDIZATION_PREFERENCES_NAME,
358 'address_standardization_provider'
359 );
360 // clean up the address via USPS web services if enabled
361 if ($asp === 'USPS' &&
362 $params['country_id'] == 1228
363 ) {
364 CRM_Utils_Address_USPS::checkAddress($params);
365
366 // do street parsing again if enabled, since street address might have changed
367 $parseStreetAddress = CRM_Utils_Array::value('street_address_parsing',
368 CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
369 'address_options'
370 ),
371 FALSE
372 );
373
374 if ($parseStreetAddress && !empty($params['street_address'])) {
375 foreach (array(
376 'street_number', 'street_name', 'street_unit', 'street_number_suffix') as $fld) {
377 unset($params[$fld]);
378 }
379 // main parse string.
380 $parseString = CRM_Utils_Array::value('street_address', $params);
381 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString);
382
383 // merge parse address in to main address block.
384 $params = array_merge($params, $parsedFields);
385 }
386 }
387
388 // add latitude and longitude and format address if needed
389 if (!empty($config->geocodeMethod) && ($config->geocodeMethod != 'CRM_Utils_Geocode_OpenStreetMaps') && empty($params['manual_geo_code'])) {
390 $class = $config->geocodeMethod;
391 $class::format($params);
392 }
393 }
394
395 /**
396 * Check if there is data to create the object
397 *
398 * @param array $params (reference ) an assoc array of name/value pairs
399 *
400 * @return boolean
401 *
402 * @access public
403 * @static
404 */
405 static function dataExists(&$params) {
406 //check if location type is set if not return false
407 if (!isset($params['location_type_id'])) {
408 return FALSE;
409 }
410
411 $config = CRM_Core_Config::singleton();
412 foreach ($params as $name => $value) {
413 if (in_array($name, array(
414 'is_primary', 'location_type_id', 'id', 'contact_id', 'is_billing', 'display', 'master_id'))) {
415 continue;
416 }
417 elseif (!CRM_Utils_System::isNull($value)) {
418 // name could be country or country id
419 if (substr($name, 0, 7) == 'country') {
420 // make sure its different from the default country
421 // iso code
422 $defaultCountry = $config->defaultContactCountry();
423 // full name
424 $defaultCountryName = $config->defaultContactCountryName();
425
426 if ($defaultCountry) {
427 if ($value == $defaultCountry ||
428 $value == $defaultCountryName ||
429 $value == $config->defaultContactCountry
430 ) {
431 // do nothing
432 }
433 else {
434 return TRUE;
435 }
436 }
437 else {
438 // return if null default
439 return TRUE;
440 }
441 }
442 else {
443 return TRUE;
444 }
445 }
446 }
447
448 return FALSE;
449 }
450
451 /**
452 * Given the list of params in the params array, fetch the object
453 * and store the values in the values array
454 *
455 * @param array $entityBlock associated array of fields
456 * @param boolean $microformat if microformat output is required
457 * @param int $fieldName conditional field name
458 *
459 * @return array $addresses array with address fields
460 * @access public
461 * @static
462 */
463 static function &getValues(&$entityBlock, $microformat = FALSE, $fieldName = 'contact_id') {
464 if (empty($entityBlock)) {
465 return NULL;
466 }
467 $addresses = array();
468 $address = new CRM_Core_BAO_Address();
469
470 if (!CRM_Utils_Array::value('entity_table', $entityBlock)) {
471 $address->$fieldName = CRM_Utils_Array::value($fieldName, $entityBlock);
472 }
473 else {
474 $addressIds = array();
475 $addressIds = self::allEntityAddress($entityBlock);
476
477 if (!empty($addressIds[1])) {
478 $address->id = $addressIds[1];
479 }
480 else {
481 return $addresses;
482 }
483 }
484 //get primary address as a first block.
485 $address->orderBy('is_primary desc, id');
486
487 $address->find();
488
489 $count = 1;
490 while ($address->fetch()) {
491 // deprecate reference.
492 if ($count > 1) {
493 foreach (array(
494 'state', 'state_name', 'country', 'world_region') as $fld) {
495 if (isset($address->$fld))unset($address->$fld);
496 }
497 }
498 $stree = $address->street_address;
499 $values = array();
500 CRM_Core_DAO::storeValues($address, $values);
501
502 // add state and country information: CRM-369
503 if (!empty($address->state_province_id)) {
504 $address->state = CRM_Core_PseudoConstant::stateProvinceAbbreviation($address->state_province_id, FALSE);
505 $address->state_name = CRM_Core_PseudoConstant::stateProvince($address->state_province_id, FALSE);
506 }
507
508 if (!empty($address->country_id)) {
509 $address->country = CRM_Core_PseudoConstant::country($address->country_id);
510
511 //get world region
512 $regionId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Country', $address->country_id, 'region_id');
513
514 $address->world_region = CRM_Core_PseudoConstant::worldregion($regionId);
515 }
516
517 $address->addDisplay($microformat);
518
519 $values['display'] = $address->display;
520 $values['display_text'] = $address->display_text;
521
522 if (is_numeric($address->master_id)) {
523 $values['use_shared_address'] = 1;
524 }
525
526 $addresses[$count] = $values;
527
528 //unset is_primary after first block. Due to some bug in earlier version
529 //there might be more than one primary blocks, hence unset is_primary other than first
530 if ($count > 1) {
531 unset($addresses[$count]['is_primary']);
532 }
533
534 $count++;
535 }
536
537 return $addresses;
538 }
539
540 /**
541 * Add the formatted address to $this-> display
542 *
543 * @param NULL
544 *
545 * @return void
546 *
547 * @access public
548 *
549 */
550 function addDisplay($microformat = FALSE) {
551 $fields = array(
552 // added this for CRM 1200
553 'address_id' => $this->id,
554 // CRM-4003
555 'address_name' => str_replace('\ 1', ' ', $this->name),
556 'street_address' => $this->street_address,
557 'supplemental_address_1' => $this->supplemental_address_1,
558 'supplemental_address_2' => $this->supplemental_address_2,
559 'city' => $this->city,
560 'state_province_name' => isset($this->state_name) ? $this->state_name : "",
561 'state_province' => isset($this->state) ? $this->state : "",
562 'postal_code' => isset($this->postal_code) ? $this->postal_code : "",
563 'postal_code_suffix' => isset($this->postal_code_suffix) ? $this->postal_code_suffix : "",
564 'country' => isset($this->country) ? $this->country : "",
565 'world_region' => isset($this->world_region) ? $this->world_region : "",
566 );
567
568 if (isset($this->county_id) && $this->county_id) {
569 $fields['county'] = CRM_Core_PseudoConstant::county($this->county_id);
570 }
571 else {
572 $fields['county'] = NULL;
573 }
574
575 $this->display = CRM_Utils_Address::format($fields, NULL, $microformat);
576 $this->display_text = CRM_Utils_Address::format($fields);
577 }
578
579 /**
580 * Get all the addresses for a specified contact_id, with the primary address being first
581 *
582 * @param int $id the contact id
583 *
584 * @return array the array of adrress data
585 * @access public
586 * @static
587 */
588 static function allAddress($id, $updateBlankLocInfo = FALSE) {
589 if (!$id) {
590 return NULL;
591 }
592
593 $query = "
594SELECT civicrm_address.id as address_id, civicrm_address.location_type_id as location_type_id
595FROM civicrm_contact, civicrm_address
596WHERE civicrm_address.contact_id = civicrm_contact.id AND civicrm_contact.id = %1
597ORDER BY civicrm_address.is_primary DESC, address_id ASC";
598 $params = array(1 => array($id, 'Integer'));
599
600 $addresses = array();
601 $dao = CRM_Core_DAO::executeQuery($query, $params);
602 $count = 1;
603 while ($dao->fetch()) {
604 if ($updateBlankLocInfo) {
605 $addresses[$count++] = $dao->address_id;
606 }
607 else {
608 $addresses[$dao->location_type_id] = $dao->address_id;
609 }
610 }
611 return $addresses;
612 }
613
614 /**
615 * Get all the addresses for a specified location_block id, with the primary address being first
616 *
617 * @param array $entityElements the array containing entity_id and
618 * entity_table name
619 *
620 * @return array the array of adrress data
621 * @access public
622 * @static
623 */
624 static function allEntityAddress(&$entityElements) {
625 if (empty($entityElements)) {
626 return $addresses;
627 }
628
629 $entityId = $entityElements['entity_id'];
630 $entityTable = $entityElements['entity_table'];
631
632 $sql = "
633SELECT civicrm_address.id as address_id
634FROM civicrm_loc_block loc, civicrm_location_type ltype, civicrm_address, {$entityTable} ev
635WHERE ev.id = %1
636 AND loc.id = ev.loc_block_id
637 AND civicrm_address.id IN (loc.address_id, loc.address_2_id)
638 AND ltype.id = civicrm_address.location_type_id
639ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC, address_id ASC ";
640
641 $params = array(1 => array($entityId, 'Integer'));
642 $addresses = array();
643 $dao = CRM_Core_DAO::executeQuery($sql, $params);
644 $locationCount = 1;
645 while ($dao->fetch()) {
646 $addresses[$locationCount] = $dao->address_id;
647 $locationCount++;
648 }
649 return $addresses;
650 }
651
652 static function addStateCountryMap(&$stateCountryMap, $defaults = NULL) {
653 // first fix the statecountry map if needed
654 if (empty($stateCountryMap)) {
655 return;
656 }
657
658 $config = CRM_Core_Config::singleton();
659 if (!isset($config->stateCountryMap)) {
660 $config->stateCountryMap = array();
661 }
662
663 $config->stateCountryMap = array_merge($config->stateCountryMap, $stateCountryMap);
664 }
665
c5e02dc5 666 static function fixAllStateSelects(&$form, $defaults, $batchFieldNames = false) {
6a488035 667 $config = CRM_Core_Config::singleton();
c5e02dc5
AH
668
669 $map = is_array($batchFieldNames) ? $batchFieldNames : $config->stateCountryMap;
6a488035 670
c5e02dc5
AH
671 if (!empty($map)) {
672 foreach ($map as $index => $match) {
6a488035
TO
673 if (
674 array_key_exists('state_province', $match) &&
675 array_key_exists('country', $match)
676 ) {
677 CRM_Contact_Form_Edit_Address::fixStateSelect(
678 $form,
679 $match['country'],
680 $match['state_province'],
681 CRM_Utils_Array::value('county', $match),
682 CRM_Utils_Array::value($match['country'], $defaults),
683 CRM_Utils_Array::value($match['state_province'], $defaults)
684 );
685 }
686 else {
687 unset($config->stateCountryMap[$index]);
688 }
689 }
690 }
691 }
692
693 /**
694 * Function to get address sequence
695 *
696 * @return array of address sequence.
697 */
698 static function addressSequence() {
699 $config = CRM_Core_Config::singleton();
700 $addressSequence = $config->addressSequence();
701
702 $countryState = $cityPostal = FALSE;
703 foreach ($addressSequence as $key => $field) {
704 if (
705 in_array($field, array('country', 'state_province')) &&
706 !$countryState
707 ) {
708 $countryState = TRUE;
709 $addressSequence[$key] = 'country_state_province';
710 }
711 elseif (
712 in_array($field, array('city', 'postal_code')) &&
713 !$cityPostal
714 ) {
715 $cityPostal = TRUE;
716 $addressSequence[$key] = 'city_postal_code';
717 }
718 elseif (
719 in_array($field, array('country', 'state_province', 'city', 'postal_code'))
720 ) {
721 unset($addressSequence[$key]);
722 }
723 }
724
725 return $addressSequence;
726 }
727
728 /**
729 * Parse given street address string in to street_name,
730 * street_unit, street_number and street_number_suffix
731 * eg "54A Excelsior Ave. Apt 1C", or "917 1/2 Elm Street"
732 *
733 * NB: civic street formats for en_CA and fr_CA used by default if those locales are active
734 * otherwise en_US format is default action
735 *
736 * @param string Street address including number and apt
737 * @param string Locale - to set locale used to parse address
738 *
739 * @return array $parseFields parsed fields values.
740 * @access public
741 * @static
742 */
743 static function parseStreetAddress($streetAddress, $locale = NULL) {
744 $config = CRM_Core_Config::singleton();
745
746 /* locales supported include:
747 * en_US - http://pe.usps.com/cpim/ftp/pubs/pub28/pub28.pdf
748 * en_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp
749 * fr_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-f.asp
750 * NB: common use of comma after street number also supported
751 * default is en_US
752 */
753
754 $supportedLocalesForParsing = array('en_US', 'en_CA', 'fr_CA');
755 if (!$locale) {
756 $locale = $config->lcMessages;
757 }
758 // as different locale explicitly requested but is not available, display warning message and set $locale = 'en_US'
759 if (!in_array($locale, $supportedLocalesForParsing)) {
760 CRM_Core_Session::setStatus(ts('Unsupported locale specified to parseStreetAddress: %1. Proceeding with en_US locale.', array(1 => $locale)), ts('Unsupported Locale'), 'alert');
761 $locale = 'en_US';
762 }
763 $parseFields = array(
764 'street_name' => '',
765 'street_unit' => '',
766 'street_number' => '',
767 'street_number_suffix' => '',
768 );
769
770 if (empty($streetAddress)) {
771 return $parseFields;
772 }
773
774 $streetAddress = trim($streetAddress);
775
776 $matches = array();
777 if (in_array($locale, array(
778 'en_CA', 'fr_CA')) && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches)) {
779 $parseFields['street_unit'] = $matches[1];
780 // unset from rest of street address
781 $streetAddress = preg_replace('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', '', $streetAddress);
782 }
783
784 // get street number and suffix.
785 $matches = array();
786 //alter street number/suffix handling so that we accept -digit
787 if (preg_match('/^[A-Za-z0-9]+([\S]+)/', $streetAddress, $matches)) {
788 // check that $matches[0] is numeric, else assume no street number
789 if (preg_match('/^(\d+)/', $matches[0])) {
790 $streetNumAndSuffix = $matches[0];
791
792 // get street number.
793 $matches = array();
794 if (preg_match('/^(\d+)/', $streetNumAndSuffix, $matches)) {
795 $parseFields['street_number'] = $matches[0];
796 $suffix = preg_replace('/^(\d+)/', '', $streetNumAndSuffix);
797 $parseFields['street_number_suffix'] = trim($suffix);
798 }
799
800 // unset from main street address.
801 $streetAddress = preg_replace('/^[A-Za-z0-9]+([\S]+)/', '', $streetAddress);
802 $streetAddress = trim($streetAddress);
803 }
804 }
805 elseif (preg_match('/^(\d+)/', $streetAddress, $matches)) {
806 $parseFields['street_number'] = $matches[0];
807 // unset from main street address.
808 $streetAddress = preg_replace('/^(\d+)/', '', $streetAddress);
809 $streetAddress = trim($streetAddress);
810 }
811
812 // suffix might be like 1/2
813 $matches = array();
814 if (preg_match('/^\d\/\d/', $streetAddress, $matches)) {
815 $parseFields['street_number_suffix'] .= $matches[0];
816
817 // unset from main street address.
818 $streetAddress = preg_replace('/^\d+\/\d+/', '', $streetAddress);
819 $streetAddress = trim($streetAddress);
820 }
821
822 // now get the street unit.
823 // supportable street unit formats.
824 $streetUnitFormats = array(
825 'APT', 'APARTMENT', 'BSMT', 'BASEMENT', 'BLDG', 'BUILDING',
826 'DEPT', 'DEPARTMENT', 'FL', 'FLOOR', 'FRNT', 'FRONT',
827 'HNGR', 'HANGER', 'LBBY', 'LOBBY', 'LOWR', 'LOWER',
828 'OFC', 'OFFICE', 'PH', 'PENTHOUSE', 'TRLR', 'TRAILER',
829 'UPPR', 'RM', 'ROOM', 'SIDE', 'SLIP', 'KEY',
830 'LOT', 'PIER', 'REAR', 'SPC', 'SPACE',
831 'STOP', 'STE', 'SUITE', 'UNIT', '#',
832 );
833
834 // overwriting $streetUnitFormats for 'en_CA' and 'fr_CA' locale
835 if (in_array($locale, array(
836 'en_CA', 'fr_CA'))) {
837 $streetUnitFormats = array('APT', 'APP', 'SUITE', 'BUREAU', 'UNIT');
838 }
839
840 $streetUnitPreg = '/(' . implode('|\s', $streetUnitFormats) . ')(.+)?/i';
841 $matches = array();
842 if (preg_match($streetUnitPreg, $streetAddress, $matches)) {
843 $parseFields['street_unit'] = trim($matches[0]);
844 $streetAddress = str_replace($matches[0], '', $streetAddress);
845 $streetAddress = trim($streetAddress);
846 }
847
848 // consider remaining string as street name.
849 $parseFields['street_name'] = $streetAddress;
850
851 //run parsed fields through stripSpaces to clean
852 foreach ($parseFields as $parseField => $value) {
853 $parseFields[$parseField] = CRM_Utils_String::stripSpaces($value);
854 }
855
856 return $parseFields;
857 }
858
859 /**
860 * Validate the address fields based on the address options enabled
861 * in the Address Settings
862 *
863 * @param array $fields an array of importable/exportable contact fields
864 *
865 * @return array $fields an array of contact fields and only the enabled address options
866 * @access public
867 * @static
868 */
869 static function validateAddressOptions($fields) {
870 static $addressOptions = NULL;
871 if (!$addressOptions) {
872 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
873 'address_options', TRUE, NULL, TRUE
874 );
875 }
876
877 if (is_array($fields) && !empty($fields)) {
878 foreach ($addressOptions as $key => $value) {
879 if (!$value && isset($fields[$key])) {
880 unset($fields[$key]);
881 }
882 }
883 }
884 return $fields;
885 }
886
887 /**
888 * Check if current address is used by any other contacts
889 *
890 * @param int $addressId address id
891 *
892 * @return count of contacts that use this shared address
893 * @access public
894 * @static
895 */
896 static function checkContactSharedAddress($addressId) {
897 $query = 'SELECT count(id) FROM civicrm_address WHERE master_id = %1';
898 return CRM_Core_DAO::singleValueQuery($query, array(1 => array($addressId, 'Integer')));
899 }
900
901 /**
902 * Function to check if current address fields are shared with any other address
903 *
904 * @param array $fields address fields in profile
905 * @param int $contactId contact id
906 *
907 * @access public
908 * @static
909 */
910 static function checkContactSharedAddressFields(&$fields, $contactId) {
911 if (!$contactId || !is_array($fields) || empty($fields)) {
912 return;
913 }
914
915 $sharedLocations = array();
916
917 $query = "
918SELECT is_primary,
919 location_type_id
920 FROM civicrm_address
921 WHERE contact_id = %1
922 AND master_id IS NOT NULL";
923
924 $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($contactId, 'Positive')));
925 while ($dao->fetch()) {
926 $sharedLocations[$dao->location_type_id] = $dao->location_type_id;
927 if ($dao->is_primary) {
928 $sharedLocations['Primary'] = 'Primary';
929 }
930 }
931
932 //no need to process further.
933 if (empty($sharedLocations)) {
934 return;
935 }
936
937 $addressFields = array(
938 'city',
939 'county',
940 'country',
941 'geo_code_1',
942 'geo_code_2',
943 'postal_code',
944 'address_name',
945 'state_province',
946 'street_address',
947 'postal_code_suffix',
948 'supplemental_address_1',
949 'supplemental_address_2',
950 );
951
952 foreach ($fields as $name => & $values) {
953 if (!is_array($values) || empty($values)) {
954 continue;
955 }
956
957 $nameVal = explode('-', $values['name']);
958 $fldName = CRM_Utils_Array::value(0, $nameVal);
959 $locType = CRM_Utils_Array::value(1, $nameVal);
960 if (CRM_Utils_Array::value('location_type_id', $values)) {
961 $locType = $values['location_type_id'];
962 }
963
964 if (in_array($fldName, $addressFields) &&
965 in_array($locType, $sharedLocations)
966 ) {
967 $values['is_shared'] = TRUE;
968 }
969 }
970 }
971
972 /**
973 * Function to update the shared addresses if master address is modified
974 *
975 * @param int $addressId address id
976 * @param array $params associated array of address params
977 *
978 * @return void
979 * @access public
980 * @static
981 */
982 static function processSharedAddress($addressId, $params) {
983 $query = 'SELECT id FROM civicrm_address WHERE master_id = %1';
984 $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($addressId, 'Integer')));
985
986 // unset contact id
987 $skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id', 'contact_id');
988 foreach ($skipFields as $value) {
989 unset($params[$value]);
990 }
991
992 $addressDAO = new CRM_Core_DAO_Address();
993 while ($dao->fetch()) {
994 $addressDAO->copyValues($params);
995 $addressDAO->id = $dao->id;
996 $addressDAO->save();
997 $addressDAO->free();
998 }
999 }
1000
1001 /**
1002 * Function to create relationship between contacts who share an address
1003 *
1004 * Note that currently we create relationship only for Individual contacts
1005 * Individual + Household and Individual + Orgnization
1006 *
1007 * @param int $masterAddressId master address id
1008 * @param array $params associated array of submitted values
1009 *
1010 * @return void
1011 * @access public
1012 * @static
1013 */
1014 static function processSharedAddressRelationship($masterAddressId, $params) {
1015 if (!$masterAddressId) {
1016 return;
1017 }
1018 // get the contact type of contact being edited / created
1019 $currentContactType = CRM_Contact_BAO_Contact::getContactType($params['contact_id']);
1020 $currentContactId = $params['contact_id'];
1021
1022 // if current contact is not of type individual return
1023 if ($currentContactType != 'Individual') {
1024 return;
1025 }
1026
1027 // get the contact id and contact type of shared contact
1028 // check the contact type of shared contact, return if it is of type Individual
1029
1030 $query = 'SELECT cc.id, cc.contact_type
1031 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1032 WHERE ca.id = %1';
1033
1034 $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($masterAddressId, 'Integer')));
1035
1036 $dao->fetch();
1037
1038 // if current contact is not of type individual return, since we don't create relationship between
1039 // 2 individuals
1040 if ($dao->contact_type == 'Individual') {
1041 return;
1042 }
1043 $sharedContactType = $dao->contact_type;
1044 $sharedContactId = $dao->id;
1045
1046 // create relationship between ontacts who share an address
1047 if ($sharedContactType == 'Organization') {
1048 return CRM_Contact_BAO_Contact_Utils::createCurrentEmployerRelationship($currentContactId, $sharedContactId);
1049 }
1050 else {
1051 // get the relationship type id of "Household Member of"
1052 $relationshipType = 'Household Member of';
1053 }
1054
1055 $cid = array('contact' => $currentContactId);
1056
1057 $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', $relationshipType, 'id', 'name_a_b');
1058
1059 if (!$relTypeId) {
1060 CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type '%1'", array(1 => $relationshipType)));
1061 }
1062
1063 // create relationship
1064 $relationshipParams = array(
1065 'is_active' => TRUE,
1066 'relationship_type_id' => $relTypeId . '_a_b',
1067 'contact_check' => array($sharedContactId => TRUE),
1068 );
1069
1070 list($valid, $invalid, $duplicate,
1071 $saved, $relationshipIds
1072 ) = CRM_Contact_BAO_Relationship::create($relationshipParams, $cid);
1073 }
1074
1075 /**
1076 * Function to check and set the status for shared address delete
1077 *
1078 * @param int $addressId address id
1079 * @param int $contactId contact id
1080 * @param boolean $returnStatus by default false
1081 *
1082 * @return string $statusMessage
1083 * @access public
1084 * @static
1085 */
1086 static function setSharedAddressDeleteStatus($addressId = NULL, $contactId = NULL, $returnStatus = FALSE) {
1087 // check if address that is being deleted has any shared
1088 if ($addressId) {
1089 $entityId = $addressId;
1090 $query = 'SELECT cc.id, cc.display_name
1091 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1092 WHERE ca.master_id = %1';
1093 }
1094 else {
1095 $entityId = $contactId;
1096 $query = 'SELECT cc.id, cc.display_name
1097 FROM civicrm_address ca1
1098 INNER JOIN civicrm_address ca2 ON ca1.id = ca2.master_id
1099 INNER JOIN civicrm_contact cc ON ca2.contact_id = cc.id
1100 WHERE ca1.contact_id = %1';
1101 }
1102
1103 $dao = CRM_Core_DAO::executeQuery($query, array(1 => array($entityId, 'Integer')));
1104
1105 $deleteStatus = array();
1106 $sharedContactList = array();
1107 $statusMessage = NULL;
1108 $addressCount = 0;
1109 while ($dao->fetch()) {
1110 if (empty($deleteStatus)) {
1111 $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.');
1112 }
1113
1114 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$dao->id}");
1115 $sharedContactList[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1116 $deleteStatus[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1117
1118 $addressCount++;
1119 }
1120
1121 if (!empty($deleteStatus)) {
1122 $statusMessage = implode('<br/>', $deleteStatus) . '<br/>';
1123 }
1124
1125 if (!$returnStatus) {
1126 CRM_Core_Session::setStatus($statusMessage, '', 'info');
1127 }
1128 else {
1129 return array(
1130 'contactList' => $sharedContactList,
1131 'count' => $addressCount,
1132 );
1133 }
1134 }
12445e1c
CW
1135
1136 /**
1137 * Call common delete function
1138 */
1139 static function del($id) {
a65e2e55 1140 return CRM_Contact_BAO_Contact::deleteObjectWithPrimary('Address', $id);
12445e1c 1141 }
6a488035 1142}