? test.patch
? archive/_________station_archive.install
? archive/____station_archive.module
? catalog/The Database.mdb
? catalog/fixit.php
? catalog/import.php
<?php

// $Id: dayhour.inc,v 1.17 2007/11/25 21:08:58 drewish Exp $

// there are 1440 minutes in a day (60minute * 24hours = 1day)
define('MINUTES_IN_DAY', 1440);
define('MINUTES_IN_WEEK', 7 * MINUTES_IN_DAY);

/**
 * Turn a string into a valid hour.
 *
 * @param $hour
 *   String containing an hour. e.g. 0-24, 0-12 am/pm, noon, midnight
 * @return
 *   An integer between 0 and 23.
 */
function station_valid_hour($hour) {
  if (!is_numeric($hour)) {
    $hour = strtolower(trim($hour));

    // take care of a couple of handy strings
    if ($hour == t('midnight')) {
      return 0;
    }
    else if ($hour == t('noon')) {
      return 12;
    }

    // try to parse out am/pm times then let it fall through to the
    // 24 hour stuff below.
    $parts = array();
    if (preg_match('/(\d+)\s*([ap]m)/', $hour, $parts)) {
      $hour = (integer) $parts[1];
      // 12am and 12pm are special cases
      if ($hour == 12) {
        $hour += 12;
      }
      if ($parts[2] == 'pm') {
        $hour += 12;
      }
    }
  }

  if ($hour < 0) {
    return ((integer) $hour % 24) + 24;
  }
  return ((integer) $hour % 24);
}

/**
 * Get a numeric day of the week from a string or integer.
 *
 * @param $day
 *   An integer from 0-6, or a case in-sensitive, English day name.
 * @return
 *   An nteger between 0 and 6 (Sunday, Monday, ... , Saturday).
 */
function station_valid_day($day) {
  if (is_numeric($day)) {
    if ($day < 0) {
      return ((integer) $day % 7) + 7;
    }
    else if ($day > 6) {
      return ((integer) $day % 7);
    }
    return (integer) $day;
  }
  else {
    $dayname = station_day_name();
    $ret = array_search(ucfirst($day), $dayname, false);
    return ($ret === false) ? 0 : $ret;
  }
}

/**
 * Return an array of the names of the days of the week.
 *
 * @param $day
 *   An optional integer for the day of the week, zero being Sunday.
 * @return
 *   If $day is specified a string, othereise an array of the names of all
 *   the days in the week. When an array is returned it will be ordered
 *   honoring the site's first day of the week setting.
 */
function station_day_name($day = NULL) {
  static $days;

  if (!isset($days)) {
    $names = array(
      0 => t('Sunday'),
      t('Monday'),
      t('Tuesday'),
      t('Wednesday'),
      t('Thursday'),
      t('Friday'),
      t('Saturday')
    );
    $day_offset = variable_get('date_first_day', 0);
    $days = array();
    for ($i = $day_offset; $i < (7 + $day_offset); $i++) {
      $days[$i % 7] = $names[$i % 7];
    }
  }

  if ($day === NULL) {
    return $days;
  }
  else {
    return $days[$day % 7];
  }
}

/**
 * Given a day and hour, compute the number of minutes since midnight Sunday.
 *
 * @param $day
 *   Integer from 0 to 6.
 * @param $hour
 *   Integer from 0 to 23.
 * @return
 *   Integer specifying the number of minutes.
 */
function station_minute_from_day_hour($day, $hour) {
  return (($day * 24) + $hour) * 60;
}

/**
 * Day of the week from minutes.
 *
 * @param $minutes
 *   Integer with the number of minutes since midnight Sunday.
 * @return
 *   Integer day of the week, zero being Sunday.
 */
function station_day_from_minute($minute) {
  return (int) (($minute) / MINUTES_IN_DAY);
}

/**
 * Compute time information for a minute in the week.
 *
 * @param $minutes
 *   Integer specifying minutes since midnight on Sunday.
 * @return
 *   An array with the following keys:
 *     'w'       - Day of week (0-6).
 *     'G'       - 24 hour.
 *     'g'       - 12 hour.
 *     'H'       - 24 hour, 0 padded.
 *     'h'       - 12 hour, 0 padded.
 *     'i'       - minutes, 0 padded.
 *     'time'    - hour and minutes acording to 12/24 setting.
 *     'minutes' - minutes since midnight Sunday.
 *     'a'       - am/pm.
 */
function station_time_from_minute($minutes) {
  $min = $minutes % 60;
  $day = (int) (($minutes) / MINUTES_IN_DAY);
  $hour24 = (int) (($minutes % MINUTES_IN_DAY) / 60);
  if (!($hour12 = $hour24 % 12)) {
    $hour12 = 12;
  }
  $i = str_pad($min, 2, '0', STR_PAD_LEFT);
  $h = str_pad($hour12, 2, '0', STR_PAD_LEFT);
  if (variable_get('station_clock', 12) == 12) {
    $time = $hour12 . (($min == 0) ? '' : ":$i");
    $a = ($hour24 > 11) ? 'pm' : 'am';
  }
  else {
    $time = "$hour24:$i";
    $a = '';
  }
  return array(
    'w' => $day,
    'G' => $hour24,
    'g' => $hour12,
    'H' => str_pad($hour24, 2, '0', STR_PAD_LEFT),
    'h' => $h,
    'i' => $i,
    'time' => $time,
    'minutes' => $minutes,
    'a' => $a,
  );
}

/**
 * Format a range of minutes into a day hour string, e.g. 
 * "Sunday 11pm - Monday 1am".
 * 
 * @param $start
 *   Integer specifying minutes.
 * @param $finish
 *   Integer specifying minutes.
 * @return 
 *   Formatted string.
 */
function theme_station_dayhour_range($start, $finish) {
  $start = station_time_from_minute($start);
  $finish = station_time_from_minute($finish);
  $format_params = array(
    '@sday' => station_day_name($start['w']), '@shour' => $start['g'],
    '@stime' => $start['time'], '@sampm' => $start['a'],
    '@fday' => station_day_name($finish['w']), '@fhour' => $finish['g'],
    '@ftime' => $finish['time'], '@fampm' => $finish['a'],
  );

  // same day
  if ($start['w'] == $finish['w']) {
    // same am pm
    if ($start['a'] == $finish['a']) {
      return t('@sday @stime-@ftime@sampm', $format_params);
    }
    else {
      return t('@sday @stime@sampm-@ftime@fampm', $format_params);
    }
  }
  else {
    return t('@sday @stime@sampm-@fday @ftime@fampm', $format_params);
  }
}

/**
 * Format a range of minutes into a hour string, e.g. "1am-3pm".
 *
 * @param $start
 *   Integer specifying minutes.
 * @param $finish
 *   Integer specifying minutes.
 * @return 
 *   Formatted string.
 */
function theme_station_hour_range($start, $finish) {
  $start = station_time_from_minute($start);
  $finish = station_time_from_minute($finish);
  $format_params = array(
    '@stime' => $start['time'], '@sampm' => $start['a'],
    '@ftime' => $finish['time'], '@fampm' => $finish['a'],
  );

  if ($start['a'] == $finish['a']) {
    return t('@stime-@ftime@sampm', $format_params);
  }
  else {
    return t('@stime@sampm-@ftime@fampm', $format_params);
  }
}

/**
 * Format minutes into a hour string, e.g. "1am".
 *
 * @param $time
 *   Integer specifying minutes.
 * @return 
 *   Formatted string.
 */
function theme_station_hour($time) {
  $time = station_time_from_minute($time);
  $format_params = array(
    '@time' => $time['time'], '@ampm' => $time['a'],
  );

  return t('@time@ampm', $format_params);
}
; $Id: station.info,v 1.4 2007/08/10 17:13:15 drewish Exp $
name = Station
description = Core of station module for running an internet radio station.
package = Station
<?php

// $Id: station.install,v 1.11 2007/11/01 17:52:11 drewish Exp $


/**
 * Implementation of hook_uninstall().
 */
function station_uninstall() {
  variable_del('station_remote_archive_url');
  variable_del('station_remote_schedule_url');
}


/**
 * Rename some variables
 */
function station_update_1() {
  _station_var_rename('station_archive_station_url', 'station_remote_station_url');
  _station_var_rename('station_schedule_archive_url', 'station_remote_archive_url');

  return array();
}


function _station_var_rename($from, $to) {
  $value = variable_get($from, FALSE);
  if ($value) {
    variable_set($to, $value);
  }
  variable_del($from);
}

/**
 * Empty update to set a schema number for Drupal 5.0.
 */
function station_update_100() {
  return array();
}

<?php

define('STATION_CVS_ID', '$Id: station.module,v 1.38 2007/11/22 06:28:09 drewish Exp $');

require_once(drupal_get_path('module', 'station') .'/dayhour.inc');

function station_help($section='') {
  switch ($section) {
    case 'admin/settings/station':
      return t('Configure the core station settings. These settings are used by all of the station modules.');
  }
  return NULL;
}

function station_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/station',
      'title' => t('Station'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('station_admin_settings'),
      'description' => t('Change settings for the Station module.'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/station/main',
      'title' => t('Core'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('station_admin_settings'),
      'type' => MENU_DEFAULT_LOCAL_TASK,
      'weight' => '-10',
    );

    $items[] = array(
      'path' => 'station',
      'title' => t('Station'),
      'callback' => 'station_page',
      'access' => user_access('access content'),
      'type' => MENU_NORMAL_ITEM
    );
  }
  return $items;
}


/**
 * the modules settings form
 */
function station_admin_settings() {
  $form['station_clock'] = array(
    '#type' => 'radios',
    '#title' => t('Time format'),
    '#options' => array(12 => t('12-hour (am/pm)'), 24 => t('24-hour')),
    '#default_value' => variable_get('station_clock', 12),
    '#description' => t('This setting determines whether times are formatted using a 12 or 24-hour clock.'),
  );

  $form['station_remote'] = array(
    '#type' => 'fieldset',
    '#title' => t('Sub-module Integration'),
    '#description' => t('The Station module is designed to allow different modules to be run on different servers. So the archive could be on a dedicated machine while the schedule runs on your primary webserver.'),
  );

  if (!module_exists('station_archive')) {
    $form['station_remote']['station_remote_archive_url'] = array(
      '#type' => 'textfield',
      '#title' => t('Station Archive remote URL'),
      '#default_value' => variable_get('station_remote_archive_url', ''),
      '#description' => t("The archive module is not enabled on this machine. If you have it installed up on a different Drupal server, enter the URL here. If this is blank no archive links will be provided. Please include the trailing slash: i.e. http://example.com/"),
    );
  }
  else {
    $form['station_remote']['archive_status'] = array(
      '#type' => 'item',
      '#title' => t('Station Archive module is enabled'),
      '#value' => t("The Station Archive module is enabled on this machine. This can be used by the Station Schedule module to provide users links to a program's archived audio."),
    );
  }

  if (!module_exists('station_schedule')) {
    $form['station_remote']['station_remote_schedule_url'] = array(
      '#type' => 'textfield',
      '#title' => t('Station Schedule remote URL'),
      '#default_value' => variable_get('station_remote_schedule_url', ''),
      '#description' => t("The station schedule module is not enabled on this machine. The Station module can connect to another Drupal server running the station schedule module and retrieve information on scheduled programs via XML-RPC. If this is left blank, no connection will be attempted but things may not work. Omit the trailing slash, i.e.: http://example.com or http://example.com/drupal"),
    );
    if ($schedules = station_get_schedules()) {
      $options = array();
      foreach (station_get_schedules() as $nid => $schedule) {
        $options[$nid] = $schedule['title'];
      }
      $form['station_remote']['station_remote_schedule_nid'] = array(
        '#type' => 'select',
        '#title' => t('Schedule'),
        '#default_value' => variable_get('station_remote_schedule_nid', 0),
        '#options' => $options,
        '#description' => t("This you select the schedule that will be used as the default for backwards compatibility."),
      );
    }
    $form['station_remote']['station_remote_schedule_offset'] = array(
      '#type' => 'textfield',
      '#title' => t('Station Schedule remote minute adjustment'),
      '#default_value' => variable_get('station_remote_schedule_offset', 0),
      '#description' => t("If the clocks on your two servers aren't accurately synchronized the archive will end up with the wrong names for programs. This number of minutes (positive or negative) will be added to the time before checking the schedule."),
    );
  }
  else {
    $form['station_remote']['schedule_status'] = array(
      '#type' => 'item',
      '#title' => t('Station Schedule module is enabled'),
      '#value' => t('The Station Schedule module is enabled on this machine. This information can be used by the Now Playing block and Station Archive module.'),
    );
  }

  $form['module_cvs_id'] = array(
    '#type' => 'item',
    '#value' => '<pre>'. STATION_CVS_ID .'</pre>',
  );

  return system_settings_form($form);
}

/**
 * Checks that the RPC endpoint is valid.
 */
function station_admin_settings_validate($form_id, $edit) {
  if (!module_exists('station_schedule')) {
    // check station url
    if ($url = $edit['station_remote_schedule_url']) {
      $url = check_url($url .'/xmlrpc.php');
      $ret = xmlrpc($url, 'station.program.get.at', time(), 0);
      if (xmlrpc_error_msg()) {
        form_set_error('station_remote_schedule_url',
          t('You must provide a a valid URL for a Drupal site with the station schedule module installed.<br />Specific error: %xmlrpc-error', array('%xmlrpc-error' => xmlrpc_error_msg()))
        );
      }
    }

    // check that the offset is an integer
    $offset = $edit['station_remote_schedule_offset'];
    if (!(is_numeric($offset) && intval($offset) == $offset)) {
      form_set_error('station_remote_schedule_offset', t('The offset must be an integer.'));
    }
  }
}


function station_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
  case 'view':
    if (user_access('access content')) {
      switch ($delta) {
        case 0:
          $block['subject'] = t('On Air');
          $block['content'] = station_block_current_program();
          break;
      }
    }
    return $block;

  case 'list':
    $blocks[0]['info'] = t('Station: Current Program');
    return $blocks;
  }
}

function station_page() {
  return theme('menu_tree', menu_get_active_item());
}


/**
 * Determine if we have a station archive module running locally or access to a
 * remote one.
 *
 * @return boolean
 */
function station_has_archive() {
  return (module_exists('station_archive') || variable_get('station_remote_archive_url', FALSE));
}

/**
 * Determine if we have a station schedule module running locally or access to a
 * remote one.
 *
 * @return boolean
 */
function station_has_schedule() {
  return (module_exists('station_schedule') || variable_get('station_remote_schedule_url', FALSE));
}

/**
 * If there's an archive, return the URL.
 *
 * @return FALSE or string URL.
 */
function station_get_archive_url() {
  if (module_exists('station_archive')) {
    return 'station/archives/';
  }
  else if ($url = variable_get('station_remote_archive_url', FALSE)) {
    return $url .'station/archives/';
  }
  return FALSE;
}

/**
 * Return a list of schedules.
 *
 * The list of schedules is cached between calls.
 *
 * @return Array keyed to nid of schedules or FALSE on error.
 */
function station_get_schedules() {
  static $schedules;

  if (!isset($schedules)) {
    if (module_exists('station_schedule')) {
      $schedules = station_schedule_get_list();
    }
    else {
      // If they haven't provided a URL we can't retreive any data.
      $url = variable_get('station_remote_schedule_url', '');
      if (empty($url)) {
        return FALSE;
      }

      // Try to load the schedules from the cache.
      $cacheid = 'station_remote:schedules_'. $url;
      if ($cache = cache_get($cacheid, 'cache')) {
        $schedules = unserialize($cache->data);
      }
      else {
        $schedules = xmlrpc(check_url($url .'/xmlrpc.php'), 'station.schedule.get.list');
        if (xmlrpc_errno()) {
          watchdog('station', t('Failed to load schedule info remotely. Error %code : %message', array('%code' => xmlrpc_errno(), '%message' => xmlrpc_error_msg())), WATCHDOG_ERROR);
          return FALSE;
        }
        // Save the value for the next call.
        cache_set($cacheid, 'cache', serialize($schedules), CACHE_TEMPORARY);
      }
    }
  }

  return $schedules;
}

/**
 * Return the default schedule.
 *
 * @return Array with schedule info.
 */
function station_default_schedule() {
  if (module_exists('station_schedule')) {
    $id = variable_get('station_schedule_default', 0);
  }
  else {
    $id = variable_get('station_remote_schedule_nid', 0);
  }
  $schedules = station_get_schedules();
  return $schedules[$id];
}

/**
 * Return an object for the current program from the local machine or RPC if
 * that's not available.
 *
 * @param $time
 *   A GMT timestamp.
 * @param $schedule_nid
 *   Schedule Id, 0 will load the default schedule.
 * @return
 *   FALSE if there was an error loading the data, NULL if nothing could be
 *   found, or, a program object if everything worked out.
 */
function station_get_program_at($timestamp, $schedule_nid) {
  // If no schedule was provided, use the default.
  if (empty($schedule_nid)) {
    $schedule = station_default_schedule();
    $schedule_nid = $schedule['nid'];
  }

  // Force the params to integers, the xmlrpc() call gets pissy if an int isn't
  // typed as an int.
  $schedule_nid = (int) $schedule_nid;
  $timestamp = (int) $timestamp; 

  // Use the local schedule if one is available.
  if (module_exists('station_schedule')) {
    $program = station_schedule_program_get_at($timestamp, $schedule_nid);
    if ($program->nid) {
      return $program;
    }
  }
  else {
    // Try to connect to a remote schedule via XMLRPC for program information.
    // The info will be cached to cut down on repeated RPC calls.

    // If they haven't provided a url we can't retreive any data
    $url = variable_get('station_remote_schedule_url', '');
    if (empty($url)) {
      return FALSE;
    }

    // add in our magic offset
    $timestamp += 60 * variable_get('station_remote_schedule_offset', 0);

    // round the time to the nearest 15 minute increment so we can do some
    // caching
    $parts = getdate($timestamp);
    $minutes = $parts['minutes'];
    if ($minutes < 15)
      $minutes = 0;
    else if ($minutes < 30)
      $minutes = 15;
    else if ($minutes < 45)
      $minutes = 30;
    else
      $minutes = 45;
    $timestamp = mktime($parts['hours'], $minutes, 0, $parts['mon'], $parts['mday'], $parts['year']);

    // try to grab it from the cache
    $cacheid = 'station_remote:program_at_'. $timestamp;
    if ($cache = cache_get($cacheid, 'cache')) {
      $program = unserialize($cache->data);
    }
    else {
      // if it isn't cached get the program info from the server
      $program = xmlrpc(check_url($url .'/xmlrpc.php'), 'station.program.get.at', $timestamp, $schedule_nid);
      if (xmlrpc_errno()) {
        watchdog('station', t('Failed to load program info remotely. Error %code : %message', array('%code' => xmlrpc_errno(), '%message' => xmlrpc_error_msg())), WATCHDOG_ERROR);
        return FALSE;
      }
      // save it to the cache
      cache_set($cacheid, 'cache', serialize($program), CACHE_TEMPORARY);
    }

    // program returned by XMLRPC is an array
    if ($program['nid']) {
      return (object) $program;
    }
  }

  return NULL;
}

/**
 * Return HTML body of the block listing the current program.
 *
 * @return string
 */
function station_block_current_program() {
  $schedule = station_default_schedule();
  $program = station_get_program_at(time(), $schedule['nid']);
  return theme('station_block_current_program', $schedule, $program);
}

/**
 * Theme the current program block.
 *
 * @param $schedule
 *   Schedule array returned by station_default_schedule().
 * @param $program
 *   Program node object.
 * @return string
 */
function theme_station_block_current_program($schedule, $program) {
  // Program or unscheduled...
  if ($program) {
    $output = l($program->title, $program->node_url) .'<br />';
  }
  else {
    $output = check_plain($schedule['unscheduled_message']) .'<br />';
  }

  // Streams
  $output .= theme('station_streams', $schedule['nid'], $schedule['streams']);

  return $output;
}

/**
 * Theme a schedule's web streams.
 *
 * @param $nid
 *   A station schedule node id (might be on a remote site).
 * @param $streams
 *   Schedule's streams.
 */
function theme_station_streams($schedule_nid, $streams) {
  if (count($streams)) {
    $schedules = station_get_schedules();
    $site_url = $schedules[$schedule_nid]['base_url'];

    $items = array();
    foreach ($streams as $key => $stream) {
      $m3u_url =  "$site_url/node/$schedule_nid/$key.m3u";
      $items[] = l($stream['name'], $m3u_url, array('title' => $stream['description']));
    }

    return t('Tune in: ') . station_ored_list($items);
  }
}


/**
 * Convert an array to a comma separates list with an 'add' between the last
 * terms.
 *
 * @param $array array of items
 */
function station_anded_list($array) {
  switch (count($array)) {
    case 0:
      return '';
    case 1:
      return array_pop($array);
    default:
      $last = array_pop($array);
      return implode(', ', $array) . t(' and ') . $last;
  }
}

/**
 * Convert an array to a comma separates list with an 'or' between the last
 * terms.
 *
 * @param $array array of items
 */
function station_ored_list($array) {
  switch (count($array)) {
    case 0:
      return '';
    case 1:
      return array_pop($array);
    default:
      $last = array_pop($array);
      return implode(', ', $array) . t(' or ') . $last;
  }
}

/**
 * Return a timezone corrected timestamp.
 */
function station_local_ts($ts = FALSE) {
  $ts = ($ts === FALSE) ? time() : $ts;
  return ($ts - date('Z', $ts)) + variable_get('date_default_timezone', 0);
}

/**
 * Return the timezone corrected day of the week (1-7).
 */
function station_today() {
  return date('w', station_local_ts());
}

/**
 * Function to send notices of station changes via hook_station_notices.
 */
function _station_send_notice($type, $op, $data) {
  module_invoke_all('station_notice', $type, $op, $data);
}

/**
 * Implementation of hook_station_notices to display simple notices about
 * station changes.
 * @param $type 'dj' or 'schedule'
 * @param $op 'add', 'remove', 'change' - Change only applies to schedule
 *    items.
 * @param $data associative array with details like: nid, sid, uid.
 */
function station_station_notice($type, $op, $data) {
  if ($type == 'dj') {
    $pid = $data['program_nid'];
    $uid = $data['uid'];
    switch ($op) {
      case 'add':
        drupal_set_message("$sid adding $uid to $nid");
        break;
      case 'remove':
        drupal_set_message("$sid removing $uid from $nid");
        break;
    }
  }
  elseif ($type == 'schedule') {
    $iid = $data['iid'];
    $sid = $data['schedule_nid'];
    $pid = $data['program_nid'];
    switch ($op) {
      case 'add':
        drupal_set_message("$sid adding $iid to $pid");
        break;
      case 'change':
        drupal_set_message("$sid changing $iid on $pid");
        break;
      case 'remove':
        drupal_set_message("$sid removing $iid from $pid");
        break;
    }
  }
}
; $Id: station_archive.info,v 1.4 2007/08/10 17:13:15 drewish Exp $
name = Station Archive
description = Provides an archive of the station's webstream.
dependencies = audio station taxonomy views views_rss
package = Station
<?php

// $Id: station_archive.install,v 1.11 2007/10/29 15:38:03 drewish Exp $

/**
 * Install the initial schema.
 */
function station_archive_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("
        CREATE TABLE {station_archive_item} (
          `audio_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `program_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `aired` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `imported` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `permanent` int(2) UNSIGNED NOT NULL DEFAULT '0',
          PRIMARY KEY(`audio_nid`, `program_nid`),
          KEY `station_archive_cleanup` (`permanent`,`imported`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      db_query("
        CREATE TABLE {station_archive_program} (
          `program_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `title` VARCHAR(128) NOT NULL DEFAULT '',
          PRIMARY KEY(`program_nid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      break;
  }
}

/**
 * Implementation of hook_uninstall().
 */
function station_archive_uninstall() {
  // Remove our tables.
  db_query('DROP TABLE {station_archive_item}');
  db_query('DROP TABLE {station_archive_program}');

  // Remove our taxonomy.
  if ($vid = variable_get('station_archive_vid', '')) {
    taxonomy_del_vocabulary($vid);
  }

  // Remove our variables.
  $variables = array(
    'station_archive_cleanup_old',
    'station_archive_delete_unscheduled',
    'station_archive_import_new',
    'station_archive_import_dir',
    'station_archive_max_age',
    'station_archive_promote_scheduled',
    'station_archive_title_format',
    'station_archive_unscheduled_title',
    'station_archive_vid',
  );
  foreach ($variables as $variable) {
    variable_del($variable);
  }
}

/**
 * Create the table and migrate in the tables
 */
function station_archive_update_1() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      // create a table
      $ret[] = update_sql("
        CREATE TABLE {station_archive} (
          `audio_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `program_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `imported` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `permanent` INT(2) NOT NULL DEFAULT '0',
          PRIMARY KEY(`audio_nid`, `program_nid`),
          KEY `station_archive_cleanup` (`permanent`,`imported`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      // if we've got a taxonomy, record any nodes in it in the table
      if ($vid = _station_archive_get_vid()) {
        $ret[] = update_sql('INSERT INTO {station_archive} (audio_nid, program_nid, imported)
          SELECT DISTINCT n.nid, 0, n.created FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid INNER JOIN {term_data} td ON tn.tid = td.tid
          WHERE td.vid = '. (int) $vid);
      }

      break;
  }
  return $ret;
}

/**
 * Create a table to track program titles
 */
function station_archive_update_2() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      // create a table
      $ret[] = update_sql("
        CREATE TABLE {station_archive_program} (
          `program_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0,
          `title` VARCHAR(128) NOT NULL DEFAULT '',
          PRIMARY KEY(`program_nid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      // find titles for programs by looking for two different audio nodes with the same title
      $ret[] = update_sql("
        INSERT INTO {station_archive_program} (program_nid, title)
        SELECT DISTINCT s1.program_nid, n1.title
        FROM {station_archive} s1 JOIN {station_archive} s2 ON s1.program_nid = s2.program_nid
        JOIN {node} n1 ON s1.audio_nid = n1.nid
        JOIN {node} n2 ON s2.audio_nid = n2.nid
        WHERE n1.title = n2.title AND n1.nid <> n2.nid;
      ");

      break;
  }
  return $ret;
}

/**
 * Add a field to track permanent audio, and an index for the clean up query
 */
function station_archive_update_3() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("
        ALTER TABLE {station_archive}
          ADD COLUMN `permanent` INT(2) UNSIGNED NOT NULL DEFAULT 0 AFTER `imported`,
          ADD INDEX `station_archive_cleanup`(`permanent`, `imported`);
      ");
      break;
  }
  return $ret;
}

/**
 * Empty update to set a schema number
 */
function station_archive_update_100() {
  return array();
}

/**
 * Correct the station archive's {vocabulary}.module value.
 */
function station_archive_update_5200() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("UPDATE {vocabulary} SET module = 'station_archive' WHERE module = 'stationarchive'");
      break;
  }
  return $ret;
}

/**
 * Add rename {station_archive} to {station_archive_item} and add a .aired
 * column to record the node's original air date. Use the node's creation date
 * as a default value.
 */
function station_archive_update_5201() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_archive} RENAME TO {station_archive_item}");
      $ret[] = update_sql("ALTER TABLE {station_archive_item} ADD COLUMN `aired` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `program_nid`");
      $ret[] = update_sql("UPDATE {station_archive_item} sai INNER JOIN {node} n ON sai.audio_nid = n.nid SET sai.aired = n.created WHERE n.type = 'audio'");
      break;
  }
  return $ret;
}
<?php

