Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
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 | /** | |
50bfb460 | 13 | * Address Utilities |
6a488035 TO |
14 | * |
15 | * @package CRM | |
ca5cec67 | 16 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
6a488035 TO |
17 | */ |
18 | class CRM_Utils_Address { | |
19 | ||
20 | /** | |
fe482240 | 21 | * Format an address string from address fields and a format string. |
6a488035 TO |
22 | * |
23 | * Format an address basing on the address fields provided. | |
24 | * Use Setting's address_format if there's no format specified. | |
25 | * | |
4c49535e J |
26 | * This function is also used to generate a contact's display_name and |
27 | * sort_name. | |
28 | * | |
77855840 TO |
29 | * @param array $fields |
30 | * The address fields. | |
31 | * @param string $format | |
32 | * The desired address format. | |
33 | * @param bool $microformat | |
34 | * If true indicates, the address to be built in hcard-microformat standard. | |
35 | * @param bool $mailing | |
36 | * If true indicates, the call has been made from mailing label. | |
f4aaa82a | 37 | * @param null $tokenFields |
6a488035 | 38 | * |
a6c01b45 CW |
39 | * @return string |
40 | * formatted address string | |
6a488035 | 41 | * |
6a488035 | 42 | */ |
389bcebf | 43 | public static function format( |
6a488035 | 44 | $fields, |
e7292422 TO |
45 | $format = NULL, |
46 | $microformat = FALSE, | |
47 | $mailing = FALSE, | |
e7292422 | 48 | $tokenFields = NULL |
6a488035 TO |
49 | ) { |
50 | static $config = NULL; | |
51 | ||
52 | if (!$format) { | |
aaffa79f | 53 | $format = Civi::settings()->get('address_format'); |
6a488035 TO |
54 | } |
55 | ||
56 | if ($mailing) { | |
aaffa79f | 57 | $format = Civi::settings()->get('mailing_format'); |
6a488035 TO |
58 | } |
59 | ||
60 | $formatted = $format; | |
61 | ||
9c1bc317 | 62 | $fullPostalCode = $fields['postal_code'] ?? NULL; |
6a488035 TO |
63 | if (!empty($fields['postal_code_suffix'])) { |
64 | $fullPostalCode .= "-$fields[postal_code_suffix]"; | |
65 | } | |
66 | ||
67 | // make sure that some of the fields do have values | |
be2fb01f | 68 | $emptyFields = [ |
6a488035 TO |
69 | 'supplemental_address_1', |
70 | 'supplemental_address_2', | |
207f62c6 | 71 | 'supplemental_address_3', |
6a488035 TO |
72 | 'state_province_name', |
73 | 'county', | |
be2fb01f | 74 | ]; |
6a488035 TO |
75 | foreach ($emptyFields as $f) { |
76 | if (!isset($fields[$f])) { | |
77 | $fields[$f] = NULL; | |
78 | } | |
79 | } | |
80 | ||
61ca7d1f | 81 | //CRM-16876 Display countries in all caps when in mailing mode. |
04c56532 SL |
82 | if ($mailing && !empty($fields['country'])) { |
83 | if (Civi::settings()->get('hideCountryMailingLabels')) { | |
84 | $domain = CRM_Core_BAO_Domain::getDomain(); | |
be2fb01f | 85 | $domainLocation = CRM_Core_BAO_Location::getValues(['contact_id' => $domain->contact_id]); |
04c56532 SL |
86 | $domainAddress = $domainLocation['address'][1]; |
87 | $domainCountryId = $domainAddress['country_id']; | |
88 | if ($fields['country'] == CRM_Core_PseudoConstant::country($domainCountryId)) { | |
89 | $fields['country'] = NULL; | |
90 | } | |
e22e10f5 | 91 | else { |
e8bbf756 | 92 | //Capitalization display on uppercase to contries with special characters |
ed3d77d5 | 93 | $fields['country'] = mb_convert_case($fields['country'], MB_CASE_UPPER, "UTF-8"); |
e22e10f5 | 94 | } |
04c56532 SL |
95 | } |
96 | else { | |
4c733767 | 97 | $fields['country'] = mb_convert_case($fields['country'], MB_CASE_UPPER, "UTF-8"); |
04c56532 SL |
98 | } |
99 | } | |
100 | ||
6a488035 | 101 | if (!$microformat) { |
e7292422 | 102 | // replacements in case of Individual Name Format |
be2fb01f | 103 | $replacements = [ |
6b409353 CW |
104 | 'contact.display_name' => $fields['display_name'] ?? NULL, |
105 | 'contact.individual_prefix' => $fields['individual_prefix'] ?? NULL, | |
106 | 'contact.formal_title' => $fields['formal_title'] ?? NULL, | |
107 | 'contact.first_name' => $fields['first_name'] ?? NULL, | |
108 | 'contact.middle_name' => $fields['middle_name'] ?? NULL, | |
109 | 'contact.last_name' => $fields['last_name'] ?? NULL, | |
110 | 'contact.individual_suffix' => $fields['individual_suffix'] ?? NULL, | |
111 | 'contact.address_name' => $fields['address_name'] ?? NULL, | |
112 | 'contact.street_address' => $fields['street_address'] ?? NULL, | |
113 | 'contact.supplemental_address_1' => $fields['supplemental_address_1'] ?? NULL, | |
114 | 'contact.supplemental_address_2' => $fields['supplemental_address_2'] ?? NULL, | |
115 | 'contact.supplemental_address_3' => $fields['supplemental_address_3'] ?? NULL, | |
116 | 'contact.city' => $fields['city'] ?? NULL, | |
117 | 'contact.state_province_name' => $fields['state_province_name'] ?? NULL, | |
118 | 'contact.county' => $fields['county'] ?? NULL, | |
119 | 'contact.state_province' => $fields['state_province'] ?? NULL, | |
6a488035 | 120 | 'contact.postal_code' => $fullPostalCode, |
6b409353 CW |
121 | 'contact.country' => $fields['country'] ?? NULL, |
122 | 'contact.world_region' => $fields['world_region'] ?? NULL, | |
123 | 'contact.geo_code_1' => $fields['geo_code_1'] ?? NULL, | |
124 | 'contact.geo_code_2' => $fields['geo_code_2'] ?? NULL, | |
125 | 'contact.current_employer' => $fields['current_employer'] ?? NULL, | |
126 | 'contact.nick_name' => $fields['nick_name'] ?? NULL, | |
127 | 'contact.email' => $fields['email'] ?? NULL, | |
128 | 'contact.im' => $fields['im'] ?? NULL, | |
129 | 'contact.do_not_email' => $fields['do_not_email'] ?? NULL, | |
130 | 'contact.do_not_phone' => $fields['do_not_phone'] ?? NULL, | |
131 | 'contact.do_not_mail' => $fields['do_not_mail'] ?? NULL, | |
132 | 'contact.do_not_sms' => $fields['do_not_sms'] ?? NULL, | |
133 | 'contact.do_not_trade' => $fields['do_not_trade'] ?? NULL, | |
134 | 'contact.job_title' => $fields['job_title'] ?? NULL, | |
135 | 'contact.birth_date' => $fields['birth_date'] ?? NULL, | |
136 | 'contact.gender' => $fields['gender'] ?? NULL, | |
137 | 'contact.is_opt_out' => $fields['is_opt_out'] ?? NULL, | |
138 | 'contact.preferred_mail_format' => $fields['preferred_mail_format'] ?? NULL, | |
139 | 'contact.phone' => $fields['phone'] ?? NULL, | |
140 | 'contact.home_URL' => $fields['home_URL'] ?? NULL, | |
141 | 'contact.contact_source' => $fields['contact_source'] ?? NULL, | |
142 | 'contact.external_identifier' => $fields['external_identifier'] ?? NULL, | |
143 | 'contact.contact_id' => $fields['id'] ?? NULL, | |
4582e7ed MD |
144 | 'contact.household_name' => $fields['household_name'] ?? NULL, |
145 | 'contact.organization_name' => $fields['organization_name'] ?? NULL, | |
6b409353 CW |
146 | 'contact.legal_name' => $fields['legal_name'] ?? NULL, |
147 | 'contact.preferred_communication_method' => $fields['preferred_communication_method'] ?? NULL, | |
148 | 'contact.communication_style' => $fields['communication_style'] ?? NULL, | |
149 | 'contact.addressee' => $fields['addressee_display'] ?? NULL, | |
150 | 'contact.email_greeting' => $fields['email_greeting_display'] ?? NULL, | |
151 | 'contact.postal_greeting' => $fields['postal_greeting_display'] ?? NULL, | |
be2fb01f | 152 | ]; |
6a488035 TO |
153 | } |
154 | else { | |
be2fb01f | 155 | $replacements = [ |
6a488035 TO |
156 | 'contact.address_name' => "<span class=\"address-name\">" . $fields['address_name'] . "</span>", |
157 | 'contact.street_address' => "<span class=\"street-address\">" . $fields['street_address'] . "</span>", | |
158 | 'contact.supplemental_address_1' => "<span class=\"extended-address\">" . $fields['supplemental_address_1'] . "</span>", | |
159 | 'contact.supplemental_address_2' => $fields['supplemental_address_2'], | |
207f62c6 | 160 | 'contact.supplemental_address_3' => $fields['supplemental_address_3'], |
6a488035 TO |
161 | 'contact.city' => "<span class=\"locality\">" . $fields['city'] . "</span>", |
162 | 'contact.state_province_name' => "<span class=\"region\">" . $fields['state_province_name'] . "</span>", | |
163 | 'contact.county' => "<span class=\"region\">" . $fields['county'], | |
164 | 'contact.state_province' => "<span class=\"region\">" . $fields['state_province'] . "</span>", | |
165 | 'contact.postal_code' => "<span class=\"postal-code\">" . $fullPostalCode . "</span>", | |
166 | 'contact.country' => "<span class=\"country-name\">" . $fields['country'] . "</span>", | |
167 | 'contact.world_region' => "<span class=\"region\">" . $fields['world_region'] . "</span>", | |
be2fb01f | 168 | ]; |
6a488035 TO |
169 | |
170 | // erase all empty ones, so we dont get blank lines | |
171 | foreach (array_keys($replacements) as $key) { | |
172 | $exactKey = substr($key, 0, 8) == 'contact.' ? substr($key, 8) : $key; | |
173 | if ($key != 'contact.postal_code' && | |
174 | CRM_Utils_Array::value($exactKey, $fields) == NULL | |
175 | ) { | |
176 | $replacements[$key] = ''; | |
177 | } | |
178 | } | |
179 | if (empty($fullPostalCode)) { | |
180 | $replacements['contact.postal_code'] = ''; | |
181 | } | |
182 | } | |
183 | ||
184 | // replacements in case of Custom Token | |
185 | if (stristr($formatted, 'custom_')) { | |
186 | $customToken = array_keys($fields); | |
187 | foreach ($customToken as $value) { | |
188 | if (substr($value, 0, 7) == 'custom_') { | |
189 | $replacements["contact.{$value}"] = $fields["{$value}"]; | |
190 | } | |
191 | } | |
192 | } | |
193 | ||
194 | // also sub all token fields | |
195 | if ($tokenFields) { | |
196 | foreach ($tokenFields as $token) { | |
9c1bc317 | 197 | $replacements["{$token}"] = $fields["{$token}"] ?? NULL; |
6a488035 TO |
198 | } |
199 | } | |
200 | ||
201 | // for every token, replace {fooTOKENbar} with fooVALUEbar if | |
202 | // the value is not empty, otherwise drop the whole {fooTOKENbar} | |
203 | foreach ($replacements as $token => $value) { | |
785374a6 | 204 | if ($value && is_string($value) || is_numeric($value)) { |
6a488035 TO |
205 | $formatted = preg_replace("/{([^{}]*)\b{$token}\b([^{}]*)}/u", "\${1}{$value}\${2}", $formatted); |
206 | } | |
207 | else { | |
208 | $formatted = preg_replace("/{[^{}]*\b{$token}\b[^{}]*}/u", '', $formatted); | |
209 | } | |
210 | } | |
211 | ||
212 | // drop any {...} constructs from lines' ends | |
213 | if (!$microformat) { | |
214 | $formatted = "\n$formatted\n"; | |
215 | } | |
216 | else { | |
217 | if ($microformat == 1) { | |
218 | $formatted = "\n<div class=\"location vcard\"><span class=\"adr\">\n$formatted</span></div>\n"; | |
219 | } | |
220 | else { | |
221 | $formatted = "\n<div class=\"vcard\"><span class=\"adr\">$formatted</span></div>\n"; | |
222 | } | |
223 | } | |
224 | ||
225 | $formatted = preg_replace('/\n{[^{}]*}/u', "\n", $formatted); | |
226 | $formatted = preg_replace('/{[^{}]*}\n/u', "\n", $formatted); | |
227 | ||
228 | // if there are any 'sibling' {...} constructs, replace them with the | |
229 | // contents of the first one; for example, when there's no state_province: | |
230 | // 1. {city}{, }{state_province}{ }{postal_code} | |
231 | // 2. San Francisco{, }{ }12345 | |
232 | // 3. San Francisco, 12345 | |
233 | $formatted = preg_replace('/{([^{}]*)}({[^{}]*})+/u', '\1', $formatted); | |
234 | ||
235 | // drop any remaining curly braces leaving their contents | |
be2fb01f | 236 | $formatted = str_replace(['{', '}'], '', $formatted); |
6a488035 TO |
237 | |
238 | // drop any empty lines left after the replacements | |
239 | $formatted = preg_replace('/^[ \t]*[\r\n]+/m', '', $formatted); | |
240 | ||
241 | if (!$microformat) { | |
242 | $finalFormatted = $formatted; | |
243 | } | |
244 | else { | |
245 | // remove \n from each line and only add at the end | |
246 | // this hack solves formatting issue, when we convert nl2br | |
be2fb01f | 247 | $lines = []; |
353ffa53 | 248 | $count = 1; |
6a488035 TO |
249 | $finalFormatted = NULL; |
250 | $formattedArray = explode("\n", $formatted); | |
251 | $formattedArray = array_filter($formattedArray); | |
252 | ||
253 | foreach ($formattedArray as $line) { | |
254 | $line = trim($line); | |
255 | if ($line) { | |
256 | if ($count > 1 && $count < count($formattedArray)) { | |
257 | $line = "$line\n"; | |
258 | } | |
259 | $finalFormatted .= $line; | |
260 | $count++; | |
261 | } | |
262 | } | |
263 | } | |
264 | return $finalFormatted; | |
265 | } | |
266 | ||
5bc392e6 EM |
267 | /** |
268 | * @param $format | |
269 | * | |
270 | * @return array | |
271 | */ | |
00be9182 | 272 | public static function sequence($format) { |
6a488035 | 273 | // also compute and store the address sequence |
be2fb01f | 274 | $addressSequence = [ |
6a488035 TO |
275 | 'address_name', |
276 | 'street_address', | |
277 | 'supplemental_address_1', | |
278 | 'supplemental_address_2', | |
207f62c6 | 279 | 'supplemental_address_3', |
6a488035 TO |
280 | 'city', |
281 | 'county', | |
282 | 'state_province', | |
283 | 'postal_code', | |
284 | 'country', | |
be2fb01f | 285 | ]; |
6a488035 TO |
286 | |
287 | // get the field sequence from the format | |
be2fb01f | 288 | $newSequence = []; |
6a488035 TO |
289 | foreach ($addressSequence as $field) { |
290 | if (substr_count($format, $field)) { | |
291 | $newSequence[strpos($format, $field)] = $field; | |
292 | } | |
293 | } | |
294 | ksort($newSequence); | |
295 | ||
296 | // add the addressSequence fields that are missing in the addressFormat | |
297 | // to the end of the list, so that (for example) if state_province is not | |
298 | // specified in the addressFormat it's still in the address-editing form | |
299 | $newSequence = array_merge($newSequence, $addressSequence); | |
300 | $newSequence = array_unique($newSequence); | |
301 | return $newSequence; | |
302 | } | |
96025800 | 303 | |
d90ab525 SL |
304 | /** |
305 | * Extract the billing fields from the form submission and format them for display. | |
306 | * | |
307 | * @param array $params | |
308 | * @param int $billingLocationTypeID | |
309 | * | |
310 | * @return string | |
311 | */ | |
312 | public static function getFormattedBillingAddressFieldsFromParameters($params, $billingLocationTypeID) { | |
be2fb01f | 313 | $addressParts = [ |
d90ab525 SL |
314 | "street_address" => "billing_street_address-{$billingLocationTypeID}", |
315 | "city" => "billing_city-{$billingLocationTypeID}", | |
316 | "postal_code" => "billing_postal_code-{$billingLocationTypeID}", | |
317 | "state_province" => "state_province-{$billingLocationTypeID}", | |
318 | "country" => "country-{$billingLocationTypeID}", | |
be2fb01f | 319 | ]; |
d90ab525 | 320 | |
be2fb01f | 321 | $addressFields = []; |
d90ab525 | 322 | foreach ($addressParts as $name => $field) { |
9c1bc317 | 323 | $value = $params[$field] ?? NULL; |
9c86599c | 324 | $alternateName = 'billing_' . $name . '_id-' . $billingLocationTypeID; |
325 | $alternate2 = 'billing_' . $name . '-' . $billingLocationTypeID; | |
326 | if (isset($params[$alternate2]) && !isset($params[$alternateName])) { | |
327 | $alternateName = $alternate2; | |
328 | } | |
dd22a911 | 329 | //Include values which prepend 'billing_' to country and state_province. |
de6c59ca | 330 | if (!empty($params[$alternateName])) { |
9c86599c | 331 | if (empty($value) || !is_numeric($value)) { |
332 | $value = $params[$alternateName]; | |
333 | } | |
334 | } | |
8568b05c | 335 | if (is_numeric($value) && ($name == 'state_province' || $name == 'country')) { |
9c86599c | 336 | if ($name == 'state_province') { |
337 | $addressFields[$name] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($value); | |
8568b05c | 338 | $addressFields[$name . '_name'] = CRM_Core_PseudoConstant::stateProvince($value); |
b6fe7f06 | 339 | } |
9c86599c | 340 | if ($name == 'country') { |
341 | $addressFields[$name] = CRM_Core_PseudoConstant::countryIsoCode($value); | |
342 | } | |
343 | } | |
344 | else { | |
345 | $addressFields[$name] = $value; | |
dd22a911 | 346 | } |
d90ab525 SL |
347 | } |
348 | return CRM_Utils_Address::format($addressFields); | |
349 | } | |
350 | ||
6a488035 | 351 | } |