You are here

yr_verdata.module in Yr Weatherdata 6

Same filename and directory in other branches
  1. 6.2 yr_verdata.module
  2. 7.3 yr_verdata.module
  3. 7 yr_verdata.module

yr_verdata.module This file provides the yr_verdata forecast module.

File

yr_verdata.module
View source
<?php

/**
 * @file yr_verdata.module
 * This file provides the yr_verdata forecast module.
 *
 * @see yr-forecast-page.tpl.php
 * @see yr-forecast-block-location.tpl.php
 */

/**
 * Implementation of hook_menu().
 */
function yr_verdata_menu() {
  $items = array();
  $items['yr_verdata'] = array(
    'title' => 'Yr weatherdata',
    'description' => 'Weather forecast from yr.no',
    'page callback' => 'yr_verdata_page',
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['yr_verdata/%'] = array(
    'title' => 'Yr weatherdata',
    'description' => 'Weather forecast from yr.no',
    'page callback' => 'yr_verdata_page',
    'page arguments' => array(
      1,
    ),
    'access arguments' => array(
      'access content',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/content/yr_verdata'] = array(
    'title' => 'Yr weatherdata',
    'description' => 'Manage the weather forecast from yr.no',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'yr_verdata_add_location',
    ),
    'access arguments' => array(
      'manage yr_verdata',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  $items['admin/content/yr_verdata/delete/%'] = array(
    'title' => 'Delete location',
    'description' => 'Delete a location from the local Yr weatherdata list',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'yr_verdata_delete_location',
      4,
    ),
    'access arguments' => array(
      'manage yr_verdata',
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/yr_verdata'] = array(
    'title' => 'Yr weatherdata',
    'description' => 'Options for yr_verdata.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'yr_verdata_settings',
    ),
    'access arguments' => array(
      'manage yr_verdata',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function yr_verdata_perm() {
  return array(
    'manage yr_verdata',
  );
}

/**
 * Function to display a page with yr_verdata stuff.
 *
 * @param $yid
 * The {yr_verdata}.yid for this location, used to get info from the database,
 * and subsequently from yr.no. If == 0, a list is displayed of all locations.
 *
 * @return
 * Returns either a formatted page with forecast for a location, or a list of
 * all locations.
 */
function yr_verdata_page($yid = 0) {
  $sql = "SELECT * FROM {yr_verdata}";
  if ($yid == 0) {
    $sql .= " ORDER BY '%s', 'location' ASC";
    $order = variable_get('yr_verdata_order', 'subregion');
    $result = db_query($sql, $order);
    $rows = array();
    while ($row = db_fetch_array($result)) {
      $tmp = explode('/', $row['url']);
      $row['title'] = isset($tmp[4]) ? $tmp[4] : '';
      $sep = isset($tmp[4]) ? ' - ' : '';
      $row['title'] .= $tmp[4] == $tmp[3] ? '' : $sep . $tmp[3];
      $row['title'] .= variable_get('yr_verdata_display_countryname', 'off') == 'on' ? ', ' . $tmp[2] . ', ' . $tmp[1] : '';
      $row['title'] = str_replace('_', ' ', $row['title']);
      $rows[] = l($row['title'], 'yr_verdata/' . $row['yid']);
    }
    if (count($rows) == 0) {
      drupal_set_message(t('No locations are stored in the database.'), 'warning');
      return FALSE;
    }
    else {
      return theme('item_list', $rows);
    }
  }
  elseif ($yid > 0) {
    $sql .= " WHERE yid = %d";
    $row = db_fetch_object(db_query($sql, $yid));
    $output = db_affected_rows() == 1 ? theme('yr_verdata_location_page', $row) : t('No matching location was found.');
    return $output;
  }
  else {
    drupal_set_message(t('The location ID @id is unknown', array(
      '@id' => $yid,
    )));
  }
}

/**
 * Implementation of hook_block().
 */
function yr_verdata_block($op = 'list', $delta = 0, $edit = array()) {
  $rows = yr_verdata_locations();
  switch ($op) {
    case 'list':
      if (variable_get('yr_verdata_multiblock', 'off') == 'on') {
        foreach ($rows as $location) {
          $loc_name = $location->subregion . ', ' . $location->region . ', ' . $location->country;
          if (!empty($location->location)) {
            $loc_name = $location->location . ', ' . $loc_name;
          }
          $blocks[$location->yid]['info'] = t('Forecast for ') . $loc_name;
        }
      }
      $blocks[0]['info'] = t('Yr.no Weather Forecast');
      return $blocks;
    case 'view':
      if (user_access('access content')) {
        if (variable_get('yr_verdata_multiblock', 'off') == 'on') {
          foreach ($rows as $row) {
            if ($delta == $row->yid) {
              $loc_name = empty($row->location) ? $row->subregion : $row->location;
              $block['subject'] = t('Forecast for ') . $loc_name;
              $block['content'] = theme('yr_verdata_location_block', $row);
            }
          }
        }
        if ($delta == 0) {
          $block['subject'] = t('Weather Forecast');
          $block['content'] = theme('yr_verdata_location_block', $rows);
        }
      }
      return $block;
  }
}

/**
 * Helper function to return all locations.
 *
 * @return
 * Returns an array of locations as objects from the {yr_verdata} table.
 */
function yr_verdata_locations() {
  $sql = "SELECT * FROM {yr_verdata} ORDER BY '%s', 'location' ASC";
  $order = variable_get('yr_verdata_order', 'subregion');
  $result = db_query($sql, $order);
  $rows = array();
  while ($row = db_fetch_object($result)) {
    $rows[] = $row;
  }
  return !empty($rows) ? $rows : array();
}

/**
 * Implementation of hook_theme().
 */
function yr_verdata_theme() {
  return array(
    'yr_verdata_location_page' => array(
      'arguments' => array(
        'row' => NULL,
      ),
    ),
    'yr_verdata_location_block' => array(
      'arguments' => array(
        'rows' => NULL,
      ),
    ),
    'yr_verdata_location_sheet' => array(
      'arguments' => array(
        'name' => NULL,
        'tabs' => NULL,
        'info' => NULL,
        'links' => NULL,
        'sun' => NULL,
        'text_forecast' => NULL,
        'symbol_forecast' => NULL,
        'radar' => NULL,
        'credit' => NULL,
      ),
      'template' => 'yr-forecast-page',
    ),
    'yr_verdata_block_location' => array(
      'arguments' => array(
        'location' => NULL,
      ),
      'template' => 'yr-forecast-block-location',
    ),
  );
}

/**
 * Theming of a page with detailed forecast for a location.
 *
 * @param $row
 * A complete row returned from a query against the database {yr_verdata} table.
 *
 * @return
 * Returns variables to be put into the template file.
 */
function theme_yr_verdata_location_page($row) {
  $modpath = drupal_get_path('module', 'yr_verdata');
  drupal_add_css($modpath . '/yr_verdata.css');

  // jquery_ui.module is not required, but highly recommended.
  if (function_exists('jquery_ui_add')) {
    jquery_ui_add('ui.tabs');
    $ui_ver = variable_get('yr_jquery_ui_version', '17');
    drupal_add_js($modpath . '/js/yr_verdata.' . $ui_ver . '.js');
  }

  // Make sure that there is no punctuation in the url, as this could possibly
  // lead to a malicious user navigating elsewhere in the filesystem?
  $row->url = str_replace('.', '_', $row->url);

  // Load the xml file.
  if (_yr_verdata_xml($row->url) != FALSE) {
    $comps = explode('/', $row->url);
    $local_dir = file_directory_path() . '/yr_verdata';
    $local_file = $local_dir . '/' . implode('_', $comps) . '.xml';
    $data = simplexml_load_file($local_file);
  }

  // Set up our forecast-array.
  $location = array();
  $location['info'] = t('@type, @altitude m.a.s. <br />Last updated !lastupdate', array(
    '@type' => $data->location->type,
    '@altitude' => $data->location->location['altitude'],
    '!lastupdate' => _yr_format_time($data->meta->lastupdate),
  ));

  // Links.
  $location_at_yr = t('Forecast for <a href="!url" title="Go to @location at yr.no">@location at yr.no</a>', array(
    '@location' => $data->location->name,
    '!url' => check_url($data->links->link[1]['url']),
  ));
  $gm_url = 'http://maps.google.com/maps?ie=UTF8&hl=en&z=12&ll=';
  $gm_url .= $data->location->location['latitude'] . ',' . $data->location->location['longitude'];
  $gmaps = t('Map over <a href="!url" title="Go to @location at Google Maps">@location at Google Maps</a>', array(
    '@location' => $data->location->name,
    '!url' => check_url($gm_url),
  ));
  $wikipedia = t('Information about <a href="!url" title="Go to @location at Wikipedia">@location at Wikipedia</a>', array(
    '@location' => $data->location->name,
    '!url' => check_url('http://en.wikipedia.org/wiki/' . drupal_urlencode($data->location->name)),
  ));
  $items = array(
    $location_at_yr,
    $gmaps,
    $wikipedia,
  );
  $location['links'] = theme('item_list', $items);

  // A credit notice.
  $location['credit'] = l($data->credit->link['text'], $data->credit->link['url']);

  // Sunrise and sunset times.
  if ($data->sun['never_set'] == 'true') {
    $location['sun'] = t('Midnight sun, the sun doesn’t set.');
  }
  elseif ($data->sun['never_rise'] == 'true') {
    $location['sun'] = t('Polar night, the sun doesn’t rise.');
  }
  else {
    $sunrise = _yr_format_time($data->sun['rise']);
    $sunset = _yr_format_time($data->sun['set']);
    $location['sun'] = t('Sunrise: !sunrise<br />Sunset: !sunset', array(
      '!sunrise' => $sunrise,
      '!sunset' => $sunset,
    ));
  }

  // Text forecast, if it exists (only for mainland Norway locations).
  $location['text_forecast'] = array();
  if (isset($data->forecast->text->location)) {
    foreach ($data->forecast->text->location->time as $item) {
      $location['text_forecast'][] = array(
        'title' => drupal_ucfirst(check_plain($item->title)),
        'body' => filter_xss($item->body),
      );
    }
  }

  // Set up an array with all the graphical forecast-periods.
  $location['symbol_forecast'] = array();
  $periods = _yr_periods();
  $last_period = 0;
  $i = 0;
  foreach ($data->forecast->tabular->time as $tab) {
    if ($i > 15 && $last_period == 3) {
      break;
    }

    // Some of the values we are putting in needs to be "calculated".
    // Main symbol:
    $symbol = _yr_verdata_get_symbol($tab);

    // Wind:
    $wind = _yr_verdata_get_wind($tab);

    // Temperature:
    $temp = _yr_verdata_get_temp($tab);

    // Pressure:
    $pressure = $tab->pressure['value'] . $tab->pressure['unit'];

    // Time:
    $this_time = _yr_format_time($tab['from'], 'custom', 'D d M') . '<br />' . $periods[(int) $tab['period']];

    // We need for the template to know if this was the last period of the day, to facilitate sexier styling.
    $open = $i == 0 || $last_period == 3 || $tab['period'] == 2 && $last_period != 1 ? '<div class="yr-period-day clear-block">' : '';
    $close = $tab['period'] == 3 || $tab['period'] == 2 && $last_period != 1 && $i != 0 ? '</div>' : '';

    // Assign the calculated and other values.
    $location['symbol_forecast'][] = array(
      'open' => $open,
      'close' => $close,
      'class' => 'yr-period-' . (int) $tab['period'],
      'time' => $this_time,
      'symbol' => $symbol,
      'wind' => $wind,
      'precip' => t('@precip mm precipitation.', array(
        '@precip' => $tab->precipitation['value'],
      )),
      'temp' => $temp,
      'pressure' => check_plain($pressure),
    );
    $last_period = (int) $tab['period'];
    $i++;
  }

  // If we are in mainland Norway, add a radarimage.
  if (in_array($comps[1], array(
    'Norway',
    'Norge',
    'Noreg',
  ))) {
    $lat = (int) $data->location->location['latitude'];
    $long = (int) $data->location->location['longitude'];
    $radarsite = FALSE;

    // Figure out which radarimage to use. Start narrow in the south and expand if lat/long is outside range.
    // Finally set $showradar = FALSE; if no compatible lat/long range is found.
    if ($lat >= 57 && $lat < 61 && $long >= 4 && $long < 7) {
      $radarsite = 'southwest_norway';
      $radartext = t('Southwest-Norway');
    }
    elseif ($lat >= 57 && $lat < 61 && $long >= 7 && $long <= 13) {
      $radarsite = 'southeast_norway';
      $radartext = t('Southeast-Norway');
    }
    elseif ($lat >= 60 && $lat < 62 && $long >= 4 && $long < 8) {
      $radarsite = 'western_norway';
      $radartext = t('Western Norway');
    }
    elseif ($lat >= 62 && $lat < 66 && $long >= 4 && $long <= 14) {
      $radarsite = 'central_norway';
      $radartext = t('Central Norway');
    }
    elseif ($lat >= 57 && $lat < 66 && $long >= 4 && $long <= 14) {
      $radarsite = 'south_norway';
      $radartext = t('Southern Norway');
    }
    elseif ($lat >= 65 && $lat < 69 && $long > 10 && $long <= 18) {
      $radarsite = 'nordland_troms';
      $radartext = t('Northern Norway');
    }
    elseif ($lat >= 68 && $lat < 72 && $long > 17 && $long <= 30) {
      $radarsite = 'troms_finnmark';
      $radartext = t('Northernmost Norway');
    }
    else {
      $radarsite = FALSE;
    }
    $radar_url = check_url('http://api.yr.no/weatherapi/radar/1.2/?radarsite=' . $radarsite . ';width=460;type=animation');
    $radar = '<img src="' . $radar_url . '" alt="' . $radartext . '" />';
    $radarlink = !empty($data->links->link[5]['url']) ? check_url($data->links->link[5]['url']) : check_url($data->links->link[1]['url']);
    $location['radar']['text'] = $radarsite == FALSE ? t('No image') : $radartext;
    $location['radar']['image'] = $radarsite == FALSE ? t('This location is currently not covered by a radar.') : l($radar, $radarlink, array(
      'html' => TRUE,
    ));
  }
  else {
    $location['radar'] = '';
  }

  // Add the tabs titles.
  $location['tabs'] = array(
    t('Info'),
    t('Text forecast'),
    t('Detailed forecast'),
    t('Radar image'),
  );

  // Add the location name, for those who want to customize the .tpl file.
  $location['name'] = check_plain($data->location->name);

  // Theme the output.
  $output = theme('yr_verdata_location_sheet', $location['name'], $location['tabs'], $location['info'], $location['links'], $location['sun'], $location['text_forecast'], $location['symbol_forecast'], $location['radar'], $location['credit']);

  // Set the page title.
  drupal_set_title(t('Weather forecast for @location', array(
    '@location' => $data->location->name,
  )));

  // If we don't have a valid location, $data will be empty, and the page should just reflect that.
  if ($data != TRUE) {
    $unavailable = t('Weather forecast unavailable');
    drupal_set_title($unavailable);
    $output = '';
  }
  return $output;
}
function theme_yr_verdata_location_block($rows) {
  $modpath = drupal_get_path('module', 'yr_verdata');
  drupal_add_css($modpath . '/yr_verdata.css');
  $output = '';

  // If we are just theming a single-location block, we still need to make it an array.
  if (!is_array($rows)) {
    $rows = array(
      $rows,
    );
  }
  foreach ($rows as $row) {

    // Make sure there are no punctuation in the url, as this could possibly lead to a malicious user
    // navigating elsewhere in the filesystem?
    $row->url = str_replace('.', '_', $row->url);
    _yr_verdata_xml($row->url);

    // Load the xml file.
    $local_dir = file_directory_path() . '/yr_verdata';
    $comps = explode('/', $row->url);
    $local_file = $local_dir . '/' . implode('_', $comps) . '.xml';
    $data = simplexml_load_file($local_file);

    // Some of the values we are putting in, needs to be "calculated".
    // Main symbol:
    $symbol = _yr_verdata_get_symbol($data->forecast->tabular->time[0]);

    // Wind:
    $wind = _yr_verdata_get_wind($data->forecast->tabular->time[0]);

    // Temperature:
    $temp = _yr_verdata_get_temp($data->forecast->tabular->time[0]);

    // Pressure:
    $pressure = $data->forecast->tabular->time[0]->pressure['value'] . $data->forecast->tabular->time[0]->pressure['unit'];

    // Time:
    $periods = _yr_periods();
    $this_time = _yr_format_time($data->forecast->tabular->time[0]['from'], 'custom', 'D d M') . '<br />' . $periods[(int) $data->forecast->tabular->time[0]['period']];
    $location = array(
      'name' => l($data->location->name, 'yr_verdata/' . $row->yid),
      'symbol' => $symbol,
      'wind' => $wind,
      'temp' => $temp,
      'precip' => t('@precip mm precipitation.', array(
        '@precip' => $data->forecast->tabular->time[0]->precipitation['value'],
      )),
      'pressure' => check_plain($pressure),
      'time' => $this_time,
    );
    $output .= theme('yr_verdata_block_location', $location);
  }
  if (count($rows) > 0) {
    $output .= '<div class="yr-credit"><p>' . t('<a href="!yrl" title="Go to yr.no to view forecasts for more than 7 million locations across the globe">Forecast from yr.no</a>', array(
      '!yrl' => 'http://yr.no',
    )) . '</p></div>';
  }
  return $output;
}

/**
 * The administrative settings form.
 *
 * @return
 * Returns a form array, processed by system_settings_form().
 */
function yr_verdata_settings() {

  // Make sure that the file directory exists.
  $local_dir = file_directory_path() . '/yr_verdata';
  file_check_directory($local_dir, 1);

  // Set up the form.
  $form['yr_verdata_settings'] = array(
    '#prefix' => '<div id="yr-admin-settings">',
    '#suffix' => '</div>',
  );
  $form['yr_verdata_settings']['yr_verdata_lang'] = array(
    '#type' => 'select',
    '#title' => t('Default language'),
    '#options' => array(
      'place' => t('English'),
      'sted' => t('Norwegian Bokmål'),
      'stad' => t('Norwegian Nynorsk'),
    ),
    '#default_value' => variable_get('yr_verdata_lang', 'place'),
    '#description' => t('Note that for locations in mainland Norway, a text forecast is included, and this is only supplied in Norwegian.'),
  );
  $form['yr_verdata_settings']['yr_verdata_maxage'] = array(
    '#type' => 'select',
    '#title' => t('Maximum age of XML files'),
    '#options' => array(
      1 => t('Always update (debugging only)'),
      900 => t('15 minutes'),
      3600 => t('One hour'),
      10800 => t('Three hours'),
      21600 => t('Six hours'),
      43200 => t('12 hours'),
      86400 => t('24 hours'),
    ),
    '#default_value' => variable_get('yr_verdata_maxage', 21600),
    '#description' => t('Setting a value less than 3 hours is probably useless, since the forecast is not updated that often at yr.no. 3 hours should be plenty to keep your site up to date while avoiding unneccessary server load.'),
  );
  $form['yr_verdata_settings']['yr_verdata_order'] = array(
    '#type' => 'select',
    '#title' => t('Default sort order'),
    '#options' => array(
      'location' => t('Location name'),
      'subregion' => t('Subregion/municipality'),
      'region' => t('Region/state/county'),
      'country' => t('Country/continent'),
    ),
    '#default_value' => variable_get('yr_verdata_order', 'subregion'),
  );
  $form['yr_verdata_settings']['yr_verdata_display_countryname'] = array(
    '#type' => 'radios',
    '#title' => t('Show the region and country name in listings'),
    '#options' => array(
      'on' => t('On'),
      'off' => t('Off'),
    ),
    '#default_value' => variable_get('yr_verdata_display_countryname', 'off'),
  );
  $form['yr_verdata_settings']['yr_verdata_multiblock'] = array(
    '#type' => 'radios',
    '#title' => t('Multiple blocks'),
    '#options' => array(
      'on' => t('On'),
      'off' => t('Off'),
    ),
    '#default_value' => variable_get('yr_verdata_multiblock', 'off'),
    '#description' => t('Yr verdata provides one block, listing all locations. If you want to, it can additionally provide one block for each location. Note that this may look cluttered, so it can be a good idea to use page-specific settings for these blocks.'),
  );
  $form['yr_verdata_settings']['yr_jquery_ui_version'] = array(
    '#type' => 'radios',
    '#title' => t('jQuery UI version'),
    '#options' => array(
      '16' => t('jQuery UI 1.6 and jQuery 1.2.x'),
      '17' => t('jQuery UI 1.7 and jQuery 1.3.x'),
    ),
    '#default_value' => variable_get('yr_jquery_ui_version', '17'),
    '#description' => t('If you are using !jquery_update with <em>jQuery 1.3.x</em>, you should use <em>jQuery UI 1.7</em>. Otherwise, use <em>jQuery UI 1.6</em> (this will work with the default jQuery version shipped with Drupal).', array(
      '!jquery_ui' => l('jQuery UI', 'http://drupal.org/project/jquery_ui'),
      '!jquery_update' => l('the jQuery update module', 'http://drupal.org/project/jquery_update'),
    )),
  );
  $form = system_settings_form($form);
  unset($form['#submit']);
  $form['#submit'][] = 'yr_verdata_settings_submit';
  return $form;
}

/**
 * Implementation of hook_submit().
 */
function yr_verdata_settings_submit($form, &$form_state) {

  // If the user hit the 'Reset to defaults button' we do that then return.
  if ($form_state['clicked_button']['#parents'][0] == 'reset') {
    variable_set('yr_verdata_lang', 'place');
    variable_set('yr_verdata_maxage', 21600);
    variable_set('yr_verdata_order', 'subregion');
    variable_set('yr_verdata_display_countryname', 'off');
    variable_set('yr_verdata_multiblock', 'off');
    variable_set('yr_jquery_ui_version', '17');
    drupal_set_message(t('The configuration options have been reset to their default values.'));
  }
  else {
    $lang = $form_state['values']['yr_verdata_lang'];
    $maxage = (int) $form_state['values']['yr_verdata_maxage'];
    $order = $form_state['values']['yr_verdata_order'];
    $multiblock = $form_state['values']['yr_verdata_multiblock'];
    variable_set('yr_verdata_lang', $lang);
    variable_set('yr_verdata_maxage', $maxage);
    variable_set('yr_verdata_order', $order);
    variable_set('yr_verdata_display_countryname', $form_state['values']['yr_verdata_display_countryname']);
    variable_set('yr_verdata_multiblock', $multiblock);
    variable_set('yr_jquery_ui_version', $form_state['values']['yr_jquery_ui_version']);
    drupal_set_message(t('Settings saved.'));
  }
}

/**
 * Function for adding locations.
 *
 * @return
 * Returns a form array, to be processed by drupal_get_form().
 */
function yr_verdata_add_location() {
  $form = array();
  $form['add_new'] = array(
    '#type' => 'fieldset',
    '#title' => t('Add new location'),
    '#collapsible' => TRUE,
  );
  $form['add_new']['language'] = array(
    '#type' => 'select',
    '#title' => t('Language'),
    '#options' => array(
      'place' => t('English'),
      'sted' => t('Norwegian Bokmål'),
      'stad' => t('Norwegian Nynorsk'),
    ),
    '#default_value' => variable_get('yr_verdata_lang', 'place'),
    '#description' => t('Select the language you want this forecast delivered in.'),
    '#required' => TRUE,
  );
  $form['add_new']['country'] = array(
    '#type' => 'textfield',
    '#title' => t('Country'),
    '#description' => t('The first part of the url at <a href="!yrl">yr.no</a> (after <em>http://yr.no/place/</em>). For example: http://yr.no/place/<strong>Norway</strong>/Vest-Agder/Kristiansand/Hamresanden.', array(
      '!yrl' => 'http://yr.no',
    )),
    '#required' => TRUE,
  );
  $form['add_new']['region'] = array(
    '#type' => 'textfield',
    '#title' => t('Region'),
    '#description' => t('The second part of the url at <a href="!yrl">yr.no</a> (after <em>http://yr.no/place/</em>). For example: http://yr.no/place/Norway/<strong>Vest-Agder</strong>/Kristiansand/Hamresanden.', array(
      '!yrl' => 'http://yr.no',
    )),
    '#required' => TRUE,
  );
  $form['add_new']['subregion'] = array(
    '#type' => 'textfield',
    '#title' => t('Subregion/City'),
    '#description' => t('The third part of the url at <a href="!yrl">yr.no</a> (after <em>http://yr.no/place/</em>). For example: http://yr.no/place/Norway/Vest-Agder/<strong>Kristiansand</strong>/Hamresanden.', array(
      '!yrl' => 'http://yr.no',
    )),
    '#required' => TRUE,
  );
  $form['add_new']['location'] = array(
    '#type' => 'textfield',
    '#title' => t('Location'),
    '#description' => t('The fourth part of the url at <a href="!yrl">yr.no</a> (after <em>http://yr.no/place/</em>). For example: http://yr.no/place/Norway/Vest-Agder/Kristiansand/<strong>Hamresanden</strong>.<br />Note that some locations do not have this part of the url and for those, this part is not required.', array(
      '!yrl' => 'http://yr.no',
    )),
  );
  $form['add_new']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save location'),
  );

  // Get all existing locations for display in a table.
  $order = variable_get('yr_verdata_order', 'subregion');
  $result = db_query("SELECT * FROM {yr_verdata} ORDER BY '%s', 'location' ASC", $order);
  $rows = array();
  while ($row = db_fetch_array($result)) {
    $tmp = explode('/', $row['url']);
    $row['title'] = $tmp[3];
    $row['title'] .= isset($tmp[4]) ? ' - ' . $tmp[4] : '';
    $row['title'] .= ', ' . $tmp[2] . ', ' . $tmp[1];
    $row['title'] = str_replace('_', ' ', $row['title']);
    $rows[] = array(
      $row['yid'],
      l($row['title'], 'yr_verdata/' . $row['yid']),
      l('X', 'admin/content/yr_verdata/delete/' . $row['yid']),
    );
  }
  if (count($rows) == 0) {
    $rows = array(
      'data' => array(
        t('No locations are stored in the database.'),
        array(
          'colspan' => 3,
        ),
      ),
    );
  }
  $headers = array(
    t('YID'),
    t('Location'),
    t('Delete'),
  );
  $form['existing'] = array(
    '#type' => 'fieldset',
    '#title' => t('Existing locations'),
    '#collapsible' => TRUE,
  );
  $form['existing']['table'] = array(
    '#type' => 'item',
    '#value' => theme('table', $headers, $rows),
  );
  return $form;
}

/**
 * Implementation of hook_validate().
 */
function yr_verdata_add_location_validate($form, &$form_state) {

  // If the country is Norway, the location is required.
  if (in_array($form_state['values']['country'], array(
    'Norway',
    'Norge',
    'Noreg',
  ))) {
    if (empty($form_state['values']['location'])) {
      form_set_error('location', t('In Norway, a specific location name is required.'));
    }
  }

  // And check to see if this location already exists in the database.
  $url = $form_state['values']['language'] . '/' . $form_state['values']['country'] . '/' . $form_state['values']['region'] . '/' . $form_state['values']['subregion'];
  $url .= !empty($form_state['values']['location']) ? '/' . $form_state['values']['location'] : '';
  $result = db_query("SELECT yid FROM {yr_verdata} WHERE url = '%s'", $url);
  if (db_affected_rows() > 0) {
    form_set_error('location', t('This location already exists in the database.'));
  }
}

/**
 * Implementation of hook_submit().
 */
function yr_verdata_add_location_submit($form, &$form_state) {
  $url = $form_state['values']['language'] . '/' . $form_state['values']['country'] . '/' . $form_state['values']['region'] . '/' . $form_state['values']['subregion'];
  $url .= !empty($form_state['values']['location']) ? '/' . $form_state['values']['location'] : '';
  $url = str_replace(' ', '_', $url);

  // Attempt to underscore any spaces in names, for better usability.
  // To make sure that locations without a locationname don't always show up first, we give them 'x' as location name.
  $location = !empty($form_state['values']['location']) ? $form_state['values']['location'] : 'x';
  db_query("INSERT INTO {yr_verdata} (url, location, subregion, region, country) VALUES ('%s', '%s', '%s', '%s', '%s')", $url, $location, $form_state['values']['subregion'], $form_state['values']['region'], $form_state['values']['country']);
}

/**
 * Function for deleting locations.
 *
 * @param $yid
 * The {yr_verdata}.yid for this location, taken from the url.
 */
function yr_verdata_delete_location($form, $yid) {
  $sql = "SELECT * FROM {yr_verdata} WHERE yid = %d";
  $row = db_fetch_object(db_query($sql, $yid));
  $form = array();
  if (db_affected_rows() == 1) {
    $form['delete_location']['name'] = array(
      '#type' => 'item',
      '#value' => t('Warning: You are about to delete the location %location from the database. This action cannot be undone.', array(
        '%location' => $row->url,
      )),
    );
    $form['delete_location']['yid'] = array(
      '#type' => 'hidden',
      '#value' => $yid,
    );
    $form['delete_location']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Delete'),
    );
    $form['delete_location']['cancel'] = array(
      '#type' => 'item',
      '#value' => l(t('Cancel'), 'admin/content/yr_verdata'),
    );
  }
  else {
    drupal_set_message(t('An invalid location id was given. Please try again.'), 'error');
    $form['delete_location']['cancel'] = array(
      '#type' => 'item',
      '#value' => l(t('Go back'), 'admin/content/yr_verdata'),
    );
  }
  return $form;
}

/**
 * Implementation of hook_validate().
 */
function yr_verdata_delete_location_validate($form, &$form_state) {
  if (empty($form_state['values']['yid'])) {
    form_set_error('yid', t('No ID for the location was given. Could not delete location.'));
  }
}

/**
 * Implementation of hook_submit().
 */
function yr_verdata_delete_location_submit($form, &$form_state) {
  if (db_query("DELETE FROM {yr_verdata} WHERE yid = %d", $form_state['values']['yid'])) {
    drupal_set_message(t('Location deleted.'));
  }
  drupal_redirect_form($form, 'admin/content/yr_verdata');
}

/**
 * Function for retrieving XML data from yr.no
 *
 * @param $url
 * The string to append to "http://yr.no/".
 *
 * @return
 * An object made with simplexml based on the XML data from yr.no.
 */
function _yr_verdata_xml($url) {
  $comps = explode('/', $url);
  switch ($comps[0]) {
    case 'place':
      $xml = 'forecast.xml';
      break;
    case 'sted':
      $xml = 'varsel.xml';
      break;
    case 'stad':
      $xml = 'varsel.xml';
      break;
  }

  // end switch lang
  $remote_file = check_url('http://www.yr.no/' . drupal_urlencode($url) . '/' . $xml);
  $local_dir = file_directory_path() . '/yr_verdata';
  file_check_directory($local_dir, 1);
  $local_file = $local_dir . '/' . implode('_', $comps) . '.xml';
  $success = FALSE;
  $local_file_age = @filemtime($local_file);
  $now = time();
  $maxage = variable_get('yr_verdata_maxage', 21600);
  $timeout = 10;
  if ($maxage > 0 && $now - $local_file_age > $maxage) {
    if (function_exists('curl_init')) {
      $c_handle = curl_init();
      curl_setopt($c_handle, CURLOPT_URL, $remote_file);
      curl_setopt($c_handle, CURLOPT_RETURNTRANSFER, TRUE);
      $data = curl_exec($c_handle);
      $valid_xml = simplexml_load_string($data);
      $c_info = curl_getinfo($c_handle);
      if ($c_info['http_code'] != '200' || $c_info['size_download'] < 5000 || !$valid_xml) {

        // If the server is unresponsive, or down for maintenance, we provide an error.
        drupal_set_message(t('The file retrieved from @url was not a valid XML-file for this data type. This may be because yr.no is down for maintenance. Also, make sure that the location you specified is spelled correctly.', array(
          '@url' => $remote_file,
        )), 'warning');
        watchdog('yr_verdata', 'The file retrieved from @url was not a valid XML-file for this data type. This may be because yr.no is down for maintenance. Also, make sure that the location you specified is spelled correctly.', array(
          '@url' => $remote_file,
        ));
        $success = FALSE;
      }
      else {

        // The server returned http = 200, we assume we can load the xml data.
        $f_handle = fopen($local_file, 'w');
        curl_setopt($c_handle, CURLOPT_FILE, $f_handle);
        curl_setopt($c_handle, CURLOPT_HEADER, 0);
        curl_setopt($c_handle, CURLOPT_CONNECTTIMEOUT, $timeout);
        curl_setopt($c_handle, CURLOPT_TIMEOUT, $timeout);
        curl_exec($c_handle);
        fclose($f_handle);
        $success = TRUE;
      }
      curl_close($c_handle);
    }
    else {
      if ($message == 1) {
        drupal_set_message(t('No method for retrieving forecast data was found. You must have PHP 5 with cURL available.'), 'error');
      }
      watchdog('yr_verdata', 'Could not retrieve remote XML data from @url, because cURL was not found.', array(
        '@url' => $remote_file,
      ));
      $success = FALSE;
    }
  }
  else {
    $success = 'not maxage';
  }
  return $success;
}

/**
 * Helper function to resolve periods of forecast.
 *
 * @return
 * Returns an array with simple hour notations in a 24H format.
 */
function _yr_periods() {
  return array(
    0 => '00-06',
    1 => '06-12',
    2 => '12-18',
    3 => '18-00',
  );
}

/**
 * Helper function to change datetime from the xml into drupal time.
 *
 * @param $datetime
 * A date and time given as yyyy-mm-ddThh:mm:ss
 * @param $type
 * The Drupal time format to use. Defaults to 'medium'.
 * @param $format
 * If $format = 'custom', use a PHP date string here.
 *
 * @return
 * Returns a date and time formatted by Drupal's format_date().
 * We pass 0 to the timezone parameter, because the datetime from
 * yr.no is given in the location's local time.
 *
 * @see format_date()
 * @see date()
 */
function _yr_format_time($datetime, $type = 'medium', $format = '') {
  $items = explode('T', $datetime);
  $date = explode('-', $items[0]);
  $time = explode(':', $items[1]);
  $tz = date_default_timezone_get();
  date_default_timezone_set('UTC');
  $timestamp = mktime($time[0], $time[1], $time[2], $date[1], $date[2], $date[0]);
  date_default_timezone_set($tz);
  return format_date($timestamp, $type, $format, 0);
}

/**
 * Helper function for creating <img> tag for the symbol.
 */
function _yr_verdata_get_symbol($tab) {
  $nsym = FALSE;
  switch ($tab->symbol['number']) {
    case '01':
    case '02':
    case '03':
    case '05':
    case '06':
    case '07':
    case '08':
      $nsym = TRUE;
      break;
  }
  $sym = drupal_strlen($tab->symbol['number']) > 1 ? $tab->symbol['number'] : '0' . $tab->symbol['number'];
  if ($nsym == TRUE) {
    $sym .= $tab['period'] === '0' ? 'n' : 'd';
  }
  $url = check_url('http://fil.nrk.no/yr/grafikk/sym/b38/' . $sym . '.png');
  return '<img src="' . $url . '" alt="' . $tab->symbol['name'] . '" />';
}

/**
 * Helper function for creating wind data and image.
 */
function _yr_verdata_get_wind($tab) {
  $wind = array();

  // Round degrees to nearest 5 and make sure it's a 3-digit number.
  $m = $tab->windDirection['deg'] % 5;
  $wind['dir']['deg'] = $tab->windDirection['deg'] - $m;
  if (drupal_strlen($wind['dir']['deg']) == 2) {
    $wind['dir']['deg'] = '0' . $wind['dir']['deg'];
  }
  elseif (drupal_strlen($wind['dir']['deg']) == 1) {
    $wind['dir']['deg'] = '00' . $wind['dir']['deg'];
  }

  // 360 degrees won't work, so set to 0 in that case.
  if ($wind['dir']['deg'] == 360) {
    $wind['dir']['deg'] = '000';
  }
  $wind['dir']['name'] = $tab->windDirection['name'];
  $wind['dir']['code'] = $tab->windDirection['code'];
  $wind['speed']['name'] = $tab->windSpeed['name'];

  // Speed is given in nearest 2.5, but in the url for the image, the number is multiplied by 10.
  $wind['speed']['mps'] = $tab->windSpeed['mps'] * 10;
  $wind['speed']['mps'] = round($wind['speed']['mps'] / 25) * 25;

  // Add leading zeroes if needed.
  if ($wind['speed']['mps'] > 99) {
    $wind['speed']['mps'] = '0' . $wind['speed']['mps'];
  }
  elseif ($wind['speed']['mps'] > 9) {
    $wind['speed']['mps'] = '00' . $wind['speed']['mps'];
  }
  else {
    $wind['speed']['mps'] = '000' . $wind['speed']['mps'];
  }

  // And set up the image url.
  $windurl = check_url('http://fil.nrk.no/yr/grafikk/vindpiler/32/vindpil.' . $wind['speed']['mps'] . '.' . $wind['dir']['deg'] . '.png');
  $wind['img'] = '<img src="' . $windurl . '" alt="' . check_plain($wind['speed']['name']) . ' ' . check_plain($wind['dir']['code']) . '" />';
  return $wind;
}

/**
 * Helper function to create temperature data.
 */
function _yr_verdata_get_temp($tab) {
  $temp = array();
  $temp['val'] = check_plain($tab->temperature['value']) . '&deg;';
  $temp['val'] .= $tab->temperature['unit'] == 'celcius' ? 'C' : 'F';
  $temp['class'] = (int) $tab->temperature['value'] >= 0 ? 'plus' : 'minus';
  return $temp;
}

Functions

Namesort descending Description
theme_yr_verdata_location_block
theme_yr_verdata_location_page Theming of a page with detailed forecast for a location.
yr_verdata_add_location Function for adding locations.
yr_verdata_add_location_submit Implementation of hook_submit().
yr_verdata_add_location_validate Implementation of hook_validate().
yr_verdata_block Implementation of hook_block().
yr_verdata_delete_location Function for deleting locations.
yr_verdata_delete_location_submit Implementation of hook_submit().
yr_verdata_delete_location_validate Implementation of hook_validate().
yr_verdata_locations Helper function to return all locations.
yr_verdata_menu Implementation of hook_menu().
yr_verdata_page Function to display a page with yr_verdata stuff.
yr_verdata_perm Implementation of hook_perm().
yr_verdata_settings The administrative settings form.
yr_verdata_settings_submit Implementation of hook_submit().
yr_verdata_theme Implementation of hook_theme().
_yr_format_time Helper function to change datetime from the xml into drupal time.
_yr_periods Helper function to resolve periods of forecast.
_yr_verdata_get_symbol Helper function for creating <img> tag for the symbol.
_yr_verdata_get_temp Helper function to create temperature data.
_yr_verdata_get_wind Helper function for creating wind data and image.
_yr_verdata_xml Function for retrieving XML data from yr.no