define('STATION_ARCHIVE_CVS_ID', '$Id: station_archive.module,v 1.39 2007/11/21 15:57:47 drewish Exp $');

if (module_exists('views')) {
  require_once(drupal_get_path('module', 'station_archive') .'/views.inc');
  require_once(drupal_get_path('module', 'station_archive') .'/views_defaults.inc');
}

/**
 * Implementation of hook_help().
 */
function station_archive_help($section = '') {
  switch ($section) {
    case 'admin/settings/station/archive':
      return t("These settings let you configure the way the station's webstream is archived into audio nodes. Audio is imported into the archive as part of Drupal's cron task. You'll need to ensure that that is properly configured. ");
    default:
      return NULL;
  }
}

/**
 * Implementation of hook_menu().
 */
function station_archive_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    // legacy menus, views is where it's at now...
    $items[] = array(
      'path' => 'last',
      'title' => t('Show archive'),
      'callback' => 'station_archive_view_html',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'last/hours',
      'callback' => 'station_archive_view_hours_html',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'rss',
      'title' => t('RSS show archive'),
      'callback' => 'station_archive_view_rss',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'rss/hours',
      'callback' => 'station_archive_view_hours_rss',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
    // end legacy menu items

    $items[] = array('path' => 'admin/settings/station/archive',
      'title' => t('Archive'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('station_archive_admin_settings'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_LOCAL_TASK
    );
  }

  return $items;
}

/**
 * Implementation of hook_perm().
 */
function station_archive_perm() {
  return array(
    'administer archive',
  );
}

function station_archive_view_html($dayname = NULL, $hourname = NULL) {
  if (!is_null($dayname)) {
    $day = station_valid_day($dayname);
    $vid = _station_archive_get_vid();
    if ($dayterm = _station_archive_get_day_term($vid, $day)) {
      $tid = $dayterm['tid'];

      if (!is_null($hourname)) {
        $hour = station_valid_hour($hourname);
        if ($hourterm = _station_archive_get_hour_term($dayterm, $hour)) {
          $tid = $hourterm['tid'];
        }
      }
      drupal_goto('taxonomy/term/'. $tid);
    }
  }
  drupal_not_found();
}

function station_archive_view_rss($dayname = NULL, $hourname = NULL) {
  if (!is_null($dayname)) {
    $day = station_valid_day($dayname);
    $vid = _station_archive_get_vid();
    if ($dayterm = _station_archive_get_day_term($vid, $day)) {
      $tid = $dayterm['tid'];

      if (!is_null($hourname)) {
        $hour = station_valid_hour($hourname);
        if ($hourterm = _station_archive_get_hour_term($dayterm, $hour)) {
          $tid = $hourterm['tid'];
        }
      }

      drupal_goto('taxonomy/term/'. $tid .'/0/feed');
    }
  }
  drupal_not_found();
}

function station_archive_view_hours_html($hour_string) {
  $title = t('Shows on...');
  $rss_url = url('rss/hours/'. str_replace(' ', '+', $hour_string));

  $attributes = array(
    'rel' => 'alternate',
    'type' => 'application/rss+xml',
    'title' => t('RSS - %title', array('%title' => $title)),
    'href' => $rss_url,
  );
  drupal_add_link($attributes);
  drupal_set_title($title);

  $vid = _station_archive_get_vid();
  $tids = _station_archive_get_hour_tids($vid, $hour_string);
  $result = taxonomy_select_nodes($tids, 'or', 0, FALSE);
  while ($node = db_fetch_object($result)) {
    $output .= node_view(node_load($node), 1);
  }
  $output .= theme('feed_icon', $rss_url);

  return $output;
}

function station_archive_view_hours_rss($hour_string) {
  $title = t('Shows on...');
  $desc = t('Description of shows...');

  $vid = _station_archive_get_vid();
  $tids = _station_archive_get_hour_tids($vid, $hour_string);
  $result = taxonomy_select_nodes($tids, 'or', 'all', FALSE);

  $channel['title'] = $title;
  $channel['description'] = $desc;

  return node_feed($result, $channel);
}

/**
 * Implementation of hook_block().
 */
function station_archive_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
  case 'view':
    if (user_access('access content')) {
      switch($delta) {
        case 1:
          $block['subject'] = t('Browse by day');
          $block['content'] = _station_archive_browse_block();
          break;
      }
      return $block;
    }
    break;

  case 'list':
    $blocks[1]['info'] = t('Station Archive: Browse by taxonomy');
    return $blocks;
  }
}

/**
 * Return HTML for browsing the taxonomy.
 */
function _station_archive_browse_block() {
  $vid = _station_archive_get_vid();
  $tree = taxonomy_get_tree($vid);
  if ($tree) {
    foreach ($tree as $term) {
      if ($term->depth == 0) {
        $items[] = l($term->name, 'taxonomy/term/'. $term->tid);
      }
    }
    return theme('item_list', $items);
  }
}

/**
 * Archive settings form.
 */
function station_archive_admin_settings() {
  $times = array(86400, 172800, 259200, 345600, 432000, 518400, 604800, 1209600, 1814400, 2419200, 3024000, 3628800, 4233600, 4838400, 5443200, 6048000);
  $ageoptions = drupal_map_assoc($times, 'format_interval');

  $form['import'] = array(
    '#type' => 'fieldset',
    '#title' => t('Import'),
    '#collapsible' => FALSE,
  );
  $form['import']['station_archive_import_new'] = array(
    '#type' => 'checkbox',
    '#title' => t('Import new audio'),
    '#default_value' => variable_get('station_archive_import_new', 1),
    '#description' => t("If this is checked, audio files left in the import directory will be added to the archive. The only reason to uncheck this is if you're running into a bug and want to stop imports until it can be fixed."),
  );
  $form['import']['station_archive_import_dir'] = array(
    '#type' => 'textfield',
    '#title' => t('Import path'),
    '#default_value' => variable_get('station_archive_import_dir', drupal_get_path('module', 'station_archive') .'/import'),
    '#after_build' => array('system_check_directory'),
    '#required' => TRUE,
    '#description' => t('The directory where new MP3s are left by the stream ripper script. Drupal will need to have write access to this directory so we can move the file.'),
  );
  $form['import']['title_format']['station_archive_title_format'] = array(
    '#type' => 'textfield',
    '#title' => t('Title format'),
    '#default_value' => variable_get('station_archive_title_format', '[audio-tag-title-raw]'),
    '#required' => TRUE,
    '#description' => t('This will be used as the title format for the audio nodes. You can use the tokens listed below to insert information into the title. <strong>Note:</strong> it is safe to use the -raw tokens.'),
  );
  $form['import']['title_format']['token_help'] = array(
    '#title' => t('Token list'),
    '#type' => 'fieldset',
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => t('This is a list of the tokens that can be used in the title of audio nodes.'),
    'help' => array('#value' => theme('token_help', 'node')),
  );
  $form['import']['scheduled'] = array(
    '#type' => 'fieldset',
    '#title' => t('Scheduled'),
    '#collapsible' => FALSE,
    '#description' => t("These settings apply to audio recorded during a time when a program is scheduled. The program's information will be used to fill in the audio node's title, artist and genre tags."),
  );
  $form['import']['scheduled']['station_archive_promote_scheduled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Promote scheduled audio'),
    '#default_value' => variable_get('station_archive_promote_scheduled', 1),
    '#description' => t("If this is checked, audio nodes recorded during scheduled programs will be promoted to the front page."),
  );
  $form['import']['unscheduled'] = array(
    '#type' => 'fieldset',
    '#title' => t('Uncheduled'),
    '#collapsible' => FALSE,
    '#description' => t("These settings apply to audio recorded during a time when no program is scheduled. "),
  );
  $form['import']['unscheduled']['station_archive_delete_unscheduled'] = array(
    '#type' => 'checkbox',
    '#title' => t('Discard unscheduled audio'),
    '#default_value' => variable_get('station_archive_delete_unscheduled', 0),
    '#description' => t("If this is checked, any audio recorded during an unscheduled time will be deleted rather than imported."),
  );
  $form['import']['unscheduled']['station_archive_unscheduled_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Unscheduled audio title'),
    '#default_value' => variable_get('station_archive_unscheduled_title', t('DJ Auto mix')),
    '#required' => TRUE,
    '#description' => t("This value will be used for the audio node's title tag field when importing audio recorded during an unscheduled time."),
  );

  $form['cleanup'] = array(
    '#type' => 'fieldset',
    '#title' => t('Clean up'),
    '#collapsible' => FALSE,
  );
  $form['cleanup']['station_archive_cleanup_old'] = array(
    '#type' => 'checkbox',
    '#title' => t('Remove old audio'),
    '#default_value' => variable_get('station_archive_cleanup_old', 1),
    '#description' => t("If this is checked, audio that's older than the maximum age will be removed."),
  );
  $form['cleanup']['station_archive_max_age'] = array(
    '#type' => 'select',
    '#title' => t('Maximum age'),
    '#default_value' => variable_get('station_archive_max_age', 604800),
    '#options' => $ageoptions,
    '#description' => t("Audio that's been in the archive longer than this will be removed."),
    '#required' => TRUE,
  );

  $form['module_cvs_id'] = array(
    '#type' => 'item',
    '#value' => '<pre>'. STATION_ARCHIVE_CVS_ID .'</pre>',
  );

  return system_settings_form($form);
}

/**
 * Implementation of hook_nodeapi().
 */
function station_archive_nodeapi(&$node, $op, $teaser, $page) {
  if ($node->type == 'audio'){

    switch ($op) {
      case 'prepare':
        // if they clicked the button, reload the program information
        if (t('Reload Program') == $_POST['op']) {
          $gmt_timestamp = $node->station_archive['aired'];
          // get a timestamp that's correct for the timezone
          $local_timestamp = station_local_ts($gmt_timestamp);
          $prettydate = date('ga \o\n M jS, Y', $local_timestamp);

          // connect to the station module and find program info based on timestamp
          $schedule = station_default_schedule();
          $program = station_get_program_at($gmt_timestamp, $schedule['nid']);
          if ($program === FALSE) {
            // couldn't get the program info, configuation error?
            drupal_set_error(t("Couldn't load the program metadata from the schedule."));
            break;
          }
          elseif ($program === NULL) {
            // nothing is scheduled...
            // if we've got no program use the default metadata
            $program_nid = 0;
            $audio_tags['title'] = variable_get('station_archive_unscheduled_title', t('DJ Auto mix'));
          }
          else {
            // we've got program information
            $program_nid = $program->nid;
            $audio_tags['title'] = $program->title;
            $audio_tags['artist'] = station_anded_list($program->djs);
            $audio_tags['genre'] = $program->genre;
            $audio_tags['url_source'] = $program->node_url;
          }
          $audio_tags['year'] = date('Y', $local_timestamp);
          $audio_tags['comment'] = t('Recorded at @date', array('@date' => $prettydate));

          // audio metadata tags
          $_POST['audio_tags'] = $audio_tags;
          // archive program nid (this won't work if there isn't already a copy
          // of the program in station_archive_program).
          $_POST['station_archive']['program_nid'] = $program_nid;
          // taxonomy
          $vid = _station_archive_get_vid();
          $_POST['taxonomy'][$vid] = _station_archive_get_taxonomy($local_timestamp);
          // promotion
          $_POST['promote'] = (int) ($program && variable_get('station_archive_promote_scheduled', 1));

          drupal_set_message(t("The program information has been reloaded from the schedule. Verify that it is correct and save the changes."));
        }
        break;

      case 'validate':
        break;

      case 'update':
        if (isset($node->station_archive) && user_access('administer archive')) {
          // Remove any existing records.
          db_query("DELETE FROM {station_archive_item} WHERE audio_nid = %d", $node->nid);
          // If this should be in the archive, create a new record.
          if ($node->station_archive['archived']) {
            if (empty($node->station_archive['aired'])) {
              $node->station_archive['aired'] = (integer) $node->created;
            }
            if (empty($node->station_archive['imported'])) {
              $node->station_archive['imported'] = time();
            }
            db_query("INSERT INTO {station_archive_item} (audio_nid, program_nid, aired, imported, permanent) VALUES (%d, %d, %d, %d, %d)", $node->nid, $node->station_archive['program_nid'], $node->station_archive['aired'], $node->station_archive['imported'], $node->station_archive['permanent']);
          }
        }
        break;

      case 'delete':
        // remove our tracking records when the audio node is deleted. we only
        // assume that audio node are on this machine, the program nodes could
        // be on a remote server.
        db_query('DELETE FROM {station_archive_item} WHERE audio_nid = %d', $node->nid);
        break;

      case 'load':
        // Sneak the '1 AS archived' in so we've got a value to indicate that
        // this node is in the archive. 
        $row = db_fetch_array(db_query("SELECT 1 AS archived, sa.program_nid, sa.aired, sap.title AS program_title, sa.imported, sa.permanent FROM {station_archive_item} sa LEFT OUTER JOIN {station_archive_program} sap ON sa.program_nid = sap.program_nid WHERE sa.audio_nid = %d", $node->nid));
        if ($row) {
          return array('station_archive' => $row);
        }
        break;

      case 'view':
        // If it's in the archives, set the menu appropriately
        if ($page && !$teaser && isset($node->station_archive)) {
          $breadcrumb[] = array('path' => 'station', 'title' => t('Station'));
          $breadcrumb[] = array('path' => 'station/archives', 'title' => t('Archives'));
          $breadcrumb[] = array('path' => 'station/archives/'. $node->station_archive['program_nid'], 'title' => $node->station_archive['program_title']);
          $breadcrumb[] = array('path' => 'node/'. $node->nid);
          menu_set_location($breadcrumb);

          // Provide information to admins on the import and deletion dates.
          if (user_access('administer archive')) {
            $node->content['station_archive'] = array(
              '#type' => 'fieldset',
              '#title' => t('Station Archive Admin Info'),
              '#collapsible' => TRUE,
              '#collapsed' => TRUE,
              station_archive_node_info_form($node),
            );
          }
        }

        break;
    }
  }
}

/**
 * Get an array containing information about programs.
 *
 * @return
 *   An array keyed by program ID, with each program stored as an object.
 */
function station_archive_get_programs() {
  $programs = array();
  $result = db_query('SELECT sap.program_nid AS nid, sap.title FROM {station_archive_program} sap ORDER BY sap.title');
  while ($obj = db_fetch_object($result)) {
    $programs[$obj->nid] = $obj;
  }
  return $programs;
}


/**
 * Implementation of hook_form_alter().
 */
function station_archive_form_alter($form_id, &$form) {
  // We only alter audio node edit forms
  if ($form_id == 'audio_node_form') {
    $node = $form['#node'];

    if (isset($node->station_archive) || user_access('administer archive')) {
      $form['station_archive'] = array(
        '#type' => 'fieldset',
        '#title' => t('Station Archive'),
        '#collapsible' => TRUE,
        '#collapsed' => (!$node->station_archive['archived'] || !isset($node->audio_file)),
        '#weight' => -5,
        '#tree' => TRUE,
        station_archive_node_info_form($node),
      );
      if (user_access('administer archive')) {
        $program_options = array();
        foreach (station_archive_get_programs() as $nid => $program) {
          $program_options[$nid] = $program->title;
        }
        $form['station_archive']['archived'] = array(
          '#type' => 'checkbox',
          '#title' => t('In Station Archive'),
          '#default_value' => $node->station_archive['archived'],
          '#description' => t("If this is checked, this then the station archive module will associate this with a program."),
          '#weight' => -1,
        );
        $form['station_archive']['program_nid'] = array(
          '#type' => 'select',
          '#title' => t('Recording of'),
          '#options' => $program_options,
          '#default_value' => (int) $node->station_archive['program_nid'],
          '#description' => t('Select the program that this audio is a recording of.')
        );
        $form['station_archive']['refresh'] = array(
          '#type' => 'fieldset',
          '#title' => t('Reload program information'),
          '#description' => t("This lets you refresh the information about which program was playing at this time. This can be useful when the program information was incorrectly retreived or retreived before changes were made to the schedule."),
        );
        // put a reload button on the form so the admin can refresh program
        // information that was retreived incorrectly or before changes were
        // made to the schedule. this is processed in station_archive_nodeapi()
        // under the $op == 'prepare'.
        $form['station_archive']['refresh']['reload'] = array(
          '#type' => 'button',
          '#value' => t('Reload Program'),
        );
        $form['station_archive']['permanent'] = array(
          '#type' => 'checkbox',
          '#title' => t('In permanent archive'),
          '#default_value' => $node->station_archive['permanent'],
          '#description' => t("If this is checked, this recording will not be deleted during the Station Archive's clean up of old recordings."),
        );
      }
    }
  }
}

/**
 * Create form elements to display the station archive information for an audio
 * node.
 * 
 * @param $node
 *   Audio node object.
 * @return
 *   Array of form information.
 */
function station_archive_node_info_form($node) {
  if (isset($node->station_archive)) {
    $form['station_archive']['aired'] = array(
      '#type' => 'item',
      '#title' => t('Air date'),
      '#value' => format_date($node->station_archive['aired'], 'large'),
      '#description' => t("The date the program first aired."),
      '#weight' => -1,
    );
    $form['station_archive']['imported'] = array(
      '#type' => 'item',
      '#title' => t('Imported on'),
      '#value' => format_date($node->station_archive['imported'], 'large'),
      '#description' => t('This is the date the audio was actually imported to the archive.'),
      '#weight' => 0,
    );
    if (variable_get('station_archive_cleanup_old', 1)) {
      if ($node->station_archive['permanent']) {
        $form['station_archive']['deletion'] = array(
          '#type' => 'item',
          '#title' => t('Deletion not scheduled'),
          '#description' => t("This audio is currently in the permanent archive and will not be removed as part of the normal cleanup process."),
          '#weight' => 1,
        );
      }
      else {
        $max_age = variable_get('station_archive_max_age', 604800);
        $delete = $node->station_archive['imported'] + $max_age;
        $form['station_archive']['deletion'] = array(
          '#type' => 'item',
          '#title' => t('Deletion scheduled for'),
          '#value' => format_date($delete, 'large'),
          '#description' => t("The administrator has specified that archived recordings will be removed after being posted for %interval.", array('%interval' => format_interval($max_age))),
          '#weight' => 1,
        );
      }
    }
  }
  return $form;
}

/**
 * Implementation of hook_cron().
 *
 * Remove old files, import new ones.
 */
function station_archive_cron() {
  // HACK: we overwrite the user so that the cron user can get around the
  // permissions check and delete old audio nodes.
  global $user;
  $olduser = $user;
  $user = user_load(array('uid' => 1));

  if (variable_get('station_archive_cleanup_old', 1) == 1) {
    _station_archive_delete_old_nodes();
  }
  if (variable_get('station_archive_import_new', 1) == 1) {
    _station_archive_import_files();
  }

  _station_archive_update_program_list();

  // restore the old user so we don't screw something else up in the cron run
  $user = $olduser;
}

/**
 * Find and delete old archived audio nodes.
 *
 * We need to make sure we only delete nodes created by this module. So, we
 * search look at nodes included in our taxonomy.
 */
function _station_archive_delete_old_nodes() {
  $cutoff = time() - variable_get('station_archive_max_age', 604800);
  $result = db_query('SELECT n.nid, n.title FROM {node} n INNER JOIN {station_archive_item} sa ON n.nid = sa.audio_nid WHERE sa.permanent = 0 AND sa.imported < %d', $cutoff);
  while ($node = db_fetch_object($result)) {
    watchdog('stationarchive', t('Removing %title from the archive.', array('%title' => $node->title)), WATCHDOG_NOTICE);
    node_delete($node->nid);
  }
}

/**
 * Scan the import directory looking for MP3s to import.
 */
function _station_archive_import_files() {
  // locate new mp3s in the import directory
  if ($dirpath = variable_get('station_archive_import_dir', drupal_get_path('module', 'station_archive') .'/import')) {
    $files = file_scan_directory($dirpath, '[0-9]+\.(mp3|ogg)$');
    foreach ($files as $file) {
      // try to avoid php's script timeout with a bunch of large files or
      // a slow machine
      set_time_limit(0);

      $cuefile = dirname($file->filename) .'/'. $file->name .'.cue';
      // if there's a .cue file, it's still being downloaded, we'll get it on
      // the next cron call.
      if (!file_exists($cuefile)) {
        watchdog('stationarchive', t('Attempting to import %filename into the archive.', array('%filename' => $file->filename)), WATCHDOG_NOTICE);
        $status = _station_archive_add_file($file);
        // if there was a problem stop the import
        if (!$status) {
          return;
        }
      }
    }
  }
}

/**
 * Update the entries in the {station_archive_program} table.
 *
 * Views uses the {station_archive_program} to group audio nodes to what may be
 * remote programs. This function keeps it up to date. 
 */
function _station_archive_update_program_list() {
  // Use the local schedule if one is available.
  if (module_exists('station_schedule')) {
    $programs = station_schedule_get_program_list();
  }
  else {
    // Try to connect to a remote schedule via XMLRPC for program information.
    // If they haven't provided a url we can't retreive any data.
    if (!$url = variable_get('station_remote_schedule_url', '')) {
      return FALSE;
    }
    $programs = xmlrpc(check_url($url .'/xmlrpc.php'), 'station.program.get.list');
    if (xmlrpc_errno()) {
      watchdog('station_archive', t('Failed to load program listing remotely. Error %code : %message', array('%code' => xmlrpc_errno(), '%message' => xmlrpc_error_msg())), WATCHDOG_ERROR);
      return FALSE;
    }
  }

  // Insert a program for unschedule times. 
  $programs[0] = array(
    'nid' => 0,
    'title' => variable_get('station_archive_unscheduled_title', t('DJ Auto mix')),
  );

  foreach ($programs as $program) {
    db_query("DELETE FROM {station_archive_program} WHERE program_nid = %d", $program['nid']);
    db_query("INSERT INTO {station_archive_program} (program_nid, title) VALUES (%d, '%s')", $program['nid'], $program['title']);
  }
}

 /**
 * Add a file to the archive.
 *
 * Create an audio node for an mp3 or ogg file that is named with an hour's
 * timestamp. Use the timestamp to load the program information.
 *
 * @param $file
 *   Drupal file object.
 * @return
 *   Boolean indicating success.
 */
function _station_archive_add_file($file) {
  // extract the timestamp from the filename (should be simply timestamp.mp3)
  $basename = explode('.', $file->basename);
  $gmt_timestamp = (int) $basename[0];
  $file_format = $basename[1];
  // get a timestamp that's correct for the timezone
  $local_timestamp = station_local_ts($gmt_timestamp);
  $prettydate = date('ga \o\n M jS, Y', $local_timestamp);

  // connect to the station module and find program info based on timestamp
  $schedule = station_default_schedule();
  $program = station_get_program_at($gmt_timestamp, $schedule['nid']);
  if ($program === FALSE) {
    // couldn't get the program info, configuation error?
    watchdog('stationarchive', t("Couldn't load the program metadata. We'll retry the import on the next cron run."), WATCHDOG_WARNING);
    return FALSE;
  }
  elseif ($program == NULL) {
    // nothing is scheduled...
    // ...check if they've elected to delete unscheduled programs, do so.
    if (variable_get('station_archive_delete_unscheduled', 0)) {
      file_delete($file->filename);
      watchdog('stationarchive', t('Deleted audio that was recorded during unscheduled time. Filename %filename', array('%filename' => $file->filename)), WATCHDOG_NOTICE);
      return TRUE;
    }
    // if we've got no program use the default metadata
    $program_nid = 0;
    $program_title = variable_get('station_archive_unscheduled_title', t('DJ Auto mix'));
  }
  elseif (!$program->may_archive) {
    // The program scheduled at this time is marked as non-archivable. We'll
    // delete the file and log it.
    file_delete($file->filename);
    watchdog('stationarchive', t('Deleted %program-title, it was marked as non-archivable.', array('%program-title' => $program->title)), WATCHDOG_NOTICE);
    return TRUE;
  }
  else {
    // we've got program information
    $program_nid = (int) $program->nid;
    $program_title = $program->title;
    $audio_tags['artist'] = station_anded_list($program->djs);
    $audio_tags['genre'] = $program->genre;
    $audio_tags['url_source'] = $program->node_url;
  }
  $audio_tags['title'] = $program_title;
  $audio_tags['year'] = date('Y', $local_timestamp);
  $audio_tags['comment'] = t('Recorded at @date', array('@date' => $prettydate));

  // then create a new audio node with it
  $title_format = variable_get('station_archive_title_format', '[audio-tag-title-raw]');
  $node = audio_api_insert($file->filename, $title_format, $audio_tags);

  // make any other changes to the node...
  $node->station_archive = array(
    'archived' => 1,
    'program_nid' => $program_nid,
    'aired' => $gmt_timestamp,
    'imported' => time(),
    'permanent' => 0,
  );
  $node->audio_file['downloadable'] = 1;
  $node->created = $gmt_timestamp;
  // find/create the proper taxonomic terms based on day and hour
  $node->taxonomy = _station_archive_get_taxonomy($local_timestamp);
  // don't bother to check for promotion if there's no metadata
  $node->promote = (int) ($program && variable_get('station_archive_promote_scheduled', 1));
  // keep the timestamp as the filename so we can re-import again if needed but
  // store a more readable filename for the user.
  $node->audio_file['file_name'] = str_replace(' ', '_', $audio_tags['title'] ." ($prettydate).$file_format");

  // ... and save it
  $node = node_submit($node);
  node_save($node);

  watchdog('stationarchive', t('Added %title to the archive.', array('%title' => $program_title)), WATCHDOG_NOTICE, l(t('view'), 'node/'. $node->nid));

  return TRUE;
}

