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