You are here

weather.module in Weather 5.6

Display <acronym title="METeorological Aerodrome Report">METAR</acronym> weather data from anywhere in the world

The module is compatible with Drupal 5.x

@author Tobias Quathamer

File

weather.module
View source
<?php

/*
 * Copyright (C) 2006-2008 Tobias Quathamer <t.quathamer@gmx.net>
 *
 * This file is part of the Drupal Weather module.
 *
 * It was inspired by the Weather module which was written in 2004 by
 * Gerard Ryan <gerardryan@canada.com>.
 *
 * Weather is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Weather is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Weather; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
 * @file
 * Display <acronym title="METeorological Aerodrome Report">METAR</acronym>
 * weather data from anywhere in the world
 *
 * The module is compatible with Drupal 5.x
 *
 * @author Tobias Quathamer
 */

// Define constants for database access. These are to be used as user ids.
define('WEATHER_SYSTEM_BLOCK', -1);
define('WEATHER_DEFAULT_BLOCK', -2);
define('WEATHER_LOCATION_BLOCK', -3);

// Include the parser for METAR data
require_once drupal_get_path('module', 'weather') . '/weather_parser.inc';

/*********************************************************************
 * General Drupal hooks for registering the module
 ********************************************************************/

/**
 * Implementation of hook_perm().
 */
function weather_perm() {
  return array(
    'administer custom weather block',
  );
}

/**
 * Implementation of hook_menu().
 */