/**
 * Find/create the term ids for a day and hour based on a timestamp.
 *
 * @param $local_timestamp
 *   A timestamp that's been adjusted for the local timezone. see station_local_ts().
 * @return
 *   Array with day and hour term ids.
 */
function _station_archive_get_taxonomy($local_timestamp) {
  $vid = _station_archive_get_vid();
  $day = date('w', $local_timestamp);
  $hour = date('G', $local_timestamp);
  $minute = station_minute_from_day_hour($day, $hour);
  $time = station_time_from_minute($minute);

  $dayterm = _station_archive_get_day_term($vid, $day);
  if (!$dayterm) {
    $dayterm = array(
      'vid' => $vid,
      'name' => $time['w'],
      'weight' => $day - 7,
    );
    taxonomy_save_term($dayterm);
  }

  $hourterm = _station_archive_get_hour_term($dayterm, $hour);
  if (!$hourterm) {
    $hourterm = array(
      'vid' => $dayterm['vid'],
      'parent' => $dayterm['tid'],
      'name' => $time['time'] . $time['a'],
      'weight' => $hour,
    );
    taxonomy_save_term($hourterm);
  }

  return array($dayterm['tid'], $hourterm['tid']);
}

/**
 * Find or create a Station Archive vocabulary ID.
 *
 * @return
 *   Vocabulary ID.
 */
function _station_archive_get_vid() {
  $vid = variable_get('station_archive_vid', '');
  if (empty($vid)) {
    // Check to see if a stationarchive vocabulary exists
    $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module = '%s'", 'station_archive'));
    if (!$vid) {
      $vocab = array(
        'name' => 'Station Archive',
        'description' => t("This vocabulary is used by the Station Archive module to track the audio nodes it has added."),
        'help' => t("Do not add nodes to this vocabulary unless you want the station archive module to delete them when they're older than the maximum archive age."),
        'multiple' => 1,
        'required' => 0,
        'hierarchy' => 1,
        'relations' => 0,
        'module' => 'station_archive',
        'nodes' => array('audio' => 1),
      );
      taxonomy_save_vocabulary($vocab);
      $vid = $vocab['vid'];
    }
    variable_set('station_archive_vid', $vid);
  }
  return $vid;
}

/**
 * Implementation of hook_taxonomy().
 * 
 * Delete our vocabulary variable if the vocabulary is deleted.
 */
function station_archive_taxonomy($op, $type, $object = NULL) {
  if ($op == 'delete' && $type == 'vocabulary' && $object->vid == _station_archive_get_vid())  {
    variable_del('station_archive_vid');
  }
}

/**
 * Give a string with hours return an array of vocabulary term ids for the
 * given hours.
 *
 * @param $vid
 *   integer vocabulary id
 * @param $hour_string
 *   string with hours values, integers from 1 to 168 separated by spaces
 */
function _station_archive_get_hour_tids($vid, $hour_string = '') {
  $ids = array();
  foreach (explode(' ', $hour_string) as $id) {
    $id = (integer) $id;
    if ($id > 0 && $id <= 24*7) {
      $ids[] = $id;
    }
  }
  sort($ids);

  $tids = array();
  foreach ($ids as $id) {
    $day = (integer) (($id - 1) / 24);
    $hour = ($id - 1) % 24;

    if ($dayterm = _station_archive_get_day_term($vid, $day)) {
      if ($hourterm = _station_archive_get_hour_term($dayterm, $hour)) {
        $tids[] = $hourterm['tid'];
      }
    }
  }

  return $tids;
}

/**
 * Find the taxonomy term for a day.
 *
 * @param $vid
 *   Our vocabulary ID.
 * @param $day
 *   Integer day between 0 and 6.
 * @return
 *   Array with a day's taxonomy term, FALSE if it doesn't exist.
 */
function _station_archive_get_day_term($vid, $day) {
  $dayname = station_day_name($day);
  $result = db_query(db_rewrite_sql("SELECT t.tid, t.* FROM {term_data} t WHERE t.vid = %d AND LOWER('%s') LIKE LOWER(name)", 't', 'tid'), array($vid, trim($dayname)));
  return db_fetch_array($result);
}

/**
 * Find or create taxonomy term for a day.
 *
 * @param $dayterm
 *   Array day taxonomy term.
 * @param $hour
 *   Integer hour between 0 and 23.
 * @return
 *   Array with an hour's taxonomy term, false if it doesn't exist.
 */
function _station_archive_get_hour_term($dayterm, $hour) {
  $minute = station_minute_from_day_hour(0, $hour);
  $time = station_time_from_minute($minute);
  $hourname = $time['time'] . $time['a'];
  $result = db_query(db_rewrite_sql("SELECT d.tid, d.* FROM {term_data} d INNER JOIN {term_hierarchy} h ON d.tid = h.tid WHERE d.vid = %d AND h.parent = %d AND LOWER('%s') LIKE LOWER(name)", 't', 'tid'), array($dayterm['vid'], $dayterm['tid'], trim($hourname)));
  return db_fetch_array($result);
}

<?php

// $Id: views.inc,v 1.12 2007/11/27 01:04:39 drewish Exp $

/**
 * Implementation of hook_views_tables().
 */
function station_archive_views_tables() {
  $tables['station_archive_item'] = array(
    'name' => 'station_archive_item',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid'
      ),
      'right' => array(
        'field' => 'audio_nid'
      )
    ),
    'fields' => array(
      'aired' => array(
        'name' => t('Station Archive: Air date'),
        'sortable' => TRUE,
        'handler' => views_handler_field_dates(),
        'option' => 'string',
        'help' => t('Date the audio was aired.'),
      ),
      'imported' => array(
        'name' => t('Station Archive: Import date'),
        'sortable' => TRUE,
        'handler' => views_handler_field_dates(),
        'option' => 'string',
        'help' => t('Date the audio was imported into the station archive.'),
      ),
    ),
    'filters' => array(
      'permanent' => array(
        'name' => t('Station Archive: Is in permanent archive'),
        'operator' => array('=' => 'Equals'),
        'list' => 'views_handler_operator_yesno',
        'list-type' => 'select',
        'help' => t('Filter weather the node is part of the permanent archive.'),
      ),
    ),
  );
  $tables['station_archive_program'] = array(
    'name' => 'station_archive_program',
    'join' => array(
      'left' => array(
        'table' => 'station_archive_item',
        'field' => 'program_nid'
      ),
      'right' => array(
        'field' => 'program_nid'
      )
    ),
    'fields' => array(
      'title' => array(
        'name' => t('Station Archive: Program title'),
        'sortable' => TRUE,
        'handler' => 'station_archive_handler_audio_program_title_field',
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'link' => 'As link',
             'nolink' => 'Without link'
           ),
        ),
        'addlfields' => array('program_nid'),
        'help' => t('The program that this audio is a recording of.'),
      ),
    ),
  );

  return $tables;
}

/**
 * Implementation of hook_views_arguments().
 */
function station_archive_views_arguments() {
  $arguments = array(
    'station_archive_program_nid' => array(
      'name' => t('Station Archive: Audio by Program'),
      'handler' => 'station_archive_arg_program_nid_handler',
      'help' => t('The Program NID argument allows visitors to filter archived audio to those of the specified program.'),
    ),
  );
  return $arguments;
}


/**
 * Handle the program node id argument.
 */
function station_archive_arg_program_nid_handler($op, &$query, $argtype, $arg = '') {
  switch($op) {
    case 'summary':
      // add these up front to avoid a wacky bug with rewriting
      $query->ensure_table('node');
      $query->add_field('nid', 'node');
      // now onto our regularly scheduled program
      $query->ensure_table('station_archive_item');
      $query->ensure_table('station_archive_program');
      $query->add_field('title', 'station_archive_program');
      $query->add_field('program_nid', 'station_archive_program');
      $query->add_where('station_archive_program.program_nid IS NOT NULL');
      $fieldinfo['field'] = "station_archive_program.title";
      return $fieldinfo;
    case 'sort':
      $query->ensure_table('station_archive_program');
      $query->add_orderby('station_archive_program', 'title', $argtype);
      break;
    case 'filter':
      $query->ensure_table("station_archive_item");
      $query->add_where("station_archive_item.program_nid = %d", $arg);
      break;
    case 'link':
      return l($query->title, "$arg/" . intval($query->program_nid));
    case 'title':
      $node = db_fetch_object(db_query("SELECT sap.title FROM {station_archive_program} sap WHERE sap.program_nid=%d", $query));
      return check_plain($node->title);
  }
}

function station_archive_handler_timestamp_field($fieldinfo, $fielddata, $value, $data) {
  return l(format_date($value, 'custom', 'j-n-y'), "node/$data->nid");
}

function station_archive_handler_audio_program_title_field($fieldinfo, $fielddata, $value, $data) {
  $schedule = station_default_schedule();
  if ($fielddata['options'] == 'link' && isset($schedule['base_url']) && !empty($data->station_archive_program_program_nid)) {
    return l($value, $schedule['base_url'] .'/node/'. $data->station_archive_program_program_nid);
  }
  return check_plain($value);
}<?php

// $Id: views_defaults.inc,v 1.11 2007/08/22 17:23:29 drewish Exp $

/**
 * Implementation of hook_views_default_views().
 */
function station_archive_views_default_views() {
  $view = new stdClass();
  $view->name = 'station_archive_audio';
  $view->description = 'Archived audio';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Archive';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = 'Sorry, no audio could be found for this program.';
  $view->page_empty_format = '1';
  $view->page_type = 'teaser';
  $view->url = 'station/archives';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '30';
  $view->menu = TRUE;
  $view->menu_title = 'Archive';
  $view->menu_tab = FALSE;
  $view->menu_tab_weight = '0';
  $view->menu_tab_default = FALSE;
  $view->menu_tab_default_parent = NULL;
  $view->menu_tab_default_parent_type = 'tab';
  $view->menu_parent_tab_weight = '0';
  $view->menu_parent_title = '';
  $view->sort = array (
    array (
      'tablename' => 'node',
      'field' => 'created',
      'sortorder' => 'DESC',
      'options' => 'normal',
    ),
  );
  $view->argument = array (
    array (
      'type' => 'station_archive_program_nid',
      'argdefault' => '4',
      'title' => 'Archive of %1',
      'options' => '',
      'wildcard' => 'all',
      'wildcard_substitution' => 'all programs',
    ),
    array (
      'type' => 'rss_feed',
      'argdefault' => '2',
      'title' => '',
      'options' => '',
      'wildcard' => '',
      'wildcard_substitution' => '',
    ),
  );
  $view->field = array (
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'audio',
),
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(node);
  $views[$view->name] = $view;





  $view = new stdClass();
  $view->name = 'station_archive_audio_rss';
  $view->description = 'Station Archive';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Program Archive';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = '';
  $view->page_empty_format = '1';
  $view->page_type = 'views_rss';
  $view->url = 'station/archives/rss';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '20';
  $view->sort = array (
    array (
      'tablename' => 'node',
      'field' => 'created',
      'sortorder' => 'DESC',
      'options' => 'normal',
    ),
  );
  $view->argument = array (
    array (
      'type' => 'station_archive_program_nid',
      'argdefault' => '2',
      'title' => 'Archive of %1',
      'options' => '',
      'wildcard' => 'all',
      'wildcard_substitution' => 'all programs',
    ),
  );
  $view->field = array (
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'audio',
),
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(node);
  $views[$view->name] = $view;



  return $views;
}
This is the default import directory for the archive. It needs to be writable
by Drupal for the import to work.; $Id: ripper.inc,v 1.5 2007/10/26 21:35:44 drewish Exp $
; These are the settings for the stream ripper script.

; Complete URL of webstream to rip
stream_url = "http://127.0.0.1:8000/high"

; Full path to the stream ripper executable, version 1.61.17 or higher.
streamripper_path = "/usr/local/bin/streamripper"

; Full path to station archive module's import directory (no trailing slash)
import_path = "/usr/local/www/drupal/sites/all/modules/station/archive/import"

; Because shows tend to start early or finish late, the overlap_seconds
; setting allows you to determine how much the recording should overlap at the
; end of the hour. The suggestion is to schedule the script to start one minute
; before the hour and use 60 seconds of overlap. That will result in a 62 minute
; recording, with one minute before the hour and an extra minute at the end.
overlap_seconds = 60

; The file type of the stream; "mp3" and "ogg" are supported. 
file_format = "mp3"
#!/usr/local/bin/php -q
<?php

// $Id: ripper.php,v 1.9 2007/10/29 15:19:38 drewish Exp $

/**
 * This script is designed to be run by a cron job or windows scheduled task.
 * It connects to a webstream, downloads one hours worth and saves it as an
 * MP3 for the station archive module to import.
 *
 *     Y O U   D O N ' T   N E E D   T O   E D I T   T H I S   F I L E !
 *       Configuration options are now set in the ripper.inc file.
 *
 * This script requires that Streamripper (http://streamripper.sourceforge.net/)
 * version 1.61.17 or higher be installed (we use the --quite option)
 */

// Make sure we're being run from the command line.
if (isset($_SERVER['REMOTE_ADDR'])) {
  exit("This script must be called from the command line.\n");
}

// Load our settings from the ripper.inc file.
$incfile = realpath(dirname(__FILE__) .'/ripper.inc');
if (!file_exists($incfile)) {
  exit("Cannot read the ripper.inc file from {$incfile}.\n");
}
$settings = parse_ini_file($incfile);

// Check that the import directory is writable.
$import_dir = realpath($settings['import_path']);
if (!is_dir($import_dir) || !is_writable($import_dir)) {
  exit("Cannot write to the import directory '{$import_dir}'.\n");
}

// Make sure we can find stream ripper. The is_executable() test might not work
// with PHP4 and Windows. Upgrade! PHP 5.1 is great.
$streamripper = realpath($settings['streamripper_path']);
if (!file_exists($streamripper) || !is_executable($streamripper)) {
  exit("Couldn't find the stream ripper executable at '{$streamripper}'.\n");
}

// Determine when we're starting, when we should end and convert that to a
// length of time in seconds.
$startTime = roundToNearestHour(time());
$endTime = $startTime + 3600;
$length = ($endTime - time()) + (int) $settings['overlap_seconds'];

// Download the stream
$stream_url = $settings['stream_url'];
$file_format = strtolower($settings['file_format']);
exec("{$streamripper} {$stream_url} -s -d {$import_dir} -A -l {$length} -a {$startTime}.{$file_format} --quiet");

// stream ripper creates the .cue file. we'll use its absence as a signal
// to the module that it's safe to import a file.
$cuefile = "{$import_dir}/{$startTime}.cue";
if (file_exists($cuefile)) {
  unlink($cuefile);
}

exit(0);


/**
 * Round a timestamp to the nearest hour.
 *
 * @param $time
 *   A UNIX timestamp
 */
function roundToNearestHour($time) {
  $parts = getdate($time);
  if ($parts['minutes'] > 50) {
    // advance it to the next hour
    $roundedTime = mktime($parts['hours'] + 1, 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
  }
  else {
    // we're late for this hour
    $roundedTime = mktime($parts['hours'], 0, 0, $parts['mon'], $parts['mday'], $parts['year']);
  }
  return $roundedTime;
}

?>
; $Id: station_catalog.info,v 1.5 2007/08/28 21:19:42 drewish Exp $
name = Station Catalog
description = Module to track albums in a radio station's library. 
package = Station
<?php

// $Id: station_catalog.install,v 1.11 2007/11/09 06:46:57 drewish Exp $

/**
 * Install the initial schema.
 */
function station_catalog_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("
        CREATE TABLE {station_catalog} (
          `nid` int(10) unsigned NOT NULL default '0',
          `number` int(10) unsigned NOT NULL default '0',
          `artist` varchar(255) NOT NULL default '',
          `album` varchar(255) NOT NULL default '',
          `year` int(10) unsigned NOT NULL,
          `label` varchar(255) NOT NULL default '',
          `mb_release_id` varchar(36) NOT NULL default  '',
          `asin` varchar(16) NOT NULL default '',
          PRIMARY KEY  (`nid`),
          UNIQUE KEY `station_catalog_number` (`number`),
          KEY `station_catalog_artist` (`artist`),
          KEY `station_catalog_album` (`album`),
          KEY `station_catalog_year` (`year`),
          KEY `station_catalog_label` (`label`),
          KEY `mb_release` (`mb_release_id`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      break;
  }
}

/**
 * Implementation of hook_uninstall().
 */
function station_catalog_uninstall() {
  db_query('DROP TABLE {station_catalog}');

  if ($vid = variable_get('station_catalog_vocabulary', FALSE)) {
    taxonomy_del_vocabulary($vid);
  }
  variable_del('station_catalog_vocabulary');
  variable_del('station_catalog_redirect_on_add');
}

/**
 * Empty update to set a schema number for Drupal 5.0.
 */
function station_catalog_update_100() {
  return array();
}

/**
 * Change the album node name from 4.7 to 5.0. 
 */
function station_catalog_update_101() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("UPDATE {node} SET type = 'station_album' WHERE type='album';");
      $ret[] = update_sql("UPDATE {node_type} SET type = 'station_album' WHERE type='album';");
    break;
  }
  return $ret;
}

/**
 * Remove the autonumber from the nid column and add a year column.
 */
function station_catalog_update_5200() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_catalog} 
        MODIFY COLUMN `nid` INTEGER UNSIGNED NOT NULL DEFAULT 0;
      ");
      $ret[] = update_sql("ALTER TABLE {station_catalog} 
        ADD COLUMN `year` INTEGER UNSIGNED NOT NULL AFTER `album`,
        ADD INDEX station_catalog_year(`year`);
      ");
    break;
  }
  return $ret;
}

/**
 * Add MusicBrainz release id field and Amazon ASIN field.
 */
function station_catalog_update_5201() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_catalog} 
        ADD COLUMN `mb_release_id` VARCHAR(36) NOT NULL DEFAULT '' AFTER `label`,
        ADD INDEX mb_release(`mb_release_id`);
      ");
      $ret[] = update_sql("ALTER TABLE {station_catalog} 
        ADD COLUMN `asin` varchar(16) NOT NULL default '' AFTER `mb_release_id`;
      ");

    break;
  }
  return $ret;
}

/**
 * Rename some incorrectly named variables.
 */
function station_catalog_update_5202() {
  if ($val = variable_get('station_category_vocabulary', FALSE)) {
    variable_set('station_catalog_vocabulary', $val);
  }

  if ($val = variable_get('station_category_redirect_on_add', FALSE)) {
    variable_set('station_catalog_redirect_on_add', $val);
  }
  
  variable_del('station_category_vocabulary');
  variable_del('station_category_redirect_on_add');

  return array();
}<?php
// $Id: station_catalog.module,v 1.27 2007/11/09 06:46:57 drewish Exp $

define('STATION_CATALOG_CVS_ID', '$Id: station_catalog.module,v 1.27 2007/11/09 06:46:57 drewish Exp $');

if (module_exists('views')) {
  require_once(drupal_get_path('module', 'station_catalog') .'/views.inc');
#  require_once(drupal_get_path('module', 'station_catalog') .'/views_defaults.inc');
}

/**
 * Implementation of hook_help().
 */
function station_catalog_help($section='') {
  switch ($section) {
    case 'admin/settings/station/catalog':
      return t("These settings allow you to configure the station catalog.");
  }
}

/**
 * Implementation of hook_menu().
 */
function station_catalog_menu($may_cache) {
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/station/catalog',
      'title' => t('Catalog'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'station_catalog_admin_settings',
      'access' => user_access('administer site configuration'),
      'type' => MENU_LOCAL_TASK
    );

    $items[] = array(
      'path' => 'station/catalog/search',
      'title' => t('Catalog'),
      'callback' => 'station_catalog_search_page',
      'access' => user_access('view catalog'),
      'type' => MENU_NORMAL_ITEM,
    );
  }

  return $items;
}


function station_catalog_admin_settings() {
  $form['station_catalog_redirect_on_add'] = array(
    '#type' => 'checkbox',
    '#title' => t('Add multiple albums'),
    '#default_value' => variable_get('station_catalog_redirect_on_add', TRUE),
    '#description' => t('If this is checked after adding a new album you will be redirected back to the new album form. This is much more convienient when entering multiple albums.'),
  );
  $form['module_cvs_id'] = array(
    '#type' => 'item',
    '#value' => '<pre>'. STATION_CATALOG_CVS_ID .'</pre>',
  );

  return system_settings_form($form);
}


/**
 * Implementation of hook_node_info().
 */
function station_catalog_node_info() {
  return array(
    'station_album' => array(
      'name' => t('Album'),
      'module' => 'station_catalog_album',
      'description' => t("An album in the station's library."),
      'has_title' => FALSE,
      'has_body' => TRUE,
      'body_label' => t('Description'),
    )
  );
}

/**
 * Implementation of hook_perm().
 */
function station_catalog_perm() {
  return array(
    'view catalog',
    'create album content',
    'edit album content',
    'administer catalog',
  );
}

/**
 * Implementation of hook_access().
 */
function station_catalog_album_access($op, $node) {
  global $user;

  // Admins can do anything.
  if (user_access('administer catalog')) {
    return TRUE;
  }
  
  switch ($op) {
    case 'view':
      return user_access('view catalog');

    case 'create':
      return user_access('create album content');

    case 'update':
    case 'delete':
      return user_access('edit album content');
  }
}

/**
 * Returns the the next unused album number.
 * 
 * @return
 *   Integer with the next, unused album number. 
 */
function station_catalog_album_next_number() {
  return db_result(db_query('SELECT MAX(number) + 1 FROM {station_catalog}'));
}

/**
 * Implementation of hook_prepare().
 */
function station_catalog_album_prepare(&$node) {
  if (isset($node->album['artist']) && isset($node->album['album'])) {
    if ($mb_info = _station_catalog_musicbrainz_album($node->album['artist'], $node->album['album'])) {
      $fields = array(
        'artist' => t('Artist'),
        'album' => t('Album'),
        'year' => t('Year'),
        'mb_release_id' => t('Music Brainz ID'),
        'asin' => t('ASIN'),
      );
      $adjusted = array();
      foreach ($fields as $field => $name) {
        if (empty($node->album[$field])) {
          $adjusted[] = $name;
          $node->album[$field] = $mb_info[$field];
        }
      }
      if ($adjusted) {
        drupal_set_message(t('Values for @fields fields were loaded from the MusicBrainz data.', array('@fields' => station_anded_list($adjusted))));
      }
    }
  }
}

/**
 * Implementation of hook_form().
 */
function station_catalog_album_form($node) {
  $type = node_get_types('type', $node);

  // Make it a little easier when doing data entry and redirect back to the
  // add form after submitting a new album.
  if (empty($node->nid) && variable_get('station_catalog_redirect_on_add', TRUE)) {
    $form['#redirect'] = 'node/add/station-album';
  }

  $form['album'] = array(
    '#type' => 'fieldset',
    '#title' => t('Album information'),
    '#tree' => TRUE,
    '#weight' => -4,
  );
  $form['album']['number'] = array(
    '#type' => 'textfield',
    '#title' => t('Number'),
    '#default_value' => isset($node->album['number']) ? $node->album['number'] : station_catalog_album_next_number(),
    '#required' => TRUE,
    '#description' => t("The album's log number must be numeric and unique."),
  );
  $form['album']['artist'] = array(
    '#type' => 'textfield',
    '#title' => t('Artist'),
    '#default_value' => $node->album['artist'],
    '#required' => TRUE,
    '#maxlength' => 255,
  );
  $form['album']['album'] = array(
    '#type' => 'textfield',
    '#title' => t('Album title'),
    '#default_value' => $node->album['album'],
    '#required' => TRUE,
    '#maxlength' => 255,
  );
  $form['album']['year'] = array(
    '#type' => 'textfield',
    '#title' => t('Year'),
    '#default_value' => $node->album['year'],
    '#required' => TRUE,
    '#description' => t("The year the album was released."),
  );
  $form['album']['label'] = array(
    '#type' => 'textfield',
    '#title' => t('Label'),
    '#default_value' => $node->album['label'],
    '#required' => FALSE,
    '#maxlength' => 255,
    '#description' => t("The year the album was released."),
  );
  $form['album']['mb_release_id'] = array(
    '#type' => 'textfield',
    '#title' => t('Music Brainz Release ID'),
    '#default_value' => $node->album['mb_release_id'],
    '#required' => FALSE,
    '#maxlength' => 255,
    '#description' => t("MusicBrainz ID for this release."),
  );
  $form['album']['asin'] = array(
    '#type' => 'textfield',
    '#title' => t('Amazon Standard Identification Number'),
    '#default_value' => $node->album['asin'],
    '#required' => FALSE,
    '#maxlength' => 255,
    '#description' => t("Amazon product ID for this album."),
  );

  if ($type->has_body) {
    $form['body_filter']['body'] = array(
      '#type' => 'textarea',
      '#title' => check_plain($type->body_label),
      '#default_value' => $node->body,
      '#rows' => 10,
      '#required' => ($type->min_word_count > 0),
    );
    $form['body_filter']['format'] = filter_form($node->format);
  }

  return $form;
}

/**
 * Implementation of hook_validate().
 */
function station_catalog_album_validate(&$node, &$form) {
  if (!is_numeric($node->album['number'])) {
    form_set_error('number', t('Album number must be a numeric value.'));
  }
  else if ($node->album['number']) {
    // look for duplicate numbers
    if ($node->nid) {
      $query = db_query('SELECT sc.nid, n.title FROM {station_catalog} sc INNER JOIN {node} n ON sc.nid = n.nid WHERE sc.number = %d AND sc.nid <> %d', $node->album['number'], $node->nid);
    }
    else {
      $query = db_query('SELECT sc.nid, n.title FROM {station_catalog} sc INNER JOIN {node} n ON sc.nid = n.nid WHERE sc.number = %d', $node->album['number']);
    }
    if ($other_node = db_fetch_object($query)) {
      form_set_error(
        'number',
        t('Another album <a href="@link">%title</a> already has this number.', array('@link' => url('node/'. $other_node->nid), '%title' => $other_node->title))
      );
    }
  }

  if (!empty($node->album['year']) && !is_numeric($node->album['year'])) {
    form_set_error('year', t('Year must be a numeric value and %value is not.', array('%value' => ($node->album['year']))));
  }

  // Compute the title.
  $node->title = $node->album['artist'] .' - '. $node->album['album'];
  form_set_value($form['title'], $node->title);
}

