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