First part of invitation handling: find ical attachments in emails and show a button to add to calendar

This commit is contained in:
Thomas Bruederli 2011-07-29 09:19:18 +02:00
parent b614298c93
commit 206557aa7d
4 changed files with 209 additions and 25 deletions

View file

@ -38,6 +38,7 @@ class calendar extends rcube_plugin
public $home; // declare public to be used in other classes
public $urlbase;
public $timezone;
public $gmt_offset;
public $ical;
public $ui;
@ -62,6 +63,9 @@ class calendar extends rcube_plugin
'Family' => '00ff00',
'Holiday' => 'ff6600',
);
private $ics_parts = array();
/**
* Plugin initialization.
@ -102,10 +106,6 @@ class calendar extends rcube_plugin
if ($this->rc->task == 'calendar' && $this->rc->action != 'save-pref') {
if ($this->rc->action != 'upload') {
$this->load_driver();
// load iCalendar functions
require($this->home . '/lib/calendar_ical.php');
$this->ical = new calendar_ical($this->rc, $this->driver);
}
// register calendar actions
@ -137,6 +137,10 @@ class calendar extends rcube_plugin
$this->add_hook('preferences_list', array($this, 'preferences_list'));
$this->add_hook('preferences_save', array($this, 'preferences_save'));
}
else if ($this->rc->task == 'mail' && ($this->rc->action == 'show' || $this->rc->action == 'preview')) {
$this->add_hook('message_load', array($this, 'mail_message_load'));
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
}
// add hook to display alarms
$this->add_hook('keep_alive', array($this, 'keep_alive'));
@ -165,6 +169,20 @@ class calendar extends rcube_plugin
}
}
/**
* Load iCalendar functions
*/
private function load_ical()
{
if (!$this->ical) {
require($this->home . '/lib/calendar_ical.php');
$this->ical = new calendar_ical($this);
}
return $this->ical;
}
/**
* Render the main calendar view from skin template
*/
@ -641,6 +659,7 @@ class calendar extends rcube_plugin
header("Content-Type: text/calendar");
header("Content-Disposition: inline; filename=".$calendar_name);
$this->load_ical();
echo $this->ical->export($events);
exit;
}
@ -1250,7 +1269,7 @@ class calendar extends rcube_plugin
$message->setParam('text_encoding', 'quoted-printable');
$message->setParam('head_encoding', 'quoted-printable');
$message->setParam('head_charset', RCMAIL_CHARSET);
$message->setParam('text_charset', RCMAIL_CHARSET);
$message->setParam('text_charset', RCMAIL_CHARSET . ";\r\n format=flowed");
// compose common headers array
$headers = array(
@ -1264,6 +1283,7 @@ class calendar extends rcube_plugin
// attach ics file for this event
$this->load_ical();
$vcal = $this->ical->export(array($event), 'REQUEST');
$message->addAttachment($vcal, 'text/calendar', 'event.ics', false, '8bit', 'attachment', RCMAIL_CHARSET);
@ -1307,7 +1327,7 @@ class calendar extends rcube_plugin
));
$message->headers($headers);
$message->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
$message->setTXTBody(rcube_message::format_flowed($body, 79));
// finally send the message
if (rcmail_deliver_message($message, $from, $mailto, $smtp_error))
@ -1326,14 +1346,15 @@ class calendar extends rcube_plugin
{
$fromto = '';
$duration = $event['end'] - $event['start'];
$date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format'));
$time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format'));
$date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']));
$time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']));
if ($event['allday']) {
$fromto = format_date($event['start'], $date_format) .
($duration > 86400 || date('d', $event['start']) != date('d', $event['end']) ? ' - ' . format_date($event['end'], $date_format) : '');
($duration > 86400 || gmdate('d', $event['start']) != gmdate('d', $event['end']) ? ' - ' . format_date($event['end'], $date_format) : '');
}
else if ($duration < 86400 && date('d', $event['start']) == date('d', $event['end'])) {
else if ($duration < 86400 && gmdate('d', $event['start']) == gmdate('d', $event['end'])) {
$fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
' - ' . format_date($event['end'], $time_format);
}
@ -1483,4 +1504,82 @@ class calendar extends rcube_plugin
return $diff;
}
/**** Event invitation plugin hooks ****/
/**
* Check mail message structure of there are .ics files attached
*/
public function mail_message_load($p)
{
$this->message = $p['object'];
// check all message parts for .ics files
foreach ((array)$this->message->mime_parts as $idx => $part) {
if ($this->is_vcalendar($part)) {
$this->ics_parts[] = $part->mime_id;
}
}
}
/**
* Add UI elements to copy event invitations or updates to the calendar
*/
public function mail_messagebody_html($p)
{
// load iCalendar functions (if necessary)
if (!empty($this->ics_parts)) {
require($this->home . '/lib/calendar_ical.php');
$this->ical = new calendar_ical($this->rc);
}
$html = '';
foreach ($this->ics_parts as $part) {
$this->load_ical();
$events = $this->ical->import($this->message->get_part_content($part));
// successfully parsed events?
if (empty($events))
continue;
foreach ($events as $idx => $event) {
// add box below messsage body
$html .= html::p('calendar-invitebox',
html::span('eventtitle', Q($event['title'])) . ' ' .
html::span('eventdate', Q('(' . $this->event_date_text($event) . ')')) . ' ' .
html::tag('input', array(
'type' => 'button',
'class' => 'button',
# 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($part.':'.$idx) . "', '" . JQ($event['title']) . "')",
'value' => $this->gettext('importtocalendar'),
))
);
}
}
// prepend event boxes to message body
if ($html) {
$this->ui->init();
$p['content'] = $html . $p['content'];
}
return $p;
}
/**
* Checks if specified message part is a vcalendar data
*
* @param rcube_message_part Part object
* @return boolean True if part is of type vcard
*/
private function is_vcalendar($part)
{
return (
$part->mimetype == 'text/calendar' ||
$part->mimetype == 'text/x-vcalendar' ||
// Apple sends files as application/x-any (!?)
($part->mimetype == 'application/x-any' && $part->filename && preg_match('/\.ics$/i', $part->filename))
);
}
}