/**
 * Implementation of hook_load().
 */
function station_catalog_album_load($node) {
  $result = db_query('SELECT number, artist, album, year, label, mb_release_id, asin FROM {station_catalog} WHERE nid = %d', $node->nid);
  return array('album' => db_fetch_array($result)); 
}

/**
 * Implementation of hook_insert().
 */
function station_catalog_album_insert($node) {
  db_query("INSERT INTO {station_catalog} (nid, number, artist, album, year, label, mb_release_id, asin) VALUES (%d, %d, '%s', '%s', %d, '%s', '%s', '%s')", $node->nid, $node->album['number'], $node->album['artist'], $node->album['album'], $node->album['year'], $node->album['label'], $node->album['mb_release_id'], $node->album['asin']);
}

/**
 * Implementation of hook_delete().
 */
function station_catalog_album_delete($node) {
  db_query("DELETE FROM {station_catalog} WHERE nid = %d", $node->nid);
}

/**
 * Implementation of hook_update().
 */
function station_catalog_album_update($node) {
  db_query("DELETE FROM {station_catalog} WHERE nid = %d", $node->nid);
  db_query("INSERT INTO {station_catalog} (nid, number, artist, album, year, label, mb_release_id, asin) VALUES (%d, %d, '%s', '%s', %d, '%s', '%s', '%s')", $node->nid, $node->album['number'], $node->album['artist'], $node->album['album'], $node->album['year'], $node->album['label'], $node->album['mb_release_id'], $node->album['asin']);
}

/**
 * Implementation of hook_view().
 */
function station_catalog_album_view(&$node, $teaser = FALSE, $page = FALSE) {
  $node = node_prepare($node, $teaser);

  if ($page) {
    $breadcrumb = array();
    $breadcrumb[] = array('path' => 'station/catalog', 'title' => t('Catalog'));
    $breadcrumb[] = array('path' => 'node/'. $node->nid);
    menu_set_location($breadcrumb);
  }

  $node->content['album'] = array(
    'number' => array(
      '#type' => 'item',
      '#title' => t('Number'),
      '#value' => $node->album['number'],
      '#weight' => -6,
    ),
    'artist' => array(
      '#type' => 'item',
      '#title' => t('Artist'),
      '#value' => $node->album['artist'],
      '#weight' => -5,
    ),
    'album' => array(
      '#type' => 'item',
      '#title' => t('Album'),
      '#value' => $node->album['album'],
      '#weight' => -4,
    ),
    'year' => array(
      '#type' => 'item',
      '#title' => t('Year'),
      '#value' => $node->album['year'] ? $node->album['year'] : '',
      '#weight' => -3,
    ),
    'label' => array(
      '#type' => 'item',
      '#title' => t('Label'),
      '#value' => $node->album['label'],
      '#weight' => -2,
    ),
  );

  if (!empty($node->album['mb_release_id'])) {
    $node->content['album']['mb_release_id'] = array(
      '#type' => 'item',
      '#title' => t('MusicBrainz'),
      '#value' => l($node->album['mb_release_id'], "http://musicbrainz.org/release/{$node->album['mb_release_id']}.html"),
      '#weight' => 0,
    );
  }
  if (!empty($node->album['asin'])) {
    $node->content['album']['asin'] = array(
      '#type' => 'item',
      '#title' => t('Amazon'),
      '#value' => l($node->album['asin'], 'http://www.amazon.com/gp/product/'. $node->album['asin']),
      '#weight' => 0,
    );
  }

  return $node;
}

/**
 * Implementation of hook_taxonomy().
 * 
 * Delete our vocabulary variable if the vocabulary is deleted.
 */
function station_catalog_taxonomy($op, $type, $object = NULL) {
  if ($op == 'delete' && $type == 'vocabulary' && $object->vid == _station_catalog_get_vid())  {
    variable_del('station_catalog_vocabulary');
  }
}


/**
 * Find or create a station catalog vocabulary ID.
 *
 * @return
 *   Vocabulary ID.
 */
function _station_catalog_get_vid() {
  $vid = variable_get('station_catalog_vocabulary', '');
  if (empty($vid)) {
    // Check to see if a a  vocabulary exists
    $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module = '%s'", 'station_catalog'));
    if (!$vid) {
      $vocabulary = array(
        'name' => t('Station album genres'),
        'description' => t("Select the appropriate genre's for this album."),
        'multiple' => '1', 
        'required' => '1', 
        'hierarchy' => '0', 
        'relations' => '0', 
        'module' => 'station_catalog', 
        'nodes' => array('station_album' => 1),
      );
      taxonomy_save_vocabulary($vocabulary);
      $vid = $vocabulary['vid'];
    }
    variable_set('station_catalog_vocabulary', $vid);
  }
  return $vid;
}

function station_catalog_search_page($field = '', $value ='') {
  $perpage = 30;

  $header = array(
    'number' => array('data' => t('Number'), 'field' => 'sc.number'),
    'artist' => array('data' => t('Artist'), 'field' => 'sc.artist'),
    'album' => array('data' => t('Album'), 'field' => 'sc.album'),
    'year' => array('data' => t('Year'), 'field' => 'sc.year'),
    'label' => array('data' => t('Label'), 'field' => 'sc.label'),
  );

  // Only search when there's a value and the field is valid.
  if (!empty($value) && in_array($field, array('number', 'artist', 'album', 'year', 'label'), TRUE)) {
    $header[$field]['sort'] = 'asc';
    $result = pager_query(db_rewrite_sql("SELECT n.nid, sc.* FROM {node} n INNER JOIN {station_catalog} sc ON n.nid = sc.nid WHERE LOWER(sc.$field) LIKE LOWER('%%%s%%')"). tablesort_sql($header), $perpage, 0, NULL, $value);
  }
  else {
    $header['number']['sort'] = 'desc';
    $result = pager_query(db_rewrite_sql("SELECT n.nid, sc.* FROM {node} n INNER JOIN {station_catalog} sc ON n.nid = sc.nid"). tablesort_sql($header), $perpage, 0);
  }

  $rows = array();
  while ($item = db_fetch_object($result)) {
    $link = 'node/'. $item->nid;
    $rows[] = array(
      array('data' => l($item->number, $link)),
      array('data' => l($item->artist, $link)),
      array('data' => l($item->album, $link)),
      array('data' => $item->year ? $item->year : ''),
      array('data' => $item->label)
    );
  }
  
  if (!$rows) {
    $rows[] = array(array('colspan' => 4, 'data' => t('No matches were found.')));
  }

  $output .= '<div id="station-catalog-search">';
  $output = drupal_get_form('station_catalog_search_form', $field, $value);
  $output .= '<div id="station-catalog-search-results">';
  $output .= theme('pager', array(), $perpage);
  $output .= theme('table', $header, $rows);
  $output .= theme('pager', array(), $perpage);
  $output .= '</div></div>';
  return $output;
}

function station_catalog_search_form($field = '', $value ='') {
  $form['search'] = array(
    '#type' => 'fieldset',
    '#title' => t('Search options'),
    '#prefix' => '<div class="container-inline">',
    '#suffix' => '</div>',
  );
  $form['search']['field'] = array(
    '#type' => 'select',
    '#title' => t('Field'),
    '#options' => array(
      'artist' => t('Artist'), 
      'album' => t('Album'), 
      'label' => t('Label'),
      'number' => t('Number'),
      'year' => t('Year'),
    ),
    '#default_value' => $field,
  );
  $form['search']['value'] = array(
    '#type' => 'textfield',
    '#title' => t('Value'),
    '#default_value' => $value,
    '#size' => 25,
    '#required' => TRUE,
  );
  $form['search']['filter'] = array(
    '#type' => 'submit', 
    '#value' => t('Filter'),
  );
  if (!empty($value)) {
    $form['search']['reset'] = array(
      '#type' => 'submit',
      '#value' => t('Reset'),
    );
  }
  return $form;
}

function station_catalog_search_form_submit($form_id, $form_values) {
  switch ($form_values['op']) {
    case t('Reset'):
      return 'station/catalog/search/'; 

    case t('Filter'):
      // Make sure it's an allowed search field.
      if (in_array($form_values['field'], array('number', 'artist', 'album', 'year', 'label'))) {
        return 'station/catalog/search/'. $form_values['field'] .'/'. $form_values['value'];
      }
  }
}

function _station_catalog_musicbrainz_album($artist, $title) {
  $args = array(
    'type' => 'xml',
    'artist' => $artist,
    'title' => $title
  );
  $url = url('http://musicbrainz.org/ws/1/release/', drupal_query_string_encode($args));

  $ret = array();

  $response = drupal_http_request($url);
  if ($response->code == 200) {
    $doc = DOMDocument::loadXML($response->data);

    $xpath = new DOMXPath($doc);
    // Can't use the default namespace, must explicitly set one...
    $xpath->registerNameSpace('mb', 'http://musicbrainz.org/ns/mmd-1.0#');
#dvm($doc->saveXML());
    // Only return exact matches.
    $result = $xpath->query('//mb:release-list/mb:release[@ext:score="100"]');
    if ($result && $result->length) {
      foreach ($result as $item) {
        $release = simplexml_import_dom($result->item(0));
#        dvm($release);
      }
      $release = simplexml_import_dom($result->item(0));
      $ret['mb_release_id'] = (string) $release['id'];
      $ret['artist'] = (string) $release->artist->name;
      $ret['album'] = (string) $release->title;
      $ret['asin'] = (string) $release->asin;
      // Release year is a bit of a pain, the tag name isn't a valid PHP 
      // identifier and we need to use a regular expression to match the 
      // year portion of the ate.
      $event = 'release-event-list';
      preg_match('/.*US (\d{4}).*/', $release->$event->event['date'], $matches);
      $ret['year'] = (string) $matches[1];

      // Now that we've got the release id, we need to make another call to get the label.
#      $label = _station_catalog_musicbrainz_label($ret['mb_release_id']);
      $ret['label'] = '';
    }
  }
  return $ret;
}

function _station_catalog_musicbrainz_label($release_id) {
  $args = array(
    'type' => 'xml',
    'inc' => 'labels',
  );
  $url = url('http://musicbrainz.org/ws/1/release/'. $release_id, drupal_query_string_encode($args));
dvm($url);
  $ret = array();
  $response = drupal_http_request($url);
  if ($response->code == 200) {
    $doc = DOMDocument::loadXML($response->data);
dvm($doc->savexml());
  }
  return $ret;
}
<?php
// $Id: views.inc,v 1.5 2007/09/14 18:24:32 drewish Exp $

/**
 * Implementation of hook_views_tables().
 */
function station_catalog_views_tables() {
  $tables['station_catalog'] = array(
    'name' => 'station_catalog',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid'
      ),
      'right' => array(
        'field' => 'nid'
      )
    ),
    'fields' => array(
      'number' => array(
        'name' => t('Station Catalog: Number'),
        'help' => t("Album's log number."),
        'sortable' => TRUE,
      ),
      'artist' => array(
        'name' => t('Station Catalog: Artist'),
        'help' => t("Album artist name."),
        'sortable' => TRUE,
      ),
      'album' => array(
        'name' => t('Station Catalog: Album title'),
        'help' => t("Album time."),
        'sortable' => TRUE,
      ),
      'year' => array(
        'name' => t('Station Catalog: Year'),
        'help' => t("Year the album was released."),
        'sortable' => TRUE,
      ),
      'label' => array(
        'name' => t('Station Catalog: Label'),
        'help' => t("Label that released the album."),
        'sortable' => TRUE,
      ),
      'mb_release_id' => array(
        'name' => t('Station Catalog: Music Brainz ID'),
        'help' => t("MusicBrainz ID for this release."),
        'sortable' => TRUE,
      ),
      'asin' => array(
        'name' => t('Station Catalog: ASIN'),
        'help' => t("Amazon product ID for this album."),
        'sortable' => TRUE,
      ),
    ),
  );

  return $tables;
}
; $Id: station_playlist.info,v 1.5 2007/08/10 17:13:21 drewish Exp $
name = Station Playlist
description = Provides a program playlist node type.
dependencies = station station_program views
package = Station
<?php

// $Id: station_playlist.install,v 1.6 2007/08/10 17:13:21 drewish Exp $

/**
 * Install the initial schema.
 */
function station_playlist_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("
        CREATE TABLE {station_playlist} (
          `nid` int(10) unsigned NOT NULL default '0',
          `program_nid` int(10) unsigned NOT NULL default '0',
          `timestamp` int(11) NOT NULL default '0'
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      db_query("
        CREATE TABLE  {station_playlist_track} (
          `nid` int(10) unsigned NOT NULL default '0',
          `weight` tinyint(4) unsigned NOT NULL default '0',
          `artist` varchar(255) NOT NULL default '',
          `album` varchar(255) NOT NULL default '',
          `title` varchar(255) NOT NULL default '',
          `label` varchar(255) NOT NULL default '',
          `link` varchar(255) NOT NULL default '',
          KEY `station_playlist_artist` (`artist`),
          KEY `station_playlist_nid_weight` (`nid`,`weight`),
          KEY `station_playlist_title` (`title`),
          KEY `station_playlist_album` (`album`),
          KEY `station_playlist_label` (`label`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      break;
  }
}

/**
 * Implementation of hook_uninstall().
 */
function station_playlist_uninstall() {
  db_query('DROP TABLE {station_playlist}');
  db_query('DROP TABLE {station_playlist_track}');

  variable_del('station_playlist_title_dateformat');
  variable_del('station_playlist_program_dateformat');
}

/**
 * Empty update to set a schema number for Drupal 5.0.
 */
function station_playlist_update_100() {
  return array();
}

/**
 * Add a link field to the track.
 */
function station_playlist_update_101() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_playlist_track} ADD COLUMN `link` VARCHAR(255) NOT NULL DEFAULT '' AFTER `label`;");
    break;
  }
  return $ret;
}



<?php

define('STATION_PLAYLIST_CVS_ID', '$Id: station_playlist.module,v 1.13 2007/10/19 15:55:35 drewish Exp $');

if (module_exists('views')) {
  require_once(drupal_get_path('module', 'station_playlist') .'/views.inc');
  require_once(drupal_get_path('module', 'station_playlist') .'/views_defaults.inc');
}


/**
 * Display help and module information
 * @param section which section of the site we're displaying help
 * @return help text for section
 */
function station_playlist_help($section='') {
  switch ($section) {
    case 'admin/settings/station/playlist':
      return t("These settings allow you to configure the playlist node.");
    default:
      return NULL;
  }
}

function station_playlist_menu($may_cache) {
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/station/playlist',
      'title' => t('Playlist'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('station_playlist_admin_settings'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_LOCAL_TASK
    );

    $items[] = array(
      'path' => 'station/autocomplete/playlist',
      'title' => t('Playlist Autocomplete'),
      'callback' => 'station_playlist_autocomplete',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

function station_playlist_node_info() {
  return array(
    'station_playlist' => array(
      'name' => t('Program Playlist'),
      'module' => 'station_playlist',
      'description' => t("A playlist lets your listeners see a list of all the music played during a program. It also allows the music director to easily chart what's being played."),
    )
  );
}

/**
 * Valid permissions for this module
 * @return array An array of valid permissions for the station module
 */
function station_playlist_perm() {
  return array(
    'create playlists',
    'edit own playlists',
  );
}

function station_playlist_admin_settings() {
  $form = array();
  $form['dates'] = array(
    '#type' => 'fieldset',
    '#title' => t('Date formats'),
    '#tree' => FALSE,
    '#description' => t("See PHP's <a href='!date-url'>date()</a> function for more information date formatting. No hour or minute infomation is collected, so it will not be displayed correctly if you attempt to use those in the format.", array('!date-url' => url('http://php.net/date'))),
  );
  $form['dates']['station_playlist_title_dateformat'] = array(
    '#type' => 'textfield',
    '#title' => t('Date format in playlist title'),
    '#default_value' => variable_get('station_playlist_title_dateformat', 'm/d/Y'),
    '#description' => t("The playlist's date is put into the title as a string. This setting lets you control how it is formatted."),
  );
  $form['dates']['station_playlist_program_dateformat'] = array(
    '#type' => 'textfield',
    '#title' => t('Date format on program node'),
    '#default_value' => variable_get('station_playlist_program_dateformat', 'F j, Y'),
    '#description' => t("The playlist's date is also displayed on the the program node. This setting lets you control how it is formatted."),
  );
  $form['module_cvs_id'] = array(
    '#type' => 'item',
    '#value' => '<pre>'. STATION_PLAYLIST_CVS_ID .'</pre>',
  );
  return system_settings_form($form);
}

/**
 * Implementation of hook_link().
 *
 * This is implemented so that an edit link is displayed for users who have
 * the rights to edit a node.
 */
function station_playlist_link($type, $node = NULL, $teaser = FALSE) {
  if ($type == 'node') {
    switch ($node->type) {
      case 'station_program':
        $links = array();
        if ($result = db_result(db_query("SELECT COUNT(nid) FROM {station_playlist} sp WHERE sp.program_nid = %d", $node->nid))) {
          $links['station_station_playlist_view'] = array(
            'title' => t('View all playlists'),
            'href' => 'station/playlists/'. $node->nid,
          );
        }

        if (!$teaser) {
          if (node_access('update', $node)) {
            $links['station_station_playlist_add'] = array(
              'title' => t('Add new playlist'),
              'href' => 'node/add/station-playlist/'. $node->nid,
            );
          }
        }
        return $links;

      case 'station_playlist':
        return array('station_program_program_view' => array(
          'title' => t('View program'),
          'href' => 'node/'. $node->program_nid,
        ));
    }
  }
  return array();
}


function station_playlist_access($op, $node) {
  global $user;

  if (user_access('administer schedule')) {
    return TRUE;
  }

  switch ($op) {
    case 'view':
      return user_access('access content');
      break;

    case 'create':
      return user_access('create playlists');
      break;

    case 'update':
    case 'delete':
      if (user_access('edit own playlists') && ($user->uid == $node->uid)) {
        return TRUE;
      }
      break;
  }
  return FALSE;
}

/**
 * Build a form for playlist nodes.
 */
function station_playlist_form(&$node) {
  // if this is a new node (with no nid) and we've got a numeric argument,
  // assume that's the program we should attach to.
  if (!$node->nid) {
    if (is_numeric(arg(3))) {
      $node->program_nid = (int) arg(3);
    }
    // and new nodes need a default date
    $date = getdate();
    $node->program_date = array(
      'month' => $date['mon'],
      'day' => $date['mday'],
      'year' => $date['year']
    );
  }

  // make sure we've got a valid program title
  if (!isset($node->program_title) && isset($node->program_nid)) {
    $program = node_load(array(
      'nid' => $node->program_nid,
      'type' => 'station_program'
    ));
    $node->program_title = $program->title;
  }

  $form['program_nid'] = array(
    '#type' => 'value',
    '#value' => $node->program_nid,
  );
  $form['program_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Program'),
    '#default_value' => $node->program_title,
    '#maxlength' => 128,
    '#autocomplete_path' => 'station/autocomplete/program',
    '#required' => TRUE,
    '#description' => t('The name of the program. If you filled in for someone else, enter the name of their program.'),
  );
  $form['timestamp'] = array(
    '#type' => 'value',
    '#value' => $node->timestamp,
  );
  $form['program_date'] = array(
    '#type' => 'date',
    '#title' => t('Program date'),
    '#default_value' => $node->program_date,
    '#required' => TRUE,
    '#description' => t('The date the program aired.'),
  );
  $form['tracks'] = array(
    '#type' => 'fieldset', '#title' => t('Tracks'),
    '#tree' => TRUE, '#collapsible' => FALSE, '#weight'=>1,
    '#theme' => 'station_playlist_track_form',
    '#description' => t('Enter the tracks played on the show, the artist and title are required. If you provide a link, it needs to begin with <code>http://</code> or you will get a link to a page on this site.') .'<br />'
      . t('The weight is used to set the order: lower weights rise, heavier sink. If you want to remove a track just clear the artist and title text boxes. If you need more tracks, just save, more will be added. '),
  );
  $form['body_filter']['body'] = array(
    '#type' => 'textarea',
    '#title' => t('Description'),
    '#default_value' => $node->body,
    '#rows' => 5,
  );
  $form['body_filter']['format'] = filter_form($node->format);


  $blanks = 10;
  $weight = 0;
  $max = max(20, $blanks + count($node->tracks) + 1);
  for($i = -1; $i < $max; $i++) {
    $weights[$i] = $i;
  }

  // load current tracks
  foreach ((array)$node->tracks as $key => $track) {
    $form['tracks'][$weight]['artist'] = array(
      '#type' => 'textfield', '#size' => 30, '#maxlength' => 255,
      '#default_value' => $track['artist'],
      '#autocomplete_path' => 'station/autocomplete/playlist/artist',
    );
    $form['tracks'][$weight]['album'] = array(
      '#type' => 'textfield', '#size' => 30, '#maxlength' => 255,
      '#default_value' => $track['album'],
      '#autocomplete_path' => 'station/autocomplete/playlist/album',
    );
    $form['tracks'][$weight]['title'] = array(
      '#type' => 'textfield', '#size' => 30, '#maxlength' => 255,
      '#default_value' => $track['title'],
    );
    $form['tracks'][$weight]['label'] = array(
      '#type' => 'textfield', '#size' => 30, '#maxlength' => 255,
      '#default_value' => $track['label'],
      '#autocomplete_path' => 'station/autocomplete/playlist/label',
    );
    $form['tracks'][$weight]['link'] = array(
      '#type' => 'textfield', '#size' => 30, '#maxlength' => 255,
      '#default_value' => $track['link'],
    );
    $form['tracks'][$weight]['weight'] = array(
      '#type' => 'select', '#options' => $weights, '#default_value' => $weight,
    );
    $weight++;
  }
  // add some empty tracks
  for ($i = 0; $i < $blanks; $i++) {
    $form['tracks'][$weight]['artist'] = array('#type'=>'textfield',
      '#size' => 30, '#maxlength' => 255,
      '#autocomplete_path' => 'station/autocomplete/playlist/artist',
    );
    $form['tracks'][$weight]['album'] = array('#type'=>'textfield',
      '#size' => 30, '#maxlength' => 255,
      '#autocomplete_path' => 'station/autocomplete/playlist/album',
    );
    $form['tracks'][$weight]['title'] = array('#type'=>'textfield',
      '#size' => 30, '#maxlength' => 255,
    );
    $form['tracks'][$weight]['label'] = array('#type'=>'textfield',
      '#size' => 30, '#maxlength' => 255,
      '#autocomplete_path' => 'station/autocomplete/playlist/label',
    );
    $form['tracks'][$weight]['link'] = array('#type'=>'textfield',
      '#size' => 30, '#maxlength' => 255,
    );
    $form['tracks'][$weight]['weight'] = array('#type' => 'select',
      '#options' => $weights, '#default_value' => $weight,
    );
    $weight++;
  }

  return $form;
}

function theme_station_playlist_track_form($form) {
  $header = array(t('Artist'), t('Title'), t('Album'), t('Label'), t('Link'), t('Weight'));

  $rows = array();
  foreach (element_children($form) as $key) {
    $row = array();
    $row[] = drupal_render($form[$key]['artist']);
    $row[] = drupal_render($form[$key]['title']);
    $row[] = drupal_render($form[$key]['album']);
    $row[] = drupal_render($form[$key]['label']);
    $row[] = drupal_render($form[$key]['link']);
    $row[] = drupal_render($form[$key]['weight']);
    $rows[] = $row;
  }

  if (count($rows) == 0) {
    $output = '';
  }
  else {
    $output = theme('table', $header, $rows);
  }

  return $output . drupal_render($form);
}

// use this hook to convert form elements to node values
function station_playlist_validate(&$node, &$form) {
  // convert the programs title into a node id
  if (isset($node->program_title)) {
    $prog = node_load(array('type' => 'station_program', 'title' => $node->program_title));
    if ($prog) {
      $node->program_nid = $prog->nid;
      form_set_value($form['program_nid'], $node->program_nid);
    }
  }
  if (!isset($node->program_nid)) {
    form_set_error('program_title', t('A valid program name must be provided.'));
  }

  // check for track errors
  foreach ($node->tracks as $track) {
    if (($track['album'] || $track['label']) && !($track['artist'] || $track['title'])) {
      form_set_error('tracks]['. $track['weight'] . '][artist', t("You must provide either an artist or a title."));
      form_set_error('tracks]['. $track['weight'] . '][title');
    }
  }

  // convert the date into a timestamp
  $node->timestamp = mktime(0, 0, 0, $node->program_date['month'], $node->program_date['day'], $node->program_date['year']);
  form_set_value($form['timestamp'], $node->timestamp);

  // set the title
  form_set_value($form['title'], t('!program-title playlist for !date', array(
    '!program-title' => $node->program_title,
    '!date' => format_date($node->timestamp, 'custom', variable_get('station_playlist_title_dateformat', 'm/d/Y')),
  )));
}

/**
 * Load a playlist.
 */
function station_playlist_load($node) {
  // id and time
  $extras = db_fetch_object(db_query('SELECT program_nid, timestamp FROM {station_playlist} WHERE nid = %d', $node->nid));

  // program title
  $prog = node_load($extras->program_nid);
  $extras->program_title = $prog->title;

  // program date
  $extras->program_date = array(
    'day' => date('j', $extras->timestamp),
    'month' => date('n', $extras->timestamp),
    'year' => date('Y', $extras->timestamp)
  );

  // tracks
  $extras->tracks = array();
  $result = db_query('SELECT weight, album, artist, title, label, link FROM {station_playlist_track} WHERE nid = %d ORDER BY weight', $node->nid);
  while ($track = db_fetch_array($result)) {
    $extras->tracks[] = $track;
  }

  return $extras;
}

/**
 * Insert a new playlist
 */
function station_playlist_insert($node) {
  db_query("INSERT INTO {station_playlist} (nid, program_nid, timestamp) VALUES (%d, %d, %d)", $node->nid, $node->program_nid, $node->timestamp);
  foreach ($node->tracks as $track) {
    if ($track['artist'] || $track['title']) {
      db_query("INSERT INTO {station_playlist_track} (nid, weight, artist, album, title, label, link) VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s')",
        $node->nid, trim($track['weight']), trim($track['artist']), trim($track['album']), trim($track['title']), trim($track['label']), trim($track['link']));
    }
  }
}

/**
 * Delete a playlist.
 */
function station_playlist_delete($node) {
  db_query('DELETE FROM {station_playlist} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {station_playlist_track} WHERE nid = %d', $node->nid);
}

/**
 * Update a playlist.
 */
function station_playlist_update($node) {
  db_query("UPDATE {station_playlist} SET program_nid = %d, timestamp= %d WHERE nid = %d", $node->program_nid, $node->timestamp, $node->nid);
  // delete and re-add tracks
  db_query('DELETE FROM {station_playlist_track} WHERE nid = %d', $node->nid);
  foreach ($node->tracks as $track) {
    if ($track['artist'] || $track['title']) {
      db_query("INSERT INTO {station_playlist_track} (nid, weight, artist, album, title, label, link) VALUES (%d, %d, '%s', '%s', '%s', '%s', '%s')",
        $node->nid, trim($track['weight']), trim($track['artist']), trim($track['album']), trim($track['title']), trim($track['label']), trim($track['link']));
    }
  }
}

/**
 * Get a playlist ready for viewing.
 */
function station_playlist_view(&$node, $teaser = false, $page = false) {
  $node = node_prepare($node, $teaser);

  if ($page) {
    $breadcrumb = array(
      array('path' => 'station/programs', 'title' => t('Programs')),
      array('path' => 'node/'. $node->program_nid, 'title' => $node->program_title),
      array('path' => 'station/playlists/'. $node->program_nid, 'title' => t('Playlists')),
      array('path' => 'node/'. $node->nid),
    );
    menu_set_location($breadcrumb);
  }

  if (!$teaser) {
    if (count($node->tracks) > 0) {
      $rows = array();
      // make one pass to figure out what fields we've got...
      $hasAlbum = $hasLabel = $hasLink = FALSE;
      foreach ((array) $node->tracks as $track) {
        $hasAlbum |= (bool) ($track['album']);
        $hasLabel |= (bool) ($track['label']);
        $hasLink |= (bool) ($track['link']);
      }
      $header = array(t('Artist'), t('Title'));
      if ($hasAlbum) {
        $header[] = t('Album');
      }
      if ($hasLabel) {
        $header[] = t('Label');
      }
      if ($hasLink) {
        $header[] = t('Link');
      }
      // ...then put the into the table
      foreach ((array) $node->tracks as $track) {
        if ($track['artist'] || $track['title']) {
          $row = array(
            check_plain($track['artist']),
            check_plain($track['title']),
          );
          if ($hasAlbum) {
            $row[] = check_plain($track['album']);
          }
          if ($hasLabel) {
            $row[] = check_plain($track['label']);
          }
          if ($hasLink) {
            if ($track['link']) {
              // Use the host name as a link text
              $parts = parse_url(url($track['link'], NULL, NULL, TRUE));
              $row[] = l($parts['host'], $track['link']);
            }
            else {
              $row[] = '';
            }
          }
          $rows[] = $row;
        }
      }

      $node->content['tracks'] = array(
        '#value' => theme('table', $header, $rows, array('class' => 'station-playlist-tracks')),
        '#title' => 'Tracks',
        '#weight' => 0,
      );
    }
  }
  return $node;
}

/**
 * Implementation of hook_nodeapi().
 */
function station_playlist_nodeapi(&$node, $op, $teaser, $page) {
  if ($node->type == 'station_program'){
    switch ($op) {
      case 'view':
        if (!$teaser) {
          // playlists
          $time = time();
          $time_format = variable_get('station_playlist_program_dateformat', 'F j, Y');

          // pull all future playlists
          $future_lists = array();
          $result = db_query('SELECT p.nid, p.timestamp FROM {station_playlist} p INNER JOIN {node} n ON p.program_nid = n.nid WHERE n.status = 1 AND p.program_nid = %d AND p.timestamp > %d ORDER BY timestamp', $node->nid, $time);
          while ($playlist = db_fetch_object($result)) {
            $future_lists[] = l(format_date($playlist->timestamp, 'custom', $time_format, 0), 'node/'. $playlist->nid);
          }
          if ($future_lists) {
            $node->content['playlists_future'] = array(
              '#type' => 'item',
              '#value' => '<ul><li>'. implode('</li><li>', $future_lists) .'</li></ul>',
              '#title' => 'Upcoming shows',
              '#weight' => 1,
            );
          }

          // pull N recent playlists
          $past_lists = array();
          $result = db_query_range('SELECT p.nid, p.timestamp FROM {station_playlist} p INNER JOIN {node} n ON p.program_nid = n.nid WHERE n.status = 1 AND p.program_nid = %d AND p.timestamp < %d ORDER BY timestamp DESC', $node->nid, $time, 0, 5);
          while ($playlist = db_fetch_object($result)) {
            $past_lists[] = l(format_date($playlist->timestamp, 'custom', $time_format, 0), 'node/'. $playlist->nid);
          }
          if ($past_lists) {
            $node->content['playlists_past'] = array(
              '#type' => 'item',
              '#value' => '<ul><li>'. implode('</li><li>', $past_lists) .'</li></ul>',
              '#title' => 'Previous shows',
              '#weight' => 2,
            );
          }
        }
        break;
    }
  }
}


/**
 * Retrieve a pipe delimited string of autocomplete suggestions for playlist
 * items.
 */
function station_playlist_autocomplete($field = '', $string = '') {
  $matches = array();
  if (in_array($field, array('artist', 'title', 'album', 'label'))) {
    $result = db_query_range('SELECT DISTINCT %s AS val FROM {station_playlist_track} WHERE LOWER(%s) LIKE LOWER("%s%%") ORDER BY val', $field, $field, $string, 0, 10);
    while ($item = db_fetch_object($result)) {
      $matches[$item->val] = check_plain($item->val);
    }
  }
  print drupal_to_js($matches);
  exit();
}
<?php

// $Id: views.inc,v 1.8 2007/11/27 01:04:39 drewish Exp $

/**
 * Implementation of hook_views_tables().
 */
function station_playlist_views_tables() {
  $tables['station_playlist'] = array(
    'name' => 'station_playlist',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid'
      ),
      'right' => array(
        'field' => 'nid'
      )
    ),
    'fields' => array(
      'timestamp' => array(
        'name' => t('Station Playlist: Date'),
        'sortable' => TRUE,
        'handler' => array(
          'station_handler_field_playlist_timestamp_title' => t('Playlist title date format'),
          'station_handler_field_playlist_timestamp_program' => t('Program node date format'),
        ),
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'listlink' => 'As link to playlist',
             'nolink' => 'Without link'
           ),
        ),
        'help' => t('Date of the program.'),
      ),
    ),
    'sorts' => array(
      'timestamp' => array(
        'name' => t('Station Playlist: Date'),
        'help' => t("Sort by the playlist's air date."),
       ),
    ),
    'filters' => array(
      'timestamp' => array(
        'name' => t('Station Playlist: Date'),
        'operator' => 'views_handler_operator_gtlt',
        'value' => views_handler_filter_date_value_form(),
        'handler' => 'views_handler_filter_timestamp',
        'help' => t('Filter depending on the date the program has/will air.'),
      ),
    ),

  );
  $tables['station_playlist_program_node'] = array(
    'name' => 'node',
    'provider' => 'internal',
    'join' => array(
      'left' => array(
        'table' => 'station_playlist',
        'field' => 'program_nid'
      ),
      'right' => array(
        'field' => 'nid'
      )
    ),
    'fields' => array(
      'title' => array(
        'name' => t('Station Playlist: Program Title'),
        'sortable' => TRUE,
        'handler' => 'station_handler_field_playlist_program_title',
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'proglink' => 'As link to program',
             'listlink' => 'As link to playlist',
             'nolink' => 'Without link'
           ),
        ),
        'addlfields' => array('nid'),
        'help' => t('Title of the program.'),
      ),
    ),
  );

  return $tables;
}

