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