Hi,

I came up with a handy way to add in support for exporting to an ICS format, in order to create an "Add to Outlook" feature to events. It works really well.

I'm not sure what the best way to go about this is, but I thought I'd post the code I used for consideration for inclusion into the next version of views_bonus. I just installed an update of views_bonus this morning and nearly lost this code: luckily I had a backup I was able to retrieve it from. If this was included in views_bonus, I wouldn't need to worry about that in the future.

I'm not familiar with how to create patches, so maybe somebody would be able to do that.

In views_bonus_export.module, I included the following:

/**
 * Preprocess ics output template.
 */
function template_preprocess_views_bonus_export_ics(&$vars) {
  drupal_set_header('Content-Type: text/calendar; charset=utf-8;');
  drupal_set_header('Content-Disposition: attachment; filename="add_event.ics"; ');

  _views_bonus_export_shared_preprocess($vars);
}

In views_bonus_export.views.inc, I added the following after the 'views_txt' section:

      'views_ics' => array(
        'title' => t('ICS file'),
        'help' => t('Display the view as a ics file.'),
        'path' => $path,
        'handler' => 'views_bonus_plugin_style_export_ics',
        'parent' => 'views_bonus_export',
        'theme' => 'views_bonus_export_ics',
        'theme file' => 'views_bonus_export.theme.inc',
        'uses row plugin' => FALSE,
        'uses fields' => TRUE,
        'uses options' => TRUE,
        'type' => 'feed',
      ),

Then, I added another file, views_bonus_plugin_style_export_ics.inc:

<?php
// $Id: views_bonus_plugin_style_export_ics.inc,v 1.5 2008/12/28 03:47:37 neclimdul Exp $
/**
 * @file
 * Plugin include file for export style plugin.
 */

/**
 * Generalized style plugin for export plugins.
 *
 * @ingroup views_style_plugins
 */
class views_bonus_plugin_style_export_ics extends views_bonus_plugin_style_export {
  /**
   * Initialize plugin.
   *
   * Set feed image for shared rendering later.
   */
  function init(&$view, &$display, $options = NULL) {
    parent::init($view, $display, $options = NULL);
    $this->feed_image = drupal_get_path('module', 'views_bonus_export') . '/images/ics.png';
  }

  /**
   * Set options fields and default values.
   *
   * @return
   * An array of options information.
   */
  function option_definition() {
    $options = parent::option_definition();

    $options['filename'] = array(
      'default' => 'view-%view.ics',
      'translatable' => FALSE,
    );

    return $options;
  }

  /**
   * Options form mini callback.
   *
   * @param $form
   * Form array to add additional fields to.
   * @param $form_state
   * State of the form.
   * @return
   * None.
   */
  function options_form(&$form, &$form_state) {
    $form['filename'] = array(
      '#type' => 'textfield',
      '#title' => t('ICS filename'),
      '#default_value' => $this->options['filename'],
      '#description' => t('The filename that will be suggested to the browser for downloading purposes. %view will be replaced with the view name.'),
      '#process' => array('views_process_dependency'),
      '#dependency' => array('edit-style-options-override' => array(FALSE)),
    );
  }
}

Finally, I added views-bonus-export-ics.tpl.php:

<?php
// $Id: views-bonus-export-ics.tpl.php,v 1.1 2008/10/08 05:50:10 neclimdul Exp $
/**
 * @file views-view-table.tpl.php
 * Template to display a view as a table.
 *
 * - $title : The title of this group of rows.  May be empty.
 * - $rows: An array of row items. Each row is an array of content
 *   keyed by field ID.
 * - $header: an array of haeaders(labels) for fields.
 * - $themed_rows: a array of rows with themed fields.
 * @ingroup views_templates
 */

foreach ($themed_rows as $count => $row):
$dtstart = strip_tags($row['field_event_date_time']);
$dtend = strip_tags($row['field_event_date_time_value2']);
?>
BEGIN:VCALENDAR
PRODID:-//Calendar//Calendar Event//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
DTSTAMP:<?php print(strip_tags($row['field_event_date_time']) . "\n") ?>
  
BEGIN:VEVENT
DTSTART:<?php print($dtstart . "00\n") ?>
<?php 
if ($dtend!=$dtstart) {?>
DTEND:<?php print($dtend . "00\n") ?>
<?php } ?>

SUMMARY: <?php print(html_entity_decode($row['title']) . "\n") ?>
DESCRIPTION: <?php print(html_entity_decode(strip_tags($row['body'])) . "\n") ?>
UID:1
LOCATION:<?php print($row['field_location_value'] . "\n") ?>
SEQUENCE:0
END:VEVENT
END:VCALENDAR


<?php endforeach;

I also created an icon for the images folder (attached).

