/[drupal]/contributions/modules/location/location.module
ViewVC logotype

Contents of /contributions/modules/location/location.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.248 - (show annotations) (download) (as text)
Mon Aug 31 22:09:35 2009 UTC (2 months, 3 weeks ago) by bdragon
Branch: MAIN
CVS Tags: HEAD
Changes since 1.247: +70 -93 lines
File MIME type: text/x-php
WARNING -- PORT IN PROGRESS -- WARNING
This is a very very very preliminary port of some pieces of Location to D7.
There's a lot of work to do before this is remotely usable.
WARNING -- PORT IN PROGRESS -- WARNING


commit 9acd11c864797596209d5c12916e25e209a074cd
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 16:51:59 2009 -0500

    D7: Fix instance saving. I have now successfully put a location on a node in D7. (Of course there's a LOT to do still, but huzzah!)

commit 868ed6352debce3720e4093b50c42397e66cf991
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 16:40:22 2009 -0500

    Fix another class bug. Fix location fieldset theming (for a given value of "fix" of course)

commit d10a646b73da9c8b00c002a13483b986f0018340
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 16:31:43 2009 -0500

    D7: fix call to private api function that got renamed.

commit b79cea16e3c74d0cc7941cd4abcd3b616b793c9f
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 16:28:00 2009 -0500

    D7: Put locative information into the vertical tabs thingie.

commit bf4df441e08a2ba1a0b6164a3688aa2275607f79
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 16:19:10 2009 -0500

    D7: Fix class arrayness and remove a stray & to make PHP stop crashing.

commit be9d1114797319f0015c266f3f6d4a6d398fea2f
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 15:22:13 2009 -0500

    D7: files[] for location_node.

commit f6c32c64cb43f06fc5baca569826ccaf88b931c3
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 15:19:30 2009 -0500

    Fix infinite loops with drupal_render_children(). Remove the drupal_render() call at the end as well as it's nonsensical now and ALSO loops.

commit 125639604c4e5c29d03b3da9265b5fc766347794
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 15:18:10 2009 -0500

    D7 does not have an implicit #theme for elements anymore.

commit 4dcf85adcd30c0a53417fa3c53b2a4882b3cadf4
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 15:08:51 2009 -0500

    Use core's country list. (Which was pulled from location in the first place, hee hee)

commit ebb58524f2b9ef2eca7513971afbb9029743290f
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Mon Aug 31 14:59:53 2009 -0500

    D7: Remove location_default_country variable and use core's site_default_country variable instead.

commit 4cf3950e1a3fa1369ad5bdecac4c3649b2783f2a
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 17:34:26 2009 -0500

    Add warning.

commit 934fdd3aecb4435b7215705d72998b13439b0920
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 17:14:51 2009 -0500

    D7: hook_node split, some of location_node.module, add file to track API changes.

commit 9e69f25d971fa6b5623fb026d5cfd6dba2cc2d70
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 16:17:18 2009 -0500

    D7: 'file' / 'file_path' stuff got rolled back.

commit 80cbd3b688c4a46dc349d449a1abc265894d619f
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 15:42:45 2009 -0500

    D7: [#161301]

commit 35f82a0845ef020411a351fb307f821f78c5e397
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 15:38:53 2009 -0500

    bit more dbtngification.

commit 161280467e70664822e397405f3892628de7acdd
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 15:26:38 2009 -0500

    dbtngify uninstall hook. The rest I'll do later.

commit d6596a08f7a6a424df5109eafae3d7a5b7ce5522
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Fri Aug 28 15:20:52 2009 -0500

    DBTNGify location.module.
    Hopefully I didn't introduce bugs.

commit 6986ba94148c19d461808d61441c0033a4abfe31
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Wed Aug 19 16:02:59 2009 -0500

    Start a todo list.

commit 397adf2c619de9708e4135442204328aac7ae2d8
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Wed Aug 19 15:57:30 2009 -0500

    D7: Use '#markup' not '#value' for markup

commit c3fbd82f21e1af2f024871b247c01bc01ca71a7e
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Wed Aug 19 15:53:17 2009 -0500

    D7: The hook_menu() and hook_theme() "file" and "file path" keys have
    been removed.

commit cf8acf59887f73b997e50aedec95aa5a9784df84
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Wed Aug 19 15:51:55 2009 -0500

    D7: Module .info files must now specify all loadable code files
    explicitly.

commit f4cf33530c11406157745db44cfab0069287da36
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Wed Aug 19 15:51:01 2009 -0500

    D7: Permissions are required to have titles and descriptions.

commit 4a29c6fbafdc1ab106c95ed74df27f7dcfe03bca
Author: Brandon Bergren <bdragon@rtk0.net>
Date:   Wed Aug 19 15:41:46 2009 -0500

    Location HEAD starting point.
    Aug 19 2009 15:42
1 <?php
2 // $Id: location.module,v 1.247 2009/07/30 20:53:48 bdragon Exp $
3
4 /**
5 * @file
6 * Location module main routines.
7 * An implementation of a universal API for location manipulation. Provides functions for
8 * postal_code proximity searching, deep-linking into online mapping services. Currently,
9 * some options are configured through an interface provided by location.module.
10 */
11
12 define('LOCATION_PATH', drupal_get_path('module', 'location'));
13
14 define('LOCATION_LATLON_UNDEFINED', 0);
15 define('LOCATION_LATLON_USER_SUBMITTED', 1);
16 define('LOCATION_LATLON_GEOCODED_APPROX', 2);
17 define('LOCATION_LATLON_GEOCODED_EXACT', 3);
18 define('LOCATION_LATLON_JIT_GEOCODING', 4); // Force regeocoding immediately.
19
20 define('LOCATION_USER_DONT_COLLECT', 0);
21 define('LOCATION_USER_COLLECT', 1);
22
23 include_once LOCATION_PATH .'/location.inc';
24
25 /**
26 * Implementation of hook_menu().
27 */
28 function location_menu() {
29 $items = array();
30
31 $items['location/autocomplete'] = array(
32 'access arguments' => array('access content'),
33 'page callback' => '_location_autocomplete',
34 'type' => MENU_CALLBACK,
35 );
36
37 $items['admin/settings/location'] = array(
38 'title' => 'Location',
39 'description' => 'Settings for Location module',
40 'page callback' => 'drupal_get_form',
41 'page arguments' => array('location_admin_settings'),
42 'file' => 'location.admin.inc',
43 'access arguments' => array('administer site configuration'),
44 );
45 $items['admin/settings/location/main'] = array(
46 'title' => 'Main settings',
47 'type' => MENU_DEFAULT_LOCAL_TASK,
48 );
49 $items['admin/settings/location/maplinking'] = array(
50 'title' => 'Map links',
51 'page callback' => 'drupal_get_form',
52 'page arguments' => array('location_map_link_options_form'),
53 'access arguments' => array('administer site configuration'),
54 'file' => 'location.admin.inc',
55 'type' => MENU_LOCAL_TASK,
56 'weight' => 1,
57 );
58 $items['admin/settings/location/geocoding'] = array(
59 'title' => 'Geocoding options',
60 'page callback' => 'drupal_get_form',
61 'page arguments' => array('location_geocoding_options_form'),
62 'access arguments' => array('administer site configuration'),
63 'file' => 'location.admin.inc',
64 'type' => MENU_LOCAL_TASK,
65 'weight' => 2,
66 );
67 $items['admin/settings/location/geocoding/%/%'] = array(
68 'page callback' => 'location_geocoding_parameters_page',
69 'page arguments' => array(4, 5),
70 'access arguments' => array('administer site configuration'),
71 'type' => MENU_CALLBACK,
72 );
73 $items['admin/settings/location/util'] = array(
74 'title' => 'Location utilities',
75 'page callback' => 'drupal_get_form',
76 'page arguments' => array('location_util_form'),
77 'access arguments' => array('administer site configuration'),
78 'file' => 'location.admin.inc',
79 'type' => MENU_LOCAL_TASK,
80 'weight' => 3,
81 );
82
83 return $items;
84 }
85
86 /**
87 *
88 */
89 function location_api_variant() {
90 return 2;
91 }
92
93 /**
94 * Implementation of hook_perm().
95 */
96 function location_perm() {
97 return array(
98 'submit latitude/longitude' => array(
99 'title' => t('Submit Latitude / Longitude'),
100 'description' => t('Allow the user to submit raw coordinates.'),
101 ),
102 );
103 }
104
105 /**
106 * Implementation of hook_help().
107 *
108 * @TODO: check/fix this: admin/content/configure/types (still use %? still same url?)
109 */
110 function location_help($path, $arg) {
111 switch ($path) {
112 case 'admin/help#location':
113 $output = '<p>'. t('The location module allows you to associate a geographic location with content and users. Users can do proximity searches by postal code. This is useful for organizing communities that have a geographic presence.') .'</p>';
114 $output .= '<p>'. t('To administer locative information for content, use the content type administration page. To support most location enabled features, you will need to install the country specific include file. To support postal code proximity searches for a particular country, you will need a database dump of postal code data for that country. As of June 2007 only U.S. and German postal codes are supported.') .'</p>';
115 $output .= t('<p>You can</p>
116 <ul>
117 <li>administer locative information at <a href="@admin-node-configure-types">Administer &gt;&gt; Content management &gt;&gt; Content types</a> to configure a type and see the locative information.</li>
118 <li>administer location at <a href="@admin-settings-location">Administer &gt;&gt; Site configuration &gt;&gt; Location</a>.</li>
119 <li>use a database dump for a U.S. and/or German postal codes table that can be found at <a href="@external-http-cvs-drupal-org">zipcode database</a>.</li>
120 ', array('@admin-node-configure-types' => url('admin/content/types'), '@admin-settings-location' => url('admin/settings/location'), '@external-http-cvs-drupal-org' => 'http://cvs.drupal.org/viewcvs/drupal/contributions/modules/location/database/')) .'</ul>';
121 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@location">Location page</a>.', array('@location' => 'http://www.drupal.org/handbook/modules/location/')) .'</p>';
122 return $output;
123 }
124 }
125
126 /**
127 * Implementation of hook_elements().
128 */
129 function location_elements() {
130 return array(
131 'location_element' => array(
132 '#input' => TRUE,
133 '#process' => array('_location_expand_location'),
134 '#tree' => TRUE,
135 '#location_settings' => array(),
136 '#required' => FALSE,
137 '#attributes' => array('class' => array('location')),
138 // Element level validation.
139 '#element_validate' => array('location_element_validate'),
140 '#theme' => 'theme_fieldset', // @@@TODO 7.x -- verify that this is sane.
141 ),
142 'location_settings' => array(
143 '#input' => TRUE,
144 '#process' => array('_location_expand_location_settings'),
145 '#collapsible' => TRUE,
146 '#collapsed' => TRUE,
147 '#tree' => TRUE,
148 '#theme' => 'location_settings',
149 ),
150 );
151 }
152
153 /**
154 * Implementation of hook_theme().
155 */
156 function location_theme() {
157 return array(
158 'location_settings' => array('arguments' => array('element')),
159 'locations' => array(
160 'template' => 'locations',
161 'arguments' => array(
162 'locations' => NULL,
163 'hide' => array(),
164 ),
165 ),
166 'location' => array(
167 'template' => 'location',
168 'arguments' => array(
169 'location' => NULL,
170 'hide' => array(),
171 ),
172 ),
173 'location_latitude_dms' => array('arguments' => array('latitude')),
174 'location_longitude_dms' => array('arguments' => array('longitude')),
175 'location_map_link_options' => array('arguments' => array('form')),
176 'location_geocoding_options' => array('arguments' => array('form')),
177 'location_element' => array('arguments' => array('element')),
178 'location_distance' => array('template' => 'location_distance', 'arguments' => array('distance' => 0, 'units' => 'km')),
179 );
180 }
181
182 /**
183 * Implementation of hook_views_api().
184 */
185 function location_views_api() {
186 return array(
187 'api' => 2,
188 //'path' => drupal_get_path('module', 'location') .'/includes',
189 );
190 }
191
192 /**
193 * Process a location element.
194 */
195 function _location_expand_location($element) {
196 drupal_add_css(drupal_get_path('module', 'location') .'/location.css');
197 $element['#tree'] = TRUE;
198
199 if (!isset($element['#title'])) {
200 $element['#title'] = t('Location');
201 }
202 if (empty($element['#location_settings'])) {
203 $element['#location_settings'] = array();
204 }
205 if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
206 $element['#default_value'] = array();
207 }
208
209 $element['location_settings'] = array(
210 '#type' => 'value',
211 '#value' => $element['#location_settings'],
212 );
213
214 // Ensure this isn't accidentally used later.
215 unset($element['#location_settings']);
216
217 // Make a reference to the settings.
218 $settings =& $element['location_settings']['#value'];
219
220 if (isset($element['#default_value']['lid']) && $element['#default_value']['lid']) {
221 // Keep track of the old LID.
222 $element['lid'] = array(
223 '#type' => 'value',
224 '#value' => $element['#default_value']['lid'],
225 );
226 }
227
228 // Fill in missing defaults, etc.
229 location_normalize_settings($settings, $element['#required']);
230
231 $defaults = location_empty_location($settings);
232
233 if (isset($element['lid']['#value']) && $element['lid']['#value']) {
234 $defaults = location_load_location($element['lid']['#value']);
235 }
236
237 $fsettings =& $settings['form']['fields'];
238
239 // $settings -> $settings['form']['fields']
240
241 // $defaults is not necessarily what we want.
242 // If #default_value was already specified, we want to use that, because
243 // otherwise we will lose our values on preview!
244 $fdefaults = $defaults;
245 foreach ($element['#default_value'] as $k => $v) {
246 $fdefaults[$k] = $v;
247 }
248
249 $fields = location_field_names();
250 foreach ($fields as $field => $title) {
251 if (!isset($element[$field])) {
252 // @@@ Permission check hook?
253 if ($fsettings[$field]['collect'] != 0) {
254 $element[$field] = location_invoke_locationapi($fdefaults[$field], 'field_expand', $field, $fsettings[$field]['collect'], $fdefaults);
255 $element[$field]['#weight'] = (int)$fsettings[$field]['weight'];
256 }
257
258 // Only include 'Street Additional' if 'Street' is 'allowed' or 'required'
259 if ($field == 'street' && $fsettings[$field]['collect']) {
260 $element['additional'] = location_invoke_locationapi($defaults['additional'], 'field_expand', 'additional', 1, $defaults);
261 $element['additional']['#weight'] = (int)$fsettings['additional']['weight'];
262 }
263 }
264 }
265 // @@@ Split into submit and view permissions?
266 if (user_access('submit latitude/longitude') && $fsettings['locpick']['collect']) {
267 $element['locpick'] = array('#weight' => $fsettings['locpick']['weight']);
268
269 if (location_has_coordinates($defaults, FALSE)) {
270 $element['locpick']['current'] = array(
271 '#type' => 'fieldset',
272 '#title' => t('Current coordinates'),
273 );
274 $element['locpick']['current']['current_latitude'] = array(
275 '#type' => 'item',
276 '#title' => t('Latitude'),
277 '#value' => $defaults['latitude'],
278 );
279 $element['locpick']['current']['current_longitude'] = array(
280 '#type' => 'item',
281 '#title' => t('Longitude'),
282 '#value' => $defaults['longitude'],
283 );
284 $source = t('Unknown');
285 switch($defaults['source']) {
286 case LOCATION_LATLON_USER_SUBMITTED:
287 $source = t('User-submitted');
288 break;
289 case LOCATION_LATLON_GEOCODED_APPROX:
290 $source = t('Geocoded (Postal code level)');
291 break;
292 case LOCATION_LATLON_GEOCODED_EXACT:
293 $source = t('Geocoded (Exact)');
294 }
295 $element['locpick']['current']['current_source'] = array(
296 '#type' => 'item',
297 '#title' => t('Source'),
298 '#value' => $source,
299 );
300 }
301
302 $element['locpick']['user_latitude'] = array(
303 '#type' => 'textfield',
304 '#title' => t('Latitude'),
305 '#default_value' => isset($element['#default_value']['locpick']['user_latitude']) ? $element['#default_value']['locpick']['user_latitude'] : '',
306 '#size' => 16,
307 '#attributes' => array('class' => array('container-inline')),
308 '#maxlength' => 20,
309 );
310 $element['locpick']['user_longitude'] = array(
311 '#type' => 'textfield',
312 '#title' => t('Longitude'),
313 '#default_value' => isset($element['#default_value']['locpick']['user_longitude']) ? $element['#default_value']['locpick']['user_longitude'] : '',
314 '#size' => 16,
315 '#maxlength' => 20,
316 );
317
318 $element['locpick']['instructions'] = array(
319 '#type' => 'markup',
320 '#weight' => 1,
321 '#prefix' => '<div class=\'description\'>',
322 '#markup' => '<br /><br />' . t('If you wish to supply your own latitude and longitude, you may enter them above. If you leave these fields blank, the system will attempt to determine a latitude and longitude for you from the entered address. To have the system recalculate your location from the address, for example if you change the address, delete the values for these fields.'),
323 '#suffix' => '</div>',
324 );
325 if (function_exists('gmap_get_auto_mapid') && variable_get('location_usegmap', FALSE)) {
326 $mapid = gmap_get_auto_mapid();
327 $map = gmap_parse_macro(variable_get('location_locpick_macro', '[gmap]'));
328 $map['id'] = $mapid;
329 $map['points'] = array();
330 $map['pointsOverlays'] = array();
331 $map['lines'] = array();
332
333 $map['behavior']['locpick'] = TRUE;
334 $map['behavior']['collapsehack'] = TRUE;
335 // Use previous coordinates to center the map.
336 if (location_has_coordinates($defaults, FALSE)) {
337 $map['latitude'] = (float)$defaults['latitude'];
338 $map['longitude'] = (float)$defaults['longitude'];
339
340 $map['markers'][] = array(
341 'latitude' => $defaults['latitude'],
342 'longitude' => $defaults['longitude'],
343 'markername' => 'small gray', // @@@ Settable?
344 'offset' => 0,
345 'opts' => array(
346 'clickable' => FALSE,
347 ),
348 );
349 }
350 $element['locpick']['user_latitude']['#map'] = $mapid;
351 gmap_widget_setup($element['locpick']['user_latitude'], 'locpick_latitude');
352 $element['locpick']['user_longitude']['#map'] = $mapid;
353 gmap_widget_setup($element['locpick']['user_longitude'], 'locpick_longitude');
354
355 $element['locpick']['map'] = array(
356 '#type' => 'gmap',
357 '#weight' => -1,
358 '#map' => $mapid,
359 '#settings' => $map,
360 );
361 $element['locpick']['map_instructions'] = array(
362 '#type' => 'markup',
363 '#weight' => 2,
364 '#prefix' => '<div class=\'description\'>',
365 '#markup' => t('You may set the location by clicking on the map, or dragging the location marker. To clear the location and cause it to be recalculated, click on the marker.'),
366 '#suffix' => '</div>',
367 );
368 }
369 }
370
371 if (isset($defaults['lid']) && !empty($defaults['lid'])) {
372 $element['delete_location'] = array(
373 '#type' => 'checkbox',
374 '#title' => t('Delete'),
375 '#default_value' => isset($fdefaults['delete_location']) ? $fdefaults['delete_location'] : FALSE,
376 '#description' => t('Check this box to delete this location.'),
377 );
378 }
379
380 $element += element_info('fieldset');
381 return $element;
382 }
383
384 function _location_expand_location_settings($element) {
385 // Set a value for the fieldset that doesn't interfere with rendering and doesn't generate a warning.
386 $element['#tree'] = TRUE;
387 $element['#theme'] = 'location_settings';
388
389 if (!isset($element['#title'])) {
390 $element['#title'] = t('Location Fields');
391 }
392 if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
393 $element['#default_value'] = array();
394 }
395
396 // Force #tree on.
397 $element['#tree'] = TRUE;
398
399 $defaults = $element['#default_value'];
400 if (!isset($defaults) || !is_array($defaults)) {
401 $defaults = array();
402 }
403 $temp = location_invoke_locationapi($element, 'defaults');
404 foreach ($temp as $k => $v) {
405 if (!isset($defaults[$k])) {
406 $defaults[$k] = array();
407 }
408 $defaults[$k] = array_merge($v, $defaults[$k]);
409 }
410
411
412 $fields = location_field_names();
413
414 // Options for fields.
415 $options = array(
416 0 => t('Do not collect'),
417 1 => t('Allow'),
418 2 => t('Require'),
419 4 => t('Force Default'), // Need to consider the new "defaults" when saving.
420 );
421
422 foreach ($fields as $field => $title) {
423 $element[$field] = array(
424 '#type' => 'fieldset',
425 '#tree' => TRUE,
426 );
427 $element[$field]['name'] = array(
428 '#type' => 'item',
429 '#value' => $title,
430 );
431 $element[$field]['collect'] = array(
432 '#type' => 'select',
433 '#default_value' => $defaults[$field]['collect'],
434 '#options' => $options,
435 );
436
437 $temp = $defaults[$field]['default'];
438 $element[$field]['default'] = location_invoke_locationapi($temp, 'field_expand', $field, 1, $defaults);
439 $defaults[$field]['default'] = $temp;
440
441 $element[$field]['weight'] = array(
442 '#type' => 'weight',
443 '#delta' => 100,
444 '#default_value' => $defaults[$field]['weight'],
445 );
446 }
447
448 // 'Street Additional' field should depend on 'Street' setting.
449 // It should never be required and should only display when the street field is 'allowed' or 'required'
450 // unset($element['additional']);
451
452 // @@@ Alter here?
453
454 return $element;
455 }
456
457 function theme_location_settings($element) {
458 $rows = array();
459 $header = array(
460 array(
461 'data' => t('Name'),
462 'colspan' => 2,
463 ),
464 t('Collect'), t('Default'), t('Weight'));
465
466 // Force country required.
467 $element['country']['default']['#required'] = TRUE;
468 unset($element['country']['collect']['#options'][0]);
469
470 foreach (element_children($element) as $key) {
471 $element[$key]['weight']['#attributes']['class'] = array('location-settings-weight');
472 unset($element[$key]['default']['#title']);
473 $row = array();
474 $row[] = array('data' => '', 'class' => array('location-settings-drag'));
475 $row[] = drupal_render($element[$key]['name']);
476 $row[] = drupal_render($element[$key]['collect']);
477 $row[] = drupal_render($element[$key]['default']);
478 $row[] = array('data' => drupal_render($element[$key]['weight']), 'class' => array('delta-order'));
479
480 $rows[] = array(
481 '#weight' => (int)$element[$key]['weight']['#value'],
482 'data' => $row,
483 'class' => array('draggable'),
484 );
485 }
486
487 uasort($rows, 'element_sort');
488 foreach ($rows as $k => $v) {
489 unset($rows[$k]['#weight']);
490 }
491
492 drupal_add_tabledrag('location-settings-table', 'order', 'sibling', 'location-settings-weight');
493
494 $output = theme('table', $header, $rows, array('id' => 'location-settings-table'));
495 //return theme('form_element', $element, $element['#children']);
496 return $output;
497 }
498
499 function location_field_names($all = FALSE) {
500 static $fields;
501 static $allfields;
502 if ($all) {
503 if (empty($allfields)) {
504 $dummy = array();
505 $allfields = location_invoke_locationapi($dummy, 'fields');
506 $virtual = location_invoke_locationapi($dummy, 'virtual fields');
507 $allfields += $virtual;
508 }
509 return $allfields;
510 }
511 else {
512 if (empty($fields)) {
513 $dummy = array();
514 $fields = location_invoke_locationapi($dummy, 'fields');
515 }
516 return $fields;
517 }
518 }
519
520 /**
521 * Implementation of hook_locationapi().
522 */
523 function location_locationapi(&$obj, $op, $a3 = NULL, $a4 = NULL, $a5 = NULL) {
524 switch ($op) {
525 case 'fields':
526 return array('name' => t('Location name'), 'street' => t('Street location'), 'additional' => t('Additional'), 'city' => t('City'), 'province' => t('State/Province'), 'postal_code' => t('Postal code'), 'country' => t('Country'), 'locpick' => t('Coordinate Chooser'));
527
528 case 'virtual fields':
529 return array('province_name' => t('Province name'), 'country_name' => t('Country name'), 'map_link' => t('Map link'), 'coords' => t('Coordinates'));
530
531 case 'defaults':
532 return array(
533 'lid' => array('default' => FALSE),
534 'name' => array('default' => '', 'collect' => 1, 'weight' => 2),
535 'street' => array('default' => '', 'collect' => 1, 'weight' => 4),
536 'additional' => array('default' => '', 'collect' => 1, 'weight' => 6),
537 'city' => array('default' => '', 'collect' => 0, 'weight' => 8),
538 'province' => array('default' => '', 'collect' => 0, 'weight' => 10),
539 'postal_code' => array('default' => '', 'collect' => 0, 'weight' => 12),
540 'country' => array('default' => variable_get('site_default_country', 'us'), 'collect' => 1, 'weight' => 14), // @@@ Fix weight?
541 'locpick' => array('default' => FALSE, 'collect' => 1, 'weight' => 20, 'nodiff' => TRUE),
542 'latitude' => array('default' => 0),
543 'longitude' => array('default' => 0),
544 'source' => array('default' => LOCATION_LATLON_UNDEFINED),
545 'is_primary' => array('default' => 0), // @@@
546 'delete_location' => array('default' => FALSE, 'nodiff' => TRUE),
547 );
548
549 case 'validate':
550 if (!empty($obj['country'])) {
551 if (!empty($obj['province'])) {
552 $provinces = location_get_provinces($obj['country']);
553 $found = FALSE;
554 $p = strtoupper($obj['province']);
555 foreach ($provinces as $k => $v) {
556 if ($p == strtoupper($k) || $p == strtoupper($v)) {
557 $found = TRUE;
558 break;
559 }
560 }
561 if (!$found) {
562 form_error($a3['province'], t('The specified province was not found in the specified country.'));
563 }
564 }
565 }
566
567 if (!empty($obj['locpick']) && is_array($obj['locpick'])) {
568 // Can't specify just latitude or just longitude.
569 if (_location_floats_are_equal($obj['locpick']['user_latitude'], 0) xor _location_floats_are_equal($obj['locpick']['user_longitude'], 0)) {
570 $ref = &$a3['locpick']['user_latitude'];
571 if (_location_floats_are_equal($obj['locpick']['user_longitude'], 0)) {
572 $ref = &$a3['locpick']['user_longitude'];
573 }
574 form_error($ref, t('You must fill out both latitude and longitude or you must leave them both blank.'));
575 }
576 }
577
578 break;
579
580 case 'field_expand':
581 switch ($a3) {
582 case 'name':
583 return array(
584 '#type' => 'textfield',
585 '#title' => t('Location name'),
586 '#default_value' => $obj,
587 '#size' => 64,
588 '#maxlength' => 64,
589 '#description' => t('e.g. a place of business, venue, meeting point'),
590 '#attributes' => NULL,
591 '#required' => ($a4 == 2),
592 );
593
594 case 'street':
595 return array(
596 '#type' => 'textfield',
597 '#title' => t('Street'),
598 '#default_value' => $obj,
599 '#size' => 64,
600 '#maxlength' => 64,
601 '#required' => ($a4 == 2),
602 );
603
604 // Additional is linked to street.
605 case 'additional':
606 return array(
607 '#type' => 'textfield',
608 '#title' => t('Additional'),
609 '#default_value' => $obj,
610 '#size' => 64,
611 '#maxlength' => 64,
612 // Required is forced OFF because this is technically part of street.
613 );
614
615 case 'city':
616 return array(
617 '#type' => 'textfield',
618 '#title' => t('City'),
619 '#default_value' => $obj,
620 '#size' => 64,
621 '#maxlength' => 64,
622 '#description' => NULL,
623 '#attributes' => NULL,
624 '#required' => ($a4 == 2),
625 );
626
627 case 'province':
628 drupal_add_js(drupal_get_path('module', 'location') .'/location_autocomplete.js');
629 $country = $a5['country'] ? $a5['country'] : variable_get('site_default_country', 'us');
630 return array(
631 '#type' => 'textfield',
632 '#title' => t('State/Province'),
633 '#autocomplete_path' => 'location/autocomplete/'. $country,
634 '#default_value' => $obj,
635 '#size' => 64,
636 '#maxlength' => 64,
637 '#description' => NULL,
638 // Used by province autocompletion js.
639 '#attributes' => array('class' => array('location_auto_province')),
640 '#required' => ($a4 == 2),
641 );
642
643 case 'country':
644 // Force default.
645 if ($a4 == 4) {
646 return array(
647 '#type' => 'value',
648 '#value' => $obj,
649 );
650 }
651 else {
652 $options = array_merge(array('' => t('Please select'), 'xx' => t('NOT LISTED')), location_get_iso3166_list());
653 return array(
654 '#type' => 'select',
655 '#title' => t('Country'),
656 '#default_value' => $obj,
657 '#options' => $options,
658 '#description' => NULL,
659 '#required' => ($a4 == 2),
660 // Used by province autocompletion js.
661 '#attributes' => array('class' => array('location_auto_country')),
662 );
663 }
664 break;
665
666 case 'postal_code':
667 return array(
668 '#type' => 'textfield',
669 '#title' => t('Postal code'),
670 '#default_value' => $obj,
671 '#size' => 16,
672 '#maxlength' => 16,
673 '#required' => ($a4 == 2),
674 );
675 }
676 break;
677
678 case 'isunchanged':
679 switch ($a3) {
680 case 'lid':
681 // Consider 0, NULL, and FALSE to be equivilent.
682 if (empty($obj[$a3]) && empty($a4)) {
683 return TRUE;
684 }
685 break;
686 case 'latitude':
687 case 'longitude':
688 if (_location_floats_are_equal($obj[$a3], $a4)) {
689 return TRUE;
690 }
691 break;
692
693 case 'country':
694 // Consider ' ' and '' to be equivilent, due to us storing country
695 // as char(2) in the database.
696 if (trim($obj[$a3]) == trim($a4)) {
697 return TRUE;
698 }
699 break;
700
701 case 'province_name':
702 case 'country_name':
703 case 'map_link':
704 case 'coords':
705 case 'locpick':
706 case 'delete_location':
707 // Always considered unchanged.
708 return TRUE;
709 }
710 break;
711
712 }
713 }
714
715 function location_geocoding_parameters_page($country_iso, $service) {
716 drupal_set_title(t('Configure parameters for %service geocoding', array('%service' => $service)));
717
718 $breadcrumbs = drupal_get_breadcrumb();
719 $breadcrumbs[] = l('location', 'admin/settings/location');
720 $breadcrumbs[] = l('geocoding', 'admin/settings/location/geocoding');
721 $countries = location_get_iso3166_list();
722 $breadcrumbs[] = l($countries[$country_iso], 'admin/settings/location/geocoding', array('fragment' => $country_iso));
723 drupal_set_breadcrumb($breadcrumbs);
724 return drupal_get_form('location_geocoding_parameters_form', $country_iso, $service);
725 }
726
727 function location_geocoding_parameters_form(&$form_state, $country_iso, $service) {
728 location_load_country($country_iso);
729 $geocode_settings_form_function_specific = 'location_geocode_'. $country_iso .'_'. $service .'_settings';
730 $geocode_settings_form_function_general = $service .'_geocode_settings';
731 if (function_exists($geocode_settings_form_function_specific)) {
732 return system_settings_form($geocode_settings_form_function_specific());
733 }
734 location_load_geocoder($service);
735 if (function_exists($geocode_settings_form_function_general)) {
736 return system_settings_form($geocode_settings_form_function_general());
737 }
738 else {
739 return system_settings_form(array(
740 '#type' => 'markup',
741 '#markup' => t('No configuration parameters are necessary, or a form to take such paramters has not been implemented.')
742 ));
743 }
744 }
745
746 /**
747 * Load associated locations.
748 *
749 * @param $id The identifier to match. (An integer.)
750 * @param $key The search key for {location_instance} (usually vid or uid.)
751 * @return An array of loaded locations.
752 */
753 function location_load_locations($id, $key = 'vid') {
754 if (empty($id)) {
755 // If the id is 0 or '' (or false), force returning early.
756 // Otherwise, this could accidentally load a huge amount of data
757 // by accident. 0 and '' are reserved for "not applicable."
758 return array();
759 }
760 $query = db_select('location_instance', 'l');
761 $lid_field = $query->addField('l', 'lid');
762 $query->condition($key, $id);
763 $result = $query->execute();
764 $locations = array();
765 foreach ($result as $lid) {
766 $locations[] = location_load_location($lid->{$lid_field});
767 }
768 return $locations;
769 }
770
771 /**
772 * Save associated locations.
773 *
774 * @param $locations The associated locations.
775 * You can pass an empty array to remove all location references associated
776 * with the given criteria. This is useful if you are about to delete an object,
777 * and need Location to clean up any locations that are no longer referenced.
778 *
779 * @param $criteria An array of instance criteria to save as.
780 * Example: array('genid' => 'my_custom_1111')
781 */
782 function location_save_locations(&$locations, $criteria) {
783 if (isset($locations) && is_array($locations) && !empty($criteria) && is_array($criteria)) {
784 foreach (array_keys($locations) as $key) {
785 location_save($locations[$key], TRUE, $criteria);
786 }
787
788 // Find affected lids.
789 $query = db_select('location_instance', 'l');
790 $lid_field = $query->addField('l', 'lid');
791 foreach ($criteria as $key => $value) {
792 $query->condition($key, $value);
793 }
794 $oldlids = $query->execute()->fetchCol();
795
796 // Delete current set of instances.
797 $query = db_delete('location_instance');
798 foreach ($criteria as $key => $value) {
799 $query->condition($key, $value);
800 }
801
802 $newlids = array();
803 $query = db_insert('location_instance')->fields(array('nid', 'vid', 'uid', 'genid', 'lid'));
804 foreach ($locations as $location) {
805 // Don't save "empty" locations.
806 // location_save() explicitly returns FALSE for empty locations,
807 // so it should be ok to rely on the data type.
808 if ($location['lid'] !== FALSE) {
809 $newlids[] = $location['lid'];
810 $instance = array(
811 'nid' => 0,
812 'vid' => 0,
813 'uid' => 0,
814 'genid' => '',
815 'lid' => $location['lid'],
816 );
817 foreach ($criteria as $k => $v) {
818 $instance[$k] = $v;
819 }
820 $query->values($instance);
821 }
822 }
823 $query->execute();
824
825 // Check anything that dropped a reference during this operation.
826 foreach (array_diff($oldlids, $newlids) as $check) {
827 // An instance may have been deleted. Check reference count.
828 $count = db_query('SELECT COUNT(*) FROM {location_instance} WHERE lid = :lid', array(':lid' => $check))->fetchField();
829 if ($count !== FALSE && $count == 0) {
830 watchdog('location', t('Deleting unreferenced location with LID %lid.', array('%lid' => $check)));
831 $location = array('lid' => $check);
832 location_invoke_locationapi($location, 'delete');
833 db_delete('location')
834 ->condition('lid', $location['lid'])
835 ->execute();
836 }
837 }
838 }
839 }
840
841 /**
842 * Load a single location by lid.
843 *
844 * @param $lid Location ID to load.
845 * @return A location array.
846 */
847 function location_load_location($lid) {
848 $location = db_query('SELECT * FROM {location} WHERE lid = :lid', array(':lid' => $lid))->fetchAssoc();
849 // @@@ Just thought of this, but I am not certain it is a good idea...
850 if (empty($location)) {
851 $location = array('lid' => $lid);
852 }
853 if (isset($location['source']) && $location['source'] == LOCATION_LATLON_USER_SUBMITTED) {
854 // Set up location chooser or lat/lon fields from the stored location.
855 $location['locpick'] = array(
856 'user_latitude' => $location['latitude'],
857 'user_longitude' => $location['longitude'],
858 );
859 }
860
861 // JIT Geocoding
862 // Geocodes during load, useful with bulk imports.
863 if (isset($location['source']) && $location['source'] == LOCATION_LATLON_JIT_GEOCODING) {
864 if (variable_get('location_jit_geocoding', FALSE)) {
865 _location_geo_logic($location, array('street' => 1), array());
866 db_update('location')
867 ->fields(array('latitude', 'longitude', 'source'))
868 ->values($location)
869 ->condition('lid', $location['lid'])
870 ->execute();
871 }
872 }
873
874 $location['province_name'] = '';
875 $location['country_name'] = '';
876
877 if (!empty($location['country'])) {
878 $location['country_name'] = location_country_name($location['country']);
879
880 if (!empty($location['province'])) {
881 $location['province_name'] = location_province_name($location['country'], $location['province']);
882 }
883 }
884
885 $location = array_merge($location, location_invoke_locationapi($location, 'load', $lid));
886
887 return $location;
888 }
889
890 /**
891 * Create a list of states from a given country.
892 *
893 * @param $country
894 * String. The country code
895 * @param $string
896 * String (optional). The state name typed by user
897 * @return
898 * Javascript array. List of states
899 */
900 function _location_autocomplete($country, $string = '') {
901 $counter = 0;
902 $string = strtolower($string);
903 $string = '/^'. $string .'/';
904 $matches = array();
905
906 if (strpos($country, ',') !== FALSE) {
907 // Multiple countries specified.
908 $provinces = array();
909 $country = explode(',', $country);
910 foreach($country as $c) {
911 $provinces = $provinces + location_get_provinces($c);
912 }
913 }
914 else {
915 $provinces = location_get_provinces($country);
916 }
917
918 if (!empty($provinces)) {
919 while (list($code, $name) = each($provinces)) {
920 if ($counter < 5) {
921 if (preg_match($string, strtolower($name))) {
922 $matches[$name] = $name;
923 ++$counter;
924 }
925 }
926 }
927 }
928 echo drupal_to_js($matches);
929 return;
930 }
931
932 /**
933 * Epsilon test.
934 * Helper function for seeing if two floats are equal. We could use other functions, but all
935 * of them belong to libraries that do not come standard with PHP out of the box.
936 */
937 function _location_floats_are_equal($x, $y) {
938 $x = floatval($x);
939 $y = floatval($y);
940 return (abs(max($x, $y) - min($x, $y)) < pow(10, -6));
941 }
942
943 /**
944 * Check whether a location has coordinates or not.
945 *
946 * @param $location The location to check.
947 * @param $canonical Is this a location that is fully saved?
948 * If set to TRUE, only the source will be checked.
949 */
950 function location_has_coordinates($location, $canonical = FALSE) {
951 // Locations that have been fully saved have an up to date source.
952 if ($canonical) {
953 return ($location['source'] != LOCATION_LATLON_UNDEFINED);
954 }
955
956 // Otherwise, we need to do the full checks.
957
958 // If latitude or longitude are empty / missing
959 if (empty($location['latitude']) || empty($location['longitude'])) {
960 return FALSE;
961 }
962
963 // If the latitude or longitude are zeroed (Although it could be a good idea to relax this slightly sometimes)
964 if (_location_floats_are_equal($location['latitude'], 0.0) || _location_floats_are_equal($location['longitude'], 0.0)) {
965 return FALSE;
966 }
967
968 return TRUE;
969 }
970
971 /**
972 * Invoke a hook_locationapi() operation on all modules.
973 *
974 * @param &$location A location object.
975 * @param $op A string containing the name of the locationapi operation.
976 * @param $a3, $a4, $a5 Arguments to pass on to the hook.
977 * @return The returned value of the invoked hooks.
978 */
979 function location_invoke_locationapi(&$location, $op, $a3 = NULL, $a4 = NULL, $a5 = NULL) {
980 $return = array();
981 foreach (module_implements('locationapi') as $name) {
982 $function = $name .'_locationapi';
983 $result = $function($location, $op, $a3, $a4, $a5);
984 if (isset($result) && is_array($result)) {
985 $return = array_merge($return, $result);
986 }
987 else if (isset($result)) {
988 $return[] = $result;
989 }
990 }
991 return $return;
992 }
993
994 /**
995 * Apply locpick twiddling to a location.
996 * This is needed before saving and comparison.
997 */
998 function _location_patch_locpick(&$location) {
999 $inhibit_geocode = FALSE;
1000 if (!empty($location['locpick'])) {
1001 $location['locpick']['user_latitude'] = trim($location['locpick']['user_latitude']);
1002 $location['locpick']['user_longitude'] = trim($location['locpick']['user_longitude']);
1003 }
1004 // If the user location was set, convert it into lat / lon.
1005 if (!empty($location['locpick']['user_latitude']) && !empty($location['locpick']['user_longitude'])) {
1006 $location['source'] = LOCATION_LATLON_USER_SUBMITTED;
1007 $location['latitude'] = $location['locpick']['user_latitude'];
1008 $location['longitude'] = $location['locpick']['user_longitude'];
1009 $inhibit_geocode = TRUE;
1010 }
1011 return $inhibit_geocode;
1012 }
1013
1014 /**
1015 * Save a location.
1016 *
1017 * This is the central function for saving a location.
1018 * @param $location Location array to save.
1019 * @param $cow Copy-on-write, i.e. whether or not to assign a new lid if something changes.
1020 * @param $criteria Instance criteria. If the only instances known by location match
1021 * the criteria, the lid will be reused, regardless of $cow status. If no criteria
1022 * is provided, there will be no attempt to reuse lids.
1023 * @return The lid of the saved location, or FALSE if the location is considered "empty."
1024 */
1025 function location_save(&$location, $cow = TRUE, $criteria = array()) {
1026 // Quick settings fixup.
1027 if (!isset($location['location_settings'])) {
1028 $location['location_settings'] = array();
1029 }
1030 location_normalize_settings($location['location_settings']);
1031
1032 $inhibit_geocode = FALSE;
1033
1034 if (isset($location['inhibit_geocode']) && $location['inhibit_geocode']) {
1035 // Workaround for people importing / generating locations.
1036 // Allows things like location_generate.module to work properly.
1037 $inhibit_geocode = TRUE;
1038 unset($location['inhibit_geocode']);
1039 }
1040
1041 if (isset($location['delete_location']) && $location['delete_location']) {
1042 // Location is being deleted.
1043 // Consider it empty and return early.
1044 $location['lid'] = FALSE;
1045 return FALSE;
1046 }
1047
1048 // If there's already a lid, we're editing an old location. Load it in.
1049 $oldloc = location_empty_location($location['location_settings']);
1050 if (isset($location['lid']) && !empty($location['lid'])) {
1051 $oldloc = (array)location_load_location($location['lid']);
1052 }
1053 if (_location_patch_locpick($location)) {
1054 $inhibit_geocode = TRUE;
1055 }
1056
1057 // Pull in fields that hold data currently not editable directly by the user.
1058 $location = array_merge($oldloc, $location);
1059
1060 // Note: If the user clears all the fields, the location can still
1061 // be non-empty if the user didn't have access to everything..
1062 $filled = array();
1063 if (location_is_empty($location, $filled)) {
1064 // This location was empty, we don't need to continue.
1065 $location['lid'] = FALSE;
1066 return FALSE;
1067 }
1068
1069 $changed = array();
1070 if (!location_calc_difference($oldloc, $location, $changed)) {
1071 // We didn't actually need to save anything.
1072 if (!empty($location['lid'])) {
1073 return $location['lid'];
1074 }
1075 else {
1076 // Unfilled location (@@@ Then how did we get here?)
1077 $location['lid'] = FALSE;
1078 return FALSE;
1079 }
1080 }
1081
1082 // Perform geocoding logic, coordinate normalization, etc.
1083 _location_geo_logic($location, $changed, $filled, $inhibit_geocode);
1084
1085 // If we are in COW mode, we *probabaly* need to make a new lid.
1086 if ($cow) {
1087 if (isset($location['lid']) && $location['lid']) {
1088 if (!empty($criteria)) {
1089