/**
 * Implementation of hook_views_arguments().
 */
function station_playlist_views_arguments() {
  $arguments = array(
    'program_nid' => array(
      'name' => t('Station Playlist: Program NID'),
      'handler' => 'station_playlist_arg_program_nid_handler',
      'help' => t('The Program NID argument allows visitors to filter playlists to those attached to the specified program.'),
    ),
  );
  return $arguments;
}


/*
 * Handle the program node id argument.
 */
function station_playlist_arg_program_nid_handler($op, &$query, $argtype, $arg = '') {
  switch($op) {
    case 'summary':
      $query->ensure_table('station_playlist');
      $query->ensure_table('station_playlist_program_node');
      $query->add_field('program_nid', 'station_playlist');
      $query->add_field('title', 'station_playlist_program_node');
      $fieldinfo['field'] = "station_playlist_program_node.title";
      return $fieldinfo;
    case 'sort':
      $query->add_orderby('station_playlist_program_node', 'title', $argtype);
      break;
    case 'filter':
      $query->ensure_table("station_playlist");
      $query->add_where("station_playlist.program_nid = %d", $arg);
      break;
    case 'link':
      return l($query->title, "$arg/" . intval($query->program_nid));
    case 'title':
      $node = db_fetch_object(db_query("SELECT title FROM {node} WHERE nid=%d", $query));
      return check_plain($node->title);
  }
}

function station_handler_field_playlist_program_title($fieldinfo, $fielddata, $value, $data) {
  if ($fielddata['options'] == 'proglink') {
    return l($value, "node/$data->station_playlist_program_node_nid");
  }
  if ($fielddata['options'] == 'listlink') {
    return l($value, "node/$data->nid");
  }
  return check_plain($value);
}

function station_handler_field_playlist_timestamp_title($fieldinfo, $fielddata, $value, $data) {
  $date = format_date($value, 'custom', variable_get('station_playlist_title_dateformat', 'm/d/Y'));
  if ($fielddata['options'] == 'listlink') {
    return l($date, "node/$data->nid");
  }
  return $date;  
}

function station_handler_field_playlist_timestamp_program($fieldinfo, $fielddata, $value, $data) {
  $date = format_date($value, 'custom', variable_get('station_playlist_program_dateformat', 'F j, Y'));
  if ($fielddata['options'] == 'listlink') {
    return l($date, "node/$data->nid");
  }
  return $date;  
}<?php

// $Id: views_defaults.inc,v 1.8 2007/10/19 17:10:02 drewish Exp $

/**
 * Implementation of hook_views_default_views().
 */
function station_playlist_views_default_views() {
  $view = new stdClass();
  $view->name = 'station_playlists';
  $view->description = 'Playlists';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Playlists';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = 'No playlists were found.';
  $view->page_empty_format = '1';
  $view->page_type = 'teaser';
  $view->url = 'station/playlists';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '20';
  $view->menu = TRUE;
  $view->menu_title = 'Playlists';
  $view->menu_tab = FALSE;
  $view->menu_tab_weight = '0';
  $view->menu_tab_default = FALSE;
  $view->menu_tab_default_parent = NULL;
  $view->menu_tab_default_parent_type = 'existing';
  $view->menu_parent_tab_weight = '0';
  $view->menu_parent_title = '';
  $view->block = TRUE;
  $view->block_title = 'Recent playlists';
  $view->block_header = '';
  $view->block_header_format = '1';
  $view->block_footer = '';
  $view->block_footer_format = '1';
  $view->block_empty = '';
  $view->block_empty_format = '1';
  $view->block_type = 'list';
  $view->nodes_per_block = '6';
  $view->block_more = TRUE;
  $view->block_use_page_header = FALSE;
  $view->block_use_page_footer = FALSE;
  $view->block_use_page_empty = FALSE;
  $view->sort = array (
    array (
      'tablename' => 'station_playlist',
      'field' => 'timestamp',
      'sortorder' => 'DESC',
      'options' => '',
    ),
  );
  $view->argument = array (
    array (
      'type' => 'program_nid',
      'argdefault' => '2',
      'title' => '%1 Playlists',
      'options' => '',
      'wildcard' => '',
      'wildcard_substitution' => '',
    ),
  );
  $view->field = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'label' => '',
      'handler' => 'views_handler_field_nodelink',
      'options' => 'link',
    ),
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'station_playlist',
),
    ),
    array (
      'tablename' => 'node',
      'field' => 'status',
      'operator' => '=',
      'options' => '',
      'value' => '1',
    ),
    array (
      'tablename' => 'station_playlist',
      'field' => 'timestamp',
      'operator' => '<=',
      'options' => '',
      'value' => 'now',
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(station_playlist, node);
  $views[$view->name] = $view;



  return $views;
}
; $Id: station_program.info,v 1.4 2007/08/10 17:13:22 drewish Exp $
name = Station Program
description = Provides a simple program node for schedule.
dependencies = station views
package = Station
<?php

// $Id: station_program.install,v 1.6 2007/11/09 07:33:28 drewish Exp $

/**
 * Install the initial schema.
 */