View file

@ -19,36 +19,92 @@
| |
+-------------------------------------------------------------------------+
| Author: Lazlo Westerhof <hello@lazlo.me> |
| Bogomil "Bogo" Shopov <shopov@kolabsys.com> |
| Thomas Bruederli <roundcube@gmail.com> |
| Bogomil "Bogo" Shopov <shopov@kolabsys.com> |
+-------------------------------------------------------------------------+
*/
/**
* Class to parse and build vCalendar (iCalendar) files
*
* Uses the Horde:iCalendar class for parsing. To install:
* > pear channel-discover pear.horde.org
* > pear install horde/Horde_Icalendar
*
*/
class calendar_ical
{
const EOL = "\r\n";
private $rc;
private $driver;
private $cal;
function __construct($rc, $driver)
function __construct($cal)
{
$this->rc = $rc;
$this->driver = $driver;
$this->cal = $cal;
$this->rc = $cal->rc;
}
/**
* Import events from iCalendar format
*
* @param array Associative events array
* @access public
* @param string vCalendar input
* @return array List of events extracted from the input
*/
public function import($events)
public function import($vcal)
{
//TODO
// for ($events as $event)
// $this->backend->newEvent(...);
// use Horde:iCalendar to parse vcalendar file format
require_once 'Horde/iCalendar.php';
$parser = new Horde_iCalendar;
$parser->parsevCalendar($vcal);
$events = array();
if ($data = $parser->getComponents()) {
foreach ($data as $comp) {
if ($comp->getType() == 'vEvent')
$events[] = $this->_to_rcube_format($comp);
}
}
return $events;
}
/**
* Convert the given File_IMC_Parse_Vcalendar_Event object to the internal event format
*/
private function _to_rcube_format($ve)
{
$event = array(
'uid' => $ve->getAttributeDefault('UID'),
'title' => $ve->getAttributeDefault('SUMMARY'),
'description' => $ve->getAttributeDefault('DESCRIPTION'),
'location' => $ve->getAttributeDefault('LOCATION'),
'start' => $ve->getAttribute('DTSTART'),
'end' => $ve->getAttribute('DTEND'),
);
// check for all-day dates
if (is_array($event['start'])) {
$event['start'] = gmmktime(0, 0, 0, $event['start']['month'], $event['start']['mday'], $event['start']['year']) + $this->cal->gmt_offset;
$event['allday'] = true;
}
if (is_array($event['end'])) {
$event['end'] = gmmktime(0, 0, 0, $event['end']['month'], $event['end']['mday'], $event['end']['year']) + $this->cal->gmt_offset - 60;
}
// TODO: complete this
// make sure event has an UID
if (!$event['uid'])
$event['uid'] = $this->cal->$this->generate_uid();
return $event;
}
/**
* Export events to iCalendar format
*
@ -69,8 +125,15 @@ class calendar_ical
foreach ($events as $event) {
$ical .= "BEGIN:VEVENT" . self::EOL;
$ical .= "UID:" . self::escpape($event['uid']) . self::EOL;
$ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
$ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
// correctly set all-day dates
if ($event['allday']) {
$ical .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL;
$ical .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 60) . self::EOL; // ends the next day
}
else {
$ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
$ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
}
$ical .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
$ical .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;

View file

@ -26,6 +26,7 @@ class calendar_ui
{
private $rc;
private $calendar;
private $ready = false;
public $screen;
function __construct($calendar)
@ -40,6 +41,9 @@ class calendar_ui
*/
public function init()
{
if ($this->ready) // already done
return;
// add taskbar button
$this->calendar->add_button(array(
'name' => 'calendar',
@ -54,6 +58,8 @@ class calendar_ui
$skin = $this->rc->config->get('skin');
$this->calendar->include_stylesheet('skins/' . $skin . '/calendar.css');
$this->ready = true;
}
/**

View file

@ -993,3 +993,19 @@ div.fc-event-location {
fieldset #calendarcategories div {
margin-bottom: 0.3em;
}
/* Invitation UI in mail */
p.calendar-invitebox {
min-height: 20px;
margin: 5px 8px;
padding: 6px 6px 6px 34px;
border: 1px solid #C2D071;
background: url('images/calendar.png') 6px 3px no-repeat #F7FDCB;
}
p.calendar-invitebox input.button {
margin-left: 1em;
font-size: 11px;
}