You are here

calendar_ical.module in Calendar 5

Same filename and directory in other branches
  1. 5.2 calendar_ical.module

Adds ical functionality to Calendar.

File

calendar_ical.module
View source
<?php

/**
 * @file
 * Adds ical functionality to Calendar.
 */

/**
 *  Implementation of hook_menu().
 */
function calendar_ical_menu($may_cache) {
  include_once drupal_get_path('module', 'calendar') . '/calendar.theme';
  drupal_add_css(drupal_get_path('module', 'calendar') . '/calendar.css');
  $items = array();
  if (!$may_cache) {
    foreach (calendar_info() as $view_name => $view) {
      $parts = explode('/', $view['url']);
      foreach ($parts as $delta => $part) {
        if (in_array($part, array(
          '$arg',
          '$node',
          '$user',
          '$group',
        ))) {
          $parts[$delta] = arg($delta);
        }
      }
      $items[] = array(
        'path' => implode('/', $parts) . '/ical',
        'title' => t('iCal'),
        'description' => t('iCal setup.'),
        'access' => user_access('administer views'),
        'callback' => 'drupal_get_form',
        'callback arguments' => array(
          'calendar_ical_setup_form',
          $view_name,
        ),
        'type' => MENU_LOCAL_TASK,
        'weight' => 6,
      );
    }
  }
  return $items;
}

/**
 * Implementation of hook_views_tabs().
 */
function calendar_ical_views_tabs($op) {
  switch ($op) {
    case 'names':
      return array(
        'ical',
      );
  }
}

/**
 * Identify the cache where the ical feeds are stored.
 *
 * @return unknown
 */
function calendar_ical_cache() {
  return 'cache';
}

/**
 * Implementation of hook_cron().
 */
function calendar_ical_cron() {
  cache_clear_all('calendar_feeds_', calendar_ical_cache(), TRUE);
}

/**
 * Helper function to load date_ical file.
 */
function calendar_ical_load_date_ical() {
  include_once drupal_get_path('module', 'date_api') . '/date_api_ical.inc';
}

/**
 * Implementation of hook_calendar_add_items().
 */
function calendar_ical_calendar_add_items($view) {
  return calendar_ical_add_feeds($view);
}

/**
 *  Implementation of hook_calendar_add_types().
 */
function calendar_ical_calendar_add_types($view) {
  return array(
    'calendar_ical' => t('Calendar: iCal Feed'),
  );
}

/**
 * Setup Calendar feeds.
 *
 * @todo - control of the stripe color is not yet implemented.
 */