function station_program_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("
        CREATE TABLE {station_program} (
          `nid` int(10) unsigned NOT NULL default '0',
          `vid` int(10) unsigned NOT NULL default '0',
          `genre` varchar(200) NOT NULL default '',
          `url` varchar(255) default NULL ,
          PRIMARY KEY  (`nid`,`vid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      break;
  }
}

/**
 * Implementation of hook_uninstall().
 */
function station_program_uninstall() {
  db_query('DROP TABLE {station_program}');

  variable_del('station_program_redirect_on_add');
}

/**
 * Empty update to set a schema number for Drupal 5.0.
 */
function station_program_update_100() {
  return array();
}

/**
 * Rename the permissions.
 * 
 * First update for the 5.1 branch, using the update naming convention layed
 * out in: http://drupal.org/node/114774#update-n
 */
function station_program_update_5100() {
  $ret = array();

  // Setup arrays holding regexps to match and the corresponding
  // strings to replace them with, for use with preg_replace().
  $old_perms = array(
    '/edit own program/', 
  );
  $new_perms = array(
    'edit own program content', 
  );

  // Now, loop over all the roles, and do the necessary transformations.
  $query = db_query("SELECT rid, perm FROM {permission} ORDER BY rid");
  while ($role = db_fetch_object($query)) {
    $fixed_perm = preg_replace($old_perms, $new_perms, $role->perm);
    if ($fixed_perm != $role->perm) {
      $ret[] = update_sql("UPDATE {permission} SET perm = '$fixed_perm' WHERE rid = $role->rid");
    }
  }

  return $ret;
}

/**
 * Change the program node name to station_program.
 */
function station_program_update_5200() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("UPDATE {node} SET type = 'station_program' WHERE type='program';");
      $ret[] = update_sql("UPDATE {node_type} SET type = 'station_program' WHERE type='program';");
    break;
  }
  return $ret;
}
<?php

define('STATION_PROGRAM_CVS_ID', '$Id: station_program.module,v 1.12 2007/11/30 01:43:42 drewish Exp $');

if (module_exists('views')) {
  require_once(drupal_get_path('module', 'station_program') .'/views.inc');
  require_once(drupal_get_path('module', 'station_program') .'/views_defaults.inc');
}

/**
 * Implementation of hook_help().
 */
function station_program_help($section='') {
  switch ($section) {
    case 'admin/settings/station/program':
      return t("These settings allow you to configure the station's program node.");
    default:
      return NULL;
  }
}

/**
 * Implementation of hook_menu().
 */
function station_program_menu($may_cache) {
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/station/program',
      'title' => t('Program'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('station_program_admin_settings'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_LOCAL_TASK
    );

    $items[] = array(
      'path' => 'station/autocomplete/program',
      'title' => t('program autocomplete'),
      'callback' => 'station_program_autocomplete',
      'access' => user_access('access content'),
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_node_info().
 */
function station_program_node_info() {
  return array(
    'station_program' => array(
      'name' => t('Program'),
      'module' => 'station_program',
      'description' => t('A radio program that you can schedule.'),
    )
  );
}

/**
 * Implementation of hook_perm().
 */
function station_program_perm() {
  return array(
    'edit program content',
    'edit own program content',
    'administer programs',
  );
}

function station_program_admin_settings() {
  $form['station_program_redirect_on_add'] = array(
    '#type' => 'checkbox',
    '#title' => t('Add multiple programs'),
    '#default_value' => variable_get('station_program_redirect_on_add', TRUE),
    '#description' => t('If this is checked after adding a new program node you will be redirected back to the new program form. This is much more convenient when entering multiple programs.'),
  );
  $form['module_cvs_id'] = array(
    '#type' => 'item',
    '#value' => '<pre>'. STATION_PROGRAM_CVS_ID .'</pre>',
  );

  return system_settings_form($form);
}

/**
 * Implementation of hook_access().
 */
function station_program_access($op, $node) {
  global $user;

  switch ($op) {
    case 'view':
      return user_access('access content');
      break;

    case 'update':
      if (user_access('edit program content')) {
        return TRUE;
      }

      // Node owner and DJs can also edit the program.
      $djs = _station_schedule_program_load_djs($node->nid);
      if ($node->uid == $user->uid || isset($djs[$user->uid])) {
        return user_access('edit own program content');
      }
      break;

    case 'delete':
    case 'create':
    default:
      // If the schedule is enabled, defer to that permision.
      if (module_exists('station_schedule')) {
        return user_access('administer schedule');
      }
      return user_access('administer programs');
  }
}

/**
 * Implementation of hook_form().
 */
function station_program_form(&$node) {
  $type = node_get_types('type', $node);

  // Make it a little easier when doing data entry and redirect back to the
  // add form after submitting a new program.
  if (empty($node->nid) && variable_get('station_program_redirect_on_add', TRUE)) {
    $form['#redirect'] = 'node/add/station-program';
  }

  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => check_plain($type->title_label),
    '#default_value' => $node->title,
    '#required' =>  TRUE,
    '#maxlength' =>  128,
    '#description' => t('The name of the program. You should avoid making major changes to this without telling the programming director.'),
  );
  $form['genre'] = array(
    '#type' => 'textfield',
    '#title' => t('Text genre'),
    '#description' => t('A free-form description of the musical genres played on the program.'),
    '#default_value' => $node->genre,
    '#required' =>  true,
    '#maxlength' => 200,
    '#weight' => -2,
  );
  $form['url'] = array(
    '#type' => 'textfield',
    '#title' => t('Homepage URL'),
    '#description' => t("Link to the program's website."),
    '#default_value' => $node->url,
    '#maxlength' =>  255,
    '#weight' => -1,
  );
  if ($type->has_body) {
    $form['body_filter']['body'] = array(
      '#type' => 'textarea',
      '#title' => check_plain($type->body_label),
      '#default_value' => $node->body,
      '#rows' => 10,
      '#required' => ($type->min_word_count > 0),
      '#description' => t("Free form description of the show."),
    );
    $form['body_filter']['format'] = filter_form($node->format);
  }

  return $form;
}

/**
 * Implementation of hook_validate().
 */
function station_program_validate(&$node) {
  // check the url
  if ($node->url) {
    if (!valid_url($node->url)) {
      form_set_error('url', t('You need to provide a valid URL.'));
    }
  }
}

/**
 * Implementation of hook_load().
 */
function station_program_load($node) {
  return db_fetch_object(db_query('SELECT genre, url FROM {station_program} WHERE vid = %d', $node->vid));
}

/**
 * Implementation of hook_insert().
 */
function station_program_insert($node) {
  db_query("INSERT INTO {station_program} (nid, vid, genre, url) VALUES (%d, %d, '%s', '%s')", $node->nid, $node->vid, $node->genre, $node->url);
}

/**
 * Implementation of hook_delete().
 */
function station_program_delete($node) {
  db_query('DELETE FROM {station_program} WHERE nid = %d', $node->nid);
}

/**
 * Implementation of hook_update().
 */
function station_program_update($node) {
  if ($node->revision){
    db_query("INSERT INTO {station_program} (nid, vid, genre, url) VALUES (%d, %d, '%s', '%s')", $node->nid, $node->vid, $node->genre, $node->url);
  }
  else {
    db_query("UPDATE {station_program} SET genre = '%s', url = '%s' WHERE vid = %d", $node->genre, $node->url, $node->vid);
  }
}

/**
 * Implementation of hook_nodeapi().
 *
 * Use this to handle deletion of revisions.
 */
function station_program_nodeapi(&$node, $op, $arg) {
  if ($node->type == 'station_program') {
    switch ($op) {
      case 'delete revision':
        db_query('DELETE FROM {station_program} WHERE vid = %d', $node->vid);
        break;
    }
  }
}


/**
 * Implementation of hook_view().
 */
function station_program_view(&$node, $teaser = FALSE, $page = FALSE) {
  $node = node_prepare($node, $teaser);
  if ($teaser) {
    $node->content['genre'] = array(
      '#value' => $node->genre,
      '#weight' => -2,
    );
  }
  else {
    $node->content['genre'] = array(
      '#type' => 'item',
      '#title' => t('Genre'),
      '#value' => $node->genre,
      '#weight' => -6,
    );
    if ($node->url) {
      $node->content['homepage'] = array(
        '#type' => 'item',
        '#title' => t('Homepage'),
        '#value' => l($node->url, $node->url),
        '#weight' => 1,
      );
    }
  }
  return $node;
}


/**
 * Retrieve a pipe delimited string of autocomplete suggestions for existing programs
 */
function station_program_autocomplete($string = '') {
  $matches = array();
  $result = db_query_range("SELECT DISTINCT title FROM {node} WHERE type = 'station_program' AND LOWER(title) LIKE LOWER('%s%%') AND status = 1 ORDER BY title", $string, 0, 10);
  while ($program = db_fetch_object($result)) {
    $matches[$program->title] = check_plain($program->title);
  }
  print drupal_to_js($matches);
  exit();
}
<?php

// $Id: views.inc,v 1.4 2007/08/22 04:56:21 drewish Exp $

/**
 * Implementation of hook_views_tables().
 */
function station_program_views_tables() {
  $tables['station_program'] = array(
    'name' => 'station_program',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'vid'
      ),
      'right' => array(
        'field' => 'vid'
      )
    ),
    'fields' => array(
      'genre' => array(
        'name' => t('Station Program: Genre'),
        'sortable' => TRUE,
        'help' => t('Freeform genre description.'),
      ),
      'url' => array(
        'name' => t('Station Program: URL'),
        'sortable' => FALSE,
        'help' => t('Link to external website.'),
        'handler' => array(
          'station_handler_field_station_program_url'    => t('As Link'),
          'station_handler_field_station_program_url_nl' => t('Without Link'),
        ),
      ),
    ),
  );

  return $tables;
}

function station_handler_field_station_program_url($fieldinfo, $fielddata, $value, $data) {
  return l($value, $value, NULL, NULL, TRUE);
}
function station_handler_field_station_program_url_nl($fieldinfo, $fielddata, $value, $data) {
  return check_plain($value);
}
<?php

// $Id: views_defaults.inc,v 1.6 2007/08/22 04:56:21 drewish Exp $

/**
 * Implementation of hook_views_default_views().
 */
function station_program_views_default_views() {
  $views = array();

  return $views;
}
/* $Id: station_schedule.css,v 1.5 2007/08/10 17:13:22 drewish Exp $ */

#station-sch tbody {
  padding: 0;
  margin: 0;
}
#station-sch th {
  text-align: center;
}
#station-sch tr {
  padding: 0;
  margin: 0;
  border: 0;
}
#station-sch td {
  padding: 0;
  margin: 0;
  width: 14%;
  vertical-align: top;
  text-align: center;
  border: 1px solid #CCC;
  border-top: 0;
  border-right: 0;
}
#station-sch td#station-sch-hours {
  width: 2%;
}

.station-sch-box {
  overflow: hidden;
  margin: -1px -1px 0 0;
  border: 0;
  border: 1px solid #CCC;
  border-left: 0;
  border-bottom: 0;
}
/* hide the time element for most schedules */
.station-sch-time {
  display: none;
}
/* display it on admin pages */
.station-sch-admin .station-sch-time {
  font-size: x-small;
  display: block;
}
.station-sch-unscheduled {
}
.station-sch-scheduled {
  text-align: center;
}
/* turn inline links into blocks */
.station-sch-scheduled a, .station-sch-unscheduled a {
  display: block;
}
/* on hover force underlining (but not the DJ names) and colour the whole block */
.station-sch-scheduled a:hover, .station-sch-unscheduled a:hover { 
  background-color: #eee;
  text-decoration: none;
}
.station-sch-djs {
  font-size: xx-small;
  display: block;
}
.station-sch-now-hour, .station-sch-now-day {
}
; $Id: station_schedule.info,v 1.4 2007/08/10 17:13:22 drewish Exp $
name = Station Schedule
description = Module to allow radio station scheduling and playlists.
dependencies = station views station_program
package = Station
<?php

// $Id: station_schedule.install,v 1.18 2007/11/21 18:12:16 drewish Exp $

/**
 * Install the initial schema.
 */
function station_schedule_install() {
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      db_query("
        CREATE TABLE {station_schedule} (
          `nid` int unsigned NOT NULL default '0',
          `increment` int unsigned NOT NULL default '0',
          `streams` longtext,
          `unscheduled_message` VARCHAR(255) NOT NULL default '',
          PRIMARY KEY(`nid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      db_query("
        CREATE TABLE {station_schedule_item} (
          `iid` int unsigned NOT NULL default '0',
          `schedule_nid` int unsigned NOT NULL default '0',
          `program_nid` int unsigned NOT NULL default '0',
          `start` int unsigned NOT NULL default '0',
          `finish` int unsigned NOT NULL default '0',
          `may_archive` tinyint NOT NULL default '1',
          PRIMARY KEY  (`iid`),
          KEY `station_schedule_start` (`schedule_nid`, `start`),
          KEY `station_schedule_program` (`schedule_nid`, `program_nid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      db_query("
        CREATE TABLE {station_dj} (
          `uid` int(10) unsigned NOT NULL default '0' COMMENT 'User Id',
          `program_nid` int(10) unsigned NOT NULL default '0' COMMENT 'Program Id',
          PRIMARY KEY  (`uid`,`program_nid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");
      break;
  }
}

/**
 * Implementation of hook_uninstall().
 */
function station_schedule_uninstall() {
  db_query('DROP TABLE {station_schedule}');
  db_query('DROP TABLE {station_schedule_item}');
  db_query('DROP TABLE {station_dj}');

  variable_del('station_schedule_redirect_old_urls');
  variable_del('station_schedule_default');
  variable_del('station_schedule_dj_role');
  variable_del('station_schedule_dj_title');
}

/**
 * Update the tables to UTF-8
 */
function station_schedule_update_2() {
  return _system_update_utf8(array(
    'station_dj', 'station_program', 'station_schedule',
  ));
}

/**
 * Add playlist tables.
 */
function station_schedule_update_3() {
  $ret = array();

  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql(
<<<MYSQL_UPDATE
        CREATE TABLE {station_playlist} (
          `nid` int(10) unsigned NOT NULL default '0' COMMENT 'Playlist node id',
          `program_nid` int(10) unsigned NOT NULL default '0' COMMENT 'Program node id',
          `timestamp` int(11) NOT NULL default '0' COMMENT 'Timestamp of show'
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
MYSQL_UPDATE
      );
      $ret[] = update_sql(
<<<MYSQL_UPDATE
        CREATE TABLE {station_playlist_track} (
          `nid` int(10) unsigned NOT NULL default '0' COMMENT 'Playlist node id',
          `weight` tinyint(4) unsigned NOT NULL default '0' COMMENT 'Ordering weight',
          `artist` varchar(128) NOT NULL default '',
          `album` varchar(128) NOT NULL default '',
          `title` varchar(128) NOT NULL default '',
          KEY `station_playlist_artist` (`artist`),
          KEY `station_playlist_nid_weight` (`nid`,`weight`),
          KEY `station_playlist_title` (`title`),
          KEY `station_playlist_album` (`title`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
MYSQL_UPDATE
      );
      break;
  }
  return $ret;
}

/**
 * Add a label to the playlist table.
 */
function station_schedule_update_4() {
  $ret = array();

  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql(
<<<MYSQL_UPDATE
        ALTER TABLE {station_playlist_track}
          MODIFY COLUMN `artist` VARCHAR(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
          MODIFY COLUMN `album` VARCHAR(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
          MODIFY COLUMN `title` VARCHAR(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
          ADD COLUMN `label` VARCHAR(255) NOT NULL DEFAULT '' AFTER `title`,
          ADD INDEX `station_playlist_label`(`label`);
MYSQL_UPDATE
      );
      break;
  }
  return $ret;
}

/**
 * Remove the onam field from station_schedule
 */
function station_schedule_update_5() {
  $ret = array();

  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_schedule} DROP COLUMN `onam`;");
      break;
  }
  return $ret;
}

/**
 * Refactor the schedule table
 */
function station_schedule_update_6() {
  $ret = array();

  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      // remove unscheduled times.
      $ret[] = update_sql('DELETE FROM {station_schedule} WHERE program_nid = 0 OR program_nid IS NULL;');
      // add our new fields and indexes
      $ret[] = update_sql("ALTER TABLE {station_schedule}
        ADD COLUMN `start` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `program_nid`,
        ADD COLUMN `finish` INTEGER UNSIGNED NOT NULL DEFAULT 0 AFTER `start`,
        ADD INDEX `station_schedule_start`(`start`);
      ");
      // drop the old indexes
      $ret[] = update_sql("ALTER TABLE {station_schedule}
        DROP INDEX `station_schedule_day`,
        DROP INDEX `station_schedule_hour`;
      ");
      // drop old fields
      $ret[] = update_sql("ALTER TABLE {station_schedule}
        DROP COLUMN `archived`;
      ");
      $ret[] = update_sql("ALTER TABLE {station_schedule}
        DROP COLUMN `day`,
        DROP COLUMN `hour`;
      ");

      // set start and finish minutes
      $ret[] = update_sql('UPDATE {station_schedule} SET start = (sid - 1) * 60, finish = start + 60');

      // loop through and merge adjacent programs into a single row.
      $adjacent_query = 'SELECT s.sid AS first_sid, t.sid AS second_sid, s.start, t.finish FROM {station_schedule} s INNER JOIN {station_schedule} t ON s.finish = t.start AND s.program_nid = t.program_nid LIMIT 1';
      $result = db_query($adjacent_query);
      while ($s = db_fetch_object($result)) {
        $ret[] = update_sql('UPDATE {station_schedule} SET finish = '. (int)$s->finish .' WHERE sid = '. (int)$s->first_sid);
        $ret[] = update_sql('DELETE FROM {station_schedule} WHERE sid = '. (int)$s->second_sid);
        $result = db_query($adjacent_query);
      }

      // we aren't allowing an sid = 0 (it gets confused with false) so make it something bigger
      $max = (int) db_result(db_query('SELECT MAX(sid)+1 FROM {station_schedule}'));
      $ret[] = update_sql("UPDATE {station_schedule} SET sid = $max WHERE sid = 0");

      // add our sequence
      $ret[] = update_sql("INSERT INTO {sequences} (name, id) VALUES ('{station_schedule}_sid', $max)");

      break;
  }

  return $ret;
}

/**
 * Fix album index.
 */
function station_schedule_update_7() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql('ALTER TABLE {station_playlist_track}
        DROP INDEX `station_playlist_album`,
        ADD INDEX `station_playlist_album` (`album`)');
    break;
  }
  return $ret;
}

/**
 * Update to set the schema number for Drupal 5.0.
 *
 * Change the playlist node name from 4.7 to 5.0. We do this here because the
 * station_schedule module was split up into sub modules as part of the upgrade
 * and the station_playlist module isn't marked as installed, so the its
 * updates won't be called.
 */
function station_schedule_update_100() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("UPDATE {node} SET type = 'station_playlist' WHERE type='program_playlist';");
      $ret[] = update_sql("UPDATE {node_type} SET type = 'station_playlist' WHERE type='program_playlist';");
    break;
  }
  return $ret;
}

/**
 * Add an archiveable setting.
 */
function station_schedule_update_101() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql('ALTER TABLE {station_schedule}
        ADD COLUMN `may_archive` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1 AFTER `finish`;
      ');
    break;
  }
  return $ret;
}

/**
 * Rename station_schedule to station_schedule_item and rename sid to iid. Add
 * a new schedule_nid field that refers to the schedule. Then create a
 * station_schedule table and update all schedule items to be a member of it.
 */
function station_schedule_update_5200() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      // Rename {station_schedule} to {station_schedule_item}, sid to iid and
      // add schedule_nid so we can join to the new {station_schedule}.
      $ret[] = update_sql('ALTER TABLE {station_schedule} RENAME TO {station_schedule_item}');
      $ret[] = update_sql('ALTER TABLE {station_schedule_item} CHANGE COLUMN `sid` `iid` INTEGER UNSIGNED NOT NULL DEFAULT 0');
      $ret[] = update_sql('ALTER TABLE {station_schedule_item} ADD COLUMN `schedule_nid` INTEGER UNSIGNED NOT NULL DEFAULT 0 FIRST');

      // Move the sequence.
      db_query('LOCK TABLES {sequences} WRITE');
      $ret[] = update_sql("DELETE FROM {sequences} WHERE name = '{station_schedule_item}_iid'");
      $ret[] = update_sql("UPDATE {sequences} SET name = '{station_schedule_item}_iid' WHERE name = '{station_schedule}_sid'");
      db_query('UNLOCK TABLES');

      // Create the table for schedule nodes.
      $ret[] = update_sql("
        CREATE TABLE {station_schedule} (
          `nid` int unsigned NOT NULL default '0',
          `increment` int unsigned NOT NULL default '0',
          `streams` longtext,
          PRIMARY KEY(`nid`)
        ) /*!40100 DEFAULT CHARACTER SET utf8 */;
      ");

      // Create a new node for the existing schedule. We can't do it the easy
      // with node_save() because it'll call station_schedule_insert() which
      // references fields added in later updates.
      $node = new stdClass();
      $node->nid = db_next_id('{node}_nid');
      $node->vid = db_next_id('{node_revisions}_vid');
      $node->created = time();
      $node->changed = time();
      $node->uid = 1;
      $node->title = t('Default');
      db_query("INSERT INTO {node} (nid, vid, title, type, uid, status, created, changed, comment, promote, sticky) 
      	VALUES (%d, %d, '%s', 'station_schedule', 1, %d, %d, %d, 0, 0, 0)",
        $node->nid, $node->vid, $node->title, $node->uid, $node->created, $node->changed
      );
      db_query("INSERT INTO {node_revisions} (nid, vid, title, body, teaser, timestamp, uid, format, log) 
        VALUES (%d, %d, '%s', '', '', %d, %d, 0, '')",
        $node->nid, $node->vid, $node->title, $node->created, $node->uid
      );
      db_query("INSERT INTO {station_schedule} (nid, increment) VALUES (%d, %d)", $node->nid, variable_get('station_schedule_increment', 60));

      // Don't need this anymore.
      variable_del('station_schedule_increment');

      // Make the new schedule the default.
      variable_set('station_schedule_default', $node->nid);
      $ret[] = update_sql("UPDATE {station_schedule_item} SET schedule_nid = ". (int) $node->nid);

      // Fix the keys.
      $ret[] = update_sql('ALTER TABLE {station_schedule_item} DROP INDEX `station_schedule_start`');
      $ret[] = update_sql('ALTER TABLE {station_schedule_item} DROP INDEX `station_schedule_program`');
      $ret[] = update_sql('ALTER TABLE {station_schedule_item} ADD INDEX `station_schedule_start` (`schedule_nid`, `start`)');
      $ret[] = update_sql('ALTER TABLE {station_schedule_item} ADD INDEX `station_schedule_program` (`schedule_nid`, `program_nid`)');
    break;
  }
  return $ret;
}

/**
 * Existing sites will want to preserve the old URLs by default.
 */
function station_schedule_update_5201() {
  variable_set('station_schedule_redirect_old_urls', 1);
  return array();
}

/**
 * Move the unscheduled message from variable into the schedule table.
 */
function station_schedule_update_5202() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_schedule} ADD COLUMN `unscheduled_message` VARCHAR(255) NOT NULL default '' AFTER `increment`");

      $unscheduled_message = variable_get('station_block_unschedule',  t("We're on autopilot."));
      db_query("UPDATE {station_schedule} SET unscheduled_message = '%s'", $unscheduled_message);
      variable_del('station_block_unschedule');

      break;
  }
  return $ret;
}
 
/**
 * Move the streams from variables into the schedule table.
 */
function station_schedule_update_5203() {
  $ret = array();
  switch ($GLOBALS['db_type']) {
    case 'mysql':
    case 'mysqli':
      $ret[] = update_sql("ALTER TABLE {station_schedule} ADD COLUMN `streams` longtext AFTER `increment`");

      $streams = array();
      if ($high = variable_get('station_stream_high_url', '')) {
        $streams['high'] = array(
           'name' => t('High'),
           'description' => t('High bandwidth stream'),
           'urls' => $high,
        );
      }
      if ($low = variable_get('station_stream_low_url', '')) {
        $streams['low'] = array(
          'name' => t('Low'),
          'description' => t('Low bandwidth stream'),
          'urls' => $low,
        );
      }
      if (count($streams)) {
        db_query("UPDATE {station_schedule} SET streams = '%s'", serialize($streams));
      }
      variable_del('station_stream_high_url');
      variable_del('station_stream_low_url');

      break;
  }
  return $ret;
}
<?php

define('STATION_SCHEDULE_CVS_ID', '$Id: station_schedule.module,v 1.69 2007/11/27 16:01:44 drewish Exp $');

if (module_exists('views')) {
  require_once(drupal_get_path('module', 'station_schedule') .'/views.inc');
  require_once(drupal_get_path('module', 'station_schedule') .'/views_defaults.inc');
}


/**
 * Display help and module information
 * @param section which section of the site we're displaying help
 * @return help text for section
 */
function station_schedule_help($section = '') {
  switch ($section) {
    case 'node/'. arg(1) .'/schedule':
      return t('Use this page to make changes to the schedule. You can select which programs air at what time.');

    case 'admin/settings/station/schedule':
      return t("These settings allow you to configure the station's schedule.");

    case 'node/'. arg(1) .'/view/'. arg(3):
      $output = '';
      if (station_has_archive()) {
        $output .= t('The !listen_img listen link will take you to the list of archived copies of a show. The !rss_img link will take you to an RSS feed of the archived copies suitable for podcasting.',
          array(
            '!listen_img' => theme('image', drupal_get_path('module', 'station_schedule') . '/images/listen_tiny.gif', 'listen'),
            '!rss_img' =>  theme('image', drupal_get_path('module', 'station_schedule') . '/images/rss_tiny.gif', 'podcast'),
          )
        );
      }
      return $output;
  }
}

function station_schedule_menu($may_cache) {
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/station/schedule',
      'title' => t('Schedule'),
      'callback' => 'drupal_get_form',
      'callback arguments' => array('station_schedule_admin_settings'),
      'access' => user_access('administer site configuration'),
      'type' => MENU_LOCAL_TASK
    );

    if (variable_get('station_schedule_redirect_old_urls', 0)) {
      $items[] = array(
        'path' => 'schedule',
        'callback' => 'station_schedule_redirect_old',
        'access' => user_access('access content'),
        'type' => MENU_CALLBACK,
      );
      $items[] = array(
        'path' => 'station/schedule',
        'callback' => 'station_schedule_redirect_old',
        'access' => user_access('access content'),
        'type' => MENU_CALLBACK,
      );
    }
  }
  else {
    // Admininister schedule nodes.
    if (arg(0) == 'node' && is_numeric(arg(1))) {
      $nid = (int) arg(1);
      $node = node_load($nid);
      if ($node->type == 'station_schedule') {
        // Stream M3U links
        foreach ($node->settings['streams'] as $key => $stream) {
          $items[] = array(
            'path' => "node/$nid/$key.m3u",
            'type' => MENU_CALLBACK,
            'callback' => 'station_schedule_stream_m3u',
            'callback arguments' => array($stream),
          );
        }

        $items[] = array(
          'path' => "node/$nid/view/week",
          'title' => t('Week'),
          'type' => MENU_DEFAULT_LOCAL_TASK,
          'weight' => -10,
        );
        // Use a separate variable to set the weights since station_day_name()
        // may return index values that are not sorted.
        $weight = 0;
        foreach (station_day_name() as $day => $dayname) {
          $items[] = array(
            'path' => "node/$nid/view/". strtolower($dayname),
            'title' => t(substr($dayname, 0, 3)),
            'callback' => 'station_schedule_day_page',
            'callback arguments' => array($node, $dayname),
            'type' => MENU_LOCAL_TASK,
            'weight' => $weight++,
          );
        }

        $items[] = array(
          'path' => "node/$nid/schedule",
          'title' => t('Alter schedule'),
          'callback' => 'station_schedule_item_list',
          'callback arguments' => array($node),
          'access' => user_access('administer schedule'),
          'type' => MENU_LOCAL_TASK,
        );
        $items[] = array(
          'path' => "node/$nid/schedule/list",
          'title' => t('View'),
          'type' => MENU_DEFAULT_LOCAL_TASK,
          'weight' => -10,
        );
        $items[] = array(
          'path' => "node/$nid/schedule/add",
          'title' => t('Add item'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array('station_schedule_item_edit_form', 'add', $node),
          'type' => MENU_LOCAL_TASK,
        );
        $items[] = array(
          'path' => "node/$nid/schedule/edit",
          'title' => t('Edit item'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array('station_schedule_item_edit_form', 'edit', $node),
          'type' => MENU_CALLBACK
        );
        $items[] = array(
          'path' => "node/$nid/schedule/remove",
          'title' => t('Remove item'),
          'callback' => 'drupal_get_form',
          'callback arguments' => array('station_schedule_item_remove_form', $node),
          'type' => MENU_CALLBACK
        );
      }
    }
  }

  return $items;
}


/**
 * Valid permissions for this module
 * @return array An array of valid permissions for the station module
 */
function station_schedule_perm() {
  return array(
    'administer schedule',
  );
}

/**
 * Implementation of hook_node_info().
 */
function station_schedule_node_info() {
  return array(
    'station_schedule' => array(
      'name' => t('Schedule'),
      'module' => 'station_schedule',
      'title_label' => t('Name'),
      'description' => t('A station schedule.'),
    )
  );
}

/**
 * Implementation of hook_access().
 */
function station_schedule_access($op, $node) {
  global $user;

  switch ($op) {
    case 'view':
      return user_access('access content');
      break;

    case 'update':
    case 'delete':
    case 'create':
    default:
      return user_access('administer schedule');
  }
}

/**
 * Implementation of hook_form().
 */
function station_schedule_form($node) {
  $type = node_get_types('type', $node);

  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => check_plain($type->title_label),
    '#default_value' => $node->title,
    '#required' => TRUE,
    '#maxlength' => 128,
    '#description' => t("The name of the schedule, e.g. 'AM', 'FM', callsign."),
  );

  $form['settings']['#tree'] = TRUE;
  $form['settings']['increment'] = array(
    '#type' => 'select',
    '#title' => t('Time increment'),
    '#default_value' => isset($node->settings['increment']) ? $node->settings['increment'] : 60,
    '#options' => array(15 => t('15 Minutes'), 30 => t('30 Minutes'), 60 => t('1 Hour'), 120 => t('2 Hours')),
    '#description' => t("This is the minimum increment that programs can be scheduled in. <strong>Caution:</strong> Increasing this value on an existing schedule will probably cause weirdness."),
  );
  $form['settings']['unscheduled_message'] = array(
    '#type' => 'textfield',
    '#title' => t('No scheduled programming message'),
    '#size' => 60,
    '#maxlength' => 255,
    '#default_value' => isset($node->settings['unscheduled_message']) ? $node->settings['unscheduled_message'] : t("We're on autopilot."),
    '#required' =>  TRUE,
    '#description' => t('This string will appear when no program is currently scheduled.'),
  );
  $form['settings']['streams'] = array(
    '#type' => 'fieldset',
    '#title' => t('Web streams'),
    '#theme' => 'station_schedule_form_streams',
    '#collapsible' => TRUE,
    '#description' => t("If your station has webstreams enter them below."),
  );
  if (!isset($node->settings['streams']['new'])) {
    $node->settings['streams']['new'] = array(
      'name' => '',
      'description' => '',
      'urls' => array(),
    );
  }
  foreach ($node->settings['streams'] as $key => $stream) {
    $form['settings']['streams'][$key]['name'] = array(
      '#type' => 'textfield',
      '#size' => 10,
      '#default_value' => $stream['name'],
    );
    $form['settings']['streams'][$key]['description'] = array(
      '#type' => 'textfield',
      '#size' => 20,
      '#default_value' => $stream['description'],
    );
    $form['settings']['streams'][$key]['urls'] = array(
      '#type' => 'textarea',
      '#rows' => 2,
      '#cols' => 20,
      '#default_value' => implode("\n", $stream['urls']),
    );
  }

  if ($type->has_body) {
    $form['body_filter']['body'] = array(
      '#type' => 'textarea',
      '#title' => check_plain($type->body_label),
      '#default_value' => $node->body,
      '#rows' => 10,
      '#required' => ($type->min_word_count > 0),
      '#description' => t("Description of the schedule."),
    );
    $form['body_filter']['format'] = filter_form($node->format);
  }

  return $form;
}


function theme_station_schedule_form_streams(&$form) {
  $header = array(t('Name'), t('Description'), t('URLs'));
  foreach (element_children($form) as $key) {
    $row = array();
    $row[] = drupal_render($form[$key]['name']);
    $row[] = drupal_render($form[$key]['description']);
    $row[] = drupal_render($form[$key]['urls']);
    $rows[] = $row;
  }
  $output .= theme('table', $header, $rows);
  $output .= drupal_render($form);

  return $output;
}

/**
 * Implementation of hook_validate().
 */
function station_schedule_validate(&$node) {
  foreach ($node->settings['streams'] as $key => $stream) {
    // Must have both a name and URL.
    if (empty($stream['name']) xor empty($stream['urls'])) {
      if (empty($stream['name'])) {
        form_set_error("settings][streams][$key][name", t('You must provide a name for the webstream.'));
      }
      else {
        form_set_error("settings][streams][$key][urls", t('You must provide a URL for the webstream.'));
      }
    }

    foreach (explode("\n", $stream['urls']) as $url) {
      $url = trim($url);
      if (!empty($url) && !valid_url($url, TRUE)) {
        form_set_error("settings][streams][$key][urls", t('An invalid webstream URL was provided: %url', array('%url' => $url)));
      }
    }
  }
}

/**
 * Implementation of hook_submit().
 */
function station_schedule_submit(&$node) {
  $streams = array();
  foreach ((array) $node->settings['streams'] as $key => $stream) {
    // Skip empty rows.
    if (!empty($stream['name'])) {
      // Convert URLs into an array.
      $stream['urls'] = array_map('trim', explode("\n", $stream['urls']));
      $streams[$stream['name']] = $stream;
    }
  }
  $node->settings['streams'] = $streams;
}


/**
 * Implementation of hook_load().
 */
function station_schedule_load($node) {
  $schedule = array();
  // Use station_day_name() for the day ordering in case Sunday isn't the
  // first day of the week.
  foreach (station_day_name() as $day => $name) {
    $schedule[$day] = array();

    $start = $day * MINUTES_IN_DAY;
    $finish = $start + MINUTES_IN_DAY;
    $result = db_query('SELECT * FROM {station_schedule_item} i WHERE i.schedule_nid = %d AND i.finish > %d AND i.start < %d ORDER BY i.start', $node->nid, $start, $finish);
    while ($s = db_fetch_object($result)) {
      // If a show spans a day, limit its start and finish times to be with-in
      // the day.
      if ($s->start < $start) {
        $s->start = $start;
      }
      if ($s->finish > $finish) {
        $s->finish = $finish;
      }
      $schedule[$day][] = $s;
    }
  }

  // Load the settings.
  $settings = db_fetch_array(db_query('SELECT increment, streams, unscheduled_message FROM {station_schedule} WHERE nid = %d', $node->nid));
  if (isset($settings['streams']) && $streams = unserialize($settings['streams'])) {
    $settings['streams'] = $streams;
  }
  else {
    $settings['streams'] = array();
  }

  return array(
    'settings' => $settings,
    'schedule' => $schedule,
  );
}

/**
 * Implementation of hook_insert().
 */
function station_schedule_insert($node) {
  db_query("INSERT INTO {station_schedule} (nid, increment, streams, unscheduled_message) VALUES (%d, %d, '%s', '%s')", $node->nid, $node->settings['increment'], serialize($node->settings['streams']), $node->settings['unscheduled_message']);
}

/**
 * Implementation of hook_delete().
 */
function station_schedule_delete($node) {
  db_query("DELETE FROM {station_schedule_item} WHERE schedule_nid = %d", $node->nid);
  db_query("DELETE FROM {station_schedule} WHERE nid = %d", $node->nid);
}

/**
 * Implementation of hook_update().
 */
function station_schedule_update($node) {
  db_query("DELETE FROM {station_schedule} WHERE nid = %d", $node->nid);
  db_query("INSERT INTO {station_schedule} (nid, increment, streams, unscheduled_message) VALUES (%d, %d, '%s', '%s')", $node->nid, $node->settings['increment'], serialize($node->settings['streams']), $node->settings['unscheduled_message']);
}

function station_schedule_admin_settings() {
  $form['djs'] = array(
    '#type' => 'fieldset',
    '#title' => t('DJ settings'),
  );
  $form['djs']['station_schedule_dj_title'] = array(
    '#type' => 'textfield',
    '#title' => t('DJ title'),
    '#default_value' => variable_get('station_schedule_dj_title', 'DJs'),
    '#description' => t("This setting lets you determine what you'd like to call the users associated with programs. It should be plural."),
    '#required' => TRUE,
  );
  $form['djs']['station_schedule_dj_role'] = array(
    '#type' => 'select',
    '#title' => t('DJs role'),
    '#default_value' => variable_get('station_schedule_dj_role', DRUPAL_AUTHENTICATED_RID),
    '#options' => user_roles(TRUE),
    '#description' => t("This lets you restrict DJs to members of a specific role. If you don't want any restrictions, select <em>authenticated user</em>s."),
  );

  $form['bc'] = array(
    '#type' => 'fieldset',
    '#title' => t('Backwards compatibility'),
  );
  $schedules = array();
  foreach (station_get_schedules() as $nid => $schedule) {
    $schedules[$nid] = $schedule['title'];
  }
  $form['bc']['station_schedule_redirect_old_urls'] = array(
    '#type' => 'checkbox',
    '#title' => t('Redirect old links'),
    '#default_value' => variable_get('station_schedule_redirect_old_urls', 0),
    '#options' => $schedules,
    '#description' => t("Checking this will ensure that the station module continues to redirect old <code>/schedule</code> and <code>/station/schedule</code> links."),
  );
  $form['bc']['station_schedule_default'] = array(
    '#type' => 'select',
    '#title' => t('Default schedule'),
    '#default_value' => variable_get('station_schedule_default', 0),
    '#options' => $schedules,
    '#description' => t("This you select the schedule that will be used as the default for backwards compatibility."),
  );

  $form['station_schedule_program_node_type'] = array(
    '#type' => 'item',
    '#title' => t('Scheduled node type'),
    '#value' => t('The schedule is using the %type node provided by the %module module.', array('%type' => 'station_program', '%module' => node_get_types('module', 'station_program'))),
  );

  $form['module_cvs_id'] = array(
    '#type' => 'item',
    '#value' => '<pre>'. STATION_SCHEDULE_CVS_ID .'</pre>',
  );

  return system_settings_form($form);
}

/**
 * Implementation of hook_link().
 *
 * This is implemented so that an edit link is displayed for users who have
 * the rights to edit a node.
 */
function station_schedule_link($type, $node = NULL, $teaser = FALSE) {
  if ($type == 'node') {
    switch ($node->type) {
    case 'station_program':
      return station_schedule_archive_links($node->nid);
    case 'station_playlist':
      if (!$teaser && $node->program_nid) {
        return station_schedule_archive_links($node->program_nid);
      }
    }
  }
  return array();
}

/**
 * Load listen links for a program.
 * @param $program_nid Node id of the program.
 * @param $short bool true for short version of links
 */
