geocoder.module 10 KB

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