Commit f947077e authored by Robert Rollins's avatar Robert Rollins

Added iCal parsing

parent c9b0df6c
......@@ -5,6 +5,11 @@ Date iCal 7.x
Version 7.x-1.x-dev
===================
- Issue #1833362, convert to using the iCalcreator library, rather than the theme system, for formatting iCal data.
- Issue #1793868, fix error when adding iCal view mode to non-standard entities.
- Issue #1863448, Remove extraneous div around the feed icon.
===================
Version 7.x-1.1
===================
......@@ -23,4 +28,4 @@ Version 7.x-1.0
- Issue #1447118 by tim.plunkett: Use properly namespaced template functions.
- Issue #1447104 by tim.plunkett: Fixed code clean-up and undefined notices.
- Issue #1446936 by tim.plunkett: Views display format will not save.
- Initial commit of D7 code.
\ No newline at end of file
- Initial commit of D7 code.
<?php
/**
* Modify a structured event before it is rendered to iCal format.
*
* This hook is invoked after the Date iCal module has generated its
* representation of the event and allows you to modify or add to the
* representation. Use this hook to set values of iCal fields that are supported
* but have no values mapped into them by default.
*
* @param $event
* An associative array representation of the iCal event. This will be used by
* the Date iCal rendering system to create an entry in an iCal feed.
* @param $view
* The view object that is being executed to render the iCal feed.
* @param $context
* An associative array of context, with the following keys and values:
* - 'entity_type': The type of entity being rendered, 'node', 'user' etc.
* - 'entity': The fully loaded entity being rendered.
* - 'language': The language code that indicates which translation of field
* data should be used.
*
*/
function hook_date_ical_feed_event_render_alter(&$event, $view, &$context) {
// Simple example adding the location to a rendered event from a simple
// textfield called 'field_location'.
$entity_type = $context['entity_type'];
$entity = $context['entity'];
$language = $context['language'];
if ($locations = field_get_items($entity_type, $entity, 'field_location', $language)) {
foreach ($locations as $location) {
$event['location'] = check_plain($location['value']);
}
}
}
/**
* Alter an iCal representation of an event.
*
* This hook allows you to modify the event that is actually being added to the
* iCal calendar. If the Date iCal doesn't support an iCal property that you
* need to use, then you can add it to the event in the iCal feed here.
*
* @param $vevent
* The iCalcreator vevent object that is being added to the iCal feed. See the
* iCalcreator library for documentation on how to use this object correctly.
* @param $view
* The view object that is being executed to render the iCal feed.
* @param $event_array
* The array representation of the event that's been rendered to the $vevent.
*/
function hook_date_ical_feed_ical_vevent_render_alter($vevent, $view, $event_array) {
}
/**
* Alter an iCal representation of a calendar.
*
* This hook allows you to modify the iCal calendar that will be rendered into
* an iCal feed. You can use this hook to add sections to the generated iCal
* feeds that the Date iCal module doesn't support etc.
*
* @param $vcalendar
* The iCalcreator vcalendar object that will be rendered to generate the iCal
* feed. See the iCalcreator library for documentation on how to use this
* object correctly.
* @param $view
* The view object that is being executed to render the iCal feed.
*/
function hook_date_ical_feed_ical_vcalendar_render_alter($vcalendar, $view) {
}
/**
* Alter the iCalcreator vcalendar object that was parsed from an iCal feed.
*
* @param $calendar
* The iCalcreator vcalendar object that was created by parsing the iCal
* feed. See the iCalcreator library for documentation on this object.
* @param $context
* An associative array of context, with the following keys and values:
* - 'source' FeedsSource object associated with this Feed.
* - 'fetcher_result': The FeedsFetcherResult object associated with this Feed.
*/
function hook_date_ical_icalcreator_calendar_alter(&$calendar, &$context) {
}
/**
* Alter an individual DateIcalIcalcreatorComponent (vevent, valarm, vtodo, etc.)
* that was parsed from an iCal feed.
*
* @param $component
* A DateIcalIcalcreatorComponent. See the definition of this class in
* includes/DateIcalIcalcreatorParser.inc for more information.
* @param $context
* An associative array of context, with the following keys and values:
* - 'calendar': The iCalcreator vcalendar object from which this
* DateIcalIcalcreatorComponent originates.
* - 'parser_result': The DateIcalParserResult object which contains the parsed
* values from the vcalendar object.
* - 'source': FeedsSource object associated with this Feed.
* - 'fetcher_result': The FeedsFetcherResult object associated with this Feed.
*/
function hook_date_ical_icalcreator_component_alter(&$component, &$context) {
// Example of what might be done with this alter hook
if ($component->getComponentType() == 'vevent') {
// Do something for vevents ...
}
if ($component->getComponentType() == 'vtimezone') {
// Do something different for vtimezones ...
}
}
/**
* Alter an individual feeds object (the source data for a single field)
* that was parsed from an iCal feed.
*
* @param $value
* A string or object representing one parsed property of an iCal component.
* @param $context
* An associative array of context, with the following keys and values:
* - 'property_key': Inernal, parser-specific identifier for this property.
* - 'property': "RAW" value of this property.
* - 'item': The DateIcalComponentInterface object that holds the unparsed component.
* - 'parser_result': The parsed result of the whole Calendar.
* - 'feeds_source': Contains all the metadata about the configuration of this Feed.
*/
function hook_date_ical_feeds_object_alter(&$value, &$context) {
// Example of what might be done with this alter hook
if ($context['property_key'] == 'dtstart') {
// Tweak the parsed FeedsDateTime object for the start time.
// ...
}
}
name = Date iCal
description = Allows creation of an iCal feed in Views.
description = Allows creation of iCal feeds using Views, and provides a Feeds plugin for parsing iCal feeds.
package = Date/Time
php = 5.3
core = 7.x
dependencies[] = date_views
dependencies[] = entity
files[] = date_ical_plugin_row_ical_feed.inc
files[] = date_ical_plugin_style_ical_feed.inc
dependencies[] = libraries (>=7.x-2.0)
dependencies[] = feeds
dependencies[] = date
dependencies[] = date_api
; Includes
files[] = includes/date_ical_plugin_row_ical_entity.inc
files[] = includes/date_ical_plugin_style_ical_feed.inc
files[] = includes/DateIcalFeedParser.inc
files[] = includes/DateIcalIcalcreatorParser.inc
files[] = includes/DateIcalDateModuleParser.inc
; Tests
files[] = tests/date_ical_parser.test
files[] = tests/date_ical_parser_text.test
files[] = tests/date_ical_parser_link.test
files[] = tests/date_ical_parser_date.test
files[] = tests/date_ical_parser_location.test
files[] = tests/date_ical_parser_categories.test
......@@ -5,3 +5,41 @@
* Install, update and uninstall functions for the date_ical module.
*
*/
/**
* Implements hook_requirements().
*/
function date_ical_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time
$t = get_t();
switch ($phase) {
case 'runtime':
if (($library = libraries_detect('iCalcreator')) && !empty($library['installed'])) {
$requirements['date_ical'] = array(
'title' => $t('Date iCal'),
'value' => $t('iCalcreator library is installed, version: @version found', array('@version' => $library['version'])),
'severity' => REQUIREMENT_OK,
);
}
else {
$requirements['date_ical'] = array(
'title' => $t('Date iCal'),
'value' => $t('iCalcreator library could not be found, check the installation instructions for Date iCal'),
'description' => $t('The error message was: @error<br>!error_message', array('@error' => $library['error'], '!error_message' => $library['error message'])),
'severity' => REQUIREMENT_ERROR,
);
}
break;
}
return $requirements;
}
/**
* Implementation of hook_enable().
*/
function date_ical_enable() {
cache_clear_all('plugins:feeds:plugins', 'cache');
}
......@@ -2,7 +2,9 @@
/**
* @file
* Adds ical functionality to Views.
* Adds ical functionality to Views, and an iCal parser to Feeds.
*
* TODO Figure out how to incorporate VVENUE information into the parser.
*/
/**
......@@ -11,7 +13,7 @@
function date_ical_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'date_ical'),
'path' => drupal_get_path('module', 'date_ical') . '/includes',
);
}
......@@ -36,38 +38,35 @@ function theme_date_ical_icon($variables) {
'title' => t('Add to calendar'), t('Add to calendar'),
);
if ($image = theme('image', $variables)) {
return '<div class="feed_icon"><a href="' . check_url($url) . '" class="ical-icon" title="ical">' . $image . '</a></div>';
return '<a href="' . check_url($url) . '" class="ical-icon" title="ical">' . $image . '</a>';
}
}
/**
* Implements hook_preprocess_HOOK() for nodes.
*
* Hide extraneous information when printing an ical view. The same thing can be
* Hide extraneous information when printing an iCal node. 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 (isset($variables['view_mode']) && $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.
// by the Manage Display settings for the iCal view mode.
// Trick the default node template into not displaying the page title by telling it this is page.
$variables['page'] = TRUE;
$variables['title_prefix'] = '';
$variables['title_suffix'] = '';
// We probably don't want to see the author information in our feed.
// We don't want to see the author information in our feed.
$variables['display_submitted'] = FALSE;
// No comments in a feed.
// Comments and links don't belong in an iCal feed.
if (isset($variables['content']['comments'])) {
unset($variables['content']['comments']);
}
// No links in a feed.
if (isset($variables['content']['links'])) {
unset($variables['content']['links']);
}
......@@ -77,10 +76,13 @@ function date_ical_preprocess_node(&$variables) {
/**
* Implements hook_entity_info_alter().
*
* Add an 'iCal' view mode for all entities, similar to RSS view mode.
* Add an 'iCal' view mode for entities, which will be used by the Views style plugin.
*/
function date_ical_entity_info_alter(&$entity_info) {
foreach ($entity_info as $entity_type => $info) {
if (!isset($entity_info[$entity_type]['view modes'])) {
$entity_info[$entity_type]['view modes'] = array();
}
$entity_info[$entity_type]['view modes'] += array(
'ical' => array(
'label' => t('iCal'),
......@@ -90,50 +92,8 @@ function date_ical_entity_info_alter(&$entity_info) {
}
}
/**
* Implements hook_views_plugins().
*/
function date_ical_views_plugins() {
$module_path = drupal_get_path('module', 'date_ical');
$theme_path = $module_path . '/theme';
$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_ical_vcalendar',
'theme file' => 'theme.inc',
'theme path' => $theme_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_ical_vevent',
'theme file' => 'theme.inc',
'theme path' => $theme_path,
'uses options' => TRUE,
'uses fields' => FALSE,
'type' => 'feed',
),
),
);
return $data;
}
// TODO: I'm pretty sure the following two functions are no longer relevant, since we've moved to the iCalcreator method.
// They don't really hurt anything, though, so I've left them in for now.
/**
* Implements hook_theme_registry_alter().
*
......@@ -141,7 +101,6 @@ function date_ical_views_plugins() {
* 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.
......@@ -176,3 +135,77 @@ function date_ical_preprocess_date_ical(&$vars) {
$vars['theme_hook_suggestions'][] = $vars['elements']['#entity_type'] . '__' . $vars['elements']['#bundle'] . '__ical';
}
}
/**
* Implements hook_libraries_info().
*/
function date_ical_libraries_info() {
$libraries['iCalcreator'] = array(
'name' => 'iCalcreator',
'vendor url' => 'http://kigkonsult.se/iCalcreator/index.php',
'download url' => 'http://kigkonsult.se/downloads/index.php#icalcreator',
'version arguments' => array(
'file' => 'iCalcreator.class.php',
'pattern' => "/define\( 'ICALCREATOR_VERSION', 'iCalcreator ([\d\.]+)' \);/",
'lines' => 100,
),
'files' => array(
'php' => array('iCalcreator.class.php'),
),
);
return $libraries;
}
////////////////////////////////////////////////////////////////////
// Additions from the Parser iCal module.
////////////////////////////////////////////////////////////////////
/**
* Implementation of hook_ctools_plugin_api().
*/
function date_ical_ctools_plugin_api($owner, $api) {
if ($owner == 'feeds' && $api == 'plugins') {
return array('version' => 2);
}
}
/**
* Implementation of ctools plugin for feeds hook_feeds_plugins().
*/
function date_ical_feeds_plugins() {
$path = drupal_get_path('module', 'date_ical') . '/includes';
$info = array();
$info['DateIcalFeedsParser'] = array(
'hidden' => TRUE,
'handler' => array(
'parent' => 'FeedsParser',
'class' => 'DateIcalFeedsParser',
'file' => 'DateIcalFeedsParser.inc',
'path' => $path,
),
);
$info['DateIcalIcalcreatorParser'] = array(
'name' => 'iCalCreator parser',
'description' => 'Use iCalcreator to parse iCal feeds.',
'help' => 'Parse feeds in the iCal format using the iCalcreator library.',
'handler' => array(
'parent' => 'DateIcalFeedsParser',
'class' => 'DateIcalIcalcreatorParser',
'file' => 'DateIcalIcalcreatorParser.inc',
'path' => $path,
),
);
$info['DateIcalDateModuleParser'] = array(
'name' => 'Date API iCal parser',
'description' => 'Use the Date module\'s API to parse iCal feeds.',
'help' => 'Parse feeds in the iCal format using API provided by the Date module.',
'handler' => array(
'parent' => 'DateIcalFeedsParser',
'class' => 'DateIcalDateModuleParser',
'file' => 'DateIcalDateModuleParser.inc',
'path' => $path,
),
);
return $info;
}
<?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;
}
}
<?php
/**
* @file
* Classes implementing Date iCal's date_api-based parser functionality.
*/
class DateIcalDateModuleParser extends DateIcalFeedsParser {
/**
* Output sources this parser offers.
*
* Includes additional field for the handler for output.
*
* @see DateIcalFeedsParser::getMappingSources().
* @see DateIcalFeedsParser::getSourceElement().
*/
static protected $sources = array(
'summary' => array(
'name' => 'Summary',
'description' => 'A short summary or subject for the calendar component.',
'date_ical_parse_handler' => 'formatText',
),
'description' => array(
'name' => 'Description',
'description' => 'A more complete description calendar component than that provided by the "summary" property.',
'date_ical_parse_handler' => 'formatText',
),
'dtstart' => array(
'name' => 'Date for feed item',
'description' => 'Start time for the feed item.',
'date_ical_parse_handler' => 'formatDateTime',
),
// TODO: Why is this commented out?
/* 'dtend' => array(
'name' => 'Date end',
'description' => 'End time for the feed item.',
'date_ical_parse_handler' => 'formatDateTime',
), */
'uid' => array(
'name' => 'UID',
'description' => 'UID of feed item',
'date_ical_parse_handler' => 'formatText',
),
'url' => array(
'name' => 'URL',
'description' => 'URL for the feed item.',
'date_ical_parse_handler' => 'formatText',
),
'location' => array(
'name' => 'Location text',
'description' => 'Text of location property of feed item.',
'date_ical_parse_handler' => 'formatText',
),
);
/**
* Load and run parser implementation of FeedsParser::parse().
*
* @params - change these to generic required paramters.
*/
public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
self::loadLibrary();
$feed_output = $fetcher_result->getRaw();
$feed_folded = explode("\n", $feed_output);
$parsed = date_ical_parse($feed_folded);
$ical = $parsed[0];
$result = new DateIcalParserResult();
$result->title = isset($ical['X-WR-CALNAME']) ? $ical['X-WR-CALNAME'] : '';
$result->description = isset($ical['X-WR-CALDESC']) ? $ical['X-WR-CALDESC'] : '';
$timezone = isset($ical['X-WR-TIMEZONE']) ? $ical['X-WR-TIMEZONE'] : '';
if (! empty($timezone)) {
try {
$tz = new DateTimeZone($xprop[1]);
$result->timezone = $tz;
}
catch (Exception $e) {
$source->log('parse', 'Invalid X-WR-TIMEZONE: %error', array('%error' => $e->getMessage()), WATCHDOG_NOTICE);
}
}
$components = array();
if (isset($ical['VEVENT'])) {
foreach ($ical['VEVENT'] as $event) {
$components[] = new DateIcalDateModuleComponent('vevent', $event);
}
}
$result->items = $components;
return $result;
}
/******
* Source output formatters.
*
* Could be in a class of their own?
**/
/**
* Format text fields.
*/
public function formatText($property_key, $property, DateIcalComponentInterface $item, FeedsParserResult $result, FeedsSource $source) {
return $property;
}
/**
* Format datetime fields.
*/
public function formatDateTime($property_key, $property, DateIcalComponentInterface $item, FeedsParserResult $result, FeedsSource $source) {
$dtstart = $item->getProperty("dtstart");
$dtend = $item->getProperty("dtend");
# drupal_set_message(print_r($dtstart, TRUE));
$dstart = new FeedsDateTime($dtstart["datetime"]);
$dend = new FeedsDateTime($dtend["datetime"]);
$dt = new FeedsDateTimeElement(
$dstart,
$dend,
$dtstart["tz"]
);
# drupal_set_message(print_r($property_key, TRUE));
# drupal_set_message(print_r($dt, TRUE));
# drupal_set_message("<pre>" . print_r($item, TRUE) . "</pre>");
return $dt;
}
/**
* Load the Date API.
*/
static public function loadLibrary() {
include_once(drupal_get_path('module', 'date_api') .'/date_api_ical.inc');
}
}
/**
* A wrapper on iCalcreator component class.
*/
class DateIcalDateModuleComponent implements DateIcalComponentInterface {
protected $component;
protected $type;
public function __construct($type, $component) {
$this->type = strtoupper($type);
$this->component = $component;
}
public function getComponentType() {
return $this->type;
}
public function getProperty($name) {
$name = strtoupper($name);
return (isset($this->component[$name]) ? $this->component[$name] : NULL);
}
// This implementation of DateIcalComponentInterface->setProperty() accepts only a
// singluar parameter, rather than an array of them.
public function setProperty($name, $value) {
$name = strtoupper($name);
$this->component[$name] = $value;
}
}
<?php
/**
* Basic classes.
*/
/**
* Parent class for Feeds integration.
*/
abstract class DateIcalFeedsParser extends FeedsParser {
/**
* The output sources the parser offers.
*
* array(
* 'feeds_output_key' => array(
* 'name' => 'Human readable name of output source.',
* 'description' => 'Longer description of source.',
* 'date_ical_parse_handler' => 'Method callback for parsing source before handing to feeds.',