geocoder.module 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. <?php
  2. /**
  3. * @file
  4. * Module file for geocoder module.
  5. */
  6. include_once 'geocoder.widget.inc';
  7. include_once 'geocoder.services.inc';
  8. define('GEOCODER_GOOGLE_AUTH_NONE', 1);
  9. define('GEOCODER_GOOGLE_AUTH_KEY', 2);
  10. define('GEOCODER_GOOGLE_AUTH_WORK', 3);
  11. /**
  12. * The Geocoder API call.
  13. * TODO.
  14. */
  15. function geocoder_reverse($handler, $lat, $long, $options = array(), $cache_type = 2, $cache_reset = FALSE) {
  16. ctools_include('plugins');
  17. static $static_cache_reverse = array();
  18. if ($cache_reset) {
  19. $static_cache_reverse = array();
  20. }
  21. if ($cache_type) {
  22. $cache_id = $handler . '_' . md5(serialize(array($lat, $long, $options)));
  23. if (!empty($static_cache_reverse[$cache_id])) {
  24. return $static_cache_reverse[$cache_id];
  25. }
  26. }
  27. $processor = ctools_get_plugins('geocoder', 'geocoder_reverse_handler', $handler);
  28. $address = call_user_func($processor['callback'], $lat, $long, $options);
  29. if ($cache_type) {
  30. $static_cache_reverse[$cache_id] = $address;
  31. }
  32. return $address;
  33. }
  34. /**
  35. * The Geocoder API call.
  36. *
  37. * Given a handler and data, geocode the data into a geometry object using the handler.
  38. *
  39. * @param string $handler
  40. * The geocoder handler to use. Call geocoder_handler_info() to get a list.
  41. * @param mixed $data
  42. * Data to be passed into the handler for geocoding. For example a address string.
  43. * @param array $options
  44. * Additional options to pass to the handler. Exact key / values to pass depend on the handler.
  45. * @param int $cache_type
  46. * DEPRECATED. All results are cached persistently.
  47. * @param bool $cache_reset
  48. * (optional) Ignore potentially matched cache record, and live fetch. Defaults to FALSE.
  49. *
  50. * @return Geometry
  51. * Returns a geoPHP geometry object. Generally a Point.
  52. * See http://drupal.org/project/geoPHP and https://github.com/phayes/geoPHP/wiki/API-Reference
  53. *
  54. * @example:
  55. * geocoder('google','4925 Gair Ave, Terrace, BC');
  56. * geocoder('google','New York City',array('geometry_type' => 'bounds'));
  57. */
  58. function geocoder($handler, $data, $options = array(), $cache_type = 'DEPRECATED', $cache_reset = FALSE) {
  59. ctools_include('plugins');
  60. $processor = ctools_get_plugins('geocoder', 'geocoder_handler', $handler);
  61. if (!$processor) {
  62. return NULL;
  63. }
  64. // Attempt to retrieve from persistent cache.
  65. $geometry = $cache_reset ? NULL : geocoder_cache_get($handler, $data, $options);
  66. // No cache record, so fetch live.
  67. if ($geometry === NULL) {
  68. try {
  69. // Load the file.
  70. geocoder_get_handler($handler);
  71. $geometry = call_user_func($processor['callback'], $data, $options);
  72. }
  73. catch (Exception $e) {
  74. watchdog_exception('geocoder', $e);
  75. return NULL;
  76. }
  77. // Always save result into persistent cache.
  78. geocoder_cache_set($geometry, $handler, $data, $options);
  79. }
  80. if (!$geometry && variable_get('geocoder_log_empty_results', FALSE)) {
  81. watchdog('geocoder', t('No results for geocoding @data', array('@data' => $data)));
  82. }
  83. return $geometry;
  84. }
  85. /**
  86. * Implements hook_menu().
  87. */
  88. function geocoder_menu() {
  89. // Admin settings for the site.
  90. $items['admin/config/content/geocoder'] = array(
  91. 'title' => 'Geocoder settings',
  92. 'description' => 'Configuration for API keys and other global settings.',
  93. 'page callback' => 'drupal_get_form',
  94. 'page arguments' => array('geocoder_admin_settings'),
  95. 'file' => 'geocoder.admin.inc',
  96. 'access arguments' => array('administer site configuration'),
  97. // Optional.
  98. 'type' => MENU_DEFAULT_LOCAL_TASK,
  99. );
  100. $items['geocoder/service/%'] = array(
  101. 'title' => 'AJAX / AJAJ geocoding service',
  102. 'description' => 'Provides basic callback for geocoding using JavaScript',
  103. 'page callback' => 'geocoder_service_callback',
  104. 'page arguments' => array(2),
  105. 'type' => MENU_CALLBACK,
  106. 'access arguments' => array(arg(2)),
  107. 'access callback' => 'geocoder_service_check_perms',
  108. );
  109. return $items;
  110. }
  111. /**
  112. * Geocoder Handler Information.
  113. *
  114. * Return a list of all handlers that might geocode something for you.
  115. * Optionally you may pass a field-type and get back a list of
  116. * handlers that are compatible with that field.
  117. */
  118. function geocoder_handler_info($field_type = NULL) {
  119. ctools_include('plugins');
  120. static $handlers;
  121. if (!$handlers) {
  122. $handlers = ctools_get_plugins('geocoder', 'geocoder_handler');
  123. }
  124. if ($field_type) {
  125. $field_handlers = $handlers;
  126. foreach ($field_handlers as $i => $handler) {
  127. if (!isset($handler['field_types']) || !in_array($field_type, $handler['field_types'], TRUE)) {
  128. unset($field_handlers[$i]);
  129. }
  130. }
  131. return $field_handlers;
  132. }
  133. return $handlers;
  134. }
  135. /**
  136. * Fetch geocoder handler.
  137. *
  138. * Given a name, fetch the full handler definition.
  139. */
  140. function geocoder_get_handler($handler_name) {
  141. $handlers = geocoder_handler_info();
  142. if (isset($handlers[$handler_name])) {
  143. require_once $handlers[$handler_name]['path'] . '/' . $handlers[$handler_name]['file'];
  144. return $handlers[$handler_name];
  145. }
  146. return FALSE;
  147. }
  148. /**
  149. * Get supported field types.
  150. *
  151. * Get a list of supported field types along with the processors that support them.
  152. */
  153. function geocoder_supported_field_types() {
  154. $supported = array();
  155. $processors = geocoder_handler_info();
  156. foreach ($processors as $processor) {
  157. if (isset($processor['field_types'])) {
  158. foreach ($processor['field_types'] as $field_type) {
  159. $supported[$field_type][] = $processor['name'];
  160. }
  161. }
  162. }
  163. return $supported;
  164. }
  165. /**
  166. * Implementation of hook_ctools_plugin_api().
  167. */
  168. function geocoder_ctools_plugin_api() {
  169. return array('version' => 1);
  170. }
  171. /**
  172. * Implementation of hook_ctools_plugin_directory() to let the system know
  173. * we implement plugins.
  174. */
  175. function geocoder_ctools_plugin_directory($module, $plugin) {
  176. return 'plugins/' . $plugin;
  177. }
  178. /**
  179. * Implements hook_ctools_plugin_type.
  180. */
  181. function geocoder_ctools_plugin_type() {
  182. return array(
  183. 'geocoder_handler' => array(
  184. 'cache' => TRUE,
  185. ),
  186. 'geocoder_reverse_handler' => array(),
  187. );
  188. }
  189. // These functions have to do with providing AJAX / AHAH
  190. // service functionality so that users can make use of
  191. // geocoder interactively via JavaScript.
  192. /**
  193. * Implements hook_permission().
  194. *
  195. * We define permissions for accessing geocoder over AJAX / services.
  196. * There is one global permission which gives access to everything,
  197. * and one permission per handler. The site-administrator can therefore
  198. * fine tune which handlers are accessible. Note that to use AJAX with
  199. * geocoder these permissions need to be set.
  200. */
  201. function geocoder_permission() {
  202. $handler_info = geocoder_handler_info();
  203. $perms = array(
  204. 'geocoder_service_all_handlers' => array(
  205. 'title' => t('Can use all Geocoder handlers through AJAX / service'),
  206. ),
  207. );
  208. foreach ($handler_info as $name => $handler) {
  209. $perms['geocoder_service_handler_' . $name] = array(
  210. 'title' => t('Can geocode using @handler through AJAX / service', array('@handler' => $handler['title'])),
  211. );
  212. }
  213. return $perms;
  214. }
  215. /**
  216. * Geocoder service check permissions.
  217. *
  218. * Given a handler, check to see if the user has
  219. * permission to execute it via AJAX / services.
  220. */
  221. function geocoder_service_check_perms($handler) {
  222. return user_access('geocoder_service_all_handlers') || user_access('geocoder_service_handler_' . $handler);
  223. }
  224. /**
  225. * Page callback for AJAX / Geocoder service.
  226. *
  227. * This service can be accessed at /geocoder/service/<handler>
  228. * and takes the query-parameter "data". Optionally a "output"
  229. * parameter may also be passed. "output" corresponds to
  230. * geoPHP output formats.
  231. *
  232. * Some examples:
  233. * /geocoder/service/google?data=4925 Gair Ave, Terrace, BC
  234. * /geocoder/service/wkt?data=POINT(10 10)
  235. * /geocoder/service/yahoo?data=94112&output=wkt
  236. */
  237. function geocoder_service_callback($handler) {
  238. if (!isset($_GET['data'])) {
  239. throw new Exception(t('No data parameter found'));
  240. exit();
  241. }
  242. $format = isset($_GET['output']) ? $_GET['output'] : 'json';
  243. geophp_load();
  244. geocoder_service_check_request($handler, $format);
  245. $geom = geocoder($handler, $_GET['data']);
  246. header('Content-Type: ' . geocoder_service_get_content_type($format));
  247. print $geom->out($format);
  248. exit();
  249. }
  250. /**
  251. * Get Content-Type for an output format.
  252. *
  253. * Given an output format, this helper function passes
  254. * a compatible HTTP content-type to be placed in the
  255. * header.
  256. */
  257. function geocoder_service_get_content_type($format) {
  258. $types = array(
  259. 'json' => 'application/json',
  260. 'kml' => 'application/xml',
  261. 'georss' => 'application/xml',
  262. 'gpx' => 'application/xml',
  263. 'wkt' => 'text/plain',
  264. 'wkb' => 'text/plain',
  265. 'google_geocode' => 'text/plain',
  266. );
  267. return $types[$format];
  268. }
  269. /**
  270. * Geocoder Service Check Request.
  271. *
  272. * Check to make sure the request to the service is valid. This
  273. * checks to make sure the handler and the format exists, and
  274. * also checks permission.
  275. */
  276. function geocoder_service_check_request($handler, $format, $check_ac = TRUE) {
  277. if (!geocoder_get_handler($handler)) {
  278. drupal_set_message(t('Could not find handler @handler', array('@handler' => $handler)), 'error');
  279. drupal_not_found();
  280. exit();
  281. }
  282. if (($format && $format !== 'default') && !in_array($format, array_keys(geoPHP::getAdapterMap()), TRUE)) {
  283. throw new Exception(t('Could not find output-format @format', array('@format' => $format)));
  284. exit();
  285. }
  286. if (!geocoder_service_check_perms($handler)) {
  287. drupal_access_denied();
  288. exit();
  289. }
  290. }
  291. /**
  292. * Create a unified cache id from relevant cache data.
  293. */
  294. function _geocoder_cache_cid($data) {
  295. ksort($data);
  296. return sha1(serialize($data));
  297. }
  298. /**
  299. * Retrieve a cached geocoded location.
  300. *
  301. * @param string $handler
  302. * The handler used to geocode this data.
  303. * @param mixed $data
  304. * The data used to fetch live geo data.
  305. * @param array $options
  306. * Handler-specific options that have effect on the result.
  307. *
  308. * @return Geometry
  309. * A Geometry object, FALSE (no result), or NULL (no cache).
  310. */
  311. function geocoder_cache_get($handler, $data, $options) {
  312. $data = compact('handler', 'data', 'options');
  313. $cid = _geocoder_cache_cid($data);
  314. geophp_load();
  315. if ($cache = cache_get($cid, 'cache_geocoder')) {
  316. return $cache->data['geometry'];
  317. }
  318. }
  319. /**
  320. * Cache a geocoded result.
  321. *
  322. * @param mixed $geometry
  323. * A Geometry object, or FALSE (no result).
  324. * @param string $handler
  325. * The handler used to geocode this data.
  326. * @param mixed $data
  327. * The data used to fetch live geo data.
  328. * @param array $options
  329. * Handler-specific options that have effect on the result.
  330. */
  331. function geocoder_cache_set($geometry, $handler, $data, $options) {
  332. // Don't cache no-results, to live geocode the same data again next time.
  333. if (!$geometry && !variable_get('geocoder_cache_empty_results', TRUE)) {
  334. return;
  335. }
  336. // Construct the cache id from result-relevant parameters.
  337. $data = compact('handler', 'data', 'options');
  338. $cid = _geocoder_cache_cid($data);
  339. // Cache result-relevant parameters together with the actual result, so cache records can be traced.
  340. $data['geometry'] = $geometry ? $geometry : FALSE;
  341. cache_set($cid, $data, 'cache_geocoder', variable_get('geocoder_cache_ttl', CACHE_PERMANENT));
  342. }
  343. /**
  344. * Implements hook_flush_caches().
  345. */
  346. function geocoder_flush_caches() {
  347. return array('cache_geocoder');
  348. }