Within Views, I created a Feed display on a CCK Date/Time calendar view. Here are the important settings I used:

Style: ICS file
Path: events/%/%/add_event.ics
Attach to: Add to Outlook block
Arguments: Node:Nid and Content: Date and time (field_event_date_time) - delta
Fields:

  • Node:Title
  • Content: Date and time (field_event_date_time) - From date (Format: iCal, Display From date only)
  • Content: Location (field_location)
  • Node: Body (Rewrite the output of this field checked, with nothing in the box)
  • Content: Date and time (field_event_date_time) - To date (Format: iCal, Display To date only)

As hinted above, I also have a Block display titled Add to Outlook, with an argument of Node:Nid (Provide default argument of Node ID from URL, Validator Node, Event content type, Node ID argument type). Fields are Node:Title and Content: Date and time (field_event_date_time) - delta (exclude from display) and Content: Date and time (field_event_date_time) - From date, with output rewritten as:

<strong>[title]: [field_event_date_time_value]</strong>

And link path of: events/!1/[delta]/add_event.ics, with a custom label of Add to Outlook, with a Short Format of Display From and To Dates.

The iCal Date format is in the form of yyyymmddThhmmss

The trick of course is that the views_bonus format is dependent on setting up the view correctly and having the same field names for the content type, which may or may not be true. In my perfect world, the necessary items would be added to the views_bonus folder, so all would work well, even if views_bonus is updated in the future. But maybe that wouldn't work so well, because of all the dependencies. I suppose this could be spun off as a separate module, although at the moment, that's beyond my powers. If nothing else, this should serve as documentation for this technique in case I ever lose the files again, or if anybody else would like to use it. I think it's a pretty useful addition to anybody creating events through Views and a CCK Date/Time Calendar.

Enjoy!

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

snorkers’s picture