function weather_menu($may_cache) {
  global $user;
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/weather',
      'title' => t('Weather'),
      'description' => t('Configure the system-wide weather block and the default configuration for new locations.'),
      'callback' => 'weather_main_page',
      'callback arguments' => array(
        WEATHER_SYSTEM_BLOCK,
      ),
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/weather/edit',
      'title' => t('Edit location'),
      'description' => t('Configure the system-wide weather block.'),
      'callback' => 'weather_custom_block',
      'callback arguments' => array(
        WEATHER_SYSTEM_BLOCK,
      ),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/settings/weather/delete',
      'title' => t('Delete location'),
      'description' => t('Delete a location from the system-wide weather block.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'weather_custom_block_delete_confirm',
        WEATHER_SYSTEM_BLOCK,
      ),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/settings/weather/default',
      'title' => t('Default configuration'),
      'description' => t('Setup the default configuration for new locations.'),
      'callback' => 'weather_custom_block',
      'callback arguments' => array(
        WEATHER_DEFAULT_BLOCK,
      ),
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/settings/weather/places',
      'title' => t('Place'),
      'callback' => 'weather_get_places_xml',
      'access' => user_access('administer site configuration'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'user/' . $user->uid . '/weather',
      'title' => t('My weather'),
      'description' => t('Configure your custom weather block.'),
      'callback' => 'weather_main_page',
      'callback arguments' => array(
        $user->uid,
      ),
      'access' => user_access('administer custom weather block'),
      'type' => MENU_LOCAL_TASK,
    );
    $items[] = array(
      'path' => 'user/' . $user->uid . '/weather/edit',
      'title' => t('Edit location'),
      'description' => t('Configure your custom weather block.'),
      'callback' => 'weather_custom_block',
      'callback arguments' => array(
        $user->uid,
      ),
      'access' => user_access('administer custom weather block'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'user/' . $user->uid . '/weather/delete',
      'title' => t('Delete location'),
      'description' => t('Delete a location from your custom weather block.'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array(
        'weather_custom_block_delete_confirm',
        $user->uid,
      ),
      'access' => user_access('administer custom weather block'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'user/' . $user->uid . '/weather/places',
      'title' => t('Place'),
      'callback' => 'weather_get_places_xml',
      'access' => user_access('administer custom weather block'),
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_help().
 */
function weather_help($section) {
  global $user;
  $output = '';
  switch ($section) {
    case 'admin/settings/weather':
      $output .= '<p>';
      $output .= t('You can add, edit, and delete locations from the system weather block. Moreover, you can specify default values for newly created locations.');
      $output .= '</p>';
      break;
    case 'user/' . $user->uid . '/weather':
      $output = '<p>';
      $output .= t('You can add, edit, and delete locations from your custom weather block.');
      $output .= "\n";
      $output .= t('Please note that the block will not be shown until you configure at least one location.');
      $output .= '</p>';
      break;
  }
  return $output;
}

/*********************************************************************
 * General Drupal hooks for maintenance tasks
 ********************************************************************/

/**
 * Implementation of hook_cron().
 * 
 * If the site uses caching for anonymous users, the cached weather
 * blocks are not updated until the page cache is flushed.
 * We rely on cron to perform necessary updates (and flushing the cache)
 * for anonymous users. In order to not clear the cache for user-defined
 * weather blocks (which are not shown to anonymous users), we only
 * check for the system weather block.
 */
function weather_cron() {
  if (variable_get('weather_use_cron', FALSE)) {
    $sql = "SELECT * FROM {weather} LEFT JOIN {weather_config}\n      ON {weather}.icao={weather_config}.icao\n      WHERE {weather_config}.uid=%d ORDER BY next_update_on ASC";
    $result = db_query($sql, WEATHER_SYSTEM_BLOCK);
    $row = db_fetch_array($result);
    if (isset($row['next_update_on'])) {
      if ($row['next_update_on'] <= time()) {
        cache_clear_all();
      }
    }
  }
}

/*********************************************************************
 * Drupal hooks for the block content
 ********************************************************************/

/**
 * Generate HTML for the weather block
 * @param op operation from the URL
 * @param delta offset
 * @returns block HTML 
 */
function weather_block($op = 'list', $delta = 0) {
  global $user;
  if ($op == 'list') {
    $block[0]['info'] = t('Weather: system-wide');
    $block[1]['info'] = t('Weather: custom user');
    $block[2]['info'] = t('Weather: location of nodes (requires Location module)');
    return $block;
  }
  else {
    if ($op == 'view') {
      if ($delta == 0 and user_access('access content')) {

        // show the system weather block
        $block['subject'] = t('Current weather');
        $block['content'] = '';
        $configs_in_use = _weather_get_configs_in_use(WEATHER_SYSTEM_BLOCK);
        if (count($configs_in_use) == 0) {
          $configs_in_use = array(
            'cid' => 1,
          );
        }
        foreach ($configs_in_use as $index) {
          $config = _weather_get_config(WEATHER_SYSTEM_BLOCK, $index['cid']);
          $metar = weather_get_metar($config['icao']);
          $block['content'] .= theme('weather', $config, $metar);
        }
        return $block;
      }
      else {
        if ($delta == 1 and user_access('administer custom weather block')) {

          // Show the user's custom weather block, if there is already
          // a location configured. Otherwise, do not show the block.
          $configs_in_use = _weather_get_configs_in_use($user->uid);
          if (count($configs_in_use) == 0) {
            return;
          }
          $block['subject'] = t('Current weather');
          $block['content'] = '';
          foreach ($configs_in_use as $index) {
            $config = _weather_get_config($user->uid, $index['cid']);
            $metar = weather_get_metar($config['icao']);
            $block['content'] .= theme('weather', $config, $metar);
          }
          return $block;
        }
        else {
          if ($delta == 2 and user_access('access content')) {

            // show the node location weather block
            if (arg(0) == 'node' and is_numeric(arg(1))) {
              $node = node_load(arg(1));
              if (isset($node->location['latitude']) and isset($node->location['longitude'])) {
                $block['subject'] = t('Current weather nearby');
                $block['content'] = '';
                foreach ($node->locations as $location) {
                  $nearest_station = weather_get_icao_from_lat_lon($location['latitude'], $location['longitude']);
                  $config = _weather_get_config(WEATHER_DEFAULT_BLOCK, 1);
                  $config = array_merge($config, $nearest_station);
                  $config['real_name'] = $config['name'];
                  $metar = weather_get_metar($config['icao']);
                  $block['content'] .= theme('weather', $config, $metar);
                }
                return $block;
              }
            }
          }
        }
      }
    }
  }
}

/**
 * Custom theme function for the weather block output
 */
function theme_weather($config, $metar) {
  if ($config['settings']['show_compact_block']) {
    $content = '<p>';
    if (isset($metar['temperature'])) {
      $text = t('<strong>@name:</strong> !condition, !temperature');
    }
    else {
      $text = t('<strong>@name:</strong> !condition');
    }
    $content .= t($text, array(
      '@name' => $config['real_name'],
      '!condition' => _weather_format_condition($metar),
      '!temperature' => _weather_format_temperature($metar['temperature'], $config['units']),
    ));
    $content .= '</p>';
    return $content;
  }
  $content = '<p><strong>' . check_plain($config['real_name']) . '</strong></p>';
  $image = _weather_get_image($metar);
  $condition = _weather_format_condition($metar);
  $content .= '<div style="text-align:center;"><img src="';
  $content .= $image['filename'];
  $content .= '" alt="' . $condition . '" ' . $image['size'];
  $content .= ' title="' . $condition . '" /></div>';
  $content .= '<ul><li>';
  $content .= $condition;
  $content .= '</li>';
  if (isset($metar['temperature'])) {
    $content .= '<li>';
    $content .= t('Temperature: !temperature', array(
      '!temperature' => _weather_format_temperature($metar['temperature'], $config['units']),
    ));
    $content .= '</li>';
  }
  if (isset($metar['wind'])) {
    $content .= '<li>';
    $content .= t('Wind: !wind', array(
      '!wind' => _weather_format_wind($metar['wind'], $config['units'], $config['settings']),
    ));
    $content .= '</li>';
  }
  if (isset($metar['pressure'])) {
    $content .= '<li>';
    $content .= t('Pressure: !pressure', array(
      '!pressure' => _weather_format_pressure($metar['pressure'], $config['units']),
    ));
    $content .= '</li>';
  }
  if (isset($metar['temperature']) and isset($metar['dewpoint'])) {
    $content .= '<li>';
    $content .= t('Rel. Humidity: !rel_humidity', array(
      '!rel_humidity' => _weather_format_relative_humidity($metar['temperature'], $metar['dewpoint']),
    ));
    $content .= '</li>';
  }
  if (isset($metar['visibility']['kilometers'])) {
    $content .= '<li>';
    $content .= t('Visibility: !visibility', array(
      '!visibility' => _weather_format_visibility($metar['visibility'], $config['units']),
    ));
    $content .= '</li>';
  }
  if ($config['settings']['show_sunrise_sunset']) {

    // Check if there is a sunrise or sunset
    if ($metar['daytime']['no_sunrise']) {
      $content .= '<li>';
      $content .= t('No sunrise today');
      $content .= '</li>';
    }
    else {
      if ($metar['daytime']['no_sunset']) {
        $content .= '<li>';
        $content .= t('No sunset today');
        $content .= '</li>';
      }
      else {
        $text = gmdate('G:i T', $metar['daytime']['sunrise_on']);
        $content .= '<li>';
        $content .= t('Sunrise: !sunrise', array(
          '!sunrise' => $text,
        ));
        $content .= '</li>';
        $text = gmdate('G:i T', $metar['daytime']['sunset_on']);
        $content .= '<li>';
        $content .= t('Sunset: !sunset', array(
          '!sunset' => $text,
        ));
        $content .= '</li>';
      }
    }
  }
  if (isset($metar['#raw']) and $config['settings']['show_unconverted_metar']) {
    $content .= '<li>';
    $content .= t('METAR data: !metar', array(
      '!metar' => '<pre>' . wordwrap($metar['#raw'], 20) . '</pre>',
    ));
    $content .= '</li>';
  }
  $content .= '</ul>';

  // If this is displayed as location block, show information about
  // which METAR station has been used for weather data
  if (isset($config['distance'])) {
    $content .= '<small>';
    $content .= t('Location of this weather station:');
    $content .= '<br />' . _weather_format_closest_station($config['distance'], $config['units'], $config['settings']);
    $content .= '</small><br />';
  }
  if (isset($metar['reported_on'])) {
    $content .= '<small>';
    $content .= t("Reported on:");
    $content .= '<br />' . format_date($metar['reported_on']);
    $content .= '</small>';
  }
  return $content;
}
function _weather_get_image($metar) {

  // is there any data available?
  if (!isset($metar['condition_text'])) {
    $name = 'nodata';
  }
  else {

    // handle special case: NSC, we just use few for the display
    if ($metar['condition_text'] == 'no-significant-clouds') {
      $metar['condition_text'] = 'few';
    }

    // calculate the sunrise and sunset times for day/night images
    $name = $metar['daytime']['condition'] . '-' . $metar['condition_text'];

    // handle rain images
    if (isset($metar['phenomena']['rain'])) {
      $rain = $metar['phenomena']['rain'];
    }
    else {
      if (isset($metar['phenomena']['drizzle'])) {
        $rain = $metar['phenomena']['drizzle'];
      }
      else {
        if (isset($metar['phenomena']['snow'])) {
          $snow = $metar['phenomena']['snow'];
        }
      }
    }
    if (isset($rain)) {
      if (isset($rain['#light'])) {
        $name .= '-light-rain';
      }
      else {
        if (isset($rain['#heavy'])) {
          $name .= '-heavy-rain';
        }
        else {
          $name .= '-moderate-rain';
        }
      }
    }
    if (isset($snow)) {
      if (isset($snow['#light'])) {
        $name .= '-light-snow';
      }
      else {
        if (isset($snow['#heavy'])) {
          $name .= '-heavy-snow';
        }
        else {
          $name .= '-moderate-snow';
        }
      }
    }
  }

  // set up final return array
  $image['filename'] = base_path() . drupal_get_path('module', 'weather') . '/images/' . $name . '.png';
  $size = getimagesize(drupal_get_path('module', 'weather') . '/images/' . $name . '.png');
  $image['size'] = $size[3];
  return $image;
}

/*********************************************************************
 * Internal functions for custom weather blocks
 ********************************************************************/

/**
 * Show an overview of configured locations and the default location,
 * if on the admin pages
 */
function weather_main_page($uid) {
  $header = array(
    array(
      'data' => t('Real name'),
      'field' => 'real_name',
    ),
    t('Weight'),
    array(
      'data' => t('Operations'),
      'colspan' => 2,
    ),
  );
  if ($uid == WEATHER_SYSTEM_BLOCK) {
    $path = 'admin/settings/weather/';
  }
  else {
    $path = 'user/' . $uid . '/weather/';
  }
  $rows = array();
  $sort_order = 'weight ' . tablesort_get_sort($header) . ', ';
  $sql = "SELECT * FROM {weather_config}\n    WHERE uid=%d" . tablesort_sql($header, $sort_order);
  $result = db_query($sql, $uid);
  while ($row = db_fetch_array($result)) {
    $rows[] = array(
      $row['real_name'],
      $row['weight'],
      l(t('edit'), $path . 'edit/' . $row['cid']),
      l(t('delete'), $path . 'delete/' . $row['cid']),
    );
  }
  if (count($rows) == 0) {
    $rows[] = array(
      array(
        'data' => '<em>' . t('There are currently no locations.') . '</em>',
        'colspan' => 4,
      ),
    );
  }
  $output = theme('table', $header, $rows);
  if (isset($form['pager']['#value'])) {
    $output .= drupal_render($form['pager']);
  }
  $free_cid = _weather_get_free_config($uid);
  $output .= '<p>' . l(t('Create new location'), $path . 'edit/' . $free_cid) . '</p>';
  if ($uid == WEATHER_SYSTEM_BLOCK) {
    $output .= '<p>' . l(t('Configure the default location'), $path . 'default') . '</p>';
    $output .= drupal_get_form('weather_main_page_form');
  }
  return $output;
}

/**
 * Construct a form for general settings of the Weather module
 */
function weather_main_page_form() {
  $form['use_cron'] = array(
    '#type' => 'checkbox',
    '#title' => t('Use cron to clear the cache once per hour'),
    '#description' => t('If you use Drupal\'s cache, the system weather block 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 block 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.'),
    '#default_value' => variable_get('weather_use_cron', FALSE),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

/**
 * Handle the submission for general settings of the Weather module
 */
function weather_main_page_form_submit($form_id, $form_values) {
  variable_set('weather_use_cron', $form_values['use_cron']);
  drupal_set_message(t('The configuration has been saved.'));
}

/**
 * Show a configuration page for a custom weather block
 */
function weather_custom_block($uid, $cid = 0) {

  // Include the ICAO codes
  require_once drupal_get_path('module', 'weather') . '/icao_codes.inc';

  // Include a nice Javascript which makes the settings easier
  drupal_add_js(drupal_get_path('module', 'weather') . '/helper.js');

  // if there's no configuration id provided, we get the first in use or 1
  if ($cid == 0) {
    $cid = _weather_get_first_valid_config($uid);
  }

  // if the provided config id is not currently used, we want the lowest
  // free configuration id. This avoid cases like 56271 for config ids
  $config_in_use = _weather_get_configs_in_use($uid);
  $cid_found = FALSE;
  foreach ($config_in_use as $index) {
    if ($index['cid'] == $cid) {
      $cid_found = TRUE;
      break;
    }
  }
  if (!$cid_found) {
    $cid = _weather_get_free_config($uid);
  }

  // get the previously determined configuration
  $config = _weather_get_config($uid, $cid);
  $config['country'] = _weather_get_country_from_icao($config['icao']);
  $config['countries'] = _weather_get_countries();
  $config['places'] = _weather_get_places($config['country']);
  $output = drupal_get_form('weather_custom_block_form', $uid, $cid, $config);
  return $output;
}

/**
 * Construct the configuration form for a weather block
 */
function weather_custom_block_form($uid, $cid, $config) {

  // set up a selection box with all countries
  $form['country'] = array(
    '#prefix' => '<div id="weather-js-show-selectbox" style="display:none;">',
    '#type' => 'select',
    '#title' => t('Country'),
    '#description' => t('Select a country to narrow down your search.'),
    '#default_value' => $config['country'],
    '#options' => drupal_map_assoc($config['countries']),
  );

  // set up a selection box with all place names of the selected country
  $form['place'] = array(
    '#type' => 'select',
    '#title' => t('Place'),
    '#description' => t('Select a place in that country for the weather display.'),
    '#default_value' => $config['icao'],
    '#options' => $config['places'],
    '#suffix' => '</div>',
    // we need to skip the check of valid options, because they get
    // modified with AJAX after a new country has been selected.
    '#DANGEROUS_SKIP_CHECK' => true,
  );
  $form['icao'] = array(
    '#type' => 'textfield',
    '#title' => t('ICAO code'),
    '#default_value' => $config['icao'],
    '#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.', array(
      '!url_1' => l('airlinecodes.co.uk', 'http://www.airlinecodes.co.uk/aptcodesearch.asp'),
      '!url_2' => l('notams.jcs.mil', 'https://www.notams.jcs.mil/common/icao/index.html'),
    )),
    '#required' => true,
    '#size' => '5',
  );
  $form['real_name'] = array(
    '#type' => 'textfield',
    '#title' => t('Real name for the selected place'),
    '#default_value' => $config['real_name'],
    '#description' => t('You may enter another name for the place selected above.'),
    '#required' => true,
    '#size' => '30',
  );
  $form['units'] = array(
    '#type' => 'fieldset',
    '#title' => t('Display units'),
    '#description' => t('You can specify which units should be used for displaying the data.'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#tree' => TRUE,
  );
  $form['units']['temperature'] = array(
    '#type' => 'select',
    '#title' => t('Temperature'),
    '#default_value' => $config['units']['temperature'],
    '#options' => array(
      'celsius' => t('Celsius'),
      'fahrenheit' => t('Fahrenheit'),
    ),
  );
  $form['units']['windspeed'] = array(
    '#type' => 'select',
    '#title' => t('Wind speed'),
    '#default_value' => $config['units']['windspeed'],
    '#options' => array(
      'kmh' => t('km/h'),
      'mph' => t('mph'),
      'knots' => t('Knots'),
      'mps' => t('meter/s'),
      'beaufort' => t('Beaufort'),
    ),
  );
  $form['units']['pressure'] = array(
    '#type' => 'select',
    '#title' => t('Pressure'),
    '#default_value' => $config['units']['pressure'],
    '#options' => array(
      'hpa' => t('hPa'),
      'inhg' => t('inHg'),
      'mmhg' => t('mmHg'),
    ),
  );
  $form['units']['visibility'] = array(
    '#type' => 'select',
    '#title' => t('Visibility'),
    '#default_value' => $config['units']['visibility'],
    '#options' => array(
      'kilometers' => t('kilometers'),
      'miles' => t('UK miles'),
    ),
  );
  $form['settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Display settings'),
    '#description' => t('You can customize the display of the block.'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#tree' => TRUE,
  );
  $form['settings']['show_unconverted_metar'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show unconverted METAR data'),
    '#default_value' => $config['settings']['show_unconverted_metar'],
    '#description' => t('Displays the original data of the METAR report.'),
  );
  $form['settings']['show_abbreviated_directions'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show abbreviated wind directions'),
    '#default_value' => $config['settings']['show_abbreviated_directions'],
    '#description' => t('Displays abbreviated wind directions like N, SE, or W instead of North, Southeast, or West.'),
  );
  $form['settings']['show_directions_degree'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show degrees of wind directions'),
    '#default_value' => $config['settings']['show_directions_degree'],
    '#description' => t('Displays the degrees of wind directions, e.g. North (20°).'),
  );
  $form['settings']['show_sunrise_sunset'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show time of sunrise and sunset'),
    '#default_value' => $config['settings']['show_sunrise_sunset'],
    '#description' => t('Displays the time of sunrise and sunset as Greenwich Mean Time (GMT). Therefore, the sunset may be earlier than the sunrise.'),
  );
  $form['settings']['show_compact_block'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show compact block'),
    '#default_value' => $config['settings']['show_compact_block'],
    '#description' => t('Displays only the name, condition, and temperature of the weather station.'),
  );
  $form['weight'] = array(
    '#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $config['weight'],
    '#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.'),
  );
  $form['uid'] = array(
    '#type' => 'value',
    '#value' => $uid,
  );
  $form['cid'] = array(
    '#type' => 'value',
    '#value' => $cid,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save configuration'),
  );
  return $form;
}

/**
 * Check the submission of the custom weather block
 */
function weather_custom_block_form_validate($form_id, $form_values) {
  if (_weather_get_country_from_icao($form_values['icao']) == '') {
    form_set_error('icao', t('The ICAO code is not supported by this module.'));
  }
}

/**
 * Handle the submission of the custom weather block
 */
function weather_custom_block_form_submit($form_id, $form_values) {

  // delete the previous entry
  $sql = "DELETE FROM {weather_config} WHERE uid=%d AND cid=%d";
  db_query($sql, $form_values['uid'], $form_values['cid']);

  // insert the new configuration values into the DB
  $sql = "INSERT INTO {weather_config}\n    (uid, cid, icao, real_name, units, settings, weight)\n    VALUES(%d, %d, '%s', '%s', '%s', '%s', %d)";
  db_query($sql, $form_values['uid'], $form_values['cid'], strtoupper($form_values['icao']), $form_values['real_name'], serialize($form_values['units']), serialize($form_values['settings']), $form_values['weight']);
  if ($form_values['uid'] == WEATHER_DEFAULT_BLOCK) {
    drupal_set_message(t('The default configuration has been saved.'));
  }
  else {
    drupal_set_message(t('The location has been saved.'));
  }
  if ($form_values['uid'] == WEATHER_SYSTEM_BLOCK or $form_values['uid'] == WEATHER_DEFAULT_BLOCK) {

    // go back to the administration of the system weather block
    drupal_goto('admin/settings/weather');
  }
  else {
    drupal_goto('user/' . $form_values['uid'] . '/weather');
  }
}

/**
 * Confirmation page before deleting a location
 */
function weather_custom_block_delete_confirm($uid, $cid) {
  if ($uid == WEATHER_SYSTEM_BLOCK) {
    $abort_path = 'admin/settings/weather';
  }
  else {
    $abort_path = 'user/' . $uid . '/weather';
  }
  $sql = "SELECT * FROM {weather_config} WHERE uid=%d and cid=%d";
  $result = db_query($sql, $uid, $cid);
  $row = db_fetch_array($result);
  $form = array();
  $form['uid'] = array(
    '#type' => 'hidden',
    '#value' => $uid,
  );
  $form['cid'] = array(
    '#type' => 'hidden',
    '#value' => $cid,
  );
  return confirm_form($form, t('Are you sure you want to delete the location %name?', array(
    '%name' => $row['real_name'],
  )), $abort_path, t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}

/**
 * Handle the deletion of a location
 */
function weather_custom_block_delete_confirm_submit($form, $form_values) {

  // delete the entry
  $sql = "DELETE FROM {weather_config} WHERE uid=%d AND cid=%d";
  db_query($sql, $form_values['uid'], $form_values['cid']);
  drupal_set_message(t('The location has been deleted.'));
  if ($form_values['uid'] == WEATHER_SYSTEM_BLOCK) {

    // go back to the administration of the system weather block
    drupal_goto('admin/settings/weather');
  }
  else {
    drupal_goto('user/' . $form_values['uid'] . '/weather');
  }
}

/**
 * Return all places with their ICAO codes for a given country.
 * 
 * This function is used to generate an XML output of the places,
 * to be used from helper.js for updating the places selection
 * box after the country has been changed.
 */
function weather_get_places_xml($country = 'Germany') {

  // Include the ICAO codes
  require_once drupal_get_path('module', 'weather') . '/icao_codes.inc';
  $places = _weather_get_places($country);
  header('Content-type: text/xml; charset=utf-8');
  echo "<?xml version=\"1.0\"?>\n";
  echo "<places>\n";
  foreach ($places as $icao => $name) {
    echo "<place icao=\"{$icao}\">" . htmlspecialchars($name) . "</place>\n";
  }
  echo "</places>\n";
}

/**
 * Return the configuration for the given user id.
 * 
 * If there is no configuration yet, get the default
 * configuration instead.
 */
function _weather_get_config($uid, $cid) {
  $sql = "SELECT * FROM {weather_config} WHERE uid=%d AND cid=%d";
  $result = db_query($sql, $uid, $cid);
  if (db_num_rows($result) != 1) {

    // There was no configuration found. See if there is a custom
    // default configuration.
    $sql = "SELECT * FROM {weather_config} WHERE uid=%d AND cid=%d";
    $result = db_query($sql, WEATHER_DEFAULT_BLOCK, 1);
    if (db_num_rows($result) != 1) {

      // There is no custom default configuration, provide the
      // module's default
      $config['icao'] = 'EDDH';
      $config['real_name'] = 'Hamburg-Fuhlsbüttel';
      $config['units'] = array(
        'temperature' => 'celsius',
        'windspeed' => 'kmh',
        'pressure' => 'hpa',
        'visibility' => 'kilometers',
      );
      $config['settings'] = array(
        'show_unconverted_metar' => FALSE,
        'show_abbreviated_directions' => FALSE,
        'show_directions_degree' => FALSE,
        'show_sunrise_sunset' => FALSE,
        'show_compact_block' => FALSE,
      );
      $config['weight'] = 0;
    }
    else {

      // get the custom default configuration
      $config = db_fetch_array($result);
      $config['units'] = unserialize($config['units']);
      $config['settings'] = unserialize($config['settings']);
    }
  }
  else {

    // get the user's configuration
    $config = db_fetch_array($result);
    $config['units'] = unserialize($config['units']);
    $config['settings'] = unserialize($config['settings']);
  }
  return $config;
}

/**
 * Return the first valid configuration or 1 if there is none
 */
function _weather_get_first_valid_config($uid) {
  $sql = 'SELECT cid FROM {weather_config} WHERE uid=%d ORDER BY weight ASC, real_name ASC';
  return max(db_result(db_query($sql, $uid)), 1);
}

/**
 * Return the first unused configuration number
 */
function _weather_get_free_config($uid) {
  $result = db_query("SELECT * FROM {weather_config}\n    WHERE uid=%d\n    ORDER BY cid ASC", $uid);
  $free_config = 1;
  while ($config = db_fetch_array($result)) {
    if ($config['cid'] > $free_config) {
      break;
    }
    $free_config++;
  }
  return $free_config;
}

/**
 * Determine how many configurations exist for the given user id.
 */
function _weather_get_configs_in_use($uid) {
  $configs = array();
  $result = db_query("SELECT * FROM {weather_config}\n    WHERE uid=%d\n    ORDER BY weight ASC, real_name ASC", $uid);
  while ($row = db_fetch_array($result)) {
    $configs[] = array(
      'cid' => $row['cid'],
      'real_name' => $row['real_name'],
    );
  }
  return $configs;
}

/*********************************************************************
 * Internal functions for converting data
 ********************************************************************/

/**
 * Format the weather condition and phenomena (rain, drizzle, snow, ...)
 */
function _weather_format_condition($metar) {
  if (!isset($metar) or !isset($metar['condition_text'])) {
    return t('No data');
  }

  // sky conditions
  switch ($metar['condition_text']) {
    case 'clear':
      $result[] = t('Clear sky');
      break;
    case 'few':
      $result[] = t('Few clouds');
      break;
    case 'scattered':
      $result[] = t('Scattered clouds');
      break;
    case 'broken':
      $result[] = t('Broken clouds');
      break;
    case 'overcast':
      $result[] = t('Overcast');
      break;
    case 'no-significant-clouds':
      $result[] = t('No significant clouds');
      break;
  }

  // weather phenomena
  if (isset($metar['phenomena']['rain'])) {
    $rain = $metar['phenomena']['rain'];
    if (isset($rain['#light'])) {
      if (isset($rain['#showers'])) {
        $result[] = t('light rain showers');
      }
      else {
        if (isset($rain['#freezing'])) {
          $result[] = t('light freezing rain');
        }
        else {
          $result[] = t('light rain');
        }
      }
    }
    else {
      if (isset($rain['#heavy'])) {
        if (isset($rain['#showers'])) {
          $result[] = t('heavy rain showers');
        }
        else {
          if (isset($rain['#freezing'])) {
            $result[] = t('heavy freezing rain');
          }
          else {
            $result[] = t('heavy rain');
          }
        }
      }
      else {
        if (isset($rain['#showers'])) {
          $result[] = t('rain showers');
        }
        else {
          if (isset($rain['#freezing'])) {
            $result[] = t('freezing rain');
          }
          else {
            $result[] = t('rain');
          }
        }
      }
    }
  }
  else {
    if (isset($metar['phenomena']['snow'])) {
      $snow = $metar['phenomena']['snow'];
      if (isset($snow['#light'])) {
        if (isset($snow['#blowing'])) {
          $result[] = t('light blowing snow');
        }
        else {
          if (isset($snow['#low_drifting'])) {
            $result[] = t('light low drifting snow');
          }
          else {
            $result[] = t('light snow');
          }
        }
      }
      else {
        if (isset($snow['#heavy'])) {
          if (isset($snow['#blowing'])) {
            $result[] = t('heavy blowing snow');
          }
          else {
            if (isset($snow['#low_drifting'])) {
              $result[] = t('heavy low drifting snow');
            }
            else {
              $result[] = t('heavy snow');
            }
          }
        }
        else {
          if (isset($snow['#blowing'])) {
            $result[] = t('blowing snow');
          }
          else {
            if (isset($snow['#low_drifting'])) {
              $result[] = t('low drifting snow');
            }
            else {
              $result[] = t('snow');
            }
          }
        }
      }
    }
  }
  return join(", ", $result);
}

/**
 * Convert temperatures
 * 
 * @param int Temperature, always in degree celsius
 * @param string The unit to be returned (celsius, fahrenheit)
 * @return string Formatted representation, either in celsius or fahrenheit
 */
function _weather_format_temperature($temperature, $unit) {
  if ($unit['temperature'] == 'fahrenheit') {
    return t('!temperature °F', array(
      '!temperature' => $temperature['fahrenheit'],
    ));
  }
  else {

    // default to metric units
    return t('!temperature °C', array(
      '!temperature' => $temperature['celsius'],
    ));
  }
}

/**
 * Convert wind
 * 
 * @param int Wind
 * @param string The unit to be returned (km/h, knots, meter/s, mph)
 * @param array Settings, used for abbreviated wind directions
 * @return string Formatted representation
 */
function _weather_format_wind($wind, $unit, $settings) {

  // shortcut for a special case
  if ($wind['direction'] == 0 and $wind['speed_kmh'] == 0) {
    return t('Calm');
  }
  $wind['direction_text'] = weather_bearing_to_text($wind['direction']);
  $wind['direction_text_short'] = weather_bearing_to_text($wind['direction'], TRUE);
  if (isset($wind['variable_start'])) {
    $wind['variable_start_text'] = weather_bearing_to_text($wind['variable_start']);
    $wind['variable_start_text_short'] = weather_bearing_to_text($wind['variable_start'], TRUE);
  }
  if (isset($wind['variable_end'])) {
    $wind['variable_end_text'] = weather_bearing_to_text($wind['variable_end']);
    $wind['variable_end_text_short'] = weather_bearing_to_text($wind['variable_end'], TRUE);
  }

  // handle variable wind directions
  if ($wind['direction'] == 'VRB' or isset($wind['variable_start'])) {
    if (isset($wind['variable_start'])) {
      if ($settings['show_abbreviated_directions']) {
        if ($settings['show_directions_degree']) {
          $result[] = t('Variable from !direction_a (!degree_a°) to !direction_b (!degree_b°)', array(
            '!direction_a' => $wind['variable_start_text_short'],
            '!degree_a' => $wind['variable_start'],
            '!direction_b' => $wind['variable_end_text_short'],
            '!degree_b' => $wind['variable_end'],
          ));
        }
        else {
          $result[] = t('Variable from !direction_a to !direction_b', array(
            '!direction_a' => $wind['variable_start_text_short'],
            '!direction_b' => $wind['variable_end_text_short'],
          ));
        }
      }
      else {
        if ($settings['show_directions_degree']) {
          $result[] = t('Variable from !direction_a (!degree_a°) to !direction_b (!degree_b°)', array(
            '!direction_a' => $wind['variable_start_text'],
            '!degree_a' => $wind['variable_start'],
            '!direction_b' => $wind['variable_end_text'],
            '!degree_b' => $wind['variable_end'],
          ));
        }
        else {
          $result[] = t('Variable from !direction_a to !direction_b', array(
            '!direction_a' => $wind['variable_start_text'],
            '!direction_b' => $wind['variable_end_text'],
          ));
        }
      }
    }
    else {
      $result[] = t('Variable');
    }
  }
  else {

    // no variable wind direction, but an exact one
    if ($settings['show_abbreviated_directions']) {
      if ($settings['show_directions_degree']) {
        $result[] = t('!direction (!degree°)', array(
          '!direction' => $wind['direction_text_short'],
          '!degree' => $wind['direction'],
        ));
      }
      else {
        $result[] = $wind['direction_text_short'];
      }
    }
    else {
      if ($settings['show_directions_degree']) {
        $result[] = t('!direction (!degree°)', array(
          '!direction' => $wind['direction_text'],
          '!degree' => $wind['direction'],
        ));
      }
      else {
        $result[] = $wind['direction_text'];
      }
    }
  }

  // set up the wind speed
  if ($unit['windspeed'] == 'mph') {
    $result[] = t('!speed mph', array(
      '!speed' => $wind['speed_mph'],
    ));
  }
  else {
    if ($unit['windspeed'] == 'knots') {
      $result[] = t('!speed knots', array(
        '!speed' => $wind['speed_knots'],
      ));
    }
    else {
      if ($unit['windspeed'] == 'mps') {
        $result[] = t('!speed meter/s', array(
          '!speed' => $wind['speed_mps'],
        ));
      }
      else {
        if ($unit['windspeed'] == 'beaufort') {
          $result[] = t('Beaufort !number', array(
            '!number' => $wind['speed_beaufort'],
          ));
        }
        else {

          // default to metric units
          $result[] = t('!speed km/h', array(
            '!speed' => $wind['speed_kmh'],
          ));
        }
      }
    }
  }

  // set up gusts, if applicable
  if ($wind['gusts_kmh'] > 0) {
    if ($unit['windspeed'] == 'mph') {
      $result[] = t('gusts up to !speed mph', array(
        '!speed' => $wind['gusts_mph'],
      ));
    }
    else {
      if ($unit['windspeed'] == 'knots') {
        $result[] = t('gusts up to !speed knots', array(
          '!speed' => $wind['gusts_knots'],
        ));
      }
      else {
        if ($unit['windspeed'] == 'mps') {
          $result[] = t('gusts up to !speed meter/s', array(
            '!speed' => $wind['gusts_mps'],
          ));
        }
        else {
          if ($unit['windspeed'] == 'beaufort') {
            $result[] = t('gusts up to Beaufort !number', array(
              '!number' => $wind['gusts_beaufort'],
            ));
          }
          else {

            // default to metric units
            $result[] = t('gusts up to !speed km/h', array(
              '!speed' => $wind['gusts_kmh'],
            ));
          }
        }
      }
    }
  }
  return join(', ', $result);
}

/**
 * Convert pressure
 * 
 * @param int Pressure
 * @param string The unit to be returned (inHg, mmHg, hPa)
 * @return string Formatted representation
 */
function _weather_format_pressure($pressure, $unit) {
  if ($unit['pressure'] == 'inhg') {
    return t('!pressure inHg', array(
      '!pressure' => $pressure['inHg'],
    ));
  }
  else {
    if ($unit['pressure'] == 'mmhg') {
      return t('!pressure mmHg', array(
        '!pressure' => $pressure['mmHg'],
      ));
    }
    else {

      // default to metric units
      return t('!pressure hPa', array(
        '!pressure' => $pressure['hPa'],
      ));
    }
  }
}

/**
 * Calculate the relative humidity
 * 
 * @param float Temperature (must be Celsius)
 * @param float Dewpoint (must be Celsius)
 * @return string Formatted representation
 */
function _weather_format_relative_humidity($temperature, $dewpoint) {
  $e = 6.11 * pow(10, 7.5 * $dewpoint['celsius'] / (237.7 + $dewpoint['celsius']));
  $es = 6.11 * pow(10, 7.5 * $temperature['celsius'] / (237.7 + $temperature['celsius']));
  return t('!rel_humidity%', array(
    '!rel_humidity' => max(0, min(100, round(100 * ($e / $es), 0))),
  ));
}

/**
 * Convert the visibility
 */
function _weather_format_visibility($visibility, $unit) {
  if ($unit['visibility'] == 'miles') {
    return t('!visibility miles', array(
      '!visibility' => $visibility['miles'],
    ));
  }
  else {

    // default to metric units
    return t('!visibility kilometers', array(
      '!visibility' => $visibility['kilometers'],
    ));
  }
}

/**
 * Convert information about nearest METAR station in the location block
 */
function _weather_format_closest_station($distance, $unit, $settings) {
  while ($distance['direction'] < 0) {
    $distance['direction'] += 360;
  }
  while ($distance['direction'] >= 360) {
    $distance['direction'] -= 360;
  }
  if ($settings['show_abbreviated_directions']) {
    $direction = weather_bearing_to_text($distance['direction'], TRUE);
  }
  else {
    $direction = weather_bearing_to_text($distance['direction']);
  }
  if ($unit['visibility'] == 'miles') {
    if ($settings['show_directions_degree']) {
      return t('!distance miles !direction (!degree°)', array(
        '!distance' => $distance['miles'],
        '!direction' => $direction,
        '!degree' => $distance['direction'],
      ));
    }
    else {
      return t('!distance miles !direction', array(
        '!distance' => $distance['miles'],
        '!direction' => $direction,
      ));
    }
  }
  else {

    // default to metric units
    if ($settings['show_directions_degree']) {
      return t('!distance kilometers !direction (!degree°)', array(
        '!distance' => $distance['kilometers'],
        '!direction' => $direction,
        '!degree' => $distance['direction'],
      ));
    }
    else {
      return t('!distance kilometers !direction', array(
        '!distance' => $distance['kilometers'],
        '!direction' => $direction,
      ));
    }
  }
}

/**
 * Converts a compass bearing to a text direction (e.g. 0° North,
 * 86° East, ...)
 * 
 * @param int Compass bearing in degrees
 * @param boolean If true, return abbreviated directions (N, NNW)
 *                instead of full text (North, North-Northwest)
 *                Defaults to full text directions
 * @return string The translated text direction
 */
function weather_bearing_to_text($bearing, $abbreviated = FALSE) {

  // Ensure the bearing to be from 0° to 359°
  while ($bearing < 0) {
    $bearing += 360;
  }
  while ($bearing >= 360) {
    $bearing -= 360;
  }

  // Determine the sector. This works for 0° up to 348.75°
  // If the bearing was greater than 348.75°, perform a wrap (%16)
  $sector = floor(($bearing + 11.25) / 22.5) % 16;
  if (!$abbreviated) {
    $direction = array(
      t('North'),
      t('North-Northeast'),
      t('Northeast'),
      t('East-Northeast'),
      t('East'),
      t('East-Southeast'),
      t('Southeast'),
      t('South-Southeast'),
      t('South'),
      t('South-Southwest'),
      t('Southwest'),
      t('West-Southwest'),
      t('West'),
      t('West-Northwest'),
      t('Northwest'),
      t('North-Northwest'),
    );
  }
  else {
    $direction = array(
      t('N'),
      t('NNE'),
      t('NE'),
      t('ENE'),
      t('E'),
      t('ESE'),
      t('SE'),
      t('SSE'),
      t('S'),
      t('SSW'),
      t('SW'),
      t('WSW'),
      t('W'),
      t('WNW'),
      t('NW'),
      t('NNW'),
    );
  }
  return $direction[$sector];
}

/*********************************************************************
 * Internal function for retrieving data
 ********************************************************************/

/**
 * Given a latitude and longitude, return the details of the nearest known
 * weather station.
 *
 * This (pythagorean) proximity search here is inaccurate - it draws
 * a circle around the base and target point ASSUMING A FLAT MAP GRID.
 * The real maths (below) are probably too heavy for a DB query.
 * So we take the top 5 near guesses, and then do the real calculation
 * on just them. It's pretty extreme cases (like > 6 stations within
 * miles of each other at the pole where that estimate won't be good
 * enough.
 * 
 * @param float Latitude to be searched
 * @param float Longitude to be searched
 * @return array METAR station with two additional fields (distance and
 *               direction)
 */
function weather_get_icao_from_lat_lon($latitude, $longitude) {
  $sql = "SELECT *,\n    ((%d - latitude) * (%d - latitude) + (%d - longitude) * (%d - longitude))\n    AS square_dist_away\n    FROM {weather_icao} ORDER BY square_dist_away LIMIT 5";
  $result = db_query($sql, $latitude, $latitude, $longitude, $longitude);
  $nearest = 9999999;
  while ($row = db_fetch_array($result)) {
    $distance = earth_distance($longitude, $latitude, $row['longitude'], $row['latitude']);
    if ($distance <= $nearest) {
      $nearest = $distance;
      $row['distance']['kilometers'] = round($distance / 1000, 1);
      $row['distance']['miles'] = round($distance / 1609.344, 1);

      // Calculate direction
      $lat1 = deg2rad($latitude);
      $lat2 = deg2rad($row['latitude']);
      $dlon = deg2rad($row['longitude'] - $longitude);
      $y = sin($dlon) * cos($lat2);
      $x = cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($dlon);
      $row['distance']['direction'] = round(rad2deg(atan2($y, $x)));
      $station = $row;
    }
  }
  return $station;
}

/**
 * Fetches the latest METAR data from the database or internet
 */
function weather_get_metar($icao) {

  // see if there's a report in the database
  $icao = strtoupper($icao);
  $sql = "SELECT * FROM {weather} WHERE icao='%s'";
  $result = db_query($sql, $icao);
  $data = db_fetch_array($result);

  // if there is no report, initialize the array
  if (!isset($data['metar_raw']) or !isset($data['next_update_on'])) {
    $data['next_update_on'] = 0;
    $data['metar_raw'] = '';
  }

  // if the time has come, download again
  if ($data['next_update_on'] <= time()) {
    $data['metar_raw'] = '';
  }

  // fetch data from the internet
  if ($data['metar_raw'] == '') {
    $data['metar_raw'] = _weather_retrieve_data($icao);
    if ($data['metar_raw']) {
      $metar = weather_parse_metar($data['metar_raw']);
      weather_store_metar($metar);
    }
    else {

      // the internet retrieval has not been successful.
      // try again in 10 minutes
      $sql = "UPDATE {weather} SET next_update_on=%d WHERE icao='%s'";
      db_query($sql, time() + 10 * 60, $icao);
    }
  }
  else {
    $metar = weather_parse_metar($data['metar_raw']);
  }
  return $metar;
}

/**
 * Stores parsed METAR data in the database
 */
function weather_store_metar($metar) {

  // if there's already a record in the database with the same ICAO
  // overwrite it
  $sql = "DELETE FROM {weather} WHERE icao='%s'";
  db_query($sql, $metar['icao']);

  // insert the new data
  $sql = "INSERT INTO {weather}\n    (icao, next_update_on, metar_raw)\n    VALUES ('%s', %d, '%s')";

  // calculate the next scheduled update: normally, we use 62
  // minutes after the reported timestamp, to allow the data
  // to propagate to the server servers.
  $next_update_on = $metar['reported_on'] + 62 * 60;

  // However, if the current time is more than 62 minutes
  // over the reported timestamp, allow ten more minutes
  // to not fetch the data on each page request.
  if ($next_update_on < time()) {
    $next_update_on = time() + 10 * 60;
  }
  db_query($sql, $metar['icao'], $next_update_on, $metar['#raw']);
}

/**
 * Retrieve data from weather.noaa.gov
 */
function _weather_retrieve_data($icao) {
  $icao = strtoupper($icao);
  $metar_raw = FALSE;

  // get information about the last successful data download
  //
  // we want to fetch via FTP, because it's much less bandwidth
  // for downloads. if the FTP access did not work, we use
  // HTTP POST instead.
  $fetch = variable_get('weather_fetch', 'FTP');

  // try alternative methods for fetching the METAR data via FTP
  if ($fetch == 'FTP') {
    $url = 'ftp://tgftp.nws.noaa.gov/data/observations/metar/stations/';
    $url .= $icao . '.TXT';
    if (function_exists('file_get_contents') and ini_get('allow_url_fopen')) {
      $metar_raw = file_get_contents($url);
    }
    else {
      if (function_exists('curl_init')) {
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_TRANSFERTEXT, TRUE);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
        $metar_raw = curl_exec($curl);
        curl_close($curl);
      }
      else {
        if (function_exists('exec')) {
          exec("wget --quiet -O- {$url}", $output, $return_val);
          if ($return_val == 0 and is_array($output)) {
            $metar_raw = join("\n", $output);
          }
        }
      }
    }

    // if we had success, store the method
    if ($metar_raw !== FALSE) {
      variable_set('weather_fetch', 'FTP');
    }
  }

  // if the FTP access does not work, try POSTing the ICAO via HTTP
  if ($metar_raw === FALSE) {
    if (function_exists('fsockopen')) {
      $handle = fsockopen('weather.noaa.gov', 80);
      if ($handle !== FALSE) {
        $request = "POST /mgetmetar.php HTTP/1.1\r\n";
        $request .= "Host: weather.noaa.gov\r\n";
        $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $request .= "Content-Length: 9\r\n\r\n";
        $request .= "cccc={$icao}\r\n";
        fwrite($handle, $request);
        while (!feof($handle)) {
          $response .= fgets($handle, 1024);
        }
        fclose($handle);

        // extract the valid METAR data from the received webpage
        if (preg_match("/({$icao} [0-9]{6}Z .+)/", $response, $matches)) {
          $metar_raw = $matches[1];

          // store the method
          variable_set('weather_fetch', 'HTTP');
        }
      }
    }
  }

  // check on errors
  if ($metar_raw === FALSE) {

    // Make an entry about this error into the watchdog table.
    watchdog('content', 'Download location for METAR data is not accessible.', WATCHDOG_ERROR);

    // Reset the fetch method to try again FTP if HTTP didn't work either
    variable_set('weather_fetch', 'FTP');

    // Show a message to users with administration priviledges
    if (user_access('administer custom weather block') or user_access('administer site configuration')) {
      drupal_set_message(t('Download location for METAR data is not accessible.'), 'error');
    }
  }
  return $metar_raw;
}

Functions

Namesort descending Description
theme_weather Custom theme function for the weather block output
weather_bearing_to_text Converts a compass bearing to a text direction (e.g. 0° North, 86° East, ...)
weather_block Generate HTML for the weather block
weather_cron Implementation of hook_cron().
weather_custom_block Show a configuration page for a custom weather block
weather_custom_block_delete_confirm Confirmation page before deleting a location
weather_custom_block_delete_confirm_submit Handle the deletion of a location
weather_custom_block_form Construct the configuration form for a weather block
weather_custom_block_form_submit Handle the submission of the custom weather block
weather_custom_block_form_validate Check the submission of the custom weather block
weather_get_icao_from_lat_lon Given a latitude and longitude, return the details of the nearest known weather station.
weather_get_metar Fetches the latest METAR data from the database or internet
weather_get_places_xml Return all places with their ICAO codes for a given country.
weather_help Implementation of hook_help().
weather_main_page Show an overview of configured locations and the default location, if on the admin pages
weather_main_page_form Construct a form for general settings of the Weather module
weather_main_page_form_submit Handle the submission for general settings of the Weather module
weather_menu Implementation of hook_menu().
weather_perm Implementation of hook_perm().
weather_store_metar Stores parsed METAR data in the database
_weather_format_closest_station Convert information about nearest METAR station in the location block
_weather_format_condition Format the weather condition and phenomena (rain, drizzle, snow, ...)
_weather_format_pressure Convert pressure
_weather_format_relative_humidity Calculate the relative humidity
_weather_format_temperature Convert temperatures
_weather_format_visibility Convert the visibility
_weather_format_wind Convert wind
_weather_get_config Return the configuration for the given user id.
_weather_get_configs_in_use Determine how many configurations exist for the given user id.
_weather_get_first_valid_config Return the first valid configuration or 1 if there is none
_weather_get_free_config Return the first unused configuration number
_weather_get_image
_weather_retrieve_data Retrieve data from weather.noaa.gov

Constants