function station_schedule_archive_links($program_nid, $short = FALSE) {
  $archive_url = station_get_archive_url();
  $may_archive = db_result(db_query('SELECT COUNT(*) FROM {station_schedule_item} s WHERE s.program_nid = %s AND s.may_archive = 1', $program_nid));

  if ($archive_url && $may_archive ) {
    $listen_url = $archive_url . $program_nid;
    $rss_url = $archive_url . $program_nid .'/feed';

    $listen_img = drupal_get_path('module', 'station_schedule') .'/images/listen_tiny.gif';
    $rss_img = drupal_get_path('module', 'station_schedule') .'/images/rss_tiny.gif';

    return array(
      'station_archive_listen' => array(
        'href' => $listen_url,
        'title' => theme('image', $listen_img, t('Listen')) . (($short) ? '' : ' '. t('Listen to previous')),
        'attributes' => array('title' => t('Listen to previous broadcasts of this show')),
        'html' => TRUE,
      ),
      'station_archive_rss' => array(
        'href' => $rss_url,
        'title' => theme('image', $rss_img, t('Podcast'))  . (($short) ? '' : ' '. t('Subscribe to podcast')),
        'attributes' => array('title' => t('Grab this show with your podcast software')),
        'html' => TRUE,
      ),
    );
  }
  return array();
}

/**
 * Implements hook_user() to associate programs with the users.
 */
function station_schedule_user($op, &$edit, &$user, $category = NULL) {
  switch ($op) {
  case 'load':
    $user->programs = array();
    $result = db_query('SELECT n.nid, n.title FROM {node} n INNER JOIN {station_dj} d ON n.nid = d.program_nid WHERE d.uid = %d ORDER BY n.title', $user->uid);
    while ($program = db_fetch_object($result)) {
      $user->programs[$program->nid] = $program->title;
    }
    break;

  case 'view':
    if ($user->programs) {
      foreach((array) $user->programs as $nid => $title) {
        $node = node_load($nid);
        $node = node_build_content($node, TRUE, FALSE);
        $items[] = array('title' => l($title, 'node/'. $nid),
          'class' => 'program',
          'value' => $node->content['genre']['#value'] . $node->content['times']['#value'],
        );
      }
      return array(t('Programs') => $items);
    }
    break;

  case 'delete':
    db_query('DELETE FROM {station_dj} WHERE uid = %d', $user->uid);
    foreach ((array) $user->programs as $nid => $title) {
      _station_send_notice('dj', 'remove', array('program_nid' => $nid, 'uid' => $user->uid));
    }
    break;
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function station_schedule_nodeapi(&$node, $op, $teaser, $page) {
  if ($node->type == 'station_program') {
    switch ($op) {
      case 'validate':
        $roles = user_roles(TRUE);
        $dj_role = variable_get('station_schedule_dj_role', DRUPAL_AUTHENTICATED_RID);

        // validate the users
        foreach ($node->djs as $key => $name) {
          if (!empty($name)) {
            $user = user_load(array('name' => $name));
            if (!$user->uid) {
              form_set_error("djs][$key", t('You need to specify a valid user name.'));
            }
            if (!isset($user->roles[$dj_role])) {
              form_set_error("djs][$key", t('%username is not a member of the %rolename role.',
                  array('%username' => $name, '%rolename' => $roles[$dj_role])));
            }
          }
        }
        break;

      case 'submit':
        // make a copy of the djs and put them back in the expected form
        $djs = (array) $node->djs;
        $node->djs = array();
        foreach ($djs as $key => $name) {
          if (!empty($name)) {
            $user = user_load(array('name' => $name));
            if ($user->uid) {
              $node->djs[$user->uid] = $user->name;
            }
          }
        }
        break;

      case 'insert':
        // dj additions
        foreach ($node->djs as $uid => $username) {
          db_query('INSERT INTO {station_dj} (uid, program_nid) VALUES (%d, %d)', $uid, $node->nid);
          _station_send_notice('dj', 'add', array('program_nid' => $node->nid, 'uid' => $uid));
        }
        break;

      case 'update':
        // only schedule admins should be able to add or remove djs
        if (user_access('administer schedule')) {
          $current_djs = _station_schedule_program_load_djs($node->nid);

          // dj removals
          $removals = array_diff_assoc($current_djs, $node->djs);
          foreach ($removals as $uid => $username) {
            db_query('DELETE FROM {station_dj} WHERE uid = %d AND program_nid = %d', $uid, $node->nid);
            _station_send_notice('dj', 'remove', array('program_nid' => $node->nid, 'uid' => $uid));
          }

          // dj additions
          $additions = array_diff_assoc($node->djs, $current_djs);
          foreach ($additions as $uid => $username) {
            db_query('INSERT INTO {station_dj} (uid, program_nid) VALUES (%d, %d)', $uid, $node->nid);
            _station_send_notice('dj', 'add', array('program_nid' => $node->nid, 'uid' => $uid));
          }
        }
        break;

      case 'delete':
        // Remove the DJs and provide notice.
        $removals = _station_schedule_program_load_djs($node->nid);
        db_query('DELETE FROM {station_dj} WHERE program_nid = %d', $node->nid);
        foreach ($removals as $uid => $username) {
          _station_send_notice('dj', 'remove', array('program_nid' => $node->nid, 'uid' => $uid));
        }

        // Remove the schedule item.
        db_query('DELETE FROM {station_schedule_item} WHERE program_nid = %d', $node->nid);
        break;

      case 'load':
        // load the program's users under djs.
        $node->djs = _station_schedule_program_load_djs($node->nid);

        $node->times = array();
        // Load the schedule items in order of start time while taking into
        // account Drupal's first day of the week setting.
        $result = db_query('SELECT * FROM {station_schedule_item} s WHERE s.program_nid = %d ORDER BY s.schedule_nid, ((s.start + %d) %% %d)', $node->nid, MINUTES_IN_DAY * (7 - variable_get('date_first_day', 0)), MINUTES_IN_WEEK);
        while ($item = db_fetch_array($result)) {
          $node->times[$item['schedule_nid']][] = $item;
        }

        break;

      case 'view':
        if ($page) {
          $breadcrumb = array();
          $breadcrumb[] = array('path' => 'station/programs', 'title' => t('Programs'));
          $breadcrumb[] = array('path' => 'node/'. $node->nid);
          menu_set_location($breadcrumb);
        }

        $djs = station_schedule_program_get_themed_djs($node);
        $schedules = station_get_schedules();
        $times_by_schedule = station_schedule_program_get_themed_times($node);

        if ($teaser) {
          if ($djs) {
            $node->content['djs'] = array(
              '#value' => t(' with !djs', array('!djs' => station_anded_list($djs))),
              '#weight' => -1,
            );
          }
          $scheduled = array();
          foreach ($times_by_schedule as $schedule_nid => $times) {
            $scheduled[] = t('@title @times', array('@title' => $schedules[$schedule_nid]['title'], '@times' => station_anded_list($times)));
          }
          $node->content['times'] = array(
            '#value' => $scheduled ? t(' on @scheduling.', array('@scheduling' => station_anded_list($scheduled))) : t(' is not currently scheduled.'),
            '#weight' => 0,
          );
        }
        else {
          if ($djs) {
            $node->content['djs'] = array(
              '#type' => 'item',
              '#title' => variable_get('station_schedule_dj_title', 'DJs'),
              '#value' => theme('item_list', $djs),
              '#weight' => -5,
            );
          }
          $node->content['times']['#weight'] = -4;
          foreach ($times_by_schedule as $schedule_nid => $times) {
            $node->content['times'][$schedule_nid] = array(
              '#type' => 'item',
              '#title' => t('Listen on <a href="!link">@title</a> at', array('@title' => $schedules[$schedule_nid]['title'], '!link' => url('node/'. $schedule_nid))),
              '#value' => $times ? theme('item_list', $times) : t('This program is currently unscheduled.'),
            );
          }
        }
        break;
    }
  }
}


/**
 * implementation of hook_form_alter()
 */
function station_schedule_form_alter($form_id, &$form) {
  // We only alter audio node edit forms
  if ($form_id == 'station_program_node_form') {
    $node = $form['#node'];

    if (user_access('administer schedule')) {
      // if it's a saved display the scheduled times.
      if ($node->nid) {
        $schedules = station_get_schedules();

        $form['schedule'] = array(
          '#type' => 'fieldset',
          '#title' => t('Station Schedule'),
          '#weight' => 1,
          '#tree' => TRUE,
          '#description' => count($schedules) ? '' : t("There are no schedules on this site."),
          '#collapsible' => TRUE,
        );
        foreach ($schedules as $schedule_nid => $schedule) {
          $links = array();
          $form['schedule'][$schedule_nid] = array(
            '#type' => 'fieldset',
            '#title' => $schedule['title'],
            '#description' => t("These are the times this program is currently scheduled:"),
          );
          if (!isset($node->times[$schedule_nid])) {
            $form['schedule'][$schedule_nid]['#description'] = t('This program is not on this schedule.');
          }
          else {
            foreach ($node->times[$schedule_nid] as $time) {
              $links[] = l(theme('station_dayhour_range', $time['start'], $time['finish']), "node/{$schedule_nid}/schedule/edit/{$time['iid']}");
            }
          }
          // Don't encourage adding unpublished stuff to the schedule.
          if ($node->status != 0) {
            $links[] = l(t('Add this program to the @name schedule', array('@name' => $schedule[''])), "node/{$schedule_nid}/schedule/add/{$node->nid}");
          }
          $form['schedule'][$schedule_nid]['links'] = array(
            '#type' => 'item',
            '#value' => theme('item_list', $links),
          );
        }
      }

      $roles = user_roles(TRUE);
      $dj_role = variable_get('station_schedule_dj_role', DRUPAL_AUTHENTICATED_RID);
      $form['djs'] = array(
        '#type' => 'fieldset',
        '#title' => variable_get('station_schedule_dj_title', 'DJs'),
        '#weight' => 1,
        '#tree' => TRUE,
        '#description' => t("Enter the names of users you'd like to add as @dj-title. The users must be members of the %role-name role.",
            array('@dj-title' => variable_get('station_schedule_dj_title', 'DJs'), '%role-name' => $roles[$dj_role])),
      );
      if ($node->djs) {
        foreach ((array)$node->djs as $uid => $username) {
          $form['djs'][$uid] = array(
            '#type' => 'textfield', '#maxlength' => 60,
            '#autocomplete_path' => 'user/autocomplete',
            '#default_value' => $username,
          );
        }
      }
      // add some blank username textfields
      for ($i = 0; $i < 4; $i++) {
        $form['djs']['new'. $i] = array(
          '#type' => 'textfield', '#maxlength' => 60,
          '#autocomplete_path' => 'user/autocomplete',
        );
      }
    }
  }
}

/**
 * Return an array of DJ info for a given program. The key is the user's uid
 * and the value is the user's name.
 */
function _station_schedule_program_load_djs($program_nid) {
  // load the dj's users users under the djs item.
  $djs = array();
  $result = db_query('SELECT d.uid, u.name FROM {station_dj} d INNER JOIN {users} u ON d.uid = u.uid WHERE d.program_nid = %d ORDER BY u.name', $program_nid);
  while ($dj = db_fetch_object($result)) {
    $djs[$dj->uid] = $dj->name;
  }
  return $djs;
}

/**
 * Returns an array of themed DJs.
 */
function station_schedule_program_get_themed_djs($program_node) {
  // make djs into links
  $djs = array();
  foreach ((array) $program_node->djs as $uid => $name) {
    if (!empty($name)) {
      $user = user_load(array('uid' => $uid));
      $djs[] = theme('username', $user);
    }
  }
  return $djs;
}

/**
 * Returns a themed array of the times a program is scheduled.
 */
function station_schedule_program_get_themed_times($program_node) {
  $ret = array();
  foreach ((array) $program_node->times as $schedule_nid => $times) {
    foreach ($times as $time) {
      $ret[$schedule_nid][] = theme('station_dayhour_range', $time['start'], $time['finish']);
    }
  }
  return $ret;
}

/**
 * Implementation of hook_xmlrpc().
 */
function station_schedule_xmlrpc() {
  return array(
    array(
      'station.program.get.at',
      'station_schedule_program_get_at',
      array('array', 'int', 'int'),
      t('Fetch info on the program playing at a day/hour of a given timestamp.')
    ),
    array(
      'station.schedule.get.list',
      'station_schedule_get_list',
      array('array'),
      t('Fetch a list of schedules on this site.')
    ),
    array(
      'station.program.get.list',
      'station_schedule_get_program_list',
      array('array'),
      t('Fetch a list of programs on this site.')
    ),
  );
}

/**
 * Function to redirect old /schedule/* links and /station/schedule/*
 * node/$default/*.
 */
function station_schedule_redirect_old($day = '', $hour = '') {
  $nid = variable_get('station_schedule_default', 0);
  $path = "node/$nid/view";
  if ($day) {
    $path .= "/$day";
    if ($hour) {
      $path .= "/$hour";
    }
  }
  drupal_goto($path);
}

/**
 * Return a list of schedules on this site.
 *
 * @return
 *   Array of schedules.
 */
function station_schedule_get_list() {
  global $base_url;

  $schedules = array();
  $result = db_query("SELECT n.nid, n.title, ss.increment, ss.streams, ss.unscheduled_message FROM {node} n INNER JOIN {station_schedule} ss ON n.nid = ss.nid WHERE n.type = 'station_schedule'");
  while ($schedule = db_fetch_array($result)) {
    $schedule['base_url'] = $base_url;

    if (isset($schedule['streams']) && $streams = unserialize($schedule['streams'])) {
      $schedule['streams'] = $streams;
    }
    else {
      $schedule['streams'] = array();
    }

    $schedules[$schedule['nid']] = $schedule;
  }

  return $schedules;
}

/**
 * Return a list of schedules on this site.
 *
 * @return
 *   Array of schedules.
 */
function station_schedule_get_program_list() {
  static $schedules;

  if (!isset($schedules)) {
    $schedules = array();
    $result = db_query("SELECT n.nid, n.title FROM {node} n WHERE n.type = 'station_program'");
    while ($o = db_fetch_array($result)) {
      $schedules[$o['nid']] = $o;
    }
  }

  return $schedules;
}
/**
 * Get the program playing at a certain time. If no time is provide, use the
 * current time.
 *
 * @param $gmt_timestamp
 *   a timestamp used to determine the day of the week an hour.
 * @param $schedule_nid
 *   Schedule node id.
 * @return
 *   program node object if one is scheduled, an empty object if nothing is
 *   scheduled.
 */
function station_schedule_program_get_at($gmt_timestamp, $schedule_nid) {
  // Load the schedule item based on the time.
  $ts = station_local_ts($gmt_timestamp);
  $minute = station_minute_from_day_hour(date('w', $ts), date('G', $ts));

  $schedule_item = db_fetch_object(db_query('SELECT * FROM {station_schedule_item} s WHERE s.schedule_nid = %d AND s.start <= %d AND s.finish > %d', $schedule_nid, $minute, $minute));

  // If there's an associated program, load it
  if (isset($schedule_item->program_nid)) {
    if ($node = node_load($schedule_item->program_nid)) {
      // set this so that if the show is scheduled for multiple times the caller
      // can easily figure out which one.
      $node->may_archive = $schedule_item->may_archive;
      // put this in so they can use a pretty url
      $node->node_url = url('node/'. $node->nid, NULL, NULL, TRUE);
      return $node;
    }
  }
  return new stdClass();
}

/**
 * Load the schedule item by its id.
 * @param $iid schedule item id
 * @return schedule item object
 */
function station_schedule_load_item($iid) {
  $result = db_query('SELECT * FROM {station_schedule_item} i WHERE i.iid = %d', $iid);
  if ($o = db_fetch_object($result)) {
    return $o;
  }
}

function station_schedule_item_list($node) {
  drupal_add_css(drupal_get_path('module', 'station_schedule') .'/station_schedule.css');
  drupal_set_title(check_plain($node->title));

  $header = array();
  $row = array();
  foreach ($node->schedule as $day => $items) {
    $header[$day] = station_day_name($day);
    $row[$day] = '';

    // The last finish pointer starts at the beginning of the day.
    $last_finish = $day * MINUTES_IN_DAY;
    $day_finish = ($day + 1) * MINUTES_IN_DAY;
    foreach ($items as $item) {
      // Display blocks for unscheduled time periods
      if ($last_finish != $item->start) {
        $row[$day] .= theme('station_schedule_admin_nonitem', $node, $last_finish, $item->start);
      }
      $last_finish = $item->finish;

      // Display the schedule item
      $item->program = node_load($item->program_nid);
      $row[$day] .= theme('station_schedule_admin_item', $node, $item);
    }
    // Display a block for any remaining time during the day.
    if ($last_finish < $day_finish) {
      $row[$day] .= theme('station_schedule_admin_nonitem', $node, $last_finish, $day_finish);
    }
  }

  // Render the table
  return theme('table', $header, array($row), array('id' => 'station-sch', 'class' => 'station-sch-admin'));
}

function theme_station_schedule_admin_nonitem($node, $start, $finish) {
  $class = 'station-sch-box station-sch-unscheduled';
  $height = ($finish - $start);
  $link = url("node/{$node->nid}/schedule/add/0/{$start}/{$finish}");

  $output = "<div class='{$class}'><a id='schedule-{$start}' href='{$link}' style='height: {$height}px;'>";
  $output .= '<span class="station-sch-time">'. theme('station_hour_range', $start, $finish) .'</span>';
  $output .= '<span class="station-sch-title">'. t('<em>Unscheduled</em>') .'</span>';
  $output .= "</a></div>\n";

  return $output;
}

function theme_station_schedule_admin_item($node, $item) {
  $class = 'station-sch-box station-sch-scheduled';
  $height = ($item->finish - $item->start);
  $link = url("node/{$node->nid}/schedule/edit/{$item->iid}");

  $output = "<div class='{$class}'><a id='schedule-{$item->start}' href='{$link}' style='height: {$height}px;'>";
  $output .= '<span class="station-sch-time">'. theme('station_hour_range', $item->start, $item->finish) .'</span>';
  $output .= '<span class="station-sch-title">'. check_plain($item->program->title) .'</span>';
  $output .= "</a></div>\n";

  return $output;
}

/**
 * @param $op
 *   String 'add' or 'edit'.
 * @param $node
 *   A station_schedule node.
 * @param $id
 *   Integer when adding it's a program id. when editing, it's a schedule item
 *   id.
 */
function station_schedule_item_edit_form($op, $node, $id = NULL, $start = 0, $finish = 60) {
  $post_op = $_POST['op'];
  if ($post_op == t('Cancel')) {
    drupal_goto("node/{$node->nid}/schedule", NULL, "schedule-{$start}");
  }
  if ($post_op == t('Remove')) {
    drupal_goto("node/{$node->nid}/schedule/remove/$id");
  }

  if ($op == 'add') {
    if ($id && $program = node_load($id)) {
      $program_title = $program->title;
    }
    $schedule_item = new stdClass;
    $schedule_item->schedule_nid = $node->nid;
    $schedule_item->iid = $id;
    $schedule_item->start = (int) $start;
    $schedule_item->finish = (int) $finish;
    $schedule_item->may_archive = TRUE;

    drupal_set_title(t('Add item to %title', array('%title' => $node->title)));
  }
  else if ($op == 'edit') {
    if ($id && $schedule_item = station_schedule_load_item($id)) {
      $program = node_load($schedule_item->program_nid);
      $program_title = $program->title;
      $form['iid'] = array(
        '#type' => 'value',
        '#value' => $schedule_item->iid,
      );
    }
    else {
      drupal_goto("node/{$node->nid}/schedule");
    }
    drupal_set_title(t('Edit %title item', array('%title' => $node->title)));
  }

  $day_options = station_day_name();
  $minute_options = array();
  // Make sure the increment will advance the counter.
  $increment = ($node->settings['increment'] < 1) ? 1 : $node->settings['increment'];
  for ($minute = 0; $minute <= 24 * 60; $minute += $increment) {
    $time = station_time_from_minute($minute);
    $minute_options[$minute] = $time['time'] . $time['a'];
  }


  $form['schedule_nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $form['start_day'] = array(
    '#type' => 'select',
    '#title' => t('Starts'),
    '#default_value' => station_day_from_minute($schedule_item->start),
    '#options' => $day_options,
  );
  $form['start_minutes'] = array(
    '#type' => 'select',
    '#default_value' => $schedule_item->start % MINUTES_IN_DAY,
    '#options' => $minute_options,
  );
  $form['finish_day'] = array(
    '#type' => 'select',
    '#title' => t('Ends'),
    '#default_value' => station_day_from_minute($schedule_item->finish),
    '#options' => $day_options,
    '#description' => $description,
  );
  $form['finish_minutes'] = array(
    '#type' => 'select',
    '#default_value' => $schedule_item->finish % MINUTES_IN_DAY,
    '#options' => $minute_options,
  );
  // Make sure a range that ends on midnigh of one day gets pushed back
  // to the previous day.
  if ($form['finish_minutes']['#default_value'] == 0) {
    $form['finish_day']['#default_value']--;
    $form['finish_minutes']['#default_value'] = MINUTES_IN_DAY;
  }
  $form['program_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Program'),
    '#description' => t("Enter the name of the program."),
    '#default_value' => $program_title,
    '#maxlength' => 128,
    '#autocomplete_path' => 'station/autocomplete/program',
  );
  $form['may_archive'] = array(
    '#type' => 'checkbox',
    '#title' => t('Can be saved in Station Archive'),
    '#default_value' => $schedule_item->may_archive,
    '#description' => t('Checking this indicates that the Station Archive module can save audio recordings of the program at this timeslot to the archive.'),
  );

  $form['buttons']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  if ($schedule_item->iid) {
    $form['buttons']['remove'] = array(
      '#type' => 'button',
      '#value' => t('Remove'),
    );
  }
  $form['buttons']['cancel'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel'),
  );

  return $form;
}

function station_schedule_item_edit_form_validate($form_id, $form_values) {
  $start = ($form_values['start_day'] * MINUTES_IN_DAY) + $form_values['start_minutes'];
  $finish = ($form_values['finish_day'] * MINUTES_IN_DAY) + $form_values['finish_minutes'];

  if ($start >= $finish) {
    form_set_error('finish', t("The program must start before it finishes."));
  }

  $result = db_query('SELECT count(*) AS count, min(s.start) AS start, max(s.finish) AS finish FROM {station_schedule_item} s WHERE s.schedule_nid = %d AND s.iid <> %d AND s.finish > %d AND s.start < %d', $form_values['schedule_nid'], $form_values['iid'], $start, $finish);
  if ($overlap = db_fetch_object($result)) {
    if ($overlap->count == 1) {
      form_set_error('', t("The program overlaps another scheduled item at %time.", array('%time' => theme('station_dayhour_range', $overlap->start, $overlap->finish))));
    }
    else if ($overlap->count > 1) {
      form_set_error('', t("The program overlaps %number scheduled items from %time.", array('%time' => theme('station_dayhour_range', $overlap->start, $overlap->finish), '%number' => $overlap->count)));
    }
  }

  // check for a valid program title
  if (!$prog = node_load(array('title' => $form_values['program_title'], 'type' => 'station_program'))) {
    form_set_error('program_title', t('You need to specify a valid program name.'));
  }
}

function station_schedule_item_edit_form_submit($form_id, $form_values) {
  $schedule_nid = $form_values['schedule_nid'];

  $start = ($form_values['start_day'] * MINUTES_IN_DAY) + $form_values['start_minutes'];
  $finish = ($form_values['finish_day'] * MINUTES_IN_DAY) + $form_values['finish_minutes'];

  $program = node_load(array('type' => 'station_program', 'title' => $form_values['program_title']));

  if ($form_values['iid']) {
    $old_sch = station_schedule_load_item($form_values['iid']);
    db_query("UPDATE {station_schedule_item} SET program_nid = %d, start = %d, finish = %d, may_archive = %d WHERE iid = %d",
      $program->nid, $start, $finish, $form_values['may_archive'], $form_values['iid']);
    if ($old_sch->program_nid != $program->nid) {
      _station_send_notice('schedule', 'remove', array('schedule_nid' => $schedule_nid, 'program_nid' => $old_sch->program_nid, 'iid' => $form_values['iid']));
      _station_send_notice('schedule', 'add', array('schedule_nid' => $schedule_nid, 'program_nid' => $program->nid, 'iid' => $form_values['iid']));
    }
    elseif ($old_sch->start != $start || $old_sch->finish != $finish) {
      _station_send_notice('schedule', 'change', array('schedule_nid' => $schedule_nid, 'program_nid' => $program->nid, 'iid' => $form_values['iid']));
    }
  }
  else {
    $iid = db_next_id('{station_schedule_item}_iid');
    db_query("INSERT INTO {station_schedule_item} (schedule_nid, iid, program_nid, start, finish, may_archive) VALUES (%d, %d, %d, %d, %d, %d)",
     $schedule_nid, $iid, $program->nid, $start, $finish, $form_values['may_archive']);
    _station_send_notice('schedule', 'add', array('schedule_nid' => $schedule_nid, 'program_nid' => $program->nid, 'iid' => $iid));
  }
  drupal_goto("node/{$schedule_nid}/schedule", NULL, "schedule-{$start}");
}

/**
 * Page to confirm the removal of a schedule item.
 */
function station_schedule_item_remove_form($node, $iid = '') {
  $schedule_item = station_schedule_load_item($iid);
  if (!$schedule_item) {
    return "node/{$node->nid}/schedule";
  }
  $program = node_load($schedule_item->program_nid);

  $form['schedule_nid'] = array('#type' => 'value', '#value' => $node->nid);
  $form['program_nid'] = array('#type' => 'value', '#value' => $schedule_item->program_nid);
  $form['iid'] = array('#type' => 'value', '#value' => $schedule_item->iid);
  return confirm_form(
    $form,
    t('Are you sure you want to unschedule %title from %schedule at %time?', array('%title' => $program->title, '%schedule' => $node->title, '%time' => theme('station_dayhour_range', $schedule_item->start, $schedule_item->finish))),
    $_GET['destination'] ? $_GET['destination'] : "node/{$node->nid}/schedule/edit/{$schedule_item->iid}",
    t('This action cannot be undone, but you can always re-add the program to the schedule.'),
    t('Remove'),
    t('Cancel')
  );
}

/**
 * Delete a schedule item.
 */
function station_schedule_item_remove_form_submit($formid, $form) {
  if ($form['confirm']) {
    db_query("DELETE FROM {station_schedule_item} WHERE iid = %d", $form['iid']);
    _station_send_notice('schedule', 'remove', array('schedule_nid' => $form['schedule_nid'], 'program_nid' => $form['program_nid'], 'iid' => $form['iid']));
    return "node/{$form['schedule_nid']}/schedule";
  }
}

/**
 * Implementation of hook_view().
 *
 * Display a weekly view for the schedule.
 */
function station_schedule_view($node, $teaser = FALSE, $page = FALSE) {
  drupal_add_css(drupal_get_path('module', 'station_schedule') .'/station_schedule.css');

  if ($page) {
    $breadcrumb = array();
    $breadcrumb[] = array('path' => 'station', 'title' => t('Station'));
    $breadcrumb[] = array('path' => 'station/schedule', 'title' => t('Schedules'));
    $breadcrumb[] = array('path' => 'node/'. $node->nid);
    menu_set_location($breadcrumb);
  }

  $node = node_prepare($node, $teaser);
  if ($teaser) {
    $node->content['streams'] = array(
      '#title' => t('Webstreams'),
      '#value' => theme('station_streams', $node->nid, $node->settings['streams']),
      '#weight' => -5,
    );
  }
  else {
    $header[0] = array('data' => t('Time'));
    $row = array();

    // First column is hours.
    $row[0]['id'] = 'station-sch-hours';
    for ($hour = 0; $hour < 24; $hour++) {
      $row[0]['data'] .= theme('station_schedule_hour', $hour);
    }

    // Then a column for each day of the week.
    foreach ((array) $node->schedule as $day => $items) {
      $header[$day + 1]['data'] = station_day_name($day);
      $row[$day + 1]['data'] = '';

      // The last finish pointer starts at the beginning of the day.
      $last_finish = $day * MINUTES_IN_DAY;
      $day_finish = ($day + 1) * MINUTES_IN_DAY;
      foreach ($items as $item) {
        // Display blocks for unscheduled time periods
        if ($last_finish != $item->start) {
          $row[$day + 1]['data'] .= theme('station_schedule_spacer', $last_finish, $item->start);
        }
        $last_finish = $item->finish;
  
        // Display the schedule item.
        $program = node_load($item->program_nid);
        $row[$day + 1]['data'] .= theme('station_schedule_item', $item->start, $item->finish, $program);
      }
      // Display a block for any remaining time during the day.
      if ($last_finish < $day_finish) {
        $row[$day + 1]['data'] .= theme('station_schedule_spacer', $last_finish, $day_finish);
      }
    }

    // Add a class to indicate what day it is.
    $today = station_today();
    $header[$today + 1]['class'] = 'station-sch-now-day';
    $row[$today + 1]['class'] = 'station-sch-now-day';

    $node->content['weekly_schedule'] = array(
      '#value' => theme('table', $header, array($row), array('id' => 'station-sch')),
      '#weight' => 1,
    );
  }
  return $node;
}

function theme_station_schedule_hour($hour) {
  $class = 'station-sch-box station-sch-hour';
  $height = 60;
  $output = "<div class='{$class}' style='height:{$height}px;'>";
  $output .= theme('station_hour', $hour * 60);
  $output .= "</div>\n";
  return $output;
}

function theme_station_schedule_spacer($start, $finish) {
  $class = 'station-sch-box station-sch-unscheduled';
  $height = ($finish - $start);
  $output = "<div class='{$class}' style='height:{$height}px;'>";
  $output .= "<span class='station-sch-time'>". theme('station_hour_range', $start, $finish) ."</span>";
  $output .= "</div>\n";
  return $output;
}

function theme_station_schedule_item($start, $finish, $program) {
  $class = 'station-sch-box station-sch-scheduled';
  $height = ($finish - $start);
  $link = url('node/'. $program->nid);

  $output = "<div class='{$class}'><a href='{$link}' style='height: {$height}px;'>";
  $output .= '<span class="station-sch-time">'. theme('station_hour_range', $start, $finish) .'</span>';
  $output .= '<span class="station-sch-title">'. check_plain($program->title) .'</span>';
  if ($program->djs) {
    $output .= '<span class="station-sch-djs">'. check_plain(implode(', ', $program->djs)) .'</span>';
  }
  $output .= "</a></div>\n";
  return $output;
}

/**
 * Print a day's schedule page.
 */
function station_schedule_day_page($node, $day) {
  drupal_set_title(check_plain($node->title));
  
  if ($view = views_get_view('station_schedule_day')) {
    // Set $view->url to current page so views with exposed filters submit
    // back to the same page
    $view->url = $_GET['q'];
    $view_args = array($node->nid, $day);
    return views_build_view('embed', $view, $view_args, FALSE, NULL);
  }
  return drupal_not_found();
}

/**
 * Send the client an M3U file of the streams.
 *
 * @param $stream An array with a 'urls' item containing an array of webstream
 *   URLs.
 */
function station_schedule_stream_m3u($stream) {
  drupal_set_header('Content-Type: audio/mpegurl');
  $output = '';
  foreach ($stream['urls'] as $url) {
    $output .= $url ."\n";
  }
  print $output;
  exit();
}
<?php

// $Id: views.inc,v 1.16 2007/11/27 16:48:16 drewish Exp $

/**
 * Implementation of hook_views_tables().
 */
function station_schedule_views_tables() {
  $tables['station_djs'] = array(
    'name' => 'station_djs',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid'
      ),
      'right' => array(
        'field' => 'nid'
      )
    ),
    'fields' => array(
      'djs' => array(
        'name' => t('Station Program: DJs'),
        'help' => t("The program's DJs."),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'station_handler_field_schedule_djs',
      ),
    ),
  );
  $tables['station_schedule'] = array(
    'name' => 'station_schedule',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid'
      ),
      'right' => array(
        'field' => 'nid'
      )
    ),
    'fields' => array(
      'streams' => array(
        'name' => t('Station Schedule: Web streams'),
        'help' => t("Web stream links"),
        'sortable' => FALSE,
        'handler' => 'station_handler_field_schedule_streams',
      ),
    ),
  );
  $tables['station_schedule_item_current'] = array(
    'name' => 'station_schedule_item',
    'join' => array(
      'left' => array(
        'table' => 'station_schedule',
        'field' => 'nid',
      ),
      'right' => array(
        'field' => 'schedule_nid'
      ),
      'extra' => array(
        'start <= ***CURRENT_STATION_MINUTE***' => NULL,
        'finish > ***CURRENT_STATION_MINUTE***' => NULL,
      ),
    ),
    'fields' => array(
      'program_nid' => array(
        'name' => t('Station Schedule: Current program'),
        'help' => t("The currently schedule program."),
        'sortable' => TRUE,
        'handler' => 'station_handler_field_schedule_current_program',
        'addlfields' => array('iid', 'start', 'finish'),
      ),
    ),
  );
  $tables['station_program_schedule_node'] = array(
    'name' => 'node',
    'provider' => 'internal',
    'join' => array(
      'left' => array(
        'table' => 'station_schedule_item',
        'field' => 'schedule_nid'
      ),
      'right' => array(
        'field' => 'nid'
      )
    ),
    'fields' => array(
      'title' => array(
        'name' => t('Station Program: Schedule Title'),
        'sortable' => TRUE,
        'handler' => 'station_handler_field_program_schedule_title',
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'link' => 'As link to schedule',
             'nolink' => 'Without link'
           ),
        ),
        'addlfields' => array('nid'),
        'help' => t('Title of the schedule.'),
      ),
    ),
  );
  $tables['station_schedule_item'] = array(
    'name' => 'station_schedule_item',
    'join' => array(
      'left' => array(
        'table' => 'node',
        'field' => 'nid'
      ),
      'right' => array(
        'field' => 'program_nid'
      )
    ),
    'fields' => array(
      'start' => array(
        'name' => t('Station Program: Single scheduled time'),
        'help' => t("Single instance of the program on the schedule. Use this when you want a program displayed for each time it's scheduled."),
        'sortable' => TRUE,
        'sort_handler' => 'station_handler_field_sort_schedule_item_start',
        'handler' => 'station_handler_field_schedule_time',
        'option' => array(
          '#type' => 'select',
          '#options' => array(
            'hour' => t('Show only hours'),
            'dayhour' => t('Show days and hours'),
          ),
        ),
        'addlfields' => array('finish', 'schedule_nid'),
      ),
      'times' => array(
        'name' => t('Station Program: All scheduled times'),
        'help' => t("Summary of all the times the program is scheduled."),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'station_handler_field_schedule_times',
      ),
      'archive_links' => array(
        'name' => t('Station Program: Archive listen and RSS links'),
        'help' => t("Links to both a page and feed for the program's archived audio."),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'station_handler_field_listen_links',
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'icon' => t('Icon'),
             'icon_text' => t('Icon and text'),
            ),
        ),
      ),
      'archive_link_page' => array(
        'name' => t('Station Program: Archive listen link'),
        'help' => t("Link to the program's archived audio page."),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'station_handler_field_listen_link_page',
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'icon' => t('Icon'),
             'icon_text' => t('Icon and text'),
            ),
        ),
      ),
      'archive_link_rss' => array(
        'name' => t('Station Program: Archive RSS link'),
        'help' => t("Link to the program's archived audio feed."),
        'sortable' => FALSE,
        'notafield' => TRUE,
        'handler' => 'station_handler_field_listen_link_rss',
        'option' => array(
           '#type' => 'select',
           '#options' => array(
             'icon' => t('Icon'),
             'icon_text' => t('Icon and text'),
            ),
        ),
      ),
    ),
    'filters' => array(
      'is_scheduled' => array(
        'name' => t('Station Program: Scheduled'),
        'operator' => 'views_handler_operator_yesno',
        'handler' => 'station_handler_filter_is_scheduled',
        'help' => t("Filter weather the node is on a schedule."),
      ),
      'now_playing' => array(
        'name' => t('Station Program: Now playing'),
        'cacheable' => FALSE,
        'operator' => array('IS' => t('now playing')),
        'value' => '',
        'handler' => 'station_handler_filter_now_playing',
        'help' => t("Filter down to the program that is currently being broadcast."),
      ),
    ),
    'sorts' => array(
      'start' => array(
        'name' => t('Station Program: Start time'),
        'help' => t('Sort the nodes based on their scheduled starting time.'),
        'handler' => 'station_handler_sort_schedule_item_start',
      ),
    ),
  );

  // If there's no archive, remove the archive link fields.
  if (!station_has_archive()) {
    unset($tables['station_schedule_item']['fields']['archive_links']);
    unset($tables['station_schedule_item']['fields']['archive_link_page']);
    unset($tables['station_schedule_item']['fields']['archive_link_rss']);
  }

  return $tables;
}

