calendar_ical.module in Calendar 5
Same filename and directory in other branches
Adds ical functionality to Calendar.
File
calendar_ical.moduleView 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
Name | 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 |