Merge pull request #15937 from seamuslee001/dev_core_1405
[civicrm-core.git] / CRM / Core / BAO / Address.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
192d36c5 19 * This is class to handle address related functions.
6a488035
TO
20 */
21class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
22
23 /**
fe482240 24 * Takes an associative array and creates a address.
6a488035 25 *
6a0b768e
TO
26 * @param array $params
27 * (reference ) an assoc array of name/value pairs.
28 * @param bool $fixAddress
29 * True if you need to fix (format) address values.
6a488035
TO
30 * before inserting in db
31 *
e63aff1c
EM
32 * @param null $entity
33 *
a1a2a83d 34 * @return array|NULL
a6c01b45 35 * array of created address
6a488035 36 */
00be9182 37 public static function create(&$params, $fixAddress = TRUE, $entity = NULL) {
6a488035 38 if (!isset($params['address']) || !is_array($params['address'])) {
a1a2a83d 39 return NULL;
6a488035
TO
40 }
41 CRM_Core_BAO_Block::sortPrimaryFirst($params['address']);
be2fb01f 42 $addresses = [];
6a488035
TO
43 $contactId = NULL;
44
45 $updateBlankLocInfo = CRM_Utils_Array::value('updateBlankLocInfo', $params, FALSE);
46 if (!$entity) {
47 $contactId = $params['contact_id'];
48 //get all the addresses for this contact
85c882c5 49 $addresses = self::allAddress($contactId);
6a488035
TO
50 }
51 else {
52 // get all address from location block
be2fb01f 53 $entityElements = [
6a488035
TO
54 'entity_table' => $params['entity_table'],
55 'entity_id' => $params['entity_id'],
be2fb01f 56 ];
6a488035
TO
57 $addresses = self::allEntityAddress($entityElements);
58 }
59
60 $isPrimary = $isBilling = TRUE;
be2fb01f 61 $blocks = [];
6a488035
TO
62 foreach ($params['address'] as $key => $value) {
63 if (!is_array($value)) {
64 continue;
65 }
66
67 $addressExists = self::dataExists($value);
dfcbddc8 68 if (empty($value['id'])) {
85c882c5 69 if (!empty($addresses) && array_key_exists(CRM_Utils_Array::value('location_type_id', $value), $addresses)) {
70 $value['id'] = $addresses[CRM_Utils_Array::value('location_type_id', $value)];
6a488035
TO
71 }
72 }
73
74 // Note there could be cases when address info already exist ($value[id] is set) for a contact/entity
75 // BUT info is not present at this time, and therefore we should be really careful when deleting the block.
76 // $updateBlankLocInfo will help take appropriate decision. CRM-5969
dfcbddc8 77 if (isset($value['id']) && !$addressExists && $updateBlankLocInfo) {
6a488035 78 //delete the existing record
be2fb01f 79 CRM_Core_BAO_Block::blockDelete('Address', ['id' => $value['id']]);
6a488035
TO
80 continue;
81 }
82 elseif (!$addressExists) {
83 continue;
84 }
85
8cc574cf 86 if ($isPrimary && !empty($value['is_primary'])) {
6a488035
TO
87 $isPrimary = FALSE;
88 }
89 else {
90 $value['is_primary'] = 0;
91 }
92
8cc574cf 93 if ($isBilling && !empty($value['is_billing'])) {
6a488035
TO
94 $isBilling = FALSE;
95 }
96 else {
97 $value['is_billing'] = 0;
98 }
99
a7488080 100 if (empty($value['manual_geo_code'])) {
6a488035
TO
101 $value['manual_geo_code'] = 0;
102 }
103 $value['contact_id'] = $contactId;
104 $blocks[] = self::add($value, $fixAddress);
105 }
106
107 return $blocks;
108 }
109
110 /**
fe482240 111 * Takes an associative array and adds address.
6a488035 112 *
6a0b768e
TO
113 * @param array $params
114 * (reference ) an assoc array of name/value pairs.
115 * @param bool $fixAddress
116 * True if you need to fix (format) address values.
6a488035
TO
117 * before inserting in db
118 *
906e6120 119 * @return CRM_Core_BAO_Address|null
6a488035 120 */
be6b584c 121 public static function add(&$params, $fixAddress = FALSE) {
e9ff5391 122
6a488035 123 $address = new CRM_Core_DAO_Address();
e9ff5391 124 $checkPermissions = isset($params['check_permissions']) ? $params['check_permissions'] : TRUE;
6a488035
TO
125
126 // fixAddress mode to be done
127 if ($fixAddress) {
128 CRM_Core_BAO_Address::fixAddress($params);
129 }
130
131 $hook = empty($params['id']) ? 'create' : 'edit';
132 CRM_Utils_Hook::pre($hook, 'Address', CRM_Utils_Array::value('id', $params), $params);
133
134 // if id is set & is_primary isn't we can assume no change
135 if (is_numeric(CRM_Utils_Array::value('is_primary', $params)) || empty($params['id'])) {
136 CRM_Core_BAO_Block::handlePrimary($params, get_class());
137 }
e9ff5391 138
be41af0b 139 // (prevent chaining 1 and 3) CRM-21214
6b60d8a3 140 if (isset($params['master_id']) && !CRM_Utils_System::isNull($params['master_id'])) {
38e60804 141 self::fixSharedAddress($params);
810c57a2
D
142 }
143
6a488035
TO
144 $address->copyValues($params);
145
146 $address->save();
147
148 if ($address->id) {
2c59f0ca
CW
149 if (isset($params['custom'])) {
150 $addressCustom = $params['custom'];
151 }
152 else {
153 $customFields = CRM_Core_BAO_CustomField::getFields('Address', FALSE, TRUE, NULL, NULL, FALSE, FALSE, $checkPermissions);
154
155 if (!empty($customFields)) {
156 $addressCustom = CRM_Core_BAO_CustomField::postProcess($params,
157 $address->id,
158 'Address',
159 FALSE,
160 $checkPermissions
161 );
162 }
6a488035
TO
163 }
164 if (!empty($addressCustom)) {
165 CRM_Core_BAO_CustomValueTable::store($addressCustom, 'civicrm_address', $address->id);
166 }
167
810c57a2
D
168 // call the function to sync shared address and create relationships
169 // if address is already shared, share master_id with all children and update relationships accordingly
38e60804 170 // (prevent chaining 2) CRM-21214
6a488035
TO
171 self::processSharedAddress($address->id, $params);
172
6a488035
TO
173 // lets call the post hook only after we've done all the follow on processing
174 CRM_Utils_Hook::post($hook, 'Address', $address->id, $address);
175 }
176
177 return $address;
178 }
179
180 /**
fe482240 181 * Format the address params to have reasonable values.
6a488035 182 *
6a0b768e
TO
183 * @param array $params
184 * (reference ) an assoc array of name/value pairs.
6a488035 185 */
00be9182 186 public static function fixAddress(&$params) {
a7488080 187 if (!empty($params['billing_street_address'])) {
b44e3f84 188 //Check address is coming from online contribution / registration page
6a488035 189 //Fixed :CRM-5076
be2fb01f 190 $billing = [
6a488035
TO
191 'street_address' => 'billing_street_address',
192 'city' => 'billing_city',
193 'postal_code' => 'billing_postal_code',
194 'state_province' => 'billing_state_province',
195 'state_province_id' => 'billing_state_province_id',
196 'country' => 'billing_country',
197 'country_id' => 'billing_country_id',
be2fb01f 198 ];
6a488035
TO
199
200 foreach ($billing as $key => $val) {
201 if ($value = CRM_Utils_Array::value($val, $params)) {
a7488080 202 if (!empty($params[$key])) {
6a488035
TO
203 unset($params[$val]);
204 }
205 else {
206 //add new key and removed old
207 $params[$key] = $value;
208 unset($params[$val]);
209 }
210 }
211 }
212 }
213
214 /* Split the zip and +4, if it's in US format */
a7488080 215 if (!empty($params['postal_code']) &&
6a488035
TO
216 preg_match('/^(\d{4,5})[+-](\d{4})$/',
217 $params['postal_code'],
218 $match
219 )
220 ) {
221 $params['postal_code'] = $match[1];
222 $params['postal_code_suffix'] = $match[2];
223 }
224
225 // add country id if not set
226 if ((!isset($params['country_id']) || !is_numeric($params['country_id'])) &&
227 isset($params['country'])
228 ) {
229 $country = new CRM_Core_DAO_Country();
230 $country->name = $params['country'];
231 if (!$country->find(TRUE)) {
232 $country->name = NULL;
233 $country->iso_code = $params['country'];
234 $country->find(TRUE);
235 }
236 $params['country_id'] = $country->id;
237 }
238
239 // add state_id if state is set
240 if ((!isset($params['state_province_id']) || !is_numeric($params['state_province_id']))
241 && isset($params['state_province'])
242 ) {
243 if (!empty($params['state_province'])) {
244 $state_province = new CRM_Core_DAO_StateProvince();
245 $state_province->name = $params['state_province'];
246
247 // add country id if present
248 if (!empty($params['country_id'])) {
249 $state_province->country_id = $params['country_id'];
250 }
251
252 if (!$state_province->find(TRUE)) {
253 unset($state_province->name);
254 $state_province->abbreviation = $params['state_province'];
255 $state_province->find(TRUE);
256 }
257 $params['state_province_id'] = $state_province->id;
258 if (empty($params['country_id'])) {
259 // set this here since we have it
260 $params['country_id'] = $state_province->country_id;
261 }
262 }
263 else {
264 $params['state_province_id'] = 'null';
265 }
266 }
267
268 // add county id if county is set
269 // CRM-7837
270 if ((!isset($params['county_id']) || !is_numeric($params['county_id']))
271 && isset($params['county']) && !empty($params['county'])
272 ) {
273 $county = new CRM_Core_DAO_County();
274 $county->name = $params['county'];
275
276 if (isset($params['state_province_id'])) {
277 $county->state_province_id = $params['state_province_id'];
278 }
279
280 if ($county->find(TRUE)) {
281 $params['county_id'] = $county->id;
282 }
283 }
284
285 // currently copy values populates empty fields with the string "null"
286 // and hence need to check for the string null
287 if (isset($params['state_province_id']) &&
288 is_numeric($params['state_province_id']) &&
289 (!isset($params['country_id']) || empty($params['country_id']))
290 ) {
291 // since state id present and country id not present, hence lets populate it
292 // jira issue http://issues.civicrm.org/jira/browse/CRM-56
293 $params['country_id'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_StateProvince',
294 $params['state_province_id'],
295 'country_id'
296 );
297 }
298
299 //special check to ignore non numeric values if they are not
300 //detected by formRule(sometimes happens due to internet latency), also allow user to unselect state/country
301 if (isset($params['state_province_id'])) {
302 if (empty($params['state_province_id'])) {
303 $params['state_province_id'] = 'null';
304 }
305 elseif (!is_numeric($params['state_province_id']) ||
306 ((int ) $params['state_province_id'] < 1000)
307 ) {
308 // CRM-3393 ( the hacky 1000 check)
309 $params['state_province_id'] = 'null';
310 }
311 }
312
313 if (isset($params['country_id'])) {
314 if (empty($params['country_id'])) {
315 $params['country_id'] = 'null';
316 }
317 elseif (!is_numeric($params['country_id']) ||
318 ((int ) $params['country_id'] < 1000)
319 ) {
320 // CRM-3393 ( the hacky 1000 check)
321 $params['country_id'] = 'null';
322 }
323 }
324
325 // add state and country names from the ids
326 if (isset($params['state_province_id']) && is_numeric($params['state_province_id'])) {
327 $params['state_province'] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($params['state_province_id']);
328 }
329
330 if (isset($params['country_id']) && is_numeric($params['country_id'])) {
331 $params['country'] = CRM_Core_PseudoConstant::country($params['country_id']);
332 }
333
aaffa79f 334 $asp = Civi::settings()->get('address_standardization_provider');
6a488035
TO
335 // clean up the address via USPS web services if enabled
336 if ($asp === 'USPS' &&
337 $params['country_id'] == 1228
338 ) {
339 CRM_Utils_Address_USPS::checkAddress($params);
e6f202ae
JM
340 }
341 // do street parsing again if enabled, since street address might have changed
342 $parseStreetAddress = CRM_Utils_Array::value(
343 'street_address_parsing',
344 CRM_Core_BAO_Setting::valueOptions(
345 CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
346 'address_options'
347 ),
348 FALSE
349 );
350
351 if ($parseStreetAddress && !empty($params['street_address'])) {
cf9ccf98 352 foreach (['street_number', 'street_name', 'street_unit', 'street_number_suffix'] as $fld) {
e6f202ae 353 unset($params[$fld]);
6a488035 354 }
e6f202ae
JM
355 // main parse string.
356 $parseString = CRM_Utils_Array::value('street_address', $params);
357 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString);
358
359 // merge parse address in to main address block.
360 $params = array_merge($params, $parsedFields);
6a488035
TO
361 }
362
4882d275
FG
363 // skip_geocode is an optional parameter through the api.
364 // manual_geo_code is on the contact edit form. They do the same thing....
365 if (empty($params['skip_geocode']) && empty($params['manual_geo_code'])) {
366 self::addGeocoderData($params);
6a488035 367 }
4882d275 368
6a488035
TO
369 }
370
371 /**
fe482240 372 * Check if there is data to create the object.
6a488035 373 *
6a0b768e
TO
374 * @param array $params
375 * (reference ) an assoc array of name/value pairs.
6a488035 376 *
5c766a0b 377 * @return bool
6a488035 378 */
00be9182 379 public static function dataExists(&$params) {
6a488035
TO
380 //check if location type is set if not return false
381 if (!isset($params['location_type_id'])) {
382 return FALSE;
383 }
384
385 $config = CRM_Core_Config::singleton();
386 foreach ($params as $name => $value) {
be2fb01f 387 if (in_array($name, [
353ffa53
TO
388 'is_primary',
389 'location_type_id',
390 'id',
391 'contact_id',
392 'is_billing',
393 'display',
af9b09df 394 'master_id',
be2fb01f 395 ])) {
6a488035
TO
396 continue;
397 }
398 elseif (!CRM_Utils_System::isNull($value)) {
399 // name could be country or country id
400 if (substr($name, 0, 7) == 'country') {
401 // make sure its different from the default country
402 // iso code
d14f0a66 403 $defaultCountry = CRM_Core_BAO_Country::defaultContactCountry();
6a488035 404 // full name
d14f0a66 405 $defaultCountryName = CRM_Core_BAO_Country::defaultContactCountryName();
6a488035
TO
406
407 if ($defaultCountry) {
408 if ($value == $defaultCountry ||
409 $value == $defaultCountryName ||
410 $value == $config->defaultContactCountry
411 ) {
412 // do nothing
413 }
414 else {
415 return TRUE;
416 }
417 }
418 else {
419 // return if null default
420 return TRUE;
421 }
422 }
423 else {
424 return TRUE;
425 }
426 }
427 }
428
429 return FALSE;
430 }
431
432 /**
433 * Given the list of params in the params array, fetch the object
434 * and store the values in the values array
435 *
6a0b768e
TO
436 * @param array $entityBlock
437 * Associated array of fields.
438 * @param bool $microformat
439 * If microformat output is required.
e63aff1c 440 * @param int|string $fieldName conditional field name
6a488035 441 *
a6c01b45
CW
442 * @return array
443 * array with address fields
6a488035 444 */
00be9182 445 public static function &getValues($entityBlock, $microformat = FALSE, $fieldName = 'contact_id') {
6a488035
TO
446 if (empty($entityBlock)) {
447 return NULL;
448 }
be2fb01f 449 $addresses = [];
6a488035
TO
450 $address = new CRM_Core_BAO_Address();
451
a7488080 452 if (empty($entityBlock['entity_table'])) {
6a488035
TO
453 $address->$fieldName = CRM_Utils_Array::value($fieldName, $entityBlock);
454 }
455 else {
be2fb01f 456 $addressIds = [];
6a488035
TO
457 $addressIds = self::allEntityAddress($entityBlock);
458
459 if (!empty($addressIds[1])) {
460 $address->id = $addressIds[1];
461 }
462 else {
463 return $addresses;
464 }
465 }
001a6635
JV
466 if (isset($entityBlock['is_billing']) && $entityBlock['is_billing'] == 1) {
467 $address->orderBy('is_billing desc, id');
468 }
469 else {
470 //get primary address as a first block.
471 $address->orderBy('is_primary desc, id');
472 }
6a488035
TO
473
474 $address->find();
475
ac44bb1c 476 $locationTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
6a488035
TO
477 $count = 1;
478 while ($address->fetch()) {
479 // deprecate reference.
480 if ($count > 1) {
cf9ccf98 481 foreach (['state', 'state_name', 'country', 'world_region'] as $fld) {
4f99ca55
TO
482 if (isset($address->$fld)) {
483 unset($address->$fld);
2aa397bc 484 }
6a488035
TO
485 }
486 }
487 $stree = $address->street_address;
be2fb01f 488 $values = [];
6a488035
TO
489 CRM_Core_DAO::storeValues($address, $values);
490
491 // add state and country information: CRM-369
ac44bb1c
WA
492 if (!empty($address->location_type_id)) {
493 $values['location_type'] = CRM_Utils_Array::value($address->location_type_id, $locationTypes);
494 }
6a488035
TO
495 if (!empty($address->state_province_id)) {
496 $address->state = CRM_Core_PseudoConstant::stateProvinceAbbreviation($address->state_province_id, FALSE);
497 $address->state_name = CRM_Core_PseudoConstant::stateProvince($address->state_province_id, FALSE);
498 }
499
500 if (!empty($address->country_id)) {
501 $address->country = CRM_Core_PseudoConstant::country($address->country_id);
502
503 //get world region
504 $regionId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Country', $address->country_id, 'region_id');
505
506 $address->world_region = CRM_Core_PseudoConstant::worldregion($regionId);
507 }
508
509 $address->addDisplay($microformat);
510
511 $values['display'] = $address->display;
512 $values['display_text'] = $address->display_text;
513
6b60d8a3 514 if (isset($address->master_id) && !CRM_Utils_System::isNull($address->master_id)) {
6a488035
TO
515 $values['use_shared_address'] = 1;
516 }
517
518 $addresses[$count] = $values;
519
96ca8376
MW
520 //There should never be more than one primary blocks, hence set is_primary = 0 other than first
521 // Calling functions expect the key is_primary to be set, so do not unset it here!
6a488035 522 if ($count > 1) {
96ca8376 523 $addresses[$count]['is_primary'] = 0;
6a488035
TO
524 }
525
526 $count++;
527 }
528
529 return $addresses;
530 }
531
532 /**
192d36c5 533 * Add the formatted address to $this-> display.
6a488035 534 *
2a6da8d7 535 * @param bool $microformat
192d36c5 536 * Unexplained parameter that I've always wondered about.
6a488035 537 */
00be9182 538 public function addDisplay($microformat = FALSE) {
be2fb01f 539 $fields = [
6a488035
TO
540 // added this for CRM 1200
541 'address_id' => $this->id,
542 // CRM-4003
543 'address_name' => str_replace('\ 1', ' ', $this->name),
544 'street_address' => $this->street_address,
545 'supplemental_address_1' => $this->supplemental_address_1,
546 'supplemental_address_2' => $this->supplemental_address_2,
207f62c6 547 'supplemental_address_3' => $this->supplemental_address_3,
6a488035
TO
548 'city' => $this->city,
549 'state_province_name' => isset($this->state_name) ? $this->state_name : "",
550 'state_province' => isset($this->state) ? $this->state : "",
551 'postal_code' => isset($this->postal_code) ? $this->postal_code : "",
552 'postal_code_suffix' => isset($this->postal_code_suffix) ? $this->postal_code_suffix : "",
553 'country' => isset($this->country) ? $this->country : "",
554 'world_region' => isset($this->world_region) ? $this->world_region : "",
be2fb01f 555 ];
6a488035
TO
556
557 if (isset($this->county_id) && $this->county_id) {
558 $fields['county'] = CRM_Core_PseudoConstant::county($this->county_id);
559 }
560 else {
561 $fields['county'] = NULL;
562 }
563
564 $this->display = CRM_Utils_Address::format($fields, NULL, $microformat);
565 $this->display_text = CRM_Utils_Address::format($fields);
566 }
567
568 /**
569 * Get all the addresses for a specified contact_id, with the primary address being first
570 *
6a0b768e
TO
571 * @param int $id
572 * The contact id.
6a488035 573 *
dfcbddc8
DG
574 * @param bool $updateBlankLocInfo
575 *
a6c01b45 576 * @return array
dfcbddc8 577 * the array of adrress data
6a488035 578 */
dfcbddc8 579 public static function allAddress($id, $updateBlankLocInfo = FALSE) {
6a488035
TO
580 if (!$id) {
581 return NULL;
582 }
583
584 $query = "
585SELECT civicrm_address.id as address_id, civicrm_address.location_type_id as location_type_id
586FROM civicrm_contact, civicrm_address
587WHERE civicrm_address.contact_id = civicrm_contact.id AND civicrm_contact.id = %1
588ORDER BY civicrm_address.is_primary DESC, address_id ASC";
be2fb01f 589 $params = [1 => [$id, 'Integer']];
dfcbddc8 590
be2fb01f 591 $addresses = [];
6a488035 592 $dao = CRM_Core_DAO::executeQuery($query, $params);
dfcbddc8 593 $count = 1;
6a488035 594 while ($dao->fetch()) {
dfcbddc8
DG
595 if ($updateBlankLocInfo) {
596 $addresses[$count++] = $dao->address_id;
597 }
598 else {
599 $addresses[$dao->location_type_id] = $dao->address_id;
600 }
6a488035
TO
601 }
602 return $addresses;
603 }
604
605 /**
606 * Get all the addresses for a specified location_block id, with the primary address being first
607 *
6a0b768e
TO
608 * @param array $entityElements
609 * The array containing entity_id and.
16b10e64 610 * entity_table name
6a488035 611 *
a6c01b45 612 * @return array
dfcbddc8 613 * the array of adrress data
6a488035 614 */
00be9182 615 public static function allEntityAddress(&$entityElements) {
be2fb01f 616 $addresses = [];
6a488035
TO
617 if (empty($entityElements)) {
618 return $addresses;
619 }
620
621 $entityId = $entityElements['entity_id'];
622 $entityTable = $entityElements['entity_table'];
623
624 $sql = "
dfcbddc8 625SELECT civicrm_address.id as address_id
6a488035
TO
626FROM civicrm_loc_block loc, civicrm_location_type ltype, civicrm_address, {$entityTable} ev
627WHERE ev.id = %1
628 AND loc.id = ev.loc_block_id
629 AND civicrm_address.id IN (loc.address_id, loc.address_2_id)
630 AND ltype.id = civicrm_address.location_type_id
631ORDER BY civicrm_address.is_primary DESC, civicrm_address.location_type_id DESC, address_id ASC ";
632
be2fb01f 633 $params = [1 => [$entityId, 'Integer']];
6a488035 634 $dao = CRM_Core_DAO::executeQuery($sql, $params);
dfcbddc8 635 $locationCount = 1;
6a488035 636 while ($dao->fetch()) {
dfcbddc8
DG
637 $addresses[$locationCount] = $dao->address_id;
638 $locationCount++;
6a488035
TO
639 }
640 return $addresses;
641 }
642
6a488035 643 /**
fe482240 644 * Get address sequence.
6a488035 645 *
a6c01b45 646 * @return array
16b10e64 647 * Array of address sequence.
6a488035 648 */
00be9182 649 public static function addressSequence() {
9b118398 650 $addressSequence = CRM_Utils_Address::sequence(\Civi::settings()->get('address_format'));
6a488035
TO
651
652 $countryState = $cityPostal = FALSE;
653 foreach ($addressSequence as $key => $field) {
654 if (
be2fb01f 655 in_array($field, ['country', 'state_province']) &&
6a488035
TO
656 !$countryState
657 ) {
658 $countryState = TRUE;
659 $addressSequence[$key] = 'country_state_province';
660 }
661 elseif (
be2fb01f 662 in_array($field, ['city', 'postal_code']) &&
6a488035
TO
663 !$cityPostal
664 ) {
665 $cityPostal = TRUE;
666 $addressSequence[$key] = 'city_postal_code';
667 }
668 elseif (
be2fb01f 669 in_array($field, ['country', 'state_province', 'city', 'postal_code'])
6a488035
TO
670 ) {
671 unset($addressSequence[$key]);
672 }
673 }
674
675 return $addressSequence;
676 }
677
678 /**
679 * Parse given street address string in to street_name,
680 * street_unit, street_number and street_number_suffix
681 * eg "54A Excelsior Ave. Apt 1C", or "917 1/2 Elm Street"
682 *
683 * NB: civic street formats for en_CA and fr_CA used by default if those locales are active
684 * otherwise en_US format is default action
685 *
6c552737
TO
686 * @param string $streetAddress
687 * Street address including number and apt.
688 * @param string $locale
689 * Locale used to parse address.
6a488035 690 *
a6c01b45
CW
691 * @return array
692 * parsed fields values.
6a488035 693 */
00be9182 694 public static function parseStreetAddress($streetAddress, $locale = NULL) {
1d148cb9
O
695 // use 'en_US' for address parsing if the requested locale is not supported.
696 if (!self::isSupportedParsingLocale($locale)) {
6a488035
TO
697 $locale = 'en_US';
698 }
1d148cb9 699
be2fb01f 700 $emptyParseFields = $parseFields = [
6a488035
TO
701 'street_name' => '',
702 'street_unit' => '',
703 'street_number' => '',
704 'street_number_suffix' => '',
be2fb01f 705 ];
6a488035
TO
706
707 if (empty($streetAddress)) {
708 return $parseFields;
709 }
710
711 $streetAddress = trim($streetAddress);
712
be2fb01f 713 $matches = [];
cf9ccf98
TO
714 if (in_array($locale, ['en_CA', 'fr_CA'])
715 && preg_match('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', $streetAddress, $matches)
353ffa53 716 ) {
6a488035
TO
717 $parseFields['street_unit'] = $matches[1];
718 // unset from rest of street address
719 $streetAddress = preg_replace('/^([A-Za-z0-9]+)[ ]*\-[ ]*/', '', $streetAddress);
720 }
721
722 // get street number and suffix.
be2fb01f 723 $matches = [];
6a488035
TO
724 //alter street number/suffix handling so that we accept -digit
725 if (preg_match('/^[A-Za-z0-9]+([\S]+)/', $streetAddress, $matches)) {
726 // check that $matches[0] is numeric, else assume no street number
727 if (preg_match('/^(\d+)/', $matches[0])) {
728 $streetNumAndSuffix = $matches[0];
729
730 // get street number.
be2fb01f 731 $matches = [];
6a488035
TO
732 if (preg_match('/^(\d+)/', $streetNumAndSuffix, $matches)) {
733 $parseFields['street_number'] = $matches[0];
734 $suffix = preg_replace('/^(\d+)/', '', $streetNumAndSuffix);
735 $parseFields['street_number_suffix'] = trim($suffix);
736 }
737
738 // unset from main street address.
739 $streetAddress = preg_replace('/^[A-Za-z0-9]+([\S]+)/', '', $streetAddress);
740 $streetAddress = trim($streetAddress);
741 }
742 }
743 elseif (preg_match('/^(\d+)/', $streetAddress, $matches)) {
744 $parseFields['street_number'] = $matches[0];
745 // unset from main street address.
746 $streetAddress = preg_replace('/^(\d+)/', '', $streetAddress);
747 $streetAddress = trim($streetAddress);
748 }
749
d01ae020 750 // If street number is too large, we cannot store it.
751 if ($parseFields['street_number'] > CRM_Utils_Type::INT_MAX) {
752 return $emptyParseFields;
753 }
754
6a488035 755 // suffix might be like 1/2
be2fb01f 756 $matches = [];
6a488035
TO
757 if (preg_match('/^\d\/\d/', $streetAddress, $matches)) {
758 $parseFields['street_number_suffix'] .= $matches[0];
759
760 // unset from main street address.
761 $streetAddress = preg_replace('/^\d+\/\d+/', '', $streetAddress);
762 $streetAddress = trim($streetAddress);
763 }
764
765 // now get the street unit.
766 // supportable street unit formats.
be2fb01f 767 $streetUnitFormats = [
353ffa53
TO
768 'APT',
769 'APARTMENT',
770 'BSMT',
771 'BASEMENT',
772 'BLDG',
773 'BUILDING',
774 'DEPT',
775 'DEPARTMENT',
776 'FL',
777 'FLOOR',
778 'FRNT',
779 'FRONT',
780 'HNGR',
781 'HANGER',
782 'LBBY',
783 'LOBBY',
784 'LOWR',
785 'LOWER',
786 'OFC',
787 'OFFICE',
788 'PH',
789 'PENTHOUSE',
790 'TRLR',
791 'TRAILER',
792 'UPPR',
793 'RM',
794 'ROOM',
795 'SIDE',
796 'SLIP',
797 'KEY',
798 'LOT',
799 'PIER',
800 'REAR',
801 'SPC',
802 'SPACE',
803 'STOP',
804 'STE',
805 'SUITE',
806 'UNIT',
807 '#',
be2fb01f 808 ];
6a488035
TO
809
810 // overwriting $streetUnitFormats for 'en_CA' and 'fr_CA' locale
be2fb01f 811 if (in_array($locale, [
353ffa53 812 'en_CA',
af9b09df 813 'fr_CA',
be2fb01f
CW
814 ])) {
815 $streetUnitFormats = ['APT', 'APP', 'SUITE', 'BUREAU', 'UNIT'];
6a488035 816 }
689cecfd
EM
817 //@todo per CRM-14459 this regex picks up words with the string in them - e.g APT picks up
818 //Captain - presuming fixing regex (& adding test) to ensure a-z does not preced string will fix
6a488035 819 $streetUnitPreg = '/(' . implode('|\s', $streetUnitFormats) . ')(.+)?/i';
be2fb01f 820 $matches = [];
6a488035
TO
821 if (preg_match($streetUnitPreg, $streetAddress, $matches)) {
822 $parseFields['street_unit'] = trim($matches[0]);
823 $streetAddress = str_replace($matches[0], '', $streetAddress);
824 $streetAddress = trim($streetAddress);
825 }
826
827 // consider remaining string as street name.
828 $parseFields['street_name'] = $streetAddress;
829
830 //run parsed fields through stripSpaces to clean
831 foreach ($parseFields as $parseField => $value) {
832 $parseFields[$parseField] = CRM_Utils_String::stripSpaces($value);
833 }
689cecfd
EM
834 //CRM-14459 if the field is too long we should assume it didn't get it right & skip rather than allow
835 // the DB to fatal
836 $fields = CRM_Core_BAO_Address::fields();
837 foreach ($fields as $fieldname => $field) {
22e263ad 838 if (!empty($field['maxlength']) && strlen(CRM_Utils_Array::value($fieldname, $parseFields)) > $field['maxlength']) {
689cecfd
EM
839 return $emptyParseFields;
840 }
841 }
6a488035
TO
842
843 return $parseFields;
844 }
845
1d148cb9
O
846 /**
847 * Determines if the specified locale is
848 * supported by address parsing.
849 * If no locale is specified then it
850 * will check the default configured locale.
851 *
852 * locales supported include:
853 * en_US - http://pe.usps.com/cpim/ftp/pubs/pub28/pub28.pdf
854 * en_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp
855 * fr_CA - http://www.canadapost.ca/tools/pg/manual/PGaddress-f.asp
856 * NB: common use of comma after street number also supported
857 *
858 * @param string $locale
859 * The locale to be checked
860 *
9a208ac3 861 * @return bool
1d148cb9
O
862 */
863 public static function isSupportedParsingLocale($locale = NULL) {
864 if (!$locale) {
865 $config = CRM_Core_Config::singleton();
866 $locale = $config->lcMessages;
867 }
868
be2fb01f 869 $parsingSupportedLocales = ['en_US', 'en_CA', 'fr_CA'];
1d148cb9
O
870
871 if (in_array($locale, $parsingSupportedLocales)) {
872 return TRUE;
873 }
874
875 return FALSE;
876 }
877
6a488035 878 /**
eceb18cc 879 * Validate the address fields based on the address options enabled.
6a488035
TO
880 * in the Address Settings
881 *
6a0b768e
TO
882 * @param array $fields
883 * An array of importable/exportable contact fields.
6a488035 884 *
a6c01b45
CW
885 * @return array
886 * an array of contact fields and only the enabled address options
6a488035 887 */
00be9182 888 public static function validateAddressOptions($fields) {
6a488035
TO
889 static $addressOptions = NULL;
890 if (!$addressOptions) {
6c552737
TO
891 $addressOptions = CRM_Core_BAO_Setting::valueOptions(
892 CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
893 'address_options'
894 );
6a488035
TO
895 }
896
897 if (is_array($fields) && !empty($fields)) {
898 foreach ($addressOptions as $key => $value) {
899 if (!$value && isset($fields[$key])) {
900 unset($fields[$key]);
901 }
902 }
903 }
904 return $fields;
905 }
906
907 /**
fe482240 908 * Check if current address is used by any other contacts.
6a488035 909 *
6a0b768e
TO
910 * @param int $addressId
911 * Address id.
6a488035 912 *
72b3a70c
CW
913 * @return int
914 * count of contacts that use this shared address
6a488035 915 */
00be9182 916 public static function checkContactSharedAddress($addressId) {
6a488035 917 $query = 'SELECT count(id) FROM civicrm_address WHERE master_id = %1';
be2fb01f 918 return CRM_Core_DAO::singleValueQuery($query, [1 => [$addressId, 'Integer']]);
6a488035
TO
919 }
920
921 /**
fe482240 922 * Check if current address fields are shared with any other address.
6a488035 923 *
6a0b768e
TO
924 * @param array $fields
925 * Address fields in profile.
926 * @param int $contactId
927 * Contact id.
6a488035 928 *
6a488035 929 */
00be9182 930 public static function checkContactSharedAddressFields(&$fields, $contactId) {
6a488035
TO
931 if (!$contactId || !is_array($fields) || empty($fields)) {
932 return;
933 }
934
be2fb01f 935 $sharedLocations = [];
6a488035
TO
936
937 $query = "
938SELECT is_primary,
939 location_type_id
940 FROM civicrm_address
941 WHERE contact_id = %1
942 AND master_id IS NOT NULL";
943
be2fb01f 944 $dao = CRM_Core_DAO::executeQuery($query, [1 => [$contactId, 'Positive']]);
6a488035
TO
945 while ($dao->fetch()) {
946 $sharedLocations[$dao->location_type_id] = $dao->location_type_id;
947 if ($dao->is_primary) {
948 $sharedLocations['Primary'] = 'Primary';
949 }
950 }
951
952 //no need to process further.
953 if (empty($sharedLocations)) {
954 return;
955 }
956
be2fb01f 957 $addressFields = [
6a488035
TO
958 'city',
959 'county',
960 'country',
961 'geo_code_1',
962 'geo_code_2',
963 'postal_code',
964 'address_name',
965 'state_province',
966 'street_address',
967 'postal_code_suffix',
968 'supplemental_address_1',
969 'supplemental_address_2',
207f62c6 970 'supplemental_address_3',
be2fb01f 971 ];
6a488035
TO
972
973 foreach ($fields as $name => & $values) {
974 if (!is_array($values) || empty($values)) {
975 continue;
976 }
977
978 $nameVal = explode('-', $values['name']);
979 $fldName = CRM_Utils_Array::value(0, $nameVal);
980 $locType = CRM_Utils_Array::value(1, $nameVal);
a7488080 981 if (!empty($values['location_type_id'])) {
6a488035
TO
982 $locType = $values['location_type_id'];
983 }
984
985 if (in_array($fldName, $addressFields) &&
986 in_array($locType, $sharedLocations)
987 ) {
988 $values['is_shared'] = TRUE;
989 }
990 }
991 }
992
38e60804
D
993 /**
994 * Fix the shared address if address is already shared
995 * or if address will be shared with itself.
996 *
997 * @param array $params
998 * Associated array of address params.
999 */
1000 public static function fixSharedAddress(&$params) {
be41af0b
D
1001 // if address master address is shared, use its master (prevent chaining 1) CRM-21214
1002 $masterMasterId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Address', $params['master_id'], 'master_id');
1003 if ($masterMasterId > 0) {
1004 $params['master_id'] = $masterMasterId;
38e60804
D
1005 }
1006
be41af0b
D
1007 // prevent an endless chain between two shared addresses (prevent chaining 3) CRM-21214
1008 if (CRM_Utils_Array::value('id', $params) == $params['master_id']) {
68499476 1009 $params['master_id'] = NULL;
38e60804
D
1010 CRM_Core_Session::setStatus(ts("You can't connect an address to itself"), '', 'warning');
1011 }
1012 }
01853fc8 1013
6a488035 1014 /**
fe482240 1015 * Update the shared addresses if master address is modified.
6a488035 1016 *
6a0b768e
TO
1017 * @param int $addressId
1018 * Address id.
1019 * @param array $params
1020 * Associated array of address params.
6a488035 1021 */
00be9182 1022 public static function processSharedAddress($addressId, $params) {
810c57a2 1023 $query = 'SELECT id, contact_id FROM civicrm_address WHERE master_id = %1';
be2fb01f 1024 $dao = CRM_Core_DAO::executeQuery($query, [1 => [$addressId, 'Integer']]);
6a488035 1025
e0fb9216
CW
1026 // Default to TRUE if not set to maintain api backward compatibility.
1027 $createRelationship = isset($params['update_current_employer']) ? $params['update_current_employer'] : TRUE;
1028
6a488035 1029 // unset contact id
be2fb01f 1030 $skipFields = ['is_primary', 'location_type_id', 'is_billing', 'contact_id'];
6b60d8a3 1031 if (isset($params['master_id']) && !CRM_Utils_System::isNull($params['master_id'])) {
e0fb9216
CW
1032 if ($createRelationship) {
1033 // call the function to create a relationship for the new shared address
1034 self::processSharedAddressRelationship($params['master_id'], $params['contact_id']);
1035 }
8156e86b
D
1036 }
1037 else {
be41af0b 1038 // else no new shares will be created, only update shared addresses
810c57a2
D
1039 $skipFields[] = 'master_id';
1040 }
6a488035
TO
1041 foreach ($skipFields as $value) {
1042 unset($params[$value]);
1043 }
1044
1045 $addressDAO = new CRM_Core_DAO_Address();
1046 while ($dao->fetch()) {
b18c70b3 1047 // call the function to update the relationship
e0fb9216 1048 if ($createRelationship && isset($params['master_id']) && !CRM_Utils_System::isNull($params['master_id'])) {
810c57a2
D
1049 self::processSharedAddressRelationship($params['master_id'], $dao->contact_id);
1050 }
6a488035
TO
1051 $addressDAO->copyValues($params);
1052 $addressDAO->id = $dao->id;
1053 $addressDAO->save();
6a488035
TO
1054 }
1055 }
1056
8bdfc216 1057 /**
fe482240 1058 * Merge contacts with the Same address to get one shared label.
6a0b768e
TO
1059 * @param array $rows
1060 * Array[contact_id][contactDetails].
8bdfc216 1061 */
1062 public static function mergeSameAddress(&$rows) {
be2fb01f 1063 $uniqueAddress = [];
8bdfc216 1064 foreach (array_keys($rows) as $rowID) {
1065 // load complete address as array key
6c552737
TO
1066 $address = trim($rows[$rowID]['street_address'])
1067 . trim($rows[$rowID]['city'])
1068 . trim($rows[$rowID]['state_province'])
1069 . trim($rows[$rowID]['postal_code'])
1070 . trim($rows[$rowID]['country']);
8bdfc216 1071 if (isset($rows[$rowID]['last_name'])) {
1072 $name = $rows[$rowID]['last_name'];
1073 }
1074 else {
1075 $name = $rows[$rowID]['display_name'];
1076 }
1077
1078 // CRM-15120
be2fb01f 1079 $formatted = [
8bdfc216 1080 'first_name' => $rows[$rowID]['first_name'],
21dfd5f5 1081 'individual_prefix' => $rows[$rowID]['individual_prefix'],
be2fb01f 1082 ];
aaffa79f 1083 $format = Civi::settings()->get('display_name_format');
4c49535e 1084 $firstNameWithPrefix = CRM_Utils_Address::format($formatted, $format, FALSE, FALSE);
8bdfc216 1085 $firstNameWithPrefix = trim($firstNameWithPrefix);
1086
1087 // fill uniqueAddress array with last/first name tree
1088 if (isset($uniqueAddress[$address])) {
1089 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
1090 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
1091 // drop unnecessary rows
1092 unset($rows[$rowID]);
1093 // this is the first listing at this address
1094 }
1095 else {
1096 $uniqueAddress[$address]['ID'] = $rowID;
1097 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['first_name'] = $rows[$rowID]['first_name'];
1098 $uniqueAddress[$address]['names'][$name][$firstNameWithPrefix]['addressee_display'] = $rows[$rowID]['addressee_display'];
1099 }
1100 }
1101 foreach ($uniqueAddress as $address => $data) {
1102 // copy data back to $rows
1103 $count = 0;
1104 // one last name list per row
1105 foreach ($data['names'] as $last_name => $first_names) {
1106 // too many to list
1107 if ($count > 2) {
1108 break;
1109 }
9b873358 1110 if (count($first_names) == 1) {
2aa397bc
TO
1111 $family = $first_names[current(array_keys($first_names))]['addressee_display'];
1112 }
1113 else {
1114 // collapse the tree to summarize
1115 $family = trim(implode(" & ", array_keys($first_names)) . " " . $last_name);
1116 }
1117 if ($count) {
1118 $processedNames .= "\n" . $family;
1119 }
1120 else {
1121 // build display_name string
1122 $processedNames = $family;
1123 }
8bdfc216 1124 $count++;
1125 }
1126 $rows[$data['ID']]['addressee'] = $rows[$data['ID']]['addressee_display'] = $rows[$data['ID']]['display_name'] = $processedNames;
1127 }
1128 }
1129
6a488035 1130 /**
fe482240 1131 * Create relationship between contacts who share an address.
6a488035 1132 *
810c57a2
D
1133 * Note that currently we create relationship between
1134 * Individual + Household and Individual + Organization
6a488035 1135 *
6a0b768e
TO
1136 * @param int $masterAddressId
1137 * Master address id.
810c57a2
D
1138 * @param int $currentContactId
1139 * Current contact id.
6a488035 1140 */
810c57a2 1141 public static function processSharedAddressRelationship($masterAddressId, $currentContactId) {
6a488035 1142 // get the contact type of contact being edited / created
810c57a2 1143 $currentContactType = CRM_Contact_BAO_Contact::getContactType($currentContactId);
6a488035
TO
1144
1145 // if current contact is not of type individual return
1146 if ($currentContactType != 'Individual') {
1147 return;
1148 }
1149
1150 // get the contact id and contact type of shared contact
1151 // check the contact type of shared contact, return if it is of type Individual
6a488035
TO
1152 $query = 'SELECT cc.id, cc.contact_type
1153 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1154 WHERE ca.id = %1';
1155
be2fb01f 1156 $dao = CRM_Core_DAO::executeQuery($query, [1 => [$masterAddressId, 'Integer']]);
6a488035
TO
1157 $dao->fetch();
1158
810c57a2 1159 // master address contact needs to be Household or Organization, otherwise return
6a488035
TO
1160 if ($dao->contact_type == 'Individual') {
1161 return;
1162 }
1163 $sharedContactType = $dao->contact_type;
1164 $sharedContactId = $dao->id;
1165
1166 // create relationship between ontacts who share an address
1167 if ($sharedContactType == 'Organization') {
1168 return CRM_Contact_BAO_Contact_Utils::createCurrentEmployerRelationship($currentContactId, $sharedContactId);
1169 }
6a488035 1170
82ffeed5 1171 // get the relationship type id of "Household Member of"
1172 $relTypeId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_RelationshipType', 'Household Member of', 'id', 'name_a_b');
6a488035
TO
1173
1174 if (!$relTypeId) {
82ffeed5 1175 CRM_Core_Error::fatal(ts("You seem to have deleted the relationship type 'Household Member of'"));
1176 }
1177
be2fb01f 1178 $relParam = [
82ffeed5 1179 'is_active' => TRUE,
1180 'relationship_type_id' => $relTypeId,
1181 'contact_id_a' => $currentContactId,
1182 'contact_id_b' => $sharedContactId,
be2fb01f 1183 ];
82ffeed5 1184
1185 // If already there is a relationship record of $relParam criteria, avoid creating relationship again or else
1186 // it will casue CRM-16588 as the Duplicate Relationship Exception will revert other contact field values on update
9272d8b5 1187 if (CRM_Contact_BAO_Relationship::checkDuplicateRelationship($relParam, $currentContactId, $sharedContactId)) {
82ffeed5 1188 return;
6a488035
TO
1189 }
1190
a7ef8c9d
EM
1191 try {
1192 // create relationship
82ffeed5 1193 civicrm_api3('relationship', 'create', $relParam);
a7ef8c9d
EM
1194 }
1195 catch (CiviCRM_API3_Exception $e) {
1196 // We catch and ignore here because this has historically been a best-effort relationship create call.
1197 // presumably it could refuse due to duplication or similar and we would ignore that.
1198 }
6a488035
TO
1199 }
1200
1201 /**
fe482240 1202 * Check and set the status for shared address delete.
6a488035 1203 *
6a0b768e
TO
1204 * @param int $addressId
1205 * Address id.
1206 * @param int $contactId
1207 * Contact id.
1208 * @param bool $returnStatus
1209 * By default false.
6a488035 1210 *
a6c01b45 1211 * @return string
6a488035 1212 */
00be9182 1213 public static function setSharedAddressDeleteStatus($addressId = NULL, $contactId = NULL, $returnStatus = FALSE) {
6a488035
TO
1214 // check if address that is being deleted has any shared
1215 if ($addressId) {
1216 $entityId = $addressId;
1217 $query = 'SELECT cc.id, cc.display_name
1218 FROM civicrm_contact cc INNER JOIN civicrm_address ca ON cc.id = ca.contact_id
1219 WHERE ca.master_id = %1';
1220 }
1221 else {
1222 $entityId = $contactId;
1223 $query = 'SELECT cc.id, cc.display_name
1224 FROM civicrm_address ca1
1225 INNER JOIN civicrm_address ca2 ON ca1.id = ca2.master_id
1226 INNER JOIN civicrm_contact cc ON ca2.contact_id = cc.id
1227 WHERE ca1.contact_id = %1';
1228 }
1229
be2fb01f 1230 $dao = CRM_Core_DAO::executeQuery($query, [1 => [$entityId, 'Integer']]);
6a488035 1231
be2fb01f
CW
1232 $deleteStatus = [];
1233 $sharedContactList = [];
6a488035
TO
1234 $statusMessage = NULL;
1235 $addressCount = 0;
1236 while ($dao->fetch()) {
1237 if (empty($deleteStatus)) {
1238 $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.');
1239 }
1240
1241 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', "reset=1&cid={$dao->id}");
1242 $sharedContactList[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1243 $deleteStatus[] = "<a href='{$contactViewUrl}'>{$dao->display_name}</a>";
1244
1245 $addressCount++;
1246 }
1247
1248 if (!empty($deleteStatus)) {
1249 $statusMessage = implode('<br/>', $deleteStatus) . '<br/>';
1250 }
1251
1252 if (!$returnStatus) {
1253 CRM_Core_Session::setStatus($statusMessage, '', 'info');
1254 }
1255 else {
be2fb01f 1256 return [
6a488035
TO
1257 'contactList' => $sharedContactList,
1258 'count' => $addressCount,
be2fb01f 1259 ];
6a488035
TO
1260 }
1261 }
12445e1c
CW
1262
1263 /**
fe482240 1264 * Call common delete function.
ad37ac8e 1265 *
1266 * @param int $id
1267 *
1268 * @return bool
12445e1c 1269 */
00be9182 1270 public static function del($id) {
a65e2e55 1271 return CRM_Contact_BAO_Contact::deleteObjectWithPrimary('Address', $id);
12445e1c 1272 }
dc86f881
CW
1273
1274 /**
1275 * Get options for a given address field.
1276 * @see CRM_Core_DAO::buildOptions
1277 *
1278 * TODO: Should we always assume chainselect? What fn should be responsible for controlling that flow?
1279 * TODO: In context of chainselect, what to return if e.g. a country has no states?
1280 *
6a0b768e
TO
1281 * @param string $fieldName
1282 * @param string $context
a1a2a83d 1283 * @see CRM_Core_DAO::buildOptionsContext
6a0b768e 1284 * @param array $props
16b10e64 1285 * whatever is known about this dao object.
77b97be7 1286 *
5c766a0b 1287 * @return array|bool
dc86f881 1288 */
be2fb01f
CW
1289 public static function buildOptions($fieldName, $context = NULL, $props = []) {
1290 $params = [];
dc86f881
CW
1291 // Special logic for fields whose options depend on context or properties
1292 switch ($fieldName) {
1293 // Filter state_province list based on chosen country or site defaults
1294 case 'state_province_id':
d0bfb983 1295 case 'state_province_name':
1296 case 'state_province':
1297 // change $fieldName to DB specific names.
1298 $fieldName = 'state_province_id';
63b56894 1299 if (empty($props['country_id']) && $context !== 'validate') {
dc86f881
CW
1300 $config = CRM_Core_Config::singleton();
1301 if (!empty($config->provinceLimit)) {
1302 $props['country_id'] = $config->provinceLimit;
1303 }
1304 else {
1305 $props['country_id'] = $config->defaultContactCountry;
1306 }
1307 }
eaf39b47 1308 if (!empty($props['country_id'])) {
62d7cac4
SL
1309 if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', (array) $props['country_id']))) {
1310 throw new CRM_Core_Exception(ts('Province limit or default country setting is incorrect'));
1311 }
dc86f881
CW
1312 $params['condition'] = 'country_id IN (' . implode(',', (array) $props['country_id']) . ')';
1313 }
1314 break;
2aa397bc 1315
dc86f881
CW
1316 // Filter country list based on site defaults
1317 case 'country_id':
d0bfb983 1318 case 'country':
1319 // change $fieldName to DB specific names.
1320 $fieldName = 'country_id';
786ad6e1
CW
1321 if ($context != 'get' && $context != 'validate') {
1322 $config = CRM_Core_Config::singleton();
1323 if (!empty($config->countryLimit) && is_array($config->countryLimit)) {
62d7cac4
SL
1324 if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', $config->countryLimit))) {
1325 throw new CRM_Core_Exception(ts('Available Country setting is incorrect'));
1326 }
786ad6e1
CW
1327 $params['condition'] = 'id IN (' . implode(',', $config->countryLimit) . ')';
1328 }
dc86f881
CW
1329 }
1330 break;
2aa397bc 1331
dc86f881
CW
1332 // Filter county list based on chosen state
1333 case 'county_id':
1334 if (!empty($props['state_province_id'])) {
62d7cac4
SL
1335 if (!CRM_Utils_Rule::commaSeparatedIntegers(implode(',', (array) $props['state_province_id']))) {
1336 throw new CRM_Core_Exception(ts('Can only accept Integers for state_province_id filtering'));
1337 }
dc86f881
CW
1338 $params['condition'] = 'state_province_id IN (' . implode(',', (array) $props['state_province_id']) . ')';
1339 }
1340 break;
2aa397bc 1341
c7130c3e
CW
1342 // Not a real field in this entity
1343 case 'world_region':
3493947a 1344 case 'worldregion':
1345 case 'worldregion_id':
b06ca616 1346 return CRM_Core_BAO_Country::buildOptions('region_id', $context, $props);
dc86f881 1347 }
786ad6e1 1348 return CRM_Core_PseudoConstant::get(__CLASS__, $fieldName, $params, $context);
dc86f881 1349 }
96025800 1350
6d04b727
FG
1351 /**
1352 * Add data from the configured geocoding provider.
1353 *
1354 * Generally this means latitude & longitude data.
1355 *
1356 * @param array $params
1357 * @return bool
1358 * TRUE if params could be passed to a provider, else FALSE.
1359 */
1360 public static function addGeocoderData(&$params) {
1361 try {
1362 $provider = CRM_Utils_GeocodeProvider::getConfiguredProvider();
1363 }
1364 catch (CRM_Core_Exception $e) {
1365 return FALSE;
1366 }
1367 $provider::format($params);
1368 return TRUE;
1369 }
1370
a7488080 1371}