I kinda got this to work (date/time format hasn't worked too well..?), I ended up using a single NID argument, and not attaching it to any view... however using a URL of event/NID/my-calendar.ics (without attaching it to any view) and a tweak of my template.php and node-event.tpl.php means I can insert a event/NID/my-calendar.vcs link on each Event type page.

Only minor issue with your posted code is closing the PHP tag at the end of views-bonus-export-ics.tpl.php (my understanding is to leave off closing PHP tags only on template.php and .module files). Anyway, thanks for your efforts. Such functionality gets my vote.

stsai’s picture

snorkers, i noticed that you took a different method to do what i'm looking for. I don't have the views bonus module on my system and from all the posts i've read there are methods to create the ics file using views and calendar ical.

i was going to attempt to use mdrummond's code to tweak it to my system, but it seems that i might be able to use your method. would you be willing to share what you did? my goal was to put it on my node-event.tpl file as well. i don't know if i took the best route to create my ics because the views are not giving me as much flexibility.

snorkers’s picture

@stsai I've pretty much used @mdrummond's code, although had to alter the field names in views-bonus-export-ics.tpl.php.

As mentioned above, the only change I made was to the View, partially because I'm not using repeat dates (so no Delta available), but use of a single argument (the Nid) made life simpler. I changed the URL to event/%/download, then created a node-event.tpl.php file which has the following snippet to generate a link to download the event as an .ics file:

<a href='/event/<?php print $node->nid; ?>/download' title="Download this event to Outlook or iCal" />Download event</a>

And this works fine for Outlook. iCal had a few issues, especially if more than one event was downloaded - the duplicate filename seemed to overwrite the first event download... so a further tweak I made was to make each .ics file a unique name - just added a server timestamp to the filename in views_bonus_export.module (about line 189):

drupal_set_header('Content-Disposition: attachment; filename="event-'.time().'.ics"; ');

With a bit of effort, this offers great functionality, but it needs a bit of work to get around the fieldnames issue before we start rolling patches. I'm not quite the man for this yet - put it this way, I've signed up to the introduction to module development pre-training at DrupalCon SF. So maybe in a few weeks!

rimu’s picture

snorkers, yeah that's the route I took too, although it's a pure module rather than needing any theme messyness.

<?php

function event_ics_menu(){
    $items['node/%node/add_to_calendar.ics'] = array(
       'title' => 'Add to calendar',
       'page callback' => 'event_ics_download',
       'page arguments' => array(1),
       'access callback' => 'event_ics_access',
       'access arguments' => array(1), 
       'type' => MENU_CALLBACK,
    );
    
    return $items;

}

function event_ics_access($node){
    if($node->type == "event" && user_access('access content'))
        return true;
    else
        return false;
    
}

function event_ics_download($node){
    
    
    $t = strtotime($node->field_event_datetime[0]['value']);
    
    drupal_set_header('Content-Type: text/calendar; charset=utf-8;');
    drupal_set_header('Content-Disposition: inline; filename="add_to_calendar.ics";');
    
    $title = $node->title;
    $description = "Read more at " . url($node->path, array('absolute' => true));
    $location = $node->field_location[0]['value'];
    $dtstart = _event_ics_date_parse($node->field_event_datetime[0]['value']);
    $dtend = _event_ics_date_parse($node->field_event_datetime[0]['value2']);
    $uid = $node->nid;
    
  
    $retval = "BEGIN:VCALENDAR
PRODID:-//Calendar//Calendar Event//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
DTSTAMP:$dtstart
BEGIN:VEVENT
DTSTART:$dtstart
DTEND:$dtend
SUMMARY:$title
DESCRIPTION:$description
LOCATION:$location
SEQUENCE:0
END:VEVENT
END:VCALENDAR";


    print($retval);
    
    exit();

}

function event_ics_link($type, $node, $teaser = false){
    if($type == 'node' && $node->type == 'event'){
        $links['iCal'] = array(
            'title' => t("Add to my calendar"),
            'href' => 'node/' . $node->nid . '/add_to_calendar.ics',
        );
        
        return $links;
    }
}

function _event_ics_date_parse($inp){
    $t = strtotime($inp);
    
    $t = format_date($t + 43200, 'custom', 'Ymd His');
    
    return strtr($t, array(' ' => 'T'));
    
}

attheshow’s picture

@snorkers When I use the method you've outlined above, everything works perfectly on a Mac or a PC when using iCal or Outlook. However, when I click on the "download" link on an iPad, safari repeatedly refreshes the download page and never actually grabs the .ics file. Any ideas what I might be doing wrong?

attheshow’s picture

After further investigation, it looks like neither the iPhone nor the iPad support the .ics file format directly. Users are expected to open .ics calendar files on either their Mac or PC and then sync the appointment details to the device using iTunes. There are even some paid apps that are meant to help users work around this particular issue (e.g., http://itunes.apple.com/us/app/ics-meeting-viewer/id382013156?mt=8, http://itunes.com/apps/calendarview)

http://www.theipadguide.com/faq/does-ipad-support-or-read-icalendar-outl...

Alex UA’s picture

This is something a potential client needs, so thanks @mdrummond! I'm thinking it would also be mighty handy for g.d.o., amongst other places...

Thanks again!

mlowrey’s picture

Hello fellow developers. I'm not a drupal developer, I develop in ColdFusion but through my searching for an issue we're having with iCal Version 4.0.3 I came across your posting here and it has a pretty extensive and up to date information on ics formatted files.

My issue is with a client who, after importing her .ics file to her MacBook iCal program she's having to accept/decline all events on the new calendar she creates from this .ics file. Outlook works properly with no issues but iCal Version 4.0.3 seems to force the individual to accept or decline the individual entries imported from her file. I believe she's also set up a sync to her Google calendar as well.

We were able to replicate the issue on our machine here OS X (10.6.4) iCal Version 4.0.3 (1388) just by simply importing an .ics file with events and when one of (or any of) these events were clicked after the calendar was created would give us this accept/deny option before viewing the event.

I will also point out that iCal Version 3.0.8 (1287) works just fine, so this is something new that has been implemented in the newer version(s) of iCal. I've been searching for some sort of code that might help me make these events automatically accepted in some way. If any of you are an expert in this area and know of a way to automatically accept these events either by sending it through the code in the .ics file or setting up some sort of Mac preference. I haven't been able to find anything to bypass this annoyance.

polishyourimage’s picture

Title: ICS file format » subscribing
kiwad’s picture

Title: subscribing » ICS file format

back to right title...

soulston’s picture

Just out of interest why do you call _event_ics_date_parse($inp)?

This adds 12 hours onto the time it I'm not mistaken?

Is this something to do with Outlook or something else?

merakli’s picture

@rimu, where is this module? How does one install it?

Has anybody tried this solution? If worked, how did you get it to work?

Thanks.

soulston’s picture

There is no module to download. Just copy the php and create your own module. Just make sure that you name the hooks the same as your module so if you call your module myicsmodule you would change the first hook for example to myicsmodule_menu (hook_menu).

You then need to call the menu item with a link, I used views to output mine but you could also do it in your node.tpl.php. I think I used something like this:

node/[nid]/add_to_calendar.ics

or in your node.tpl

node/$node->nid/add_to_calendar.ics

Also make sure that you adjust these variables to suit your content type:

function myicsmodule_download($node) {
    $t = strtotime($node->field_event_datetime[0]['value']);
    
    drupal_set_header('Content-Type: text/calendar; charset=utf-8;');
    drupal_set_header('Content-Disposition: inline; filename="add_to_calendar.ics";');
    
    $title = $node->title;
    $description = "Read more at " . url($node->path, array('absolute' => true));
    $location = $node->field_location[0]['value'];
    $dtstart = _event_ics_date_parse($node->field_event_datetime[0]['value']);
    $dtend = _event_ics_date_parse($node->field_event_datetime[0]['value2']);
    $uid = $node->nid;
}
Jody Lynn’s picture

This is essentially a cleaned-up version of rimu's approach. This approach has no connection to views, but this is a useful page on ics links regardless.

<?php

/**
* @file
* Create an 'Add to Outlook' link on events that opens an .ics file.
*/

/**
* Implements hook_menu().
*/
function event_ics_menu() {
$items['node/%node/add_to_calendar.ics'] = array(
'title' => 'Add to Outlook',
'page callback' => 'event_ics_download',
'page arguments' => array(1),
'access callback' => 'node_access',
'access arguments' => array('view', 1),
'type' => MENU_CALLBACK,
);
return $items;
}

/**
* Implements hook_link().
*/
function event_ics_link($type, $node, $teaser = false){
if ($type == 'node' && $node->type == 'event'){
$links['event_ics'] = array(
'title' => t("Add to Outlook"),
'href' => 'node/' . $node->nid . '/add_to_calendar.ics',
);
return $links;
}
}

/**
* Menu callback. Create the .ics file.
*/
function event_ics_download($node) {
drupal_set_header('Content-Type: text/calendar; charset=utf-8;');
drupal_set_header('Content-Disposition: inline; filename="add_to_calendar.ics";');

$title = check_plain($node->title);
$description = t("Read more at !link", array('!link' => url('node/' . $node->nid, array('absolute' => TRUE))));
$location = check_plain($node->field_location[0]['value']);
$dtstart = _event_ics_date_parse($node->field_date[0]['value']);
$dtend = _event_ics_date_parse($node->field_date[0]['value2']);
print "BEGIN:VCALENDAR
PRODID:-//Calendar//Calendar Event//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
DTSTAMP:$dtstart
BEGIN:VEVENT
DTSTART:$dtstart
DTEND:$dtend
SUMMARY:$title
DESCRIPTION:$description
LOCATION:$location
SEQUENCE:0
END:VEVENT
END:VCALENDAR";
exit();
}

/**
* Convert dates to the required format for ics.
*/
function _event_ics_date_parse($date) {
$date = date_convert($date, DATE_ISO, DATE_UNIX);
$date = format_date($date, 'custom', 'Ymd His');
return str_replace(' ', 'T', $date);
}

Matt V.’s picture

Status: Active » Needs review
FileSize
4.66 KB

In case it might help get ics support added to the module, I've created a patch containing the changes suggested by mdrummond in the original post. The only thing I changed was to clear out the $Id$ comments in the new files.

Matt V.’s picture

FileSize
4.74 KB

I made a few changes to the patch that I think make it significantly more flexible. Rather than hard coding the fields to be used, I switched to looping through the fields specified in the View and using the Labels associated with fields as the iCal properties. One downside is that you need to get familiar with the iCalendar specification, to know how to configure the labels.

A few tips that may come in handy when using this method to generate the feed:

  • To use a time/date field, I specified the following Custom format: Ymd\THis\Z
  • In some cases, it can be useful to combine multiple fields, using the Rewrite the output of this field setting.
  • The iCalendar Validator can be useful for checking new feeds.
Matt V.’s picture

FileSize
5.32 KB

Here's an updated patch file using git diff --full-index --binary, so the icon file is now included too.

RainbowArray’s picture

Something went a little fubar after a recent update to the Date module. I think it has something to do with the way deltas are handled when there is not more than one date associated with a particular node.

In any case, it's a pretty simple fix, although it took me a bit to get there.

In my view, on my "Add to Outlook block," I had to make two small changes to the Content: Date and delta field. First, make sure to uncheck "Count the number 0 as empty," then also uncheck "Do not rewrite if empty."

This ensure that you always at least get a 0 assigned as a delta.

On another note, it would be great to figure out a way to either get this incorporated into Views Bonus or posted at its own module. Every time Views Bonus gets updated, I forget that I have these custom files in there, and then I typically have to recreate them based on this post.

Not the end of the world, but I find this functionality useful, and it would be nice to make it easier to help others too.

Unfortunately I know jack squat about getting a module set up here and handling patches and such. Something I should learn, I know.

druser01’s picture

Does it work for D7 too?
I am getting an error "call to undefined function drupal_set_header () ;
Can I get some help using the same for D7

Thanks

attheshow’s picture

The method I'm using for D7 is the Views iCal module. http://drupal.org/project/views_ical

neclimdul’s picture

Status: Needs review » Closed (outdated)