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