First part of invitation handling: find ical attachments in emails and show a button to add to calendar
This commit is contained in:
parent
b614298c93
commit
206557aa7d
4 changed files with 209 additions and 25 deletions
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue