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