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

Contents of /contributions/modules/weather/weather.module

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


Revision 1.209 - (show annotations) (download) (as text)
Mon Sep 28 13:14:28 2009 UTC (2 months ago) by toddy
Branch: MAIN
CVS Tags: HEAD
Changes since 1.208: +25 -15 lines
File MIME type: text/x-php
Merge changes from DRUPAL-6--5-6 to DRUPAL-6--5-7
1 <?php
2 /* $Id: weather.module,v 1.208 2009/09/03 20:31:09 toddy Exp $
3 *
4 * Copyright © 2006-2009 Tobias Quathamer <t.quathamer@gmx.net>
5 *
6 * This file is part of the Drupal Weather module.
7 *
8 * It was inspired by the Weather module which was written in 2004 by
9 * Gerard Ryan <gerardryan@canada.com>.
10 *
11 * Weather is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * Weather is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with Weather; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 */
25
26
27
28 /**
29 * @file
30 * Display <acronym title="METeorological Aerodrome Report">METAR</acronym>
31 * weather data from anywhere in the world
32 *
33 * The module is compatible with Drupal 6.x
34 *
35 * @author Tobias Quathamer
36 */
37
38
39
40 // Define the start of block deltas for system-wide blocks
41 define('SYSTEM_BLOCK_DELTA_START', 3);
42
43 // Include the parser for METAR data
44 require_once drupal_get_path('module', 'weather') .'/weather_parser.inc';
45
46
47
48 /*********************************************************************
49 * General Drupal hooks for registering the module
50 ********************************************************************/
51
52
53
54 /**
55 * Implementation of hook_perm().
56 */
57 function weather_perm() {
58 return array(
59 'administer custom weather block',
60 'access weather pages'
61 );
62 }
63
64
65
66 /**
67 * Implementation of hook_menu().
68 */
69 function weather_menu() {
70 $items['admin/settings/weather'] = array(
71 'title' => 'Weather',
72 'description' => 'Configure system-wide weather blocks and the default configuration for new locations.',
73 'page callback' => 'weather_admin_main_page',
74 'access arguments' => array('administer site configuration'),
75 'type' => MENU_NORMAL_ITEM,
76 );
77 $items['admin/settings/weather/edit/%/%'] = array(
78 'title' => 'Edit location',
79 'description' => 'Configure a system-wide weather block.',
80 'page callback' => 'weather_custom_block',
81 'page arguments' => array(4, 5),
82 'access arguments' => array('administer site configuration'),
83 'type' => MENU_CALLBACK,
84 );
85 $items['admin/settings/weather/delete/%/%'] = array(
86 'title' => 'Delete location',
87 'description' => 'Delete a location from a system-wide weather block.',
88 'page callback' => 'drupal_get_form',
89 'page arguments' => array('weather_custom_block_delete_confirm', 4, 5),
90 'access arguments' => array('administer site configuration'),
91 'type' => MENU_CALLBACK,
92 );
93 $items['admin/settings/weather/default'] = array(
94 'title' => 'Default configuration',
95 'description' => 'Setup the default configuration for new locations.',
96 'page callback' => 'weather_custom_block',
97 'page arguments' => array('0'),
98 'access arguments' => array('administer site configuration'),
99 'type' => MENU_CALLBACK,
100 );
101 $items['user/%/weather'] = array(
102 'title' => 'My weather',
103 'description' => 'Configure your custom weather block.',
104 'page callback' => 'weather_user_main_page',
105 'page arguments' => array(1),
106 'access callback' => 'weather_custom_block_access',
107 'access arguments' => array(1),
108 'type' => MENU_LOCAL_TASK,
109 );
110 $items['user/%/weather/edit/%'] = array(
111 'title' => 'Edit location',
112 'description' => 'Configure your custom weather block.',
113 'page callback' => 'weather_custom_block',
114 'page arguments' => array(1, 4),
115 'access callback' => 'weather_custom_block_access',
116 'access arguments' => array(1),
117 'type' => MENU_CALLBACK,
118 );
119 $items['user/%/weather/delete/%'] = array(
120 'title' => 'Delete location',
121 'description' => 'Delete a location from your custom weather block.',
122 'page callback' => 'drupal_get_form',
123 'page arguments' => array('weather_custom_block_delete_confirm',
124 1, 4),
125 'access callback' => 'weather_custom_block_access',
126 'access arguments' => array(1),
127 'type' => MENU_CALLBACK,
128 );
129 $items['weather/js'] = array(
130 'page callback' => 'weather_js',
131 'access arguments' => array('access content'),
132 'type' => MENU_CALLBACK,
133 );
134 $items['weather'] = array(
135 'title' => 'Weather',
136 'description' => 'Search for locations and display their current weather.',
137 'page callback' => 'weather_search_location',
138 'access arguments' => array('access weather pages'),
139 'type' => MENU_NORMAL_ITEM,
140 );
141 $items['weather/autocomplete'] = array(
142 'page callback' => 'weather_search_autocomplete',
143 'access arguments' => array('access weather pages'),
144 'type' => MENU_CALLBACK,
145 );
146 return $items;
147 }
148
149
150
151 /**
152 * Implementation of hook_help().
153 */
154 function weather_help($path, $arg) {
155 $output = '';
156
157 switch ($path) {
158 case 'admin/settings/weather':
159 $output .= '<p>';
160 $output .= t('You can add, edit, and delete locations from system-wide weather blocks. Moreover, you can specify default values for newly created locations.');
161 $output .= '</p>';
162 break;
163 case 'user/%/weather':
164 $output = '<p>';
165 $output .= t('You can add, edit, and delete locations from your custom weather block.');
166 $output .= "\n";
167 $output .= t('Please note that the block will not be shown until you configure at least one location.');
168 $output .= '</p>';
169 break;
170 }
171
172 return $output;
173 }
174
175
176
177 /*********************************************************************
178 * General Drupal hooks for maintenance tasks
179 ********************************************************************/
180
181
182
183 /**
184 * Implementation of hook_cron().
185 *
186 * If the site uses caching for anonymous users, the cached weather
187 * blocks are not updated until the page cache is flushed.
188 * We rely on cron to perform necessary updates (and flushing the cache)
189 * for anonymous users. In order to not clear the cache for user-defined
190 * weather blocks (which are not shown to anonymous users), we only
191 * check for system weather blocks.
192 */
193 function weather_cron() {
194 if (variable_get('weather_use_cron', FALSE)) {
195 $sql = "SELECT * FROM {weather_metar} LEFT JOIN {weather_config}
196 ON {weather_metar}.icao={weather_config}.icao
197 WHERE {weather_config}.uid < 0 ORDER BY next_update_on ASC";
198 $result = db_query($sql);
199 $row = db_fetch_array($result);
200 if (isset($row['next_update_on'])) {
201 if ($row['next_update_on'] <= time()) {
202 cache_clear_all();
203 }
204 }
205 }
206 }
207
208
209
210 /*********************************************************************
211 * Drupal hooks for the block content
212 ********************************************************************/
213
214
215
216 /**
217 * Generate HTML for the weather block
218 * @param op operation from the URL
219 * @param delta offset
220 * @returns block HTML
221 */
222 function weather_block($op='list', $delta=0) {
223 global $user;
224
225 if ($op == 'list') {
226 $block[0]['info'] = t('Weather: custom user');
227 $block[1]['info'] = t('Weather: location of nodes (requires Location or Node Map module)');
228 $current_blocks = _weather_get_blocks_in_use();
229 if (!empty($current_blocks)) {
230 foreach ($current_blocks as $block_id) {
231 // $block_id is at least 1, so make sure the delta is at least 2
232 $block[SYSTEM_BLOCK_DELTA_START + $block_id - 1]['info'] =
233 t('Weather: system-wide !number', array('!number' => $block_id));
234 }
235 }
236 return $block;
237 }
238 else if ($op == 'view') {
239 if ($delta == 0 and weather_custom_block_access()) {
240 // Show the user's custom weather block, if there is already
241 // a location configured. Otherwise, do not show the block.
242 $configs_in_use = _weather_get_configs_in_use($user->uid);
243 if (count($configs_in_use) == 0) {
244 return;
245 }
246 $block['subject'] = t('Current weather');
247 $block['content'] = '';
248 foreach ($configs_in_use as $index) {
249 $config = _weather_get_config($user->uid, $index['cid']);
250 $metar = weather_get_metar($config['icao']);
251 $block['content'] .= theme('weather_theming', $config, $metar);
252 }
253 return $block;
254 }
255 else if ($delta == 1 and user_access('access content')) {
256 // show the node location weather block
257 if (arg(0) == 'node' and is_numeric(arg(1))) {
258 $node = node_load(arg(1));
259 $block['content'] = '';
260 // This checks the location module
261 if (isset($node->locations)) {
262 // Iterate through all available locations and check
263 // for lat/long information. If there is no information,
264 // the location module return 0.0/0.0 instead of NULL values
265 foreach ($node->locations as $location) {
266 if (($location['latitude'] != 0) or ($location['longitude'] != 0)) {
267 $nearest_station = weather_get_icao_from_lat_lon(
268 $location['latitude'], $location['longitude']);
269 $config = _weather_get_config(0, 1);
270 $config = array_merge($config, $nearest_station);
271 $config['real_name'] = $config['name'];
272 $metar = weather_get_metar($config['icao']);
273 $block['content'] .= theme('weather_theming', $config, $metar);
274 }
275 }
276 }
277 if (isset($node->nodemap_latitude_field)
278 and isset($node->nodemap_longitude_field)
279 and ($node->nodemap_latitude_field != 0 or $node->nodemap_longitude_field != 0)) {
280 $nearest_station = weather_get_icao_from_lat_lon(
281 $node->nodemap_latitude_field, $node->nodemap_longitude_field);
282 $config = _weather_get_config(0, 1);
283 $config = array_merge($config, $nearest_station);
284 $config['real_name'] = $config['name'];
285 $metar = weather_get_metar($config['icao']);
286 $block['content'] .= theme('weather_theming', $config, $metar);
287 }
288 // Do not show block if no lat/long information has been found
289 if ($block['content'] != '') {
290 $block['subject'] = t('Current weather nearby');
291 return $block;
292 }
293 }
294 }
295 else if ($delta >= SYSTEM_BLOCK_DELTA_START and user_access('access content')) {
296 // show a system-wide weather block
297 $system_block_id = SYSTEM_BLOCK_DELTA_START - $delta - 1;
298 $block['subject'] = t('Current weather');
299 $block['content'] = '';
300 $configs_in_use = _weather_get_configs_in_use($system_block_id);
301 if (count($configs_in_use) == 0) {
302 $configs_in_use[] = array('cid' => 1);
303 }
304 foreach ($configs_in_use as $index) {
305 $config = _weather_get_config($system_block_id, $index['cid']);
306 $metar = weather_get_metar($config['icao']);
307 $block['content'] .= theme('weather_theming', $config, $metar);
308 }
309 return $block;
310 }
311 }
312 }
313
314
315
316 /**
317 * Check whether the user has access to their own custom weather block
318 */
319 function weather_custom_block_access($uid=NULL) {
320 global $user;
321 // If $uid is not set, just check for the access permission
322 if (is_null($uid) || $user->uid == $uid) {
323 return user_access('administer custom weather block');
324 }
325 return FALSE;
326 }
327
328
329
330 /**
331 * Implementation of hook_theme().
332 */
333 function weather_theme() {
334 return array(
335 // Custom theme function for preprocessing variables
336 'weather_theming' => array(
337 'arguments' => array('config' => NULL, 'metar' => NULL),
338 ),
339 // Default block layout
340 'weather' => array(
341 'template' => 'weather',
342 'arguments' => array('weather' => NULL),
343 ),
344 // Compact block layout
345 'weather_compact' => array(
346 'template' => 'weather_compact',
347 'arguments' => array('weather' => NULL),
348 ),
349 );
350 }
351
352
353
354 /**
355 * Custom theme function for preprocessing the weather block output
356 */
357 function theme_weather_theming($config, $metar) {
358 // Set up variables which might be needed in the templates
359 $weather['real_name'] = check_plain($config['real_name']);
360 $weather['condition'] = _weather_format_condition($metar);
361 $weather['image'] = _weather_get_image($metar);
362 if (isset($metar['temperature']) and $config['units']['temperature'] != 'dont-display') {
363 $weather = array_merge($weather, _weather_format_temperature(
364 $metar['temperature'], $metar['wind'], $config['units'], $config['settings']
365 ));
366 }
367 if (isset($metar['wind']) and $config['units']['windspeed'] != 'dont-display') {
368 $weather['wind'] = _weather_format_wind($metar['wind'], $config['units'],
369 $config['settings']);
370 }
371 if (isset($metar['pressure']) and $config['units']['pressure'] != 'dont-display') {
372 $weather['pressure'] = _weather_format_pressure($metar['pressure'], $config['units']);
373 }
374 if (isset($metar['temperature']) and isset($metar['dewpoint'])
375 and $config['units']['humidity'] != 'dont-display') {
376 $weather['rel_humidity'] = _weather_format_relative_humidity($metar['temperature'], $metar['dewpoint']);
377 }
378 if (isset($metar['visibility']['kilometers']) and $config['units']['visibility'] != 'dont-display') {
379 $weather['visibility'] = _weather_format_visibility($metar['visibility'], $config['units']);
380 }
381 if ($config['settings']['show_sunrise_sunset']) {
382 // Check if there is a sunrise or sunset
383 if ($metar['daytime']['no_sunrise']) {
384 $weather['sunrise'] = t('No sunrise today');
385 }
386 else if ($metar['daytime']['no_sunset']) {
387 $weather['sunset'] = t('No sunset today');
388 }
389 else {
390 // Set up timezone with a sensible default value
391 $timezone = $config['settings']['sunrise_sunset_timezone'];
392 // If the timezone is numeric, just use that value
393 if (!is_numeric($timezone)) {
394 if ($timezone == 'drupal') {
395 // Use Drupal's default timezone or user's timezone
396 $timezone = NULL;
397 }
398 else {
399 // Fall back to using GMT
400 $timezone = 0;
401 }
402 }
403 // Try to extract a time format from the system wide date format
404 $date_format_short = variable_get('date_format_short', 'm/d/Y - H:i');
405 preg_match("/[GgHh].*?i(.*?[Aa])?/", $date_format_short, $matches);
406 if (isset($matches[0])) {
407 $format = $matches[0];
408 }
409 else {
410 $format = 'G:i';
411 }
412 // If the selected timezone is "drupal", just show the time.
413 // Otherwise, we append either "GMT" or the current GMT offset.
414 if (is_null($timezone)) {
415 // This is Drupal's default timezone, so do nothing.
416 }
417 else if ($timezone == 0) {
418 // This is GMT
419 $format .= ' T';
420 }
421 else {
422 // Append the GMT offset
423 $format .= ' O';
424 }
425 $text = format_date($metar['daytime']['sunrise_on'], 'custom', $format, $timezone);
426 $weather['sunrise'] = t('Sunrise: !sunrise', array('!sunrise' => $text));
427 $text = format_date($metar['daytime']['sunset_on'], 'custom', $format, $timezone);
428 $weather['sunset'] = t('Sunset: !sunset', array('!sunset' => $text));
429 }
430 }
431 if (isset($metar['#raw']) and $config['settings']['show_unconverted_metar']) {
432 $weather['metar'] = $metar['#raw'];
433 }
434 // If this is displayed as location block, show information about
435 // which METAR station has been used for weather data
436 if (isset($config['distance'])) {
437 $weather['location'] = _weather_format_closest_station($config['distance'],
438 $config['units'], $config['settings']);
439 }
440 if (isset($metar['reported_on'])) {
441 $weather['reported_on'] = format_date($metar['reported_on']);
442 }
443
444 // Use compact block, if desired
445 if ($config['settings']['show_compact_block']) {
446 return theme('weather_compact', $weather);
447 }
448 else {
449 return theme('weather', $weather);
450 }
451 }
452
453
454
455 function _weather_get_image($metar) {
456 // is there any data available?
457 if (!isset($metar['condition_text'])) {
458 $name = 'nodata';
459 }
460 else {
461 // handle special case: NSC, we just use few for the display
462 if ($metar['condition_text'] == 'no-significant-clouds') {
463 $metar['condition_text'] = 'few';
464 }
465 // calculate the sunrise and sunset times for day/night images
466 $name = $metar['daytime']['condition'] .'-'. $metar['condition_text'];
467
468 // Use fog image, if needed
469 if (isset($metar['phenomena']['#mist'])
470 or isset($metar['phenomena']['fog'])
471 or isset($metar['phenomena']['#smoke'])) {
472 $name .= '-fog';
473 }
474
475 // handle rain images
476 if (isset($metar['phenomena']['rain'])) {
477 $rain = $metar['phenomena']['rain'];
478 }
479 else if (isset($metar['phenomena']['drizzle'])) {
480 $rain = $metar['phenomena']['drizzle'];
481 }
482 // handle snow images
483 else if (isset($metar['phenomena']['snow'])) {
484 $snow = $metar['phenomena']['snow'];
485 }
486 if (isset($rain)) {
487 if (isset($rain['#light'])) {
488 $name .= '-light-rain';
489 }
490 else if (isset($rain['#heavy'])) {
491 $name .= '-heavy-rain';
492 }
493 else {
494 $name .= '-moderate-rain';
495 }
496 }
497 if (isset($snow)) {
498 if (isset($snow['#light'])) {
499 $name .= '-light-snow';
500 }
501 else if (isset($snow['#heavy'])) {
502 $name .= '-heavy-snow';
503 }
504 else {
505 $name .= '-moderate-snow';
506 }
507 }
508 }
509
510 // set up final return array
511 $image['filename'] = base_path() . drupal_get_path('module', 'weather') .'/images/'. $name .'.png';
512 $size = getimagesize(drupal_get_path('module', 'weather') .'/images/'. $name .'.png');
513 $image['size'] = $size[3];
514 return $image;
515 }
516
517
518
519 /*********************************************************************
520 * Internal functions for custom weather blocks
521 ********************************************************************/
522
523
524
525 /**
526 * Show an overview of configured locations
527 */
528 function weather_user_main_page($uid) {
529 $header = array(
530 t('Real name'),
531 t('Weight'),
532 array('data' => t('Operations'), 'colspan' => 2),
533 );
534
535 $path = 'user/'. $uid .'/weather/';
536 $rows = array();
537 $sql = "SELECT * FROM {weather_config}
538 WHERE uid=%d ORDER BY weight ASC, real_name ASC";
539 $result = db_query($sql, $uid);
540 while ($row = db_fetch_array($result)) {
541 $rows[] = array(
542 $row['real_name'],
543 $row['weight'],
544 l(t('edit'), $path .'edit/'. $row['cid']),
545 l(t('delete'), $path .'delete/'. $row['cid']),
546 );
547 }
548
549 if (count($rows) == 0) {
550 $rows[] = array(
551 array(
552 'data' => '<em>'. t('There are currently no locations.') .'</em>',
553 'colspan' => 4
554 )
555 );
556 }
557
558 $output = theme('table', $header, $rows);
559 if (isset($form['pager']['#value'])) {
560 $output .= drupal_render($form['pager']);
561 }
562
563 $free_cid = _weather_get_free_config($uid);
564 $output .= '<p>'. l(t('Create new location'),
565 $path .'edit/'. $free_cid) .'</p>';
566
567 return $output;
568 }
569
570
571
572 /**
573 * Show an overview of configured locations and the default location
574 */
575 function weather_admin_main_page() {
576 $output = '';
577 $blocks = _weather_get_blocks_in_use();
578 $path = 'admin/settings/weather/';
579
580 if (!empty($blocks)) {
581 foreach ($blocks as $block_id) {
582 $header = array(
583 t('System-wide block !number', array('!number' => $block_id)),
584 t('Weight'),
585 array('data' => t('Operations'), 'colspan' => 2),
586 );
587
588 $rows = array();
589 $sql = "SELECT * FROM {weather_config}
590 WHERE uid=%d ORDER BY weight ASC, real_name ASC";
591 $result = db_query($sql, -$block_id);
592 while ($row = db_fetch_array($result)) {
593 $rows[] = array(
594 $row['real_name'],
595 $row['weight'],
596 l(t('edit'), $path .'edit/'. -$block_id .'/'. $row['cid']),
597 l(t('delete'), $path .'delete/'. -$block_id .'/'. $row['cid']),
598 );
599 }
600
601 $output .= theme('table', $header, $rows);
602 if (isset($form['pager']['#value'])) {
603 $output .= drupal_render($form['pager']);
604 }
605
606 $free_cid = _weather_get_free_config(-$block_id);
607 $output .= '<p>'. l(t('Create new location in block !number',
608 array('!number' => $block_id)),
609 $path .'edit/'. -$block_id .'/'. $free_cid) .'</p>';
610 }
611 }
612
613 // Allow creation of another system-wide block
614 $free_block_id = _weather_get_free_block_id();
615 $output .= '<p>'. l(t('Create new system-wide block'),
616 $path .'edit/'. $free_block_id .'/1') .'</p>';
617
618 // Add the default location
619 $output .= '<p>'. l(t('Configure the default location'),
620 $path .'default') .'</p>';
621 $output .= drupal_get_form('weather_main_page_form');
622 return $output;
623 }
624
625
626
627 /**
628 * Construct a form for general settings of the Weather module
629 */
630 function weather_main_page_form() {
631 $form['use_cron'] = array(
632 '#type' => 'checkbox',
633 '#title' => t('Use cron to clear the cache once per hour'),
634 '#description' => t('If you use Drupal\'s cache, the system weather blocks will not be updated for anonymous users unless the cache is cleared. This happens e.g. when new nodes are created. If you want the system weather blocks to be updated when new weather data is available, you can clear the cache once per hour. Please note that this might slow down your site.'),
635 '#default_value' => variable_get('weather_use_cron', FALSE),
636 );
637 $form['submit'] = array(
638 '#type' => 'submit',
639 '#value' => t('Save configuration'),
640 );
641
642 return $form;
643 }
644
645
646
647 /**
648 * Handle the submission for general settings of the Weather module
649 */
650 function weather_main_page_form_submit($form, &$form_state) {
651 variable_set('weather_use_cron', $form_state['values']['use_cron']);
652 drupal_set_message(t('The configuration has been saved.'));
653 }
654
655
656
657 /**
658 * Show a configuration page for a custom weather block
659 */
660 function weather_custom_block($uid, $cid=0) {
661 // Include a nice Javascript which makes the settings easier
662 drupal_add_js(drupal_get_path('module', 'weather') .'/helper.js');
663
664 // if there's no configuration id provided, we get the first in use or 1
665 if ($cid == 0) {
666 $cid = _weather_get_first_valid_config($uid);
667 }
668
669 // if the provided config id is not currently used, we want the lowest
670 // free configuration id. This avoid cases like 56271 for config ids
671 $config_in_use = _weather_get_configs_in_use($uid);
672 $cid_found = FALSE;
673 foreach ($config_in_use as $index) {
674 if ($index['cid'] == $cid) {
675 $cid_found = TRUE;
676 break;
677 }
678 }
679 if (!$cid_found) {
680 $cid = _weather_get_free_config($uid);
681 }
682 // get the previously determined configuration
683 $config = _weather_get_config($uid, $cid);
684
685 $config['country'] = weather_get_country_from_icao($config['icao']);
686 $config['countries'] = weather_get_countries();
687 $config['places'] = weather_get_places($config['country']);
688
689 $output = drupal_get_form('weather_custom_block_form', $uid, $cid, $config);
690
691 return $output;
692 }
693
694
695
696 /**
697 * Return a new place selection box based on the country selection
698 */
699 function weather_js() {
700 // Get the current selected country for the new places
701 $form_state = array('values' => $_POST);
702 $new_places = weather_get_places($form_state['values']['country']);
703
704 // Get form from cache and store modified place selection
705 $form = form_get_cache($_POST['form_build_id'], $form_state);
706 $form['place'] = array(
707 '#type' => 'select',
708 '#title' => t('Place'),
709 '#description' => t('Select a place in that country for the weather display.'),
710 '#options' => $new_places,
711 );
712 form_set_cache($_POST['form_build_id'], $form, $form_state);
713 $form += array(
714 '#post' => $_POST,
715 '#programmed' => FALSE,
716 );
717
718 // Rebuild the form.
719 $form_state = array('submitted' => FALSE);
720 $form = form_builder('weather_custom_block_form', $form, $form_state);
721
722 // Render the new output.
723 $output = theme('status_messages') . drupal_render($form['place']);
724
725 // Don't call drupal_json(). ahah.js uses an iframe and
726 // the header output by drupal_json() causes problems in some browsers.
727 print drupal_to_js(array('status' => TRUE, 'data' => $output));
728 exit;
729 }
730
731
732
733 /**
734 * Construct the configuration form for a weather block
735 */
736 function weather_custom_block_form($dummy, $uid, $cid, $config) {
737 // set up a selection box with all countries
738 $form['country'] = array(
739 '#type' => 'select',
740 '#title' => t('Country'),
741 '#description' => t('Select a country to narrow down your search.'),
742 '#default_value' => $config['country'],
743 '#options' => drupal_map_assoc($config['countries']),
744 '#ahah' => array(
745 'path' => 'weather/js',
746 'wrapper' => 'edit-place-wrapper',
747 ),
748 );
749 // set up a selection box with all place names of the selected country
750 $form['place'] = array(
751 '#type' => 'select',
752 '#title' => t('Place'),
753 '#description' => t('Select a place in that country for the weather display.'),
754 '#default_value' => $config['icao'],
755 '#options' => $config['places'],
756 );
757 $form['icao'] = array(
758 '#type' => 'textfield',
759 '#title' => t('ICAO code'),
760 '#default_value' => $config['icao'],
761 '#description' => t('Enter the 4-letter ICAO code of the weather station. If you first need to look up the code, you can use !url_1 or !url_2. Please note that not all stations listed at those URLs are providing weather data and thus may not be supported by this module.',
762 array(
763 '!url_1' => l('airlinecodes.co.uk', 'http://www.airlinecodes.co.uk/aptcodesearch.asp'),
764 '!url_2' => l('notams.jcs.mil', 'https://www.notams.jcs.mil/common/icao/index.html')
765 )
766 ),
767 '#required' => true,
768 '#size' => '5',
769 );
770 $form['real_name'] = array(
771 '#type' => 'textfield',
772 '#title' => t('Real name for the selected place'),
773 '#default_value' => $config['real_name'],
774 '#description' => t('You may enter another name for the place selected above.'),
775 '#required' => true,
776 '#size' => '30'
777 );
778 $form['units'] = array(
779 '#type' => 'fieldset',
780 '#title' => t('Display units'),
781 '#description' => t('You can specify which units should be used for displaying the data.'),
782 '#collapsible' => TRUE,
783 '#collapsed' => FALSE,
784 '#tree' => TRUE,
785 );
786 $form['units']['temperature'] = array(
787 '#type' => 'select',
788 '#title' => t('Temperature'),
789 '#default_value' => $config['units']['temperature'],
790 '#options' => array('celsius' => t('Celsius'), 'fahrenheit' => t('Fahrenheit'),
791 'celsiusfahrenheit' => t('Celsius / Fahrenheit'),
792 'fahrenheitcelsius' => t('Fahrenheit / Celsius'),
793 'dont-display' => t('Don\'t display')),
794 );
795 $form['units']['windspeed'] = array(
796 '#type' => 'select',
797 '#title' => t('Wind speed'),
798 '#default_value' => $config['units']['windspeed'],
799 '#options' => array('kmh' => t('km/h'), 'mph' => t('mph'), 'knots' => t('Knots'),
800 'mps' => t('meter/s'), 'beaufort' => t('Beaufort'),
801 'dont-display' => t('Don\'t display')),
802 );
803 $form['units']['pressure'] = array(
804 '#type' => 'select',
805 '#title' => t('Pressure'),
806 '#default_value' => $config['units']['pressure'],
807 '#options' => array('hpa' => t('hPa'), 'kpa' => t('kPa'), 'inhg' => t('inHg'), 'mmhg' => t('mmHg'),
808 'dont-display' => t('Don\'t display')),
809 );
810 $form['units']['humidity'] = array(
811 '#type' => 'select',
812 '#title' => t('Rel. Humidity'),
813 '#default_value' => $config['units']['humidity'],
814 '#options' => array('display' => t('Display'), 'dont-display' => t('Don\'t display')),
815 );
816 $form['units']['visibility'] = array(
817 '#type' => 'select',
818 '#title' => t('Visibility'),
819 '#default_value' => $config['units']['visibility'],
820 '#options' => array('kilometers' => t('kilometers'), 'miles' => t('UK miles'),
821 'dont-display' => t('Don\'t display')),
822 );
823 $form['settings'] = array(
824 '#type' => 'fieldset',
825 '#title' => t('Display settings'),
826 '#description' => t('You can customize the display of the block.'),
827 '#collapsible' => TRUE,
828 '#collapsed' => TRUE,
829 '#tree' => TRUE,
830 );
831 $form['settings']['show_windchill'] = array(
832 '#type' => 'checkbox',
833 '#title' => t('Show windchill temperature'),
834 '#default_value' => $config['settings']['show_windchill'],
835 '#description' => t('Calculates the temperature resulting from windchill. This is how the temperature <q>feels like</q>.'),
836 );
837 $form['settings']['show_unconverted_metar'] = array(
838 '#type' => 'checkbox',
839 '#title' => t('Show unconverted METAR data'),
840 '#default_value' => $config['settings']['show_unconverted_metar'],
841 '#description' => t('Displays the original data of the METAR report.'),
842 );
843 $form['settings']['show_abbreviated_directions'] = array(
844 '#type' => 'checkbox',
845 '#title' => t('Show abbreviated wind directions'),
846 '#default_value' => $config['settings']['show_abbreviated_directions'],
847 '#description' => t('Displays abbreviated wind directions like N, SE, or W instead of North, Southeast, or West.'),
848 );
849 $form['settings']['show_directions_degree'] = array(
850 '#type' => 'checkbox',
851 '#title' => t('Show degrees of wind directions'),
852 '#default_value' => $config['settings']['show_directions_degree'],
853 '#description' => t('Displays the degrees of wind directions, e.g. North (20°).'),
854 );
855 $form['settings']['show_sunrise_sunset'] = array(
856 '#type' => 'checkbox',
857 '#title' => t('Show time of sunrise and sunset'),
858 '#default_value' => $config['settings']['show_sunrise_sunset'],
859 '#description' => t('Displays the time of sunrise and sunset.'),
860 );
861 // Construct timezones
862 $timezones = array('gmt' => t('GMT'), 'drupal' => t('Drupal'));
863 $system_timezones = _system_zonelist();
864 foreach ($system_timezones as $seconds_offset => $date) {
865 $timezones[$seconds_offset] = sprintf("%+03d:%02d",
866 $seconds_offset / 3600,
867 abs($seconds_offset % 3600) / 60);
868 }
869 $form['settings']['sunrise_sunset_timezone'] = array(
870 '#type' => 'select',
871 '#title' => t('Timezone for sunrise and sunset'),
872 '#default_value' => $config['settings']['sunrise_sunset_timezone'],
873 '#description' => t('Choose either Greenwich Mean Time (GMT), Drupal\'s standard timezone as set in the configuration, or a custom timezone.'),
874 '#options' => $timezones,
875 );
876 $form['settings']['show_compact_block'] = array(
877 '#type' => 'checkbox',
878 '#title' => t('Show compact block'),
879 '#default_value' => $config['settings']['show_compact_block'],
880 '#description' => t('Displays only the name, condition, and temperature of the weather station.'),
881 );
882 $form['weight'] = array(
883 '#type' => 'weight',
884 '#title' => t('Weight'),
885 '#default_value' => $config['weight'],
886 '#description' => t('Optional. In the block, the heavier locations will sink and the lighter locations will be positioned nearer the top. Locations with equal weights are sorted alphabetically.'),
887 );
888 $form['uid'] = array(
889 '#type' => 'value',
890 '#value' => $uid,
891 );
892 $form['cid'] = array(
893 '#type' => 'value',
894 '#value' => $cid,
895 );
896 $form['submit'] = array(
897 '#type' => 'submit',
898 '#value' => t('Save configuration'),
899 );
900
901 return $form;
902 }
903
904
905
906 /**
907 * Check the submission of the custom weather block
908 */
909 function weather_custom_block_form_validate($form, &$form_state) {
910 if (weather_get_country_from_icao($form_state['values']['icao']) == '') {
911 form_set_error('icao', t('The ICAO code is not supported by this module.'));
912 }
913 }
914
915
916
917 /**
918 * Handle the submission of the custom weather block
919 */
920 function weather_custom_block_form_submit($form, &$form_state) {
921 // delete the previous entry
922 $sql = "DELETE FROM {weather_config} WHERE uid=%d AND cid=%d";
923 db_query($sql, $form_state['values']['uid'], $form_state['values']['cid']);
924
925 // insert the new configuration values into the DB
926 $sql = "INSERT INTO {weather_config}
927 (uid, cid, icao, real_name, units, settings, weight)
928 VALUES(%d, %d, '%s', '%s', '%s', '%s', %d)";
929 db_query($sql,
930 $form_state['values']['uid'],
931 $form_state['values']['cid'],
932 strtoupper($form_state['values']['icao']),
933 $form_state['values']['real_name'],
934 serialize($form_state['values']['units']),
935 serialize($form_state['values']['settings']),
936 $form_state['values']['weight']
937 );
938
939 if ($form_state['values']['uid'] == 0) {
940 drupal_set_message(t('The default configuration has been saved.'));
941 }
942 else {
943 drupal_set_message(t('The location has been saved.'));
944 }
945
946 if ($form_state['values']['uid'] <= 0) {
947 // go back to the administration of the system weather block,
948 // if this is the default configuration or a system-wide block
949 $form_state['redirect'] = 'admin/settings/weather';
950 /** TODO
951 * Rehashing is not needed on every submission, only if the block
952 * is newly created. On the other hand, this happens only
953 * rarely and surely is not a performance bottleneck.
954 */
955 _block_rehash();
956 }
957 else {
958 $form_state['redirect'] = 'user/'. $form_state['values']['uid'] .'/weather';
959 }
960 }
961
962
963
964 /**
965 * Confirmation page before deleting a location
966 */
967 function weather_custom_block_delete_confirm($form, $uid, $cid) {
968 if ($uid < 0) {
969 $abort_path = 'admin/settings/weather';
970 }
971 else {
972 $abort_path = 'user/'. $uid .'/weather';
973 }
974
975 $sql = "SELECT * FROM {weather_config} WHERE uid=%d and cid=%d";
976 $result = db_query($sql, $uid, $cid);
977 $row = db_fetch_array($result);
978
979 $form = array();
980 $form['uid'] = array('#type' => 'hidden', '#value' => $uid);
981 $form['cid'] = array('#type' => 'hidden', '#value' => $cid);
982 return confirm_form($form,
983 t('Are you sure you want to delete the location %name?',
984 array('%name' => $row['real_name'])),
985 $abort_path,
986 t('This action cannot be undone.'),
987 t('Delete'),
988 t('Cancel')
989 );
990 }
991
992
993
994 /**
995 * Handle the deletion of a location
996 */
997 function weather_custom_block_delete_confirm_submit($form, &$form_state) {
998 // delete the entry
999 $sql = "DELETE FROM {weather_config} WHERE uid=%d AND cid=%d";
1000 db_query($sql, $form_state['values']['uid'], $form_state['values']['cid']);
1001
1002 drupal_set_message(t('The location has been deleted.'));
1003
1004 if ($form_state['values']['uid'] < 0) {
1005 // go back to the administration of system-wide weather blocks
1006 $form_state['redirect'] = 'admin/settings/weather';
1007 /** TODO
1008 * Rehashing is not needed on every submission, only if the block
1009 * is totally empty. On the other hand, this happens only
1010 * rarely and surely is not a performance bottleneck.
1011 */
1012 _block_rehash();
1013 }
1014 else {
1015 $form_state['redirect'] = 'user/'. $form_state['values']['uid'] .'/weather';
1016 }
1017 }
1018
1019
1020
1021 /**
1022 * Searches for the specified location, whether it is a place name or
1023 * an ICAO code. For example, weather/fuhlsbüttel will display the weather
1024 * for Hamburg-Fuhlsbüttel.
1025 *
1026 * @param string The argument passed in the URL that specifies the
1027 * location which should be searched for.
1028 */
1029 function weather_search_location($search = NULL) {
1030 if ($search == NULL) {
1031 // The user did not enter a search string in the URL, so just
1032 // display the search form.
1033 return drupal_get_form('weather_search_form');
1034 }
1035 else {
1036 $search = urldecode($search);
1037 // Do some sanity checks first
1038 if ((strlen($search) < 3) || (strlen($search) > 64)) {
1039 drupal_set_message(t('The string to search for must be between 3 and 64 characters.'), 'error');
1040 drupal_goto('weather');
1041 }
1042
1043 // Try to match the ICAO code
1044 if (strlen($search) == 4) {
1045 $sql = "SELECT icao, country, name FROM {weather_icao} WHERE icao = '%s'";
1046 $result = db_query($sql, strtoupper($search));
1047 if ($location = db_fetch_object($result)) {
1048 // Use the default configuration for display
1049 $config = _weather_get_config(0, 0);
1050 $config['icao'] = $location->icao;
1051 $config['real_name'] = $location->name;
1052 $metar = weather_get_metar($location->icao);
1053 $output = theme('weather_theming', $config, $metar);
1054 $output .= drupal_get_form('weather_search_form');
1055 return $output;
1056 }
1057 }
1058
1059 // Try to match on icao, name, or country
1060 $locations = array();
1061 $sql = "SELECT icao, country, name FROM {weather_icao}
1062 WHERE icao LIKE UPPER('%%%s%%')
1063 OR UPPER(country) LIKE UPPER('%%%s%%')
1064 OR UPPER(name) LIKE UPPER('%%%s%%')
1065 ORDER BY name ASC";
1066 $result = db_query($sql, $search, $search, $search);
1067 while ($location = db_fetch_object($result)) {
1068 $locations[] = $location;
1069 }
1070
1071 // If there are no results, notify user
1072 if (empty($locations)) {
1073 drupal_set_message(t('Your search did not return any results.'), 'error');
1074 drupal_goto('weather');
1075 }
1076 else {
1077 if (count($locations) == 1) {
1078 $location = $locations[0];
1079 // There's only one search result, so show the weather directly
1080 // using the default configuration for display
1081 $config = _weather_get_config(0, 0);
1082 $config['icao'] = $location->icao;
1083 $config['real_name'] = $location->name;
1084 $metar = weather_get_metar($location->icao);
1085 $output = theme('weather_theming', $config, $metar);
1086 $output .= drupal_get_form('weather_search_form');
1087 return $output;
1088 }
1089 else {
1090 // There is more than one result, so show all of them
1091 // to let the user decide
1092 $links = array();
1093 foreach ($locations as $location) {
1094 $links[] = l($location->name, 'weather/' . $location->icao);
1095 }
1096 $title = t('Search results for <q>@search</q>', array('@search' => $search));
1097 $output = theme('item_list', $links, $title);
1098 $output .= drupal_get_form('weather_search_form');
1099 return $output;
1100 }
1101 }
1102 }
1103 }
1104
1105
1106
1107 /**
1108 * Display a form for the user to search for weather locations.
1109 */
1110 function weather_search_form() {
1111 $form = array();
1112 $form['search'] = array(
1113 '#type' => 'textfield',
1114 '#title' => t('Search for a location'),
1115 '#description' => t('Type in an ICAO code, a name, or a country to search for weather conditions at that location.'),
1116 '#autocomplete_path' => 'weather/autocomplete',
1117 );
1118 $form['submit'] = array(
1119 '#type' => 'submit',
1120 '#value' => t('Search'),
1121 );
1122 return $form;
1123 }
1124
1125
1126
1127 /**
1128 * Validate the input from the weather search form
1129 */
1130 function weather_search_form_validate($form, &$form_state) {
1131 if ((strlen($form_state['values']['search']) < 3)
1132 || (strlen($form_state['values']['search']) > 64)) {
1133 form_set_error('search', t('The string to search for must be between 3 and 64 characters.'));
1134 }
1135 }
1136
1137
1138
1139 /**
1140 * Submission handler for the weather search form.
1141 *
1142 * Just redirect the user to the weather URL with the search term stuffed
1143 * on the end of it. We've been through validation but make sure the
1144 * search contains no dodgy characters here.
1145 */
1146 function weather_search_form_submit($form, &$form_state) {
1147 drupal_goto('weather/' . urlencode($form_state['values']['search']));
1148 }
1149
1150
1151
1152 /**
1153 * Given a partial string, search for a location or ICAO code matching that
1154 * string.
1155 *
1156 * @param string $input The partial text to search for.
1157 */