/**
 * Implementation of hook_views_query_substitutions().
 *
 * Substitute current time; this works with cached queries.
 */
function station_schedule_views_query_substitutions($view) {
  $timestamp = station_local_ts(time());
  $minute = station_minute_from_day_hour(date('w', $timestamp), date('G', $timestamp));
  return array('***CURRENT_STATION_MINUTE***' => $minute);
}

/**
 * Implementation of hook_views_arguments().
 */
function station_schedule_views_arguments() {
  $arguments = array(
    'station_schedule_argument_schedule_item_day' => array(
      'name' => t('Station Schedule: Day of week'),
      'handler' => 'station_schedule_argument_schedule_item_day',
      'help' => t('Limit the schedule items to a single day of the week.'),
    ),
    'station_schedule_argument_schedule_nid' => array(
      'name' => t('Station Program: Schedule NID'),
      'handler' => 'station_schedule_argument_schedule_nid',
      'help' => t('The Schedule NID argument allows you to filter programs attached to the specified schedule.'),
    ),
  );
  return $arguments;
}

function station_schedule_argument_schedule_item_day($op, &$query, $a1, $a2 = NULL) {
  $min_in_day = MINUTES_IN_DAY;

  switch($op) {
    case 'summary':
      $query->ensure_table('station_schedule_item');
      // Need to do the modulus by week to avoid having Sunday listed twice as 0 and 7.
      $fieldinfo['field'] = "CAST(station_schedule_item.start / $min_in_day AS UNSIGNED INTEGER) %% 7";
      $fieldinfo['fieldname'] = 'station_schedule_item_start_day';
      return $fieldinfo;

    case 'sort':
      $query->ensure_table('station_schedule_item');
      $first_day = 7 - variable_get('date_first_day', 0);
      if ($first_day != 0) {
        $formula = "(CAST(station_schedule_item.start / $min_in_day AS UNSIGNED INTEGER) + $first_day) %% 7";
      }
      else {
        $formula = "CAST(station_schedule_item.start / $min_in_day AS UNSIGNED INTEGER)";
      }
      $query->add_orderby(NULL, $formula, $a1, 'station_schedule_item_start_day_sort');

      break;

    case 'link':
      dvm($query);
      $day = (int) $query->station_schedule_item_start_day;
      $dayname = station_day_name($day);
      return l($dayname, $a2 .'/'. $dayname);

    case 'filter':
      $day = station_valid_day($a2);
      $start = $day * MINUTES_IN_DAY;
      $finish = $start + MINUTES_IN_DAY;
      $query->ensure_table('station_schedule_item');
      $query->add_where("station_schedule_item.finish > %d AND station_schedule_item.start < %d", $start, $finish);
      break;

    case 'title':
      return station_day_name(station_valid_day($a2));
  }
}

function station_schedule_argument_schedule_nid($op, &$query, $a1, $a2 = NULL) {
  switch($op) {
    case 'summary':
      $query->ensure_table('station_schedule_item');
      $query->ensure_table('station_program_schedule_node');
      $query->add_field('title', 'station_program_schedule_node');
      $query->add_field('schedule_nid', 'station_schedule_item');
      $query->add_where('station_schedule_item.schedule_nid IS NOT NULL');
      $fieldinfo['field'] = 'station_schedule_item.schedule_nid';
      return $fieldinfo;

    case 'sort':
      $query->ensure_table('station_schedule_item');
      $query->ensure_table('station_program_schedule_node');
      $query->add_orderby('station_program_schedule_node', 'title', $a1);
      break;

    case 'link':
      return l($query->title, $a2 .'/'. intval($query->schedule_nid));

    case 'filter':
      $query->ensure_table('station_schedule_item');
      $query->add_where('station_schedule_item.schedule_nid = %d', $a2);
      break;

    case 'title':
      $node = db_fetch_object(db_query("SELECT title FROM {node} WHERE nid=%d", $query));
      return check_plain($node->title);
  }
}

function station_handler_field_schedule_current_program($fieldinfo, $fielddata, $value, $data) {
  if (isset($data->station_schedule_item_current_program_nid)) {
    if ($program = node_load($data->station_schedule_item_current_program_nid)) {
      return l($program->title, "node/$program->nid");
    }
  }
  else {
    return check_plain(variable_get('station_block_unschedule',  t("We're on autopilot.")));
  }
}

function station_handler_field_schedule_streams($fieldinfo, $fielddata, $value, $data) {
  if ($streams = unserialize($data->station_schedule_streams)) {
    return theme('station_streams', $data->nid, $streams);
  }
}

function station_handler_field_schedule_time($fieldinfo, $fielddata, $value, $data) {
  if (isset($data->station_schedule_item_start) && isset($data->station_schedule_item_finish)) {
    if ($fielddata['options'] == 'hour') {
      return theme('station_hour_range', $data->station_schedule_item_start, $data->station_schedule_item_finish);
    }
    return theme('station_dayhour_range', $data->station_schedule_item_start, $data->station_schedule_item_finish);
  }
}

function station_handler_field_schedule_times($fieldinfo, $fielddata, $value, $data) {
  if ($program = node_load($data->nid)) {
    $scheduled = array();
    $schedules = station_schedule_get_list();

    if (count($schedules) == 1) {
      foreach (station_schedule_program_get_themed_times($program) as $schedule_nid => $times) {
        $scheduled[] = t('@times', array('@times' => station_anded_list($times)));
      }
    }
    else {
      foreach (station_schedule_program_get_themed_times($program) as $schedule_nid => $times) {
        $scheduled[] = t('On @title @times', array('@title' => $schedules[$schedule_nid]['title'], '@times' => station_anded_list($times)));
      }
    }
    return station_anded_list($scheduled);
  }
}

function station_handler_field_schedule_djs($fieldinfo, $fielddata, $value, $data) {
  if ($program = node_load($data->nid)) {
    return station_anded_list(station_schedule_program_get_themed_djs($program));
  }
}

function station_handler_field_listen_links($fieldinfo, $fielddata, $value, $data) {
  $output = '';
  if ($fielddata['options'] == 'icon') {
    foreach(station_schedule_archive_links($data->nid, TRUE) as $link) {
      $output .= l($link['title'], $link['href'], NULL, NULL, NULL, TRUE, TRUE) .' ';
    }
  }
  else {
    $output = theme('links', station_schedule_archive_links($data->nid, FALSE));
  }
  return $output;
}
function station_handler_field_listen_link_page($fieldinfo, $fielddata, $value, $data) {
  $links = station_schedule_archive_links($data->nid, ($fielddata['options'] == 'icon'));
  return l($links['station_archive_listen']['title'], $links['station_archive_listen']['href'], NULL, NULL, NULL, TRUE, TRUE);
}
function station_handler_field_listen_link_rss($fieldinfo, $fielddata, $value, $data) {
  $links = station_schedule_archive_links($data->nid, ($fielddata['options'] == 'icon'));
  return l($links['station_archive_rss']['title'], $links['station_archive_rss']['href'], NULL, NULL, NULL, TRUE, TRUE);
}

function station_handler_field_program_schedule_title($fieldinfo, $fielddata, $value, $data) {
  if ($fielddata['options'] == 'link') {
    return l($value, "node/$data->station_program_schedule_node_nid");
  }
  return check_plain($value);
}


function _station_schedule_item_start_sort($fieldname) {
  $start_offset = (int) MINUTES_IN_DAY * (7 - variable_get('date_first_day', 0));
  $divisor = (int) MINUTES_IN_WEEK;
  return "(({$fieldname} + {$start_offset}) %% {$divisor})"; 
}

function station_handler_field_sort_schedule_item_start($fielddata, $fieldinfo) {
  return _station_schedule_item_start_sort($fielddata['fullname']); 
}

function station_handler_sort_schedule_item_start($op, &$query, $sortinfo, $sortdata) {
  $formula = _station_schedule_item_start_sort($sortdata['field']);
  $query->add_orderby(NULL, $formula, $sortdata['sortorder'], $sortinfo['table'] . '_start_sort');
}


function station_handler_filter_is_scheduled($op, $filterdata, $filterinfo, &$query) {
  switch ($op) {
  case 'handler':
    $query->ensure_table('station_schedule_item');
    if ($filterdata['operator']) {
      $query->add_where('station_schedule_item.program_nid IS NOT NULL');
    }
    else {
      $query->add_where('station_schedule_item.program_nid IS NULL');
    }
    break;
  }
}

function station_handler_filter_now_playing($op, $filterdata, $filterinfo, &$query) {
  switch ($op) {
  case 'handler':
    $query->ensure_table('node');
    $query->add_field('nid', 'node');
    $minute = '***CURRENT_STATION_MINUTE***';
    $query->add_where('EXISTS (SELECT * FROM {station_schedule_item} WHERE {station_schedule_item}.program_nid = node.nid AND {station_schedule_item}.start <= %d AND {station_schedule_item}.finish > %d)', $minute, $minute);
    break;
  }
}
<?php

// $Id: views_defaults.inc,v 1.15 2007/11/28 00:29:44 drewish Exp $

/**
 * Implementation of hook_views_default_views().
 */
function station_schedule_views_default_views() {
  $view = new stdClass();
  $view->name = 'station_schedule';
  $view->description = 'Schedules';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Schedule listing';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = '';
  $view->page_empty_format = '1';
  $view->page_type = 'list';
  $view->url = 'station/schedules';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '10';
  $view->menu = TRUE;
  $view->menu_title = 'Schedules';
  $view->menu_tab = FALSE;
  $view->menu_tab_weight = '0';
  $view->menu_tab_default = FALSE;
  $view->menu_tab_default_parent = NULL;
  $view->menu_tab_default_parent_type = 'existing';
  $view->menu_parent_tab_weight = '0';
  $view->menu_parent_title = '';
  $view->sort = array (
  );
  $view->argument = array (
  );
  $view->field = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'label' => '',
      'handler' => 'views_handler_field_nodelink',
      'options' => 'link',
    ),
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'station_schedule',
),
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(node);
  $views[$view->name] = $view;



  $view = new stdClass();
  $view->name = 'station_schedule_day';
  $view->description = 'What\'s on the schedule on a given day.';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Day Schedule';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = 'There are no programs scheduled for this day.';
  $view->page_empty_format = '1';
  $view->page_type = 'table';
  $view->url = '';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '50';
  $view->sort = array (
    array (
      'tablename' => 'station_schedule_item',
      'field' => 'start',
      'sortorder' => 'ASC',
      'options' => '',
    ),
  );
  $view->argument = array (
    array (
      'type' => 'station_schedule_argument_schedule_nid',
      'argdefault' => '4',
      'title' => '',
      'options' => '',
      'wildcard' => '',
      'wildcard_substitution' => '',
    ),
    array (
      'type' => 'station_schedule_argument_schedule_item_day',
      'argdefault' => '4',
      'title' => '',
      'options' => '',
      'wildcard' => '',
      'wildcard_substitution' => '',
    ),
  );
  $view->field = array (
    array (
      'tablename' => 'station_schedule_item',
      'field' => 'start',
      'label' => 'Time',
      'defaultsort' => 'DESC',
      'options' => 'hour',
    ),
    array (
      'tablename' => 'station_schedule_item',
      'field' => 'archive_links',
      'label' => 'Listen',
      'options' => 'icon',
    ),
    array (
      'tablename' => 'node',
      'field' => 'title',
      'label' => 'Program',
      'handler' => 'views_handler_field_nodelink',
      'options' => 'link',
    ),
    array (
      'tablename' => 'station_program',
      'field' => 'genre',
      'label' => 'Genre',
    ),
    array (
      'tablename' => 'station_djs',
      'field' => 'djs',
      'label' => variable_get('station_schedule_dj_title', 'DJs'),
    ),
  );
  // If there's no archive remove the listen links.
  if (!station_has_archive()) {
    unset($view->field[1]);
  }
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'station_program',
),
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(station_schedule_item, node, station_program, station_djs);
  $views[$view->name] = $view;



  $view = new stdClass();
  $view->name = 'station_scheduled_programs';
  $view->description = 'Scheduled programs';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Programs';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = 'There are currently no scheduled programs.';
  $view->page_empty_format = '1';
  $view->page_type = 'table';
  $view->url = 'station/programs/scheduled';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '50';
  $view->menu = TRUE;
  $view->menu_title = 'Scheduled';
  $view->menu_tab = TRUE;
  $view->menu_tab_weight = '0';
  $view->menu_tab_default = TRUE;
  $view->menu_tab_default_parent = NULL;
  $view->menu_tab_default_parent_type = 'normal';
  $view->menu_parent_tab_weight = '0';
  $view->menu_parent_title = 'Programs';
  $view->sort = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'sortorder' => 'ASC',
      'options' => '',
    ),
  );
  $view->argument = array (
  );
  $view->field = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'label' => 'Title',
      'handler' => 'views_handler_field_nodelink',
      'options' => 'link',
    ),
    array (
      'tablename' => 'station_program',
      'field' => 'genre',
      'label' => 'Genre',
    ),
    array (
      'tablename' => 'station_djs',
      'field' => 'djs',
      'label' => variable_get('station_schedule_dj_title', 'DJs'),
    ),
    array (
      'tablename' => 'station_schedule_item',
      'field' => 'times',
      'label' => 'Times',
    ),
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'station_program',
),
    ),
    array (
      'tablename' => 'node',
      'field' => 'status',
      'operator' => '=',
      'options' => '',
      'value' => '1',
    ),
    array (
      'tablename' => 'station_schedule_item',
      'field' => 'is_scheduled',
      'operator' => '1',
      'options' => '',
      'value' => '',
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(node, station_program, station_djs, station_schedule_item);
  $views[$view->name] = $view;



  $view = new stdClass();
  $view->name = 'station_unscheduled_programs';
  $view->description = 'Unscheduled programs';
  $view->access = array (
);
  $view->view_args_php = '';
  $view->page = TRUE;
  $view->page_title = 'Programs';
  $view->page_header = '';
  $view->page_header_format = '1';
  $view->page_footer = '';
  $view->page_footer_format = '1';
  $view->page_empty = 'There are currently no unscheduled programs.';
  $view->page_empty_format = '1';
  $view->page_type = 'table';
  $view->url = 'station/programs/unscheduled';
  $view->use_pager = TRUE;
  $view->nodes_per_page = '50';
  $view->menu = TRUE;
  $view->menu_title = 'Unscheduled';
  $view->menu_tab = TRUE;
  $view->menu_tab_weight = '1';
  $view->menu_tab_default = FALSE;
  $view->menu_tab_default_parent = NULL;
  $view->menu_tab_default_parent_type = 'tab';
  $view->menu_parent_tab_weight = '0';
  $view->menu_parent_title = '';
  $view->sort = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'sortorder' => 'ASC',
      'options' => '',
    ),
  );
  $view->argument = array (
  );
  $view->field = array (
    array (
      'tablename' => 'node',
      'field' => 'title',
      'label' => 'Title',
      'handler' => 'views_handler_field_nodelink',
      'options' => 'link',
    ),
    array (
      'tablename' => 'station_program',
      'field' => 'genre',
      'label' => 'Genre',
    ),
    array (
      'tablename' => 'station_djs',
      'field' => 'djs',
      'label' => variable_get('station_schedule_dj_title', 'DJs'),
    ),
  );
  $view->filter = array (
    array (
      'tablename' => 'node',
      'field' => 'type',
      'operator' => 'OR',
      'options' => '',
      'value' => array (
  0 => 'station_program',
),
    ),
    array (
      'tablename' => 'node',
      'field' => 'status',
      'operator' => '=',
      'options' => '',
      'value' => '1',
    ),
    array (
      'tablename' => 'station_schedule_item',
      'field' => 'is_scheduled',
      'operator' => '0',
      'options' => '',
      'value' => '',
    ),
  );
  $view->exposed_filter = array (
  );
  $view->requires = array(node, station_program, station_djs, station_schedule_item);
  $views[$view->name] = $view;


  return $views;
}
GIF89a 
 € ÿ3 ÿÿÿ!ù   ,     
  Œ€¡lÍ ´ºYé�yg=9�$>f¹8 ;GIF89a  €  ÿÿÿÿf !ù     ,       Œ�©Ëí€DÄ™lÐ:¶Ju›è‘[¦p¸bÐÇp ;