location.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  1. <?php
  2. /**
  3. * @file
  4. * Public API for the Location module.
  5. *
  6. * @defgroup Location Location: An API for working with locations.
  7. */
  8. /**
  9. * @file
  10. * An implementation of a universal API for location manipulation. Provides functions for
  11. * postal_code proximity searching, deep-linking into online mapping services. Currently,
  12. * some options are configured through an interface provided by location.module.
  13. */
  14. include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'location') . '/earth.inc';
  15. /**
  16. * Get a deep-link to a mapping service such as Yahoo! Maps or MapPoint given an location.
  17. *
  18. * The call is delegated based on the 'country' value in the $location parameter.
  19. *
  20. * @param array $location
  21. * An associative array where
  22. * 'street' => A string representing the street location
  23. * 'additional' => A string for any additional portion of the street location
  24. * 'city' => A string for the city name
  25. * 'province' => The standard postal abbreviation for the province
  26. * 'country' => The two-letter ISO code for the country of the location (REQUIRED)
  27. * 'postal_code' => The international postal code for the location
  28. *
  29. * @param string $link_text
  30. * Text for a link.
  31. *
  32. * @return string
  33. * A link to a map provided by a third-party. The idea is to encode the appropriate
  34. * parameters as HTTP GET variables to the URL.
  35. *
  36. * @ingroup Location
  37. */
  38. function location_map_link($location = array(), $link_text = 'See map: ') {
  39. if (!isset($location['country']) || $location['country'] == 'xx') {
  40. return '';
  41. }
  42. location_load_country($location['country']);
  43. $default_func = 'location_map_link_' . $location['country'] . '_default_providers';
  44. $providers_func = 'location_map_link_' . $location['country'] . '_providers';
  45. // Merge the global providers with country specific ones so that countries
  46. // can add to or override the defaults.
  47. $providers = function_exists($providers_func) ? array_merge(
  48. location_map_link_providers(),
  49. $providers_func()
  50. ) : location_map_link_providers();
  51. // Default providers will be taken from the country specific default providers
  52. // function if it exists, otherwise it will use the global function.
  53. $selected_providers = variable_get(
  54. 'location_map_link_' . $location['country'],
  55. function_exists($default_func) ? $default_func() : location_map_link_default_providers()
  56. );
  57. $links = array();
  58. foreach ($selected_providers as $mapper) {
  59. $link_func = 'location_map_link_' . $location['country'] . '_' . $mapper;
  60. // If there is no country function try for a global one.
  61. $link_func = function_exists($link_func) ? $link_func : 'location_map_link_' . $mapper;
  62. if (function_exists($link_func)) {
  63. if ($link = $link_func($location)) {
  64. $links[] = '<a href="' . $link . '"' . (variable_get('location_maplink_external', 0) ? ' ' . variable_get(
  65. 'location_maplink_external_method',
  66. 'target="_blank"'
  67. ) : '') . '>' . $providers[$mapper]['name'] . '</a>';
  68. }
  69. }
  70. }
  71. if (count($links)) {
  72. return '<div class="location map-link">' . t($link_text) . implode($links, ", ") . '</div>';
  73. }
  74. else {
  75. return NULL;
  76. }
  77. }
  78. /**
  79. * Provide default map link providers.
  80. *
  81. * By implementing location_map_link_COUNTRYCODE_providers, individual
  82. * countries can add to this (they will be merged so this default can also be
  83. * overridden using those functions).
  84. *
  85. * @return array
  86. * An array where
  87. * - the key is the a descriptive key for the provider.
  88. * The key is also used in the function name of the function that
  89. * generates the map links for that provider. For example, a key of
  90. * 'google' means the name of the function that builds a link to a map
  91. * Google Maps would be 'location_map_link_google', or
  92. * 'location_map_link_COUNTRYCODE_google' for country overrides.
  93. * - the value is itself an array with 3 key/value pairs:
  94. * - 'name' => points to the name of the mapping service. For 'google',
  95. * this would be 'Google Maps'
  96. * - 'url' => the url of the main page of the mapping service. For
  97. * 'google', this would be 'http://maps.google.com'
  98. * - 'tos' => the url of the page that explains the map providers Terms of
  99. * Service, or Terms of Use. For 'google', this would be
  100. * 'http://www.google.com/help/terms_local.html'
  101. */
  102. function location_map_link_providers() {
  103. return array(
  104. 'google' => array(
  105. 'name' => t('Google Maps'),
  106. 'url' => 'http://maps.google.com',
  107. 'tos' => 'http://www.google.com/help/terms_local.html',
  108. ),
  109. );
  110. }
  111. /**
  112. * Provide the default map link providers.
  113. *
  114. * This can be overridden by implementing
  115. * location_map_link_COUNTRYCODE_default_providers for a given country.
  116. *
  117. * @return array
  118. * An array of values that work as keys to the array returned by
  119. * location_map_link_providers (and country versions of that function).
  120. * The idea is that if the administrator of the site has not yet had a chance
  121. * to visit the "Map Links" subtab on the location module's settings page,
  122. * that we can provide deep-linking to a relatively safe default.
  123. * By 'relatively safe', we mean that the Terms Of Service of the provider
  124. * of the maps are flexible enough for most parties.
  125. *
  126. * For example, in the case of the U.S., 'google' has relatively flexible
  127. * Terms Of Service, whereas Yahoo! Maps and MapQuest have more restrictive
  128. * Terms Of Service.
  129. */
  130. function location_map_link_default_providers() {
  131. return array('google');
  132. }
  133. /**
  134. * Google link.
  135. */
  136. function location_map_link_google($location = array()) {
  137. $query_params = array();
  138. foreach (array('street', 'city', 'province', 'postal_code', 'country') as $field) {
  139. if (isset($location[$field])) {
  140. $query_params[] = $location[$field];
  141. }
  142. }
  143. if (count($query_params)) {
  144. return ('http://maps.google.com?q=' . urlencode(implode(", ", $query_params)));
  145. }
  146. else {
  147. return NULL;
  148. }
  149. }
  150. /**
  151. * Try to extract the the Latitude and Longitude data from the postal code.
  152. *
  153. * @param array $location
  154. * Array. the location data
  155. * -> the values are:
  156. * 'street' => the string representing the street location (REQUIRED)
  157. * 'additional' => the string representing the additional street location portion in the location form
  158. * 'city' => the city name (REQUIRED)
  159. * 'province' => the province code defined in the country-specific include file
  160. * 'country' => the lower-case of the two-letter ISO code (REQUIRED)
  161. * 'postal_code' => the postal-code (REQUIRED)
  162. *
  163. * @return array|null
  164. * Array or NULL. NULL if the delegated-to function that does the
  165. * actual look-up does not exist. If the appropriate function exists,
  166. * then this function returns an associative array where
  167. * 'lon' => A floating point number for the longitude coordinate of the parameter location
  168. * 'lat' => A floating point number for the latitude coordinate of the parameter location
  169. *
  170. * @ingroup Location
  171. */
  172. function location_get_postalcode_data($location = array()) {
  173. $location['country'] = isset($location['country']) ? trim($location['country']) : NULL;
  174. $location['postal_code'] = isset($location['postal_code']) ? trim($location['postal_code']) : NULL;
  175. if (is_null($location['postal_code']) || is_null(
  176. $location['country']
  177. ) || empty($location['country']) || empty($location['postal_code']) || $location['postal_code'] == 'xx'
  178. ) {
  179. return NULL;
  180. }
  181. location_load_country($location['country']);
  182. $country_specific_function = 'location_get_postalcode_data_' . $location['country'];
  183. if (function_exists($country_specific_function)) {
  184. return $country_specific_function($location);
  185. }
  186. else {
  187. return NULL;
  188. }
  189. }
  190. /**
  191. * Given two points in lat/lon form, returns the distance between them.
  192. *
  193. * @param array $latlon_a
  194. * An associative array where
  195. * 'lon' => is a floating point of the longitude coordinate for the point given by latlonA
  196. * 'lat' => is a floating point of the latitude coordinate for the point given by latlonB
  197. *
  198. * @param array $latlon_b
  199. * Another point formatted like $latlon_b
  200. *
  201. * @param string $distance_unit
  202. * A string that is either 'km' or 'mile'.
  203. * If neither 'km' or 'mile' is passed, the parameter is forced to 'km'
  204. *
  205. * @return array|null
  206. * NULL if sense can't be made of the parameters.
  207. * An associative array where
  208. * 'scalar' => Is the distance between the two lat/lon parameter points
  209. * 'distance_unit' => Is the unit of distance being represented by 'scalar'.
  210. * This will be 'km' unless 'mile' is passed for the $distance_unit param
  211. *
  212. * @ingroup Location
  213. */
  214. function location_distance_between($latlon_a = array(), $latlon_b = array(), $distance_unit = 'km') {
  215. if (!isset($latlon_a['lon']) || !isset($latlon_a['lat']) || !isset($latlon_b['lon']) || !isset($latlon_b['lat'])) {
  216. return NULL;
  217. }
  218. if ($distance_unit != 'km' && $distance_unit != 'mile') {
  219. return NULL;
  220. }
  221. // $conversion_factor = number to divide by to convert meters to $distance_unit
  222. // At this point, $distance_unit == 'km' or 'mile' and nothing else
  223. // $conversion_factor = ($distance_unit == 'km') ? 1000.0 : 1609.347;
  224. $meters = earth_distance($latlon_a['lon'], $latlon_a['lat'], $latlon_b['lon'], $latlon_b['lat']);
  225. return array(
  226. 'scalar' => round($meters / (($distance_unit == 'km') ? 1000.0 : 1609.347), 1),
  227. 'distance_unit' => $distance_unit,
  228. );
  229. }
  230. /**
  231. * Takes two locations and tries to return a deep-link to driving directions.
  232. *
  233. * @param array $location_a
  234. * An associative array that represents an location where
  235. * 'street' => the street portions of the location
  236. * 'additional' => additional street portion of the location
  237. * 'city' => the city name
  238. * 'province' => the province, state, or territory
  239. * 'country' => lower-cased two-letter ISO code (REQUIRED)
  240. * 'postal_code' => the postal code
  241. *
  242. * @param array $location_b
  243. * An associative array that represents an location in the same way that
  244. * parameter $location_a does.
  245. *
  246. * @param string $link_text
  247. * The text of the HTML link that is to be generated.
  248. *
  249. * @return string
  250. * A deep-link to driving directions on Yahoo! or some other mapping service, if enough fields are filled in the parameters.
  251. * A deep-link to a form for driving directions with information pre-filled if not enough, but some fields are filled in the parameters.
  252. * The empty string if no information is provided (or if so little information is provided that there is no function to which to delegate
  253. * the call.
  254. *
  255. * We dispatch the call to a country-specific function. The country-specific function, in this case,
  256. * will be the one reflected by the country parameter of the first function. We require that
  257. * both locationes supplied have a country field at the minimum.
  258. *
  259. * The country-specific functions will ultimately decide, with the parameters given, whether to
  260. * to link to a form for driving directions is provided, where this form will be
  261. * pre-populated with whatever values were available or whether to link directly to the driving
  262. * directions themselves if enough fields are filled for each location.
  263. *
  264. * @ingroup Location
  265. */
  266. function location_driving_directions_link($location_a = array(), $location_b = array(), $link_text = 'Get directions') {
  267. if (!isset($location_a['country']) or !isset($location_b['country'])) {
  268. return '';
  269. }
  270. // For now, return empty string if starting-point and destinations are in different countries
  271. // if ($location_a['country'] != $location_b['country']) {
  272. // return '';
  273. // }
  274. // Lines above commented out because I want to let the country-specific function of the departure point decide
  275. // what it will do with driving destination locationes from other countries. As an example, Yahoo! Maps supports driving
  276. // direction queries for locations between the U.S. and Canada.
  277. $driving_direction_function = 'location_driving_directions_link_' . $location_a['country'];
  278. if (function_exists($driving_direction_function)) {
  279. $http_link = $driving_direction_function($location_a, $location_b);
  280. if (strlen($http_link)) {
  281. return '<a href="' . $http_link . '">' . $link_text . '</a>';
  282. }
  283. else {
  284. return '';
  285. }
  286. }
  287. return '';
  288. }
  289. /**
  290. * Convert distances to meter.
  291. *
  292. * @param int $distance
  293. * A number in either miles or km.
  294. *
  295. * @param string $distance_unit
  296. * String (optional). Either 'mile' or 'km' (default)
  297. *
  298. * @return float
  299. * A floating point number where the number in meters after the initially passed scalar has been ceil()'d
  300. * This is done after the $distance_unit parmeter is forced to be 'km' or 'mile'
  301. */
  302. function _location_convert_distance_to_meters($distance, $distance_unit = 'km') {
  303. if (!is_numeric($distance)) {
  304. return NULL;
  305. }
  306. if ($distance == 0) {
  307. return NULL;
  308. }
  309. if ($distance_unit != 'km' && $distance_unit != 'mile') {
  310. $distance_unit = 'km';
  311. }
  312. // Convert distance to meters.
  313. $retval = round(floatval($distance) * (($distance_unit == 'km') ? 1000.0 : 1609.347), 2);
  314. return $retval;
  315. }
  316. /**
  317. * Takes an location and returns a "rough" latitude/longitude pair based on the postal code data available for the given country.
  318. *
  319. * @param array $location
  320. * An associative array $location where
  321. * 'street' => the street portion of the location
  322. * 'additional' => additional street portion of the location
  323. * 'province' => the province, state, or territory
  324. * 'country' => lower-cased two-letter ISO code (REQUIRED)
  325. * 'postal_code' => international postal code (REQUIRED)
  326. *
  327. * @return array|null
  328. * NULL if data cannot be found.
  329. * Otherwise, an associative array where
  330. * 'lat' => is a floating point of the latitude coordinate of this location
  331. * 'lon' => is a floating point of the longitude coordinate of this location
  332. *
  333. * @ingroup Location
  334. */
  335. function location_latlon_rough($location = array()) {
  336. if (!isset($location['country']) || !isset($location['postal_code'])) {
  337. return NULL;
  338. }
  339. location_load_country($location['country']);
  340. $latlon_function = 'location_latlon_rough_' . $location['country'];
  341. if (function_exists($latlon_function) && $result = $latlon_function($location)) {
  342. return $result;
  343. }
  344. else {
  345. return location_latlon_rough_default($location);
  346. }
  347. }
  348. /**
  349. * Returns a lat/lon pair of the approximate center of the given postal code in the given country.
  350. *
  351. * This function is a default implementation, in case the country support doesn't implement a proper function for this.
  352. *
  353. * @param array $location
  354. * An associative array $location where
  355. * 'street' => the street portion of the location
  356. * 'supplemental' => additional street portion of the location
  357. * 'province' => the province, state, or territory
  358. * 'country' => lower-cased two-letter ISO code (REQUIRED)
  359. * 'postal_code' => the international postal code for this location (REQUIRED)
  360. *
  361. * @return array
  362. * An associative array where
  363. * 'lat' => approximate latitude of the center of the postal code's area
  364. * 'lon' => approximate longitude of the center of the postal code's area
  365. */
  366. function location_latlon_rough_default($location = array()) {
  367. if (!isset($location['country']) || !isset($location['postal_code'])) {
  368. return NULL;
  369. }
  370. $query = db_select('zipcodes', 'z');
  371. $query->addField('z', 'latitude', 'lat');
  372. $query->addField('z', 'longitude', 'lon');
  373. $coords = $query->condition('country', $location['country'])
  374. ->condition('zip', $location['postal_code'])
  375. ->execute()
  376. ->fetchAssoc();
  377. if ($coords) {
  378. return $coords;
  379. }
  380. // If nothing found internally (new zipcode, or incomplete zipcodes table).
  381. elseif (($newlatlong = location_latlon_exact($location)) != NULL) {
  382. // Try a one-time external geocoding.
  383. if ($newlatlong['lat']) {
  384. // And store results in zipcodes table for next lookups being internally handled
  385. // (yeah this is missing city/state info a.t.m., but is way better than nothing!)
  386. db_insert('zipcodes')
  387. ->fields(
  388. array(
  389. 'latitude' => $newlatlong['lat'],
  390. 'longitude' => $newlatlong['lon'],
  391. 'country' => $location['country'],
  392. 'zip' => $location['postal_code'],
  393. )
  394. )
  395. ->execute();
  396. }
  397. return $newlatlong;
  398. }
  399. else {
  400. return NULL;
  401. }
  402. }
  403. /**
  404. * Get an appropriate bounding box for showing an entire country on a map.
  405. *
  406. * Target is a bounding box large enough to show the country in both spherical
  407. * mercator and mercator projections.
  408. *
  409. * @param array $location
  410. * Either a location array or a country code.
  411. *
  412. * @return array
  413. * An array with 'minlng', 'minlat', 'maxlng', and 'maxlat' elements.
  414. */
  415. function location_country_bounds($location = array()) {
  416. if (!is_array($location)) {
  417. $location = array('country' => $location);
  418. }
  419. if (!empty($location['country'])) {
  420. location_load_country($location['country']);
  421. }
  422. $country_bounds_function = 'location_bounds_' . $location['country'];
  423. if (function_exists($country_bounds_function)) {
  424. return $country_bounds_function();
  425. }
  426. else {
  427. return array(
  428. 'minlng' => -180.0,
  429. 'minlat' => -90.0,
  430. 'maxlng' => 180.0,
  431. 'maxlat' => 90.0,
  432. );
  433. }
  434. }
  435. /**
  436. * Currently, this is not a priority until there is an implementable use for exact longitude latitude coordinates for an location.
  437. *
  438. * The idea is that this call will eventually retrieve
  439. * information through a web-service. Whereas location_latlon_rough() returns an approximate
  440. * lat/lon pair based strictly on the postal code where this lat/lon pair is pulled from a
  441. * database table, this function is intended to send the entire location to a web-service and
  442. * to retrieve exact lat/lon coordinates.
  443. *
  444. * @param array $location
  445. * An array where
  446. * -> the key values are 'street', 'additional', 'province', 'country', 'postal_code'
  447. * -> the values are:
  448. * 'street' => the string representing the street location (REQUIRED)
  449. * 'additional' => the string representing the additional street location portion in the location form
  450. * 'city' => the city name (REQUIRED)
  451. * 'province' => the province code defined in the country-specific include file
  452. * 'country' => the lower-case of the two-letter ISO code (REQUIRED)
  453. * 'postal_code' => the postal-code (REQUIRED)
  454. *
  455. * @return array|null
  456. * NULL if the delegated-to function that does the actual look-up does not exist.
  457. * If the appropriate function exists, then this function returns an associative array where
  458. * 'lon' => A floating point number for the longitude coordinate of the parameter location
  459. * 'lat' => A floating point number for the latitude coordinate of the parameter location
  460. *
  461. * @ingroup Location
  462. */
  463. function location_latlon_exact($location = array()) {
  464. $country = $location['country'];
  465. location_standardize_country_code($country);
  466. $service = variable_get('location_geocode_' . $country, 'none');
  467. if (!empty($country) && $service != 'none') {
  468. // Figure out what the exact function should be.
  469. if (strpos($service, '|')) {
  470. location_load_country($country);
  471. // The code change below fixes the problem of the country specific
  472. // function for geocoding not being correctly called (it removes any
  473. // text from the pipe (|) onwards).
  474. $exact_latlon_function = 'location_geocode_' . $country . '_' . substr($service, 0, strpos($service, '|'));
  475. }
  476. else {
  477. location_load_geocoder($service);
  478. $exact_latlon_function = $service . '_geocode_location';
  479. }
  480. if (function_exists($exact_latlon_function)) {
  481. return $exact_latlon_function($location);
  482. }
  483. else {
  484. return NULL;
  485. }
  486. }
  487. return NULL;
  488. }
  489. /**
  490. * Returns an associative array of countries currently supported by the location system where.
  491. *
  492. * -> the keys represent the two-letter ISO code and
  493. * -> the values represent the English name of the country.
  494. * The array is sorted the index (i.e., by the short English name of the country).
  495. *
  496. * Please note the different between "supported" countries and "configured"
  497. * countries: A country being "supported" means that there is an include file
  498. * to support the country while "configure" implies that the site admin has
  499. * configured the site to actually use that country.
  500. *
  501. * @ingroup Location
  502. */
  503. function _location_supported_countries() {
  504. $supported_countries = & drupal_static(__FUNCTION__, array());
  505. // If this function has already been called this request, we can avoid a DB hit.
  506. if (!empty($supported_countries)) {
  507. return $supported_countries;
  508. }
  509. // Try first to load from cache, it's much faster than the scan below.
  510. if ($cache = cache_get('location:supported-countries', 'cache_location')) {
  511. $supported_countries = $cache->data;
  512. }
  513. else {
  514. // '<ISO two-letter code>' => '<English name for country>'
  515. $iso_list = location_get_iso3166_list();
  516. $path = drupal_get_path('module', 'location') . '/supported/location.';
  517. foreach ($iso_list as $cc => $name) {
  518. if (file_exists($path . $cc . '.inc')) {
  519. $supported_countries[$cc] = $name;
  520. }
  521. }
  522. cache_set('location:supported-countries', $supported_countries, 'cache_location');
  523. }
  524. return $supported_countries;
  525. }
  526. // @@@ New in 3.x, document.
  527. /**
  528. * Fetch the provinces for a country.
  529. */
  530. function location_get_provinces($country = 'us') {
  531. $provinces = & drupal_static(__FUNCTION__, array());
  532. // Current language.
  533. $lang_code = $GLOBALS['language']->language;
  534. location_standardize_country_code($country);
  535. if (!isset($provinces[$country])) {
  536. if ($cache = cache_get("provinces:$country:$lang_code", 'cache_location')) {
  537. $provinces[$country] = $cache->data;
  538. }
  539. else {
  540. location_load_country($country);
  541. $func = 'location_province_list_' . $country;
  542. if (function_exists($func)) {
  543. $provinces[$country] = $func();
  544. cache_set("provinces:$country:$lang_code", $provinces[$country], 'cache_location');
  545. }
  546. }
  547. }
  548. // Invoke hook_location_provinces_alter and
  549. // hook_location_provinces_COUNTRY_CODE_alter.
  550. drupal_alter(
  551. array('location_provinces', 'location_provinces_' . $country),
  552. $provinces,
  553. $country
  554. );
  555. return isset($provinces[$country]) ? $provinces[$country] : array();
  556. }
  557. // @@@ New in 3.x, document.
  558. /**
  559. * Get the translated name of a country code.
  560. */
  561. function location_country_name($country = 'us') {
  562. location_standardize_country_code($country);
  563. $countries = location_get_iso3166_list();
  564. if (isset($countries[$country])) {
  565. return $countries[$country];
  566. }
  567. else {
  568. return '';
  569. }
  570. }
  571. // @@@ New in 3.x, document.
  572. /**
  573. * Get the full name of a province code.
  574. */
  575. function location_province_name($country = 'us', $province = 'xx') {
  576. $provinces = location_get_provinces($country);
  577. $province = strtoupper($province);
  578. if (isset($provinces[$province])) {
  579. return $provinces[$province];
  580. }
  581. else {
  582. return '';
  583. }
  584. }
  585. // @@@ New in 3.x, document.
  586. /**
  587. * Get a province code from a code or full name and a country.
  588. */
  589. function location_province_code($country = 'us', $province = 'xx') {
  590. // An array of countries is useful if someone specified multiple countries
  591. // in an autoselect for example.
  592. // It *is* possibly ambiguous, especially if the province was already a code.
  593. // We make an array here for single (the usual case) for code simplicity reasons.
  594. if (!is_array($country)) {
  595. $country = array($country);
  596. }
  597. $p = strtoupper($province);
  598. foreach ($country as $c) {
  599. if ($c == 'xx') {
  600. return $province;
  601. }
  602. $provinces = location_get_provinces($c);
  603. foreach ($provinces as $k => $v) {
  604. if ($p == strtoupper($k) || $p == strtoupper($v)) {
  605. return $k;
  606. }
  607. }
  608. }
  609. return '';
  610. }
  611. // @@@ New in 3.x, document.
  612. /**
  613. * Canonicalize a country code.
  614. */
  615. function location_standardize_country_code(&$country) {
  616. $country = trim($country);
  617. // @@@ Double check the validity of this validity check. ;)
  618. if (!ctype_alpha($country) || strlen($country) != 2) {
  619. $country = 'xx';
  620. return FALSE;
  621. }
  622. else {
  623. $country = strtolower($country);
  624. return TRUE;
  625. }
  626. }
  627. /**
  628. * Load support for a country.
  629. *
  630. * This function will load support for a country identified by its two-letter ISO code.
  631. *
  632. * @param string $country
  633. * Two-letter ISO code for country.
  634. *
  635. * @return bool
  636. * TRUE if the file was found and loaded, FALSE otherwise.
  637. */
  638. function location_load_country($country) {
  639. location_standardize_country_code($country);
  640. $file = DRUPAL_ROOT . '/' . drupal_get_path('module', 'location') . '/supported/location.' . $country . '.inc';
  641. if (file_exists($file)) {
  642. include_once $file;
  643. return TRUE;
  644. }
  645. return FALSE;
  646. }
  647. // @@@ New in 3.x, document.
  648. /**
  649. * Load a general geocoding service.
  650. */
  651. function location_load_geocoder($geocoder) {
  652. include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'location') . '/geocoding/' . $geocoder . '.inc';
  653. }
  654. /**
  655. * Create a single line address.
  656. *
  657. * @param array $location
  658. * The address parts
  659. *
  660. * @return string
  661. * The single line address
  662. */
  663. function location_address2singleline($location = array()) {
  664. // Check if its a valid address.
  665. if (empty($location)) {
  666. return '';
  667. }
  668. $address = '';
  669. if (!empty($location['street'])) {
  670. $address .= $location['street'];
  671. }
  672. if (!empty($location['city'])) {
  673. if (!empty($location['street'])) {
  674. $address .= ', ';
  675. }
  676. $address .= $location['city'];
  677. }
  678. if (!empty($location['province'])) {
  679. if (!empty($location['street']) || !empty($location['city'])) {
  680. $address .= ', ';
  681. }
  682. // @@@ Fix this!
  683. if (substr($location['province'], 0, 3) == $location['country'] . '-') {
  684. $address .= substr($location['province'], 3);
  685. watchdog('Location', 'BUG: Country found in province attribute.');
  686. }
  687. else {
  688. $address .= $location['province'];
  689. }
  690. }
  691. if (!empty($location['postal_code'])) {
  692. if (!empty($address)) {
  693. $address .= ' ';
  694. }
  695. $address .= $location['postal_code'];
  696. }
  697. if (!empty($location['country'])) {
  698. $address .= ', ' . $location['country'];
  699. }
  700. return $address;
  701. }
  702. /**
  703. * Geocoder list.
  704. */
  705. function location_get_general_geocoder_list() {
  706. $list = & drupal_static(__FUNCTION__, array());
  707. if (!count($list)) {
  708. $files = file_scan_directory(
  709. drupal_get_path('module', 'location') . '/geocoding',
  710. '/\.inc$/',
  711. array('nomask' => '/(\.\.?|CVS|\.svn)$/')
  712. );
  713. foreach ($files as $full_path_name => $fileinfo) {
  714. $list[] = $fileinfo->name;
  715. }
  716. }
  717. return $list;
  718. }
  719. /**
  720. * The following is an array of all.
  721. *
  722. * countrycode => country-name pairs as layed out in
  723. * ISO 3166-1 alpha-2
  724. */
  725. function location_get_iso3166_list($upper = FALSE) {
  726. include_once DRUPAL_ROOT . '/includes/locale.inc';
  727. // Statically cache a version of the core Drupal list of countries
  728. // with lower case country codes for use by this module.
  729. $countries = & drupal_static(__FUNCTION__);
  730. if ($upper) {
  731. // Drupal core stores ISO 3166-1 alpha2 codes in upper case, as
  732. // per the ISO standard.
  733. return country_get_list();
  734. }
  735. elseif (!isset($countries)) {
  736. // Location module uses lower-case ISO 3166-1 alpha2 codes, so we need
  737. // to convert.
  738. $countries = array_change_key_case(country_get_list(), CASE_LOWER);
  739. }
  740. return $countries;
  741. }