function calendar_ical_setup_form($view_name) {
  $form = array();
  $view = views_get_view($view_name);
  for ($i = 0; $i < 10; $i++) {
    $node = new StdClass();
    $node->stripe = $i;
    $stripes[$i] = $i . '<div class="calendar" style="width:150px;">' . theme('calendar_stripe_stripe', $node) . '</div>';
  }
  $form['#suffix'] = t('<h3>Stripe options</h3>') . implode($stripes);
  $period = drupal_map_assoc(array(
    0,
    3600,
    10800,
    21600,
    32400,
    43200,
    86400,
    172800,
    259200,
    604800,
    1209600,
    2419200,
    4838400,
    9676800,
  ), 'format_interval');
  $form['calendar_ical_expire_' . $view->name] = array(
    '#type' => 'select',
    '#title' => t('Expire iCal cache'),
    '#default_value' => variable_get('calendar_ical_expire_' . $view->name, 9676800),
    '#options' => $period,
    '#description' => t('iCal feeds are cached to improve performance. Set an expiration time for cached feeds.'),
  );
  $empty_feed = array(
    0 => array(
      'name' => '',
      'url' => '',
      'type' => 'ical',
      'stripe' => 0,
    ),
  );
  $form[$view->name] = array(
    '#type' => 'fieldset',
    '#title' => t('iCal Feeds'),
    '#description' => t('Use this section to set up iCal feeds that should be displayed in this calendar. They will be shown along with any internal items that match the calendar criteria.'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#tree' => TRUE,
  );

  // One empty input form will be added after any existing items.
  $view_feeds = array_merge((array) variable_get('calendar_feeds_' . $view->name, $empty_feed), $empty_feed);
  foreach ($view_feeds as $delta => $feed) {
    $form[$view->name][$delta] = array(
      'type' => array(
        '#title' => t('Feed type'),
        '#type' => 'hidden',
        '#value' => 'ical',
      ),
      'name' => array(
        '#title' => t('Name'),
        '#type' => 'textfield',
        '#default_value' => $feed['name'],
        '#description' => t('The name of a feed to include in this calendar.'),
      ),
      'url' => array(
        '#title' => t('Url'),
        '#type' => 'textarea',
        '#rows' => 2,
        '#default_value' => $feed['url'],
        '#description' => t('The external feed url or internal file path and name. Change \'webcal://\' to \'http://\'.'),
      ),
      'stripe' => array(
        '#title' => t('Stripe'),
        '#type' => 'select',
        '#options' => range(0, 10),
        '#default_value' => $feed['stripe'],
        '#description' => t('The color stripe to use for this feed (not working yet).'),
        '#suffix' => '<hr>',
      ),
    );
  }
  $form['view_name'] = array(
    '#type' => 'hidden',
    '#value' => $view->name,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Save requested values.
 */
function calendar_ical_setup_form_submit($form_id, $form_values) {
  calendar_ical_load_date_ical();
  $view_name = $form_values['view_name'];
  foreach ($form_values as $value_name => $value) {
    if ($value_name == 'calendar_ical_expire_' . $view_name) {
      variable_set('calendar_ical_expire_' . $view_name, $value);
    }
    elseif (is_array($value)) {
      foreach ($value as $delta => $item) {

        // Don't save empty values.
        if (trim($item['url']) == '' || trim($item['name']) == '') {
          unset($value[$delta]);
        }
        else {

          // Replace 'webcal' protocol with http protocol.
          $item['url'] = str_replace('webcal:', 'http:', $item['url']);

          // Don't save invalid urls.
          $events = date_ical_import($item['url']);
          if (!is_array($events)) {
            unset($value[$delta]);
          }
          else {
            $value[$delta]['url'] = $item['url'];
          }
        }
      }
      variable_set('calendar_feeds_' . $value_name, $value);
    }
  }
  cache_clear_all('calendar_feeds_' . $view->name, calendar_ical_cache(), TRUE);
}

/**
 * Bring an ical feed into the calendar.
 *
 * @param $view - the view being manipulated
 * @param $refresh - whether or not to force a refresh of the feed
 * @return $nodes to add to calendar
 * @todo probably need to add more validation of the results in case the url doesn't work.
 */
function calendar_ical_add_feeds($view, $refresh = FALSE) {
  $nodes = array();
  calendar_load_date_api();
  calendar_ical_load_date_ical();
  $feeds = variable_get('calendar_feeds_' . $view->name, array());
  $expire = intval(variable_get('calendar_ical_expire_' . $view->name, 9676800) + time());
  foreach ($feeds as $delta => $feed) {
    if ($view->build_type == 'page') {
      $GLOBALS['calendar_stripe_labels'][$feed['stripe']] = $feed['name'];
    }
    if (!$refresh && ($cached = cache_get('calendar_feeds_' . $view->name . ':' . $feed['url'], calendar_ical_cache()))) {
      $nodes += (array) unserialize($cached->data);
    }
    else {
      $expire = intval(variable_get('calendar_ical_expire_' . $view->name, 9676800) + time());
      switch ($feed['type']) {
        case 'ical':
          $filename = $feed['url'];
          $events = date_ical_import($filename);
          $new_nodes = array();
          foreach ($events as $key => $value) {
            if (is_numeric($key) && $value['TYPE'] == 'VEVENT') {
              $date = date_ical_date($value['DTSTART'], $value['DTSTART']['tz']);
              $start = $date->local->timestamp;
              $start_timezone = $date->local->timezone;
              $start_offset = $date->local->offset;
              $date = date_ical_date($value['DTEND'], $value['DTEND']['tz']);
              $end = $date->local->timestamp;
              if (empty($start)) {
                continue;
              }
              $node = new StdClass();
              $node->nid = $value['UID'];
              $node->title = $value['SUMMARY'];
              $node->label = $feed['name'];
              $node->teaser = $value['DESCRIPTION'];
              $node->calendar_start = $start;
              $node->start_offset = $start_offset;
              $node->calendar_end = $end;
              $node->end_offset = $date->local->offset;
              $show_tz = ' e';
              $time_format = variable_get('calendar_time_format_' . $view->name, 'H:i');
              $full_format = date_limit_format(variable_get('date_format_short', 'm/d/Y - H:i'), $value['DTSTART']['granularity']) . $show_tz;
              $node->start_time_format = $value['DTSTART']['all_day'] ? t('All day') : date_format_date($time_format, $start, $start_offset, $start_timezone);
              $node->end_time_format = $value['DTEND']['all_day'] ? t('All day') : date_format_date($time_format, $end, $start_offset, $start_timezone);
              $node->start_format = date_format_date($full_format, $start, $start_offset, $start_timezone);
              $node->end_format = date_format_date($full_format, $end, $start_offset, $start_timezone);
              $node->all_day = $value['DTSTART']['all_day'];
              $node->stripe = $feed['stripe'];
              $node->remote = TRUE;
              $node->uid = $value['uid'] ? $value['UID'] : (is_numeric($node->nid) ? url("node/{$node->nid}", NULL, NULL, 1) : $node->nid);
              $node->url = $value['URL'] ? $value['URL'] : (is_numeric($node->nid) ? url("node/{$node->nid}", NULL, NULL, 1) : $node->nid);
              $node->calendar_node_theme = 'calendar_ical_node';
              $new_nodes[$value['UID']] = $node;
            }
          }
          cache_set('calendar_feeds_' . $view->name . ':' . $feed['url'], calendar_ical_cache(), serialize($new_nodes), $expire);
          $nodes += (array) $new_nodes;
          break;
      }
    }
  }
  return $nodes;
}

/**
 * Provide views plugins for the feed types we support.
 */
function calendar_ical_views_style_plugins() {
  return array(
    'calendar_ical' => array(
      'name' => t('Calendar: iCal Feed'),
      'theme' => 'calendar_ical_feed',
      'needs_table_header' => TRUE,
      'needs_fields' => TRUE,
      'even_empty' => TRUE,
    ),
  );
}

/**
 * While we support the global selector, some might want to allow
 * ONLY ical feeds so we support a stingy selector too
 */
function calendar_ical_views_arguments() {
  $arguments = array(
    'calendar_ical' => array(
      'name' => t('Calendar: iCal Feed'),
      'handler' => 'views_handler_arg_ical',
      'help' => t('Add this as the last argument to a calendar view to provide an iCal feed of the view.'),
    ),
  );
  return $arguments;
}

/**
 * handler for our own ical argument; mimics the feed selector
 */
function views_handler_arg_ical($op, &$query, $argtype, $arg = '') {
  switch ($op) {
    case 'summary':
    case 'sort':
    case 'link':
    case 'title':
      break;
    case 'filter':

      // This is a clone of the default selector, but it just invokes ours
      // rather than calling all of them.
      calendar_ical_views_feed_argument('argument', $GLOBALS['current_view'], $arg, $argtype);
  }
}

/**
 * post view for our own op -- mimics the feed selector
 */
function calendar_ical_views_post_view($view, $items, $output) {
  foreach ($view->argument as $id => $argument) {
    if ($argument['type'] == 'calendar_ical') {
      $feed = $id;
      break;
    }
  }
  if ($feed !== NULL) {
    $query = NULL;
    return calendar_ical_views_feed_argument('post_view', $view, 'ical', $query);
  }
}

/**
 * feed argument hook that will convert us to ical or display an icon.
 * the 4th argument isn't part of the hook, but we use it to differentiate
 * when called as a hook or when called manually from calendar_ical_views_post_view
 */
function calendar_ical_views_feed_argument($op, &$view, $arg, $argtype = NULL) {
  if ($op == 'argument' && $arg == 'ical') {

    // Keep devel module from appending queries to ical export.
    $GLOBALS['devel_shutdown'] = FALSE;
    $view->page_type = 'calendar_ical';

    // reset the 'real url' to the URL without the feed argument.
    $view_args = array();
    $max = count($view->args);
    foreach ($view->args as $id => $view_arg) {
      ++$count;
      if ($view_arg == $arg && $view->argument[$id]['id'] == $argtype['id']) {
        if ($count != $max) {
          $view_args[] = $argtype['wildcard'];
        }
      }
      else {
        $view_args[] = $view_arg;
      }
    }
    $view->feed_url = views_get_url($view, $view_args);
  }
  else {
    if ($op == 'post_view') {
      $args = calendar_ical_post_view_make_args($view, $arg, 'ical');
      $url = views_get_url($view, $args);
      $title = views_get_title($view, 'page', $args);
      if ($view->used_filters) {
        $filters = drupal_query_string_encode($view->used_filters);
      }
      return implode(calendar_ical_add_ical(url($url, $filters), $title));
    }
  }
}

/**
 * helper function -- this function builds a URL for a given feed.
 * It defaults to the built in feed selector, but the 3rd arg can
 * be used to set it up for custom selectors too.
 */
function calendar_ical_post_view_make_args($view, $feed_id, $arg) {

  // assemble the URL
  $args = array();
  foreach ($view->argument as $id => $argdata) {
    if (isset($view->args[$id])) {
      $args[] = $view->args[$id];
    }
    else {
      if ($argdata['id'] == $feed_id && !in_array($arg, $args)) {
        $args[] = $arg;
      }
      else {
        if ($argdata['argdefault'] != 1 && !in_array($arg, $args)) {
          $args[] = $arg;
        }
      }
    }
  }
  return $args;
}
function calendar_ical_add_ical($url = NULL, $title = '') {
  if (!is_null($url)) {
    $stored_feed_links[$url] = theme('ical_icon', $url);
    drupal_add_link(array(
      'rel' => 'alternate',
      'type' => 'application/calendar',
      'title' => $title,
      'href' => $url,
    ));
  }
  return $stored_feed_links;
}
function theme_ical_icon($url) {
  if ($image = theme('image', drupal_get_path('module', 'date_api') . '/images/ical16x16.gif', t('Add to calendar'), t('Add to calendar'))) {
    return '<div style="text-align:right"><a href="' . check_url($url) . '" class="ical-icon" title="ical">' . $image . '</a></div>';
  }
}

/**
 * plugin that actually displays an ical feed
 */
function theme_calendar_ical_feed($view, $items, $type) {
  calendar_ical_load_date_ical();
  if ($type == 'block') {
    return;
  }
  $results = calendar_get_nodes($view, $items, $type);
  $nodes = $results['nodes'];

  // Unset empty nodes that might have been created to fill out empty calendars.
  unset($nodes[0]);
  $params = $results['params'];
  foreach ((array) $nodes as $delta => $node) {

    // switch our psuedo nids back to the right values
    $tmp = explode(':', $node->nid);
    $node->nid = $tmp[0];
    $nodes[$delta] = $node;
  }
  foreach (module_implements('calendar_add_items') as $module) {
    $function = $module . '_calendar_add_items';
    $nodes += $function($view);
  }
  drupal_set_header('Content-Type: text/calendar; charset=utf-8');
  drupal_set_header('Content-Disposition: attachment; filename="calendar.ics"; ');
  $events = array();
  foreach ($nodes as $node) {
    $event = array();

    // Allow modules to affect item fields
    node_invoke_nodeapi($node, 'ical item');
    $event['start'] = $node->calendar_start;
    $event['end'] = $node->calendar_end;
    $event['timezone'] = 'GMT';
    $event['location'] = $node->calendar_location;
    $event['summary'] = $node->title;
    $event['description'] = $node->teaser;
    $event['uid'] = $node->uid ? $node->uid : (is_numeric($node->nid) ? url("node/{$node->nid}", NULL, NULL, 1) : $node->nid);
    $event['url'] = $node->url ? $node->url : (is_numeric($node->nid) ? url("node/{$node->nid}", NULL, NULL, 1) : $node->nid);
    $events[] = $event;
  }
  $description = $headertitle . ($title ? ' | ' . $title : '');
  print date_ical_export($events, $description);
  module_invoke_all('exit');
  exit;
}

/**
 * A theme for the ical nodes.
 */
function theme_calendar_ical_node($node, $type) {

  // Remote nodes may come in with lengthy descriptions that won't fit
  // in small boxes of year, month, and week calendars.
  if ($type != 'calendar_node_day') {
    $node->teaser = '';
  }

  // Check plain may leave some html entities in the title.
  $node->title = html_entity_decode(check_plain($node->title), ENT_QUOTES);
  $node->teaser = check_markup($node->teaser);
  return theme($type, $node);
}

Functions

Namesort descending Description
calendar_ical_add_feeds Bring an ical feed into the calendar.
calendar_ical_add_ical
calendar_ical_cache Identify the cache where the ical feeds are stored.
calendar_ical_calendar_add_items Implementation of hook_calendar_add_items().
calendar_ical_calendar_add_types Implementation of hook_calendar_add_types().
calendar_ical_cron Implementation of hook_cron().
calendar_ical_load_date_ical Helper function to load date_ical file.
calendar_ical_menu Implementation of hook_menu().
calendar_ical_post_view_make_args helper function -- this function builds a URL for a given feed. It defaults to the built in feed selector, but the 3rd arg can be used to set it up for custom selectors too.
calendar_ical_setup_form Setup Calendar feeds.
calendar_ical_setup_form_submit Save requested values.
calendar_ical_views_arguments While we support the global selector, some might want to allow ONLY ical feeds so we support a stingy selector too
calendar_ical_views_feed_argument feed argument hook that will convert us to ical or display an icon. the 4th argument isn't part of the hook, but we use it to differentiate when called as a hook or when called manually from calendar_ical_views_post_view
calendar_ical_views_post_view post view for our own op -- mimics the feed selector
calendar_ical_views_style_plugins Provide views plugins for the feed types we support.
calendar_ical_views_tabs Implementation of hook_views_tabs().
theme_calendar_ical_feed plugin that actually displays an ical feed
theme_calendar_ical_node A theme for the ical nodes.
theme_ical_icon
views_handler_arg_ical handler for our own ical argument; mimics the feed selector