Commit db06bc87 authored by Karen Stevenson's avatar Karen Stevenson

Initial commit of D7 code.

parent 97f398ae
Date iCal Module
Roadmap and Background:
This module contains code to create an iCal feed in Views. It has been pulled out
of the Calendar iCal module because it has no dependency on Calendar and it could
be used on any view. The code has also been generalized so that it will work for
any entity, not just nodes. It creates an 'iCal' view mode for every entity type
that can be used to to configure the description used in the iCal feed, and adds
theme suggestions to tell Drupal to look for an iCal version of the entity template.
The Calendar iCal module will be deprecated in favor of this module.
Future plans for this module:
- Add in an iCal parser for the Feeds module that can parse an iCal feed
from another site so it is possible to import as well as export iCal information.
- Add in ways to take advantage of other parts of the iCal specification,
like LOCATION or VALARM.
- Add some pre-configured features that illustrate how to set up a site
that provides an iCal feed and consume iCal information from other sites.
To use this module to create an iCal feed:
- Enable Date API, Views, and Date iCal.
- Go to the Display Fields screen for the entity you want to export in an iCal field.
You will see that there is a new view mode for all entities, called 'iCal'.
- Set up the iCal view mode to contain whatever should be exported as the 'Description'
field for the iCal field. You can trim the text to the desired size, include other fields, etc.
- Optional, but recommended, create a new node.tpl file for the iCal view mode,
using the name ENTITYTYPE--ical.tpl.php or ENTITYTYPE--BUNDLE--ical.tpl.php. Make this a very
simple template that omits the title, submitted by information, links, and comments.
Place that file in the theme folder.
- Clear the cache so the new template can be discovered by Drupal.
- Create a new view that contains the items you want to display in the iCal feed.
- Add a feed to the view, using the feed type 'Date iCal'.
- Choose to 'Show' the feed as 'Date iCal Entities' (rather than 'Content' or 'Fields').
- In the settings for Date iCal Entities, select the specific date field
that should be used as the event date for the iCal feed.
- Give the feed a path like 'calendar/ical/%/calendar.ics', where you have a '/%/' for every
contextual filter in the view.
- Add date filters or arguments that will constrain the view to the items you want to display in the feed.
- Attach the feed to a page view.
- Navigate to the page view. You should see the iCal icon at the bottom of the view.
If you click on the icon it will download an .ics file with the events that matched
the view criteria.
\ No newline at end of file
<?php
/**
* @file
* Default theme implementation to display an iCal alarm.
*
* Available variables:
* - $alarm: An array with the following information about each alarm:
* - $alarm['action']: The action to take, either 'DISPLAY' or 'EMAIL'
* - $alarm['trigger']: The time period for the trigger, like -P2D.
* - $alarm['repeat']: The number of times to repeat the alarm.
* - $alarm['duration']: The time period between repeated alarms, like P1D.
* - $alarm['description']: The description of the alarm.
*
* An email alarm should have two additional parts:
* - $alarm['email']: A comma-separated list of email recipients.
* - $alarm['summary']: The subject of the alarm email.
*
* If you are editing this file, remember that all output lines generated by it
* must end with DOS-style \r\n line endings, and not Unix-style \n, in order to
* be compliant with the iCal spec.
*
* @see http://tools.ietf.org/html/rfc5545#section-3.1
*
* @ingroup themeable
*/
print "BEGIN:VALARM\r\n";
print "ACTION:" . $alarm['action'] . "\r\n";
if (!empty($alarm['trigger'])):
print "TRIGGER:" . $alarm['trigger'] . "\r\n";
endif;
if (!empty($alarm['repeat'])):
print "REPEAT:" . $alarm['repeat'] . "\r\n";
endif;
if (!empty($alarm['duration'])):
print "DURATION:" . $alarm['duration'] . "\r\n";
endif;
if ($alarm['action'] == 'EMAIL'):
print "ATTENDEE:MAILTO:" . $alarm['email'] . "\r\n";
print "SUMMARY:" . $alarm['summary'] . "\r\n";
endif;
print "DESCRIPTION:" . $alarm['description'] . "\r\n";
print "END:VALARM\r\n";
<?php
/**
* @file
* Default theme implementation to display an iCal calendar.
*
* Available variables:
* - $title: The name of the calendar.
* - $rows: An array of events.
*
* If you are editing this file, remember that all output lines generated by it
* must end with DOS-style \r\n line endings, and not Unix-style \n, in order to
* be compliant with the iCal spec.
*
* @see http://tools.ietf.org/html/rfc5545#section-3.1
* @see date-vevent.tpl.php.
* @see date-valarm.tpl.php.
*
* @ingroup themeable
*/
if (empty($method)):
$method = 'PUBLISH';
endif;
print "BEGIN:VCALENDAR\r\n";
print "VERSION:2.0\r\n";
print "METHOD:$method\r\n";
if (!empty($title)):
print "X-WR-CALNAME;VALUE=TEXT:$title\r\n";
endif;
print "PRODID:-//Drupal iCal API//EN\r\n";
// Note that theme('date_vevent') already has the right line endings.
foreach($rows as $row):
print $row;
endforeach;
print "END:VCALENDAR\r\n";
<?php
/**
* @file
* Default theme implementation to display an iCal event.
*
* Available variables:
* - $event: An array with the following information about each event:
* - $event['uid']: A unique id for the event (usually the url).
* - $event['summary']: The name of the event.
* - $event['start']: The formatted start date of the event.
* - $event['end']: The formatted end date of the event.
* - $event['all_day'] - whether the item is an all day event.
* - $event['rrule']: The RRULE of the event, if any.
* - $event['timezone']: The formatted timezone name of the event, if any.
* - $event['url']: The url for the event.
* - $event['location']: Either the name of the event location, or a vvenue
* location id.
* - $event['description']: A description of the event.
* - $event['alarm']: An optional array of alarm values.
*
* If you are editing this file, remember that all output lines generated by it
* must end with DOS-style \r\n line endings, and not Unix-style \n, in order to
* be compliant with the iCal spec.
*
* @see http://tools.ietf.org/html/rfc5545#section-3.1
* @see date-valarm.tpl.php.
*
* @ingroup themeable
*/
$date = date_now('UTC');
$current_date = !empty($event['current_date']) ? $event['current_date'] : $date->format(DATE_FORMAT_ICAL);
print "BEGIN:VEVENT\r\n";
print "UID:" . $event['uid'] . "\r\n";
print "SUMMARY:" . $event['summary'] . "\r\n";
print "DTSTAMP:" . $current_date . "Z\r\n";
if ($event['all_day']):
print "DTSTART;VALUE=DATE:" . $event['start'] . "\r\n";
else:
print "DTSTART:" . $event['start'] . "Z\r\n";
endif;
if (!empty($event['end'])):
if (!empty($event['all_day'])):
print "DTEND;VALUE=DATE:" . $event['end'] . "\r\n";
else:
print "DTEND:" . $event['end'] . "Z\r\n";
endif;
endif;
if (!empty($event['rrule'])):
print $event['rrule'] . "\r\n";
endif;
if (!empty($event['url'])):
print "URL;VALUE=URI:" . $event['url'] . "\r\n";
endif;
if (!empty($event['location'])):
print "LOCATION:" . $event['location'] . "\r\n";
endif;
if (!empty($event['description'])):
print "DESCRIPTION:" . $event['description'] . "\r\n";
endif;
print "END:VEVENT\r\n";
<?php
/**
* @file
* Install, update and uninstall functions for the date_ical module.
*
*/
<?php
/**
* @file
* Adds ical functionality to Views.
*/
/**
* Implementation of hook_views_api().
*
*/
function date_ical_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'date_ical'),
);
}
/**
* Implements hook_theme().
*/
function date_ical_theme() {
return array(
'date_ical_icon' => array(
'variables' => array('url'),
),
);
}
/**
* The theme for the ical icon.
*/
function theme_date_ical_icon($vars) {
$url = $vars['url'];
$variables = array(
'path' => drupal_get_path('module', 'date_ical') . '/images/ical-feed-icon-34x14.png',
'title' => t('Add to calendar'), t('Add to calendar'),
);
if ($image = theme('image', $variables)) {
return '<div style="text-align:right"><a href="' . check_url($url) . '" class="ical-icon" title="ical">' . $image . '</a></div>';
}
}
/**
* Implementation of hook_preprocess_node().
*
* Hide extraneous information when printing an ical view.
* The same thing can be done in the theme for other entities,
* and this function can be overridden in the theme to produce
* different results for nodes.
*/
function date_ical_preprocess_node(&$variables){
if ($variables['view_mode'] == 'ical') {
// We hide the page elements we won't want to see.
// The display of the body and other fields will be controlled
// by the Display Fields settings for the ical view mode.
$variables['title'] = '';
$variables['title_prefix'] = '';
$variables['title_suffix'] = '';
$variables['display_submitted'] = FALSE;
$variables['links'] = '';
$variables['comments'] = '';
}
}
/**
* Implements hook_entity_info_alter().
*
* Add an 'iCal' view mode for all entities, similar to RSS view mode.
*/
function date_ical_entity_info_alter(&$info) {
$data = entity_get_info();
foreach ($data as $entity_type => $entity_info) {
$info[$entity_type]['view modes'] += array(
'ical' => array(
'label' => t('iCal'),
'custom settings' => FALSE,
),
);
}
}
/**
* Implementation of hook_views_plugins
*/
function date_ical_views_plugins() {
$module_path = drupal_get_path('module', 'date_ical');
module_load_include('inc', 'date_ical', 'theme.inc');
$data = array(
'module' => 'date_ical', // This just tells our themes are elsewhere.
'style' => array(
'date_ical' => array(
'title' => t('Date iCal Feed'),
'help' => t('Generates an iCal VCALENDAR feed from a view.'),
'handler' => 'date_ical_plugin_style_ical_feed',
'path' => "$module_path",
'theme' => 'date_vcalendar',
'theme file' => 'theme.inc',
'theme path' => "$module_path",
'uses fields' => TRUE,
'uses grouping' => FALSE,
'uses row plugin' => TRUE,
'uses options' => TRUE,
'type' => 'feed',
'even empty' => TRUE,
),
),
'row' => array(
'date_ical' => array(
'title' => t('Date iCal Entities'),
'help' => t('Display each entity as an iCal VEVENT item.'),
'handler' => 'date_ical_plugin_row_ical_feed',
'path' => "$module_path",
'theme' => 'date_vevent',
'theme file' => 'theme.inc',
'theme path' => "$module_path",
'uses options' => TRUE,
'uses fields' => FALSE,
'type' => 'feed',
),
),
);
return $data;
}
/**
* Implemention of hook_theme_registry_alter().
* Technique borrowed from Display Suite module.
* Add a custom preprocess hook that will work for all types of entities
*/
function date_ical_theme_registry_alter(&$theme_registry) {
$entity_info = entity_get_info();
foreach ($entity_info as $entity => $info) {
// User uses user_profile for theming.
if ($entity == 'user') $entity = 'user_profile';
// Only add preprocess functions if entity exposes theme function.
if (isset($theme_registry[$entity])) {
$theme_registry[$entity]['preprocess functions'][] = 'date_ical_preprocess';
}
}
// Support for File Entity.
if (isset($theme_registry['file_entity'])) {
$theme_registry['file_entity']['preprocess functions'][] = 'date_ical_preprocess';
}
// Support for Entity API.
if (isset($theme_registry['entity'])) {
$theme_registry['entity']['preprocess functions'][] = 'date_ical_preprocess';
}
}
/**
* Technique borrowed from Display Suite module.
* Add ical template suggestions to all types of entities.
*/
function date_ical_preprocess(&$vars) {
if (isset($vars['elements']) && isset($vars['elements']['#entity_type']) && isset($vars['elements']['#bundle']) && $vars['view_mode'] == 'ical') {
$vars['theme_hook_suggestions'][] = $vars['elements']['#entity_type'] . '__ical';
$vars['theme_hook_suggestions'][] = $vars['elements']['#entity_type'] . '__' . $vars['elements']['#bundle'] . '__ical';
}
}
<?php
/**
* @file
* Contains the iCal row style plugin.
*/
/**
* Plugin which creates a view on the resulting object
* and formats it as an iCal VEVENT.
*/
class date_ical_plugin_row_ical_feed extends views_plugin_row {
// Basic properties that let the row style follow relationships.
var $base_table = 'node';
var $base_field = 'nid';
// Stores the nodes loaded with pre_render.
var $entities = array();
function init(&$view, &$display, $options = NULL) {
parent::init($view, $display, $options);
$this->base_table = $view->base_table;
$this->base_field = $view->base_field;
}
function option_definition() {
$options = parent::option_definition();
$options['date_field'] = array('default' => array());
return $options;
}
/**
* Provide a form for setting options.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$data = date_views_fields($this->base_table);
$options = array();
foreach ($data['name'] as $item => $value) {
// We only want to see one value for each field, skip '_value2', and other columns.
if ($item == $value['fromto'][0]) {
$options[$item] = $value['label'];
}
}
$form['date_field'] = array(
'#type' => 'select',
'#title' => t('Date field'),
'#options' => $options,
'#default_value' => $this->options['date_field'],
'#description' => t('Please identify the field to use as the iCal date for each item in this view. Add a Date Filter or a Date Argument to the view to limit results to content in a specified date range.'),
'#required' => TRUE,
'#prefix' => t("The entity label will be used as the 'summary' and the entity view will be used as the 'description' for the iCal event. To control the description, hide or show fields for the iCal view mode on the 'Display Fields' screen. Keep in mind that any html in the entity view will be stripped out in the feed, to comply with the iCal standards."),
);
}
function pre_render($values) {
// @TODO When the date is coming in through a relationship, the nid
// of the view is not the right node to use, then we need the related node.
// Need to sort out how that should be handled.
// Preload each entity used in this view from the cache.
// Provides all the entity values relatively cheaply, and we don't
// need to do it repeatedly for the same entity if there are
// multiple results for one entity.
$ids = array();
foreach ($values as $row) {
// Use the $id as the key so we don't create more than one value per entity.
$id = $row->{$this->field_alias};
// Node revisions need special loading.
if ($this->view->base_table == 'node_revision') {
$this->entities[$id] = node_load(NULL, $id);
}
// For other entities we just create an array of ids to pass
// to entity_load().
else {
$ids[$id] = $id;
}
}
$base_tables = date_views_base_tables();
$this->entity_type = $base_tables[$this->view->base_table];
if (!empty($ids)) {
$this->entities = entity_load($this->entity_type, $ids);
}
// Get the language for this view.
$this->language = $this->display->handler->get_option('field_language');
$substitutions = views_views_query_substitutions($this->view);
if (array_key_exists($this->language, $substitutions)) {
$this->language = $substitutions[$this->language];
}
}
function render($row) {
global $base_url;
$id = $row->{$this->field_alias};
if (!is_numeric($id)) {
return;
}
// Load the specified entity:
$entity = $this->entities[$id];
if (empty($entity)) {
return;
}
$data = date_views_fields($this->base_table);
$info = $data['name'][$this->options['date_field']];
$field_name = str_replace(array('_value', '_value2'), '', $info['real_field_name']);
$table_name = $info['table_name'];
$delta_field = $info['delta_field'];
$is_field = $info['is_field'];
// This is ugly and hacky but I can't figure out any generic way to
// recognize that the node module is going to give some the revision timestamp
// a different field name on the entity than the actual column name in the database.
if ($this->view->base_table == 'node_revision' && $field_name == 'timestamp') {
$field_name = 'revision_timestamp';
}
// Identify the field value that matched our query.
$item = $entity->$field_name;
$entity->date_id = array();
$start = NULL;
$end = NULL;
if ($is_field) {
$lang = $entity->language;
$delta = isset($row->$delta_field) ? $row->$delta_field : 0;
$item = array_key_exists($lang, $item) ? $item[$lang][$delta] : $item['und'][$delta];
$entity->date_id[] = 'calendar.' . $entity->nid . '.' . $field_name . '.' . $delta;
if (!empty($item['value'])) {
$start = new dateObject($item['value'], $item['timezone_db']);
if (array_key_exists('value2', $item)) {
$end = new dateObject($item['value2'], $item['timezone_db']);
}
else {
$end = clone $start;
}
}
}
elseif (!$is_field && !empty($item)) {
$start = new dateObject($item, $item['timezone_db']);
$end = new dateObject($item, $item['timezone_db']);
}
// If we don't have an iCal date value, go no further.
if (empty($start)) {
return;
}
// Set the item date to the proper display timezone;
$start->setTimezone(new dateTimezone($item['timezone']));
$end->setTimezone(new dateTimezone($item['timezone']));
$start_formatted = $start->format(DATE_FORMAT_DATETIME);
$end_formatted = $end->format(DATE_FORMAT_DATETIME);
$all_day = date_is_all_day($start_formatted, $end_formatted, date_granularity_precision($info['granularity']));
// According to RFC 2445 (clarified in RFC 5545) the DTEND value is
// non-inclusive. When it is a DATE rather than a DATETIME, this means
// that we should add one day to its value.
if ($all_day) {
$end->modify("+1 day");
}
module_load_include('inc', 'date_api', 'date_api_ical');
$item_text = '';
// Create the rendered display using the display settings from the 'iCal' view mode.
$rendered_array = entity_view($this->entity_type, array($entity), 'ical', $this->language, TRUE);
$item_text = drupal_render($rendered_array);
$event = array();
$event['summary'] = date_ical_escape_text(entity_label($this->entity_type, $entity));
$event['all_day'] = $all_day;
$event['start'] = $start->format($all_day ? DATE_FORMAT_ICAL_DATE : DATE_FORMAT_ICAL);
$event['end'] = $end->format($all_day ? DATE_FORMAT_ICAL_DATE : DATE_FORMAT_ICAL);
$event['description'] = date_ical_escape_text(($item_text));
$event['url'] = url(entity_uri($this->entity_type, $entity), array('absolute' => TRUE));
$event['uid'] = !empty($entity->date_id) ? $entity->date_id[0] : $event['url'];
$event['rrule'] = $is_field && array_key_exists('rrule', $item) ? $item['rrule'] : '';
return theme($this->theme_functions(),
array(
'view' => $this->view,
'options' => $this->options,
'event' => $event
));
}
}
<?php
/**
* @file
* Views style plugin for the Date iCal module.
*/
/**
* Default style plugin to render an iCal feed.
*/
class date_ical_plugin_style_ical_feed extends views_plugin_style_rss {
function attach_to($display_id, $path, $title) {
$display = $this->view->display[$display_id]->handler;
$url_options = array();
$input = $this->view->get_exposed_input();
if ($input) {
$url_options['query'] = $input;
}
$url_options['absolute'] = TRUE;
$url = url($this->view->get_url(NULL, $path), $url_options);
if (empty($this->preview)) {
$this->view->feed_icon = theme('date_ical_icon', array('url' => $url, 'title' => $title));
drupal_add_html_head_link(array(
'rel' => 'alternate',
'type' => 'application/calendar',
'title' => $title,
'href' => $url
));
}
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Override the text in the RSS description field.
$form['description'] = array(
'#type' => 'textfield',
'#title' => t('iCal description'),
'#default_value' => $this->options['description'],
'#description' => t('This will appear in the iCal feed itself.'),
);
}
function render() {
if (empty($this->row_plugin)) {
debug('views_plugin_style_default: Missing row plugin');
return;
}
$rows = array();
foreach ($this->view->result as $row_index => $row) {
$this->view->row_index = $row_index;
$rows[] = $this->row_plugin->render($row);
}
$output = theme($this->theme_functions(),
array(
'view' => $this->view,
'options' => $this->options,
'rows' => $rows
));
unset($this->view->row_index);
return $output;
}
}
\ No newline at end of file
<?php
/**
* @file
* Theme files for Date iCal.
*/
/**
* Preprocess an iCal feed
*/