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

Contents of /contributions/modules/gmap/gmap.module

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


Revision 1.101 - (show annotations) (download) (as text)
Fri Apr 17 18:31:59 2009 UTC (7 months, 1 week ago) by bdragon
Branch: MAIN
CVS Tags: HEAD
Changes since 1.100: +7 -1 lines
File MIME type: text/x-php
Usability: Don't show a JS error when attempting to display a map before the API key has been configured.

This should help when visiting the settings page for the first time, etc. The immediate JS error isn't very nice.
1 <?php
2 // $Id: gmap.module,v 1.100 2009/03/11 16:43:02 bdragon Exp $
3
4 /**
5 * @file
6 * GMap Filters is a module to include Google Map in a module
7 *
8 * GMap filter allows the insertion of a googlemap in a module. It has
9 * a page to creat a macro and then a filter to convet the macro into the
10 * html and javascript code required to insert a google map.
11 */
12
13 /**
14 * Define the Google Maps API version being used.
15 *
16 * Current minimum version: 2.113
17 *
18 * Minimum version last changed on: June 9 2008
19 *
20 * Reason: G_SATELLITE_3D_MAP support in gmap_addons. See http://code.google.com/apis/earth/.
21 *
22 * See http://groups.google.com/group/Google-Maps-API/web/api-version-changes
23 * for details on using other version numbers.
24 */
25 define('GMAP_API_VERSION', '2.115');
26
27 /**
28 * Get the defaults for a gmap.
29 */
30 function gmap_defaults() {
31 $defaults = array(
32 'width' => '300px',
33 'height' => '200px',
34 'zoom' => 3,
35 'maxzoom' => 14,
36 'controltype' => 'Small',
37 'align' => 'None',
38 'latlong' => '40,0',
39 'maptype' => 'Map',
40 'mtc' => 'standard',
41 'baselayers' => array('Map', 'Satellite', 'Hybrid'),
42 'styles' => array(
43 'line_default' => array('0000ff', 5, 45, '', 0, 0),
44 'poly_default' => array('000000', 3, 25, 'ff0000', 45),
45 ),
46 'line_colors' => array('#00cc00', '#ff0000', '#0000ff'),
47 );
48 $defaults['behavior'] = array();
49 $m = array();
50 $behaviors = module_invoke_all('gmap', 'behaviors', $m);
51 foreach ($behaviors as $k => $v) {
52 $defaults['behavior'][$k] = $v['default'];
53 }
54 $defaults = array_merge($defaults, variable_get('gmap_default', array()));
55 return $defaults;
56 }
57
58 /**
59 * Implementation of hook_theme().
60 */
61 function gmap_theme() {
62 return array(
63 'views_view_gmap' => array('arguments' => array('element')),
64 'gmap_views_marker_label' => array('arguments' => array('element')),
65 'gmap_marker_popup' => array('arguments' => array('label')),
66 'gmap_overlay_edit' => array('arguments' => array('element')),
67 'gmap_macrotext' => array('arguments' => array('element')),
68 'gmap_dimension' => array('arguments' => array('element')),
69 'gmap_address' => array('arguments' => array('element')),
70 'gmap_align' => array('arguments' => array('element')),
71 'gmap_style' => array('arguments' => array('element')),
72 'gmap' => array('arguments' => array('element')),
73 );
74 }
75
76 /**
77 * Implementation of hook_gmap().
78 */
79 function gmap_gmap($op, &$map) {
80 switch ($op) {
81 case 'macro':
82 return array(
83 'points' => array(
84 'multiple' => TRUE,
85 ),
86 'markers' => array(
87 'multiple' => TRUE,
88 ),
89 'feed' => array(
90 'multiple' => TRUE,
91 ),
92 'style' => array(
93 'multiple' => TRUE,
94 ),
95 );
96 case 'pre_theme_map':
97 $path = drupal_get_path('module', 'gmap') .'/js/';
98 // Activate markers if needed.
99 if ((isset($map['behavior']['dynmarkers']) && $map['behavior']['dynmarkers']) || !empty($map['markers'])) {
100 static $header_set = FALSE;
101 if (!$header_set) {
102 $header_set = TRUE;
103 // If the user is using private download method, it's up to them to get the path set up.
104 if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) != FILE_DOWNLOADS_PUBLIC) {
105 $markerpath = variable_get('gmap_private_markerfile', '');
106 if (empty($markerpath) || !file_exists($markerpath)) {
107 drupal_set_message(t('GMap marker file settings are not configured properly for Private download method, markers will not work!'), 'error');
108 }
109 else {
110 drupal_add_js($markerpath, 'module', 'header', FALSE, TRUE, FALSE);
111 }
112 }
113 // With public method, we can handle all bookkeeping ourselves.
114 else {
115 $markerpath = file_create_path('js');
116 if (!$markerpath || !file_exists("$markerpath/gmap_markers.js")) {
117 gmap_regenerate_markers();
118 $markerpath = file_create_path('js');
119 }
120 drupal_add_js("$markerpath/gmap_markers.js", 'module', 'header', FALSE, TRUE, FALSE);
121 }
122 }
123
124 drupal_add_js($path .'icon.js');
125 drupal_add_js($path .'marker.js');
126
127 $mm = variable_get('gmap_mm_type', 'gmap');
128 // If you really really want to override the marker manager, implement
129 // this, take $mm by ref, and have fun. --Bdragon
130 if (function_exists('_gmap_markermanager_override')) {
131 _gmap_markermanager_override($mm);
132 }
133
134 drupal_add_js($path . $mm .'_marker.js');
135 }
136 if (isset($map['behavior']['locpick']) && $map['behavior']['locpick']) {
137 drupal_add_js($path .'locpick.js');
138 }
139 if (!empty($map['markers']) || !empty($map['lines'])) {
140 drupal_add_js($path .'markerloader_static.js');
141 }
142 if (!empty($map['shapes'])) {
143 drupal_add_js($path .'shapeloader_static.js');
144 drupal_add_js($path .'gmap_shapes.js');
145 }
146 if (isset($map['feed']) && is_array($map['feed'])) {
147 drupal_add_js($path .'markerloader_georss.js');
148 }
149 break;
150 case 'macro_multiple':
151 return array('points', 'markers', 'feed', 'circle', 'rpolygon', 'polygon', 'line', 'style');
152 case 'behaviors':
153 return array(
154 'locpick' => array(
155 'title' => t('Location chooser'),
156 'default' => FALSE,
157 'help' => t('Used to activate location choosing using a gmap.'),
158 'internal' => TRUE,
159 ),
160 'nodrag' => array(
161 'title' => t('Disable dragging'),
162 'default' => FALSE,
163 'help' => t('Remove the ability for the user to drag the map. If dragging is disabled, keyboard shortcuts are implicitly disabled.'),
164 ),
165 'nokeyboard' => array(
166 'title' => t('Disable keyboard'),
167 'default' => TRUE,
168 'help' => t('Disable the keyboard shortcuts.'),
169 ),
170 'nomousezoom' => array(
171 'title' => t('Disable mousezoom'),
172 'default' => FALSE,
173 'help' => t('Disable using the scroll wheel to zoom the map.'),
174 ),
175 'nocontzoom' => array(
176 'title' => t('Disable Continuous Zoom'),
177 'default' => FALSE,
178 'help' => t('Disable dynamically resizing images while waiting for tiles to load when zooming.'),
179 ),
180 'autozoom' => array(
181 'title' => t('Use AutoZoom'),
182 'default' => FALSE,
183 'help' => t('Automatically zoom the map to fit all markers when markers are added.'),
184 ),
185 'dynmarkers' => array(
186 'title' => t('Unconditionally enable marker interface'),
187 'default' => FALSE,
188 'help' => t('Load the marker loader system even if no markers to load are detected. Useful if you are injecting markers from somewhere else.'),
189 ),
190 'overview' => array(
191 'title' => t('Enable Overview Map'),
192 'default' => FALSE,
193 'help' => t('Enable the "overview map" in the bottom right corner.'),
194 'previewable' => TRUE,
195 ),
196 /* 'notype' => array(
197 'title' => t('Disable map type control'),
198 'default' => FALSE,
199 'help' => t('Removes the map type control from the upper right corner. Recommended for very narrow maps.'),
200 'previewable' => TRUE,
201 ), */
202 'collapsehack' => array(
203 'title' => t('Work around bugs when maps appear in collapsible fieldsets'),
204 'default' => FALSE,
205 'help' => t('Enabling this will work around some issues that can occur when maps appear inside collapsible fieldsets.'),
206 ),
207 // Note to myself, who keeps forgetting what a scale control actually IS.:
208 // |------------ 1mi ------------|
209 'scale' => array(
210 'title' => t('Add scale control to map.'),
211 'default' => FALSE,
212 'help' => t('Adds a scale control to the map in the default position.'),
213 'previewable' => TRUE,
214 ),
215 'extramarkerevents' => array(
216 'title' => t('Enable extra marker events.'),
217 'default' => FALSE,
218 'help' => t('Used for advanced javascript work, this will enable the <em>mouseovermarker</em>, <em>mouseoutmarker</em>, and <em>dblclickmarker</em> events.'),
219 'internal' => TRUE,
220 ),
221 'clickableshapes' => array(
222 'title' => t('Enable clickable shapes.'),
223 'default' => FALSE,
224 'help' => t('Used for advanced javascript work, this will enable the <em>clickshape</em> event.'),
225 'internal' => TRUE,
226 ),
227 );
228 break;
229
230 case 'baselayers':
231 $map['Google']['Map'] = array(
232 'title' => t('Map: Standard street map.'),
233 'default' => TRUE,
234 'help' => t('The standard default street map. Internal name: G_NORMAL_MAP'),
235 );
236 $map['Google']['Satellite'] = array(
237 'title' => t('Satellite: Standard satellite map.'),
238 'default' => TRUE,
239 'help' => t('Satellite view without street overlay. Internal name: G_SATELLITE_MAP'),
240 );
241 $map['Google']['Hybrid'] = array(
242 'title' => t('Hybrid: Hybrid satellite map.'),
243 'default' => TRUE,
244 'help' => t('Satellite view with street overlay. Internal name: G_HYBRID_MAP'),
245 );
246 $map['Google']['Physical'] = array(
247 'title' => t('Terrain: Physical feature map.'),
248 'default' => FALSE,
249 'help' => t('Map with physical data (terrain, vegetation.) Internal name: G_PHYSICAL_MAP'),
250 );
251 break;
252 }
253 }
254
255 /**
256 * Set up the HTML header for GMap.
257 * If you are going to include a custom JS file that extends GMap, you probabaly
258 * want to call this first to ensure that the core js files have been added.
259 */
260 function _gmap_doheader() {
261 static $gmap_initialized = FALSE;
262 if ($gmap_initialized) {
263 return;
264 }
265 $gmap_path = drupal_get_path('module', 'gmap');
266 drupal_add_css($gmap_path .'/gmap.css');
267 drupal_add_js($gmap_path .'/js/gmap.js');
268 $mm = variable_get('gmap_mm_type', 'gmap');
269 $mms = variable_get('gmap_markermanager', array());
270 if (empty($mms[$mm])) {
271 $mms[$mm] = array();
272 }
273 // If you really really want to override the marker manager, implement
274 // this, take $mm by ref, and have fun. --Bdragon
275 if (function_exists('_gmap_markermanager_override')) {
276 _gmap_markermanager_override($mm, $mms);
277 }
278 if ($mm == 'clusterer' || $mm == 'clustermarker') {
279 // Needed for access to clusterer marker.
280 drupal_add_js($gmap_path .'/js/icon.js');
281 }
282 if (isset($mms[$mm]['filename'])) {
283 drupal_add_js($gmap_path .'/thirdparty/'. $mms[$mm]['filename']);
284 }
285 drupal_add_js($gmap_path .'/js/marker.js');
286 drupal_add_js($gmap_path .'/js/'. $mm .'_marker.js');
287 drupal_add_js(array('gmap_markermanager' => $mms[$mm]), 'setting');
288 // @@@
289 drupal_add_js($gmap_path .'/js/poly.js');
290
291 global $language;
292 $query = array(
293 'file' => 'api',
294 'v' => variable_get('gmap_api_version', GMAP_API_VERSION),
295 'key' => gmap_get_key(),
296 'hl' => $language->language,
297 );
298 drupal_set_html_head('<script src="'. check_url(url('http://maps.google.com/maps', array('query' => $query))) .'" type="text/javascript"></script>');
299
300 $gmap_initialized = TRUE;
301 }
302
303 /**
304 * Convert a macro string into a GMap array.
305 *
306 * @param $instring
307 * Macro to process.
308 * @param $ver
309 * Version to treat macro as.
310 * Set to 1 when processing very old macros, otherwise leave as is.
311 * @return
312 * A GMap array.
313 */
314 function gmap_parse_macro($instring, $ver = 2) {
315 require_once drupal_get_path('module', 'gmap') .'/gmap_parse_macro.inc';
316 return _gmap_parse_macro($instring, $ver);
317 }
318
319 /**
320 * Theme a marker popup.
321 * This will get called for markers embedded in macros.
322 * @ingroup themeable
323 */
324 function theme_gmap_marker_popup($label) {
325 return $label;
326 }
327
328 /**
329 * Location chooser utility function.
330 *
331 * Creates a map that can be interactively used to fill a form with a
332 * location (latitude, longitude and zoom level).
333 *
334 * Note: This is a utility function designed for location.module, there is no
335 * guarantee it will not be removed eventually.
336 *
337 * @param $map
338 * Either a macro to use as the base map for setting a location, or an already set map associative array.
339 * @param $form
340 * A formset associative array. Cannot be more than one deep.
341 * @param $fields
342 * An associative array for the field names. 'latitude', 'longitude'=>name of respective array, 'address' is optional.
343 * @return
344 * A string with the google map code to be inserted onto the page.
345 *
346 */
347 function gmap_set_location($map, &$form, $fields) {
348 static $ctr = 0;
349 $ctr++;
350 if (!is_array($map)) {
351 $map = array_merge(gmap_defaults(), gmap_parse_macro($map));
352 }
353 $id = 'loc'. $ctr;
354 $map['id'] = $id;
355
356 // This is a locpick map.
357 $map['behavior']['locpick'] = TRUE;
358
359 $element = array(
360 '#type' => 'gmap',
361 '#map' => $map['id'],
362 '#settings' => $map,
363 );
364
365 $form[$fields['latitude']]['#map']=$id;
366 gmap_widget_setup($form[$fields['latitude']], 'locpick_latitude');
367
368 $form[$fields['longitude']]['#map']=$id;
369 gmap_widget_setup($form[$fields['longitude']], 'locpick_longitude');
370
371 if (isset($fields['address'])) {
372 $form[$fields['address']]['#map'] = $id;
373 gmap_widget_setup($form[$fields['address']], 'locpick_address');
374 }
375 return theme('gmap', $element);
376 }
377
378 /**
379 * Handle filter preparation.
380 */
381 function _gmap_prepare($intext) {
382 $out = FALSE;
383 $matches = array();
384 preg_match_all('/\[gmap([^\[\]]+ )* \] /x', $intext, $matches);
385 $i = 0;
386
387 while (isset($matches[1][$i])) {
388 $out[0][$i] = $matches[0][$i];
389 if ($matches[1][$i][0] == '1') {
390 $ver = 1;
391 $matches[1][$i] = substr($matches[0][$i], 1);
392 }
393 else {
394 $ver = 2;
395 }
396 $map = array('#settings' => gmap_parse_macro($matches[1][$i], $ver));
397 $out[1][$i] = theme('gmap', $map);
398 $i++;
399 } // endwhile process macro
400 return $out;
401 }
402
403 /**
404 * Make sure a string is a valid css dimension.
405 */
406 function gmap_todim($instring) {
407 if (!is_string($instring)) {
408 return FALSE;
409 }
410 $s = strtolower(trim($instring));
411 $matches = array();
412 if (preg_match('/^([\d.]+)\s*(em|ex|px|in|cm|mm|pt|pc|%)$/', $s, $matches)) {
413 return $matches[1] . $matches[2];
414 }
415 else {
416 return FALSE;
417 }
418 }
419
420 /**
421 * Ensure a textfield is a valid css dimension string.
422 */
423 function gmap_dimension_validate(&$elem, &$form_state) {
424 $value = gmap_todim($elem['#value']);
425 if ($value) {
426 // Normalize the css dimension string.
427 form_set_value($elem, $value, $form_state);
428 }
429 else {
430 form_error($elem, t('The specified value is not a valid CSS dimension.'));
431 }
432 }
433
434 /**
435 * Implementation of hook_filter().
436 */
437 function gmap_filter($op, $delta = 0, $format = -1, $text = '') {
438 switch ($op) {
439 case 'list':
440 return (array(0 => t('GMap macro expander')));
441
442 case 'name':
443 return t('Google map filter');
444
445 case 'description':
446 return t('Converts a Google Map macro into the HTML required for inserting a Google Map.');
447
448 case 'process':
449 $gmaps = _gmap_prepare($text); //returns an array of $tables[0] = table macro $table[1]= table html
450 if ($gmaps) { // there are table macros in this node
451 return str_replace($gmaps[0], $gmaps[1], $text);
452 }
453 else {
454 return $text;
455 }
456
457 case 'prepare':
458 return $text;
459
460 case 'no cache':
461 return TRUE; // @@@ Possibly improve efficiency in the future?
462 }
463 }
464
465 /**
466 * Implementation of hook_filter_tips().
467 */
468 function gmap_filter_tips($delta, $format, $long = FALSE) {
469 if (user_access('create gmap macro')) { // only display macro if user can create one
470 return t('Insert Google Map macro.') .'<a href="'. url('map/macro') .'" target="_blank" >'. t('Create a macro') .'</a>';
471 }
472 else {
473 return t('Insert Google Map macro.');
474 }
475 }
476
477 /**
478 * Implementation of hook_menu().
479 */
480 function gmap_menu() {
481 $items['admin/settings/gmap'] = array(
482 'title' => 'GMap',
483 'description' => 'Configure GMap settings',
484 'page callback' => 'drupal_get_form',
485 'page arguments' => array('gmap_admin_settings'),
486 'file' => 'gmap_settings_ui.inc',
487 'access arguments' => array('administer site configuration'),
488 'type' => MENU_NORMAL_ITEM,
489 );
490 return $items;
491 }
492
493 /**
494 * Regenerate the markerdata file.
495 */
496 function gmap_regenerate_markers() {
497 $contents = '';
498
499 // Create the js/ within the files folder.
500 $jspath = file_create_path('js');
501 file_check_directory($jspath, FILE_CREATE_DIRECTORY);
502
503 $contents .= "// GMap marker image data.\n";
504 $contents .= "Drupal.gmap.iconpath = ". drupal_to_js(base_path() . variable_get('gmap_markerfiles', drupal_get_path('module', 'gmap') .'/markers')) .";\n";
505 $contents .= "Drupal.gmap.icondata = ". drupal_to_js(gmap_get_icondata(TRUE)) .";\n";
506
507 file_save_data($contents, "$jspath/gmap_markers.js", FILE_EXISTS_REPLACE);
508
509 // Also regenerate the cached marker titles array
510 gmap_get_marker_titles(TRUE);
511 }
512
513 /**
514 * Implementation of hook_flush_caches().
515 */
516 function gmap_flush_caches() {
517 gmap_regenerate_markers();
518 }
519
520 /**
521 * Implementation of hook_elements().
522 */
523 function gmap_elements() {
524 return array(
525 'gmap' => array(
526 '#input' => FALSE, // This isn't a *form* input!!
527 '#settings' => array_merge(gmap_defaults(), array(
528 'points' => array(),
529 'pointsOverlays' => array(),
530 'lines' => array(),
531 )),
532 ),
533 'gmap_macrotext' => array(
534 '#input' => TRUE,
535 '#gmap_newtype' => 'textarea',
536 '#theme' => 'gmap_macrotext',
537 '#process' => array('process_gmap_control'),
538 ),
539 'gmap_overlay_edit' => array('#input' => TRUE, '#process' => array('process_gmap_overlay_edit')),
540 'gmap_style' => array('#input' => TRUE, '#tree' => TRUE, '#gmap_style_type' => 'poly', '#process' => array('process_gmap_style')),
541 'gmap_address' => array('#input' => TRUE, '#process' => array('process_gmap_address')),
542 'gmap_align' => array('#input' => TRUE, '#process' => array('process_gmap_align')),
543 'gmap_latitude' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control')),
544 'gmap_longitude' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control')),
545 'gmap_latlon' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control')),
546 'gmap_markerchooser' => array('#input' => TRUE, '#process' => array('process_gmap_markerchooser')),
547 'gmap_dimension' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control'), '#element_validate' => array('gmap_dimension_validate')),
548 );
549 }
550
551 /**
552 * Generic gmap control #process function.
553 */
554 function process_gmap_control($element, $edit, &$form_state, $complete_form) {
555 $control = substr($element['#type'], 5);
556 $element['#type'] = $element['#gmap_newtype'];
557 unset($element['#gmap_newtype']);
558 gmap_widget_setup($element, $control);
559 // Attempt to run any #process handlers of our transmogrified type.
560 // However, if we aren't the only #process handler, we assume someone else
561 // is taking care of setting up any default handlers.
562 $chain = FALSE;
563 if (count($element['#process']) == 1) {
564 // Unset #process so we can pull in the default.
565 unset($element['#process']);
566 $chain = TRUE;
567 }
568 // Inherit #input from the new type.
569 unset($element['#input']);
570 // Merge in the defaults for the target element type.
571 $element += _element_info($element['#type']);
572 if (isset($element['#input']) && $element['#input']) {
573 if (isset($element['#process']) && $chain) {
574 // Chain process.
575 foreach ($element['#process'] as $process) {
576 if (function_exists($process)) {
577 $form = $process($element, $edit, $form_state, $complete_form);
578 }
579 }
580 }
581 }
582 return $element;
583 }
584
585 /**
586 * Style fieldset #process function.
587 */
588 function process_gmap_style($element) {
589 $isline = ($element['#gmap_style_type'] == 'line');
590 // Stroke color
591 $element[0] = array(
592 '#type' => 'textfield',
593 '#size' => 6,
594 '#maxlength' => 6,
595 '#field_prefix' => '#',
596 '#title' => t('Stroke color'),
597 '#value' => $element['#value'][0],
598 '#attributes' => array('class' => 'gmap_style'),
599 );
600 // Stroke weight
601 $element[1] = array(
602 '#type' => 'textfield',
603 '#title' => t('Stroke weight'),
604 '#description' => t('Thickness of line, in pixels.'),
605 '#size' => 3,
606 '#maxlength' => 3,
607 '#field_suffix' => t('px'),
608 '#value' => $element['#value'][1],
609 '#attributes' => array('class' => 'gmap_style'),
610 );
611 // Stroke opacity
612 $element[2] = array(
613 '#type' => 'textfield',
614 '#title' => t('Stroke opacity'),
615 '#size' => 3,
616 '#maxlength' => 3,
617 '#field_suffix' => '%',
618 '#value' => $element['#value'][2],
619 '#attributes' => array('class' => 'gmap_style'),
620 );
621 // Fill color
622 $element[3] = array(
623 '#type' => 'textfield',
624 '#title' => t('Fill color'),
625 '#description' => t('Hex color value for fill color. Example: #<em>00AA33</em>'),
626 '#size' => 6,
627 '#maxlength' => 6,
628 '#field_prefix' => '#',
629 '#value' => $isline ? '' : $element['#value'][3],
630 '#disabled' => $isline,
631 '#attributes' => array('class' => 'gmap_style'),
632 );
633 $element[4] = array(
634 '#type' => 'textfield',
635 '#title' => t('Fill opacity'),
636 '#description' => t('Opacity of fill, from 0 to 100%.'),
637 '#size' => 3,
638 '#maxlength' => 3,
639 '#field_suffix' => '%',
640 '#value' => $isline ? '' : $element['#value'][4],
641 '#disabled' => $isline,
642 '#attributes' => array('class' => 'gmap_style'),
643 );
644 unset($element['#input']);
645 $element += _element_info('fieldset');
646 return $element;
647 }
648
649 /**
650 * Theme a gmap_style fieldset.
651 * @ingroup themeable
652 */
653 function theme_gmap_style($element) {
654 // Fieldsets print #value at the end, so we need to empty it out.
655 // Otherwise, it puts "Array" at the end of the fieldset.
656 $element['#value'] = '';
657 return theme('fieldset', $element, $element['#children']);
658 }
659
660 /**
661 * Overlay editor #process function.
662 */
663 function process_gmap_overlay_edit($element) {
664 // Conver the root element into a fieldset.
665 $element['#type'] = 'fieldset';
666 if (!isset($element['#title'])) {
667 $element['#title'] = t('Overlay editor');
668 }
669 $element['#tree'] = TRUE;
670
671 $element['mapclicktype'] = array(
672 '#type' => 'select',
673 '#title' => t('Click map'),
674 '#map' => $element['#map'],
675 '#options' => array(
676 'Points' => t('Points'),
677 'Lines' => t('Lines'),
678 'Circles' => t('Circles'),
679 'GPolygon' => t('Filled Polygons'),
680 ),
681 );
682 gmap_widget_setup($element['mapclicktype'], 'overlayedit_mapclicktype');
683 $element['markerclicktype'] = array(
684 '#type' => 'select',
685 '#title' => t('Click marker / segment'),
686 '#map' => $element['#map'],
687 '#options' => array(
688 'Remove' => t('Remove'),
689 // 'updatestyle' => t('Update Styles'),
690 // 'removestyle' => t('Remove Styles'),
691 'Edit Info' => t('Edit Info'),
692 ),
693 );
694 gmap_widget_setup($element['markerclicktype'], 'overlayedit_markerclicktype');
695
696 $element['marker'] = array(
697 '#type' => 'select',
698 '#map' => $element['#map'],
699 '#options' => gmap_get_marker_titles(),
700 '#title' => t('Marker'),
701 '#theme' => 'gmap_overlay_edit',
702 );
703 gmap_widget_setup($element['marker'], 'overlayedit');
704
705 $element['linestyle'] = array(
706 '#type' => 'gmap_style',
707 '#title' => t('Line style'),
708 '#gmap_style_type' => 'line',
709 '#default_value' => array('0000ff', 5, 45, '', ''), // @@@
710 );
711 gmap_widget_setup($element['linestyle'], 'overlayedit_linestyle', $element['#map']);
712 $element['linestyle']['linestyle_apply'] = array(
713 '#tree' => FALSE,
714 '#type' => 'checkbox',
715 '#title' => t('Use for new and changed lines'),
716 '#default_value' => FALSE,
717 );
718 gmap_widget_setup($element['linestyle']['linestyle_apply'], 'overlayedit_linestyle_apply', $element['#map']);
719
720 $element['polystyle'] = array(
721 '#type' => 'gmap_style',
722 '#title' => t('Polygon style'),
723 '#gmap_style_type' => 'poly',
724 '#default_value' => array('000000', 3, 25, 'ff0000', 45), // @@@
725 );
726 gmap_widget_setup($element['polystyle'], 'overlayedit_polystyle', $element['#map']);
727 $element['polystyle']['polystyle_apply'] = array(
728 '#tree' => FALSE,
729 '#type' => 'checkbox',
730 '#title' => t('Use for new and changed polygons'),
731 '#default_value' => FALSE,
732 );
733 gmap_widget_setup($element['polystyle']['polystyle_apply'], 'overlayedit_polystyle_apply', $element['#map']);
734
735 $element += _element_info('fieldset');
736 return $element;
737 }
738
739 /**
740 * Alignment selector #process function.
741 */
742 function process_gmap_align($element) {
743 $element['#type'] = 'select';
744 gmap_widget_setup($element, 'align');
745 $element['#options'] = drupal_map_assoc(array('None', 'Right', 'Left', 'Center'));
746 $element['#theme'] = 'gmap_align';
747 $element += _element_info('select');
748 return $element;
749 }
750
751 /**
752 * Address widget #process function.
753 */
754 function process_gmap_address($element) {
755 $element['#type'] = 'textfield';
756 gmap_widget_setup($element, 'address');
757 $element['#theme'] = 'gmap_address';
758 $element += _element_info('textfield');
759 return $element;
760 }
761
762 /**
763 * Marker chooser #process function.
764 */
765 function process_gmap_markerchooser($element) {
766 $element['#type'] = 'select';
767 $options = gmap_get_marker_titles();
768 if (!isset($element['#required']) || !$element['#required']) {
769 $options = array('' => t('Default')) + $options;
770 }
771 $element['#options'] = $options;
772 $element += _element_info('select');
773 return $element;
774 }
775
776 /**
777 * Overlay editor theme function.
778 * @ingroup themeable
779 */
780 function theme_gmap_overlay_edit($element) {
781 $path = drupal_get_path('module', 'gmap');
782 drupal_add_js($path .'/js/gmap.js');
783 drupal_add_js($path .'/js/gmap_shapes.js');
784 drupal_add_js($path .'/js/overlay_edit.js');
785 return theme('select', $element);
786 }
787
788 /**
789 * Perform some normalization on the map object
790 * to prevent errors.
791 */
792 function gmap_map_cleanup(&$map) {
793 // Google is picky about this one.
794 $map['zoom'] = (int)$map['zoom'];
795 // Normalize latitude / longitude
796 if ($map['latlong']) {
797 $map['latlon'] = $map['latlong'];
798 unset($map['latlong']);
799 }
800 if (isset($map['latlon']) && (!isset($map['latitude']) || !isset($map['longitude']))) {
801 list($map['latitude'], $map['longitude']) = explode(',', $map['latlon']);
802 }
803 unset($map['latlon']);
804
805 foreach ($map['baselayers'] as $k => $v) {
806 if (!$v) {
807 unset($map['baselayers'][$k]);
808 }
809 }
810 }
811
812 function theme_gmap_macrotext($element) {
813 drupal_add_js(drupal_get_path('module', 'gmap') .'/js/macro.js');
814 // @@@
815 drupal_add_js(drupal_get_path('module', 'gmap') .'/js/macrobuilder.js');
816 return theme('textarea', $element);
817 }
818
819 function theme_gmap_address($element) {
820 drupal_add_js(drupal_get_path('module', 'gmap') .'/js/address.js');
821 $element['#autocomplete_path'] = '';
822 return theme('textfield', $element);
823 }
824
825 function theme_gmap_align($element) {
826 drupal_add_js(drupal_get_path('module', 'gmap') .'/js/align.js');
827 $element['#multiple'] = FALSE;
828 return theme('select', $element);
829 }
830
831 /**
832 * Gmap element theme hook
833 */
834 function theme_gmap($element) {
835 // Usability: Prevent js errors on first visit to settings page, etc.
836 // Of course it will still error if the *wrong* key is on file.
837 if (gmap_get_key() == '') {
838 return t('Unable to render map: Google Maps API key is missing.');
839 }
840
841 // Track the mapids we've used already.
842 static $mapids = array();
843
844 _gmap_doheader();
845
846 // Convert from raw map array if needed.
847 if (!isset($element['#settings'])) {
848 $element = array(
849 '#settings' => $element,
850 );
851 }
852
853 $mapid = FALSE;
854 if (isset($element['#map']) && $element['#map']) {
855 // The default mapid is #map.
856 $mapid = $element['#map'];
857 }
858 if (isset($element['#settings']['id'])) {
859 // Settings overrides it.
860 $mapid = $element['#settings']['id'];
861 }
862 if (!$mapid) {
863 // Hmm, no mapid. Generate one.
864 $mapid = gmap_get_auto_mapid();
865 }
866 // Push the mapid back into #map.
867 $element['#map'] = $mapid;
868
869 gmap_widget_setup($element, 'gmap', $mapid);
870
871 if (!$element['#settings']) {
872 $element['#settings'] = array();
873 }
874
875 // Push the mapid back into #settings.
876 $element['#settings']['id'] = $mapid;
877
878 $mapdefaults = gmap_defaults();
879 $map = array_merge($mapdefaults, $element['#settings']);
880 // Styles is a subarray.
881 if (isset($element['#settings']['styles'])) {
882 $map['styles'] = array_merge($mapdefaults['styles'], $element['#settings']['styles']);
883 }
884 gmap_map_cleanup($map);
885
886 // Add a class around map bubble contents.
887 // @@@ Bdragon sez: Becw, this doesn't belong here. Theming needs to get fixed instead..
888 if (isset($map['markers'])) {
889 foreach ($map['markers'] as $i => $marker) {
890 $map['markers'][$i]['text'] = '<div class="gmap-popup">' . $marker['text'] . '</div>';
891 }
892 }
893
894 switch (strtolower($map['align'])) {
895 case 'left':
896 $element['#attributes']['class'] .= ' gmap-left';
897 break;
898 case 'right':
899 $element['#attributes']['class'] .= ' gmap-right';
900 break;
901 case 'center':
902 case 'centre':
903 $element['#attributes']['class'] .= ' gmap-center';
904 }
905
906 $style = array();
907 $style[] = 'width: '. $map['width'];
908 $style[] = 'height: '. $map['height'];
909
910 $element['#attributes']['class'] = trim($element['#attributes']['class'] .' gmap gmap-map gmap-'. $mapid .'-gmap');
911
912 // Some markup parsers (IE) don't handle empty inners well. Use the space to let users know javascript is required.
913 // @@@ Bevan sez: Google static maps could be useful here.
914 // @@@ Bdragon sez: Yeah, would be nice, but hard to guarantee functionality. Not everyone uses the static markerloader.
915 $o = '<div style="'. implode('; ', $style) .';" id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .'>'. t('Javascript is required to view this map.') .'</div>';
916
917 // $map can be manipulated by reference.
918 foreach (module_implements('gmap') as $module) {
919 call_user_func_array($module .'_gmap', array('pre_theme_map', &$map));
920 }
921
922 if (isset($mapids[$element['#map']])) {
923 drupal_set_message(t('Duplicate map detected! GMap does not support multiplexing maps onto one MapID! GMap MapID: %mapid', array('%mapid' => $element['#map'])), 'error');
924 // Return the div anyway. All but one map for a given id will be a graymap,
925 // because obj.map gets stomped when trying to multiplex maps!
926 return $o;
927 }
928 $mapids[$element['#map']] = TRUE;
929
930 // Inline settings extend.
931 $o .= '<script type="text/javascript">'."\n";
932 $o .= "/* <![CDATA[ */\n";
933 $o .= 'jQuery.extend(true, Drupal, { settings: '. drupal_to_js(array('gmap' => array($element['#map'] => $map))) ." });\n";
934 $o .= "/* ]]> */\n";
935 $o .= "</script>\n";
936 return $o;
937 }
938
939 /**
940 * Set up widget.
941 * This function will change a form element's ID so it is found
942 * by the GMap handlers system.
943 * @param &$element
944 * The form element to modify.
945 * @param $type
946 * The gmap widget type to map to.
947 * @param $map
948 * The map id. If not defined, $element['#map'] will be used.
949 * @return
950 * None.
951 */
952 function gmap_widget_setup(&$element, $type, $map=NULL) {
953 if (!$map) {
954 if (isset($element['#map'])) {
955 $map = $element['#map'];
956 }
957 else {
958 // Hmm, missing #map. Try to figure it out.
959 if (isset($element['#settings']['id'])) {
960 $map = $element['#settings']['id'];
961 }
962 }
963 }
964 if (!isset($element['#attributes']['class'])) {
965 $element['#attributes']['class'] = '';
966 }
967 $element['#attributes']['class'] = trim(implode(' ', array(
968 $element['#attributes']['class'],
969 'gmap-control',
970 'gmap-'. $type,
971 )));
972 $element['#id'] = gmap_get_id($map, $type);
973 $element['#map'] = $map;
974 }
975
976 /**
977 * Get a CSS id for a map and type.
978 * Since CSS ids have to be unique, GMap related IDs are assigned by
979 * this function.
980 */
981 function gmap_get_id($map, $type) {
982 static $serial = array();
983 if (!isset($serial[$map])) {
984 $serial[$map] = array();
985 }
986 if (!isset($serial[$map][$type])) {
987 $serial[$map][$type] = -1;
988 }
989 $serial[$map][$type]++;
990 return 'gmap-'. $map .'-'. $type . $serial[$map][$type];
991 }
992
993 /**
994 * Generate a dynamic map identifier.
995 */
996 function gmap_get_auto_mapid() {
997 static $auto = 0;
998 $auto++;
999 return 'auto'. $auto .'map';
1000 }
1001
1002 /**
1003 * Get the list of marker titles.
1004 */
1005 function gmap_get_marker_titles($reset = FALSE) {
1006 static $titles;
1007
1008 if (!$reset) {
1009 if (is_array($titles)) {
1010 return $titles;
1011 }
1012
1013 $cached = cache_get('gmap_marker_titles', 'cache');
1014 if (!empty($cached)) {
1015 $titles = $cached->data;
1016 if (is_array($titles)) {
1017 return $titles;
1018 }
1019 }
1020 }
1021
1022 require_once(drupal_get_path('module', 'gmap') .'/gmap_markerinfo.inc');
1023 $titles = _gmap_get_marker_titles();
1024 cache_set('gmap_marker_titles', $titles, 'cache');
1025 return $titles;
1026 }
1027
1028 /**
1029 * Get the JSON icon data for all the default markers.
1030 */
1031 function gmap_get_icondata($reset = FALSE) {
1032 static $icons;
1033 if (is_array($icons) && !$reset) {
1034 return $icons;
1035 }
1036
1037 $icons = cache_get('gmap_icondata');
1038 if ($icons) {
1039 $icons = $icons->data;
1040 }
1041
1042 if ($reset || !$icons) {
1043 require_once(drupal_get_path('module', 'gmap') .'/gmap_markerinfo.inc');
1044 $icons = _gmap_get_icondata();
1045 }
1046 cache_set('gmap_icondata', $icons, 'cache');
1047 return $icons;
1048 }
1049
1050 /**
1051 * Utility function to allow high-precision decimals to work with the SQL layer.
1052 * Use concatenation. (Apparently unquoted %s is bad.)
1053 */
1054 function gmap_decimal($num) {
1055 // Paraphrased from postgresql documentation:
1056 //
1057 // Numbers in SQL can be in one of these forms:
1058 // digits
1059 // digits.[digits][e[+-]digits]
1060 // [digits].digits[e[+-]digits]
1061 // digitse[+-]digits
1062 // where "digits" is one or more decimal digits.
1063
1064 // Trim extra whitespace
1065 $num = trim($num);
1066 // Check if we're in an acceptable form.
1067 if (preg_match('/^[+\-]?((\d+)|(\d+\.\d*)|(\d*\.\d+))(e[+\-]?\d+)?$/', $num)===1) {
1068 // Good, we can pass that right along.
1069 return $num;
1070 }
1071 // Otherwise, cast to float, possibly losing precision.
1072 return (float) $num;
1073 }
1074
1075 /**
1076 * Utility function to use the google maps geocoder server side.
1077 * This is an easy, quick way to geocode a single address.
1078 * Note: This is a REMOTE CALL TO GOOGLE. Do NOT use this where performance matters,
1079 * as it could possibly take several seconds for this function to return.
1080 * See http://www.google.com/apis/maps/documentation/reference.html#GGeoStatusCode
1081 * for a description of the possible status codes.
1082 */
1083 function gmap_geocode($address, $tld = 'com') {
1084 $key = gmap_get_key();
1085 $data = drupal_http_request('http://maps.google.'. $tld .'/maps/geo?q='. drupal_urlencode($address) .'&output=csv&key='. $key);
1086 if ($data->code == 200) {
1087 $r = explode(',', $data->data);
1088 return array(
1089 'status' => (int)$r[0],
1090 'accuracy' => (int)$r[1],
1091 'latitude' => $r[2],
1092 'longitude' => $r[3],
1093 );
1094 }
1095 // Non 200 is G_GEO_SERVER_ERROR (500).
1096 return array(
1097 'status' => 500,
1098 );
1099 }
1100
1101 /**
1102 * Simple way to draw a map from inside a theme.
1103 * @param $latitude
1104 * Latitude of marker.
1105 * @param $longitude
1106 * Longitude of marker.
1107 * @param $markername
1108 * Marker to use.
1109 * '' will fall back to google's default marker.
1110 * @param $info
1111 * What to show in the bubble when the marker is clicked.
1112 * Leave blank if you don't want a bubble.
1113 * @param $zoom
1114 * Map zoom.
1115 * 'default' will use the default zoom from the settings page.
1116 * 3 is usually a good value to use.
1117 * @param $width
1118 * Map width.
1119 * 'default' will use the default width from the settings page.
1120 * @param $height
1121 * Map height.
1122 * 'default' will use the default height from the settings page.
1123 * @param $autoshow
1124 * If set to TRUE, automatically show the marker bubble.
1125 * @param $map
1126 * Override parts of the map array.
1127 * If you need to do much with this, you should probabaly be putting together
1128 * the map array manually.
1129 */
1130 function gmap_simple_map($latitude, $longitude, $markername = '', $info = '', $zoom = 'auto', $width = 'default', $height = 'default', $autoshow = FALSE, $map = array()) {
1131 $settings = array(
1132 'id' => gmap_get_auto_mapid(),
1133 'latitude' => $latitude, // Center the map
1134 'longitude' => $longitude, // on the marker.
1135 );
1136 if ($zoom != 'default') {
1137 $settings['zoom'] = $zoom;
1138 }
1139 if ($width != 'default') {
1140 $settings['width'] = $width;
1141 }
1142 if ($height != 'default') {
1143 $settings['height'] = $height;
1144 }
1145
1146 $settings['markers'] = array(array(
1147 'latitude' => $latitude,
1148 'longitude' => $longitude,
1149 'markername' => $markername,
1150 'offset' => 0,
1151 ));
1152
1153 if (!empty($info)) {
1154 $settings['markers'][0]['text'] = $info;
1155 }
1156
1157