You are here

calendar_ical.module in Calendar 5.2

Same filename and directory in other branches
  1. 5 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) {
  $items = array();
  if (!$may_cache) {
    drupal_add_css(drupal_get_path('module', 'calendar') . '/calendar.css');
    require_once './' . drupal_get_path('module', 'calendar') . '/calendar.module';
    foreach (calendar_info() as $view_name => $view) {
      $items[] = array(
        'path' => 'admin/settings/calendar/' . $view_name . '/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;
}
function calendar_ical_setup_form($view_name) {
  require_once './' . drupal_get_path('module', 'calendar') . '/calendar_ical_admin.inc';
  return _calendar_ical_setup_form($view_name);
}

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

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

/**
 * Implementation of hook_calendar_add_items().
 */
function calendar_ical_calendar_add_items($view) {
  require_once './' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc';
  $items = array();
  $feeds = calendar_ical_add_feeds($view);
  $min = date_format($view->min_date, DATE_FORMAT_DATETIME);
  $max = date_format($view->max_date, DATE_FORMAT_DATETIME);
  foreach ($feeds as $feed) {
    if (!empty($feed->calendar_start) && !empty($feed->calendar_end)) {
      if ($feed->calendar_start >= date_format($view->min_date, DATE_FORMAT_DATETIME) && $feed->calendar_end <= date_format($view->max_date, DATE_FORMAT_DATETIME)) {
        if ($feed->calendar_start >= $min && $feed->calendar_end <= $max || $feed->calendar_start >= $min && $feed->all_day == 1) {

          // We have to add a calendar start and end object to the feed,
          // the cached date objects will not work correctly.
          $feed->calendar_start_date = date_make_date($feed->calendar_start, $feed->timezone);
          $feed->calendar_end_date = date_make_date($feed->calendar_end, $feed->timezone);
          $items[] = $feed;
        }
      }
    }
  }
  return $items;
}

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

/**
 * 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();
  $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());
      require_once './' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc';
      switch ($feed['type']) {
        case 'ical':
          $filename = $feed['url'];
          $default = $feed['link'];
          $new_nodes = array();
          $calendars = date_ical_import($filename);
          if (empty($calendars)) {
            continue;
          }
          foreach ($calendars as $calendar) {
            if (empty($calendar) || empty($calendar['VEVENT'])) {
              continue;
            }
            foreach ($calendar['VEVENT'] as $key => $value) {
              if (empty($value['DTSTART'])) {
                continue;
              }
              $node = new stdClass();
              $node->nid = $value['UID'];
              $node->title = $value['SUMMARY'];
              $node->label = $feed['name'];
              $node->teaser = $value['DESCRIPTION'];
              $node->timezone = $value['DTSTART']['tz'];
              $node->calendar_start_date = date_ical_date($value['DTSTART'], $value['DTSTART']['tz']);
              $node->calendar_start = date_format_date($node->calendar_start_date, 'custom', DATE_FORMAT_DATETIME);
              $node->calendar_end_date = date_ical_date($value['DTEND'], $value['DTEND']['tz']);
              $node->calendar_end = date_format_date($node->calendar_end_date, 'custom', DATE_FORMAT_DATETIME);
              $show_tz = ' e';
              $granularity = $value['DTSTART']['type'] == 'DATE' ? array(
                'year',
                'month',
                'day',
              ) : array(
                'year',
                'month',
                'day',
                'hour',
                'minute',
                'second',
              );
              $node->format_time = variable_get('calendar_time_format_' . $view->name, 'H:i');
              $node->format = date_limit_format(variable_get('date_format_short', 'm/d/Y - H:i'), $granularity) . $show_tz;
              $node->all_day = $value['all_day'];
              $node->location = $value['LOCATION'];
              $node->stripe = $feed['stripe'];
              $node->stripe_label = $feed['name'];
              $node->remote = TRUE;
              $node->uid = $value['uid'] ? $value['UID'] : calendar_get_node_link($node, $default);
              $node->url = $value['URL'] ? $value['URL'] : calendar_get_node_link($node, $default);
              $node->calendar_node_theme = 'calendar_ical_node';
              $node->calendar_field_theme = 'calendar_ical_field';
              $new_nodes[$value['UID']] = $node;
            }
          }
          if (!empty($new_nodes)) {
            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;
      }
    }
    $url = calendar_get_url($view, $args, FALSE);
    $view->feed_url = $url;
  }
  else {
    if ($op == 'post_view') {
      $args = calendar_ical_post_view_make_args($view, $arg, 'ical');
      $url = calendar_get_url($view, $args, FALSE);
      $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) {
  if ($type == 'block') {
    return;
  }
  drupal_set_header('Content-Type: text/calendar; charset=utf-8');
  drupal_set_header('Content-Disposition: attachment; filename="calendar.ics"; ');
  $display_formats = variable_get('calendar_display_format_' . $view->name, array(
    'year' => 'calendar',
    'month' => 'calendar',
    'week' => 'calendar',
    'day' => 'calendar',
    'block' => 'calendar',
  ));
  $events = array();
  include_once './' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc';
  include_once './' . drupal_get_path('module', 'calendar') . '/calendar.inc';
  $items = calendar_build_nodes($view, $items, $display_formats[$view->calendar_type]);
  foreach ($items as $time) {
    foreach ($time as $node) {
      $event = array();

      // Allow modules to affect item fields
      node_invoke_nodeapi($node, 'ical item');
      if (!$node->remote) {
        $real_node = node_load($node->nid);
        $node->teaser = $real_node->teaser;
      }
      $event['start'] = $node->calendar_start_date;
      $event['end'] = $node->calendar_end_date;
      $event['location'] = $node->calendar_location;
      $event['summary'] = $node->title;
      $event['description'] = $node->teaser;
      $event['url'] = $node->url ? $node->url : calendar_get_node_link($node);
      $event['uid'] = !empty($node->date_id) ? $node->date_id : $event['url'];
      $events[] = $event;
    }
  }
  $headertitle = filter_xss_admin(views_get_title($view, 'page'));
  $title = variable_get('site_name', 'Drupal');
  $description = $headertitle . ($title ? ' | ' . $title : '');
  print date_ical_export($events, $description);
  module_invoke_all('exit');
  exit;
}

/**
 * Theme for an entire ical node.
 */
function theme_calendar_ical_node($node, $view) {

  // Check plain may leave html entities in the title.
  $node->title = html_entity_decode(check_plain($node->title), ENT - QUOTES);
  $node->teaser = check_markup($node->teaser);
  if ($view->calendar_display == 'calendar') {

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

    // Create a pseudo node view for this item.
    $field = array();
    $output = '<div class="node">';
    $output .= '<h2>' . l($node->title, $node->url) . '</h2>';
    $output .= '<div>' . theme('calendar_date_combo', $field, $node, $node->format, t('Dates'), $view) . '</div>';
    $output .= '<div class="content">' . $node->teaser . '</div>';
    $output .= '</div>';
    return $output;
  }
}

/**
 * Views field theme for an ical field.
 *
 * Used for table and list views.
 *
 * For non-calendar views that use fields, the field needs a value from the
 * imported node that matches the kind of field in the view. Most possible
 * Views fields make no sense here and will return nothing.
 */
function theme_calendar_ical_field($fieldname, $fields, $field, $node, $view, $type) {
  static $datefield;

  // Find the first date field in the view to attach the ical date to.
  // There may be more than one date field in the view and we only
  // want to put the ical field in one place.
  if (empty($datefield)) {
    $calendar_fields = calendar_fields();
    foreach ($view->field as $viewfield) {
      if (in_array($viewfield['field'], array_keys($calendar_fields))) {
        $datefield = $viewfield['queryname'];
        break;
      }
    }
  }

  // Check plain may leave html entities in the title.
  $node->title = html_entity_decode(check_plain($node->title), ENT - QUOTES);
  $node->teaser = check_markup($node->teaser);

  // Fix some common html entities that may not get decoded correctly.
  // You can add more of them in this array.
  $replace = array(
    '&#039;' => "'",
  );
  foreach (array(
    'title',
    'teaser',
  ) as $key) {
    $node->{$key} = strtr($node->{$key}, $replace);
  }
  if ($fieldname == $datefield) {
    return theme('calendar_date_combo', $field, $node, '', $view);
  }
  switch ($field['fullname']) {
    case 'node.title':
      return l($node->title, $node->url);
    case 'node.teaser':
    case 'node.body':
      return $node->teaser;
    case 'node.type':
      return $node->label;
  }
  return;
}

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_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
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.
theme_calendar_ical_feed plugin that actually displays an ical feed
theme_calendar_ical_field Views field theme for an ical field.
theme_calendar_ical_node Theme for an entire ical node.
theme_ical_icon
views_handler_arg_ical handler for our own ical argument; mimics the feed selector